【实现自己的可视化引擎13】仪表盘

1,361 阅读3分钟

【实现自己的可视化引擎01】认识Canvas
【实现自己的可视化框架引擎02】抽象图像元素
【实现自己的可视化引擎03】构建基础图元库
【实现自己的可视化引擎04】图像元素动画
【实现自己的可视化引擎05】交互与事件
【实现自己的可视化引擎06】折线图
【实现自己的可视化引擎07】柱状图
【实现自己的可视化引擎08】条形图
【实现自己的可视化引擎09】饼图
【实现自己的可视化引擎10】散点图
【实现自己的可视化引擎11】雷达图
【实现自己的可视化引擎12】K线图
【实现自己的可视化引擎13】仪表盘
【实现自己的可视化引擎14】地图
【实现自己的可视化引擎15】关系图

前言

仪表盘(Gauge)是一种拟物化的图表,刻度表示度量,指针表示维度,指针角度表示数值。仪表盘图表就像汽车的速度表一样,有一个圆形的表盘及相应的刻度,有一个指针指向当前数值。
本章节我们提供两种类型仪表盘的实现思路
虚线形 type=DashBoard.TYPE.STROKE

填充形type=DashBoard.TYPE.FILL
组件使用代码示例:

render() {
    return (
      <DashBoard
        value={0.2} // 刻度偏转角度,取值范围0-1
        className="chart"
        style={{
          pointColor: '#333333', // 指针颜色
          // linearGradient: [
          //   [0, '#058F05'],
          //   [0.3, '#15AF05'],
          //   [0.5, '#DCD313'],
          //   [0.8, '#AE1315'],
          //   [1, '#9E0305']
          // ], // 渐变
          type: DashBoard.TYPE.FILL,
          graduation: ['强烈买入','买入',  '持平', '卖出', '强烈卖出'], //刻度字符串
          color:'#999999'
        }}

      />
      )
  }

仪表盘图层

有效果图可以看出,仪表盘图层可以有几个简单的基本图层圆环(圆弧)Ring、三角形指针(多边形Polygon)、刻度(线Line与文字Text)组合而成。
确定了基本图形之后,我们可以设置基本图形的属性以便组合而成我们需要的图像。
仪表圆环我们确定角度为150°~390°,而指针偏移的角度依据外边闯入的属性值value计算等腰三角形的旋转角度rotation=-120° + value * 240°。 仪表图层源代码如下:

make() {
    this.childs.splice(0, this.childs.length);
    // 计算仪表盘圆环最大可用半径
    const raduis = this.height > this.width ? this.width / 2 : this.height / 2;
    // 仪表盘
    if (this.type === DashBoard.TYPE.FILL) {
      // 圆环
      let ring = new Ring(this.canvas, {
        linearGradient: this.linearGradient, // 渐变
        color: this.color,
        position: new Point(this.width / 2, raduis * 0.45), // 圆环中心
        longRadius: raduis * 0.9, // 圆环大半径
        shortRadius: raduis * 0.75, // 圆环小半径
        type: Ring.TYPE.FILL,
        startAngle: 150,
        endAngle: 390,
      });
      this.addChild(ring);
    } else {
      // 圆弧
      let ring = new Arc(this.canvas, {
        linearGradient: this.linearGradient,
        color: this.color,
        radius: raduis * 0.825,
        position: new Point(this.width / 2, raduis * 0.45),
        lineDash: [10, 5], // 设置为虚线
        lineWidth: raduis * 0.15, // 线框
        startAngle: 150,
        endAngle: 390,
      });
      this.addChild(ring);
    }

    // 三角形指针
    let angle = new Polygon(this.canvas, {
      type: Polygon.TYPE.FILL,
      position: new Point(this.width / 2, raduis * 0.45),
      color: this.pointColor,
    }, [
      new Point(this.width / 2 - 10, raduis * 0.45),
      new Point(this.width / 2 + 10, raduis * 0.45),
      new Point(this.width / 2, raduis)
    ]);
    // 值的偏移角度
    const valueAngle = -120 + this.value * 240;
    // 指针偏移
    angle.rotation = valueAngle;
    const angleStep = 240 / (this.graduation.length - 1);
    // 添加刻度线
    for (let i = 0;i < this.graduation.length; i++) {
      let color = this.color;
      // 刻度值偏移角度
      const angle = (210 - i * angleStep) / 180 * Math.PI;
      // 刻度值是否超过value,超过修改刻度线颜色为选中颜色
      if ((1 - this.value) * 240 - 30 < (210 - i * angleStep) ) {
        color = this.selectColor;
      }
      // 刻度线
      let line = new Line(this.canvas, {
        position: new Point(
          this.width / 2 + raduis * 0.65 * Math.cos(angle),
          raduis * 0.45 + raduis * 0.65 * Math.sin(angle)
        ),
        to: new Point(
          this.width / 2 + raduis * 0.75 * Math.cos(angle),
          raduis * 0.45 + raduis * 0.75 * Math.sin(angle)
        ),
        color,
        lineWidth: 1,
      });
      // 刻度值
      const text = new Text(this.canvas, {
        text: this.graduation[i],
        size: this.fontSize,
        color,
        textAlign: 'center',
        position: new Point(
          this.width / 2 + raduis * 0.6 * Math.cos(angle),
          raduis * 0.45 + raduis * 0.6 * Math.sin(angle)
        )
      });
      this.addChild(line, text);
    }
    this.addChild(angle);
    // 填充选中部分颜色
    if (this.linearGradient.length === 0) {
      if (this.type === DashBoard.TYPE.FILL) {
        let selectRing = new Ring(this.canvas, {
          color: this.selectColor,
          position: new Point(this.width / 2, raduis * 0.45),
          longRadius: raduis * 0.9,
          shortRadius: raduis * 0.75,
          type: Ring.TYPE.FILL,
          startAngle: 150,
          endAngle: 150 + this.value * 240,
        });
        this.addChild(selectRing);
      } else {
        let selectRing = new Arc(this.canvas, {
          color: this.selectColor,
          radius: raduis * 0.825,
          position: new Point(this.width / 2, raduis * 0.45),
          lineDash: [10, 5],
          lineWidth: raduis * 0.15,
          startAngle: 150,
          endAngle: 150 + this.value * 240,
        });
        this.addChild(selectRing);
      }
    }
  }

React 封装

React封装需要DOM的挂载完成,所以我们在生命周期componentDidMount函数中构建我们的图层。

componentDidMount () {
    const { style = {}, value =  0 } = this.props;
    this.canvas = new Canvas({
      ele: this.ref.current,
      canAction: false,
    });
    this.dash = new DashBoard(this.canvas, style, value);
    this.canvas.addChild(this.dash);
    this.dash.make();
    this.canvas.paint();
}