3 progress bar in one html file using js - javascript

How to display 3 progress bar that have different data using js and html.
First of all, sorry for my poor English, I hope you will understand me after all
I want 3 same circle to show 3 different data.
When I add 3 of the same js codes in which I change the "radialprogress" ID, I always see one progress bar.
I have this code
Html:
<div class="row">
<div>
<div class="radialprogress" id="radialprogress1"></div>
</div>
<div>
<div class="radialprogress" id="radialprogress2"></div>
</div>
<div>
<div class="radialprogress" id="radialprogress3"></div>
</div> </div>
JS code which I also put in html:
<script>
var svg ;
function drawProgress(end){
d3.select("svg").remove()
if(svg){
svg.selectAll("*").remove();
}
var wrapper = document.getElementById('radialprogress1');
var start = 0;
var colours = {
fill: '#12ea8d',
track: '#555555',
text: '#00C0FF',
stroke: '#172b4d',
}
var radius = 80;
var border = 12;
var strokeSpacing = 4;
var endAngle = Math.PI * 2;
var formatText = d3.format('.0%');
var boxSize = radius * 2;
var count = end;
var progress = start;
var step = end < start ? -0.01 : 0.01;
//Define the circle
var circle = d3.svg.arc()
.startAngle(0)
.innerRadius(radius)
.outerRadius(radius - border);
//setup SVG wrapper
svg = d3.select(wrapper)
.append('svg')
.attr('width', boxSize)
.attr('height', boxSize);
// ADD Group container
var g = svg.append('g')
.attr('transform', 'translate(' + boxSize / 2 + ',' + boxSize / 2 + ')');
//Setup track
var track = g.append('g').attr('class', 'radial-progress');
track.append('path')
.attr('fill', colours.track)
.attr('stroke', colours.stroke)
.attr('stroke-width', strokeSpacing + 'px')
.attr('d', circle.endAngle(endAngle));
//Add colour fill
var value = track.append('path')
.attr('fill', colours.fill)
.attr('stroke', colours.stroke)
.attr('stroke-width', strokeSpacing + 'px');
//Add text value
var numberText = track.append('text')
.attr('fill', colours.text)
.attr('text-anchor', 'middle')
.attr('dy', '.5rem');
//update position of endAngle
value.attr('d', circle.endAngle(endAngle * end));
//update text value
numberText.text(formatText(end));
}
$('#submitClick').click(function(){
var val = parseInt($('{$statwi}').val());
drawProgress({$statwi}/100)
})
drawProgress({$statwi}/100)
</script>
<script>
var svg ;
function drawProgress(end){
d3.select("svg").remove()
if(svg){
svg.selectAll("*").remove();
}
var wrapper = document.getElementById('radialprogress2');
var start = 0;
var colours = {
fill: '#12ea8d',
track: '#555555',
text: '#00C0FF',
stroke: '#172b4d',
}
var radius = 80;
var border = 12;
var strokeSpacing = 4;
var endAngle = Math.PI * 2;
var formatText = d3.format('.0%');
var boxSize = radius * 2;
var count = end;
var progress = start;
var step = end < start ? -0.01 : 0.01;
//Define the circle
var circle = d3.svg.arc()
.startAngle(0)
.innerRadius(radius)
.outerRadius(radius - border);
//setup SVG wrapper
svg = d3.select(wrapper)
.append('svg')
.attr('width', boxSize)
.attr('height', boxSize);
// ADD Group container
var g = svg.append('g')
.attr('transform', 'translate(' + boxSize / 2 + ',' + boxSize / 2 + ')');
//Setup track
var track = g.append('g').attr('class', 'radial-progress');
track.append('path')
.attr('fill', colours.track)
.attr('stroke', colours.stroke)
.attr('stroke-width', strokeSpacing + 'px')
.attr('d', circle.endAngle(endAngle));
//Add colour fill
var value = track.append('path')
.attr('fill', colours.fill)
.attr('stroke', colours.stroke)
.attr('stroke-width', strokeSpacing + 'px');
//Add text value
var numberText = track.append('text')
.attr('fill', colours.text)
.attr('text-anchor', 'middle')
.attr('dy', '.5rem');
//update position of endAngle
value.attr('d', circle.endAngle(endAngle * end));
//update text value
numberText.text(formatText(end));
}
drawProgress(1/100)
</script>
<script>
var svg ;
function drawProgress(end){
d3.select("svg").remove()
if(svg){
svg.selectAll("*").remove();
}
var wrapper = document.getElementById('radialprogress3');
var start = 0;
var colours = {
fill: '#12ea8d',
track: '#555555',
text: '#00C0FF',
stroke: '#172b4d',
}
var radius = 80;
var border = 12;
var strokeSpacing = 4;
var endAngle = Math.PI * 2;
var formatText = d3.format('.0%');
var boxSize = radius * 2;
var count = end;
var progress = start;
var step = end < start ? -0.01 : 0.01;
//Define the circle
var circle = d3.svg.arc()
.startAngle(0)
.innerRadius(radius)
.outerRadius(radius - border);
//setup SVG wrapper
svg = d3.select(wrapper)
.append('svg')
.attr('width', boxSize)
.attr('height', boxSize);
// ADD Group container
var g = svg.append('g')
.attr('transform', 'translate(' + boxSize / 2 + ',' + boxSize / 2 + ')');
//Setup track
var track = g.append('g').attr('class', 'radial-progress');
track.append('path')
.attr('fill', colours.track)
.attr('stroke', colours.stroke)
.attr('stroke-width', strokeSpacing + 'px')
.attr('d', circle.endAngle(endAngle));
//Add colour fill
var value = track.append('path')
.attr('fill', colours.fill)
.attr('stroke', colours.stroke)
.attr('stroke-width', strokeSpacing + 'px');
//Add text value
var numberText = track.append('text')
.attr('fill', colours.text)
.attr('text-anchor', 'middle')
.attr('dy', '.5rem');
//update position of endAngle
value.attr('d', circle.endAngle(endAngle * end));
//update text value
numberText.text(formatText(end));
}
drawProgress(3/100)
</script>
Thank You

