Интерактивная инфографика

Дима Семьюшкин / @devgru

## Зачем изучать D3? - Программировать - Понимать ограничения - Объяснять другим
# Принципы
Данные важнее всего

Веб — платформа для визуализаций

Инструмент должен быть удобен

# Инструменты
## Веб-технологии - HTML, CSS, JS - SVG, Canvas - D3.js - Frameworks
## Библиотека D3.js
## Сервисы - jsbin.com - blockbuilder.org
# Что такое D3?
# Data # Driven # Documents
# Документы, # управляемые # данными

Правильные концепции и их сочетание

# HTML
# SVG
````html <svg> <circle cx=50 cy=50 r=10 fill=firebrick /> <rect x=70 y=50 width=40 height=20 fill=red /> <text x=50 y=90>Hello, world!</text> </svg> ````
# JavaScript
## Простые типы ````javascript var age = 26 var name = 'Dmitriy Semyushkin' ````
## Простые типы ````javascript var age = 26 var name = 'Dmitriy Semyushkin' age == 26 // true ````
## Объекты ````javascript var person = { age: 26, name: 'Dmitriy Semyushkin' } person.age == 26 // true person.name.length // 18 ````
## Массивы ````javascript var primes = [ 2, 3, 5, 7, 11, 13, 17, 19, 23, 29 ] primes[6] // 17 primes.length // 10 ````
## Функции ````javascript var lessThanTen = function (n) { return n < 10 } lessThanTen(6) // true lessThanTen(15) // false ````
## Функции ````javascript var primes = [ 2, 3, 5, 7, 11, 13, 17, 19, 23, 29 ] var lessThanTen = function (n) { return n < 10 } primes.filter(lessThanTen) // [2, 3, 5, 7] ````
# DOM
[![](img/web.png)]()
## Выборка ````javascript d3.selectAll('svg') // D3 ```` ````javascript $('svg') // jQuery ````
## Аттрибуты ````javascript d3.selectAll('circle') // D3 .attr('r', '5') ```` ````javascript $('circle') // jQuery .attr('r', '5') ````
### General update pattern
## Привязка данных
````html <div></div> <div></div> <div></div> ````
## Выведем текст ````javascript d3.selectAll('div') .data([18, 4, 7]) .text(function (d) { return d }) ```` ![](img/selections-data.png)
### Как быть?
````javascript d3.selectAll('div') .data([18, 4, 7, 11]) ??? .text(function (d) { return d }) ```` ![](img/selections-enter.png)
### Добавляем элементы
````javascript d3.selectAll('div') .data([18, 4, 7, 11]) .enter() .append('div') .text(function (d) { return d }) ```` ![](img/selections-enter.png)
## Удаляем элементы ````javascript d3.selectAll('div') .data([18, 4]) .exit() .remove() ```` ![](img/selections-exit.png)
### Основной кейс ````javascript d3.selectAll('div') .data([18, 4, 7]) .enter() .append('div') .text(function (d) { return d }) ```` ![](img/selections-data.png)
# Шкалы
## Шкала на карте
## Виды шкал - Дискретные - Непрерывные
## Метрики и шкалы
![](img/attrs.png)
### Линейная шкала
````javascript var scale = d3.scaleLinear() .domain([10, 170]) .range([0, 960]) scale(10) // 0 scale(170) // 960 scale(40) // 180 scale.invert(0) // 10 scale.invert(960) // 170 scale.invert(180) // 40 ````
### Линейная шкала
````javascript var scale = d3.scaleLinear() .domain([10, 170]) .range([0, 960]) scale(40) // 180 scale.invert(180) // 40 ````
### Убывающие значения
````javascript var scale = d3.scaleLinear() .domain([170, 10]) .range([0, 960]) scale(40) // 780 scale.invert(780) // 40 ````
## Цвета
````javascript var scale = d3.scaleLinear() .domain([0, 80]) .range(['black', 'white']) scale(10) // "#202020" scale.invert("#202020") // NaN ````
## Несколько цветов
````javascript var scale = d3.scaleLinear() .domain([-100, 0, 100]) .range(['#F99', '#DDD', '#39F']) scale(-25) // rgb(230, 204, 204) scale(50) // rgb(136, 187, 238) ````
## Пороговая шкала [![](img/swiss-map-threshold.png)](http://bl.ocks.org/herrstucki/6312708)
## Пороговая шкала
````javascript var scale = d3.scaleThreshold() .domain([45, 55, 65, 75, 85]) .range(['F', 'E', 'D', 'C', 'B', 'A']) scale(50) // "E" scale.invertExtent("F") // [undefined, 45] scale.invertExtent("E") // [45, 55] ````
## Первая визуализация
![](img/dots3.png)
![](img/datasaurus.png)
Создание набора данных - Draw my data - csvjson.com
## Данные ````javascript [ { X: 1, Y: 2 }, { X: 42, Y: 7 }, { X: 15, Y: 3 }, { X: 32, Y: 8 }, { X: 26, Y: 9 }, { X: 18, Y: 5 } ] ````
jsbin.com/sicedozuba/edit ````javascript var data = [ { X: 1, Y: 2 }, { X: 42, Y: 7 }, { X: 15, Y: 3 }, { X: 32, Y: 8 }, { X: 26, Y: 9 }, { X: 18, Y: 5 } ] var width = 500 var height = 500 ````
````javascript var xScale = d3.scaleLinear() .domain([0, 50]) .range([0, width]) var yScale = d3.scaleLinear() .domain([0, 10]) .range([height, 0]) var color = d3.scaleLinear() .domain([0, 10]) .range(['#500', '#F00']) ````
````javascript d3.select('body') .append('svg') .attr('width', width) .attr('height', height) .selectAll('circle') .data(data) .enter() .append('circle') .attr('r', 5) ````
````javascript ... .attr('r', 5) .attr('cx', function (d) { return xScale(d.X) }) .attr('cy', function (d) { return yScale(d.Y) }) .attr('fill', function (d) { return color(d.Y) }) ````
## Margin convention [![](img/margins.png)](https://bl.ocks.org/mbostock/3019563)
````javascript var margin = { top: 20, right: 20, bottom: 20, left: 20 } var fullWidth = 500 var fullHeight = 500 var width = fullWidth - margin.left - margin.right var height = fullHeight - margin.top - margin.bottom ````
````javascript ... .append('svg') .attr('width', fullWidth) .attr('height', fullHeight) ... ````
````javascript ... .append('svg') .attr('width', fullWidth) .attr('height', fullHeight) .append('g') .attr('transform', 'translate(' + margin.left + ',' + margin.top + ')') ... ````
# Оси
````javascript var xAxis = d3.axisBottom() .scale(xScale) var yAxis = d3.axisLeft() .scale(yScale) var group = d3.select('g') ````
````javascript group .append('g') .call(xAxis) .attr('transform', 'translate(0,' + height + ')') group .append('g') .call(yAxis) ````
# Загрузка данных
# Форматы - текст - CSV - JSON
## D3-методы ````javascript var firesUrl = 'http://d3.datalaboratory.ru' + '/data/fire-calls.json' d3.json(firesUrl, function (data) { console.log(data) }) var accidentsUrl = 'http://d3.datalaboratory.ru' + '/data/Accidents.csv' d3.csv(accidentsUrl, function (data) { console.log(data) }) ````
# Heatmap
## Начало работы jsbin.com/lamedimecu/edit
````javascript var firesUrl = 'http://d3.datalaboratory.ru' + '/data/fire-calls.json' d3.json(firesUrl, function (data) { var ordinal = d3.scaleOrdinal() .range(d3.range(9)) var color = d3.scaleSequential(d3.interpolateYlOrRd) var maxCalls = d3.max(data, function (d) { return d.calls }) color.domain([0, maxCalls]) }) ````
Привязка данных и создание элементов
````javascript d3.select('body') .append('svg') .selectAll('rect') .data(data) .enter() .append('rect') ````
Использование данных
````javascript ... .attr('x', function (d) { return ((d.year - 2015) * 12 + d.month - 1) * 20 }) .attr('y', function (d) { return ordinal(d.area) * 20 }) .attr('width', 20) .attr('height', 20) .attr('fill', function (d) { return color(d.calls) }) ````
# Обработка событий
## Источники событий - мышь - клавиатура - браузер - сеть
Контейнер
````javascript var tooltip = svg .append('text') .attr('y', 10 * 20) .attr('x', 0) ````
Обработка событий
````javascript ... .on('mouseover', function (d) { tooltip.text( d.area + ': ' + d.calls + ' вызовов за ' + d.month + '.' + d.year ) }) ````
````javascript .on('mouseout', function (d) { tooltip.text('') }) ````
# Проекции
![](img/projections.png)
- [The True Size](http://thetruesize.com/) - [Transitions](http://bl.ocks.org/mbostock/3711652) - [D3 Geo API](https://github.com/d3/d3/blob/master/API.md#geographies-d3-geo)
# Картограмма

Инструменты

  • OGR
  • GeoJSON
  • TopoJSON
Улучшаем существующую карту http://bl.ocks.org/KoGor/5685876
## Начало работы http://jsbin.com/webibajiwi/edit
## Подключение библиотек
```` <​script src='http://d3js.org/d3.v4.js'><​/script> <​script src='http://d3js.org/topojson.v1.js'><​/script> <​script src='http://d3js.org/colorbrewer.v1.js'><​/script> ````
Размер, шкала, контейнер
````javascript var color = d3.scaleThreshold() .domain([300, 600, 900, 1200, 1500]) .range(colorbrewer['RdYlGn'][6].reverse()) var width = 960 var height = 500 var svg = d3.select('body') .append('svg') .attr('width', width) .attr('height', height) ````
Загрузка данных
````javascript d3.queue() .defer(d3.json, 'http://d3.datalaboratory.ru' + '/data/russia.json') .defer(d3.csv, 'http://d3.datalaboratory.ru' + '/data/accidents.csv') .await(ready) function ready(error, map, data) { ... } ````
Обработка данных
````javascript var rateById = {} data.forEach(function (d) { rateById[d.RegionCode] = Number(d.Deaths) }) ````
Отрисовка
````javascript var path = d3.geoPath().projection(projection) var mapData = topojson .feature(map, map.objects.russia) .features ````
Отрисовка
````javascript svg.selectAll('path') .data(mapData) .enter() .append('path') .attr('stroke', 'white') .attr('stroke-width', 1) .attr('d', path) .attr('fill', function (d) { return color(rateById[d.properties.region]) }) ````
Проекция
````javascript var projection = d3.geoAlbers() .rotate([-105, 0]) .center([-10, 65]) .parallels([52, 64]) .scale(700) .translate([width * 0.5, height * 0.5]) ````
## Оформление визуализации
## Вторая карта
````javascript var svg2 = d3.select('body').append('svg') .attr('width', width * 0.5) .attr('height', height) .style('border-left', '1px solid #000') .style('margin', '10px auto') var projection2 = d3.geoAlbers() .rotate([-105, 0]) .center([-10, 65]) .parallels([52, 64]) .scale(500) .translate([width * 0.1425, height * 0.65]) drawMap(svg2, map, cities, rateById, projection2) ````
## Легенда
## Легенда
````javascript var legend_labels = [ '< 300', '300 +', '600 +', '900 +', '1200 +', '> 1500' ] var legend = svg2.selectAll('g.legend') .data(ext_color_domain) .enter().append('g') .attr('class', 'legend') ````
````javascript legend.append('rect') .attr('x', 200) .attr('y', function (d, i) { return i * ls_h }) .attr('width', ls_w) .attr('height', ls_h) .attr('fill', function (d, i) { return color(d) }) legend.append('text') .attr('x', 230) .attr('dy','-4') .attr('y', function (d, i) { return (i + 1) * ls_h }) .text(function (d, i) { return legend_labels[i] }) ````
## Тень для текста ````javascript city.append('text').attr('class', 'shade') city.append('text') ```` ````css .shade { stroke: #fff; stroke-width: 2.75px; fill: #fff; text-shadow: 0 0 2px #fff; } ````