Статьи

Создание директив диаграмм с использованием AngularJS и D3.js

D3 — это библиотека JavaScript, которую можно использовать для создания интерактивных диаграмм с технологией HTML5 Scalable Vector Graphics (SVG). Работать напрямую с SVG для создания диаграмм может быть болезненно, поскольку нужно помнить формы, поддерживаемые SVG, и сделать несколько вызовов API, чтобы сделать диаграмму динамичной. D3 устраняет большинство проблем и предоставляет простой интерфейс для построения графиков на основе SVG. Джей Радж опубликовал две замечательные статьи SitePoint о работе с D3, ознакомьтесь с ними, если вы еще не знакомы с D3.

Большинству из вас может не понадобиться официальное введение в AngularJS . AngularJS — это клиентский JavaScript-фреймворк для создания многофункциональных веб-приложений. Одним из главных преимуществ AngularJS является поддержка директив. Директивы предоставляют отличный способ определить наши собственные свойства и элементы HTML. Это также помогает хранить разметку и код отдельно друг от друга.

AngularJS также очень силен в связывании данных. Эта функция экономит много времени и усилий, необходимых для обновления пользовательского интерфейса в соответствии с данными в модели. В современном веб-мире клиенты просят разработчиков создавать веб-сайты, которые отвечают в режиме реального времени. Это означает, что клиенты хотят всегда видеть последние данные на экране. Пользовательский интерфейс данных должен обновляться, как только кто-то изменяет часть данных в серверной части. Выполнение таких обновлений в реальном времени будет очень трудным и неэффективным, если у нас не будет поддержки привязки данных.

В этой статье мы увидим, как создавать директивы AngularJS в реальном времени, которые обертывают диаграммы D3.

Настройка

Во-первых, нам нужно настроить среду. Нам нужны AngularJS и D3, включенные в HTML-страницу. Поскольку мы создадим только директиву диаграммы, нам нужно создать контроллер AngularJS и директиву. В контроллере нам нужна коллекция, содержащая данные, которые должны быть нанесены на график. Следующий фрагмент показывает начальный контроллер и директиву. Мы добавим больше кода к этим компонентам позже.

var app = angular.module("chartApp", []);

app.controller("SalesController", ["$scope", function($scope) {
  $scope.salesData = [
    {hour: 1,sales: 54},
    {hour: 2,sales: 66},
    {hour: 3,sales: 77},
    {hour: 4,sales: 70},
    {hour: 5,sales: 60},
    {hour: 6,sales: 63},
    {hour: 7,sales: 55},
    {hour: 8,sales: 47},
    {hour: 9,sales: 55},
    {hour: 10,sales: 30}
  ];
}]);

app.directive("linearChart", function($window) {
  return{
    restrict: "EA",
    template: "<svg width='850' height='200'></svg>",
    link: function(scope, elem, attrs){
    }
  };
});

Мы заполним функцию связи в вышеприведенной директиве, чтобы использовать данные, хранящиеся в контроллере, и построим линейную диаграмму, используя D3. Шаблон директивы содержит элемент svg Мы применим API D3 к этому элементу, чтобы построить график. Следующий фрагмент демонстрирует пример использования директивы:

 <div linear-chart chart-data="salesData"></div>

Теперь давайте соберем основные данные, необходимые для построения графика. Он включает в себя данные для построения графика, объект JavaScript элемента SVG и другие статические данные.

 var salesDataToPlot=scope[attrs.chartData];
var padding = 20;
var pathClass = "path";
var xScale, yScale, xAxisGen, yAxisGen, lineFun;
    
var d3 = $window.d3;
var rawSvg = elem.find("svg")[0];
var svg = d3.select(rawSvg);

После загрузки библиотеки для d3 объект d3 Но если мы используем его непосредственно внутри блока кода, этот блок кода будет сложно протестировать. Чтобы сделать директиву тестируемой, я использую объект через $ window .

Рисование простой линейной диаграммы

