D3.js 教程: 使用 JavaScript 建立可交互的柱状图

原文连接:D3.js Tutorial: Building Interactive Bar Charts with JavaScriptjavascript

译者:OFEDcss

最近,咱们有幸参与了一个机器学习项目,该项目涉及 React 和 D3.js 之类的库。在许多任务中,我开发了几个图表用来展现诸如朴素贝叶斯这样的机器学习模型的处理结果,图表以折线图或分组柱状图的形式呈现。html

我会在此文中介绍使用 D3.js 的过程,以及经过一个简单的柱状图示例演示库的基本使用。前端

读完此文后,你将学到如何轻松建立相似的 D3.js 图表:java

bar chart

这里有完整的源代码git

咱们在 RisingStack(公司)喜欢 JavaScript 生态,前端,后端都喜欢。就我我的而言,我对先后端都感兴趣。经过后端开发,我能够看透应用程序的底层业务逻辑,同时也有机会在前端建立使人惊叹的效果。这正是 D3.js 的用武之地!github

D3.js 是什么?

D3.js 是一个能够基于数据来操做文档的 JavaScript 库。编程

“D3 能够帮助你使用 HTML, CSS, SVG 以及 Canvas 来展现数据。D3 遵循现有的 Web 标准,能够不须要其余任何框架独立运行在现代浏览器中,它结合强大的可视化组件来驱动 DOM 操做。” - d3js.org后端

首先考虑为何要用 D3.js 建立图表?为何不仅显示图片呢?api

图表是基于第三方资源的信息,在渲染时须要动态可视化。此外,SVG 是一个很是强大的工具,很是适合这个应用场景。

让咱们先看看 SVG 有什么好。

SVG 的优势

SVG 表明可缩放矢量图形,从技术上讲,这是一种基于 XML 的标记语言。

它一般用于绘制矢量图形,好比线条和形状或修改现有图像。你能够在这里找到可用元素的列表。

优势:

  • 支持全部主流浏览器;
  • 有 DOM 接口,不须要第三方库;
  • 可伸缩,可保持高分辨率;
  • 和其余图像格式相比,体积更小。

缺点:

  • 只能显示二维图像;
  • 学习曲线长;
  • 对于计算密集型操做,渲染可能须要很长时间。

SVG 尽管有缺点,但它还是显示图标,logo,插图或者此文说起的图表的优良工具。

开始使用 D3.js

我选择以柱状图做为开始,由于它表明了一个低复杂度的视觉元素,同时它还能教会 D3.js 自己的基本应用。没骗你,D3 提供了一套很棒的可视化数据的工具。看看它的 github page 页面,欣赏一些很是好的用例!

柱状图能够是水平或垂直的,取决于它的方向。咱们从垂直的柱状图开始。

在这个图表中,我将根据 Stack Overlow 2018年开发者调查结果显示前十个最受欢迎的编程语言。

画起来!

SVG 的坐标系从左上角开始(0;0)。正 x 轴向右,正 y 轴向下。所以,在计算元素的 y 坐标时,必须考虑 SVG 的高度。

axis

背景知识差很少了,让咱们撸代码吧!

我想建立一个宽1000像素、高600像素的图表。

<body>
    <svg />
</body>
<script> const margin = 60; const width = 1000 - 2 * margin; const height = 600 - 2 * margin; const svg = d3.select('svg'); </script>
复制代码

以上代码片断中,我用 d3 select 选择了 HTML 建立的 <svg> 元素。此选择方法接收各类类型的选择器字符串并返回第一个匹配元素。若是想获取全部匹配元素,使用 selectAll

我还定义了一个边距值,它给图表提供了一点间距。间距也能够应用到 <g> 元素上,经过 translate 移动指望的值。从如今起,我将在这个分组中绘制,确保与页面其它内容保持合理的间距。

const chart = svg.append('g')
    .attr('transform', `translate(${margin}, ${margin})`);
复制代码

往元素添加属性就像调用 attr 方法同样简单。方法的第一个参数接收用于所选 DOM 元素的属性。第二个参数是属性值或返回其值的回调函数。以上代码简单将图表的原点移到 SVG 的 (60;60) 位置。

D3.js 支持的数据源格式

要开始绘图,我须要定义使用的数据源。本教程中,我使用了一个简单的 JavaScript 数组,该数组保存了语言名称及其所占百分比率的对象,可是这里着重提到一点,D3.js 支持多种数据格式。

该库具有从 XMLHttpRequest,.csv 文件,文本文件等数据源加载数据的内置功能。每一种数据源均可能包含 D3.js 可用的数据,最重要的是把它们构建成数组。注意,从 版本5.0 开始,D3 库使用 Promise 取代回调来加载数据,这是一次不向后兼容的更改。

缩放,坐标轴