You could modify the function to accept the target element as a parameter:
function drawProgress(end, target) {
var svg;
var wrapper = document.getElementById(target);
wrapper.innerHTML = "";
var start = 0;
var colours = {
fill: '#12ea8d',
track: '#555555',
text: '#00C0FF',
stroke: '#172b4d',
}
var radius = 80;
var border = 12;
var strokeSpacing = 4;
var endAngle = Math.PI * 2;
var formatText = d3.format('.0%');
var boxSize = radius * 2;
var count = end;
var progress = start;
var step = end < start ? -0.01 : 0.01;
//Define the circle
var circle = d3.arc()
.startAngle(0)
.innerRadius(radius)
.outerRadius(radius - border);
//setup SVG wrapper
svg = d3.select(wrapper)
.append('svg')
.attr('width', boxSize)
.attr('height', boxSize);
// ADD Group container
var g = svg.append('g')
.attr('transform', 'translate(' + boxSize / 2 + ',' + boxSize / 2 + ')');
//Setup track
var track = g.append('g').attr('class', 'radial-progress');
track.append('path')
.attr('fill', colours.track)
.attr('stroke', colours.stroke)
.attr('stroke-width', strokeSpacing + 'px')
.attr('d', circle.endAngle(endAngle));
//Add colour fill
var value = track.append('path')
.attr('fill', colours.fill)
.attr('stroke', colours.stroke)
.attr('stroke-width', strokeSpacing + 'px');
//Add text value
var numberText = track.append('text')
.attr('fill', colours.text)
.attr('text-anchor', 'middle')
.attr('dy', '.5rem');
//update position of endAngle
value.attr('d', circle.endAngle(endAngle * end));
//update text value
numberText.text(formatText(end));
}
drawProgress(3 / 100, 'radialprogress1');
drawProgress(15 / 100, 'radialprogress2');
drawProgress(80 / 100, 'radialprogress3');
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<div class="row">
<div>
<div class="radialprogress" id="radialprogress1"></div>
</div>
<div>
<div class="radialprogress" id="radialprogress2"></div>
</div>
<div>
<div class="radialprogress" id="radialprogress3"></div>
</div>
</div>
I made these changes to the code, to help explain it:
added param/args target
at the bottom, replaced the code to call the function with a simpler example
the variable 'svg' is now only inside the function
for removing the previous SVG i swapped it from removing All to removing just the target's innerHTML
renamed one broken function call d3.svg.arc -> d3.arc

First, never use one ID for multiple elements. Use different IDs, so for example:
<div class="row">
<div>
<div style="width: 160px;float: left;margin-right: 0px;" id="radialprogress1"></div>
</div>
<div>
<div style="width: 160px;float: left;margin-right: 0px;" id="radialprogress2"></div>
</div>
<div>
<div style="width: 160px;float: left;margin-right: 0px;" id="radialprogress3"></div>
</div>
</div>
Then, you have to adjust your JavaScript code, too.
In Addition, I have a tip: Use classes and CSS to style multiple elements at once:
<div class="row">
<div>
<div class="radialprogress" id="radialprogress1"></div>
</div>
<div>
<div class="radialprogress" id="radialprogress2"></div>
</div>
<div>
<div class="radialprogress" id="radialprogress3"></div>
</div>
</div>
<style>
.radialprogress {
width: 160px;
float: left;
margin-right: 0px;
}
</style>
If you want to learn CSS, visit W3Wchools.

