Light weight jQuery plugin specialized only in drawing doughnut chart. Also please check out Pie chart version of this! http://codepen.io/githiro/pen/xABCi Github repository: https://github.com/githiro/drawDoughnutChart
Created
December 13, 2018 18:10
-
-
Save scoaband/dee8198e23614485a2a6cd24467ceee5 to your computer and use it in GitHub Desktop.
SVG Doughnut chart with animation and tooltip
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <div id="doughnutChart" class="chart"></div> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| $(function(){ | |
| $("#doughnutChart").drawDoughnutChart([ | |
| { title: "Tokyo", value : 120, color: "#2C3E50" }, | |
| { title: "San Francisco", value: 80, color: "#FC4349" }, | |
| { title: "New York", value: 70, color: "#6DBCDB" }, | |
| { title: "London", value : 50, color: "#F7E248" }, | |
| { title: "Sydney", value : 40, color: "#D7DADB" }, | |
| { title: "Berlin", value : 20, color: "#FFF" } | |
| ]); | |
| }); | |
| /*! | |
| * jquery.drawDoughnutChart.js | |
| * Version: 0.4.1(Beta) | |
| * Inspired by Chart.js(http://www.chartjs.org/) | |
| * | |
| * Copyright 2014 hiro | |
| * https://github.com/githiro/drawDoughnutChart | |
| * Released under the MIT license. | |
| * | |
| */ | |
| ;(function($, undefined) { | |
| $.fn.drawDoughnutChart = function(data, options) { | |
| var $this = this, | |
| W = $this.width(), | |
| H = $this.height(), | |
| centerX = W/2, | |
| centerY = H/2, | |
| cos = Math.cos, | |
| sin = Math.sin, | |
| PI = Math.PI, | |
| settings = $.extend({ | |
| segmentShowStroke : true, | |
| segmentStrokeColor : "#0C1013", | |
| segmentStrokeWidth : 1, | |
| baseColor: "rgba(0,0,0,0.5)", | |
| baseOffset: 4, | |
| edgeOffset : 10,//offset from edge of $this | |
| percentageInnerCutout : 75, | |
| animation : true, | |
| animationSteps : 90, | |
| animationEasing : "easeInOutExpo", | |
| animateRotate : true, | |
| tipOffsetX: -8, | |
| tipOffsetY: -45, | |
| tipClass: "doughnutTip", | |
| summaryClass: "doughnutSummary", | |
| summaryTitle: "TOTAL:", | |
| summaryTitleClass: "doughnutSummaryTitle", | |
| summaryNumberClass: "doughnutSummaryNumber", | |
| beforeDraw: function() { }, | |
| afterDrawed : function() { }, | |
| onPathEnter : function(e,data) { }, | |
| onPathLeave : function(e,data) { } | |
| }, options), | |
| animationOptions = { | |
| linear : function (t) { | |
| return t; | |
| }, | |
| easeInOutExpo: function (t) { | |
| var v = t<.5 ? 8*t*t*t*t : 1-8*(--t)*t*t*t; | |
| return (v>1) ? 1 : v; | |
| } | |
| }, | |
| requestAnimFrame = function() { | |
| return window.requestAnimationFrame || | |
| window.webkitRequestAnimationFrame || | |
| window.mozRequestAnimationFrame || | |
| window.oRequestAnimationFrame || | |
| window.msRequestAnimationFrame || | |
| function(callback) { | |
| window.setTimeout(callback, 1000 / 60); | |
| }; | |
| }(); | |
| settings.beforeDraw.call($this); | |
| var $svg = $('<svg width="' + W + '" height="' + H + '" viewBox="0 0 ' + W + ' ' + H + '" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"></svg>').appendTo($this), | |
| $paths = [], | |
| easingFunction = animationOptions[settings.animationEasing], | |
| doughnutRadius = Min([H / 2,W / 2]) - settings.edgeOffset, | |
| cutoutRadius = doughnutRadius * (settings.percentageInnerCutout / 100), | |
| segmentTotal = 0; | |
| //Draw base doughnut | |
| var baseDoughnutRadius = doughnutRadius + settings.baseOffset, | |
| baseCutoutRadius = cutoutRadius - settings.baseOffset; | |
| $(document.createElementNS('http://www.w3.org/2000/svg', 'path')) | |
| .attr({ | |
| "d": getHollowCirclePath(baseDoughnutRadius, baseCutoutRadius), | |
| "fill": settings.baseColor | |
| }) | |
| .appendTo($svg); | |
| //Set up pie segments wrapper | |
| var $pathGroup = $(document.createElementNS('http://www.w3.org/2000/svg', 'g')); | |
| $pathGroup.attr({opacity: 0}).appendTo($svg); | |
| //Set up tooltip | |
| var $tip = $('<div class="' + settings.tipClass + '" />').appendTo('body').hide(), | |
| tipW = $tip.width(), | |
| tipH = $tip.height(); | |
| //Set up center text area | |
| var summarySize = (cutoutRadius - (doughnutRadius - cutoutRadius)) * 2, | |
| $summary = $('<div class="' + settings.summaryClass + '" />') | |
| .appendTo($this) | |
| .css({ | |
| width: summarySize + "px", | |
| height: summarySize + "px", | |
| "margin-left": -(summarySize / 2) + "px", | |
| "margin-top": -(summarySize / 2) + "px" | |
| }); | |
| var $summaryTitle = $('<p class="' + settings.summaryTitleClass + '">' + settings.summaryTitle + '</p>').appendTo($summary); | |
| var $summaryNumber = $('<p class="' + settings.summaryNumberClass + '"></p>').appendTo($summary).css({opacity: 0}); | |
| for (var i = 0, len = data.length; i < len; i++) { | |
| segmentTotal += data[i].value; | |
| $paths[i] = $(document.createElementNS('http://www.w3.org/2000/svg', 'path')) | |
| .attr({ | |
| "stroke-width": settings.segmentStrokeWidth, | |
| "stroke": settings.segmentStrokeColor, | |
| "fill": data[i].color, | |
| "data-order": i | |
| }) | |
| .appendTo($pathGroup) | |
| .on("mouseenter", pathMouseEnter) | |
| .on("mouseleave", pathMouseLeave) | |
| .on("mousemove", pathMouseMove); | |
| } | |
| //Animation start | |
| animationLoop(drawPieSegments); | |
| //Functions | |
| function getHollowCirclePath(doughnutRadius, cutoutRadius) { | |
| //Calculate values for the path. | |
| //We needn't calculate startRadius, segmentAngle and endRadius, because base doughnut doesn't animate. | |
| var startRadius = -1.570,// -Math.PI/2 | |
| segmentAngle = 6.2831,// 1 * ((99.9999/100) * (PI*2)), | |
| endRadius = 4.7131,// startRadius + segmentAngle | |
| startX = centerX + cos(startRadius) * doughnutRadius, | |
| startY = centerY + sin(startRadius) * doughnutRadius, | |
| endX2 = centerX + cos(startRadius) * cutoutRadius, | |
| endY2 = centerY + sin(startRadius) * cutoutRadius, | |
| endX = centerX + cos(endRadius) * doughnutRadius, | |
| endY = centerY + sin(endRadius) * doughnutRadius, | |
| startX2 = centerX + cos(endRadius) * cutoutRadius, | |
| startY2 = centerY + sin(endRadius) * cutoutRadius; | |
| var cmd = [ | |
| 'M', startX, startY, | |
| 'A', doughnutRadius, doughnutRadius, 0, 1, 1, endX, endY,//Draw outer circle | |
| 'Z',//Close path | |
| 'M', startX2, startY2,//Move pointer | |
| 'A', cutoutRadius, cutoutRadius, 0, 1, 0, endX2, endY2,//Draw inner circle | |
| 'Z' | |
| ]; | |
| cmd = cmd.join(' '); | |
| return cmd; | |
| }; | |
| function pathMouseEnter(e) { | |
| var order = $(this).data().order; | |
| $tip.text(data[order].title + ": " + data[order].value) | |
| .fadeIn(200); | |
| settings.onPathEnter.apply($(this),[e,data]); | |
| } | |
| function pathMouseLeave(e) { | |
| $tip.hide(); | |
| settings.onPathLeave.apply($(this),[e,data]); | |
| } | |
| function pathMouseMove(e) { | |
| $tip.css({ | |
| top: e.pageY + settings.tipOffsetY, | |
| left: e.pageX - $tip.width() / 2 + settings.tipOffsetX | |
| }); | |
| } | |
| function drawPieSegments (animationDecimal) { | |
| var startRadius = -PI / 2,//-90 degree | |
| rotateAnimation = 1; | |
| if (settings.animation && settings.animateRotate) rotateAnimation = animationDecimal;//count up between0~1 | |
| drawDoughnutText(animationDecimal, segmentTotal); | |
| $pathGroup.attr("opacity", animationDecimal); | |
| //If data have only one value, we draw hollow circle(#1). | |
| if (data.length === 1 && (4.7122 < (rotateAnimation * ((data[0].value / segmentTotal) * (PI * 2)) + startRadius))) { | |
| $paths[0].attr("d", getHollowCirclePath(doughnutRadius, cutoutRadius)); | |
| return; | |
| } | |
| for (var i = 0, len = data.length; i < len; i++) { | |
| var segmentAngle = rotateAnimation * ((data[i].value / segmentTotal) * (PI * 2)), | |
| endRadius = startRadius + segmentAngle, | |
| largeArc = ((endRadius - startRadius) % (PI * 2)) > PI ? 1 : 0, | |
| startX = centerX + cos(startRadius) * doughnutRadius, | |
| startY = centerY + sin(startRadius) * doughnutRadius, | |
| endX2 = centerX + cos(startRadius) * cutoutRadius, | |
| endY2 = centerY + sin(startRadius) * cutoutRadius, | |
| endX = centerX + cos(endRadius) * doughnutRadius, | |
| endY = centerY + sin(endRadius) * doughnutRadius, | |
| startX2 = centerX + cos(endRadius) * cutoutRadius, | |
| startY2 = centerY + sin(endRadius) * cutoutRadius; | |
| var cmd = [ | |
| 'M', startX, startY,//Move pointer | |
| 'A', doughnutRadius, doughnutRadius, 0, largeArc, 1, endX, endY,//Draw outer arc path | |
| 'L', startX2, startY2,//Draw line path(this line connects outer and innner arc paths) | |
| 'A', cutoutRadius, cutoutRadius, 0, largeArc, 0, endX2, endY2,//Draw inner arc path | |
| 'Z'//Cloth path | |
| ]; | |
| $paths[i].attr("d", cmd.join(' ')); | |
| startRadius += segmentAngle; | |
| } | |
| } | |
| function drawDoughnutText(animationDecimal, segmentTotal) { | |
| $summaryNumber | |
| .css({opacity: animationDecimal}) | |
| .text((segmentTotal * animationDecimal).toFixed(1)); | |
| } | |
| function animateFrame(cnt, drawData) { | |
| var easeAdjustedAnimationPercent =(settings.animation)? CapValue(easingFunction(cnt), null, 0) : 1; | |
| drawData(easeAdjustedAnimationPercent); | |
| } | |
| function animationLoop(drawData) { | |
| var animFrameAmount = (settings.animation)? 1 / CapValue(settings.animationSteps, Number.MAX_VALUE, 1) : 1, | |
| cnt =(settings.animation)? 0 : 1; | |
| requestAnimFrame(function() { | |
| cnt += animFrameAmount; | |
| animateFrame(cnt, drawData); | |
| if (cnt <= 1) { | |
| requestAnimFrame(arguments.callee); | |
| } else { | |
| settings.afterDrawed.call($this); | |
| } | |
| }); | |
| } | |
| function Max(arr) { | |
| return Math.max.apply(null, arr); | |
| } | |
| function Min(arr) { | |
| return Math.min.apply(null, arr); | |
| } | |
| function isNumber(n) { | |
| return !isNaN(parseFloat(n)) && isFinite(n); | |
| } | |
| function CapValue(valueToCap, maxValue, minValue) { | |
| if (isNumber(maxValue) && valueToCap > maxValue) return maxValue; | |
| if (isNumber(minValue) && valueToCap < minValue) return minValue; | |
| return valueToCap; | |
| } | |
| return $this; | |
| }; | |
| })(jQuery); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <script src="//ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| @import url(//fonts.googleapis.com/css?family=Oswald:400); | |
| body { | |
| background: #222428; | |
| font-family: "Oswald", sans-serif; | |
| } | |
| .chart { | |
| position: absolute; | |
| width: 450px; | |
| height: 450px; | |
| top: 50%; | |
| left: 50%; | |
| margin: -225px 0 0 -225px; | |
| } | |
| .doughnutTip { | |
| position: absolute; | |
| min-width: 30px; | |
| max-width: 300px; | |
| padding: 5px 15px; | |
| border-radius: 1px; | |
| background: rgba(0,0,0,.8); | |
| color: #ddd; | |
| font-size: 17px; | |
| text-shadow: 0 1px 0 #000; | |
| text-transform: uppercase; | |
| text-align: center; | |
| line-height: 1.3; | |
| letter-spacing: .06em; | |
| box-shadow: 0 1px 3px rgba(0,0,0,0.5); | |
| pointer-events: none; | |
| &::after { | |
| position: absolute; | |
| left: 50%; | |
| bottom: -6px; | |
| content: ""; | |
| height: 0; | |
| margin: 0 0 0 -6px; | |
| border-right: 5px solid transparent; | |
| border-left: 5px solid transparent; | |
| border-top: 6px solid rgba(0,0,0,.7); | |
| line-height: 0; | |
| } | |
| } | |
| .doughnutSummary { | |
| position: absolute; | |
| top: 50%; | |
| left: 50%; | |
| color: #d5d5d5; | |
| text-align: center; | |
| text-shadow: 0 -1px 0 #111; | |
| cursor: default; | |
| } | |
| .doughnutSummaryTitle { | |
| position: absolute; | |
| top: 50%; | |
| width: 100%; | |
| margin-top: -27%; | |
| font-size: 22px; | |
| letter-spacing: .06em; | |
| } | |
| .doughnutSummaryNumber { | |
| position: absolute; | |
| top: 50%; | |
| width: 100%; | |
| margin-top: -15%; | |
| font-size: 55px; | |
| } | |
| .chart path:hover { opacity: 0.65; } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment