d3.js основы

Apr 7, 2019

Официальная документация: d3js

Изучаем D3.js за 5 минут
learn-to-visualize-data-with-this-free-d3-js-course

import d3

import * as d3 from "d3";

Манипуляция с DOM элементами

Методы для выбора DOM элементов

d3.select()
d3. selectAll()
d3.select('h1')

Используя d3 мы можем манипулироватьс DOM элементами:

d3.select('h1').style('color', 'red');

text()

Мы можем обновить текст используя метод text():

d3.select('h1').style('color', 'red')
    .attr('class', 'heading')
    .text('Updated h1 tag');

append()

Мы можем вставить элемент используя метод append():

d3.select('div').append('p').text('First Paragraph');

Введение в работу с данными

Создадим теги p на основе массива с данными:

Метод data() переведет полученные данные в состояние ожидания для дальнейшей обработки.

Метод enter() принимает данные один за одним и выполняет с ними какие-либо операции.

В нашем примере мы просто вставляем параграфы с одним и тем же текстом:

var dataset = [1, 2, 3, 4, 5];
d3.select('.divForP')
    .selectAll('p') // так как p нет, вернется пустой выбор
    .data(dataset)  // передаем наш массив методу data
    .enter()
    .append('p') // appends paragraph for each data element
    .text('D3 is awesome!!');

// output
/*
D3 is awesome!!
D3 is awesome!!
D3 is awesome!!
D3 is awesome!!
D3 is awesome!!
*/

При помощи метода text() мы можем вставить в качестве текста наших параграфов значения из массива dataset для соответствующих абзацев:

var dataset = [1, 2, 3, 4, 5];

d3.select('body')
    .selectAll('p')
    .data(dataset)
    .enter()
    .append('p') // appends paragraph for each data element
    //.text('D3 is awesome!!');
    .text(function(d) { return d; });
/*
1
2
3
4
5
*/

Создание простого bar chart (гистограмма/столбчатая диаграмма)

Для начала мы имеем svg тег:

<svg class="bar-chart"></svg>
//-------------------------bar chart-------------------------
var dataset = [80, 100, 56, 120, 180, 30, 40, 120, 160];
var svgWidth = 500, svgHeight = 300, barPadding = 5;
// определяем ширину каждого bar
var barWidth = (svgWidth / dataset.length);


var svg = d3.select('svg')
    .attr("width", svgWidth)
    .attr("height", svgHeight);

// столбцы не что иное как прямоугольники (rect)
// так как rect нет, то будет возвращен пустой выбор
var barChart = svg.selectAll("rect")
    // перадает наш dataset (данные в состоянии ожидания)
    .data(dataset)
    // метод enter принимает наши данные, и выполняет над каждым итемом
    // приведенные ниже операции
    .enter()
    // вставляем rect и проставялем attr для каждого rect
    .append("rect")
    .attr("y", function(d) {
        return svgHeight - d
    })
    .attr("height", function(d) {
        return d;
    })
    .attr("width", barWidth - barPadding)
    // translate используется для сдвига по оси x
    .attr("transform", function (d, i) {
        var translate = [barWidth * i, 0];
        return "translate("+ translate +")";
    });

Проставим метки (label) для гистограммы

Метки в нашем случае это текст над каждым bar-ом.

Текст добавим аналогично тому, как были добавлены rect в предыдущем примере. Лишь для расположения по оси x мы указываем X атрибут на основе ширины и индексов колонок.

// Выберем все text элементы внутри нашего svg (вернется пустая выборка)
var text = svg.selectAll("text")
    .data(dataset)
    .enter()
    .append("text")
    .text(function(d) {
        return d;
    })
    .attr("y", function(d, i) {
        return svgHeight - d - 2;
    })
    .attr("x", function(d, i) {
        return barWidth * i;
    })
    .attr("fill", "#A64C38");

Scales (масштабирование)

Scales - это функции, которые трансформируют наши данные, увеличивая или уменьшая значения для лучшей визуализации. Например, значения так малы, что график визуально оказывактся маленьким. Исправим это.

Метод d3.scaleLinear упрощает работу с масштабированием графики.

Метод domain() определяет полные границы графика. Первый параметр указывает на начало графика (0), второй - на максимальное значение в наборе данных. Рассчитаем это значение при помощи метода d3.max().

Метод range() будет держать наши значения в пределах нашего SVG контейнера, то есть данные метод отвечает за физические границы нашего изображения. Первый параметр получает значение (0), второй - высоту SVG контейнера.

var dataset = [1,2,3,4,5];

var svgWidth = 500, svgHeight = 300, barPadding = 5;
var barWidth = (svgWidth / dataset.length);


var svg = d3.select('svg')
    .attr("width", svgWidth)
    .attr("height", svgHeight);

var yScale = d3.scaleLinear()
    .domain([0, d3.max(dataset)])
    .range([0, svgHeight]);