You could use a data-attribute to store percentage data for each gauge instance:
<div class="radialprogress" data-progress="3 / 100"></div>
Your drawing function drawProgressGauges() would search/query for all <div> with a class of "radialprogress".
So you don't need to run/call it for every new gauge element added to your HTML.
Unique IDs are also added automatically.
// draw gauges if present
drawProgressGauges();
function drawProgressGauges() {
let gauges = document.querySelectorAll(".radialprogress");
if (gauges.length) {
gauges.forEach(function (el, i) {
// set auto ids
let autoID = 'progress'+i;
el.id = autoID;
// get percentages by data attribute
let endAtt = el.getAttribute('data-progress').split('/');
end = endAtt[0] / endAtt[1];
var colours = {
fill: "#12ea8d",
track: "#555555",
text: "#00C0FF",
stroke: "#172b4d"
};
var radius = 80;
var border = 12;
var strokeSpacing = 4;
var endAngle = Math.PI * 2;
var formatText = d3.format(".0%");
var boxSize = radius * 2 + (border/2);
//Define the circle
var circle = d3
.arc()
.startAngle(0)
.innerRadius(radius)
.outerRadius(radius - border );
//setup SVG wrapper
let svg = d3
.select(el)
.append("svg")
.attr("width", boxSize)
.attr("height", boxSize);
// ADD Group container
var g = svg
.append("g")
.attr(
"transform",
"translate(" + boxSize / 2 + "," + boxSize / 2 + ")"
);
//Setup track
var track = g.append("g").attr("class", "radial-progress");
track
.append("path")
.attr("fill", colours.track)
.attr("stroke", colours.stroke)
.attr("stroke-width", strokeSpacing + "px")
.attr("d", circle.endAngle(endAngle));
//Add colour fill
var value = track
.append("path")
.attr("fill", colours.fill)
.attr("stroke", colours.stroke)
.attr("stroke-width", strokeSpacing + "px");
//Add text value
var numberText = track
.append("text")
.attr("fill", colours.text)
.attr("text-anchor", "middle")
.attr("dy", ".5rem");
//update position of endAngle
value.attr("d", circle.endAngle(endAngle * end));
//update text value
numberText.text(formatText(end));
});
}
}
.row{
display:flex;
width:100%;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<div class="row">
<div class="radialprogress" data-progress="3 / 100"></div>
<div class="radialprogress" data-progress="15 / 100"></div>
<div class="radialprogress" data-progress="33 / 100"></div>
<div class="radialprogress" data-progress="80 / 100"></div>
</div>
If you're interested in a static approach, using only svg:
Pie chart using circle element

Related

Round edges at the ends of radial progress bar

I'm using d3.js to create a radial progress bar i have achieved that but the problem which I'm facing is that I'm unable to create the round edges of each side
Im attaching the image of the output and the expected change in output as well.
Below is thee code that I'm using to create the radial progress bar
<script src="https://d3js.org/d3.v6.min.js"></script>
<style>
.progress {
display: inline-block;
padding: 20px;
}
.radial-progress {
&__text {
font-family: Arial, sans-serif;
font-size: 2rem;
font-weight: bold;
}
}
</style>
<div style="background:#2E98C5" class="background">
<div
data-text-colour="#2E98C5" //Text color
class="progress">
</div>
</div>
<script>
var renderProgress = function(el,background) {
//Note: i have kept the track color to be same as the background color so that
// it seems that there is no track
var start = 0;
//var background='#2E98C5';
var actual_count=12;
var total_count=20;
var end = (actual_count/total_count)*100;
//var end = el.dataset.progress;
var colours = {
fill: "white", //common for both
//track: '#' + el.dataset.trackColour,
text: el.dataset.textColour,
//stroke: '#' + el.dataset.strokeColour,
}
var radius = 68;
var border = 8;
var strokeSpacing = el.dataset.strokeSpacing;
var endAngle = Math.PI * 2;
var formatText = d3.format('.0%');
var boxSize = radius * 2;
var count = end;
var progress = start;
var step = end < start ? -0.01 : 0.01;
//Define the circle
var circle = d3.arc()
.startAngle(0)
.innerRadius(radius)
.outerRadius(radius - border);
//setup SVG wrapper
var svg = d3.select(el)
.append('svg')
.attr('width', boxSize)
.attr('height', boxSize);
// ADD Group container
var g = svg.append('g')
.attr('transform', 'translate(' + boxSize / 2 + ',' + boxSize / 2 + ')');
//Setup track
var track = g.append('g').attr('class', 'radial-progress');
track.append('path')
.attr('class', 'radial-progress__background')
.attr('fill', background)
.attr('stroke', colours.stroke)
.attr('stroke-width', '0px')
.attr('d', (end/100)*360);
//.attr('d', circle.endAngle(endAngle));
//Add colour fill
var value = track.append('path')
.attr('class', 'radial-progress__value')
.attr('fill', colours.fill)
.attr('stroke', colours.stroke)
.attr('stroke-width', strokeSpacing + 'px');
//This circle is just created to give the effect of border over circle
var numberText = track.append('circle')
.attr('fill', "#DBDBDB") // This colr is constant in bot the control design
.attr('dy', '.5rem')
.attr('r', 51);
//Add circle
var numberText = track.append('circle')
.attr('fill', "white") //This
.attr('dy', '.5rem')
.attr('r', 48);
//Add text value
var numberText = track.append('text')
.attr('class', 'radial-progress__text')
.attr('fill', colours.text)
.text( function (d) {
return actual_count.toString() +"/" +total_count.toString();
})//Pass the value that should be at the centre
.attr('font-size', '30px')
.attr('text-anchor', 'middle')
.attr('dy', '.5rem');
function update(progress) {
//update position of endAngle
value.attr('d', circle.endAngle(-(endAngle * progress)));// -ve so that it goes left to right
//update text value
//numberText.text(formatText(progress));
//numberText.text(actual_count.toString() +"/" +total_count.toString()); //Puts the text in the center
};
function iterate() {
//call update to begin animation
update(progress);
if (count > 0) {
//reduce count till it reaches 0
count--;
//increase progress
progress += step;
//Control the speed of the fill
setTimeout(iterate, 10);
}
};
iterate();
}
Array.prototype.slice.call(document.querySelectorAll('.progress')).forEach(el => {
renderProgress(el,'#2E98C5');
});
</script>
I have taken the image from some other source as it has highlighted the thing that I want to do .
Thanks in advance any help or guidance will be great.
Edit 1 :
As per adrew Reid suggestion I was able to make to work and below is the code which worked
<script src="https://d3js.org/d3.v6.min.js"></script>
<style>
.progress {
display: inline-block;
padding: 20px;
}
.radial-progress {
&__text {
font-family: Arial, sans-serif;
font-size: 2rem;
font-weight: bold;
}
}
</style>
<div style="background:#2E98C5" class="background">
<div
data-text-colour="#2E98C5" //Text color
class="progress">
</div>
</div>
<script>
var renderProgress = function(el,background) {
//Note: i have kept the track color to be same as the background color so that
// it seems that there is no track
var start = 0;
//var background='#2E98C5';
var actual_count=12;
var total_count=20;
var end = (actual_count/total_count)*100;
//var end = el.dataset.progress;
var colours = {
fill: "white", //common for both
//track: '#' + el.dataset.trackColour,
text: el.dataset.textColour,
//stroke: '#' + el.dataset.strokeColour,
}
var radius = 68;
var border = 8;
var strokeSpacing = el.dataset.strokeSpacing;
var endAngle = Math.PI * 2;
var formatText = d3.format('.0%');
var boxSize = radius * 2;
var count = end;
var progress = start;
var step = end < start ? -0.01 : 0.01;
//Define the circle
var circle = d3.arc()
.startAngle(0)
.innerRadius(radius)
.outerRadius(radius - border)
.cornerRadius(12);
//setup SVG wrapper
var svg = d3.select(el)
.append('svg')
.attr('width', boxSize)
.attr('height', boxSize);
// ADD Group container
var g = svg.append('g')
.attr('transform', 'translate(' + boxSize / 2 + ',' + boxSize / 2 + ')');
//Setup track
var track = g.append('g').attr('class', 'radial-progress');
track.append('path')
.attr('class', 'radial-progress__background')
.attr('fill', background)
.attr('stroke', colours.stroke)
.attr('stroke-width', '0px')
.attr('d', (end/100)*360);
//.attr('d', circle.endAngle(endAngle));
//Add colour fill
var value = track.append('path')
.attr('class', 'radial-progress__value')
.attr('fill', colours.fill)
.attr('stroke', colours.stroke)
.attr('stroke-width', strokeSpacing + 'px');
//This circle is just created to give the effect of border over circle
var numberText = track.append('circle')
.attr('fill', "#DBDBDB") // This colr is constant in bot the control design
.attr('dy', '.5rem')
.attr('r', 51);
//Add circle
var numberText = track.append('circle')
.attr('fill', "white") //This
.attr('dy', '.5rem')
.attr('r', 48);
//Add text value
var numberText = track.append('text')
.attr('class', 'radial-progress__text')
.attr('fill', colours.text)
.text( function (d) {
return actual_count.toString() +"/" +total_count.toString();
})//Pass the value that should be at the centre
.attr('font-size', '30px')
.attr('text-anchor', 'middle')
.attr('dy', '.5rem');
function update(progress) {
//update position of endAngle
value.attr('d', circle.endAngle(-(endAngle * progress)));// -ve so that it goes left to right
//update text value
//numberText.text(formatText(progress));
//numberText.text(actual_count.toString() +"/" +total_count.toString()); //Puts the text in the center
};
function iterate() {
//call update to begin animation
update(progress);
if (count > 0) {
//reduce count till it reaches 0
count--;
//increase progress
progress += step;
//Control the speed of the fill
setTimeout(iterate, 10);
}
};
iterate();
}
Array.prototype.slice.call(document.querySelectorAll('.progress')).forEach(el => {
renderProgress(el,'#2E98C5');
});
</script>

d3 donut chart transform: translateY

I am making a graph in which it shows the percentage of each data type like this:
but I have a problem copy the code and I have created it the same as the previous example but the titles put them above the percentage and I would like to fix it by placing them above the numbers as it is in the first image, since I have it like this:
This is the code where I send to call the text and in example it already throws me a transformation but I want to upload it so that it remains as a title
svg
.selectAll("allLabels")
.data(name_ready)
.enter()
.append("text")
.text(function (d) {
console.log(d.data.key);
return d.data.key;
})
.style("font-size", "1rem")
.attr("transform", function (d) {
var pos = outerArc.centroid(d);
var midangle = d.startAngle + (d.endAngle - d.startAngle) / 2;
pos[0] = radius * 0.99 * (midangle < Math.PI ? 1 : -1);
return "translate(" + pos + ")";
})
.attr("class", "fontDonut")
.style("text-anchor", function (d) {
var midangle = d.startAngle + (d.endAngle - d.startAngle) / 2;
return midangle < Math.PI ? "start" : "end";
});
The code is like this since it is automatic and it positions itself depending on how much data there is
// set the dimensions and margins of the graph
var width = 400;
var height = 250;
// The radius of the pieplot is half the width or half the height (smallest one). I subtract a bit of margin.
var radius = 100;
// append the svg object to the div called 'my_dataviz'
var svg = d3
.select("#my_char")
.append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
// Create dummy data
var data = {
"25%": 25,
"30% ": 30,
"20%": 20,
"25% ": 25
}
var name = {
"Married": 30,
"Divorced": 30,
"Single": 40,
"Single2 ": 25
}
// set the color scale
var color = d3
.scaleOrdinal([`#C8DBFB`, `#93B6F8`, `#256EF1`]);
// Compute the position of each group on the pie:
var pie = d3
.pie()
.sort(null) // Do not sort group by size
.value(function(d) {
return d.value;
});
var data_ready = pie(d3.entries(data));
var name_ready = pie(d3.entries(name));
// The arc generator
var arc = d3
.arc()
.innerRadius(radius * 0.6) // This is the size of the donut hole
.outerRadius(radius * 0.8);
// Another arc that won't be drawn. Just for labels positioning
var outerArc = d3
.arc()
.innerRadius(radius * 0.9)
.outerRadius(radius * 0.9);
// Build the pie chart: Basically, each part of the pie is a path that we build using the arc function.
svg
.selectAll("allSlices")
.data(data_ready)
.enter()
.append("path")
.attr("d", arc)
.attr("fill", function(d) {
return color(d.data.key);
})
.style("opacity", 0.7);
// Add the polylines between chart and labels:
svg
.selectAll("allLabels")
.data(data_ready)
.enter()
.append("text")
.text(function(d) {
console.log(d.data.key);
return d.data.key;
})
.style("font-size", "2rem")
.style("font-weight", "700")
.attr("transform", function(d) {
var pos = outerArc.centroid(d);
var midangle = d.startAngle + (d.endAngle - d.startAngle) / 2;
pos[0] = radius * 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";
});
svg
.selectAll("allLabels")
.data(name_ready)
.enter()
.append("text")
.text(function(d) {
console.log(d.data.key);
return d.data.key;
})
.style("font-size", "1rem")
.attr("transform", function(d) {
var pos = outerArc.centroid(d);
var midangle = d.startAngle + (d.endAngle - d.startAngle) / 2;
pos[0] = radius * 0.99 * (midangle < Math.PI ? 1 : -1);
return "translate(" + pos + ")";
})
.attr("class", "fontDonut")
.style("text-anchor", function(d) {
var midangle = d.startAngle + (d.endAngle - d.startAngle) / 2;
return midangle < Math.PI ? "start" : "end";
});
.fontDonut {
margin-top: 8rem;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.0.0/d3.min.js"></script>
<div class="text-center bg-white shadow py-6 px-6 chart-container">
<div class="flex justify-start font-bold mb-4 ">
<p class="font-bold ml-4">Children</p>
</div>
<div id="my_char" />
</div>
Don't treat the two values as separate, but use them like a bloc instead. I used basic trigonometry to find the angle of the midpoint like you did, but then draw a line from the centre, so the label is aligned with the middle of the arc and all labels are the same distance from the donut.
Then, I don't have to fiddle with the labels, and instead just add the percentage on top, with a small offset. Note that margin only works in HTML, not in SVG.
// set the dimensions and margins of the graph
var width = 400;
var height = 250;
// The radius of the pieplot is half the width or half the height (smallest one). I subtract a bit of margin.
var radius = 100;
// append the svg object to the div called 'my_dataviz'
var svg = d3
.select("#my_char")
.append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
// Create dummy data
var data = {
//"Married": 30,
"Divorced": 30,
"Single": 40,
"Single2 ": 25
}
// set the color scale
var color = d3
.scaleOrdinal([`#C8DBFB`, `#93B6F8`, `#256EF1`, `darkblue`]);
// Compute the position of each group on the pie:
var pie = d3
.pie()
.sort(null) // Do not sort group by size
.value(function(d) {
return d.value;
});
var data_ready = pie(d3.entries(data));
// The arc generator
var arc = d3
.arc()
.innerRadius(radius * 0.6) // This is the size of the donut hole
.outerRadius(radius * 0.8);
// Another arc that won't be drawn. Just for labels positioning
var outerArc = d3
.arc()
.innerRadius(radius * 0.9)
.outerRadius(radius * 0.9);
// Build the pie chart: Basically, each part of the pie is a path that we build using the arc function.
svg
.selectAll("path")
.data(data_ready)
.enter()
.append("path")
.attr("d", arc)
.attr("fill", function(d) {
return color(d.data.key);
})
.style("opacity", 0.7);
// Add the polylines between chart and labels:
const labelGroup = svg
.selectAll(".labelGroup")
.data(data_ready)
.enter()
.append("g")
.attr("class", "labelGroup")
// Transform the whole group, not the individual text items
.attr("transform", function(d) {
// Get the angle
var midAngle = d.startAngle + (d.endAngle - d.startAngle) / 2;
// Define the radius
var textRadius = 1.4 * radius;
// Use trigonometry to find the correct position
var x = Math.sin(midAngle) * textRadius;
var y = -Math.cos(midAngle) * textRadius;
y = Math.min(y, height / 2 - 20);
return "translate(" + [x, y] + ")";
})
.style("text-anchor", "middle");
labelGroup
.append("text")
.text(function(d) {
return d.data.key;
})
.attr("dominant-baseline", "hanging")
.attr("dy", 5)
.style("font-size", "2rem")
.style("font-weight", "700");
labelGroup
.append("text")
.text(function(d) {
return d.data.value + "%";
})
.attr("dy", -5)
.style("font-size", "1rem");
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.0.0/d3.min.js"></script>
<div class="text-center bg-white shadow py-6 px-6 chart-container">
<div class="flex justify-start font-bold mb-4 ">
<p class="font-bold ml-4">Children</p>
</div>
<div id="my_char" />
</div>

Multiple progress bar circles on single row

I am using this example to create a circle progress bar:
https://codepen.io/shellbryson/pen/KzaKLe
However, I would like to have multiple elements together on a row. I have tried this for two elements by adding two separate divs and two separate css IDs (both with display: inline-block;).
This successfully creates two progress bars together, but the first stays at 0%. In this case, the first should go to 90% and the second to 95% (as defined with var end in the js).
var wrapper = document.getElementById('progress1');
var start = 0;
var end = 90;
var colours = {
fill: '#' + wrapper.dataset.fillColour,
track: '#' + wrapper.dataset.trackColour,
text: '#' + wrapper.dataset.textColour,
stroke: '#' + wrapper.dataset.strokeColour,
}
var radius = 100;
var border = wrapper.dataset.trackWidth;
var strokeSpacing = wrapper.dataset.strokeSpacing;
var endAngle = Math.PI * 2;
var formatText = d3.format('.0%');
var boxSize = radius * 2;
var count = end;
var progress = start;
var step = end < start ? -0.01 : 0.01;
//Define the circle
var circle = d3.arc()
.startAngle(0)
.innerRadius(radius)
.outerRadius(radius - border);
//setup SVG wrapper
var svg = d3.select(wrapper)
.append('svg')
.attr('width', boxSize)
.attr('height', boxSize);
// ADD Group container
var g = svg.append('g')
.attr('transform', 'translate(' + boxSize / 2 + ',' + boxSize / 2 + ')');
//Setup track
var track = g.append('g').attr('class', 'radial-progress');
track.append('path')
.attr('class', 'radial-progress__background')
.attr('fill', colours.track)
.attr('stroke', colours.stroke)
.attr('stroke-width', strokeSpacing + 'px')
.attr('d', circle.endAngle(endAngle));
//Add colour fill
var value = track.append('path')
.attr('class', 'radial-progress__value')
.attr('fill', colours.fill)
.attr('stroke', colours.stroke)
.attr('stroke-width', strokeSpacing + 'px');
//Add text value
var numberText = track.append('text')
.attr('class', 'radial-progress__text')
.attr('fill', colours.text)
.attr('font-size', '30px')
.attr('text-anchor', 'middle')
.attr('dy', '.5rem');
function update(progress) {
//update position of endAngle
value.attr('d', circle.endAngle(endAngle * progress));
//update text value
numberText.text(formatText(progress));
}
(function iterate() {
//call update to begin animation
update(progress);
if (count > 0) {
//reduce count till it reaches 0
count--;
//increase progress
progress += step;
//Control the speed of the fill
setTimeout(iterate, 10);
}
})();
var wrapper = document.getElementById('progress2');
var start = 0;
var end = 95;
var colours = {
fill: '#' + wrapper.dataset.fillColour,
track: '#' + wrapper.dataset.trackColour,
text: '#' + wrapper.dataset.textColour,
stroke: '#' + wrapper.dataset.strokeColour,
}
var radius = 100;
var border = wrapper.dataset.trackWidth;
var strokeSpacing = wrapper.dataset.strokeSpacing;
var endAngle = Math.PI * 2;
var formatText = d3.format('.0%');
var boxSize = radius * 2;
var count = end;
var progress = start;
var step = end < start ? -0.01 : 0.01;
//Define the circle
var circle = d3.arc()
.startAngle(0)
.innerRadius(radius)
.outerRadius(radius - border);
//setup SVG wrapper
var svg = d3.select(wrapper)
.append('svg')
.attr('width', boxSize)
.attr('height', boxSize);
// ADD Group container
var g = svg.append('g')
.attr('transform', 'translate(' + boxSize / 2 + ',' + boxSize / 2 + ')');
//Setup track
var track = g.append('g').attr('class', 'radial-progress');
track.append('path')
.attr('class', 'radial-progress__background')
.attr('fill', colours.track)
.attr('stroke', colours.stroke)
.attr('stroke-width', strokeSpacing + 'px')
.attr('d', circle.endAngle(endAngle));
//Add colour fill
var value = track.append('path')
.attr('class', 'radial-progress__value')
.attr('fill', colours.fill)
.attr('stroke', colours.stroke)
.attr('stroke-width', strokeSpacing + 'px');
//Add text value
var numberText = track.append('text')
.attr('class', 'radial-progress__text')
.attr('fill', colours.text)
.attr('font-size', '30px')
.attr('text-anchor', 'middle')
.attr('dy', '.5rem');
function update(progress) {
//update position of endAngle
value.attr('d', circle.endAngle(endAngle * progress));
//update text value
numberText.text(formatText(progress));
}
(function iterate() {
//call update to begin animation
update(progress);
if (count > 0) {
//reduce count till it reaches 0
count--;
//increase progress
progress += step;
//Control the speed of the fill
setTimeout(iterate, 10);
}
})();
#progress1 {
display: inline-block;
padding: 20px;
}
#progress2 {
display: inline-block;
padding: 20px;
}
.radial-progress {
&__text {
font-family: Arial, sans-serif;
font-size: 2rem;
font-weight: bold;
}
}
<script src="https://d3js.org/d3.v5.min.js"></script>
<div id="progress1"
data-track-width="15"
data-track-colour="555555"
data-fill-colour="228B22"
data-text-colour="383838"
data-stroke-colour="FFFFFF"
data-stroke-spacing="1">
</div>
<div id="progress2"
data-track-width="15"
data-track-colour="555555"
data-fill-colour="228B22"
data-text-colour="383838"
data-stroke-colour="FFFFFF"
data-stroke-spacing="1">
</div>
I have tried re-naming variables in the js to be unique for each element (i.e. wrapper1 and wrapper2 or end1 and end2), but I cannot seem to get the first element to pay attention to the 90% end setting.
Why so much code duplication? You could simply generalize the JS into a function and then use it on any amount of progress circles, just add another data attribute for the progress, e.g data-progress:
var renderProgress = function(el) {
var start = 0;
var end = el.dataset.progress;
var colours = {
fill: '#' + el.dataset.fillColour,
track: '#' + el.dataset.trackColour,
text: '#' + el.dataset.textColour,
stroke: '#' + el.dataset.strokeColour,
}
var radius = 100;
var border = el.dataset.trackWidth;
var strokeSpacing = el.dataset.strokeSpacing;
var endAngle = Math.PI * 2;
var formatText = d3.format('.0%');
var boxSize = radius * 2;
var count = end;
var progress = start;
var step = end < start ? -0.01 : 0.01;
//Define the circle
var circle = d3.arc()
.startAngle(0)
.innerRadius(radius)
.outerRadius(radius - border);
//setup SVG wrapper
var svg = d3.select(el)
.append('svg')
.attr('width', boxSize)
.attr('height', boxSize);
// ADD Group container
var g = svg.append('g')
.attr('transform', 'translate(' + boxSize / 2 + ',' + boxSize / 2 + ')');
//Setup track
var track = g.append('g').attr('class', 'radial-progress');
track.append('path')
.attr('class', 'radial-progress__background')
.attr('fill', colours.track)
.attr('stroke', colours.stroke)
.attr('stroke-width', strokeSpacing + 'px')
.attr('d', circle.endAngle(endAngle));
//Add colour fill
var value = track.append('path')
.attr('class', 'radial-progress__value')
.attr('fill', colours.fill)
.attr('stroke', colours.stroke)
.attr('stroke-width', strokeSpacing + 'px');
//Add text value
var numberText = track.append('text')
.attr('class', 'radial-progress__text')
.attr('fill', colours.text)
.attr('font-size', '30px')
.attr('text-anchor', 'middle')
.attr('dy', '.5rem');
function update(progress) {
//update position of endAngle
value.attr('d', circle.endAngle(endAngle * progress));
//update text value
numberText.text(formatText(progress));
};
function iterate() {
//call update to begin animation
update(progress);
if (count > 0) {
//reduce count till it reaches 0
count--;
//increase progress
progress += step;
//Control the speed of the fill
setTimeout(iterate, 10);
}
};
iterate();
}
Array.prototype.slice.call(document.querySelectorAll('.progress')).forEach(el => {
renderProgress(el);
});
.progress {
display: inline-block;
padding: 20px;
}
.radial-progress {
&__text {
font-family: Arial, sans-serif;
font-size: 2rem;
font-weight: bold;
}
}
<script src="https://d3js.org/d3.v5.min.js"></script>
<div
data-progress="90"
data-track-width="15"
data-track-colour="555555"
data-fill-colour="228B22"
data-text-colour="383838"
data-stroke-colour="FFFFFF"
data-stroke-spacing="1"
class="progress">
</div>
<div
data-progress="95"
data-track-width="15"
data-track-colour="555555"
data-fill-colour="228B22"
data-text-colour="383838"
data-stroke-colour="FFFFFF"
data-stroke-spacing="1"
class="progress">
</div>

