Использование d3 для области тени между двумя линиями

Итак, у меня есть график, отображающий трафик против даты и скорости по сравнению с датой. Я пытаюсь затенять область между двумя линиями. Тем не менее, я хочу затенять его другим цветом, в зависимости от того, какая линия выше. Следующее работает без этого последнего требования:

var area = d3.svg.area()
    .x0(function(d) { return x(d3.time.format("%m/%d/%Y").parse(d.original.date)); })
    .x1(function(d) { return x(d3.time.format("%m/%d/%Y").parse(d.original.date)); })
    .y0(function(d) { return y(parseInt(d.original.traffic)); })
    .y1(function(d) { return y(parseInt(d.original.rate)); })

Однако, добавив последнее требование, я попытался использовать defined():

.defined(function(d){ return parseInt(d.original.traffic) >= parseInt(d.original.rate); })

Теперь это в основном работает, за исключением случаев, когда пересекаются линии. Как затенять область под одной строкой МЕЖДУ пунктами? Это затенение основано на точках, и я хочу, чтобы он затенялся по линии. Если у меня нет двух последовательных точек на одной стороне линии, я вообще не получаю никакого затенения.

Ответ 1

Поскольку у вас нет точек доступа на пересечениях, самым простым решением, вероятно, является получение областей выше и ниже каждой строки и использование clipPath для обрезки разницы.

Предполагаю, что вы используете d3.svg.line для рисования линий, на которых основаны области. Таким образом, мы сможем повторно использовать функции доступа .x() и .y() на следующих участках:

var trafficLine = d3.svg.line()
  .x(function(d) { return x(d3.time.format("%m/%d/%Y").parse(d.original.date)); })
  .y(function(d) { return y(parseInt(d.original.traffic)); });

var rateLine = d3.svg.line()
  .x(trafficLine.x()) // reuse the traffic line x
  .y(function(d) { return y(parseInt(d.original.rate)); })

Вы можете создавать отдельные функции области для расчета областей, расположенных выше и ниже двух линий. Область под каждой линией будет использоваться для рисования фактического пути, а область выше будет использоваться в качестве обтравочного контура. Теперь мы можем повторно использовать аксессоров из строк:

var areaAboveTrafficLine = d3.svg.area()
  .x(trafficLine.x())
  .y0(trafficLine.y())
  .y1(0);
var areaBelowTrafficLine = d3.svg.area()
  .x(trafficLine.x())
  .y0(trafficLine.y())
  .y1(height);
var areaAboveRateLine = d3.svg.area()
  .x(rateLine.x())
  .y0(rateLine.y())
  .y1(0);
var areaBelowRateLine = d3.svg.area()
  .x(rateLine.x())
  .y0(rateLine.y())
  .y1(height);

... где height - высота вашей диаграммы, и если 0 является y-координатой вершины диаграммы, в противном случае соответствующим образом измените эти значения.

Теперь вы можете использовать функции area-above для создания обтравочных контуров следующим образом:

var defs = svg.append('defs');

defs.append('clipPath')
  .attr('id', 'clip-traffic')
  .append('path')
  .datum(YOUR_DATASET)
  .attr('d', areaAboveTrafficLine);

defs.append('clipPath')
  .attr('id', 'clip-rate')
  .append('path')
  .datum(YOUR_DATASET)
  .attr('d', areaAboveRateLine);

Атрибуты id необходимы, потому что мы должны ссылаться на эти определения, когда на самом деле обрезаем пути.

Наконец, используйте функции, расположенные ниже, чтобы рисовать пути к svg. Важно помнить, что для каждой области ниже мы должны кликать в противоположную область выше, поэтому область Rate будет обрезана на основе #clip-traffic и наоборот:

// TRAFFIC IS ABOVE RATE
svg.append('path')
  .datum(YOUR_DATASET)
  .attr('d', areaBelowTrafficLine)
  .attr('clip-path', 'url(#clip-rate)')

// RATE IS ABOVE TRAFFIC
svg.append('path')
  .datum(YOUR_DATASET)
  .attr('d', areaBelowRateLine)
  .attr('clip-path', 'url(#clip-traffic)')

После этого вам просто нужно дать двум регионам разные цвета заливки или то, что вы хотите сделать, чтобы отличить их друг от друга. Надеюсь, что это поможет!