freeCodeCamp 旅途17 - D3
D3
D3.js,也叫 D3,表示数据驱动文档。 D3 是一个在浏览器里创建动态可视化数据的 JavaScript 库。它基于 web 标准,即 HTML、CSS 和 SVG 技术。
D3 获取输入数据并且映射到相应的可视化内容里。它支持不同的数据格式。D3 允许将数据绑定到 DOM 上。你可以使用 D3 的内建方法通过 HMTL 或者 SVG 元素实现数据可视化。以下例子中的 d3 是 D3 的引用
用 D3 实现数据可视化:用 D3 给文档添加元素
D3 有多种方法可以用来在文档中增加元素、修改元素。
select()
方法用来从文档中选择元素,它以你查询的元素名称作为参数,返回第一个符合条件的 HTML 节点。以下是一个例子:const anchor = d3.select("a");
append()
方法以你想添加到文档中的元素作为参数,给选中的元素添加一个 HTML 节点,返回那个节点的句柄。
text()
方法既可以给节点设置新的文本,也可以获取节点的当前文本。 如果要设置文字内容,需要在圆括号中传入一个 string(字符串)类型的参数。
// 选择无序列表、添加列表项和文字
d3.select("ul")
.append("li")
.text("Very important item");
d3.select('body')
.append('h1')
.text('Learning D3');
用 D3 实现数据可视化:用 D3 选择一组元素
selectAll()
方法选择一组元素。它以 HTML 节点数组的形式返回该文本中所有匹配所输入字符串的对象。选择文本中所有锚标签: const anchors = d3.selectAll("a");
;选择所有 li 标签并设置内容: d3.selectAll('li').text('list item');
。
用 D3 实现数据可视化:使用 D3 中的数据
第一步是让 D3 知道数据。data()
方法选择连接着数据的 DOM 元素,数据集作为参数传递给该方法。
常见的方法是在文档中为数据集中的每一个数据创建一个元素,为此,你可以使用 D3 的enter()
方法。
当enter()
和data()
方法一起使用时,它把从页面中选择的元素和数据集中的元素作比较。如果页面中选择的元素较少则创建缺少的元素。
// enter()方法发现页面中没有 li 元素,但是需要 3 个
<body>
<ul></ul>
<script>
const dataset = ["a", "b", "c"];
d3.select("ul").selectAll("li")
.data(dataset)
.enter()
.append("li")
.text("New item");
</script>
</body>
// 没有元素就创建元素
<body>
<script>
const dataset = [12, 31, 22, 17, 25, 18, 29, 14, 9];
d3.select('body')
.selectAll('h2')
.data(dataset)
.enter()
.append('h2')
.text('New Title');
</script>
</body>
用 D3 实现数据可视化:使用 D3 中的动态数据
text()
方法以字符串或者回调函数作为参数:selection.text((d) => d)
const dataset = [12, 31, 22, 17, 25, 18, 29, 14, 9];
d3.select("body").selectAll("h2")
.data(dataset)
.enter()
.append("h2")
.text((d) => d + ' ' + 'USD');
用 D3 实现数据可视化:给元素添加内联样式
D3 可以使用style()
方法为动态元素添加内联 CSS 样式表。style()
方法以用逗号分隔的键值对作为参数: selection.style("color","blue");
const dataset = [12, 31, 22, 17, 25, 18, 29, 14, 9];
d3.select("body").selectAll("h2")
.data(dataset)
.enter()
.append("h2")
.text((d) => (d + " USD"))
.style("font-family", "verdana")
用 D3 实现数据可视化:根据数据更改样式
在style()
方法中使用回调函数为不同元素改变样式。例如,你想将值小于 20 的数据点设置为蓝色,其余设置为红色。你可以在style()方法中使用包含条件逻辑的回调函数。回调函数以d作为参数来表示一个数据点:
// style()方法不仅仅可以设置color——它也适用于其他 CSS 属性
selection.style("color", (d) => {
return d < 20 ? "blue" : "red"
});
const dataset = [12, 31, 22, 17, 25, 18, 29, 14, 9];
d3.select("body").selectAll("h2")
.data(dataset)
.enter()
.append("h2")
.text((d) => (d + " USD"))
.style("color", d => d < 20 ? "red" : "green")
用 D3 实现数据可视化:用 D3 添加 Class
D3 中的attr()
方法可以给元素添加任何 HTML 属性,包括类名称。attr()
方法和style()
的使用方法一样。它以逗号分隔的键值对为参数使用回调函数。
<style>
.bar {
width: 25px;
height: 100px;
display: inline-block;
background-color: blue;
}
</style>
<body>
<script>
const dataset = [12, 31, 22, 17, 25, 18, 29, 14, 9];
d3.select("body").selectAll("div")
.data(dataset)
.enter()
.append("div")
.attr("class", "bar");
</script>
</body>
用 D3 实现数据可视化:动态更新元素的高度
只需两步创建一个简单的条形图:
-
为每一个数组中的数据点都创建一个
div
-
为每个
div
动态分配高度值,在style()
方法中使用回调函数将高度值设置为数据大小
<style>
.bar {
width: 25px;
height: 100px;
display: inline-block;
background-color: blue;
}
</style>
<body>
<script>
const dataset = [12, 31, 22, 17, 25, 18, 29, 14, 9];
d3.select("body").selectAll("div")
.data(dataset)
.enter()
.append("div")
.attr("class", "bar")
.style("height", d => d);
</script>
</body>
用 D3 实现数据可视化:更改条形图的显示方式
<style>
.bar {
width: 25px;
height: 100px;
margin: 2px;
display: inline-block;
background-color: blue;
}
</style>
<body>
<script>
const dataset = [12, 31, 22, 17, 25, 18, 29, 14, 9];
d3.select("body").selectAll("div")
.data(dataset)
.enter()
.append("div")
.attr("class", "bar")
.style("height", (d) => (d * 10 + "px"))
</script>
</body>
用 D3 实现数据可视化:了解 D3 中的 SVG
当使用相对单位(例如vh
、vw
或者百分比)时,CSS 是可伸缩的。但是在实现数据可视化的时候 SVG 更加的灵活。
<style>
svg {
background-color: pink;
}
</style>
<body>
<script>
const dataset = [12, 31, 22, 17, 25, 18, 29, 14, 9];
const w = 500;
const h = 100;
const svg = d3.select("body")
.append("svg")
.attr("width", w)
.attr("height", h)
</script>
</body>
用 D3 实现数据可视化:用 SVG 显示形状
<body>
<script>
const dataset = [12, 31, 22, 17, 25, 18, 29, 14, 9];
const w = 500;
const h = 100;
const svg = d3.select("body")
.append("svg")
.attr("width", w)
.attr("height", h)
.append('rect')
.attr("width", 25)
.attr("height", 100)
.attr("x", 0)
.attr("y", 0)
</script>
</body>
用 D3 实现数据可视化:为集合中的每个数据点创建一个 Bar
<body>
<script>
const dataset = [12, 31, 22, 17, 25, 18, 29, 14, 9];
const w = 500;
const h = 100;
const svg = d3.select("body")
.append("svg")
.attr("width", w)
.attr("height", h);
svg.selectAll("rect")
.data(dataset)
.enter()
.append("rect")
.attr("x", 0)
.attr("y", 0)
.attr("width", 25)
.attr("height", 100);
</script>
</body>
用 D3 实现数据可视化:动态设置每个 Bar 的坐标
<body>
<script>
const dataset = [12, 31, 22, 17, 25, 18, 29, 14, 9];
const w = 500;
const h = 100;
const svg = d3.select("body")
.append("svg")
.attr("width", w)
.attr("height", h);
svg.selectAll("rect")
.data(dataset)
.enter()
.append("rect")
// i 表示索引
.attr("x", (d, i) => {
return i * 30
})
.attr("y", 0)
.attr("width", 25)
.attr("height", 100);
</script>
</body>
用 D3 实现数据可视化:动态更改每个条的高度
<body>
<script>
const dataset = [12, 31, 22, 17, 25, 18, 29, 14, 9];
const w = 500;
const h = 100;
const svg = d3.select("body")
.append("svg")
.attr("width", w)
.attr("height", h);
svg.selectAll("rect")
.data(dataset)
.enter()
.append("rect")
.attr("x", (d, i) => i * 30)
.attr("y", 0)
.attr("width", 25)
.attr("height", (d, i) => {
return d * 3
});
</script>
</body>
用 D3 实现数据可视化:反转 SVG 元素
<body>
<script>
const dataset = [12, 31, 22, 17, 25, 18, 29, 14, 9];
const w = 500;
const h = 100;
const svg = d3.select("body")
.append("svg")
.attr("width", w)
.attr("height", h);
svg.selectAll("rect")
.data(dataset)
.enter()
.append("rect")
.attr("x", (d, i) => i * 30)
.attr("y", (d, i) => {
// 高度关系为 y = h - m * d,其中m是对数据点进行缩放的比例
return 100 - d * 3
})
.attr("width", 25)
.attr("height", (d, i) => 3 * d);
</script>
</body>
用 D3 实现数据可视化:更改 SVG 元素的颜色
在 SVG 中,rect
图形用fill
属性着色,它支持十六进制代码、颜色名称、rgb 值以及更复杂的选项,比如渐变和透明。
<body>
<script>
const dataset = [12, 31, 22, 17, 25, 18, 29, 14, 9];
const w = 500;
const h = 100;
const svg = d3.select("body")
.append("svg")
.attr("width", w)
.attr("height", h);
svg.selectAll("rect")
.data(dataset)
.enter()
.append("rect")
.attr("x", (d, i) => i * 30)
.attr("y", (d, i) => h - 3 * d)
.attr("width", 25)
.attr("height", (d, i) => 3 * d)
.attr("fill", "navy")
</script>
</body>
用 D3 实现数据可视化:给 D3 元素添加标签
D3 允许使用 SVG 的text
元素给图形元素贴标签,例如给条形图中的各组都贴上标签。text
元素也需要x
和y
属性来指定其放置在 SVG 画布上的位置,它也需要能够获取数据来显示数据值。
<body>
<script>
const dataset = [12, 31, 22, 17, 25, 18, 29, 14, 9];
const w = 500;
const h = 100;
const svg = d3.select("body")
.append("svg")
.attr("width", w)
.attr("height", h);
svg.selectAll("rect")
.data(dataset)
.enter()
.append("rect")
.attr("x", (d, i) => i * 30)
.attr("y", (d, i) => h - 3 * d)
.attr("width", 25)
.attr("height", (d, i) => 3 * d)
.attr("fill", "navy");
svg.selectAll("text")
.data(dataset)
.enter()
.append("text")
.attr("x", (d, i) => i * 30)
.attr("y", (d, i) => h - 3 * d - 3)
.text((d, i) => d)
</script>
<body>
用 D3 实现数据可视化:给 D3 标签添加样式
<body>
<script>
const dataset = [12, 31, 22, 17, 25, 18, 29, 14, 9];
const w = 500;
const h = 100;
const svg = d3.select("body")
.append("svg")
.attr("width", w)
.attr("height", h);
svg.selectAll("rect")
.data(dataset)
.enter()
.append("rect")
.attr("x", (d, i) => i * 30)
.attr("y", (d, i) => h - 3 * d)
.attr("width", 25)
.attr("height", (d, i) => d * 3)
.attr("fill", "navy");
svg.selectAll("text")
.data(dataset)
.enter()
.append("text")
.text((d) => d)
.attr("x", (d, i) => i * 30)
.attr("y", (d, i) => h - (3 * d) - 3)
.attr("font-size", "25px")
.attr("fill", "red")
</script>
</body>
用 D3 实现数据可视化:给 D3 元素添加悬停效果
<style>
.bar:hover {
fill: brown;
}
</style>
<body>
<script>
const dataset = [12, 31, 22, 17, 25, 18, 29, 14, 9];
const w = 500;
const h = 100;
const svg = d3.select("body")
.append("svg")
.attr("width", w)
.attr("height", h);
svg.selectAll("rect")
.data(dataset)
.enter()
.append("rect")
.attr("x", (d, i) => i * 30)
.attr("y", (d, i) => h - 3 * d)
.attr("width", 25)
.attr("height", (d, i) => 3 * d)
.attr("fill", "navy")
.attr("class", "bar")
svg.selectAll("text")
.data(dataset)
.enter()
.append("text")
.text((d) => d)
.attr("x", (d, i) => i * 30)
.attr("y", (d, i) => h - (3 * d) - 3);
</script>
</body>
用 D3 实现数据可视化:给 D3 元素添加工具提示
<style>
.bar:hover {
fill: brown;
}
</style>
<body>
<script>
const dataset = [12, 31, 22, 17, 25, 18, 29, 14, 9];
const w = 500;
const h = 100;
const svg = d3.select("body")
.append("svg")
.attr("width", w)
.attr("height", h);
svg.selectAll("rect")
.data(dataset)
.enter()
.append("rect")
.attr("x", (d, i) => i * 30)
.attr("y", (d, i) => h - 3 * d)
.attr("width", 25)
.attr("height", (d, i) => d * 3)
.attr("fill", "navy")
.attr("class", "bar")
.append("title")
.text(d => d)
svg.selectAll("text")
.data(dataset)
.enter()
.append("text")
.text((d) => d)
.attr("x", (d, i) => i * 30)
.attr("y", (d, i) => h - (d * 3 + 3))
</script>
</body>
用 D3 实现数据可视化:使用 SVG Circles 创建散点图
SVG 用circle标签来创建圆形,它和之前用来构建条形图的rect非常相像。
<body>
<script>
const dataset = [
[ 34, 78 ],
[ 109, 280 ],
[ 310, 120 ],
[ 79, 411 ],
[ 420, 220 ],
[ 233, 145 ],
[ 333, 96 ],
[ 222, 333 ],
[ 78, 320 ],
[ 21, 123 ]
];
const w = 500;
const h = 500;
const svg = d3.select("body")
.append("svg")
.attr("width", w)
.attr("height", h);
svg.selectAll("circle")
.data(dataset)
.enter()
.append("circle")
</script>
</body>
用 D3 实现数据可视化:给 Circle 元素添加属性
在 SVG 中circle
有三个主要的属性。cx
和cy
属性是坐标,它们告诉 D3 将图形的中心放在 SVG 画布的何处。半径(r
属性)给出circle
的大小。
<body>
<script>
const dataset = [
[ 34, 78 ],
[ 109, 280 ],
[ 310, 120 ],
[ 79, 411 ],
[ 420, 220 ],
[ 233, 145 ],
[ 333, 96 ],
[ 222, 333 ],
[ 78, 320 ],
[ 21, 123 ]
];
const w = 500;
const h = 500;
const svg = d3.select("body")
.append("svg")
.attr("width", w)
.attr("height", h);
svg.selectAll("circle")
.data(dataset)
.enter()
.append("circle")
.attr("cx", d => d[0])
.attr("cy", d => h - d[1])
.attr("r", 5)
</script>
</body>
用 D3 实现数据可视化:向散点图的 Circles 添加标签
text节点需要x
和y
属性来指定放置在 SVG 画布中的位置。
<body>
<script>
const dataset = [
[ 34, 78 ],
[ 109, 280 ],
[ 310, 120 ],
[ 79, 411 ],
[ 420, 220 ],
[ 233, 145 ],
[ 333, 96 ],
[ 222, 333 ],
[ 78, 320 ],
[ 21, 123 ]
];
const w = 500;
const h = 500;
const svg = d3.select("body")
.append("svg")
.attr("width", w)
.attr("height", h);
svg.selectAll("circle")
.data(dataset)
.enter()
.append("circle")
.attr("cx", (d, i) => d[0])
.attr("cy", (d, i) => h - d[1])
.attr("r", 5);
svg.selectAll("text")
.data(dataset)
.enter()
.append("text")
.attr("x", d => d[0] + 5)
.attr("y", d => h - d[1])
.text(d => d[0]+", "+d[1])
</script>
</body>
用 D3 实现数据可视化:用 D3 创建线性比例
D3 中,比例尺可帮助布局数据。Scales
是告诉程序如何将一组原始数据点映射到 SVG 画布上像素的函数。D3 有几种缩放类型。对于线性缩放(通常使用于定量数据),使用 D3 的scaleLinear()
方法:const scale = d3.scaleLinear()
<body>
<script>
const scale = d3.scaleLinear();
const output = scale(50);
d3.select("body")
.append("h2")
.text(output);
</script>
</body>
用 D3 实现数据可视化:按比例设置域和范围
默认情况下,比例尺使用同一关系(identity relationship),即输入值直接映射为输出值。但是比例尺可以更灵活更有趣。
假设有一个数据集范围为 50 到 480,这是缩放的输入信息,也被称为域(domain)。你想沿着 10 个单位到 500 个单位的x轴映射这些点到 SVG 画布上。这是输出信息,也被称为范围(range)。
domain()
和range()
方法设置缩放的值,它们都以至少有两个元素的数组为参数。下面是一个例子:
// 设置域
// 域覆盖了一组输入值
scale.domain([50, 480]);
// 设置范围
// 范围覆盖了一组输出值
scale.range([10, 500]);
scale(50) // 返回 10
scale(480) // 返回 500
scale(325) // 返回 323.37
scale(750) // 返回 807.67
d3.scaleLinear()
<body>
<script>
const scale = d3.scaleLinear();
scale.domain([250, 500]);
scale.range([10, 150])
const output = scale(50);
d3.select("body")
.append("h2")
.text(output);
</script>
</body>
使用 d3.max 和 d3.min 函数在数据集中查找最小值和最大值
D3 有两个方法——min()和max()来返回这些值:
const exampleData = [34, 234, 73, 90, 6, 52];
d3.min(exampleData) // 返回 6
d3.max(exampleData) // 返回 234
const locationData = [[1, 7],[6, 3],[8, 3]];
// 返回第一个元素中的最小值s
const minX = d3.min(locationData, (d) => d[0]);
// 在 1,6,8 中 minX 为 1
<body>
<script>
const positionData = [[1, 7, -4],[6, 3, 8],[2, 8, 3]]
const output = d3.max(positionData, d => d[2]); // 8
d3.select("body")
.append("h2")
.text(output)
</script>
</body>
用 D3 实现数据可视化:使用动态比例
const dataset = [
[ 34, 78 ],
[ 109, 280 ],
[ 310, 120 ],
[ 79, 411 ],
[ 420, 220 ],
[ 233, 145 ],
[ 333, 96 ],
[ 222, 333 ],
[ 78, 320 ],
[ 21, 123 ]
];
const w = 500;
const h = 500;
// SVG 画布边缘和散点图之间的 padding
const padding = 30;
const xScale = d3.scaleLinear()
.domain([0, d3.max(dataset, (d) => d[0])])
.range([padding, w - padding]);
<body>
<script>
const dataset = [
[ 34, 78 ],
[ 109, 280 ],
[ 310, 120 ],
[ 79, 411 ],
[ 420, 220 ],
[ 233, 145 ],
[ 333, 96 ],
[ 222, 333 ],
[ 78, 320 ],
[ 21, 123 ]
];
const w = 500;
const h = 500;
// SVG 画布边缘和图形之间的padding
const padding = 30;
// 创建 x 和 y 的比例尺
const xScale = d3.scaleLinear()
.domain([0, d3.max(dataset, (d) => d[0])])
.range([padding, w - padding]);
const yScale = d3.scaleLinear()
.domain([0, d3.max(dataset, d => d[1])])
.range([h - padding, padding])
const output = yScale(411); // 返回 30
d3.select("body")
.append("h2")
.text(output)
</script>
</body>
// 正向布局,当你为 y 坐标设置 range 时,
// 大的值(height 减去 padding)是第一个参数,小的值是第二个参数。
用 D3 实现数据可视化:使用预定义的比例放置元素
用比例尺函数为 SVG 图形设置坐标属性值。这包括rect
或者text
元素的x
和y
属性,或者circles
的cx
和cy
。以下是一个例子:
shape
.attr("x", (d) => xScale(d[0]))
<body>
<script>
const dataset = [
[ 34, 78 ],
[ 109, 280 ],
[ 310, 120 ],
[ 79, 411 ],
[ 420, 220 ],
[ 233, 145 ],
[ 333, 96 ],
[ 222, 333 ],
[ 78, 320 ],
[ 21, 123 ]
];
const w = 500;
const h = 500;
const padding = 60;
const xScale = d3.scaleLinear()
.domain([0, d3.max(dataset, (d) => d[0])])
.range([padding, w - padding]);
const yScale = d3.scaleLinear()
.domain([0, d3.max(dataset, (d) => d[1])])
.range([h - padding, padding]);
const svg = d3.select("body")
.append("svg")
.attr("width", w)
.attr("height", h);
// 创建离散的圆点
svg.selectAll("circle")
.data(dataset)
.enter()
.append("circle")
.attr("cx", d => xScale(d[0]))
.attr("cy", d => yScale(d[1]))
.attr("r", 5)
// 创建文本
svg.selectAll("text")
.data(dataset)
.enter()
.append("text")
.text((d) => (d[0] + ", "
+ d[1]))
.attr("x", d => xScale(d[0] + 10))
.attr("y", d => yScale(d[1]))
</script>
</body>
用 D3 实现数据可视化:添加坐标轴到可视化中
另一种改进散点图的方法是添加 x 轴和 y 轴。D3 有两种方法来渲染 y 轴和 x 轴,分别是axisLeft
和axisBottom
。const xAxis = d3.axisBottom(xScale);
第一步,在 SVG 画布上渲染 x 轴。为此,你可以使用一个常见的 SVG 组件,g
元素,g
是英文中组(group)的缩写。不同于rect
、circle
、text
,在渲染时,轴只是一条直线。因为它是一个简单的图形,所以可以用g
。
第二步,使用transforms
属性将轴放置在 SVG 画布的正确位置上。否则,轴将会沿着 SVG 画布的边缘渲染,从而不可见。SVG 支持多种transforms
,但是放置轴需要translate
。当它应用在g
元素上时,它根据给出的总量移动整组。下面是一个例子:
const xAxis = d3.axisBottom(xScale);
svg.append("g")
.attr("transform", "translate(0, " + (h - padding) + ")")
.call(xAxis);
<body>
<script>
const dataset = [
[ 34, 78 ],
[ 109, 280 ],
[ 310, 120 ],
[ 79, 411 ],
[ 420, 220 ],
[ 233, 145 ],
[ 333, 96 ],
[ 222, 333 ],
[ 78, 320 ],
[ 21, 123 ]
];
const w = 500;
const h = 500;
const padding = 60;
const xScale = d3.scaleLinear()
.domain([0, d3.max(dataset, (d) => d[0])])
.range([padding, w - padding]);
const yScale = d3.scaleLinear()
.domain([0, d3.max(dataset, (d) => d[1])])
.range([h - padding, padding]);
const svg = d3.select("body")
.append("svg")
.attr("width", w)
.attr("height", h);
svg.selectAll("circle")
.data(dataset)
.enter()
.append("circle")
.attr("cx", (d) => xScale(d[0]))
.attr("cy",(d) => yScale(d[1]))
.attr("r", (d) => 5);
svg.selectAll("text")
.data(dataset)
.enter()
.append("text")
.text((d) => (d[0] + "," + d[1]))
.attr("x", (d) => xScale(d[0] + 10))
.attr("y", (d) => yScale(d[1]))
const xAxis = d3.axisBottom(xScale);
svg.append("g")
.attr("transform", "translate(0," + (h - padding) + ")")
.call(xAxis);
const yAxis = d3.axisLeft(yScale);
svg.append("g")
.attr("transform", "translate("+padding+", 0")
.call(yAxis)
</script>
</body>