先看最终效果图
需要:箱图种每个箱子都有本人对应的圆心和半径,以及以后图上对应的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<CirCleProps> = (props: CirCleProps) => { // console.log(props, 'props______-'); const { range, data, boxPlotColorDic } = props; const canvasDom = useRef<HTMLCanvasElement>(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 ( <div className={styles.container}> <canvas ref={canvasDom} width={1200} height={1200} style={{ height: 130, width: 130 }}> 您的浏览器不反对canvas标签,无奈看到图形 </canvas> </div> );};
注:代码种的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