一段时间以来,您可能已经看到了很多“covidtracker”或加密货币图表应用程序,其中一些提供了非常棒的图表,您很难使用“准备制作”图表的公共库来制作这些图表。

我必须承认......我喜欢玩数据,看到这些漂亮的 UI 和所有这些数字都呈现得如此出色让我嫉妒,我想:哦,我真的需要能够构建它!

所以我最近开始研究著名的 d3.js 库。

不要指望我在这里想出一些令人印象深刻的东西,不,但我希望有一个很好的基础来向您介绍使用 d3.js 进行数据可视化。

我们要构建的是按国家/地区划分的前 10 名人口的条形图:

演示

d3.js 是什么?

事实上d3.js不是一个图表库,它是一种操作 DOM 的大 API,并提供了很多实用功能。您可以操作 svg、canvas、html 等构建图表、图形、地图,以及数据可视化所需的一切。

使用 d3 的最大优势是,您在网络上看到的每一个很酷的数据可视化都可能使用 d3.js 是可行的。

主要缺点是 d3.js 一开始可能会让人不知所措,而且在我看来很难学习。

构建应用程序

准备项目:

我们不会在这里使用任何框架或特定配置,只是为了简单起见一些纯 javascript。

创建一个 index.html 文件并包含以下内容:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link rel="stylesheet" href="style.css">
    <title>d3.js Demo</title>
</head>
<body>
    <h1>Top 10 population by country</h1>
    <div id="chart"></div>
    <div id="tooltip">
        <h3 id="country_name"></h3>
        <p id="country_population"></p>
    </div>

    <script src="https://d3js.org/d3.v6.js"></script>
    <script src="index.js"></script>
</body>
</html>

进入全屏模式 退出全屏模式

我们正在导入 d3.js 库和 index.js 文件,该文件将包含我们的代码。

使用这些样式创建一个 style.css 文件:

* {
    margin: 0;
    box-sizing: border-box;
}

body {
    box-sizing: border-box;
    min-height: 100vh;
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
    font-family: Avenir, Helvetica, Arial, sans-serif;
}

svg {
    background-color: #2a2a2e;
    color: white;
    border-radius: 5px;
}

h1 {
    padding-bottom: 2rem;
}

#tooltip {
    position: absolute;
    visibility: hidden;
    background-color: white;
    font-size: .7rem;
    border-radius: 5px;
    padding: .3rem;
    display: flex;
    flex-direction: column;
    justify-content: center;
    text-align: center;
}

#country_name {
    padding: .3rem;
}

#country_population {
    padding: .3rem;
}

.bar {
    transition: .2s all;
}

进入全屏模式 退出全屏模式

最后,添加一个 index.js 文件。我们现在准备开始编码。

准备数据

我们将使用restcountryAPI。

我们想要在图表上显示的是人口最多的 10 个国家的列表,因此我们只需要每个国家的 x/y 轴的名称和人口。

让我们首先收集这些数据并将它们格式化为所需的形状:

const API_URL = 'https://restcountries.eu/rest/v2/all';

const getData = async () => {
  const reponse = await fetch(API_URL);
  const result = await reponse.json();
  // Sort by population
  const dataSort = result.sort((a, b) => b.population - a.population);
  // only keep the top 10 population
  dataSort.length = 10;
  // We only need name + population
  const dataReady = dataSort.map((country) => ({
    name: country.name,
    population: Math.floor(country.population / 1000000),
  }));
  return dataReady;
};

进入全屏模式 退出全屏模式

现在我们有了我们需要的东西:

https://i.gyazo.com/384c61698aae5dc6f39c86d8b5447150.png

构建图表:

创建主元素

创建一个新函数,它将在 d3 的帮助下负责生成条形图:

const generateChart = (popData) => {
  const margin = {
    top: 20,
    right: 40,
    bottom: 60,
    left: 80,
  };
  const width = 1000 - margin.left - margin.right;
  const height = 500 - margin.top - margin.bottom;

  // Create svg
  const svgElement = d3
    .select('#chart')
    .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.select”来选择一个dom元素并链接其他方法,例如“append”和“attr”来构造我们的元素。这些确实是d3的基础。

边距、宽度和高度变量用于设置 svg 元素的位置,也将用于定位我们的轴。

创建轴

接下来让我们使用 d3-scale 方法创建我们的轴:

    // Add X axis
    const xScale = d3.scaleBand()
    .range([0, width])
    .domain(popData.map((s) => s.name))
    .padding(0.2)
    svgElement.append('g')
        .attr('transform', `translate(0, ${height})`)
        .call(d3.axisBottom(xScale));

    // Add Y axis
    const yScale = d3.scaleLinear()
        .domain([popData[0].population, popData[9].population])
        .range([0, height]);
    svgElement.append('g')
        .call(d3.axisLeft(yScale));

   // Add grid
   svgElement
    .append('g')
.call(d3.axisLeft(yScale).ticks().tickSize(-width).tickFormat(''));

进入全屏模式 退出全屏模式

我们使用了比例模块的 d3 部分的几种实用方法来正确映射我们的轴与数据(scaleLinear、scaleBand)。

如果您打开导航器,您现在会看到一个带有两个轴但还没有数据的 svg 元素。

用条形表示数据

为了用 bar 表示我们的数据,我们只需创建矩形并将它们添加到我们的主 svg 元素中,再次使用正确的宽度和比例,这要归功于某些 d3-scale 方法(带宽、xScale、yScale):

  // Draw the bars
  svgElement
    .append('g')
    .selectAll('.bar')
    .data(popData)
    .enter()
    .append('rect')
    .attr('class', 'bar')
    .attr('x', (d) => xScale(d.name))
    .attr('width', xScale.bandwidth())
    .attr('y', (d) => yScale(d.population))
    .attr('height', 0)
    .style('fill', '#00FA9A')
    .transition()
    .duration(750)
    .attr('height', (d) => height - yScale(d.population));

进入全屏模式 退出全屏模式

我们的图表现在正在运行,但让我们让它更“生动”一点。

添加工具提示和悬停效果:

我们希望在悬停一个栏时显示确切的人口,因此我们需要创建一个工具提示并在每个栏上添加鼠标事件。

请记住:在我们的 index.html 页面中有一个 div 元素,它带有一个工具提示 id,以及 css 中的一些样式。事实上一切都准备好了,工具提示在那里但被隐藏了,我们现在只需要添加鼠标事件:

 // create a tooltip
  const tooltip = d3.select('#tooltip');
  const tooltip_name = d3.select('#country_name');
  const tooltip_pop = d3.select('#country_population');

  // Add mouse event to show the tooltip when hovering bars
  d3.selectAll('.bar')
    .on('mouseover', function () {
      d3.select(this).style('fill', '#59ffb2');
      tooltip.style('visibility', 'visible');
    })
    .on('mousemove', function (e, d) {
      tooltip
        .style('top', event.pageY - 10 + 'px')
        .style('left', event.pageX + 10 + 'px');
      tooltip_name.text(d.name);
      tooltip_pop.text(`Population: ${d.population} Millions`);
    })
    .on('mouseout', function () {
      d3.select(this).style('fill', '#00FA9A');
      tooltip.style('visibility', 'hidden');
    });

进入全屏模式 退出全屏模式

为轴添加文本标签:

  // text label for the y axis
  svgElement
    .append('text')
    .attr('transform', 'rotate(-90)')
    .attr('y', 0 - margin.left)
    .attr('x', 0 - height / 2)
    .attr('dy', '1em')
    .style('text-anchor', 'middle')
    .style('fill', 'white')
    .text('Population (in millions)');

  // text label for the y axis
  svgElement
    .append('text')
    .attr('y', height + 30)
    .attr('x', 0 + width / 2)
    .attr('dy', '1em')
    .style('text-anchor', 'middle')
    .style('fill', 'white')
    .text('Country name');  

进入全屏模式 退出全屏模式

执行代码:

在主范围内简单地执行我们的功能

getData().then(generateChart);

进入全屏模式 退出全屏模式

你去了,现在你应该有这个结果。

如果你想检查整个代码:这里

数据可视化领域真的有很多可能性和东西可以构建,只是好奇和探索!

我希望我已经足够清楚,它可以帮助您理解图表构造以创建更好的 UI。

祝你有美好的一天!

Logo

华为、百度、京东云现已入驻,来创建你的专属开发者社区吧!

更多推荐