D3, SVG and textPath: How to move the text down? - text

I've got a diagram with some text written along one of the paths using a textPath. However, my problem is: I need the text to be on the other side of the text path, ie, sitting below it.
Here's an example:
I need the text here to be within the solid blue area. Ie, so you can actually read it. The blue arc here being the textPath. In other words, I just want to move the text down about 20px.
Whats really confusing me, is that I can set an "x" property on the text and move it left and right , but I can't set a "y" property to move it up or down.
I can't figure it out. Can anyone help?
Here's my code
var labels = svg.selectAll("text.label")
.data(partition.nodes(data))
.enter()
.append("text")
.attr("class", "label")
.attr("x", 10)
.attr("stroke","black")
.style("background-color",'white')
labels.append("textPath")
.attr("xlink:href", function(d) { return '#' + d.name })
.text(function(d) { return d.name.capitalize() })

You can use the dy property to change the vertical alignment.

Created JS fiddle example showing labels over links in D3 Forced layout chart
See working demo in JS Fiddle: http://jsfiddle.net/bc4um7pc/
var linktext = svg.append("svg:g").selectAll("g.linklabelholder").data(force.links());
linktext.enter().append("g").attr("class", "linklabelholder")
.append("text")
.attr("class", "linklabel")
.style("font-size", "13px")
.attr("dx", "30")
.attr("dy", "-5")
.attr("text-anchor", "start")
.style("fill","#000")
.append("textPath")
.attr("xlink:href",function(d,i) { return "#linkId_" + i;})
.text(function(d) {
return d.type;
});

Related

Creating a dynamic 2nd chart from clicked data in 1st chart using D3.js (almost, but not quite)

