Как импортировать подмодули из d3, чтобы легко создавать легенду?

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

введите здесь описание изображения

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

import {legend} from "@d3/color-legend"

что не удалось, как и ожидалось. Я не уверен, что делать, чтобы этот импорт работал. Я загрузил последний файл d3.zip и безуспешно пробовал следующие источники.

<script type="text/javascript" src="https://d3js.org/d3.v5.js"></script>
<script type="text/javascript" src="d3.js"></script>

Мы будем очень признательны за любые рекомендации.


person Manny    schedule 27.02.2020    source источник


Ответы (1)


Учитывая относительный путь имени этого модуля, import будет работать только в ячейке Observable. Если это не ваш случай, просто скопируйте всю функцию, которую вы найдете ниже (нажмите «Показать фрагмент кода»), или сохраните ее как файл (и тогда вы можете сделать import с соответствующим именем модуля):

function legend({
  color,
  title,
  tickSize = 6,
  width = 320,
  height = 44 + tickSize,
  marginTop = 18,
  marginRight = 0,
  marginBottom = 16 + tickSize,
  marginLeft = 0,
  ticks = width / 64,
  tickFormat,
  tickValues
} = {}) {

  const svg = d3.create("svg")
    .attr("width", width)
    .attr("height", height)
    .attr("viewBox", [0, 0, width, height])
    .style("overflow", "visible")
    .style("display", "block");

  let x;

  // Continuous
  if (color.interpolator) {
    x = Object.assign(color.copy()
      .interpolator(d3.interpolateRound(marginLeft, width - marginRight)), {
        range() {
          return [marginLeft, width - marginRight];
        }
      });

    svg.append("image")
      .attr("x", marginLeft)
      .attr("y", marginTop)
      .attr("width", width - marginLeft - marginRight)
      .attr("height", height - marginTop - marginBottom)
      .attr("preserveAspectRatio", "none")
      .attr("xlink:href", ramp(color.interpolator()).toDataURL());

    // scaleSequentialQuantile doesn’t implement ticks or tickFormat.
    if (!x.ticks) {
      if (tickValues === undefined) {
        const n = Math.round(ticks + 1);
        tickValues = d3.range(n).map(i => d3.quantile(color.domain(), i / (n - 1)));
      }
      if (typeof tickFormat !== "function") {
        tickFormat = d3.format(tickFormat === undefined ? ",f" : tickFormat);
      }
    }
  }

  // Discrete
  else if (color.invertExtent) {
    const thresholds = color.thresholds ? color.thresholds() // scaleQuantize
      :
      color.quantiles ? color.quantiles() // scaleQuantile
      :
      color.domain(); // scaleThreshold

    const thresholdFormat = tickFormat === undefined ? d => d :
      typeof tickFormat === "string" ? d3.format(tickFormat) :
      tickFormat;

    x = d3.scaleLinear()
      .domain([-1, color.range().length - 1])
      .rangeRound([marginLeft, width - marginRight]);

    svg.append("g")
      .selectAll("rect")
      .data(color.range())
      .join("rect")
      .attr("x", (d, i) => x(i - 1))
      .attr("y", marginTop)
      .attr("width", (d, i) => x(i) - x(i - 1))
      .attr("height", height - marginTop - marginBottom)
      .attr("fill", d => d);

    tickValues = d3.range(thresholds.length);
    tickFormat = i => thresholdFormat(thresholds[i], i);
  }

  svg.append("g")
    .attr("transform", `translate(0, ${height - marginBottom})`)
    .call(d3.axisBottom(x)
      .ticks(ticks, typeof tickFormat === "string" ? tickFormat : undefined)
      .tickFormat(typeof tickFormat === "function" ? tickFormat : undefined)
      .tickSize(tickSize)
      .tickValues(tickValues))
    .call(g => g.selectAll(".tick line").attr("y1", marginTop + marginBottom - height))
    .call(g => g.select(".domain").remove())
    .call(g => g.append("text")
      .attr("y", marginTop + marginBottom - height - 6)
      .attr("fill", "currentColor")
      .attr("text-anchor", "start")
      .attr("font-weight", "bold")
      .text(title));

  return svg.node();
}

Однако есть еще одно препятствие: в настоящее время эта функция также будет работать только в ячейке Observable. Если вы хотите, чтобы он работал на обычной веб-странице, измените возвращаемое значение (или добавьте SVG в качестве побочного эффекта).

person Gerardo Furtado    schedule 27.02.2020
comment
Спасибо за быстрый ответ! Я последовал вашему совету и получил ошибку DOM is not defined в функции рампы при создании холста. Нужен ли мне дополнительный тег источника, чтобы исправить это, или есть другой способ? Еще раз спасибо!! - person Manny; 27.02.2020
comment
@Manny Ну, это другая проблема: DOM - это метод только для наблюдаемых ноутбуков (проверьте мой ответ здесь: stackoverflow.com/a/55865767 /5768908). Здесь, в Stack Overflow, мы сохраняем только одну проблему на вопрос, поэтому, если вы столкнулись с другой проблемой, не стесняйтесь опубликовать новый вопрос со всей соответствующей информацией. Но эту проблему DOM легко исправить: просто добавьте canvas обычным способом. - person Gerardo Furtado; 27.02.2020
comment
Спасибо за ссылку и за то, что научили меня этикету переполнения стека. Я заменил строку DOM на const canvas = document.createElement("canvas");, которая не вызывает ошибок, однако легенда не отображается, даже если легенда svg добавлена ​​​​к основному тегу div. Считается ли это другой проблемой для другого вопроса? Если нет, то знаете ли вы, что может быть причиной этого? Вот ссылка на скрипку. - person Manny; 27.02.2020
comment
@ Мэнни, да, это другая проблема, не связанная с этим текущим вопросом, на который уже был дан ответ. Таким образом, пожалуйста, задайте его как новый вопрос. - person Gerardo Furtado; 27.02.2020
comment
@ Спасибо, что дали мне знать. Вот ссылка на новый вопрос. - person Manny; 28.02.2020