You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

在React.js中创建带半圆形Y轴的高尔夫击球散布图

实现半圆形Y轴高尔夫击球散布图方案

核心思路

因为Chart.js默认是笛卡尔坐标系,要实现类似练习场的半圆形布局,本质是把击球数据从「距离+左右偏移」转换成极坐标(距离=极径,左右偏移=极角),再映射到画布的笛卡尔坐标上,同时通过自定义插件绘制半圆形的练习场背景和刻度。

Chart.js + React 实现代码

首先安装依赖:

npm install chart.js react-chartjs-2

React组件代码:

import React from 'react';
import { Chart, registerables } from 'chart.js';
import { Scatter } from 'react-chartjs-2';

Chart.register(...registerables);

// 自定义插件:绘制半圆形练习场背景与刻度
const semicircleFieldPlugin = {
  id: 'semicircleField',
  beforeDraw: (chart) => {
    const ctx = chart.ctx;
    const { width, height } = chart.canvas;
    const centerX = width / 2;
    const centerY = height; // 画布底部作为发球点
    const maxDistance = 300; // 最大击球距离,根据你的实际数据调整

    // 绘制半圆形练习场外轮廓
    ctx.beginPath();
    ctx.arc(centerX, centerY, maxDistance, Math.PI, 0);
    ctx.strokeStyle = '#ccc';
    ctx.lineWidth = 2;
    ctx.stroke();

    // 绘制距离刻度(同心圆)
    for (let d = 50; d <= maxDistance; d += 50) {
      ctx.beginPath();
      ctx.arc(centerX, centerY, d, Math.PI, 0);
      ctx.strokeStyle = '#eee';
      ctx.lineWidth = 1;
      ctx.stroke();
      // 标注距离数值
      ctx.fillStyle = '#666';
      ctx.font = '12px Arial';
      ctx.textAlign = 'center';
      ctx.fillText(`${d}yd`, centerX, centerY - d - 5);
    }

    // 绘制左右方向刻度(射线)
    const angles = [-60, -30, 0, 30, 60]; // 自定义角度刻度
    angles.forEach(angle => {
      const rad = (angle * Math.PI) / 180;
      const x = centerX + maxDistance * Math.sin(rad);
      const y = centerY - maxDistance * Math.cos(rad);
      // 绘制刻度线
      ctx.beginPath();
      ctx.moveTo(centerX, centerY);
      ctx.lineTo(x, y);
      ctx.strokeStyle = '#eee';
      ctx.lineWidth = 1;
      ctx.stroke();
      // 标注方向标签
      ctx.fillStyle = '#666';
      ctx.textAlign = angle < 0 ? 'right' : angle > 0 ? 'left' : 'center';
      const labelX = angle === 0 ? x : x + (angle > 0 ? 5 : -5);
      ctx.fillText(angle === 0 ? '正前方' : `${Math.abs(angle)}°${angle < 0 ? '左' : '右'}`, labelX, y - 5);
    });
  }
};

const GolfShotScatter = () => {
  // 示例击球数据:distance=击球距离,offsetAngle=左右偏移角度(范围-90°到90°)
  const shotData = [
    { distance: 250, offsetAngle: 10 },
    { distance: 220, offsetAngle: -15 },
    { distance: 280, offsetAngle: 5 },
    { distance: 200, offsetAngle: 25 },
    { distance: 260, offsetAngle: -8 },
  ];

  // 将极坐标数据转换为Chart.js散点图的笛卡尔坐标
  const chartData = {
    datasets: [
      {
        label: '击球落点',
        data: shotData.map(shot => {
          const centerX = chart.canvas.width / 2;
          const centerY = chart.canvas.height;
          const rad = (shot.offsetAngle * Math.PI) / 180;
          return {
            x: centerX + shot.distance * Math.sin(rad),
            y: centerY - shot.distance * Math.cos(rad),
          };
        }),
        backgroundColor: '#ff5722',
        pointRadius: 6,
        pointHoverRadius: 8,
      },
    ],
  };

  const options = {
    responsive: true,
    maintainAspectRatio: false,
    plugins: {
      legend: { display: true },
      title: { display: true, text: '高尔夫击球散布图' },
    },
    scales: {
      // 隐藏默认XY轴,使用自定义极坐标刻度
      x: { display: false },
      y: { display: false },
    },
  };

  return (
    <div style={{ width: '800px', height: '400px', margin: '0 auto' }}>
      <Scatter data={chartData} options={options} plugins={[semicircleFieldPlugin]} />
    </div>
  );
};

export default GolfShotScatter;

数据适配说明

如果你的原始数据是「距离+左右偏移距离」(比如左侧10码、右侧5码),可以用下面的公式转换成角度:

// offsetDistance:左右偏移距离(左负右正),distance:击球距离
const offsetAngle = Math.atan2(offsetDistance, distance) * (180 / Math.PI);

备选方案:D3.js

如果需要更精细的自定义控制,D3.js对极坐标的支持更原生,你可以用d3.arc绘制半圆形背景,d3.scaleLinear映射距离和角度,直接在SVG上绘制散点和刻度,灵活性更高。

内容的提问来源于stack exchange,提问作者vinay

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.09 20:56:35