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

7.3 根据方程式绘制图形

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

本节,我们将创建一个可配置的Graph类,该类可以绘制带有刻度线及刻度值的x轴和y轴,再构造一个drawEquation()方法,该方法允许我们绘制f(x)函数的曲线图。我们将实例化一个Graph对象,并绘制一条正弦波,一条抛物线方程,和一条直线方程。

根据方程式绘制图形
图7-3 根据方程式绘制图形

操作步骤

按照以下步骤,创建一个Graph类,该类既能绘制带有刻度线及刻度值的x轴和y轴,也能绘制f(x)函数的曲线图:

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

function Graph(config){
  //用户自定义属性
  this.canvas  = document.getElementById(config.canvasId); 
  this.minX  = config.minX;
  this.minY  = config.minY;
  this.maxX  = config.maxX;
  this.maxY  = config.maxY;
  this.unitsPerTick  = config.unitsPerTick;
  // 常量
  this.axisColor  = "#aaa";
  this.font  = "8pt Calibri"; this.tickSize  =  20;
  //关系
  this.context  = this.canvas.getContext("2d");
  this.rangeX  = this.maxX  - this.minX;
  this.rangeY  = this.maxY  - this.minY;
  this.unitX  = this.canvas.width  / this.rangeX;
  this.unitY  = this.canvas.height  / this.rangeY;
  this.centerY  = Math.round(Math.abs(this.minY  / this.rangeY)  * this.canvas.height);
  this.centerX  = Math.round(Math.abs(this.minX  / this.rangeX)  * this.canvas.width);
  this.iteration =  (this.maxX  - this.minX)  /  1000; 
  this.scaleX  = this.canvas.width  / this.rangeX;
  this.scaleY  = this.canvas.height  / this.rangeY;
  //绘制x轴和y轴
  this.drawXAxis();
  this.drawYAxis();
}

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

Graph.prototype.drawXAxis  = function(){
  var context  = this.context;
  context.save();
  context.beginPath();
  context.moveTo(0, this.centerY);
  context.lineTo(this.canvas.width, this.centerY); 
  context.strokeStyle  = this.axisColor; 
  context.lineWidth  =  2;
  context.stroke();
  // 绘制刻度线
  var xPosIncrement  = this.unitsPerTick  * this.unitX;
  var xPos, unit;
  context.font  = this.font;
  context.textAlign  = "center";
  context.textBaseline  = "top";
  //绘制左侧刻度线
  xPos  = this.centerX  - xPosIncrement;
  unit  =  -1  * this.unitsPerTick; 
  while  (xPos  >  0)  {
    context.moveTo(xPos, this.centerY  - this.tickSize  /  2); 
    context.lineTo(xPos, this.centerY  + this.tickSize  /  2); 
    context.stroke();
    context.fillText(unit, xPos, this.centerY  + this.tickSize /  2  +  3);
    unit  -= this.unitsPerTick;
    xPos  = Math.round(xPos  - xPosIncrement);
  }
  //绘制右侧刻度线
  xPos  = this.centerX  + xPosIncrement; 
  unit  = this.unitsPerTick;
  while  (xPos  < this.canvas.width)  {
    context.moveTo(xPos, this.centerY  - this.tickSize  /  2); 
    context.lineTo(xPos, this.centerY  + this.tickSize  /  2); 
    context.stroke();
    context.fillText(unit, xPos, this.centerY  + this.tickSize /  2  +  3);
    unit  += this.unitsPerTick;
    xPos  = Math.round(xPos  + xPosIncrement);
  }
  context.restore();
};

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

