My d3 multi bar does not refresh after data changes. If there is more bars it add it in the end of old but does not remove old ones. If there is less bars it does not add it at all. Axis changes all the time for that from new data
var bars = svg.select(".chart-group")
.selectAll(".state")
.data(data);
bars.enter().append("g")
.attr("class", "state")
.attr("transform", function (d) {
return "translate(" + x0Scale(d[KEY]) + ",0)";
});
bars.exit().remove();
bars.selectAll("rect")
.data(function (d) {
return d.ages;
})
.enter().append("rect")
.attr("width", x1Scale.rangeBand())
.attr("x", function (d) {
return x1Scale(d[SEGMENT]);
})
.attr("y", function (d) {
return yScale(d[DATA]);
})
.attr("height", function (d) {
return chartH - yScale(d[DATA]);
})
.style("fill", function (d) {
return color(d[SEGMENT]);
});
Edit:
Here is fiddle with my problem https://jsfiddle.net/wxw5zdws/
I achieve my goal when whole container is removed:
if (container) {
container.remove();
}
I think it is bad practice, there is an issue in my drawing bars method. It should use old elements and remove/add usseless/needed elements.
What is wrong with this bars?
Related
I was trying to add labels to circles that are constantly moving in D3. One of the ways I found was to insert the label and the circle to a g tag. However, this causes errors whenever I want to EXIT the elements and merge them (which is not happening right now).
Is there any other way to do it? My graph right now seems to be with lower frames per second because it is not using the merge to update from the current x/y position to the new one.
The code is as follow:
var t = d3.transition()
.duration(0);
// JOIN new data with old elements.
var circles = g.append("g").selectAll('g')
.data(data.values)
.enter()
.append('g')
// EXIT elements
d3.selectAll("circle").transition()
.attr("class", "exit")
.remove();
// EXIT elements
d3.selectAll(".label")
.attr("class", "exit")
.remove();
// ENTER new elements present in new data.
// Circles
circles.append("circle")
.attr("class", "enter")
.attr("fill", function (d) { return color(d.country); })
.attr("cy", function (d) { return y(d.active_cases); })
.attr("cx", function (d) { return x(d.total_deaths) })
.merge(circles)
.transition(t)
.attr("r", 15)
// Labels
circles.append("text")
.attr("class", "label")
.attr("font-size", 10)
.style("text-anchor", "middle")
.style("fill", "white")
.attr("x", function (d) { return x(d.total_deaths) })
.attr("y", function (d) { return 3 + y(d.active_cases) })
.merge(circles)
.text(function (d) { return d.country })
script type="text/javascript">
d3.csv("mydata.csv", function(data){
var svgcontainer = d3.select("body").append("svg")
.attr("width",500)
.attr("height",500)
svgcontainer.selectAll("rect")
.data(data)
.enter()
.append("rect")
.attr("width" ,function (d) { return d.age * 10;})
.attr("height" ,45)
.attr("y",function(d,i) { return i*50; })
.attr("fill","blue")
svgcontainer.selectAll("text")
.data(data)
.enter()
.append("text")
.attr("fill","white")
//.attr("y",function(d,i) { return i*50 ; })
.text( function (d) { return d.name; })
})
I have to append text in my d3.js code but it's not displaying text.I am new to d3.js so please any one help me. Here is my code-
From the first observation i will get to know the issue related with the text element position in svg. In SVG Coordinate positions are very important to achieve the graphical representation. In above code snippet x and y positions are missing. sample code below:-
svgcontainer.append("text")
.attr("x", function(d) { return x(d.value) - 3; })
.attr("y", barHeight / 2)
.attr("dy", ".35em")
.text(function(d) { return d.value; });
Example
Following the County Bubbles example, it's easy to add a bubble for each county. This is how it is added in the example:
svg.append("g")
.attr("class", "bubble")
.selectAll("circle")
.data(topojson.feature(us, us.objects.counties).features
.sort(function(a, b) { return b.properties.population - a.properties.population; }))
.enter().append("circle")
.attr("transform", function(d) { return "translate(" + path.centroid(d) + ")"; })
.attr("r", function(d) { return radius(d.properties.population); })
.append("title")
.text(function(d) {
return d.properties.name
+ "\nPopulation " + formatNumber(d.properties.population);
});
However, rather than using a variable from the json file (population), I need to update the radii according to a variable which dynamically changes (so I cannot put it in the json file beforehand as was done in the example). I call updateRadii() when a county is clicked, which needs access to the FIPS.
var currFIPS,
flowByFIPS;
var g = svg.append("g");
queue()
.defer(d3.json, "us.json")
.defer(d3.csv, "census.csv", function(d) {
return {
work: +d.workplace,
home: +d.residence,
flow: +d.flow
}
})
.await(ready);
function ready(error, us, commute) {
// Counties
g.append("g")
.attr("class", "counties")
.selectAll("path")
.data(topojson.feature(us, us.objects.counties).features)
.enter().append("path")
.attr("d", path)
.on("click", function(d) {
// Get FIPS of selected county
currFIPS = d.id;
// Filter on selected county (i.e., grab
// people who work in the selected county)
var data = commute.filter(function(d) {
return d.work == currFIPS;
});
// Create d3.map for where these people live
flowByFIPS = d3.map(data, function(d) {
return d.home;
});
// Update radii at "home" counties to reflect flow
updateRadii();
});
// Bubbles
g.append("g")
.attr("class", "counties")
.selectAll("circle")
.data(topojson.feature(us, us.objects.counties).features)
.enter().append("circle")
.attr("id", function(d) { return d.id; })
.attr("transform", function(d) {
return "translate(" + path.centroid(d) + ")";
})
.attr("r", 0); // invisible before a county is clicked
}
function updateRadii() {
svg.selectAll(".counties circle")
.transition()
.duration(300)
.attr("r", function(d) {
return flowByFIPS.get(d.id).flow
});
}
According to the error code, I believe that the circles do not have an id (FIPS code) attached. How do I get them to have an id? (I tried nesting the circle with the path using .each as explained in this answer, but could not get it working.)
Note that the above code works for updating fill on paths (rather than circles). For example, sub updateRadii(); for updateFill(); with the function as:
function updateFill() {
svg.selectAll(".counties path")
.transition()
.duration(300)
.attr("fill", function(d) {
return flowByFIPS.get(d.id).color; // e.g., "#444"
});
}
The problem here is that you don't supply d3 with data in the update function. I will recommend you update the data loaded from the file on the clicks, and from there you update the svg.
var update = function() {
g.selectAll(".country")
.data(data)
.attr("r", function(d) { return d.properties.flow_pct });
};
var data = topojson.feature(us, us.objects.counties).features;
data.forEach(function(x) { x.properties.flow_pct = /* calc the value */; })
g.append("g")
.attr("class", "counties")
.selectAll(".country")
.data(data)
.enter()
.append("circle")
.attr("class", "country")
.attr("transform", function(d) {
return "translate(" + path.centroid(d) + ")";
})
.on("click", function(d) {
// more code
data.forEach(function(x) { x.properties.flow_pct = /* calc the NEW value */; })
update();
});
update();
I've tried to use as much as the same code as before, but still trying to straiten it a bit out. The flow is now more d3-like, since the update function works on the changed data.
Another plus which this approach is both first render and future updates use the same logic for finding the radius.
It turns out to be an obvious solution. I forgot to check for the cases where a flow doesn't exist. The code above works if updateRadii() is changed to:
function updateRadii() {
svg.selectAll(".counties circle")
.transition()
.duration(300)
.attr("r", function(d) {
if (currFIPS == d.id) {
return 0;
}
var county = flowByFIPS.get(d.id);
if (typeof county === "undefined") {
return 0;
} else {
return county.flow;
}
});
}
So that I can transition the bars in a bar chart smoothly I need to set the height before I call transition().
When the chart first renders the bars animate up from the bottom of the chart as required:
chart.svg.selectAll('.bar')
.attr('y', chart.options.height)
.attr('x', function (d) {
return chart.xScale(d.title);
})
.attr('width', chart.xScale.rangeBand())
.attr('height', function () {
return 0;
})
.transition()
.attr('y', function (d) {
return chart.yScale(d.score);
})
.attr('height', function (d) {
return chart.options.height - chart.yScale(d.score);
});
However, when I change the data I don't want to set the height back to 0. Instead I need to set the height to the current height of the rectangle. How can I access this from the attr function?
.attr('height', function () {
return 0; // how do I get the current height
})
When I log this I have access to the DOM element but not sure where to go from there. I tried d3.select(this).attr('height') but it always returns null.
As #LarsKotthoff is hinting at in his comment, just break apart your initial draw from your update:
// intial draw of bars
node
.enter()
.append("rect")
.attr("class", "myBars")
.style("fill", "steelblue")
.attr('y', config.height)
.attr('x', function(d, i) {
return xScale(i);
})
.attr('width', xScale.rangeBand())
.attr('height', function() {
return 0;
});
Then fire the update to transition the bars from their current position:
function update() {
node = svg
.selectAll(".myBars")
.data(data);
node
.transition()
.attr('y', function(d) {
return yScale(d);
})
.attr("height", function(d) {
return config.height - yScale(d);
});
}
Here's the most minimal example I could code up.
So I have the next force layout graph code for setting nodes, links and other elements:
var setLinks = function ()
{
link = visualRoot.selectAll("line.link")
.data(graphData.links)
.enter().append("svg:line")
.attr("class", "link")
.style("stroke-width", function (d) { return nodeStrokeColorDefault; })
.style("stroke", function (d) { return fill(d); })
.attr("x1", function (d) { return d.source.x; })
.attr("y1", function (d) { return d.source.y; })
.attr("x2", function (d) { return d.target.x; })
.attr("y2", function (d) { return d.target.y; });
graphData.links.forEach(function (d)
{
linkedByIndex[d.source.index + "," + d.target.index] = 1;
});
};
var setNodes = function ()
{
node = visualRoot.selectAll(".node")
.data(graphData.nodes)
.enter().append("g")
.attr("id", function (d) { return d.id; })
.attr("title", function (d) { return d.name; })
.attr("class", "node")
.on("click", function (d, i) { loadAdditionalData(d.userID, this); })
.call(force.drag)
.on("mouseover", fadeNode(.1)).on("mouseout", fadeNode(1));
};
//append the visual element to the node
var appendVisualElementsToNodes = function ()
{
node.append("circle")
.attr("id", function (d) { return "circleid_" + d.id; })
.attr("class", "circle")
.attr("cx", function (d) { return 0; })
.attr("cy", function (d) { return 0; })
.attr("r", function (d) { return getNodeSize(d); })
.style("fill", function (d) { return getNodeColor(d); })
.style("stroke", function (d) { return nodeStrokeColorDefault; })
.style("stroke-width", function (d) { return nodeStrokeWidthDefault; });
//context menu:
d3.selectAll(".circle").on("contextmenu", function (data, index)
{
d3.select('#my_custom_menu')
.style('position', 'absolute')
.style('left', d3.event.dx + "px")
.style('top', d3.event.dy + "px")
.style('display', 'block');
d3.event.preventDefault();
});
//d3.select("svg").node().oncontextmenu = function(){return false;};
node.append("image")
.attr("class", "image")
.attr("xlink:href", function (d) { return d.profile_image_url; })//"Images/twitterimage_2.png"
.attr("x", -12)
.attr("y", -12)
.attr("width", 24)
.attr("height", 24);
node.append("svg:title")
.text(function (d) { return d.name + "\n" + d.description; });
};
Now, the colors and size dependencies changed and I need to redraw the graph circles (+all appended elements) with different color and radius. Having problem with it.
I can do this:
visualRoot.selectAll(".circle").remove();
but I have all the images that I attached to '.circles' still there.
In any way, any help will be appreciated, let me know if the explanation is not clear enough, I will try to fix it.
P.S. what is the difference between graphData.nodes and d3.selectAll('.nodes')?
Your answer will work, but for posterity, these methods are more generic.
Remove all children from HTML:
d3.select("div.parent").html("");
Remove all children from SVG/HTML:
d3.select("g.parent").selectAll("*").remove();
The .html("") call works with my SVG, but it might be a side effect of using innerSVG.
If you want to remove the element itself, just use element.remove(), as you did. In case you just want to remove the content of the element, but keep the element as-is, you can use f.ex.
visualRoot.selectAll(".circle").html(null);
visualRoot.selectAll(".image").html(null);
instead of .html("") (I wasn't sure which element's children you want deleted). This keeps the element itself, but cleans all included content. It the official way to do this, so should work cross-browser.
PS: you wanted to change the circle sizes. Have you tried
d3.selectAll(".circle").attr("r", newValue);
My first advice is that you should read the d3.js API about selections: https://github.com/mbostock/d3/wiki/Selections
You have to understand how the enter() command works (API). The fact that you have to use it to handle new nodes has a meaning which will help you.
Here is the basic process when you deal with selection.data():
first you want to "attach" some data to the selection. So you have:
var nodes = visualRoot.selectAll(".node")
.data(graphData.nodes)
Then you can modify all nodes each times data is changed (this will do exactly what you want). If for example you change the radius of old nodes which are in the new dataset you loaded
nodes.attr("r", function(d){return d.radius})
Then, you have to handle new nodes, for this you have to select the new nodes, this is what selection.enter() is made for:
var nodesEnter = nodes.enter()
.attr("fill", "red")
.attr("r", function(d){return d.radius})
Finally you certainly want to remove the nodes you don't want anymore, to do this, you have to select them, this is what selection.exit() is made for.
var nodesRemove = nodes.exit().remove()
A good example of the whole process can also be found on the API wiki: https://github.com/mbostock/d3/wiki/Selections#wiki-exit
in this way, I have resolved it very easily,
visualRoot.selectAll(".circle").remove();
visualRoot.selectAll(".image").remove();
and then I just re-added visual elements which were rendered differently because the code for calculating radius and color had changed properties. Thank you.
To remove all element from a node:
var siblings = element.parentNode.childNodes;
for (var i = 0; i < siblings.length; i++) {
for (var j = 0; j < siblings.length; j++) {
siblings[i].parentElement.removeChild(siblings[j]);
}
}`