Reath Illu
Reath Illu

Reputation: 171

D3 append element only if element not exist

I have a problem with D3, if I append an Element a second time I will get duplicate elements in the parent node.

  node.enter().insert("svg:g")
      .attr("class", 'test')
      .attr("width", '63px')
      .attr("height", '68px')
      .call(force.drag);

  node.append("svg:circle")
      .attr("class", "bg-circle")
      .attr("r", "30px");

For Example i will get:

<g class="test">
   <circle class="bg-circle" />
   <circle class="bg-circle" />
</g>

But i want:

<g class="test">
   <circle class="bg-circle" />
</g>

Even when I call my function to set the nodes a second time.

Upvotes: 15

Views: 11342

Answers (4)

This question asked 9 years ago but I have been struggling with same problem and fixed like this

Create function for check that

const d3ExistRootControl = <GElement extends BaseType, Datum, PElement extends BaseType, PDatum>(
  selection: Selection<GElement, Datum, PElement, PDatum>,
  createSelection: () => Selection<GElement, Datum, PElement, PDatum>) => {

  if (selection.size()) return selection;
  return createSelection();

};

Usage

  const mouseG = d3ExistRootControl(
    svg.select(`g.${config.classes.mouseOverEffects}`),
    () => svg.append('g')
      .attr('class', config.classes.mouseOverEffects)
  );

With data and type implementations

 const mousePerLine = d3ExistRootControl(
        mouseG.selectAll(`.${config.classes.mousePerLine}`) as any,
        () => mouseG.selectAll(`.${config.classes.mousePerLine}`)
          .data(sumstat)
          .enter()
          .append('g')
          .attr('class', config.classes.mousePerLine)
  );

Upvotes: 0

duhaime
duhaime

Reputation: 27611

I had a single function that either initialized or updated a plot, and to prevent duplicate boilerplate from being made while allowing for transitions to the element, I ended up doing the following:

const grid = d3.select('.grid').node()
  ? d3.select('.grid')
  : g.append('g')
      .attr('class', 'grid');

Upvotes: 5

Luis Medina
Luis Medina

Reputation: 555

I had the same problem and the previews answers didn't help me so I solved it by doing this:

In my html. index i have two buttons that calls different data sources to make the same kind of charts, so every time I clicked one of the buttons I got duplicate graphs (function 1 calls data for divs with _des and function 2 calls data for _ic divs)

<div>
  <h3>Graficas</h3>
  <label>
    <input id="des" type="checkbox" class="radio"  name="fooby[1][]" checked onchange='load_des(this)'/>data 1 </label>
  <label>
    <input id="ic" type="checkbox" class="radio"  name="fooby[1][]" onchange='load_ic(this)'/>data 2</label>
</div>

<div class="container">
    <div id="donut_des"></div>
    <div id="bars_des" ></div>
    <div id="donut_ic"></div>
    <div id="bars_ic" ></div>
</div>

So I just added this in my main.js file for each function:

function load_des(obj) {
  if($(obj).is(":checked")){

  document.getElementById('donut_ic').innerHTML = "";
  document.getElementById('bars_ic').innerHTML = "";

// do everything else normally 
}

function load_ic(obj) {
  if($(obj).is(":checked")){

  document.getElementById('donut_des').innerHTML = "";
  document.getElementById('bars_des').innerHTML = "";

// do everything else normally 
}

Hope this works for anyone, since I couldn't solve this any other way.

Upvotes: 0

Wlodzislav K.
Wlodzislav K.

Reputation: 404

I guess you want to use d3 to draw some UI. I used something like that:

    function appendOnce(selection, s) {
        var l = s.split("."); // l[0] tag, l[0] dot separated classes

        var g = selection.selectAll(s)
            .data([0])

        g.enter().append(l[0])
            .attr("class", l.slice(1).join(" "))

        return g;
    }

    // and then use
    var clild = appendOnce(parent, "text.y-caption")
        .attr(...)
        .style(...);


Upvotes: -1

Related Questions