Как рисовать диаграммы с использованием JavaScript и HTML5 Canvas
Russian (Pусский) translation by Masha Kolesnikova (you can also view the original English article)



В этом уроке я покажу вам, как использовать JavaScript и canvas в качестве средства для отображения числовой информации в виде круговых и кольцевых диаграмм.
Есть более простые способы создания диаграмм, чем писать код с нуля, например, эта полная библиотека для графиков от CodeCanyon.



Но если вы хотите знать, что происходит за кулисами в такой библиотеке, то читайте дальше.
Что такое круговая диаграмма?
Диаграмма представляет собой статистический инструмент, используемый для графического представления числовых данных. Круговая диаграмма отображает эти числовые данные в виде круга, разделенного на срезы. Размер каждого среза пропорционален числовому значению, которое он обозначает.
Что такое кольцевая диаграмма?
Проще говоря, кольцевая диаграмма является вариацией круговой диаграммы. Разница в том, что срезы разрезаются по направлению к центру пирога, так что виден только обод. Таким образом, диаграмма выглядит как кольцо и, следовательно, от сюда такое имя.
Начинаем рисование с помощью Canvas
Прежде чем рисовать круговую диаграмму, мы рассмотрим ее детали. Мы увидим, как мы можем использовать компонент canvas и JavaScript чтобы нарисовать:
- Линию
- Дуга (часть круга)
- Цветную форму
Чтобы начать рисование с использованием canvas HTML5, нам нужно создать несколько вещей:
- Одна папка для хранения файлов проекта; Давайте назовем эту папку
piechart-tutorial. - Один HTML-файл
index.htmlвнутри папкиpiechart-tutorial. Этот файл будет содержать HTML-код. - Один JS-файл
script.jsвнутри папкиpiechart-tutorial. Этот файл будет содержать наш код JavaScript.
Мы сохраним все очень просто и добавим следующий код внутри index.html:
1 |
<html>
|
2 |
<body>
|
3 |
<canvas id="myCanvas"></canvas> |
4 |
<script type="text/javascript" src="script.js"></script> |
5 |
</body>
|
6 |
</html>
|
У нас есть элемент <canvas> с идентификатором myCanvas, чтобы мы могли ссылаться на него в нашем JS-коде. Затем мы загружаем JS-код через тег <script>.
Внутри script.js код JS сначала получает ссылку на холст, а затем устанавливает его ширину и высоту. Чтобы рисовать на холсте, нам нужна только ссылка на его 2D-контекст, который содержит все методы рисования.
1 |
var myCanvas = document.getElementById("myCanvas"); |
2 |
myCanvas.width = 300; |
3 |
myCanvas.height = 300; |
4 |
|
5 |
var ctx = myCanvas.getContext("2d"); |
Теперь, когда мы создали canvas, а также ссылку на холст рисунка, давайте определим несколько функций JavaScript, которые мы сможем использовать при рисовании круговой диаграммы. Мы добавим функции в наш файл script.js.
1 |
function drawLine(ctx, startX, startY, endX, endY){ |
2 |
ctx.beginPath(); |
3 |
ctx.moveTo(startX,startY); |
4 |
ctx.lineTo(endX,endY); |
5 |
ctx.stroke(); |
6 |
}
|
Функция drawLine принимает пять параметров:
-
ctx: ссылка на контекст рисунка -
startX: координата X начальной точки линии -
startY: координата Y начальной точки линии
-
endX: координата X конечной точки линии
-
endY: координата Y конечной точки линии
Мы начинаем рисовать линию, вызывая beginPath(). Это сообщает контексту рисования, что мы начинаем рисовать что-то новое на холсте. Мы используем moveTo(), чтобы установить начальную точку, callTo(), чтобы указать конечную точку, а затем выполнить фактический чертеж, вызвав stroke().
Давайте посмотрим, как мы можем нарисовать часть круга, также называемую дугой.
1 |
function drawArc(ctx, centerX, centerY, radius, startAngle, endAngle){ |
2 |
ctx.beginPath(); |
3 |
ctx.arc(centerX, centerY, radius, startAngle, endAngle); |
4 |
ctx.stroke(); |
5 |
}
|
Функция drawArc принимает шесть параметров:
-
ctx: ссылка на контекст чертежа -
centerX: координата X центра окружности -
centerY: координата Y центра окружности -
radius: координата X конечной точки линии -
startAngle: угол начала в радианах, где начинается часть круга -
endAngle: конечный угол в радианах, где заканчивается часть круга
Мы видели, как рисовать линию и как рисовать дугу, поэтому теперь давайте посмотрим, как нарисовать цветную фигуру. Поскольку наша цель состоит в том, чтобы нарисовать круговую диаграмму, состоящую из срезов, давайте создадим функцию, которая рисует кусочек пирога.
1 |
function drawPieSlice(ctx,centerX, centerY, radius, startAngle, endAngle, color ){ |
2 |
ctx.fillStyle = color; |
3 |
ctx.beginPath(); |
4 |
ctx.moveTo(centerX,centerY); |
5 |
ctx.arc(centerX, centerY, radius, startAngle, endAngle); |
6 |
ctx.closePath(); |
7 |
ctx.fill(); |
8 |
}
|
Функция drawPieSlice имеет семь параметров:
-
ctx: ссылка на контекст рисунка -
centerX: координата X центра окружности -
centerY: координата Y центра окружности -
radius: координата X конечной точки линии -
startAngle: угол начала в радианах, где начинается часть круга - EndAngle: конечный угол в радианах, где заканчивается часть круга
-
color: цвет, используемый для заполнения среза
Ниже приведен пример вызова трех функций:
1 |
drawLine(_ctx,100,100,200,200); |
2 |
drawArc(_ctx, 150,150,150, 0, Math.PI/3); |
3 |
drawPieSlice(_ctx, 150,150,150, Math.PI/2, Math.PI/2 + Math.PI/4, '#ff0000'); |
Это даст результат:



