Визуализация данных с помощью HTML5 Canvas и SVG

(Обзорная статья по следам конференции по разработке ПО в Екатеринбурге и другим выступлениям. Видео-версию доклада в Екатеринбурге см. на techdays.ru )

Что такое HTML5 Canvas и SVG?

HTML5 Canvas

<canvas> – элемент представляет собой холст для отрисовки растровой графики. Фактически, это пустой блок заданных размеров, на котором можно рисовать с помощью специальных API для JavaScript.

image

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

image

То, что называется, generative art – динамичная генерация графических изображений (https://www.endlessmural.com/).

Music Can Be Fun

image

Красивая музыкально-графическая игра-визуализация (https://musiccanbefun.edankwan.com/).

Примеры схем на SVG

image

Схема человеческого скелета, переодических система химических элементов и респираторная система (https://ie.microsoft.com/testdrive/Graphics/RealWorldDataAndDiagrams/Default.xhtml).

Карты Яндекс

image

Более близкий пример из реальной жизни – при отрисовке маршрутов используется SVG (если браузер поддерживает). См. также доклад “Карты и SVG” с нашего HTML5 Camp.

Еще примеры:

 

Разница между 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 заключается в том, что эта технология обеспечивает попиксельный доступ к отображаемым данным и позволяет проектировать на холст различные графические элементы, включая видео.

Хорошим примером того, где это нужно, являюется задача обработки/анализа изображений.

image

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-элемента будет обновляться синхронно с обновлением видео:

image

(Стоит отметить, что это не самый лучший способ отслеживания изменения позиции внутри видео-элемента, иногда больше подходит событие 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);

В качестве примера обработки видео-изображения в данном случае делается инверсия цвета пикселей по каждому из цветовых каналов.

image

С готовым примером можно поиграться здесь 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.

image

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.

image

Для отображения данных 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);  

image

Тестовый пример можно найти тут https://silverbook.ru/projects/html5datavisualization/demo3-jit.htm.

jQuery Sparklines

jQuery Sparklines – еще одна интересная библиотека, позволяющая делать мини-визуализации массивов данных, похожих на функционал Sparklines в Excel. Библиотека использует в своей работе jQuery.

image

Для визуализации достаточно вызвать соответствующую функцию:

 $('.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 и начнем с простого примера. Представьте себе, что вам нужно отобразить какие-то данные на карте регионов, как это сделать проще всего?

image

Если у вас есть готовая карта в виде 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);
}

image

Готовый пример можной найти тут 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]);

image

Несколькими дополнительными операциями можно добавить легенду, подписи к диаграмме и интерактивные подсказки:

 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});
    }
}); 

image

Аналогичным образом можно выводить и другие типы диаграмм, используя соответствующие методы. См. примеры непосредственно на сайте расширения 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]}]
});

image

Немного более сложным скриптом можно указать дополнительные детали, например, вывести легенду, настроить подсказки:

 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]
      }]
   });

image

При необходимости можно заменить стили по умолчанию на свои собственные.

 

Что выбрать: Canvas или SVG?

Как видно из примеров выше, для задач визуализации данных зачастую подходит и та, и другая технология. Многие вещи делаются схожим образом. В случаях, где нужен попиксельный вывод, очевидно, лучше подходит Canvas. Там, где диаграмма бьется на отдельные объекты, в которых нужно поддерживать интерактивность, лучше подходит SVG.

Лучше подходит Canvas
  • Редактирование растровой графики
  • Наложение эффектов на графику/видео
  • Генерирование растровой графики (визуализация данных, фракталы, графики функций)
  • Анализ изображенией
  • Игровая графика (спрайты, фон и т.п.)
Лучше подходит SVG
  • Масштабируемые интерфейсы
  • Интерактивные интерфейсы
  • Диаграммы, схемы
  • Векторное редактирование изображений

В графической форме это можно представить так:

image

Наконец, еще один важный срез, который также важно учитывать в выборе технологии – производительность отрисовки при использовании Canvas и SVG:

image

На практике Сanvas лучше работает при небольших размерах области отрисовки и на большом числе объектов, в SVG лучше подходит при необходимости масштабирования или вывода на большой экран и на не слишком большом количестве выводимых за раз объектов.

 

Дополнительные материалы и инструменты

Cheat Sheets:

Инструменты: