Резюме:

Из этого туториала Вы узнаете, как добавить градиентную кисть к диаграмме с областями в D3.js. Мы добавим градиент к значениям SVG и применим градиент в качестве заливки к диаграмме с областями.

Предпосылки

Предварительные условия заключаются в том, что у вас есть некоторые базовые знания D3.js, в противном случае я бы порекомендовал проверить Scrimba для их бесплатного курса D3 с интерактивными видео: https://scrimba.com/g/gd3js

Итак, начнем….

У нас есть наша базовая диаграмма с областями, созданная с помощью D3 в коде ниже:

Он содержит набор поддельных данных:

const data = [
  {
    year: 2000,
    popularity: 50
  },
  {
    year: 2001,
    popularity: 150
  }....
]

С этим набором данных мы используем популярность для расчета шкалы y для нашей диаграммы и год для расчета шкалы x для нашей диаграммы.

// Create scales
const yScale = d3
  .scaleLinear()
  .range([height, 0])
  .domain([0, d3.max(data, dataPoint => dataPoint.popularity)]);
const xScale = d3
  .scaleLinear()
  .range([0, width])
  .domain(d3.extent(data, dataPoint => dataPoint.year));

Используя эти масштабы, мы генерируем линию с помощью D3 area ();

const area = d3
  .area()
  .x(dataPoint => xScale(dataPoint.year))
  .y0(height)
  .y1(dataPoint => yScale(dataPoint.popularity));

И, наконец, добавляем путь с заливкой на нашу диаграмму:

// Add area
grp
  .append("path")
  .attr("transform", `translate(${margin.left},0)`)
  .datum(data)
  .style("fill", "lightblue")
  .attr("stroke", "steelblue")
  .attr("stroke-linejoin", "round")
  .attr("stroke-linecap", "round")
  .attr("stroke-width", strokeWidth)
  .attr("d", area);

Создание SVG defs

Теперь у нас есть базовая диаграмма с областями, необходимая для создания градиента. Градиенты можно разместить в SVG defs. Defs используются для определения графических объектов в SVG для будущего использования. Внутри defs вы можете создавать такие вещи, как узоры, символы и градиенты.

<defs>
    <linearGradient id="gradient01">
      <stop offset="20%" stop-color="red" />
      <stop offset="90%" stop-color="blue" />
    </linearGradient>
 </defs>

Итак, из приведенного выше примера вы можете видеть, что мы создаем тег defs, а внутри него находится linearGradient с идентификатором. Затем идентификатор можно использовать для установки заливки элемента svg с помощью «url (# Gradient01)».

Есть два типа градиентов, линейный градиент и радиальный градиент, для полного объяснения я бы рекомендовал проверить MDN. Https://developer.mozilla.org/en-US/docs/Web/SVG/Tutorial/Gradients, который разбивает оба градиента.

Но в итоге: стопы - это цветные стопы, которые задают позицию для остановки цвета. В нашем примере наша первая остановка - 20%. Поскольку мы не установили цвет на 0%, градиент начнется с «красного» и продолжится до 20%, затем он начнет переходить к следующему цвету «синий», который будет полностью синим на 90%. Чем больше зазор между этими двумя градиентами, тем более плавный градиент.

Мы собираемся установить две остановки в одной и той же точке, чтобы не было выцветания / градиента между двумя цветовыми точками.

Как вы можете видеть из второго градиента, мы разместили ограничители цветов друг над другом, чтобы удалить любой градиент и выцветание.

<linearGradient id="gradient02">
      <stop offset="40%" stop-color="red" />
      <stop offset="40%" stop-color="blue" />
      <stop offset="90%" stop-color="blue" />
      <stop offset="90%" stop-color="red" />
 </linearGradient>

Итак, сначала давайте создадим кисть D3 на нашей диаграмме с областями.

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

Для получения дополнительной информации об этой функции посетите https://github.com/d3/d3-brush.

Сначала мы создаем кисть d3, вызывая функцию кисти. Я вызвал brushX (), так как я хочу использовать только измерение по оси x, а затем передаю ему степень, к которой должна быть применена кисть, которая в нашем случае равна 0 для полной ширины и высоты диаграммы.

const brush = d3.brushX().extent([[0, 0], [width, height]]);

Затем мы можем добавить кисть с помощью функции вызова D3.

chart
  .append("g")
  .attr("class", "brush")
  .call(brush);

Затем нам нужно добавить градиент к нашей диаграмме, сначала создав элемент defs, а затем добавив в него элемент linearGradient.

// Add gradient defs to svg
const defs = svg.append("defs");
const gradient = defs.append("linearGradient").attr("id", "svgGradient");

Кажется достаточно простым, и теперь мы можем добавить к градиенту точки цвета.

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

gradient
  .append("stop")
  .attr("offset", "30%")
  .attr("stop-color", "red");

Чтобы добиться желаемого эффекта, мы хотим создать градиент, который будет прозрачным в начале и в конце и будет иметь цвет только в середине. Затем, когда кисть выбирает область, мы можем обновить значения смещения, чтобы изменить начало и конец цвета.

Всего нам понадобится 4 ступени, две для представления начала кисти и две для представления конца кисти. Оба набора будут иметь один прозрачный, а другой цветной, который будет иметь одинаковое значение смещения, давая четкую линию, а не затухание градиента.

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

Я создал процент сброса градиента, при котором все остановки цвета будут запускать смещение при сбросе градиента, поэтому градиент не будет отображаться на диаграмме.

Затем, когда кисть используется, мы можем обновить начальные и конечные остановки.

const gradientResetPercentage = "50%";
const gradientColors = ["#84fab0", "#8fd3f4"];
gradient
  .append("stop")
  .attr("class", "start")
  .attr("offset", gradientResetPercentage)
  .attr("stop-color", "transparent");
gradient
  .append("stop")
  .attr("class", "start")
  .attr("offset", gradientResetPercentage)
  .attr("stop-color", gradientColors[0]);
gradient
  .append("stop")
  .attr("class", "end")
  .attr("offset", gradientResetPercentage)
  .attr("stop-color", gradientColors[1])
  .attr("stop-opacity", 1);
gradient
  .append("stop")
  .attr("class", "end")
  .attr("offset", gradientResetPercentage)
  .attr("stop-color", "transparent");

Затем мы можем прослушать, когда был сделан выбор кисти. D3 упростил это с помощью прослушивателя событий «on».

// Create brush
const brush = d3
  .brushX()
  .extent([[0, 0], [width, height]])
  .on("brush", brushed);

Привязав к концу нашей кисти, мы добавим прослушиватель событий к событию «brush», он запускается, когда кисть перемещается, например, «mousemove». Итак, когда кисть движется, мы будем выполнять нашу функцию кисти.

// Update the gradient start and end points to match the selected brush area
function brushed() {
  const selection = d3.event.selection;
  const x1Percentage = selection[0] / width * 100;
  const x2Percentage = selection[1] / width * 100;
  d3.selectAll(".start").attr("offset", `${x1Percentage}%`);
  d3.selectAll(".end").attr("offset", `${x2Percentage}%`);
}

Затем, используя d3.event.selection, мы получим текущую очищенную область в единицах svg.

Например, если SVG-кисть имеет ширину 100 единиц, и мы чистим кисть от начала области кисти ровно до середины, выбор вернется [0, 50].

С помощью этого выделения нам нужно преобразовать его в процент, чтобы мы могли обновить наш градиент.

Затем мы можем обновить атрибут смещения для наших colorStops в нашем градиенте.

Вот и все! Ну вроде…

Итак, в настоящее время у нас есть стандартное наложение синей кисти, которое мы можем удалить с помощью CSS.

.selection {
  fill: none;
}

Поведение по умолчанию для кисти D3 - при щелчке по диаграмме она удаляет выделение, поэтому нам нужно обновить наш градиент, чтобы отразить это. Итак, если мы добавим еще один прослушиватель событий в нашу кисть

const brush = d3
  .brushX()
  .extent([[0, 0], [width, height]])
  .on("brush", brushed)
  .on("start", resetGradient);

Событие start запускается в начале жеста кисти, например при mousedown. Это означает, что он будет запущен перед нашим событием «brush», если щелкнуть кисть и перетащить выделенный фрагмент, а также если щелкнуть диаграмму без выделения.

Поэтому нам нужно будет проверить, не выбрано ли что-нибудь, прежде чем мы вернем градиент в положение по умолчанию.

Снова используя d3.event.selection, если выделение пусто, мы можем сбросить градиент, создав эффект очищенной кисти.

function resetGradient() {
  const selection = d3.event.selection;
  // If the brush area is clicked but there is a selected area
  // don't clear the gradient as it could be the brush area being moved 
  if(selection[1] - selection[0] === 0) {
    d3.selectAll(".start").attr("offset", gradientResetPercentage);
    d3.selectAll(".end").attr("offset", gradientResetPercentage); 
  }
}

Итак, вот окончательный график:

У градиента может быть столько остановок, сколько вам нужно, чтобы вы могли усложнить его работу.

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

Надеюсь, это было полезно, если я допустил ошибки или у вас есть вопросы / комментарии, оставьте комментарий.

Спасибо за чтение 👋