This question already has answers here:
Circular percent progress bar
(5 answers)
Closed 5 years ago.
how to display percentage the form a circle or arc in JavaScript.
I want to display the black color circumference in percentage. Say If I input max value 20 and min value 10, it should display 50% of a circle (arc)
How to do it?.
<!DOCTYPE html>
<html>
<body>
//displays circle with dimensions
<svg height="300" width="300">
<circle cx="150" cy="100" r="90" stroke="brown" stroke-width="10" fill="white"/>
</svg>
<br/><br/>
maxValue: <input type="text" value="" id="value1" /><br/>
minValue: <input type="text" value="" id="value2" /><br/>
<input type="button" value="Stroke-percentage" onclick="" />
stroke-percentage = maxValue/minValue * 100
</body>
</html>
Santho's answer is correct for SVG, but i would like to mention HTML5's Canvas element as an alternative:
/**
* arcPercentage
*
* #param {{ radius?: number, rate?: number, color?: string }} parameters
* #returns
*/
function arcPercentage(parameters) {
var radius = (parameters.radius !== void 0 ? parameters.radius : 100);
var rate = (parameters.rate !== void 0 ? parameters.rate : 1);
var color = (parameters.color !== void 0 ? parameters.color : "rgba(255,0,0,1)");
var c = document.createElement("canvas");
var size = c.width = c.height = radius * 2;
var ctx = c.getContext("2d");
if (rate == 0) {
return c;
}
ctx.fillStyle = color;
ctx.beginPath();
//Start in origo
ctx.arc(radius, radius, 0, 0, 0);
//Move to start position
ctx.arc(radius, radius, radius, 0, 0);
//Arc to percentage
ctx.arc(radius, radius, radius, 0, (Math.PI * 2) * rate);
//move to origo
ctx.arc(radius, radius, 0, (Math.PI * 2) * rate, (Math.PI * 2) * rate);
ctx.fill();
ctx.closePath();
return c;
}
//TEST
//Get nodes
var inputNode = document.getElementById("circle-input");
var imageNode = document.getElementById("circle-image");
//Bind event
inputNode.onchange = inputNode.onkeyup = inputNode.onmouseup = function() {
//Only fire if valid input
if (inputNode.validity.valid) {
//Parse value
var value = parseInt(inputNode.value, 10) / 100;
//Draw the arc
imageNode.src = arcPercentage({
color: "blue",
radius: 100,
rate: value
}).toDataURL();
}
};
<input id="circle-input" min="0" max="100" type="number" value="0">
<br/>
<img id="circle-image">
HTML code:
<!DOCTYPE html>
<html>
<body>
<svg id="svg" width="200" height="200" viewport="0 0 100 100" version="1.1" xmlns="http://www.w3.org/2000/svg">
<circle r="90" cx="100" cy="100" fill="transparent" stroke-width="1em" stroke-dasharray="565.48" stroke-dashoffset="0" stroke="#666"></circle>
<circle id="bar" r="90" cx="100" cy="100" fill="transparent" stroke-dasharray="297.48" stroke-dashoffset="0" stroke="red" stroke-width="1em"></circle>
</svg>
Value: <input type="text" value="" id="value1" /><br/>
<input type="button" value="Stroke-percentage" id="generateProgress" />
</body>
</html>
Jquery script:
$('#generateProgress').on('click', function(){
var val = parseInt($('#value1').val());
var $circle = $('#svg #bar');
if (isNaN(val)) {
val = 100;
}
else{
var r = $circle.attr('r');
var c = Math.PI*(r*2);
if (val < 0) { val = 0;}
if (val > 100) { val = 100;}
var pct = ((100-val)/100)*c;
$circle.css({ strokeDashoffset: pct});
$('#cont').attr('data-pct',val);
}
});
HTML code:
<!DOCTYPE html>
<html>
<body>
<svg id="svg" width="200" height="200" viewport="0 0 100 100" version="1.1" xmlns="http://www.w3.org/2000/svg">
<circle r="90" cx="100" cy="100" fill="transparent" stroke-width="1em" stroke-dasharray="565.48" stroke-dashoffset="0" stroke="#666"></circle>
<circle id="bar" r="90" cx="100" cy="100" fill="transparent" stroke-dasharray="297.48" stroke-dashoffset="0" stroke="red" stroke-width="1em"></circle>
</svg>
Value: <input type="text" value="" id="value1" /><br/>
<input type="button" value="Stroke-percentage" onclick="generateProgress()" />
</body>
</html>
Jquery script:
$('#generateProgress').on('click', function(){
var val = parseInt($('#value1').val());
var $circle = $('#svg #bar');
if (isNaN(val)) {
val = 100;
}
else{
var r = $circle.attr('r');
var c = Math.PI*(r*2);
if (val < 0) { val = 0;}
if (val > 100) { val = 100;}
var pct = ((100-val)/100)*c;
$circle.css({ strokeDashoffset: pct});
$('#cont').attr('data-pct',val);
}
});
Javascript code:
var value = document.getElementById("value1");
function generateProgress() {
var val = parseInt(value);
var circle = document.getElementById('bar');
if (isNaN(val)) {
val = 100;
}
else{
var r = circle.getAttribute('r');
var c = Math.PI*(r*2);
if (val < 0) { val = 0;}
if (val > 100) { val = 100;}
var pct = ((100-val)/100)*c;
circle.style.strokeDashoffset = pct;
}
}
Related
I need a dynamic donut chart with SVG and Vue.
It has to be exactly the same SVG as on this page:
https://www.chipotle.co.uk/nutrition-calculator (to see the chart diagram in motion, first you must choose a dish and then click on some ingredients)
Maybe it was bad direction but that is what I was able find and modify to the task
(https://codepen.io/fifuruho/pen/zYOBWLx)
All code in codeopen.io
I'm really bad at complex svg's.
I need any solutions or tips you can offer.
(but better in code examples)
I would do this in a different way. Instead of using strokes I would use paths. I would make the paths touching one with another and next I would apply the same path (stroke:#000; stroke-width:3; fill: #ffffff) as a mask. You can change the stroke width for a different gap.
This would be the resulting svg:
svg{border:1px solid}
mask use{stroke:#000; stroke-width:3; fill: #ffffff}
<svg width="320" viewBox="-80 -80 160 160" class="donut-chart">
<defs id="theDefs">
<path d="M5.475206276408082,-34.56909192082982 L10.16824022761501,-64.19974213868396 A65,65 0 0 1 10.16824022761501,64.19974213868396 L5.475206276408082,34.56909192082982 A35,35 0 0 0 5.475206276408082,-34.56909192082982" id="elmt45" style="mask: url(#mask45)"></path>
<mask id="mask45">
<use xlink:href="#elmt45"></use>
</mask>
<path d="M18.75393782426488,-29.55147739257053 L34.828741673634774,-54.88131515763098 A65,65 0 0 1 34.828741673634774,54.88131515763098 L18.75393782426488,29.55147739257053 A35,35 0 0 0 18.75393782426488,-29.55147739257053" id="elmt32" style="mask: url(#mask32)"></path>
<mask id="mask32">
<use xlink:href="#elmt32"></use>
</mask>
<path d="M26.253887437066084,-23.145915286327813 L48.75721952597986,-42.98527124603737 A65,65 0 0 1 48.75721952597986,42.98527124603737 L26.253887437066084,23.145915286327813 A35,35 0 0 0 26.253887437066084,-23.145915286327813" id="elmt23" style="mask: url(#mask23)"></path>
<mask id="mask23">
<use xlink:href="#elmt23"></use>
</mask>
</defs>
<g id="theG">
<use xlink:href="#elmt45" fill="rgb(182, 130, 7)" transform="rotate(-9)"></use><use xlink:href="#elmt32" fill="rgb(120, 98, 89)" transform="rotate(129.6)"></use>
<use xlink:href="#elmt23" fill="rgb(195, 180, 166)" transform="rotate(228.6)"></use>
</g>
</svg>
As for the Javascript to produce this, I'm using plain javascript. I hope you'll be able to translate it to Vue.
const SVG_NS = "http://www.w3.org/2000/svg";
const SVG_XLINK = "http://www.w3.org/1999/xlink";
let colors = ["rgb(182, 130, 7)", "rgb(120, 98, 89)", "rgb(195, 180, 166)"];
class Sector {
constructor(color, prev, a) {
this.color = color;
this.R = 65; //external radius
this.r = 35; //inner radius
this.a = a * 360 / 100; //360degs = 100%
this.A = this.a * Math.PI / 180; // angle in radians
this.prev = prev;
this.color = color;
this.elmt = document.createElementNS(SVG_NS, "path");
document.querySelector("#theDefs").appendChild(this.elmt);
this.p1 = {};
this.p2 = {};
this.p3 = {};
this.p4 = {};
this.p1.x = this.r * Math.cos(-this.A / 2);
this.p1.y = this.r * Math.sin(-this.A / 2);
this.p2.x = this.R * Math.cos(-this.A / 2);
this.p2.y = this.R * Math.sin(-this.A / 2);
this.p3.x = this.R * Math.cos(this.A / 2);
this.p3.y = this.R * Math.sin(this.A / 2);
this.p4.x = this.r * Math.cos(this.A / 2);
this.p4.y = this.r * Math.sin(this.A / 2);
this.d = `M${this.p1.x},${this.p1.y} L${this.p2.x},${this.p2.y} A${
this.R
},${this.R} 0 0 1 ${this.p3.x},${this.p3.y} L${this.p4.x},${this.p4.y} A${
this.r
},${this.r} 0 0 0 ${this.p1.x},${this.p1.y}`;
this.elmt.setAttributeNS(null, "d", this.d);
this.elmt.setAttribute("id", `elmt${a}`);
this.mask = document.createElementNS(SVG_NS, "mask");
this.use = document.createElementNS(SVG_NS, "use");
this.use.setAttributeNS(SVG_XLINK, "xlink:href", `#elmt${a}`);
this.mask.appendChild(this.use);
this.mask.setAttribute("id", `mask${a}`);
document.querySelector("#theDefs").appendChild(this.mask);
this.use1 = document.createElementNS(SVG_NS, "use");
this.use1.setAttributeNS(SVG_XLINK, "xlink:href", `#elmt${a}`);
this.use1.setAttributeNS(null, "fill", this.color);
theG.appendChild(this.use1);
this.elmt.setAttribute("style", `mask: url(#mask${a})`);
this.use1.setAttributeNS(
null,
"transform",
`rotate(${-90 + this.a / 2 + this.prev})`
);
}
}
let s1 = new Sector(colors[0], 0, 45);
let s2 = new Sector(colors[1], s1.a, 32);
let s3 = new Sector(colors[2], s1.a + s2.a, 23);
svg{border:1px solid}
mask use{stroke:#000; stroke-width:3; fill: #ffffff}
<svg height="250" width="320" viewBox="-80 -80 160 160" class="donut-chart">
<defs id="theDefs"></defs>
<g id="theG">
</g>
</svg>
I am trying to create a hexagon using an svg polygon.
I want to create the x and why coordinates but my code is not working.
I thought I could use the trig functions by transforming each point by 60 degrees.
It is clearly not working.
const radius = 25;
const points = [0, 1, 2, 3, 4, 5, 6].map((n) => {
const current = n * 60;
return [radius * Math.cos(current), -radius * Math.sin(current)];
}).map((p) => p.join(','))
.join(' ');
document.querySelector('polygon')
.setAttribute("points", "100,0 50,0 100,100");
<svg width="200px" height="200px" viewBox="0 0 200 200">
<polygon points="" style="fill: #ccffcc; stroke: red;stroke-width: 3;"
/>
</svg>
According to this article, it can be converted to javascript like:
const radius = 50;
const height = 200;
const width = 200;
const points = [0, 1, 2, 3, 4, 5, 6].map((n, i) => {
var angle_deg = 60 * i - 30;
var angle_rad = Math.PI / 180 * angle_deg;
return [width/2 + radius * Math.cos(angle_rad), height/2 + radius * Math.sin(angle_rad)];
}).map((p) => p.join(','))
.join(' ');
document.querySelector('polygon')
.setAttribute("points", points);
<svg width="200px" height="200px" viewBox="0 0 200 200">
<polygon points="" style="fill: #ccffcc; stroke: red;stroke-width: 3;"
/>
</svg>
You have one too many indexes in the example above and your actually adding commas in your first join when you don't need to be. Here is a cleaned-up version.
const generateHexPoints = (radius, height, width) => {
const hexPoints = new Array(6)
for (let i = 0; i < hexPoints.length; i++) {
const angleDeg = 60 * i - 30
const angleRad = (Math.PI / 180) * angleDeg;
hexPoints[i] = [
width/2 + radius * Math.cos(angleRad),
height/2 + radius * Math.sin(angleRad)
];
}
return hexPoints.map((p) => p.join(' ')).join(' ')
}
I've got an svg element and I'm adding a simple rotation attribute to it.
Problem is, I want the element to rotate relative to center of itself, not the whole svg, so I specify x and y for rotate, but it has a weird bouncy effect.
let currentAngle = 0;
function rotate() {
d3.select('.group1')
.transition()
.attr('transform', function() {
let bb = this.getBBox();
let rx = bb.x + bb.width / 2;
let ry = bb.y + bb.height / 2;
currentAngle += 90;
return `rotate(${currentAngle}, ${rx}, ${ry})`;
});
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<svg viewbox="0 0 1600 1600" width="500" height="500">
<g class="group1" onclick="rotate()">
<rect x="250" y="250" width="100" height="100" />
<circle cx="420" cy="300" r="50" />
</g>
</svg>
As an alternative, I tried adding transform-origin on css, something like transform-origin: 800px 800px; (but with valid center px of course) and while it works in Chrome, it doesn't work in IE and Safari.
Why is supplying x and y on rotate making my element bounce?
There are a few questions floating around that deal with this issue. Here's one that explains a bit what's going on: D3.js animate rotation
The way Mike Bostock does it here: https://bl.ocks.org/mbostock/3305854 is by placing the object in a <g> that is translated to the position you want and then rotating. This might be the easiest way to get a nice animation. For example:
let currentAngle = 0;
function rotate() {
d3.select('rect')
.transition()
.attr('transform', function() {
let bb = this.getBBox();
let rx = bb.x + bb.width / 2;
let ry = bb.y + bb.height / 2;
currentAngle += 45;
return `rotate(${currentAngle}, ${rx}, ${ry})`;
});
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<svg viewbox="0 0 1600 1600" width="500" height="500">
<g transform="translate(250, 250)">
<rect x="-50", y="-50" width="100" height="100" onclick="rotate()" />
</g>
</svg>
LOOK AT THIS DIFFERENT
if you select by id or by class and rotate it slowly, you will see the red dot in middle rotation move by little, but select by id i thing it more stable not like select by class it bouncing harder
let currentAngle = 0;
let currentAngle2 = 0;
function rotate() {
d3.select('#aa')
.transition()
.duration(300)
.attr('transform', function() {
let bb = this.getBBox();
let rx = bb.x + bb.width / 2;
let ry = bb.y + bb.height / 2;
d3.select(this).append('circle')
.attr('cx',rx)
.attr('cy',ry)
.attr('r',20)
.attr('fill','red')
currentAngle += 10;
return `rotate(${currentAngle}, ${rx}, ${ry})`;
});
}
function rotate2() {
d3.selectAll('.aa')
.transition()
.attr('transform', function() {
let bb = this.getBBox();
let rx = bb.x + bb.width / 2;
let ry = bb.y + bb.height / 2;
d3.select(this).append('circle')
.attr('cx',rx)
.attr('cy',ry)
.attr('r',20)
.attr('fill','red')
currentAngle2 += 10;
return `rotate(${currentAngle2}, ${rx}, ${ry})`;
});
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<svg viewbox="0 0 1600 1600" width="500" height="500">
<g id ='aa' onclick="rotate()">
<rect x="100", y="100" width="100" height="100" />
<circle cx="250" cy="150" r="50"/>
</g>
<g class ='aa' onclick="rotate2()">
<rect x="600", y="100" width="100" height="100" />
<circle cx="750" cy="150" r="50" />
</g>
</svg>
then if you bouncing it with harsh value(big value) you will defintely see the red dot bouncing, but once again select by id doing more stable
let currentAngle = 0;
let currentAngle2 = 0;
function rotate() {
d3.select('#aa')
.transition()
.duration(300)
.attr('transform', function() {
let bb = this.getBBox();
let rx = bb.x + bb.width / 2;
let ry = bb.y + bb.height / 2;
d3.select(this).append('circle')
.attr('cx',rx)
.attr('cy',ry)
.attr('r',20)
.attr('fill','red')
currentAngle += 90;
return `rotate(${currentAngle}, ${rx}, ${ry})`;
});
}
function rotate2() {
d3.selectAll('.aa')
.transition()
.attr('transform', function() {
let bb = this.getBBox();
let rx = bb.x + bb.width / 2;
let ry = bb.y + bb.height / 2;
d3.select(this).append('circle')
.attr('cx',rx)
.attr('cy',ry)
.attr('r',20)
.attr('fill','red')
currentAngle2 += 90;
return `rotate(${currentAngle2}, ${rx}, ${ry})`;
});
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<svg viewbox="0 0 1600 1600" width="500" height="500">
<g id ='aa' onclick="rotate()">
<rect x="100", y="100" width="100" height="100" />
<circle cx="250" cy="150" r="50"/>
</g>
<g class ='aa' onclick="rotate2()">
<rect x="600", y="100" width="100" height="100" />
<circle cx="750" cy="150" r="50" />
</g>
</svg>
SOLUTION
you must make it slowly to rotated to end point, you can make tween
using d3.interpolate, and boom... it rotated perfecly by id or by
class
let currentAngle = 0;
let currentAngle2 = 0;
function rotate() {
d3.select('#aa')
.transition()
.duration(300)
.attrTween("transform", tween);
/*
.attr('transform', function() {
let bb = this.getBBox();
let rx = bb.x + bb.width / 2;
let ry = bb.y + bb.height / 2;
d3.select(this).append('circle')
.attr('cx',rx)
.attr('cy',ry)
.attr('r',20)
.attr('fill','red')
currentAngle += 10;
// console.log(rx,ry,currentAngle)
return `rotate(${currentAngle}, ${rx}, ${ry})`;
});
*/
function tween(d, i, a) {
let bb = this.getBBox();
let rx = bb.x + bb.width / 2;
let ry = bb.y + bb.height / 2;
d3.select(this).append('circle')
.attr('cx',rx)
.attr('cy',ry)
.attr('r',20)
.attr('fill','red')
var e = `rotate(${currentAngle}, ${rx}, ${ry})`
currentAngle += 90;
var o = `rotate(${currentAngle}, ${rx}, ${ry})`
return d3.interpolateString(e,o);
}
}
function rotate2() {
d3.selectAll('.aa')
.transition()
.duration(300)
.attrTween("transform", tween);
/*
.attr('transform', function() {
let bb = this.getBBox();
let rx = bb.x + bb.width / 2;
let ry = bb.y + bb.height / 2;
d3.select(this).append('circle')
.attr('cx',rx)
.attr('cy',ry)
.attr('r',20)
.attr('fill','red')
currentAngle2 += 10;
// console.log(rx,ry,currentAngle)
return `rotate(${currentAngle2}, ${rx}, ${ry})`;
});
*/
function tween(d, i, a) {
let bb = this.getBBox();
let rx = bb.x + bb.width / 2;
let ry = bb.y + bb.height / 2;
d3.select(this).append('circle')
.attr('cx',rx)
.attr('cy',ry)
.attr('r',20)
.attr('fill','red')
var e = `rotate(${currentAngle2}, ${rx}, ${ry})`
currentAngle2 += 90;
var o = `rotate(${currentAngle2}, ${rx}, ${ry})`
return d3.interpolateString(e,o);
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<svg viewbox="0 0 1600 1600" width="500" height="500">
<g id ='aa' onclick="rotate()">
<rect x="100", y="100" width="100" height="100" />
<circle cx="250" cy="150" r="50"/>
</g>
<g class ='aa' onclick="rotate2()">
<rect x="600", y="100" width="100" height="100" />
<circle cx="750" cy="150" r="50" />
</g>
</svg>
I am trying to animate SVG's in a web browser using JavaScript. My current methodology is using innerHTML:
var e = new entity();
function draw() {
element = document.getElementById("canvas");
e.radius += .1;
e.pos[0] += .1; e.pos[1] += .1;
var insides = "";
insides += '<svg height="80%" viewBox="0 0 100 100">' + e.show() + '</svg>';
element.innerHTML = insides;
}
function entity() {
this.pos = [0, 0];
this.radius = 1;
this.show = function() {
return '<circle cx="' + String(this.pos[0]) + '" cy="' + String(this.pos[1]) + '" r="' + String(this.radius) + '" />';
}
}
window.setInterval(draw, 60);
<div id="canvas" style="text-align:center;"></div>
I want to make sure I am not wasting too many resources, so are there any ways that are less resource-intensive in the HTML document/JavaScript to do controlled interactive animations with SVG's such as this?
Recreating the whole SVG every time is extremely inefficient. Just update the geometry attributes.
var e = new entity();
function draw() {
element = document.getElementById("canvas");
e.radius += .1;
e.pos[0] += .1; e.pos[1] += .1;
e.show();
}
function entity() {
this.element = document.getElementById("mycircle");
this.pos = [0, 0];
this.radius = 1;
this.show = function() {
this.element.cx.baseVal.value = this.pos[0];
this.element.cy.baseVal.value = this.pos[1];
this.element.r.baseVal.value = this.radius;
}
}
window.setInterval(draw, 60);
<div id="canvas" style="text-align:center;">
<svg height="80%" viewBox="0 0 100 100">
<circle id="mycircle" cx="0" cy="0" r="0" />
</svg>
</div>
I found some code for a chart on Codepen (credits: Henry Poydar) that uses snap.svg to create an animated gauge chart.
angular.module('app', []);
angular.module('app')
.controller('metricsCtrl', function($scope) {
$scope.percentage = .8;
var polar_to_cartesian, svg_circle_arc_path, animate_arc;
polar_to_cartesian = function(cx, cy, radius, angle) {
var radians;
radians = (angle - 90) * Math.PI / 180.0;
return [Math.round((cx + (radius * Math.cos(radians))) * 100) / 100, Math.round((cy + (radius * Math.sin(radians))) * 100) / 100];
};
svg_circle_arc_path = function(x, y, radius, start_angle, end_angle) {
var end_xy, start_xy;
start_xy = polar_to_cartesian(x, y, radius, end_angle);
end_xy = polar_to_cartesian(x, y, radius, start_angle);
return "M " + start_xy[0] + " " + start_xy[1] + " A " + radius + " " + radius + " 0 0 0 " + end_xy[0] + " " + end_xy[1];
};
animate_arc = function(ratio, svg, perc) {
var arc;
arc = svg.path('');
return Snap.animate(0, ratio, (function(val) {
var path;
arc.remove();
path = svg_circle_arc_path(500, 500, 450, -90, val * 180.0 - 90);
arc = svg.path(path);
arc.attr({
class: 'data-arc'
});
perc.text(Math.round(val * 100) + '%');
}), Math.round(2000 * ratio), mina.easeinout);
};
$scope.$watch('percentage', function() {
$('.metric').each(function() {
var ratio, svg, perc;
//ratio = $(this).data('ratio');
ratio = $scope.percentage;
svg = Snap($(this).find('svg')[0]);
perc = $(this).find('text.percentage');
animate_arc(ratio, svg, perc);
});
});
});
.metric {
padding: 10%;
}
.metric svg {
max-width: 100%;
}
.metric path {
stroke-width: 75;
stroke: #ecf0f1;
fill: none;
}
.metric path.data-arc {
stroke: #3498db;
}
.metric text {
fill: #3498db;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/snap.svg/0.4.1/snap.svg-min.js"></script>
<div ng-app="app" ng-controller="metricsCtrl">
<div class="metric">
<svg viewBox="0 0 1000 500">
<path d="M 950 500 A 450 450 0 0 0 50 500"></path>
<text class='percentage' text-anchor="middle" alignment-baseline="middle" x="500" y="300" font-size="140" font-weight="bold">0%
</text>
<text class='title' text-anchor="middle" alignment-baseline="middle" x="500" y="450" font-size="90" font-weight="normal">Empty
</text>
</svg>
</div>
<input ng-model="percentage">
</div>
I would like to be able to dynamically update the charts data and have the SVG render accordingly. I am able to get the chart to show an increase value, but a decrease in value is not working. Here is a demo that reproduces my problem http://codepen.io/EvanWieland/pen/bpxqpV. In the demo, if you increase the value in the input below the chart and then decrease it, you will be able to observe my dilemma. Note that the demo uses Angularjs, this is not a requirement. Thanks in advanced!
This was due to "svg.path(path)" that creates a new arc each time, thus decreasing value draws an arc hidden by the previous ones. The solution is to remove the previous arc at each repaint.
angular.module('app', []);
angular.module('app')
.controller('metricsCtrl', function($scope) {
$scope.percentage = .8;
var polar_to_cartesian, svg_circle_arc_path, animate_arc;
polar_to_cartesian = function(cx, cy, radius, angle) {
var radians;
radians = (angle - 90) * Math.PI / 180.0;
return [Math.round((cx + (radius * Math.cos(radians))) * 100) / 100, Math.round((cy + (radius * Math.sin(radians))) * 100) / 100];
};
svg_circle_arc_path = function(x, y, radius, start_angle, end_angle) {
var end_xy, start_xy;
start_xy = polar_to_cartesian(x, y, radius, end_angle);
end_xy = polar_to_cartesian(x, y, radius, start_angle);
return "M " + start_xy[0] + " " + start_xy[1] + " A " + radius + " " + radius + " 0 0 0 " + end_xy[0] + " " + end_xy[1];
};
animate_arc = function(ratio, svg, perc) {
var arc;
arc = svg.path('');
return Snap.animate(0, ratio, (function(val) {
var path;
arc.remove();
path = svg_circle_arc_path(500, 500, 450, -90, val * 180.0 - 90);
var previousArc = svg.select('.data-arc')
if (previousArc){
previousArc.remove(); // REMOVES PREVIOUS ARC
}
arc = svg.path(path);
arc.attr({
class: 'data-arc'
});
perc.text(Math.round(val * 100) + '%');
}), Math.round(2000 * ratio), mina.easeinout);
};
$scope.$watch('percentage', function() {
$('.metric').each(function() {
var ratio, svg, perc;
//ratio = $(this).data('ratio');
ratio = $scope.percentage;
svg = Snap($(this).find('svg')[0]);
perc = $(this).find('text.percentage');
animate_arc(ratio, svg, perc);
});
});
});
.metric {
padding: 10%;
}
.metric svg {
max-width: 100%;
}
.metric path {
stroke-width: 75;
stroke: #ecf0f1;
fill: none;
}
.metric path.data-arc {
stroke: #3498db;
}
.metric text {
fill: #3498db;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/snap.svg/0.4.1/snap.svg-min.js"></script>
<div ng-app="app" ng-controller="metricsCtrl">
<div class="metric">
<svg viewBox="0 0 1000 500">
<path d="M 950 500 A 450 450 0 0 0 50 500"></path>
<text class='percentage' text-anchor="middle" alignment-baseline="middle" x="500" y="300" font-size="140" font-weight="bold">0%
</text>
<text class='title' text-anchor="middle" alignment-baseline="middle" x="500" y="450" font-size="90" font-weight="normal">Empty
</text>
</svg>
</div>
<input ng-model="percentage">
</div>