专栏首页Crossin的编程教室【数据可视化】D3.js实现动态气泡图

【数据可视化】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

本文分享自微信公众号 - Crossin的编程教室(crossincode)

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2021-03-12

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 动态气泡图,拿走不谢!

    https://observablehq.com/@unkleho/covid-19-bubble-chart-with-d3-render

    小F
  • 结合d3.js实现气象数据的可视化

    需要将格点气象数据实现前端的展示,数据传输的方式有三种:1、json;2、二进制;3、灰度图。三种方式各有优劣,这个需要在实际的项目中去酌情选择,本文为方便理解...

    lzugis
  • 工具 | 一张图,教你用25种可视化工具如何完成

    散点图真是一个比较神奇的图形,正如它的名字一样,一堆纷乱如麻的圆点,看似无迹可寻却能显示出数据难以显示的内在逻辑关系。很多人称它“万表之王”,它在数据分析师手...

    小莹莹
  • 这款免费插件,让Excel轻松制作酷炫图表​

    最近我看一篇介绍如何用Excel来制作径向树图[1]的文章,在其中学到了一个很有趣的Excel 加载项。

    朱小五
  • 数据可视化实践之美

    来源:中国统计网 作者:daniel.xie(谢佳标) 原文链接:http://dwz.cn/5Pz3BX 本文长度为2900字,建议阅读5分钟 本文主要为大家...

    数据派THU
  • 数据可视化实践之美

    开篇主要是介绍了一些常用的数据可视化工具和图表,让各位看官对数据可视化有一个较为全面的认识。后续篇章会深入介绍如何运用工具绘制精美图表的技术细节。 随着DT时代...

    小莹莹
  • 数据视觉盛宴—数据可视化实践之美

    随着DT时代的到来,传统的统计图表很难对复杂数据进行直观地展示。这几年数据可视化作为一个新研究领域也变得越来越火。成功的可视化,如果做得漂亮,虽表面简单却富含深...

    钱塘数据
  • 数据可视化之美:经典案例与实践解析

    本文来自作者在GitChat(ID:GitChat_Club)上的精彩分享,CSDN独家合作发布。 随着DT时代的到来,传统的统计图表很难对复杂数据进行直观地展...

    CSDN技术头条
  • 「R」数据可视化5 : 气泡图

    在生物信息领域我们常常使用R语言对数据可视化。在对数据可视化的时候,我们需要明确想要展示的信息,从而选择最为合适的图突出该信息。本系列文章将介绍多种基于不同R包...

    王诗翔呀

扫码关注云+社区

领取腾讯云代金券