web: add missing and all remaining flot js
This commit is contained in:
		
							parent
							
								
									f2d9c6e9c1
								
							
						
					
					
						commit
						ed44ee99e4
					
				
							
								
								
									
										345
									
								
								hledger-web/static/js/jquery.flot.canvas.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										345
									
								
								hledger-web/static/js/jquery.flot.canvas.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,345 @@ | ||||
| /* Flot plugin for drawing all elements of a plot on the canvas. | ||||
| 
 | ||||
| Copyright (c) 2007-2014 IOLA and Ole Laursen. | ||||
| Licensed under the MIT license. | ||||
| 
 | ||||
| Flot normally produces certain elements, like axis labels and the legend, using | ||||
| HTML elements. This permits greater interactivity and customization, and often | ||||
| looks better, due to cross-browser canvas text inconsistencies and limitations. | ||||
| 
 | ||||
| It can also be desirable to render the plot entirely in canvas, particularly | ||||
| if the goal is to save it as an image, or if Flot is being used in a context | ||||
| where the HTML DOM does not exist, as is the case within Node.js. This plugin | ||||
| switches out Flot's standard drawing operations for canvas-only replacements. | ||||
| 
 | ||||
| Currently the plugin supports only axis labels, but it will eventually allow | ||||
| every element of the plot to be rendered directly to canvas. | ||||
| 
 | ||||
| The plugin supports these options: | ||||
| 
 | ||||
| { | ||||
|     canvas: boolean | ||||
| } | ||||
| 
 | ||||
| The "canvas" option controls whether full canvas drawing is enabled, making it | ||||
| possible to toggle on and off. This is useful when a plot uses HTML text in the | ||||
| browser, but needs to redraw with canvas text when exporting as an image. | ||||
| 
 | ||||
| */ | ||||
| 
 | ||||
| (function($) { | ||||
| 
 | ||||
| 	var options = { | ||||
| 		canvas: true | ||||
| 	}; | ||||
| 
 | ||||
| 	var render, getTextInfo, addText; | ||||
| 
 | ||||
| 	// Cache the prototype hasOwnProperty for faster access
 | ||||
| 
 | ||||
| 	var hasOwnProperty = Object.prototype.hasOwnProperty; | ||||
| 
 | ||||
| 	function init(plot, classes) { | ||||
| 
 | ||||
| 		var Canvas = classes.Canvas; | ||||
| 
 | ||||
| 		// We only want to replace the functions once; the second time around
 | ||||
| 		// we would just get our new function back.  This whole replacing of
 | ||||
| 		// prototype functions is a disaster, and needs to be changed ASAP.
 | ||||
| 
 | ||||
| 		if (render == null) { | ||||
| 			getTextInfo = Canvas.prototype.getTextInfo, | ||||
| 			addText = Canvas.prototype.addText, | ||||
| 			render = Canvas.prototype.render; | ||||
| 		} | ||||
| 
 | ||||
| 		// Finishes rendering the canvas, including overlaid text
 | ||||
| 
 | ||||
| 		Canvas.prototype.render = function() { | ||||
| 
 | ||||
| 			if (!plot.getOptions().canvas) { | ||||
| 				return render.call(this); | ||||
| 			} | ||||
| 
 | ||||
| 			var context = this.context, | ||||
| 				cache = this._textCache; | ||||
| 
 | ||||
| 			// For each text layer, render elements marked as active
 | ||||
| 
 | ||||
| 			context.save(); | ||||
| 			context.textBaseline = "middle"; | ||||
| 
 | ||||
| 			for (var layerKey in cache) { | ||||
| 				if (hasOwnProperty.call(cache, layerKey)) { | ||||
| 					var layerCache = cache[layerKey]; | ||||
| 					for (var styleKey in layerCache) { | ||||
| 						if (hasOwnProperty.call(layerCache, styleKey)) { | ||||
| 							var styleCache = layerCache[styleKey], | ||||
| 								updateStyles = true; | ||||
| 							for (var key in styleCache) { | ||||
| 								if (hasOwnProperty.call(styleCache, key)) { | ||||
| 
 | ||||
| 									var info = styleCache[key], | ||||
| 										positions = info.positions, | ||||
| 										lines = info.lines; | ||||
| 
 | ||||
| 									// Since every element at this level of the cache have the
 | ||||
| 									// same font and fill styles, we can just change them once
 | ||||
| 									// using the values from the first element.
 | ||||
| 
 | ||||
| 									if (updateStyles) { | ||||
| 										context.fillStyle = info.font.color; | ||||
| 										context.font = info.font.definition; | ||||
| 										updateStyles = false; | ||||
| 									} | ||||
| 
 | ||||
| 									for (var i = 0, position; position = positions[i]; i++) { | ||||
| 										if (position.active) { | ||||
| 											for (var j = 0, line; line = position.lines[j]; j++) { | ||||
| 												context.fillText(lines[j].text, line[0], line[1]); | ||||
| 											} | ||||
| 										} else { | ||||
| 											positions.splice(i--, 1); | ||||
| 										} | ||||
| 									} | ||||
| 
 | ||||
| 									if (positions.length == 0) { | ||||
| 										delete styleCache[key]; | ||||
| 									} | ||||
| 								} | ||||
| 							} | ||||
| 						} | ||||
| 					} | ||||
| 				} | ||||
| 			} | ||||
| 
 | ||||
| 			context.restore(); | ||||
| 		}; | ||||
| 
 | ||||
| 		// Creates (if necessary) and returns a text info object.
 | ||||
| 		//
 | ||||
| 		// When the canvas option is set, the object looks like this:
 | ||||
| 		//
 | ||||
| 		// {
 | ||||
| 		//     width: Width of the text's bounding box.
 | ||||
| 		//     height: Height of the text's bounding box.
 | ||||
| 		//     positions: Array of positions at which this text is drawn.
 | ||||
| 		//     lines: [{
 | ||||
| 		//         height: Height of this line.
 | ||||
| 		//         widths: Width of this line.
 | ||||
| 		//         text: Text on this line.
 | ||||
| 		//     }],
 | ||||
| 		//     font: {
 | ||||
| 		//         definition: Canvas font property string.
 | ||||
| 		//         color: Color of the text.
 | ||||
| 		//     },
 | ||||
| 		// }
 | ||||
| 		//
 | ||||
| 		// The positions array contains objects that look like this:
 | ||||
| 		//
 | ||||
| 		// {
 | ||||
| 		//     active: Flag indicating whether the text should be visible.
 | ||||
| 		//     lines: Array of [x, y] coordinates at which to draw the line.
 | ||||
| 		//     x: X coordinate at which to draw the text.
 | ||||
| 		//     y: Y coordinate at which to draw the text.
 | ||||
| 		// }
 | ||||
| 
 | ||||
| 		Canvas.prototype.getTextInfo = function(layer, text, font, angle, width) { | ||||
| 
 | ||||
| 			if (!plot.getOptions().canvas) { | ||||
| 				return getTextInfo.call(this, layer, text, font, angle, width); | ||||
| 			} | ||||
| 
 | ||||
| 			var textStyle, layerCache, styleCache, info; | ||||
| 
 | ||||
| 			// Cast the value to a string, in case we were given a number
 | ||||
| 
 | ||||
| 			text = "" + text; | ||||
| 
 | ||||
| 			// If the font is a font-spec object, generate a CSS definition
 | ||||
| 
 | ||||
| 			if (typeof font === "object") { | ||||
| 				textStyle = font.style + " " + font.variant + " " + font.weight + " " + font.size + "px " + font.family; | ||||
| 			} else { | ||||
| 				textStyle = font; | ||||
| 			} | ||||
| 
 | ||||
| 			// Retrieve (or create) the cache for the text's layer and styles
 | ||||
| 
 | ||||
| 			layerCache = this._textCache[layer]; | ||||
| 
 | ||||
| 			if (layerCache == null) { | ||||
| 				layerCache = this._textCache[layer] = {}; | ||||
| 			} | ||||
| 
 | ||||
| 			styleCache = layerCache[textStyle]; | ||||
| 
 | ||||
| 			if (styleCache == null) { | ||||
| 				styleCache = layerCache[textStyle] = {}; | ||||
| 			} | ||||
| 
 | ||||
| 			info = styleCache[text]; | ||||
| 
 | ||||
| 			if (info == null) { | ||||
| 
 | ||||
| 				var context = this.context; | ||||
| 
 | ||||
| 				// If the font was provided as CSS, create a div with those
 | ||||
| 				// classes and examine it to generate a canvas font spec.
 | ||||
| 
 | ||||
| 				if (typeof font !== "object") { | ||||
| 
 | ||||
| 					var element = $("<div> </div>") | ||||
| 						.css("position", "absolute") | ||||
| 						.addClass(typeof font === "string" ? font : null) | ||||
| 						.appendTo(this.getTextLayer(layer)); | ||||
| 
 | ||||
| 					font = { | ||||
| 						lineHeight: element.height(), | ||||
| 						style: element.css("font-style"), | ||||
| 						variant: element.css("font-variant"), | ||||
| 						weight: element.css("font-weight"), | ||||
| 						family: element.css("font-family"), | ||||
| 						color: element.css("color") | ||||
| 					}; | ||||
| 
 | ||||
| 					// Setting line-height to 1, without units, sets it equal
 | ||||
| 					// to the font-size, even if the font-size is abstract,
 | ||||
| 					// like 'smaller'.  This enables us to read the real size
 | ||||
| 					// via the element's height, working around browsers that
 | ||||
| 					// return the literal 'smaller' value.
 | ||||
| 
 | ||||
| 					font.size = element.css("line-height", 1).height(); | ||||
| 
 | ||||
| 					element.remove(); | ||||
| 				} | ||||
| 
 | ||||
| 				textStyle = font.style + " " + font.variant + " " + font.weight + " " + font.size + "px " + font.family; | ||||
| 
 | ||||
| 				// Create a new info object, initializing the dimensions to
 | ||||
| 				// zero so we can count them up line-by-line.
 | ||||
| 
 | ||||
| 				info = styleCache[text] = { | ||||
| 					width: 0, | ||||
| 					height: 0, | ||||
| 					positions: [], | ||||
| 					lines: [], | ||||
| 					font: { | ||||
| 						definition: textStyle, | ||||
| 						color: font.color | ||||
| 					} | ||||
| 				}; | ||||
| 
 | ||||
| 				context.save(); | ||||
| 				context.font = textStyle; | ||||
| 
 | ||||
| 				// Canvas can't handle multi-line strings; break on various
 | ||||
| 				// newlines, including HTML brs, to build a list of lines.
 | ||||
| 				// Note that we could split directly on regexps, but IE < 9 is
 | ||||
| 				// broken; revisit when we drop IE 7/8 support.
 | ||||
| 
 | ||||
| 				var lines = (text + "").replace(/<br ?\/?>|\r\n|\r/g, "\n").split("\n"); | ||||
| 
 | ||||
| 				for (var i = 0; i < lines.length; ++i) { | ||||
| 
 | ||||
| 					var lineText = lines[i], | ||||
| 						measured = context.measureText(lineText); | ||||
| 
 | ||||
| 					info.width = Math.max(measured.width, info.width); | ||||
| 					info.height += font.lineHeight; | ||||
| 
 | ||||
| 					info.lines.push({ | ||||
| 						text: lineText, | ||||
| 						width: measured.width, | ||||
| 						height: font.lineHeight | ||||
| 					}); | ||||
| 				} | ||||
| 
 | ||||
| 				context.restore(); | ||||
| 			} | ||||
| 
 | ||||
| 			return info; | ||||
| 		}; | ||||
| 
 | ||||
| 		// Adds a text string to the canvas text overlay.
 | ||||
| 
 | ||||
| 		Canvas.prototype.addText = function(layer, x, y, text, font, angle, width, halign, valign) { | ||||
| 
 | ||||
| 			if (!plot.getOptions().canvas) { | ||||
| 				return addText.call(this, layer, x, y, text, font, angle, width, halign, valign); | ||||
| 			} | ||||
| 
 | ||||
| 			var info = this.getTextInfo(layer, text, font, angle, width), | ||||
| 				positions = info.positions, | ||||
| 				lines = info.lines; | ||||
| 
 | ||||
| 			// Text is drawn with baseline 'middle', which we need to account
 | ||||
| 			// for by adding half a line's height to the y position.
 | ||||
| 
 | ||||
| 			y += info.height / lines.length / 2; | ||||
| 
 | ||||
| 			// Tweak the initial y-position to match vertical alignment
 | ||||
| 
 | ||||
| 			if (valign == "middle") { | ||||
| 				y = Math.round(y - info.height / 2); | ||||
| 			} else if (valign == "bottom") { | ||||
| 				y = Math.round(y - info.height); | ||||
| 			} else { | ||||
| 				y = Math.round(y); | ||||
| 			} | ||||
| 
 | ||||
| 			// FIXME: LEGACY BROWSER FIX
 | ||||
| 			// AFFECTS: Opera < 12.00
 | ||||
| 
 | ||||
| 			// Offset the y coordinate, since Opera is off pretty
 | ||||
| 			// consistently compared to the other browsers.
 | ||||
| 
 | ||||
| 			if (!!(window.opera && window.opera.version().split(".")[0] < 12)) { | ||||
| 				y -= 2; | ||||
| 			} | ||||
| 
 | ||||
| 			// Determine whether this text already exists at this position.
 | ||||
| 			// If so, mark it for inclusion in the next render pass.
 | ||||
| 
 | ||||
| 			for (var i = 0, position; position = positions[i]; i++) { | ||||
| 				if (position.x == x && position.y == y) { | ||||
| 					position.active = true; | ||||
| 					return; | ||||
| 				} | ||||
| 			} | ||||
| 
 | ||||
| 			// If the text doesn't exist at this position, create a new entry
 | ||||
| 
 | ||||
| 			position = { | ||||
| 				active: true, | ||||
| 				lines: [], | ||||
| 				x: x, | ||||
| 				y: y | ||||
| 			}; | ||||
| 
 | ||||
| 			positions.push(position); | ||||
| 
 | ||||
| 			// Fill in the x & y positions of each line, adjusting them
 | ||||
| 			// individually for horizontal alignment.
 | ||||
| 
 | ||||
| 			for (var i = 0, line; line = lines[i]; i++) { | ||||
| 				if (halign == "center") { | ||||
| 					position.lines.push([Math.round(x - line.width / 2), y]); | ||||
| 				} else if (halign == "right") { | ||||
| 					position.lines.push([Math.round(x - line.width), y]); | ||||
| 				} else { | ||||
| 					position.lines.push([Math.round(x), y]); | ||||
| 				} | ||||
| 				y += line.height; | ||||
| 			} | ||||
| 		}; | ||||
| 	} | ||||
| 
 | ||||
| 	$.plot.plugins.push({ | ||||
| 		init: init, | ||||
| 		options: options, | ||||
| 		name: "canvas", | ||||
| 		version: "1.0" | ||||
| 	}); | ||||
| 
 | ||||
| })(jQuery); | ||||
							
								
								
									
										7
									
								
								hledger-web/static/js/jquery.flot.canvas.min.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								hledger-web/static/js/jquery.flot.canvas.min.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,7 @@ | ||||
| /* Javascript plotting library for jQuery, version 0.8.3. | ||||
| 
 | ||||
| Copyright (c) 2007-2014 IOLA and Ole Laursen. | ||||
| Licensed under the MIT license. | ||||
| 
 | ||||
| */ | ||||
| (function($){var options={canvas:true};var render,getTextInfo,addText;var hasOwnProperty=Object.prototype.hasOwnProperty;function init(plot,classes){var Canvas=classes.Canvas;if(render==null){getTextInfo=Canvas.prototype.getTextInfo,addText=Canvas.prototype.addText,render=Canvas.prototype.render}Canvas.prototype.render=function(){if(!plot.getOptions().canvas){return render.call(this)}var context=this.context,cache=this._textCache;context.save();context.textBaseline="middle";for(var layerKey in cache){if(hasOwnProperty.call(cache,layerKey)){var layerCache=cache[layerKey];for(var styleKey in layerCache){if(hasOwnProperty.call(layerCache,styleKey)){var styleCache=layerCache[styleKey],updateStyles=true;for(var key in styleCache){if(hasOwnProperty.call(styleCache,key)){var info=styleCache[key],positions=info.positions,lines=info.lines;if(updateStyles){context.fillStyle=info.font.color;context.font=info.font.definition;updateStyles=false}for(var i=0,position;position=positions[i];i++){if(position.active){for(var j=0,line;line=position.lines[j];j++){context.fillText(lines[j].text,line[0],line[1])}}else{positions.splice(i--,1)}}if(positions.length==0){delete styleCache[key]}}}}}}}context.restore()};Canvas.prototype.getTextInfo=function(layer,text,font,angle,width){if(!plot.getOptions().canvas){return getTextInfo.call(this,layer,text,font,angle,width)}var textStyle,layerCache,styleCache,info;text=""+text;if(typeof font==="object"){textStyle=font.style+" "+font.variant+" "+font.weight+" "+font.size+"px "+font.family}else{textStyle=font}layerCache=this._textCache[layer];if(layerCache==null){layerCache=this._textCache[layer]={}}styleCache=layerCache[textStyle];if(styleCache==null){styleCache=layerCache[textStyle]={}}info=styleCache[text];if(info==null){var context=this.context;if(typeof font!=="object"){var element=$("<div> </div>").css("position","absolute").addClass(typeof font==="string"?font:null).appendTo(this.getTextLayer(layer));font={lineHeight:element.height(),style:element.css("font-style"),variant:element.css("font-variant"),weight:element.css("font-weight"),family:element.css("font-family"),color:element.css("color")};font.size=element.css("line-height",1).height();element.remove()}textStyle=font.style+" "+font.variant+" "+font.weight+" "+font.size+"px "+font.family;info=styleCache[text]={width:0,height:0,positions:[],lines:[],font:{definition:textStyle,color:font.color}};context.save();context.font=textStyle;var lines=(text+"").replace(/<br ?\/?>|\r\n|\r/g,"\n").split("\n");for(var i=0;i<lines.length;++i){var lineText=lines[i],measured=context.measureText(lineText);info.width=Math.max(measured.width,info.width);info.height+=font.lineHeight;info.lines.push({text:lineText,width:measured.width,height:font.lineHeight})}context.restore()}return info};Canvas.prototype.addText=function(layer,x,y,text,font,angle,width,halign,valign){if(!plot.getOptions().canvas){return addText.call(this,layer,x,y,text,font,angle,width,halign,valign)}var info=this.getTextInfo(layer,text,font,angle,width),positions=info.positions,lines=info.lines;y+=info.height/lines.length/2;if(valign=="middle"){y=Math.round(y-info.height/2)}else if(valign=="bottom"){y=Math.round(y-info.height)}else{y=Math.round(y)}if(!!(window.opera&&window.opera.version().split(".")[0]<12)){y-=2}for(var i=0,position;position=positions[i];i++){if(position.x==x&&position.y==y){position.active=true;return}}position={active:true,lines:[],x:x,y:y};positions.push(position);for(var i=0,line;line=lines[i];i++){if(halign=="center"){position.lines.push([Math.round(x-line.width/2),y])}else if(halign=="right"){position.lines.push([Math.round(x-line.width),y])}else{position.lines.push([Math.round(x),y])}y+=line.height}}}$.plot.plugins.push({init:init,options:options,name:"canvas",version:"1.0"})})(jQuery); | ||||
							
								
								
									
										190
									
								
								hledger-web/static/js/jquery.flot.categories.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										190
									
								
								hledger-web/static/js/jquery.flot.categories.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,190 @@ | ||||
| /* Flot plugin for plotting textual data or categories. | ||||
| 
 | ||||
| Copyright (c) 2007-2014 IOLA and Ole Laursen. | ||||
| Licensed under the MIT license. | ||||
| 
 | ||||
| Consider a dataset like [["February", 34], ["March", 20], ...]. This plugin | ||||
| allows you to plot such a dataset directly. | ||||
| 
 | ||||
| To enable it, you must specify mode: "categories" on the axis with the textual | ||||
| labels, e.g. | ||||
| 
 | ||||
| 	$.plot("#placeholder", data, { xaxis: { mode: "categories" } }); | ||||
| 
 | ||||
| By default, the labels are ordered as they are met in the data series. If you | ||||
| need a different ordering, you can specify "categories" on the axis options | ||||
| and list the categories there: | ||||
| 
 | ||||
| 	xaxis: { | ||||
| 		mode: "categories", | ||||
| 		categories: ["February", "March", "April"] | ||||
| 	} | ||||
| 
 | ||||
| If you need to customize the distances between the categories, you can specify | ||||
| "categories" as an object mapping labels to values | ||||
| 
 | ||||
| 	xaxis: { | ||||
| 		mode: "categories", | ||||
| 		categories: { "February": 1, "March": 3, "April": 4 } | ||||
| 	} | ||||
| 
 | ||||
| If you don't specify all categories, the remaining categories will be numbered | ||||
| from the max value plus 1 (with a spacing of 1 between each). | ||||
| 
 | ||||
| Internally, the plugin works by transforming the input data through an auto- | ||||
| generated mapping where the first category becomes 0, the second 1, etc. | ||||
| Hence, a point like ["February", 34] becomes [0, 34] internally in Flot (this | ||||
| is visible in hover and click events that return numbers rather than the | ||||
| category labels). The plugin also overrides the tick generator to spit out the | ||||
| categories as ticks instead of the values. | ||||
| 
 | ||||
| If you need to map a value back to its label, the mapping is always accessible | ||||
| as "categories" on the axis object, e.g. plot.getAxes().xaxis.categories. | ||||
| 
 | ||||
| */ | ||||
| 
 | ||||
| (function ($) { | ||||
|     var options = { | ||||
|         xaxis: { | ||||
|             categories: null | ||||
|         }, | ||||
|         yaxis: { | ||||
|             categories: null | ||||
|         } | ||||
|     }; | ||||
|      | ||||
|     function processRawData(plot, series, data, datapoints) { | ||||
|         // if categories are enabled, we need to disable
 | ||||
|         // auto-transformation to numbers so the strings are intact
 | ||||
|         // for later processing
 | ||||
| 
 | ||||
|         var xCategories = series.xaxis.options.mode == "categories", | ||||
|             yCategories = series.yaxis.options.mode == "categories"; | ||||
|          | ||||
|         if (!(xCategories || yCategories)) | ||||
|             return; | ||||
| 
 | ||||
|         var format = datapoints.format; | ||||
| 
 | ||||
|         if (!format) { | ||||
|             // FIXME: auto-detection should really not be defined here
 | ||||
|             var s = series; | ||||
|             format = []; | ||||
|             format.push({ x: true, number: true, required: true }); | ||||
|             format.push({ y: true, number: true, required: true }); | ||||
| 
 | ||||
|             if (s.bars.show || (s.lines.show && s.lines.fill)) { | ||||
|                 var autoscale = !!((s.bars.show && s.bars.zero) || (s.lines.show && s.lines.zero)); | ||||
|                 format.push({ y: true, number: true, required: false, defaultValue: 0, autoscale: autoscale }); | ||||
|                 if (s.bars.horizontal) { | ||||
|                     delete format[format.length - 1].y; | ||||
|                     format[format.length - 1].x = true; | ||||
|                 } | ||||
|             } | ||||
|              | ||||
|             datapoints.format = format; | ||||
|         } | ||||
| 
 | ||||
|         for (var m = 0; m < format.length; ++m) { | ||||
|             if (format[m].x && xCategories) | ||||
|                 format[m].number = false; | ||||
|              | ||||
|             if (format[m].y && yCategories) | ||||
|                 format[m].number = false; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     function getNextIndex(categories) { | ||||
|         var index = -1; | ||||
|          | ||||
|         for (var v in categories) | ||||
|             if (categories[v] > index) | ||||
|                 index = categories[v]; | ||||
| 
 | ||||
|         return index + 1; | ||||
|     } | ||||
| 
 | ||||
|     function categoriesTickGenerator(axis) { | ||||
|         var res = []; | ||||
|         for (var label in axis.categories) { | ||||
|             var v = axis.categories[label]; | ||||
|             if (v >= axis.min && v <= axis.max) | ||||
|                 res.push([v, label]); | ||||
|         } | ||||
| 
 | ||||
|         res.sort(function (a, b) { return a[0] - b[0]; }); | ||||
| 
 | ||||
|         return res; | ||||
|     } | ||||
|      | ||||
|     function setupCategoriesForAxis(series, axis, datapoints) { | ||||
|         if (series[axis].options.mode != "categories") | ||||
|             return; | ||||
|          | ||||
|         if (!series[axis].categories) { | ||||
|             // parse options
 | ||||
|             var c = {}, o = series[axis].options.categories || {}; | ||||
|             if ($.isArray(o)) { | ||||
|                 for (var i = 0; i < o.length; ++i) | ||||
|                     c[o[i]] = i; | ||||
|             } | ||||
|             else { | ||||
|                 for (var v in o) | ||||
|                     c[v] = o[v]; | ||||
|             } | ||||
|              | ||||
|             series[axis].categories = c; | ||||
|         } | ||||
| 
 | ||||
|         // fix ticks
 | ||||
|         if (!series[axis].options.ticks) | ||||
|             series[axis].options.ticks = categoriesTickGenerator; | ||||
| 
 | ||||
|         transformPointsOnAxis(datapoints, axis, series[axis].categories); | ||||
|     } | ||||
|      | ||||
|     function transformPointsOnAxis(datapoints, axis, categories) { | ||||
|         // go through the points, transforming them
 | ||||
|         var points = datapoints.points, | ||||
|             ps = datapoints.pointsize, | ||||
|             format = datapoints.format, | ||||
|             formatColumn = axis.charAt(0), | ||||
|             index = getNextIndex(categories); | ||||
| 
 | ||||
|         for (var i = 0; i < points.length; i += ps) { | ||||
|             if (points[i] == null) | ||||
|                 continue; | ||||
|              | ||||
|             for (var m = 0; m < ps; ++m) { | ||||
|                 var val = points[i + m]; | ||||
| 
 | ||||
|                 if (val == null || !format[m][formatColumn]) | ||||
|                     continue; | ||||
| 
 | ||||
|                 if (!(val in categories)) { | ||||
|                     categories[val] = index; | ||||
|                     ++index; | ||||
|                 } | ||||
|                  | ||||
|                 points[i + m] = categories[val]; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     function processDatapoints(plot, series, datapoints) { | ||||
|         setupCategoriesForAxis(series, "xaxis", datapoints); | ||||
|         setupCategoriesForAxis(series, "yaxis", datapoints); | ||||
|     } | ||||
| 
 | ||||
|     function init(plot) { | ||||
|         plot.hooks.processRawData.push(processRawData); | ||||
|         plot.hooks.processDatapoints.push(processDatapoints); | ||||
|     } | ||||
|      | ||||
|     $.plot.plugins.push({ | ||||
|         init: init, | ||||
|         options: options, | ||||
|         name: 'categories', | ||||
|         version: '1.0' | ||||
|     }); | ||||
| })(jQuery); | ||||
							
								
								
									
										7
									
								
								hledger-web/static/js/jquery.flot.categories.min.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								hledger-web/static/js/jquery.flot.categories.min.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,7 @@ | ||||
| /* Javascript plotting library for jQuery, version 0.8.3. | ||||
| 
 | ||||
| Copyright (c) 2007-2014 IOLA and Ole Laursen. | ||||
| Licensed under the MIT license. | ||||
| 
 | ||||
| */ | ||||
| (function($){var options={xaxis:{categories:null},yaxis:{categories:null}};function processRawData(plot,series,data,datapoints){var xCategories=series.xaxis.options.mode=="categories",yCategories=series.yaxis.options.mode=="categories";if(!(xCategories||yCategories))return;var format=datapoints.format;if(!format){var s=series;format=[];format.push({x:true,number:true,required:true});format.push({y:true,number:true,required:true});if(s.bars.show||s.lines.show&&s.lines.fill){var autoscale=!!(s.bars.show&&s.bars.zero||s.lines.show&&s.lines.zero);format.push({y:true,number:true,required:false,defaultValue:0,autoscale:autoscale});if(s.bars.horizontal){delete format[format.length-1].y;format[format.length-1].x=true}}datapoints.format=format}for(var m=0;m<format.length;++m){if(format[m].x&&xCategories)format[m].number=false;if(format[m].y&&yCategories)format[m].number=false}}function getNextIndex(categories){var index=-1;for(var v in categories)if(categories[v]>index)index=categories[v];return index+1}function categoriesTickGenerator(axis){var res=[];for(var label in axis.categories){var v=axis.categories[label];if(v>=axis.min&&v<=axis.max)res.push([v,label])}res.sort(function(a,b){return a[0]-b[0]});return res}function setupCategoriesForAxis(series,axis,datapoints){if(series[axis].options.mode!="categories")return;if(!series[axis].categories){var c={},o=series[axis].options.categories||{};if($.isArray(o)){for(var i=0;i<o.length;++i)c[o[i]]=i}else{for(var v in o)c[v]=o[v]}series[axis].categories=c}if(!series[axis].options.ticks)series[axis].options.ticks=categoriesTickGenerator;transformPointsOnAxis(datapoints,axis,series[axis].categories)}function transformPointsOnAxis(datapoints,axis,categories){var points=datapoints.points,ps=datapoints.pointsize,format=datapoints.format,formatColumn=axis.charAt(0),index=getNextIndex(categories);for(var i=0;i<points.length;i+=ps){if(points[i]==null)continue;for(var m=0;m<ps;++m){var val=points[i+m];if(val==null||!format[m][formatColumn])continue;if(!(val in categories)){categories[val]=index;++index}points[i+m]=categories[val]}}}function processDatapoints(plot,series,datapoints){setupCategoriesForAxis(series,"xaxis",datapoints);setupCategoriesForAxis(series,"yaxis",datapoints)}function init(plot){plot.hooks.processRawData.push(processRawData);plot.hooks.processDatapoints.push(processDatapoints)}$.plot.plugins.push({init:init,options:options,name:"categories",version:"1.0"})})(jQuery); | ||||
							
								
								
									
										176
									
								
								hledger-web/static/js/jquery.flot.crosshair.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										176
									
								
								hledger-web/static/js/jquery.flot.crosshair.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,176 @@ | ||||
| /* Flot plugin for showing crosshairs when the mouse hovers over the plot. | ||||
| 
 | ||||
| Copyright (c) 2007-2014 IOLA and Ole Laursen. | ||||
| Licensed under the MIT license. | ||||
| 
 | ||||
| The plugin supports these options: | ||||
| 
 | ||||
| 	crosshair: { | ||||
| 		mode: null or "x" or "y" or "xy" | ||||
| 		color: color | ||||
| 		lineWidth: number | ||||
| 	} | ||||
| 
 | ||||
| Set the mode to one of "x", "y" or "xy". The "x" mode enables a vertical | ||||
| crosshair that lets you trace the values on the x axis, "y" enables a | ||||
| horizontal crosshair and "xy" enables them both. "color" is the color of the | ||||
| crosshair (default is "rgba(170, 0, 0, 0.80)"), "lineWidth" is the width of | ||||
| the drawn lines (default is 1). | ||||
| 
 | ||||
| The plugin also adds four public methods: | ||||
| 
 | ||||
|   - setCrosshair( pos ) | ||||
| 
 | ||||
|     Set the position of the crosshair. Note that this is cleared if the user | ||||
|     moves the mouse. "pos" is in coordinates of the plot and should be on the | ||||
|     form { x: xpos, y: ypos } (you can use x2/x3/... if you're using multiple | ||||
|     axes), which is coincidentally the same format as what you get from a | ||||
|     "plothover" event. If "pos" is null, the crosshair is cleared. | ||||
| 
 | ||||
|   - clearCrosshair() | ||||
| 
 | ||||
|     Clear the crosshair. | ||||
| 
 | ||||
|   - lockCrosshair(pos) | ||||
| 
 | ||||
|     Cause the crosshair to lock to the current location, no longer updating if | ||||
|     the user moves the mouse. Optionally supply a position (passed on to | ||||
|     setCrosshair()) to move it to. | ||||
| 
 | ||||
|     Example usage: | ||||
| 
 | ||||
| 	var myFlot = $.plot( $("#graph"), ..., { crosshair: { mode: "x" } } }; | ||||
| 	$("#graph").bind( "plothover", function ( evt, position, item ) { | ||||
| 		if ( item ) { | ||||
| 			// Lock the crosshair to the data point being hovered
 | ||||
| 			myFlot.lockCrosshair({ | ||||
| 				x: item.datapoint[ 0 ], | ||||
| 				y: item.datapoint[ 1 ] | ||||
| 			}); | ||||
| 		} else { | ||||
| 			// Return normal crosshair operation
 | ||||
| 			myFlot.unlockCrosshair(); | ||||
| 		} | ||||
| 	}); | ||||
| 
 | ||||
|   - unlockCrosshair() | ||||
| 
 | ||||
|     Free the crosshair to move again after locking it. | ||||
| */ | ||||
| 
 | ||||
| (function ($) { | ||||
|     var options = { | ||||
|         crosshair: { | ||||
|             mode: null, // one of null, "x", "y" or "xy",
 | ||||
|             color: "rgba(170, 0, 0, 0.80)", | ||||
|             lineWidth: 1 | ||||
|         } | ||||
|     }; | ||||
|      | ||||
|     function init(plot) { | ||||
|         // position of crosshair in pixels
 | ||||
|         var crosshair = { x: -1, y: -1, locked: false }; | ||||
| 
 | ||||
|         plot.setCrosshair = function setCrosshair(pos) { | ||||
|             if (!pos) | ||||
|                 crosshair.x = -1; | ||||
|             else { | ||||
|                 var o = plot.p2c(pos); | ||||
|                 crosshair.x = Math.max(0, Math.min(o.left, plot.width())); | ||||
|                 crosshair.y = Math.max(0, Math.min(o.top, plot.height())); | ||||
|             } | ||||
|              | ||||
|             plot.triggerRedrawOverlay(); | ||||
|         }; | ||||
|          | ||||
|         plot.clearCrosshair = plot.setCrosshair; // passes null for pos
 | ||||
|          | ||||
|         plot.lockCrosshair = function lockCrosshair(pos) { | ||||
|             if (pos) | ||||
|                 plot.setCrosshair(pos); | ||||
|             crosshair.locked = true; | ||||
|         }; | ||||
| 
 | ||||
|         plot.unlockCrosshair = function unlockCrosshair() { | ||||
|             crosshair.locked = false; | ||||
|         }; | ||||
| 
 | ||||
|         function onMouseOut(e) { | ||||
|             if (crosshair.locked) | ||||
|                 return; | ||||
| 
 | ||||
|             if (crosshair.x != -1) { | ||||
|                 crosshair.x = -1; | ||||
|                 plot.triggerRedrawOverlay(); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         function onMouseMove(e) { | ||||
|             if (crosshair.locked) | ||||
|                 return; | ||||
|                  | ||||
|             if (plot.getSelection && plot.getSelection()) { | ||||
|                 crosshair.x = -1; // hide the crosshair while selecting
 | ||||
|                 return; | ||||
|             } | ||||
|                  | ||||
|             var offset = plot.offset(); | ||||
|             crosshair.x = Math.max(0, Math.min(e.pageX - offset.left, plot.width())); | ||||
|             crosshair.y = Math.max(0, Math.min(e.pageY - offset.top, plot.height())); | ||||
|             plot.triggerRedrawOverlay(); | ||||
|         } | ||||
|          | ||||
|         plot.hooks.bindEvents.push(function (plot, eventHolder) { | ||||
|             if (!plot.getOptions().crosshair.mode) | ||||
|                 return; | ||||
| 
 | ||||
|             eventHolder.mouseout(onMouseOut); | ||||
|             eventHolder.mousemove(onMouseMove); | ||||
|         }); | ||||
| 
 | ||||
|         plot.hooks.drawOverlay.push(function (plot, ctx) { | ||||
|             var c = plot.getOptions().crosshair; | ||||
|             if (!c.mode) | ||||
|                 return; | ||||
| 
 | ||||
|             var plotOffset = plot.getPlotOffset(); | ||||
|              | ||||
|             ctx.save(); | ||||
|             ctx.translate(plotOffset.left, plotOffset.top); | ||||
| 
 | ||||
|             if (crosshair.x != -1) { | ||||
|                 var adj = plot.getOptions().crosshair.lineWidth % 2 ? 0.5 : 0; | ||||
| 
 | ||||
|                 ctx.strokeStyle = c.color; | ||||
|                 ctx.lineWidth = c.lineWidth; | ||||
|                 ctx.lineJoin = "round"; | ||||
| 
 | ||||
|                 ctx.beginPath(); | ||||
|                 if (c.mode.indexOf("x") != -1) { | ||||
|                     var drawX = Math.floor(crosshair.x) + adj; | ||||
|                     ctx.moveTo(drawX, 0); | ||||
|                     ctx.lineTo(drawX, plot.height()); | ||||
|                 } | ||||
|                 if (c.mode.indexOf("y") != -1) { | ||||
|                     var drawY = Math.floor(crosshair.y) + adj; | ||||
|                     ctx.moveTo(0, drawY); | ||||
|                     ctx.lineTo(plot.width(), drawY); | ||||
|                 } | ||||
|                 ctx.stroke(); | ||||
|             } | ||||
|             ctx.restore(); | ||||
|         }); | ||||
| 
 | ||||
|         plot.hooks.shutdown.push(function (plot, eventHolder) { | ||||
|             eventHolder.unbind("mouseout", onMouseOut); | ||||
|             eventHolder.unbind("mousemove", onMouseMove); | ||||
|         }); | ||||
|     } | ||||
|      | ||||
|     $.plot.plugins.push({ | ||||
|         init: init, | ||||
|         options: options, | ||||
|         name: 'crosshair', | ||||
|         version: '1.0' | ||||
|     }); | ||||
| })(jQuery); | ||||
							
								
								
									
										7
									
								
								hledger-web/static/js/jquery.flot.crosshair.min.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								hledger-web/static/js/jquery.flot.crosshair.min.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,7 @@ | ||||
| /* Javascript plotting library for jQuery, version 0.8.3. | ||||
| 
 | ||||
| Copyright (c) 2007-2014 IOLA and Ole Laursen. | ||||
| Licensed under the MIT license. | ||||
| 
 | ||||
| */ | ||||
| (function($){var options={crosshair:{mode:null,color:"rgba(170, 0, 0, 0.80)",lineWidth:1}};function init(plot){var crosshair={x:-1,y:-1,locked:false};plot.setCrosshair=function setCrosshair(pos){if(!pos)crosshair.x=-1;else{var o=plot.p2c(pos);crosshair.x=Math.max(0,Math.min(o.left,plot.width()));crosshair.y=Math.max(0,Math.min(o.top,plot.height()))}plot.triggerRedrawOverlay()};plot.clearCrosshair=plot.setCrosshair;plot.lockCrosshair=function lockCrosshair(pos){if(pos)plot.setCrosshair(pos);crosshair.locked=true};plot.unlockCrosshair=function unlockCrosshair(){crosshair.locked=false};function onMouseOut(e){if(crosshair.locked)return;if(crosshair.x!=-1){crosshair.x=-1;plot.triggerRedrawOverlay()}}function onMouseMove(e){if(crosshair.locked)return;if(plot.getSelection&&plot.getSelection()){crosshair.x=-1;return}var offset=plot.offset();crosshair.x=Math.max(0,Math.min(e.pageX-offset.left,plot.width()));crosshair.y=Math.max(0,Math.min(e.pageY-offset.top,plot.height()));plot.triggerRedrawOverlay()}plot.hooks.bindEvents.push(function(plot,eventHolder){if(!plot.getOptions().crosshair.mode)return;eventHolder.mouseout(onMouseOut);eventHolder.mousemove(onMouseMove)});plot.hooks.drawOverlay.push(function(plot,ctx){var c=plot.getOptions().crosshair;if(!c.mode)return;var plotOffset=plot.getPlotOffset();ctx.save();ctx.translate(plotOffset.left,plotOffset.top);if(crosshair.x!=-1){var adj=plot.getOptions().crosshair.lineWidth%2?.5:0;ctx.strokeStyle=c.color;ctx.lineWidth=c.lineWidth;ctx.lineJoin="round";ctx.beginPath();if(c.mode.indexOf("x")!=-1){var drawX=Math.floor(crosshair.x)+adj;ctx.moveTo(drawX,0);ctx.lineTo(drawX,plot.height())}if(c.mode.indexOf("y")!=-1){var drawY=Math.floor(crosshair.y)+adj;ctx.moveTo(0,drawY);ctx.lineTo(plot.width(),drawY)}ctx.stroke()}ctx.restore()});plot.hooks.shutdown.push(function(plot,eventHolder){eventHolder.unbind("mouseout",onMouseOut);eventHolder.unbind("mousemove",onMouseMove)})}$.plot.plugins.push({init:init,options:options,name:"crosshair",version:"1.0"})})(jQuery); | ||||
							
								
								
									
										353
									
								
								hledger-web/static/js/jquery.flot.errorbars.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										353
									
								
								hledger-web/static/js/jquery.flot.errorbars.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,353 @@ | ||||
| /* Flot plugin for plotting error bars. | ||||
| 
 | ||||
| Copyright (c) 2007-2014 IOLA and Ole Laursen. | ||||
| Licensed under the MIT license. | ||||
| 
 | ||||
| Error bars are used to show standard deviation and other statistical | ||||
| properties in a plot. | ||||
| 
 | ||||
| * Created by Rui Pereira  -  rui (dot) pereira (at) gmail (dot) com | ||||
| 
 | ||||
| This plugin allows you to plot error-bars over points. Set "errorbars" inside | ||||
| the points series to the axis name over which there will be error values in | ||||
| your data array (*even* if you do not intend to plot them later, by setting | ||||
| "show: null" on xerr/yerr). | ||||
| 
 | ||||
| The plugin supports these options: | ||||
| 
 | ||||
| 	series: { | ||||
| 		points: { | ||||
| 			errorbars: "x" or "y" or "xy", | ||||
| 			xerr: { | ||||
| 				show: null/false or true, | ||||
| 				asymmetric: null/false or true, | ||||
| 				upperCap: null or "-" or function, | ||||
| 				lowerCap: null or "-" or function, | ||||
| 				color: null or color, | ||||
| 				radius: null or number | ||||
| 			}, | ||||
| 			yerr: { same options as xerr } | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| Each data point array is expected to be of the type: | ||||
| 
 | ||||
| 	"x"  [ x, y, xerr ] | ||||
| 	"y"  [ x, y, yerr ] | ||||
| 	"xy" [ x, y, xerr, yerr ] | ||||
| 
 | ||||
| Where xerr becomes xerr_lower,xerr_upper for the asymmetric error case, and | ||||
| equivalently for yerr. Eg., a datapoint for the "xy" case with symmetric | ||||
| error-bars on X and asymmetric on Y would be: | ||||
| 
 | ||||
| 	[ x, y, xerr, yerr_lower, yerr_upper ] | ||||
| 
 | ||||
| By default no end caps are drawn. Setting upperCap and/or lowerCap to "-" will | ||||
| draw a small cap perpendicular to the error bar. They can also be set to a | ||||
| user-defined drawing function, with (ctx, x, y, radius) as parameters, as eg. | ||||
| 
 | ||||
| 	function drawSemiCircle( ctx, x, y, radius ) { | ||||
| 		ctx.beginPath(); | ||||
| 		ctx.arc( x, y, radius, 0, Math.PI, false ); | ||||
| 		ctx.moveTo( x - radius, y ); | ||||
| 		ctx.lineTo( x + radius, y ); | ||||
| 		ctx.stroke(); | ||||
| 	} | ||||
| 
 | ||||
| Color and radius both default to the same ones of the points series if not | ||||
| set. The independent radius parameter on xerr/yerr is useful for the case when | ||||
| we may want to add error-bars to a line, without showing the interconnecting | ||||
| points (with radius: 0), and still showing end caps on the error-bars. | ||||
| shadowSize and lineWidth are derived as well from the points series. | ||||
| 
 | ||||
| */ | ||||
| 
 | ||||
| (function ($) { | ||||
|     var options = { | ||||
|         series: { | ||||
|             points: { | ||||
|                 errorbars: null, //should be 'x', 'y' or 'xy'
 | ||||
|                 xerr: { err: 'x', show: null, asymmetric: null, upperCap: null, lowerCap: null, color: null, radius: null}, | ||||
|                 yerr: { err: 'y', show: null, asymmetric: null, upperCap: null, lowerCap: null, color: null, radius: null} | ||||
|             } | ||||
|         } | ||||
|     }; | ||||
| 
 | ||||
|     function processRawData(plot, series, data, datapoints){ | ||||
|         if (!series.points.errorbars) | ||||
|             return; | ||||
| 
 | ||||
|         // x,y values
 | ||||
|         var format = [ | ||||
|             { x: true, number: true, required: true }, | ||||
|             { y: true, number: true, required: true } | ||||
|         ]; | ||||
| 
 | ||||
|         var errors = series.points.errorbars; | ||||
|         // error bars - first X then Y
 | ||||
|         if (errors == 'x' || errors == 'xy') { | ||||
|             // lower / upper error
 | ||||
|             if (series.points.xerr.asymmetric) { | ||||
|                 format.push({ x: true, number: true, required: true }); | ||||
|                 format.push({ x: true, number: true, required: true }); | ||||
|             } else | ||||
|                 format.push({ x: true, number: true, required: true }); | ||||
|         } | ||||
|         if (errors == 'y' || errors == 'xy') { | ||||
|             // lower / upper error
 | ||||
|             if (series.points.yerr.asymmetric) { | ||||
|                 format.push({ y: true, number: true, required: true }); | ||||
|                 format.push({ y: true, number: true, required: true }); | ||||
|             } else | ||||
|                 format.push({ y: true, number: true, required: true }); | ||||
|         } | ||||
|         datapoints.format = format; | ||||
|     } | ||||
| 
 | ||||
|     function parseErrors(series, i){ | ||||
| 
 | ||||
|         var points = series.datapoints.points; | ||||
| 
 | ||||
|         // read errors from points array
 | ||||
|         var exl = null, | ||||
|                 exu = null, | ||||
|                 eyl = null, | ||||
|                 eyu = null; | ||||
|         var xerr = series.points.xerr, | ||||
|                 yerr = series.points.yerr; | ||||
| 
 | ||||
|         var eb = series.points.errorbars; | ||||
|         // error bars - first X
 | ||||
|         if (eb == 'x' || eb == 'xy') { | ||||
|             if (xerr.asymmetric) { | ||||
|                 exl = points[i + 2]; | ||||
|                 exu = points[i + 3]; | ||||
|                 if (eb == 'xy') | ||||
|                     if (yerr.asymmetric){ | ||||
|                         eyl = points[i + 4]; | ||||
|                         eyu = points[i + 5]; | ||||
|                     } else eyl = points[i + 4]; | ||||
|             } else { | ||||
|                 exl = points[i + 2]; | ||||
|                 if (eb == 'xy') | ||||
|                     if (yerr.asymmetric) { | ||||
|                         eyl = points[i + 3]; | ||||
|                         eyu = points[i + 4]; | ||||
|                     } else eyl = points[i + 3]; | ||||
|             } | ||||
|         // only Y
 | ||||
|         } else if (eb == 'y') | ||||
|             if (yerr.asymmetric) { | ||||
|                 eyl = points[i + 2]; | ||||
|                 eyu = points[i + 3]; | ||||
|             } else eyl = points[i + 2]; | ||||
| 
 | ||||
|         // symmetric errors?
 | ||||
|         if (exu == null) exu = exl; | ||||
|         if (eyu == null) eyu = eyl; | ||||
| 
 | ||||
|         var errRanges = [exl, exu, eyl, eyu]; | ||||
|         // nullify if not showing
 | ||||
|         if (!xerr.show){ | ||||
|             errRanges[0] = null; | ||||
|             errRanges[1] = null; | ||||
|         } | ||||
|         if (!yerr.show){ | ||||
|             errRanges[2] = null; | ||||
|             errRanges[3] = null; | ||||
|         } | ||||
|         return errRanges; | ||||
|     } | ||||
| 
 | ||||
|     function drawSeriesErrors(plot, ctx, s){ | ||||
| 
 | ||||
|         var points = s.datapoints.points, | ||||
|                 ps = s.datapoints.pointsize, | ||||
|                 ax = [s.xaxis, s.yaxis], | ||||
|                 radius = s.points.radius, | ||||
|                 err = [s.points.xerr, s.points.yerr]; | ||||
| 
 | ||||
|         //sanity check, in case some inverted axis hack is applied to flot
 | ||||
|         var invertX = false; | ||||
|         if (ax[0].p2c(ax[0].max) < ax[0].p2c(ax[0].min)) { | ||||
|             invertX = true; | ||||
|             var tmp = err[0].lowerCap; | ||||
|             err[0].lowerCap = err[0].upperCap; | ||||
|             err[0].upperCap = tmp; | ||||
|         } | ||||
| 
 | ||||
|         var invertY = false; | ||||
|         if (ax[1].p2c(ax[1].min) < ax[1].p2c(ax[1].max)) { | ||||
|             invertY = true; | ||||
|             var tmp = err[1].lowerCap; | ||||
|             err[1].lowerCap = err[1].upperCap; | ||||
|             err[1].upperCap = tmp; | ||||
|         } | ||||
| 
 | ||||
|         for (var i = 0; i < s.datapoints.points.length; i += ps) { | ||||
| 
 | ||||
|             //parse
 | ||||
|             var errRanges = parseErrors(s, i); | ||||
| 
 | ||||
|             //cycle xerr & yerr
 | ||||
|             for (var e = 0; e < err.length; e++){ | ||||
| 
 | ||||
|                 var minmax = [ax[e].min, ax[e].max]; | ||||
| 
 | ||||
|                 //draw this error?
 | ||||
|                 if (errRanges[e * err.length]){ | ||||
| 
 | ||||
|                     //data coordinates
 | ||||
|                     var x = points[i], | ||||
|                         y = points[i + 1]; | ||||
| 
 | ||||
|                     //errorbar ranges
 | ||||
|                     var upper = [x, y][e] + errRanges[e * err.length + 1], | ||||
|                         lower = [x, y][e] - errRanges[e * err.length]; | ||||
| 
 | ||||
|                     //points outside of the canvas
 | ||||
|                     if (err[e].err == 'x') | ||||
|                         if (y > ax[1].max || y < ax[1].min || upper < ax[0].min || lower > ax[0].max) | ||||
|                             continue; | ||||
|                     if (err[e].err == 'y') | ||||
|                         if (x > ax[0].max || x < ax[0].min || upper < ax[1].min || lower > ax[1].max) | ||||
|                             continue; | ||||
| 
 | ||||
|                     // prevent errorbars getting out of the canvas
 | ||||
|                     var drawUpper = true, | ||||
|                         drawLower = true; | ||||
| 
 | ||||
|                     if (upper > minmax[1]) { | ||||
|                         drawUpper = false; | ||||
|                         upper = minmax[1]; | ||||
|                     } | ||||
|                     if (lower < minmax[0]) { | ||||
|                         drawLower = false; | ||||
|                         lower = minmax[0]; | ||||
|                     } | ||||
| 
 | ||||
|                     //sanity check, in case some inverted axis hack is applied to flot
 | ||||
|                     if ((err[e].err == 'x' && invertX) || (err[e].err == 'y' && invertY)) { | ||||
|                         //swap coordinates
 | ||||
|                         var tmp = lower; | ||||
|                         lower = upper; | ||||
|                         upper = tmp; | ||||
|                         tmp = drawLower; | ||||
|                         drawLower = drawUpper; | ||||
|                         drawUpper = tmp; | ||||
|                         tmp = minmax[0]; | ||||
|                         minmax[0] = minmax[1]; | ||||
|                         minmax[1] = tmp; | ||||
|                     } | ||||
| 
 | ||||
|                     // convert to pixels
 | ||||
|                     x = ax[0].p2c(x), | ||||
|                         y = ax[1].p2c(y), | ||||
|                         upper = ax[e].p2c(upper); | ||||
|                     lower = ax[e].p2c(lower); | ||||
|                     minmax[0] = ax[e].p2c(minmax[0]); | ||||
|                     minmax[1] = ax[e].p2c(minmax[1]); | ||||
| 
 | ||||
|                     //same style as points by default
 | ||||
|                     var lw = err[e].lineWidth ? err[e].lineWidth : s.points.lineWidth, | ||||
|                         sw = s.points.shadowSize != null ? s.points.shadowSize : s.shadowSize; | ||||
| 
 | ||||
|                     //shadow as for points
 | ||||
|                     if (lw > 0 && sw > 0) { | ||||
|                         var w = sw / 2; | ||||
|                         ctx.lineWidth = w; | ||||
|                         ctx.strokeStyle = "rgba(0,0,0,0.1)"; | ||||
|                         drawError(ctx, err[e], x, y, upper, lower, drawUpper, drawLower, radius, w + w/2, minmax); | ||||
| 
 | ||||
|                         ctx.strokeStyle = "rgba(0,0,0,0.2)"; | ||||
|                         drawError(ctx, err[e], x, y, upper, lower, drawUpper, drawLower, radius, w/2, minmax); | ||||
|                     } | ||||
| 
 | ||||
|                     ctx.strokeStyle = err[e].color? err[e].color: s.color; | ||||
|                     ctx.lineWidth = lw; | ||||
|                     //draw it
 | ||||
|                     drawError(ctx, err[e], x, y, upper, lower, drawUpper, drawLower, radius, 0, minmax); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     function drawError(ctx,err,x,y,upper,lower,drawUpper,drawLower,radius,offset,minmax){ | ||||
| 
 | ||||
|         //shadow offset
 | ||||
|         y += offset; | ||||
|         upper += offset; | ||||
|         lower += offset; | ||||
| 
 | ||||
|         // error bar - avoid plotting over circles
 | ||||
|         if (err.err == 'x'){ | ||||
|             if (upper > x + radius) drawPath(ctx, [[upper,y],[Math.max(x + radius,minmax[0]),y]]); | ||||
|             else drawUpper = false; | ||||
|             if (lower < x - radius) drawPath(ctx, [[Math.min(x - radius,minmax[1]),y],[lower,y]] ); | ||||
|             else drawLower = false; | ||||
|         } | ||||
|         else { | ||||
|             if (upper < y - radius) drawPath(ctx, [[x,upper],[x,Math.min(y - radius,minmax[0])]] ); | ||||
|             else drawUpper = false; | ||||
|             if (lower > y + radius) drawPath(ctx, [[x,Math.max(y + radius,minmax[1])],[x,lower]] ); | ||||
|             else drawLower = false; | ||||
|         } | ||||
| 
 | ||||
|         //internal radius value in errorbar, allows to plot radius 0 points and still keep proper sized caps
 | ||||
|         //this is a way to get errorbars on lines without visible connecting dots
 | ||||
|         radius = err.radius != null? err.radius: radius; | ||||
| 
 | ||||
|         // upper cap
 | ||||
|         if (drawUpper) { | ||||
|             if (err.upperCap == '-'){ | ||||
|                 if (err.err=='x') drawPath(ctx, [[upper,y - radius],[upper,y + radius]] ); | ||||
|                 else drawPath(ctx, [[x - radius,upper],[x + radius,upper]] ); | ||||
|             } else if ($.isFunction(err.upperCap)){ | ||||
|                 if (err.err=='x') err.upperCap(ctx, upper, y, radius); | ||||
|                 else err.upperCap(ctx, x, upper, radius); | ||||
|             } | ||||
|         } | ||||
|         // lower cap
 | ||||
|         if (drawLower) { | ||||
|             if (err.lowerCap == '-'){ | ||||
|                 if (err.err=='x') drawPath(ctx, [[lower,y - radius],[lower,y + radius]] ); | ||||
|                 else drawPath(ctx, [[x - radius,lower],[x + radius,lower]] ); | ||||
|             } else if ($.isFunction(err.lowerCap)){ | ||||
|                 if (err.err=='x') err.lowerCap(ctx, lower, y, radius); | ||||
|                 else err.lowerCap(ctx, x, lower, radius); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     function drawPath(ctx, pts){ | ||||
|         ctx.beginPath(); | ||||
|         ctx.moveTo(pts[0][0], pts[0][1]); | ||||
|         for (var p=1; p < pts.length; p++) | ||||
|             ctx.lineTo(pts[p][0], pts[p][1]); | ||||
|         ctx.stroke(); | ||||
|     } | ||||
| 
 | ||||
|     function draw(plot, ctx){ | ||||
|         var plotOffset = plot.getPlotOffset(); | ||||
| 
 | ||||
|         ctx.save(); | ||||
|         ctx.translate(plotOffset.left, plotOffset.top); | ||||
|         $.each(plot.getData(), function (i, s) { | ||||
|             if (s.points.errorbars && (s.points.xerr.show || s.points.yerr.show)) | ||||
|                 drawSeriesErrors(plot, ctx, s); | ||||
|         }); | ||||
|         ctx.restore(); | ||||
|     } | ||||
| 
 | ||||
|     function init(plot) { | ||||
|         plot.hooks.processRawData.push(processRawData); | ||||
|         plot.hooks.draw.push(draw); | ||||
|     } | ||||
| 
 | ||||
|     $.plot.plugins.push({ | ||||
|                 init: init, | ||||
|                 options: options, | ||||
|                 name: 'errorbars', | ||||
|                 version: '1.0' | ||||
|             }); | ||||
| })(jQuery); | ||||
							
								
								
									
										7
									
								
								hledger-web/static/js/jquery.flot.errorbars.min.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								hledger-web/static/js/jquery.flot.errorbars.min.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										226
									
								
								hledger-web/static/js/jquery.flot.fillbetween.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										226
									
								
								hledger-web/static/js/jquery.flot.fillbetween.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,226 @@ | ||||
| /* Flot plugin for computing bottoms for filled line and bar charts. | ||||
| 
 | ||||
| Copyright (c) 2007-2014 IOLA and Ole Laursen. | ||||
| Licensed under the MIT license. | ||||
| 
 | ||||
| The case: you've got two series that you want to fill the area between. In Flot | ||||
| terms, you need to use one as the fill bottom of the other. You can specify the | ||||
| bottom of each data point as the third coordinate manually, or you can use this | ||||
| plugin to compute it for you. | ||||
| 
 | ||||
| In order to name the other series, you need to give it an id, like this: | ||||
| 
 | ||||
| 	var dataset = [ | ||||
| 		{ data: [ ... ], id: "foo" } ,         // use default bottom
 | ||||
| 		{ data: [ ... ], fillBetween: "foo" }, // use first dataset as bottom
 | ||||
| 	]; | ||||
| 
 | ||||
| 	$.plot($("#placeholder"), dataset, { lines: { show: true, fill: true }}); | ||||
| 
 | ||||
| As a convenience, if the id given is a number that doesn't appear as an id in | ||||
| the series, it is interpreted as the index in the array instead (so fillBetween: | ||||
| 0 can also mean the first series). | ||||
| 
 | ||||
| Internally, the plugin modifies the datapoints in each series. For line series, | ||||
| extra data points might be inserted through interpolation. Note that at points | ||||
| where the bottom line is not defined (due to a null point or start/end of line), | ||||
| the current line will show a gap too. The algorithm comes from the | ||||
| jquery.flot.stack.js plugin, possibly some code could be shared. | ||||
| 
 | ||||
| */ | ||||
| 
 | ||||
| (function ( $ ) { | ||||
| 
 | ||||
| 	var options = { | ||||
| 		series: { | ||||
| 			fillBetween: null	// or number
 | ||||
| 		} | ||||
| 	}; | ||||
| 
 | ||||
| 	function init( plot ) { | ||||
| 
 | ||||
| 		function findBottomSeries( s, allseries ) { | ||||
| 
 | ||||
| 			var i; | ||||
| 
 | ||||
| 			for ( i = 0; i < allseries.length; ++i ) { | ||||
| 				if ( allseries[ i ].id === s.fillBetween ) { | ||||
| 					return allseries[ i ]; | ||||
| 				} | ||||
| 			} | ||||
| 
 | ||||
| 			if ( typeof s.fillBetween === "number" ) { | ||||
| 				if ( s.fillBetween < 0 || s.fillBetween >= allseries.length ) { | ||||
| 					return null; | ||||
| 				} | ||||
| 				return allseries[ s.fillBetween ]; | ||||
| 			} | ||||
| 
 | ||||
| 			return null; | ||||
| 		} | ||||
| 
 | ||||
| 		function computeFillBottoms( plot, s, datapoints ) { | ||||
| 
 | ||||
| 			if ( s.fillBetween == null ) { | ||||
| 				return; | ||||
| 			} | ||||
| 
 | ||||
| 			var other = findBottomSeries( s, plot.getData() ); | ||||
| 
 | ||||
| 			if ( !other ) { | ||||
| 				return; | ||||
| 			} | ||||
| 
 | ||||
| 			var ps = datapoints.pointsize, | ||||
| 				points = datapoints.points, | ||||
| 				otherps = other.datapoints.pointsize, | ||||
| 				otherpoints = other.datapoints.points, | ||||
| 				newpoints = [], | ||||
| 				px, py, intery, qx, qy, bottom, | ||||
| 				withlines = s.lines.show, | ||||
| 				withbottom = ps > 2 && datapoints.format[2].y, | ||||
| 				withsteps = withlines && s.lines.steps, | ||||
| 				fromgap = true, | ||||
| 				i = 0, | ||||
| 				j = 0, | ||||
| 				l, m; | ||||
| 
 | ||||
| 			while ( true ) { | ||||
| 
 | ||||
| 				if ( i >= points.length ) { | ||||
| 					break; | ||||
| 				} | ||||
| 
 | ||||
| 				l = newpoints.length; | ||||
| 
 | ||||
| 				if ( points[ i ] == null ) { | ||||
| 
 | ||||
| 					// copy gaps
 | ||||
| 
 | ||||
| 					for ( m = 0; m < ps; ++m ) { | ||||
| 						newpoints.push( points[ i + m ] ); | ||||
| 					} | ||||
| 
 | ||||
| 					i += ps; | ||||
| 
 | ||||
| 				} else if ( j >= otherpoints.length ) { | ||||
| 
 | ||||
| 					// for lines, we can't use the rest of the points
 | ||||
| 
 | ||||
| 					if ( !withlines ) { | ||||
| 						for ( m = 0; m < ps; ++m ) { | ||||
| 							newpoints.push( points[ i + m ] ); | ||||
| 						} | ||||
| 					} | ||||
| 
 | ||||
| 					i += ps; | ||||
| 
 | ||||
| 				} else if ( otherpoints[ j ] == null ) { | ||||
| 
 | ||||
| 					// oops, got a gap
 | ||||
| 
 | ||||
| 					for ( m = 0; m < ps; ++m ) { | ||||
| 						newpoints.push( null ); | ||||
| 					} | ||||
| 
 | ||||
| 					fromgap = true; | ||||
| 					j += otherps; | ||||
| 
 | ||||
| 				} else { | ||||
| 
 | ||||
| 					// cases where we actually got two points
 | ||||
| 
 | ||||
| 					px = points[ i ]; | ||||
| 					py = points[ i + 1 ]; | ||||
| 					qx = otherpoints[ j ]; | ||||
| 					qy = otherpoints[ j + 1 ]; | ||||
| 					bottom = 0; | ||||
| 
 | ||||
| 					if ( px === qx ) { | ||||
| 
 | ||||
| 						for ( m = 0; m < ps; ++m ) { | ||||
| 							newpoints.push( points[ i + m ] ); | ||||
| 						} | ||||
| 
 | ||||
| 						//newpoints[ l + 1 ] += qy;
 | ||||
| 						bottom = qy; | ||||
| 
 | ||||
| 						i += ps; | ||||
| 						j += otherps; | ||||
| 
 | ||||
| 					} else if ( px > qx ) { | ||||
| 
 | ||||
| 						// we got past point below, might need to
 | ||||
| 						// insert interpolated extra point
 | ||||
| 
 | ||||
| 						if ( withlines && i > 0 && points[ i - ps ] != null ) { | ||||
| 							intery = py + ( points[ i - ps + 1 ] - py ) * ( qx - px ) / ( points[ i - ps ] - px ); | ||||
| 							newpoints.push( qx ); | ||||
| 							newpoints.push( intery ); | ||||
| 							for ( m = 2; m < ps; ++m ) { | ||||
| 								newpoints.push( points[ i + m ] ); | ||||
| 							} | ||||
| 							bottom = qy; | ||||
| 						} | ||||
| 
 | ||||
| 						j += otherps; | ||||
| 
 | ||||
| 					} else { // px < qx
 | ||||
| 
 | ||||
| 						// if we come from a gap, we just skip this point
 | ||||
| 
 | ||||
| 						if ( fromgap && withlines ) { | ||||
| 							i += ps; | ||||
| 							continue; | ||||
| 						} | ||||
| 
 | ||||
| 						for ( m = 0; m < ps; ++m ) { | ||||
| 							newpoints.push( points[ i + m ] ); | ||||
| 						} | ||||
| 
 | ||||
| 						// we might be able to interpolate a point below,
 | ||||
| 						// this can give us a better y
 | ||||
| 
 | ||||
| 						if ( withlines && j > 0 && otherpoints[ j - otherps ] != null ) { | ||||
| 							bottom = qy + ( otherpoints[ j - otherps + 1 ] - qy ) * ( px - qx ) / ( otherpoints[ j - otherps ] - qx ); | ||||
| 						} | ||||
| 
 | ||||
| 						//newpoints[l + 1] += bottom;
 | ||||
| 
 | ||||
| 						i += ps; | ||||
| 					} | ||||
| 
 | ||||
| 					fromgap = false; | ||||
| 
 | ||||
| 					if ( l !== newpoints.length && withbottom ) { | ||||
| 						newpoints[ l + 2 ] = bottom; | ||||
| 					} | ||||
| 				} | ||||
| 
 | ||||
| 				// maintain the line steps invariant
 | ||||
| 
 | ||||
| 				if ( withsteps && l !== newpoints.length && l > 0 && | ||||
| 					newpoints[ l ] !== null && | ||||
| 					newpoints[ l ] !== newpoints[ l - ps ] && | ||||
| 					newpoints[ l + 1 ] !== newpoints[ l - ps + 1 ] ) { | ||||
| 					for (m = 0; m < ps; ++m) { | ||||
| 						newpoints[ l + ps + m ] = newpoints[ l + m ]; | ||||
| 					} | ||||
| 					newpoints[ l + 1 ] = newpoints[ l - ps + 1 ]; | ||||
| 				} | ||||
| 			} | ||||
| 
 | ||||
| 			datapoints.points = newpoints; | ||||
| 		} | ||||
| 
 | ||||
| 		plot.hooks.processDatapoints.push( computeFillBottoms ); | ||||
| 	} | ||||
| 
 | ||||
| 	$.plot.plugins.push({ | ||||
| 		init: init, | ||||
| 		options: options, | ||||
| 		name: "fillbetween", | ||||
| 		version: "1.0" | ||||
| 	}); | ||||
| 
 | ||||
| })(jQuery); | ||||
							
								
								
									
										7
									
								
								hledger-web/static/js/jquery.flot.fillbetween.min.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								hledger-web/static/js/jquery.flot.fillbetween.min.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,7 @@ | ||||
| /* Javascript plotting library for jQuery, version 0.8.3. | ||||
| 
 | ||||
| Copyright (c) 2007-2014 IOLA and Ole Laursen. | ||||
| Licensed under the MIT license. | ||||
| 
 | ||||
| */ | ||||
| (function($){var options={series:{fillBetween:null}};function init(plot){function findBottomSeries(s,allseries){var i;for(i=0;i<allseries.length;++i){if(allseries[i].id===s.fillBetween){return allseries[i]}}if(typeof s.fillBetween==="number"){if(s.fillBetween<0||s.fillBetween>=allseries.length){return null}return allseries[s.fillBetween]}return null}function computeFillBottoms(plot,s,datapoints){if(s.fillBetween==null){return}var other=findBottomSeries(s,plot.getData());if(!other){return}var ps=datapoints.pointsize,points=datapoints.points,otherps=other.datapoints.pointsize,otherpoints=other.datapoints.points,newpoints=[],px,py,intery,qx,qy,bottom,withlines=s.lines.show,withbottom=ps>2&&datapoints.format[2].y,withsteps=withlines&&s.lines.steps,fromgap=true,i=0,j=0,l,m;while(true){if(i>=points.length){break}l=newpoints.length;if(points[i]==null){for(m=0;m<ps;++m){newpoints.push(points[i+m])}i+=ps}else if(j>=otherpoints.length){if(!withlines){for(m=0;m<ps;++m){newpoints.push(points[i+m])}}i+=ps}else if(otherpoints[j]==null){for(m=0;m<ps;++m){newpoints.push(null)}fromgap=true;j+=otherps}else{px=points[i];py=points[i+1];qx=otherpoints[j];qy=otherpoints[j+1];bottom=0;if(px===qx){for(m=0;m<ps;++m){newpoints.push(points[i+m])}bottom=qy;i+=ps;j+=otherps}else if(px>qx){if(withlines&&i>0&&points[i-ps]!=null){intery=py+(points[i-ps+1]-py)*(qx-px)/(points[i-ps]-px);newpoints.push(qx);newpoints.push(intery);for(m=2;m<ps;++m){newpoints.push(points[i+m])}bottom=qy}j+=otherps}else{if(fromgap&&withlines){i+=ps;continue}for(m=0;m<ps;++m){newpoints.push(points[i+m])}if(withlines&&j>0&&otherpoints[j-otherps]!=null){bottom=qy+(otherpoints[j-otherps+1]-qy)*(px-qx)/(otherpoints[j-otherps]-qx)}i+=ps}fromgap=false;if(l!==newpoints.length&&withbottom){newpoints[l+2]=bottom}}if(withsteps&&l!==newpoints.length&&l>0&&newpoints[l]!==null&&newpoints[l]!==newpoints[l-ps]&&newpoints[l+1]!==newpoints[l-ps+1]){for(m=0;m<ps;++m){newpoints[l+ps+m]=newpoints[l+m]}newpoints[l+1]=newpoints[l-ps+1]}}datapoints.points=newpoints}plot.hooks.processDatapoints.push(computeFillBottoms)}$.plot.plugins.push({init:init,options:options,name:"fillbetween",version:"1.0"})})(jQuery); | ||||
							
								
								
									
										241
									
								
								hledger-web/static/js/jquery.flot.image.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										241
									
								
								hledger-web/static/js/jquery.flot.image.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,241 @@ | ||||
| /* Flot plugin for plotting images. | ||||
| 
 | ||||
| Copyright (c) 2007-2014 IOLA and Ole Laursen. | ||||
| Licensed under the MIT license. | ||||
| 
 | ||||
| The data syntax is [ [ image, x1, y1, x2, y2 ], ... ] where (x1, y1) and | ||||
| (x2, y2) are where you intend the two opposite corners of the image to end up | ||||
| in the plot. Image must be a fully loaded Javascript image (you can make one | ||||
| with new Image()). If the image is not complete, it's skipped when plotting. | ||||
| 
 | ||||
| There are two helpers included for retrieving images. The easiest work the way | ||||
| that you put in URLs instead of images in the data, like this: | ||||
| 
 | ||||
| 	[ "myimage.png", 0, 0, 10, 10 ] | ||||
| 
 | ||||
| Then call $.plot.image.loadData( data, options, callback ) where data and | ||||
| options are the same as you pass in to $.plot. This loads the images, replaces | ||||
| the URLs in the data with the corresponding images and calls "callback" when | ||||
| all images are loaded (or failed loading). In the callback, you can then call | ||||
| $.plot with the data set. See the included example. | ||||
| 
 | ||||
| A more low-level helper, $.plot.image.load(urls, callback) is also included. | ||||
| Given a list of URLs, it calls callback with an object mapping from URL to | ||||
| Image object when all images are loaded or have failed loading. | ||||
| 
 | ||||
| The plugin supports these options: | ||||
| 
 | ||||
| 	series: { | ||||
| 		images: { | ||||
| 			show: boolean | ||||
| 			anchor: "corner" or "center" | ||||
| 			alpha: [ 0, 1 ] | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| They can be specified for a specific series: | ||||
| 
 | ||||
| 	$.plot( $("#placeholder"), [{ | ||||
| 		data: [ ... ], | ||||
| 		images: { ... } | ||||
| 	]) | ||||
| 
 | ||||
| Note that because the data format is different from usual data points, you | ||||
| can't use images with anything else in a specific data series. | ||||
| 
 | ||||
| Setting "anchor" to "center" causes the pixels in the image to be anchored at | ||||
| the corner pixel centers inside of at the pixel corners, effectively letting | ||||
| half a pixel stick out to each side in the plot. | ||||
| 
 | ||||
| A possible future direction could be support for tiling for large images (like | ||||
| Google Maps). | ||||
| 
 | ||||
| */ | ||||
| 
 | ||||
| (function ($) { | ||||
|     var options = { | ||||
|         series: { | ||||
|             images: { | ||||
|                 show: false, | ||||
|                 alpha: 1, | ||||
|                 anchor: "corner" // or "center"
 | ||||
|             } | ||||
|         } | ||||
|     }; | ||||
| 
 | ||||
|     $.plot.image = {}; | ||||
| 
 | ||||
|     $.plot.image.loadDataImages = function (series, options, callback) { | ||||
|         var urls = [], points = []; | ||||
| 
 | ||||
|         var defaultShow = options.series.images.show; | ||||
|          | ||||
|         $.each(series, function (i, s) { | ||||
|             if (!(defaultShow || s.images.show)) | ||||
|                 return; | ||||
|              | ||||
|             if (s.data) | ||||
|                 s = s.data; | ||||
| 
 | ||||
|             $.each(s, function (i, p) { | ||||
|                 if (typeof p[0] == "string") { | ||||
|                     urls.push(p[0]); | ||||
|                     points.push(p); | ||||
|                 } | ||||
|             }); | ||||
|         }); | ||||
| 
 | ||||
|         $.plot.image.load(urls, function (loadedImages) { | ||||
|             $.each(points, function (i, p) { | ||||
|                 var url = p[0]; | ||||
|                 if (loadedImages[url]) | ||||
|                     p[0] = loadedImages[url]; | ||||
|             }); | ||||
| 
 | ||||
|             callback(); | ||||
|         }); | ||||
|     } | ||||
|      | ||||
|     $.plot.image.load = function (urls, callback) { | ||||
|         var missing = urls.length, loaded = {}; | ||||
|         if (missing == 0) | ||||
|             callback({}); | ||||
| 
 | ||||
|         $.each(urls, function (i, url) { | ||||
|             var handler = function () { | ||||
|                 --missing; | ||||
|                  | ||||
|                 loaded[url] = this; | ||||
|                  | ||||
|                 if (missing == 0) | ||||
|                     callback(loaded); | ||||
|             }; | ||||
| 
 | ||||
|             $('<img />').load(handler).error(handler).attr('src', url); | ||||
|         }); | ||||
|     }; | ||||
|      | ||||
|     function drawSeries(plot, ctx, series) { | ||||
|         var plotOffset = plot.getPlotOffset(); | ||||
|          | ||||
|         if (!series.images || !series.images.show) | ||||
|             return; | ||||
|          | ||||
|         var points = series.datapoints.points, | ||||
|             ps = series.datapoints.pointsize; | ||||
|          | ||||
|         for (var i = 0; i < points.length; i += ps) { | ||||
|             var img = points[i], | ||||
|                 x1 = points[i + 1], y1 = points[i + 2], | ||||
|                 x2 = points[i + 3], y2 = points[i + 4], | ||||
|                 xaxis = series.xaxis, yaxis = series.yaxis, | ||||
|                 tmp; | ||||
| 
 | ||||
|             // actually we should check img.complete, but it
 | ||||
|             // appears to be a somewhat unreliable indicator in
 | ||||
|             // IE6 (false even after load event)
 | ||||
|             if (!img || img.width <= 0 || img.height <= 0) | ||||
|                 continue; | ||||
| 
 | ||||
|             if (x1 > x2) { | ||||
|                 tmp = x2; | ||||
|                 x2 = x1; | ||||
|                 x1 = tmp; | ||||
|             } | ||||
|             if (y1 > y2) { | ||||
|                 tmp = y2; | ||||
|                 y2 = y1; | ||||
|                 y1 = tmp; | ||||
|             } | ||||
|              | ||||
|             // if the anchor is at the center of the pixel, expand the 
 | ||||
|             // image by 1/2 pixel in each direction
 | ||||
|             if (series.images.anchor == "center") { | ||||
|                 tmp = 0.5 * (x2-x1) / (img.width - 1); | ||||
|                 x1 -= tmp; | ||||
|                 x2 += tmp; | ||||
|                 tmp = 0.5 * (y2-y1) / (img.height - 1); | ||||
|                 y1 -= tmp; | ||||
|                 y2 += tmp; | ||||
|             } | ||||
|              | ||||
|             // clip
 | ||||
|             if (x1 == x2 || y1 == y2 || | ||||
|                 x1 >= xaxis.max || x2 <= xaxis.min || | ||||
|                 y1 >= yaxis.max || y2 <= yaxis.min) | ||||
|                 continue; | ||||
| 
 | ||||
|             var sx1 = 0, sy1 = 0, sx2 = img.width, sy2 = img.height; | ||||
|             if (x1 < xaxis.min) { | ||||
|                 sx1 += (sx2 - sx1) * (xaxis.min - x1) / (x2 - x1); | ||||
|                 x1 = xaxis.min; | ||||
|             } | ||||
| 
 | ||||
|             if (x2 > xaxis.max) { | ||||
|                 sx2 += (sx2 - sx1) * (xaxis.max - x2) / (x2 - x1); | ||||
|                 x2 = xaxis.max; | ||||
|             } | ||||
| 
 | ||||
|             if (y1 < yaxis.min) { | ||||
|                 sy2 += (sy1 - sy2) * (yaxis.min - y1) / (y2 - y1); | ||||
|                 y1 = yaxis.min; | ||||
|             } | ||||
| 
 | ||||
|             if (y2 > yaxis.max) { | ||||
|                 sy1 += (sy1 - sy2) * (yaxis.max - y2) / (y2 - y1); | ||||
|                 y2 = yaxis.max; | ||||
|             } | ||||
|              | ||||
|             x1 = xaxis.p2c(x1); | ||||
|             x2 = xaxis.p2c(x2); | ||||
|             y1 = yaxis.p2c(y1); | ||||
|             y2 = yaxis.p2c(y2); | ||||
|              | ||||
|             // the transformation may have swapped us
 | ||||
|             if (x1 > x2) { | ||||
|                 tmp = x2; | ||||
|                 x2 = x1; | ||||
|                 x1 = tmp; | ||||
|             } | ||||
|             if (y1 > y2) { | ||||
|                 tmp = y2; | ||||
|                 y2 = y1; | ||||
|                 y1 = tmp; | ||||
|             } | ||||
| 
 | ||||
|             tmp = ctx.globalAlpha; | ||||
|             ctx.globalAlpha *= series.images.alpha; | ||||
|             ctx.drawImage(img, | ||||
|                           sx1, sy1, sx2 - sx1, sy2 - sy1, | ||||
|                           x1 + plotOffset.left, y1 + plotOffset.top, | ||||
|                           x2 - x1, y2 - y1); | ||||
|             ctx.globalAlpha = tmp; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     function processRawData(plot, series, data, datapoints) { | ||||
|         if (!series.images.show) | ||||
|             return; | ||||
| 
 | ||||
|         // format is Image, x1, y1, x2, y2 (opposite corners)
 | ||||
|         datapoints.format = [ | ||||
|             { required: true }, | ||||
|             { x: true, number: true, required: true }, | ||||
|             { y: true, number: true, required: true }, | ||||
|             { x: true, number: true, required: true }, | ||||
|             { y: true, number: true, required: true } | ||||
|         ]; | ||||
|     } | ||||
|      | ||||
|     function init(plot) { | ||||
|         plot.hooks.processRawData.push(processRawData); | ||||
|         plot.hooks.drawSeries.push(drawSeries); | ||||
|     } | ||||
|      | ||||
|     $.plot.plugins.push({ | ||||
|         init: init, | ||||
|         options: options, | ||||
|         name: 'image', | ||||
|         version: '1.1' | ||||
|     }); | ||||
| })(jQuery); | ||||
							
								
								
									
										7
									
								
								hledger-web/static/js/jquery.flot.image.min.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								hledger-web/static/js/jquery.flot.image.min.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,7 @@ | ||||
| /* Javascript plotting library for jQuery, version 0.8.3. | ||||
| 
 | ||||
| Copyright (c) 2007-2014 IOLA and Ole Laursen. | ||||
| Licensed under the MIT license. | ||||
| 
 | ||||
| */ | ||||
| (function($){var options={series:{images:{show:false,alpha:1,anchor:"corner"}}};$.plot.image={};$.plot.image.loadDataImages=function(series,options,callback){var urls=[],points=[];var defaultShow=options.series.images.show;$.each(series,function(i,s){if(!(defaultShow||s.images.show))return;if(s.data)s=s.data;$.each(s,function(i,p){if(typeof p[0]=="string"){urls.push(p[0]);points.push(p)}})});$.plot.image.load(urls,function(loadedImages){$.each(points,function(i,p){var url=p[0];if(loadedImages[url])p[0]=loadedImages[url]});callback()})};$.plot.image.load=function(urls,callback){var missing=urls.length,loaded={};if(missing==0)callback({});$.each(urls,function(i,url){var handler=function(){--missing;loaded[url]=this;if(missing==0)callback(loaded)};$("<img />").load(handler).error(handler).attr("src",url)})};function drawSeries(plot,ctx,series){var plotOffset=plot.getPlotOffset();if(!series.images||!series.images.show)return;var points=series.datapoints.points,ps=series.datapoints.pointsize;for(var i=0;i<points.length;i+=ps){var img=points[i],x1=points[i+1],y1=points[i+2],x2=points[i+3],y2=points[i+4],xaxis=series.xaxis,yaxis=series.yaxis,tmp;if(!img||img.width<=0||img.height<=0)continue;if(x1>x2){tmp=x2;x2=x1;x1=tmp}if(y1>y2){tmp=y2;y2=y1;y1=tmp}if(series.images.anchor=="center"){tmp=.5*(x2-x1)/(img.width-1);x1-=tmp;x2+=tmp;tmp=.5*(y2-y1)/(img.height-1);y1-=tmp;y2+=tmp}if(x1==x2||y1==y2||x1>=xaxis.max||x2<=xaxis.min||y1>=yaxis.max||y2<=yaxis.min)continue;var sx1=0,sy1=0,sx2=img.width,sy2=img.height;if(x1<xaxis.min){sx1+=(sx2-sx1)*(xaxis.min-x1)/(x2-x1);x1=xaxis.min}if(x2>xaxis.max){sx2+=(sx2-sx1)*(xaxis.max-x2)/(x2-x1);x2=xaxis.max}if(y1<yaxis.min){sy2+=(sy1-sy2)*(yaxis.min-y1)/(y2-y1);y1=yaxis.min}if(y2>yaxis.max){sy1+=(sy1-sy2)*(yaxis.max-y2)/(y2-y1);y2=yaxis.max}x1=xaxis.p2c(x1);x2=xaxis.p2c(x2);y1=yaxis.p2c(y1);y2=yaxis.p2c(y2);if(x1>x2){tmp=x2;x2=x1;x1=tmp}if(y1>y2){tmp=y2;y2=y1;y1=tmp}tmp=ctx.globalAlpha;ctx.globalAlpha*=series.images.alpha;ctx.drawImage(img,sx1,sy1,sx2-sx1,sy2-sy1,x1+plotOffset.left,y1+plotOffset.top,x2-x1,y2-y1);ctx.globalAlpha=tmp}}function processRawData(plot,series,data,datapoints){if(!series.images.show)return;datapoints.format=[{required:true},{x:true,number:true,required:true},{y:true,number:true,required:true},{x:true,number:true,required:true},{y:true,number:true,required:true}]}function init(plot){plot.hooks.processRawData.push(processRawData);plot.hooks.drawSeries.push(drawSeries)}$.plot.plugins.push({init:init,options:options,name:"image",version:"1.1"})})(jQuery); | ||||
							
								
								
									
										346
									
								
								hledger-web/static/js/jquery.flot.navigate.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										346
									
								
								hledger-web/static/js/jquery.flot.navigate.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,346 @@ | ||||
| /* Flot plugin for adding the ability to pan and zoom the plot. | ||||
| 
 | ||||
| Copyright (c) 2007-2014 IOLA and Ole Laursen. | ||||
| Licensed under the MIT license. | ||||
| 
 | ||||
| The default behaviour is double click and scrollwheel up/down to zoom in, drag | ||||
| to pan. The plugin defines plot.zoom({ center }), plot.zoomOut() and | ||||
| plot.pan( offset ) so you easily can add custom controls. It also fires | ||||
| "plotpan" and "plotzoom" events, useful for synchronizing plots. | ||||
| 
 | ||||
| The plugin supports these options: | ||||
| 
 | ||||
| 	zoom: { | ||||
| 		interactive: false | ||||
| 		trigger: "dblclick" // or "click" for single click
 | ||||
| 		amount: 1.5         // 2 = 200% (zoom in), 0.5 = 50% (zoom out)
 | ||||
| 	} | ||||
| 
 | ||||
| 	pan: { | ||||
| 		interactive: false | ||||
| 		cursor: "move"      // CSS mouse cursor value used when dragging, e.g. "pointer"
 | ||||
| 		frameRate: 20 | ||||
| 	} | ||||
| 
 | ||||
| 	xaxis, yaxis, x2axis, y2axis: { | ||||
| 		zoomRange: null  // or [ number, number ] (min range, max range) or false
 | ||||
| 		panRange: null   // or [ number, number ] (min, max) or false
 | ||||
| 	} | ||||
| 
 | ||||
| "interactive" enables the built-in drag/click behaviour. If you enable | ||||
| interactive for pan, then you'll have a basic plot that supports moving | ||||
| around; the same for zoom. | ||||
| 
 | ||||
| "amount" specifies the default amount to zoom in (so 1.5 = 150%) relative to | ||||
| the current viewport. | ||||
| 
 | ||||
| "cursor" is a standard CSS mouse cursor string used for visual feedback to the | ||||
| user when dragging. | ||||
| 
 | ||||
| "frameRate" specifies the maximum number of times per second the plot will | ||||
| update itself while the user is panning around on it (set to null to disable | ||||
| intermediate pans, the plot will then not update until the mouse button is | ||||
| released). | ||||
| 
 | ||||
| "zoomRange" is the interval in which zooming can happen, e.g. with zoomRange: | ||||
| [1, 100] the zoom will never scale the axis so that the difference between min | ||||
| and max is smaller than 1 or larger than 100. You can set either end to null | ||||
| to ignore, e.g. [1, null]. If you set zoomRange to false, zooming on that axis | ||||
| will be disabled. | ||||
| 
 | ||||
| "panRange" confines the panning to stay within a range, e.g. with panRange: | ||||
| [-10, 20] panning stops at -10 in one end and at 20 in the other. Either can | ||||
| be null, e.g. [-10, null]. If you set panRange to false, panning on that axis | ||||
| will be disabled. | ||||
| 
 | ||||
| Example API usage: | ||||
| 
 | ||||
| 	plot = $.plot(...); | ||||
| 
 | ||||
| 	// zoom default amount in on the pixel ( 10, 20 )
 | ||||
| 	plot.zoom({ center: { left: 10, top: 20 } }); | ||||
| 
 | ||||
| 	// zoom out again
 | ||||
| 	plot.zoomOut({ center: { left: 10, top: 20 } }); | ||||
| 
 | ||||
| 	// zoom 200% in on the pixel (10, 20)
 | ||||
| 	plot.zoom({ amount: 2, center: { left: 10, top: 20 } }); | ||||
| 
 | ||||
| 	// pan 100 pixels to the left and 20 down
 | ||||
| 	plot.pan({ left: -100, top: 20 }) | ||||
| 
 | ||||
| Here, "center" specifies where the center of the zooming should happen. Note | ||||
| that this is defined in pixel space, not the space of the data points (you can | ||||
| use the p2c helpers on the axes in Flot to help you convert between these). | ||||
| 
 | ||||
| "amount" is the amount to zoom the viewport relative to the current range, so | ||||
| 1 is 100% (i.e. no change), 1.5 is 150% (zoom in), 0.7 is 70% (zoom out). You | ||||
| can set the default in the options. | ||||
| 
 | ||||
| */ | ||||
| 
 | ||||
| // First two dependencies, jquery.event.drag.js and
 | ||||
| // jquery.mousewheel.js, we put them inline here to save people the
 | ||||
| // effort of downloading them.
 | ||||
| 
 | ||||
| /* | ||||
| jquery.event.drag.js ~ v1.5 ~ Copyright (c) 2008, Three Dub Media (http://threedubmedia.com)
 | ||||
| Licensed under the MIT License ~ http://threedubmedia.googlecode.com/files/MIT-LICENSE.txt
 | ||||
| */ | ||||
| (function(a){function e(h){var k,j=this,l=h.data||{};if(l.elem)j=h.dragTarget=l.elem,h.dragProxy=d.proxy||j,h.cursorOffsetX=l.pageX-l.left,h.cursorOffsetY=l.pageY-l.top,h.offsetX=h.pageX-h.cursorOffsetX,h.offsetY=h.pageY-h.cursorOffsetY;else if(d.dragging||l.which>0&&h.which!=l.which||a(h.target).is(l.not))return;switch(h.type){case"mousedown":return a.extend(l,a(j).offset(),{elem:j,target:h.target,pageX:h.pageX,pageY:h.pageY}),b.add(document,"mousemove mouseup",e,l),i(j,!1),d.dragging=null,!1;case!d.dragging&&"mousemove":if(g(h.pageX-l.pageX)+g(h.pageY-l.pageY)<l.distance)break;h.target=l.target,k=f(h,"dragstart",j),k!==!1&&(d.dragging=j,d.proxy=h.dragProxy=a(k||j)[0]);case"mousemove":if(d.dragging){if(k=f(h,"drag",j),c.drop&&(c.drop.allowed=k!==!1,c.drop.handler(h)),k!==!1)break;h.type="mouseup"}case"mouseup":b.remove(document,"mousemove mouseup",e),d.dragging&&(c.drop&&c.drop.handler(h),f(h,"dragend",j)),i(j,!0),d.dragging=d.proxy=l.elem=!1}return!0}function f(b,c,d){b.type=c;var e=a.event.dispatch.call(d,b);return e===!1?!1:e||b.result}function g(a){return Math.pow(a,2)}function h(){return d.dragging===!1}function i(a,b){a&&(a.unselectable=b?"off":"on",a.onselectstart=function(){return b},a.style&&(a.style.MozUserSelect=b?"":"none"))}a.fn.drag=function(a,b,c){return b&&this.bind("dragstart",a),c&&this.bind("dragend",c),a?this.bind("drag",b?b:a):this.trigger("drag")};var b=a.event,c=b.special,d=c.drag={not:":input",distance:0,which:1,dragging:!1,setup:function(c){c=a.extend({distance:d.distance,which:d.which,not:d.not},c||{}),c.distance=g(c.distance),b.add(this,"mousedown",e,c),this.attachEvent&&this.attachEvent("ondragstart",h)},teardown:function(){b.remove(this,"mousedown",e),this===d.dragging&&(d.dragging=d.proxy=!1),i(this,!0),this.detachEvent&&this.detachEvent("ondragstart",h)}};c.dragstart=c.dragend={setup:function(){},teardown:function(){}}})(jQuery); | ||||
| 
 | ||||
| /* jquery.mousewheel.min.js | ||||
|  * Copyright (c) 2011 Brandon Aaron (http://brandonaaron.net)
 | ||||
|  * Licensed under the MIT License (LICENSE.txt). | ||||
|  * Thanks to: http://adomas.org/javascript-mouse-wheel/ for some pointers.
 | ||||
|  * Thanks to: Mathias Bank(http://www.mathias-bank.de) for a scope bug fix.
 | ||||
|  * Thanks to: Seamus Leahy for adding deltaX and deltaY | ||||
|  * | ||||
|  * Version: 3.0.6 | ||||
|  * | ||||
|  * Requires: 1.2.2+ | ||||
|  */ | ||||
| (function(d){function e(a){var b=a||window.event,c=[].slice.call(arguments,1),f=0,e=0,g=0,a=d.event.fix(b);a.type="mousewheel";b.wheelDelta&&(f=b.wheelDelta/120);b.detail&&(f=-b.detail/3);g=f;void 0!==b.axis&&b.axis===b.HORIZONTAL_AXIS&&(g=0,e=-1*f);void 0!==b.wheelDeltaY&&(g=b.wheelDeltaY/120);void 0!==b.wheelDeltaX&&(e=-1*b.wheelDeltaX/120);c.unshift(a,f,e,g);return(d.event.dispatch||d.event.handle).apply(this,c)}var c=["DOMMouseScroll","mousewheel"];if(d.event.fixHooks)for(var h=c.length;h;)d.event.fixHooks[c[--h]]=d.event.mouseHooks;d.event.special.mousewheel={setup:function(){if(this.addEventListener)for(var a=c.length;a;)this.addEventListener(c[--a],e,!1);else this.onmousewheel=e},teardown:function(){if(this.removeEventListener)for(var a=c.length;a;)this.removeEventListener(c[--a],e,!1);else this.onmousewheel=null}};d.fn.extend({mousewheel:function(a){return a?this.bind("mousewheel",a):this.trigger("mousewheel")},unmousewheel:function(a){return this.unbind("mousewheel",a)}})})(jQuery); | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| (function ($) { | ||||
|     var options = { | ||||
|         xaxis: { | ||||
|             zoomRange: null, // or [number, number] (min range, max range)
 | ||||
|             panRange: null // or [number, number] (min, max)
 | ||||
|         }, | ||||
|         zoom: { | ||||
|             interactive: false, | ||||
|             trigger: "dblclick", // or "click" for single click
 | ||||
|             amount: 1.5 // how much to zoom relative to current position, 2 = 200% (zoom in), 0.5 = 50% (zoom out)
 | ||||
|         }, | ||||
|         pan: { | ||||
|             interactive: false, | ||||
|             cursor: "move", | ||||
|             frameRate: 20 | ||||
|         } | ||||
|     }; | ||||
| 
 | ||||
|     function init(plot) { | ||||
|         function onZoomClick(e, zoomOut) { | ||||
|             var c = plot.offset(); | ||||
|             c.left = e.pageX - c.left; | ||||
|             c.top = e.pageY - c.top; | ||||
|             if (zoomOut) | ||||
|                 plot.zoomOut({ center: c }); | ||||
|             else | ||||
|                 plot.zoom({ center: c }); | ||||
|         } | ||||
| 
 | ||||
|         function onMouseWheel(e, delta) { | ||||
|             e.preventDefault(); | ||||
|             onZoomClick(e, delta < 0); | ||||
|             return false; | ||||
|         } | ||||
|          | ||||
|         var prevCursor = 'default', prevPageX = 0, prevPageY = 0, | ||||
|             panTimeout = null; | ||||
| 
 | ||||
|         function onDragStart(e) { | ||||
|             if (e.which != 1)  // only accept left-click
 | ||||
|                 return false; | ||||
|             var c = plot.getPlaceholder().css('cursor'); | ||||
|             if (c) | ||||
|                 prevCursor = c; | ||||
|             plot.getPlaceholder().css('cursor', plot.getOptions().pan.cursor); | ||||
|             prevPageX = e.pageX; | ||||
|             prevPageY = e.pageY; | ||||
|         } | ||||
|          | ||||
|         function onDrag(e) { | ||||
|             var frameRate = plot.getOptions().pan.frameRate; | ||||
|             if (panTimeout || !frameRate) | ||||
|                 return; | ||||
| 
 | ||||
|             panTimeout = setTimeout(function () { | ||||
|                 plot.pan({ left: prevPageX - e.pageX, | ||||
|                            top: prevPageY - e.pageY }); | ||||
|                 prevPageX = e.pageX; | ||||
|                 prevPageY = e.pageY; | ||||
|                                                      | ||||
|                 panTimeout = null; | ||||
|             }, 1 / frameRate * 1000); | ||||
|         } | ||||
| 
 | ||||
|         function onDragEnd(e) { | ||||
|             if (panTimeout) { | ||||
|                 clearTimeout(panTimeout); | ||||
|                 panTimeout = null; | ||||
|             } | ||||
|                      | ||||
|             plot.getPlaceholder().css('cursor', prevCursor); | ||||
|             plot.pan({ left: prevPageX - e.pageX, | ||||
|                        top: prevPageY - e.pageY }); | ||||
|         } | ||||
|          | ||||
|         function bindEvents(plot, eventHolder) { | ||||
|             var o = plot.getOptions(); | ||||
|             if (o.zoom.interactive) { | ||||
|                 eventHolder[o.zoom.trigger](onZoomClick); | ||||
|                 eventHolder.mousewheel(onMouseWheel); | ||||
|             } | ||||
| 
 | ||||
|             if (o.pan.interactive) { | ||||
|                 eventHolder.bind("dragstart", { distance: 10 }, onDragStart); | ||||
|                 eventHolder.bind("drag", onDrag); | ||||
|                 eventHolder.bind("dragend", onDragEnd); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         plot.zoomOut = function (args) { | ||||
|             if (!args) | ||||
|                 args = {}; | ||||
|              | ||||
|             if (!args.amount) | ||||
|                 args.amount = plot.getOptions().zoom.amount; | ||||
| 
 | ||||
|             args.amount = 1 / args.amount; | ||||
|             plot.zoom(args); | ||||
|         }; | ||||
|          | ||||
|         plot.zoom = function (args) { | ||||
|             if (!args) | ||||
|                 args = {}; | ||||
|              | ||||
|             var c = args.center, | ||||
|                 amount = args.amount || plot.getOptions().zoom.amount, | ||||
|                 w = plot.width(), h = plot.height(); | ||||
| 
 | ||||
|             if (!c) | ||||
|                 c = { left: w / 2, top: h / 2 }; | ||||
|                  | ||||
|             var xf = c.left / w, | ||||
|                 yf = c.top / h, | ||||
|                 minmax = { | ||||
|                     x: { | ||||
|                         min: c.left - xf * w / amount, | ||||
|                         max: c.left + (1 - xf) * w / amount | ||||
|                     }, | ||||
|                     y: { | ||||
|                         min: c.top - yf * h / amount, | ||||
|                         max: c.top + (1 - yf) * h / amount | ||||
|                     } | ||||
|                 }; | ||||
| 
 | ||||
|             $.each(plot.getAxes(), function(_, axis) { | ||||
|                 var opts = axis.options, | ||||
|                     min = minmax[axis.direction].min, | ||||
|                     max = minmax[axis.direction].max, | ||||
|                     zr = opts.zoomRange, | ||||
|                     pr = opts.panRange; | ||||
| 
 | ||||
|                 if (zr === false) // no zooming on this axis
 | ||||
|                     return; | ||||
|                      | ||||
|                 min = axis.c2p(min); | ||||
|                 max = axis.c2p(max); | ||||
|                 if (min > max) { | ||||
|                     // make sure min < max
 | ||||
|                     var tmp = min; | ||||
|                     min = max; | ||||
|                     max = tmp; | ||||
|                 } | ||||
| 
 | ||||
|                 //Check that we are in panRange
 | ||||
|                 if (pr) { | ||||
|                     if (pr[0] != null && min < pr[0]) { | ||||
|                         min = pr[0]; | ||||
|                     } | ||||
|                     if (pr[1] != null && max > pr[1]) { | ||||
|                         max = pr[1]; | ||||
|                     } | ||||
|                 } | ||||
| 
 | ||||
|                 var range = max - min; | ||||
|                 if (zr && | ||||
|                     ((zr[0] != null && range < zr[0] && amount >1) || | ||||
|                      (zr[1] != null && range > zr[1] && amount <1))) | ||||
|                     return; | ||||
|              | ||||
|                 opts.min = min; | ||||
|                 opts.max = max; | ||||
|             }); | ||||
|              | ||||
|             plot.setupGrid(); | ||||
|             plot.draw(); | ||||
|              | ||||
|             if (!args.preventEvent) | ||||
|                 plot.getPlaceholder().trigger("plotzoom", [ plot, args ]); | ||||
|         }; | ||||
| 
 | ||||
|         plot.pan = function (args) { | ||||
|             var delta = { | ||||
|                 x: +args.left, | ||||
|                 y: +args.top | ||||
|             }; | ||||
| 
 | ||||
|             if (isNaN(delta.x)) | ||||
|                 delta.x = 0; | ||||
|             if (isNaN(delta.y)) | ||||
|                 delta.y = 0; | ||||
| 
 | ||||
|             $.each(plot.getAxes(), function (_, axis) { | ||||
|                 var opts = axis.options, | ||||
|                     min, max, d = delta[axis.direction]; | ||||
| 
 | ||||
|                 min = axis.c2p(axis.p2c(axis.min) + d), | ||||
|                 max = axis.c2p(axis.p2c(axis.max) + d); | ||||
| 
 | ||||
|                 var pr = opts.panRange; | ||||
|                 if (pr === false) // no panning on this axis
 | ||||
|                     return; | ||||
|                  | ||||
|                 if (pr) { | ||||
|                     // check whether we hit the wall
 | ||||
|                     if (pr[0] != null && pr[0] > min) { | ||||
|                         d = pr[0] - min; | ||||
|                         min += d; | ||||
|                         max += d; | ||||
|                     } | ||||
|                      | ||||
|                     if (pr[1] != null && pr[1] < max) { | ||||
|                         d = pr[1] - max; | ||||
|                         min += d; | ||||
|                         max += d; | ||||
|                     } | ||||
|                 } | ||||
|                  | ||||
|                 opts.min = min; | ||||
|                 opts.max = max; | ||||
|             }); | ||||
|              | ||||
|             plot.setupGrid(); | ||||
|             plot.draw(); | ||||
|              | ||||
|             if (!args.preventEvent) | ||||
|                 plot.getPlaceholder().trigger("plotpan", [ plot, args ]); | ||||
|         }; | ||||
| 
 | ||||
|         function shutdown(plot, eventHolder) { | ||||
|             eventHolder.unbind(plot.getOptions().zoom.trigger, onZoomClick); | ||||
|             eventHolder.unbind("mousewheel", onMouseWheel); | ||||
|             eventHolder.unbind("dragstart", onDragStart); | ||||
|             eventHolder.unbind("drag", onDrag); | ||||
|             eventHolder.unbind("dragend", onDragEnd); | ||||
|             if (panTimeout) | ||||
|                 clearTimeout(panTimeout); | ||||
|         } | ||||
|          | ||||
|         plot.hooks.bindEvents.push(bindEvents); | ||||
|         plot.hooks.shutdown.push(shutdown); | ||||
|     } | ||||
|      | ||||
|     $.plot.plugins.push({ | ||||
|         init: init, | ||||
|         options: options, | ||||
|         name: 'navigate', | ||||
|         version: '1.3' | ||||
|     }); | ||||
| })(jQuery); | ||||
							
								
								
									
										7
									
								
								hledger-web/static/js/jquery.flot.navigate.min.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								hledger-web/static/js/jquery.flot.navigate.min.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										820
									
								
								hledger-web/static/js/jquery.flot.pie.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										820
									
								
								hledger-web/static/js/jquery.flot.pie.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,820 @@ | ||||
| /* Flot plugin for rendering pie charts. | ||||
| 
 | ||||
| Copyright (c) 2007-2014 IOLA and Ole Laursen. | ||||
| Licensed under the MIT license. | ||||
| 
 | ||||
| The plugin assumes that each series has a single data value, and that each | ||||
| value is a positive integer or zero.  Negative numbers don't make sense for a | ||||
| pie chart, and have unpredictable results.  The values do NOT need to be | ||||
| passed in as percentages; the plugin will calculate the total and per-slice | ||||
| percentages internally. | ||||
| 
 | ||||
| * Created by Brian Medendorp | ||||
| 
 | ||||
| * Updated with contributions from btburnett3, Anthony Aragues and Xavi Ivars | ||||
| 
 | ||||
| The plugin supports these options: | ||||
| 
 | ||||
| 	series: { | ||||
| 		pie: { | ||||
| 			show: true/false | ||||
| 			radius: 0-1 for percentage of fullsize, or a specified pixel length, or 'auto' | ||||
| 			innerRadius: 0-1 for percentage of fullsize or a specified pixel length, for creating a donut effect | ||||
| 			startAngle: 0-2 factor of PI used for starting angle (in radians) i.e 3/2 starts at the top, 0 and 2 have the same result | ||||
| 			tilt: 0-1 for percentage to tilt the pie, where 1 is no tilt, and 0 is completely flat (nothing will show) | ||||
| 			offset: { | ||||
| 				top: integer value to move the pie up or down | ||||
| 				left: integer value to move the pie left or right, or 'auto' | ||||
| 			}, | ||||
| 			stroke: { | ||||
| 				color: any hexidecimal color value (other formats may or may not work, so best to stick with something like '#FFF') | ||||
| 				width: integer pixel width of the stroke | ||||
| 			}, | ||||
| 			label: { | ||||
| 				show: true/false, or 'auto' | ||||
| 				formatter:  a user-defined function that modifies the text/style of the label text | ||||
| 				radius: 0-1 for percentage of fullsize, or a specified pixel length | ||||
| 				background: { | ||||
| 					color: any hexidecimal color value (other formats may or may not work, so best to stick with something like '#000') | ||||
| 					opacity: 0-1 | ||||
| 				}, | ||||
| 				threshold: 0-1 for the percentage value at which to hide labels (if they're too small) | ||||
| 			}, | ||||
| 			combine: { | ||||
| 				threshold: 0-1 for the percentage value at which to combine slices (if they're too small) | ||||
| 				color: any hexidecimal color value (other formats may or may not work, so best to stick with something like '#CCC'), if null, the plugin will automatically use the color of the first slice to be combined | ||||
| 				label: any text value of what the combined slice should be labeled | ||||
| 			} | ||||
| 			highlight: { | ||||
| 				opacity: 0-1 | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| More detail and specific examples can be found in the included HTML file. | ||||
| 
 | ||||
| */ | ||||
| 
 | ||||
| (function($) { | ||||
| 
 | ||||
| 	// Maximum redraw attempts when fitting labels within the plot
 | ||||
| 
 | ||||
| 	var REDRAW_ATTEMPTS = 10; | ||||
| 
 | ||||
| 	// Factor by which to shrink the pie when fitting labels within the plot
 | ||||
| 
 | ||||
| 	var REDRAW_SHRINK = 0.95; | ||||
| 
 | ||||
| 	function init(plot) { | ||||
| 
 | ||||
| 		var canvas = null, | ||||
| 			target = null, | ||||
| 			options = null, | ||||
| 			maxRadius = null, | ||||
| 			centerLeft = null, | ||||
| 			centerTop = null, | ||||
| 			processed = false, | ||||
| 			ctx = null; | ||||
| 
 | ||||
| 		// interactive variables
 | ||||
| 
 | ||||
| 		var highlights = []; | ||||
| 
 | ||||
| 		// add hook to determine if pie plugin in enabled, and then perform necessary operations
 | ||||
| 
 | ||||
| 		plot.hooks.processOptions.push(function(plot, options) { | ||||
| 			if (options.series.pie.show) { | ||||
| 
 | ||||
| 				options.grid.show = false; | ||||
| 
 | ||||
| 				// set labels.show
 | ||||
| 
 | ||||
| 				if (options.series.pie.label.show == "auto") { | ||||
| 					if (options.legend.show) { | ||||
| 						options.series.pie.label.show = false; | ||||
| 					} else { | ||||
| 						options.series.pie.label.show = true; | ||||
| 					} | ||||
| 				} | ||||
| 
 | ||||
| 				// set radius
 | ||||
| 
 | ||||
| 				if (options.series.pie.radius == "auto") { | ||||
| 					if (options.series.pie.label.show) { | ||||
| 						options.series.pie.radius = 3/4; | ||||
| 					} else { | ||||
| 						options.series.pie.radius = 1; | ||||
| 					} | ||||
| 				} | ||||
| 
 | ||||
| 				// ensure sane tilt
 | ||||
| 
 | ||||
| 				if (options.series.pie.tilt > 1) { | ||||
| 					options.series.pie.tilt = 1; | ||||
| 				} else if (options.series.pie.tilt < 0) { | ||||
| 					options.series.pie.tilt = 0; | ||||
| 				} | ||||
| 			} | ||||
| 		}); | ||||
| 
 | ||||
| 		plot.hooks.bindEvents.push(function(plot, eventHolder) { | ||||
| 			var options = plot.getOptions(); | ||||
| 			if (options.series.pie.show) { | ||||
| 				if (options.grid.hoverable) { | ||||
| 					eventHolder.unbind("mousemove").mousemove(onMouseMove); | ||||
| 				} | ||||
| 				if (options.grid.clickable) { | ||||
| 					eventHolder.unbind("click").click(onClick); | ||||
| 				} | ||||
| 			} | ||||
| 		}); | ||||
| 
 | ||||
| 		plot.hooks.processDatapoints.push(function(plot, series, data, datapoints) { | ||||
| 			var options = plot.getOptions(); | ||||
| 			if (options.series.pie.show) { | ||||
| 				processDatapoints(plot, series, data, datapoints); | ||||
| 			} | ||||
| 		}); | ||||
| 
 | ||||
| 		plot.hooks.drawOverlay.push(function(plot, octx) { | ||||
| 			var options = plot.getOptions(); | ||||
| 			if (options.series.pie.show) { | ||||
| 				drawOverlay(plot, octx); | ||||
| 			} | ||||
| 		}); | ||||
| 
 | ||||
| 		plot.hooks.draw.push(function(plot, newCtx) { | ||||
| 			var options = plot.getOptions(); | ||||
| 			if (options.series.pie.show) { | ||||
| 				draw(plot, newCtx); | ||||
| 			} | ||||
| 		}); | ||||
| 
 | ||||
| 		function processDatapoints(plot, series, datapoints) { | ||||
| 			if (!processed)	{ | ||||
| 				processed = true; | ||||
| 				canvas = plot.getCanvas(); | ||||
| 				target = $(canvas).parent(); | ||||
| 				options = plot.getOptions(); | ||||
| 				plot.setData(combine(plot.getData())); | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		function combine(data) { | ||||
| 
 | ||||
| 			var total = 0, | ||||
| 				combined = 0, | ||||
| 				numCombined = 0, | ||||
| 				color = options.series.pie.combine.color, | ||||
| 				newdata = []; | ||||
| 
 | ||||
| 			// Fix up the raw data from Flot, ensuring the data is numeric
 | ||||
| 
 | ||||
| 			for (var i = 0; i < data.length; ++i) { | ||||
| 
 | ||||
| 				var value = data[i].data; | ||||
| 
 | ||||
| 				// If the data is an array, we'll assume that it's a standard
 | ||||
| 				// Flot x-y pair, and are concerned only with the second value.
 | ||||
| 
 | ||||
| 				// Note how we use the original array, rather than creating a
 | ||||
| 				// new one; this is more efficient and preserves any extra data
 | ||||
| 				// that the user may have stored in higher indexes.
 | ||||
| 
 | ||||
| 				if ($.isArray(value) && value.length == 1) { | ||||
|     				value = value[0]; | ||||
| 				} | ||||
| 
 | ||||
| 				if ($.isArray(value)) { | ||||
| 					// Equivalent to $.isNumeric() but compatible with jQuery < 1.7
 | ||||
| 					if (!isNaN(parseFloat(value[1])) && isFinite(value[1])) { | ||||
| 						value[1] = +value[1]; | ||||
| 					} else { | ||||
| 						value[1] = 0; | ||||
| 					} | ||||
| 				} else if (!isNaN(parseFloat(value)) && isFinite(value)) { | ||||
| 					value = [1, +value]; | ||||
| 				} else { | ||||
| 					value = [1, 0]; | ||||
| 				} | ||||
| 
 | ||||
| 				data[i].data = [value]; | ||||
| 			} | ||||
| 
 | ||||
| 			// Sum up all the slices, so we can calculate percentages for each
 | ||||
| 
 | ||||
| 			for (var i = 0; i < data.length; ++i) { | ||||
| 				total += data[i].data[0][1]; | ||||
| 			} | ||||
| 
 | ||||
| 			// Count the number of slices with percentages below the combine
 | ||||
| 			// threshold; if it turns out to be just one, we won't combine.
 | ||||
| 
 | ||||
| 			for (var i = 0; i < data.length; ++i) { | ||||
| 				var value = data[i].data[0][1]; | ||||
| 				if (value / total <= options.series.pie.combine.threshold) { | ||||
| 					combined += value; | ||||
| 					numCombined++; | ||||
| 					if (!color) { | ||||
| 						color = data[i].color; | ||||
| 					} | ||||
| 				} | ||||
| 			} | ||||
| 
 | ||||
| 			for (var i = 0; i < data.length; ++i) { | ||||
| 				var value = data[i].data[0][1]; | ||||
| 				if (numCombined < 2 || value / total > options.series.pie.combine.threshold) { | ||||
| 					newdata.push( | ||||
| 						$.extend(data[i], {     /* extend to allow keeping all other original data values | ||||
| 						                           and using them e.g. in labelFormatter. */ | ||||
| 							data: [[1, value]], | ||||
| 							color: data[i].color, | ||||
| 							label: data[i].label, | ||||
| 							angle: value * Math.PI * 2 / total, | ||||
| 							percent: value / (total / 100) | ||||
| 						}) | ||||
| 					); | ||||
| 				} | ||||
| 			} | ||||
| 
 | ||||
| 			if (numCombined > 1) { | ||||
| 				newdata.push({ | ||||
| 					data: [[1, combined]], | ||||
| 					color: color, | ||||
| 					label: options.series.pie.combine.label, | ||||
| 					angle: combined * Math.PI * 2 / total, | ||||
| 					percent: combined / (total / 100) | ||||
| 				}); | ||||
| 			} | ||||
| 
 | ||||
| 			return newdata; | ||||
| 		} | ||||
| 
 | ||||
| 		function draw(plot, newCtx) { | ||||
| 
 | ||||
| 			if (!target) { | ||||
| 				return; // if no series were passed
 | ||||
| 			} | ||||
| 
 | ||||
| 			var canvasWidth = plot.getPlaceholder().width(), | ||||
| 				canvasHeight = plot.getPlaceholder().height(), | ||||
| 				legendWidth = target.children().filter(".legend").children().width() || 0; | ||||
| 
 | ||||
| 			ctx = newCtx; | ||||
| 
 | ||||
| 			// WARNING: HACK! REWRITE THIS CODE AS SOON AS POSSIBLE!
 | ||||
| 
 | ||||
| 			// When combining smaller slices into an 'other' slice, we need to
 | ||||
| 			// add a new series.  Since Flot gives plugins no way to modify the
 | ||||
| 			// list of series, the pie plugin uses a hack where the first call
 | ||||
| 			// to processDatapoints results in a call to setData with the new
 | ||||
| 			// list of series, then subsequent processDatapoints do nothing.
 | ||||
| 
 | ||||
| 			// The plugin-global 'processed' flag is used to control this hack;
 | ||||
| 			// it starts out false, and is set to true after the first call to
 | ||||
| 			// processDatapoints.
 | ||||
| 
 | ||||
| 			// Unfortunately this turns future setData calls into no-ops; they
 | ||||
| 			// call processDatapoints, the flag is true, and nothing happens.
 | ||||
| 
 | ||||
| 			// To fix this we'll set the flag back to false here in draw, when
 | ||||
| 			// all series have been processed, so the next sequence of calls to
 | ||||
| 			// processDatapoints once again starts out with a slice-combine.
 | ||||
| 			// This is really a hack; in 0.9 we need to give plugins a proper
 | ||||
| 			// way to modify series before any processing begins.
 | ||||
| 
 | ||||
| 			processed = false; | ||||
| 
 | ||||
| 			// calculate maximum radius and center point
 | ||||
| 
 | ||||
| 			maxRadius =  Math.min(canvasWidth, canvasHeight / options.series.pie.tilt) / 2; | ||||
| 			centerTop = canvasHeight / 2 + options.series.pie.offset.top; | ||||
| 			centerLeft = canvasWidth / 2; | ||||
| 
 | ||||
| 			if (options.series.pie.offset.left == "auto") { | ||||
| 				if (options.legend.position.match("w")) { | ||||
| 					centerLeft += legendWidth / 2; | ||||
| 				} else { | ||||
| 					centerLeft -= legendWidth / 2; | ||||
| 				} | ||||
| 				if (centerLeft < maxRadius) { | ||||
| 					centerLeft = maxRadius; | ||||
| 				} else if (centerLeft > canvasWidth - maxRadius) { | ||||
| 					centerLeft = canvasWidth - maxRadius; | ||||
| 				} | ||||
| 			} else { | ||||
| 				centerLeft += options.series.pie.offset.left; | ||||
| 			} | ||||
| 
 | ||||
| 			var slices = plot.getData(), | ||||
| 				attempts = 0; | ||||
| 
 | ||||
| 			// Keep shrinking the pie's radius until drawPie returns true,
 | ||||
| 			// indicating that all the labels fit, or we try too many times.
 | ||||
| 
 | ||||
| 			do { | ||||
| 				if (attempts > 0) { | ||||
| 					maxRadius *= REDRAW_SHRINK; | ||||
| 				} | ||||
| 				attempts += 1; | ||||
| 				clear(); | ||||
| 				if (options.series.pie.tilt <= 0.8) { | ||||
| 					drawShadow(); | ||||
| 				} | ||||
| 			} while (!drawPie() && attempts < REDRAW_ATTEMPTS) | ||||
| 
 | ||||
| 			if (attempts >= REDRAW_ATTEMPTS) { | ||||
| 				clear(); | ||||
| 				target.prepend("<div class='error'>Could not draw pie with labels contained inside canvas</div>"); | ||||
| 			} | ||||
| 
 | ||||
| 			if (plot.setSeries && plot.insertLegend) { | ||||
| 				plot.setSeries(slices); | ||||
| 				plot.insertLegend(); | ||||
| 			} | ||||
| 
 | ||||
| 			// we're actually done at this point, just defining internal functions at this point
 | ||||
| 
 | ||||
| 			function clear() { | ||||
| 				ctx.clearRect(0, 0, canvasWidth, canvasHeight); | ||||
| 				target.children().filter(".pieLabel, .pieLabelBackground").remove(); | ||||
| 			} | ||||
| 
 | ||||
| 			function drawShadow() { | ||||
| 
 | ||||
| 				var shadowLeft = options.series.pie.shadow.left; | ||||
| 				var shadowTop = options.series.pie.shadow.top; | ||||
| 				var edge = 10; | ||||
| 				var alpha = options.series.pie.shadow.alpha; | ||||
| 				var radius = options.series.pie.radius > 1 ? options.series.pie.radius : maxRadius * options.series.pie.radius; | ||||
| 
 | ||||
| 				if (radius >= canvasWidth / 2 - shadowLeft || radius * options.series.pie.tilt >= canvasHeight / 2 - shadowTop || radius <= edge) { | ||||
| 					return;	// shadow would be outside canvas, so don't draw it
 | ||||
| 				} | ||||
| 
 | ||||
| 				ctx.save(); | ||||
| 				ctx.translate(shadowLeft,shadowTop); | ||||
| 				ctx.globalAlpha = alpha; | ||||
| 				ctx.fillStyle = "#000"; | ||||
| 
 | ||||
| 				// center and rotate to starting position
 | ||||
| 
 | ||||
| 				ctx.translate(centerLeft,centerTop); | ||||
| 				ctx.scale(1, options.series.pie.tilt); | ||||
| 
 | ||||
| 				//radius -= edge;
 | ||||
| 
 | ||||
| 				for (var i = 1; i <= edge; i++) { | ||||
| 					ctx.beginPath(); | ||||
| 					ctx.arc(0, 0, radius, 0, Math.PI * 2, false); | ||||
| 					ctx.fill(); | ||||
| 					radius -= i; | ||||
| 				} | ||||
| 
 | ||||
| 				ctx.restore(); | ||||
| 			} | ||||
| 
 | ||||
| 			function drawPie() { | ||||
| 
 | ||||
| 				var startAngle = Math.PI * options.series.pie.startAngle; | ||||
| 				var radius = options.series.pie.radius > 1 ? options.series.pie.radius : maxRadius * options.series.pie.radius; | ||||
| 
 | ||||
| 				// center and rotate to starting position
 | ||||
| 
 | ||||
| 				ctx.save(); | ||||
| 				ctx.translate(centerLeft,centerTop); | ||||
| 				ctx.scale(1, options.series.pie.tilt); | ||||
| 				//ctx.rotate(startAngle); // start at top; -- This doesn't work properly in Opera
 | ||||
| 
 | ||||
| 				// draw slices
 | ||||
| 
 | ||||
| 				ctx.save(); | ||||
| 				var currentAngle = startAngle; | ||||
| 				for (var i = 0; i < slices.length; ++i) { | ||||
| 					slices[i].startAngle = currentAngle; | ||||
| 					drawSlice(slices[i].angle, slices[i].color, true); | ||||
| 				} | ||||
| 				ctx.restore(); | ||||
| 
 | ||||
| 				// draw slice outlines
 | ||||
| 
 | ||||
| 				if (options.series.pie.stroke.width > 0) { | ||||
| 					ctx.save(); | ||||
| 					ctx.lineWidth = options.series.pie.stroke.width; | ||||
| 					currentAngle = startAngle; | ||||
| 					for (var i = 0; i < slices.length; ++i) { | ||||
| 						drawSlice(slices[i].angle, options.series.pie.stroke.color, false); | ||||
| 					} | ||||
| 					ctx.restore(); | ||||
| 				} | ||||
| 
 | ||||
| 				// draw donut hole
 | ||||
| 
 | ||||
| 				drawDonutHole(ctx); | ||||
| 
 | ||||
| 				ctx.restore(); | ||||
| 
 | ||||
| 				// Draw the labels, returning true if they fit within the plot
 | ||||
| 
 | ||||
| 				if (options.series.pie.label.show) { | ||||
| 					return drawLabels(); | ||||
| 				} else return true; | ||||
| 
 | ||||
| 				function drawSlice(angle, color, fill) { | ||||
| 
 | ||||
| 					if (angle <= 0 || isNaN(angle)) { | ||||
| 						return; | ||||
| 					} | ||||
| 
 | ||||
| 					if (fill) { | ||||
| 						ctx.fillStyle = color; | ||||
| 					} else { | ||||
| 						ctx.strokeStyle = color; | ||||
| 						ctx.lineJoin = "round"; | ||||
| 					} | ||||
| 
 | ||||
| 					ctx.beginPath(); | ||||
| 					if (Math.abs(angle - Math.PI * 2) > 0.000000001) { | ||||
| 						ctx.moveTo(0, 0); // Center of the pie
 | ||||
| 					} | ||||
| 
 | ||||
| 					//ctx.arc(0, 0, radius, 0, angle, false); // This doesn't work properly in Opera
 | ||||
| 					ctx.arc(0, 0, radius,currentAngle, currentAngle + angle / 2, false); | ||||
| 					ctx.arc(0, 0, radius,currentAngle + angle / 2, currentAngle + angle, false); | ||||
| 					ctx.closePath(); | ||||
| 					//ctx.rotate(angle); // This doesn't work properly in Opera
 | ||||
| 					currentAngle += angle; | ||||
| 
 | ||||
| 					if (fill) { | ||||
| 						ctx.fill(); | ||||
| 					} else { | ||||
| 						ctx.stroke(); | ||||
| 					} | ||||
| 				} | ||||
| 
 | ||||
| 				function drawLabels() { | ||||
| 
 | ||||
| 					var currentAngle = startAngle; | ||||
| 					var radius = options.series.pie.label.radius > 1 ? options.series.pie.label.radius : maxRadius * options.series.pie.label.radius; | ||||
| 
 | ||||
| 					for (var i = 0; i < slices.length; ++i) { | ||||
| 						if (slices[i].percent >= options.series.pie.label.threshold * 100) { | ||||
| 							if (!drawLabel(slices[i], currentAngle, i)) { | ||||
| 								return false; | ||||
| 							} | ||||
| 						} | ||||
| 						currentAngle += slices[i].angle; | ||||
| 					} | ||||
| 
 | ||||
| 					return true; | ||||
| 
 | ||||
| 					function drawLabel(slice, startAngle, index) { | ||||
| 
 | ||||
| 						if (slice.data[0][1] == 0) { | ||||
| 							return true; | ||||
| 						} | ||||
| 
 | ||||
| 						// format label text
 | ||||
| 
 | ||||
| 						var lf = options.legend.labelFormatter, text, plf = options.series.pie.label.formatter; | ||||
| 
 | ||||
| 						if (lf) { | ||||
| 							text = lf(slice.label, slice); | ||||
| 						} else { | ||||
| 							text = slice.label; | ||||
| 						} | ||||
| 
 | ||||
| 						if (plf) { | ||||
| 							text = plf(text, slice); | ||||
| 						} | ||||
| 
 | ||||
| 						var halfAngle = ((startAngle + slice.angle) + startAngle) / 2; | ||||
| 						var x = centerLeft + Math.round(Math.cos(halfAngle) * radius); | ||||
| 						var y = centerTop + Math.round(Math.sin(halfAngle) * radius) * options.series.pie.tilt; | ||||
| 
 | ||||
| 						var html = "<span class='pieLabel' id='pieLabel" + index + "' style='position:absolute;top:" + y + "px;left:" + x + "px;'>" + text + "</span>"; | ||||
| 						target.append(html); | ||||
| 
 | ||||
| 						var label = target.children("#pieLabel" + index); | ||||
| 						var labelTop = (y - label.height() / 2); | ||||
| 						var labelLeft = (x - label.width() / 2); | ||||
| 
 | ||||
| 						label.css("top", labelTop); | ||||
| 						label.css("left", labelLeft); | ||||
| 
 | ||||
| 						// check to make sure that the label is not outside the canvas
 | ||||
| 
 | ||||
| 						if (0 - labelTop > 0 || 0 - labelLeft > 0 || canvasHeight - (labelTop + label.height()) < 0 || canvasWidth - (labelLeft + label.width()) < 0) { | ||||
| 							return false; | ||||
| 						} | ||||
| 
 | ||||
| 						if (options.series.pie.label.background.opacity != 0) { | ||||
| 
 | ||||
| 							// put in the transparent background separately to avoid blended labels and label boxes
 | ||||
| 
 | ||||
| 							var c = options.series.pie.label.background.color; | ||||
| 
 | ||||
| 							if (c == null) { | ||||
| 								c = slice.color; | ||||
| 							} | ||||
| 
 | ||||
| 							var pos = "top:" + labelTop + "px;left:" + labelLeft + "px;"; | ||||
| 							$("<div class='pieLabelBackground' style='position:absolute;width:" + label.width() + "px;height:" + label.height() + "px;" + pos + "background-color:" + c + ";'></div>") | ||||
| 								.css("opacity", options.series.pie.label.background.opacity) | ||||
| 								.insertBefore(label); | ||||
| 						} | ||||
| 
 | ||||
| 						return true; | ||||
| 					} // end individual label function
 | ||||
| 				} // end drawLabels function
 | ||||
| 			} // end drawPie function
 | ||||
| 		} // end draw function
 | ||||
| 
 | ||||
| 		// Placed here because it needs to be accessed from multiple locations
 | ||||
| 
 | ||||
| 		function drawDonutHole(layer) { | ||||
| 			if (options.series.pie.innerRadius > 0) { | ||||
| 
 | ||||
| 				// subtract the center
 | ||||
| 
 | ||||
| 				layer.save(); | ||||
| 				var innerRadius = options.series.pie.innerRadius > 1 ? options.series.pie.innerRadius : maxRadius * options.series.pie.innerRadius; | ||||
| 				layer.globalCompositeOperation = "destination-out"; // this does not work with excanvas, but it will fall back to using the stroke color
 | ||||
| 				layer.beginPath(); | ||||
| 				layer.fillStyle = options.series.pie.stroke.color; | ||||
| 				layer.arc(0, 0, innerRadius, 0, Math.PI * 2, false); | ||||
| 				layer.fill(); | ||||
| 				layer.closePath(); | ||||
| 				layer.restore(); | ||||
| 
 | ||||
| 				// add inner stroke
 | ||||
| 
 | ||||
| 				layer.save(); | ||||
| 				layer.beginPath(); | ||||
| 				layer.strokeStyle = options.series.pie.stroke.color; | ||||
| 				layer.arc(0, 0, innerRadius, 0, Math.PI * 2, false); | ||||
| 				layer.stroke(); | ||||
| 				layer.closePath(); | ||||
| 				layer.restore(); | ||||
| 
 | ||||
| 				// TODO: add extra shadow inside hole (with a mask) if the pie is tilted.
 | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		//-- Additional Interactive related functions --
 | ||||
| 
 | ||||
| 		function isPointInPoly(poly, pt) { | ||||
| 			for(var c = false, i = -1, l = poly.length, j = l - 1; ++i < l; j = i) | ||||
| 				((poly[i][1] <= pt[1] && pt[1] < poly[j][1]) || (poly[j][1] <= pt[1] && pt[1]< poly[i][1])) | ||||
| 				&& (pt[0] < (poly[j][0] - poly[i][0]) * (pt[1] - poly[i][1]) / (poly[j][1] - poly[i][1]) + poly[i][0]) | ||||
| 				&& (c = !c); | ||||
| 			return c; | ||||
| 		} | ||||
| 
 | ||||
| 		function findNearbySlice(mouseX, mouseY) { | ||||
| 
 | ||||
| 			var slices = plot.getData(), | ||||
| 				options = plot.getOptions(), | ||||
| 				radius = options.series.pie.radius > 1 ? options.series.pie.radius : maxRadius * options.series.pie.radius, | ||||
| 				x, y; | ||||
| 
 | ||||
| 			for (var i = 0; i < slices.length; ++i) { | ||||
| 
 | ||||
| 				var s = slices[i]; | ||||
| 
 | ||||
| 				if (s.pie.show) { | ||||
| 
 | ||||
| 					ctx.save(); | ||||
| 					ctx.beginPath(); | ||||
| 					ctx.moveTo(0, 0); // Center of the pie
 | ||||
| 					//ctx.scale(1, options.series.pie.tilt);	// this actually seems to break everything when here.
 | ||||
| 					ctx.arc(0, 0, radius, s.startAngle, s.startAngle + s.angle / 2, false); | ||||
| 					ctx.arc(0, 0, radius, s.startAngle + s.angle / 2, s.startAngle + s.angle, false); | ||||
| 					ctx.closePath(); | ||||
| 					x = mouseX - centerLeft; | ||||
| 					y = mouseY - centerTop; | ||||
| 
 | ||||
| 					if (ctx.isPointInPath) { | ||||
| 						if (ctx.isPointInPath(mouseX - centerLeft, mouseY - centerTop)) { | ||||
| 							ctx.restore(); | ||||
| 							return { | ||||
| 								datapoint: [s.percent, s.data], | ||||
| 								dataIndex: 0, | ||||
| 								series: s, | ||||
| 								seriesIndex: i | ||||
| 							}; | ||||
| 						} | ||||
| 					} else { | ||||
| 
 | ||||
| 						// excanvas for IE doesn;t support isPointInPath, this is a workaround.
 | ||||
| 
 | ||||
| 						var p1X = radius * Math.cos(s.startAngle), | ||||
| 							p1Y = radius * Math.sin(s.startAngle), | ||||
| 							p2X = radius * Math.cos(s.startAngle + s.angle / 4), | ||||
| 							p2Y = radius * Math.sin(s.startAngle + s.angle / 4), | ||||
| 							p3X = radius * Math.cos(s.startAngle + s.angle / 2), | ||||
| 							p3Y = radius * Math.sin(s.startAngle + s.angle / 2), | ||||
| 							p4X = radius * Math.cos(s.startAngle + s.angle / 1.5), | ||||
| 							p4Y = radius * Math.sin(s.startAngle + s.angle / 1.5), | ||||
| 							p5X = radius * Math.cos(s.startAngle + s.angle), | ||||
| 							p5Y = radius * Math.sin(s.startAngle + s.angle), | ||||
| 							arrPoly = [[0, 0], [p1X, p1Y], [p2X, p2Y], [p3X, p3Y], [p4X, p4Y], [p5X, p5Y]], | ||||
| 							arrPoint = [x, y]; | ||||
| 
 | ||||
| 						// TODO: perhaps do some mathmatical trickery here with the Y-coordinate to compensate for pie tilt?
 | ||||
| 
 | ||||
| 						if (isPointInPoly(arrPoly, arrPoint)) { | ||||
| 							ctx.restore(); | ||||
| 							return { | ||||
| 								datapoint: [s.percent, s.data], | ||||
| 								dataIndex: 0, | ||||
| 								series: s, | ||||
| 								seriesIndex: i | ||||
| 							}; | ||||
| 						} | ||||
| 					} | ||||
| 
 | ||||
| 					ctx.restore(); | ||||
| 				} | ||||
| 			} | ||||
| 
 | ||||
| 			return null; | ||||
| 		} | ||||
| 
 | ||||
| 		function onMouseMove(e) { | ||||
| 			triggerClickHoverEvent("plothover", e); | ||||
| 		} | ||||
| 
 | ||||
| 		function onClick(e) { | ||||
| 			triggerClickHoverEvent("plotclick", e); | ||||
| 		} | ||||
| 
 | ||||
| 		// trigger click or hover event (they send the same parameters so we share their code)
 | ||||
| 
 | ||||
| 		function triggerClickHoverEvent(eventname, e) { | ||||
| 
 | ||||
| 			var offset = plot.offset(); | ||||
| 			var canvasX = parseInt(e.pageX - offset.left); | ||||
| 			var canvasY =  parseInt(e.pageY - offset.top); | ||||
| 			var item = findNearbySlice(canvasX, canvasY); | ||||
| 
 | ||||
| 			if (options.grid.autoHighlight) { | ||||
| 
 | ||||
| 				// clear auto-highlights
 | ||||
| 
 | ||||
| 				for (var i = 0; i < highlights.length; ++i) { | ||||
| 					var h = highlights[i]; | ||||
| 					if (h.auto == eventname && !(item && h.series == item.series)) { | ||||
| 						unhighlight(h.series); | ||||
| 					} | ||||
| 				} | ||||
| 			} | ||||
| 
 | ||||
| 			// highlight the slice
 | ||||
| 
 | ||||
| 			if (item) { | ||||
| 				highlight(item.series, eventname); | ||||
| 			} | ||||
| 
 | ||||
| 			// trigger any hover bind events
 | ||||
| 
 | ||||
| 			var pos = { pageX: e.pageX, pageY: e.pageY }; | ||||
| 			target.trigger(eventname, [pos, item]); | ||||
| 		} | ||||
| 
 | ||||
| 		function highlight(s, auto) { | ||||
| 			//if (typeof s == "number") {
 | ||||
| 			//	s = series[s];
 | ||||
| 			//}
 | ||||
| 
 | ||||
| 			var i = indexOfHighlight(s); | ||||
| 
 | ||||
| 			if (i == -1) { | ||||
| 				highlights.push({ series: s, auto: auto }); | ||||
| 				plot.triggerRedrawOverlay(); | ||||
| 			} else if (!auto) { | ||||
| 				highlights[i].auto = false; | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		function unhighlight(s) { | ||||
| 			if (s == null) { | ||||
| 				highlights = []; | ||||
| 				plot.triggerRedrawOverlay(); | ||||
| 			} | ||||
| 
 | ||||
| 			//if (typeof s == "number") {
 | ||||
| 			//	s = series[s];
 | ||||
| 			//}
 | ||||
| 
 | ||||
| 			var i = indexOfHighlight(s); | ||||
| 
 | ||||
| 			if (i != -1) { | ||||
| 				highlights.splice(i, 1); | ||||
| 				plot.triggerRedrawOverlay(); | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		function indexOfHighlight(s) { | ||||
| 			for (var i = 0; i < highlights.length; ++i) { | ||||
| 				var h = highlights[i]; | ||||
| 				if (h.series == s) | ||||
| 					return i; | ||||
| 			} | ||||
| 			return -1; | ||||
| 		} | ||||
| 
 | ||||
| 		function drawOverlay(plot, octx) { | ||||
| 
 | ||||
| 			var options = plot.getOptions(); | ||||
| 
 | ||||
| 			var radius = options.series.pie.radius > 1 ? options.series.pie.radius : maxRadius * options.series.pie.radius; | ||||
| 
 | ||||
| 			octx.save(); | ||||
| 			octx.translate(centerLeft, centerTop); | ||||
| 			octx.scale(1, options.series.pie.tilt); | ||||
| 
 | ||||
| 			for (var i = 0; i < highlights.length; ++i) { | ||||
| 				drawHighlight(highlights[i].series); | ||||
| 			} | ||||
| 
 | ||||
| 			drawDonutHole(octx); | ||||
| 
 | ||||
| 			octx.restore(); | ||||
| 
 | ||||
| 			function drawHighlight(series) { | ||||
| 
 | ||||
| 				if (series.angle <= 0 || isNaN(series.angle)) { | ||||
| 					return; | ||||
| 				} | ||||
| 
 | ||||
| 				//octx.fillStyle = parseColor(options.series.pie.highlight.color).scale(null, null, null, options.series.pie.highlight.opacity).toString();
 | ||||
| 				octx.fillStyle = "rgba(255, 255, 255, " + options.series.pie.highlight.opacity + ")"; // this is temporary until we have access to parseColor
 | ||||
| 				octx.beginPath(); | ||||
| 				if (Math.abs(series.angle - Math.PI * 2) > 0.000000001) { | ||||
| 					octx.moveTo(0, 0); // Center of the pie
 | ||||
| 				} | ||||
| 				octx.arc(0, 0, radius, series.startAngle, series.startAngle + series.angle / 2, false); | ||||
| 				octx.arc(0, 0, radius, series.startAngle + series.angle / 2, series.startAngle + series.angle, false); | ||||
| 				octx.closePath(); | ||||
| 				octx.fill(); | ||||
| 			} | ||||
| 		} | ||||
| 	} // end init (plugin body)
 | ||||
| 
 | ||||
| 	// define pie specific options and their default values
 | ||||
| 
 | ||||
| 	var options = { | ||||
| 		series: { | ||||
| 			pie: { | ||||
| 				show: false, | ||||
| 				radius: "auto",	// actual radius of the visible pie (based on full calculated radius if <=1, or hard pixel value)
 | ||||
| 				innerRadius: 0, /* for donut */ | ||||
| 				startAngle: 3/2, | ||||
| 				tilt: 1, | ||||
| 				shadow: { | ||||
| 					left: 5,	// shadow left offset
 | ||||
| 					top: 15,	// shadow top offset
 | ||||
| 					alpha: 0.02	// shadow alpha
 | ||||
| 				}, | ||||
| 				offset: { | ||||
| 					top: 0, | ||||
| 					left: "auto" | ||||
| 				}, | ||||
| 				stroke: { | ||||
| 					color: "#fff", | ||||
| 					width: 1 | ||||
| 				}, | ||||
| 				label: { | ||||
| 					show: "auto", | ||||
| 					formatter: function(label, slice) { | ||||
| 						return "<div style='font-size:x-small;text-align:center;padding:2px;color:" + slice.color + ";'>" + label + "<br/>" + Math.round(slice.percent) + "%</div>"; | ||||
| 					},	// formatter function
 | ||||
| 					radius: 1,	// radius at which to place the labels (based on full calculated radius if <=1, or hard pixel value)
 | ||||
| 					background: { | ||||
| 						color: null, | ||||
| 						opacity: 0 | ||||
| 					}, | ||||
| 					threshold: 0	// percentage at which to hide the label (i.e. the slice is too narrow)
 | ||||
| 				}, | ||||
| 				combine: { | ||||
| 					threshold: -1,	// percentage at which to combine little slices into one larger slice
 | ||||
| 					color: null,	// color to give the new slice (auto-generated if null)
 | ||||
| 					label: "Other"	// label to give the new slice
 | ||||
| 				}, | ||||
| 				highlight: { | ||||
| 					//color: "#fff",		// will add this functionality once parseColor is available
 | ||||
| 					opacity: 0.5 | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 	}; | ||||
| 
 | ||||
| 	$.plot.plugins.push({ | ||||
| 		init: init, | ||||
| 		options: options, | ||||
| 		name: "pie", | ||||
| 		version: "1.1" | ||||
| 	}); | ||||
| 
 | ||||
| })(jQuery); | ||||
							
								
								
									
										7
									
								
								hledger-web/static/js/jquery.flot.pie.min.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								hledger-web/static/js/jquery.flot.pie.min.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										59
									
								
								hledger-web/static/js/jquery.flot.resize.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										59
									
								
								hledger-web/static/js/jquery.flot.resize.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,59 @@ | ||||
| /* Flot plugin for automatically redrawing plots as the placeholder resizes. | ||||
| 
 | ||||
| Copyright (c) 2007-2014 IOLA and Ole Laursen. | ||||
| Licensed under the MIT license. | ||||
| 
 | ||||
| It works by listening for changes on the placeholder div (through the jQuery | ||||
| resize event plugin) - if the size changes, it will redraw the plot. | ||||
| 
 | ||||
| There are no options. If you need to disable the plugin for some plots, you | ||||
| can just fix the size of their placeholders. | ||||
| 
 | ||||
| */ | ||||
| 
 | ||||
| /* Inline dependency: | ||||
|  * jQuery resize event - v1.1 - 3/14/2010 | ||||
|  * http://benalman.com/projects/jquery-resize-plugin/
 | ||||
|  * | ||||
|  * Copyright (c) 2010 "Cowboy" Ben Alman | ||||
|  * Dual licensed under the MIT and GPL licenses. | ||||
|  * http://benalman.com/about/license/
 | ||||
|  */ | ||||
| (function($,e,t){"$:nomunge";var i=[],n=$.resize=$.extend($.resize,{}),a,r=false,s="setTimeout",u="resize",m=u+"-special-event",o="pendingDelay",l="activeDelay",f="throttleWindow";n[o]=200;n[l]=20;n[f]=true;$.event.special[u]={setup:function(){if(!n[f]&&this[s]){return false}var e=$(this);i.push(this);e.data(m,{w:e.width(),h:e.height()});if(i.length===1){a=t;h()}},teardown:function(){if(!n[f]&&this[s]){return false}var e=$(this);for(var t=i.length-1;t>=0;t--){if(i[t]==this){i.splice(t,1);break}}e.removeData(m);if(!i.length){if(r){cancelAnimationFrame(a)}else{clearTimeout(a)}a=null}},add:function(e){if(!n[f]&&this[s]){return false}var i;function a(e,n,a){var r=$(this),s=r.data(m)||{};s.w=n!==t?n:r.width();s.h=a!==t?a:r.height();i.apply(this,arguments)}if($.isFunction(e)){i=e;return a}else{i=e.handler;e.handler=a}}};function h(t){if(r===true){r=t||1}for(var s=i.length-1;s>=0;s--){var l=$(i[s]);if(l[0]==e||l.is(":visible")){var f=l.width(),c=l.height(),d=l.data(m);if(d&&(f!==d.w||c!==d.h)){l.trigger(u,[d.w=f,d.h=c]);r=t||true}}else{d=l.data(m);d.w=0;d.h=0}}if(a!==null){if(r&&(t==null||t-r<1e3)){a=e.requestAnimationFrame(h)}else{a=setTimeout(h,n[o]);r=false}}}if(!e.requestAnimationFrame){e.requestAnimationFrame=function(){return e.webkitRequestAnimationFrame||e.mozRequestAnimationFrame||e.oRequestAnimationFrame||e.msRequestAnimationFrame||function(t,i){return e.setTimeout(function(){t((new Date).getTime())},n[l])}}()}if(!e.cancelAnimationFrame){e.cancelAnimationFrame=function(){return e.webkitCancelRequestAnimationFrame||e.mozCancelRequestAnimationFrame||e.oCancelRequestAnimationFrame||e.msCancelRequestAnimationFrame||clearTimeout}()}})(jQuery,this); | ||||
| 
 | ||||
| (function ($) { | ||||
|     var options = { }; // no options
 | ||||
| 
 | ||||
|     function init(plot) { | ||||
|         function onResize() { | ||||
|             var placeholder = plot.getPlaceholder(); | ||||
| 
 | ||||
|             // somebody might have hidden us and we can't plot
 | ||||
|             // when we don't have the dimensions
 | ||||
|             if (placeholder.width() == 0 || placeholder.height() == 0) | ||||
|                 return; | ||||
| 
 | ||||
|             plot.resize(); | ||||
|             plot.setupGrid(); | ||||
|             plot.draw(); | ||||
|         } | ||||
|          | ||||
|         function bindEvents(plot, eventHolder) { | ||||
|             plot.getPlaceholder().resize(onResize); | ||||
|         } | ||||
| 
 | ||||
|         function shutdown(plot, eventHolder) { | ||||
|             plot.getPlaceholder().unbind("resize", onResize); | ||||
|         } | ||||
|          | ||||
|         plot.hooks.bindEvents.push(bindEvents); | ||||
|         plot.hooks.shutdown.push(shutdown); | ||||
|     } | ||||
|      | ||||
|     $.plot.plugins.push({ | ||||
|         init: init, | ||||
|         options: options, | ||||
|         name: 'resize', | ||||
|         version: '1.0' | ||||
|     }); | ||||
| })(jQuery); | ||||
							
								
								
									
										7
									
								
								hledger-web/static/js/jquery.flot.resize.min.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								hledger-web/static/js/jquery.flot.resize.min.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,7 @@ | ||||
| /* Javascript plotting library for jQuery, version 0.8.3. | ||||
| 
 | ||||
| Copyright (c) 2007-2014 IOLA and Ole Laursen. | ||||
| Licensed under the MIT license. | ||||
| 
 | ||||
| */ | ||||
| (function($,e,t){"$:nomunge";var i=[],n=$.resize=$.extend($.resize,{}),a,r=false,s="setTimeout",u="resize",m=u+"-special-event",o="pendingDelay",l="activeDelay",f="throttleWindow";n[o]=200;n[l]=20;n[f]=true;$.event.special[u]={setup:function(){if(!n[f]&&this[s]){return false}var e=$(this);i.push(this);e.data(m,{w:e.width(),h:e.height()});if(i.length===1){a=t;h()}},teardown:function(){if(!n[f]&&this[s]){return false}var e=$(this);for(var t=i.length-1;t>=0;t--){if(i[t]==this){i.splice(t,1);break}}e.removeData(m);if(!i.length){if(r){cancelAnimationFrame(a)}else{clearTimeout(a)}a=null}},add:function(e){if(!n[f]&&this[s]){return false}var i;function a(e,n,a){var r=$(this),s=r.data(m)||{};s.w=n!==t?n:r.width();s.h=a!==t?a:r.height();i.apply(this,arguments)}if($.isFunction(e)){i=e;return a}else{i=e.handler;e.handler=a}}};function h(t){if(r===true){r=t||1}for(var s=i.length-1;s>=0;s--){var l=$(i[s]);if(l[0]==e||l.is(":visible")){var f=l.width(),c=l.height(),d=l.data(m);if(d&&(f!==d.w||c!==d.h)){l.trigger(u,[d.w=f,d.h=c]);r=t||true}}else{d=l.data(m);d.w=0;d.h=0}}if(a!==null){if(r&&(t==null||t-r<1e3)){a=e.requestAnimationFrame(h)}else{a=setTimeout(h,n[o]);r=false}}}if(!e.requestAnimationFrame){e.requestAnimationFrame=function(){return e.webkitRequestAnimationFrame||e.mozRequestAnimationFrame||e.oRequestAnimationFrame||e.msRequestAnimationFrame||function(t,i){return e.setTimeout(function(){t((new Date).getTime())},n[l])}}()}if(!e.cancelAnimationFrame){e.cancelAnimationFrame=function(){return e.webkitCancelRequestAnimationFrame||e.mozCancelRequestAnimationFrame||e.oCancelRequestAnimationFrame||e.msCancelRequestAnimationFrame||clearTimeout}()}})(jQuery,this);(function($){var options={};function init(plot){function onResize(){var placeholder=plot.getPlaceholder();if(placeholder.width()==0||placeholder.height()==0)return;plot.resize();plot.setupGrid();plot.draw()}function bindEvents(plot,eventHolder){plot.getPlaceholder().resize(onResize)}function shutdown(plot,eventHolder){plot.getPlaceholder().unbind("resize",onResize)}plot.hooks.bindEvents.push(bindEvents);plot.hooks.shutdown.push(shutdown)}$.plot.plugins.push({init:init,options:options,name:"resize",version:"1.0"})})(jQuery); | ||||
							
								
								
									
										360
									
								
								hledger-web/static/js/jquery.flot.selection.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										360
									
								
								hledger-web/static/js/jquery.flot.selection.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,360 @@ | ||||
| /* Flot plugin for selecting regions of a plot. | ||||
| 
 | ||||
| Copyright (c) 2007-2014 IOLA and Ole Laursen. | ||||
| Licensed under the MIT license. | ||||
| 
 | ||||
| The plugin supports these options: | ||||
| 
 | ||||
| selection: { | ||||
| 	mode: null or "x" or "y" or "xy", | ||||
| 	color: color, | ||||
| 	shape: "round" or "miter" or "bevel", | ||||
| 	minSize: number of pixels | ||||
| } | ||||
| 
 | ||||
| Selection support is enabled by setting the mode to one of "x", "y" or "xy". | ||||
| In "x" mode, the user will only be able to specify the x range, similarly for | ||||
| "y" mode. For "xy", the selection becomes a rectangle where both ranges can be | ||||
| specified. "color" is color of the selection (if you need to change the color | ||||
| later on, you can get to it with plot.getOptions().selection.color). "shape" | ||||
| is the shape of the corners of the selection. | ||||
| 
 | ||||
| "minSize" is the minimum size a selection can be in pixels. This value can | ||||
| be customized to determine the smallest size a selection can be and still | ||||
| have the selection rectangle be displayed. When customizing this value, the | ||||
| fact that it refers to pixels, not axis units must be taken into account. | ||||
| Thus, for example, if there is a bar graph in time mode with BarWidth set to 1 | ||||
| minute, setting "minSize" to 1 will not make the minimum selection size 1 | ||||
| minute, but rather 1 pixel. Note also that setting "minSize" to 0 will prevent | ||||
| "plotunselected" events from being fired when the user clicks the mouse without | ||||
| dragging. | ||||
| 
 | ||||
| When selection support is enabled, a "plotselected" event will be emitted on | ||||
| the DOM element you passed into the plot function. The event handler gets a | ||||
| parameter with the ranges selected on the axes, like this: | ||||
| 
 | ||||
| 	placeholder.bind( "plotselected", function( event, ranges ) { | ||||
| 		alert("You selected " + ranges.xaxis.from + " to " + ranges.xaxis.to) | ||||
| 		// similar for yaxis - with multiple axes, the extra ones are in
 | ||||
| 		// x2axis, x3axis, ...
 | ||||
| 	}); | ||||
| 
 | ||||
| The "plotselected" event is only fired when the user has finished making the | ||||
| selection. A "plotselecting" event is fired during the process with the same | ||||
| parameters as the "plotselected" event, in case you want to know what's | ||||
| happening while it's happening, | ||||
| 
 | ||||
| A "plotunselected" event with no arguments is emitted when the user clicks the | ||||
| mouse to remove the selection. As stated above, setting "minSize" to 0 will | ||||
| destroy this behavior. | ||||
| 
 | ||||
| The plugin allso adds the following methods to the plot object: | ||||
| 
 | ||||
| - setSelection( ranges, preventEvent ) | ||||
| 
 | ||||
|   Set the selection rectangle. The passed in ranges is on the same form as | ||||
|   returned in the "plotselected" event. If the selection mode is "x", you | ||||
|   should put in either an xaxis range, if the mode is "y" you need to put in | ||||
|   an yaxis range and both xaxis and yaxis if the selection mode is "xy", like | ||||
|   this: | ||||
| 
 | ||||
| 	setSelection({ xaxis: { from: 0, to: 10 }, yaxis: { from: 40, to: 60 } }); | ||||
| 
 | ||||
|   setSelection will trigger the "plotselected" event when called. If you don't | ||||
|   want that to happen, e.g. if you're inside a "plotselected" handler, pass | ||||
|   true as the second parameter. If you are using multiple axes, you can | ||||
|   specify the ranges on any of those, e.g. as x2axis/x3axis/... instead of | ||||
|   xaxis, the plugin picks the first one it sees. | ||||
| 
 | ||||
| - clearSelection( preventEvent ) | ||||
| 
 | ||||
|   Clear the selection rectangle. Pass in true to avoid getting a | ||||
|   "plotunselected" event. | ||||
| 
 | ||||
| - getSelection() | ||||
| 
 | ||||
|   Returns the current selection in the same format as the "plotselected" | ||||
|   event. If there's currently no selection, the function returns null. | ||||
| 
 | ||||
| */ | ||||
| 
 | ||||
| (function ($) { | ||||
|     function init(plot) { | ||||
|         var selection = { | ||||
|                 first: { x: -1, y: -1}, second: { x: -1, y: -1}, | ||||
|                 show: false, | ||||
|                 active: false | ||||
|             }; | ||||
| 
 | ||||
|         // FIXME: The drag handling implemented here should be
 | ||||
|         // abstracted out, there's some similar code from a library in
 | ||||
|         // the navigation plugin, this should be massaged a bit to fit
 | ||||
|         // the Flot cases here better and reused. Doing this would
 | ||||
|         // make this plugin much slimmer.
 | ||||
|         var savedhandlers = {}; | ||||
| 
 | ||||
|         var mouseUpHandler = null; | ||||
|          | ||||
|         function onMouseMove(e) { | ||||
|             if (selection.active) { | ||||
|                 updateSelection(e); | ||||
|                  | ||||
|                 plot.getPlaceholder().trigger("plotselecting", [ getSelection() ]); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         function onMouseDown(e) { | ||||
|             if (e.which != 1)  // only accept left-click
 | ||||
|                 return; | ||||
|              | ||||
|             // cancel out any text selections
 | ||||
|             document.body.focus(); | ||||
| 
 | ||||
|             // prevent text selection and drag in old-school browsers
 | ||||
|             if (document.onselectstart !== undefined && savedhandlers.onselectstart == null) { | ||||
|                 savedhandlers.onselectstart = document.onselectstart; | ||||
|                 document.onselectstart = function () { return false; }; | ||||
|             } | ||||
|             if (document.ondrag !== undefined && savedhandlers.ondrag == null) { | ||||
|                 savedhandlers.ondrag = document.ondrag; | ||||
|                 document.ondrag = function () { return false; }; | ||||
|             } | ||||
| 
 | ||||
|             setSelectionPos(selection.first, e); | ||||
| 
 | ||||
|             selection.active = true; | ||||
| 
 | ||||
|             // this is a bit silly, but we have to use a closure to be
 | ||||
|             // able to whack the same handler again
 | ||||
|             mouseUpHandler = function (e) { onMouseUp(e); }; | ||||
|              | ||||
|             $(document).one("mouseup", mouseUpHandler); | ||||
|         } | ||||
| 
 | ||||
|         function onMouseUp(e) { | ||||
|             mouseUpHandler = null; | ||||
|              | ||||
|             // revert drag stuff for old-school browsers
 | ||||
|             if (document.onselectstart !== undefined) | ||||
|                 document.onselectstart = savedhandlers.onselectstart; | ||||
|             if (document.ondrag !== undefined) | ||||
|                 document.ondrag = savedhandlers.ondrag; | ||||
| 
 | ||||
|             // no more dragging
 | ||||
|             selection.active = false; | ||||
|             updateSelection(e); | ||||
| 
 | ||||
|             if (selectionIsSane()) | ||||
|                 triggerSelectedEvent(); | ||||
|             else { | ||||
|                 // this counts as a clear
 | ||||
|                 plot.getPlaceholder().trigger("plotunselected", [ ]); | ||||
|                 plot.getPlaceholder().trigger("plotselecting", [ null ]); | ||||
|             } | ||||
| 
 | ||||
|             return false; | ||||
|         } | ||||
| 
 | ||||
|         function getSelection() { | ||||
|             if (!selectionIsSane()) | ||||
|                 return null; | ||||
|              | ||||
|             if (!selection.show) return null; | ||||
| 
 | ||||
|             var r = {}, c1 = selection.first, c2 = selection.second; | ||||
|             $.each(plot.getAxes(), function (name, axis) { | ||||
|                 if (axis.used) { | ||||
|                     var p1 = axis.c2p(c1[axis.direction]), p2 = axis.c2p(c2[axis.direction]);  | ||||
|                     r[name] = { from: Math.min(p1, p2), to: Math.max(p1, p2) }; | ||||
|                 } | ||||
|             }); | ||||
|             return r; | ||||
|         } | ||||
| 
 | ||||
|         function triggerSelectedEvent() { | ||||
|             var r = getSelection(); | ||||
| 
 | ||||
|             plot.getPlaceholder().trigger("plotselected", [ r ]); | ||||
| 
 | ||||
|             // backwards-compat stuff, to be removed in future
 | ||||
|             if (r.xaxis && r.yaxis) | ||||
|                 plot.getPlaceholder().trigger("selected", [ { x1: r.xaxis.from, y1: r.yaxis.from, x2: r.xaxis.to, y2: r.yaxis.to } ]); | ||||
|         } | ||||
| 
 | ||||
|         function clamp(min, value, max) { | ||||
|             return value < min ? min: (value > max ? max: value); | ||||
|         } | ||||
| 
 | ||||
|         function setSelectionPos(pos, e) { | ||||
|             var o = plot.getOptions(); | ||||
|             var offset = plot.getPlaceholder().offset(); | ||||
|             var plotOffset = plot.getPlotOffset(); | ||||
|             pos.x = clamp(0, e.pageX - offset.left - plotOffset.left, plot.width()); | ||||
|             pos.y = clamp(0, e.pageY - offset.top - plotOffset.top, plot.height()); | ||||
| 
 | ||||
|             if (o.selection.mode == "y") | ||||
|                 pos.x = pos == selection.first ? 0 : plot.width(); | ||||
| 
 | ||||
|             if (o.selection.mode == "x") | ||||
|                 pos.y = pos == selection.first ? 0 : plot.height(); | ||||
|         } | ||||
| 
 | ||||
|         function updateSelection(pos) { | ||||
|             if (pos.pageX == null) | ||||
|                 return; | ||||
| 
 | ||||
|             setSelectionPos(selection.second, pos); | ||||
|             if (selectionIsSane()) { | ||||
|                 selection.show = true; | ||||
|                 plot.triggerRedrawOverlay(); | ||||
|             } | ||||
|             else | ||||
|                 clearSelection(true); | ||||
|         } | ||||
| 
 | ||||
|         function clearSelection(preventEvent) { | ||||
|             if (selection.show) { | ||||
|                 selection.show = false; | ||||
|                 plot.triggerRedrawOverlay(); | ||||
|                 if (!preventEvent) | ||||
|                     plot.getPlaceholder().trigger("plotunselected", [ ]); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         // function taken from markings support in Flot
 | ||||
|         function extractRange(ranges, coord) { | ||||
|             var axis, from, to, key, axes = plot.getAxes(); | ||||
| 
 | ||||
|             for (var k in axes) { | ||||
|                 axis = axes[k]; | ||||
|                 if (axis.direction == coord) { | ||||
|                     key = coord + axis.n + "axis"; | ||||
|                     if (!ranges[key] && axis.n == 1) | ||||
|                         key = coord + "axis"; // support x1axis as xaxis
 | ||||
|                     if (ranges[key]) { | ||||
|                         from = ranges[key].from; | ||||
|                         to = ranges[key].to; | ||||
|                         break; | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             // backwards-compat stuff - to be removed in future
 | ||||
|             if (!ranges[key]) { | ||||
|                 axis = coord == "x" ? plot.getXAxes()[0] : plot.getYAxes()[0]; | ||||
|                 from = ranges[coord + "1"]; | ||||
|                 to = ranges[coord + "2"]; | ||||
|             } | ||||
| 
 | ||||
|             // auto-reverse as an added bonus
 | ||||
|             if (from != null && to != null && from > to) { | ||||
|                 var tmp = from; | ||||
|                 from = to; | ||||
|                 to = tmp; | ||||
|             } | ||||
|              | ||||
|             return { from: from, to: to, axis: axis }; | ||||
|         } | ||||
|          | ||||
|         function setSelection(ranges, preventEvent) { | ||||
|             var axis, range, o = plot.getOptions(); | ||||
| 
 | ||||
|             if (o.selection.mode == "y") { | ||||
|                 selection.first.x = 0; | ||||
|                 selection.second.x = plot.width(); | ||||
|             } | ||||
|             else { | ||||
|                 range = extractRange(ranges, "x"); | ||||
| 
 | ||||
|                 selection.first.x = range.axis.p2c(range.from); | ||||
|                 selection.second.x = range.axis.p2c(range.to); | ||||
|             } | ||||
| 
 | ||||
|             if (o.selection.mode == "x") { | ||||
|                 selection.first.y = 0; | ||||
|                 selection.second.y = plot.height(); | ||||
|             } | ||||
|             else { | ||||
|                 range = extractRange(ranges, "y"); | ||||
| 
 | ||||
|                 selection.first.y = range.axis.p2c(range.from); | ||||
|                 selection.second.y = range.axis.p2c(range.to); | ||||
|             } | ||||
| 
 | ||||
|             selection.show = true; | ||||
|             plot.triggerRedrawOverlay(); | ||||
|             if (!preventEvent && selectionIsSane()) | ||||
|                 triggerSelectedEvent(); | ||||
|         } | ||||
| 
 | ||||
|         function selectionIsSane() { | ||||
|             var minSize = plot.getOptions().selection.minSize; | ||||
|             return Math.abs(selection.second.x - selection.first.x) >= minSize && | ||||
|                 Math.abs(selection.second.y - selection.first.y) >= minSize; | ||||
|         } | ||||
| 
 | ||||
|         plot.clearSelection = clearSelection; | ||||
|         plot.setSelection = setSelection; | ||||
|         plot.getSelection = getSelection; | ||||
| 
 | ||||
|         plot.hooks.bindEvents.push(function(plot, eventHolder) { | ||||
|             var o = plot.getOptions(); | ||||
|             if (o.selection.mode != null) { | ||||
|                 eventHolder.mousemove(onMouseMove); | ||||
|                 eventHolder.mousedown(onMouseDown); | ||||
|             } | ||||
|         }); | ||||
| 
 | ||||
| 
 | ||||
|         plot.hooks.drawOverlay.push(function (plot, ctx) { | ||||
|             // draw selection
 | ||||
|             if (selection.show && selectionIsSane()) { | ||||
|                 var plotOffset = plot.getPlotOffset(); | ||||
|                 var o = plot.getOptions(); | ||||
| 
 | ||||
|                 ctx.save(); | ||||
|                 ctx.translate(plotOffset.left, plotOffset.top); | ||||
| 
 | ||||
|                 var c = $.color.parse(o.selection.color); | ||||
| 
 | ||||
|                 ctx.strokeStyle = c.scale('a', 0.8).toString(); | ||||
|                 ctx.lineWidth = 1; | ||||
|                 ctx.lineJoin = o.selection.shape; | ||||
|                 ctx.fillStyle = c.scale('a', 0.4).toString(); | ||||
| 
 | ||||
|                 var x = Math.min(selection.first.x, selection.second.x) + 0.5, | ||||
|                     y = Math.min(selection.first.y, selection.second.y) + 0.5, | ||||
|                     w = Math.abs(selection.second.x - selection.first.x) - 1, | ||||
|                     h = Math.abs(selection.second.y - selection.first.y) - 1; | ||||
| 
 | ||||
|                 ctx.fillRect(x, y, w, h); | ||||
|                 ctx.strokeRect(x, y, w, h); | ||||
| 
 | ||||
|                 ctx.restore(); | ||||
|             } | ||||
|         }); | ||||
|          | ||||
|         plot.hooks.shutdown.push(function (plot, eventHolder) { | ||||
|             eventHolder.unbind("mousemove", onMouseMove); | ||||
|             eventHolder.unbind("mousedown", onMouseDown); | ||||
|              | ||||
|             if (mouseUpHandler) | ||||
|                 $(document).unbind("mouseup", mouseUpHandler); | ||||
|         }); | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     $.plot.plugins.push({ | ||||
|         init: init, | ||||
|         options: { | ||||
|             selection: { | ||||
|                 mode: null, // one of null, "x", "y" or "xy"
 | ||||
|                 color: "#e8cfac", | ||||
|                 shape: "round", // one of "round", "miter", or "bevel"
 | ||||
|                 minSize: 5 // minimum number of pixels
 | ||||
|             } | ||||
|         }, | ||||
|         name: 'selection', | ||||
|         version: '1.1' | ||||
|     }); | ||||
| })(jQuery); | ||||
							
								
								
									
										7
									
								
								hledger-web/static/js/jquery.flot.selection.min.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								hledger-web/static/js/jquery.flot.selection.min.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										188
									
								
								hledger-web/static/js/jquery.flot.stack.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										188
									
								
								hledger-web/static/js/jquery.flot.stack.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,188 @@ | ||||
| /* Flot plugin for stacking data sets rather than overlyaing them. | ||||
| 
 | ||||
| Copyright (c) 2007-2014 IOLA and Ole Laursen. | ||||
| Licensed under the MIT license. | ||||
| 
 | ||||
| The plugin assumes the data is sorted on x (or y if stacking horizontally). | ||||
| For line charts, it is assumed that if a line has an undefined gap (from a | ||||
| null point), then the line above it should have the same gap - insert zeros | ||||
| instead of "null" if you want another behaviour. This also holds for the start | ||||
| and end of the chart. Note that stacking a mix of positive and negative values | ||||
| in most instances doesn't make sense (so it looks weird). | ||||
| 
 | ||||
| Two or more series are stacked when their "stack" attribute is set to the same | ||||
| key (which can be any number or string or just "true"). To specify the default | ||||
| stack, you can set the stack option like this: | ||||
| 
 | ||||
| 	series: { | ||||
| 		stack: null/false, true, or a key (number/string) | ||||
| 	} | ||||
| 
 | ||||
| You can also specify it for a single series, like this: | ||||
| 
 | ||||
| 	$.plot( $("#placeholder"), [{ | ||||
| 		data: [ ... ], | ||||
| 		stack: true | ||||
| 	}]) | ||||
| 
 | ||||
| The stacking order is determined by the order of the data series in the array | ||||
| (later series end up on top of the previous). | ||||
| 
 | ||||
| Internally, the plugin modifies the datapoints in each series, adding an | ||||
| offset to the y value. For line series, extra data points are inserted through | ||||
| interpolation. If there's a second y value, it's also adjusted (e.g for bar | ||||
| charts or filled areas). | ||||
| 
 | ||||
| */ | ||||
| 
 | ||||
| (function ($) { | ||||
|     var options = { | ||||
|         series: { stack: null } // or number/string
 | ||||
|     }; | ||||
|      | ||||
|     function init(plot) { | ||||
|         function findMatchingSeries(s, allseries) { | ||||
|             var res = null; | ||||
|             for (var i = 0; i < allseries.length; ++i) { | ||||
|                 if (s == allseries[i]) | ||||
|                     break; | ||||
|                  | ||||
|                 if (allseries[i].stack == s.stack) | ||||
|                     res = allseries[i]; | ||||
|             } | ||||
|              | ||||
|             return res; | ||||
|         } | ||||
|          | ||||
|         function stackData(plot, s, datapoints) { | ||||
|             if (s.stack == null || s.stack === false) | ||||
|                 return; | ||||
| 
 | ||||
|             var other = findMatchingSeries(s, plot.getData()); | ||||
|             if (!other) | ||||
|                 return; | ||||
| 
 | ||||
|             var ps = datapoints.pointsize, | ||||
|                 points = datapoints.points, | ||||
|                 otherps = other.datapoints.pointsize, | ||||
|                 otherpoints = other.datapoints.points, | ||||
|                 newpoints = [], | ||||
|                 px, py, intery, qx, qy, bottom, | ||||
|                 withlines = s.lines.show, | ||||
|                 horizontal = s.bars.horizontal, | ||||
|                 withbottom = ps > 2 && (horizontal ? datapoints.format[2].x : datapoints.format[2].y), | ||||
|                 withsteps = withlines && s.lines.steps, | ||||
|                 fromgap = true, | ||||
|                 keyOffset = horizontal ? 1 : 0, | ||||
|                 accumulateOffset = horizontal ? 0 : 1, | ||||
|                 i = 0, j = 0, l, m; | ||||
| 
 | ||||
|             while (true) { | ||||
|                 if (i >= points.length) | ||||
|                     break; | ||||
| 
 | ||||
|                 l = newpoints.length; | ||||
| 
 | ||||
|                 if (points[i] == null) { | ||||
|                     // copy gaps
 | ||||
|                     for (m = 0; m < ps; ++m) | ||||
|                         newpoints.push(points[i + m]); | ||||
|                     i += ps; | ||||
|                 } | ||||
|                 else if (j >= otherpoints.length) { | ||||
|                     // for lines, we can't use the rest of the points
 | ||||
|                     if (!withlines) { | ||||
|                         for (m = 0; m < ps; ++m) | ||||
|                             newpoints.push(points[i + m]); | ||||
|                     } | ||||
|                     i += ps; | ||||
|                 } | ||||
|                 else if (otherpoints[j] == null) { | ||||
|                     // oops, got a gap
 | ||||
|                     for (m = 0; m < ps; ++m) | ||||
|                         newpoints.push(null); | ||||
|                     fromgap = true; | ||||
|                     j += otherps; | ||||
|                 } | ||||
|                 else { | ||||
|                     // cases where we actually got two points
 | ||||
|                     px = points[i + keyOffset]; | ||||
|                     py = points[i + accumulateOffset]; | ||||
|                     qx = otherpoints[j + keyOffset]; | ||||
|                     qy = otherpoints[j + accumulateOffset]; | ||||
|                     bottom = 0; | ||||
| 
 | ||||
|                     if (px == qx) { | ||||
|                         for (m = 0; m < ps; ++m) | ||||
|                             newpoints.push(points[i + m]); | ||||
| 
 | ||||
|                         newpoints[l + accumulateOffset] += qy; | ||||
|                         bottom = qy; | ||||
|                          | ||||
|                         i += ps; | ||||
|                         j += otherps; | ||||
|                     } | ||||
|                     else if (px > qx) { | ||||
|                         // we got past point below, might need to
 | ||||
|                         // insert interpolated extra point
 | ||||
|                         if (withlines && i > 0 && points[i - ps] != null) { | ||||
|                             intery = py + (points[i - ps + accumulateOffset] - py) * (qx - px) / (points[i - ps + keyOffset] - px); | ||||
|                             newpoints.push(qx); | ||||
|                             newpoints.push(intery + qy); | ||||
|                             for (m = 2; m < ps; ++m) | ||||
|                                 newpoints.push(points[i + m]); | ||||
|                             bottom = qy;  | ||||
|                         } | ||||
| 
 | ||||
|                         j += otherps; | ||||
|                     } | ||||
|                     else { // px < qx
 | ||||
|                         if (fromgap && withlines) { | ||||
|                             // if we come from a gap, we just skip this point
 | ||||
|                             i += ps; | ||||
|                             continue; | ||||
|                         } | ||||
|                              | ||||
|                         for (m = 0; m < ps; ++m) | ||||
|                             newpoints.push(points[i + m]); | ||||
|                          | ||||
|                         // we might be able to interpolate a point below,
 | ||||
|                         // this can give us a better y
 | ||||
|                         if (withlines && j > 0 && otherpoints[j - otherps] != null) | ||||
|                             bottom = qy + (otherpoints[j - otherps + accumulateOffset] - qy) * (px - qx) / (otherpoints[j - otherps + keyOffset] - qx); | ||||
| 
 | ||||
|                         newpoints[l + accumulateOffset] += bottom; | ||||
|                          | ||||
|                         i += ps; | ||||
|                     } | ||||
| 
 | ||||
|                     fromgap = false; | ||||
|                      | ||||
|                     if (l != newpoints.length && withbottom) | ||||
|                         newpoints[l + 2] += bottom; | ||||
|                 } | ||||
| 
 | ||||
|                 // maintain the line steps invariant
 | ||||
|                 if (withsteps && l != newpoints.length && l > 0 | ||||
|                     && newpoints[l] != null | ||||
|                     && newpoints[l] != newpoints[l - ps] | ||||
|                     && newpoints[l + 1] != newpoints[l - ps + 1]) { | ||||
|                     for (m = 0; m < ps; ++m) | ||||
|                         newpoints[l + ps + m] = newpoints[l + m]; | ||||
|                     newpoints[l + 1] = newpoints[l - ps + 1]; | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             datapoints.points = newpoints; | ||||
|         } | ||||
|          | ||||
|         plot.hooks.processDatapoints.push(stackData); | ||||
|     } | ||||
|      | ||||
|     $.plot.plugins.push({ | ||||
|         init: init, | ||||
|         options: options, | ||||
|         name: 'stack', | ||||
|         version: '1.2' | ||||
|     }); | ||||
| })(jQuery); | ||||
							
								
								
									
										7
									
								
								hledger-web/static/js/jquery.flot.stack.min.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								hledger-web/static/js/jquery.flot.stack.min.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,7 @@ | ||||
| /* Javascript plotting library for jQuery, version 0.8.3. | ||||
| 
 | ||||
| Copyright (c) 2007-2014 IOLA and Ole Laursen. | ||||
| Licensed under the MIT license. | ||||
| 
 | ||||
| */ | ||||
| (function($){var options={series:{stack:null}};function init(plot){function findMatchingSeries(s,allseries){var res=null;for(var i=0;i<allseries.length;++i){if(s==allseries[i])break;if(allseries[i].stack==s.stack)res=allseries[i]}return res}function stackData(plot,s,datapoints){if(s.stack==null||s.stack===false)return;var other=findMatchingSeries(s,plot.getData());if(!other)return;var ps=datapoints.pointsize,points=datapoints.points,otherps=other.datapoints.pointsize,otherpoints=other.datapoints.points,newpoints=[],px,py,intery,qx,qy,bottom,withlines=s.lines.show,horizontal=s.bars.horizontal,withbottom=ps>2&&(horizontal?datapoints.format[2].x:datapoints.format[2].y),withsteps=withlines&&s.lines.steps,fromgap=true,keyOffset=horizontal?1:0,accumulateOffset=horizontal?0:1,i=0,j=0,l,m;while(true){if(i>=points.length)break;l=newpoints.length;if(points[i]==null){for(m=0;m<ps;++m)newpoints.push(points[i+m]);i+=ps}else if(j>=otherpoints.length){if(!withlines){for(m=0;m<ps;++m)newpoints.push(points[i+m])}i+=ps}else if(otherpoints[j]==null){for(m=0;m<ps;++m)newpoints.push(null);fromgap=true;j+=otherps}else{px=points[i+keyOffset];py=points[i+accumulateOffset];qx=otherpoints[j+keyOffset];qy=otherpoints[j+accumulateOffset];bottom=0;if(px==qx){for(m=0;m<ps;++m)newpoints.push(points[i+m]);newpoints[l+accumulateOffset]+=qy;bottom=qy;i+=ps;j+=otherps}else if(px>qx){if(withlines&&i>0&&points[i-ps]!=null){intery=py+(points[i-ps+accumulateOffset]-py)*(qx-px)/(points[i-ps+keyOffset]-px);newpoints.push(qx);newpoints.push(intery+qy);for(m=2;m<ps;++m)newpoints.push(points[i+m]);bottom=qy}j+=otherps}else{if(fromgap&&withlines){i+=ps;continue}for(m=0;m<ps;++m)newpoints.push(points[i+m]);if(withlines&&j>0&&otherpoints[j-otherps]!=null)bottom=qy+(otherpoints[j-otherps+accumulateOffset]-qy)*(px-qx)/(otherpoints[j-otherps+keyOffset]-qx);newpoints[l+accumulateOffset]+=bottom;i+=ps}fromgap=false;if(l!=newpoints.length&&withbottom)newpoints[l+2]+=bottom}if(withsteps&&l!=newpoints.length&&l>0&&newpoints[l]!=null&&newpoints[l]!=newpoints[l-ps]&&newpoints[l+1]!=newpoints[l-ps+1]){for(m=0;m<ps;++m)newpoints[l+ps+m]=newpoints[l+m];newpoints[l+1]=newpoints[l-ps+1]}}datapoints.points=newpoints}plot.hooks.processDatapoints.push(stackData)}$.plot.plugins.push({init:init,options:options,name:"stack",version:"1.2"})})(jQuery); | ||||
							
								
								
									
										71
									
								
								hledger-web/static/js/jquery.flot.symbol.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										71
									
								
								hledger-web/static/js/jquery.flot.symbol.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,71 @@ | ||||
| /* Flot plugin that adds some extra symbols for plotting points. | ||||
| 
 | ||||
| Copyright (c) 2007-2014 IOLA and Ole Laursen. | ||||
| Licensed under the MIT license. | ||||
| 
 | ||||
| The symbols are accessed as strings through the standard symbol options: | ||||
| 
 | ||||
| 	series: { | ||||
| 		points: { | ||||
| 			symbol: "square" // or "diamond", "triangle", "cross"
 | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| */ | ||||
| 
 | ||||
| (function ($) { | ||||
|     function processRawData(plot, series, datapoints) { | ||||
|         // we normalize the area of each symbol so it is approximately the
 | ||||
|         // same as a circle of the given radius
 | ||||
| 
 | ||||
|         var handlers = { | ||||
|             square: function (ctx, x, y, radius, shadow) { | ||||
|                 // pi * r^2 = (2s)^2  =>  s = r * sqrt(pi)/2
 | ||||
|                 var size = radius * Math.sqrt(Math.PI) / 2; | ||||
|                 ctx.rect(x - size, y - size, size + size, size + size); | ||||
|             }, | ||||
|             diamond: function (ctx, x, y, radius, shadow) { | ||||
|                 // pi * r^2 = 2s^2  =>  s = r * sqrt(pi/2)
 | ||||
|                 var size = radius * Math.sqrt(Math.PI / 2); | ||||
|                 ctx.moveTo(x - size, y); | ||||
|                 ctx.lineTo(x, y - size); | ||||
|                 ctx.lineTo(x + size, y); | ||||
|                 ctx.lineTo(x, y + size); | ||||
|                 ctx.lineTo(x - size, y); | ||||
|             }, | ||||
|             triangle: function (ctx, x, y, radius, shadow) { | ||||
|                 // pi * r^2 = 1/2 * s^2 * sin (pi / 3)  =>  s = r * sqrt(2 * pi / sin(pi / 3))
 | ||||
|                 var size = radius * Math.sqrt(2 * Math.PI / Math.sin(Math.PI / 3)); | ||||
|                 var height = size * Math.sin(Math.PI / 3); | ||||
|                 ctx.moveTo(x - size/2, y + height/2); | ||||
|                 ctx.lineTo(x + size/2, y + height/2); | ||||
|                 if (!shadow) { | ||||
|                     ctx.lineTo(x, y - height/2); | ||||
|                     ctx.lineTo(x - size/2, y + height/2); | ||||
|                 } | ||||
|             }, | ||||
|             cross: function (ctx, x, y, radius, shadow) { | ||||
|                 // pi * r^2 = (2s)^2  =>  s = r * sqrt(pi)/2
 | ||||
|                 var size = radius * Math.sqrt(Math.PI) / 2; | ||||
|                 ctx.moveTo(x - size, y - size); | ||||
|                 ctx.lineTo(x + size, y + size); | ||||
|                 ctx.moveTo(x - size, y + size); | ||||
|                 ctx.lineTo(x + size, y - size); | ||||
|             } | ||||
|         }; | ||||
| 
 | ||||
|         var s = series.points.symbol; | ||||
|         if (handlers[s]) | ||||
|             series.points.symbol = handlers[s]; | ||||
|     } | ||||
|      | ||||
|     function init(plot) { | ||||
|         plot.hooks.processDatapoints.push(processRawData); | ||||
|     } | ||||
|      | ||||
|     $.plot.plugins.push({ | ||||
|         init: init, | ||||
|         name: 'symbols', | ||||
|         version: '1.0' | ||||
|     }); | ||||
| })(jQuery); | ||||
							
								
								
									
										7
									
								
								hledger-web/static/js/jquery.flot.symbol.min.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								hledger-web/static/js/jquery.flot.symbol.min.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,7 @@ | ||||
| /* Javascript plotting library for jQuery, version 0.8.3. | ||||
| 
 | ||||
| Copyright (c) 2007-2014 IOLA and Ole Laursen. | ||||
| Licensed under the MIT license. | ||||
| 
 | ||||
| */ | ||||
| (function($){function processRawData(plot,series,datapoints){var handlers={square:function(ctx,x,y,radius,shadow){var size=radius*Math.sqrt(Math.PI)/2;ctx.rect(x-size,y-size,size+size,size+size)},diamond:function(ctx,x,y,radius,shadow){var size=radius*Math.sqrt(Math.PI/2);ctx.moveTo(x-size,y);ctx.lineTo(x,y-size);ctx.lineTo(x+size,y);ctx.lineTo(x,y+size);ctx.lineTo(x-size,y)},triangle:function(ctx,x,y,radius,shadow){var size=radius*Math.sqrt(2*Math.PI/Math.sin(Math.PI/3));var height=size*Math.sin(Math.PI/3);ctx.moveTo(x-size/2,y+height/2);ctx.lineTo(x+size/2,y+height/2);if(!shadow){ctx.lineTo(x,y-height/2);ctx.lineTo(x-size/2,y+height/2)}},cross:function(ctx,x,y,radius,shadow){var size=radius*Math.sqrt(Math.PI)/2;ctx.moveTo(x-size,y-size);ctx.lineTo(x+size,y+size);ctx.moveTo(x-size,y+size);ctx.lineTo(x+size,y-size)}};var s=series.points.symbol;if(handlers[s])series.points.symbol=handlers[s]}function init(plot){plot.hooks.processDatapoints.push(processRawData)}$.plot.plugins.push({init:init,name:"symbols",version:"1.0"})})(jQuery); | ||||
							
								
								
									
										142
									
								
								hledger-web/static/js/jquery.flot.threshold.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										142
									
								
								hledger-web/static/js/jquery.flot.threshold.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,142 @@ | ||||
| /* Flot plugin for thresholding data. | ||||
| 
 | ||||
| Copyright (c) 2007-2014 IOLA and Ole Laursen. | ||||
| Licensed under the MIT license. | ||||
| 
 | ||||
| The plugin supports these options: | ||||
| 
 | ||||
| 	series: { | ||||
| 		threshold: { | ||||
| 			below: number | ||||
| 			color: colorspec | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| It can also be applied to a single series, like this: | ||||
| 
 | ||||
| 	$.plot( $("#placeholder"), [{ | ||||
| 		data: [ ... ], | ||||
| 		threshold: { ... } | ||||
| 	}]) | ||||
| 
 | ||||
| An array can be passed for multiple thresholding, like this: | ||||
| 
 | ||||
| 	threshold: [{ | ||||
| 		below: number1 | ||||
| 		color: color1 | ||||
| 	},{ | ||||
| 		below: number2 | ||||
| 		color: color2 | ||||
| 	}] | ||||
| 
 | ||||
| These multiple threshold objects can be passed in any order since they are | ||||
| sorted by the processing function. | ||||
| 
 | ||||
| The data points below "below" are drawn with the specified color. This makes | ||||
| it easy to mark points below 0, e.g. for budget data. | ||||
| 
 | ||||
| Internally, the plugin works by splitting the data into two series, above and | ||||
| below the threshold. The extra series below the threshold will have its label | ||||
| cleared and the special "originSeries" attribute set to the original series. | ||||
| You may need to check for this in hover events. | ||||
| 
 | ||||
| */ | ||||
| 
 | ||||
| (function ($) { | ||||
|     var options = { | ||||
|         series: { threshold: null } // or { below: number, color: color spec}
 | ||||
|     }; | ||||
|      | ||||
|     function init(plot) { | ||||
|         function thresholdData(plot, s, datapoints, below, color) { | ||||
|             var ps = datapoints.pointsize, i, x, y, p, prevp, | ||||
|                 thresholded = $.extend({}, s); // note: shallow copy
 | ||||
| 
 | ||||
|             thresholded.datapoints = { points: [], pointsize: ps, format: datapoints.format }; | ||||
|             thresholded.label = null; | ||||
|             thresholded.color = color; | ||||
|             thresholded.threshold = null; | ||||
|             thresholded.originSeries = s; | ||||
|             thresholded.data = []; | ||||
|   | ||||
|             var origpoints = datapoints.points, | ||||
|                 addCrossingPoints = s.lines.show; | ||||
| 
 | ||||
|             var threspoints = []; | ||||
|             var newpoints = []; | ||||
|             var m; | ||||
| 
 | ||||
|             for (i = 0; i < origpoints.length; i += ps) { | ||||
|                 x = origpoints[i]; | ||||
|                 y = origpoints[i + 1]; | ||||
| 
 | ||||
|                 prevp = p; | ||||
|                 if (y < below) | ||||
|                     p = threspoints; | ||||
|                 else | ||||
|                     p = newpoints; | ||||
| 
 | ||||
|                 if (addCrossingPoints && prevp != p && x != null | ||||
|                     && i > 0 && origpoints[i - ps] != null) { | ||||
|                     var interx = x + (below - y) * (x - origpoints[i - ps]) / (y - origpoints[i - ps + 1]); | ||||
|                     prevp.push(interx); | ||||
|                     prevp.push(below); | ||||
|                     for (m = 2; m < ps; ++m) | ||||
|                         prevp.push(origpoints[i + m]); | ||||
|                      | ||||
|                     p.push(null); // start new segment
 | ||||
|                     p.push(null); | ||||
|                     for (m = 2; m < ps; ++m) | ||||
|                         p.push(origpoints[i + m]); | ||||
|                     p.push(interx); | ||||
|                     p.push(below); | ||||
|                     for (m = 2; m < ps; ++m) | ||||
|                         p.push(origpoints[i + m]); | ||||
|                 } | ||||
| 
 | ||||
|                 p.push(x); | ||||
|                 p.push(y); | ||||
|                 for (m = 2; m < ps; ++m) | ||||
|                     p.push(origpoints[i + m]); | ||||
|             } | ||||
| 
 | ||||
|             datapoints.points = newpoints; | ||||
|             thresholded.datapoints.points = threspoints; | ||||
|              | ||||
|             if (thresholded.datapoints.points.length > 0) { | ||||
|                 var origIndex = $.inArray(s, plot.getData()); | ||||
|                 // Insert newly-generated series right after original one (to prevent it from becoming top-most)
 | ||||
|                 plot.getData().splice(origIndex + 1, 0, thresholded); | ||||
|             } | ||||
|                  | ||||
|             // FIXME: there are probably some edge cases left in bars
 | ||||
|         } | ||||
|          | ||||
|         function processThresholds(plot, s, datapoints) { | ||||
|             if (!s.threshold) | ||||
|                 return; | ||||
|              | ||||
|             if (s.threshold instanceof Array) { | ||||
|                 s.threshold.sort(function(a, b) { | ||||
|                     return a.below - b.below; | ||||
|                 }); | ||||
|                  | ||||
|                 $(s.threshold).each(function(i, th) { | ||||
|                     thresholdData(plot, s, datapoints, th.below, th.color); | ||||
|                 }); | ||||
|             } | ||||
|             else { | ||||
|                 thresholdData(plot, s, datapoints, s.threshold.below, s.threshold.color); | ||||
|             } | ||||
|         } | ||||
|          | ||||
|         plot.hooks.processDatapoints.push(processThresholds); | ||||
|     } | ||||
|      | ||||
|     $.plot.plugins.push({ | ||||
|         init: init, | ||||
|         options: options, | ||||
|         name: 'threshold', | ||||
|         version: '1.2' | ||||
|     }); | ||||
| })(jQuery); | ||||
							
								
								
									
										7
									
								
								hledger-web/static/js/jquery.flot.threshold.min.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								hledger-web/static/js/jquery.flot.threshold.min.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,7 @@ | ||||
| /* Javascript plotting library for jQuery, version 0.8.3. | ||||
| 
 | ||||
| Copyright (c) 2007-2014 IOLA and Ole Laursen. | ||||
| Licensed under the MIT license. | ||||
| 
 | ||||
| */ | ||||
| (function($){var options={series:{threshold:null}};function init(plot){function thresholdData(plot,s,datapoints,below,color){var ps=datapoints.pointsize,i,x,y,p,prevp,thresholded=$.extend({},s);thresholded.datapoints={points:[],pointsize:ps,format:datapoints.format};thresholded.label=null;thresholded.color=color;thresholded.threshold=null;thresholded.originSeries=s;thresholded.data=[];var origpoints=datapoints.points,addCrossingPoints=s.lines.show;var threspoints=[];var newpoints=[];var m;for(i=0;i<origpoints.length;i+=ps){x=origpoints[i];y=origpoints[i+1];prevp=p;if(y<below)p=threspoints;else p=newpoints;if(addCrossingPoints&&prevp!=p&&x!=null&&i>0&&origpoints[i-ps]!=null){var interx=x+(below-y)*(x-origpoints[i-ps])/(y-origpoints[i-ps+1]);prevp.push(interx);prevp.push(below);for(m=2;m<ps;++m)prevp.push(origpoints[i+m]);p.push(null);p.push(null);for(m=2;m<ps;++m)p.push(origpoints[i+m]);p.push(interx);p.push(below);for(m=2;m<ps;++m)p.push(origpoints[i+m])}p.push(x);p.push(y);for(m=2;m<ps;++m)p.push(origpoints[i+m])}datapoints.points=newpoints;thresholded.datapoints.points=threspoints;if(thresholded.datapoints.points.length>0){var origIndex=$.inArray(s,plot.getData());plot.getData().splice(origIndex+1,0,thresholded)}}function processThresholds(plot,s,datapoints){if(!s.threshold)return;if(s.threshold instanceof Array){s.threshold.sort(function(a,b){return a.below-b.below});$(s.threshold).each(function(i,th){thresholdData(plot,s,datapoints,th.below,th.color)})}else{thresholdData(plot,s,datapoints,s.threshold.below,s.threshold.color)}}plot.hooks.processDatapoints.push(processThresholds)}$.plot.plugins.push({init:init,options:options,name:"threshold",version:"1.2"})})(jQuery); | ||||
							
								
								
									
										409
									
								
								hledger-web/static/js/jquery.flot.tooltip.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										409
									
								
								hledger-web/static/js/jquery.flot.tooltip.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,409 @@ | ||||
| /* | ||||
|  * jquery.flot.tooltip | ||||
|  *  | ||||
|  * description: easy-to-use tooltips for Flot charts | ||||
|  * version: 0.7.1 | ||||
|  * author: Krzysztof Urbas @krzysu [myviews.pl] | ||||
|  * website: https://github.com/krzysu/flot.tooltip
 | ||||
|  *  | ||||
|  * build on 2014-06-22 | ||||
|  * released under MIT License, 2012 | ||||
| */  | ||||
| // IE8 polyfill for Array.indexOf
 | ||||
| if (!Array.prototype.indexOf) { | ||||
|     Array.prototype.indexOf = function (searchElement, fromIndex) { | ||||
|         if ( this === undefined || this === null ) { | ||||
|             throw new TypeError( '"this" is null or not defined' ); | ||||
|         } | ||||
|         var length = this.length >>> 0; // Hack to convert object.length to a UInt32
 | ||||
|         fromIndex = +fromIndex || 0; | ||||
|         if (Math.abs(fromIndex) === Infinity) { | ||||
|             fromIndex = 0; | ||||
|         } | ||||
|         if (fromIndex < 0) { | ||||
|             fromIndex += length; | ||||
|             if (fromIndex < 0) { | ||||
|                 fromIndex = 0; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         for (;fromIndex < length; fromIndex++) { | ||||
|             if (this[fromIndex] === searchElement) { | ||||
|                 return fromIndex; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         return -1; | ||||
|     }; | ||||
| } | ||||
| 
 | ||||
| (function ($) { | ||||
| 
 | ||||
|     // plugin options, default values
 | ||||
|     var defaultOptions = { | ||||
|         tooltip: false, | ||||
|         tooltipOpts: { | ||||
|             content: "%s | X: %x | Y: %y", | ||||
|             // allowed templates are:
 | ||||
|             // %s -> series label,
 | ||||
|             // %lx -> x axis label (requires flot-axislabels plugin https://github.com/markrcote/flot-axislabels),
 | ||||
|             // %ly -> y axis label (requires flot-axislabels plugin https://github.com/markrcote/flot-axislabels),
 | ||||
|             // %x -> X value,
 | ||||
|             // %y -> Y value,
 | ||||
|             // %x.2 -> precision of X value,
 | ||||
|             // %p -> percent
 | ||||
|             xDateFormat: null, | ||||
|             yDateFormat: null, | ||||
|             monthNames: null, | ||||
|             dayNames: null, | ||||
|             shifts: { | ||||
|                 x: 10, | ||||
|                 y: 20 | ||||
|             }, | ||||
|             defaultTheme: true, | ||||
| 
 | ||||
|             // callbacks
 | ||||
|             onHover: function(flotItem, $tooltipEl) {} | ||||
|         } | ||||
|     }; | ||||
| 
 | ||||
|     // object
 | ||||
|     var FlotTooltip = function(plot) { | ||||
| 
 | ||||
|         // variables
 | ||||
|         this.tipPosition = {x: 0, y: 0}; | ||||
| 
 | ||||
|         this.init(plot); | ||||
|     }; | ||||
| 
 | ||||
|     // main plugin function
 | ||||
|     FlotTooltip.prototype.init = function(plot) { | ||||
| 
 | ||||
|         var that = this; | ||||
| 
 | ||||
|         // detect other flot plugins
 | ||||
|         var plotPluginsLength = $.plot.plugins.length; | ||||
|         this.plotPlugins = []; | ||||
| 
 | ||||
|         if (plotPluginsLength) { | ||||
|             for (var p = 0; p < plotPluginsLength; p++) { | ||||
|                 this.plotPlugins.push($.plot.plugins[p].name); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         plot.hooks.bindEvents.push(function (plot, eventHolder) { | ||||
| 
 | ||||
|             // get plot options
 | ||||
|             that.plotOptions = plot.getOptions(); | ||||
| 
 | ||||
|             // if not enabled return
 | ||||
|             if (that.plotOptions.tooltip === false || typeof that.plotOptions.tooltip === 'undefined') return; | ||||
| 
 | ||||
|             // shortcut to access tooltip options
 | ||||
|             that.tooltipOptions = that.plotOptions.tooltipOpts; | ||||
| 
 | ||||
|             // create tooltip DOM element
 | ||||
|             var $tip = that.getDomElement(); | ||||
| 
 | ||||
|             // bind event
 | ||||
|             $( plot.getPlaceholder() ).bind("plothover", plothover); | ||||
| 
 | ||||
|             $(eventHolder).bind('mousemove', mouseMove); | ||||
|         }); | ||||
| 
 | ||||
|         plot.hooks.shutdown.push(function (plot, eventHolder){ | ||||
|             $(plot.getPlaceholder()).unbind("plothover", plothover); | ||||
|             $(eventHolder).unbind("mousemove", mouseMove); | ||||
|         }); | ||||
| 
 | ||||
|         function mouseMove(e){ | ||||
|             var pos = {}; | ||||
|             pos.x = e.pageX; | ||||
|             pos.y = e.pageY; | ||||
|             that.updateTooltipPosition(pos); | ||||
|         } | ||||
| 
 | ||||
|         function plothover(event, pos, item) { | ||||
|             var $tip = that.getDomElement(); | ||||
|             if (item) { | ||||
|                 var tipText; | ||||
| 
 | ||||
|                 // convert tooltip content template to real tipText
 | ||||
|                 tipText = that.stringFormat(that.tooltipOptions.content, item); | ||||
| 
 | ||||
|                 $tip.html( tipText ); | ||||
|                 that.updateTooltipPosition({ x: pos.pageX, y: pos.pageY }); | ||||
|                 $tip.css({ | ||||
|                         left: that.tipPosition.x + that.tooltipOptions.shifts.x, | ||||
|                         top: that.tipPosition.y + that.tooltipOptions.shifts.y | ||||
|                     }) | ||||
|                     .show(); | ||||
| 
 | ||||
|                 // run callback
 | ||||
|                 if(typeof that.tooltipOptions.onHover === 'function') { | ||||
|                     that.tooltipOptions.onHover(item, $tip); | ||||
|                 } | ||||
|             } | ||||
|             else { | ||||
|                 $tip.hide().html(''); | ||||
|             } | ||||
|         } | ||||
|     }; | ||||
| 
 | ||||
|     /** | ||||
|      * get or create tooltip DOM element | ||||
|      * @return jQuery object | ||||
|      */ | ||||
|     FlotTooltip.prototype.getDomElement = function() { | ||||
|         var $tip = $('#flotTip'); | ||||
| 
 | ||||
|         if( $tip.length === 0 ){ | ||||
|             $tip = $('<div />').attr('id', 'flotTip'); | ||||
|             $tip.appendTo('body').hide().css({position: 'absolute'}); | ||||
| 
 | ||||
|             if(this.tooltipOptions.defaultTheme) { | ||||
|                 $tip.css({ | ||||
|                     'background': '#fff', | ||||
|                     'z-index': '1040', | ||||
|                     'padding': '0.4em 0.6em', | ||||
|                     'border-radius': '0.5em', | ||||
|                     'font-size': '0.8em', | ||||
|                     'border': '1px solid #111', | ||||
|                     'display': 'none', | ||||
|                     'white-space': 'nowrap' | ||||
|                 }); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         return $tip; | ||||
|     }; | ||||
| 
 | ||||
|     // as the name says
 | ||||
|     FlotTooltip.prototype.updateTooltipPosition = function(pos) { | ||||
|         var $tip = $('#flotTip'); | ||||
| 
 | ||||
|         var totalTipWidth = $tip.outerWidth() + this.tooltipOptions.shifts.x; | ||||
|         var totalTipHeight = $tip.outerHeight() + this.tooltipOptions.shifts.y; | ||||
|         if ((pos.x - $(window).scrollLeft()) > ($(window).innerWidth() - totalTipWidth)) { | ||||
|             pos.x -= totalTipWidth; | ||||
|         } | ||||
|         if ((pos.y - $(window).scrollTop()) > ($(window).innerHeight() - totalTipHeight)) { | ||||
|             pos.y -= totalTipHeight; | ||||
|         } | ||||
|         this.tipPosition.x = pos.x; | ||||
|         this.tipPosition.y = pos.y; | ||||
|     }; | ||||
| 
 | ||||
|     /** | ||||
|      * core function, create tooltip content | ||||
|      * @param  {string} content - template with tooltip content | ||||
|      * @param  {object} item - Flot item | ||||
|      * @return {string} real tooltip content for current item | ||||
|      */ | ||||
|     FlotTooltip.prototype.stringFormat = function(content, item) { | ||||
| 
 | ||||
|         var percentPattern = /%p\.{0,1}(\d{0,})/; | ||||
|         var seriesPattern = /%s/; | ||||
|         var xLabelPattern = /%lx/; // requires flot-axislabels plugin https://github.com/markrcote/flot-axislabels, will be ignored if plugin isn't loaded
 | ||||
|         var yLabelPattern = /%ly/; // requires flot-axislabels plugin https://github.com/markrcote/flot-axislabels, will be ignored if plugin isn't loaded
 | ||||
|         var xPattern = /%x\.{0,1}(\d{0,})/; | ||||
|         var yPattern = /%y\.{0,1}(\d{0,})/; | ||||
|         var xPatternWithoutPrecision = "%x"; | ||||
|         var yPatternWithoutPrecision = "%y"; | ||||
|         var customTextPattern = "%ct"; | ||||
| 
 | ||||
|         var x, y, customText; | ||||
| 
 | ||||
|         // for threshold plugin we need to read data from different place
 | ||||
|         if (typeof item.series.threshold !== "undefined") { | ||||
|             x = item.datapoint[0]; | ||||
|             y = item.datapoint[1]; | ||||
|             customText = item.datapoint[2]; | ||||
|         } else if (typeof item.series.lines !== "undefined" && item.series.lines.steps) { | ||||
|             x = item.series.datapoints.points[item.dataIndex * 2]; | ||||
|             y = item.series.datapoints.points[item.dataIndex * 2 + 1]; | ||||
|             // TODO: where to find custom text in this variant?
 | ||||
|             customText = ""; | ||||
|         } else { | ||||
|             x = item.series.data[item.dataIndex][0]; | ||||
|             y = item.series.data[item.dataIndex][1]; | ||||
|             customText = item.series.data[item.dataIndex][2]; | ||||
|         } | ||||
| 
 | ||||
|         // I think this is only in case of threshold plugin
 | ||||
|         if (item.series.label === null && item.series.originSeries) { | ||||
|             item.series.label = item.series.originSeries.label; | ||||
|         } | ||||
| 
 | ||||
|         // if it is a function callback get the content string
 | ||||
|         if( typeof(content) === 'function' ) { | ||||
|             content = content(item.series.label, x, y, item); | ||||
|         } | ||||
| 
 | ||||
|         // percent match for pie charts
 | ||||
|         if( typeof (item.series.percent) !== 'undefined' ) { | ||||
|             content = this.adjustValPrecision(percentPattern, content, item.series.percent); | ||||
|         } | ||||
| 
 | ||||
|         // series match
 | ||||
|         if( typeof(item.series.label) !== 'undefined' ) { | ||||
|             content = content.replace(seriesPattern, item.series.label); | ||||
|         } | ||||
|         else { | ||||
|             //remove %s if label is undefined
 | ||||
|             content = content.replace(seriesPattern, ""); | ||||
|         } | ||||
| 
 | ||||
|         // x axis label match
 | ||||
|         if( this.hasAxisLabel('xaxis', item) ) { | ||||
|             content = content.replace(xLabelPattern, item.series.xaxis.options.axisLabel); | ||||
|         } | ||||
|         else { | ||||
|             //remove %lx if axis label is undefined or axislabels plugin not present
 | ||||
|             content = content.replace(xLabelPattern, ""); | ||||
|         } | ||||
| 
 | ||||
|         // y axis label match
 | ||||
|         if( this.hasAxisLabel('yaxis', item) ) { | ||||
|             content = content.replace(yLabelPattern, item.series.yaxis.options.axisLabel); | ||||
|         } | ||||
|         else { | ||||
|             //remove %ly if axis label is undefined or axislabels plugin not present
 | ||||
|             content = content.replace(yLabelPattern, ""); | ||||
|         } | ||||
| 
 | ||||
|         // time mode axes with custom dateFormat
 | ||||
|         if(this.isTimeMode('xaxis', item) && this.isXDateFormat(item)) { | ||||
|             content = content.replace(xPattern, this.timestampToDate(x, this.tooltipOptions.xDateFormat, item.series.xaxis.options)); | ||||
|         } | ||||
| 
 | ||||
|         if(this.isTimeMode('yaxis', item) && this.isYDateFormat(item)) { | ||||
|             content = content.replace(yPattern, this.timestampToDate(y, this.tooltipOptions.yDateFormat, item.series.yaxis.options)); | ||||
|         } | ||||
| 
 | ||||
|         // set precision if defined
 | ||||
|         if(typeof x === 'number') { | ||||
|             content = this.adjustValPrecision(xPattern, content, x); | ||||
|         } | ||||
|         if(typeof y === 'number') { | ||||
|             content = this.adjustValPrecision(yPattern, content, y); | ||||
|         } | ||||
| 
 | ||||
|         // change x from number to given label, if given
 | ||||
|         if(typeof item.series.xaxis.ticks !== 'undefined') { | ||||
| 
 | ||||
|             var ticks; | ||||
|             if(this.hasRotatedXAxisTicks(item)) { | ||||
|                 // xaxis.ticks will be an empty array if tickRotor is being used, but the values are available in rotatedTicks
 | ||||
|                 ticks = 'rotatedTicks'; | ||||
|             } | ||||
|             else { | ||||
|                 ticks = 'ticks'; | ||||
|             } | ||||
| 
 | ||||
|             // see https://github.com/krzysu/flot.tooltip/issues/65
 | ||||
|             var tickIndex = item.dataIndex + item.seriesIndex; | ||||
| 
 | ||||
|             if(item.series.xaxis[ticks].length > tickIndex && !this.isTimeMode('xaxis', item)) { | ||||
|                 var valueX = (this.isCategoriesMode('xaxis', item)) ? item.series.xaxis[ticks][tickIndex].label : item.series.xaxis[ticks][tickIndex].v; | ||||
|                 if (valueX === x) { | ||||
|                     content = content.replace(xPattern, item.series.xaxis[ticks][tickIndex].label); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         // change y from number to given label, if given
 | ||||
|         if(typeof item.series.yaxis.ticks !== 'undefined') { | ||||
|             for (var index in item.series.yaxis.ticks) { | ||||
|                 if (item.series.yaxis.ticks.hasOwnProperty(index)) { | ||||
|                     var valueY = (this.isCategoriesMode('yaxis', item)) ? item.series.yaxis.ticks[index].label : item.series.yaxis.ticks[index].v; | ||||
|                     if (valueY === y) { | ||||
|                         content = content.replace(yPattern, item.series.yaxis.ticks[index].label); | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         // if no value customization, use tickFormatter by default
 | ||||
|         if(typeof item.series.xaxis.tickFormatter !== 'undefined') { | ||||
|             //escape dollar
 | ||||
|             content = content.replace(xPatternWithoutPrecision, item.series.xaxis.tickFormatter(x, item.series.xaxis).replace(/\$/g, '$$')); | ||||
|         } | ||||
|         if(typeof item.series.yaxis.tickFormatter !== 'undefined') { | ||||
|             //escape dollar
 | ||||
|             content = content.replace(yPatternWithoutPrecision, item.series.yaxis.tickFormatter(y, item.series.yaxis).replace(/\$/g, '$$')); | ||||
|         } | ||||
| 
 | ||||
|         if(customText) { | ||||
|             content = content.replace(customTextPattern, customText); | ||||
|         } | ||||
| 
 | ||||
|         return content; | ||||
|     }; | ||||
| 
 | ||||
|     // helpers just for readability
 | ||||
|     FlotTooltip.prototype.isTimeMode = function(axisName, item) { | ||||
|         return (typeof item.series[axisName].options.mode !== 'undefined' && item.series[axisName].options.mode === 'time'); | ||||
|     }; | ||||
| 
 | ||||
|     FlotTooltip.prototype.isXDateFormat = function(item) { | ||||
|         return (typeof this.tooltipOptions.xDateFormat !== 'undefined' && this.tooltipOptions.xDateFormat !== null); | ||||
|     }; | ||||
| 
 | ||||
|     FlotTooltip.prototype.isYDateFormat = function(item) { | ||||
|         return (typeof this.tooltipOptions.yDateFormat !== 'undefined' && this.tooltipOptions.yDateFormat !== null); | ||||
|     }; | ||||
| 
 | ||||
|     FlotTooltip.prototype.isCategoriesMode = function(axisName, item) { | ||||
|         return (typeof item.series[axisName].options.mode !== 'undefined' && item.series[axisName].options.mode === 'categories'); | ||||
|     }; | ||||
| 
 | ||||
|     //
 | ||||
|     FlotTooltip.prototype.timestampToDate = function(tmst, dateFormat, options) { | ||||
|         var theDate = $.plot.dateGenerator(tmst, options); | ||||
|         return $.plot.formatDate(theDate, dateFormat, this.tooltipOptions.monthNames, this.tooltipOptions.dayNames); | ||||
|     }; | ||||
| 
 | ||||
|     //
 | ||||
|     FlotTooltip.prototype.adjustValPrecision = function(pattern, content, value) { | ||||
| 
 | ||||
|         var precision; | ||||
|         var matchResult = content.match(pattern); | ||||
|         if( matchResult !== null ) { | ||||
|             if(RegExp.$1 !== '') { | ||||
|                 precision = RegExp.$1; | ||||
|                 value = value.toFixed(precision); | ||||
| 
 | ||||
|                 // only replace content if precision exists, in other case use thickformater
 | ||||
|                 content = content.replace(pattern, value); | ||||
|             } | ||||
|         } | ||||
|         return content; | ||||
|     }; | ||||
| 
 | ||||
|     // other plugins detection below
 | ||||
| 
 | ||||
|     // check if flot-axislabels plugin (https://github.com/markrcote/flot-axislabels) is used and that an axis label is given
 | ||||
|     FlotTooltip.prototype.hasAxisLabel = function(axisName, item) { | ||||
|         return (this.plotPlugins.indexOf('axisLabels') !== -1 && typeof item.series[axisName].options.axisLabel !== 'undefined' && item.series[axisName].options.axisLabel.length > 0); | ||||
|     }; | ||||
| 
 | ||||
|     // check whether flot-tickRotor, a plugin which allows rotation of X-axis ticks, is being used
 | ||||
|     FlotTooltip.prototype.hasRotatedXAxisTicks = function(item) { | ||||
|         return ($.grep($.plot.plugins, function(p){ return p.name === "tickRotor"; }).length === 1 && typeof item.series.xaxis.rotatedTicks !== 'undefined'); | ||||
|     }; | ||||
| 
 | ||||
|     //
 | ||||
|     var init = function(plot) { | ||||
|       new FlotTooltip(plot); | ||||
|     }; | ||||
| 
 | ||||
|     // define Flot plugin
 | ||||
|     $.plot.plugins.push({ | ||||
|         init: init, | ||||
|         options: defaultOptions, | ||||
|         name: 'tooltip', | ||||
|         version: '0.6.7' | ||||
|     }); | ||||
| 
 | ||||
| })(jQuery); | ||||
							
								
								
									
										12
									
								
								hledger-web/static/js/jquery.flot.tooltip.min.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								hledger-web/static/js/jquery.flot.tooltip.min.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
		Loading…
	
		Reference in New Issue
	
	Block a user