【发布时间】:2018-01-05 04:58:39
【问题描述】:
我想要做的是使用 D3.js (v4) 创建一个条形图,它将显示 2 或 3 个值有很大差异的条形图。
如下图所示,黄色条的值为 1596.6,而绿色条的值为 177.2。因此,为了以优雅的方式显示图表,决定将 y 轴切割为某个值,该值将接近绿色条的值并继续接近黄色条的值。
图片上y轴在500之后被切割,在1500之后继续。
如何使用 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)"));
<script src="https://d3js.org/d3.v4.js"></script>
如您所见,baz 和 foobar 条与其他条相比相形见绌,因为它们的差异非常大。
在我看来,最简单的解决方案是在 y 刻度的范围和域中添加中点。所以,在上面的 sn-p 中,这是 y 比例:
var yScale = d3.scaleLinear()
.domain([0, d3.max(data, function(d) {
return d.value
})])
.range([h - marginBottom, 0]);
如果我们给它添加中点,它会变成这样:
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]);
如您所见,这里有一些神奇的数字。根据您的需要更改它们。
通过插入中点,这是域和范围之间的相关性:
+--------+-------------+---------------+---------------+------------+
| | 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 |
+--------+-------------+---------------+---------------+------------+
您可以看到相关性不成比例,而这正是我们想要的。
结果如下:
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, 20, 700, d3.max(data, function(d) {
return d.value
})])
.range([h - marginBottom, h/2 + 2, h/2 - 2, 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).tickValues([0, 5, 10, 15, 700, 750, 800])(svg.append("g").attr("transform", "translate(" + marginLeft + ",0)"));
svg.append("rect")
.attr("x", marginLeft - 10)
.attr("y", yScale(19.5))
.attr("height", 10)
.attr("width", 20);
svg.append("rect")
.attr("x", marginLeft - 10)
.attr("y", yScale(19))
.attr("height", 6)
.attr("width", 20)
.style("fill", "white");
<script src="https://d3js.org/d3.v4.js"></script>
如您所见,我必须使用 tickValues 设置 y 轴刻度,否则会一团糟。
最后,我怎么强调都不过分:不要忘记显示 y 轴已损坏。在上面的 sn-p 中,我在 y 轴上的数字 15 和数字 700 之间放置了符号(参见我的答案中的第一个图)。顺便说一句,值得注意的是,在您分享的图中,其作者没有在 y 轴上放置任何符号,这不是一个好习惯。
【讨论】: