先看最终效果图
需求:箱图种每个箱子都有自己对应的圆心和半径,以及当前图上对应的y轴的取值范围(可缩放),根据这三个值,画出一个圆心在同一条竖线上一一对应的圆
形象的画一下:
,
再给它想办法搬到标准直角坐标系上
可以看到,所有的圆的圆心坐标都是(0,?),而此时的直角坐标系的y的最大值和最小值,我们可以任意给,且是对称的,我们给到600.-600,此时我们只需要算出来真实的每个圆在右侧的坐标系里的圆心坐标,和半径长度,就可以利用canvas来画出圆。
难点在于坐标的转换,这里我是利用比例的关系进行的计算:
1、首先是关于半径的计算,列出公式:
真实圆的半径 / 真实的范围 = 坐标系圆的半径 / 坐标系y轴的范围
真实圆的半径由后端发回,
真实的范围由左侧的箱图的y轴范围也可以拿到 = y_end - y_start
坐标系y轴的范围 = 1200,(我们设置的y轴范围为600到-600)
即可计算出坐标系上缩放后的圆的半径
2、坐标系上圆心的计算,同样,我们可以利用圆心距竖直中心的距离,来列出等比例的公式:
(真实圆心坐标 - 真实范围中心)/ 真实的范围 = (坐标系圆心y坐标 - 坐标系范围中心y坐标)/坐标系y轴的范围
真实圆的半径由后端发回,
真实的范围 = y_end - y_start
真实范围中心 = (y_end - y_start)/2 + y_start
坐标系范围中心y坐标 = 0
坐标系y轴的范围 = 1200
即可得到坐标系上缩放后 圆心y的坐标
知道这两个,我们只需要在canvans上把圆画出来即可,我们建立一个大小为1200的canvans画布,pencil?.clearRect(0, 0, 1200, 1200);
而canvans自带的坐标系为这样子的
所以我们的坐标系的原点对应的canvans画布的坐标为(600,600)
那圆心的坐标还是需要转化,
,
可以看到,原先圆心的y坐标为200,新的即为600-200=400,即
canvans画布上 圆心的y坐标 = 600 - 缩放后缩放后 圆心y的坐标
那么就可以利用canvas画圆的方法来把圆绘制出来
pencil.arc(600,canvans画布上圆心的y坐标,缩放后圆的半径,0,2 * Math.PI,);
所有的数学逻辑分析与canvans的方法分析完毕。
直接上代码:
type BoxPlotCircleData = {
diameter: number;
mean: number;
parameterX: string;
testItem: string;
};
type CirCleProps = {
// colors: Color[];
range: [number, number];
boxPlotColorDic: { [key: string]: Color };
width: number;
data: BoxPlotCircleData[];
};
// 计算缩放后直径值的函数,使用之前保证realRange为大于0 的数
const computerDiameter = (realDiameter: number, realRange: number, computerRange: number) => {
// 公式:computerDiameter/realDiameter = computerRange/realRange
// if (!realRange) return;
const computerDiameterRes: number = bignumberFc.multiply(bignumberFc.divide(computerRange, realRange), realDiameter);
return computerDiameterRes;
};
// 计算缩放后圆心Y坐标的函数,使用之前保证realRange为大于0 的数
const computerCircleCenter = (
realCircleCenter: number,
realRange: number,
realStartPoint: number,
computerRange: number,
) => {
// 公式:中点坐标 = realRange/2 + range[0]
// 公式:(realCircleCenter - 中点) /realRange = (computerCircleCenter - 0 ) /computerRange
// if (!realRange) return;
const realMiddle: number = bignumberFc.add(bignumberFc.divide(realRange, 2), realStartPoint);
const computerCircleCenterRes = bignumberFc.multiply(
bignumberFc.divide(bignumberFc.subtract(realCircleCenter, realMiddle), realRange),
computerRange,
);
return computerCircleCenterRes;
};
const AnovaCircle: React.FC = (props: CirCleProps) => {
// console.log(props, 'props______-');
const { range, data, boxPlotColorDic } = props;
const canvasDom = useRef(null);
useEffect(() => {
const pencil = canvasDom.current?.getContext('2d');
if (!pencil) return;
pencil?.clearRect(0, 0, 1200, 1200);
if (!range) return;
const realRange = bignumberFc.subtract(range[1], range[0]);
if (!(realRange > 0)) return;
data.map((item) => {
// console.log(computerCircleCenter(item.mean, realRange, range[0], 1200), '圆心坐标');
// console.log(bignumberFc.divide(computerDiameter(item.diameter, realRange, 1200), 2), '半径');
pencil.beginPath();
pencil.strokeStyle = boxPlotColorDic[item.parameterX] as string;
pencil.lineWidth = 12;
pencil.arc(
600,
bignumberFc.subtract(600, computerCircleCenter(item.mean, realRange, range[0], 1200)),
bignumberFc.divide(computerDiameter(item.diameter, realRange, 1200), 2),
0,
2 * Math.PI,
);
pencil.stroke();
pencil.closePath();
});
}, [range, data]);
return (
);
};
注:代码种的bigNumber.方法,可均替换成加减乘除
bigNumber.add(a,b) 即为 a+b
bigNumber.subtract(a,b) 即为 a-b
bigNumber.multiply(a,b) 即为 a*b
bigNumber.divide(a,b) 即为 a/b