Only the last radial progress bar works when created within a for-loop

I am trying to create three radial progress bars, but only the last one is working. Any idea why the first two are not working?
html
<div id="radialProgress1"
data-percentage="30"
data-track-width="5"
data-track-colour="656975"
data-fill-colour="#5AAAFA"
data-text-colour="#959595"
data-stroke-colour="#C7D2D2"
data-stroke-spacing="4">
</div>
<div id="radialProgress2"
data-percentage="30"
data-track-width="5"
data-track-colour="656975"
data-fill-colour="#5AAAFA"
data-text-colour="#959595"
data-stroke-colour="#C7D2D2"
data-stroke-spacing="4">
</div>
<div id="radialProgress3"
data-percentage="30"
data-track-width="5"
data-track-colour="656975"
data-fill-colour="#5AAAFA"
data-text-colour="#959595"
data-stroke-colour="#C7D2D2"
data-stroke-spacing="4">
</div>
javascript
var progressRadial = [document.getElementById('radialProgress1'), document.getElementById('radialProgress2'), document.getElementById('radialProgress3')];
for(i=0; i<progressRadial.length; i++) {
var wrapper = progressRadial[i];
var start = 0;
var end = parseFloat(wrapper.dataset.percentage);
var colours = {
fill: '#' + wrapper.dataset.fillColour,
track: '#' + wrapper.dataset.trackColour,
text: '#' + wrapper.dataset.textColour,
stroke: '#' + wrapper.dataset.strokeColour,
}
var radius = 75;
var border = wrapper.dataset.trackWidth;
var strokeSpacing = wrapper.dataset.strokeSpacing;
var endAngle = Math.PI * 2;
var formatText = d3.format('.0%');
var boxSize = radius * 2;
var count = end;
var radialprogress = start;
var step = end < start ? -0.01 : 0.01;
//Define the circle
var circle = d3.svg.arc()
.startAngle(0)
.innerRadius(radius)
.outerRadius(radius - border);
//setup SVG wrapper
var svg = d3.select(wrapper)
.append('svg')
.attr('width', boxSize)
.attr('height', boxSize);
// ADD Group container
var g = svg.append('g')
.attr('transform', 'translate(' + boxSize / 2 + ',' + boxSize / 2 + ')');
//Setup track
var track = g.append('g').attr('class', 'radial-progress');
track.append('path')
.attr('class', 'radial-progress__background')
.attr('fill', colours.track)
.attr('stroke', colours.stroke)
.attr('stroke-width', strokeSpacing + 'px')
.attr('d', circle.endAngle(endAngle));
//Add colour fill
var value = track.append('path')
.attr('class', 'radial-progress__value')
.attr('fill', colours.fill)
.attr('stroke', colours.stroke)
.attr('stroke-width', strokeSpacing + 'px');
//Add text value
var numberText = track.append('text')
.attr('class', 'radial-progress__text')
.attr('fill', colours.text)
.attr('text-anchor', 'middle')
.attr('font-size', 40)
.attr('dy', '.5rem');
function update(progress) {
//update position of endAngle
value.attr('d', circle.endAngle(endAngle * radialprogress));
//update text value
numberText.text(formatText(radialprogress));
}
(function iterate() {
//call update to begin animation
update(radialprogress);
if (count > 0) {
//reduce count till it reaches 0
count--;
//increase progress
radialprogress += step;
//Control the speed of the fill
setTimeout(iterate, 10);
}
})();
}
Using var within your for loop does not create a new variable for each iteration. In the latest version of JavaScript (ES6+), you can use let and const to provide this sort of block scoping, but an ES5 solution would be to surround your loop body with an IIFE:
var progressRadial = [document.getElementById('radialProgress1'), document.getElementById('radialProgress2'), document.getElementById('radialProgress3')];
for (i = 0; i < progressRadial.length; i++) (function () {
var wrapper = progressRadial[i];
var start = 0;
var end = parseFloat(wrapper.dataset.percentage);
var colours = {
fill: '#' + wrapper.dataset.fillColour,
track: '#' + wrapper.dataset.trackColour,
text: '#' + wrapper.dataset.textColour,
stroke: '#' + wrapper.dataset.strokeColour,
}
var radius = 75;
var border = wrapper.dataset.trackWidth;
var strokeSpacing = wrapper.dataset.strokeSpacing;
var endAngle = Math.PI * 2;
var formatText = d3.format('.0%');
var boxSize = radius * 2;
var count = end;
var radialprogress = start;
var step = end < start ? -0.01 : 0.01;
//Define the circle
var circle = d3.svg.arc()
.startAngle(0)
.innerRadius(radius)
.outerRadius(radius - border);
//setup SVG wrapper
var svg = d3.select(wrapper)
.append('svg')
.attr('width', boxSize)
.attr('height', boxSize);
// ADD Group container
var g = svg.append('g')
.attr('transform', 'translate(' + boxSize / 2 + ',' + boxSize / 2 + ')');
//Setup track
var track = g.append('g').attr('class', 'radial-progress');
track.append('path')
.attr('class', 'radial-progress__background')
.attr('fill', colours.track)
.attr('stroke', colours.stroke)
.attr('stroke-width', strokeSpacing + 'px')
.attr('d', circle.endAngle(endAngle));
//Add colour fill
var value = track.append('path')
.attr('class', 'radial-progress__value')
.attr('fill', colours.fill)
.attr('stroke', colours.stroke)
.attr('stroke-width', strokeSpacing + 'px');
//Add text value
var numberText = track.append('text')
.attr('class', 'radial-progress__text')
.attr('fill', colours.text)
.attr('text-anchor', 'middle')
.attr('font-size', 40)
.attr('dy', '.5rem');
function update(progress) {
//update position of endAngle
value.attr('d', circle.endAngle(endAngle * radialprogress));
//update text value
numberText.text(formatText(radialprogress));
}
(function iterate() {
//call update to begin animation
update(radialprogress);
if (count > 0) {
//reduce count till it reaches 0
count--;
//increase progress
radialprogress += step;
//Control the speed of the fill
setTimeout(iterate, 10);
}
})();
})(i)
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<div id="radialProgress1" data-percentage="30" data-track-width="5" data-track-colour="656975" data-fill-colour="#5AAAFA" data-text-colour="#959595" data-stroke-colour="#C7D2D2" data-stroke-spacing="4">
</div>
<div id="radialProgress2" data-percentage="30" data-track-width="5" data-track-colour="656975" data-fill-colour="#5AAAFA" data-text-colour="#959595" data-stroke-colour="#C7D2D2" data-stroke-spacing="4">
</div>
<div id="radialProgress3" data-percentage="30" data-track-width="5" data-track-colour="656975" data-fill-colour="#5AAAFA" data-text-colour="#959595" data-stroke-colour="#C7D2D2" data-stroke-spacing="4">
</div>

