From 86f806313c506c3d3e532b961cddec6546277e34 Mon Sep 17 00:00:00 2001 From: wql7013 <125299886@qq.com> Date: Fri, 1 May 2015 21:18:44 +0800 Subject: [PATCH 1/3] 1.defined as AMD and CommonJS module; 2.use pattern in title string; 3.value can be a string that containing unit --- jquery.drawDoughnutChart.js | 522 +++++++++++++++++------------------- 1 file changed, 241 insertions(+), 281 deletions(-) diff --git a/jquery.drawDoughnutChart.js b/jquery.drawDoughnutChart.js index 17d80c9..a89a7a9 100644 --- a/jquery.drawDoughnutChart.js +++ b/jquery.drawDoughnutChart.js @@ -1,301 +1,261 @@ /*! * jquery.drawDoughnutChart.js - * Version: 0.4(Beta) + * Version: 0.3(Beta) * Inspired by Chart.js(http://www.chartjs.org/) * - * Copyright 2014 hiro + * Copyright 2013 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, - showTip: true, - showLabel: false, - ratioFont: 1.5, - shortInt: false, - 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); - }; - }(); +!function( factory ) { + if ( typeof define === "function" && define.amd ) { - settings.beforeDraw.call($this); + // AMD. Register as an anonymous module. + define([ "jquery" ], factory ); + } + else if(typeof module === "object" && typeof module.exports === "object" ) { + module.exports = factory(jQuery); + } + else { - var $svg = $('').appendTo($this), - $paths = [], - easingFunction = animationOptions[settings.animationEasing], - doughnutRadius = Min([H / 2,W / 2]) - settings.edgeOffset, - cutoutRadius = doughnutRadius * (settings.percentageInnerCutout / 100), - segmentTotal = 0; + // Browser globals + factory( jQuery ); + } +}(function($){ + (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: "", + 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); + }; + }(); - //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); + settings.beforeDraw.call($this); - //Set up pie segments wrapper - var $pathGroup = $(document.createElementNS('http://www.w3.org/2000/svg', 'g')); - $pathGroup.attr({opacity: 0}).appendTo($svg); + var $svg = $('').appendTo($this), + $paths = [], + easingFunction = animationOptions[settings.animationEasing], + doughnutRadius = Min([H / 2,W / 2]) - settings.edgeOffset, + cutoutRadius = doughnutRadius * (settings.percentageInnerCutout / 100), + segmentTotal = 0, + value = data.map(function(elem) { // extract the numerical part of the string of data[i].value + return parseFloat(elem.value) || 0; + }); - //Set up tooltip - if (settings.showTip) { - var $tip = $('
').appendTo('body').hide(), - tipW = $tip.width(), - tipH = $tip.height(); - } + //Draw base doughnut + var baseDoughnutRadius = doughnutRadius + settings.baseOffset, + baseCutoutRadius = cutoutRadius - settings.baseOffset; + var drawBaseDoughnut = function() { + //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) * baseDoughnutRadius, + startY = centerY + sin(startRadius) * baseDoughnutRadius, + endX2 = centerX + cos(startRadius) * baseCutoutRadius, + endY2 = centerY + sin(startRadius) * baseCutoutRadius, + endX = centerX + cos(endRadius) * baseDoughnutRadius, + endY = centerY + sin(endRadius) * baseDoughnutRadius, + startX2 = centerX + cos(endRadius) * baseCutoutRadius, + startY2 = centerY + sin(endRadius) * baseCutoutRadius; + var cmd = [ + 'M', startX, startY, + 'A', baseDoughnutRadius, baseDoughnutRadius, 0, 1, 1, endX, endY, + 'L', startX2, startY2, + 'A', baseCutoutRadius, baseCutoutRadius, 0, 1, 0, endX2, endY2,//reverse + 'Z' + ]; + $(document.createElementNS('http://www.w3.org/2000/svg', 'path')) + .attr({ + "d": cmd.join(' '), + "fill": settings.baseColor + }) + .appendTo($svg); + }(); - //Set up center text area - var summarySize = (cutoutRadius - (doughnutRadius - cutoutRadius)) * 2, - $summary = $('') - .appendTo($this) - .css({ - width: summarySize + "px", - height: summarySize + "px", - "margin-left": -(summarySize / 2) + "px", - "margin-top": -(summarySize / 2) + "px" - }); - var $summaryTitle = $('' + settings.summaryTitle + '
').appendTo($summary); - $summaryTitle.css('font-size', getScaleFontSize( $summaryTitle, settings.summaryTitle )); // In most of case useless - var $summaryNumber = $('').appendTo($summary).css({opacity: 0}); + //Set up pie segments wrapper + var $pathGroup = $(document.createElementNS('http://www.w3.org/2000/svg', 'g')); + $pathGroup.attr({opacity: 0}).appendTo($svg); - 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) - .on("click", pathClick); - } + //Set up tooltip + var $tip = $('').appendTo('body').hide(), + tipW = $tip.width(), + tipH = $tip.height(); - //Animation start - animationLoop(drawPieSegments); + //Set up center text area + var summarySize = (cutoutRadius - (doughnutRadius - cutoutRadius)) * 2, + $summary = $('') + .appendTo($this) + .css({ + width: summarySize + "px", + height: summarySize + "px", + "margin-left": -(summarySize / 2) + "px", + "margin-top": -(summarySize / 2) + "px" + }); + var $summaryTitle = $('' + settings.summaryTitle + '
').appendTo($summary); + var $summaryNumber = $('').appendTo($summary).css({opacity: 0}); - //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; - if (settings.showTip) { - $tip.text(data[order].title + ": " + data[order].value) - .fadeIn(200); - } - if(settings.showLabel) { - $summaryTitle.text(data[order].title).css('font-size', getScaleFontSize( $summaryTitle, data[order].title)); - var tmpNumber = settings.shortInt ? shortKInt(data[order].value) : data[order].value; - $summaryNumber.html(tmpNumber).css('font-size', getScaleFontSize( $summaryNumber, tmpNumber)); - } - settings.onPathEnter.apply($(this),[e,data]); - } - function pathMouseLeave(e) { - if (settings.showTip) $tip.hide(); - if(settings.showLabel) { - $summaryTitle.text(settings.summaryTitle).css('font-size', getScaleFontSize( $summaryTitle, settings.summaryTitle)); - var tmpNumber = settings.shortInt ? shortKInt(segmentTotal) : segmentTotal; - $summaryNumber.html(tmpNumber).css('font-size', getScaleFontSize( $summaryNumber, tmpNumber)); - } - settings.onPathLeave.apply($(this),[e,data]); - } - function pathMouseMove(e) { - if (settings.showTip) { - $tip.css({ - top: e.pageY + settings.tipOffsetY, - left: e.pageX - $tip.width() / 2 + settings.tipOffsetX - }); - } - } - function pathClick(e){ - var order = $(this).data().order; - if (typeof data[order].action != "undefined") - data[order].action(); - } - function drawPieSegments (animationDecimal) { - var startRadius = -PI / 2,//-90 degree - rotateAnimation = 1; - if (settings.animation && settings.animateRotate) rotateAnimation = animationDecimal;//count up between0~1 + for (var i = 0, len = data.length; i < len; i++) { + segmentTotal += value[i]; + $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); + } + segmentTotal = segmentTotal === 0 ? 1:segmentTotal; // avoid denominator equaling to zero - drawDoughnutText(animationDecimal, segmentTotal); + //Animation start + animationLoop(drawPieSegments); - $pathGroup.attr("opacity", animationDecimal); + function pathMouseEnter(e) { + var order = $(this).data().order; + $tip.text(data[order].title.replace(/\$(\d+)/g,function(match,$1){ // replace the pattern of $i in data[j].title with data[i-1].value, but when i equals zero, replace that with data[j].value + if($1 == 0) { + return data[order].value; + } + else if($1 > 0 && $1 <= data.length) { + return data[$1-1].value; + } + else return match; + })).fadeIn(200); + settings.onPathEnter.apply($(this),[e,data]); + } + function pathMouseLeave(e) { + $tip.stop().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 - //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)); - var tmpNumber = settings.shortInt ? shortKInt(segmentTotal) : segmentTotal; - $summaryNumber.html(tmpNumber).css('font-size', getScaleFontSize( $summaryNumber, tmpNumber)); - } - 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; - } - function shortKInt (int) { - int = int.toString(); - var strlen = int.length; - if(strlen<5) - return int; - if(strlen<8) - return '' + int.substring(0, strlen-3) + 'K'; - return '' + int.substring( 0, strlen-6) + 'M'; - } - function getScaleFontSize(block, newText) { - block.css('font-size', ''); - newText = newText.toString().replace(/(<([^>]+)>)/ig,""); - var newFontSize = block.width() / newText.length * settings.ratioFont; - // Not very good : http://stephensite.net/WordPressSS/2008/02/19/how-to-calculate-the-character-width-accross-fonts-and-points/ - // But best quick way the 1.5 number is to affinate in function of the police - var maxCharForDefaultFont = block.width() - newText.length * block.css('font-size').replace(/px/, '') / settings.ratioFont; - if(maxCharForDefaultFont<0) - return newFontSize+'px'; - else - return ''; - } - /** - function getScaleFontSize(block, newText) { - block.css('font-size', ''); - newText = newText.toString().replace(/(<([^>]+)>)/ig,""); - var newFontSize = block.width() / newText.length; - if(newFontSize