携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第 28 天,点击查看活动详情
前因
在上一节中,我们了解到在 D3
中是如何根据动态数据进行动态更新,主要是通过 Data - Join
来处理相关的数据更新,这一小节咱们就一起通过一个实例来加强对 Data - Join
的使用和理解吧!
散点图
散点图,顾名思义就是由一些散乱的点组成的图表,这些点在哪个位置,是由其X值和Y值确定的。所以也叫做XY散点图。
我们要实现一个湖北2020年的疫情散点图,需要有一个基础的画布,跟前面一节的示例一样,我们首先创建一个画布,代码如下:
<svg width="800" height="600" id="svg" class="svgs" style="background-color: #ffffff;"></svg>
当画布已经准备好,我们就需要设置相关的基础属性了,还是跟前面一节的代码类似,如下:
const svg = d3.select('#svg');
const width = +svg.attr('width');
const height = +svg.attr('height');
const margin = {top: 100, right: 120, bottom: 100, left: 120};
const innerWidth = width - margin.left - margin.right;
const innerHeight = height - margin.top - margin.bottom;
以上代码在前面一节已经讲解过相关原理,这里就不做相关的赘述了。
我们还需要添加横坐标与纵坐标的标题,以及它们的比例尺,代码如下:
let xScale, yScale;
const xAxisLabel = '累计确诊人数(对数)';
const yAxisLabel = '新增人数(对数)';
当然,我们还需要将湖北各个城市用不同的颜色来标示出来,代码如下:
const color = {
"武汉":"#ff1c12",
"黄石": "#de5991",
"十堰": "#759AA0",
"荆州": "#E69D87",
"宜昌": "#be3259",
"襄阳": "#EA7E53",
"鄂州": "#EEDD78",
"荆门": "#9359b1",
"孝感": "#47c0d4",
"黄冈": "#F49F42",
"咸宁": "#AA312C",
"恩施州": "#B35E45",
"随州": "#4B8E6F",
"仙桃": "#ff8603",
"天门": "#ffde1d",
"潜江": "#1e9d95",
"神农架": "#7289AB"
}
基础的准备工作都已经做完了,下面我们就开始正式的编码。
因为我们要实现的是一个可视化的散点图,因此我们需要先获取数据,在前面的示例中,我们的数据是在前端界面写死的,这当然是不行的。在 D3
中我们一般是通过 d3.csv(path)
来读取一个远程的数据,之所有用 csv
这样的数据格式,是因为它本质上是纯文本,区别于 excle
的格式,但是它却能在 excle
中打开。
接下来我们就先读取一个 csv
文件,动态的获取数据,代码如下:
d3.csv("./static/data/hubeinxt.csv").then(data => {
// ... data
});
我们通过 d3.csv
读取一个 csv
文件,返回的是一个 Promise
,其中的 data
就是读取出来的数据,原始的 csv
文件数据如下图所示:
读取到的 data
是一个数组对象,其中就包括上图中的所有值,而这里我们需要筛选一下相关的字段来获取我们需要的数据,代码如下:
data = data.filter((d) => d["地区"] !== "总计");
data.forEach((d) => {
d["确诊人数"] = +d["确诊人数"];
d["新增确诊"] = +d["新增确诊"];
if (d["新增确诊"] < 0) {
d["新增确诊"] = 0;
}
});
通过筛选,我们获取到一个新的数组,其中的确诊人数
和新增确诊
转换成了数字类型,并且判断当新增确诊
的数据小于0时,就直接设置为0。得到基础的数据后,我们还需要组装成我们能够使用的数据,代码如下:
allDates = Array.from(new Set(data.map((d) => d["日期"])));
allDates = allDates.sort((a: number, b: number) => {
return new Date(a) - new Date(b);
});
sequantial = [];
allDates.forEach(() => {
sequantial.push([]);
});
data.forEach((d) => {
sequantial[allDates.indexOf(d["日期"])].push(d);
});
通过一系列的操作,我们最终得到了我们的坐标轴数据,接下来我们就需要将这些数据渲染在画布上了,因此我们需要有一个画布的初始方法,代码如下:
const renderInit = (data: Record<string, any>[]) => {
xScale = d3
.scaleLinear()
.domain([d3.min(data, xValue), d3.max(data, xValue)])
.range([0, iWidth])
.nice();
yScale = d3
.scaleLinear()
.domain(d3.extent(data, yValue).reverse())
.range([0, iHeight])
.nice();
const g = svg
.append("g")
.attr("transform", `translate(${margin.left}, ${margin.top})`)
.attr("id", "svg");
const yAxis = d3.axisLeft(yScale).tickSize(-iWidth).tickPadding(10);
const xAxis = d3.axisBottom(xScale).tickSize(-iHeight).tickPadding(10);
let yAxisGroup = g.append("g").call(yAxis).attr("id", "yaxis");
yAxisGroup
.append("text")
.attr("font-size", "2em")
.attr("transform", "rotate(-90)")
.attr("x", -iHeight / 2)
.attr("y", -50)
.attr("fill", "#333333")
.text(yAxisLabel)
.attr("text-anchor", "middle");
yAxisGroup.selectAll(".domain").remove();
let xAxisGroup = g
.append("g")
.call(xAxis)
.attr("transform", `translate(0, ${iHeight})`)
.attr("id", "xaxis");
xAxisGroup
.append("text")
.attr("font-size", "2em")
.attr("y", 60)
.attr("x", iWidth / 2)
.attr("fill", "#333333")
.text(xAxisLabel);
xAxisGroup.selectAll(".domain").remove();
};
跟前面一节的操作很类似,都是比较简单的操作。然后我们将数据渲染到画布上,最终的坐标轴如下图所示:
具体的效果可以狠戳这里。
坐标轴已经准备好了,接下来还需要讲坐标轴中的散列点渲染到画布中,这个基本的散列图就完成了,我们还需要实现一个更新散列点的函数,代码如下:
const renderUpdate = (seq) => {
const g = d3.select("#svg");
let circleUpdates = g.selectAll("circle").data(seq, (d) => d["地区"]);
let circleEnter = circleUpdates
.enter()
.append("circle")
.attr("cx", (d) => xScale(xValue(d)))
.attr("cy", (d) => yScale(yValue(d)))
.attr("r", 10)
.attr("fill", (d) => color[d["地区"]])
.attr("opacity", 0.8);
circleUpdates
.merge(circleEnter)
.transition()
.ease(d3.easeLinear)
.duration(aduration)
.attr("cx", (d) => xScale(xValue(d)))
.attr("cy", (d) => yScale(yValue(d)));
};
最终实现的效果可以狠戳这里。
最后
这一小节我们主要是为了加强对上一节中 Data - Join
的学习和理解,并实现了一个基础的散列图示例,其中需要注意的就是 csv
数据的读取和处理。我们一起加油吧!
未完待续...
最后,如果这篇文章有帮助到你,❤️关注+点赞❤️鼓励一下作者,谢谢大家