【问题标题】:D3.js: calculate width of bars in time scale with changing range?D3.js:随着范围的变化计算时间尺度的条形宽度?
【发布时间】:2013-09-16 18:44:23
【问题描述】:

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

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

这是我的代码:

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) // ??

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

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

【问题讨论】:

    标签: javascript d3.js


    【解决方案1】:

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

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

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

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

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

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

    完整的 jsfiddle here.

    【讨论】:

    • 谢谢!第一个建议几乎就在那里,但x 函数似乎在条之间插入一些填充:jsfiddle.net/aWJtJ/1 如果我再次尝试使用第二个函数,它几乎是正确的,但它仍然在条之间插入填充:jsfiddle.net/aWJtJ/2 - 我不明白为什么x 函数可以告诉我的代码条形的x 位置应该在哪里,但不知道宽度应该在哪里,因为它显然确实对什么有一个坚定的想法宽度应该是。
    • 好吧,你实际上需要更多——特别是,你需要调整范围,因为第一个刻度将是第一个值(见编辑的答案)。至于为什么 D3 不告诉你宽度本身,我不知道——你可以打开一个功能请求。
    【解决方案2】:

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

    下面的 sn-p 灵感来自 D3 Google Group 中关于该主题的the discussion。序数刻度的单位是天。

    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>

    【讨论】:

      【解决方案3】:

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

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

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

      如果您希望栏始终为“月”宽,无论缩放级别如何,您都可以执行以下操作。我假设 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));
        });
      

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

      【讨论】:

        【解决方案4】:

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

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

        不要说做你正在做的事情是错误的。值得深思。

        【讨论】:

        • 谢谢!我确实需要使用时间刻度,因为我想对刻度使用时间格式:github.com/mbostock/d3/wiki/…
        • 如果格式化是唯一的原因并且您有已知数量的离散值,您可以在将日期传递到序数刻度之前将它们格式化为字符串。当然,您需要小心在缩放之前以相同的方式将所有日期数据格式化为字符串,可能使用共享的 getter 函数或其他东西。或者尽早将数据转换为字符串。这实际上取决于您是否需要时间数据类型的语义。
        • 顺便说一句,您可以使用 D3 的时间解析器和格式化程序,不受任何规模的影响:github.com/mbostock/d3/wiki/Time-Formatting
        • 谢谢 - 我想知道这是否是最好的方法 - 首先格式化字符串,然后使用序数比例。我会试试的。
        猜你喜欢
        • 1970-01-01
        • 2012-08-24
        • 1970-01-01
        • 1970-01-01
        • 2012-09-23
        • 1970-01-01
        • 2023-03-10
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多