This is on a react application, what might be going wrong? A new svg is appended everytime, instead of the one that exists updating…
I’m not sure what other details I should add, I already included all the info that you may need to help me, please let me know if there’s anything else I should add. Thank you.

This is my component, I’m passing the data through props.
export const FourDirectionsTimeChart = ({data}) => {
useEffect(() => {
const width = document.getElementById("container").clientWidth;
const height = document.getElementById("container").clientHeight;
const R = (width + height) / 8;
const CX = width / 2;
const CY = height / 2;
const smallR = R * 0.1;
const circleColor = "bisque";
const itemColor = "#3F4200";
let svg = d3.select("#container")
.append("svg")
.attr("preserveAspectRatio", "xMinYMin meet")
.attr("viewBox", `0 0 ${width} ${height}`)
let mainCircle = svg.append("circle")
.attr("fill", circleColor)
.attr("r", R)
.attr("cx", CX)
.attr("cy", CY);
let centerCircle = svg.append("circle")
.attr("fill", "white")
.attr("r", smallR)
.attr("cx", CX)
.attr("cy", CY);
const timePercentage = (time) => {
const percentage = (time * 100 / 23) / 100;
return percentage;
};
function timeToRadius(time) {
return (smallR + timePercentage(time) * (R - smallR))
}
// Times concentric circles ---
for (let i = 0; i <= 23; i += 4) {
svg.append("circle")
.attr("fill", "none")
.attr("cx", CX)
.attr("cy", CY)
.attr("stroke-dasharray", "4 20")
.attr("stroke", "gray")
.attr("stroke-width", 1)
.attr("r", timeToRadius(i))
}
// Cardinal points ---
const textTime = 25;
const fontSize = R * 0.25;
svg.append("text")
.attr("dx", getPosition(0, textTime).x)
.attr("dy", getPosition(0, textTime).y)
.text("N")
.attr("fill", "black")
.attr("font-size", fontSize)
.attr("text-anchor", "middle")
.style("font-family", "serif")
svg.append("text")
.attr("dx", getPosition(180, textTime).x)
.attr("dy", getPosition(180, textTime).y)
.text("S")
.attr("fill", "black")
.attr("font-size", fontSize)
.attr("text-anchor", "middle")
.style("font-family", "serif")
.attr("alignment-baseline", "hanging")
svg.append("text")
.attr("dx", getPosition(-90, textTime).x)
.attr("dy", getPosition(-90, textTime).y)
.text("E")
.attr("fill", "black")
.attr("font-size", fontSize)
.attr("text-anchor", "start")
.attr("alignment-baseline", "middle")
.style("font-family", "serif");
svg.append("text")
.attr("dx", getPosition(90, textTime).x)
.attr("dy", getPosition(90, textTime).y)
.text("O")
.attr("fill", "black")
.attr("font-size", fontSize)
.attr("text-anchor", "end")
.attr("alignment-baseline", "middle")
.style("font-family", "serif")
// Ships positions ---
function getPosition(degrees, time) {
const getRadians = (degrees) => degrees * Math.PI / 180 + Math.PI / 2;
let x = (smallR + timePercentage(time) * (R - smallR)) * Math.cos(getRadians(degrees)) + CX;
let y = (smallR + timePercentage(time) * (R - smallR)) * Math.sin(getRadians(degrees)) * -1 + CY;
return { x, y };
}
// Data mapping ---
let parsedData = [];
(() => {
data?.forEach((x) => {
parsedData.push({
id: x.Patente,
course: x.Rumbo,
speed: x.Velocidad,
time: new Date(x.Fecha_gps).getHours()
})
});
let position;
parsedData.map(d => {
position = getPosition(d.course, d.time);
svg.append("circle")
.attr("fill", () => {
if (d.speed == 0) return "brown";
else return "brown";
})
.attr("r", R * 0.015)
.attr("cx", position.x)
.attr("cy", position.y)
.attr("opacity", 0.4);
});
})();
}, [data]);
return (
<div id="container" style={{ width: '100%', height: '100%' }}></div>
)
}
>Solution :
What do you think the line let svg = d3.select("#container").append("svg") does? It selects the element with ID "container" and appends a new SVG element to it.
You can essentially do one of two things:
- Select and remove all existing SVG elements in the container and then draw the chart from scratch on update:
d3.select("#container svg").remove(), thend3.select("#container").append("svg"); - Split the initialization (append svg, set size, draw axes) logic from the logic that might need to be run multiple times (update the values) and make sure to call only the second part on update. This is more complex, but also efficient, especially in an already DOM-heavy ecosystem like React.