adding input text box on clicking the data labels d3.js - javascript

I am trying to implement annotations on my scatterplot using d3.js v3. I want to insert a text box when I click on the text labels on my chart. For which I wrote this code:
circleGroup.selectAll("text")
.data(data)
.enter()
.append("text")
.text(function (d) {
if (d.label) {
return d.label;
}
return "";
})
.attr("x", function (d) {
return x(d.time) + 6;
})
.attr("y", function (d) {
return y(d.plotY) + 4;
})
.attr("font-size", "10px")
.attr("fill", "#2d3d45")
.on("click", function(d) {
d3.select(this).append("input").attr("type", "text").attr("name", "textInput").attr("value", "Text goes here");
});
The selection of the text element is working correctly. Its just not popping text box when i click on the text labels. Where am i going wrong? Is my approach not in right direction ?

You cannot append html element into svg. The only way to do it - use foreignObject element.
The foreignObject SVG element allows for inclusion of a foreign XML
namespace which has its graphical content drawn by a different user
agent. The included foreign graphical content is subject to SVG
transformations and compositing.
Look at the very simplified example:
var texts = ['foo', 'bar'];
var container = d3.select('g');
container
.selectAll('text')
.data(texts)
.enter()
.append('text')
.text(function(d) { return d; })
.attr('y', function(d,i) { return 50 * i })
.on('click', function(d,i) {
container
.append("foreignObject")
.attr("x", 0)
.attr("y", function() { return 50 * i })
.attr("width", 140)
.attr("height", 20)
.html(function(d) {
return '<input type="text" value="Text goes here" />'
})
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<div style="font-size: 12px;">Click on the texts below:</div>
<svg width="300" heigth="400">
<g transform="translate(20, 20)">
</g>
</svg>

Related

d3-zoom breaks when cursor is over an inner svg element

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)
);

Draw circles with text in the middle using Javascript/CSS [duplicate]

