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

7.2 创建柱状图

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

紧跟在饼图之后,柱状图是另外一个流行的数据可视化工具。本节,我们将创建一个可配置的Bar Chart类,它接受一个数据元素的数组,并生成一个简单的柱状图。我们将复用上一节的数据结构来比较其结果。跟Pie Chart类似,柱状图也尽可能自动填满整个画布。

创建柱状图
图7-2 创建柱状图

操作步骤

按照以下步,骤创建Bar Chart类,它根据一个数据的数组创建柱状图,并能够自动定位并设置柱状图和图例的尺寸:1. 定义BarChart类的构造函数,来绘制柱状图:

/* BarChart类的构造函数 */
function BarChart(config){
  // 用户自定义属性
  this.canvas  = document.getElementById(config.canvasId); 
  this.data  = config.data;
  this.color  = config.color;
  this.barWidth  = config.barWidth;
  this.gridLineIncrement  = config.gridLineIncrement;
  /*  把最大值调整为能被网格的增量整除的最大可能值,且小于请求的最大值 */
  this.maxValue  = config.maxValue  - Math.floor(config.maxValue  % this.gridLineIncrement);
  this.minValue  = config.minValue;
  //常量定义
  this.font  = "12pt Calibri"; 
  this.axisColor  = "#555"; 
  this.gridColor  = "#aaa"; 
  this.padding  =  10;
  //关系定义
  this.context  = this.canvas.getContext("2d"); 
  this.range  = this.maxValue  - this.minValue;
  this.numGridLines  = this.numGridLines  = Math.round(this.range / this.gridLineIncrement);
  this.longestValueWidth  = this.getLongestValueWidth(); 
  this.x  = this.padding  + this.longestValueWidth; 
  this.y  = this.padding  *  2;
  this.width  = this.canvas.width  -  (this.longestValueWidth  + this.padding  *  2);
  this.height  = this.canvas.height  -  (this.getLabelAreaHeight() + this.padding  *  4);
  //绘制柱状图
  this.drawGridlines();
  this.drawYAxis();
  this.drawXAxis();
  this.drawBars();
  this.drawYVAlues();
  this.drawXLabels();
}

2. 定义getLabelAreaHeight()方法,该方法决定标签区域的高度(标签位于x轴的下方):

/* 通过找出标签的最大宽度,并用三角函数计算出其投影高度(因为标签文本会被旋转45°),来获取标签高度 */
BarChart.prototype.getLabelAreaHeight  = function(){
  this.context.font  = this.font;
  var maxLabelWidth  =  0;
  /*
   * 遍历所有标签并确定哪个标签是最长的,使用该信息来决定标签的宽度
   */
  for  (var n  =  0; n  < this.data.length; n++)  {
    var label  = this.data[n].label;
    maxLabelWidth  = Math.max(maxLabelWidth, this.context. measureText(label).width);
  }
  /*返回 labelWidth 的 y 分量,labelWidth 在45°角的方向上
   *
   * a^2  + b^2  = c^2 
   * a  = b
   * c  = labelWidth
   * a  = 直角三角形的高度分量
   * 解出a
  */
  return Math.round(maxLabelWidth  / Math.sqrt(2));
};

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

BarChart.prototype.getLongestValueWidth  = function(){
  this.context.font  = this.font;
  var longestValueWidth  =  0;
  for  (var n  =  0; n  <= this.numGridLines; n++)  {
    var value  = this.maxValue  -  (n  * this.gridLineIncrement);
    longestValueWidth  = Math.max(longestValueWidth, this.context.measureText(value).width);
  }
  return longestValueWidth;
};

4. 定义drawXLabels()方法,该方法绘制x轴标签:

 BarChart.prototype.drawXLabels  = function(){
  var context  = this.context;
  context.save();
  var data  = this.data;
  var barSpacing  = this.width  / data.length;
  for  (var n  =  0; n  < data.length; n++)  {
    var label  = data[n].label; context.save();
    context.translate(this.x  +  ((n  +  1  /  2)  * barSpacing), this.y  + this.height  +  10);
    context.rotate(-1  * Math.PI  /  4);   // 旋转45°
    context.font  = this.font;
    context.fillStyle  = "black";
    context.textAlign  = "right";
    context.textBaseline  = "middle"; 
    context.fillText(label,  0,  0); 
    context.restore();
  }
  context.restore();
};

