I'm newbie in D3 and I'm trying to set a symbol on the left of the text of a legend. The legend is on the right of the graphic and all the texts of the legend are correctly located but I cannot be able to located on their left the symbol which corresponds with the legend.
The function which locate the legend and try to do the same with the symbols are:
setLegend(canvas, symbols, width, offset_right, height) {
canvas
.selectAll("legends")
.data(symbols)
.enter()
.append("text")
.attr("transform", function(d, i) {
let x = width - offset_right + 50;
let y = height / 2 - 100 + i * 24;
return "translate( " + x + "," + y + ")";
})
.attr(
"d",
d3
.symbol()
.type(function(d) {
return d.symbol;
})
.size("75")
)
.style("text-anchor", "left")
.text(d => {
return d.stats;
})
.attr("fill", "#FFFFFF")
.style("font-size", "10pt")
.style("font-weight", "bold"); }
You can check in this screen cap how the legend is correctly located but there are no any symbol on its left.
You can check all the code of the development in codesanbox:
What am I doing wrong?
Right now you're setting an attribute called d to text elements, which has no effect on those texts (only paths have the d attribute). On top of that, you're not appending any path.
A simple and common fix is appending groups in the enter selection, to which you append the paths and texts. Here is an example (I'm setting the x and y positions of the texts so they don't start right over the symbols):
setLegend(canvas, symbols, width, offset_right, height) {
const groups = canvas
.selectAll("legends")
.data(symbols)
.enter()
.append("g")
.attr("transform", function(d, i) {
let x = width - offset_right + 50;
let y = height / 2 - 100 + i * 24;
return "translate( " + x + "," + y + ")";
});
groups.append("path")
.attr("d", d3.symbol().type(function(d) {
return d.symbol;
}).size("75"))
.attr("fill", function(d) {
return d.color;
});
groups.append("text")
.attr("x", 10)
.attr("y", 5)
.style("text-anchor", "left")
.text(d => {
return d.stats;
})
.attr("fill", function(d) {
return d.color;
})
.style("font-size", "10pt")
.style("font-weight", "bold");
}
I have a working, zoomable D3 bubble chart.
See fiddle here.
var theData = {
children:[{"source":3,"value":2367257,"formattedValue":"€2,367,257","name":"Legacies","tooltip":"Legacies: €2,367,257","colour":"#3182bd","$$hashKey":"object:106"},{"source":4,"value":1199595,"formattedValue":"€1,199,595","name":"Donations including donations in kind","tooltip":"Donations including donations in kind: €1,199,595","colour":"#6baed6","$$hashKey":"object:101"},{"source":2,"value":1154618,"formattedValue":"€1,154,618","name":"Tax relief income","tooltip":"Tax relief income: €1,154,618","colour":"#9ecae1","$$hashKey":"object:110"},{"source":2,"value":81447065,"formattedValue":"€81,447,065","name":"Grants and service fees from government sources","tooltip":"Grants and service fees from government sources: €81,447,065","colour":"#c6dbef","$$hashKey":"object:104"},{"source":3,"value":151798455,"formattedValue":"€151,798,455","name":"Non-government grants and donations","tooltip":"Non-government grants and donations: €151,798,455","colour":"#e6550d","$$hashKey":"object:108"},{"source":4,"value":15039907,"formattedValue":"€15,039,907","name":"Memberships and subscriptions","tooltip":"Memberships and subscriptions: €15,039,907","colour":"#fd8d3c","$$hashKey":"object:107"},{"source":2,"value":278004,"formattedValue":"€278,004","name":"Church collection","tooltip":"Church collection: €278,004","colour":"#fdae6b","$$hashKey":"object:100"},{"source":4,"value":113941393,"formattedValue":"€113,941,393","name":"Unspecified voluntary income","tooltip":"Unspecified voluntary income: €113,941,393","colour":"#fdd0a2","$$hashKey":"object:114"},{"source":1,"value":22890793,"formattedValue":"€22,890,793","name":"Fundraising events and activities","tooltip":"Fundraising events and activities: €22,890,793","colour":"#31a354","$$hashKey":"object:103"},{"source":1,"value":10713266,"formattedValue":"€10,713,266","name":"Charity shop income","tooltip":"Charity shop income: €10,713,266","colour":"#74c476","$$hashKey":"object:99"},{"source":2,"value":3800759,"formattedValue":"€3,800,759","name":"Unspecified activities for generating funds","tooltip":"Unspecified activities for generating funds: €3,800,759","colour":"#a1d99b","$$hashKey":"object:112"},{"source":2,"value":26174523,"formattedValue":"€26,174,523","name":"Investment income (including deposit interest)","tooltip":"Investment income (including deposit interest): €26,174,523","colour":"#c7e9c0","$$hashKey":"object:105"},{"source":3,"value":1605097,"formattedValue":"€1,605,097","name":"Unspecified incoming resources from generated funds","tooltip":"Unspecified incoming resources from generated funds: €1,605,097","colour":"#756bb1","$$hashKey":"object:113"},{"source":1,"value":150535745,"formattedValue":"€150,535,745","name":"Fees and income from trading activities","tooltip":"Fees and income from trading activities: €150,535,745","colour":"#9e9ac8","$$hashKey":"object:102"},{"source":1,"value":14580809,"formattedValue":"€14,580,809","name":"Other activities","tooltip":"Other activities: €14,580,809","colour":"#bcbddc","$$hashKey":"object:109"},{"source":4,"value":147269606,"formattedValue":"€147,269,606","name":"Uncategorized and other income","tooltip":"Uncategorized and other income: €147,269,606","colour":"#dadaeb","$$hashKey":"object:111"}]
};
function randomComparator (a, b) {
return Math.floor(Math.random() * 10) + 1
}
function clipText (d, t) {
if (d.r < 40) {
return "";
}
var name = t.substring(0, d.r / 5);
if (name.length < t.length) {
name = name.substring (0, name.length - Math.min(2, name.length)) + "...";
}
return name;
}
var diameter = 577,
width = 577,
height = diameter,
format = d3.format(",d");
var bubble = d3.layout.pack()
.sort(randomComparator)
.size([width, height])
.padding(3);
var svg = d3.select("#chart").append("svg")
.attr("width", width)
.attr("height", diameter)
.attr("class", "bubble");
var container = svg.append("g");
var node = container.selectAll(".node")
.data(bubble.nodes(theData)
.filter(function(d) { return !d.children; }))
.enter().append("g")
.attr("class", "node")
.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
});
node.append("title")
.text(function(d) {
return d.name + ": €" + format(d.value);
});
node.append("circle")
.attr("r", function(d) { return d.r; })
.style("fill", function(d) {
return d.colour;
//return color(d.source);
})
.style("pointer-events", "all");
var text = node.append("text")
.attr("dy", ".3em")
.style("text-anchor", "middle")
.style("fill", "#fff");
text.append("tspan")
.attr("x", "0")
.attr("dy", "0")
.style("font-weight", "600")
.text(function(d) {
return clipText(d, d.formattedValue);
});
text.append("tspan")
.attr("x", "0")
.attr("dy", "1.2em")
.text(function(d) {
return clipText(d, d.name);
});
// Setup zooming
function zoomed() {
container.attr("transform", "translate(" + d3.event.translate + ")scale(" + d3.event.scale + ")");
}
var zoom = d3.behavior.zoom()
.scaleExtent([-10, 50])
.on("zoom", zoomed);
zoom(svg);
However, not all the bubbles have descriptive text in them, as the text wont fit inside the radius of the bubble; My "algorithm" is pretty blunt; I either don't return any text if the radius is too small or I truncate it.
How do I scale the text so that it shows up when I zoom in?
TIA,
Jeff
Managed to solve it I think. Basically what you wanted to do was when zooming show more and more of the name yes ?
So what I did was when zooming, get the scale and change the font size and the amount of letters that get outputted via your 'cliptext' function depending on the scale value. I also used a general fontsize so the sizing stays consistent.
Updated fiddle : https://jsfiddle.net/24y0qL5e/7/
I changed the cliptext function to get a scale value :
function clipText (d, t, scale) {
if (d.r < fontsize/scale) {
return "";
}
console.log(scale)
var name = t.substring(0, d.r/scale);
if (name.length < t.length) {
name = name.substring (0, name.length - Math.min(2, name.length)) + "...";
}
return name;
}
I added a class for the text that you wish to change just so it's easily selected in future :
text.append("tspan").attr('class', "nodeTextToClip") //added class
.attr("x", "0")
.attr("dy", "1.2em").style("font-size", fontsize)
.text(function(d) {
return clipText(d, d.name,8);
And then changed the size of the text and amount of letters outputted by the cliptext function like so:
d3.selectAll('.node text .nodeTextToClip') //select text that you want to change
.style('font-size', fontsize/scale).text(function(d){return clipText(d, d.name,fontsize/scale/3 );})
This is just a quick try out, obviously there is some simple changes that need to be made, but I think this should help you get an idea of what's needed :)
I am facing a problem trying to position text inside the wedges of a Sunburst chart which is based on d3.js.The text elements seem to be not positioned as desired even on zooming..
Here is the brief snippet of the code that i tried, but unsuccessfully :
var slices = svg.selectAll(".form")
.data(function(d) { return data_slices; })
.enter()
.append("g");
slices.append("path")
.attr("d", arc)
.attr("id",function(d,i){return d[2]+""+i;})
.style("fill", function(d) { return color(d[2]);})
.on("click",animate)
.attr("class","form")
.append("svg:title")
.text(function(d) { return Math.round(d[0]*100)/100 +" , "+ Math.round(d[1]*100)/100; });
//Something needs to change below....
slices.append("text")
.style("font-size", "10px")
.attr("x", function(d) { return y(d[1]); })
.attr("transform", function(d) { return "rotate(" + this.parentNode.getBBox().width + ")"; })
.attr("dx", "6") // margin
.attr("dy", ".35em") // vertical-align
.text(function(d){return d[2]})
.attr("pointer-events","none");
Here is the Fiddle of the chart Fiddle
What can be possible problem ? and can anyone please tell me or guide me as to how to position the <text> inside svg <path>.Looks like the solution is a minor tweak to this, but i am not able to get to it even after trying for a long time..
Any help/comment in the direction of a solution would be greatly appreciated...Thanks in Advance..
I think this comes close to what you aimed to achieve: http://jsfiddle.net/4PS53/3/
The changes needed are the following:
function getAngle(d) {
// Offset the angle by 90 deg since the '0' degree axis for arc is Y axis, while
// for text it is the X axis.
var thetaDeg = (180 / Math.PI * (arc.startAngle()(d) + arc.endAngle()(d)) / 2 - 90);
// If we are rotating the text by more than 90 deg, then "flip" it.
// This is why "text-anchor", "middle" is important, otherwise, this "flip" would
// a little harder.
return (thetaDeg > 90) ? thetaDeg - 180 : thetaDeg;
}
slices.append("text")
.style("font-size", "10px")
.attr("x", function(d) { return d[1]; })
// Rotate around the center of the text, not the bottom left corner
.attr("text-anchor", "middle")
// First translate to the desired point and set the rotation
// Not sure what the intent of using this.parentNode.getBBox().width was here (?)
.attr("transform", function(d) { return "translate(" + arc.centroid(d) + ")" + "rotate(" + getAngle(d) + ")"; })
.attr("dx", "6") // margin
.attr("dy", ".35em") // vertical-align
.text(function(d){return d[2]})
.attr("pointer-events","none");
Now to make it work with zooming, where the reference point changes, we need a bit more infrastructure.
Make the g.form-container and not only the path.form visible/invisible. This means that we do not have to worry about making the labels disappear separately. (I have added the form-container class.)
Calculate the new point and calculate the centroid and rotation for it. This is a bit more tricky, but not too difficult:
function change_ref(data_point, reference_point) {
return [
get_start_angle(data_point, reference_point),
get_stop_angle (data_point, reference_point),
data_point[2],
get_level (data_point, reference_point)
];
}
// And while doing transitioning the `text.label`:
svg.selectAll('.label')
.filter(
function (b)
{
return b[0] >= new_ref[0] && b[1] <= new_ref[1] && b[3] >= new_ref[3];
}
).transition().duration(1000)
.attr("transform", function(b) {
var b_prime = change_ref(b, d);
return "translate(" + arc.centroid(b_prime) + ")" +
"rotate(" + getAngle(b_prime) + ")";
})
I have added the class label to the text.
Updated Demo: http://jsfiddle.net/4PS53/6/
However, I have argued that there might be better ways of presenting this data, esp. if you are allowing zooming and panning: D3 put arc labels in a Pie Chart if there is enough space
I've made a little improve in musically_ut code.
Now you can change from one data to another.
$('#change').click(function () {
if (animating) {
return;
}
if (currentSet == 0) {
currentSet = 1;
svg.selectAll(".form").filter(
function (d) {
return d[0] >= ref[0] && d[1] <= ref[1] && d[level_index] >= ref[level_index];
}
)
.transition().duration(1000)
.attrTween("d", changeDatarebaseTween(0, 1, 2, 3));
svg.selectAll('.label').filter(
function (d) {
return d[0] >= ref[0] && d[1] <= ref[1] && d[level_index] >= ref[level_index];
}
)
.transition().duration(1000)
.attr("transform", function (b) {
var b_prime = change_ref_CD(b);
return "translate(" + arc.centroid(b_prime) + ")" +
"rotate(" + getAngle(b_prime) + ")";
})
}
else {
currentSet = 0;
svg.selectAll(".form").filter(
function (d) {
return d[2] >= ref[2] && d[3] <= ref[3] && d[level_index] >= ref[level_index];
}
)
.transition().duration(1000).attrTween("d", changeDatarebaseTween(2, 3, 0, 1));
svg.selectAll('.label').filter(
function (d) {
return d[2] >= ref[2] && d[3] <= ref[3] && d[level_index] >= ref[level_index];
}
)
.transition().duration(1000)
.attr("transform", function (b) {
var b_prime = change_ref_CD(b);
return "translate(" + arc.centroid(b_prime) + ")" +
"rotate(" + getAngle(b_prime) + ")";
})
}
setTimeout(function () {
animating = false;
}, 1000);
});
EDIT: http://jsfiddle.net/k1031ogo/3/
(code could be cleaner, too much copy/paste)
Hi I was wondering anyone knew a suitable way of aligning text on the right or left of a circle dependent on the data it holds.
At the moment I have this & it works:
//creating the circles & text together
var node = svg.selectAll("a.node")
.data(json)
.enter().append("a")
.attr("class", "node")
;
//returning the shape & colors variable & linking it with the relevance in DB
node.append('path')
.attr("d", d3.svg.symbol().type(circle))
.attr("transform", "translate(0,0)")
;
//returning the names from the db
node.append("text")
.text(function(d) { return d.Name; })
.style("font-size", "8px")
.style("font", "Arial")
.attr('x', function (d) {
if (d.Field === "Canda"&& d.Name.length > 40) {return -180 }
else if (d.Field === "Canda") {return 9}
else {return 2}
;})
.attr('y', 2);
But, due to my json text being different lengths - when I say '-140' the texts that are shorter aren't even close to the circle. Therefore, is there a dynamic way to have the text all the same distance from the circle no matter if the length varies?
EDIT: just added .length .. but that would still mean that I would need to do more if statements as the text would be varied lengths.
http://jsfiddle.net/xwZjN/66/
A solution involving text-anchor:
force.on("tick", function() {
// edit to include text-anchor
text.attr("text-anchor", function(d) {
if( d.Country ==="USA" ) { return "end" }
else { return "start" }
} )
// edit to change x starting point
text.attr("x", function(d) {
if (d.Country ==="USA") {
return d.x - 10;
}
else {
return d.x + 6;
}
})
.attr("y", function(d) { return d.y + 4; });
node.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; });
});
I've used arc.Centroid to try to plot my circles on the arcs with labels. However, the labels do not stay with it?
force.on("tick", function() {
text.attr("x", function(d) { return d.x + 6; })
.attr("y", function(d) { return d.y + 4; });
node.attr("transform", function(d,i) {
return "translate(" + arc[i].centroid(d) + ")"; })
});
I have attempted to put centroid & arc[i] instead of the x & y. How can I put my circles with text? http://jsfiddle.net/xwZjN/20/
Also say if I were to have more json data, would I be able to restrict the plots only going into each section e.g. each section being a category?
Any help would be great. I think the solution may be similar to this - http://jsfiddle.net/nrabinowitz/GQDUS/
It seems that the force layout is not the right choice for your application. Try to group your symbol and text in a g element and place them at the calculated coordinates. See updated fiddle without force layout: http://jsfiddle.net/xwZjN/26/
var node = svg.selectAll("g.node")
.data(nodes)
.enter().append("g")
.attr("class", "node")
.attr("transform", function(d,i) {
return "translate(" + arc[i].centroid() + ")";
});
node.append("path")
.attr("d", d3.svg.symbol().type(function(d) { return d.type; }))
// change (0,0) for exact symbol placement
.attr("transform", "translate(0,0)")
.style("fill", "blue" );
node.append("text")
.text(function(d) { return d.Name; })
// shift text in nice position
.attr("x", 10)
.attr("y", 5);