By Philippe Leefsma (@F3lipek)
Here is some entertainment material to finish the week: I'm working on a project where I'm using some 2D markers overlaid on top of my View & Data 3D models. To achieve that feature no doubt that SVG is the best tool, this lets you produce beautiful graphics which can be animated and styled at will. There are plenty of JavaScript libraries that wraps svg and alleviate working with it. I used in the past Raphaƫl and Paper.js but the new cool kid in town is Snap.svg a nice and friendly documented tool.
So what I needed is to easily create a simple custom symbol looking like a pie chart, so there are a bunch of libs out there that helps create graphs and charts. I used Chart.js in one of my previous post. However it looked like an overblown for my use case. An svg arc path can do the trick and ended up being fun and more flexible than relying on yet another lib.
Here is how an arc path has to be defined, see there for more details about paths:
A rx ry x-axis-rotation large-arc-flag sweep-flag x y
a rx ry x-axis-rotation large-arc-flag sweep-flag dx dy
The large-arc and sweep flags might be a bit tricky to figure out, so here is a pictures that helps a lot:
Browsing the web, I found an example by Rachel Smith that helped me getting started with snap.svg and arc paths. I tweaked the original code to be more flexible and allows creating a pie chart sector or donut sector by specifying arguments like inner/outer radius, start angle and delta angle:
1 /////////////////////////////////////////////////////////////////// 2 // Draws a pie sector 3 // 4 /////////////////////////////////////////////////////////////////// 5 function drawPieSector(snap, centre, 6 rIn, rOut, startDeg, delta, attr) { 7 8 var startOut = { 9 x: centre.x + rOut * Math.cos(Math.PI*(startDeg)/180), 10 y: centre.y + rOut * Math.sin(Math.PI*(startDeg)/180) 11 }; 12 13 var endOut = { 14 x: centre.x + rOut * Math.cos(Math.PI*(startDeg + delta)/180), 15 y: centre.y + rOut * Math.sin(Math.PI*(startDeg + delta)/180) 16 }; 17 18 var startIn = { 19 x: centre.x + rIn * Math.cos(Math.PI*(startDeg + delta)/180), 20 y: centre.y + rIn * Math.sin(Math.PI*(startDeg + delta)/180) 21 }; 22 23 var endIn = { 24 x: centre.x + rIn * Math.cos(Math.PI*(startDeg)/180), 25 y: centre.y + rIn * Math.sin(Math.PI*(startDeg)/180) 26 }; 27 28 var largeArc = delta > 180 ? 1 : 0; 29 30 var path = "M" + startOut.x + "," + startOut.y + 31 " A" + rOut + "," + rOut + " 0 " + 32 largeArc + ",1 " + endOut.x + "," + endOut.y + 33 " L" + startIn.x + "," + startIn.y + 34 " A" + rIn + "," + rIn + " 0 " + 35 largeArc + ",0 " + endIn.x + "," + endIn.y + 36 " L" + startOut.x + "," + startOut.y + " Z"; 37 38 var path = snap.path(path); 39 40 path.attr(attr); 41 42 return path; 43 }
The tweaked example from Rachel looks like this when rewritten with my pie sector function:
1 /////////////////////////////////////////////////////////////////// 2 // Draws the percent counter 3 // 4 /////////////////////////////////////////////////////////////////// 5 function run(percent) { 6 7 var snap = Snap('#svg1'); 8 9 var attr = { 10 stroke: '#3da08d', 11 fill: '#3da08d', 12 fillOpacity: 0.5, 13 strokeWidth: 1 14 }; 15 16 Snap.animate(0, percent * 359.99, function (deg) { 17 18 if(sector) sector.remove(); 19 20 sector = drawPieSector(snap, 21 {x:100, y:100}, 22 50, 95, 23 0, deg, 24 attr); 25 26 percDiv.innerHTML = Math.round(deg/360 * 100) +'%'; 27 28 }, 1000, mina.easeinout); 29 }This wouldn't be fun without an interactive example (change value in input and hit run again):
We can now easily write another utility method to draw an entire pie chart creating a sector for each entry in the data argument:
1 /////////////////////////////////////////////////////////////////// 2 // Draws a pie chart 3 // 4 /////////////////////////////////////////////////////////////////// 5 function drawPie(snap, centre, rIn, rOut, data) { 6 7 var total = 0; 8 9 for(var key in data){ 10 11 total += data[key].value; 12 } 13 14 var startDeg = 0; 15 16 var pie = snap.group(); 17 18 for(var key in data){ 19 20 var delta = 359.99 * data[key].value / total; 21 22 var sector = drawPieSector(snap, centre, 23 rIn, rOut, startDeg, delta, data[key].attr); 24 25 pie.add(sector); 26 27 startDeg += delta; 28 } 29 30 return pie; 31 }And to use our pie chart function a little test that displays three sector for RGB color values and blends the result at the centre:
1 /////////////////////////////////////////////////////////////////// 2 // Draws the color chart 3 // 4 /////////////////////////////////////////////////////////////////// 5 function drawColorChart() { 6 7 var r = parseInt(document.getElementById('inputR').value); 8 var g = parseInt(document.getElementById('inputG').value); 9 var b = parseInt(document.getElementById('inputB').value); 10 11 var data = { 12 Red: { 13 value: r, 14 attr: { 15 stroke: 'rgb(' + r + ', 0, 0)', 16 fill: 'rgb(' + r + ', 0, 0)', 17 fillOpacity: 0.5, 18 strokeWidth: 1 19 } 20 }, 21 Green: { 22 value: g, 23 attr: { 24 stroke: 'rgb(0, ' + g + ', 0)', 25 fill: 'rgb(0, ' + g + ', 0)', 26 fillOpacity: 0.5, 27 strokeWidth: 1 28 } 29 }, 30 Blue: { 31 value: b, 32 attr: { 33 stroke: 'rgb(0, 0, ' + b + ')', 34 fill: 'rgb(0, 0, ' + b + ')', 35 fillOpacity: 0.5, 36 strokeWidth: 1 37 } 38 } 39 } 40 41 var snap = Snap('#svg2'); 42 43 if(chart) chart.remove(); 44 45 var pie = drawPie(snap, 46 {x:100, y:100}, 47 50, 95, data); 48 49 var circle = snap.circle(100, 100, 50); 50 51 circle.attr({ 52 stroke: 'rgb(' + r + ',' + g + ', ' + b + ')', 53 fill: 'rgb(' + r + ',' + g + ', ' + b + ')', 54 fillOpacity: 1, 55 strokeWidth: 1 56 }); 57 58 chart = snap.group(pie, circle); 59 }Give it a try there:
Full source code for both sample below:
Comments
You can follow this conversation by subscribing to the comment feed for this post.