跳至主要內容

D3 学习笔记

Kevin 吴嘉文大约 8 分钟知识笔记计算机语言

D3 为一个 JavaScript 函数库,是数据可视化工具,他能够灵活、简单地通过 web 端展示数据分析的结果。

d3 中文文档 新open in new windowd3 中文 V3 之前open in new window, 旧版本 d3 画 bar chartopen in new windowD3 绘制动态表图open in new window

导入

直接在 html 中导入 d3 来源,如:

<script src="https://d3js.org/d3.v7.min.js"></script>

当然,也可以导入其他来源的 d3 包。

数据输入

官方指南open in new window ;以下以 CSV 为例,同理可以导入 json, text, tsv 等格式

DOM 导入

直接定义成变量,primes = [2, 3, 5, 7, 11, 13];检索:primes[0]

对于 CSV 格式的可以这样导入:data = d3.csv.parse(d3.select("pre#data").text()); 通过 data[i] 访问第 i 行,每行元素以键值对形式储存。新版本的 d3 可以直接 d3.csvParse

<pre id="data">
name,mpg,cyl,disp,hp,drat,wt,qsec,vs,am,gear,carb
Mazda RX4,21,6,160,110,3.9,2.62,16.46,0,1,4,4
Mazda RX4 Wag,21,6,160,110,3.9,2.875,17.02,0,1,4,4
Datsun 710,22.8,4,108,93,3.85,2.32,18.61,1,1,4,1
Hornet 4 Drive,21.4,6,258,110,3.08,3.215,19.44,1,0,3,1
Hornet Sportabout,18.7,8,360,175,3.15,3.44,17.02,0,0,3,2
Valiant,18.1,6,225,105,2.76,3.46,20.22,1,0,3,1
Duster 360,14.3,8,360,245,3.21,3.57,15.84,0,0,3,4
Merc 240D,24.4,4,146.7,62,3.69,3.19,20,1,0,4,2
</pre>

从文件读取:

flare = FileAttachment("flare.csv").csv()
flareGist = d3.csv("https://gist.githubusercontent.com/mbostock/4063570/raw/11847750012dfe5351ee1eb290d2a254a67051d0/flare.csv")

从 API 导入:

client = DatabaseClient("Baby Names") // sample client for demonstration purposes

names = client.query(`SELECT name, gender, year, SUM(number) AS number
FROM names
WHERE year > ?
GROUP BY name, gender, year`, [1920])

数据处理

d3 数组操作open in new window

导入文件时候可以为每行进行格式:d3 时间处理open in new window

const parseDate = d3.utcParse("%Y-%m-%d");
var data = d3.csvParse(string, function(d) {
  return {
    year: parseDate(d.year), // lowercase and convert "Year" to Date
    make: d.Make, // lowercase
    model: d.Model, // lowercase
    length: +d.Length // lowercase and convert "Length" to number
  };
});

d3 解析 CSV 文件后,返回 object 对象的形式为: [{key:value},{}],每行数据以逗号隔开。可以通过 .map(func()) 对每行数据批量处理。

时间处理

旧版本时间处理,可能需要导入对应的时间库:<script src="https://d3js.org/d3-time-format.v3.min.js"></script>

var parseTime = d3.timeParse("%Y-%m");
year_and_month = parseTime(d.FinancialYear); //返回 date 对象

数组统计

常用:min, max, sum, mean, variance, deviation

排序:arr.ascending() 默认使用数值,也可以自定义比较器:

function ascending(a, b) {
  return a < b ? -1 : a > b ? 1 : a >= b ? 0 : NaN;  //
}

二分搜索:d3.bisectLeft(arr, x) 返回小于 x 的最大数的位置

数据分组

网友笔记open in new window

console.log(data[0], typeof data);
var d_nestByYear = d3.nest()
                    .key(d => d.year)   // 根据 year 属性分组
					.key(d => d.month)  // 再根据 month 分组
                    .entries(data);  // 应用在 data 数据集上

.entries 替换为 .map,得到的数据形式不同,但结构大同小异

也可以对数组进行聚合操作,聚合后的数据通过 d.values 访问:

var d_nestByYear = d3.nest()
    .key(d => d.year)
    .rollup(function(leaves){
        return {"length":leaves.length,"mean_score":d3.mean(leaves,d=>d.ConditionScore)}   // 聚合多个类别
    })
    .entries(data);

