Why does rotating element make it bounce? - javascript

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>

Related

Recreating the dynamic donut chart with SVG and Vue

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>

display percentage arc in javascript [duplicate]

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;
}
}

Optimizing Interactive SVG JavaScript Animation

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>

Dynamically update Snap.svg gauge chart

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>

How to use requestAnimationFrame in mousemove event?

In my SVG based web application, a user can select a large number of shapes (even 800 or more) & move them about on the screen, which as one can imagine severely impacts the framerate. After reading the merits of requestAnimationFrame, I have been working since yesterday trying to incorporate it into my mousemove function, with the hope that by using a combination of throttling & requestAnimationFrame, I can get to a smooth framerate.
Here is the jsFiddle in which I am moving 600 svg shapes on mousemove. Ignoring throttling for now, how can I incorporate requestAnimationFrame into the mousemove function.
http://jsfiddle.net/rehankhalid/5t5pX/
HTML CODE
<svg width="1200" height="800">
<symbol>
<g id="symbol1">
<path fill="#3ab6d1" d="M27.7,1.1C16-2,3.9,5.1,0.8,16.9s4,23.8,15.7,26.9c11.7,3.1,23.8-4,26.9-15.7C46.6,16.3,39.6,4.2,27.7,1.1z M38.3,26.9C36,35.7,26.9,41,18,38.7C9.1,36.4,3.8,27.3,6.1,18.5C8.4,9.6,17.5,4.3,26.4,6.6C35.3,9,40.6,18,38.3,26.9z"/>
<g fill="#d5e1db">
<path d="m25.8,.8c2.5,.4 4.8,1.2 7,2.5l-2.9,4.6c-1.5-.8-3.1-1.4-4.8-1.7l.7-5.4z"/>
<path d="m13.9,2.2l1.7,5.1c-1.5,.7-3,1.5-4.3,2.7l-3.8-3.9c.9-.8 1.9-1.5 2.9-2.2 1.2-.7 2.3-1.3 3.5-1.7z"/>
</g>
<path fill="#196275" d="m26.4,38.5l1.8,5.2c-2.5,.7-4.9,.9-7.4,.8l.6-5.4c1.6,0 3.3-.2 5-.6z"/>
<path fill="#42aec2" d="M6.1,18.4C8.4,9.5,17.5,4.2,26.4,6.5S40.7,18,38.4,26.8C36,35.7,26.9,41,18,38.7C9.1,36.3,3.7,27.2,6.1,18.4 z"/>
<path fill="#d2dfdd" d="m28.7,25.9l.5-4.9 9.5-1.3c0,.3 .1,.6 .2,.8 .4,2.9 0,5.8-1,8.3l-9.2-2.9z"/>
<path fill="#1b6273" d="m19.7,29.3h4.9l2.1,9.4c-.3,.1-.6,.2-.8,.2-2.9,.6-5.7,.5-8.4-.3l2.2-9.3z"/>
<path fill="#368098" d="m15.9,25.8l3.6,3.4-4.8,8.3c-.3-.1-.5-.3-.8-.4-2.6-1.5-4.6-3.5-6-5.9l8-5.4z"/>
<path fill="#d2dfdd" d="m18.6,16.5l-3,3.9-8.7-4c.1-.3 .2-.5 .3-.8 1.2-2.7 3.1-4.9 5.3-6.5l6.1,7.4z"/>
<path fill="#bde2e9" d="m26.4,32.1l-8.4-.5-4.9-6.8 2.3-8 7.7-3.2 7.3,4 1.5,8.2-5.5,6.3z"/>
</g>
</symbol>
<g id="default">
<g id="my-element">
</g>
</g>
<g id="draggroup">
</g>
</svg>
Javascript
document.addEventListener('mousedown', mousedown, false);
var element = document.getElementById('my-element');
var defaultg = document.getElementById('default');
var mainsvg = document.getElementsByTagName('svg')[0];
var draggroup = document.getElementById('draggroup');
var svgns = "http://www.w3.org/2000/svg";
var x = 0, y = 0;
var scale = '0.25';
for (var i = 0; i < 600; i++) {
var useelement = document.createElementNS(svgns, "use");
useelement.setAttributeNS('http://www.w3.org/1999/xlink', 'href', '#symbol1');
useelement.setAttribute('transform', 'translate(' + x + ',' + y + ') scale(' + scale + ')');
x += 12;
if (x === 240) {
x = 0;
y += 12;
}
element.appendChild(useelement);
}
var bbox = element.getBBox();
var selectionRect = document.createElementNS(svgns, "rect");
selectionRect.setAttribute('id', 'selectionrect');
selectionRect.setAttribute('x', bbox.x);
selectionRect.setAttribute('y', bbox.y);
selectionRect.setAttribute('width', bbox.width);
selectionRect.setAttribute('height', bbox.height);
selectionRect.setAttribute('fill', 'grey');
selectionRect.setAttribute('opacity', '0.2');
draggroup.appendChild(selectionRect);
var dx = 0, dy = 0, mx = 0, my = 0;
var rectdx = 0, rectdy = 0;
var elementdx = 0, elementdy = 0;
function getSvgCordinates(event) {
var m = mainsvg.getScreenCTM();
var p = mainsvg.createSVGPoint();
var x, y;
x = event.pageX;
y = event.pageY;
p.x = x;
p.y = y;
p = p.matrixTransform(m.inverse());
x = p.x;
y = p.y;
x = parseFloat(x.toFixed(3));
y = parseFloat(y.toFixed(3));
return {x: x, y: y};
}
function mousedown(event) {
if (event.target.id === 'selectionrect') {
var svgXY = getSvgCordinates(event);
mx = svgXY.x; // mouse down x
my = svgXY.y;// mouse down y
draggroup.appendChild(element);
document.addEventListener('mousemove', mousemove, false);
document.addEventListener('mouseup', mouseup, false);
}
}
function mouseup() {
document.removeEventListener('mousemove', mousemove, false);
document.removeEventListener('mouseup', mouseup, false);
draggroup.setAttribute('transform', 'translate(0,0)');
rectdx += dx;
rectdy += dy;
elementdx += dx;
elementdy += dy;
selectionRect.setAttribute('transform', 'translate(' + rectdx + ',' + rectdy + ')');
element.setAttribute('transform', 'translate(' + elementdx + ',' + elementdy + ')');
defaultg.appendChild(element);
dx = 0;
dy = 0;
}
function mousemove(event) {
var svgXY = getSvgCordinates(event);
dx = svgXY.x - mx;
dy = svgXY.y - my;
draggroup.setAttribute('transform', 'translate(' + dx + ',' + dy + ')');
}
Basically, you'd need to move the draggroup.setAttribute() calls to another function. Since you only need to animate when the mouse is down, add another variable to indicate whether you're dragging or not, and only call requestAnimationFrame when that's the case.
var isDragging = false;
function update() { // New function
if (isDragging) {
requestAnimationFrame(update); // Call self again, if still dragging
}
draggroup.setAttribute('transform', 'translate(' + dx + ',' + dy + ')');
}
function mousedown(event) {
if (event.target.id === 'selectionrect') {
isDragging = true;
requestAnimationFrame(update); // Start animation loop
// existing logic here...
}
}
function mouseup() {
isDragging = false;
// existing logic here...
}
You're already storing the updated location for the group (dx, dy) in a separate variable, so remove the draggroup.setAttribute() call from mousemove() and add the above modifications, and you should be good!

Categories