Xiao H
Xiao H

Reputation: 165

failed to use d3 to update legend data, entering is empty for changed data

I have an "add dataset" button, and once I click it the legend should show the changed dataset, this is all good. I also have a click event with the legend, so when I click the legend, the dataset get deleted, and I put a line through to the style, and here is the problem, when I use the add button to add another dataset, the deleted dataset legend should be changed to the new dataset number. The console shows the data is correct, but the legend is not changing. I have followed some tutorial and I completely followed it, but still get this, I have been working on this for several hours, I would like some help, thank you

enter image description here

let jsonObj = [{
    category: "Jan",
    values: [{
        value: 9,
        source: "dataset1",
      },
      {
        value: 8,
        source: "dataset2",
      },
    ],
  },
  {
    category: "Feb",
    values: [{
        value: 15,
        source: "dataset1",
      },
      {
        value: 21,
        source: "dataset2",
      },
    ],
  },
];
// function for adding data
let counter = 2;
const add_set = (arr) => {
  const ran = () => Math.floor(Math.random() * 15 + 1);
  const add = (arr) => {
    counter++;
    arr.map((i) =>
      i.values.push({
        value: ran(),
        source: `dataset${counter}`
      })
    );
  };
  add(arr);
};

//initial variables
let svg, totalWidth, totalHeight, legend;

const setup = () => {
  totalWidth = 100;
  totalHeight = 100;
  svg = d3
    .select("body")
    .append("svg")
    .attr("width", totalWidth)
    .attr("height", totalHeight);
};

const draw = (data) => {
  //No.6 set lengend
  legend = svg
    .selectAll(".legend")
    .data(data[0].values.map((d) => d.source));
  console.log(data[0].values.map((d) => d.source));
  let entering = legend.enter();

  let newLegend = entering
    .append("g")
    .attr("class", "legend")
    .attr("transform", (d, i) => "translate(0," + i * 20 + ")")
    .append("text")
    .attr("x", totalWidth)
    .attr("y", 9)
    .style("text-anchor", "end")
    .text((d) => d);

  entering.text((d) => d);
  let exiting = legend.exit();
  exiting.remove();

  newLegend.on("click", function(e, d) {
    jsonObj.forEach((i) => {
      i.values = i.values.filter((s) => s.source != d);
    });
    newLegend
      .filter((x) => x === d)
      .style("text-decoration", "line-through");
  });
};
setup();
draw(jsonObj);

const update = () => {
  add_set(jsonObj);
  draw(jsonObj);
};
<script src="https://d3js.org/d3.v6.min.js"></script>
<button onclick="update()">Add dataset</button>

enter image description here

Upvotes: 1

Views: 87

Answers (1)

Ruben Helsloot
Ruben Helsloot

Reputation: 13129

The following solution is exactly what you asked for. I don't remove any nodes, because you always re-use them.

There is also a difference between the entering nodes and the newLegend nodes - one has actual content, the other is just a sort of empty array.

let jsonObj = [{
    category: "Jan",
    values: [{
        value: 9,
        source: "dataset1",
      },
      {
        value: 8,
        source: "dataset2",
      },
    ],
  },
  {
    category: "Feb",
    values: [{
        value: 15,
        source: "dataset1",
      },
      {
        value: 21,
        source: "dataset2",
      },
    ],
  },
];
// function for adding data
let counter = 2;
const add_set = (arr) => {
  const ran = () => Math.floor(Math.random() * 15 + 1);
  const add = (arr) => {
    counter++;
    arr.map((i) =>
      i.values.push({
        value: ran(),
        source: `dataset${counter}`
      })
    );
  };
  add(arr);
};

//initial variables
let svg, totalWidth, totalHeight, legend;

const setup = () => {
  totalWidth = 100;
  totalHeight = 100;
  svg = d3
    .select("body")
    .append("svg")
    .attr("width", totalWidth)
    .attr("height", totalHeight);
};

const draw = (data) => {
  //No.6 set lengend
  legend = svg
    .selectAll(".legend")
    .data(data[0].values.map((d) => d.source));
  console.log(data[0].values.map((d) => d.source));

  legend.exit().remove();
  let newLegend = legend.enter()
    .append("g")
    .attr("class", "legend")
    .attr("transform", (d, i) => "translate(0," + i * 20 + ")");

  newLegend
    .append("text")
    .datum((d) => d)
    .attr("x", totalWidth)
    .attr("y", 9)
    .style("text-anchor", "end")
    .on("click", function(e, d) {
      jsonObj.forEach((i) => {
        i.values = i.values.filter((s) => s.source != d);
      });
      newLegend
        .filter((x) => x === d)
        
        .style("text-decoration", "line-through");
    });

  legend = newLegend.merge(legend);
  legend
    .style("text-decoration", null)
    .select("text")
    .text((d) => d);
};
setup();
draw(jsonObj);

const update = () => {
  add_set(jsonObj);
  draw(jsonObj);
};
<script src="https://d3js.org/d3.v6.min.js"></script>
<button onclick="update()">Add dataset</button>

Upvotes: 1

Related Questions