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

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

В прошлой статье я показал заготовку диаграммы: создал несколько массивов с числовыми данными (серии данных) и рассказал о том, как настроить диаграмму с данными, расположенными вдоль временной шкалы. Сейчас я покажу то, как создать диаграмму с двумя осями OX или OY. Для этого трюка не требуется никаких специальных действий по подготовке серий данных – это все те же массивы пар чисел “x” и “y”. Единственное, что я должен сделать, так это при вызове функции plot указать для каждой из серий данных, то к какой оси она относится. Итак, первым шагом я создаю два массива данных с данными для графика функций y=sin(x) и y=exp(x). Учитывая, что значения первой функции колеблются от -1 до +1, а значения экспоненты возрастают очень быстро, то очевидно, что одновременно эти два графика можно разместить на одной диаграмме, только если привязать их к разным масштабам оси OY. Итак, первый шаг – подготовка данных:
  1. var seriesSin = [];
  2. var seriesExp = [];
  3. var i = 0;
  4. for (var i = -Math.PI/2; i < +Math.PI/2; i+=0.1){
  5.   seriesSin.push ( [i, Math.sin(i)] );
  6.   seriesExp.push ( [i, Math.exp(i)] );
  7. }
Завершающий шаг при вызове функции построения графика plot – это указать для каждой из серии данных, то к какой оси OY она относится. И служит для этого свойство “yaxis”, принимающее два значения “1” (значение по-умолчанию) или “2” (результат см. на рис. 1):


  1. var chartConfig = {};
  2. $.plot( $("#placeholder"),
  3.   [ 
  4.     { data: seriesSin, label: "sin (x)", yaxis: 1, xaxis: 1  },
  5.     { data: seriesExp, label: "exp (x)", yaxis: 2  , xaxis: 1},
  6.   ], 
  7.   chartConfig
  8. );
Иногда случается ситуация, когда значения двух серий данных сильно отличаются не только по значениям оси OY, но и по значениям оси OX. В этом случае мы можем на одной диаграмме привязать эти серии данных не только к двум различным осям OY, но и двум различным осям OX (очевидно, что вторая ось OX будет расположена вверху рисунка). И как было показано на примере выше за “привязку” серии данных к определенной оси OX отвечает свойство “xaxis”. Следующее улучшение диаграммы – декоративное. Т.к. значения по оси OX откладываются в радианах, то я хотел бы изменить правило по которому flot будет рисовать вертикальные линии-отметок (tick) и ставить подписи по оси OX. Первое изменение, заключается в том, большое количество отметок для графика sin(x) не нужно – достаточно показать, где находится значение число –Pi/4 затем “0” и число +Pi/4. Еще одним улучшением будет назначение специальной функции форматирования надписи tick-а перед выводом. Отвечать за это будет функция “formatRad”, в которой я округлю выводимое значение до тысячных долей. Как всегда для того, чтобы изменить параметры внешнего вида диаграммы я работаю с объектом chartConfig:
  1. var chartConfig = { 
  2.   xaxis: {tickFormatter: formatRad, ticks: [-Math.PI/4, 0, Math.PI / 4]}
  3. }
  4.  
  5. // а теперь функция форматирования подписей по оси OX
  6. function formatRad (value){
  7.   return Math.round(value*1000)/1000;
  8. }
То, что у меня получилось, показано на рис. 1, где прошу обратить внимание на подписи оси OX. С равной долей успеха из функции форматирования tick-а можно вернуть небольшой кусочек текста с html-форматированием. Так в следующем примере я поместил надпись tick-а внутрь прямоугольника с красным цветом границ:
  1. function formatRad (value){
  2.   return '<div style="border: 1px solid red;">'+value+'</div>';
  3. }
