I am trying to generate some text labels and to then transition them onto a D3 graph.
Pseudo code: (1) Generate text labels at coordinates 0,0
(2) Transition labels to desired [x,y]
When I run the script below, however, I get the following issue in the console log window:
My code is as follows:
svg.selectAll(".groups")
.data(sampleData)
.append("text")
.attr("class", "label")
.text(function(d){return d.c})
.attr("dx",0)
.attr("dy", 0)
.style("fill-opacity",0)
.each("end", function(){
d3.selectAll(".label")
.transition()
.duration(2000)
.style("fill-opacity", 1)
.attr("dx", function(d) {
return x(d.x);
})
.attr("dy", function(d) {
return y(d.y);
});
})
Have you any idea what is going wrong? The two bits of code are running just fine. It's the transition that is giving me the headache.
You don't need that each here. Each adds a listener to a transition, but you have no transition selection when you get to that each function:
svg.selectAll(".groups")
.data(sampleData)
.append("text")
.attr("class", "label")
.text(function(d) {
return d.c
})
.attr("dx", 0)
.attr("dy", 0)
.style("fill-opacity", 0)
.each("end", function() {...
//No 'transition()' before this point
(by the way, you also don't have an "enter" selection, since there is no enter in the code)
Thus, it can be just this: setting the positions to zero (which you don't need to do, because the positions are zero by default), and changing them in the transition selection. Here is the demo:
var svg = d3.select("svg");
var data = ["foo", "bar", "baz", "foobar", "foobaz", "barbaz"];
svg.selectAll("foo")
.data(data)
.enter()
.append("text")
.text(function(d) {
return d
})
.style("fill-opacity", 0)
.transition()
.duration(2000)
.style("fill-opacity", 1)
.attr("dx", function(){ return Math.random()*280})
.attr("dy", function(){ return 20 + Math.random()*130});
<script src="https://d3js.org/d3.v4.min.js"></script>
<svg></svg>
Related
I would like to set a filter on paths and append text but nothing happens.
var filteredElements = svgContainer.selectAll("path")
//.data(feat.features)
.append("text")
.filter(function (d) {
if (d.properties.myID > 0) {
return true;
};
})
.attr("x", function (d) {
return path.centroid(d)[0];
})
.attr("y", function (d) {
return path.centroid(d)[1];
})
.attr("text-anchor", "middle")
.attr("font-size", "2px")
.text("foo");
filteredElements contains 46 elements which are correct but the text is not being appended.
With that code, it works fine but I need the condition in my filter:
svgContainer.selectAll("path[PE='1442']")
.data(feat.features)
.enter().append("text")
.attr("x", function (d) {
return path.centroid(d)[0];
})
.attr("y", function (d) {
return path.centroid(d)[1];
})
.attr("text-anchor", "middle")
.attr("font-size", "2px")
.text("foo");
I'm adding this as a second answer because there isn't enough room in a comment, but it suffices as an answer itself.
You have paths drawn on the svg, and you want to draw text for a subset of those paths.
There are two approaches that could be used for this. One is to use a parent g element to hold both path and text:
// Append a parent:
var g = svg.selectAll(null) // we want to enter an element for each item in the data array
.data(features)
.enter()
.append("g");
// Append the path
g.append("path")
.attr("d",path)
.attr("fill", function(d) { ... // etc.
// Append the text to a subset of the features:
g.filter(function(d) {
return d.properties.myID > 0; // filter based on the datum
})
.append("text")
.text(function(d) { .... // etc.
The bound data is passed to the children allowing you to filter the parent selection before adding the child text.
The other approach is closer to what you have done already, but you don't quite have idiomatic d3. We also don't need to re-bind the data to the paths (d3.selectAll("path").data(), instead we can use:
svgContainer.selectAll(null)
.data(feat.features.filter(function(d) { return d.properties.myID > 0; }))
.enter()
.append("text")
.attr("x", path.centroid(d)[0])
.attr("y", path.centroid(d)[1])
.attr("text-anchor", "middle")
.attr("font-size", "2px")
.text("foo")
As an aside, your initial approach was problematic in that it:
it appends text to path elements directly, which won't render (as you note)
it is binding data to the paths again, for each element in the selection, you are binding an item of the data array to a selected element - since the selection is a sub-set of your paths, but your data is the full dataset, you are likely assigning different data to each path (without specifying an identifier, the ith item in the full dataset is bound to the ith element in the sub-selection).
I have now a solution, I think. My text nodes were inside my path nodes. Now I'm just doing this in my if condition and add my text node under my paths.
svgContainer.selectAll("path")
.data(feat.features)
.filter(function (d) {
if (d.properties.myID > 0) {
d3.select("svg")
.append("text")
.attr("x", path.centroid(d)[0])
.attr("y", path.centroid(d)[1])
.attr("text-anchor", "middle")
.attr("font-size", "2px")
.text("foo")
};
})
In a previous question a user helped me with a problem that consisted in not knowing how to put images where the circles are. this time my problem is that I can not drag the nodes. This gif illustrates my problem (first I show how the nodes should move normally, then I illustrate my problem.).
Why does this happen?
var link = g.append("g")
.attr("class", "links")
.selectAll("line")
.data(graph.links)
.enter().append("line")
.attr("stroke", function(d) {
console.log(d);
return colorLink(d.group);
})
.attr("marker-end", "url(#arrow)");
var node = g.selectAll("g.node")
.data(graph.nodes)
//.filter(function(d){ return d.type=="circle"; })
var NodeCircleEnter= node.enter().append("g")
.attr("class","node")
.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; })
NodeCircleEnter
.append("circle")
.attr("r", 20)
.attr("fill", function(d) {
return colorNode(d.group);
})
.style("stroke", function(d) {
return colorNode(d.group);
})
// Append images
var images = NodeCircleEnter.append("svg:image")
.attr("xlink:href", function(d) {console.log(d); return d.image;})
.attr("x", function(d) { return -25;})
.attr("y", function(d) { return -25;})
.attr("height", 50)
.attr("width", 50);
This is my full code:
https://plnkr.co/edit/9uH13N3or3G9VTgQlMm9?p=preview
Your event handlers need to be applied to the elements themselves rather than the canvas as a whole.
In this code (only existing in your Plunker project, but not in your question):
var drag_handler = d3.drag()
.on("start", drag_start)
.on("drag", drag_drag)
.on("end", drag_end);
drag_handler(node);
Your variable node is the drawing. You actually want a collection of elements to affect which you get from functions like .enter(). Your variable NodeCircleEnter contains that collection so the particular line should be:
drag_handler(NodeCircleEnter);
This still leaves an issue where dragging on the labels themselves doesn't work. This is because the labels aren't children of the elements you set the handlers on.
I have implemented d3-zoom by following this brief tutorial.
I'm using https://d3js.org/d3.v5.min.js. This is my first project with d3.
My goal is to have a kind of floor plan showing booth tables at a venue. Similar to the tutorial, I've drawn shape elements from an array. In my case I've entered an array of booth information into a grid of elements.
The zoom functionality works just fine, except when my cursor is over the border or fill of one of my rectangles, or on the text of a element. If the point of my cursor is touching any of these elements, the zooming behavior stops working.
Try to zoom with the mousewheel with your cursor in blank space versus touching a shape or text.
I've tried to fit a console.log in somewhere to see what's not getting passed in the event, but have had trouble even finding where I can get the event argument.
Any help greatly appreciated! Here is my code:
var svg = d3.select("#venue-svg"); // this is my svg element
// the zoom rectangle. from the tutorial: 'The zoom behavior is applied
// to an invisible rect overlaying the SVG element; this ensures that it
// receives input, and that the pointer coordinates are not affected by
// the zoom behavior’s transform.'
svg.append("rect")
.attr("width", "100%")
.attr("height", "100%")
.style("fill", "none")
.style("pointer-events", "all")
.call(
d3
.zoom()
.scaleExtent([1 / 2, 4])
.on("zoom", zoomed)
);
function zoomed() {
g.attr("transform", d3.event.transform);
}
// a parent <g> that holds everything else and is targeted
// for the transform (from the tutorial).
var g = svg.append("g");
// the groups that hold each booth table, associated org name, etc.
var tables = g
.selectAll("g")
.data(venueBooths)
.enter()
.append("g")
.attr("transform", function(d) {
return "translate(" + d.x + " " + d.y + ")";
});
var tableRects = tables
.append("rect")
.attr("stroke", "steelblue")
.attr("stroke-width", "2px")
.attr("width", function(d) {
return d.w;
})
.attr("height", function(d) {
return d.h;
})
.attr("fill", "none")
.attr("fill", function(d) {
return $.isEmptyObject(d.reservation) ? "none" : "#FF5733";
})
.attr("id", function(d) {
return "table-" + d.id;
});
tables
.append("text")
.text(function(d) {
return "Booth " + d.id;
})
.attr("dx", 5)
.attr("dy", 60)
.attr("font-size", "8px");
tables
.append("text")
.text(function(d) {
return d.reservation.orgName ? d.reservation.orgName : "Available";
})
.attr("dy", 15)
.attr("dx", 5)
.attr("font-size", "9px")
.attr("font-weight", "bold");
Try creating the rect in the end such that the DOM looks like this:
<svg>
<g></g>
<rect></rect>
</svg>
Since the zoom function is attached to the large rectangle, creating the smaller boxes above it prevents a zoom event from propagating to the large rectangle below them. It works for the boxes with a fill: none; since it behaves like a hollow box.
Try modifying the code to something like:
var svg = d3.select("#venue-svg"); // this is my svg element
// the zoom rectangle. from the tutorial: 'The zoom behavior is applied
// to an invisible rect overlaying the SVG element; this ensures that it
// receives input, and that the pointer coordinates are not affected by
// the zoom behavior’s transform.'
function zoomed() {
g.attr("transform", d3.event.transform);
}
// a parent <g> that holds everything else and is targeted
// for the transform (from the tutorial).
var g = svg.append("g");
// the groups that hold each booth table, associated org name, etc.
var tables = g
.selectAll("g")
.data(venueBooths)
.enter()
.append("g")
.attr("transform", function(d) {
return "translate(" + d.x + " " + d.y + ")";
});
var tableRects = tables
.append("rect")
.attr("stroke", "steelblue")
.attr("stroke-width", "2px")
.attr("width", function(d) {
return d.w;
})
.attr("height", function(d) {
return d.h;
})
.attr("fill", "none")
.attr("fill", function(d) {
return $.isEmptyObject(d.reservation) ? "none" : "#FF5733";
})
.attr("id", function(d) {
return "table-" + d.id;
});
tables
.append("text")
.text(function(d) {
return "Booth " + d.id;
})
.attr("dx", 5)
.attr("dy", 60)
.attr("font-size", "8px");
tables
.append("text")
.text(function(d) {
return d.reservation.orgName ? d.reservation.orgName : "Available";
})
.attr("dy", 15)
.attr("dx", 5)
.attr("font-size", "9px")
.attr("font-weight", "bold");
svg.append("rect")
.attr("width", "100%")
.attr("height", "100%")
.style("fill", "none")
.style("pointer-events", "all")
.call(
d3
.zoom()
.scaleExtent([1 / 2, 4])
.on("zoom", zoomed)
);
I have created a force directed graph but I'm unable to add text to the links created.
How can I do so?
Following is my code link
I have used the following line to append the titles on the link's, but its not coming.
link.append("title")
.text(function (d) {
return d.value;
});
What am I doing wrong with this ?
This link contains the solution that you need.
The key point here is that "title" adds tooltip. For label, you must provide slightly more complex (but not overly complicated) code, like this one from the example from the link above:
// Append text to Link edges
var linkText = svgCanvas.selectAll(".gLink")
.data(force.links())
.append("text")
.attr("font-family", "Arial, Helvetica, sans-serif")
.attr("x", function(d) {
if (d.target.x > d.source.x) {
return (d.source.x + (d.target.x - d.source.x)/2); }
else {
return (d.target.x + (d.source.x - d.target.x)/2); }
})
.attr("y", function(d) {
if (d.target.y > d.source.y) {
return (d.source.y + (d.target.y - d.source.y)/2); }
else {
return (d.target.y + (d.source.y - d.target.y)/2); }
})
.attr("fill", "Black")
.style("font", "normal 12px Arial")
.attr("dy", ".35em")
.text(function(d) { return d.linkName; });
The idea of the code is simple: It calculates the midpoint of the link, and displays some text at that place (you can decide what that text actually is). There are some additional calculations and conditions, you can figure it out from the code, however you'll anyway want to change them depending on your needs and aesthetics.
EDIT: Important note here is that "gLink" is the name of the class of links, previously defined with this code:
// Draw lines for Links between Nodes
var link = svgCanvas.selectAll(".gLink")
.data(force.links())
In your example, it may be different, you need to adjust the code.
Here is a guide how to incorporate solution from example above to another example of force layout that doesn't have link labels:
SVG Object Organization and Data Binding
In D3 force-directed layouts, layout must be supplied with array of nodes and links, and force.start() must be called. After that, visual elements may be created as requirements and desing say. In our case, following code initializes SVG "g" element for each link. This "g" element is supposed to contain a line that visually represent link, and the text that corresponds to that link as well.
force
.nodes(graph.nodes)
.links(graph.links)
.start();
var link = svg.selectAll(".link")
.data(graph.links)
.enter()
.append("g")
.attr("class", "link")
.append("line")
.attr("class", "link-line")
.style("stroke-width", function (d) {
return Math.sqrt(d.value);
});
var linkText = svg.selectAll(".link")
.append("text")
.attr("class", "link-label")
.attr("font-family", "Arial, Helvetica, sans-serif")
.attr("fill", "Black")
.style("font", "normal 12px Arial")
.attr("dy", ".35em")
.attr("text-anchor", "middle")
.text(function(d) {
return d.value;
});
"g" elements have class "link", lines have class "link-line", ad labels have class "link-label". This is done so that "g" elements may be easily selected, and lines and labels can be styled in CSS file conveninetly via classes "link-line" and "link-label" (though such styling is not used in this example).
Initialization of positions of lines and text is not done here, since they will be updated duting animation anyway.
Force-directed Animation
In order for animation to be visible, "tick" function must contain code that determine position of lines and text:
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; });
linkText
.attr("x", function(d) {
return ((d.source.x + d.target.x)/2);
})
.attr("y", function(d) {
return ((d.source.y + d.target.y)/2);
});
Here is the resulting example: plunker
I am trying to modify this D3.js example (Dynamic Node-Link Tree) by adding a specific label (SVG text) to each node, but unsucessfully.
If I understand correctly, after a brief look at SVG specs and D3 documentation, the best way would be to create SVG groups and move them around.
Unfortunately, this is not working, as the transitions have no effect on the groups.
Is there a simple(r) way I am not aware of?
Many thanks.
If you're looking for an effect where you switch the circles for text labels, you can do the following:
// Enter any new nodes at the parent's previous position.
node.enter().append("svg:text")
.attr("class", "node")
.attr("x", function(d) { return d.parent.data.x0; })
.attr("y", function(d) { return d.parent.data.y0; })
.attr("text-anchor", "middle")
.text(function(d) { return "Node "+(nodeCount++); })
.transition()
.duration(duration)
.attr("x", x)
.attr("y", y);
See the fiddle here: http://jsfiddle.net/mccannf/pcwMa/4/
Edit
However, if you're looking to add labels alongside the circles, I would not recommend using svg:g in this case, because then you would have to use transforms to move the groups around. Instead, just double up on the circle nodes and text nodes like so in the update function:
// Update the nodes…
var cnode = vis.selectAll("circle.node")
.data(nodes, nodeId);
cnode.enter().append("svg:circle")
.attr("class", "node")
.attr("r", 3.5)
.attr("cx", function(d) { return d.parent.data.x0; })
.attr("cy", function(d) { return d.parent.data.y0; })
.transition()
.duration(duration)
.attr("cx", x)
.attr("cy", y);
var tnode = vis.selectAll("text.node")
.data(nodes, nodeId);
tnode.enter().append("svg:text")
.attr("class", "node")
.text(function(d) { return "Node "+(nodeCount++); })
.attr("x", function(d) { return d.parent.data.x0; })
.attr("y", function(d) { return d.parent.data.y0; })
.transition()
.duration(duration)
.attr("x", x)
.attr("y", y);
// Transition nodes to their new position.
cnode.transition()
.duration(duration)
.attr("cx", x)
.attr("cy", y);
tnode.transition()
.duration(duration)
.attr("x", x)
.attr("y", y)
.attr("dx", 4)
.attr("dy", 4); //padding-left and padding-top
A fiddle that demonstrates this can be found here: http://jsfiddle.net/mccannf/8ny7w/19/