如何在D3.js中将scaleBand与scaleLinear相匹配

时间:2017-02-18 03:28:38

标签: d3.js

我有两个系列的比例,一个是线性的,另一个是波段,如果数据中有一些上限,我怎么能让它们匹配。

如有必要,请查看示例。 将鼠标移到上方,您会看到这些框与行中断不匹配。

2 个答案:

答案 0 :(得分:3)

嗯,对你来说是坏消息:他们从不匹配(在你的情况下)。让我们看看为什么。

这是您的数据:

let testData1 = [
    [1, 10],
    [2, 30],
    [8, 34],
    [9, 26],
    [13, 37],
    [14, 12],
    [20, 23],
    [22, 16],
];

正如你所看到的,关于x坐标,线从1跳到2,然后从2跳到8,从8跳到9,然后从9跳到13 ......也就是说,x范围间隔是不规则,均匀分布。到目前为止,非常好。

然而,当你将相同的数据传递给波段时,它就是这样做的:它将范围([0, width],基本上是宽度)除以testData1.length,即它将范围除以8,并创建8个相等的区间。那些是你的乐队,这是乐队规模的预期行为。来自documentation

  

通过将连续范围除以均匀波段,自动计算离散输出值。 (强调我的)

这里的一个解决方案就是使用另一个线性刻度:

let xBand = d3.scaleLinear()
    .domain(xDomain)
    .rangeRound([0, width]);

这个数学到矩形的宽度:

.attr('width', (d,i) => testData1[i+1] ? xBand(testData1[i+1][0]) - xBand(d[0]) : 0)

以下是您更新的Codepen:http://codepen.io/anon/pen/MJdGyY?editors=0010

答案 1 :(得分:3)

如果您希望缩放(扩展)scaleBand数据丢失,我不认为scaleBand是正确的方法,但目前还不清楚是否是你想要的东西。带尺度旨在为每个数据值提供相等的间距,并且所有值都存在 - 它是一个有序的尺度。

假设您只希望将频段比例与存在的数据对齐:

如果您记录每个x标度(scaleBandscaleLinear)的域名,我们会发现scaleBand的域名为:

[ "1", "2", "8", "9", "13", "14", "20", "22" ] // 8 elements

scaleLinear的域名为:

[ 1, 22 ]  // a span of 22 'elements'

scaleBand需要与scaleLinear等效的域名。你可以静态地做这个(我主要是为了演示d3.range如何工作):

let xBand = d3.scaleBand()
  .domain(d3.range(1,23))
  .rangeRound([0, width]);

这实际上会生成一个包含22到22个元素的域。

或动态:

let xBand = d3.scaleBand()
   .domain(d3.range(d3.min(testData1, d => d[0],
                    d3.max(testData1, d => d[0]+1)))

您可以通过其他方式执行此操作,但d3.range() function is nice and easy

然而,仍然存在一个问题,即这是两个尺度之间的对齐。对于线性刻度,第一个值(1)的刻度位于y轴上,但带隙刻度在y轴上开始(并且不居中)并填充1和换句话说,带的中心点不与线图的顶点垂直对齐。

这可以通过在线性标度域的下限和上限上加0.5来解决:

let xDomain = [
  d3.min(testData1, d => d[0]-0.5),
  d3.max(testData1, d => d[0]+0.5)
];

我已使用相关更改来编写您的codepen:codepen

如果消失的话,这里有一个片段(鼠标悬停在片段中由于某种原因对我不起作用,它在codepen中有效)



let width = 1000;
let height = 300;

let svg = d3.select(".wrapper-area-simple").append("svg")
  .attr("width", width + 80)
  .attr("height", height + 80)
  .append('svg:g')
  .attr('transform', 'translate(40, 30)');

let testData1 = [
  [ 1, 10],
  [ 2, 30],
  [ 8, 34],
  [ 9, 26],
  [13, 37],
  [14, 12],
  [20, 23],
  [22, 16],
];

let xDomain = [
  d3.min(testData1, d => d[0]-0.5),
  d3.max(testData1, d => d[0]+0.5)
];



let x = d3.scaleLinear()
  .rangeRound([0, width])
  .domain(xDomain);

let y = d3.scaleLinear()
  .range([height, 0])
  .domain(d3.extent(testData1, d => d[1]));

let line = d3.line()
  .x(d => x(d[0]))
  .y(d => y(d[1]));

svg.append('svg:g')
  .datum(testData1)
  .append('svg:path')
  .attr('d', line)
  .attr('fill', 'none')
  .attr('stroke', '#000');

let xAxis = d3.axisBottom(x)
  .ticks(testData1.length);
svg.append('svg:g')
  .call(xAxis)
  .attr('transform', `translate(0, 300)`);
  
let xBand = d3.scaleBand()
   .domain(d3.range(d3.min(testData1, d => d[0]),
                    d3.max(testData1, d => d[0]+1)
                    ))
   .rangeRound([0, width]);

svg.append('svg:g')
  .selectAll('rect')
  .data(testData1)
  .enter()
  .append('svg:rect')
  .attr('x', d => xBand(d[0]))
  .attr('width', xBand.bandwidth())
  .attr('height', height)
  .attr('fill', '#000')
  .on('mouseover', function() {
    d3.select(this).classed('over', true);
  })
  .on('mouseout', function() {
    d3.select(this).classed('over', false);
  });

svg {
  border: 1px solid red;
}
rect {
  opacity: .1;
}
rect.over {
    opacity: .2;
}

<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.5.0/d3.min.js"> </script>

<div class="wrapper-area-simple"></div>
&#13;
&#13;
&#13;