Graph.prototype.drawYAxis  = function(){
  var context  = this.context;
  context.save();
  context.beginPath();
  context.moveTo(this.centerX,  0);
  context.lineTo(this.centerX, 
  this.canvas.height); 
  context.strokeStyle  = this.axisColor; 
  context.lineWidth  =  2;
  context.stroke();
  //绘制刻度线
  var yPosIncrement  = this.unitsPerTick  * this.unitY;
  var yPos, unit;
  context.font  = this.font;
  context.textAlign  = "right";
  context.textBaseline  = "middle";
  //绘制顶部刻度线
  yPos  = this.centerY  - yPosIncrement; 
  unit  = this.unitsPerTick;
  while  (yPos  >  0)  {
    context.moveTo(this.centerX  - this.tickSize  /  2, yPos);
    context.lineTo(this.centerX  + this.tickSize  /  2, yPos); 
    context.stroke();
    context.fillText(unit, this.centerX - this.tickSize / 2 - 3, yPos);
    unit  += this.unitsPerTick;
    yPos  = Math.round(yPos  - yPosIncrement);
  }
  //绘制底部刻度线
  yPos  = this.centerY  + yPosIncrement; 
  unit  =  -1  * this.unitsPerTick; 
  while  (yPos  < this.canvas.height)  {
    context.moveTo(this.centerX  - this.tickSize  /  2, yPos); 
    context.lineTo(this.centerX  + this.tickSize  /  2, yPos); 
    context.stroke();
    context.fillText(unit, this.centerX - this.tickSize / 2 - 3, yPos);
    unit  -= this.unitsPerTick;
    yPos  = Math.round(yPos  + yPosIncrement);
  }
  context.restore();
};

4. 调用drawEquation()方法,该方法接受一个函数f(x)参数,然后,通过从minX到maxX循遍历x值来绘制该方程:

Graph.prototype.drawEquation  = function(equation, color, thickness){
  var context  = this.context; context.save();
  context.save();
  this.transformContext();
  context.beginPath();
  context.moveTo(this.minX, equation(this.minX));
  for  (var x = this.minX + this.iteration; x <= this.maxX; x += this.iteration)  {
    context.lineTo(x, equation(x));
  }
  context.restore();
  context.lineJoin  = "round";
  context.lineWidth  = thickness; 
  context.strokeStyle  = color; 
  context.stroke();
  context.restore();
};

5. 定义transformContext()方法,该方法把上下文平移到图形的中央,拉伸图形以适应画布,然后翻转y轴:

Graph.prototype.transformContext  = function(){
  var context  = this.context;
  //把上下文平移到画布中央
  this.context.translate(this.centerX, this.centerY);
  /* 拉伸网格以适应画布,并翻转y轴,
   * 以便y轴刻度值沿向上方向依次递增
   */
  context.scale(this.scaleX,  -this.scaleY);
};

6. 页面加载完成后,实例化一个Graph对象,然后调用drawEquation()方法绘制三个方程:

window.onload  = function(){
  var myGraph  = new Graph({
    canvasId: "myCanvas",
    minX:  -10,
    minY:  -10, 
    maxX:  10, 
    maxY:  10,
    unitsPerTick:  1
  });
  myGraph.drawEquation(function(x){
      return  5  * Math.sin(x);
  }, "green",  3);
  myGraph.drawEquation(function(x){
      return x  * x;
  }, "blue",  3);
  myGraph.drawEquation(function(x){
      return  1  * x;
  }, "red",  3);
};

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

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

工作原理

我们的Graph类仅仅需要6个参数,canvasId, minX, minY, maxX, maxY和 unitsPerTick。实例化完成后,该实例调用drawXAxis()方法绘制x轴,调用drawYAxis()方法绘制y轴。

Graph对象的精华是drawEquation()方法,该方法接受一个方程f(x),一个线条颜色,一个线条宽度参数。虽然该方法相对较短(大约20行代码),但它实际上很强大。其工作原理如下:

  1. 该方法定位画布上下文,缩放上下文以适应画布,调用scale()方法,通过给y分量乘以-1来翻转y轴。
  2. 画布上下文准备就绪后,使用equation()函数来确定x等于minX时的y值,即f(minX)。
  3. 调用moveTo()方法移动绘制光标。
  4. 使用一个for循环,每个迭代中,慢慢增加x值,并使用f(x)方程确定相应的y值。
  5. 调用lineTo()方法,从上一个点到当前点画直线。
  6. 继续循环,直到x等于maxX。

由于每次迭代绘制的线条都很短,肉眼无法分辨,最终产生平滑曲线的错觉感。

页面加载完成后,我们可以实例化一个Graph对象,并调用drawEquation()方法,来绘制一条绿色的正弦波,一条蓝色的抛物线方程,和一条红色的直线方程。

相关参考

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