var barChart = svg.selectAll("rect")
    .data(dataset)
    .enter()
    .append("rect")
    .attr("y", function(d) {
        return svgHeight - d
    })
    .attr("height", function(d) {
        return d;
    })
    .attr("width", barWidth - barPadding)
    .attr("transform", function (d, i) {
        var translate = [barWidth * i, 0];
        return "translate("+ translate +")";
    });

Далее мы передаем значения полученные в attr Y координате и у attr высоты методу yScale:

var dataset = [1,2,3,4,5];

var svgWidth = 500, svgHeight = 300, barPadding = 5;
var barWidth = (svgWidth / dataset.length);

var svg = d3.select('svg')
    .attr("width", svgWidth)
    .attr("height", svgHeight);

var yScale = d3.scaleLinear()
    .domain([0, d3.max(dataset)])
    .range([0, svgHeight]);

var barChart = svg.selectAll("rect")
    .data(dataset)
    .enter()
    .append("rect")
    .attr("y", function(d) {
        return svgHeight - yScale(d)
    })
    .attr("height", function(d) {
        return yScale(d);
    })
    .attr("width", barWidth - barPadding)
    .attr("transform", function (d, i) {
        var translate = [barWidth * i, 0];
        return "translate("+ translate +")";
    });

Теперь наши колонки правильно масштабированы.

Axes (оси)

d3.js предоставляет 4 метода для создания осей:

  • d3.axisTop()
  • d3.axisRight()
  • d3.axisBottom() - используем для создания оси x. Будет возвращена функция, которую мы свяжем (chain) с другой функцией scale и предоставим ей шкалу по оси X.
  • d3.axisLeft() - используем для создания оси y. Будет возвращена функция, которую мы свяжем (chain) с другой функцией scale и предоставим ей шкалу по оси Y.

Далее создадим group элемент внутри нашего SVG элемента и предоставляем ему атрибут translate и вызоваем метод call, передавав ему y-axis. Аналогичые операции делаем для отрисовки x оси.

var data= [80, 100, 56, 120, 180, 30, 40, 120, 160];

var svgWidth = 500, svgHeight = 300;

var svg = d3.select('svg')
    .attr("width", svgWidth)
    .attr("height", svgHeight);

var xScale = d3.scaleLinear()
    .domain([0, d3.max(data)])
    .range([0, svgWidth]);

var yScale = d3.scaleLinear()
    .domain([0, d3.max(data)])
    .range([svgHeight, 0]);

var x_axis = d3.axisBottom().scale(xScale);

console.log('d3.axisBottom(): ', d3.axisBottom())

var y_axis = d3.axisLeft().scale(yScale);

svg.append("g")
    .attr("transform", "translate(50, 10)")
    .call(y_axis);

var xAxisTranslate = svgHeight - 20;

svg.append("g")
    .attr("transform", "translate(50, " + xAxisTranslate  +")")
    .call(x_axis);

Создаем svg элементы: линию, прямоугольник, круг

Для элемента line нам нужно указать координаты для начальной и конечной точки, а также атрибут stroke.

// line
var svgWidth = 600, svgHeight = 500;
var svg = d3.select("svg")
    .attr("width", svgWidth)
    .attr("height", svgHeight)
    .attr("class", "svg-container");

var line = svg.append("line")
    .attr("x1", 100)
    .attr("x2", 500)
    .attr("y1", 50)
    .attr("y2", 50)
    .attr("stroke", "red");

Создадим прямоугольник (rect).

var rect = svg.append("rect")
    .attr("x", 100)
    .attr("y", 100)
    .attr("width", 200)
    .attr("height", 100)
    .attr("fill", "green");

Создадим прямоугольник (circle).

var circle = svg.append("circle")
    .attr("cx", 200)
    .attr("cy", 300)
    .attr("r", 80)
    .attr("fill", "#7CE8D5");

pie график

var data = [
    {"platform": "Android", "percentage": 40.11},
    {"platform": "Windows", "percentage": 36.69},
    {"platform": "iOS", "percentage": 13.06}
];

var svgWidth = 500, svgHeight = 300, radius =  Math.min(svgWidth, svgHeight) / 2;
var svg = d3.select('svg')
    .attr("width", svgWidth)
    .attr("height", svgHeight);

//Create group element to hold pie chart
var g = svg.append("g")
    .attr("transform", "translate(" + radius + "," + radius + ")") ;

var color = d3.scaleOrdinal(d3.schemeCategory10);

var pie = d3.pie().value(function(d) {
    return d.percentage;
});

var path = d3.arc()
    .outerRadius(radius)
    .innerRadius(0);

var arc = g.selectAll("arc")
    .data(pie(data))
    .enter()
    .append("g");

arc.append("path")
    .attr("d", path)
    .attr("fill", function(d) { return color(d.data.percentage); });

var label = d3.arc()
    .outerRadius(radius)
    .innerRadius(0);

arc.append("text")
    .attr("transform", function(d) {
        return "translate(" + label.centroid(d) + ")";
    })
    .attr("text-anchor", "middle")
    .text(function(d) { return d.data.platform+":"+d.data.percentage+"%"; });

Линейный график

// API to fetch historical data of Bitcoin Price Index
const api = 'https://api.coindesk.com/v1/bpi/historical/close.json?start=2017-12-31&end=2018-04-01';

/**
 * Loading data from API when DOM Content has been loaded'.
 */
document.addEventListener("DOMContentLoaded", function(event) {
fetch(api)
    .then(function(response) { return response.json(); })
    .then(function(data) {
        var parsedData = parseData(data);

        /* parsedData[]:
            0:
                date: Sun Dec 31 2017 03:00:00 GMT+0300 (Москва, стандартное время) {}
                value: 13860.1363
        */

        drawChart(parsedData);
    })
    .catch(function(err) { console.log(err); })
});

/**
 * Parse data into key-value pairs
 * @param {object} data Object containing historical data of BPI
 */
function parseData(data) {
    var arr = [];
    for (var i in data.bpi) {
        arr.push({
            date: new Date(i), //date
            value: +data.bpi[i] //convert string to number
        });
    }
    return arr;
}

/**
 * Creates a chart using D3
 * @param {object} data Object containing historical data of BPI
 */
function drawChart(data) {
    var svgWidth = 600, svgHeight = 400;
    var margin = { top: 20, right: 20, bottom: 30, left: 50 };
    var width = svgWidth - margin.left - margin.right;
    var height = svgHeight - margin.top - margin.bottom;

    var svg = d3.select('svg')
        .attr("width", svgWidth)
        .attr("height", svgHeight);

    // сдвигаем элемент g на 50 и 20
    var g = svg.append("g")
        .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

    // зададим scales (масштабирование), scaleTime так как мы используем дату
    var x = d3.scaleTime()
        .rangeRound([0, width]);

    // зададим scales (масштабирование) по оси Y
    var y = d3.scaleLinear()
        .rangeRound([height, 0]);

    // создаем линейнный график
    var line = d3.line()
        // обратите внимание, что мы передаем данные через scale методы (x, y)
        .x(function(d) { return x(d.date)})
        .y(function(d) { return y(d.value)})
        // ф-я domain предназначена для того, что d3 узнал об объеме данных
        // когда они переданы scale функции
        // метод domain min и max значения
        x.domain(d3.extent(data, function(d) { return d.date }));
        y.domain(d3.extent(data, function(d) { return d.value }));

    g.append("g")
        .attr("transform", "translate(0," + height + ")")
        .call(d3.axisBottom(x))
        .select(".domain")
        .remove();

    g.append("g")
        .call(d3.axisLeft(y))
        .append("text")
        .attr("fill", "#000")
        .attr("transform", "rotate(-90)")
        .attr("y", 6)
        .attr("dy", "0.71em")
        .attr("text-anchor", "end")
        .text("Price ($)");

    g.append("path")
        .datum(data)
        .attr("fill", "none")
        .attr("stroke", "steelblue")
        .attr("stroke-linejoin", "round")
        .attr("stroke-linecap", "round")
        .attr("stroke-width", 1.5)
        .attr("d", line);
}

По материалам Learn D3.js

Основные концепты d3.js / наброски

d3-selection

github.com/d3/d3-selection

d3-scale

Основная задача - сопостовление размеров абстрактных данных к визуальному представлению.

//jsfiddle.net/dnzl/1gejk3mf/embed/js,html,css,result/dark/
var x = d3.scaleLinear()
    .domain([10, 130])
    .range([0, 960]);

x(20); // 80
x(50); // 320

// shortand
var x = d3.scaleLinear([10, 130], [0, 960]);

update pattern

bl.ocks.org/cmgiven

d3-transform

github.com/trinary/d3-transform

d3-transform делает простым создание и переиспользование функций, которые создают строковые transform атрибуты для SVG элементов.

d3.select('svg').selectAll('g')
    .data([{ size: 5 }, { size: 10 }])
  .enter().append('g')
    .attr('transform', function(d, i) {
      return "translate(20," + d.size * 10 + ") rotate (40) scale(" + ( d.size + 2 ) + ")");
    });

// с использованием d3-transform:
var transform = d3.transform()
    .translate(function(d) { return [20, d.size * 10] })
    .rotate(40)
    .scale(function(d) { return d.size + 2 });

var svg = d3.select('svg').selectAll('g')
    .data([{ size: 5 }, { size: 10 }])
    .enter()
    .append('g')
    .attr('transform', transform);

d3-zoom

github.com/d3/d3-zoom

Добавить комментарий