在D3

时间:2018-01-03 10:04:43

标签: javascript animation d3.js text sunburst-diagram

根据我发现的一些例子,我试图整理一个可以在数据表示之间缩放和更改的动画sunburst图表:VascoVisual Cinnamon,{{3等等。

我无法让动画部分正常工作。弧正在更新,但文本标签不是。有什么建议?另外,我对此非常陌生,所以我也非常感谢有关如何构建代码的任何其他提示或建议。



// Variables
const width = window.innerWidth;
const height = window.innerHeight;
const radius = (Math.min(width, height) / 2) - 5;
const color = d3.scaleOrdinal(d3.schemeCategory20b);
const x = d3.scaleLinear().range([0, 2 * Math.PI]).clamp(true);
const y = d3.scaleLinear().range([0, radius]);

// Create our sunburst data structure and size it.
const partition = d3.partition();

// Size our <svg> element
const svg = d3.select('#chart').append('svg')
  .style('width', '100vw')
  .style('height', '100vh')
  .attr('viewBox', `${-width / 2} ${-height / 2} ${width} ${height}`)
  .attr("id", "container");

arc = d3.arc()
  .startAngle(function(d) {
    return Math.max(0, Math.min(2 * Math.PI, x(d.x0)));
  })
  .endAngle(function(d) {
    return Math.max(0, Math.min(2 * Math.PI, x(d.x1)));
  })
  .innerRadius(function(d) {
    return Math.max(0, y(d.y0));
  })
  .outerRadius(function(d) {
    return Math.max(0, y(d.y1));
  });

// JSON data
    var nodeData = {
        "name": "TOPICS", "children": [{
            "name": "Topic A",
            "children": [{"name": "Sub A1", "size": 4}, {"name": "Sub A2", "size": 4}]
        }, {
            "name": "Topic B",
            "children": [{"name": "Sub B1", "size": 3}, {"name": "Sub B2", "size": 3}, {
                "name": "Sub B3", "size": 3}]
        }, {
            "name": "Topic C",
            "children": [{"name": "Sub A1", "size": 4}, {"name": "Sub A2", "size": 4}]
        }]
    };


createVisualization()

// Main function to draw and set up the visualization
function createVisualization() {
  // Find the root node, calculate the node.value, and sort our nodes by node.value
  root = d3.hierarchy(nodeData)
    .sum(function(d) {
      return d.size;
    })
    .sort(function(a, b) {
      return b.value - a.value;
    });
  original = root;
  partition(root);

  // Add a <g> element for each node in thd data, then append <path> elements and draw lines based on the arc
  // variable calculations. Last, color the lines and the slices.
  slices = svg.selectAll('path')
    .data(root.descendants())
    .enter().append('g').attr("class", "node")
    .on("click", focusOn);

  slices.append('path')
    .attr('class', 'main-arc')
    .attr("id", function(d, i) {
      return "arc_" + i;
    })
    .attr("d", arc)
    .style('stroke', '#fff')
    .style("fill", function(d) {
      return color((d.children ? d : d.parent).data.name);
    })
    .each(function(d, i) {
      const halfPi = Math.PI / 2;
      const angles = [Math.max(0, Math.min(2 * Math.PI, x(d.x0))) - halfPi, Math.max(0, Math.min(2 * Math.PI, x(d.x1))) - halfPi];

      const r = Math.max(0, (y(d.y0) + y(d.y1)) / 2);

      const middleAngle = (angles[1] + angles[0]) / 2;
      const invertDirection = middleAngle > 0 && middleAngle < Math.PI; // On lower quadrants write text ccw
      if (invertDirection) {
        angles.reverse();
      }

      const path = d3.path();
      path.arc(0, 0, r, angles[0], angles[1], invertDirection);

      //Create a new invisible arc that the text can flow along
      d3.select(this).append("path")
        .attr("class", "hiddenArcs")
        .attr("id", "hiddenArc_" + i)
        .attr("d", path.toString())
        .style("fill", "none");
    });

  text = slices.append("text")
    .attr("class", "arcText");

  text.append("textPath")
    .attr("xlink:href", function(d, i) {
      return "#hiddenArc_" + i;
    })
    .attr('startOffset', '50%')
    .style('fill', 'none')
    .style('stroke', '#fff')
    .style('stroke-width', 5)
    .style('stroke-linejoin', 'round');

  text.append("textPath")
    .attr("xlink:href", function(d, i) {
      return "#hiddenArc_" + i;
    })
    .attr('startOffset', '50%');

  slices.selectAll("textPath")
    .text(function(d) {
      return d.parent ? d.data.name : ""
    });

  // Redraw the Sunburst Based on User Input
  d3.selectAll(".sizeSelect").on("click", build);
}

function build() {
  // Determine how to size the slices.
  if (this.value === "size") {
    root.sum(function(d) {
      return d.size;
    });
  } else {
    root.count();
  }

  // Calculate the sizes of each arc that we'll draw later.
  partition(root);

  slices.selectAll("path.main-arc").transition().duration(750).attrTween("d", arcTweenData);
  slices.selectAll("path.hiddenArcs").transition().duration(750).attrTween("d", hiddenArcTweenData);

  slices.selectAll("textPath")
    .text(function(d) {
      return d.parent ? d.data.name : ""
    });
}

// Respond to slice click.
function focusOn(d) {
  original = d;
  svg.selectAll("path").transition().duration(1000).attrTween("d", arcTweenZoom(d))
}

// When zooming: interpolate the scales.
function arcTweenZoom(d) {
  var xd = d3.interpolate(x.domain(), [d.x0, d.x1]),
    yd = d3.interpolate(y.domain(), [d.y0, 1]), // [d.y0, 1]
    yr = d3.interpolate(y.range(), [d.y0 ? 40 : 0, radius]);
  return function(d, i) {
    return i ? function(t) {
      return arc(d);
    } : function(t) {
      x.domain(xd(t));
      y.domain(yd(t)).range(yr(t));
      return arc(d);
    };
  };
}

// When switching data: interpolate the arcs in data space.
function arcTweenData(a, i) {
  // (a.x0s ? a.x0s : 0) -- grab the prev saved x0 or set to 0 (for 1st time through)
  // avoids the stash() and allows the sunburst to grow into being
  // var oi = d3.interpolate({ x0: (a.x0s ? a.x0s : 0), x1: (a.x1s ? a.x1s : 0) }, a);
  var oi = d3.interpolate({
    x0: (a.x0s ? a.x0s : 0),
    x1: (a.x1s ? a.x1s : 0)
  }, a);

  function tween(t) {
    var b = oi(t);
    a.x0s = b.x0;
    a.x1s = b.x1;
    return arc(b);
  }
  if (i == 0) {
    // If we are on the first arc, adjust the x domain to match the root node
    // at the current zoom level. (We only need to do this once.)
    var xd = d3.interpolate(x.domain(), [original.x0, original.x1]);
    return function(t) {
      x.domain(xd(t));
      return tween(t);
    };
  } else {
    return tween;
  }
}

function hiddenArcTweenData(a, i) {
  // (a.x0s ? a.x0s : 0) -- grab the prev saved x0 or set to 0 (for 1st time through)
  var oi = d3.interpolate({
    x0: (a.x0s ? a.x0s : 0),
    x1: (a.x1s ? a.x1s : 0)
  }, a);

  function tween(t) {
    var b = oi(t);
    a.x0s = b.x0;
    a.x1s = b.x1;
    return middleArc(b);
  }
  if (i == 0) {
    // If we are on the first arc, adjust the x domain to match the root node
    // at the current zoom level. (We only need to do this once.)
    var xd = d3.interpolate(x.domain(), [original.x0, original.x1]);
    return function(t) {
      x.domain(xd(t));
      return tween(t);
    };
  } else {
    return tween;
  }
}

function middleArc(d) {
  const halfPi = Math.PI / 2;
  const angles = [Math.max(0, Math.min(2 * Math.PI, x(d.x0))) - halfPi, Math.max(0, Math.min(2 * Math.PI, x(d.x1))) - halfPi];

  const r = Math.max(0, (y(d.y0) + y(d.y1)) / 2);

  const middleAngle = (angles[1] + angles[0]) / 2;
  const invertDirection = middleAngle > 0 && middleAngle < Math.PI; // On lower quadrants write text ccw
  if (invertDirection) {
    angles.reverse();
  }

  const path = d3.path();
  path.arc(0, 0, r, angles[0], angles[1], invertDirection);
  return path;
}
&#13;
body {
  @import "https://fonts.googleapis.com/css?family=Fakt:400,600";
  font-family: 'Fakt', fakt;
  font-size: 12px;
  font-weight: 400;
  background-color: #fff;
  width: 960px;
  height: 700px;
  margin-top: 10px;
}

.node {
  cursor: pointer;
}

.node .main-arc {
  stroke: #fff;
  stroke-width: 1px;
}

.node .hidden-arc {
  stroke-width: 1px;
  stroke: #000;
}

.node text {
  pointer-events: none;
  dominant-baseline: middle;
  text-anchor: middle;
  fill: #000
}
&#13;
<!DOCTYPE html>
<html>

  <head>
    <meta charset="utf-8">
    <title>Sunburst</title>
    <script src="https://d3js.org/d3.v4.min.js"></script>
    <link rel="stylesheet" type="text/css" href="sunburst.css" />
  </head>

  <body>
    <div id="main">
      <label>
        <input class="sizeSelect" type="radio" name="mode" value="count" checked/> Count </label>
      <label>
        <input class="sizeSelect" type="radio" name="mode" value="size" /> Size </label>
      <div id="chart">
        <script type="text/javascript" src="sunburst.js"></script>
      </div>
    </div>
  </body>

</html>
&#13;
&#13;
&#13;

1 个答案:

答案 0 :(得分:1)

第97行的JS代码说:

  d3.select(this).append("path")

这会将.hiddenArcs路径添加为.main-arc <path>的子项,而不是父<g>。这使得SVG无效,但它也使选择器slices.selectAll("path.hiddenArcs")返回一个空集。因此,隐藏的弧永远不会补间,因此文本保持原样。

更改第97行,以便将隐藏的弧添加到路径的父节点而不是路径,修复此问题:

  d3.select(this.parentNode).append("path")