首页 > 解决方案 > Edit svgs instead of recreating

问题描述

I have an apparently simple misunderstanding of d3, and can't find a matching explanation, although lots of stuff I look for seems close.

Basically, I have an array of data, and I want to render a in an for each item. I want each to have its own children elements (a and a text />) that depends on the datum bound to each .

My problem is that when the data updates, additional and elements are added to each , rather than replacing it.

A minimal reproduction codepen is attached. Any assistance would be greatly appreciated!

https://codepen.io/kamry-bowman/pen/GaLvKJ

html:

<body>
  <svg />
  <button>Increment</button>
</body>

js:

let state = [];

for (let i = 0; i < 5; i++) {
  state.push({ id: i, value: (i + 1) * 2 });
}

function render() {
  console.log('running', state)
   const svg = d3
      .select("svg")
      .attr("width", "1000")
      .attr("height", "200");

    const gGroup = svg
      .selectAll("g")
      .data(state, d => d.id)
      .join("g")
      .attr("transform", (d, i) => `translate(${100 * i})`);

    gGroup
      .append("circle")
      .attr("cx", "50")
      .attr("cy", "50")
      .attr("r", "50")
      .attr("fill", "none")
      .attr("stroke", "black");

    gGroup
      .append("text")
      .text(d => d.value)
      .attr("stroke", "black")
      .attr("x", "40")
      .attr("y", "55")
      .attr("style", "font: bold 30px sans-serif");
 }

function clickHandler() {
  state = state.map(obj => ({ ...obj, value: obj.value + 1 }))
  render()
}

document.querySelector('button').addEventListener('click', clickHandler)

render()

I have found a hacky fix of calling gGroup.html(''), but that seems like I'm missing something with how the library is supposed to work.

标签: javascriptd3.js

解决方案


Add this line directly after you create your svg in the render method:

svg.selectAll("*").remove();

This will remove all children of the root svg. Otherwise you'll just keep drawing on top of the same svgs.

Try it here

And the docs:

d3.selectAll() selection.remove()


EDIT

To update just the text values themselves, call this update() function in the click handler instead:

function update() {
  const svg = d3.select("svg")
  d3.selectAll("svg text").each(function(d, i) {
    d3.select(this).text(state[i].value);
  });
}

The codepen has been updated to use this method


EDIT AGAIN

If you want to bind the values to their id, just access it from d in .each() method instead of i (which is the index in the collection):

function update() {
  const svg = d3.select("svg")
  d3.selectAll("svg text").each(function(d, i) {
    d3.select(this).text(
      state.find((e) => e.id === d.id).value
    );
  });
}

Since the entries can be in any order, we'll have to use Array.find() to find the element with the matching id. Here's another codepen for this.


推荐阅读