VChart
VChart copied to clipboard
[Feature] radar support cornerRadius
What problem does this feature solve?
雷达图拐点处支持圆角。
最理想的期望是最后的:根据角度弧度会不同 其次降级方案是中间的:统一有个最小的弧度,尽量别出现过于尖锐的尖头
What does the proposed API look like?
curveType 下新增配置项
- vchart-extension 中通过 pathProxy 来支持
- 关注凹点的圆角是否有问题
demo 供参考:
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;