Графики и диаграммы на веб-страницах. Часть 1

August 10, 2009Comments Off on Графики и диаграммы на веб-страницах. Часть 1

Нет лучшего и более наглядного способа отображения больших таблиц с числовыми данными, чем отображение их в виде диаграмм или графиков. Применительно к отображению диаграмм на веб-страницах у разработчиков есть и богатство подходов и конкретных инструментов, позволяющих внедрять на html-страницы не только красивые, но и обладающие некоторой долей интерактивности диаграммы. Осенью 2007 года я уже поднимал вопрос о том, как мы можем внедрять в html-страницы диаграммы. Тогда серия из двух статей была посвящена методике, когда диаграммы строились на стороне сервера, т.е. некий php-скрипт генерировал статическую картинку диаграммы и отдавал ее браузеру клиента. В ряде случаев, когда нас не устраивала статическая картинка, то использовался подход с flash-роликом внедренным в страницу, который сначала загружал с сервера поток данных в виде xml или json массива, а затем рисовал диаграмму. Сегодняшняя статья рассмотрит третий подход, когда графики строятся без использования “тяжелой артиллерии” - с помощью javascript.

Неправильным было бы говорить, что графики, построенные с помощью flash, заведомо лучше перед графиками, построенными на javascript или сгенерированными на стороне сервера с помощью php. Каждый из подходов имеет свои плюсы и минусы. Наиболее надежным и гарантированно работающим во всех ситуациях является подход с картинками диаграмм, созданными с помощью серверного php-скрипта. Недостатком является отсутствие какой-либо динамики на диаграммах, т.е. мы не можем масштабировать рисунок, динамически изменять просматриваемый диапазон значений, подгружать данные для отображения по требованию, т.е. без полной перезагрузки страницы или рисунка. Кроме того, в большинстве случаев диаграммы-картинки имеют слишком большой размер при передаче по сети. Всех этих недостатков лишены диаграммы, созданные с помощью javascript или flash. На рынке есть большое количество как платных, так и бесплатных библиотек, позволяющих строить графики с помощью flash. Однако если вы не располагаете знаниями в actionscript и flash, то эти библиотеки и создаваемые ими графики приходится рассматривать как “черные ящики”. Плюс, для создания хорошего и удобного интерфейса необходимы средства взаимодействия между javascript (ведь на нем и создается львиная доля обработчиков событий в html-страничке) и flash. Гораздо удобнее если и интерфейс веб-странички и графики управляются единым образом, т.е. с помощью javascript. Единственная неприятность в том, что изначально в стандарте javascript не предусматривалось каких-либо средств для рисования. Поэтому приходится “выкручиваться” с помощью смежных технологий, таких как svg, canvas, vml, которые, к сожалению, не поддерживались всеми браузерами. Так идеальным средством для рисования диаграмм, даже самых сложных, был бы canvas. Вкратце, canvas – это специальный тег, создающий на html-странице прямоугольную область рисования. Затем вы, вызывая javascript-функции, можете рисовать линии, полигоны, закрашивать области разными цветами. Если эта тема вас заинтересовала, то я опубликовал на своем сайте пару статей, рассказывающих о рисовании с помощью canvas (Тег html canvas. Часть 1 и Тег html canvas. Часть 2). Увы и ах, но поддержка тега canvas уже несколько лет есть и в opera и в firefox, но разработчики internet explorer, даже в последней восьмой версии браузера не спешат поддерживать требования стандарта html 5. Еще одним доступным инструментом рисования с помощью javascript является SVG. SVG – это текстовой, основанный на xml, формат для описания как статических, так и динамических изображений. Подобно тому, как мы с помощью html тегов создаем структуру веб-страницы, так и с помощью тегов svg мы управляем тем из каких графических примитивов будет составлено изображение. Например, следующий пример, я любезно позаимствовал с сайта wikipedia (текст примера вполне читаем, если выделить в нем ключевые слова “circle” – круг, “fill” – заливка и т.д.):
  1. <?xml version="1.0" encoding="utf-8" standalone="yes"?>
  2. <svg version = "1.1"
  3.   baseProfile="full"
  4.   xmlns = "http://www.w3.org/2000/svg" 
  5.   xmlns:xlink = "http://www.w3.org/1999/xlink"
  6.   xmlns:ev = "http://www.w3.org/2001/xml-events"
  7.   height = "400px"  width = "400px">
  8.   <rect x="0" y="0" width="400" height="400" 
  9.     fill="none" stroke="black" stroke-width="5px" stroke-opacity="0.5"/>
  10.   <g fill-opacity="0.7" stroke="black" stroke-width="0.5px">
  11.     <circle cx="200px" cy="200px" r="100px" fill="red"   transform="translate(  0,-50)" />
  12.     <circle cx="200px" cy="200px" r="100px" fill="blue"  transform="translate( 70, 50)" />
  13.     <circle cx="200px" cy="200px" r="100px" fill="green" transform="translate(-70, 50)" />
  14.   </g>
  15. </svg>
