基于VML与HTML5 Canva实现的跨浏览器饼图与折线图

简介: 本来想自己做个库的,毅力不够啊,所以放出源代码 当前实现的框架功能有: 1. 支持IE6+以上版本,支持Chrome, 支持FireFox 2. 动画加载机制 3. tooltip支持 4. legend支持 5. 功能丰富的参数设置 当前支持的图形种类有两种:饼图与折线图,将来也许会不定期更新,不断完善! 希望还有点毅力和时间做话。

本来想自己做个库的,毅力不够啊大哭,所以放出源代码

当前实现的框架功能有:

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

目录
相关文章
|
8月前
|
存储 缓存 网络协议
html&css&浏览器相关面试题大全(二)
html&css&浏览器相关面试题大全
|
8月前
VSCode用浏览器预览/运行html文件
VSCode用浏览器预览/运行html文件
|
8月前
|
Web App开发 编解码 前端开发
html&css&浏览器相关面试题大全(四)
html&css&浏览器相关面试题大全
|
6月前
|
Web App开发 移动开发 JavaScript
【前端用法】HTML5 Video标签如何屏蔽右键视频另存为的js代码以及如何禁用浏览器控件,Video 禁止鼠标右键下载
【前端用法】HTML5 Video标签如何屏蔽右键视频另存为的js代码以及如何禁用浏览器控件,Video 禁止鼠标右键下载
168 0
|
12天前
|
搜索推荐 前端开发 UED
html页面实现自动适应手机浏览器(一行代码搞定)
html页面实现自动适应手机浏览器(一行代码搞定)
17 0
|
3月前
|
移动开发 JavaScript 前端开发
如何处理html5新标签的浏览器兼容问题?
如何处理html5新标签的浏览器兼容问题?
17 0
|
3月前
|
搜索推荐 前端开发 UED
html页面实现自动适应手机浏览器(一行代码搞定)
html页面实现自动适应手机浏览器(一行代码搞定)
56 0
|
8月前
|
Web App开发 移动开发 iOS开发
如何在浏览器中启用 WebGL 以使用 HTML5 3D 查看器
如何在浏览器中启用 WebGL 以使用 HTML5 3D 查看器
118 0
|
8月前
|
存储 缓存 JavaScript
html&css&浏览器相关面试题大全(三)
html&css&浏览器相关面试题大全
|
4月前
|
编解码 JavaScript 前端开发
BOM和DOM:BOM(浏览器对象模型)提供了与浏览器交互的能力,如弹窗、定时器等;DOM(文档对象模型)提供了操作HTML元素的能力,如获取、修改元素内容和属性。
BOM和DOM:BOM(浏览器对象模型)提供了与浏览器交互的能力,如弹窗、定时器等;DOM(文档对象模型)提供了操作HTML元素的能力,如获取、修改元素内容和属性。
38 2