语法笔记

HTML 元素选择与操作

关于本部分的 更多 APIopen in new window

选中展示位置:

  • d3.select() 返回第一个匹配的元素

  • d3.selectAll() 返回所有匹配的元素

对于选中的对象,我们可以用 .style().text() 等方式为其 html 添加内容,如:我们 html 中存在 circle 对象:

<circle cx="40" cy="60" r="10"></circle>

d3 通过以下方式更改 circle 对应的 html 内容:

var circles = d3.select('body').selectAll('circle');  //也可以不用 select body
circles.style("fill", "steelblue");

以上操作相当于:

<circle cx="40" cy="60" r="10" style="fill: steelblue;"></circle>

除了 style 还支持:

  • circles.data(data):将一个数组绑定到选择集上,并且数据各项值分别与选择集的各元素对应
  • circles.attr("r", function(d) { return Math.sqrt(d); }):修改元素已有属性,此处 r 的值将设置为 sqrt(data),需要提前绑定数据;
  • circles.enter().append('circle'):在选择集后面添加元素

添加删除元素:

var body = d3.select("body");
body.append("div");  //在一个元素内添加一个元素
body.remove();  // 删除元素

元素与数据配对:假设我们目前有 3 个 circle;但绑定的数据为 4 个,那么可以采用 enter() 来选中多出的元素。反之,使用 exit()

var data2 = [32, 57, 112, 293]
var circles = body.selectAll('circle').data(data2);  // 选择 body 元素下的所有 circle 元素
var circleEnter = circles.enter().append("circle");  // 在元素集中添加一个额外的 circle 需要使用 enter 占位;
circleEnter.attr("cy", 60);  // append 函数返回添加的元素对象,可以直接修改他的属性

var data3 = [32, 57]
var circles = svg.selectAll("circle").data(data3);
circles.exit().remove();  // 删除没有数据绑定的元素

SVG 相关函数

SVG 可缩放 矢量 图形,用于回执可视化的图形;其使用 xml 来定义图形,大部分的浏览器都支持 SVG,可以将 SVG 文本直接嵌入到 HTML 中显示。SVG 对象中的元素改变时,其图形也会改变,因此可以进行动态的图形化展示。svg 基础语法|菜鸟教程open in new window

通过 D3 对 SVG 内容进行操作,先获得 SVG 画布。

  • d3.select("body").append("svg"):添加新画布
  • d3.select("svg"):选择已有画布

柱状图案例

以下以 d3 旧版本代码做演示,绘制柱状图:

var margin = { top: 50, right: 10, bottom: 30, left: 50 }
var width = 500 - margin.left - margin.right,
    height = 250 - margin.top - margin.bottom,
    barWidth = 30,
    barOffset = 10;

d3 比例尺

d3 官方示例open in new window

比例尺可以看做一个函数,他将变量映射到画布的位置上。我们需要传入变量的域 domain 和画布的范围 range。以下先定义比例尺,如何使用参考绘图与坐标轴部分。

var sampleData = d_nestByYear.map(d => d.values.length);
// y 值对应 [45, 543, 123,..., 500]
var sampleYear = d_nestByYear.map(d => d.key);
// x 值对应年份 [2000, 2001, ...]

var yScale = d3.scale.linear()  // d3 线性比例尺
    .domain([0, d3.max(sampleData)])  // 输入 y 的域。即 [0, 500] 区间
.range([height, 0])  // 输入 y 在画布上的范围,即最顶端 0,到最底端 height.
var xScale = d3.scale.ordinal()  // 离散型
    .domain(sampleYear)  // 此处 x 的域就是 sampleYear array 本身。
    .rangeBands([0, width])  

绘图

在绘图时,应用比例尺来来决定柱状图的位置 x, y, height。比例尺自动根据画布大小调整表格大小和位置。

.attr('height', d=>yScale(d.values.length)) ;若 d.values.length[10,20,30] ,比例尺对应画布range[0,200]。那么 yScale(d.values.length) == [0,100,200]

var q1 = d3.select("#question1")
	.append("svg")  // svg 矢量图
    .attr('width', width + margin.left + margin.right)  // 用 margin 留出坐标轴的空间
    .attr('height', height +  margin.top + margin.bottom)
    .selectAll("rect").data(d_nestByYear)
    .enter().append('rect')
    .style({"fill":"steelblue"})
    .attr('width', barWidth)
    .attr('height', d => height - yScale(d.values.length))
    .attr('x', (d, i) => 2 + margin.left + xScale(d.key))
	.attr('y', d => margin.top + yScale(d.values.length))
	// y=0 时为顶部,因此做个变换,然表格翻转

