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 })
Related
I have created a interactive legend, that works 100% perfectly fine. I just didnt manage to duplicate it for another column of my dataframe.
It starts with bubbles I'm plotting with the code below. Then I draw the actual legend.
1. creating bubbles
selection
.selectAll('circle')
.data(cities)
//.attr('r', 14)
.attr('r', function(d) {
return Math.max(Math.pow(d.population, 0.57) / 40, 7);})
.attr('cx', function(d) { return projection.latLngToLayerPoint([d.latitude, d.longitude]).x;
})
.attr('cy', function(d) { return projection.latLngToLayerPoint([d.latitude, d.longitude]).y;
})
.attr("class", function(d) { return "bubbles " + d[attribute] }) //Important feature for legend
//.attr("class", function(d) { return "bubbles " + d.category })
.attr('stroke', 'white')
.attr('stroke-width', function(d) {
return 1.2 / projection.scale;})
.style("fill", function(d) {
return color(d[attribute])})
.attr("stroke", "#FFFF")
.attr("stroke-width", 1)
.attr("fill-opacity", .9)
.on("mouseover", showTooltip)
.on("mousemove", moveTooltip)
.on("mouseleave", hideTooltip)
2. Drawing actual legend
svg_chorop.selectAll("mydots")
.data(allgroups)
.enter()
.append("rect")
.attr("x", 0)
.attr("y", function(d, i) {
return 60 + i * (size + 10)
}) // 60 is where the first dot appears. 10 is the distance between dots
.attr("width", size)
.attr("height", size)
.style("fill", function(d) {
return mycolor(d)
})
.style("stroke", "#DCDCDC")
.on("mouseover", highlight)
.on("mouseleave", noHighlight)
My goal : make the exact same legend with another hovering option.
Problem : I can't find a way to ADD another class attribute (in my case d.category, for economic sector) in the first bunch of code.
Fiddle link, so it's easier for you to see the code : jsfiddle
I have simply tried to add another .attr("class",..). It doesn't work. And I can find a similar fix for this problem online.
.attr("class", function(d) { return "bubbles " + d[attribute] }) //Important feature for legend
//.attr("class", function(d) { return "bubbles " + d.category })
Here you can see the two legends, left one functionnal, right, the
one to be implemented
Here you can see the dataframe and the category column
I draw a graph from a set of nodes and links using the following update function with nodes and links arrays defined earlier in the code.
function update() {
var link = svg.selectAll(".link")
.data(links);
link.enter().insert("line")
.attr("class", "link");
var node = svg.selectAll(".node")
.data(nodes);
node.enter().append("svg:g")
.attr("class", "node")
.attr("id", function(d) { return d.name })
.on("dblclick", dblclick)
.on("mouseover", function(d) {drawTags(tags, d.name);})
.on("mouseout", function(d) {
d3.select("#"+d.name).selectAll(".tool")
.transition()
.delay(800)
.remove();
})
.call(force.drag);
node.append("circle")
.attr("class", "circle")
.attr("r", 40);
// display name in nodes if node structure
node.append("text")
.attr("text-anchor", "middle")
.attr("dy", ".35em")
.text(function(d) { return d.name; });
force.on("tick", function() {
link.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; });
node.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
});
});
force.start();
}
On dynamic update of the graph, after double click event on a node, I fetch additional nodes and links and display them. So far, I was able to append nodes and links to their corresponding arrays, hence update drawing. However, the update function would loop through all elements for the display.
Therefore I would like to pass newNodes and newEdges to the update function and only draw new elements. The issue is that new elements replace existing ones in the svg code rather than to be appended.
Is there a way to append new elements to previously existing ones in the drawing?
Full code is available on fiddle (https://jsfiddle.net/pducrot/4eyb81kx/) mouse over a node and click on any appearing tag to see what's happening.
The effect I'm going for in a map based visualisation is:
Red circles on a map represent data (things at locations)
When something happens at a location, we briefly (2 seconds) show another, blue circle fading out over the top.
I'm trying to work out how to do this in the D3 paradigm. Specifically: how do you represent the same thing twice?
The problem I run into is that when I try to add the same dataset twice to a given SVG canvas group, nothing gets added. That is, using code like this:
g = svg.append("g");
var feature = g.selectAll("circle")
.data(stations)
.enter().append("circle")
.style("stroke", "black")
.style("opacity", .6)
.style("fill", "red")
.attr("r", function(d, i) { return d.free_bikes; });
var emphasis = g.selectAll("circle")
.data(stations)
.enter().append("circle")
.style("stroke", "black")
.style("opacity", .6)
.style("fill", "blue")
.attr("r", function(d, i) { return d.free_bikes; });
This workaround is ok, but kludgy and potentially limiting:
g2 = svg.append("g");
var emphasis = g2.selectAll("circle")
That is, adding the second group of elements to a different SVG group.
The proper way to do this is to use classes to select the circles (and applying that class when you create them). So you create the features like so:
var feature = g.selectAll("circle.feature")
.data(stations, function (d) { return d.id; } )
.enter().append("circle")
.attr("class", "feature") // <- assign the class
....
Similarly, for the emphasis:
var feature = g.selectAll("circle.emphasis")
.data(stations, function (d) { return d.id; } )
.enter().append("circle")
.attr("class", "emphasis") // <- assign the class
....
I've finally (sort of) figured it out. The two sets of data are treated as one because they share the same key, according to the rules of D3 constancy. So an easy way around is to give each set a key that can't overlap:
var feature = g.selectAll("circle")
.data(stations, function (d) { return d.id; } )
.enter().append("circle")
.style("stroke", "black")
.style("opacity", .6)
.style("fill", "red")
.attr("r", function(d, i) { return d.free_bikes * 1; });
var emphasis = g.selectAll("notathing")
.data(stations, function (d) { return d.id + " cannot possibly overlap"; } )
.enter().append("circle")
.style("stroke", "black")
.style("opacity", .6)
.style("fill", "blue")
.attr("r", function(d, i) { return d.free_bikes * 1; });
The only slight quirk is I have to modify the second selector (g.selectAll("notathing")) so it doesn't match any of the circles created by the first one.
I'm relatively new to D3 and trying to add labels to a bar chart.. I keep running into the problem of labels applying all values to each label. Precedding this code is normal data load etc.
// Controls Bar Layout and Offset (x(d.weekOf)+20)
var property = svg.selectAll(".property")
.data(data)
.enter().append("g")
.attr("class", "g")
.attr("transform", function(d) { return "translate(" + (x(d.weekOf)+20) + ",0)"; });
// Theese are the bars, width is the width of the bars
property.selectAll("rect")
.data(function(d) { return d.emissions; })
.enter().append("rect")
.attr("width", "80")
.attr("y", function(d) { return y(d.y1); })
.attr("height", function(d) { return y(d.y0) - y(d.y1); })
.attr("opacity", "0.7")
.style("fill", function(d) { return color(d.name); });
// Add Capacity Labels
property.selectAll("text")
.data(data)
.enter()
.append("text")
.text(function(d, i) {return d.Internal; })
.attr("x", 41)
.attr("y", 210)
.attr("text-anchor", "middle");
Clearly I'm missing something simple..?
Your labels are a subselection of the property selection, so instead of using data() to join each property label to the entire dataset as you are, you should instead use data to join to the corresponding parent datum, like so:
property.selectAll("text")
.data(function(d) {return [d];})
.enter()
.append("text")
.text(function(d, i) {return d.Internal; })
.attr("x", 41)
.attr("y", 210)
.attr("text-anchor", "middle");
I've been trying to get my force directed graph to go faster/smoother, and it seems commenting this part out of the "tick" function does the trick. Of course, it also makes all the edges disappear although the nodes still move together as if attached by invisible threads.
I have about 2-3 hundred nodes in a network-ish graph and when setting the opacity of each element I also check it's weight and if it's 1 I remove it.I repeat this for all nodes and text labels. (and edges using d.target.weight)
Is it just a number of nodes that weighing down everything? After removing the elements down to 20 or so why is it still so slow? Do I really have to piggyback my removal onto .style("opacity", function(d){//do stuff, return 1;})?
force.on("tick", function() {
// edges.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; })
// .style("stroke", function(d){
// return d.source.color;
// });
nodes.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; });
text.attr("x", function(d) { return d.x; })
.attr("y", function(d) { return d.y; });
});
functions for drawing the svg elements if it helps:
//Create edges as lines
var edges = group.selectAll("line")
.data(dataset.edges)
.enter()
.append("line")
.style("stroke", function(d,i){
if (d.target.weight === 1)
d3.select(this).remove();
return "#FFFFFF";
})
.style("opacity",0.5)
.style("stroke-width", 2);
//Create nodes as circles
var nodes = group.selectAll("circle")
.data(dataset.nodes)
.enter()
.append("circle")
.style("opacity",0.8)
.attr("r", function(d,i){
if (d.weight === 1)
d3.select(this).remove();
return nodeScale(d.weight * 2);
})
.style("fill", function(d, i) {
return d.color;
})
.call(force.drag);
var text = group.selectAll("text")
.data(dataset.nodes)
.enter()
.append("text")
.attr("fill","black")
.style("font-size",function(d){
return d.size;
})
.style("text-anchor", "middle")
.text(function(d){
return d.name;
})
.style("opacity",function(d){
if (d.weight === 1)
d3.select(this).remove();
else
return 0.8;
})
.on("mouseover",function(){
d3.select(this)
.style("opacity",1)
.style("font-size", 25);
})
.on("mouseout",function(){
d3.select(this)
.style("font-size", function(d) { return d.size; });
})
.call(force.drag);
Also the initiation function, became quite random after I fiddle around with it a lot:
(I also have a slider for each one that I play with when rendered)
var force = d3.layout.force()
.nodes(dataset.nodes)
.links(dataset.edges)
.size([w, h])
.linkDistance([50])
.charge([-2000])
.friction(0.5)
.gravity(0.5)
.theta(0.5)
.start();
I believe the problem was that removing an element still doesn't affect the actual dataset being used which is composed of hundred of nodes in this case.
Changing the code to removing the element from the dataset and using exit().remove() makes it go faster.