Akb*_*bar 3 data-visualization d3.js
我想要做的是使用 D3.js (v4) 创建一个条形图,它将显示 2 或 3 个数值差异较大的条形图。
如下图所示,黄色条的值为 1596.6,而绿色条的值为 177.2。因此,为了以优雅的方式显示图表,决定将 y 轴切割为某个值,该值接近绿色条的值,并继续接近黄色条的值。
在图中,y 轴在 500 后被切断,并在 1500 后继续。
如何使用 D3.js 做到这一点?
从技术上讲,你想要的是一个损坏的 y 轴。
只要您明确告诉用户您的 y 轴已损坏,它就是数据可视化中的有效表示(尽管有些人对此不悦)。有几种方法可以直观地指示它,例如:
但是,不要忘记在图表文本或其图例中明确说明这一点。除此之外,请记住,“为了以优雅的方式显示图表”永远不应该成为这里的目标:我们不会使图表变得优雅(但如果优雅的话就很好),我们制作图表是为了澄清信息或给用户一个模式。而且,在一些非常有限的情况下,打破 y 轴可以更好地显示信息。
回到你的问题:
正如您在问题中所说,当您的值存在很大差异时,打破 y 轴非常有用。例如,看看我制作的这个简单的演示:
var w = 500,
h = 220,
marginLeft = 30,
marginBottom = 30;
var svg = d3.select("body")
.append("svg")
.attr("width", w)
.attr("height", h);
var data = [{
name: "foo",
value: 800
}, {
name: "bar",
value: 720
}, {
name: "baz",
value: 10
}, {
name: "foobar",
value: 17
}, {
name: "foobaz",
value: 840
}];
var xScale = d3.scaleBand()
.domain(data.map(function(d) {
return d.name
}))
.range([marginLeft, w])
.padding(.5);
var yScale = d3.scaleLinear()
.domain([0, d3.max(data, function(d) {
return d.value
})])
.range([h - marginBottom, 0]);
var bars = svg.selectAll(null)
.data(data)
.enter()
.append("rect")
.attr("x", function(d) {
return xScale(d.name)
})
.attr("width", xScale.bandwidth())
.attr("y", function(d) {
return yScale(d.value)
})
.attr("height", function(d) {
return h - marginBottom - yScale(d.value)
})
.style("fill", "teal");
var xAxis = d3.axisBottom(xScale)(svg.append("g").attr("transform", "translate(0," + (h - marginBottom) + ")"));
var yAxis = d3.axisLeft(yScale)(svg.append("g").attr("transform", "translate(" + marginLeft + ",0)"));
Run Code Online (Sandbox Code Playgroud)
<script src="https://d3js.org/d3.v4.js"></script>
Run Code Online (Sandbox Code Playgroud)
正如您所看到的,baz
和foobar
条形图与其他条形图相比显得相形见绌,因为它们的差异非常大。
在我看来,最简单的解决方案是在 y 尺度的范围和域中添加中点。因此,在上面的代码片段中,这是 y 比例:
var yScale = d3.scaleLinear()
.domain([0, d3.max(data, function(d) {
return d.value
})])
.range([h - marginBottom, 0]);
Run Code Online (Sandbox Code Playgroud)
如果我们添加中点,它就会变成这样:
var yScale = d3.scaleLinear()
.domain([0, 20, 700, d3.max(data, function(d) {
return d.value
})])
.range([h - marginBottom, h/2 + 2, h/2 - 2, 0]);
Run Code Online (Sandbox Code Playgroud)
正如您所看到的,这里有一些神奇的数字。根据您的需要更改它们。
通过插入中点,这就是域和范围之间的相关性:
+--------+-------------+---------------+---------------+------------+
| | First value | Second value | Third value | Last value |
+--------+-------------+---------------+---------------+------------+
| Domain | 0 | 20 | 700 | 840 |
| | maps to... | maps to... | maps to... | maps to... |
| Range | height | heigh / 2 - 2 | heigh / 2 + 2 | 0 |
+--------+-------------+---------------+---------------+------------+
Run Code Online (Sandbox Code Playgroud)
您可以看到相关性不是成比例的,而这正是我们想要的。
这是结果:
var yScale = d3.scaleLinear()
.domain([0, d3.max(data, function(d) {
return d.value
})])
.range([h - marginBottom, 0]);
Run Code Online (Sandbox Code Playgroud)
var yScale = d3.scaleLinear()
.domain([0, 20, 700, d3.max(data, function(d) {
return d.value
})])
.range([h - marginBottom, h/2 + 2, h/2 - 2, 0]);
Run Code Online (Sandbox Code Playgroud)
正如你所看到的,我必须使用 设置 y 轴刻度tickValues
,否则会很混乱。
最后,我必须强调这一点:不要忘记表明 y 轴已损坏。在上面的代码片段中,我将符号(请参阅答案中的第一个数字)放在数字和y 轴中的15
数字之间。700
顺便说一句,值得注意的是,在您分享的图中,其作者没有在 y 轴上放置任何符号,这不是一个好的做法。