如何使用JavaScript、HTML Canvas和CSS创建饼状图

138 阅读5分钟

使用JavaScript、HTML Canvas和CSS创建饼状图

有时候,你可能想在没有任何库的情况下创建一个图表。本教程将带你了解如何使用JavaScript、HTML Canvas和CSS来实现这一目的。

前提条件

要想继续学习,你需要对这些编程语言有一个基本的了解。

  1. HTML
  2. CSS
  3. JavaScript

画布的概述

使用所提供的CSS样式为DOM中的项目定位、赋予自定义形状和颜色是很容易的。但是,有些操作,比如在两个或多个位置之间画线,特别难以用标准的HTML组件完成。也就是说,还提供了另外两种选择。

  1. 可扩展矢量图形(SVG)
  2. 画布

顾名思义,SVG是用来创建XML格式的矢量图形的。本文的重点是画布,所以我们不会进一步讨论SVG。

第二个选择是画布。画布是一个单一的DOM元素,它为我们提供了一种使用其提供的绘图接口和方法在节点的空间上绘制形状的方法。你可以声明一个简单的画布元素,如下面这行所示。

<canvas width="200" height="200"></canvas>

它的大小是以像素为单位确定的。因此,你可以设置它的尺寸(高度和宽度)来满足你的需要。要开始操作画布,我们需要访问它的绘图方法。这些方法可以在它的绘图接口中找到,我们通过创建一个上下文对象来访问它。这个对象包含了我们将用来绘制形状、设置颜色、创建旋转、平移等的所有方法。

该对象提供了绘图样式:2d 用于二维(2D)图形,webgl 用于三维(3D)图形。我们把我们要在上下文的getContext() 方法中使用的样式作为一个参数传递,即

let context = canvas.getContext("2d");

我们将在我们的文章中使用2d 。关于详细的webgl 指南,请点击此链接

我们将在文章中使用的两个主要的画布2D方法是。

  1. arc() - 用于绘制饼状图的弧线。
  2. lineTo() - 用于在饼图的各片之间绘制分隔线。

最后,我们将使用JavaScript的数组reduce() 方法。这在我们处理数据以在图表中显示时将会很方便。

弧线()

在这个片段中显示了一个简单的例子来演示弧线的创建。

<!DOCTYPE html>
<html>
<head>
    <title>canvas test</title>
    <style type="text/css">
                .container {
          width: 100%;
          height: 100vh;
          display: flex;
          justify-content: center;
          align-items: center;
        }
    </style>
</head>
<body>
<div class="container">
<canvas width="200" height="200"></canvas>
</div>
</body>
<script>
    const canvas = document.querySelector('canvas');
    const ctx = canvas.getContext('2d');
    ctx.beginPath();
    ctx.arc(50,50, 50, Math.PI/2,  Math.PI);
    ctx.stroke();
</script>
</html>

这就产生了。

pie-one

这是arc() 方法的语法。

void ctx.arc(center-x-coordinate,center-y-coordinate, arc-radius-length, startAngle, endAngle [, counterclockwise]);

我们传入中心的xy 坐标,半径的长度,以及弧线的起点和终点的角度。然后,我们有一个可选的布尔值来表示我们的弧线在两个角度之间的方向。默认情况下是逆时针 (true)。如果false ,它将以顺时针的方式绘制。

注意:我们使用弧度方法来表示我们的角度,其中Math.PI(π)180°。它从0°开始,在我们的例子中,以逆时针的方式,这就是为什么我们的90°(Math.PI(π)/2) 位于底部。

我们还对它进行了描边,使其具有黑线。在我们的饼图中,我们将使用fill() 方法。我们将在后面看一下。

lineTo()

这个方法是用来画一条直线的。它与beginPath()moveTo() 一起用于创建一条直线。beginPath() 用于开始一个新的路径,而moveTo() 创建一个点(一个 "从 "坐标),该点将由lineTo() 方法连接。让我们通过一个例子来看看。

ctx.beginPath();
ctx.moveTo(50,0);   
ctx.lineTo(200, 0);
ctx.stroke();

我们从(50,0) 开始,然后在(200,0) 结束。

输出结果如下所示。

line

Array.prototype.reduce()