Dual chart rendering from click event using absolute references.
Solution....not elegant, but create white background and use absolute positioning to overwrite:
//Click feature added to show bar chart data
.on("click", function(d) {
d3.select(this)
.transition()
.attr("fill", "red");
var svg2 = d3.select("#container2")
.append("svg:svg")
.attr("width", w2)
.attr("height", h)
.style("position", "absolute")
.style("background-color", "white");

Why labels on chart are not shown when drawn after axes

I'm making a simple line chart with in-chart labels using d3.js.
When I draw the axes before the labels, the latter do not get added to the svg element, but they do if I draw them first:
In the second chart, the labels are not just hidden. They are not being appended to the DOM at all.
Here's the bl.ock. The only difference between both scripts is that before.js writes labels before axes, while after.js does it afterwards.
Why does this happen?
The problem is this line when adding the labels:
svg.selectAll('text')
If you have drawn the axes at this point. There will be text elements and the selection above will not be empty. Calling .data() on it causes D3 to match data elements to DOM elements in the selection. In this case, everything is matched and therefore the .enter() selection is empty and no new labels are added.
It works if you run this code first because there are no text elements, the selection is empty and no data is matched. To prevent this, you can for example identify the label text elements explicitly with a class:
svg.selectAll('text.label')
.data(dataset)
.enter()
.append('text')
.attr("class", "label")
.text(Math.floor)
.attr('class', 'data-label')
.attr('x', function (d, i) { return x(i); })
.attr('y', function (d, i) { return y(d + 1); });
With this code, it doesn't matter whether you run it before or after adding the axes.

Appending multiple svg text with d3

I have been stuck on this problem for days.
So I have a dataset of objects of the following form:
dataset = [{metric:"revenue",value:0.03},{metric:"sales", value:0.15},{metric:"churn", value: 0.06},{metric:"logins", value: 0.45}]
The following code would display the 4 metric names in a grid pattern (meshy, meshx are the coordinates points of the grid and meshsize is the size of the grid, so this is just putting the text in the middle of a grid square):
svg.selectAll("text")
.data(dataset)
.enter()
.append("text")
.text(function(d){
return d.metric;
})
.attr("y",function(d,i){
return meshy[i] + meshsize/2;
})
.attr("x", function(d,i){
return meshx[i] + meshsize/2;
})
.attr("font-size",25)
.attr("font-family","serif")
.attr("text-anchor","middle")
.attr("font-weight","bold");
Now I would like to put the value of the metric right underneath the metric name like so:
svg.append("text")
.data(dataset)
.text(function(d){
return (d.value);
})
.attr("y",function(d,i){
return meshy[i] + meshsize/2 + 20;
})
.attr("x", function(d,i){
return meshx[i] + meshsize/2 ;
})
.attr("font-size",25)
.attr("font-family","serif")
.attr("text-anchor","middle")
.attr("font-weight","bold");
But this only returns the value underneath the metric name for the FIRST metric, the other 3 value texts are not even in the DOM. I have tried multiple approaches including replacing .text with .html as described here:https://github.com/mbostock/d3/wiki/Selections#wiki-html with no success. I have also tried appending paragraph elements instead - this works but the p elements are positioned below the svg body in a list with no obvious way to move them into the right position. The code above is the closest I have come to getting what I need, but for some reason only the first value text shows up. However, I am open to any approach in d3 that gets the job done: 4 metric names with the values right underneath them
In your second block of code, you are only appending one text element, hence only one of them is appearing. What you need to do is to append the text similar to your first block, i.e. with the .enter() selection. For this, you have two choices. You can either save and reuse the .enter() selection, or assign different classes to the two kinds of text to be able to distinguish between them.
Option 1:
var texts = svg.selectAll("text")
.data(dataset)
.enter();
texts.append("text")
.text(function(d){
return d.metric;
})
// set position etc.
texts.append("text")
.text(function(d){
return d.value;
})
// set position etc.
Option 2:
svg.selectAll("text.title")
.data(dataset)
.enter()
.append("text")
.attr("class", "title")
.text(function(d){
return d.metric;
})
// set position etc.
svg.selectAll("text.value")
.data(dataset)
.enter()
.append("text")
.attr("class", "value")
.text(function(d){
return d.value;
})
// set position etc.
The first option is obviously shorter, but depending on what else you want to do, the second option may be preferable -- if you want to modify the text afterwards, it will be a lot easier if you can distinguish between the two kinds of text. You can also use the different classes to give different CSS styles.

d3.js selectAll().each on svg path.. undefined?

I'm importing a svg (served as static content from the server) in this way
d3.xml("http://localhost:3000/mysvg.svg", "image/svg+xml", function(xml) {
var importedNode = document.importNode(xml.documentElement, true);
var mySvg = d3.select("#somediv").node().appendChild(importedNode);
then I'm trying to iterate through all svg paths and do something with them
d3.selectAll("#somediv svg path").each(function(d, i) {
console.log(this, d, i);
});
}
what I'm getting is this problem
i is from 1 to number of path, which is correct.
d is undefined instead of being the right svg path element.
this is the svg path element, like this one
<path id="m021" fill="#00AAFF" d="M225.438,312.609c-0.665-1.084-1.062-1.691-2.368-1.963c-0.582-0.121-1.686-0.271-2.265-0.069 c-0.507,0.174-0.637,0.649-1.431,0.368c-0.934-0.33-0.665-1.272-0.71-2.104c-0.597-0.021-1.18,0-1.733,0.262 ...etc" ></path>
I expected d to be the real svg path, why is it not?
EDIT:
A little insight on what I want to do could maybe help.
I have a svg with one path for each district of my town. I want to make some piecharts in the center of each path. I don't have the data now, it will be used for the piecharts. I want to make a mouseover function on the path, and add a little red circle (that in a future step will become the pie chart) on each path.
What is the best way to do this?
Simplifying your original request, let's suppose you want to add a circle in the center of each district. Let's assume that the districts are relatively square. Note that this would be much more simpler if you have geographical data instead of paths.
var svg = d3.select("#somediv svg");
var districts = svg.selectAll("path");
var district_centers = districts[0].map(function(d, i) {
var bbox = this.getBBox();
return [bbox.left + bbox.width/2, bbox.top + bbox.height/2];
});
svg
.selectAll("circle")
.data(district_centers)
.enter()
.append("circle")
.attr("class", "district_circle")
.attr("cx", function(d){ return d[0]})
.attr("cy", function(d){ return d[1]})
.attr("r", 10)
.attr("fill", "red");
According to the API doc for selection.each, d should be the datum, which you will not have if you have not previously called .data() to bind data to the nodes. All you have is pure SVG with no data bound to it.
I notice that your paths do have IDs, so if you have a dataset matching those ID's you can probably bind to it using the keys parameter of the .data function

Nesting data using D3.js (heatmap)

So I am new to working with Javascript (especially the D3 library) and I am trying to do something not unlike the following example: http://mbostock.github.com/d3/talk/20111116/iris-splom.html. In my case though each cell is the same thing a 4 x 4 grid with exactly the same scale.
So in my case the top level element is a plate. Each plate has rows and columns at the intersection of a row and column a value (a heatmap if you will). I able to create the proper plate elements; however, the data for ALL plates is present under each element rather than properly nested. I tried to attach an image so you can see that each "plate" is the same, if you look at the underlying document structure it is the same and essentially each rectangle is two overlaid data points.
In looking more closely at Mike's example (link above), it looks like he uses a cross function to help out with the nesting of data, I am wondering if that is where my code falls down. Thank you for any help you all can provide.
I have posted my code below
d3.csv("plateData.csv", function(data) {
var m = 20,
w = 400,
h = 300,
x_extent = d3.extent(data, function(d){return d.Rows}),
y_extent = d3.extent(data, function(d){return d.Columns}),
z_extent = d3.extent(data, function(d){return d.Values});
var x_scale = d3.scale.linear()
.range([m, w-m])
.domain(x_extent)
var y_scale = d3.scale.linear()
.range([h-m,m])
.domain(y_extent)
var ramp=d3.scale.linear()
.domain(z_extent)
.range(["blue","red"]);
// Nest data by Plates
var plates = d3.nest()
.key(function(d) { return d.Plate; })
.entries(data);
// Insert an svg element (with margin) for each plate in our dataset.
var svg = d3.select("body").selectAll("svg")
.data(plates)
.enter().append("svg:svg")
.attr("width", w)
.attr("height", h)
//add grouping and rect
.append("svg:g")
.selectAll('rect')
.data(data)
.enter()
.append('svg:rect')
.attr('x', function(d){return x_scale(d.Rows)})
.attr('y', function(d){return y_scale(d.Columns)})
.attr('width', 10)
.attr('height', 10)
.style('fill', function(d){return ramp(d.Values)});
}
);
AND example Data:
Plate,Rows,Columns,Values
12345,1,1,1158.755
12345,1,2,1097.768
12345,1,3,1097.768
12345,1,4,914.807
12345,2,1,1189.249
12345,2,2,1128.261
12345,2,3,1433.197
12345,2,4,701.352
12345,3,1,914.807
12345,3,2,1433.197
12345,3,3,1189.249
12345,3,4,1402.703
12345,4,1,1158.755
12345,4,2,1067.274
12345,4,3,701.352
12345,4,4,1372.21
56987,1,1,20.755
56987,1,2,97.768
56987,1,3,97.768
56987,1,4,14.807
56987,2,1,89.249
56987,2,2,28.261
56987,2,3,33.197
56987,2,4,15.352
56987,3,1,2000.807
56987,3,2,14.197
56987,3,3,89.249
56987,3,4,402.703
56987,4,1,158.755
56987,4,2,3067.274
56987,4,3,701.352
56987,4,4,182.21
You problem has two sources:
You are inserting one svg element for each nested group, but you don't have defined a position for each element (all the elements are in the body).
You are selecting all the svg elements in the body, and appending one rectangle for each data element, without considering the nested structure.
One solution is to create one svg element inside the body, one group for each plate, define a position for each plate, translate the group to its position (in the svg element) and then create the rectangles for the heatmap.

Resources