问题:
画出美国1900与2000人口分布
介绍:
只是一个非常粗糙的可视化模板,注意后续改一下颜色什么的~
步骤:
1. 安装vscode
2.下载安装图片中插件
3.新建一个文件夹并添加到工作区
4.创建一个html文件
5.将数据粘贴到文件夹中
6.将以下代码粘贴到html文件中
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>美国人口分布1900与2000</title>
<script src="https://d3js.org/d3.v7.min.js"></script>
<style>
.bar {
fill-opacity: 0.5; /*透明度*/
}
.bar:hover {
fill-opacity: 1;
}
.axis text {
font: 10px sans-serif; /*行字体大小*/
}
.legend text {
font: 12px sans-serif; /*类别字体大小*/
}
.label {
font-size: 5px; /*标签字体大小*/
text-anchor: middle;
fill: #333;
}
</style>
</head>
<body>
<script>
// 加载csv文件
d3.csv('census2000.csv').then(data => {
// 把各属性字符转换成数字类型
data.forEach(d => {
d.Sex = +d.Sex;
d.Year = +d.Year;
d.Age = +d.Age;
d.People = +d.People;
});
// 设置SVG图表的尺寸和边缘留白
const margin = {top: 100, right: 30, bottom: 100, left: 60},
width = 2000 - margin.left - margin.right,
height = 1000 - margin.top - margin.bottom;
// 创建SVG容器
const 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})`);
// 设置D3.js中的X,Y轴比例尺
const x0 = d3.scaleBand()
.domain([...new Set(data.map(d => d.Year))])
.range([0, width])
.padding(0.1);
const x1 = d3.scaleBand()
.domain([...new Set(data.map(d => d.Age))])
.range([0, x0.bandwidth()])
.padding(0.05);
// 让y轴从中间开始,设置两个y轴
const y = d3.scaleLinear()
.domain([0, d3.max(data, d => Math.abs(d.People))])
.nice()
.range([height / 2, 0]); // Male
const yBottom = d3.scaleLinear()
.domain([0, d3.max(data, d => Math.abs(d.People))])
.nice()
.range([height / 2, height]); // Female
const color = d3.scaleOrdinal()
.domain([1, 2])
.range(['steelblue', 'pink']);
// 创造柱子与定义标签
const groups = svg.append('g')
.selectAll('g')
.data(d3.group(data, d => d.Year))
.enter().append('g')
.attr('transform', d => `translate(${x0(d[0])},0)`);
groups.selectAll('rect')
.data(d => d[1])
.enter().append('rect')
.attr('x', d => x1(d.Age))
.attr('y', d => d.Sex === 1 ? y(d.People) : height / 2) // Male (Sex==1) bars go upward, Female (Sex==2) bars start from the middle and go downward
.attr('width', x1.bandwidth())
.attr('height', d => d.Sex === 1 ? height / 2 - y(d.People) : yBottom(d.People) - height / 2) // Adjust height based on Sex
.attr('fill', d => color(d.Sex))
.attr('class', 'bar');
// 把标签贴到柱子上
groups.selectAll('text')
.data(d => d[1])
.enter().append('text')
.attr('x', d => x1(d.Age) + x1.bandwidth() / 2)
.attr('y', d => d.Sex === 1 ? y(d.People) - 5 : yBottom(d.People) + 15) // Adjust text position above or below the bar
.attr('class', 'label')
.text(d => `${d.Age} yrs\n${Math.abs(d.People).toLocaleString()}`);
// 添加x轴
svg.append('g')
.attr('class', 'x-axis')
.attr('transform', `translate(0,${height / 2})`) // Center x-axis
.call(d3.axisBottom(x0).tickSize(0))
.selectAll('text')
.attr('transform', 'rotate(-45)')
.style('text-anchor', 'end');
// 添加y轴
svg.append('g')
.attr('class', 'y-axis')
.call(d3.axisLeft(y).ticks(5));
svg.append('g')
.attr('class', 'y-axis-bottom')
.attr('transform', `translate(0,${0})`)
.call(d3.axisLeft(yBottom).ticks(5));
// 添加标签
const legend = svg.append('g')
.attr('class', 'legend')
.attr('transform', `translate(${width - 150},0)`);
legend.selectAll('rect')
.data([1, 2])
.enter().append('rect')
.attr('x', 0)
.attr('y', (d, i) => i * 20)
.attr('width', 18)
.attr('height', 18)
.attr('fill', d => color(d));
legend.selectAll('text')
.data([1, 2])
.enter().append('text')
.attr('x', 30)
.attr('y', (d, i) => i * 20 + 9)
.attr('dy', '0.32em')
.text(d => d === 1 ? 'Male' : 'Female');
});
</script>
</body>
</html>
7.点击图中选项
8.获得结果
实验总结:
本实验让我学习了html与javascript的基本语法并对D3可视化库的使用有了初步的理解,如果想要创建一个柱状图步骤如下:
- 输入!形成html模板
- head中加入<script src="https://d3js.org/d3.v7.min.js"></script>导入d3库
- 定义style模块
-
<style> .bar { fill-opacity: 0.5; /*透明度*/ } .bar:hover { fill-opacity: 1; } .axis text { font: 10px sans-serif; /*行字体大小*/ } .legend text { font: 12px sans-serif; /*类别字体大小*/ } .label { font-size: 5px; /*标签字体大小*/ text-anchor: middle; fill: #333; } </style>
- style可以定义的东西:
- 加载数据并进行格式转换
-
// 加载csv文件 d3.csv('census2000.csv').then(data => { // 把各属性字符转换成数字类型 data.forEach(d => { d.Sex = +d.Sex; d.Year = +d.Year; d.Age = +d.Age; d.People = +d.People; });
- 设置SVG图表的尺寸和边缘留白
const margin = {top: 100, right: 30, bottom: 100, left: 60},
width = 2000 - margin.left - margin.right,
height = 1000 - margin.top - margin.bottom;
- 创建SVG容器
// 创建SVG容器
const 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})`);
- 定义比例尺,以x0为例定义比例尺的形状:scaleBand,定义比例尺由什么决定:year。domain方法确定了比例尺的输入范围,即数据集中出现的所有唯一年份。range方法指定了输出范围。padding 方法指定比例尺间距。
// 设置D3.js中的X,Y轴比例尺
const x0 = d3.scaleBand()
.domain([...new Set(data.map(d => d.Year))])
.range([0, width])
.padding(0.1);
const x1 = d3.scaleBand()
.domain([...new Set(data.map(d => d.Age))])
.range([0, x0.bandwidth()])
.padding(0.05);
// 让y轴从中间开始,设置两个y轴
const y = d3.scaleLinear()
.domain([0, d3.max(data, d => Math.abs(d.People))])
.nice()
.range([height / 2, 0]); // Male
const yBottom = d3.scaleLinear()
.domain([0, d3.max(data, d => Math.abs(d.People))])
.nice()
.range([height / 2, height]); // Female
const color = d3.scaleOrdinal()
.domain([1, 2])
.range(['steelblue', 'pink']);
- 创建SVG组元素
-
const groups = svg.append('g');
这行代码在SVG容器中添加一个新的
<g>
元素。<g>
元素是一个容器,用于将其他SVG元素分组在一起,方便对这些元素进行变换操作。 -
选择所有
<g>
元素 -
.selectAll('g');
这行代码准备选择所有的
<g>
元素,但实际上此时还没有这些元素存在,因此这只是准备操作。 -
绑定数据:
.data(d3.group(data, d => d.Year));
这里使用
d3.group
函数对原始数据集data
进行分组,根据每个数据项的Year
属性。d3.group
函数返回一个Map对象,其中键是分组的依据(在这里是年份),值是一个数组,包含所有该年份的数据项。 -
进入模式:
.enter().append('g');
进入模式是D3.js中的一个重要概念,用于处理数据绑定到DOM元素时的新增情况。这里为每个分组的数据创建一个新的
<g>
元素。如果之前没有相应的<g>
元素存在,则会创建它们。 - 创建矩形并为矩形指定位置
groups.selectAll('rect') .data(d => d[1]) .enter().append('rect') .attr('x', d => x1(d.Age)) .attr('y', d => d.Sex === 1 ? y(d.People) : height / 2) // Male (Sex==1) bars go upward, Female (Sex==2) bars start from the middle and go downward .attr('width', x1.bandwidth()) .attr('height', d => d.Sex === 1 ? height / 2 - y(d.People) : yBottom(d.People) - height / 2) // Adjust height based on Sex .attr('fill', d => color(d.Sex)) .attr('class', 'bar');
selectAll('rect'):创建矩形,.data(d => d[1]):将数据与矩形绑定,.enter().append('rect'):如果矩形数量不够就生成新的矩形,.attr('x', d => x1(d.Age)):设置矩形的左边缘位置,使用
x1
比例尺将年龄映射到X轴上的位置。.attr('y', d => d.Sex === 1 ? y(d.People) : height / 2):设置矩形的顶部位置。如果是男性(Sex == 1
),使用y
比例尺映射人数到Y轴,并且条形图向上延伸;如果是女性(Sex == 2
),条形图的顶部固定在中间位置(height / 2
),并且条形图向下延申。.attr('fill', d => color(d.Sex)): 设置矩形的颜色。使用之前定义的color
比例尺根据性别分配颜色。.attr('class', 'bar'):为每个矩形添加一个类名'bar'
,便于后续的CSS样式控制。
- 创建标签并指定位置,方式类似于创建矩形,只是多了一条.text(d => `${d.Age}yrs\n${Math.abs(d.People).toLocaleString()}`),它的作用是指定标签内容
groups.selectAll('text')
.data(d => d[1])
.enter().append('text')
.attr('x', d => x1(d.Age) + x1.bandwidth() / 2)
.attr('y', d => d.Sex === 1 ? y(d.People) - 5 : yBottom(d.People) + 15) // Adjust text position above or below the bar
.attr('class', 'label')
.text(d => `${d.Age} yrs\n${Math.abs(d.People).toLocaleString()}`);
这段代码创建了一个简单的图例,其中包含了两个条目:“Male”和“Female”,每个条目都有一个颜色标记和一个描述性标签。
- 添加坐标轴相关信息
// 添加x轴 svg.append('g') .attr('class', 'x-axis') .attr('transform', `translate(0,${height / 2})`) // Center x-axis .call(d3.axisBottom(x0).tickSize(0)) .selectAll('text') .attr('transform', 'rotate(-45)') .style('text-anchor', 'end'); // 添加y轴 svg.append('g') .attr('class', 'y-axis') .call(d3.axisLeft(y).ticks(5)); svg.append('g') .attr('class', 'y-axis-bottom') .attr('transform', `translate(0,${0})`) .call(d3.axisLeft(yBottom).ticks(5));
-
创建图例组:
const legend = svg.append('g') .attr('class', 'legend') .attr('transform', `translate(${width - 150},0)`);
这段代码在SVG容器中添加了一个新的
<g>
元素,用于容纳图例。通过设置transform
属性,将图例定位到SVG容器的右侧边缘,距离右边150个单位的位置。这里假设width
变量代表整个SVG的宽度。 -
添加矩形标记:
legend.selectAll('rect') .data([1, 2]) .enter().append('rect') .attr('x', 0) .attr('y', (d, i) => i * 20) .attr('width', 18) .attr('height', 18) .attr('fill', d => color(d));
这里使用了D3的数据绑定方法
.selectAll('rect').data([1, 2])
,将数据[1, 2]
与图例中的矩形元素关联起来。然后使用.enter().append('rect')
为每个数据点创建一个矩形。这些矩形将作为图例中的颜色标记。它们的位置、大小以及填充颜色都是根据特定的规则设定的:- 每个矩形的左上角的x坐标固定为0。
- y坐标基于索引
i
,每个矩形向下偏移20个单位,因此第一个矩形位于顶部,第二个矩形紧随其下。 - 宽度和高度均为18个单位。
- 填充颜色由
color(d)
函数决定,该函数应该是一个颜色比例尺,根据输入值返回相应的颜色。
-
为图例添加文本标签:
legend.selectAll('text') .data([1, 2]) .enter().append('text') .attr('x', 30) .attr('y', (d, i) => i * 20 + 9) .attr('dy', '0.32em') .text(d => d === 1 ? 'Male' : 'Female');
这段代码创建了两个文本元素,分别对应于之前创建的两个矩形标记。每个文本元素的文本内容取决于其关联的数据值:当数据值为1时,显示“Male”;当数据值为2时,显示“Female”。这些文本标签的位置也进行了适当的调整,以确保它们位于对应的矩形标记旁边,且垂直居中对齐。