Давайте настроим параметры, необходимые для построения графика. Диаграмме нужны ось x, ось y и область данных, которые будут представлены этими осями. В этом примере ось X обозначает время в часах. Мы можем взять первое и последнее значения в массиве. На оси Y возможные значения находятся в диапазоне от нуля до максимального значения продаж. Максимальное значение продаж можно найти с помощью d3.max() Диапазон осей варьируется в зависимости от высоты и ширины элемента svg

Используя приведенные выше значения, нам нужно попросить d3 нарисовать оси с желаемой ориентацией и количеством тиков. Наконец, нам нужно использовать d3.svg.line() Все вышеперечисленные компоненты должны быть добавлены к элементу svg Мы можем применять стили и преобразования к диаграмме при добавлении элементов. Следующий код устанавливает параметры и добавляет к SVG:

 function setChartParameters(){
  xScale = d3.scale.linear()
             .domain([salesDataToPlot[0].hour, salesDataToPlot[salesDataToPlot.length - 1].hour])
             .range([padding + 5, rawSvg.clientWidth - padding]);

              yScale = d3.scale.linear()
                .domain([0, d3.max(salesDataToPlot, function (d) {
                  return d.sales;
                })])
             .range([rawSvg.clientHeight - padding, 0]);

  xAxisGen = d3.svg.axis()
               .scale(xScale)
               .orient("bottom")
               .ticks(salesDataToPlot.length - 1);

  yAxisGen = d3.svg.axis()
               .scale(yScale)
               .orient("left")
               .ticks(5);

  lineFun = d3.svg.line()
              .x(function (d) {
                return xScale(d.hour);
              })
              .y(function (d) {
                return yScale(d.sales);
              })
              .interpolate("basis");
}
         
function drawLineChart() {

  setChartParameters();

  svg.append("svg:g")
     .attr("class", "x axis")
     .attr("transform", "translate(0,180)")
     .call(xAxisGen);

   svg.append("svg:g")
      .attr("class", "y axis")
      .attr("transform", "translate(20,0)")
      .call(yAxisGen);

   svg.append("svg:path")
      .attr({
        d: lineFun(salesDataToPlot),
        "stroke": "blue",
        "stroke-width": 2,
        "fill": "none",
        "class": pathClass
   });
}

drawLineChart();

Вот демо, показывающее график выше.

Обновление графика в реальном времени

Как указывалось ранее, благодаря сегодняшним возможностям Интернета наши пользователи хотят, чтобы диаграммы данных обновлялись немедленно по мере изменения базовых данных. Измененная информация может быть передана клиенту с использованием таких технологий, как WebSockets. Директива диаграммы, которую мы только что создали, должна быть в состоянии реагировать на такие изменения и обновлять диаграмму.

Для передачи данных через WebSockets нам нужен компонент на сервере, созданный с использованием Socket.IO с Node.js, SignalR с .NET или аналогичная технология на других платформах. Для демонстрации я использовал $interval

 $interval(function() {
  var hour = $scope.salesData.length + 1;
  var sales = Math.round(Math.random() * 100);

  $scope.salesData.push({hour: hour, sales: sales});
}, 1000, 10);

Чтобы обновить диаграмму, как только будут загружены новые данные, нам нужно перерисовать диаграмму с обновленными данными. Наблюдатель сбора должен использоваться в директиве для отслеживания изменений в данных сбора. Наблюдатель вызывается, когда в коллекцию вносятся какие-либо изменения. График перерисовывается в наблюдателе.

 scope.$watchCollection(exp, function(newVal, oldVal) {
  salesDataToPlot = newVal;
  redrawLineChart();
});

function redrawLineChart() {

  setChartParameters();
  svg.selectAll("g.y.axis").call(yAxisGen);
  svg.selectAll("g.x.axis").call(xAxisGen);

  svg.selectAll("." + pathClass)
     .attr({
       d: lineFun(salesDataToPlot)
     });
}

Полное демо можно найти здесь .

Вывод

AngularJS и D3 — очень полезные библиотеки для создания многофункциональных бизнес-приложений в Интернете. Мы обсудили, как использовать их вместе, чтобы создать простую диаграмму. Вы можете расширить эти знания для создания диаграмм для ваших приложений.