Modify D3.js Circle Progress Graph to show range of percentages

I am using d3.js to display a circle progress graph. It's working great but I'd like to show a specific percentage range on the graph in a different color. I'd like to show 75% - 90% as a different color on the graph. How can I achieve this? I looked at the donut chart to accomplish this but I like that the circle graph is animated so I'd like to stick with it and modify it to achieve my needs.
Goal:
Add 75%-90% percentage range in a different line color on the graph. The graph exists to show a 75%-90% "accuracy rating".
Bonus:
Add "75% to 90%" label like shown in this image:
JS:
var colors = {
'pink': '#ffffff',
'yellow': '#f0ff08',
'green': '#47e495'
};
var color = colors.pink;
var line_two_color = colors.green;
var radius = 50;
var border = 3;
var padding = 10;
var startPercent = 0;
var endPercent = 0.90;
var twoPi = Math.PI * 2;
var formatPercent = d3.format('.0%');
var boxSize = 130;
var count = Math.abs((endPercent - startPercent) / 0.01);
var step = endPercent < startPercent ? -0.01 : 0.01;
var arc = d3.svg.arc()
.startAngle(0)
.innerRadius(radius)
.outerRadius(radius - border);
var parent = d3.select('div#circle_graph');
var svg = parent.append('svg')
.attr('width', boxSize)
.attr('height', boxSize);
var defs = svg.append('defs');
var g = svg.append('g')
.attr('transform', 'translate(' + boxSize / 2 + ',' + boxSize / 2 + ')');
var meter = g.append('g')
.attr('class', 'progress-meter');
meter.append('path')
.attr('class', 'background')
.attr('fill', '#ccc')
.attr('fill-opacity', 0.5)
.attr('d', arc.endAngle(twoPi));
var foreground = meter.append('path')
.attr('class', 'foreground')
.attr('fill', color)
.attr('fill-opacity', 1)
.attr('stroke', color)
.attr('stroke-width', 5)
.attr('stroke-opacity', 1)
var front = meter.append('path')
.attr('class', 'foreground')
.attr('fill', color)
.attr('fill-opacity', 1);
var numberText = meter.append('text')
.attr('fill', '#fff')
.attr('text-anchor', 'middle')
.attr('dy', '.35em');
function updateProgress(progress) {
foreground.attr('d', arc.endAngle(twoPi * progress));
front.attr('d', arc.endAngle(twoPi * progress));
numberText.text(formatPercent(progress));
}
var progress = startPercent;
(function loops() {
updateProgress(progress);
if (count > 0) {
count--;
progress += step;
setTimeout(loops, 10);
}
})();
CSS:
.progress-meter text {
font-family:"Helvetica Neue", Helvetica, Arial, sans-serif;
font-size: 24px;
font-weight: bold;
}
HTML:
<div id="circle_graph"></div>
Hopefully I'm understanding your question correctly!
Also note, if your data is in % (rather than radians), you'll have to add a d3.scale to convert [0,100] domain to [0, 2pi].
The below code mimics a single progress arc with two separate arcs. One for the 0-75% range and a second for above 75%. Both arcs draw based on the same data but the key is using min and max functions to clamp the data as it passes the 75% threshold.
For the first bar, the end angle is stopped when the progress passes 75%...
.attr('d', function(d){
progressArc.startAngle(0)
return progressArc.endAngle( Math.min(d,(3/2)*Math.PI) )();
})
Math.min(d,(3/2)*Math.PI)
while for the second bar, the end angle only begins to change after the data passes 75%...
.attr('d', function(d){
progressArc.startAngle((3/2)*Math.PI)
return progressArc.endAngle( Math.max(d,(3/2)*Math.PI ))();
})
Math.max(d,(3/2)*Math.PI
The end result looks like one bar changing color as it passes a threshold.
var height = 20,
width = 70,
progress = 3;
d3.select('div').append('svg')
.attr('width','100%')
.attr('viewBox','0 0 ' + width + ' ' + height)
d3.select('svg').append('g')
.attr('transform','translate('+width/2+','+height/2+')')
.attr('id','main')
var progressArc = d3.svg.arc()
.innerRadius(7)
.outerRadius(9)
.startAngle(0)
.endAngle(2*Math.PI)
var progressBar1 = d3.select('#main').append('g').attr('class','progressBar1'),
progressBar2 = d3.select('#main').append('g').attr('class','progressBar2');
progressBar1.selectAll('path')
.data([progress])
.enter()
.append('path')
.attr('d', function(d){
progressArc.startAngle(0)
return progressArc.endAngle( Math.min(d,(3/2)*Math.PI) )();
})
progressBar2.selectAll('path')
.data([progress])
.enter()
.append('path')
.attr('fill','red')
.attr('d', function(d){
progressArc.startAngle((3/2)*Math.PI)
return progressArc.endAngle( Math.max(d,(3/2)*Math.PI ))();
})
var update = function(){
progress = progress >= 2*Math.PI ? 0 : progress + Math.random()*(1/200)*Math.PI;
console.log(progress)
progressBar1.selectAll('path')
.data([progress])
.attr('d', function(d){
progressArc.startAngle(0)
return progressArc.endAngle( Math.min(d,(3/2)*Math.PI) )();
})
progressBar2.selectAll('path')
.data([progress])
.attr('d', function(d){
progressArc.startAngle((3/2)*Math.PI)
return progressArc.endAngle( Math.max(d,(3/2)*Math.PI ))();
})
}
setInterval( update, 12);
svg{
border: solid green 1px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<div></div>

Categories