В том, случае, если количество линий-отметок не велико, то можно использовать прием с явным перечислением списка значений оси OX, при которых нужно ставить отметку. И каждому значению tick-а можно поставить в соответствии текст надписи, который выводится рядом с линей tick-а. В следующем примере я решил вывести три точки отметки –Pi/4, затем 0 и +Pi/4. Но в отличие от предыдущего примера, где количество радиан перед выводом на экран просто округлялось до тысячи, теперь мне хочется вывести подписи, содержащие изображение числа (Unicode код символа pi равен ‘\u03c0'):
  1. var chartConfig = { 
  2.  xaxis: {tickFormatter: formatRad, 
  3.     ticks: [[-Math.PI/4, "-\u03c0/4"], 0, [+Math.PI/4, "+\u03c0/4"]]
  4.  }
  5. }
Следующая часть материала будет посвящена различным “красивостям”, т.е. я расскажу о том, как можно изменять внешний вид линий диаграммы и ее легенды. Наиболее часто изменяемая настройка внешнего вида диаграммы – это цвет линии, ее толщина и наличие точек-отметок, соответствующих значениям серий данных.
  1. var chartConfig = { 
  2.   points: {  show: true, radius: 5, lineWidth: 2, fill: true, fillColor: '#ffffff'  },
  3.   lines: {show: true, lineWidth: 3},
  4.   shadowSize: 4,
  5.   colors: ['#ff0000', '#00ff00']
  6.  };
Первое что я изменил - это цвет линии графика, и служит для этого свойство “colors”. Colors – это массив из стольких элементов, сколько серий данных мы хотим показать на диаграмме. И каждый элемент этого массива – цвет линии данных с соответствующим номером. Для того, чтобы видеть не просто линию, а то какие значения эта линия соединяет между собой, нужно настроить свойство “points”. Так я включил отображение точек-маркеров с помощью свойства “show”, задал им радиус, затем толщину линий и цвет, которым кружки-маркеры будут закрашены внутри. Если включить отображение точек, то flot тут же выключит отображение линий соединяющих точки между собой. Поскольку я хотел бы видеть на диаграмме и точки-маркеры и соединяющие их линии, то я должен настроить свойство “lines”. Помимо показанных в примере характеристик “show” и “lineWidth”, есть пара забавных конфигурационных параметров “fill” и “fillColor”. Применять их имеет смысл очень аккуратно, т.к. если режим заливки включен, то flot закрасит области диаграммы, находящиеся между осью OX и линией функции (см. рис. 2).



Если у нас есть несколько пересекающихся линий с сериями данных, то заливка их всех может сделать диаграмму нечитаемой. Следующее свойство позволит полностью изменить внешний вид диаграммы, превратив ее в столбчатую – “bars”. Вот основные характеристики, управляющие внешним видом столбчатой диаграммы. Во-первых, это “show” для включения отображения столбцов, затем с помощью “lineWidth” мы задаем толщину линий (в пикселях). А что касается ширины, собственно, столбца, то за это отвечает параметр “barWidth”. Вот только задается его значение в отличие от “lineWidth” или свойства, управляющего радиусом “radius” кружков-маркеров, не в пикселях, а в относительных единицах измерения. Так в следующем примере, я задал ширину столбца в “0.05” единицы (радиана). А, учитывая, что ранее, когда я формировал массив с данными для графика функции y=sin(x), то использовал шаг приращения “x”, равным “0.1”. То, как и показано на рис. 3, размер столбика будет составлять ровно половину размера шага функции.


  1. var chartConfig = { 
  2.   points: {  show: true, radius: 10, lineWidth: 2, fill: true, fillColor: '#ffffff'  },
  3.   lines: {show: true, lineWidth: 3 , fill: false, fillColor: '#ca0000'  },
  4.   bars: {show: true, lineWidth: 1, barWidth: 0.05, fill: true,fillColor: '#ca0000' , align: "center" },
  5. };
Внимательно посмотрите на рис. 3. Еще там вы увидите то, как центр столбика диаграммы совпадает с центром кружка-маркера. Для того, чтобы управлять способом выравнивания столбика со значением серии данных либо по центру, либо по левому краю, я в примере выше указал для характеристики “align” значение “center”. Внимательный читатель уже задумался, что описанные выше характеристики внешнего вида диаграммы lines, bars, points носят “слишком глобальный характер” и применяются ко всем сериям данных. А есть ли способ для каждой серии данных индивидуально указать то, как она должна выглядеть? Например, совместить на одной диаграмме серию данных в виде линии, а вторую серию показать в виде набора столбиков? Да можно. Так, когда я вызываю функцию построения диаграммы plot и передаю вторым параметром массив с сериями данных, то для каждой серии можно указать свой индивидуальный набор характеристик lines, points, bars. Результаты выполнения следующего кода показаны на рис. 4:


  1. // никаких глобальных настроек диаграммы
  2. var chartConfig = {};
  3. // а теперь настраиваем каждую серию индивидуально
  4.  
  5. $.plot( $("#placeholder"), [ 
  6.   // первая серия данных в виде столбиков
  7.   { 
  8.      data: seriesSin, label: "sin (x)", yaxis: 1, xaxis: 1, 
  9.      bars: {show: true, lineWidth: 1, barWidth: 0.05, fill: true,fillColor: '#ca0000' , align: "center" }
  10.   },
  11.   // а вторая линией
  12.   { 
  13.      data: seriesExp, label: "exp (x)", yaxis: 2  , xaxis: 1,
  14.      points: {  show: true, radius: 5, lineWidth: 2, fill: true, fillColor: '#ffffff'  },
  15.      lines: {show: true, lineWidth: 3 , fill: false, fillColor: '#ca0000'  }
  16.   },
  17. ], chartConfig);
Теперь рассмотрим то, какие возможности есть в flot для управления внешним видом легенды диаграммы. Прежде всего, мы можем включать и отключать показ легенды с помощью свойства “show” . Если легенда диаграммы показывается, то мы можем настроить ее местоположение с помощью свойства “position”. Значения для “position” кодируются двумя буквами, обозначающими края света. Так комбинация “sw” (south, west) задает положение легенды в нижнем левом краю диаграммы. Для простого управления внешним видом легенды можно использовать работающие в паре свойства “backgroundColor” и “backgroundOpacity”. Они задают, соответственно, фоновый цвет прямоугольника, содержащего легенду диаграммы, и степень прозрачности этого фона (значения от 0 до 1). Для того, чтобы получить полный контроль над внешним видом подписи к серии данных диаграммы, то я могу указать специальную функцию форматирования. В следующем примере я решил вывести текст названия серии данных в виде курсива:
  1. function formatLabel (value){
  2.  return '<i>'+value+'</i>';
  3. }
  4.  
  5. var chartConfig = { 
  6.  legend: {
  7.    show: true, position: "sw", backgroundColor: "#00ff00", 
  8.    backgroundOpacity: 0.5, labelBoxBorderColor: "#caca00", labelFormatter: formatLabel
  9.  } 
  10. }
Иногда возникает потребность в размещении легенды диаграммы не внутри диаграммы, а где-нибудь в другом месте html-страницы, например, под диаграммой. Или содержимое страницы представляется в виде таблицы из двух колонок. Так, первая колонка будет содержать диаграмму, а вторая ее легенду. В любом случае, разработчики flot предусмотрели способ создать html-блок “заглушку” для последующего размещения в ней легенды диаграммы:
  1. <div id="legend" style="position: absolute; left: 900px; top: 800px; border: 1px solid red;"> 
  2.    Это заглушка для легенды 
  3. </div>
А при настройке внешнего вида диаграммы, я могу подсказать flot о том, что легенду нужно поместить, именно, внутрь блока “legend”:
  1. var chartConfig = { 
  2.   legend: {
  3.     show: true, 
  4.     container : $('#legend') 
  5.   } 
  6. }
Последняя характеристика диаграммы, связанная только с управлением ее внешним видом – это “grid”. Если внимательно присмотреться к любому из трех показанных выше рисунков диаграмм, то вы увидите на них набор вертикальных и горизонтальных линий как на бумаге “миллиметровке” – это и есть grid. Самое главное то, что помимо настройки внешнего вида диаграммы, “grid” является “точкой доступа” к еще одному большому пласту функций, предусмотренных в flot. Я говорю о средствах добавить к диаграмме немного интерактивности, т.е. возможности реагировать на действия пользователя. В следующем примере я, во-первых, изменил фоновый цвет сетки диаграммы на темно-серый (если же свойство “backgroundColor” будет равно не значению цвета, а специальному обозначению “null”, то фоновый цвет диаграммы будет прозрачным). Еще я поменял цвет линий контура “сетки” диаграммы на синий. И, самое важное, я включил режим интерактивности диаграммы (autoHighlight). Теперь при наведении мыши на какое-либо из значений диаграммы (точнее при попадании мыши в радиус mouseActiveRadius), то flot подсветит кружок со значением.
  1. var chartConfig = {   
  2.  grid: {
  3.    color: "#0000ff", 
  4.    backgroundColor: '#5a5a5a',
  5.    tickColor: "#dddddd", 
  6.    clickable: true,
  7.    hoverable: true,
  8.    autoHighlight: true, 
  9.    mouseActiveRadius: 15 
  10.  }  
  11. };
То, что у меня получилось, показано на рис. 5.



Но мне мало простой подсветки – я хочу создать собственную функцию, которую flot будет вызывать, извещая о каждом действии пользователя. Т.к. flot построен на базе и в соответствии с идеологией jquery, то для того, чтобы “привязать” функцию обработки события “наведена мышь на диаграмму” я использую унифицированный прием с вызовом jquery функции bind. Первым параметром для которой я передаю ссылку на объект графика (тот самый блок div играющий роль “холста” для диаграммы). Второй и третий параметры функции bind – это, соответственно, название события, которое я хочу “слушать” и ссылка на функцию-слушатель события.
  1. $("#placeholder").bind("plothover", 
  2.   function (event, pos, item) {
  3.      $("#tooltip").remove();
  4.      if (! item) return;
  5.  
  6.      var x = item.datapoint[0].toFixed(2);
  7.      var y = item.datapoint[1].toFixed(2);
  8.      var label = x + " = " + y + " ["+item.series.label+"]";
  9.      showTooltip(item.pageX, item.pageY,  label);
  10.   }
  11. );
Обработчик события “plothover” вызывается при каждом движении мыши над диаграммой, и каждый раз внутри свойства pos содержится информация о координатах мыши. Что очень важно, задаются эти координаты так, чтобы соответствовать сериям данных диаграммы. Т.е. если ось OX диаграммы изменяется в отрезке от –P/2 до +P/2, то и координаты мыши меняются в этом же диапазоне. Если же вас заинтересовали координаты мыши в абсолютном счислении, т.е. в измеряемые в пикселях и с центром отсчета в левом верхнем углу окна браузера, то не стоит забывать, что “холст” для рисования диаграммы – это всего лишь обычный блок “div”. Для которого мы можем приказать ловить “классическое” событие перемещения мыши:
  1. $("#placeholder").bind("mousemove", 
  2.    function (event) {
  3.      alert (event.clientX + ", "+ event.clientY);
  4.    }  
  5. );
Возвращаясь назад к задаче показа на диаграмме всплывающих подсказок. Первым делом, внутри функции обработки события “plothover” я проверил то, была ли эта функция вызвана в случае, когда курсор мыши попал в активную зону одного из кружков-маркеров диаграммы или нет. Критерием попадания является то, что переменная “item” (хранящая пару значений x, y) не равна null. После того, как я извлек из item пару значений “x, y” и сформировал строку надписи для всплывающей подсказки, остается только создать эту самую всплывающую подсказку. Не мудрствуя лукаво, я скопировал в одном из примеров идущих в поставке flot (пример называется “interacting”) код функции создающей в заданных координатах всплывающую подсказку:
  1. function showTooltip(x, y, contents) {
  2.   $('<div id="tooltip">' + contents + '</div>').css( {
  3.      position: 'absolute',
  4.      display: 'none',
  5.      top: y + 5, left: x + 5,
  6.      border: '1px solid #fdd',
  7.      padding: '2px',
  8.      'background-color': '#fee',
  9.      opacity: 0.80
  10.   }).appendTo("body").fadeIn(200);
  11. }
То, что у меня получилось, показано на рис. 5. По-аналогии с событием “hover” вы можете создать свою функцию отслеживающую “клики” пользователя по диаграмме. Как сделать это можно подсмотреть в файлах примеров flot.