Yahor Vaziyanau
Профессиональный формошлеп / JS разработчик / Bug Janitor.
Software Engineer в
iTechArt Group (по состоянию на апрель 2021).
Профессиональный формошлёп.
JavaScript-разработчик со стажем более 39.4 тыщ часов или 1642 дней и (поверьте мне ).
Это Я (формошлеп ).
Телеграм ("нежелателен" на территории РБ в виду своей деструктивной деятельности) поэтому смотри ниже cпособы связи.
Рабочая почта:
yahor.vaziyanau@itechart-group.com.
P.S. Если как-то найдете в телеграме, то кодовое слово - "я дивергент".
Слэк ЖэЭс Клана: Yahor Vaziyanau.
Average Chart.js fan
Average D3.js enjoyer
Approved by
tldr; Это библиотека для визуализации данных;
D3 = Data Driven Documents
D3.js представляет из себя систему независимых модулей (около 30) которые были спроектированы чтобы работать "вместе" как трансформеры, т.е. любой график "собирается" на основе определенных модулей.
D3.js (последняя версия на момент апреля 2021 - v6.7.0) поддерживает все современные версии браузеров таких как:
Также стоит проверять поддержку для каждого модуля D3.js отдельно, к примеру модуль d3-selection в мажорных версиях D3.js имеет разную имплементацию (Selectors API Level 1).
npm i d3
или yarn add d3
/* USAGE */
// Typical ES6 modules
import * as d3 from "d3";
import * as d3GeoProjection from "d3-geo-projection";
// OR (support "TreeShaking")
import {select, selectAll} from "d3-selection";
import {geoPath} from "d3-geo";
import {geoPatterson} from "d3-geo-projection";
// Node.js
var d3 = require("d3"),
jsdom = require("jsdom");
var document = jsdom.jsdom(),
svg = d3.select(document.body).append("svg");
P.S. Эта статья всегда в топе выдачи почти-что на любой запрос связанный c D3.js.
ВЫ ЗДЕСЬ
СКОЛЬКО ТЫ ЗАРАБАТЫВАЕШЬ РАЗ ИСПОЛЬЗОВАЛ D3.js?
<!DOCTYPE html>
<meta charset="utf-8">
<style type="text/css">
/* 13. Basic Styling with CSS */
/* Style the lines by removing the fill and applying a stroke */
.line {
fill: none;
stroke: #ffab00;
stroke-width: 3;
}
.overlay {
fill: none;
pointer-events: all;
}
/* Style the dots by assigning a fill and stroke */
.dot {
fill: #ffab00;
stroke: #fff;
}
.focus circle {
fill: none;
stroke: steelblue;
}
</style>
<!-- Body tag is where we will append our SVG and SVG objects-->
<body>
</body>
<!-- Load in the d3 library -->
<script src="https://d3js.org/d3.v5.min.js"></script>
<script>
// 2. Use the margin convention practice
var margin = {top: 50, right: 50, bottom: 50, left: 50}
, width = window.innerWidth - margin.left - margin.right // Use the window's width
, height = window.innerHeight - margin.top - margin.bottom; // Use the window's height
// The number of datapoints
var n = 21;
// 5. X scale will use the index of our data
var xScale = d3.scaleLinear()
.domain([0, n-1]) // input
.range([0, width]); // output
// 6. Y scale will use the randomly generate number
var yScale = d3.scaleLinear()
.domain([0, 1]) // input
.range([height, 0]); // output
// 7. d3's line generator
var line = d3.line()
.x(function(d, i) { return xScale(i); }) // set the x values for the line generator
.y(function(d) { return yScale(d.y); }) // set the y values for the line generator
.curve(d3.curveMonotoneX) // apply smoothing to the line
// 8. An array of objects of length N. Each object has key -> value pair, the key being "y" and the value is a random number
var dataset = d3.range(n).map(function(d) { return {"y": d3.randomUniform(1)() } })
// 1. Add the SVG to the page and employ #2
var svg = d3.select("body").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
// 3. Call the x axis in a group tag
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(xScale)); // Create an axis component with d3.axisBottom
// 4. Call the y axis in a group tag
svg.append("g")
.attr("class", "y axis")
.call(d3.axisLeft(yScale)); // Create an axis component with d3.axisLeft
// 9. Append the path, bind the data, and call the line generator
svg.append("path")
.datum(dataset) // 10. Binds data to the line
.attr("class", "line") // Assign a class for styling
.attr("d", line); // 11. Calls the line generator
// 12. Appends a circle for each datapoint
svg.selectAll(".dot")
.data(dataset)
.enter().append("circle") // Uses the enter().append() method
.attr("class", "dot") // Assign a class for styling
.attr("cx", function(d, i) { return xScale(i) })
.attr("cy", function(d) { return yScale(d.y) })
.attr("r", 5)
.on("mouseover", function(a, b, c) {
console.log(a)
this.attr('class', 'focus')
})
.on("mouseout", function() { })
// .on("mousemove", mousemove);
// var focus = svg.append("g")
// .attr("class", "focus")
// .style("display", "none");
// focus.append("circle")
// .attr("r", 4.5);
// focus.append("text")
// .attr("x", 9)
// .attr("dy", ".35em");
// svg.append("rect")
// .attr("class", "overlay")
// .attr("width", width)
// .attr("height", height)
// .on("mouseover", function() { focus.style("display", null); })
// .on("mouseout", function() { focus.style("display", "none"); })
// .on("mousemove", mousemove);
// function mousemove() {
// var x0 = x.invert(d3.mouse(this)[0]),
// i = bisectDate(data, x0, 1),
// d0 = data[i - 1],
// d1 = data[i],
// d = x0 - d0.date > d1.date - x0 ? d1 : d0;
// focus.attr("transform", "translate(" + x(d.date) + "," + y(d.close) + ")");
// focus.select("text").text(d);
// }
</script>
import { Component, OnInit, ViewChild } from '@angular/core';
@Component({
selector: 'component',
template: `<div #chartContainer></div>`,
styleUrls: ['./name.component.scss']
})
export class NameComponent implements OnInit {
@ViewChild('chartContainer', { static: false }) chartContainer: ElementRef
constructor() { }
ngOnInit(): void {
if (this.chartContainer.nativeElement) {
// D3 is going BRRRRRRRRRRRRRRRRRRRRR!!!
}
}
}
* Angular используется только для получения DOM элементов.
function App {
const divRef = React.useRef()
useEffect(()=>{ d3.init(divRef.current); return d3.destroy(divRef.current) }, [])
return (
<div ref={divRef}></div>
)
}
* React используется только для получения DOM элементов.
Суть остается такой же - получение ссылок на DOM элементы с помощью фреимворков, а дальше использовать непосредственно саму библиотеку.
Так же существуют уже готовые различные "обертки-библиотеки" над D3.js для фреимворков, как пример - react-d3-library.
Но я Вам не советую их использовать, потому что проще получить ссылку на элемент, чем скачивать стороннюю библиотеку, которая (из моего опыта) чаще всего плохо поддерживается и плохо кастомизируется.
// For ES6 modules
import { timeFormat as d3TimeFormat } from 'd3-time-format'
// Formatter
var formatTime = d3.timeFormat("%B %d, %Y");
formatTime(new Date); // "June 30, 2015"
// Parser
var parseTime = d3.timeParse("%B %d, %Y");
parseTime("June 30, 2015"); // Tue Jun 30 2015 00:00:00 GMT-0700 (PDT)
var expense = {"name":"jim","amount":34,"date":"11/12/2015"};
var parser = d3.timeParse("%m/%d/%Y");
expense.date = parser(expense.date);
console.log(expense);
// => {name: "jim", amount: 34, date: Thu Nov 12 2015 00:00:00 GMT-0500 (EST)}
var date = d3.timeParse("%A, %B %-d, %Y")("Wednesday, November 12, 2014");
console.log(date);
// => Wed Nov 12 2014 00:00:00 GMT-0500 (EST)
time = d3.timeParse("%m/%d/%Y %H:%M:%S %p")("1/2/2014 8:22:05 AM");
console.log(time);
// => Thu Jan 02 2014 08:22:05 GMT-0500 (EST)
var hourParser = d3.timeParse("%I:%M%p");
var time = hourParser("10:34pm");
var hour = d3.timeHour.round(time);
console.log(hour);
// => Mon Jan 01 1900 23:00:00 GMT-0500
var hourFormater = d3.timeFormat("%I:%M%p")
console.log(hourFormater(hour));
// => 11:00PM
// ES6 modules
import { format as d3Format } from 'd3-format'
d3.format(".0%")(0.123); // rounded percentage, "12%"
d3.format("($.2f")(-3.5); // localized fixed-point currency, "(£3.50)"
d3.format("+20")(42); // space-filled and signed, " +42"
d3.format(".^20")(42); // dot-filled and centered, ".........42........."
d3.format(".2s")(42e6); // SI-prefix with two significant digits, "42M"
d3.format("#x")(48879); // prefixed lowercase hexadecimal, "0xbeef"
d3.format(",.2r")(4223); // grouped thousands with two significant digits, "4,200"
d3.format("s")(1500); // "1.50000k"
d3.format("~s")(1500); // "1.5k"
d3.format(".2")(42); // "42"
d3.format(".2")(4.2); // "4.2"
d3.format(".1")(42); // "4e+1"
d3.format(".1")(4.2); // "4"
// We can get our own formatters (new FormatSpecifier) => new FormatSpecifier({type: "s"}) and we can cusomize them...
FormatSpecifier {
"fill": " ",
"align": ">",
"sign": "-",
"symbol": "",
"zero": false,
"width": undefined,
"comma": false,
"precision": undefined,
"trim": false,
"type": "s"
}
// CSV data
name,amount,date
jim,34.0,11/12/2015
carl,120.11,11/12/2015
jim,45.0,12/01/2015
stacy,12.00,01/04/2016
stacy,34.10,01/04/2016
stacy,44.80,01/05/2016
// After parsing
var expenses = [
{"name":"jim","amount":34,"date":"11/12/2015"},
{"name":"carl","amount":120.11,"date":"11/12/2015"},
{"name":"jim","amount":45,"date":"12/01/2015"},
{"name":"stacy","amount":12.00,"date":"01/04/2016"},
{"name":"stacy","amount":34.10,"date":"01/04/2016"},
{"name":"stacy","amount":44.80,"date":"01/05/2016"}
];
var expensesByName = d3.nest()
.key(function(d) { return d.name; })
.entries(expenses);
// Result
expensesByName = [
{"key":"jim","values":[
{"name":"jim","amount":34,"date":"11/12/2015"},
{"name":"jim","amount":45,"date":"12/01/2015"}
]},
{"key":"carl","values":[
{"name":"carl","amount":120.11,"date":"11/12/2015"}
]},
{"key":"stacy","values":[
{"name":"stacy","amount":12.00,"date":"01/04/2016"},
{"name":"stacy","amount":34.10,"date":"01/04/2016"},
{"name":"stacy","amount":44.80,"date":"01/05/2016"}
]}
];
// To sum up
var expensesCount = d3.nest()
.key(function(d) { return d.name; })
.rollup(function(v) { return v.length; })
.entries(expenses);
console.log(JSON.stringify(expensesCount));
// => [{"key":"jim","values":2},{"key":"carl","values":1},{"key":"stacy","values":3}]
// Multi-level nesting
var expensesTotalByDay = d3.nest()
.key(function(d) { return d.name; })
.key(function(d) { return d.date; })
.rollup(function(v) { return d3.sum(v, function(d) { return d.amount; }); })
.object(expenses);
console.log(JSON.stringify(expensesTotalByDay));
// => {"jim":{"11/12/2015":34,"12/01/2015":45},
// "carl":{"11/12/2015":120.11},
// "stacy":{"01/04/2016":46.1,"01/05/2016":44.8}}
var map = d3.map([{name: "foo"}, {name: "bar"}], function(d) { return d.name; });
map.get("foo"); // {"name": "foo"}
map.get("bar"); // {"name": "bar"}
map.get("baz"); // undefined
var map = d3.map()
.set("foo", 1)
.set("bar", 2)
.set("baz", 3);
map.get("foo"); // 1
d3.set(["foo", "bar", "foo", "baz"]).values(); // "foo", "bar", "baz"
var a = [0, 10, 30];
d3.quantile(a, 0); // 0
d3.quantile(a, 0.5); // 10
d3.quantile(a, 1); // 30
d3.quantile(a, 0.25); // 5
d3.quantile(a, 0.75); // 20
d3.quantile(a, 0.1); // 2
const array = [{foo: 42}, {foo: 91}];
d3.least(array, (a, b) => a.foo - b.foo); // {foo: 42}
d3.least(array, (a, b) => b.foo - a.foo); // {foo: 91}
d3.least(array, a => a.foo); // {foo: 42}
d3.selectAll("p")
.attr("class", "graf")
.style("color", "red");
// Same as
const p = d3.selectAll("p");
p.attr("class", "graf");
p.style("color", "red");
d3.select("body")
.append("svg")
.attr("width", 960)
.attr("height", 500)
.append("g")
.attr("transform", "translate(20,20)")
.append("rect")
.attr("width", 920)
.attr("height", 460);
d3.selectAll("p").on("click", function(event) {
d3.select(this).style("color", "red");
});
d3.selectAll("div").append(() => document.createElement("p"));
selection.select(function() {
return this.parentNode.insertBefore(this.cloneNode(deep), this.nextSibling);
});
d3.create("svg") // equivalent to svg:svg
d3.create("svg:svg") // more explicitly
d3.create("svg:g") // an SVG G element
d3.create("g") // an HTML G (unknown) element
const div = d3.select("body")
.selectAll("div")
.data([4, 8, 15, 16, 23, 42])
.enter().append("div")
.text(d => d);
<div>4</div>
<div>8</div>
<div>15</div>
<div>16</div>
<div>23</div>
<div>42</div>
var data = d3.range(50);
var colors = d3.scaleQuantize()
.domain([0,50])
.range(["#5E4FA2", "#3288BD", "#66C2A5", "#ABDDA4", "#E6F598",
"#FFFFBF", "#FEE08B", "#FDAE61", "#F46D43", "#D53E4F", "#9E0142"]);
colors(1)
var c = d3.color("steelblue"); // {r: 70, g: 130, b: 180, opacity: 1}
var c = d3.hsl("steelblue"); // {h: 207.27…, s: 0.44, l: 0.4902…, opacity: 1}
var svg = d3.select("#divContinuous").append("svg").attr("width", 1000).attr("height",400)
// Create data
var data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]
// Option 1: give 2 color names
var myColor = d3.scaleLinear().domain([1,10])
.range(["white", "blue"])
svg.selectAll(".firstrow").data(data).enter().append("circle").attr("cx", function(d,i){return 30 + i*60}).attr("cy", 50).attr("r", 19).attr("fill", function(d){return myColor(d) })
// Option 2: Color brewer.
// Include <script src="https://d3js.org/d3-scale-chromatic.v1.min.js"></script> in your code!
var myColor = d3.scaleSequential().domain([1,10])
.interpolator(d3.interpolatePuRd);
svg.selectAll(".secondrow").data(data).enter().append("circle").attr("cx", function(d,i){return 30 + i*60}).attr("cy", 150).attr("r", 19).attr("fill", function(d){return myColor(d) })
// Option 3: Viridis.
// Include <script src="https://d3js.org/d3-scale-chromatic.v1.min.js"></script> in your code!
var myColor = d3.scaleSequential().domain([1,10])
.interpolator(d3.interpolateViridis);
svg.selectAll(".secondrow").data(data).enter().append("circle").attr("cx", function(d,i){return 30 + i*60}).attr("cy", 250).attr("r", 19).attr("fill", function(d){return myColor(d) })
// Example of formatting function for Euro locale
var NL = d3.locale ({
"decimal": ".",
"thousands": ",",
"grouping": [3],
"currency": ["", "€"],
"dateTime": "%a %b %e %X %Y",
"date": "%m/%d/%Y",
"time": "%H:%M:%S",
"periods": ["AM", "PM"],
"days": ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"],
"shortDays": ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"],
"months": ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"],
"shortMonths": ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]
})
var eur = NL.numberFormat("$,.2f");
console.log(eur(3))
// => "3.00€"
// For german days and month
d3.timeFormatDefaultLocale({
"decimal": ",",
"thousands": ".",
"grouping": [3],
"currency": ["€", ""],
"dateTime": "%a %b %e %X %Y",
"date": "%d.%m.%Y",
"time": "%H:%M:%S",
"periods": ["AM", "PM"],
"days": ["Sonntag", "Montag", "Dienstag", "Mittwoch", "Donnerstag", "Freitag", "Samstag"],
"shortDays": ["So", "Mo", "Di", "Mi", "Do", "Fr", "Sa"],
"months": ["Januar", "Februar", "März", "April", "Mai", "Juni", "Juli", "August", "September", "Oktober", "November", "Dezember"],
"shortMonths": ["Jan", "Feb", "Mär", "Apr", "Mai", "Jun", "Jul", "Aug", "Sep", "Okt", "Nov", "Dez"]
})
d3.csvParse("foo,bar\n1,2"); // [{foo: "1", bar: "2"}, columns: ["foo", "bar"]]
d3.tsvParse("foo\tbar\n1\t2"); // [{foo: "1", bar: "2"}, columns: ["foo", "bar"]]
d3.csvFormat([{foo: "1", bar: "2"}]); // "foo,bar\n1,2"
d3.tsvFormat([{foo: "1", bar: "2"}]); // "foo\tbar\n1\t2"
var psv = d3.dsvFormat("|");
console.log(psv.parse("foo|bar\n1|2")); // [{foo: "1", bar: "2"}, columns: ["foo", "bar"]]
// CSV file
cities.csv:
city,state,population,land area
seattle,WA,652405,83.9
new york,NY,8405837,302.6
boston,MA,645966,48.3
kansas city,MO,467007,315.0
d3.csv("/data/cities.csv").then(function(data) {
console.log(data[0]);
});
// => {city: "seattle", state: "WA", population: "652405", land area: "83.9"}
d3.csv("/data/cities.csv").then(function(data) {
data.forEach(function(d) {
d.population = +d.population;
d["land area"] = +d["land area"];
});
console.log(data[0]);
});
// => {city: "seattle", state: "WA", population: 652405, land area: 83.9}
d3.csv("/data/cities.csv", function(d) {
return {
city : d.city,
state : d.state,
population : +d.population,
land_area : +d["land area"]
};
}).then(function(data) {
console.log(data[0]);
});
// => {city: "seattle", state: "WA", population: 652405, land_area: 83.9}
// JSON file
[
{"name":"Andy Hunt",
"title":"Big Boss",
"age": 68,
"bonus": true
},
{"name":"Charles Mack",
"title":"Jr Dev",
"age":24,
"bonus": false
}
]
d3.json("/data/employees.json").then(function(data) {
console.log(data[0]);
});
// => {name: "Andy Hunt", title: "Big Boss", age: 68, bonus: true}
Average [name-Chart].js fan
Average D3.js enjoyer
СЛУШАТЕЛИ
By Yahor Vaziyanau
Монолог (не реклама) о том что D3.js достаточно "простая" библиотека и что ее можно и нужно использовать не только для визуализации данных.