I've been using the sample code from this d3 project to learn how to display d3 graphs and I can't seem to get text to show up in the middle of the circles (similar to this example and this example). I've looked at other examples and have tried adding
node.append("title").text("Node Name To Display")
and
node.append("text")
.attr("text-anchor", "middle")
.attr("dy", ".3em").text("Node Name To Display")
right after node is defined but the only results I see is "Node Name To Display" is showing up when I hover over each node. It's not showing up as text inside the circle. Do I have to write my own svg text object and determine the coordinates of that it needs to be placed at based on the coordinates of radius of the circle? From the other two examples, it would seem like d3 already takes cares of this somehow. I just don't know the right attribute to call/set.
There are lots of examples showing how to add labels to graph and tree visualizations, but I'd probably start with this one as the simplest:
http://bl.ocks.org/950642
You haven’t posted a link to your code, but I'm guessing that node refers to a selection of SVG circle elements. You can’t add text elements to circle elements because circle elements are not containers; adding a text element to a circle will be ignored.
Typically you use a G element to group a circle element (or an image element, as above) and a text element for each node. The resulting structure looks like this:
<g class="node" transform="translate(130,492)">
<circle r="4.5"/>
<text dx="12" dy=".35em">Gavroche</text>
</g>
Use a data-join to create the G elements for each node, and then use selection.append to add a circle and a text element for each. Something like this:
var node = svg.selectAll(".node")
.data(nodes)
.enter().append("g")
.attr("class", "node")
.call(force.drag);
node.append("circle")
.attr("r", 4.5);
node.append("text")
.attr("dx", 12)
.attr("dy", ".35em")
.text(function(d) { return d.name });
One downside of this approach is that you may want the labels to be drawn on top of the circles. Since SVG does not yet support z-index, elements are drawn in document order; so, the above approach causes a label to be drawn above its circle, but it may be drawn under other circles. You can fix this by using two data-joins and creating separate groups for circles and labels, like so:
<g class="nodes">
<circle transform="translate(130,492)" r="4.5"/>
<circle transform="translate(110,249)" r="4.5"/>
…
</g>
<g class="labels">
<text transform="translate(130,492)" dx="12" dy=".35em">Gavroche</text>
<text transform="translate(110,249)" dx="12" dy=".35em">Valjean</text>
…
</g>
And the corresponding JavaScript:
var circle = svg.append("g")
.attr("class", "nodes")
.selectAll("circle")
.data(nodes)
.enter().append("circle")
.attr("r", 4.5)
.call(force.drag);
var text = svg.append("g")
.attr("class", "labels")
.selectAll("text")
.data(nodes)
.enter().append("text")
.attr("dx", 12)
.attr("dy", ".35em")
.text(function(d) { return d.name });
This technique is used in the Mobile Patent Suits example (with an additional text element used to create a white shadow).
I found this guide very useful in trying to accomplish something similar :
https://www.dashingd3js.com/svg-text-element
Based on above link this code will generate circle labels :
<!DOCTYPE html>
<html>
<head>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script>
</head>
<body style="overflow: hidden;">
<div id="canvas" style="overflow: hidden;"></div>
<script type="text/javascript">
var graph = {
"nodes": [
{name: "1", "group": 1, x: 100, y: 90, r: 10 , connected : "2"},
{name: "2", "group": 1, x: 200, y: 50, r: 15, connected : "1"},
{name: "3", "group": 2, x: 200, y: 130, r: 25, connected : "1"}
]
}
$( document ).ready(function() {
var width = 2000;
var height = 2000;
var svg = d3.select("#canvas").append("svg")
.attr("width", width)
.attr("height", height)
.append("g");
var lines = svg.attr("class", "line")
.selectAll("line").data(graph.nodes)
.enter().append("line")
.style("stroke", "gray") // <<<<< Add a color
.attr("x1", function (d, i) {
return d.x
})
.attr("y1", function (d) {
return d.y
})
.attr("x2", function (d) {
return findAttribute(d.connected).x
})
.attr("y2", function (d) {
return findAttribute(d.connected).y
})
var circles = svg.selectAll("circle")
.data(graph.nodes)
.enter().append("circle")
.style("stroke", "gray")
.style("fill", "white")
.attr("r", function (d, i) {
return d.r
})
.attr("cx", function (d, i) {
return d.x
})
.attr("cy", function (d, i) {
return d.y
});
var text = svg.selectAll("text")
.data(graph.nodes)
.enter()
.append("text");
var textLabels = text
.attr("x", function(d) { return d.x; })
.attr("y", function(d) { return d.y; })
.text( function (d) { return d.name })
.attr("font-family", "sans-serif")
.attr("font-size", "10px")
.attr("fill", "red");
});
function findAttribute(name) {
for (var i = 0, len = graph.nodes.length; i < len; i++) {
if (graph.nodes[i].name === name)
return graph.nodes[i]; // Return as soon as the object is found
}
return null; // The object was not found
}
</script>
</body>
</html>
If you want to grow the nodes to fit large labels, you can use the getBBox property of an SVG text node after you've drawn it. Here's how I did it, for a list of nodes with fixed coordinates, and two possible shapes:
nodes.forEach(function(v) {
var nd;
var cx = v.coord[0];
var cy = v.coord[1];
switch (v.shape) {
case "circle":
nd = svg.append("circle");
break;
case "rectangle":
nd = svg.append("rect");
break;
}
var w = 10;
var h = 10;
if (v.label != "") {
var lText = svg.append("text");
lText.attr("x", cx)
.attr("y", cy + 5)
.attr("class", "labelText")
.text(v.label);
var bbox = lText.node().getBBox();
w = Math.max(w,bbox.width);
h = Math.max(h,bbox.height);
}
var pad = 4;
switch (v.shape) {
case "circle":
nd.attr("cx", cx)
.attr("cy", cy)
.attr("r", Math.sqrt(w*w + h*h)/2 + pad);
break;
case "rectangle":
nd.attr("x", cx - w/2 - pad)
.attr("y", cy - h/2 - pad)
.attr("width", w + 2*pad)
.attr("height", h + 2*pad);
break;
}
});
Note that the shape is added, the text is added, then the shape is positioned, in order to get the text to show on top.

