рассчитать спиральный подраздел с помощью d3js - PullRequest
0 голосов
/ 08 ноября 2018

Я создал спиральную визуализацию с помощью d3js. Живой пример здесь Я пытаюсь создать (в основном для выделения) подразделы спирали. Спираль определяется следующим образом:

const start = 0
const end = 2.25

const theta = function(r) {
  return numSpirals * Math.PI * r
}

const radius = d3.scaleLinear()
  .domain([start, end])
  .range([20, r])

const spiral = d3.radialLine()
  .curve(d3.curveCardinal)
  .angle(theta)
  .radius(radius)

const points = d3.range(start, end + 0.001, (end - start) / 1000)

const path = g.append("path")
  .datum(points)
  .attr("id", "spiral")
  .attr("d", spiral)
  .style("fill", "none")
  .style("stroke", "steelblue");

Некоторые случайные точки помещаются в спираль, используя что-то вроде:

  const positionScale = d3.scaleLinear()
      .domain(d3.extent(mockedData, (d, i) => i))
      .range([0, spiralLength]);

  const circles = g.selectAll("circle")
    .data(mockedData)
    .enter()
    .append("circle")
    .attr("cx", (d,i) => {
      const linePos = positionScale(i)
      const posOnLine = path.node().getPointAtLength(linePos)

      d.cx = posOnLine.x
      d.cy = posOnLine.y

      return d.cx;
    })
   .attr("cy", d => d.cy)
   .attr("r", d => circleRadiusScale(d.value))

Если начало и конец спирали равен [0, 2.25], легко получить подраздел спирали от 0 до 1, создав новый набор точек от 0 до 1 (вместо 0 до 2,25):

const pointSubSpiral = d3.range(0, 1 + 0.001, 1 / 1000)

Проблема у меня возникает, когда я пытаюсь создать подраздел спирали на основе точек данных. Например, от 0 до положения точки 3. Линейная шкала не работает:

const spiralSectionScale = d3.scaleLinear()
  .range([start, end])
  .domain([0, mockedData.length])

const spiralEnd = spiralSectionScale(i)
const sectionPoints = d3.range(0, 1 + 0.001, 1 / 1000)

const path2 = g.append("path")
    .datum(sectionPoints)
    .attr("id", "spiral-section")
    .attr("d", spiral)
    .style("fill", "none")
    .style("stroke", "red")
    .style("stroke-width", "1.2em")
    .style("opacity", "0.2")
    .style("pointer-events", "none")

Есть ли способ преобразовать значения в области данных в спиральную область?

ОБНОВЛЕНИЕ : Основываясь на ответе @ rioV8, приведенном ниже, мне удалось получить работающие участки спирали. В основном путем создания бинарного поиска по радиусу узла:

  function findSpiralSection(targetRadius, start, end) {
    const endSection = (end + start) / 2
    const endSectionRadius = radius(endSection)

    if (Math.abs(targetRadius - endSectionRadius) < 0.1) {
      return endSection 
    }

    if ((targetRadius - endSectionRadius) > 0) {
      return findSpiralSection(targetRadius, endSection, end)
    } else {
      return findSpiralSection(targetRadius, start, endSection)
    }
  }

  function higlightSubSpiral(d, i) {
    const linePos = positionScale(i);
    const targetNode = path.node().getPointAtLength(linePos);
    const nodeRadius = Math.sqrt((targetNode.x * targetNode.x) + (targetNode.y * targetNode.y))

    const bestEndSection = findSpiralSection(nodeRadius, start, end);

    const sectionPoints = d3.range(0, bestEndSection + 0.001, bestEndSection / 1000)

    const path2 = g.append("path")
        .datum(sectionPoints)
        .attr("id", "spiral-section")
        .attr("d", spiral)
        .style("fill", "none")
        .style("stroke", "red")
        .style("stroke-width", "1.2em")
        .style("opacity", "0.2")
        .style("pointer-events", "none")
  }

1 Ответ

0 голосов
/ 08 ноября 2018

ваша спираль разрезана на ~ 1000 частей, каждый кусок имеет одинаковый дельта-угол, но с переменным радиусом. Таким образом, каждый сегмент имеет разную длину.

Вы располагаете круги с регулярными интервалами длины (total_length/99) вдоль спирали. Они не соответствуют линейному кумулятивному углу, потому что длина сегмента отличается.

Вы должны использовать двоичный поиск, чтобы найти необходимое значение end-angle.

spiral highlight

Это должно работать для конечной точки, поэтому проблема в spiralSectionScale. Имеет диапазон [0, mockedData.length], это 1 слишком большой

var spiralSectionScale = d3.scaleLinear()
  .range([start, end])
  .domain([0, mockedData.length-1]);

Оригинальный positionScale имеет запутанный способ вычисления домена. Это более читабельно и быстрее.

const positionScale = d3.scaleLinear()
    .domain([0, mockedData.length-1])
    .range([0, spiralLength]);
...