Визуализация данных с помощью HTML5 Canvas и SVG
(Обзорная статья по следам конференции по разработке ПО в Екатеринбурге и другим выступлениям. Видео-версию доклада в Екатеринбурге см. на techdays.ru )
Что такое HTML5 Canvas и SVG?
HTML5 Canvas
<canvas> – элемент представляет собой холст для отрисовки растровой графики. Фактически, это пустой блок заданных размеров, на котором можно рисовать с помощью специальных API для JavaScript.
API включает в себя 45 специальных методов и 21 атрибут, используемые для отображения графических примитивов, задания стилей, трансформаций, доступа к отдельным пикселям, проецирования изображений и видео.
Сам <canvas> элемент определен непосредственно в спецификации HTML5. API для него описывается отдельным документом -- HTML Canvas 2D Context.
SVG
SVG = Scalable Vector Graphics. Отдельный стандарт для векторного описания изображений, базирующийся на XML, имеющий свою DOM (Document Object Model) для работы через JavaScript.
В настоящий момент актуальная версия стандарта – SVG 1.1 проходит процесс обновления до второй редакции и буквально совсем недавно эта спецификация достигла статуса Proposed Recommendation.
В рамках HTML5 определен специальный тег <svg> в дополнение к вставке в виде изображений и объектов:
<img src="elvis.svg" … />
<object data="elvis.svg" type="image/svg+xml" … />
позволяющий делать inline-вставку SVG-контента непосредственно в тело документа (и в этом виде с ним можно работать в том же контексте JavaScript, что и с другими элементами документа).
Примеры
Несколько примеров того, что позволяют сделать Canvas и SVG:
Endless Mural
То, что называется, generative art – динамичная генерация графических изображений (https://www.endlessmural.com/).
Music Can Be Fun
Красивая музыкально-графическая игра-визуализация (https://musiccanbefun.edankwan.com/).
Примеры схем на SVG
Схема человеческого скелета, переодических система химических элементов и респираторная система (https://ie.microsoft.com/testdrive/Graphics/RealWorldDataAndDiagrams/Default.xhtml).
Карты Яндекс
Более близкий пример из реальной жизни – при отрисовке маршрутов используется SVG (если браузер поддерживает). См. также доклад “Карты и SVG” с нашего HTML5 Camp.
Еще примеры:
- Beauty of the Web https://www.beautyoftheweb.com/ – реальные сайты из реального мира
- Dev: unplugged https://contest.beautyoftheweb.com/ – проекты-участники соревнования приложений на HTML5
Разница между Canvas и SVG
В различных сценариях для динамичной отрисовки графики может лучше подходить или Canvas или SVG – к этому вопросу мы еще вернемся в конце. А пока остановимся на ключевых различиях между одним и другим:
Canvas | SVG | |
Формат | Растровый | Векторный |
Масштабирование | Zoom |
Scale |
Доступ | Доступ к отдельным пикселям (RGBA) |
Доступ к отдельным элементам (DOM) |
Индексируемость и Accessibility | Виден только конечный растр (нельзя выделить фигуры, текст и т.п.) — плохо для Accessibility |
Можно посмотреть структуру (например, вытащить весь текст) |
Стилизация | Визуальные стили задаются при отрисовке через API |
Визуальные стили задаются атрибутами, можно подключать CSS |
Программирование | JS API для работы с примитивами |
DOM для работы с элементами |
Обновление | Для обновления — рисование поверх или полная перерисовка |
Возможно изменение отдельных элементов |
События | Нет легкого способа для обработки событий мыши. Объекты под курсором надо определять вручную. |
Легко вешаются события от мыши через DOM, обрабатываются автоматически. |
Интеграция кода | Код на JS отдельно от Canvas |
Внутрь можно включать JS |
Эти различия необходимо учитывать при использовании той или иной технологии для визуализации данных. Например, отрисовка графика функции может быть легче с помощью Canvas, в то же время вывод подсказок (с определением объекта под указателем мыши), проще сделать с помощью SVG.
На практике, правда, уже есть ряд готовых библиотек для визуализации данных, которые частично нивелируют эти различия.
---
Я не буду вдаваться в основы работы с каждой из технологий, в качестве вводной рекомендую доклад Вадима Макеева (Opera) с HTML5 Camp “Динамическая графика: Canvas и SVG”.
См. также доклады MIX 2011:
---
Обработка изображений с помощью Canvas
Одна из примечательных особенностей Canvas заключается в том, что эта технология обеспечивает попиксельный доступ к отображаемым данным и позволяет проектировать на холст различные графические элементы, включая видео.
Хорошим примером того, где это нужно, являюется задача обработки/анализа изображений.
Mihai Şucan в своей статье “SVG or Canvas? Choosing between the two” приводит как раз интересный пример построения гистограммы изображения с помощью Canvas.
Анализ видео-изображения
Давайте посмотрим, как с помощью Canvas можно заниматься обработкой видео. Начнем с простой задачи проектирования видео-контента на canvas-элемент.
<div id="canvasholder">
<canvas width="320" height="200" class="thumb" id="myCanvas">
</div>
<video id="myVideo" width="320" height="200" src="video/gizmo.mp4" controls>
Для решения этой задачи в Canvas есть специальный метод drawImage:
var video = document.getElementById("myVideo");
var ctx = document.getElementById("myCanvas").getContext("2d");
setInterval(function() {
ctx.drawImage(video, 0,0, 560, 320, 0, 0, ctx.canvas.width, ctx.canvas.height);
}, 67);
Если запустить проигрывание видео, изображение внутри canvas-элемента будет обновляться синхронно с обновлением видео:
(Стоит отметить, что это не самый лучший способ отслеживания изменения позиции внутри видео-элемента, иногда больше подходит событие timeupdate.)
Теперь, из canvas-элемента можно получить доступ к отдельным пикселям видео-изображения. Для этого мы проектирование видео перенесем на промежуточный canvas, хранящийся в памяти. Чтобы получить доступ к пикселям и записывать пиксели в canvas есть соответствующие методы getImageData и putImageData.
var video = document.getElementById("myVideo");
var ctx = document.getElementById("myCanvas").getContext("2d");
var canvasTemp = document.createElement("canvas");
canvasTemp.width = 320;
canvasTemp.height = 200;
var ctxTemp = canvasTemp.getContext("2d");
setInterval(function() {
ctxTemp.drawImage(video, 0,0, 560, 320, 0, 0, ctx.canvas.width, ctx.canvas.height);
var pixels = ctxTemp.getImageData(0,0, ctx.canvas.width, ctx.canvas.height);
for (var i=0, n = pixels.data.length; i < n; i+= 4) {
pixels.data[i+0] = 255 - pixels.data[i+0];
pixels.data[i+1] = 255 - pixels.data[i+1];
pixels.data[i+2] = 255 - pixels.data[i+2];
}
ctx.putImageData(pixels, 0, 0);
}, 67);
В качестве примера обработки видео-изображения в данном случае делается инверсия цвета пикселей по каждому из цветовых каналов.
С готовым примером можно поиграться здесь https://silverbook.ru/projects/html5datavisualization/demo2-image-processing.htm (код выполнить можно из консоли – F12 в Internet Explorer).
Библиотеки для визуализации с помощью Canvas
Переходим непосредственно к визуализации данных. С одной из библиотек для работы с Canvas (EaselJS) мы уже ранее разбирались, делая открытку к 8 марта. Если ваша (динамическая) визуализация предполагает работу со спрайтами, рекомендую обратить на нее внимание.
Здесь же мы остановимся на трех библиотеках для визуализации, представляющих собой различные подходы и направления для визуализации. Это, безусловно, не исчерпывающий список и есть много других библиотек со схожим и отличным функционалом.
ProcessingJS
Processing.js является портом на JS знаменитой библиотеки для визуализации данных Processing. Примеры работы можно найти тут https://processingjs.org/exhibition.
Processing.js предлагает два подхода к описанию визуализации: промежуточный код, в дальнейшем разбираемый самой библиотекой (отдельным файлом или внутри страницы) и явный код на JavaScript.
Например, чтобы нарисовать фрактал множество Мандельброта, можно использовать как вариант, указанный на странице с соответствующим примером, так и такой код на JavaScript:
var xmin = -2.5;
var ymin = -2;
var wh = 4;
function sketchProc(processing) {
processing.setup = function() {
processing.size(200, 200);
processing.noLoop();
};
processing.draw = function () {
processing.loadPixels();
var maxiterations = 200;
var xmax = xmin + wh;
var ymax = ymin + wh;
var dx = (xmax - xmin) / (processing.width);
var dy = (ymax - ymin) / (processing.height);
var y = ymin;
for(var j = 0; j < processing.height; j++) {
var x = xmin;
for(var i = 0; i < processing.width; i++) {
var a = x;
var b = y;
var n = 0;
while (n < maxiterations) {
var aa = a * a;
var bb = b * b;
var twoab = 2.0 * a * b;
a = aa - bb + x;
b = twoab + y;
if(aa + bb > 16.0) {
break;
}
n++;
}
if (n == maxiterations)
processing.pixels.setPixel(i+j*processing.width, 0);
else
processing.pixels.setPixel(i+j*processing.width, processing.color(n*16 % 255));
x += dx;
}
y += dy;
}
processing.updatePixels();
};
}
var canvas = document.getElementById("myCanvas");
var p = new Processing(canvas, sketchProc);
Попробовать самостоятельно можно здесь: https://silverbook.ru/projects/html5datavisualization/demo3-processingjs.htm (копируем код, вставляем в консоль и выполняем).
JavaScript InfoVis Toolkit (JIT)
JIT – библиотека для визуализации данных, разрабатываемая в Sencha. Некоторые примеры визуализации можно посмотреть на сайте библиотеки в разделе Demos.
Для отображения данных JIT принимает исходные значения в виде JSON:
var json = {
'label': ['label A', 'label B', 'label C', 'label D'],
'values': [
{
'label': 'date A',
'values': [20, 40, 15, 5]
},
{
'label': 'date B',
'values': [30, 10, 45, 10]
},
{
'label': 'date E',
'values': [38, 20, 35, 17]
},
{
'label': 'date F',
'values': [58, 10, 35, 32]
},
{
'label': 'date D',
'values': [55, 60, 34, 38]
},
{
'label': 'date C',
'values': [26, 40, 25, 40]
}]
};
Далее после небольшой настройки диаграммы (обратите внимание на подсказки – они выводятся поверх визуализации обычными html-блоками, с учетом стилизации в CSS):
var pieChart = new $jit.PieChart({
injectInto: 'infovis',
animate: true,
offset: 30,
sliceOffset: 0,
labelOffset: 20,
type: 'stacked:gradient',
showLabels:true,
resizeLabels: 7,
Label: {
type: 'Native',
size: 20,
family: 'Arial',
color: 'white'
},
Tips: {
enable: true,
onShow: function(tip, elem) {
tip.innerHTML = "<b>" + elem.name + "</b>: " + elem.value;
}
}
});
достаточно вызвать отрисовку:
pieChart.loadJSON(json);
Тестовый пример можно найти тут https://silverbook.ru/projects/html5datavisualization/demo3-jit.htm.
jQuery Sparklines
jQuery Sparklines – еще одна интересная библиотека, позволяющая делать мини-визуализации массивов данных, похожих на функционал Sparklines в Excel. Библиотека использует в своей работе jQuery.
Для визуализации достаточно вызвать соответствующую функцию:
$('.inlinesparkline').sparkline();
var myvalues = [10,8,5,7,4,4,1];
$('.dynamicsparkline').sparkline(myvalues);
$('.dynamicbar').sparkline(myvalues, {type: 'bar', barColor: 'green'} );
$('.inlinebar').sparkline('html', {type: 'bar', barColor: 'red'} );
В первом и последнем случаях данные указаны в коде страницы, во втором и третьем передаются при вызове функции. Попробовать самостоятельно можно тут https://silverbook.ru/projects/html5datavisualization/demo3-sparklines.htm
Визуализация на карте с помощью SVG
Переходим к SVG и начнем с простого примера. Представьте себе, что вам нужно отобразить какие-то данные на карте регионов, как это сделать проще всего?
Если у вас есть готовая карта в виде SVG (я взял карту России с сайта Википедии), то это делается очень просто – достаточно, чтобы внутри SVG-документа у каждого региона был свой уникальный id, далее вставляем карту как inline svg и простым кодом раскрашиваем в нужный цвет:
var SverdlovskOblast = document.getElementById("SverdlovskOblast");
SverdlovskOblast.style.fill = "#fe3300";
Если сделать все то же самое в цикле, то уже можно раскрасить не просто область, но и целый регион или даже всю страну:
var data = [{id: "KurganOblast", value: 30},
{id: "SverdlovskOblast", value: 200},
{id: "TyumenOblast", value: 75},
{id: "KhantiaMansia", value: 100},
{id: "YamaloNenetsAutDistrict", value: 20},
{id: "ChelyabinskOblast", value: 150}];
for (var i = 0; i< data.length; i++) {
var item = data[i];
var region = document.getElementById(item.id);
region.style.fill = RGBtoHex(item.value, 0, 0);
}
Готовый пример можной найти тут https://silverbook.ru/projects/html5datavisualization/demo6-visualization.htm.
Библиотеки для визуализации данных с помощью SVG
Как я уже говорил, для решения традиционной задачи визуализации числовых данных в виде графиков и диаграмм подходят как Canvas, так и SVG. В обоих случаях это достаточно легко делается с помощью соответствующих библиотек.
Примеры с Canvas мы уже посмотрели, давайте теперь посмотрим на несколько библиотек для работы с SVG. (Это также не исчерпывающий список, но довольно качественные и популярные решения.)
Raphaël
Raphaël – известная библиотека для работы с SVG, написанная Дмитрием Барановским. Помимо того, что она просто делает жизнь легче, она интересная также тем, что в старых версиях IE реализует свой функционал с помощью VML.
Оставляя за рамками данной статьи вопросы работы с самой библиотекой (в качестве интересного десерта, вот здесь https://raphaeljs.com/icons/ можно найти библиотеку векторных иконок для Raphaël), перейдем к расширению для работы с диаграммами и графиками -- https://g.raphaeljs.com/.
Чтобы добавить простую круговую диаграмму достаточно такого кода:
var r = Raphael("chart", 640, 480);
var pie = r.g.piechart(320, 240, 100, [55, 20, 13, 32, 5, 1, 2, 10]);
Несколькими дополнительными операциями можно добавить легенду, подписи к диаграмме и интерактивные подсказки:
var r = Raphael("chart", 640, 480);
r.g.txtattr.font = "12px 'Fontin Sans', Fontin-Sans, sans-serif";
r.g.text(320, 100, "Interactive Pie Chart").attr({"font-size": 20});
var pie = r.g.piechart(320, 240, 100, [55, 20, 13, 32, 5, 1, 2, 10],
{legend: ["%%.%% – Enterprise Users", "IE Users"], legendpos: "west",
href: ["https://raphaeljs.com", https://g.raphaeljs.com]});
pie.hover(function () {
this.sector.stop();
this.sector.scale(1.1, 1.1, this.cx, this.cy);
if (this.label) {
this.label[0].stop();
this.label[0].scale(1.5);
this.label[1].attr({"font-weight": 800});
}
}, function () {
this.sector.animate({scale: [1, 1, this.cx, this.cy]}, 500, "bounce");
if (this.label) {
this.label[0].animate({scale: 1}, 500, "bounce");
this.label[1].attr({"font-weight": 400});
}
});
Аналогичным образом можно выводить и другие типы диаграмм, используя соответствующие методы. См. примеры непосредственно на сайте расширения https://g.raphaeljs.com/
Highcharts JS
Наконец, еще одна интересная библиотека с богатым функционалом – Highcharts JS. Большое колиечество примеров можно найти в галерее на сайте.
API библиотеки позволяет достаточно легко сгенерировать диаграмму по данным в JSON:
var chart1 = new Highcharts.Chart({
chart: {
renderTo: 'charts', defaultSeriesType: 'bar'
},
title: {
text: 'Fruit Consumption'
},
xAxis: {
categories: ['Apples', 'Bananas', 'Oranges']
},
yAxis: {
title: { text: 'Fruit eaten' }
},
series: [{ name: 'Jane', data: [1, 0, 4] },
{ name: 'John', data: [5, 7, 3]}]
});
Немного более сложным скриптом можно указать дополнительные детали, например, вывести легенду, настроить подсказки:
var chart = new Highcharts.Chart({
chart: {
renderTo: 'charts',
defaultSeriesType: 'area',
spacingBottom: 30
},
title: {
text: 'Fruit consumption *'
},
subtitle: {
text: '* Jane\'s banana consumption is unknown',
floating: true,
align: 'right',
verticalAlign: 'bottom',
y: 15
},
legend: {
layout: 'vertical',
align: 'left',
verticalAlign: 'top',
x: 150,
y: 100,
floating: true,
borderWidth: 1,
backgroundColor: '#FFFFFF'
},
xAxis: {
categories: ['Apples', 'Pears', 'Oranges', 'Bananas', 'Grapes', 'Plums', 'Strawberries', 'Raspberries']
},
yAxis: {
title: {
text: 'Y-Axis'
},
labels: {
formatter: function() {
return this.value;
}
}
},
tooltip: {
formatter: function() {
return '<b>'+ this.series.name +'</b><br/>'+
this.x +': '+ this.y;
}
},
plotOptions: {
area: {
fillOpacity: 0.5
}
},
series: [{
name: 'John',
data: [0, 1, 4, 4, 5, 2, 3, 7]
}, {
name: 'Jane',
data: [1, 0, 3, null, 3, 1, 2, 1]
}]
});
При необходимости можно заменить стили по умолчанию на свои собственные.
Что выбрать: Canvas или SVG?
Как видно из примеров выше, для задач визуализации данных зачастую подходит и та, и другая технология. Многие вещи делаются схожим образом. В случаях, где нужен попиксельный вывод, очевидно, лучше подходит Canvas. Там, где диаграмма бьется на отдельные объекты, в которых нужно поддерживать интерактивность, лучше подходит SVG.
Лучше подходит Canvas
- Редактирование растровой графики
- Наложение эффектов на графику/видео
- Генерирование растровой графики (визуализация данных, фракталы, графики функций)
- Анализ изображенией
- Игровая графика (спрайты, фон и т.п.)
Лучше подходит SVG
- Масштабируемые интерфейсы
- Интерактивные интерфейсы
- Диаграммы, схемы
- Векторное редактирование изображений
В графической форме это можно представить так:
Наконец, еще один важный срез, который также важно учитывать в выборе технологии – производительность отрисовки при использовании Canvas и SVG:
На практике Сanvas лучше работает при небольших размерах области отрисовки и на большом числе объектов, в SVG лучше подходит при необходимости масштабирования или вывода на большой экран и на не слишком большом количестве выводимых за раз объектов.
Дополнительные материалы и инструменты
Cheat Sheets:
- HTML5 Canvas Cheat Sheet — https://blog.nihilogic.dk/2009/02/html5-canvas-cheat-sheet.html
- SVG Visual Cheat Sheet — https://www.cheat-sheets.org/own/svg/index.xhtml
Инструменты:
- Microsoft Visio
- Export as SVG
- Adobe Illustrator
- Export as SVG
- ai2Canvas (http://visitmix.com/labs/ai2canvas)
- Inkscape — www.inkscape.org