本来想自己做个库的,毅力不够啊,所以放出源代码
当前实现的框架功能有:
1. 支持IE6+以上版本,支持Chrome, 支持FireFox
2. 动画加载机制
3. tooltip支持
4. legend支持
5. 功能丰富的参数设置
当前支持的图形种类有两种:饼图与折线图,将来也许会不定期更新,不断完善!
希望还有点毅力和时间做话。不废话啦,直接上效果图:
折线图演示代码:
<html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <script src="js/fishchart.js"></script> <title>Line Demo</title> <script> window.onload = function() { var container = document.getElementById("my_container"); var seriesData = [{name:"本一", data:[330,348,355,345, 350, 348], color:"RGB(255,0,0)"}, {name:"本二", data:[300,326,328,320, 330, 325], color:"RGB(0,0,255)"}, {name:"本三", data:[248,288,277,290, 280, 290], color:"RGB(255,0,255)"}]; var config = { type : "line", width : 800, height: 400, series: seriesData, container: container, title:"江苏省2008~2013高考录取分数线", tooltip : { enable : true }, animation :{ enable: true }, legend : { enable : true }, yAxis :{ tickSize: 10, title: "分数" }, xAxis :{ tickSize: 10, title: "年份", categories: ["2008", "2009", "2010", "2011", "2012", "2013"] } }; fishChart.initSettings(config); fishChart.render(); } </script> </head> <body> <h1>VML Line Chart Demo</h1> <div id="my_container" style="width:600px; height:400px;"> </div> </body> </html>饼图演示代码:
<html> <head> <script src="js/fishchart.js"></script> <title>My Demo 1</title> <script> window.onload = function() { var container = document.getElementById("my_container"); var seriesData = [{name:"apples", value:150, color:"RGB(255,0,0)"}, {name:"orange", value:100, color:"RGB(255,255,0)"}, {name:"banana", value:80, color:"RGB(255,0,255)"}, {name:"peaches", value:60, color:"RGB(0,255,255)"}, {name:"strawberries", value:40, color:"RGB(0,127,255)"}] var config = { type : "pie", width : 600, height: 400, series: seriesData, container: container, unit: "kg", title:"Fruit Sales", tooltip : { enable : true }, animation :{ enable: true }, legend : { enable : true }, text : { enable: true } }; fishChart.initSettings(config); fishChart.render(); } </script> </head> <body> <h1>farchart - Pie Demo</h1> <div id="my_container" style="width:600px; height:400px;"> </div> </body> </html>JS库源代码:有兴趣者可以自己添加更多图表支持
// farchart farchart.js version 0.0.1, 28 August 2013 // (c) 2013 by Jia ZhiGang. All Rights reserved. // Copyright (c) 2013 Jia ZhiGang (http://www.edinme.com) // // far chart is freely distributable under the terms of an GPL-style license. // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the // "Software"), to deal in the Software without restriction, including // without limitation the rights to use, copy, modify, merge, publish, // distribute, sublicense, and/or sell copies of the Software, and to // permit persons to whom the Software is furnished to do so, subject to // the following conditions: // // The above copyright notice and this permission notice shall be // included in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // // For details, see the farchart web site: http://www.edinme.com function check_vmlCapability() { if(document.namespaces['v']==null) { var e=["shape","shapetype","group","background","path","formulas","handles","fill","stroke","shadow","textbox","textpath","imagedata","line","polyline","curve","roundrect","oval","rect","arc","image"],s=document.createStyleSheet(); for(var i=0; i<e.length; i++) {s.addRule("v\\:"+e[i],"behavior: url(#default#VML); display: inline-block;");} document.namespaces.add("v","urn:schemas-microsoft-com:vml"); } } function msieversion() { var ua = window.navigator.userAgent; var msie = ua.indexOf ( "MSIE " ); if ( msie > 0 ) { // If Internet Explorer, return version number return parseInt (ua.substring (msie+5, ua.indexOf (".", msie ))); } else { // If another browser, return 0 return 0; } } var fishChart = { type: "null", // global flag, default width: 600, height: 400, series: [], unit: "kg", container: null, title: { text: "Far Chart", x: -100 // center offset }, isIE: true, legend : { enable : true }, edge : { width: 50, // for pie height: 50, // for pie left: 50, // for other plot upper: 40, bottom: 40, right: 60 }, yAxis: { title: "Y Axis", vgap: 0, plotHeight: 0, min: 0, max: 0, tickSize: 10, padding: 5 }, xAxis: { categories: [], tickSize : 10, autoTick:true, xgap : 0, min:0, max:0, title:"X Axis" }, animation: { enable: true, hh: 1, animCanvas:null, pctx: null }, tooltip: { enable: true, tooltipCanvas : null, ttContext: null, index: -1 }, circle : { // for pie cx: 0, cy: 0, radius: 0 }, text : { // for pie enable: false, content:[] }, point : { // for line and scatter plot enable : true, radius : 4 }, initSettings: function (config) { this.type = config.type; this.container = config.container; this.width = config.width; this.height = config.height; this.series = config.series; this.title.text = config.title; this.unit = config.unit; // tool-tip, animation, legend setting data if(config.tooltip != undefined) { this.tooltip.enable = config.tooltip.enable; } if(config.animation != undefined) { this.animation.enable = config.animation.enable; } if(config.legend != undefined) { this.legend.enable = config.legend.enable; } if(config.text != undefined) { this.text.enable = config.text.enable; } // edge setting data if(config.edge != undefined && config.edge.right != undefined) { this.edge.right = config.edge.right; } if(config.edge != undefined && config.edge.left != undefined) { this.edge.left = config.edge.left; } if(config.edge != undefined && config.edge.bottom != undefined) { this.edge.bottom = config.edge.bottom; } if(config.edge != undefined && config.edge.upper != undefined) { this.edge.upper = config.edge.upper; } // xAxis setting if(config.xAxis != undefined) { this.xAxis.title= config.xAxis.title; this.xAxis.tickSize = config.xAxis.tickSize; } if(config.xAxis != undefined && config.xAxis.categories != undefined) { this.xAxis.categories = config.xAxis.categories; } // yAxis setting if(config.yAxis != undefined) { this.yAxis.title = config.yAxis.title; this.yAxis.tickSize = config.yAxis.tickSize; } // decide whether render plot using HTML5 Canvas or VML if(msieversion() == 0) { this.chartCanvas = document.createElement("canvas"); this.chartCanvas.id = "fc_canvas"; this.container.appendChild(this.chartCanvas); this.chartCanvas.width = config.width; this.chartCanvas.height = config.height; this.isIE = false; } else { check_vmlCapability(); this.isIE = true; } }, render : function() { var ctx = this.getCanvasContext(); this.renderBorder(ctx); if(this.type === "pie") { // initialization circle this.circle.cx = this.width/2; this.circle.cy = this.height/2; this.circle.radius = Math.min(this.width/2, this.height/2) - Math.max(this.edge.width, this.edge.height); if(this.circle.radius <= 0) { console.log("Can not reader the chart, Circle is too small."); return; } // draw each arc according to data series var sum = 0; var nums = this.series.length; for(var i=0; i<nums; i++) { sum += this.series[i].value; } // draw title this.customFillText(ctx, this.width/2 - this.edge.width, (this.isIE ? 10 : 30), this.circle.radius, 40, this.title.text, "white", "white", "black", '18pt Calibri'); if(this.isIE) { this.renderPieShadow(ctx); } // render bar var start = 0; for(var i=0; i<nums; i++) { var precent = this.series[i].value/sum; this.renderPie(ctx, i, start, precent); if(this.isIE) { start += precent; } else { start += 2*Math.PI * precent; } } // post add blur shadow if(!this.isIE) { this.renderPieShadow(ctx); } // render legend this.renderPieLegend(ctx, sum); } // line plot else if(this.type == "line") { // draw title this.customFillText(ctx, (this.width/2 + this.title.x), (this.isIE ? 2 : this.edge.upper/2), this.width/2, 0, this.title.text, "white", "white", "black", '18pt Calibri'); // draw XY Axis this.renderXYAxis(ctx); // draw lines var nums = this.series.length; for(var i=0; i<nums; i++) { this.renderOneLineCurve(ctx, i); } } // render legend this.renderLegend(ctx); // enable tool-tip if(this.tooltip.enable && !this.isIE && !this.animation.enable) { var parent = this; this.chartCanvas.addEventListener('mousemove', function(event) { var x = event.pageX; var y = event.pageY; var canvas = event.target; var bbox = canvas.getBoundingClientRect(); var loc = { x: x - bbox.left * (canvas.width / bbox.width), y: y - bbox.top * (canvas.height / bbox.height)}; parent.showTooltips(loc, ctx); }, false); } // enable animation if(this.animation.enable) { var parent = this; if(this.isIE) { ctx.insertBefore(this.animation.animCanvas); } setTimeout(function() {parent.playAnimation(parent);}, 1000/20); } }, renderOneLineCurve : function(ctx, index) { var size = this.series[index].data.length; var plotwidth = this.width - this.edge.left - this.edge.right; var deltax = plotwidth/size; var xpos = this.edge.left; if(this.isIE) { var vpolyLine = document.createElement("v:polyline"); vpolyLine.strokecolor=this.series[index].color; vpolyLine.id = "vml-fline-" + index; vpolyLine.filled="false"; // default is true, will fill area of close path var points = ""; for(var i=0; i<size; i++) { var value = this.series[index].data[i]; var deltay = Math.floor((value - this.yAxis.min) * this.yAxis.ygap); // highlight the data point by circle. if(this.point.enable) { var hvoval = document.createElement("v:oval"); hvoval.style.left = (xpos + Math.floor(deltax/2) - this.point.radius); hvoval.style.top = (this.height - this.edge.bottom - deltay - this.point.radius); hvoval.style.width = (this.point.radius * 2) + "px"; hvoval.style.height = (this.point.radius * 2) + "px"; hvoval.strokecolor = this.series[index].color; hvoval.fillcolor = this.series[index].color; hvoval.title = this.xAxis.categories[i] + " " + this.series[index].name + " : " + + value; // tool-tip ctx.insertBefore(hvoval); } // - end it points += (xpos + Math.floor(deltax/2)) + "," + (this.height - this.edge.bottom - deltay) + " "; xpos += deltax; } vpolyLine.strokeweight="1px"; vpolyLine.points=points; ctx.insertBefore(vpolyLine); } else { ctx.save(); ctx.beginPath(); ctx.lineWidth = 1; for(var i=0; i<size; i++) { var value = this.series[index].data[i]; var deltay = Math.floor((value - this.yAxis.min) * this.yAxis.ygap); ctx.strokeStyle=this.series[index].color; if(i == 0) { ctx.moveTo(xpos + Math.floor(deltax/2), this.height - this.edge.bottom - deltay); } else { ctx.lineTo(xpos + Math.floor(deltax/2), this.height - this.edge.bottom - deltay); } xpos += deltax; } ctx.stroke(); ctx.restore(); // draw dot circle if(!this.point.enable) return; ctx.save(); var xpos = this.edge.left; for(var i=0; i<size; i++) { var value = this.series[index].data[i]; var deltay = Math.floor((value - this.yAxis.min) * this.yAxis.ygap); ctx.beginPath(); ctx.arc(xpos + Math.floor(deltax/2), this.height - this.edge.bottom - deltay, this.point.radius, 0, 2*Math.PI, false); ctx.closePath(); ctx.fillStyle=this.series[index].color; ctx.fill(); xpos += deltax; } ctx.restore(); } }, renderPieLegend : function(ctx, sum) { if(!this.legend.enable) return; var nums = this.series.length; ctx.font = '10pt Calibri'; var pos = (this.width/2 > (this.circle.radius+50)) ? 50 : (this.circle.cx - this.circle.radius); for(var i=0; i<nums; i++) { var x = this.series[i].value/sum; x = Math.round (x*100) / 100; var tipStr = this.series[i].name + ": " + (x * 100).toFixed(0) + "%"; this.series[i].precent = tipStr; this.renderRect(ctx, (pos - 40), (20*i+10), 10, 10, this.series[i].color, this.series[i].color, 0); this.customFillText(ctx, (pos - 25), (this.isIE ? (20*i + 1) : (20*i+20)), 100, 0, tipStr, "white", "white", "black", "10pt Calibri"); } }, renderPieShadow : function(ctx) { if(this.isIE) { var voval = document.createElement("v:oval"); voval.style.left=(this.circle.cx - this.circle.radius) + "px"; voval.style.top=(this.circle.cy - this.circle.radius) + "px"; voval.style.width=this.circle.radius*2 + "px"; voval.style.height=this.circle.radius*2 + "px"; voval.style.position="absolute"; voval.strokecolor="RGB(150,150,150)"; voval.strokeweight="4pt"; ctx.insertBefore(voval); } else { ctx.save(); ctx.shadowColor = "black"; ctx.shadowOffsetX = 0; ctx.shadowOffsetY = 0; ctx.shadowBlur = 10; ctx.beginPath(); ctx.arc(this.circle.cx, this.circle.cy, this.circle.radius, 0, Math.PI * 2, false); ctx.closePath(); ctx.lineWidth = 1; ctx.strokeStyle = "RGBA(127,127,127,1)"; ctx.stroke(); ctx.restore(); } }, renderPie : function(ctx, index, startp, precent) { if(this.isIE) { var start = Math.round (startp*100); var end = Math.round (precent*100) + start; var part = document.createElement("v:shape"); part.style.width=Math.min(this.width, this.height) + "px"; part.style.height=Math.min(this.width, this.height) + "px"; part.style.position="absolute"; part.id = "vml-pie-part-" + index; var path = ""; for (var i= start; i <= end; i++) { x = Math.floor((Math.cos(i*Math.PI/50)*this.circle.radius)+this.circle.cx); y = Math.floor((Math.sin(i*Math.PI/50)*this.circle.radius)+this.circle.cy); xs = x.toString(); ys = y.toString(); path += xs + ", " + ys + ", "; } part.strokecolor=this.series[index].color; part.strokeweight="0pt"; part.fillcolor = this.series[index].color; part.path= "m " + this.circle.cx + "," + this.circle.cy +" l " + path + this.circle.cx + "," + this.circle.cy + " x e"; ctx.insertBefore(part); // render text content if(this.text.enable) { var textLine = document.createElement("v:line"); var fx = Math.floor((Math.cos((start + (end - start)/2)*Math.PI/50)*this.circle.radius)+this.circle.cx); var fy = Math.floor((Math.sin((start + (end - start)/2)*Math.PI/50)*this.circle.radius)+this.circle.cy); var tx = (fx < this.circle.cx) ? (fx - this.edge.width) : (fx + this.edge.width); textLine.from = fx + "," + fy; textLine.to = tx + "," + fy; ctx.insertBefore(textLine); var textPos = (fx < this.circle.cx) ? (fx - this.edge.width*2) : (fx + this.edge.width); this.customFillText(ctx, (textPos), (fy-20), 100, 0, this.series[index].name + ": " + (precent * 100).toFixed(0) + "%", "white", "white", "black", "10pt Calibri"); } if(this.tooltip.enable) { var _parent = this; part.title = "Index: " + (index + 1) + "\n" + this.series[index].name + ": " + this.series[index].value + this.unit + "\n" + this.series[index].name + ": " + (precent * 100).toFixed(0) + "%" + "\n"; part.attachEvent('onmouseover', function (event) { var srcElement = event.srcElement; srcElement.strokecolor= "white"; srcElement.strokeweight="3pt"; }); part.attachEvent('onmouseout', function (event) { var srcElement = event.srcElement; srcElement.strokecolor=_parent.series[index].color; srcElement.strokeweight="0pt"; }); } } else { var endAngle = startp + 2*Math.PI*precent; ctx.save(); ctx.beginPath(); ctx.arc(this.circle.cx, this.circle.cy, this.circle.radius, startp, endAngle, false); ctx.moveTo(this.circle.cx, this.circle.cy); ctx.lineTo(this.circle.cx + this.circle.radius * Math.cos(startp), this.circle.cy + this.circle.radius * Math.sin(startp)); ctx.lineTo(this.circle.cx + this.circle.radius * Math.cos(endAngle), this.circle.cy + this.circle.radius * Math.sin(endAngle)); ctx.lineTo(this.circle.cx, this.circle.cy); ctx.closePath(); ctx.fillStyle = this.series[index].color; ctx.strokeStyle = this.series[index].color; ctx.fill(); ctx.stroke(); ctx.restore(); // render text content if(this.text.enable) { var halfEndAngle = startp + Math.PI*precent; var hx = this.circle.cx + this.circle.radius * Math.cos(halfEndAngle); var hy = this.circle.cy + this.circle.radius * Math.sin(halfEndAngle); ctx.beginPath(); ctx.moveTo(hx, hy); var linePos = (hx < this.circle.cx) ? (hx - this.edge.width) : (hx + this.edge.width); ctx.lineTo(linePos, hy); ctx.closePath(); ctx.strokeStyle="black"; ctx.stroke(); var textPos = (hx < this.circle.cx) ? (hx - this.edge.width*2) : (hx + this.edge.width); precent = Math.round (precent*100) / 100; var size = this.text.content.length; var tipStr = (size > index) ? this.text.content[index] : this.series[index].name + ": " + (precent * 100).toFixed(0) + "%"; ctx.font = '10pt Calibri'; ctx.fillStyle="black"; ctx.fillText(tipStr, textPos, hy); } } }, showLineTooltips : function(loc, ctx) { if(!this.tooltip.enable) { return; } var size = this.series[0].data.length; var plotwidth = this.width - this.edge.left - this.edge.right; var deltax = (plotwidth/ size); var xunit = ((loc.x - this.edge.left)/deltax); //: */loc.x - this.edge.left; var deltay = this.height - this.edge.bottom - loc.y; if(xunit > 0 && deltay > 0) { var value = deltay/this.yAxis.ygap + this.yAxis.min; var xindex = Math.floor(xunit); var xpoint = xunit - xindex; if(xpoint < 0.55 && xpoint >0.45) { var num = this.series.length; var distance = []; for(var i=0; i<num; i++) { var dis = this.series[i].data[xindex] - value; distance[i] = Math.abs(dis); } var min = distance[0]; var yindex = 0; for(var i=0; i<num; i++) { if(min > distance[i]) { min = distance[i]; yindex = i; } } if(this.series[yindex].data[xindex] > Math.floor(value - 3) && this.series[yindex].data[xindex] < Math.floor(value + 3) ) { this.renderLineTooltips(ctx, yindex, xindex, loc); } // clear tool tip else { this.clearTooltips(ctx); } } else { this.clearTooltips(ctx); } } else { this.clearTooltips(ctx); } }, renderLineTooltips : function(ctx, yindex, xindex, loc) { // show tool tip this.clearTooltips(ctx); if(this.tooltip.tooltipCanvas == null) { this.tooltip.tooltipCanvas = document.createElement("canvas"); this.tooltip.ttContext = this.tooltip.tooltipCanvas.getContext("2d"); this.tooltip.tooltipCanvas.width = 150; this.tooltip.tooltipCanvas.height = 100; } var m_context = this.tooltip.ttContext; m_context.save(); m_context.clearRect(0, 0, this.tooltip.tooltipCanvas.width, this.tooltip.tooltipCanvas.height); m_context.lineWidth = 2; m_context.strokeStyle = this.series[yindex].color; m_context.fillStyle="RGBA(255,255,255,0.7)"; this.getRoundRect(m_context, 2,2,this.tooltip.tooltipCanvas.width-4, this.tooltip.tooltipCanvas.height-4, 5, true, true); m_context.font="14px Arial"; m_context.fillStyle="RGBA(0,0,0,1)"; if(this.type == "line") { m_context.fillText((this.xAxis.title + ": " + this.xAxis.categories[xindex]), 5, 20); m_context.fillText(this.series[yindex].name + ": " + this.series[yindex].data[xindex], 5, 40); } else { m_context.fillText(this.series[yindex].name, 5, 20); m_context.fillText(this.xAxis.title + ":" + this.series[yindex].data[xindex][0], 5, 40); m_context.fillText(this.yAxis.title + ":" + this.series[yindex].data[xindex][1], 5, 60); } m_context.restore(); // make tool-tip rectangle is always visible if((loc.x + this.tooltip.tooltipCanvas.width)> this.width) { loc.x = loc.x - this.tooltip.tooltipCanvas.width; } if((loc.y - this.tooltip.tooltipCanvas.height) <= 0) { loc.y = loc.y + this.tooltip.tooltipCanvas.height; } ctx.drawImage(this.tooltip.tooltipCanvas, 0, 0, this.tooltip.tooltipCanvas.width, this.tooltip.tooltipCanvas.height, loc.x, loc.y-this.tooltip.tooltipCanvas.height, this.tooltip.tooltipCanvas.width, this.tooltip.tooltipCanvas.height); }, showPieTooltips : function(loc, ctx) { if(!this.tooltip.enable) { return; } var dx = loc.x - this.width/2; var dy = loc.y - this.height/2; var dis = Math.floor(Math.sqrt(dx * dx + dy * dy)); if(dis <= this.circle.radius) { // draw tool tip text var angle = Math.atan2(dy,dx); if(angle <= 0) { // if[-Math.PI, 0], make it[Math.PI, 2*Math.PI] angle = angle + 2*Math.PI; } var sum = 0; var nums = this.series.length; for(var s=0; s<nums; s++) { sum += this.series[s].value; } var deltaArc = 0; var index = 0; for(var i=0; i<nums; i++) { var precent = this.series[i].value/sum; deltaArc += 2*Math.PI * precent; if(angle<=deltaArc) { index = i; break; } } if(this.tooltip.tooltipCanvas == null) { this.tooltip.tooltipCanvas = document.createElement("canvas"); this.tooltip.ttContext = this.tooltip.tooltipCanvas.getContext("2d"); this.tooltip.tooltipCanvas.width = 150; this.tooltip.tooltipCanvas.height = 100; } // only draw once // if(index == this.tooltips.index){ // return; // } this.clearTooltips(ctx); this.tooltip.index = index; var m_context = this.tooltip.ttContext; m_context.save(); m_context.clearRect(0, 0, this.tooltip.tooltipCanvas.width, this.tooltip.tooltipCanvas.height); m_context.lineWidth = 2; m_context.strokeStyle = this.series[index].color; m_context.fillStyle="RGBA(255,255,255,0.7)"; // m_context.strokeRect(2, 2, this.tooltips.tooltipCanvas.width-4, this.tooltips.tooltipCanvas.height-4); // m_context.fillRect(2,2,this.tooltips.tooltipCanvas.width-4, this.tooltips.tooltipCanvas.height-4); this.getRoundRect(m_context, 2,2,this.tooltip.tooltipCanvas.width-4, this.tooltip.tooltipCanvas.height-4, 5, true, true); m_context.font="14px Arial"; m_context.fillStyle="RGBA(0,0,0,1)"; m_context.fillText("Index: " + (index + 1), 5, 20); m_context.fillText(this.series[index].name + ": " + this.series[index].value + this.unit, 5, 40); m_context.fillText(this.series[index].precent, 5, 60); m_context.restore(); // make tool-tip rectangle is always visible if((loc.x + this.tooltip.tooltipCanvas.width)> this.width) { loc.x = loc.x - this.tooltip.tooltipCanvas.width; } if((loc.y - this.tooltip.tooltipCanvas.height) <= 0) { loc.y = loc.y + this.tooltip.tooltipCanvas.height; } ctx.drawImage(this.tooltip.tooltipCanvas, 0, 0, this.tooltip.tooltipCanvas.width, this.tooltip.tooltipCanvas.height, loc.x, loc.y-this.tooltip.tooltipCanvas.height, this.tooltip.tooltipCanvas.width, this.tooltip.tooltipCanvas.height); } else { this.tooltip.index = -1; this.clearTooltips(ctx); } }, redrawPie : function(ctx) { if(this.animation.enable) { ctx.clearRect(0,0,this.width, this.height); this.renderBorder(ctx); ctx.drawImage(this.animation.animCanvas, 0, 0, this.width, this.height, 0, 0, this.width, this.height); } else { // draw each arc according to data series var sum = 0; var nums = this.series.length; for(var i=0; i<nums; i++) { sum += this.series[i].value; } // draw title this.customFillText(ctx, this.width/2 - this.edge.width, (this.isIE ? 10 : 30), this.circle.radius, 40, this.title, "white", "white", "black", '18pt Calibri'); if(this.isIE) { this.renderPieShadow(ctx); } // render bar var start = 0; for(var i=0; i<nums; i++) { var precent = this.series[i].value/sum; this.renderPie(ctx, i, start, precent); if(this.isIE) { start += precent; } else { start += 2*Math.PI * precent; } } // post add blur shadow if(!this.isIE) { this.renderPieShadow(ctx); } // render legend this.renderPieLegend(ctx, sum); } }, renderXYAxis : function(ctx) { this.drawXYTitles(ctx); if(this.type == "line") { // draw ticks and markers var nums = this.series.length; if(nums == 0) return; var min = 0; var max = 0; min = this.series[0].data[0]; max = this.series[0].data[0]; for(var s = 0; s < nums; s++) { var size = this.series[s].data.length; for(var i=0; i<size; i++) { min = Math.min(min, this.series[s].data[i]); max = Math.max(max, this.series[s].data[i]); } } // calculate the text gap in Y Axis var delta = max - min; this.yAxis.padding = delta/10; this.yAxis.min = min - this.yAxis.padding; this.yAxis.max = max + this.yAxis.padding; var sum = this.yAxis.max - this.yAxis.min; var plotHeight = this.height - this.edge.upper - this.edge.bottom; var ygap = plotHeight/sum; this.yAxis.plotheight = plotHeight; this.yAxis.ygap = ygap; var index = Math.floor(this.yAxis.min); var first = true; // draw Y Axis and dash line for(var ypos=0; ypos<(plotHeight + this.edge.upper/2); ypos+=ygap*this.yAxis.tickSize) { this.customFillText(ctx, this.isIE ? (this.edge.left/3) : this.edge.left/2, this.isIE ? (this.height - this.edge.bottom - Math.floor(ypos)-15) : this.height - this.edge.bottom - Math.floor(ypos), 40, 0, "" + index, "white", "white", "black", "10px Arial"); index = index + this.yAxis.tickSize; // draw dash line if(!first) { this.dashedLineTo(ctx, this.edge.left, this.height - this.edge.bottom - Math.floor(ypos), this.width - this.edge.right, this.height - this.edge.bottom - Math.floor(ypos), 5); } first = false; } // draw X Axis sub title var csize = this.xAxis.categories.length; var plotwidth = this.width - this.edge.left - this.edge.right; delta = plotwidth/ csize; var xpos = this.edge.left; for(var x=0; x<csize; x++) { this.customFillText(ctx, (xpos + delta/2), this.isIE ? (this.height - (this.edge.bottom*0.75) - 15):this.height - (this.edge.bottom*0.75), 50, 0, this.xAxis.categories[x], "white", "white", "black", "10px Arial"); xpos += delta; } } else if(this.type == "scatter") { } else if(this.type == "column") { } else if(this.type == "bar") { } }, drawXYTitles : function(ctx) { if(this.isIE) { // draw X Axis and title var xLine = document.createElement("v:line"); xLine.from = this.edge.left + "," + (this.height-this.edge.bottom); xLine.to = (this.width - this.edge.right) + "," + (this.height-this.edge.bottom); ctx.insertBefore(xLine); this.customFillText(ctx, this.width/2, this.height-this.edge.bottom/2, (this.width/2 - this.edge.right), 0, this.xAxis.title, "white", "white", "black", "12px Arial"); // draw Y title, VML rotate text is very trick thing, // the center point is itself half of width and height, very bad way var yTitleRect = document.createElement("v:rect"); yTitleRect.style.left= Math.floor(-this.height/2 + 10) + "px"; yTitleRect.style.top= (this.height - this.edge.bottom) + "px"; yTitleRect.style.width=Math.floor(this.height) + "px"; yTitleRect.style.height="0px"; yTitleRect.fillcolor = "white"; yTitleRect.strokecolor="white"; yTitleRect.strokeweight="0pt"; var ytitleTextBox = document.createElement("v:textbox"); ytitleTextBox.style.font = "12px Arial"; ytitleTextBox.innerText = this.yAxis.title; yTitleRect.style.rotation = "90"; // only make the text vertical, bad!!! yTitleRect.insertBefore(ytitleTextBox); ctx.insertBefore(yTitleRect); } else { // draw X Axis and title ctx.save(); this.onePixelLineTo(ctx, this.edge.left, this.height-this.edge.bottom, this.width - this.edge.right, this.height-this.edge.bottom, "white", false); ctx.restore(); ctx.font="12px Arial"; ctx.fillText(this.xAxis.title, this.width/2, this.height-this.edge.bottom/4); // draw Y title ctx.save(); ctx.font="12px Arial"; ctx.translate(this.width/2, this.height/2); ctx.rotate(-Math.PI/2); ctx.fillText(this.yAxis.title, 20, -(this.width/2 - 20)); ctx.restore(); } }, renderLegend : function(ctx) { if(!this.legend.enable || this.type == "pie") { return; } // TODO:zhigang, legend here }, renderBorder : function(ctx) { this.renderRect(ctx, 0, 0, this.width, this.height, "white", "black", 1); }, playAnimation : function(parent) { if(parent.animation.hh < parent.height) { if(parent.isIE) { var node = document.getElementById("fishchart_mask_rect"); node.style.top= this.animation.hh + "px"; node.style.height= (this.height - this.animation.hh) + "px"; parent.animation.hh = parent.animation.hh + 10; setTimeout(function() {parent.playAnimation(parent);}, 1000/20); } else { parent.animation.pctx.save(); parent.animation.pctx.globalAlpha=0.5; parent.animation.pctx.clearRect(0,0,parent.width, parent.height); parent.renderBorder(parent.animation.pctx); parent.animation.pctx.drawImage(parent.animation.animCanvas, 0, 0, parent.width, this.animation.hh, 0, 0, parent.width, this.animation.hh); parent.animation.hh = parent.animation.hh + 10; parent.animation.pctx.restore(); setTimeout(function() {parent.playAnimation(parent);}, 1000/20); } } else { // remove xxxxx or change the alpha value if(parent.isIE) { var node = document.getElementById("fishchart_mask_rect"); if(node != null) { parent.animation.pctx.removeChild(node); } } else { parent.animation.pctx.clearRect(0,0,parent.width, parent.height); parent.renderBorder(parent.animation.pctx); parent.animation.pctx.drawImage(parent.animation.animCanvas, 0, 0, parent.width, parent.height, 0, 0, parent.width, parent.height); } // enable tool-tip functionality if(!this.isIE && parent.animation.enable && parent.tooltip.enable) { parent.chartCanvas.addEventListener('mousemove', function(event) { var x = event.pageX; var y = event.pageY; var canvas = event.target; var bbox = canvas.getBoundingClientRect(); var loc = { x: x - bbox.left * (canvas.width / bbox.width), y: y - bbox.top * (canvas.height / bbox.height)}; console.log("ddddddddddddddddddddd"); parent.showTooltips(loc, (parent.animation.enable ? parent.animation.pctx : ctx)); }, false); } } }, clearTooltips : function(ctx) { if(this.animation.enable) { ctx.clearRect(0,0,this.width, this.height); this.renderBorder(ctx); ctx.drawImage(this.animation.animCanvas, 0, 0, this.width, this.height, 0, 0, this.width, this.height); } else { this.redrawPlot(ctx); } }, redrawPlot : function(ctx) { if(this.type === "pie") { this.redrawPie(ctx); } else if(this.type == "line") { ctx.clearRect(0,0,this.width, this.height); // draw title this.customFillText(ctx, (this.width/2 + this.title.x), (this.isIE ? 2 : this.edge.upper/2), this.width/2, 0, this.title.text, "white", "white", "black", '18pt Calibri'); // draw XY Axis this.renderXYAxis(ctx); // draw lines var nums = this.series.length; for(var i=0; i<nums; i++) { this.renderOneLineCurve(ctx, i); } } }, showTooltips : function(loc, ctx) { if(this.type === "pie") { this.showPieTooltips(loc, ctx); } else if(this.type === "line") { this.showLineTooltips(loc, ctx); } }, getCanvasContext : function() { var ctx = null; if(this.isIE) { // create group ctx = document.createElement("v:group"); var min_size = Math.min(this.width, this.height); ctx.style.width = min_size; ctx.style.height = min_size; // default the group will be center inside the DIV element ctx.coordsize = min_size + ' ' + min_size; this.container.insertBefore(ctx); if(this.animation.enable) { this.animation.animCanvas = document.createElement("v:rect"); this.animation.animCanvas.id = "fishchart_mask_rect"; this.animation.animCanvas.style.left= "0px"; this.animation.animCanvas.style.top= "0px"; this.animation.animCanvas.style.width= this.width + "px"; this.animation.animCanvas.style.height= this.height + "px"; this.animation.animCanvas.fillcolor = "white"; this.animation.animCanvas.strokecolor = "white"; this.animation.animCanvas.strokeweight= "0pt"; var ttfill = document.createElement("v:fill"); ttfill.opacity = "100%"; this.animation.animCanvas.insertBefore(ttfill); this.animation.pctx = ctx; } } else { if(this.animation.enable) { this.animation.animCanvas = document.createElement("canvas"); this.animation.animCanvas.width = this.width; this.animation.animCanvas.height = this.height; this.animation.pctx = this.chartCanvas.getContext("2d"); // parent ctx ctx = this.animation.animCanvas.getContext("2d"); } else { ctx = this.chartCanvas.getContext("2d"); } } return ctx; }, getRoundRect : function(ctx, x, y, width, height, radius, fill, stroke) { if (typeof stroke == "undefined") { stroke = true; } if (typeof radius === "undefined") { radius = 5; } ctx.beginPath(); ctx.moveTo(x + radius, y); ctx.lineTo(x + width - radius, y); ctx.quadraticCurveTo(x + width, y, x + width, y + radius); ctx.lineTo(x + width, y + height - radius); ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y+ height); ctx.lineTo(x + radius, y + height); ctx.quadraticCurveTo(x, y + height, x, y + height - radius); ctx.lineTo(x, y + radius); ctx.quadraticCurveTo(x, y, x + radius, y); ctx.closePath(); if (stroke) { ctx.stroke(); } if (fill) { ctx.fill(); } }, renderRect : function (ctx, x, y, width, height, fillColor, strokeColor, lineWidth) { if(this.isIE) { var vrect = document.createElement("v:rect"); vrect.style.left= x + "px"; vrect.style.top= y + "px"; vrect.style.width= width + "px"; vrect.style.height= height + "px"; vrect.fillcolor = fillColor; vrect.strokecolor=strokeColor; vrect.strokeweight= lineWidth + "pt"; ctx.insertBefore(vrect); } else { ctx.save(); ctx.fillStyle= fillColor; ctx.strokeStyle= strokeColor; ctx.fillRect(x, y, width, height); ctx.strokeRect(x, y, width, height); ctx.restore(); } }, customFillText : function (ctx, x, y, width, height, textContent, fillColor, strokeColor, fontColor, fontSize) { if(this.isIE) { var textRect = document.createElement("v:rect"); textRect.style.left=(x) + "px"; textRect.style.top=(y) + "px"; textRect.style.width=width + "px"; textRect.style.height=height + "px"; textRect.fillcolor = fillColor; textRect.strokecolor = strokeColor; textRect.strokeweight="0pt"; var textBox = document.createElement("v:textbox"); textBox.style.font = fontSize;// "10pt Calibri"; textBox.innerText = textContent; // make stroke and fill attribute transparent is 100% var ttfill = document.createElement("v:fill"); ttfill.opacity = "0%"; var ttstroke = document.createElement("v:stroke"); ttstroke.opacity = "0%"; // append to group element textRect.insertBefore(ttstroke); textRect.insertBefore(ttfill); textRect.insertBefore(textBox); ctx.insertBefore(textRect); } else { ctx.save(); ctx.font = fontSize;//'10pt Calibri'; ctx.fillStyle=fontColor; ctx.fillText(textContent, x, y); ctx.restore(); } }, onePixelLineTo : function(ctx, fromX, fromY, toX, toY, backgroundColor, vertical) { var currentStrokeStyle = this.strokeStyle; ctx.beginPath(); ctx.moveTo(fromX, fromY); ctx.lineTo(toX, toY); ctx.closePath(); ctx.lineWidth=2; ctx.stroke(); ctx.beginPath(); if(vertical) { ctx.moveTo(fromX+1, fromY); ctx.lineTo(toX+1, toY); } else { ctx.moveTo(fromX, fromY+1); ctx.lineTo(toX, toY+1); } ctx.closePath(); ctx.lineWidth=2; ctx.strokeStyle=backgroundColor; ctx.stroke(); ctx.strokeStyle = currentStrokeStyle; }, dashedLineTo : function (ctx, fromX, fromY, toX, toY, pattern) { if(this.isIE) { var vdashline = document.createElement("v:line"); var vdashlineStroke = document.createElement("v:stroke"); vdashline.from = fromX + "," + fromY; vdashline.to = toX + "," + toY; vdashlineStroke.dashstyle="dash"; vdashline.insertBefore(vdashlineStroke); ctx.insertBefore(vdashline); } else { ctx.save(); ctx.lineWidth = 1; ctx.translate(0.5,0.5); // default interval distance -> 5px if (typeof pattern === "undefined") { pattern = 5; } // calculate the delta x and delta y var dx = (toX - fromX); var dy = (toY - fromY); var distance = Math.floor(Math.sqrt(dx*dx + dy*dy)); var dashlineInteveral = (pattern <= 0) ? distance : (distance/pattern); var deltay = Math.floor((dy/distance) * pattern); var deltax = Math.floor((dx/distance) * pattern); // draw dash line ctx.beginPath(); for(var dl=0; dl<dashlineInteveral; dl++) { if(dl%2) { ctx.lineTo(fromX + dl*deltax, fromY + dl*deltay); } else { ctx.moveTo(fromX + dl*deltax, fromY + dl*deltay); } } ctx.stroke(); ctx.restore(); } } };