javascript D3.js:计算变化范围内的时间尺度条的宽度?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/18835053/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-27 13:22:17  来源:igfitidea点击:

D3.js: calculate width of bars in time scale with changing range?

javascriptd3.js

提问by Richard

I'm building a D3 bar chart with a time scale on the x-axis. The range of the x-axis can vary.

我正在构建一个在 x 轴上带有时间刻度的 D3 条形图。x 轴的范围可以变化。

How can I specify the correct width for the bars on the bar chart? I've seen people use rangeBandsfor ordinal scales, but I'm not sure how to do this with a time scale.

如何为条形图上的条形指定正确的宽度?我见过人们使用rangeBands序数尺度,但我不确定如何使用时间尺度来做到这一点。

Here is my code:

这是我的代码:

var x = d3.time.scale().range([0, width]);
var xAxis = d3.svg.axis().scale(x).orient("bottom");
[...]

// After new data has been fetched, update the domain of x...
x.domain(d3.extent(data, function(d) { return d.date; }));
d3.select(".x.axis").call(xAxis);

// ... and draw bars for chart
var bars = svg.selectAll(".air_used")
    .data(data, function(d) { return d.date; });

bars.attr("y", function(d) { return y(d.air_used); })
.attr("height", function(d) { return height - y(d.air_used); })
.attr("x", function(d) { return x(d.date); })
.attr("width", 20) // ??

What should I put on the last line, in place of 20?

我应该在最后一行放什么来代替 20?

UPDATE: JSFiddle here: http://jsfiddle.net/aWJtJ/2/

更新:JSFiddle 在这里:http: //jsfiddle.net/aWJtJ/2/

回答by Lars Kotthoff

There's no function to get the width, but you can calculate it quite easily:

没有获取宽度的函数,但您可以很容易地计算它:

.attr("width", width/data.length);

You might want to subtract a small amount from that if you don't want the bars to touch. You would also need to adjust the xposition accordingly, e.g.

如果您不希望条形接触,您可能需要从中减去少量。您还需要相应地调整x位置,例如

.attr("x", function(d) { return x(d.date) - (width/data.length)/2; })
.attr("width", width/data.length);

To get the ticks to align properly, you'll also need to adjust the range of the x axis scale because the first tick will be placed at the first value:

为了使刻度正确对齐,您还需要调整 x 轴刻度的范围,因为第一个刻度将放置在第一个值处:

var x = d3.time.scale().range([width/data.length/2, width-width/data.length/2]);

Complete jsfiddle here.

在这里完成 jsfiddle 。

回答by saaj

Even though I agree with Scott that a bar chart is meant to be used with ordinal or categorical data on x axis, I guess the question is more about a convenience of drawing a time axis. As d3.time.scaledoes a really good job with help of d3.time.format.multiof drawing time axis of various duration (hours, days, months, etc.) and its ticks, it could be a good idea to combine d3.time.scalefor an axis, and d3.scale.ordinalfor a band width calculation.

尽管我同意 Scott 的观点,即条形图旨在与 x 轴上的序数或分类数据一起使用,但我想问题更多的是关于绘制时间轴的便利性。由于d3.time.scale确实有帮助切实做好d3.time.format.multi绘制的各种持续时间(小时,天,月等)及其蜱时间轴,它可能是一个好主意,合并d3.time.scale为一轴线,并d3.scale.ordinal为宽度计算带。

The snippet below is inspired by the discussionin D3 Google Group about the topic. The unit for ordinal scale is a day.

该片段下面被激发讨论的话题在D3谷歌集团。序数刻度的单位是天。

function prepare(data)
{
  var dateParse = d3.time.format('%Y-%m-%d');
  data.forEach(function(v)
  {
    v.date = dateParse.parse(v.date);
  });

  var dateValueMap = data.reduce(function(r, v)
  {
    r[v.date.toISOString()] = v.value;
    return r;
  }, {});

  var dateExtent = d3.extent(data.map(function(v)
  {
    return v.date;
  }));

  // make data have each date within the extent
  var fullDayRange = d3.time.day.range(
    dateExtent[0], 
    d3.time.day.offset(dateExtent[1], 1)
  );
  fullDayRange.forEach(function(date)
  {
    if(!(date.toISOString() in dateValueMap))
    {
      data.push({
        'date'  : date,
        'value' : 0
      });
    }
  });

  data = data.sort(function(a, b)
  {
    return a.date - b.date;
  });

  return data;
}

function draw(data)
{
  var margin = {
    'top'    : 10, 
    'right'  : 20, 
    'bottom' : 20, 
    'left'   : 60
  };
  var size = {
    'width'  : 600 - margin.left - margin.right,
    'height' : 180 - margin.top - margin.bottom
  };

  var svg = d3.select('#chart').append('svg')
    .attr('width',  '100%')
    .attr('height', '100%')
    .append('g')
    .attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');

  var dates = data.map(function(v)
  {
    return v.date;
  });
  var x = d3.time.scale()
    .range([0, size.width])
    .domain(d3.extent(dates));

  var y = d3.scale.linear()
    .range([size.height, 0])
    .domain([0, d3.max(data.map(function(v)
    {
      return v.value;
    }))]);

  var xAxis = d3.svg.axis()
  .scale(x)
  .orient('bottom');

  var yAxis = d3.svg.axis()
    .scale(y)
    .orient('left');

  var barWidth = d3.scale.ordinal()
    .domain(dates)
    .rangeRoundBands(x.range(), 0.1)
    .rangeBand(); 

  svg.append('g')
    .attr('class', 'x axis')
    .attr('transform', 'translate(' + barWidth / 2 + ',' + size.height + ')')
    .call(xAxis);

  svg.append('g')
    .attr('class', 'y axis')
    .call(yAxis)
    .append('text')
    .attr('transform', 'rotate(-90)')
    .attr('y', 6)
    .attr('dy', '.71em')
    .style('text-anchor', 'end')
    .text('Amount');

  svg.selectAll('.bar')
    .data(data)
    .enter()
    .append('rect')
    .attr('class', 'bar')
    .attr('x', function(d) 
    { 
      return x(d.date); 
    })
    .attr('width', barWidth)
    .attr('y', function(d) 
    { 
      return y(d.value); 
    })
    .attr('height', function(d) 
    { 
      return size.height - y(d.value); 
    });
}

function getData()
{
  return [
    {'date': '2014-01-31', 'value': 5261.38}, 
    {'date': '2014-02-02', 'value': 7460.23}, 
    {'date': '2014-02-03', 'value': 8553.39}, 
    {'date': '2014-02-04', 'value': 3897.18}, 
    {'date': '2014-02-05', 'value': 2822.22}, 
    {'date': '2014-02-06', 'value': 6762.49}, 
    {'date': '2014-02-07', 'value': 8624.56}, 
    {'date': '2014-02-08', 'value': 7870.35}, 
    {'date': '2014-02-09', 'value': 7991.43}, 
    {'date': '2014-02-10', 'value': 9947.14}, 
    {'date': '2014-02-11', 'value': 6539.75}, 
    {'date': '2014-02-12', 'value': 2487.3}, 
    {'date': '2014-02-15', 'value': 3517.38}, 
    {'date': '2014-02-16', 'value': 1919.08}, 
    {'date': '2014-02-19', 'value': 1764.8}, 
    {'date': '2014-02-20', 'value': 5607.57}, 
    {'date': '2014-02-21', 'value': 7148.87}, 
    {'date': '2014-02-22', 'value': 5496.45}, 
    {'date': '2014-02-23', 'value': 296.89}, 
    {'date': '2014-02-24', 'value': 1578.59}, 
    {'date': '2014-02-26', 'value': 1763.16}, 
    {'date': '2014-02-27', 'value': 8622.26},
    {'date': '2014-02-28', 'value': 7298.99}, 
    {'date': '2014-03-01', 'value': 3014.06}, 
    {'date': '2014-03-05', 'value': 6971.12}, 
    {'date': '2014-03-06', 'value': 2949.03}, 
    {'date': '2014-03-07', 'value': 8512.96}, 
    {'date': '2014-03-09', 'value': 7734.72}, 
    {'date': '2014-03-10', 'value': 6703.21}, 
    {'date': '2014-03-11', 'value': 9798.07}, 
    {'date': '2014-03-12', 'value': 6541.8}, 
    {'date': '2014-03-13', 'value': 915.44}, 
    {'date': '2014-03-14', 'value': 9570.82}, 
    {'date': '2014-03-16', 'value': 6459.17}, 
    {'date': '2014-03-17', 'value': 9389.62},
    {'date': '2014-03-18', 'value': 6216.9}, 
    {'date': '2014-03-19', 'value': 4433.5}, 
    {'date': '2014-03-20', 'value': 9017.23},
    {'date': '2014-03-23', 'value': 2828.45},
    {'date': '2014-03-24', 'value': 63.29}, 
    {'date': '2014-03-25', 'value': 3855.02},
    {'date': '2014-03-26', 'value': 4203.06},
    {'date': '2014-03-27', 'value': 3132.32}
  ];
}