Вы можете скопировать текст примера в блокнот, сохранить под именем example.svg или example.xml. Затем, открыв файл в firefox, opera либо в google chrome, вы увидите изображение трех пересекающихся кругов красного, зеленого и синего цвета. С помощью svg можно создавать очень сложные, насыщенные элементами изображения, и делать это можно не только с помощью блокнота, но и рисуя картинки в полноценных графических редакторах, например, corel или inkscape. Большого смысла в использовании svg, кроме создания векторных и, соответственно, масштабируемых изображений, нет. Гораздо проще и привычнее сохранять рисунки в традиционных форматах jpg, gif или png. С другой стороны, преимуществом svg является динамичность, т.е. размещенное на странице svg изображение не статично и вы можете с помощью javascript манипулировать картинкой – т.е. создавать анимацию. Несколько лет назад в internet было распространено мнение, что в ближайшем будущем открытый и свободный формат svg станет “убийцей” проприетарного формата flash и заменит его как средство создания всевозможных баннеров, анимационных роликов и спецэффектов на страницах. Как и многие другие “киллеры-альтернативы” svg закончил ничем. Главным недостатком svg является то, что в браузере internet explorer поддержки формата svg нет, если только вы не будете устанавливать специальный плагин. Таким образом, получается, что для internet explorer не работает ни один из стандартных для других браузеров подходов с рисованием на javascript: не работает ни canvas, ни svg. К счастью, для internet explorer доступен подход с подменой отсутствующей функциональности canvas/svg на технологию vml (никакими другими браузерами кроме IE не поддерживаемыми). Ирония судьбы в том, что в далеком 1998 г. microsoft внесла на рассмотрение в консорциум W3C стандарт VML (Vector Markup Language) как язык описания векторных изображений. В последствии VML и разрабатывавшийся параллельно язык PGML легли в основу SVG (Scalable Vector Graphics). Я хочу посоветовать всем, кто заинтересован в рисовании на javascript, обратить внимание на библиотеку Raphael. Она представляет собой “обертку” с унифицированным интерфейсом над svg и vml. Так, не изучая теги svg и, уж тем более, не касаясь мертворожденной vml, вы можете создавать сложные и красочные изображения, которые будут гарантированно доступны для safari, google chrome, opera, firefox и даже internet explorer. По правде говоря, Raphael не является специализированной библиотекой для создания графиков или диаграмм, но не упомянуть о ней будет просто преступлением. Как вывод: описываемые далее javascript-библиотеки нужно применять очень осторожно, зная, что часть посетителей сайта диаграммы может не увидеть. Здесь наиболее уместен прием с “progressive enhancement”, т.е. когда на странице есть массив данных в виде html-таблицы и эта информация доступа всем посетителям, но в том случае, если перед нами посетитель с “правильным браузером”, то он получает возможность дополнительно увидеть содержимое таблицы в виде наглядного графика. Если же требование доступности изображения с диаграммой является основным, то лучше всего генерировать файлы с картинками на сервере.

