Подтвердить что ты не робот

D3.js: рассчитать ширину баров по шкале времени с изменением диапазона?

Я строю диаграмму D3 с временной шкалой по оси 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?

UPDATE: JSFiddle здесь: http://jsfiddle.net/aWJtJ/2/

4b9b3361

Ответ 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 здесь.

Ответ 2

Несмотря на то, что я согласен с Скоттом в том, что гистограмма предназначена для использования с ординальными или категориальными данными по оси x, я думаю, что вопрос связан скорее с удобством рисования временной оси. Поскольку d3.time.scale выполняет действительно хорошую работу с помощью d3.time.format.multi рисования временной оси различной продолжительности (часы, дни, месяцы и т.д.) И ее тиков, может быть хорошей идеей объединить d3.time.scale для оси, и d3.scale.ordinal для расчета ширины полосы.

Ниже приведен фрагмент ниже обсуждение в D3 Google Group по этой теме. Единица для порядковой шкалы - это день.

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

Поскольку временные масштабы непрерывны, может быть много идей о том, что такое "правильная" ширина бара. Если ваши точки данных очень гранулированы и неравномерно распределены, вы можете использовать тонкую полосу фиксированной ширины для минимизации перекрытий. Если вы знаете что-то о ваших ожидаемых значениях данных раньше времени, и они являются одинаковой детализацией, вы можете сделать что-то вроде того, что @LarsKotthoff равномерно распределяет их.

Одна вещь, которую следует учитывать, - это то, что вы на самом деле хотите иметь временную шкалу. Бары обычно используются для представления категориальных значений, а не точек непрерывного масштаба. Может быть, порядковая шкала с доменом, полученным из диапазона дат, на самом деле нужна вам. В этом случае вы можете использовать диапазоны диапазонов в соответствии с вашим исходным сообщением.

Не сказать, что неправильно делать то, что вы делаете. Просто пища для размышлений.

Ответ 4

Я столкнулся с этой проблемой при изменении уровней масштабирования на гистограмме с осью X на основе временных рядов и нашел два решения.

1) Создайте порядковый масштаб компаньона, чтобы рассчитать ширину (боль)

2) Х-ось временного ряда - ваш друг - используйте его для вычисления ширины.

Если вы хотите, чтобы ваш бар всегда был "месяцем", независимо от уровня масштабирования, вы можете сделать что-то вроде этого. Im, предполагающий, что 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));
  });

Это хорошо работает, потому что оно работает для каждой шкалы масштабирования, и вам просто нужно вызвать эту функцию в конце вашего обработчика "zoom", т.е. "в" ( "масштабирование", увеличение); В этот момент ось x будет обычно отрегулирована.