当前位置: 首页 > 文档资料 > HTML5 Canvas 实战 >

7.4 通过折线图标记数据点

优质
小牛编辑
124浏览
2023-12-01

如果你曾经上过科学课,你可能对根据一套实验数据生成折线图比较熟悉。在反映数据趋势时,折线图可能是最有用的数据可视化工具之一。本节,我们将创建一个可配置的折线图,它接受一个数据元素的数组,并在使用线段连接每个点的过程中,标记每个点。

通过折线图标注数据点
图7-4 通过折线图标注数据点

操作步骤

按照以下步骤,创建一个Line Chart类,它能够根据一个数据的数组自动定位并设置折线图的尺寸:

1. 定义LineChart类的构造绘制,该函数绘制x轴和y轴:

function LineChart(config){
  //用户自定义属性
  this.canvas  = document.getElementById(config.canvasId); 
  this.minX  = config.minX;
  this.minY  = config.minY;
  this.maxX  = config.maxX;
  this.maxY  = config.maxY;
  this.unitsPerTickX  = config.unitsPerTickX;
  this.unitsPerTickY  = config.unitsPerTickY;
  // constants
  this.padding  =  10;
  this.tickSize  =  10;
  this.axisColor  = "#555"; 
  this.pointRadius  =  5;
  this.font  = "12pt Calibri";
  /* 由于measureText没有提供文本高度的度量值
   * 所以,我们不得不通过硬编码来指定文本高度的值
   */
  this.fontHeight  =  12; 
  // relationships
  this.context  = this.canvas.getContext("2d"); 
  this.rangeX  = this.maxX  - this.minY;
  this.rangeY  = this.maxY  - this.minY;
  this.numXTicks  = Math.round(this.rangeX  / this.unitsPerTickX);
  this.numYTicks  = Math.round(this.rangeY  / this.unitsPerTickY); 
  this.x  = this.getLongestValueWidth()  + this.padding  *  2; 
  this.y  = this.padding  *  2;
  this.width  = this.canvas.width  - this.x  - this.padding  *  2;
  this.height  = this.canvas.height  - this.y  - this.padding  -
  this.fontHeight;
  this.scaleX  = this.width  / this.rangeX;
  this.scaleY  = this.height  / this.rangeY;
  // draw x y axis and tick marks this.drawXAxis();
  this.drawYAxis();
}

2. 定义getLongestValueWidth()方法,该方法返回最长文本的像素宽度:

LineChart.prototype.getLongestValueWidth  = function(){
  this.context.font  = this.font;
  var longestValueWidth  =  0;
  for  (var n  =  0; n  <= this.numYTicks; n++)  {
    var value  = this.maxY  -  (n  * this.unitsPerTickY);
    longestValueWidth  = Math.max(longestValueWidth, this. context.measureText(value).width);
  }
  return longestValueWidth;
};

3. 定义drawXAxis()方法,该方法绘制x轴及其标签:

 LineChart.prototype.drawXAxis  = function(){
  var context  = this.context;
  context.save();
  context.beginPath();
  context.moveTo(this.x, this.y  + this.height);
  context.lineTo(this.x  + this.width, this.y  + this.height); 
  context.strokeStyle  = this.axisColor;
  context.lineWidth  =  2;
  context.stroke();
  // draw tick marks
  for  (var n  =  0; n  < this.numXTicks; n++)  {
    context.beginPath();
    context.moveTo((n  +  1)  * this.width  / this.numXTicks  + this.x, this.y  + this.height);
    context.lineTo((n  +  1)  * this.width  / this.numXTicks  + this.x, this.y  + this.height  - this.tickSize);
    context.stroke();
  }
  // draw labels
  context.font  = this.font;
  context.fillStyle  = "black";
  context.textAlign  = "center";
  context.textBaseline  = "middle";
  for  (var n  =  0; n  < this.numXTicks; n++)  {
    var label  = Math.round((n  +  1)  * this.maxX  / this. numXTicks);
    context.save();
    context.translate((n  +  1)  * this.width  / this.numXTicks  + this.x, this.y  + this.height  + this.padding);
    context.fillText(label,  0,  0);
    context.restore();
  }
  context.restore();
};

4. 定义drawYAxis()方法,该方法绘制y轴及其标签:

 LineChart.prototype.drawYAxis  = function(){
  var context  = this.context;
  context.save();
  context.save();
  context.beginPath();
  context.moveTo(this.x, this.y);
  context.lineTo(this.x, this.y  + this.height);
  context.strokeStyle  = this.axisColor; 
  context.lineWidth  =  2;
  context.stroke();
  context.restore();
  // draw tick marks
  for  (var n  =  0; n  < this.numYTicks; n++)  {
    context.beginPath();
    context.moveTo(this.x, n  * this.height  / this.numYTicks  +
    this.y);
    context.lineTo(this.x  + this.tickSize, n  * this.height  / this.numYTicks  + this.y);
    context.stroke();
  }
  // draw values
  context.font  = this.font;
  context.fillStyle  = "black";
  context.textAlign  = "right";
  context.textBaseline  = "middle";
  for  (var n  =  0; n  < this.numYTicks; n++)  {
    var value  = Math.round(this.maxY  - n  * this.maxY  / this. numYTicks);
    context.save();
    context.translate(this.x  - this.padding, n  * this.height  / this.numYTicks  + this.y);
    context.fillText(value,  0,  0); context.restore();
  }
  context.restore();
};

5. 定义drawLine()方法,该方法遍历数据点,并绘制线段来连接每个数据点:

LineChart.prototype.drawLine  = function(data, color, width){
  var context  = this.context;
  context.save();
  this.transformContext();
  context.lineWidth  = width;
  context.strokeStyle  = color; context.fillStyle  = color; context.beginPath();
  context.moveTo(data[0].x  * this.scaleX, data[0].y  * this. scaleY);
  for  (var n  =  0; n  < data.length; n++)  {
    var point  = data[n];
    // draw segment
    context.lineTo(point.x  * this.scaleX, point.y  * this. scaleY);
    context.stroke();
    context.closePath();
    context.beginPath();
    context.arc(point.x  * this.scaleX, point.y  * this.scaleY, this.pointRadius,  0,  2  * Math.PI, false);
    context.fill();
    context.closePath();
    // position for next segment context.beginPath();
    context.moveTo(point.x  * this.scaleX, point.y  * this. scaleY);
  }
  context.restore();
};

6. 定义transformContext()方法,该方法平移上下文,并在垂直方向上翻转上下文:

LineChart.prototype.transformContext  = function(){
  var context  = this.context;
  // move context to center of canvas
  this.context.translate(this.x, this.y  + this.height);
  // invert the y scale so that that increments
  // as you move upwards
  context.scale(1,  -1);
};

7. 页面加载完成后,实例化一个LineChart对象,创建蓝色折线的数据集,并使用drawLine()方法绘制该折线,再定义另一个红色折线的数据集,并绘制该折线:

window.onload  = function(){
  var myLineChart  = new LineChart({
    canvasId: "myCanvas",
    minX:  0,
    minY:  0,
    maxX:  140, 
    maxY:  100,
    unitsPerTickX:  10,
    unitsPerTickY:  10
  });
  var data  =  [{
    x:  0,
    y:  0
  },  {
    x:  20,
    y:  10
  },  {
    x:  40,
    y:  15
  },  {
    x:  60,
    y:  40
  },  {
    x:  80,
    y:  60
  },  {
    x:  100, 
    y:  50
  },  {
    x:  120, 
    y:  85
  },  {
    x:  140,
    y:  100
  }];
  myLineChart.drawLine(data, "blue",  3);
  var data  =  [{
    x:  20,
    y:  85
  },  {
    x:  40,
    y:  75
  },  {
    x:  60,
    y:  75
  },  {
    x:  80,
    y:  45
  },  {
    x:  100, 
    y:  65
  },  {
    x:  120, 
    y:  40
  },  {
    x:  140, 
    y:  35
  }];
  myLineChart.drawLine(data, "red",  3);
};

8. 在HTML文档的body部分嵌入canvas标签:

<canvas id="myCanvas" width="600" height="300" style="border:1px solid black;">
</canvas>

工作原理

开始,我们需要使用7个属性来配置LineChart对象,这些属性包括canvasId, minX, minY, maxX, maxY, unitsPerTickX和unitsPerTickY。LineChart。对象实例化完成后,我们将渲染x轴和y轴。

大多数有趣的事情发生在drawLine()方法中,该方法需要一个数据元素的数组,一个线条颜色,一个线条宽度参数。其工作原理如下:

1. 使用transformContext()方法平移、缩放、翻转上下文。

2. 调用moveTo()方法,把绘图光标定位到数据数组中的第一个数据点。

3. 遍历所有数据元素,从前一点到当前点绘制直线,然后,使用arc()方法,在当前点绘制一个小圆。

页面加载完成后,实例化一个LineChart对象,创建蓝色折线的数据点数组,并使用drawLine()方法绘制该折线,再定义另一个红色折线的数据点数组,并绘制该折线

相关参考

  • 第1章 绘制直线
  • 第2章 处理文本
  • 第4章 平移画布上下文