如何使用JavaScript和HTML5 Canvas绘制图表
在本教程中,我将向您展示如何使用javascript和画布以饼图和圆环图的形式显示数字信息。
什么是饼图?
图表是一种统计工具,用于以图形方式表示数值数据。饼图将数值数据显示为一个分成多个切片的圆圈。每个切片的大小与其所代表的数值成正比。
什么是甜甜圈图?
简而言之,圆环图是饼图的变体。不同之处在于,切片是朝着馅饼的中心切开的,这样只有边缘是可见的。这样,图表看起来像一个甜甜圈,因此得名。
使用画布开始绘图
在绘制饼图之前,我们先来看看绘制它的各个部分。我们将看到如何使用画布组件和 JavaScript 进行绘制:
-
一条线
-
圆弧(圆的一部分)
-
一个充满颜色的形状
要使用html 5 画布开始绘图,我们需要创建一些东西:
-
一个文件夹来保存项目文件;让我们称这个文件夹piechart-tutorial。
-
index.html 文件夹内的一个 HTML 文件piechart-tutorial 。该文件将包含 HTML 代码。
-
script.js 文件夹内 有一个 JS 文件piechart-tutorial 。该文件将包含我们的 javaScript 代码。
我们将保持非常简单,并在其中添加以下代码 index.html:
<html> <body> <canvas id="myCanvas"></canvas> <script type="text/javascript" src="script.js"></script> </body> </html>
我们拥有<canvas>带有 ID 的元素,myCanvas以便我们可以在 JS 代码中引用它。然后我们通过<script>标签加载 JS 代码。
在内部script.js,JS 代码将首先获取对画布的引用,然后设置其宽度和高度。要在画布上绘图,我们只需要引用其包含所有绘图方法的 2D 上下文。
var myCanvas = document.getElementById("myCanvas"); myCanvas.width = 300; myCanvas.height = 300; var ctx = myCanvas.getContext("2d");
现在我们已经设置了画布并且还引用了绘图画布,让我们定义一些 JavaScript 函数,我们将能够在绘制饼图时重用它们。我们将在 script.js 文件中添加函数。
function drawLine(ctx, startX, startY, endX, endY){ ctx.beginPath(); ctx.moveTo(startX,startY); ctx.lineTo(endX,endY); ctx.stroke(); }
该drawLine函数有五个参数:
-
ctx: 引用绘图上下文
-
startX:线起点的X坐标
-
startY:线起点的Y坐标
-
endX: 线终点的X坐标
-
endY: 线终点的Y坐标
我们通过调用 开始画线 beginPath()。这会通知绘图上下文我们开始在画布上绘制新的东西。我们使用moveTo()设置起点,调用lineTo()指示终点,然后通过调用进行实际绘制stroke()。
现在让我们看看如何绘制圆的一部分,也称为圆弧。
function drawArc(ctx, centerX, centerY, radius, startAngle, endAngle){ ctx.beginPath(); ctx.arc(centerX, centerY, radius, startAngle, endAngle); ctx.stroke(); }
该 drawArc 函数有六个参数:
-
ctx: 引用绘图上下文
-
centerX:圆心的X坐标
-
centerY:圆心的Y坐标
-
radius: 线终点的X坐标
-
startAngle:圆的部分开始的弧度开始角度
-
endAngle:圆的部分结束处的弧度结束角度
我们已经了解了如何绘制一条线和如何绘制弧线,现在让我们看看如何绘制彩色形状。由于我们的目标是绘制一个由切片组成的饼图,让我们创建一个绘制饼图的函数。
function drawPieSlice(ctx,centerX, centerY, radius, startAngle, endAngle, color ){ ctx.fillStyle = color; ctx.beginPath(); ctx.moveTo(centerX,centerY); ctx.arc(centerX, centerY, radius, startAngle, endAngle); ctx.closePath(); ctx.fill(); }
该 drawPieSlice 函数有七个参数:
-
ctx: 引用绘图上下文
-
centerX:圆心的X坐标
-
centerY:圆心的Y坐标
-
radius: 线终点的X坐标
-
startAngle:圆的部分开始的弧度开始角度
-
endAngle:圆的部分结束处的弧度结束角度
-
color:用于填充切片的颜色
下面是调用三个函数的示例:
drawLine(_ctx,100,100,200,200); drawArc(_ctx, 150,150,150, 0, Math.PI/3); drawPieSlice(_ctx, 150,150,150, Math.PI/2, Math.PI/2 + Math.PI/4, '#ff0000');
它将产生以下结果:
现在我们有了绘制饼图所需的所有工具,让我们看看我们如何一起使用它们。
绘制饼图
从概念上讲,任何图表都有两个主要部分:
-
数据模型包含要表示的数值数据。这是以特定于图表类型的格式构建的。
-
图形表示是数据模型中的数值数据如何根据一些规则以数学公式的形式由可视元素表示。
饼图数据模型
构建饼图数据模型的最常见方法是一系列类别和相应的值,其中每个类别和值都与饼图的一部分相关联。
例如,显示我按类型分组的乙烯基数量的饼图数据模型如下所示:
-
古典音乐:10
-
另类摇滚:14
-
流行:2
-
爵士乐:12
我们可以在文件中添加一个 JS 对象来 script.js 存储数据模型,如下所示:
var myVinyls = { "Classical music": 10, "Alternative rock": 14, "Pop": 2, "Jazz": 12 };
饼图图形表示
饼图使用一个圆圈来显示数据模型中的信息,将其划分为多个切片。每个切片对应于数据模型中的一个类别,切片的大小与类别值成正比。
我收藏的 38 张黑胶唱片有四个类别。每个类别将获得与该类别中乙烯基数量成比例的饼图切片。
但是我们如何测量切片的大小呢?这很容易——我们通过切片尖端的角度来做到这一点。我们所要知道的是,整个圆对应于360 degrees或的角度2 * PI。所以半个圆圈是180 degor PI,四分之一是90 degor PI/2,依此类推。
为了确定每个类别切片的角度,我们使用以下公式:
slice angle = 2 * PI * category value / total value
根据这个公式,十张古典音乐黑胶唱片的切片角度约为。0.526 * PI 或 94 度。
让我们开始画画吧。为此,我们将使用我们将命名的 JavaScript 类Piechart。构造函数将接收一个选项参数,一个包含以下内容的对象:
-
canvas:引用我们要绘制饼图的画布
-
数据:对持有数据模型的对象的引用
-
颜色:一个数组,保存我们要为每个切片使用的颜色
该类Piechart还包含一种draw()实际绘制图表的方法。
var Piechart = function(options){ this.options = options; this.canvas = options.canvas; this.ctx = this.canvas.getContext("2d"); this.colors = options.colors; this.draw = function(){ var total_value = 0; var color_index = 0; for (var categ in this.options.data){ var val = this.options.data[categ]; total_value += val; } var start_angle = 0; for (categ in this.options.data){ val = this.options.data[categ]; var slice_angle = 2 * Math.PI * val / total_value; drawPieSlice( this.ctx, this.canvas.width/2, this.canvas.height/2, Math.min(this.canvas.width/2,this.canvas.height/2), start_angle, start_angle+slice_angle, this.colors[color_index%this.colors.length] ); start_angle += slice_angle; color_index++; } } }
该类首先存储 options传递的参数。它存储 canvas引用并创建一个也存储为类成员的绘图上下文。然后它存储colors作为选项传递的数组。
下一部分是最一致的, draw()功能。这将从数据模型中提取数据。首先,它计算数据模型中所有值的总和。然后,对于数据模型中的每个类别,我们应用上面提到的公式来计算饼图的角度。最后我们使用drawPieSlice() 以画布中心作为切片中心的函数。作为半径,我们使用画布宽度的一半和画布高度的一半之间的最小值,因为我们不希望饼图超出画布。
每次绘制类别时,我们还会偏移切片的开始和结束角度,否则切片会重叠。
要使用该类,我们必须创建一个实例,然后 draw()在创建的对象上调用该方法。
var myPiechart = new Piechart( { canvas:myCanvas, data:myVinyls, colors:["#fde23e","#f16e23", "#57d9ff","#937e88"] } ); myPiechart.draw();
结果看起来像这样
绘制圆环图
我们已经了解了如何绘制饼图。我们还知道,圆环图的不同之处仅在于图表中间有一个洞。我们如何画洞?我们可以在饼图上画一个白色圆圈。
让我们修改 Piechart类的代码来做到这一点。
var Piechart = function(options){ this.options = options; this.canvas = options.canvas; this.ctx = this.canvas.getContext("2d"); this.colors = options.colors; this.draw = function(){ var total_value = 0; var color_index = 0; for (var categ in this.options.data){ var val = this.options.data[categ]; total_value += val; } var start_angle = 0; for (categ in this.options.data){ val = this.options.data[categ]; var slice_angle = 2 * Math.PI * val / total_value; drawPieSlice( this.ctx, this.canvas.width/2, this.canvas.height/2, Math.min(this.canvas.width/2,this.canvas.height/2), start_angle, start_angle+slice_angle, this.colors[color_index%this.colors.length] ); start_angle += slice_angle; color_index++; } //drawing a white circle over the chart //to create the doughnut chart if (this.options.doughnutHoleSize){ drawPieSlice( this.ctx, this.canvas.width/2, this.canvas.height/2, this.options.doughnutHoleSize * Math.min(this.canvas.width/2,this.canvas.height/2), 0, 2 * Math.PI, "#ff0000" ); } } }
添加的代码在 options 参数中查找成员变量 doughnutHoleSize。如果选项中不存在,则代码将像以前一样绘制饼图,但如果确实存在,则绘制与饼图相同中心的白色圆圈。
圆的半径由饼图半径乘以 的值确定doughnutHoleSize。这应该是一个介于 0 和 1 之间的数字,其中 0 将导致饼图,任何高于 0 的值都会导致圆环的孔越来越大,1 使图表不可见。
要绘制一个带有一半大小的孔的圆环图,我们需要使用 doughnutHoleSize0.5 的 a 并进行以下调用:
var myDougnutChart = new Piechart( { canvas:myCanvas, data:myVinyls, colors:["#fde23e","#f16e23", "#57d9ff","#937e88"], doughnutHoleSize:0.5 } ); myDougnutChart.draw();
结果如下:
添加标签和图表图例
我们的饼图和甜甜圈图看起来不错,但我们可以通过添加两件事来使它们变得更好:
-
值标签:显示每个切片对应的百分比
-
图表图例:在图表中显示类别及其对应的颜色
通常,与切片关联的值表示为计算为 的百分比值100 * value associated to a slice / total value,整个圆圈表示100%。
例如,在我们的样本数据中,带有古典音乐的黑胶唱片大约代表26%. 如果能够在相应的切片上直接写入该值,那就太好了。为此,我们将使用 fillText(text,x,y)绘图上下文的功能。这个函数接受三个参数:文本x和y坐标。
我们如何计算 放置文本的坐标x和 坐标?y我们必须利用一些几何知识和一种叫做 极坐标的东西。基本上,极坐标使用半径和角度来定义点的位置。我们将使用的两个公式是:
x = R * cos(angle)
y = R * sin(angle)
我们将应用这两个公式将文本放置在饼图半径的一半和每个饼片角度的一半。为此,我们需要修改我们的 Piechart类并在块之后添加以下代码 if (this.options.doughnutHoleSize){...}:
... start_angle = 0; for (categ in this.options.data){ val = this.options.data[categ]; slice_angle = 2 * Math.PI * val / total_value; var pieRadius = Math.min(this.canvas.width/2,this.canvas.height/2); var labelX = this.canvas.width/2 + (pieRadius / 2) * Math.cos(start_angle + slice_angle/2); var labelY = this.canvas.height/2 + (pieRadius / 2) * Math.sin(start_angle + slice_angle/2); if (this.options.doughnutHoleSize){ var offset = (pieRadius * this.options.doughnutHoleSize ) / 2; labelX = this.canvas.width/2 + (offset + pieRadius / 2) * Math.cos(start_angle + slice_angle/2); labelY = this.canvas.height/2 + (offset + pieRadius / 2) * Math.sin(start_angle + slice_angle/2); } var labelText = Math.round(100 * val / total_value); this.ctx.fillStyle = "white"; this.ctx.font = "bold 20px Arial"; this.ctx.fillText(labelText+"%", labelX,labelY); start_angle += slice_angle; } ...
代码遍历每个切片,计算百分比,计算位置,并使用 fillText()方法将其绘制在图表上。我们使用该fillStyle属性将文本颜色设置为白色,并使用该font属性设置标签的大小、样式和字体系列。还需要注意的是,如果图表是圆环图并且 doughnutHoleSize 已设置,则标签将被推向图表的边缘,使其以圆环切片为中心。
以下是带有值标签的结果图表的外观:
为了完成我们的图表,我们将添加的最后一件事是图表图例。我们的图表图例将显示我们的数据模型的类别和用于相应切片的颜色。首先,我们必须通过添加将存储我们的图例元素index.html的标签来对我们的文件进行一些修改 。<div>
<html> <body> <canvas id="myCanvas"></canvas> <div id="myLegend"></div> <script type="text/javascript" src="script.js"></script> </body> </html>
然后script.js 我们添加创建图例元素内容的代码。draw()我们在类的函数 末尾添加这段代码 Piechart:
... if (this.options.legend){ color_index = 0; var legendHTML = ""; for (categ in this.options.data){ legendHTML += "<div><span style='display:inline-block;width:20px;background-color:"+this.colors[color_index++]+";'> </span> "+categ+"</div>"; } this.options.legend.innerHTML = legendHTML; } ...
代码查找 legend通过options参数传递的元素。如果提供了一个,则此元素将填充 HTML 代码,其中包含一个彩色框和数据模型类别的名称。
我们还需要改变我们调用饼图绘制的方式,如下所示:
var myLegend = document.getElementById("myLegend"); var myDougnutChart = new Piechart( { canvas:myCanvas, data:myVinyls, colors:["#fde23e","#f16e23", "#57d9ff","#937e88"], legend:myLegend } ); myDougnutChart.draw();
这是生成的图表和图表图例:
恭喜
我们已经看到,使用 html5 画布绘制图表实际上并不难。它只需要一点数学知识和一点 JavaScript 知识。您现在拥有绘制自己的饼图和圆环图所需的一切。
https://www.weixiaolive.com/en/index.php/post/347.html
以下是整理后的完整代码:
<canvas id="myCanvas"></canvas> <div id="myLegend"></div> <script src="~/Scripts/script.js"></script>
//绘制直线 function drawLine(ctx, startX, startY, endX, endY) { ctx.beginPath(); ctx.moveTo(startX, startY); ctx.lineTo(endX, endY); ctx.stroke(); } //绘制圆弧 function drawArc(ctx, centerX, centerY, radius, startAngle, endAngle) { ctx.beginPath(); ctx.arc(centerX, centerY, radius, startAngle, endAngle); ctx.stroke(); } //绘制饼图 function drawPieSlice(ctx, centerX, centerY, radius, startAngle, endAngle, color) { ctx.fillStyle = color; ctx.beginPath(); ctx.moveTo(centerX, centerY); ctx.arc(centerX, centerY, radius, startAngle, endAngle); ctx.closePath(); ctx.fill(); } //饼图类 var Piechart = function (options) { this.options = options; this.canvas = options.canvas; this.ctx = this.canvas.getContext("2d"); this.colors = options.colors; this.draw = function () { var totalValue = 0; var colorIndex = 0; var data = this.options.data; var categ; var val; for (categ in data) { if (data.hasOwnProperty(categ)) { val = data[categ]; totalValue += val; } } var startAngle = 0; for (categ in data) { if (data.hasOwnProperty(categ)) { val = data[categ]; var sliceAngle = 2 * Math.PI * val / totalValue; drawPieSlice( this.ctx, this.canvas.width / 2, this.canvas.height / 2, Math.min(this.canvas.width / 2, this.canvas.height / 2), startAngle, startAngle + sliceAngle, this.colors[colorIndex % this.colors.length] ); startAngle += sliceAngle; colorIndex++; } } } } //圆环类 var Ringchart = function (options) { this.options = options; this.canvas = options.canvas; this.ctx = this.canvas.getContext("2d"); this.colors = options.colors; this.draw = function () { var totalValue = 0; var colorIndex = 0; var data = this.options.data; var categ; var val; for (categ in data) { if (data.hasOwnProperty(categ)) { val = data[categ]; totalValue += val; } } var startAngle = 0; var sliceAngle; for (categ in data) { if (data.hasOwnProperty(categ)) { val = data[categ]; sliceAngle = 2 * Math.PI * val / totalValue; drawPieSlice( this.ctx, this.canvas.width / 2, this.canvas.height / 2, Math.min(this.canvas.width / 2, this.canvas.height / 2), startAngle, startAngle + sliceAngle, this.colors[colorIndex % this.colors.length] ); startAngle += sliceAngle; colorIndex++; } } //drawing a white circle over the chart //to create the doughnut chart if (this.options.doughnutHoleSize) { drawPieSlice( this.ctx, this.canvas.width / 2, this.canvas.height / 2, this.options.doughnutHoleSize * Math.min(this.canvas.width / 2, this.canvas.height / 2), 0, 2 * Math.PI, "White" ); } //添加标签 startAngle = 0; for (categ in data) { if (data.hasOwnProperty(categ)) { val = data[categ]; sliceAngle = 2 * Math.PI * val / totalValue; var pieRadius = Math.min(this.canvas.width / 2, this.canvas.height / 2); var labelX = this.canvas.width / 2 + (pieRadius / 2) * Math.cos(startAngle + sliceAngle / 2); var labelY = this.canvas.height / 2 + (pieRadius / 2) * Math.sin(startAngle + sliceAngle / 2); if (this.options.doughnutHoleSize) { var offset = (pieRadius * this.options.doughnutHoleSize) / 2; labelX = this.canvas.width / 2 + (offset + pieRadius / 2) * Math.cos(startAngle + sliceAngle / 2); labelY = this.canvas.height / 2 + (offset + pieRadius / 2) * Math.sin(startAngle + sliceAngle / 2); } var labelText = Math.round(100 * val / totalValue); this.ctx.fillStyle = "white"; this.ctx.font = "bold 20px Arial"; this.ctx.fillText(labelText + "%", labelX, labelY); startAngle += sliceAngle; } } //添加图例 if (this.options.legend) { colorIndex = 0; var legendHtml = ""; for (categ in data) { if (data.hasOwnProperty(categ)) { legendHtml += "<div><span style='display:inline-block;width:20px;background-color:" + this.colors[colorIndex++] + ";'> </span> " + categ + "</div>"; } } this.options.legend.innerHTML = legendHtml; } } } //调用绘制 var myCanvas = document.getElementById("myCanvas"); myCanvas.width = 300; myCanvas.height = 300; var _ctx = myCanvas.getContext("2d"); drawLine(_ctx, 100, 100, 200, 200); drawArc(_ctx, 150, 150, 150, 0, Math.PI / 3); drawPieSlice(_ctx, 150, 150, 150, Math.PI / 2, Math.PI / 2 + Math.PI / 4, '#ff0000'); //饼图数据 var myVinyls = { "Classical music": 10, "Alternative rock": 14, "Pop": 2, "Jazz": 12 }; //使用饼图类绘制饼图 var myPiechart = new Piechart( { canvas: myCanvas, data: myVinyls, colors: ["#fde23e", "#f16e23", "#57d9ff", "#937e88"] } ); myPiechart.draw(); //使用圆环类画圆环(带图例的) var myLegend = document.getElementById("myLegend"); var myDougnutChart2 = new Ringchart( { canvas: myCanvas, data: myVinyls, colors: ["#fde23e", "#f16e23", "#57d9ff", "#937e88"], legend: myLegend } ); myDougnutChart2.draw();