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!
Related
So I've been trying to create a donut chart in d3.js and am having trouble adding labels to the chart. My chart data is in an array, but I think because of the "pie" variable, only the "value" from the data is being passed through and not the "text". Have tried multiple ways to try and bring the "text" in but with no luck. Hopefully a fresh set of eyes can see where my mistake is!
var margin = {top: 10, right: 30, bottom: 30, left: 60},
width = 750 - margin.left - margin.right,
height = 520 - margin.top - margin.bottom;
var r = height/3;
var aColor = [
'#0652DD',
'#C4E538',
'#F79F1F',
'#5758BB',
'#D980FA',
"#EA2027"
]
var piedata = [
{text:"Facebook", "value":76},
{text:"Website", "value":13},
{text:"HardwareZone", "value":4},
{text:"YouTube", "value":5},
{text:"Instagram", "value":1},
{text:"Twitter","value":1},
];
var vis = d3.select('#chart2')
.append("svg:svg")
.data([piedata])
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("svg:g")
.attr("transform",
"translate(" + margin.left + "," + margin.top + ")");
var pie = d3.layout.pie().sort(null).value(function(d){return d.value;});
// Declare an arc generator function
var arc = d3.svg.arc().innerRadius(r *0.5).outerRadius(r*0.8);
var outerArc = d3.svg.arc()
.innerRadius(r*0.95)
.outerRadius(r*0.95);
// Select paths, use arc generator to draw
var arcs = vis.selectAll("g.slice").data(pie).enter().append("svg:g").attr("class", "slice").attr("transform", "translate(" + width/2 + "," + height/2 + ")");
arcs.append("g:path")
.attr("fill", function(d, i){return aColor[i];})
.attr("d", function (d) {return arc(d);})
.attr("stroke", "white")
.style("stroke-width", "3px")
.style("opacity", 0.7)
;
// Add the polylines between chart and labels:
arcs.append("g:polyline")
.attr("stroke", "black")
.style("fill", "none")
.attr("stroke-width", "1px")
.attr('points', function(d) {
var posA = arc.centroid(d) // line insertion in the slice
var posB = outerArc.centroid(d) + 5 // line break: we use the other arc generator that has been built only for that
var posC = outerArc.centroid(d) + 5; // Label position = almost the same as posB
var midangle = d.startAngle + (d.endAngle - d.startAngle) / 2 // we need the angle to see if the X position will be at the extreme right or extreme left
posC[0] = r * 0.95 * (midangle < Math.PI ? 1 : -1); // multiply by 1 or -1 to put it on the right or on the left
return [posA, posB, posC]
});
//Add text labels
arcs.append("g:label")
.attr('transform', function(d) {
var pos = outerArc.centroid(d);
var midangle = d.startAngle + (d.endAngle - d.startAngle) / 2
pos[0] = r * 0.99 * (midangle < Math.PI ? 1 : -1);
return 'translate(' + pos + ')';
})
.style('text-anchor', function(d) {
var midangle = d.startAngle + (d.endAngle - d.startAngle) / 2
return (midangle < Math.PI ? 'start' : 'end')
})
.text(function(d) { return d.text; }); //this is where the problem is!
Here is how you can add labels:
arcs.append('text')
.text(d => d.data.text)
.attr('dy', 4)
.attr('text-anchor', d => (d.startAngle + d.endAngle) / 2 > Math.PI ? 'end' : 'start')
.attr('x', d => outerArc.centroid(d)[0])
.attr('y', d => outerArc.centroid(d)[1]);
var margin = {top: 10, right: 30, bottom: 30, left: 60},
width = 750 - margin.left - margin.right,
height = 520 - margin.top - margin.bottom;
var r = height/3;
var aColor = [
'#0652DD',
'#C4E538',
'#F79F1F',
'#5758BB',
'#D980FA',
"#EA2027"
]
var piedata = [
{text:"Facebook", "value":76},
{text:"Website", "value":13},
{text:"HardwareZone", "value":4},
{text:"YouTube", "value":5},
{text:"Instagram", "value":1},
{text:"Twitter","value":1},
];
var vis = d3.select('#chart2')
.append("svg:svg")
.data([piedata])
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("svg:g")
.attr("transform",
"translate(" + margin.left + "," + margin.top + ")");
var pie = d3.layout.pie().sort(null).value(function(d){return d.value;});
// Declare an arc generator function
var arc = d3.svg.arc().innerRadius(r *0.5).outerRadius(r*0.8);
var outerArc = d3.svg.arc()
.innerRadius(r*0.95)
.outerRadius(r*1.1);
// Select paths, use arc generator to draw
var arcs = vis.selectAll("g.slice").data(pie).enter().append("svg:g").attr("class", "slice").attr("transform", "translate(" + width/2 + "," + height/2 + ")");
arcs.append("g:path")
.attr("fill", function(d, i){return aColor[i];})
.attr("d", function (d) {return arc(d);})
.attr("stroke", "white")
.style("stroke-width", "3px")
.style("opacity", 0.7)
;
// Add the polylines between chart and labels:
arcs.append("g:polyline")
.attr("stroke", "black")
.style("fill", "none")
.attr("stroke-width", "1px")
.attr('points', function(d) {
var posA = arc.centroid(d) // line insertion in the slice
var posB = outerArc.centroid(d) + 5 // line break: we use the other arc generator that has been built only for that
var posC = outerArc.centroid(d) + 5; // Label position = almost the same as posB
var midangle = d.startAngle + (d.endAngle - d.startAngle) / 2 // we need the angle to see if the X position will be at the extreme right or extreme left
posC[0] = r * 0.95 * (midangle < Math.PI ? 1 : -1); // multiply by 1 or -1 to put it on the right or on the left
return [posA, posB, posC]
});
//Add text labels
arcs.append('text')
.text(d => d.data.text)
.attr('dy', 4)
.each(d => console.log(d))
.attr('text-anchor', d => (d.startAngle + d.endAngle) / 2 > Math.PI ? 'end' : 'start')
.attr('x', d => outerArc.centroid(d)[0])
.attr('y', d => outerArc.centroid(d)[1]);
text {
font-size: 16px;
font-family: "Ubuntu";
fill: black;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.min.js"></script>
<div id="chart2" />
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>';
};
});
}
I have js code that plots a simple polar chart in the HTML page. I am trying to have it update the data on the plot every 1 second.
I was able to update the data and plot the new points, however, there are two things I have not figured out yet.
Right now every second I am cleaning the plot area and plotting everything new, instead I would like to update the data by creating a smooth sliding transition from the previous position to the new one.
The second issue is that the labeling of the points is not being deleted with the old point position, which creates multiple labeling on top of each other.
What should I add in order to create this smooth transition for the plotted points and their labels?
Here is my code snippet:
<html>
<head>
<meta charset='ISO-8859-1'>
<script src="https://d3js.org/d3.v3.min.js" charset="utf-8"></script>
</head>
<body style='background-color:lightgray'>
<div id="chart" style='width: 400px; height: 400px; padding-left: 5px; padding-bottom: 5px;'></div>
<script>
var color = d3.scale.category20();
var deg2rad = Math.PI / 180;
var width = 400,
height = 350,
radius = Math.min(width, height) / 2 - 30;
var svg = d3.select("#chart").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
var r = d3.scale.linear()
.domain([90, 0])
.range([0, radius]);
var line = d3.svg.line.radial()
.radius(function(d) {
return r(d[1]);
})
.angle(function(d) {
return -d[0] + Math.PI / 2;
});
var gr = null;
createSkyplot();
updateSkyPlot();
function createSkyplot() {
//////////////////////
gr = svg.append("g")
.attr("class", "r axis")
.selectAll("g")
.data(r.ticks(5))
.enter().append("g");
gr.append("circle").attr("r", r).style('fill', 'white');
gr.append("text")
.attr("y", function(d) {
return -r(d) - 4;
})
.attr("transform", "rotate(20)")
.style("text-anchor", "middle")
.style('fill', 'blue')
.text(function(d) {
return d;
});
/////////////////////
/////////////////////
var ga = svg.append("g")
.attr("class", "a axis")
.selectAll("g")
.data(d3.range(0, 360, 45))
.enter().append("g")
.attr("transform", function(d) {
return "rotate(" + (d - 90) + ")";
});
ga.append("line").attr("x2", radius).style('stroke', 'black').style('stroke-dasharray', '1,8');
ga.append("text")
.attr("x", radius + 6)
.attr("dy", ".35em")
.style("text-anchor", function(d) {
return d < 360 && d > 90 ? "end" : null;
})
.attr("transform", function(d) {
return d < 360 && d > 90 ? "rotate(180 " + (radius + 3) + ",0)" : null;
})
.text(function(d) {
return d + "°";
});
/////////////////////
}
function updateSkyPlot() {
var pos = [{
"position": [1, Math.random() * 20, Math.random() * 20],
"label": 1
}, {
"position": [3, Math.random() * 20, Math.random() * 20],
"label": 5
}];
var r = d3.scale.linear()
.domain([90, 0])
.range([0, radius]);
var line = d3.svg.line.radial()
.radius(function(d) {
return r(d[1]);
})
.angle(function(d) {
return -d[0] + Math.PI / 2;
});
svg.selectAll('circle').remove();
gr.append("circle").attr("r", r).style('fill', 'white');
var points = svg.selectAll("point")
.data(pos)
.enter()
.append("a") // The container
.attr("transform", function(d) {
var coors = line([d.position]).slice(1).slice(0, -1);
return "translate(" + coors + ")"
});
points.append("circle")
.attr("class", "point")
.attr("r", 10)
.attr("fill", function(d, i) {
return color(i);
});
points.append("text")
.attr("class", "label")
.text(function(d) {
return d.label
})
.style("font-size", "10")
.attr("transform", "translate(-4,5)");
setTimeout(updateSkyPlot, 1000);
}
</script>
</body>
</html>
Code can also be seen here.
I have tried adding
svg.selectAll("label").remove(); or svg.selectAll('point.label').remove();
but it does not work and when i add svg.selectAll("text").remove(); it removes all the text in the plot which is obviously something i don't want.
Any help on fixing those issues would be appreciated. Thank you.
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.