如何剪切y轴让图表看起来更好?

Akb*_*bar 3 data-visualization d3.js

我想要做的是使用 D3.js (v4) 创建一个条形图,它将显示 2 或 3 个数值差异较大的条形图。

如下图所示,黄色条的值为 1596.6,而绿色条的值为 177.2。因此,为了以优雅的方式显示图表,决定将 y 轴切割为某个值,该值接近绿色条的值,并继续接近黄色条的值。

在图中,y 轴在 500 后被切断,并在 1500 后继续。

如何使用 D3.js 做到这一点?

在此输入图像描述

Ger*_*ado 5

从技术上讲,你想要的是一个损坏的 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)

正如您所看到的,bazfoobar条形图与其他条形图相比显得相形见绌,因为​​它们的差异非常大。

在我看来,最简单的解决方案是在 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 轴上放置任何符号,这不是一个好的做法。