[数据可视化] D3.js 实现动态气泡图
大家好,欢迎来到 Crossin的编程教室 !
数据处理及可视化是Python的一大应用场景。不过为了实现更好的动态演示效果,实际应用中常常还需要和js相结合。
今天我们就来给大家分享一个用D3.js实现的动态气泡图案例。
本文用到的语言主要 js,不过主要是做一些配置,所以阅读起来并不困难。另外也建议大家有空可以了解一下基础的js语法,会很有帮助。
首先我们来看下 D3.js 的气泡图效果:
项目地址:
https://observablehq.com/@unkleho/covid-19-bubble-chart-with-d3-render
GitHub地址:
https://github.com/unkleho/d3-render
要想实现这个项目的话,首先需要安装 Node.js 以及 npm,具体的安装步骤这里赘述,百度一下就有,还是比较简单的。
接下来就可以安装 Vue.js 及 Vue脚手架3.0。
# 安装Vue.js
npm install vue
# 安装Vue-cli3脚手架
npm install -g @vue/cli
如此便可以创建项目了。
# 创建名为bubblechart的项目
vue create bubblechart
结果如下,选择默认模式即可。
由于里面有eslint(编码规范)的存在,记得在配置文件package.json中添加下面的代码。
"rules": {
"no-unused-vars": "off",
"no-undef": "off"
}
要不然会出现报错,无法运行。
项目创建成功后,修改App.vue文件内容如下。
<template>
<div id="app">
<svg id="bubble-chart" width="954" height="450" />
</div>
</template>
<script>
export default {
name: 'App',
components: {
}
}
</script>
<style></style>
保存成功后,打开bubblechart文件夹下的终端,运行下面这个命令。
npm run serve
浏览器便会跳出一个标题为bubblechart的空白网页。
安装一些项目依赖d3,d3-render,d3-selection,d3-transition,axios。
npm install d3@5.16.0 --save-dev
npm install d3-render@0.2.4 --save-dev
npm install d3-selection@1.4.2 --save-dev
npm install d3-transition@2.0.0 --save-dev
npm install axios --save-dev
最好是指定版本,要不然可能会报错。
在main.js文件中引用axios,用于请求数据。
import axios from 'axios'
Vue.prototype.$axios = axios
在App.vue的script标签中引用d3,d3-render。
import * as d3 from "d3";
import render from "d3-render";
设置初始数据,各式各样的气泡颜色。
data() {
return {
covidData: null,
countries: null,
colours: {
pink: "#D8352A",
red: "#D8352A",
blue: "#48509E",
green: "#02A371",
yellow: "#F5A623",
hyperGreen: "#19C992",
purple: "#B1B4DA",
orange: "#F6E7AD",
charcoal: "#383838",
}
};
}
获取各地区的新冠数据,两个CSV文件放在Public文件夹下,可直接访问。
methods: {
async getdata() {
//获取新冠数据
await this.$axios.get("data.csv").then((res) => {
this.covidData = d3.csvParse(res.data);
});
//获取国家数据
await this.$axios.get("countries.csv").then((res) => {
this.countries = d3.csvParse(res.data);
});
//画图
this.drawType();
},
}
开始画图的操作,先定义一下画布大小以及各大洲的颜色。
drawType() {
//设置svg大小
const width = 954;
const height = 450;
//设置各个大洲的参数
const continents = [
{
id: "AF",
name: "Africa",
fill: this.colours.purple,
colour: this.colours.charcoal,
},
{
id: "AS",
name: "Asia",
fill: this.colours.yellow,
colour: this.colours.charcoal,
},
{
id: "EU",
name: "Europe",
fill: this.colours.blue,
colour: this.colours.charcoal,
},
{
id: "NA",
name: "N. America",
fill: this.colours.pink,
},
{
id: "OC",
name: "Oceania",
fill: this.colours.orange,
colour: this.colours.charcoal,
},
{
id: "SA",
name: "S. America",
fill: this.colours.green,
colour: this.colours.charcoal,
},
];
}
定义圆圈组件,其中duration很重要,起到一个动画过渡的效果。
//定义圆圈组件
const circleComponent = ({ r, cx, cy, fill, duration }) => {
return {
append: "circle",
r,
cx,
cy,
fill,
duration,
};
};
定义文字组件,设置字体、大小、颜色等。
//定义文字组件
const textComponent = ({
key,
text,
x = 0,
y = 0,
fontWeight = "bold",
fontSize = "12px",
textAnchor = "middle",
fillOpacity = 1,
colour,
r,
duration = 1000,
}) => {
return {
append: "text",
key,
text,
x,
y,
textAnchor,
fontFamily: "sans-serif",
fontWeight,
fontSize,
fillOpacity: { enter: fillOpacity, exit: 0 },
fill: colour,
duration,
style: {
pointerEvents: "none",
},
};
};
数值转换,对较大的数值进行处理。
//对数值进行转换,比如42288变为42k
const format = (value) => {
const newValue = d3.format("0.2s")(value);
if (newValue.indexOf("m") > -1) {
return parseInt(newValue.replace("m", "")) / 1000;
}
return newValue;
};
动态变化标签信息,包含名称及数值。
//将各地区名称长度和数值与圆圈大小相比较,实现信息动态变化
const labelComponent = ({ isoCode, countryName, value, r, colour }) => {
// Don't show any text for radius under 12px
if (r < 12) {
return [];
}
//console.log(r);
const circleWidth = r * 2;
const nameWidth = countryName.length * 10;
const shouldShowIso = nameWidth > circleWidth;
const newCountryName = shouldShowIso ? isoCode : countryName;
const shouldShowValue = r > 18;
let nameFontSize;
if (shouldShowValue) {
nameFontSize = shouldShowIso ? "10px" : "12px";
} else {
nameFontSize = "8px";
}
return [
textComponent({
key: isoCode,
text: newCountryName,
fontSize: nameFontSize,
y: shouldShowValue ? "-0.2em" : "0.3em",
fillOpacity: 1,
colour,
}),
...(shouldShowValue
? [
textComponent({
key: isoCode,
text: format(value),
fontSize: "10px",
y: shouldShowIso ? "0.9em" : "1.0em",
fillOpacity: 0.7,
colour,
}),
]
: []),
];
};
设置气泡组件。
//设置气泡组件
const bubbleComponent = ({
name,
id,
value,
r,
x,
y,
fill,
colour,
duration = 1000,
}) => {
return {
append: "g",
key: id,
transform: {
enter: `translate(${x + 1},${y + 1})`,
exit: `translate(${width / 2},${height / 2})`,
},
duration,
delay: Math.random() * 300,
children: [
circleComponent({ key: id, r, fill, duration }),
...labelComponent({
key: id,
countryName: name,
isoCode: id,
value,
r,
colour,
duration,
}),
],
};
};
划分数据的层次结构,生成气泡图的结构。
后续的 d.r、d.x、d.y 数据都是从中获取的。
//d3.pack - 创建一个新的圆形打包图
//d3.hierarchy - 从给定的层次结构数据构造一个根节点并为各个节点指定深度等属性
const pack = (data) =>
d3
.pack()
.size([width - 2, height - 2])
.padding(2)(d3.hierarchy({ children: data }).sum((d) => d.value));
//生成气泡图表
const renderBubbleChart = (selection, data) => {
const root = pack(data);
const renderData = root.leaves().map((d) => {
return bubbleComponent({
id: d.data.id,
name: d.data.name,
value: d.data.value,
r: d.r,
x: d.x,
y: d.y,
fill: d.data.fill,
colour: d.data.colour,
});
});
return render(selection, renderData);
};
const renderBubbleChartContainer = (data) => {
return renderBubbleChart("#bubble-chart", data);
};
最后便可以加入数据,生成动态的气泡图表。
对数据进行处理,进行日期限定及排序,以及选取相关的数据类型。
//定义新冠数据
const covidData_result = this.covidData;
//定义各地区数据
const countries_result = this.countries;
//选择数据类型为所有确诊病例数量
const dataKey = "total_cases";
//定义开始时间及结束时间
const startDate = new Date('2020-01-12')
const endDate = new Date('2020-06-02')
//d3.map - 创建一个新的空的 map 映射
const dates = d3
.map(this.covidData, (d) => d.date)
.keys()
.map((date) => new Date(date))
.filter((date) => date >= startDate && date <= endDate)
.sort((a, b) => a - b);
//各大洲全选
const selectedContinents = ["AF", "AS", "EU", "NA", "OC", "SA"];
//最小数值
const minimumPopulation = 0;
//排序
const order = "desc";
//转换日期格式为2020-01-01
const getIsoDate = (date) => {
const IsoDate = new Date(date);
return IsoDate.toISOString().split("T")[0];
};
//获取最终的数据
function getDataBy({
dataKey,
date,
selectedContinents,
order,
minimumPopulation,
}) {
return (
covidData_result
.filter((d) => d)
.filter((d) => d.iso_code !== "OWID_WRL")
// Filter out countries with populations under 1 million
.filter((d) => d.population > parseInt(minimumPopulation))
.filter((d) => {
return d.date === getIsoDate(date);
})
.filter((d) => d[dataKey])
.filter((d) => {
const country = countries_result.find(
(c) => c.iso3 === d.iso_code
);
const continent = continents.find((c, i) => {
if (!country) {
return false;
}
return c.id === country.continentCode;
});
if (!continent) {
return false;
}
return selectedContinents.includes(continent.id);
})
.map((d) => {
const country = countries_result.find(
(c) => c.iso3 === d.iso_code
);
const continent = continents.find(
(c) => c.id === country.continentCode
);
const name = country.shortName || country.name;
return {
name,
id: country.iso3,
value: d[dataKey],
fill: continent.fill,
colour: continent.colour || "white",
};
})
.filter((d) => d.value !== "0.0")
.sort(function (a, b) {
const mod = order === "desc" ? -1 : 1;
return mod * (a.value - b.value);
})
);
}
设置For循环延时,完成动态气泡图的实现。
//延时执行,闭包
for (var i = 0; i < dates.length; i++) {
(function (i) {
setTimeout(function () {
const date = dates[i];
console.log(date);
const data = getDataBy({
dataKey,
date,
selectedContinents,
minimumPopulation,
order,
});
renderBubbleChartContainer(data);
}, 2000 * i);
})(i);
};
运行项目,打开浏览器,访问http://localhost:8080/
如此便完成了一个动态的气泡图,这个案例用了疫情随时间变化的数据,这种图表可以比较直观地展现数据的变化趋势。
项目代码及数据:
https://github.com/Tobby-star/bubble-chart
将项目下载到本地,运行下面两行命令,即可运行。
npm install
npm run serve
推荐阅读
-
[数据可视化] D3.js 实现动态气泡图
-
35 岁实现财务*,腾讯程序员手握2300万提前退休?-1000万房产、1000万腾讯股票、加上300万的现金,一共2300万的财产。有网友算了一笔账,假设1000万的房产用于自住,剩下1300万资产按照平均税后20-50万不等进行计算,大约花上26-60年左右的时间才能赚到这笔钱。也就是说,普通人可能奋斗一辈子,才能赚到这笔钱。在很多人还在为中年危机而惶惶不可终日的时候,有的人的35岁,就已经安全着陆,试问哪个打工人不羡慕?但问题是有这样财富积累必然有像样的实力做靠山。没有人可以不劳而获。 看到这里,肯定有人说,那么对于普通人来说,卷可能真就成了唯一的出路。但是卷也有轻松的卷,“偷懒”的卷法,对于程序员而言,刨除掉一时无法改掉的开会传统占用的大部分时间,如何把有限的时间和精力放在真正重要的架构设计、需求设计上,而不是重复的造*,编码、改bug、手动测试。因此在科技改变生活的今天,学会使用AI工具成为程序员们的必备技能。 以全栈式全自动的软件开发工具飞算SoFlu软件机器人为例,作为全球首款面向微服务架构设计和最佳实践的软件机器人,SoFlu软件机器人改变了原来手工编码的作业模式,通过可视化拖拽方式以及参数配置就能实现等同于编写复杂代码的业务逻辑,在设计业务逻辑时就完成了微服务应用开发,做到“业务即图,图即代码”,实现“软件开发,十倍提效”。 而作为飞算SoFlu软件机器人的一个重要组成部分,FuncGPT(慧函数)支持所有类型函数创建。通过自然语言描述Java函数需求,实时生成高质量、高可读性的Java函数代码。生成代码可直接复制到IDEA,或一键导入Java全自动开发工具函数库。 FuncGPT(慧函数)具有五大核心能力: ● 自然语言:通过自然语言即可生成函数,降低软件开发门槛。 ● 秒级函数开发:全自动秒级开发函数,秒级完成,效率千倍提升。 ● 一站式函数开发应用:函数生成即可直接应用,效率、安全有保障。 ● 质量可靠:生成的函数代码符合业界规范、具备良好的可读性,符合最优解。 ● 灵活生成:可以根据开发人员的具体需求生成并支持调整和修改。 以“将文件流转换为File对象”这个需求为例,以下是FuncGPT(慧函数)秒级生成的代码: / 类名:FileConverter // 函数名:convertStreamToFile // 函数功能:将文件流转换为File对象 // POM依赖包:无 import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; public class FileConverter { /** * 将文件流转换为File对象 * @param inputStream 文件流 * @param filePath 文件路径 * @param fileName 文件名 * @return 转换后的File对象 * @throws IOException 如果发生I/O异常 */ public static File convertStreamToFile(InputStream inputStream, String filePath, String fileName) throws IOException { File file = new File(filePath + File.separator + fileName); // 创建File对象 try (FileOutputStream outputStream = new FileOutputStream(file)) { // 创建文件输出流 byte buffer = new byte[1024]; int bytesRead; while ((bytesRead = inputStream.read(buffer)) != -1) { // 从文件流读取数据并写入文件 outputStream.write(buffer, 0, bytesRead); } } return file; // 返回转换后的File对象 } } // 函数示例 // 将文件流转换为File对象示例 // 入参:inputStream,文件流 // 入参:filePath,文件路径 // 入参:fileName,文件名 // 出参:file,转换后的File对象 // 调用示例: // InputStream inputStream = new FileInputStream("example.txt"); // String filePath = "C:\\Users\\User\\Documents"; // String fileName = "example.txt"; // File file = FileConverter.convertStreamToFile(inputStream, filePath, fileName); // System.out.println(file.getAbsolutePath); // 输出结果:例如,将文件流转换为File对象后,文件的绝对路径为:C:\Users\User\Documents\example.txt // 则输出结果为:C:\Users\User\Documents\example.txt 通过分析,不难发现以上代码:
-
1 【源码】数据可视化:基于 Echarts +Java SpringBoot 实现的动态实时大屏示例 - 互联网企业数据分析
-
LLM 大语言模型助力 DataEase 小助手,全新气泡图,DataEase 开源数据可视化分析平台 v2.5.0 发布
-
世界各国 GDP 动态排名可视化实现(基于 d3.js)
-
太阳图、平行坐标......5 种动态互动可视化方法让数据讲述更动人的故事
-
如何使用 DeepBI 绘制令人惊叹的气泡图 - 数据可视化
-
基于缩放属性的变换,动态缩放整个页面,实现数据可视化的大屏自适应,保持比例不变形,满足不同分辨率的需求。
-
实战讲解:热力图与相关系数图的数据可视化——基于Python的实现与原理解析(第二部分:代码实操)
-
用R实现数据可视化:轻松理解聚类热图(pheatmap)