How to add text labels on edges [duplicate]

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

Text label hides behind the circle node in d3.js [duplicate]

I've been using the sample code from this d3 project to learn how to display d3 graphs and I can't seem to get text to show up in the middle of the circles (similar to this example and this example). I've looked at other examples and have tried adding
node.append("title").text("Node Name To Display")
and
node.append("text")
.attr("text-anchor", "middle")
.attr("dy", ".3em").text("Node Name To Display")
right after node is defined but the only results I see is "Node Name To Display" is showing up when I hover over each node. It's not showing up as text inside the circle. Do I have to write my own svg text object and determine the coordinates of that it needs to be placed at based on the coordinates of radius of the circle? From the other two examples, it would seem like d3 already takes cares of this somehow. I just don't know the right attribute to call/set.
There are lots of examples showing how to add labels to graph and tree visualizations, but I'd probably start with this one as the simplest:
http://bl.ocks.org/950642
You haven’t posted a link to your code, but I'm guessing that node refers to a selection of SVG circle elements. You can’t add text elements to circle elements because circle elements are not containers; adding a text element to a circle will be ignored.
Typically you use a G element to group a circle element (or an image element, as above) and a text element for each node. The resulting structure looks like this:
<g class="node" transform="translate(130,492)">
<circle r="4.5"/>
<text dx="12" dy=".35em">Gavroche</text>
</g>
Use a data-join to create the G elements for each node, and then use selection.append to add a circle and a text element for each. Something like this:
var node = svg.selectAll(".node")
.data(nodes)
.enter().append("g")
.attr("class", "node")
.call(force.drag);
node.append("circle")
.attr("r", 4.5);
node.append("text")
.attr("dx", 12)
.attr("dy", ".35em")
.text(function(d) { return d.name });
One downside of this approach is that you may want the labels to be drawn on top of the circles. Since SVG does not yet support z-index, elements are drawn in document order; so, the above approach causes a label to be drawn above its circle, but it may be drawn under other circles. You can fix this by using two data-joins and creating separate groups for circles and labels, like so:
<g class="nodes">
<circle transform="translate(130,492)" r="4.5"/>
<circle transform="translate(110,249)" r="4.5"/>
…
</g>
<g class="labels">
<text transform="translate(130,492)" dx="12" dy=".35em">Gavroche</text>
<text transform="translate(110,249)" dx="12" dy=".35em">Valjean</text>
…
</g>
And the corresponding JavaScript:
var circle = svg.append("g")
.attr("class", "nodes")
.selectAll("circle")
.data(nodes)
.enter().append("circle")
.attr("r", 4.5)
.call(force.drag);
var text = svg.append("g")
.attr("class", "labels")
.selectAll("text")
.data(nodes)
.enter().append("text")
.attr("dx", 12)
.attr("dy", ".35em")
.text(function(d) { return d.name });
This technique is used in the Mobile Patent Suits example (with an additional text element used to create a white shadow).
I found this guide very useful in trying to accomplish something similar :
https://www.dashingd3js.com/svg-text-element
Based on above link this code will generate circle labels :
<!DOCTYPE html>
<html>
<head>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script>
</head>
<body style="overflow: hidden;">
<div id="canvas" style="overflow: hidden;"></div>
<script type="text/javascript">
var graph = {
"nodes": [
{name: "1", "group": 1, x: 100, y: 90, r: 10 , connected : "2"},
{name: "2", "group": 1, x: 200, y: 50, r: 15, connected : "1"},
{name: "3", "group": 2, x: 200, y: 130, r: 25, connected : "1"}
]
}
$( document ).ready(function() {
var width = 2000;
var height = 2000;
var svg = d3.select("#canvas").append("svg")
.attr("width", width)
.attr("height", height)
.append("g");
var lines = svg.attr("class", "line")
.selectAll("line").data(graph.nodes)
.enter().append("line")
.style("stroke", "gray") // <<<<< Add a color
.attr("x1", function (d, i) {
return d.x
})
.attr("y1", function (d) {
return d.y
})
.attr("x2", function (d) {
return findAttribute(d.connected).x
})
.attr("y2", function (d) {
return findAttribute(d.connected).y
})
var circles = svg.selectAll("circle")
.data(graph.nodes)
.enter().append("circle")
.style("stroke", "gray")
.style("fill", "white")
.attr("r", function (d, i) {
return d.r
})
.attr("cx", function (d, i) {
return d.x
})
.attr("cy", function (d, i) {
return d.y
});
var text = svg.selectAll("text")
.data(graph.nodes)
.enter()
.append("text");
var textLabels = text
.attr("x", function(d) { return d.x; })
.attr("y", function(d) { return d.y; })
.text( function (d) { return d.name })
.attr("font-family", "sans-serif")
.attr("font-size", "10px")
.attr("fill", "red");
});
function findAttribute(name) {
for (var i = 0, len = graph.nodes.length; i < len; i++) {
if (graph.nodes[i].name === name)
return graph.nodes[i]; // Return as soon as the object is found
}
return null; // The object was not found
}
</script>
</body>
</html>
If you want to grow the nodes to fit large labels, you can use the getBBox property of an SVG text node after you've drawn it. Here's how I did it, for a list of nodes with fixed coordinates, and two possible shapes:
nodes.forEach(function(v) {
var nd;
var cx = v.coord[0];
var cy = v.coord[1];
switch (v.shape) {
case "circle":
nd = svg.append("circle");
break;
case "rectangle":
nd = svg.append("rect");
break;
}
var w = 10;
var h = 10;
if (v.label != "") {
var lText = svg.append("text");
lText.attr("x", cx)
.attr("y", cy + 5)
.attr("class", "labelText")
.text(v.label);
var bbox = lText.node().getBBox();
w = Math.max(w,bbox.width);
h = Math.max(h,bbox.height);
}
var pad = 4;
switch (v.shape) {
case "circle":
nd.attr("cx", cx)
.attr("cy", cy)
.attr("r", Math.sqrt(w*w + h*h)/2 + pad);
break;
case "rectangle":
nd.attr("x", cx - w/2 - pad)
.attr("y", cy - h/2 - pad)
.attr("width", w + 2*pad)
.attr("height", h + 2*pad);
break;
}
});
Note that the shape is added, the text is added, then the shape is positioned, in order to get the text to show on top.

