canvas 绘制折线图及思考

发布于:2024-04-26 ⋅ 阅读:(22) ⋅ 点赞:(0)

在学习了 G2 源码后,就想着自己写个 chart 加深理解。这次我是用的 G 来实现的。其实用原生的 canvas api 或者其他绘图库都是差不多的。重要是捋一下思路。

功能效果图

折线图

实现步骤

前期准备

导入绘图库 G,它的 API 都会绑定到 window.G 上面.

  <link rel="shortcut icon" href="https://gw.alipayobjects.com/zos/antfincdn/yAeuB2%24niG/favicon.png" />
  <!-- G 核心 -->
  <script src="https://unpkg.com/@antv/g/dist/index.umd.min.js" type="application/javascript"></script>
  <!-- G 渲染器,支持 Canvas2D/SVG/WebGL -->
  <script src="https://unpkg.com/@antv/g-canvas/dist/index.umd.min.js" type="application/javascript"></script>

创建需要渲染的 div

  <div>
    <div id="app"></div>
  </div>

引入所需对象

    const { Group, Circle, Text, Canvas, Line, Rect, CanvasEvent } = window.G;

创建画布

    // 创建一个渲染器,这里使用 Canvas2D
    const canvasRenderer = new window.G.Canvas2D.Renderer();

    // 创建画布
    const app = document.getElementById('app');
    const canvas = new Canvas({
      container: app,
      width: options.width,
      height: options.height,
      renderer: canvasRenderer,
    });

数据和配置

类似于 G2,应该要将数据和配置项解耦出来。

    const data = [
      { "legend": "新登设备", "x": "2023-03-17", "y": 290 },
      { "legend": "新登设备", "x": "2023-03-18", "y": 371 },
      { "legend": "新登设备", "x": "2023-03-19", "y": 303 },
      { "legend": "新登设备", "x": "2023-03-20", "y": 580 },
      { "legend": "新登设备", "x": "2023-03-21", "y": 671 },
      { "legend": "新登设备", "x": "2023-03-22", "y": 776 },
      { "legend": "新登设备", "x": "2023-03-23", "y": 41340 },
      { "legend": "新登设备", "x": "2023-03-24", "y": 175221 }
    ]

    // 集合管理配置
    const options = {
      width: 1000,
      height: 600,
      padding: 30,
      lineWidth: 2,
      tickLength: 5,
      color: '#DCDFE6',
    }

根据 padding 确定绘制区域

      // 绘制边框
      const rect = new Rect({
        style: {
          x: 0,
          y: 0,
          width: options.width,
          height: options.height,
          stroke: options.color,
          lineWidth: options.lineWidth,
        }
      })
      canvas.appendChild(rect)

      // 绘制横坐标
      const xAxis = new Line({
        style: {
          x1: options.padding,
          y1: options.height - options.padding,
          x2: options.width - options.padding,
          y2: options.height - options.padding,
          stroke: options.color,
          lineWidth: options.lineWidth,
        }
      })
      canvas.appendChild(xAxis);

      // 绘制纵坐标
      const yAxis = new Line({
        style: {
          x1: options.padding,
          y1: options.padding,
          x2: options.padding,
          y2: options.height - options.padding,
          stroke: options.color,
          lineWidth: options.lineWidth,
        }
      })
      canvas.appendChild(yAxis);

根据数据源来确定坐标轴的刻度

      // 绘制横坐标刻度
      const step = (options.width - (2 * options.padding)) / (data.length + 1)
      for (let i = 0; i < data.length; i++) {
        const item = data[i];
        const tickLine = new Line({
          style: {
            x1: options.padding + (step * (i + 1)),
            y1: options.height - options.padding,
            x2: options.padding + (step * (i + 1)),
            y2: options.height - options.padding - options.tickLength,
            stroke: options.color,
            lineWidth: options.lineWidth,
          }
        })
        canvas.appendChild(tickLine);

        const tickText = new Text({
          style: {
            x: options.padding + (step * (i + 1)) - 10,
            y: options.height - options.padding + 20,
            text: item.x.slice(8),
            fill: '#303133',
            fontSize: 12,
          }
        })
        canvas.appendChild(tickText);
      }

      // 绘制纵坐标刻度
      const valueStep = Math.max(...data.map(item => item.y)) * 1.1 / 6
      const positionStep = (options.height - (2 * options.padding) - 10) / 6
      for (let i = 0; i < 7; i++) {
        const y = positionStep * (i)
        const tickLine = new Line({
          style: {
            x1: options.padding,
            y1: options.height - options.padding - y,
            x2: options.padding + options.tickLength,
            y2: options.height - options.padding - y,
            stroke: options.color,
            lineWidth: options.lineWidth,
          }
        })
        canvas.appendChild(tickLine);

        const gridLine = new Line({
          style: {
            x1: options.padding,
            y1: options.height - options.padding - y,
            x2: options.width - options.padding,
            y2: options.height - options.padding - y,
            stroke: '#EBEEF5',
            lineWidth: 2,
          }
        })
        canvas.appendChild(gridLine);

        const tickText = new Text({
          style: {
            x: options.padding - 10,
            y: options.height - options.padding - y + 3,
            fill: '#303133',
            fontSize: 12,
            textAlign: 'right',
            text: parseInt(valueStep * i / 10000),
          }
        })
        canvas.appendChild(tickText);
      }

折线图绘制

已知横坐标系和纵坐标系,那么就可以很好的计算出每个数据点在图表上的位置了。再将他们通过线段连接起来就实现了折线图。

      // 绘制折线图
      for (let i = 0; i < data.length; i++) {
        const item = data[i];

        const x = options.padding + (step * (i + 1))
        const y = options.height - options.padding - (item.y / valueStep) * positionStep

        if (i < data.length - 1) {
          const nextItem = data[i + 1];

          const nx = options.padding + (step * (i + 2))
          const ny = options.height - options.padding - (nextItem.y / valueStep) * positionStep

          const line = new Line({
            style: {
              x1: x,
              y1: y,
              x2: nx,
              y2: ny,
              stroke: '#409EFF',
              lineWidth: options.lineWidth * 2,
            }
          })
          canvas.appendChild(line);
        }

        const circle = new Circle({
          style: {
            cx: x,
            cy: y,
            r: 2,
            lineWidth: 1,
            fill: '#FFFFFF',
            stroke: '#409EFF'
          }
        })

        canvas.appendChild(circle)
      }

网站公告

今日签到

点亮在社区的每一天
去签到