Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Feature] radar support cornerRadius #3665

Open
xiaoluoHe opened this issue Jan 14, 2025 · 2 comments
Open

[Feature] radar support cornerRadius #3665

xiaoluoHe opened this issue Jan 14, 2025 · 2 comments
Assignees

Comments

@xiaoluoHe
Copy link
Contributor

xiaoluoHe commented Jan 14, 2025

What problem does this feature solve?

雷达图拐点处支持圆角。

最理想的期望是最后的:根据角度弧度会不同
其次降级方案是中间的:统一有个最小的弧度,尽量别出现过于尖锐的尖头

image

image

What does the proposed API look like?

curveType 下新增配置项

@xile611 xile611 self-assigned this Jan 15, 2025
@xile611
Copy link
Contributor

xile611 commented Jan 15, 2025

  1. vchart-extension 中通过 pathProxy 来支持
  2. 关注凹点的圆角是否有问题

@xile611 xile611 assigned xiaoluoHe and unassigned xile611 Jan 17, 2025
@xiaoluoHe
Copy link
Contributor Author

demo 供参考:

Image
const linkPoints = (points, path) => {
  const firstPoint = points[0];
  const lastPoint = points[points.length - 1];

  // 处理第一个点的圆角
  const v0 = { x: firstPoint.x - lastPoint.x, y: firstPoint.y - lastPoint.y };
  const v1First = { x: points[1].x - firstPoint.x, y: points[1].y - firstPoint.y };
  const v0Len = Math.sqrt(v0.x * v0.x + v0.y * v0.y);
  const v1FirstLen = Math.sqrt(v1First.x * v1First.x + v1First.y * v1First.y);

  const cosTheta0 = (v0.x * v1First.x + v0.y * v1First.y) / (v0Len * v1FirstLen);
  const theta0 = Math.acos(Math.min(Math.max(cosTheta0, -1), 1));
  const baseRadius0 = Math.min(v0Len, v1FirstLen) * 0.2;
  const radius0 = baseRadius0 * Math.min(1, theta0 / (Math.PI / 2));

  // 计算第一个点的圆角控制点
  const cp01 = {
    x: firstPoint.x - (v0.x * radius0) / v0Len,
    y: firstPoint.y - (v0.y * radius0) / v0Len
  };
  const cp02 = {
    x: firstPoint.x + (v1First.x * radius0) / v1FirstLen,
    y: firstPoint.y + (v1First.y * radius0) / v1FirstLen
  };

  // 从最后一个圆角控制点开始绘制
  path.moveTo(cp01.x, cp01.y);
  path.quadraticCurveTo(firstPoint.x, firstPoint.y, cp02.x, cp02.y);

  for (let i = 1; i < points.length; i++) {
    const curr = points[i];
    const prev = points[i - 1];
    const next = points[(i + 1) % points.length];

    // 计算夹角大小
    const v1 = { x: curr.x - prev.x, y: curr.y - prev.y };
    const v2 = { x: next.x - curr.x, y: next.y - curr.y };
    const v1Len = Math.sqrt(v1.x * v1.x + v1.y * v1.y);
    const v2Len = Math.sqrt(v2.x * v2.x + v2.y * v2.y);
    const cosTheta = (v1.x * v2.x + v1.y * v2.y) / (v1Len * v2Len);
    const theta = Math.acos(Math.min(Math.max(cosTheta, -1), 1));

    // 根据夹角计算实际圆角半径:夹角越大,圆角半径越大
    // 根据相邻向量长度计算基础圆角半径
    const baseRadius = Math.min(v1Len, v2Len) * 0.2;
    const radius = baseRadius * Math.min(1, theta / (Math.PI / 2));

    // 计算圆角控制点
    const cp1 = {
      x: curr.x - (v1.x * radius) / v1Len,
      y: curr.y - (v1.y * radius) / v1Len
    };
    const cp2 = {
      x: curr.x + (v2.x * radius) / v2Len,
      y: curr.y + (v2.y * radius) / v2Len
    };

    path.lineTo(cp1.x, cp1.y);
    path.quadraticCurveTo(curr.x, curr.y, cp2.x, cp2.y);
  }
  path.closePath();
};

const roundedLine = (data, attrs, path) => {
  linkPoints(attrs.points, path);
  return path;
};

