How do I best update a body text element from a SVG tree node mouseover event? When I try the following the text is updated, but the SVG is removed from the display. Here is a the code:
var svg = d3.select('body')
.append('text').text('The Entry Point and M Code: ')
.attr('class', 'centralText')
.attr('x', 10)
.attr('y', 10)
.attr('text-anchor', 'middle')
.append('svg')
here is my event code:
var nodeEnter = node.enter().append('g')
.attr('class', node_class)
.attr('transform', function(d) {
return 'translate(' + source.x0 + ',' + source.y0 + ')'; })
.style('cursor', function(d) {
return (d.children || d._children) ? 'pointer' : '';})
.on('click', click)
.on("mouseover", function(d) {
d3.select('body')
.text('M Code: is this')
There are two separate issues that are coming up. First, your initialization of svg is appending it to a text element, which is a bad idea. Second, to update the text you're replacing the body text and not the text element.
Two changes need to be made. First, change your mouseover function to this:
d3.select('body').select('text')
.text('M Code: is this');
This would normally fix it, but the second problem is that the svg is appended to the text element so it will still be erased. To fix this, change your declaration to the following:
d3.select('body')
.append('text').text('The Entry Point and M Code: ')
.attr('class', 'centralText')
.attr('x', 10)
.attr('y', 10)
.attr('text-anchor', 'middle');
var svg = d3.select('body').append('svg');
Related
I have the following enter section which is transitioned later to set the opacity to 1. The 'click' on the circle works. The 'click' on the text does not.
If I substitute the 'text' for a 'rect' and set the appropriate attributes on the rect then both clicks function correctly.
When I inspect the dom tree, the listeners are correctly visible on both the circle and the text elements.
Could somebody possibly point out any obvious mistakes or tell me why using 'text' won't work with a listener?
var nodeEnter = node.enter().append('g')
.attr('class', 'node')
.attr('transform', function () {
return 'translate(' + source.y0 + ',' + source.x0 + ')';
})
.style('opacity', 1e-6);
nodeEnter.append('circle')
.attr('r', 1e-6)
.style('fill', function (d: any) {
return d._children ? 'lightsteelblue' : '#fff';
})
.on('click', this.circle_click);
nodeEnter.append('text')
.attr('dx', 3.5)
.attr('dy', 5.5)
.text(function (d: any) { return d.data.name; })
.style('fill-opacity', 1e-6)
.on('click', this.text_click);
Discovered that for some reason 'pointer-events "none"' was always set on the element - when I specifically set the style to "visible" during the update transition then it worked. Would be interesting to know why this happened - the style certainly wasn't set in either my code or css files.
I am trying to nest an individual rect inside each svg element created in the g element, but I can't seem to get it to work.
Here is my code + Plnk;
Plunker
var bar = g.selectAll(".barContainer")
.data(thisData.labels)
.enter()
bar
.append("svg")
.attr("class", "barContainer")
bar
.insert('rect')
.attr('width', function(d) {
console.log(d)
return d.value
})
.attr('height', 20)
.attr('x', 0)
.attr('y', 0)
Currently the DOM is displaying the rect and container class on the same level, where as I would like to nest the rect inside each container.
I've tried a few things but can't seem to crack it, I was hoping someone could point me in the right direction?
Thanks
You have a 'g' element which you append the svg to and then you also append the rect to the 'g'. You want to append the rect to the svg element you create. Something like this :
var bar = g.selectAll(".barContainer")
.data(thisData.labels)
.enter()
var barRectSVG = bar
.append("svg")
.attr("class", "barContainer")
barRectSVG
.insert('rect')
.attr('width', function(d) {
console.log(d)
return d.value
})
.attr('height', 20)
.attr('x', 0)
.attr('y', 0)
Updated plnkr : http://plnkr.co/edit/WYbjT7ekjUuopzs0H9Pi?p=preview
I am trying to make the <text> and <path> elements in a donut chart I have in d3 clickable, but am running into some issues. Here is the code I am using:
var g = svg.append('g')
.attr('transform', 'translate(' + width / 2 + ',' + ((height / 2)) + ')');
var arcs = g.selectAll('path');
arcs = arcs.data(pie(formattedData));
arcs.exit().remove();
arcs.enter().append('path')
.style('stroke', 'white')
.attr('fill', function (d, i) { return color(i) });
arcs.attr('d', arc);
arcs.append("text")
.attr("transform", function (d) { return "translate(" + labelArc.centroid(d) + ")"; })
.attr("dy", "0em")
.attr("style", "font-size: 1.5em;")
.attr("fill", "white")
.text(function (d) { return d.value; })
I can add the <a> tag around the <path> and <text> elements by editing the DOM directly in dev tools, but I can't do a .wrap() or change the code to use this either:
.append("a").attr("href", "http://wwww.gooogle.com")
What am I missing?
You should be able to do this just by appending the a element before the path or text element:
arcs.enter()
.append('a')
.attr('href', 'http://www.google.com')
.append('path')
.style('stroke', 'white')
.attr('fill', function (d, i) { return color(i) })
.attr('d', arc);
See https://jsfiddle.net/m4mj8u7L/1/
It looks like you're trying to edit SVG DOM elements with jQuery. If so, I ran into this problem myself not long ago. SVG elements aren't treated the same as normal HTML elements, and thus many of the jQuery functions won't affect them. There's some alternate syntax that you can use to work around the limitations though. Look at this answer for some direction.
I have a set of graphs that can be dynamically added and removed from the page. Each one has an invisible 'rect' element appended to the base svg hosting each graph, and on that rect element I can append mouseover elements. However, these are all limited to the single svg/rect that the mouse is hovering over; I'd like to extend them to cover all visible graphs. Here's the main code affecting that:
var focus = svg.append('g') // An invisible layer over the top. Problem is, it only overlays on one graph at a time...
.style('display', 'none');
svg.append("rect")
.attr("width", width)
.attr("height", height)
.style("fill", "none")
.style("pointer-events", "all")
.on("mouseover", function() { focus.style("display", null); })
.on("mouseout", function() { focus.style("display", "none"); })
.on("mousemove", mousemove);
// append the x line
focus.append("line")
.attr("class", "x")
.style("stroke", "blue")
.style("stroke-dasharray", "3,3")
.style("opacity", 0.5)
.attr("y1", 0)
.attr("y2", height);
function mousemove() {
var x0 = x.invert(d3.mouse(this)[0]),
i = bisectDate(dataset, x0, 1),
d0 = dataset[i - 1],
d1 = dataset[i],
d = x0 - d0.time > d1.time - x0 ? d1 : d0;
focus.select(".x")
.attr("transform", function() {
return "translate(" + x(d.time) + "," + rightDomain(symbol, d) + ")";
})
.attr("y2", height - y(d[symbol]));
}
All of this is inside a forEach() loop, where it loops over an array containing the names of the graphs to be shown, so multiple graphs (albeit in their separate svgs) show up.
I also have a plunker here: http://plnkr.co/edit/s4K84f5HGRjHFWMwiuIA?p=preview. I'm not sure why it's failing to work since I've copied and pasted my code, which I know works elsewhere.
Edit: I've managed to attach another svg element to the body but for some reason I can't get it to overlay on top of the existing svgs (the graphs). Here's my code (where I've tried several ways of adjusting the position):
var overlay = d3.select('html')
.append('div')
.attr('height', function() { return (symbols.length - 1) * 135 + 130; })
.attr('width', 1000)
.attr('z-index', 2)
//.attr('transform', 'translate(' + margin.left + ',' + extraBuffer/2 + ')');
//.attr('x', margin.left)
//.attr('y', extraBuffer/2);
.attr('position', 'absolute')
.attr('top', '20')
.attr('right', '40');
Looking at this in chrome devtools I always see it below existing graphs, even if I explicitly set its x/y values.
I am starting with d3.js, and am trying to create a row of nodes each of which contains a centered number label.
I am able to produce the desired result visually, but the way I did it is hardly optimal as it involves hard-coding the x-y coordinates for each text element. Below is the code:
var svg_w = 800;
var svg_h = 400;
var svg = d3.select("body")
.append("svg")
.attr("width", svg_w)
.attr("weight", svg_h);
var dataset = [];
for (var i = 0; i < 6; i++) {
var datum = 10 + Math.round(Math.random() * 20);
dataset.push(datum);
}
var nodes = svg.append("g")
.attr("class", "nodes")
.selectAll("circle")
.data(dataset)
.enter()
.append("circle")
.attr("class", "node")
.attr("cx", function(d, i) {
return (i * 70) + 50;
})
.attr("cy", svg_h / 2)
.attr("r", 20);
var labels = svg.append("g")
.attr("class", "labels")
.selectAll("text")
.data(dataset)
.enter()
.append("text")
.attr("dx", function(d, i) {
return (i * 70) + 42
})
.attr("dy", svg_h / 2 + 5)
.text(function(d) {
return d;
});
The node class is custom CSS class I've defined separately for the circle elements, whereas classes nodes and labels are not explicitly defined and they are borrowed from this answer.
As seen, the positioning of each text label is hard-coded so that it appears at the center of the each node. Obviously, this is not the right solution.
My question is that how should I correctly associate each text label with each node circle dynamically so that if the positioning of a label changes along with that of a circle automatically. Conceptual explanation is extremely welcome with code example.
The text-anchor attribute works as expected on an svg element created by D3. However, you need to append the text and the circle into a common g element to ensure that the text and the circle are centered with one another.
To do this, you can change your nodes variable to:
var nodes = svg.append("g")
.attr("class", "nodes")
.selectAll("circle")
.data(dataset)
.enter()
// Add one g element for each data node here.
.append("g")
// Position the g element like the circle element used to be.
.attr("transform", function(d, i) {
// Set d.x and d.y here so that other elements can use it. d is
// expected to be an object here.
d.x = i * 70 + 50,
d.y = svg_h / 2;
return "translate(" + d.x + "," + d.y + ")";
});
Note that the dataset is now a list of objects so that d.y and d.x can be used instead of just a list of strings.
Then, replace your circle and text append code with the following:
// Add a circle element to the previously added g element.
nodes.append("circle")
.attr("class", "node")
.attr("r", 20);
// Add a text element to the previously added g element.
nodes.append("text")
.attr("text-anchor", "middle")
.text(function(d) {
return d.name;
});
Now, instead of changing the position of the circle you change the position of the g element which moves both the circle and the text.
Here is a JSFiddle showing centered text on circles.
If you want to have your text be in a separate g element so that it always appears on top, then use the d.x and d.y values set in the first g element's creation to transform the text.
var text = svg.append("svg:g").selectAll("g")
.data(force.nodes())
.enter().append("svg:g");
text.append("svg:text")
.attr("text-anchor", "middle")
.text(function(d) { return d.name; });
text.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
});
The best answer came from the asker himself:
just a further observation: with only .attr("text-anchor", "middle")
for each text element, the label is at the middle horizontally but
slightly off vertically. I fixed this by adding attr("y", ".3em")
(borrowed from examples at d3.js website), which seems to work well
even for arbitrary size of node circle. However, what exactly this
additional attribute does eludes my understanding. Sure, it does
something to the y-coordinate of each text element, but why .3em in
particular? It seems almost magical to me...
Just add .attr("text-anchor", "middle") to each text element.
Example:
node.append("text")
.attr("x", 0)
.attr("dy", ".35em")
.attr("text-anchor", "middle")
.text(function(d) { return d.name; });
This page describes what's going on under the svg hood when it comes to text elements. Understanding the underlying machinery and data structures helped me get a better handle on how I had to modify my code to get it working.