d3 How to trigger event on the object behind text

I draw A rect and text on svg.
In order to show text, I render rect first.
I add mouse click event to rect
when I click the text, it seems the rect is not selected, because rect is behind text, so text is selected frist.
I need to select trigger the event when mouse click inside the rect.
How should I do?
Thanks!
Fiddle
You can see, when you mouse click on the text, the rect is not clicked.
var data = ["TEXT SAMPLE TEXT SAMPLE TEXT SAMPLE"];
var svg = d3.select("svg");
var bar = svg.selectAll("g")
.data(data)
.enter().append("g")
.attr("transform", function(d, i) { return "translate(100,100)"; });
bar.append("rect")
.attr("width", 200)
.attr("height", 50)
.style("fill", "#f00")
.on("click", function (d) {
alert("text");
});
bar.append("text")
.attr("x", 10)
.attr("y", 25)
.attr("dy", "-.35em")
.text(function(d) { return d; });
You can attach a style to the text to ignore mouse events. Here is the example:
Style:
.bar-text {
pointer-events: none;
}
Modified code:
bar.append("text")
.attr("x", 10)
.attr("y", 25)
.attr("class", "bar-text") // add class
.attr("dy", "-.35em")
.text(function(d) { return d; });
Attach the handler to the g element. Complete demo here.

Categories

Resources