draw(prepare(getData()));
#chart {
  width  : 600px;
  height : 180px;
}
.bar {
  fill : steelblue;
}

.axis {
  font : 10px sans-serif;
}

.axis path,
.axis line {
  fill            : none;
  stroke          : #000;
  shape-rendering : crispEdges;
}

.x.axis path {
  display : none;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<div id='chart'></div>

回答by user2283043

I ran into this problem when changing zoom levels on a bar chart with a time series based x-axis and found two solutions.

我在使用基于时间序列的 x 轴更改条形图上的缩放级别时遇到了这个问题,并找到了两个解决方案。

1) Create a companion ordinal scale to help calculate the widths.(a pain)

1)创建一个伴随序数比例来帮助计算宽度。(痛苦)

2) The time-series x axis is your friend - use it to calculate the width.

2) 时间序列 x 轴是你的朋友 - 用它来计算宽度。

If you want your bar to always be a "month" wide, irrespective of zoom level in you can do something like this. Im assuming d.date is available in the data.

如果您希望您的条形始终为“一个月”宽,无论缩放级别如何,您都可以执行以下操作。我假设 d.date 在数据中可用。

 svg.selectAll(".air_used").attr("width", function(d.date) {
    var next = d3.time.month.offset(d.date, 1);
    return (x(next)- x(d));
  });

This works well because it works for every zoom scale, and you just need to call this function at the end of your on "zoom" handler ie .on("zoom", zoomed); The x axis will usually have been adjusted at this point.

这很有效,因为它适用于每个缩放比例,您只需要在“缩放”处理程序的末尾调用此函数,即 .on("zoom", zoomed); 此时通常已调整 x 轴。

回答by Scott Cameron

Since time scales are continuous, there can be many ideas of what a "correct" bar width is. If your data points are very granular and unevenly distributed, you may want to use a thin bar of fixed width to minimize overlaps. If you know something about your expected data values ahead of time and they are uniform granularity, you can do something like what @LarsKotthoff to space them out evenly.

由于时间尺度是连续的,关于“正确”的条宽是什么,可以有很多想法。如果您的数据点非常细粒度且分布不均,您可能需要使用固定宽度的细条来最大程度地减少重叠。如果您提前了解预期数据值并且它们是统一的粒度,您可以执行类似@LarsKotthoff 的操作以将它们均匀隔开。

One thing to consider is whether what you actually want a time scale at all. Bars are generally used to represent categorical values, not points on a continuous scale. Maybe an ordinal scale with the domain derived from a date range is actually want you want. In that case you could use rangeBands as per your original post.

需要考虑的一件事是您是否真的想要一个时间尺度。条形通常用于表示分类值,而不是连续尺度上的点。也许您想要一个具有从日期范围派生的域的序数比例尺。在这种情况下,您可以根据原始帖子使用 rangeBands。

Not saying it is wrong to do what you're doing. Just food for thought.

并不是说做你正在做的事情是错误的。只是深思熟虑。