5. 定义drawYValues()方法,该方法绘制y轴的值:

 BarChart.prototype.drawYVAlues  = function(){
  var context  = this.context;
  context.save();
  context.font  = this.font;
  context.fillStyle  = "black";
  context.textAlign  = "right";
  context.textBaseline  = "middle";
  for  (var n  =  0; n  <= this.numGridLines; n++)  {
    var value  = this.maxValue  -  (n  * this.gridLineIncrement);
    var thisY  =  (n  * this.height  / this.numGridLines)  +  this.y;
    context.fillText(value, this.x  -  5, thisY);
  }
  context.restore();
};

6. 定义drawBars()方法,该方法遍历所有数据元素,并为每个元素绘制一根柱子:

BarChart.prototype.drawBars  = function(){
  var context  = this.context;
  context.save();
  var data  = this.data;
  var barSpacing  = this.width  / data.length;
  var unitHeight  = this.height  / this.range;
  for  (var n  =  0; n  < data.length; n++)  {
    var bar  = data[n];
    var barHeight  =  (data[n].value  - this.minValue)  * unitHeight;
    /* 如果柱子的高度小于0,意味着其值小于最小值。
     * 由于我们不想在x轴的下方绘制柱子,所以,只绘制高度大于0的柱子
     */
    if  (barHeight  >  0)  {
      context.save();
      context.translate(Math.round(this.x + ((n + 1 / 2) * barSpacing)), Math.round(this.y + this.height));
      /* 为了方便,我们可以绘制以x轴为起点、颠倒的柱子,
       * 然后使用scale(1,  -1)把它翻转到正确的方向。
       * 这是变换有助于减少计算的很好的例子
       */
      context.scale(1,  -1);
      context.beginPath();
      context.rect(-this.barWidth  /  2,  0, this.barWidth,
      barHeight);
      context.fillStyle  = this.color;
      context.fill();
      context.restore();
    }
  }
  context.restore();
};

7. 定义drawGridlines()方法,该方法绘制柱状图上的水平网格线:

BarChart.prototype.drawGridlines  = function(){
  var context  = this.context;
  context.save();
  context.strokeStyle  = this.gridColor; 
  context.lineWidth  =  2;
  // 绘制y轴网格线
  for  (var n  =  0; n  < this.numGridLines; n++)  {
    var y  =  (n  * this.height  / this.numGridLines)  + this.y; 
    context.beginPath();
    context.moveTo(this.x, y);
    context.lineTo(this.x  + this.width, y); 
    context.stroke();
  }
  context.restore();
};

8. 定义drawXAxis()方法,该方法绘制x轴:

 BarChart.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();
  context.restore();
};

9. 定义drawYAxis()方法,该方法绘制y轴:

BarChart.prototype.drawYAxis  = function(){
  var context  = this.context;
  context.save();
  context.beginPath();
  context.moveTo(this.x, this.y);
  context.lineTo(this.x, this.height  + this.y); 
  context.strokeStyle  = this.axisColor; 
  context.lineWidth  =  2;
  context.stroke();
  context.restore();
};

10. 页面加载完成后,构建数据,并实例化一个BarChart对象:

window.onload  = function(){
  var data  =  [{
    label: "Eating", 
    value:  2
  },  {
    label: "Working", 
    value:  8
  },  {
    label: "Sleeping", 
    value:  8
  },  {
    label: "Errands", 
    value:  2
  },  {
    label: "Entertainment", 
    value:  4
  }];
  new BarChart({
    canvasId: "myCanvas",
    data: data,
    color: "blue", 
    barWidth:  50, 
    minValue:  0, 
    maxValue:  10,
    gridLineIncrement:  2
  });
};

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

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

工作原理

与饼图相比,为了使柱状图变得真正通用,它需要多一点配置。实现BarChart类,我们需要传递画布ID、一个数据元素的数组、柱子颜色、柱子宽度、网格线的增量(网格线之间的单位的数目)、最大值和最小值。BarChart构造函数使用6个方法来渲染柱状图,它们分别是drawGridlines(),drawYAxis(),drawXAxis(),drawBars(),drawYValues(), drawXLabels()。

BarChart类的关键是drawBars()方法,该方法迭代所有的数据元素,再针对每个数据元素,绘制一个矩形。绘制每个柱子的最简单方法是,先把上下文转换为垂直方向(以便正的y值是向上的,而不是向下的),把绘制光标定位到x轴,再绘制一个向下的矩形,矩形的高度等于数据元素的值。因为上下文在垂直方向上是颠倒的,于是,柱子就会向上直立。

相关参考

  • 第1章 处理文本
  • 第2章 绘制矩形
  • 第4章 平移画布上下文
  • 第4章 旋转画布上下文
  • 第4章 创建镜像变换