Создание комбинированных гистограмм с D3.js

В этом учебном руководстве я расскажу вам основы D3.js, также создадим инфографику с несколькими диаграммами, с дополнительным инструментом масштабирования контекста и данных.

Сложность

На официальном сайте D3.js описывает себя как "библиотека JavaScript  для управления документами на основе данных." Это - всего лишь одна из многих визуальных библиотек в JavaScript. D3.js создавался Майком Бостоком, ведущим разработчиком Protovis, который основывается на D3.js.

Я решил использовать D3.js потому, что это одна из самых надежных, гибких и бесплатных платформ.

Есть возможность работать с SVG, Canvas или HTML (путем приема данных и генерации HTML таблиц). Данная библиотека, позволяет Вам выбрать метод представления данных. SVG, Canvas и HTML имеют свои плюсы и минусы. Данная платформа не должна форсировать события так, как это делают другие.

Итак, какой вид диаграммы Вы можете создать с помощью D3.js? Почти всё что сможете вообразить. Он обрабатывает как простые информационные диаграммы, такие как line, bar и area, так и более сложные диаграммы, которые используют картограммы или деревья.

Комбинированные гистограммы довольно часто встречаются в работе, следовательно с них и начнем. В этом примере мы быстро пробежимся по основам создания инфографики с многократными комбинированными гистограммами вместе со специальным инструментом контекста, для масштабирования данных. Данные мы взяли с сайта Gapminder.org, который является хорошим помощником при изучении D3.js.

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

С этими данными мы создадим 5 комбинированных гистограмм. По оси Y расположим диапазон потребления, на оси X - года.

Начнем с HTML разметки.

<!DOCTYPE html>
<html>
<head>
    <script src="http://d3js.org/d3.v2.js"></script>
</head>
<body>
    <div id="chart-container">
        <h1>Electricity Consumption per capita</h1>
    </div>
</body>
</html>

Большая часть кода будет на JavaScript, который можете разместить непосредственно на странице, либо отдельным файлом, на ваше усмотрение. Первое, что нам нужно сделать в JavaScript - это создать некоторые переменные с размерами и положением диаграммы.

var margin = {top: 10, right: 40, bottom: 150, left: 60},
    width = 940 - margin.left - margin.right,
    height = 500 - margin.top - margin.bottom,
    contextHeight = 50;
    contextWidth = width * .5;

Теперь добавим тег SVG к странице. Функция выбора в D3.js удобно использует строковые селекторы стиля CSS.

В этом примере я рекомендую использовать SVG по двум причинам:

  1. SVG использует узлы в разметке, которые можно легко посмотреть в режиме разработчика. Благодаря этому, отладка становится проще.
  2. Поскольку SVG использует узлы в разметке, их можно перенести в CSS.
var svg = d3.select("#chart-container")
                  .append("svg")
                  .attr("width", width + margin.left + margin.right)
                  .attr("height", (height + margin.top + margin.bottom));

//d3.csv берет путь к файлу и функцию обратного вызова 
  d3.csv('data.csv', createChart);

  function createChart(data){
         var countries = [],
               charts = [],
               maxDataPoint = 0;

После загрузки файла CSV, создаем цикл в первой строке, для сбора всех стран, которые будем хранить в массиве для дальнейшего использования.

for (var prop in data[0]) {
   if (data[0].hasOwnProperty(prop)) {
      if (prop != 'Year') {
         countries.push(prop);
      }
   }
};
                     
var countriesCount = countries.length,
   startYear = data[0].Year,
   endYear = data[data.length - 1].Year,
   chartHeight = height * (1 / countriesCount);

Прогоним цикл через весь набор данных, для того чтобы присвоить все года в объект Data с помощью JavaScript. Также определим максимальную точку, это понадобится, чтобы установить масштаб оси Y.

data.forEach(function(d) {
   for (var prop in d) {
      if (d.hasOwnProperty(prop)) {
         d[prop] = parseFloat(d[prop]);
                              
         if (d[prop] > maxDataPoint) {
            maxDataPoint = d[prop];
         }
      }
   }
                        
   // <span id="result_box" lang="ru" xml:lang="ru">D3 требуется объект даты, давайте преобразуем его только один раз</span>
   d.Year = new Date(d.Year,0,1);
});
            
for(var i = 0; i < countriesCount; i++){
   charts.push(new Chart({
      data: data.slice(), // copy the array
      id: i,
      name: countries[i],
      width: width,
      height: height * (1 / countriesCount),
      maxDataPoint: maxDataPoint,
      svg: svg,
      margin: margin,
      showBottomAxis: (i == countries.length - 1)
   }));
}

Здесь мы создадим инструмент масштабирования.

var contextXScale = d3.time.scale()
            .range([0, contextWidth])
            .domain(charts[0].xScale.domain()); 
                     
var contextAxis = d3.svg.axis()
         .scale(contextXScale)
         .tickSize(contextHeight)
         .tickPadding(-10)
         .orient("bottom");
                     
var contextArea = d3.svg.area()
         .interpolate("monotone")
         .x(function(d) { return contextXScale(d.date); })
         .y0(contextHeight)
         .y1(0);
            
var brush = d3.svg.brush()
         .x(contextXScale)
         .on("brush", onBrush);
            
var context = svg.append("g")
      .attr("class","context")
      .attr("transform", "translate(" + (margin.left + width * .25) + "," + (height + margin.top + chartHeight) + ")");

context.append("g")
      .attr("class", "x axis top")
      .attr("transform", "translate(0,0)")
      .call(contextAxis);

context.append("g")
      .attr("class", "x brush")
      .call(brush)
      .selectAll("rect")
      .attr("y", 0)
      .attr("height", contextHeight);

context.append("text")
      .attr("class","instructions")
      .attr("transform", "translate(0," + (contextHeight + 20) + ")")
      .text('Click and drag above to zoom / pan the data');
                                    
function onBrush(){
   /* 
   this will return a date range to pass into the chart object 
   */

   var b = brush.empty() ? contextXScale.domain() : brush.extent();

   for(var i = 0; i < countriesCount; i++){
      charts[i].showOnly(b);
   }
}
}
                  
function Chart(options){
this.chartData = options.data;
this.width = options.width;
this.height = options.height;
this.maxDataPoint = options.maxDataPoint;
this.svg = options.svg;
this.id = options.id;
this.name = options.name;
this.margin = options.margin;
this.showBottomAxis = options.showBottomAxis;
                     
var localName = this.name;

Теперь определим масштаб. Масштаб основывается по времени или количеству (линейный, логарифмический или мощностной). Так как по оси X расположены года, то для неё мы будем использовать горизонтальную шкалу. Ось Y представляет потребление, следовательно будем использовать вертикальную шкалу.

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

Домен представляет собой минимальное и максимальное значение входного сигнала. Например, для оси X это будет 1960 - 2008. Для оси Y это будет 0 (минимальное значение) и maxData point, которую мы определили ранее.

Диапазон - минимальные и максимальные значения для вывода. Нам нужно, чтобы диаграммы были размером в 840px (this.width). Если мы установим диапазон [0, this.width], то D3.js будет использовать это в качестве записи для домена. Таким образом, для 1960 года будет выводится значение 0, а у 2008 - 840. Если бы наш домен был [200,400], то у 2008 года значение было бы равно 400, а у 1960 - 200.

/* xScale основан на времени */
this.xScale = d3.time.scale()
         .range([0, this.width])
         .domain(d3.extent(this.chartData.map(function(d) { return d.Year; })));
                     
/* yScale основан на maxData point которую мы нашли ранее */
this.yScale = d3.scale.linear()
         .range([this.height,0])
         .domain([0,this.maxDataPoint]);

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

this.svg.append("defs").append("clipPath")
         .attr("id", "clip-" + this.id)
         .append("rect")
         .attr("width", this.width)
         .attr("height", this.height);

/*
Присвойте класс, таким образом мы сможем задать цвет заливки.
Присвойте позицию это на странице.
*/

this.chartContainer = svg.append("g")
         .attr('class',this.name.toLowerCase())
         .attr("transform", "translate(" + this.margin.left + "," + (this.margin.top + (this.height * this.id) + (10 * this.id)) + ")");
            
/* Мы всё создали, теперь давайте добавим это к странице */

this.chartContainer.append("path")
         .data([this.chartData])
         .attr("class", "chart")
         .attr("clip-path", "url(#clip-" + this.id + ")")
         .attr("d", this.area);
                                             
this.xAxisTop = d3.svg.axis().scale(this.xScale).orient("bottom");
this.xAxisBottom = d3.svg.axis().scale(this.xScale).orient("top");

/* Зададим первой стране верхнюю ось */

if(this.id == 0){
   this.chartContainer.append("g")
         .attr("class", "x axis top")
         .attr("transform", "translate(0,0)")
         .call(this.xAxisTop);
}
                     
/* Зададим последней стране нижнюю ось */

if(this.showBottomAxis){
   this.chartContainer.append("g")
      .attr("class", "x axis bottom")
      .attr("transform", "translate(0," + this.height + ")")
      .call(this.xAxisBottom);
}  
                        
this.yAxis = d3.svg.axis().scale(this.yScale).orient("left").ticks(5);
                        
                     this.chartContainer.append("g")
         .attr("class", "y axis")
         .attr("transform", "translate(-15,0)")
         .call(this.yAxis);
               this.chartContainer.append("text")
         .attr("class","country-title")
         .attr("transform", "translate(15,40)")
         .text(this.name);
                     
}

При масштабировании диаграммы, вызываем функцию showOnly. Она задает начало и конец операции масштабирования. После этого, диаграмма будет перерисована, чтобы соответствовать диапазону дат. Далее, сбрасываем домен xScale, области видимости и обновляем ось X.

Chart.prototype.showOnly = function(b){
   this.xScale.domain(b);
   this.chartContainer.select("path").data([this.chartData]).attr("d", this.area);
   this.chartContainer.select(".x.axis.top").call(this.xAxisTop);
   this.chartContainer.select(".x.axis.bottom").call(this.xAxisBottom);
}

Как я упоминал выше, SVG может быть стилизован в CSS. Но CSS свойства для SVG имеют отличия. Например, вместо background-color используется fill.

Давайте установим контейнер и заголовок диаграммы.

#chart-container {
   width: 1000px; 
   margin: 0 auto 50px auto;
   background: rgba(255,255,255,0.5);
   box-shadow: 1px 1px 4px rgba(0,0,0,0.2);
   padding: 20px 30px;
}

#chart-container h2 {
   color: #444;
   margin: 0 0 10px;
   font-weight: 300;
   padding: 10px;
   text-shadow: 0 1px 1px rgba(255,255,255,0.8);
   font-size: 24px;
}

Перейдем к инструменту масштабирования, удалим рамки и сделаем строки осей полупрозрачными. Для видимой части, установим полупрозрачный фон и белую рамку слева и справа.

g.context g.axis path{ stroke-opacity: 0;}
g.context g.axis line{ stroke-opacity: .1;}

g.context g.brush rect.background{ fill: rgba(0,0,0,.1);}
.brush .extent {
   stroke: #fff;
   fill-opacity: .125;
   shape-rendering: crispEdges;
}

g.context rect.background{
   fill: rgb(200,200,255);
   visibility: visible !important;
}

Зададим цвет для каждой страны и удалим заливку осей.

g.france path.chart{ fill: rgba(127,201,127,0.5);}
g.germany path.chart{ fill: rgba(127,201,174,0.5);}
g.japan path.chart{ fill: rgba(127,183,201,0.5);}
g.uk path.chart{ fill: rgba(127,130,201,0.5);}
g.usa path.chart{ fill: rgba(171,127,201,0.5);}
         
.axis path, .axis line {
   fill: none;
   stroke: #aaa;
   shape-rendering: crispEdges;
}

ДЕМО СКАЧАТЬ Перевод статьи Multiple Area Charts with D3.js

Тэги: chartd3.js

Вход

Уважаемый пользователь! Мы обнаружили, что вы используете AdBlock и вынуждены скрыть часть материалов на нашем сайте. Siteacademy существует и развивается за счет доходов от рекламы. Просим внести наш сайт в список исключений или отключить Блокировщик рекламы на нашем сайте.