【问题标题】:calculate spiral subsection with d3js用 d3js 计算螺旋分段
【发布时间】:2019-04-12 02:34:13
【问题描述】:

我用 d3js 创建了一个螺旋可视化。实例here 我正在尝试创建(基本上是为了突出显示)螺旋的子部分。 螺旋定义如下:

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 到 2.25)的新点,很容易得到从 0 到 1 的螺旋子部分:

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")

有没有办法将数据域中的值转换为螺旋域?

更新:根据下面@ri​​oV8 的回答,我设法让螺旋的各个部分正常工作。基本上是通过在节点的半径上创建一个二分搜索:

  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")
  }

【问题讨论】:

标签: d3.js svg geometry data-visualization


【解决方案1】:

你的螺旋被切割成大约 1000 块,每块都有相同的 delta 角,但半径不同。因此,每个段都有不同的长度。

您沿螺旋线以固定长度间隔(total_length/99) 放置圆圈。它们不对应于线性累积角度,因为线段长度不同。

您必须使用二进制搜索来找到所需的end-angle 值。

它应该适用于最后一点,所以spiralSectionScale 中存在问题。 In 有一个范围[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]);

【讨论】:

  • 谢谢,您已经很好地描述了这个问题。我仍然不确定如何创建二进制搜索,搜索应该接近的最终值是多少?能否请您详细说明一下?
  • 我已经设法让一个版本正常工作(在半径上使用二进制搜索)。我已经更新了原始帖子中的链接。再次感谢您的指点!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2016-06-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多