• canvas 绘制折线图及思考


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

    功能效果图

    折线图

    实现步骤

    前期准备

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

      <link rel="shortcut icon" href="https://gw.alipayobjects.com/zos/antfincdn/yAeuB2%24niG/favicon.png" />
      
      <script src="https://unpkg.com/@antv/g/dist/index.umd.min.js" type="application/javascript">script>
      
      <script src="https://unpkg.com/@antv/g-canvas/dist/index.umd.min.js" type="application/javascript">script>
    
    • 1
    • 2
    • 3
    • 4
    • 5

    创建需要渲染的 div

      <div>
        <div id="app">div>
      div>
    
    • 1
    • 2
    • 3

    引入所需对象

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

    创建画布

        // 创建一个渲染器,这里使用 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,
        });
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    数据和配置

    类似于 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',
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    根据 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);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38

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

          // 绘制横坐标刻度
          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);
          }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69

    折线图绘制

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

          // 绘制折线图
          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)
          }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
  • 相关阅读:
    淘宝API获取——商品详情信息、DESC信息、主图
    【数据结构】单链表按位序插入元素e【前插】(带头结点的和不带头结点的)这篇很重要,文字说明比起其他篇是正确的
    回归预测 | MATLAB实现BP神经网络多输入多输出回归预测
    postgresql源码学习(24)—— 事务日志⑤-日志写入WAL Buffer
    数据治理:元数据管理篇
    制作一个简单HTML个人网页网页(HTML+CSS)大话西游之大圣娶亲电影网页设计
    记一次 .NET某管理局检测系统 内存暴涨分析
    R在GIS中用ggmap地理空间数据分析
    基于springboot实现校友社交平台管理系统项目【项目源码+论文说明】计算机毕业设计
    Super easy to understand decision trees (part one)
  • 原文地址:https://blog.csdn.net/violetjack0808/article/details/138214956