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

后端 未结 4 1555
猫巷女王i
猫巷女王i 2020-12-29 03:37

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

How can I specify the correct width for the bars on the bar chart? I

相关标签:
4条回答
  • 2020-12-29 04:02

    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 x position accordingly, e.g.

    .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:

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

    Complete jsfiddle here.

    0 讨论(0)
  • 2020-12-29 04:13

    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.

    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.

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

    0 讨论(0)
  • 2020-12-29 04:14

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

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

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

    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.

     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.

    0 讨论(0)
  • 2020-12-29 04:26

    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.scale does a really good job with help of d3.time.format.multi of drawing time axis of various duration (hours, days, months, etc.) and its ticks, it could be a good idea to combine d3.time.scale for an axis, and d3.scale.ordinal for a band width calculation.

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

    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>

    0 讨论(0)
提交回复
热议问题