const roundedArea = (data, attrs, path) => {
  const { points } = attrs;
  const topPoints = points.map(point => ({ x: point.x, y: point.y }));
  const bottomPoints = points.map(point => ({ x: point.x1, y: point.y1 })).reverse();

  linkPoints(topPoints, path);
  linkPoints(bottomPoints, path);

  return path;
};


const spec = {
  type: 'radar',
  data: [
    {
      values: [
        {
          month: 'Jan.',
          value: 45,
          type: 'A'
        },
        {
          month: 'Feb.',
          value: 61,
          type: 'A'
        },
        {
          month: 'Mar.',
          value: 92,
          type: 'A'
        },
        {
          month: 'Apr.',
          value: 57,
          type: 'A'
        },
        {
          month: 'May.',
          value: 46,
          type: 'A'
        },
        {
          month: 'Jun.',
          value: 36,
          type: 'A'
        },
        {
          month: 'Jul.',
          value: 33,
          type: 'A'
        },
        {
          month: 'Aug.',
          value: 63,
          type: 'A'
        },
        {
          month: 'Sep.',
          value: 57,
          type: 'A'
        },
        {
          month: 'Oct.',
          value: 53,
          type: 'A'
        },
        {
          month: 'Nov.',
          value: 69,
          type: 'A'
        },
        {
          month: 'Dec.',
          value: 40,
          type: 'A'
        },
        {
          month: 'Jan.',
          value: 31,
          type: 'B'
        },
        {
          month: 'Feb.',
          value: 39,
          type: 'B'
        },
        {
          month: 'Mar.',
          value: 81,
          type: 'B'
        },
        {
          month: 'Apr.',
          value: 39,
          type: 'B'
        },
        {
          month: 'May.',
          value: 64,
          type: 'B'
        },
        {
          month: 'Jun.',
          value: 21,
          type: 'B'
        },
        {
          month: 'Jul.',
          value: 58,
          type: 'B'
        },
        {
          month: 'Aug.',
          value: 72,
          type: 'B'
        },
        {
          month: 'Sep.',
          value: 47,
          type: 'B'
        },
        {
          month: 'Oct.',
          value: 37,
          type: 'B'
        },
        {
          month: 'Nov.',
          value: 80,
          type: 'B'
        },
        {
          month: 'Dec.',
          value: 74,
          type: 'B'
        },
        {
          month: 'Jan.',
          value: 90,
          type: 'C'
        },
        {
          month: 'Feb.',
          value: 95,
          type: 'C'
        },
        {
          month: 'Mar.',
          value: 62,
          type: 'C'
        },
        {
          month: 'Apr.',
          value: 52,
          type: 'C'
        },
        {
          month: 'May.',
          value: 74,
          type: 'C'
        },
        {
          month: 'Jun.',
          value: 87,
          type: 'C'
        },
        {
          month: 'Jul.',
          value: 80,
          type: 'C'
        },
        {
          month: 'Aug.',
          value: 69,
          type: 'C'
        },
        {
          month: 'Sep.',
          value: 74,
          type: 'C'
        },
        {
          month: 'Oct.',
          value: 84,
          type: 'C'
        },
        {
          month: 'Nov.',
          value: 94,
          type: 'C'
        },
        {
          month: 'Dec.',
          value: 23,
          type: 'C'
        }
      ]
    }
  ],
  categoryField: 'month',
  valueField: 'value',
  seriesField: 'type',
  stack: true,
   line: {
    visible: true,
    customShape: roundedLine
  },
  area: {
    visible: true,
    customShape: roundedArea
  },
  point:{visible: false},
  axes: [
    {
      orient: 'radius',
      min: 0,
      domainLine: {
        visible: true
      },
      label: {
        visible: true
      },
      grid: {
        smooth: true
      }
    },
    {
      orient: 'angle',
      tick: {
        visible: false
      },
      grid: {
        style: {
          lineDash: [0]
        }
      }
    }
  ],
  legends: {
    visible: true,
    orient: 'top'
  }
};

const vchart = new VChart(spec, { dom: CONTAINER_ID });
vchart.renderSync();

// Just for the convenience of console debugging, DO NOT COPY!
window['vchart'] = vchart;

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants