你将要创造什么
在本教程中,我将向您展示如何使用 JAVAScript 和画布以饼图和圆环图的形式显示数字信息。
图表是一种统计工具,用于以图形方式表示数值数据。饼图将数值数据显示为一个分成多个切片的圆圈。每个切片的大小与其所代表的数值成正比。
简而言之,圆环图是饼图的变体。不同之处在于,切片是朝着馅饼的中心切开的,这样只有边缘是可见的。这样,图表看起来像一个甜甜圈,因此得名。
在绘制饼图之前,我们先来看看绘制它的各个部分。我们将看到如何使用画布组件和 JavaScript 进行绘制:
要使用 html5 画布开始绘图,我们需要创建一些东西:
我们将保持非常简单,并在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, color){
ctx.save();
ctx.strokeStyle = color;
ctx.beginPath();
ctx.moveTo(startX,startY);
ctx.l.NETo(endX,endY);
ctx.stroke();
ctx.restore();
}
该drawLine函数有六个参数:
我们在函数内部做的第一件事是保存当前上下文。之后,我们通过调用 开始画线beginPath()。这会通知绘图上下文我们开始在画布上绘制新的东西。我们使用moveTo()设置起点,调用lineTo()指示终点,然后通过调用进行实际绘制stroke()。
现在让我们看看如何绘制圆的一部分,也称为圆弧。
function drawArc(ctx, centerX, centerY, radius, startAngle, endAngle, color){
ctx.save();
ctx.strokeStyle = color;
ctx.beginPath();
ctx.arc(centerX, centerY, radius, startAngle, endAngle);
ctx.stroke();
ctx.restore();
}
该drawArc函数有七个参数:
我们已经了解了如何绘制一条线和如何绘制弧线,现在让我们看看如何绘制彩色形状。由于我们的目标是绘制一个由切片组成的饼图,让我们创建一个绘制饼图的函数。
function drawPieSlice(ctx,centerX, centerY, radius, startAngle, endAngle, fillColor, strokeColor) {
ctx.save();
ctx.fillStyle = fillColor;
ctx.strokeStyle = strokeColor;
ctx.beginPath();
ctx.moveTo(centerX, centerY);
ctx.arc(centerX, centerY, radius, startAngle, endAngle, strokeColor);
ctx.closePath();
ctx.fill();
ctx.restore();
}
该drawPieSlice函数有七个参数:
下面是调用三个函数的示例:
drawLine(ctx, 200, 200, 300, 300, "#000");
drawArc(ctx, 250, 250, 150, 0, Math.PI/3, "#000");
drawPieSlice(ctx, 250, 250, 150, Math.PI/2, Math.PI/2 + Math.PI/3, "#F00", "#000");
它将产生以下结果:
现在我们有了绘制饼图所需的所有工具,让我们看看我们如何一起使用它们。
从概念上讲,任何图表都有两个主要部分:
为饼图构建数据模型的最常见方法是一系列类别和相应的值,其中每个类别和值都与饼图的一部分相关联。
例如,显示我按类型分组的乙烯基数量的饼图数据模型如下所示:
这些数据可以以对象的形式传递给我们的饼图绘制类。稍后我们将把它传递给我们的PieChart对象:
{
"Classical Music": 16,
"Alternative Rock": 12,
"Pop": 18,
"Jazz": 32,
}
广告
饼图使用一个圆圈来显示数据模型中的信息,将其划分为多个切片。每个切片对应于数据模型中的一个类别,切片的大小与类别值成正比。
我收藏的 88 张黑胶唱片有四个类别。每个类别将获得与该类别中乙烯基数量成比例的饼图切片。
但是我们如何测量切片的大小呢?这很容易——我们通过切片尖端的角度来做到这一点。我们所要知道的是,整个圆对应于360 degrees或的角度2 * PI。所以半个圆圈是180 degor PI,四分之一是90 degor PI/2,依此类推。
为了确定每个类别切片的角度,我们使用以下公式:
slice angle = 2 * PI * category value / total value
根据这个公式,16 块古典音乐黑胶唱片的切片角度约为 0.36 * PI 或 64.8 度。
让我们开始画画吧。为此,我们将使用我们将命名的 JavaScript 类Piechart。构造函数将接收一个options参数,一个包含以下内容的对象:
该类Piechart还包含一个方法,draw(),它执行图表的实际绘制。
var Piechart = function(options){
constructor(options) {
this.options = options;
this.canvas = options.canvas;
this.ctx = this.canvas.getContext("2d");
this.colors = options.colors;
this.titleOptions = options.titleOptions;
this.totalValue = [...Object.values(this.options.data)].reduce((a, b) => a + b, 0);
this.radius = Math.min(this.canvas.width / 2, this.canvas.height / 2) - options.padding;
}
drawSlices() {
var colorIndex = 0;
var startAngle = -Math.PI / 2;
for (var categ in this.options.data) {
var val = this.options.data[categ];
var sliceAngle = (2 * Math.PI * val) / this.totalValue;
drawPieSlice(
this.ctx,
this.canvas.width / 2,
this.canvas.height / 2,
this.radius,
startAngle,
startAngle + sliceAngle,
this.colors[colorIndex % this.colors.length]
);
startAngle += sliceAngle;
colorIndex++;
}
}
}
该类首先将传入options的不同属性存储在constructor()方法中。它存储canvas引用并创建一个也存储为类成员的绘图上下文。然后它存储colors作为选项传递的数组。
我们将对象值转换为构造函数内的数组,然后使用reduce()方法获取所有数据点的总数。该值稍后用于计算不同切片的角度。我们还在构造方法中计算饼图的半径,方法是取画布宽度的一半和画布高度的一半之间的最小值,然后减去填充。减去填充以计算半径有助于在不同图表元素之间引入一些空间。
该drawSlices()方法完成了饼图的所有实际绘制。对于数据模型中的每个类别,我们应用上面提到的公式来计算饼图的角度。最后,我们使用drawPieSlice()函数,使用画布的中心作为切片的中心。每次绘制类别时,我们还会偏移切片的开始和结束角度,否则切片会重叠。
要使用该类,我们必须创建一个实例,然后drawSlices()在创建的对象上调用该方法。
var myPiechart = new Piechart(
{
canvas: myCanvas,
data: {
"Classical Music": 16,
"Alternative Rock": 12,
"Pop": 18,
"Jazz": 32
},
colors: ["#80DEEA", "#FFE082", "#FFAB91", "#CE93D8"]
}
);
myPiechart.drawSlices();
结果如下所示:
我们已经了解了如何绘制饼图。我们还知道,圆环图的不同之处仅在于图表中间有一个洞。我们如何画洞?我们可以在饼图上画一个白色圆圈。
让我们修改类的drawSlices()方法Piechart来做到这一点。
drawSlices() {
var colorIndex = 0;
var startAngle = -Math.PI / 2;
for (var categ in this.options.data) {
var val = this.options.data[categ];
var sliceAngle = (2 * Math.PI * val) / this.totalValue;
drawPieSlice(
this.ctx,
this.canvas.width / 2,
this.canvas.height / 2,
this.radius,
startAngle,
startAngle + sliceAngle,
this.colors[colorIndex % this.colors.length]
);
startAngle += sliceAngle;
colorIndex++;
}
if (this.options.doughnutHoleSize) {
drawPieSlice(
this.ctx,
this.canvas.width / 2,
this.canvas.height / 2,
this.options.doughnutHoleSize * this.radius,
0,
2 * Math.PI,
"#FFF",
"#FFF"
);
drawArc(
this.ctx,
this.canvas.width / 2,
this.canvas.height / 2,
this.options.doughnutHoleSize * this.radius,
0,
2 * Math.PI,
"#000"
);
}
}
添加的代码在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.drawSlices();
结果如下:
我们的饼图和甜甜圈图看起来不错,但我们可以通过添加两件事来使它们变得更好:
通常,与切片关联的值表示为计算为 的百分比值100 * value associated with a slice / total value,整个圆圈表示100%。
例如,在我们的样本数据中,带有古典音乐的黑胶唱片大约代表26%. 如果能够在相应的切片上直接写入该值,那就太好了。为此,我们将使用fillText(text,x,y)绘图上下文的功能。这个函数接受三个参数:文本x和y坐标。
我们如何计算放置文本的坐标x和坐标?y我们必须利用一些几何知识和一种叫做极坐标的东西。基本上,极坐标使用半径和角度来定义点的位置。我们将使用的两个公式是:
x = R * cos(angle)
y = R * sin(angle)
我们将应用这两个公式将文本放置在饼图半径的一半和每个饼片角度的一半。为此,我们需要向drawLabels()我们的Piechart类添加另一个调用方法:
drawLabels() {
var colorIndex = 0;
var startAngle = -Math.PI / 2;
for (var categ in this.options.data) {
var val = this.options.data[categ];
var sliceAngle = (2 * Math.PI * val) / this.totalValue;
var labelX =
this.canvas.width / 2 +
(this.radius / 2) * Math.cos(startAngle + sliceAngle / 2);
var labelY =
this.canvas.height / 2 +
(this.radius / 2) * Math.sin(startAngle + sliceAngle / 2);
if (this.options.doughnutHoleSize) {
var offset = (this.radius * this.options.doughnutHoleSize) / 2;
labelX =
this.canvas.width / 2 +
(offset + this.radius / 2) * Math.cos(startAngle + sliceAngle / 2);
labelY =
this.canvas.height / 2 +
(offset + this.radius / 2) * Math.sin(startAngle + sliceAngle / 2);
}
var labelText = Math.round((100 * val) / this.totalValue);
this.ctx.fillStyle = "black";
this.ctx.font = "32px Khand";
this.ctx.fillText(labelText + "%", labelX, labelY);
startAngle += sliceAngle;
}
}
代码遍历每个切片,计算百分比,计算位置,并使用fillText()方法将其绘制在图表上。我们使用该fillStyle属性将文本颜色设置为黑色,并使用该font属性设置标签的大小和字体系列。还需要注意的是,如果图表是圆环图并且doughnutHoleSize已设置,则标签将被推向图表的边缘,使其以圆环切片为中心。
以下是带有值标签的结果图表的外观:
为了完成我们的图表,我们将添加的最后一件事是图表图例。我们的图表图例将显示我们的数据模型的类别和用于相应切片的颜色。首先,我们必须通过添加将存储我们的图例元素index.html的标签来对我们的文件进行一些修改。<div>
<html>
<body>
<canvas id="myCanvas"></canvas>
<div for="myCanvas"></div>
<script type="text/javascript" src="script.js"></script>
</body>
</html>
然后在script.js中添加创建图例元素内容的代码。我们drawLegend()在类的方法中这样做Piechart:
drawLegend() {
let pIndex = 0;
let legend = document.querySelector("div[for='myCanvas']");
let ul = document.createElement("ul");
legend.Append(ul);
for (let ctg of Object.keys(this.options.data)) {
let li = document.createElement("li");
li.style.listStyle = "none";
li.style.borderLeft =
"20px solid " + this.colors[pIndex % this.colors.length];
li.style.padding = "5px";
li.textContent = ctg;
ul.append(li);
pIndex++;
}
}
代码查找legend通过options参数传递的元素。如果提供了一个,则此元素将填充 HTML 代码,其中包含一个彩色框和数据模型类别的名称。
我们的图表还将有一个标题来描述图表应该向我们展示的内容。我们可以通过添加另一个调用drawTitle()到我们的PieChart类的方法来做到这一点。图表标题的配置选项将从titleOptions传递给PieChart类的对象中获取。
drawTitle() {
this.ctx.save();
this.ctx.textBaseline = "bottom";
this.ctx.textAlign = this.titleOptions.align;
this.ctx.fillStyle = this.titleOptions.fill;
this.ctx.font = `${this.titleOptions.font.weight} ${this.titleOptions.font.size} ${this.titleOptions.font.family}`;
let xPos = this.canvas.width / 2;
if (this.titleOptions.align == "left") {
xPos = 10;
}
if (this.titleOptions.align == "right") {
xPos = this.canvas.width - 10;
}
this.ctx.fillText(this.options.seriesName, xPos, this.canvas.height);
this.ctx.restore();
}
为方便起见,我们可以做的另一件事是定义一个draw()方法,该方法调用该类的所有其他方法来绘制整个图表。
draw() {
this.drawSlices();
this.drawLabels();
this.drawTitle();
this.drawLegend();
}
我们还需要改变我们调用饼图绘制的方式,如下所示:
var myPiechart = new PieChart({
canvas: myCanvas,
seriesName: "Vinyl records",
padding: 40,
doughnutHoleSize: 0.4,
data: {
"Classical Music": 16,
"Alternative Rock": 12,
"Pop": 18,
"Jazz": 32
},
colors: ["#80DEEA", "#FFE082", "#FFAB91", "#CE93D8"],
titleOptions: {
align: "center",
fill: "black",
font: {
weight: "bold",
size: "18px",
family: "Lato"
}
}
});
myPiechart.draw();
我们已经看到,使用 HTML5 画布绘制图表实际上并不难。它只需要一点数学知识和一点 JavaScript 知识。您现在拥有绘制自己的饼图和圆环图所需的一切。