让咱们继续讨论图表的坐标轴。为了画 y 轴,我须要设定最小和最大值,分别设置为0和100。

本教程中,我正在研究使用百分比,可是除了数字以外,还有其余数据类型的实用函数,我将在后面解释。

我必须将图表的高度在这两个值之间均分。为此,我建立了一个缩放函数。

const yScale = d3.scaleLinear()
    .range([height, 0])
    .domain([0, 100]);
复制代码

线性缩放是最多见的缩放类型。它将连续输入范围转换为连续输出范围。请注意 rangedomain 方法。第一个 range 方法取的长度应该在 domain 的边界值之间。

记住,SVG 坐标系从左上角开始,这就是为何 range 将高度做为第一个参数而不是零。

在左侧建立一个坐标轴跟添加另外一个分组同样简单,调用 d3 的 axisLeft 方法,并把缩放函数做为参数。

chart.append('g')
    .call(d3.axisLeft(yScale));
复制代码

如今,继续添加 x 轴。

const xScale = d3.scaleBand()
    .range([0, width])
    .domain(sample.map((s) => s.language))
    .padding(0.2)

chart.append('g')
    .attr('transform', `translate(0, ${height})`)
    .call(d3.axisBottom(xScale));
复制代码

请注意,我使用 scaleBand 方法建立 x 轴,它将 x 轴 分红多段,而且使用余下的间隙计算柱状图的坐标和宽度。

D3.js 还能处理许多其余日期类型。scaleTimescaleLinear 很是类似,只是这里的 domain 是一个日期数组。

使用 D3.js 绘制柱状图

想一想咱们须要什么样的输入来画柱条。它们各自表明一个用简单形状,特别是矩形来展现的值。下一段代码中,我把它们添加到已建立的分组元素中了。

chart.selectAll()
    .data(goals)
    .enter()
    .append('rect')
    .attr('x', (s) => xScale(s.language))
    .attr('y', (s) => yScale(s.value))
    .attr('height', (s) => height - yScale(s.value))
    .attr('width', xScale.bandwidth())
复制代码

首先,我 selectAll 图表上的全部元素,返回结果为空。而后,data 函数根据数组长度通知 DOM 应该更新多少元素。若是数据个数多于 DOM 个数时,则 enter 会标识出缺乏的元素。enter 会返回须要添加的元素。 一般,后面紧跟 append 方法会把元素添加到 DOM 中。

基本上,我用 D3.js 给数组每一项都追加了一个矩形。

当前只在彼此顶部添加了没有宽高的矩形。这两个属性必须经过以前的缩放函数计算所得。

我调用 attr 方法添加了矩形坐标。第二个参数能够是回调,它返回3个参数:当前绑定的数据,索引和全部数据数组。

.attr(’x’, (actual, index, array) =>
    xScale(actual.value))
复制代码

缩放函数返回给定范围值的坐标。计算坐标就是小菜一碟,诀窍是利用柱子的高度。必须从图表的高度减去计算出的 y 坐标,才能获得正确的列值。

定义矩形的宽度也会用到缩放函数。scaleBand 有一个 bandwidth 函数,它基于设置的间距返回一个元素的计算宽度。

干得不错,但没那么花哨,对吧?

为了防止观众视觉疲劳,让咱们添加一些信息改善下视觉效果!

制做柱状图的技巧

有一些基本规则值得一提。

  • 避免使用 3D 效果;
  • 直观地排序数据点 - 按字母顺序或按数字排序;
  • 柱条之间保持必定距离;
  • y 轴从 0 开始,而不是从最小值开始;
  • 使用统一的颜色;
  • 添加轴标签、标题、导引线。

D3.js 网格系统

我想在背景中添加栅格线突出那些值。

垂直和水平的线均可以添加,个人建议是只添加一种。过多的线会分散注意力。如下代码片断演示了如何添加水平和垂直的栅格。

chart.append('g')
    .attr('class', 'grid')
    .attr('transform', `translate(0, ${height})`)
    .call(d3.axisBottom()
        .scale(xScale)
        .tickSize(-height, 0, 0)
        .tickFormat(''))

chart.append('g')
    .attr('class', 'grid')
    .call(d3.axisLeft()
        .scale(yScale)
        .tickSize(-width, 0, 0)
        .tickFormat(''))
复制代码

此例中,我更喜欢垂直栅格线,由于它能够引导视线,保持总体画面简介明快。

D3.js 中的标签

我还想添加一些文字指导,从而使图表更加全面。让咱们给图表命个名,并为坐标轴添加标签吧。

文本是 SVG 元素,一样能够添加到 SVG 或者分组中。它们可使用 x 和 y 坐标定位,文本对齐是经过 text-anchor 属性实现的。 添加标签文字,只需调用文本元素上的 text 方法。