Итак, первая библиотека, о которой мы поговорим сегодня, – это Flot (домашний сайт проекта http://code.google.com/p/flot/). Для того, чтобы вы сразу составили представление о том, что умеет делать flot, прошу посмотреть примеры использования flot по следующему адресу http://code.google.com/p/flot/wiki/FlotUsage Flot построен на базе другой известной javascript библиотеки jquery. В то время как jquery представляет собой набор универсальных функций для манипулирования DOM-деревом html-страницы, то flot специализируется именно на создании диаграмм с помощью canvas и vml. В арсенале flot есть поддержка построения графиков с произвольным количеством серий данных. И каждая серия может быть либо в виде линии, либо в виде столбца. Причем возможно совмещение на одной диаграмме нескольких видов отображения серий данных одновременно, равно как вы можете на одной диаграмме показать две оси OX или OY. Каждая из серий может иметь оформлена в виде обычной линии с маркерами или без, вы можете залить в определенный цвет площадь, занимаемую линей диаграммы, можно даже задать график в виде набора точек не связанных линией между собой. Давайте рассмотрим на примерах описанные выше возможности flot. Начнем мы с того, что распакуем скачанный с сайта http://code.google.com/p/flot/ архив с flot и рассмотрим его содержимое. В поставку входит библиотека jquery (версия 1.2.3, но я успешно использовал flot вместе с последней на момент написания статьи версией jquery 1.3.2). Далее в архиве находится сама библиотека jquery.flot.js (в сжатом виде занимая всего 30 килобайт), затем идет excanvas.js (размер 13 кб.). Последняя библиотека содержит код имитации canvas для браузера internet explorer c помощью VML. Для того, чтобы для пользователей opera или firefox не подключать библиотеку extcanvas.js, то рекомендуется использовать условный комментарий, срабатывающий только для internet explorer:
  1. <html>  
  2.  <head>
  3.     <!--[if IE]><script language="javascript" type="text/javascript" src="excanvas.pack.js"></script><![endif]-->
  4.     <script language="javascript" type="text/javascript" src="jquery.js"></script>
  5.     <script language="javascript" type="text/javascript" src="jquery.flot.js"></script>
  6.  </head> 
  7.  <body>
  8.   <div id="placeholder" style="width:640px; height:480px;"></div>
  9. </body>
  10. </html>
Вкратце, после того как я подключил все три нужные для работы flot библиотеки мне нужно создать внутри html-страницы пустой тег div, играющий роль заглушки для графика (важно указать начальные размеры блока). Именно внутри этого div-а и будет через несколько минут построен график. Что касается, собственно, построения графика, то все сводится к вызову одной единственной функции “plot”. Единственная сложность в том, что нужно подготовить для нее как данные (т.е. массив пар X, Y), а также специальный объект “конфигурации”, который говорит flot-у, как нужно интерпретировать массив данных и как должны выглядеть оси, легенда и остальные визуальные характеристики диаграммы. В следующем примере рисуются графики трех функций y1=sin(x), y2=sin(x), y3=x^2. Построение диаграммы можно выполнить только тогда, когда было полностью загружено dom-дерево html-страницы. Для этого я использовал стандартный для jquery прием, привязав вызов функции “doFlot” к событию “domReady”. Внутри функции “doFlot” я в цикле формирую три массива со значениями серий:
  1. // привязываем функцию doFlot к событию “html-страница” загружена
  2. $(document).ready(doFlot);
  3.  
  4. // и сама функция построения диаграммы
  5. function doFlot (){
  6.  
  7.  var seriesSin = [];
  8.  var seriesCos = [];
  9.  var seriesX2 = [];
  10.  // формируем массивы с данными
  11.  for (var x = -Math.PI/2; x <= Math.PI/2; x+=0.1){
  12.    seriesSin.push ([x, Math.sin(x)]);
  13.    seriesCos.push ([x, Math.cos(x)]);
  14.    seriesX2.push ([x, x*x]);
  15.  }
  16.  
  17.  var chartConfig = { 
  18.    xaxis: {},
  19.    legend: {},
  20.    yaxis: {},
  21.    x2axis: {},
  22.    y2axis: {},
  23.    points: {},
  24.    lines: {},
  25.    bars: {},
  26.    grid: {},
  27.    selection: {},
  28.    shadowSize: 4,
  29.    colors: []   
  30.  };
  31.  
  32. // и строим саму диагрумму
  33.  
  34.  $.plot($("#placeholder"), 
  35.     [ 
  36.       { data: seriesSin, label: "sin (x)" },
  37.       { data: seriesCos, label: "cos (x)" },
  38.       { data: seriesX2, label: "x^2" }
  39.     ], 
  40.     chartConfig);
  41.  };
Итак, три величины были переданы как аргументы функции plot: ссылка на html-блок страницы, затем массив с данными для серий диаграммы (каждая серия состоит из массива данных “data” и надписи “label”). Третий же параметр chartConfig – управляет внешним видом диаграммы. В предыдущем примере я просто перечислил то, на какие категории разделяется список всех доступных конфигурационных переменных. К слову, если вы откроете исходный текст файла библиотеки jquery.flot.js, то в самом начале файла увидите перечень всех доступных для настройки конфигурационных переменных. И хотя не для всех из них есть комментарии, но догадаться о значении переменных вполне возможно исходя из их названий. К примеру, внутри объекта “xaxis” будут храниться настройки внешнего вида первой оси OX диаграммы (нижней). Из настроек представляющих для нас интерес можно, прежде всего, выделить переменные “min” и “max”, управляющие диапазоном значений по оси OX. Если эти значения не задать явно, то flot автоматически эти значения исходя из массива данных. Конечный результат выполнения примера показан на рис. 1.



Очень часто диаграммы показывают зависимость изменения во времени некоторой величин. В этом случае мало указывать массив исходных данных так (в пример я хочу показать значения прибыли некоторой организации за полтора года):
  1. var seriesProrofit = [];
  2. var i = 0;
  3. for (var i = 0; i < 18; i++)
  4.   seriesProrofit.push ( [new Date (2005, i, 1), 100+Math.tan(i/2)*i ]);
Если такие данные подать на вход функции plot, то хотя сама кривая будет нарисована верно, но подписи для этой оси будут иметь неожиданные значения – какие-то огромные да еще и отрицательные числа. А ведь нам хотелось бы видеть названия месяцев и номера года. Для того, чтобы подсказать flot о правилах форматирования оси OX, во-первых, нужно указать тип оси:
  1. var chartConfig = { 
  2.   xaxis: {mode: “time” },
  3.   // и все как обычно …
  4. }
Даже сейчас flot будет достаточно умен, чтобы выбрать тип подсказок в зависимости от диапазона значений оси OX. В моем примере, захватившем весь год 2005 и еще половинку 2006 г., flot выводит как подписи оси OX и номер года, и название месяца (правда, на английском). Если же диапазон будет умещаться в рамках одного года, то подписи будут только с названиями месяцев. А если вы решите поэкспериментировать и укажите, что хотите построить график не для 18, а для 180 месяцев, то flot масштабирует подписи, так что будут отображаться только номера годов. К слову, если нужно поменять названия месяцев с английского на русский, то нужно указать массив с названиями месяцев как значение свойства “monthNames ”. Для еще более тонкого контроля над внешним видом подписей оси OX можно использовать конфигурационную переменную “timeformat”, значением которой является строка с шаблоном форматирования даты/времени. В этой строке могут находиться специальные символы. Например '%h' и '%H' служит для подстановки значения часов (во втором случае значение количества часов всегда выровнено до двух цифр). Символ '%M' служит для подстановки количества минут, символ '%S' задает количество секунд. Номер дня месяца подставляется вместо поля '%d', а номер месяца вместо символа '%m' (название месяца замещает символ '%b'). И последним идет номер года – '%y’. К сожалению, полностью полагаться на flot в определении того, как должны быть сформированы подписи отметок по оси OX не возможно, поскольку flot может ошибиться и вывести настолько большое количество отметок (tick-ов) и так близко друг к другу, то они сольются (по-умолчанию flot делает отметки каждые 100 пикселей). Поэтому было предусмотрена конфигурационная переменная ticks. Чтобы понять, как влияет на построение графика ticks лучше всего читать справку по flot и много поэкспериментировать. В самом просто случае ticks равно числу отметок, которые будут нанесены на ось OX, в более сложных случаях можно указывать значение интервала, через который нужно делать отметки. Обратите внимание на то, как выглядят подписи на рис. 2.


  1. var chartConfig = { 
  2.   xaxis: {mode: "time", 
  3.   monthNames: 
  4.       [
  5.         "январь", "февраль", "март", "апрель", "май", "июнь", 
  6.         "июль", "август", "сентябрь", "октябрь", "ноябрь", "декабрь"
  7.       ],
  8.   timeformat: "%d %b %y", 
  9.   ticks: 5
  10.   }, 
  11.   // и все как обычно 
  12. }
В следующий раз я завершу рассказ об основных и наиболее важных функциях flot (о том, как управлять легендой, настройкой внешнего вида линий и “сетки” данных). А затем я перейду к рассказу об других интересных javascript библиотеках, позволяющих создавать иные типы диаграмм, кроме линейных и столбчатых.