我尝试做的是使用D3.js(v4)创建条形图,该条形图将显示2或3个值,这些条形图有很大差异。
如下图所示黄色条的值为1596.6,而绿色条的值仅为177.2。因此,为了以优雅的方式显示图表,决定将y轴切割为接近绿条值的特定值,并继续接近黄条的值。
在图片上,y轴在500之后被切断,并在1500之后继续。
如何使用D3.js做到这一点?
答案 0 :(得分:3)
从技术上讲,你想要的是一个断开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
栏与其他栏相比相形见绌,因为它们的差异非常大。
在我看来,最简单的解决方案是在
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轴被打破。在上面的代码段中,我将符号(请参阅我的答案中的第一个数字)放在数字15
和数字700
之间的y轴上。顺便说一下,值得注意的是,在你共享的图中,作者没有在y轴上放置任何符号,这不是一个好习惯。