Reputation: 165
I have gone through quite a few tutorial but still couldn't work out how to correctly update data, any help would be highly appreciated, thank you!
For some reason, the code doesn't run properly in the snippet, I am quite new to use it, what I want to achieve is by clicking the button, the data got updated, I tried to use selection.exit().remove, but the exit array is always empty, I don't really understand how that works
const arr = [{
name: "A",
dataset_1: 5,
dataset_2: 6,
},
{
name: "B",
dataset_1: 7,
dataset_2: 22,
},
{
name: "C",
dataset_1: 33,
dataset_2: 23,
},
{
name: "D",
dataset_1: 20,
dataset_2: 12,
},
{
name: "E",
dataset_1: 21,
dataset_2: 15,
},
{
name: "F",
dataset_1: 15,
dataset_2: 18,
},
];
//function for adding dataset
let counter = 2;
const add_set = (arr) => {
const ran = () => Math.floor(Math.random() * 20 + 1);
const add = (arr) => {
counter++;
arr.map((i) => (i[`dataset_${counter}`] = ran()));
};
add(arr);
};
//function for removing dataset
const remove_set = (arr) => {
arr.map((i) => delete i[`dataset_${counter}`]);
counter >= 1 ? counter-- : counter;
};
//draw area chart
const draw = () => {
//No.1 define place to draw
let svg = d3.select("svg"),
margin = {
left: 50,
right: 50,
top: 15,
bottom: 30
},
width = +svg.attr("width") - margin.left - margin.right,
height = +svg.attr("height") - margin.top - margin.bottom,
g = svg
.append("g")
.attr(
"transform",
"translate(" + margin.left + "," + margin.top + ")"
);
//No.2 set axes
let categoriesNames = arr.map((d) => d.name);
let xScale = d3.scalePoint().domain(categoriesNames).range([0, width]); // scalepoint make the axis starts with value compared with scaleBand
let copy = JSON.parse(JSON.stringify(arr));
copy.map((i) => delete i.name);
var yScale = d3
.scaleLinear()
.range([height, 0])
.domain([0, d3.max(copy, (i) => Math.max(...Object.values(i)))]);
let colorScale = d3.scaleOrdinal().range(d3.schemeSet3);
let dataSets = Object.keys(arr[0]).filter((i) => i !== "name");
colorScale.domain(dataSets);
g.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(xScale));
g.append("g").attr("class", "y axis").call(d3.axisLeft(yScale));
g.exit().remove();
//No.3 draw chart
let areaData = dataSets.map((i) => {
return {
id: i,
values: arr.map((d) => {
return {
name: d.name,
value: d[i]
};
}),
};
});
let generator = d3
.area()
.curve(d3.curveMonotoneX)
.y0(yScale(0))
.x((d) => xScale(d.name))
.y1((d) => yScale(d.value));
let area = g
.selectAll(".area")
.data(areaData)
.enter()
.append("g")
.attr("class", (d) => `area${d.id}`);
area.exit().remove();
area
.append("path")
.attr("d", (d) => {
return generator(d.values);
})
.attr("opacity", 0.6)
.style("fill", (d) => colorScale(d.id));
};
//buttons
draw();
const update = () => {
add_set(arr);
draw();
};
const remove = () => {
remove_set(arr);
draw();
};
d3.select("body")
.append("button")
.text("Remove dataset")
.on("click", remove);
d3.select("body")
.append("button")
.text("Add dataset")
.on("click", update);
<script src="https://d3js.org/d3-selection-multi.v1.min.js"></script>
<script src="https://d3js.org/d3.v6.min.js"></script>
<html>
<body>
<svg width="700" height="500"></svg>
</body>
</html>
Upvotes: 0
Views: 900
Reputation: 1846
In my case the selectAll
and append
were using different types:
groups.selectAll('rect.bar-item')
///...
enter.append('path')
Making them the same rect
or only path
solved it for me.
Hope this helps someone.
Upvotes: 0
Reputation: 13129
selection.exit()
is empty, because selection
is a subset of nodes from g
, and g
is newly added every time you call draw
. You need to separate one-time logic (draw axes, append container elements, set height/width/etc) from logic you want to run each time.
You should also see in your code that the colour of one of the areas changed. That was because another area was drawn in front of it. If something was really wrong, it would have repurposed the existing areas and failed to remove the one you didn't need. This is a pointer that you append something the second time that you shouldn't have appended!
But there was more. You use g.selectAll(".area")
, append a g
element, but don't give it class area
. Instead, you give it class areadataset_1
and areadataset_2
. This way, on subsequent calls, you can't find the area elements!
You also need to learn about .merge()
. I recommend looking up some tutorials, like this one. You only updated the new paths, never the old ones. Since you didn't need g
, I removed it for now, making the rest of the logic easier.
const arr = [{
name: "A",
dataset_1: 5,
dataset_2: 6,
},
{
name: "B",
dataset_1: 7,
dataset_2: 22,
},
{
name: "C",
dataset_1: 33,
dataset_2: 23,
},
{
name: "D",
dataset_1: 20,
dataset_2: 12,
},
{
name: "E",
dataset_1: 21,
dataset_2: 15,
},
{
name: "F",
dataset_1: 15,
dataset_2: 18,
},
];
//function for adding dataset
let counter = 2;
const add_set = (arr) => {
const ran = () => Math.floor(Math.random() * 20 + 1);
const add = (arr) => {
counter++;
arr.map((i) => (i[`dataset_${counter}`] = ran()));
};
add(arr);
};
//function for removing dataset
const remove_set = (arr) => {
arr.map((i) => delete i[`dataset_${counter}`]);
counter >= 1 ? counter-- : counter;
};
const margin = {
left: 50,
right: 50,
top: 15,
bottom: 30
};
let svg,
width,
height,
g;
const setup = () => {
//No.1 define place to draw
svg = d3.select("svg");
width = +svg.attr("width") - margin.left - margin.right;
height = +svg.attr("height") - margin.top - margin.bottom;
g = svg
.append("g")
.attr(
"transform",
"translate(" + margin.left + "," + margin.top + ")"
);
}
//draw area chart
const draw = () => {
//No.2 set axes
let categoriesNames = arr.map((d) => d.name);
let xScale = d3.scalePoint().domain(categoriesNames).range([0, width]); // scalepoint make the axis starts with value compared with scaleBand
let copy = JSON.parse(JSON.stringify(arr));
copy.map((i) => delete i.name);
var yScale = d3
.scaleLinear()
.range([height, 0])
.domain([0, d3.max(copy, (i) => Math.max(...Object.values(i)))]);
let colorScale = d3.scaleOrdinal().range(d3.schemeSet3);
let dataSets = Object.keys(arr[0]).filter((i) => i !== "name");
colorScale.domain(dataSets);
g.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(xScale));
g.append("g").attr("class", "y axis").call(d3.axisLeft(yScale));
g.exit().remove();
//No.3 draw chart
let areaData = dataSets.map((i) => {
return {
id: i,
values: arr.map((d) => {
return {
name: d.name,
value: d[i]
};
}),
};
});
let generator = d3
.area()
.curve(d3.curveMonotoneX)
.y0(yScale(0))
.x((d) => xScale(d.name))
.y1((d) => yScale(d.value));
let area = g
.selectAll(".area")
.data(areaData);
area.exit().remove();
area
.enter()
.append("path")
.attr("class", (d) => `area area${d.id}`)
.attr("opacity", 0.6)
.merge(area)
.attr("d", (d) => {
return generator(d.values);
})
.style("fill", (d) => colorScale(d.id));
};
//buttons
setup();
draw();
const update = () => {
add_set(arr);
draw();
};
const remove = () => {
remove_set(arr);
draw();
};
d3.select("body")
.append("button")
.text("Remove dataset")
.on("click", remove);
d3.select("body")
.append("button")
.text("Add dataset")
.on("click", update);
<script src="https://d3js.org/d3-selection-multi.v1.min.js"></script>
<script src="https://d3js.org/d3.v6.min.js"></script>
<html>
<body>
<svg width="700" height="500"></svg>
</body>
</html>
Upvotes: 1