添加坐标轴

var xAxis = d3.svg.axis()
    .scale(xScale)
    .orient('bottom')  // 位于底部
    .tickValues(sampleYear)  // 坐标轴上显示的标签

var xTicks = d3.select("#question1 svg").append("g").call(xAxis)
xTicks.attr('transform', "translate(" + margin.left + "," + (height + margin.top) + ")")
    .selectAll('path').style({ fill: 'none', stroke: "#000" })

var yAxis = d3.svg.axis()
    .scale(yScale)
    .orient('left')
    .ticks(10)

var yTicks = d3.select("#question1 svg").append("g").call(yAxis)
yTicks.attr('transform', "translate(" + margin.left + "," + margin.top + ")")
    .selectAll('path').style

添加其他标签,如标题、节点信息等,可以通过 <text> 节点来添加,并设计对应的 x,y 属性来控制位置,如添加节点信息:

var numbers = q1.append("g")
    .style({'fill':'white','font-size':font_size})
    .selectAll("text").data(d_nestByYear)
    .enter().append('text').text(d=>d.values.length)
    .attr('x', (d, i) => 4 + margin.left + xScale(d.key))
    .attr('y',  d => margin.top + yScale(d.values.length)+font_size)

折线图案例

D3 V3open in new window D3 V4+open in new window

D3 中提供 shape 函数,包括 line, arc 等等。利用他们能够轻松地绘制折线图、饼图等形状。以下以折线图为案例,坐标轴添加方式同上。

在选中svg 画布后,绑定数据到我们的画布上,这边使用 .datum()

var q2 = d3.select("#question2").append("svg").datum(data)
    .attr('width', width + margin.left + margin.right)
    .attr('height', height + margin.top + margin.bottom);

构造 line 类别,他是一个折线图数据生成器。 line 中保存画布上的坐标数据 (x,y) 。因此需要用比例尺进行转换。

var line = d3.svg.line()
    .x(d=> xScale(d.key))  // 映射原数据到画布上的坐标
    .y(d => yScale(d.values.mean_score))

生成折线图时,为了更好地调整 margin 位置,可以考虑将所有线和点信息放在一个 g 元素下。

var q2_line = q2.append("g")               
                .attr('transform', `translate(${margin.left},${margin.top})`) 

q2_line.append("path")  // 生成折线图
    .attr("class", "line")
    .attr("d", line);

q2_line.selectAll(".dot")  // 绘制图上对应的点
    .data(d_nestByYear)
    .enter().append("circle")
    .style({'fill': 'white', stroke: 'steelblue'})
    .attr("class", "dot")
    .attr("cx", line.x())
    .attr("cy", line.y())
    .attr("r", 3.5);

多色散点图案例

散点图案例open in new window

散点图的添加方式与上一节这项图中的散点添加方式一样。使用 svg.dot.circle 进行添加。以下案例为散点添加了不同色彩。

var color = d3.scale.category10();

q3.selectAll(".dot")
    .data(flat_data3)
    .enter().append("circle")
    .attr("class", "dot")
    .attr("r", 5)
    .attr("cx", d=>xScale(d.Manufacturer))
    .attr("cy", d=>yScale(d.mean_score))
    .style("fill", function(d) { return color(d.VehicleType); });

以上方式可以对三维数组进行可视化,此处的 flat_data3[{Manufacturer:xx,mean_score:xx,VehicleType:xx}]。除了坐标轴外,需要使用 legend 对色彩提供信息:

var legend = q3.append("g")
    .selectAll(".legend")
    .data(color.domain())
    .enter().append("g")
    .attr("class", "legend")
    .attr("transform", function(d, i) { return "translate(0," + i * 20 + ")"; });

legend.append("rect")
    .attr("x", width - font_size)
    .attr("width", font_size)
    .attr("height",font_size)
    .style("fill", color);

legend.append("text")
    .attr("x", width - 20)
    .attr("y", 9)
    .style("text-anchor", "end")
    .text(function(d) { return d; });
上次编辑于:
贡献者: kevinng77