如何切割y轴使图表看起来更好?

时间:2018-01-05 04:58:40

标签: d3.js data-visualization

我尝试做的是使用D3.js(v4)创建条形图,该条形图将显示2或3个值,这些条形图有很大差异。

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

在图片上,y轴在500之后被切断,并在1500之后继续。

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

enter image description here

1 个答案:

答案 0 :(得分:3)

从技术上讲,你想要的是一个断开y轴

它是数据可视化中的有效表示(尽管有些人不赞成),只要您明确告诉用户您的y轴已损坏。有几种方法可以直观地表明它,如下所示:

enter image description here

但是,不要忘记在图表文本或其图例中明确说明。除此之外,请记住“为了以优雅的方式显示图表” 永远不会成为这里的目标:我们不会使图表优雅(但是如果它们是好的,我们制作图表以澄清信息或模式给用户。而且,在一些非常有限的情况下,打破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>

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

在我看来,最简单的解决方案是在添加中点。因此,在上面的代码片段中,这是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轴被打破。在上面的代码段中,我将符号(请参阅我的答案中的第一个数字)放在数字15和数字700之间的y轴上。顺便说一下,值得注意的是,在你共享的图中,作者没有在y轴上放置任何符号,这不是一个好习惯。