Теперь у нас есть все инструменты, необходимые для рисования круговой диаграммы, поэтому давайте посмотрим, как мы будем использовать их все вместе.
Рисование круговой диаграммы
Концептуально любая диаграмма имеет две основные части:
- Модель данных содержит численные данные, которые должны быть представлены. Это структурировано в формате, специфичном для типа диаграммы.
- Графическое представление состоит в том, как числовые данные в модели данных представлены визуальными элементами в соответствии с некоторыми правилами в форме математических формул.
Модель данных круговой диаграммы
Наиболее распространенным способом структурирования модели данных для круговых диаграмм является серия категорий и соответствующих значений, где каждая из категорий и значений связана с кусочком пирога.
Например, модель данных круговой диаграммы, отображающей количество винилов, сгруппированных по жанрам, будет выглядеть примерно так:
- Классическая музыка: 10
- Альтернативный рок: 14
- Поп: 2
- Джаз: 12
Мы можем добавить объект JS к файлу script.js для хранения модели данных следующим образом:
1 |
var myVinyls = { |
2 |
"Classical music": 10, |
3 |
"Alternative rock": 14, |
4 |
"Pop": 2, |
5 |
"Jazz": 12 |
6 |
};
|
Графическое представление круговой диаграммы
Круговая диаграмма использует круг для отображения информации в модели данных, деля ее на срезы. Каждый срез соответствует категории из модели данных, а размер среза пропорционален значению категории.
Моя небольшая коллекция из 38 винилов имеет четыре категории. Каждая категория получит кусочек круговой диаграммы, пропорциональный числу винилов в этой категории.
Но как мы измеряем размер среза? Это легко - мы делаем это под углом на кончике среза. Все, что мы должны знать, это то, что полный круг соответствует углу 360 градусов или 2 * PI. Таким образом, половина круга будет составлять 180 градусов или PI, четверть 90 градусов или PI / 2 и т.д.
Для определения угла для каждого сегмента категории мы используем формулу:
Угол среза = 2 * PI * значение категории / общее значение
Согласно этой формуле, десять виниловых пластинок классической музыки получат угол среза примерно. 0,526 * PI или 94 град.
Давайте перейдем к рисованию. Для этого мы будем использовать класс JavaScript, который мы назовем Piechart. Конструктор получит один аргумент options, объект, содержащий следующее:
- canvas: ссылка на холст, где мы хотим нарисовать круговую диаграмму
- data: ссылка на объект, содержащий модель данных
- colors: массив, содержащий цвета, которые мы хотим использовать для каждого фрагмента
Класс Piechart также содержит один метод draw(), который делает фактический чертеж диаграммы.
1 |
var Piechart = function(options){ |
2 |
this.options = options; |
3 |
this.canvas = options.canvas; |
4 |
this.ctx = this.canvas.getContext("2d"); |
5 |
this.colors = options.colors; |
6 |
|
7 |
this.draw = function(){ |
8 |
var total_value = 0; |
9 |
var color_index = 0; |
10 |
for (var categ in this.options.data){ |
11 |
var val = this.options.data[categ]; |
12 |
total_value += val; |
13 |
}
|
14 |
|
15 |
var start_angle = 0; |
16 |
for (categ in this.options.data){ |
17 |
val = this.options.data[categ]; |
18 |
var slice_angle = 2 * Math.PI * val / total_value; |
19 |
|
20 |
drawPieSlice( |
21 |
this.ctx, |
22 |
this.canvas.width/2, |
23 |
this.canvas.height/2, |
24 |
Math.min(this.canvas.width/2,this.canvas.height/2), |
25 |
start_angle, |
26 |
start_angle+slice_angle, |
27 |
this.colors[color_index%this.colors.length] |
28 |
);
|
29 |
|
30 |
start_angle += slice_angle; |
31 |
color_index++; |
32 |
}
|
33 |
|
34 |
}
|
35 |
}
|
Класс начинается с сохранения options , переданных в качестве параметров. Он сохраняет ссылку на canvas и создает контекст рисования, также сохраняемый как член класса. Затем он сохраняет массив colors, переданный как параметры.
Следующая часть - сама функция draw(). Она отобразит данные из модели данных. Сначала она вычисляет сумму всех значений в модели данных. Затем для каждой категории в модели данных применяем приведенную выше формулу для вычисления угла среза пирога. Наконец, мы используем функцию drawPieSlice(), используя центр холста в качестве центра среза. В качестве радиуса мы используем минимальное значение между половиной ширины холста и половиной высоты холста, так как мы не хотим, чтобы наш пирог выходил из холста.
Мы также компенсируем угол начала и конца срезов каждый раз, когда рисуем категорию, иначе срезы будут перекрываться.
Чтобы использовать класс, мы должны создать экземпляр, а затем вызвать метод draw() у созданного объекта.
1 |
var myPiechart = new Piechart( |
2 |
{
|
3 |
canvas:myCanvas, |
4 |
data:myVinyls, |
5 |
colors:["#fde23e","#f16e23", "#57d9ff","#937e88"] |
6 |
}
|
7 |
);
|
8 |
myPiechart.draw(); |
И результат выглядит так