这个方法对传给它的数组的每个元素依次执行一个用户定义的回调函数。它返回一个单一的值。让我们用一个乘法的例子来看看。

const testArray = [5, 5, 9, 1];
//multiplying our previous element  with the current element
const ourCallback = (prevElem, currElem) => prevElem * currElem;
console.log(testArray.reduce(ourCallback));

结果将是225

至于reduce() 函数与此有关,我们将只使用该功能。

绘制馅饼

这是我们这篇文章的基础所在。我们将首先创建一个results 对象,在那里我们将创建我们的馅饼。

const results = [
        {mood: "Angry", total: 1499, shade: "#0a9627"},
        {mood: "Happy", total: 478, shade: "#960A2C"},
        {mood: "Melancholic", total:332, shade: "#332E2E"},
        {mood: "Gloomy", total: 195, shade: "#F73809"}
    ];

它包含了人们不同的情绪,他们的总人数,以及代表情绪的颜色,按降序排列。(让我们假设它来自一个调查的API😃)。

为了得到参加调查的总人数,我们使用reduce() ,如下图所示。

let sum = 0;
let totalNumberOfPeople = results.reduce((sum, {total}) => sum + total, 0);

接下来,我们画饼。

    let currentAngle = 0;

    for (let moodValue of results) {
        //calculating the angle the slice (portion) will take in the chart
        let portionAngle = (moodValue.total / totalNumberOfPeople) * 2 * Math.PI;
        //drawing an arc and a line to the center to differentiate the slice from the rest
        ctx.beginPath();
        ctx.arc(100, 100, 100, currentAngle, currentAngle + portionAngle);
        currentAngle += portionAngle;
        ctx.lineTo(100, 100);
        //filling the slices with the corresponding mood's color
        ctx.fillStyle = moodValue.shade;
        ctx.fill();
    }

我们有一个for/of 循环,我们先用这个公式计算出切片(部分)在图表中的角度。

(total number of people containing a mood / total number of people) * 360°

我们从0°到360°,以逆时针的方式开始。

然后我们画一条弧线和一条直线到中心,以区别于其他的片断。我们将当前角度设置为该部分所消耗的角度。这将被用来设置下一个片断的开始位置。

对于颜色,我们使用fill() 方法将该部分与相应的情绪的颜色进行样式化。这里,我们不使用stroke() 方法。

最终的输出如下所示。

pie

下面是完整的代码。

<!DOCTYPE html>
<html>
<head>
    <title>canvas test</title>
    <style type="text/css">
                .container {
          width: 100%;
          height: 100vh;
          display: flex;
          justify-content: center;
          align-items: center;
        }
    </style>
</head>
<body>
<div class="container">
<canvas width="200" height="200"></canvas>
</div>
</body>
<script>
    let ctx = document.querySelector("canvas").getContext("2d");

    const results = [
        {mood: "Angry", total: 1499, shade: "#0a9627"},
        {mood: "Happy", total: 478, shade: "#960A2C"},
        {mood: "Melancholic", total:332, shade: "#332E2E"},
        {mood: "Gloomy", total: 195, shade: "#F73809"}
    ];

    let sum = 0;
    let totalNumberOfPeople = results.reduce((sum, {total}) => sum + total, 0);
    let currentAngle = 0;

    for (let moodValue of results) {
        //calculating the angle the slice (portion) will take in the chart
        let portionAngle = (moodValue.total / totalNumberOfPeople) * 2 * Math.PI;
        //drawing an arc and a line to the center to differentiate the slice from the rest
        ctx.beginPath();
        ctx.arc(100, 100, 100, currentAngle, currentAngle + portionAngle);
        currentAngle += portionAngle;
        ctx.lineTo(100, 100);
        //filling the slices with the corresponding mood's color
        ctx.fillStyle = moodValue.shade;
        ctx.fill();
    }

</script>
</html>

在此基础上,你可以使用画布创建线图、柱状图等。你必须玩旋转,因为画布是从左上角开始画的,而不是从左下角开始。你还可以在切片中添加文本。

总结

简而言之,我们向您介绍了画布的概况,查看了我们将使用的画布方法和 JavaScript 的reduce() 方法。最后,我们用一个简单的JavaScript代码创建了饼图。