Round edges at the ends of radial progress bar - javascript

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>

Related

3 progress bar in one html file using js

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

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>

D3 - smooth radial chart transition animation

I've prepared D3 Radial Chart component to show percentage value of some target. It would be great to add smoothy transition effect when start drawing foreground circle from 0 to chartPercentage (eg. 70%).
The question is - how to prepare transition / delay / duration effect with code which is attached below ?
Second idea which I also want to implement is to count value inside of the circle (radial-content) with animation from 0 to chartValue. How to prepare such solution?
Thank you !
const chartPercentage = 70;
const chartValue = 1.1242
const radius = 75;
const border = 7;
const padding = 0;
const width = 400;
const height = 400;
const twoPi = Math.PI * 2;
const boxSize = (radius + padding) * 2;
let svg;
function setArc() {
return d3.arc()
.startAngle(0)
.innerRadius(radius)
.outerRadius(radius - border)
.cornerRadius(50);
}
function draw() {
svg = d3.select(".chart").append("svg")
.attr('width', width)
.attr('height', height);
svg.append("foreignObject")
.attr("width", boxSize)
.attr("height", boxSize)
.append("xhtml:div")
.attr('class', 'radial-wrapper')
.html(`<div class="radial-content">${chartValue}</div>`);
const field = svg.append('g')
.attr('transform', 'translate(' + boxSize / 2 + ',' + boxSize / 2 + ')');
const meter = field.append('g')
.attr('class', 'progress-meter');
const background = meter.append("path")
.datum({endAngle: twoPi})
.attr('class', 'background')
.attr('fill', '#2D2E2F')
.attr('fill-opacity', 0.1)
.attr("d", setArc());
const foreground = meter.append("path")
.datum({endAngle: (chartPercentage/100) * twoPi})
.attr('class', 'foreground')
.attr('fill', 'red')
.attr('fill-opacity', 1)
.attr('d', setArc());
}
draw();
body { margin:30px;position:fixed;top:0;right:0;bottom:0;left:0; }
.radial-wrapper{ display: flex; align-items: center; justify-content: center;width: 100%; height: 100%;}
<script src="https://d3js.org/d3.v4.min.js"></script>
<div class="chart"></div>
I rewrote your code. When you need to animate some attribute, you should use attrTween not attr method.
const chartPercentage = 70;
const chartValue = 1.1242
const radius = 75;
const border = 7;
const padding = 0;
const width = 400;
const height = 400;
const twoPi = Math.PI * 2;
const boxSize = (radius + padding) * 2;
let svg;
const setArc = d3.arc()
.startAngle(0)
.innerRadius(radius)
.outerRadius(radius - border)
.cornerRadius(50);
const arcParams = {};
function draw() {
svg = d3.select(".chart").append("svg")
.attr('width', width)
.attr('height', height);
svg.append("foreignObject")
.attr("width", boxSize)
.attr("height", boxSize)
.append("xhtml:div")
.attr('class', 'radial-wrapper')
.html(`<div class="radial-content"></div>`);
const field = svg.append('g')
.attr('transform', 'translate(' + boxSize / 2 + ',' + boxSize / 2 + ')');
const meter = field.append('g')
.attr('class', 'progress-meter');
const background = meter
.append("path")
.attr('class', 'background')
.attr('fill', '#2D2E2F')
.attr('fill-opacity', 0.1)
.attr("d", setArc({ endAngle: twoPi }));
const foreground = meter
.append("path")
.transition()
.ease(d3.easeBounce)
.duration(1500)
.attr('class', 'foreground')
.attr('fill', 'red')
.attr('fill-opacity', 1)
.attrTween("d", function() {
return arcTween({ endAngle: 0 }, chartPercentage/100 )
})
}
function arcTween(d, new_score) {
var new_startAngle = 0
var new_endAngle = new_startAngle + new_score * 2 * Math.PI
var interpolate_start = d3.interpolate(d.startAngle, new_startAngle)
var interpolate_end = d3.interpolate(d.endAngle, new_endAngle)
return function(t) {
d.endAngle = interpolate_end(t)
d3.select('.radial-content')
.text((d.endAngle / new_endAngle * chartValue).toFixed(4));
return setArc(d)
}
}
draw();
body {
margin: 30px;
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
}
.radial-wrapper {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 100%;
}
<script src="https://d3js.org/d3.v4.min.js"></script>
<div class="chart"></div>
Make a function for attribute tween.
function arcTween(a) {
var j = {"endAngle":0};//start angle
var i = d3.interpolateObject(j, a);
return function(t) {
d3.select(".radial-content").text(d3.format(".4n")(chartValue*t));
return arc(i(t));
};
}
In the above function
d3.select(".radial-content").text(d3.format(".4n")(chartValue*t));
this will change the text(and output it in the format) in the radial content as the transition runs.
now add the tween function to the foreground path.
const foreground = meter.append("path")
.datum({
endAngle: (chartPercentage / 100) * twoPi
})
.attr('class', 'foreground')
.attr('fill', 'red')
.attr('fill-opacity', 1)
.transition().duration(750).attrTween("d", arcTween);
working code here

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