关于javascript:记录一次利用canvans解决数据可视化问题箱图转换为对应圆

31次阅读

共计 3236 个字符,预计需要花费 9 分钟才能阅读完成。

先看最终效果图

需要:箱图种每个箱子都有本人对应的圆心和半径,以及以后图上对应的 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

正文完
 0