svg.append('text')
    .attr('x', -(height / 2) - margin)
    .attr('y', margin / 2.4)
    .attr('transform', 'rotate(-90)')
    .attr('text-anchor', 'middle')
    .text('Love meter (%)')

svg.append('text')
    .attr('x', width / 2 + margin)
    .attr('y', 40)
    .attr('text-anchor', 'middle')
    .text('Most loved programming languages in 2018')
复制代码

与 D3.js 交互

咱们的图表内容已然丰富,可是仍然能够添加些互动效果。

如下的代码演示了如何给 SVG 元素添加事件监听。

svgElement
    .on('mouseenter', function (actual, i) {
        d3.select(this).attr(‘opacity’, 0.5)
    })
    .on('mouseleave’, function (actual, i) {
        d3.select(this).attr(‘opacity’, 1)
    })
复制代码

注意,我用了函数表达式而不是箭头函数,由于我经过 this 关键字访问元素。

当鼠标滑过选中的 SVG 元素时,它的透明度变为原始值的一半,鼠标离开元素时透明度恢复原始值。

你也能够经过 d3.mouse 获取鼠标坐标。它返回一个具备 x 和 y 坐标的数组。在光标所在位置显示提示,就能够经过这个实现。

建立使人瞠目结舌的图表并没那么简单。

可能须要图形设计师,UX 研究员和其余牛人的智慧。如下例子展现了几个提高图表效果的可能性!

咱们的图表显示了很是类似的值,因此为了突出条形值之间的差别,我添加了一个 mouseenter 事件。每当用户悬停在特定的列时,该栏的顶部就会画一条水平线。此外,我还计算了与其余柱条的差别,并显示在了相应的柱条上。

很整齐吧?我还在此例中增长了透明度,加大了柱条的宽度。

.on(‘mouseenter’, function (s, i) {
    d3.select(this)
        .transition()
        .duration(300)
        .attr('opacity', 0.6)
        .attr('x', (a) => xScale(a.language) - 5)
        .attr('width', xScale.bandwidth() + 10)

    chart.append('line')
        .attr('x1', 0)
        .attr('y1', y)
        .attr('x2', width)
        .attr('y2', y)
        .attr('stroke', 'red')

    // 部分实现,总体效果见源码
})
复制代码

transition 方法代表我想把 DOM 改变绘制成动画。它的时间间隔是用 duration 函数设置的,该函数以毫秒做为参数。上面的过渡会淡化带状颜色,并加宽条形的宽度。

要画一条 SVG 线,我须要起点和终点。这能够经过 x1y1x2y2 坐标来设置。直到我用 stroke 属性设置线条的颜色,线条才可见。

这里只展现了 mouseenter 事件这部分,切记,必须在 mouseout 事件上恢复或删除更改。本文末尾提供了完整的源代码。

让咱们给图表添加一些样式吧!

回顾下咱们目前为止完成了那些功能,以及如何经过样式装扮图表。能够经过先前用过的 attr 方法给 SVG 元素添加 class 属性。

咱们的图表功能丰富,而不是死板的静态图片,鼠标悬停时能够显示各个柱条的差值。标题交表明格的背景,标签帮助识别坐标轴的测量单位。我还在右下角添加了新的标签,注明数据来源。

剩下的事情就差颜色和字体了!

深色背景的图表使亮色柱条看起来很酷。我还使用了 open Sans 字体,并给不一样的标签设置不一样的大小和粗细。

注意到那条虚线了吗?它是经过 stroke-widthstroke-dasharray 属性实现的。使用 stroke-dasharray,能够定义虚线的图案和间距,从而改变形状的轮廓。

line#limit {
    stroke: #FED966;
    stroke-width: 3;
    stroke-dasharray: 3 6;
}

.grid path {
    stroke-width: 3;
}

.grid .tick line {
    stroke: #9FAAAE;
    stroke-opacity: 0.2;
}
复制代码

网格线比较讨巧,我给分组中的路径元素使用了 stroke-width: 0,为了隐藏表格的框架,我还经过设置线条的透明度下降它们的可见性。

全部其它有关字体大小和颜色的 CSS 能够参照源码。

收尾咱们的 D3.js 柱状图教程

D3.js 是一个使人惊叹的 DOM 操做库。它的内部埋藏了无数的宝藏等待你去探索(确切的说,不是埋藏,文档也很齐全)。此文仅仅使用了它的工具集的冰山一角,就建立了一个不一样凡响的柱状图。

继续探索吧,定能创造出无比壮观的视觉效果!

这是本文示例源代码 的连接。

你用 D3.js 作过一些炫酷的东西吗?和咱们分享一下!你有任何问题,或者想要关于这个主题的另外一个教程,欢迎留言!

谢谢阅读,下次再见!

相关文章
相关标签/搜索