I'm using d3 to draw a pie with radio button to change the paths showed. My problem is to recalculate the label position based on the new paths. On load the labels are drawn correctly but on click the position still the same of first load.
I think that the problem is that the g's take only the first data value and i don't know how to say to take the current data values.
The function that draw the labels is
//Labels
d3.selectAll("g").select("text").transition()
.ease("linear")
.duration(500)
.style("opacity", 0).remove();
svg.selectAll("g").append("text")
.attr("transform", function (d) {
var c = arc.centroid(d),
x = c[0],
y = c[1],
// pythagorean theorem for hypotenuse
h = Math.sqrt(x * x + y * y);
return "translate(" + (x / h * labelr) + ',' +
(y / h * labelr) + ")";
})
.attr("dy", ".35em")
.style("opacity", 0)
.style("fill", "#000")
.style("font-size", 12)
.attr("text-anchor", function (d) {
// are we past the center?
return (d.endAngle + d.startAngle) / 2 > Math.PI ?
"end" : "start";
})
.text(function (d) { return d.data.label; })
.transition()
.ease("linear")
.delay(1000)
.duration(500)
.style("opacity", 1);
For more info see https://jsfiddle.net/w0ckw4tb/
Thanks
Just apply new data binding before you append new text nodes:
svg.selectAll("g").data(pie) // <== !!!
.append("text")
.attr("transform" ...
If you did not do it, this code:
.attr("transform", function (d) {
var c = arc.centroid(d),
x = c[0],
y = c[1],
// pythagorean theorem for hypotenuse
h = Math.sqrt(x * x + y * y);
return "translate(" + (x / h * labelr) + ',' +
(y / h * labelr) + ")";
})
returns the same value for transform attribute so the labels remain at the same place.
Check working fiddle.
Related
I am working on a project with d3.js where I want a visualisation to centre on the screen. My current solution works perfectly on desktop screens, even when resizing, but when I load the webpage on my iPhone screen, the visualisation is no longer centred. The target div for this visualisation is:
<div id="grades_circular" class="my_dataviz"></div>
The css to style is:
.my_dataviz {
display: flex;
justify-content: center;}
The JavaScript code for the visualisation is :
const grades_margin = {top: 30, right: 0, bottom: 70, left: 0},
grades_width = 460 - grades_margin.left - grades_margin.right,
grades_height = 460 - grades_margin.top - grades_margin.bottom;
innerRadius = 50,
outerRadius = Math.min(grades_width, grades_height) / 2;
const grades_svg = d3.select("#grades_circular")
.append("svg")
.attr("width", grades_width + grades_margin.left + grades_margin.right)
.attr("height", grades_height + grades_margin.top + grades_margin.bottom)
.append("g")
.attr("transform", `translate(${grades_width/2+grades_margin.left}, ${grades_height/2+grades_margin.top})`);
d3.csv("https://raw.githubusercontent.com/ben-austin27/ben-austin27.github.io/main/data/results.csv").then( function(grades_data) {
const grades_x = d3.scaleBand()
.range([0, 2 * Math.PI]) // X axis goes from 0 to 2pi = all around the circle. If I stop at 1Pi, it will be around a half circle
.align(0) // This does nothing
.domain(grades_data.map(d => d.module)); // The domain of the X axis is the list of states.
const grades_y = d3.scaleRadial()
.range([innerRadius, outerRadius]) // Domain will be define later.
.domain([40, 100]); // Domain of Y is from 0 to the max seen in the data
// Add the bars
bars = grades_svg.append("g")
.selectAll("path")
.data(grades_data)
.join("path")
.attr("fill", d => "#" + d.color )
.attr("d", d3.arc() // imagine your doing a part of a donut plot
.innerRadius(innerRadius)
.outerRadius(innerRadius+0.05)//d => grades_y(d['grade'])
.startAngle(d => grades_x(d.module))
.endAngle(d => grades_x(d.module) + grades_x.bandwidth())
.padAngle(0.05)
.padRadius(innerRadius))
modules = grades_svg.append("g")
.selectAll("g")
.data(grades_data)
.join("g")
.attr("text-anchor", function(d) { return (grades_x(d.module) + grades_x.bandwidth() / 2 + Math.PI) % (2 * Math.PI) < Math.PI ? "end" : "start"; })
.attr("transform", function(d) { return "rotate(" + ((grades_x(d.module) + grades_x.bandwidth() / 2) * 180 / Math.PI - 90) + ")"+"translate(" + (innerRadius+10) + ",0)"; })//
.append("text")
.text(function(d){return(d.module)})
.attr("transform", function(d) { return (grades_x(d.module) + grades_x.bandwidth() / 2 + Math.PI) % (2 * Math.PI) < Math.PI ? "rotate(180)" : "rotate(0)"; })
.style("font-size", "11px")
.attr("alignment-baseline", "middle")
grades = grades_svg.append("g")
.selectAll("g")
.data(grades_data)
.join("g")
.attr("text-anchor", function(d) { return (grades_x(d.module) + grades_x.bandwidth() / 2 + Math.PI) % (2 * Math.PI) < Math.PI ? "end" : "start"; })
.attr("transform", function(d) { return "rotate(" + ((grades_x(d.module) + grades_x.bandwidth() / 2) * 180 / Math.PI - 90) + ")"+"translate(" + (grades_y(d['grade'])+7) + ",0)"; })//
.append("text")
.text(function(d){return(d.grade)})
.attr("transform", function(d) { return (grades_x(d.module) + grades_x.bandwidth() / 2 + Math.PI) % (2 * Math.PI) < Math.PI ? "rotate(180)" : "rotate(0)"; })
.style("font-size", "11px")
.attr("alignment-baseline", "middle")
function update_bars() {
d3.selectAll("path")
.transition()
.ease(d3.easePolyInOut.exponent(3)) //https://observablehq.com/#d3/easing-animations
.duration(2000)
.attr("d", d3.arc() // imagine your doing a part of a donut plot
.innerRadius(innerRadius)
.outerRadius(d => grades_y(d['grade']))
.startAngle(d => grades_x(d.module))
.endAngle(d => grades_x(d.module) + grades_x.bandwidth())
.padAngle(0.05)
.padRadius(innerRadius))
// alter opactity of the labeling as well, after 2 seconds
}
var controller = new ScrollMagic.Controller();
new ScrollMagic.Scene({
// the element to scroll inside
triggerElement: '#grades_circular'
})
.on('enter', function(e) {
update_bars(e);
}).addTo(controller)
});
Thanks!
How can I have all numbers from 13 - 11 not rotated? (I'd like all of them be displayed in current positions, but without rotating them - as here: http://bl.ocks.org/tomgp/6475678)
This is my code right now:
var xTick = xAxis
// .selectAll("g")
.selectAll(".radial")
.data(x.ticks(24))
.enter().append("g")
.attr("text-anchor", "middle")
.attr("transform", function(d) {
return "rotate(" + ((x(d)) * 180 / Math.PI - 90) + ")translate(" + innerRadius + ",0)";
});
xTick.append("line")
.attr("x2", -5)
.attr("stroke", "#595D5C");
xTick.append("text")
.attr("transform", function(d) {
var angle = x(d.key);
return ((angle < Math.PI / 2) || (angle > (Math.PI * 3 / 2))) ? "rotate(90)translate(0,0)" : "rotate(-90)translate(0, -15)"; })
.text(function(d) {
return d;
})
.style("font-size", 10)
.attr("color", "#595D5C")
.attr("opacity", 1)
I have been looking to create a moving gauge and struggled to find an off the shelf solution. I then stumbled across the gauge posted in the below link. Currently it runs random numbers in the chart. I would like it to change value based on an array of numerical values (not %ages) that I have, over a timeframe that I specify. The numbers are currently a simple Excel column.
So the gauge would go through the thousand or so numbers I have across, say, a minute.
I'll be frank, I'm a novice at coding and have limited experience in NetLogo and R. Hence why I'm here asking for pointers.
Any advice would be greatly appreciated. Thank you.
https://codepen.io/leomarquine/pen/xGzMjZ
var size = 150,
thickness = 60;
var color = d3.scale.linear()
.domain([0, 50, 100])
.range(['#db2828', '#fbbd08', '#21ba45']);
// .domain([0, 17, 33, 50, 67, 83, 100])
// .range(['#db4639', '#db7f29', '#d1bf1f', '#92c51b', '#48ba17', '#12ab24', '#0f9f59']);
var arc = d3.svg.arc()
.innerRadius(size - thickness)
.outerRadius(size)
.startAngle(-Math.PI / 2);
var svg = d3.select('#chart').append('svg')
.attr('width', size * 2)
.attr('height', size + 20)
.attr('class', 'gauge');
var chart = svg.append('g')
.attr('transform', 'translate(' + size + ',' + size + ')')
var background = chart.append('path')
.datum({
endAngle: Math.PI / 2
})
.attr('class', 'background')
.attr('d', arc);
var foreground = chart.append('path')
.datum({
endAngle: -Math.PI / 2
})
.style('fill', '#db2828')
.attr('d', arc);
var value = svg.append('g')
.attr('transform', 'translate(' + size + ',' + (size * .9) + ')')
.append('text')
.text(0)
.attr('text-anchor', 'middle')
.attr('class', 'value');
var scale = svg.append('g')
.attr('transform', 'translate(' + size + ',' + (size + 15) + ')')
.attr('class', 'scale');
scale.append('text')
.text(100)
.attr('text-anchor', 'middle')
.attr('x', (size - thickness / 2));
scale.append('text')
.text(0)
.attr('text-anchor', 'middle')
.attr('x', -(size - thickness / 2));
setInterval(function() {
update(Math.random() * 100);
}, 1500);
function update(v) {
v = d3.format('.1f')(v);
foreground.transition()
.duration(750)
.style('fill', function() {
return color(v);
})
.call(arcTween, v);
value.transition()
.duration(750)
.call(textTween, v);
}
function arcTween(transition, v) {
var newAngle = v / 100 * Math.PI - Math.PI / 2;
transition.attrTween('d', function(d) {
var interpolate = d3.interpolate(d.endAngle, newAngle);
return function(t) {
d.endAngle = interpolate(t);
return arc(d);
};
});
}
function textTween(transition, v) {
transition.tween('text', function() {
var interpolate = d3.interpolate(this.innerHTML, v),
split = (v + '').split('.'),
round = (split.length > 1) ? Math.pow(10, split[1].length) : 1;
return function(t) {
this.innerHTML = d3.format('.1f')(Math.round(interpolate(t) * round) / round) + '<tspan>%</tspan>';
};
});
}
So, I'm basically trying to make a multilevel circular partition (aka sunburst diagram) with D3.js (v4) and a JSON data.
I placed some labels, which must have different angles depending of their levels (circles) on the partition :
- Level < 3 must be curved and "follow" the arc radius.
- level == 3 must be straight and perpendicular of the arc radius.
I didn't use textPath tags, because I'm not really experienced in SVG and it looks overly complicated to me, and I don't really know how to use it.
here's my code (without the JSON but this is a really classical one, I can add a part of it if it is needed):
var width = 800;
var height = 800;
var radius = 400;
var formatNumber = d3.format(",d");
var x = d3.scaleLinear().range([0, 2 * Math.PI]);
var y = d3.scaleSqrt().range([0, radius]);
var arc = d3.arc()
.startAngle(function(d) { return Math.max(0, Math.min(2 * Math.PI, x(d.x0))); })
.endAngle(function(d) { return Math.max(0, Math.min(2 * Math.PI, x(d.x1))); })
.innerRadius(function(d) { return setRadius("inner", d.data.level); })
.outerRadius(function(d) { return setRadius("outer", d.data.level); });
var svg = d3.select("#chart")
.append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width/2 + "," + (height/2) + ")");
var hierarchy = d3.hierarchy(dataset)
.sum(function(d) { return d.size; });
var partition = d3.partition();
svg.selectAll("path")
.data(partition(hierarchy).descendants())
.enter().append("path")
.attr("id", function(d, i){ return "path" + i; })
.attr("d", arc)
.attr("stroke", "white")
.attr("stroke-width", "1px")
.style("fill", function(d) { return (d.data.color) ? d.data.color : 'black'; });
svg.selectAll("text")
.data(partition(hierarchy).descendants())
.enter().append("text")
.attr("transform", function(d){ return setLabelPosition(d); })
.attr("text-anchor", "middle")
.attr("alignment-baseline", "middle")
.attr("font-size", "18px")
.attr("fill", function(d){ return d.data.textcolor; })
.text(function(d){ if(parseInt(d.data.level) > 0 && parseInt(d.data.level) < 4){ return (d.data.name).toUpperCase(); }});
d3.select(self.frameElement)
.style("height", height + "px");
function setRadius(side, level){
var result = 0;
var innerValues = [0, 120, 180, 240, 365];
var outerValues = [0, 180, 240, 365, 400];
if(!side){
throw error;
}
if(side === "inner"){
result = innerValues[level];
}
if(side === "outer"){
result = outerValues[level];
}
return result;
};
function setLabelPosition(d){
var result = '';
var angle = 0;
var centroid = arc.centroid(d);
if(parseInt(d.data.level) === 3){
angle = (180/Math.PI * (arc.startAngle()(d) + arc.endAngle()(d))/2 - 90);
if(angle > 90){
angle = angle - 180;
}
result = "translate(" + centroid + ")rotate(" + angle + ")";
} else {
angle = (180/Math.PI * (arc.startAngle()(d) + arc.endAngle()(d))/2);
result = "translate(" + centroid + ")rotate(" + angle + ")";
}
return result;
};
And the result :
My problem is, how to curve these level 1 & 2 labels (like the one which have a red border), but keep my lvl 3 labels as they currently are.
It's really a pain in the head, and I did many search (on Google and SO) but I didn't find any satisfying answer.
A solution without using a textPath will be awesome if possible, but any advice is welcome.
Many thanks guys and sorry for my English (as you can probably see it's not my birth language).
PS : This is D3.js v4.
I have this D3 radial tree graph and what I need is the images to not being rotated, but appear straight together it's corresponding blue circle. Here is the code working in Codepen and I copy it here:
var radius = 960 / 2;
var tree = d3.layout.tree()
.size([360, radius - 120])
.separation(function (a, b) { return (a.parent == b.parent ? 1 : 2) / a.depth; });
var diagonal = d3.svg.diagonal.radial()
.projection(function (d) { return [d.y, d.x / 180 * Math.PI]; })
var vis = d3.select('#graph').append('svg:svg')
.attr('width', radius * 2)
.attr('height', radius * 2 - 150)
.append('g')
.attr('transform', 'translate(' + radius + ',' + radius + ')');
d3.json('flare2.json', function (json) {
var nodes = tree.nodes(json);
var link = vis.selectAll('path.link')
.data(tree.links(nodes))
.enter().append('path')
.attr('class', 'link')
.attr('d', diagonal);
var node = vis.selectAll('g.node')
.data(nodes)
.enter().append('g')
.attr('class', 'node')
.attr("transform", function(d) { return "rotate(" + (d.x - 90) + ")translate(" + d.y + ")"; });
node.append('circle')
.attr('r', 4.5);
node.append('image')
.attr('xlink:href', 'img/avatar.40.gif')
.attr('width', 40)
.attr('height', 40)
.attr('x', 10);
node.append('image')
.attr('xlink:href', 'img/avatar.41.gif')
.attr('width', 40)
.attr('height', 40)
.attr('x', 50 );
});
Your nodes are rotated
.attr("transform", function(d) { return "rotate(" + (d.x - 90) + ")translate(" + d.y + ")"; })
And your images are appended to your nodes
node.append('image')
So you need to rotate the images back
.attr("transform", function(d) { return "rotate(" + (90 - d.x) + ")"; })
I'm not sure exactly how you want to position them, but you need to translate them on both x and y.
See a working example: http://codepen.io/anon/pen/qiCeG