Рисование кольцевой диаграммы
Мы видели, как рисовать круговую диаграмму. Мы также знаем, что кольцевая диаграмма отличается только наличием отверстия в середине диаграммы. Как нам нарисовать дыру? Мы можем нарисовать белый кружок над круговой диаграммой.
Давайте изменим код класса Piechart, чтобы сделать это.
1 |
var Piechart = function(options){ |
2 |
this.options = options; |
3 |
this.canvas = options.canvas; |
4 |
this.ctx = this.canvas.getContext("2d"); |
5 |
this.colors = options.colors; |
6 |
|
7 |
this.draw = function(){ |
8 |
var total_value = 0; |
9 |
var color_index = 0; |
10 |
for (var categ in this.options.data){ |
11 |
var val = this.options.data[categ]; |
12 |
total_value += val; |
13 |
}
|
14 |
|
15 |
var start_angle = 0; |
16 |
for (categ in this.options.data){ |
17 |
val = this.options.data[categ]; |
18 |
var slice_angle = 2 * Math.PI * val / total_value; |
19 |
|
20 |
drawPieSlice( |
21 |
this.ctx, |
22 |
this.canvas.width/2, |
23 |
this.canvas.height/2, |
24 |
Math.min(this.canvas.width/2,this.canvas.height/2), |
25 |
start_angle, |
26 |
start_angle+slice_angle, |
27 |
this.colors[color_index%this.colors.length] |
28 |
);
|
29 |
|
30 |
start_angle += slice_angle; |
31 |
color_index++; |
32 |
}
|
33 |
|
34 |
//drawing a white circle over the chart
|
35 |
//to create the doughnut chart
|
36 |
if (this.options.doughnutHoleSize){ |
37 |
drawPieSlice( |
38 |
this.ctx, |
39 |
this.canvas.width/2, |
40 |
this.canvas.height/2, |
41 |
this.options.doughnutHoleSize * Math.min(this.canvas.width/2,this.canvas.height/2), |
42 |
0, |
43 |
2 * Math.PI, |
44 |
"#ff0000" |
45 |
);
|
46 |
}
|
47 |
|
48 |
}
|
49 |
}
|
Добавленный код просматривает параметр options для переменной-члена doughnutHoleSize. Если его не существует в options, тогда код будет рисовать круговую диаграмму, как и раньше, но если существует, тогда белый круг рисуется с тем же центром, что и круговая диаграмма.
Радиус круга определяется путем умножения радиуса круговой диаграммы и значения параметра doughnutHoleSize. Это должно быть число от 0 до 1, где 0 приведет к круговой диаграмме, и любые значения, превышающие 0, приведут к тому, что кольцо с отверстием будет больше и больше, 1 сделает диаграмму невидимой.
Чтобы нарисовать кольцевую диаграмму с отверстием на половину размера диаграммы, нам нужно будет использовать значение doughnutHoleSize 0.5 и сделать следующие вызовы:
1 |
var myDougnutChart = new Piechart( |
2 |
{
|
3 |
canvas:myCanvas, |
4 |
data:myVinyls, |
5 |
colors:["#fde23e","#f16e23", "#57d9ff","#937e88"], |
6 |
doughnutHoleSize:0.5 |
7 |
}
|
8 |
);
|
9 |
myDougnutChart.draw(); |
И вот результат:



Добавление меток и легенды диаграммы
Наша круговая и кольцевая диаграммы выглядят довольно хорошо, но мы можем сделать их еще лучше, добавив две вещи:
- Метки значений: отображение процента, соответствующего каждому фрагменту
- Легенда диаграммы: отображение категорий и соответствующих цветов на диаграмме
Обычно значения, связанные со срезами, представляются в виде процентных значений, рассчитанных как 100 * значение, связанное со значением среза / общего значения, при этом весь круг составляет 100%.
Например, в случае наших выборочных данных винилы с классической музыкой будут составлять примерно 26%. Было бы неплохо написать это значение прямо на соответствующем фрагменте. Для этого мы будем использовать функцию fillText(text, x, y) контекста рисования. Эта функция принимает три параметра: текст и координаты x и y.
Как мы вычисляем координаты x и y для размещения текста? Мы должны использовать некоторые знания геометрии и что-то, что называется полярными координатами. В основном полярные координаты используют радиус и угол для определения положения точки. Мы будем использовать две формулы:
x = R * cos(angle)
y = R * sin(angle)
Мы применим эти две формулы, чтобы поместить текст наполовину вдоль радиуса круговой диаграммы и на полпути вокруг угла для каждого кусочка пирога. Для этого нам нужно изменить наш класс Piechart и добавить следующий код сразу после блока if (this.options.doughnutHoleSize) {...}:
1 |
...
|
2 |
start_angle = 0; |
3 |
for (categ in this.options.data){ |
4 |
val = this.options.data[categ]; |
5 |
slice_angle = 2 * Math.PI * val / total_value; |
6 |
var pieRadius = Math.min(this.canvas.width/2,this.canvas.height/2); |
7 |
var labelX = this.canvas.width/2 + (pieRadius / 2) * Math.cos(start_angle + slice_angle/2); |
8 |
var labelY = this.canvas.height/2 + (pieRadius / 2) * Math.sin(start_angle + slice_angle/2); |
9 |
|
10 |
if (this.options.doughnutHoleSize){ |
11 |
var offset = (pieRadius * this.options.doughnutHoleSize ) / 2; |
12 |
labelX = this.canvas.width/2 + (offset + pieRadius / 2) * Math.cos(start_angle + slice_angle/2); |
13 |
labelY = this.canvas.height/2 + (offset + pieRadius / 2) * Math.sin(start_angle + slice_angle/2); |
14 |
}
|
15 |
|
16 |
var labelText = Math.round(100 * val / total_value); |
17 |
this.ctx.fillStyle = "white"; |
18 |
this.ctx.font = "bold 20px Arial"; |
19 |
this.ctx.fillText(labelText+"%", labelX,labelY); |
20 |
start_angle += slice_angle; |
21 |
}
|
22 |
...
|
Код перебирает каждый фрагмент, вычисляет процент, вычисляет позицию и использует метод fillText(), чтобы нарисовать его на диаграмме. Мы использовали свойство fillStyle, чтобы установить цвет текста в белый цвет и свойство font, чтобы установить размер, стиль и семейство шрифтов метки. Также важно отметить, что если диаграмма представляет собой кольцевую диаграмму, а значение параметра doughnutHoleSize установлено, тогда метка будет сдвигаться к краю диаграммы, чтобы сделать ее центрированной на куске пончика.
И вот как выглядят полученные диаграммы с помощью меток значений:



Чтобы завершить наш график, последнее, что мы добавим, это легенда диаграммы. В нашей легенде диаграммы будут отображаться категории нашей модели данных и цвет, используемый для соответствующего фрагмента. Сначала мы должны внести некоторые изменения в наш файл index.html, добавив тег <div>, который сохранит наш элемент легенды.
1 |
<html>
|
2 |
<body>
|
3 |
<canvas id="myCanvas"></canvas> |
4 |
<div id="myLegend"></div> |
5 |
<script type="text/javascript" src="script.js"></script> |
6 |
</body>
|
7 |
</html>
|
Затем в script.js мы добавляем код, который создает содержимое элемента легенды. Мы добавляем этот код в конце функции draw() класса Piechart:
1 |
...
|
2 |
if (this.options.legend){ |
3 |
color_index = 0; |
4 |
var legendHTML = ""; |
5 |
for (categ in this.options.data){ |
6 |
legendHTML += "<div><span style='display:inline-block;width:20px;background-color:"+this.colors[color_index++]+";'> </span> "+categ+"</div>"; |
7 |
}
|
8 |
this.options.legend.innerHTML = legendHTML; |
9 |
}
|
10 |
...
|
Код ищет элемент legend, переданный через параметр options. Если один из них указан, этот элемент заполняется кодом HTML, содержащим цветное поле и название категории модели данных.
Нам также необходимо внести изменения в то, как мы называем рисунок нашей круговой диаграммы следующим образом:
1 |
var myLegend = document.getElementById("myLegend"); |
2 |
|
3 |
var myDougnutChart = new Piechart( |
4 |
{
|
5 |
canvas:myCanvas, |
6 |
data:myVinyls, |
7 |
colors:["#fde23e","#f16e23", "#57d9ff","#937e88"], |
8 |
legend:myLegend |
9 |
}
|
10 |
);
|
11 |
myDougnutChart.draw(); |
И вот результирующая диаграмма и легенда диаграммы:



Поздравления
Мы увидели, что рисование диаграмм с использованием холста HTML5 на самом деле не так сложно. Для этого требуется только немного математики и немного знаний JavaScript. У вас теперь есть все, что вам нужно для рисования собственных круговых и кольцевых диаграмм и диаграмм.
Если вам нужно быстрое и простое решение для создания не только круговых и кольцевых диаграмм, но и других типов диаграмм, вы можете загрузить Infographic Charts and Graphics HTML Tags Library или их плагинWordPress Charts and Graphs WordPress Visual Designer.



