首页 > 解决方案 > D3.js V5 - 为什么我的条形图轴没有缩放?

问题描述

我正在使用以下数据在 D3 中制作条形图:[-76, -55, -51, -44, -35, 27]

Y 轴应该从 -100 到 100。

这是代码:

var data = [-76, -55, -51, -44, -35, 27];

var leftMargin = 50;  // Space to the left of first bar; accomodates y-axis labels
var rightMargin = 10; // Space to the right of last bar
var margin = {left: leftMargin, right: rightMargin, top: 10, bottom: 10};
var barWidth = 30;  // Width of the bars
var chartHeight = height;  // Height of chart, from x-axis (ie. y=0)
var chartWidth = margin.left + data.length * barWidth + margin.right;

/* This scale produces negative output for negatve input */
var yScale = d3.scaleLinear()
  .domain([0, d3.max(data)])
  .range([0, chartHeight]);

/*
 * We need a different scale for drawing the y-axis. It needs
 * a reversed range, and a larger domain to accomodate negaive values.
 */
var yAxisScale = d3.scaleLinear()
  .domain([-100, 100])
  .range([chartHeight - yScale(d3.min(data)), 0 ]);

svg2
  .attr('height', chartHeight + 100)
  .attr('width', chartWidth)
  .style('border', '1px solid');

svg2
  .selectAll("rect")
  .data(data)
  .enter()
  .append("rect")
  .attr("x", function(d, i) { return margin.left + i * barWidth; })
  .attr("y", function(d, i) { return chartHeight - Math.max(0, yScale(d));})
  .attr("height", function(d) { return Math.abs(yScale(d)); })
  .attr("width", barWidth)
  .style("fill", "grey")
  .style("stroke", "black")
  .style("stroke-width", "1px")
  .style("opacity", function(d, i) { return 1 /*- (i * (1/data.length)); */});

var yAxis = d3.axisLeft(yAxisScale);

svg2.append('g')
  .attr('transform', function(d) {
    return 'translate(' + margin.left + ', 0)';
  })
  .call(yAxis);



这是输出的图表——你可以看到比例关闭并且图像没有缩放到 SVG 容器——负条超出了 svg 的底部。在此处输入图像描述

有人可以帮我正确格式化这个条形图吗?

标签: javascriptd3.js

解决方案


/* * 我们需要不同的比例来绘制 y 轴。它需要 * 一个反向范围和一个更大的域来容纳负值。*/

这不是必需的,并且会产生更多的复杂性。一个刻度就足够了,并且始终将您的数据与您的轴相匹配。一旦为绘制的轴和数据创建不同的比例,您就会发现两者之间存在更大的冲突可能性。

相反,让我们在这里创建一个比例yScale

var yScale = d3.scaleLinear()
   .domain([-100,100])  // min & max of input values
   .range([chartHeight,0]) // mapped to the bottom and top of the plot area

对于 0 以下的值,负条,条的顶部将位于yScale(0)。矩形的底部将位于yScale(value)。两者之差,即高度,将等于yScale(value) - yScale(0)

对于大于 0 的值,条形的底部将位于yScale(0),条形的顶部将位于yScale(value)。要找到我们使用的高度yScale(0)-yScale(value)

由于高度只是两者之间的绝对差yScale(0)yScale(value)我们可以用它Math.abs(yScale(0)-yScale(value))来找到所有矩形的高度。

考虑到上述情况,我已经对您的示例代码进行了改造:

var data = [-76, -55, -51, -44, -35, 27];

var height = 250;
var width = 500;
var margin = {left: 50, right: 10, top: 20, bottom: 20};

var svg = d3.select("body").append("svg")
  .attr('height', height)
  .attr('width', width)
  .style('border', '1px solid')
  .append("g")
  // apply the margins:
  .attr("transform","translate("+[margin.left+","+margin.top]+")");


var barWidth = 30;  // Width of the bars

// plot area is height - vertical margins.
var chartHeight = height-margin.top-margin.left;  

// set the scale:
var yScale = d3.scaleLinear()
  .domain([-100, 100])
  .range([chartHeight, 0]);

// draw some rectangles:
svg
  .selectAll("rect")
  .data(data)
  .enter()
  .append("rect")
  .attr("x", function(d, i) { return i * barWidth; })
  .attr("y", function(d,i) {
     if(d < 0) {
       return yScale(0); // if the value is under zero, the top of the bar is at yScale(0);
     }
     else {
      return yScale(d);  // otherwise the rectangle' top is above yScale(0) at yScale(d);
     }
  })
  .attr("height", function(d) {
     // the height of the rectangle is the difference between the scale value and yScale(0);
      return Math.abs(yScale(0) - yScale(d));
   }) 
  .attr("width", barWidth)
  .style("fill", "grey")
  .style("stroke", "black")
  .style("stroke-width", "1px")

var yAxis = d3.axisLeft(yScale);

svg.append('g')
  .call(yAxis);
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>

此外,我们可以进一步简化一点。yScale(value)yScale(0)(接近 SVG 顶部的值)的最小值将是矩形的顶部,无论矩形表示的值如何。

var data = [-76, -55, -51, -44, -35, 27];

var height = 250;
var width = 500;
var margin = {left: 50, right: 10, top: 20, bottom: 20};

var svg = d3.select("body").append("svg")
  .attr('height', height)
  .attr('width', width)
  .style('border', '1px solid')
  .append("g")
  // apply the margins:
  .attr("transform","translate("+[margin.left+","+margin.top]+")");


var barWidth = 30;  // Width of the bars

// plot area is height - vertical margins.
var chartHeight = height-margin.top-margin.left;  

// set the scale:
var yScale = d3.scaleLinear()
  .domain([-100, 100])
  .range([chartHeight, 0]);

// draw some rectangles:
svg
  .selectAll("rect")
  .data(data)
  .enter()
  .append("rect")
  .attr("x", function(d, i) { return i * barWidth; })
  .attr("y", function(d,i) {
     return Math.min(yScale(0),yScale(d))
  })
  .attr("height", function(d) {
     // the height of the rectangle is the difference between the scale value and yScale(0);
      return Math.abs(yScale(0) - yScale(d));
   }) 
  .attr("width", barWidth)
  .style("fill", "grey")
  .style("stroke", "black")
  .style("stroke-width", "1px")

var yAxis = d3.axisLeft(yScale);

svg.append('g')
  .call(yAxis);
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>

通过使用两者中的最小值,它给了我们更多的自由:我们可以通过只改变尺度的域来翻转图形(如果出于某种原因我们想将负值表示为正值......)


推荐阅读