Path: blob/master/web-gui/buildyourownbotnet/assets/js/jquery.sparkline.min.js
1292 views
/**1*2* jquery.sparkline.js3*4* v2.1.25* (c) Splunk, Inc6* Contact: Gareth Watts ([email protected])7* http://omnipotent.net/jquery.sparkline/8*9* Generates inline sparkline charts from data supplied either to the method10* or inline in HTML11*12* Compatible with Internet Explorer 6.0+ and modern browsers equipped with the canvas tag13* (Firefox 2.0+, Safari, Opera, etc)14*15* License: New BSD License16*17* Copyright (c) 2012, Splunk Inc.18* All rights reserved.19*20* Redistribution and use in source and binary forms, with or without modification,21* are permitted provided that the following conditions are met:22*23* * Redistributions of source code must retain the above copyright notice,24* this list of conditions and the following disclaimer.25* * Redistributions in binary form must reproduce the above copyright notice,26* this list of conditions and the following disclaimer in the documentation27* and/or other materials provided with the distribution.28* * Neither the name of Splunk Inc nor the names of its contributors may29* be used to endorse or promote products derived from this software without30* specific prior written permission.31*32* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY33* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES34* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT35* SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,36* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT37* OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)38* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,39* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS40* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.41*42*43* Usage:44* $(selector).sparkline(values, options)45*46* If values is undefined or set to 'html' then the data values are read from the specified tag:47* <p>Sparkline: <span class="sparkline">1,4,6,6,8,5,3,5</span></p>48* $('.sparkline').sparkline();49* There must be no spaces in the enclosed data set50*51* Otherwise values must be an array of numbers or null values52* <p>Sparkline: <span id="sparkline1">This text replaced if the browser is compatible</span></p>53* $('#sparkline1').sparkline([1,4,6,6,8,5,3,5])54* $('#sparkline2').sparkline([1,4,6,null,null,5,3,5])55*56* Values can also be specified in an HTML comment, or as a values attribute:57* <p>Sparkline: <span class="sparkline"><!--1,4,6,6,8,5,3,5 --></span></p>58* <p>Sparkline: <span class="sparkline" values="1,4,6,6,8,5,3,5"></span></p>59* $('.sparkline').sparkline();60*61* For line charts, x values can also be specified:62* <p>Sparkline: <span class="sparkline">1:1,2.7:4,3.4:6,5:6,6:8,8.7:5,9:3,10:5</span></p>63* $('#sparkline1').sparkline([ [1,1], [2.7,4], [3.4,6], [5,6], [6,8], [8.7,5], [9,3], [10,5] ])64*65* By default, options should be passed in as teh second argument to the sparkline function:66* $('.sparkline').sparkline([1,2,3,4], {type: 'bar'})67*68* Options can also be set by passing them on the tag itself. This feature is disabled by default though69* as there's a slight performance overhead:70* $('.sparkline').sparkline([1,2,3,4], {enableTagOptions: true})71* <p>Sparkline: <span class="sparkline" sparkType="bar" sparkBarColor="red">loading</span></p>72* Prefix all options supplied as tag attribute with "spark" (configurable by setting tagOptionPrefix)73*74* Supported options:75* lineColor - Color of the line used for the chart76* fillColor - Color used to fill in the chart - Set to '' or false for a transparent chart77* width - Width of the chart - Defaults to 3 times the number of values in pixels78* height - Height of the chart - Defaults to the height of the containing element79* chartRangeMin - Specify the minimum value to use for the Y range of the chart - Defaults to the minimum value supplied80* chartRangeMax - Specify the maximum value to use for the Y range of the chart - Defaults to the maximum value supplied81* chartRangeClip - Clip out of range values to the max/min specified by chartRangeMin and chartRangeMax82* chartRangeMinX - Specify the minimum value to use for the X range of the chart - Defaults to the minimum value supplied83* chartRangeMaxX - Specify the maximum value to use for the X range of the chart - Defaults to the maximum value supplied84* composite - If true then don't erase any existing chart attached to the tag, but draw85* another chart over the top - Note that width and height are ignored if an86* existing chart is detected.87* tagValuesAttribute - Name of tag attribute to check for data values - Defaults to 'values'88* enableTagOptions - Whether to check tags for sparkline options89* tagOptionPrefix - Prefix used for options supplied as tag attributes - Defaults to 'spark'90* disableHiddenCheck - If set to true, then the plugin will assume that charts will never be drawn into a91* hidden dom element, avoding a browser reflow92* disableInteraction - If set to true then all mouseover/click interaction behaviour will be disabled,93* making the plugin perform much like it did in 1.x94* disableTooltips - If set to true then tooltips will be disabled - Defaults to false (tooltips enabled)95* disableHighlight - If set to true then highlighting of selected chart elements on mouseover will be disabled96* defaults to false (highlights enabled)97* highlightLighten - Factor to lighten/darken highlighted chart values by - Defaults to 1.4 for a 40% increase98* tooltipContainer - Specify which DOM element the tooltip should be rendered into - defaults to document.body99* tooltipClassname - Optional CSS classname to apply to tooltips - If not specified then a default style will be applied100* tooltipOffsetX - How many pixels away from the mouse pointer to render the tooltip on the X axis101* tooltipOffsetY - How many pixels away from the mouse pointer to render the tooltip on the r axis102* tooltipFormatter - Optional callback that allows you to override the HTML displayed in the tooltip103* callback is given arguments of (sparkline, options, fields)104* tooltipChartTitle - If specified then the tooltip uses the string specified by this setting as a title105* tooltipFormat - A format string or SPFormat object (or an array thereof for multiple entries)106* to control the format of the tooltip107* tooltipPrefix - A string to prepend to each field displayed in a tooltip108* tooltipSuffix - A string to append to each field displayed in a tooltip109* tooltipSkipNull - If true then null values will not have a tooltip displayed (defaults to true)110* tooltipValueLookups - An object or range map to map field values to tooltip strings111* (eg. to map -1 to "Lost", 0 to "Draw", and 1 to "Win")112* numberFormatter - Optional callback for formatting numbers in tooltips113* numberDigitGroupSep - Character to use for group separator in numbers "1,234" - Defaults to ","114* numberDecimalMark - Character to use for the decimal point when formatting numbers - Defaults to "."115* numberDigitGroupCount - Number of digits between group separator - Defaults to 3116*117* There are 7 types of sparkline, selected by supplying a "type" option of 'line' (default),118* 'bar', 'tristate', 'bullet', 'discrete', 'pie' or 'box'119* line - Line chart. Options:120* spotColor - Set to '' to not end each line in a circular spot121* minSpotColor - If set, color of spot at minimum value122* maxSpotColor - If set, color of spot at maximum value123* spotRadius - Radius in pixels124* lineWidth - Width of line in pixels125* normalRangeMin126* normalRangeMax - If set draws a filled horizontal bar between these two values marking the "normal"127* or expected range of values128* normalRangeColor - Color to use for the above bar129* drawNormalOnTop - Draw the normal range above the chart fill color if true130* defaultPixelsPerValue - Defaults to 3 pixels of width for each value in the chart131* highlightSpotColor - The color to use for drawing a highlight spot on mouseover - Set to null to disable132* highlightLineColor - The color to use for drawing a highlight line on mouseover - Set to null to disable133* valueSpots - Specify which points to draw spots on, and in which color. Accepts a range map134*135* bar - Bar chart. Options:136* barColor - Color of bars for postive values137* negBarColor - Color of bars for negative values138* zeroColor - Color of bars with zero values139* nullColor - Color of bars with null values - Defaults to omitting the bar entirely140* barWidth - Width of bars in pixels141* colorMap - Optional mappnig of values to colors to override the *BarColor values above142* can be an Array of values to control the color of individual bars or a range map143* to specify colors for individual ranges of values144* barSpacing - Gap between bars in pixels145* zeroAxis - Centers the y-axis around zero if true146*147* tristate - Charts values of win (>0), lose (<0) or draw (=0)148* posBarColor - Color of win values149* negBarColor - Color of lose values150* zeroBarColor - Color of draw values151* barWidth - Width of bars in pixels152* barSpacing - Gap between bars in pixels153* colorMap - Optional mappnig of values to colors to override the *BarColor values above154* can be an Array of values to control the color of individual bars or a range map155* to specify colors for individual ranges of values156*157* discrete - Options:158* lineHeight - Height of each line in pixels - Defaults to 30% of the graph height159* thesholdValue - Values less than this value will be drawn using thresholdColor instead of lineColor160* thresholdColor161*162* bullet - Values for bullet graphs msut be in the order: target, performance, range1, range2, range3, ...163* options:164* targetColor - The color of the vertical target marker165* targetWidth - The width of the target marker in pixels166* performanceColor - The color of the performance measure horizontal bar167* rangeColors - Colors to use for each qualitative range background color168*169* pie - Pie chart. Options:170* sliceColors - An array of colors to use for pie slices171* offset - Angle in degrees to offset the first slice - Try -90 or +90172* borderWidth - Width of border to draw around the pie chart, in pixels - Defaults to 0 (no border)173* borderColor - Color to use for the pie chart border - Defaults to #000174*175* box - Box plot. Options:176* raw - Set to true to supply pre-computed plot points as values177* values should be: low_outlier, low_whisker, q1, median, q3, high_whisker, high_outlier178* When set to false you can supply any number of values and the box plot will179* be computed for you. Default is false.180* showOutliers - Set to true (default) to display outliers as circles181* outlierIQR - Interquartile range used to determine outliers. Default 1.5182* boxLineColor - Outline color of the box183* boxFillColor - Fill color for the box184* whiskerColor - Line color used for whiskers185* outlierLineColor - Outline color of outlier circles186* outlierFillColor - Fill color of the outlier circles187* spotRadius - Radius of outlier circles188* medianColor - Line color of the median line189* target - Draw a target cross hair at the supplied value (default undefined)190*191*192*193* Examples:194* $('#sparkline1').sparkline(myvalues, { lineColor: '#f00', fillColor: false });195* $('.barsparks').sparkline('html', { type:'bar', height:'40px', barWidth:5 });196* $('#tristate').sparkline([1,1,-1,1,0,0,-1], { type:'tristate' }):197* $('#discrete').sparkline([1,3,4,5,5,3,4,5], { type:'discrete' });198* $('#bullet').sparkline([10,12,12,9,7], { type:'bullet' });199* $('#pie').sparkline([1,1,2], { type:'pie' });200*/201202/*jslint regexp: true, browser: true, jquery: true, white: true, nomen: false, plusplus: false, maxerr: 500, indent: 4 */203204(function(document, Math, undefined) { // performance/minified-size optimization205(function(factory) {206if(typeof define === 'function' && define.amd) {207define(['jquery'], factory);208} else if (jQuery && !jQuery.fn.sparkline) {209factory(jQuery);210}211}212(function($) {213'use strict';214215var UNSET_OPTION = {},216getDefaults, createClass, SPFormat, clipval, quartile, normalizeValue, normalizeValues,217remove, isNumber, all, sum, addCSS, ensureArray, formatNumber, RangeMap,218MouseHandler, Tooltip, barHighlightMixin,219line, bar, tristate, discrete, bullet, pie, box, defaultStyles, initStyles,220VShape, VCanvas_base, VCanvas_canvas, VCanvas_vml, pending, shapeCount = 0;221222/**223* Default configuration settings224*/225getDefaults = function () {226return {227// Settings common to most/all chart types228common: {229type: 'line',230lineColor: '#00f',231fillColor: '#cdf',232defaultPixelsPerValue: 3,233width: 'auto',234height: 'auto',235composite: false,236tagValuesAttribute: 'values',237tagOptionsPrefix: 'spark',238enableTagOptions: false,239enableHighlight: true,240highlightLighten: 1.4,241tooltipSkipNull: true,242tooltipPrefix: '',243tooltipSuffix: '',244disableHiddenCheck: false,245numberFormatter: false,246numberDigitGroupCount: 3,247numberDigitGroupSep: ',',248numberDecimalMark: '.',249disableTooltips: false,250disableInteraction: false251},252// Defaults for line charts253line: {254spotColor: '#f80',255highlightSpotColor: '#5f5',256highlightLineColor: '#f22',257spotRadius: 1.5,258minSpotColor: '#f80',259maxSpotColor: '#f80',260lineWidth: 1,261normalRangeMin: undefined,262normalRangeMax: undefined,263normalRangeColor: '#ccc',264drawNormalOnTop: false,265chartRangeMin: undefined,266chartRangeMax: undefined,267chartRangeMinX: undefined,268chartRangeMaxX: undefined,269tooltipFormat: new SPFormat('<span style="color: {{color}}">●</span> {{prefix}}{{y}}{{suffix}}')270},271// Defaults for bar charts272bar: {273barColor: '#3366cc',274negBarColor: '#f44',275stackedBarColor: ['#3366cc', '#dc3912', '#ff9900', '#109618', '#66aa00',276'#dd4477', '#0099c6', '#990099'],277zeroColor: undefined,278nullColor: undefined,279zeroAxis: true,280barWidth: 4,281barSpacing: 1,282chartRangeMax: undefined,283chartRangeMin: undefined,284chartRangeClip: false,285colorMap: undefined,286tooltipFormat: new SPFormat('<span style="color: {{color}}">●</span> {{prefix}}{{value}}{{suffix}}')287},288// Defaults for tristate charts289tristate: {290barWidth: 4,291barSpacing: 1,292posBarColor: '#6f6',293negBarColor: '#f44',294zeroBarColor: '#999',295colorMap: {},296tooltipFormat: new SPFormat('<span style="color: {{color}}">●</span> {{value:map}}'),297tooltipValueLookups: { map: { '-1': 'Loss', '0': 'Draw', '1': 'Win' } }298},299// Defaults for discrete charts300discrete: {301lineHeight: 'auto',302thresholdColor: undefined,303thresholdValue: 0,304chartRangeMax: undefined,305chartRangeMin: undefined,306chartRangeClip: false,307tooltipFormat: new SPFormat('{{prefix}}{{value}}{{suffix}}')308},309// Defaults for bullet charts310bullet: {311targetColor: '#f33',312targetWidth: 3, // width of the target bar in pixels313performanceColor: '#33f',314rangeColors: ['#d3dafe', '#a8b6ff', '#7f94ff'],315base: undefined, // set this to a number to change the base start number316tooltipFormat: new SPFormat('{{fieldkey:fields}} - {{value}}'),317tooltipValueLookups: { fields: {r: 'Range', p: 'Performance', t: 'Target'} }318},319// Defaults for pie charts320pie: {321offset: 0,322sliceColors: ['#3366cc', '#dc3912', '#ff9900', '#109618', '#66aa00',323'#dd4477', '#0099c6', '#990099'],324borderWidth: 0,325borderColor: '#000',326tooltipFormat: new SPFormat('<span style="color: {{color}}">●</span> {{value}} ({{percent.1}}%)')327},328// Defaults for box plots329box: {330raw: false,331boxLineColor: '#000',332boxFillColor: '#cdf',333whiskerColor: '#000',334outlierLineColor: '#333',335outlierFillColor: '#fff',336medianColor: '#f00',337showOutliers: true,338outlierIQR: 1.5,339spotRadius: 1.5,340target: undefined,341targetColor: '#4a2',342chartRangeMax: undefined,343chartRangeMin: undefined,344tooltipFormat: new SPFormat('{{field:fields}}: {{value}}'),345tooltipFormatFieldlistKey: 'field',346tooltipValueLookups: { fields: { lq: 'Lower Quartile', med: 'Median',347uq: 'Upper Quartile', lo: 'Left Outlier', ro: 'Right Outlier',348lw: 'Left Whisker', rw: 'Right Whisker'} }349}350};351};352353// You can have tooltips use a css class other than jqstooltip by specifying tooltipClassname354defaultStyles = '.jqstooltip { ' +355'position: absolute;' +356'display: block;' +357'left: 0px;' +358'top: 0px;' +359'visibility: hidden;' +360'background: rgb(43, 48, 58) transparent;' +361'background-color: rgba(43, 48, 58,0.8);' +362'filter:progid:DXImageTransform.Microsoft.gradient(startColorstr=#99000000, endColorstr=#99000000);' +363'-ms-filter: "progid:DXImageTransform.Microsoft.gradient(startColorstr=#99000000, endColorstr=#99000000)";' +364'color: white;' +365'font: 10px arial, san serif;' +366'text-align: left;' +367'white-space: nowrap;' +368'z-index: 10000;' +369'padding: 5px 5px 5px 5px;' +370'min-height: 22px;' +371'min-width: 30px;' +372'border-radius: 3px;' +373'}' +374'.jqsfield { ' +375'color: white;' +376'font: 10px arial, san serif;' +377'text-align: left;' +378'}';379380/**381* Utilities382*/383384createClass = function (/* [baseclass, [mixin, ...]], definition */) {385var Class, args;386Class = function () {387this.init.apply(this, arguments);388};389if (arguments.length > 1) {390if (arguments[0]) {391Class.prototype = $.extend(new arguments[0](), arguments[arguments.length - 1]);392Class._super = arguments[0].prototype;393} else {394Class.prototype = arguments[arguments.length - 1];395}396if (arguments.length > 2) {397args = Array.prototype.slice.call(arguments, 1, -1);398args.unshift(Class.prototype);399$.extend.apply($, args);400}401} else {402Class.prototype = arguments[0];403}404Class.prototype.cls = Class;405return Class;406};407408/**409* Wraps a format string for tooltips410* {{x}}411* {{x.2}412* {{x:months}}413*/414$.SPFormatClass = SPFormat = createClass({415fre: /\{\{([\w.]+?)(:(.+?))?\}\}/g,416precre: /(\w+)\.(\d+)/,417418init: function (format, fclass) {419this.format = format;420this.fclass = fclass;421},422423render: function (fieldset, lookups, options) {424var self = this,425fields = fieldset,426match, token, lookupkey, fieldvalue, prec;427return this.format.replace(this.fre, function () {428var lookup;429token = arguments[1];430lookupkey = arguments[3];431match = self.precre.exec(token);432if (match) {433prec = match[2];434token = match[1];435} else {436prec = false;437}438fieldvalue = fields[token];439if (fieldvalue === undefined) {440return '';441}442if (lookupkey && lookups && lookups[lookupkey]) {443lookup = lookups[lookupkey];444if (lookup.get) { // RangeMap445return lookups[lookupkey].get(fieldvalue) || fieldvalue;446} else {447return lookups[lookupkey][fieldvalue] || fieldvalue;448}449}450if (isNumber(fieldvalue)) {451if (options.get('numberFormatter')) {452fieldvalue = options.get('numberFormatter')(fieldvalue);453} else {454fieldvalue = formatNumber(fieldvalue, prec,455options.get('numberDigitGroupCount'),456options.get('numberDigitGroupSep'),457options.get('numberDecimalMark'));458}459}460return fieldvalue;461});462}463});464465// convience method to avoid needing the new operator466$.spformat = function(format, fclass) {467return new SPFormat(format, fclass);468};469470clipval = function (val, min, max) {471if (val < min) {472return min;473}474if (val > max) {475return max;476}477return val;478};479480quartile = function (values, q) {481var vl;482if (q === 2) {483vl = Math.floor(values.length / 2);484return values.length % 2 ? values[vl] : (values[vl-1] + values[vl]) / 2;485} else {486if (values.length % 2 ) { // odd487vl = (values.length * q + q) / 4;488return vl % 1 ? (values[Math.floor(vl)] + values[Math.floor(vl) - 1]) / 2 : values[vl-1];489} else { //even490vl = (values.length * q + 2) / 4;491return vl % 1 ? (values[Math.floor(vl)] + values[Math.floor(vl) - 1]) / 2 : values[vl-1];492493}494}495};496497normalizeValue = function (val) {498var nf;499switch (val) {500case 'undefined':501val = undefined;502break;503case 'null':504val = null;505break;506case 'true':507val = true;508break;509case 'false':510val = false;511break;512default:513nf = parseFloat(val);514if (val == nf) {515val = nf;516}517}518return val;519};520521normalizeValues = function (vals) {522var i, result = [];523for (i = vals.length; i--;) {524result[i] = normalizeValue(vals[i]);525}526return result;527};528529remove = function (vals, filter) {530var i, vl, result = [];531for (i = 0, vl = vals.length; i < vl; i++) {532if (vals[i] !== filter) {533result.push(vals[i]);534}535}536return result;537};538539isNumber = function (num) {540return !isNaN(parseFloat(num)) && isFinite(num);541};542543formatNumber = function (num, prec, groupsize, groupsep, decsep) {544var p, i;545num = (prec === false ? parseFloat(num).toString() : num.toFixed(prec)).split('');546p = (p = $.inArray('.', num)) < 0 ? num.length : p;547if (p < num.length) {548num[p] = decsep;549}550for (i = p - groupsize; i > 0; i -= groupsize) {551num.splice(i, 0, groupsep);552}553return num.join('');554};555556// determine if all values of an array match a value557// returns true if the array is empty558all = function (val, arr, ignoreNull) {559var i;560for (i = arr.length; i--; ) {561if (ignoreNull && arr[i] === null) continue;562if (arr[i] !== val) {563return false;564}565}566return true;567};568569// sums the numeric values in an array, ignoring other values570sum = function (vals) {571var total = 0, i;572for (i = vals.length; i--;) {573total += typeof vals[i] === 'number' ? vals[i] : 0;574}575return total;576};577578ensureArray = function (val) {579return $.isArray(val) ? val : [val];580};581582// http://paulirish.com/2008/bookmarklet-inject-new-css-rules/583addCSS = function(css) {584var tag;585//if ('\v' == 'v') /* ie only */ {586if (document.createStyleSheet) {587document.createStyleSheet().cssText = css;588} else {589tag = document.createElement('style');590tag.type = 'text/css';591document.getElementsByTagName('head')[0].appendChild(tag);592tag[(typeof document.body.style.WebkitAppearance == 'string') /* webkit only */ ? 'innerText' : 'innerHTML'] = css;593}594};595596// Provide a cross-browser interface to a few simple drawing primitives597$.fn.simpledraw = function (width, height, useExisting, interact) {598var target, mhandler;599if (useExisting && (target = this.data('_jqs_vcanvas'))) {600return target;601}602603if ($.fn.sparkline.canvas === false) {604// We've already determined that neither Canvas nor VML are available605return false;606607} else if ($.fn.sparkline.canvas === undefined) {608// No function defined yet -- need to see if we support Canvas or VML609var el = document.createElement('canvas');610if (!!(el.getContext && el.getContext('2d'))) {611// Canvas is available612$.fn.sparkline.canvas = function(width, height, target, interact) {613return new VCanvas_canvas(width, height, target, interact);614};615} else if (document.namespaces && !document.namespaces.v) {616// VML is available617document.namespaces.add('v', 'urn:schemas-microsoft-com:vml', '#default#VML');618$.fn.sparkline.canvas = function(width, height, target, interact) {619return new VCanvas_vml(width, height, target);620};621} else {622// Neither Canvas nor VML are available623$.fn.sparkline.canvas = false;624return false;625}626}627628if (width === undefined) {629width = $(this).innerWidth();630}631if (height === undefined) {632height = $(this).innerHeight();633}634635target = $.fn.sparkline.canvas(width, height, this, interact);636637mhandler = $(this).data('_jqs_mhandler');638if (mhandler) {639mhandler.registerCanvas(target);640}641return target;642};643644$.fn.cleardraw = function () {645var target = this.data('_jqs_vcanvas');646if (target) {647target.reset();648}649};650651$.RangeMapClass = RangeMap = createClass({652init: function (map) {653var key, range, rangelist = [];654for (key in map) {655if (map.hasOwnProperty(key) && typeof key === 'string' && key.indexOf(':') > -1) {656range = key.split(':');657range[0] = range[0].length === 0 ? -Infinity : parseFloat(range[0]);658range[1] = range[1].length === 0 ? Infinity : parseFloat(range[1]);659range[2] = map[key];660rangelist.push(range);661}662}663this.map = map;664this.rangelist = rangelist || false;665},666667get: function (value) {668var rangelist = this.rangelist,669i, range, result;670if ((result = this.map[value]) !== undefined) {671return result;672}673if (rangelist) {674for (i = rangelist.length; i--;) {675range = rangelist[i];676if (range[0] <= value && range[1] >= value) {677return range[2];678}679}680}681return undefined;682}683});684685// Convenience function686$.range_map = function(map) {687return new RangeMap(map);688};689690MouseHandler = createClass({691init: function (el, options) {692var $el = $(el);693this.$el = $el;694this.options = options;695this.currentPageX = 0;696this.currentPageY = 0;697this.el = el;698this.splist = [];699this.tooltip = null;700this.over = false;701this.displayTooltips = !options.get('disableTooltips');702this.highlightEnabled = !options.get('disableHighlight');703},704705registerSparkline: function (sp) {706this.splist.push(sp);707if (this.over) {708this.updateDisplay();709}710},711712registerCanvas: function (canvas) {713var $canvas = $(canvas.canvas);714this.canvas = canvas;715this.$canvas = $canvas;716$canvas.mouseenter($.proxy(this.mouseenter, this));717$canvas.mouseleave($.proxy(this.mouseleave, this));718$canvas.click($.proxy(this.mouseclick, this));719},720721reset: function (removeTooltip) {722this.splist = [];723if (this.tooltip && removeTooltip) {724this.tooltip.remove();725this.tooltip = undefined;726}727},728729mouseclick: function (e) {730var clickEvent = $.Event('sparklineClick');731clickEvent.originalEvent = e;732clickEvent.sparklines = this.splist;733this.$el.trigger(clickEvent);734},735736mouseenter: function (e) {737$(document.body).unbind('mousemove.jqs');738$(document.body).bind('mousemove.jqs', $.proxy(this.mousemove, this));739this.over = true;740this.currentPageX = e.pageX;741this.currentPageY = e.pageY;742this.currentEl = e.target;743if (!this.tooltip && this.displayTooltips) {744this.tooltip = new Tooltip(this.options);745this.tooltip.updatePosition(e.pageX, e.pageY);746}747this.updateDisplay();748},749750mouseleave: function () {751$(document.body).unbind('mousemove.jqs');752var splist = this.splist,753spcount = splist.length,754needsRefresh = false,755sp, i;756this.over = false;757this.currentEl = null;758759if (this.tooltip) {760this.tooltip.remove();761this.tooltip = null;762}763764for (i = 0; i < spcount; i++) {765sp = splist[i];766if (sp.clearRegionHighlight()) {767needsRefresh = true;768}769}770771if (needsRefresh) {772this.canvas.render();773}774},775776mousemove: function (e) {777this.currentPageX = e.pageX;778this.currentPageY = e.pageY;779this.currentEl = e.target;780if (this.tooltip) {781this.tooltip.updatePosition(e.pageX, e.pageY);782}783this.updateDisplay();784},785786updateDisplay: function () {787var splist = this.splist,788spcount = splist.length,789needsRefresh = false,790offset = this.$canvas.offset(),791localX = this.currentPageX - offset.left,792localY = this.currentPageY - offset.top,793tooltiphtml, sp, i, result, changeEvent;794if (!this.over) {795return;796}797for (i = 0; i < spcount; i++) {798sp = splist[i];799result = sp.setRegionHighlight(this.currentEl, localX, localY);800if (result) {801needsRefresh = true;802}803}804if (needsRefresh) {805changeEvent = $.Event('sparklineRegionChange');806changeEvent.sparklines = this.splist;807this.$el.trigger(changeEvent);808if (this.tooltip) {809tooltiphtml = '';810for (i = 0; i < spcount; i++) {811sp = splist[i];812tooltiphtml += sp.getCurrentRegionTooltip();813}814this.tooltip.setContent(tooltiphtml);815}816if (!this.disableHighlight) {817this.canvas.render();818}819}820if (result === null) {821this.mouseleave();822}823}824});825826827Tooltip = createClass({828sizeStyle: 'position: static !important;' +829'display: block !important;' +830'visibility: hidden !important;' +831'float: left !important;',832833init: function (options) {834var tooltipClassname = options.get('tooltipClassname', 'jqstooltip'),835sizetipStyle = this.sizeStyle,836offset;837this.container = options.get('tooltipContainer') || document.body;838this.tooltipOffsetX = options.get('tooltipOffsetX', 10);839this.tooltipOffsetY = options.get('tooltipOffsetY', 12);840// remove any previous lingering tooltip841$('#jqssizetip').remove();842$('#jqstooltip').remove();843this.sizetip = $('<div/>', {844id: 'jqssizetip',845style: sizetipStyle,846'class': tooltipClassname847});848this.tooltip = $('<div/>', {849id: 'jqstooltip',850'class': tooltipClassname851}).appendTo(this.container);852// account for the container's location853offset = this.tooltip.offset();854this.offsetLeft = offset.left;855this.offsetTop = offset.top;856this.hidden = true;857$(window).unbind('resize.jqs scroll.jqs');858$(window).bind('resize.jqs scroll.jqs', $.proxy(this.updateWindowDims, this));859this.updateWindowDims();860},861862updateWindowDims: function () {863this.scrollTop = $(window).scrollTop();864this.scrollLeft = $(window).scrollLeft();865this.scrollRight = this.scrollLeft + $(window).width();866this.updatePosition();867},868869getSize: function (content) {870this.sizetip.html(content).appendTo(this.container);871this.width = this.sizetip.width() + 2;872this.height = this.sizetip.height();873this.sizetip.remove();874},875876setContent: function (content) {877if (!content) {878this.tooltip.css('visibility', 'hidden');879this.hidden = true;880return;881}882this.getSize(content);883this.tooltip.html(content)884.css({885'width': this.width,886'height': this.height,887'visibility': 'visible'888});889if (this.hidden) {890this.hidden = false;891this.updatePosition();892}893},894895updatePosition: function (x, y) {896if (x === undefined) {897if (this.mousex === undefined) {898return;899}900x = this.mousex - this.offsetLeft;901y = this.mousey - this.offsetTop;902903} else {904this.mousex = x = x - this.offsetLeft;905this.mousey = y = y - this.offsetTop;906}907if (!this.height || !this.width || this.hidden) {908return;909}910911y -= this.height + this.tooltipOffsetY;912x += this.tooltipOffsetX;913914if (y < this.scrollTop) {915y = this.scrollTop;916}917if (x < this.scrollLeft) {918x = this.scrollLeft;919} else if (x + this.width > this.scrollRight) {920x = this.scrollRight - this.width;921}922923this.tooltip.css({924'left': x,925'top': y926});927},928929remove: function () {930this.tooltip.remove();931this.sizetip.remove();932this.sizetip = this.tooltip = undefined;933$(window).unbind('resize.jqs scroll.jqs');934}935});936937initStyles = function() {938addCSS(defaultStyles);939};940941$(initStyles);942943pending = [];944$.fn.sparkline = function (userValues, userOptions) {945return this.each(function () {946var options = new $.fn.sparkline.options(this, userOptions),947$this = $(this),948render, i;949render = function () {950var values, width, height, tmp, mhandler, sp, vals;951if (userValues === 'html' || userValues === undefined) {952vals = this.getAttribute(options.get('tagValuesAttribute'));953if (vals === undefined || vals === null) {954vals = $this.html();955}956values = vals.replace(/(^\s*<!--)|(-->\s*$)|\s+/g, '').split(',');957} else {958values = userValues;959}960961width = options.get('width') === 'auto' ? values.length * options.get('defaultPixelsPerValue') : options.get('width');962if (options.get('height') === 'auto') {963if (!options.get('composite') || !$.data(this, '_jqs_vcanvas')) {964// must be a better way to get the line height965tmp = document.createElement('span');966tmp.innerHTML = 'a';967$this.html(tmp);968height = $(tmp).innerHeight() || $(tmp).height();969$(tmp).remove();970tmp = null;971}972} else {973height = options.get('height');974}975976if (!options.get('disableInteraction')) {977mhandler = $.data(this, '_jqs_mhandler');978if (!mhandler) {979mhandler = new MouseHandler(this, options);980$.data(this, '_jqs_mhandler', mhandler);981} else if (!options.get('composite')) {982mhandler.reset();983}984} else {985mhandler = false;986}987988if (options.get('composite') && !$.data(this, '_jqs_vcanvas')) {989if (!$.data(this, '_jqs_errnotify')) {990alert('Attempted to attach a composite sparkline to an element with no existing sparkline');991$.data(this, '_jqs_errnotify', true);992}993return;994}995996sp = new $.fn.sparkline[options.get('type')](this, values, options, width, height);997998sp.render();9991000if (mhandler) {1001mhandler.registerSparkline(sp);1002}1003};1004if (($(this).html() && !options.get('disableHiddenCheck') && $(this).is(':hidden')) || !$(this).parents('body').length) {1005if (!options.get('composite') && $.data(this, '_jqs_pending')) {1006// remove any existing references to the element1007for (i = pending.length; i; i--) {1008if (pending[i - 1][0] == this) {1009pending.splice(i - 1, 1);1010}1011}1012}1013pending.push([this, render]);1014$.data(this, '_jqs_pending', true);1015} else {1016render.call(this);1017}1018});1019};10201021$.fn.sparkline.defaults = getDefaults();102210231024$.sparkline_display_visible = function () {1025var el, i, pl;1026var done = [];1027for (i = 0, pl = pending.length; i < pl; i++) {1028el = pending[i][0];1029if ($(el).is(':visible') && !$(el).parents().is(':hidden')) {1030pending[i][1].call(el);1031$.data(pending[i][0], '_jqs_pending', false);1032done.push(i);1033} else if (!$(el).closest('html').length && !$.data(el, '_jqs_pending')) {1034// element has been inserted and removed from the DOM1035// If it was not yet inserted into the dom then the .data request1036// will return true.1037// removing from the dom causes the data to be removed.1038$.data(pending[i][0], '_jqs_pending', false);1039done.push(i);1040}1041}1042for (i = done.length; i; i--) {1043pending.splice(done[i - 1], 1);1044}1045};104610471048/**1049* User option handler1050*/1051$.fn.sparkline.options = createClass({1052init: function (tag, userOptions) {1053var extendedOptions, defaults, base, tagOptionType;1054this.userOptions = userOptions = userOptions || {};1055this.tag = tag;1056this.tagValCache = {};1057defaults = $.fn.sparkline.defaults;1058base = defaults.common;1059this.tagOptionsPrefix = userOptions.enableTagOptions && (userOptions.tagOptionsPrefix || base.tagOptionsPrefix);10601061tagOptionType = this.getTagSetting('type');1062if (tagOptionType === UNSET_OPTION) {1063extendedOptions = defaults[userOptions.type || base.type];1064} else {1065extendedOptions = defaults[tagOptionType];1066}1067this.mergedOptions = $.extend({}, base, extendedOptions, userOptions);1068},106910701071getTagSetting: function (key) {1072var prefix = this.tagOptionsPrefix,1073val, i, pairs, keyval;1074if (prefix === false || prefix === undefined) {1075return UNSET_OPTION;1076}1077if (this.tagValCache.hasOwnProperty(key)) {1078val = this.tagValCache.key;1079} else {1080val = this.tag.getAttribute(prefix + key);1081if (val === undefined || val === null) {1082val = UNSET_OPTION;1083} else if (val.substr(0, 1) === '[') {1084val = val.substr(1, val.length - 2).split(',');1085for (i = val.length; i--;) {1086val[i] = normalizeValue(val[i].replace(/(^\s*)|(\s*$)/g, ''));1087}1088} else if (val.substr(0, 1) === '{') {1089pairs = val.substr(1, val.length - 2).split(',');1090val = {};1091for (i = pairs.length; i--;) {1092keyval = pairs[i].split(':', 2);1093val[keyval[0].replace(/(^\s*)|(\s*$)/g, '')] = normalizeValue(keyval[1].replace(/(^\s*)|(\s*$)/g, ''));1094}1095} else {1096val = normalizeValue(val);1097}1098this.tagValCache.key = val;1099}1100return val;1101},11021103get: function (key, defaultval) {1104var tagOption = this.getTagSetting(key),1105result;1106if (tagOption !== UNSET_OPTION) {1107return tagOption;1108}1109return (result = this.mergedOptions[key]) === undefined ? defaultval : result;1110}1111});111211131114$.fn.sparkline._base = createClass({1115disabled: false,11161117init: function (el, values, options, width, height) {1118this.el = el;1119this.$el = $(el);1120this.values = values;1121this.options = options;1122this.width = width;1123this.height = height;1124this.currentRegion = undefined;1125},11261127/**1128* Setup the canvas1129*/1130initTarget: function () {1131var interactive = !this.options.get('disableInteraction');1132if (!(this.target = this.$el.simpledraw(this.width, this.height, this.options.get('composite'), interactive))) {1133this.disabled = true;1134} else {1135this.canvasWidth = this.target.pixelWidth;1136this.canvasHeight = this.target.pixelHeight;1137}1138},11391140/**1141* Actually render the chart to the canvas1142*/1143render: function () {1144if (this.disabled) {1145this.el.innerHTML = '';1146return false;1147}1148return true;1149},11501151/**1152* Return a region id for a given x/y co-ordinate1153*/1154getRegion: function (x, y) {1155},11561157/**1158* Highlight an item based on the moused-over x,y co-ordinate1159*/1160setRegionHighlight: function (el, x, y) {1161var currentRegion = this.currentRegion,1162highlightEnabled = !this.options.get('disableHighlight'),1163newRegion;1164if (x > this.canvasWidth || y > this.canvasHeight || x < 0 || y < 0) {1165return null;1166}1167newRegion = this.getRegion(el, x, y);1168if (currentRegion !== newRegion) {1169if (currentRegion !== undefined && highlightEnabled) {1170this.removeHighlight();1171}1172this.currentRegion = newRegion;1173if (newRegion !== undefined && highlightEnabled) {1174this.renderHighlight();1175}1176return true;1177}1178return false;1179},11801181/**1182* Reset any currently highlighted item1183*/1184clearRegionHighlight: function () {1185if (this.currentRegion !== undefined) {1186this.removeHighlight();1187this.currentRegion = undefined;1188return true;1189}1190return false;1191},11921193renderHighlight: function () {1194this.changeHighlight(true);1195},11961197removeHighlight: function () {1198this.changeHighlight(false);1199},12001201changeHighlight: function (highlight) {},12021203/**1204* Fetch the HTML to display as a tooltip1205*/1206getCurrentRegionTooltip: function () {1207var options = this.options,1208header = '',1209entries = [],1210fields, formats, formatlen, fclass, text, i,1211showFields, showFieldsKey, newFields, fv,1212formatter, format, fieldlen, j;1213if (this.currentRegion === undefined) {1214return '';1215}1216fields = this.getCurrentRegionFields();1217formatter = options.get('tooltipFormatter');1218if (formatter) {1219return formatter(this, options, fields);1220}1221if (options.get('tooltipChartTitle')) {1222header += '<div class="jqs jqstitle">' + options.get('tooltipChartTitle') + '</div>\n';1223}1224formats = this.options.get('tooltipFormat');1225if (!formats) {1226return '';1227}1228if (!$.isArray(formats)) {1229formats = [formats];1230}1231if (!$.isArray(fields)) {1232fields = [fields];1233}1234showFields = this.options.get('tooltipFormatFieldlist');1235showFieldsKey = this.options.get('tooltipFormatFieldlistKey');1236if (showFields && showFieldsKey) {1237// user-selected ordering of fields1238newFields = [];1239for (i = fields.length; i--;) {1240fv = fields[i][showFieldsKey];1241if ((j = $.inArray(fv, showFields)) != -1) {1242newFields[j] = fields[i];1243}1244}1245fields = newFields;1246}1247formatlen = formats.length;1248fieldlen = fields.length;1249for (i = 0; i < formatlen; i++) {1250format = formats[i];1251if (typeof format === 'string') {1252format = new SPFormat(format);1253}1254fclass = format.fclass || 'jqsfield';1255for (j = 0; j < fieldlen; j++) {1256if (!fields[j].isNull || !options.get('tooltipSkipNull')) {1257$.extend(fields[j], {1258prefix: options.get('tooltipPrefix'),1259suffix: options.get('tooltipSuffix')1260});1261text = format.render(fields[j], options.get('tooltipValueLookups'), options);1262entries.push('<div class="' + fclass + '">' + text + '</div>');1263}1264}1265}1266if (entries.length) {1267return header + entries.join('\n');1268}1269return '';1270},12711272getCurrentRegionFields: function () {},12731274calcHighlightColor: function (color, options) {1275var highlightColor = options.get('highlightColor'),1276lighten = options.get('highlightLighten'),1277parse, mult, rgbnew, i;1278if (highlightColor) {1279return highlightColor;1280}1281if (lighten) {1282// extract RGB values1283parse = /^#([0-9a-f])([0-9a-f])([0-9a-f])$/i.exec(color) || /^#([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})$/i.exec(color);1284if (parse) {1285rgbnew = [];1286mult = color.length === 4 ? 16 : 1;1287for (i = 0; i < 3; i++) {1288rgbnew[i] = clipval(Math.round(parseInt(parse[i + 1], 16) * mult * lighten), 0, 255);1289}1290return 'rgb(' + rgbnew.join(',') + ')';1291}12921293}1294return color;1295}12961297});12981299barHighlightMixin = {1300changeHighlight: function (highlight) {1301var currentRegion = this.currentRegion,1302target = this.target,1303shapeids = this.regionShapes[currentRegion],1304newShapes;1305// will be null if the region value was null1306if (shapeids) {1307newShapes = this.renderRegion(currentRegion, highlight);1308if ($.isArray(newShapes) || $.isArray(shapeids)) {1309target.replaceWithShapes(shapeids, newShapes);1310this.regionShapes[currentRegion] = $.map(newShapes, function (newShape) {1311return newShape.id;1312});1313} else {1314target.replaceWithShape(shapeids, newShapes);1315this.regionShapes[currentRegion] = newShapes.id;1316}1317}1318},13191320render: function () {1321var values = this.values,1322target = this.target,1323regionShapes = this.regionShapes,1324shapes, ids, i, j;13251326if (!this.cls._super.render.call(this)) {1327return;1328}1329for (i = values.length; i--;) {1330shapes = this.renderRegion(i);1331if (shapes) {1332if ($.isArray(shapes)) {1333ids = [];1334for (j = shapes.length; j--;) {1335shapes[j].append();1336ids.push(shapes[j].id);1337}1338regionShapes[i] = ids;1339} else {1340shapes.append();1341regionShapes[i] = shapes.id; // store just the shapeid1342}1343} else {1344// null value1345regionShapes[i] = null;1346}1347}1348target.render();1349}1350};13511352/**1353* Line charts1354*/1355$.fn.sparkline.line = line = createClass($.fn.sparkline._base, {1356type: 'line',13571358init: function (el, values, options, width, height) {1359line._super.init.call(this, el, values, options, width, height);1360this.vertices = [];1361this.regionMap = [];1362this.xvalues = [];1363this.yvalues = [];1364this.yminmax = [];1365this.hightlightSpotId = null;1366this.lastShapeId = null;1367this.initTarget();1368},13691370getRegion: function (el, x, y) {1371var i,1372regionMap = this.regionMap; // maps regions to value positions1373for (i = regionMap.length; i--;) {1374if (regionMap[i] !== null && x >= regionMap[i][0] && x <= regionMap[i][1]) {1375return regionMap[i][2];1376}1377}1378return undefined;1379},13801381getCurrentRegionFields: function () {1382var currentRegion = this.currentRegion;1383return {1384isNull: this.yvalues[currentRegion] === null,1385x: this.xvalues[currentRegion],1386y: this.yvalues[currentRegion],1387color: this.options.get('lineColor'),1388fillColor: this.options.get('fillColor'),1389offset: currentRegion1390};1391},13921393renderHighlight: function () {1394var currentRegion = this.currentRegion,1395target = this.target,1396vertex = this.vertices[currentRegion],1397options = this.options,1398spotRadius = options.get('spotRadius'),1399highlightSpotColor = options.get('highlightSpotColor'),1400highlightLineColor = options.get('highlightLineColor'),1401highlightSpot, highlightLine;14021403if (!vertex) {1404return;1405}1406if (spotRadius && highlightSpotColor) {1407highlightSpot = target.drawCircle(vertex[0], vertex[1],1408spotRadius, undefined, highlightSpotColor);1409this.highlightSpotId = highlightSpot.id;1410target.insertAfterShape(this.lastShapeId, highlightSpot);1411}1412if (highlightLineColor) {1413highlightLine = target.drawLine(vertex[0], this.canvasTop, vertex[0],1414this.canvasTop + this.canvasHeight, highlightLineColor);1415this.highlightLineId = highlightLine.id;1416target.insertAfterShape(this.lastShapeId, highlightLine);1417}1418},14191420removeHighlight: function () {1421var target = this.target;1422if (this.highlightSpotId) {1423target.removeShapeId(this.highlightSpotId);1424this.highlightSpotId = null;1425}1426if (this.highlightLineId) {1427target.removeShapeId(this.highlightLineId);1428this.highlightLineId = null;1429}1430},14311432scanValues: function () {1433var values = this.values,1434valcount = values.length,1435xvalues = this.xvalues,1436yvalues = this.yvalues,1437yminmax = this.yminmax,1438i, val, isStr, isArray, sp;1439for (i = 0; i < valcount; i++) {1440val = values[i];1441isStr = typeof(values[i]) === 'string';1442isArray = typeof(values[i]) === 'object' && values[i] instanceof Array;1443sp = isStr && values[i].split(':');1444if (isStr && sp.length === 2) { // x:y1445xvalues.push(Number(sp[0]));1446yvalues.push(Number(sp[1]));1447yminmax.push(Number(sp[1]));1448} else if (isArray) {1449xvalues.push(val[0]);1450yvalues.push(val[1]);1451yminmax.push(val[1]);1452} else {1453xvalues.push(i);1454if (values[i] === null || values[i] === 'null') {1455yvalues.push(null);1456} else {1457yvalues.push(Number(val));1458yminmax.push(Number(val));1459}1460}1461}1462if (this.options.get('xvalues')) {1463xvalues = this.options.get('xvalues');1464}14651466this.maxy = this.maxyorg = Math.max.apply(Math, yminmax);1467this.miny = this.minyorg = Math.min.apply(Math, yminmax);14681469this.maxx = Math.max.apply(Math, xvalues);1470this.minx = Math.min.apply(Math, xvalues);14711472this.xvalues = xvalues;1473this.yvalues = yvalues;1474this.yminmax = yminmax;14751476},14771478processRangeOptions: function () {1479var options = this.options,1480normalRangeMin = options.get('normalRangeMin'),1481normalRangeMax = options.get('normalRangeMax');14821483if (normalRangeMin !== undefined) {1484if (normalRangeMin < this.miny) {1485this.miny = normalRangeMin;1486}1487if (normalRangeMax > this.maxy) {1488this.maxy = normalRangeMax;1489}1490}1491if (options.get('chartRangeMin') !== undefined && (options.get('chartRangeClip') || options.get('chartRangeMin') < this.miny)) {1492this.miny = options.get('chartRangeMin');1493}1494if (options.get('chartRangeMax') !== undefined && (options.get('chartRangeClip') || options.get('chartRangeMax') > this.maxy)) {1495this.maxy = options.get('chartRangeMax');1496}1497if (options.get('chartRangeMinX') !== undefined && (options.get('chartRangeClipX') || options.get('chartRangeMinX') < this.minx)) {1498this.minx = options.get('chartRangeMinX');1499}1500if (options.get('chartRangeMaxX') !== undefined && (options.get('chartRangeClipX') || options.get('chartRangeMaxX') > this.maxx)) {1501this.maxx = options.get('chartRangeMaxX');1502}15031504},15051506drawNormalRange: function (canvasLeft, canvasTop, canvasHeight, canvasWidth, rangey) {1507var normalRangeMin = this.options.get('normalRangeMin'),1508normalRangeMax = this.options.get('normalRangeMax'),1509ytop = canvasTop + Math.round(canvasHeight - (canvasHeight * ((normalRangeMax - this.miny) / rangey))),1510height = Math.round((canvasHeight * (normalRangeMax - normalRangeMin)) / rangey);1511this.target.drawRect(canvasLeft, ytop, canvasWidth, height, undefined, this.options.get('normalRangeColor')).append();1512},15131514render: function () {1515var options = this.options,1516target = this.target,1517canvasWidth = this.canvasWidth,1518canvasHeight = this.canvasHeight,1519vertices = this.vertices,1520spotRadius = options.get('spotRadius'),1521regionMap = this.regionMap,1522rangex, rangey, yvallast,1523canvasTop, canvasLeft,1524vertex, path, paths, x, y, xnext, xpos, xposnext,1525last, next, yvalcount, lineShapes, fillShapes, plen,1526valueSpots, hlSpotsEnabled, color, xvalues, yvalues, i;15271528if (!line._super.render.call(this)) {1529return;1530}15311532this.scanValues();1533this.processRangeOptions();15341535xvalues = this.xvalues;1536yvalues = this.yvalues;15371538if (!this.yminmax.length || this.yvalues.length < 2) {1539// empty or all null valuess1540return;1541}15421543canvasTop = canvasLeft = 0;15441545rangex = this.maxx - this.minx === 0 ? 1 : this.maxx - this.minx;1546rangey = this.maxy - this.miny === 0 ? 1 : this.maxy - this.miny;1547yvallast = this.yvalues.length - 1;15481549if (spotRadius && (canvasWidth < (spotRadius * 4) || canvasHeight < (spotRadius * 4))) {1550spotRadius = 0;1551}1552if (spotRadius) {1553// adjust the canvas size as required so that spots will fit1554hlSpotsEnabled = options.get('highlightSpotColor') && !options.get('disableInteraction');1555if (hlSpotsEnabled || options.get('minSpotColor') || (options.get('spotColor') && yvalues[yvallast] === this.miny)) {1556canvasHeight -= Math.ceil(spotRadius);1557}1558if (hlSpotsEnabled || options.get('maxSpotColor') || (options.get('spotColor') && yvalues[yvallast] === this.maxy)) {1559canvasHeight -= Math.ceil(spotRadius);1560canvasTop += Math.ceil(spotRadius);1561}1562if (hlSpotsEnabled ||1563((options.get('minSpotColor') || options.get('maxSpotColor')) && (yvalues[0] === this.miny || yvalues[0] === this.maxy))) {1564canvasLeft += Math.ceil(spotRadius);1565canvasWidth -= Math.ceil(spotRadius);1566}1567if (hlSpotsEnabled || options.get('spotColor') ||1568(options.get('minSpotColor') || options.get('maxSpotColor') &&1569(yvalues[yvallast] === this.miny || yvalues[yvallast] === this.maxy))) {1570canvasWidth -= Math.ceil(spotRadius);1571}1572}157315741575canvasHeight--;15761577if (options.get('normalRangeMin') !== undefined && !options.get('drawNormalOnTop')) {1578this.drawNormalRange(canvasLeft, canvasTop, canvasHeight, canvasWidth, rangey);1579}15801581path = [];1582paths = [path];1583last = next = null;1584yvalcount = yvalues.length;1585for (i = 0; i < yvalcount; i++) {1586x = xvalues[i];1587xnext = xvalues[i + 1];1588y = yvalues[i];1589xpos = canvasLeft + Math.round((x - this.minx) * (canvasWidth / rangex));1590xposnext = i < yvalcount - 1 ? canvasLeft + Math.round((xnext - this.minx) * (canvasWidth / rangex)) : canvasWidth;1591next = xpos + ((xposnext - xpos) / 2);1592regionMap[i] = [last || 0, next, i];1593last = next;1594if (y === null) {1595if (i) {1596if (yvalues[i - 1] !== null) {1597path = [];1598paths.push(path);1599}1600vertices.push(null);1601}1602} else {1603if (y < this.miny) {1604y = this.miny;1605}1606if (y > this.maxy) {1607y = this.maxy;1608}1609if (!path.length) {1610// previous value was null1611path.push([xpos, canvasTop + canvasHeight]);1612}1613vertex = [xpos, canvasTop + Math.round(canvasHeight - (canvasHeight * ((y - this.miny) / rangey)))];1614path.push(vertex);1615vertices.push(vertex);1616}1617}16181619lineShapes = [];1620fillShapes = [];1621plen = paths.length;1622for (i = 0; i < plen; i++) {1623path = paths[i];1624if (path.length) {1625if (options.get('fillColor')) {1626path.push([path[path.length - 1][0], (canvasTop + canvasHeight)]);1627fillShapes.push(path.slice(0));1628path.pop();1629}1630// if there's only a single point in this path, then we want to display it1631// as a vertical line which means we keep path[0] as is1632if (path.length > 2) {1633// else we want the first value1634path[0] = [path[0][0], path[1][1]];1635}1636lineShapes.push(path);1637}1638}16391640// draw the fill first, then optionally the normal range, then the line on top of that1641plen = fillShapes.length;1642for (i = 0; i < plen; i++) {1643target.drawShape(fillShapes[i],1644options.get('fillColor'), options.get('fillColor')).append();1645}16461647if (options.get('normalRangeMin') !== undefined && options.get('drawNormalOnTop')) {1648this.drawNormalRange(canvasLeft, canvasTop, canvasHeight, canvasWidth, rangey);1649}16501651plen = lineShapes.length;1652for (i = 0; i < plen; i++) {1653target.drawShape(lineShapes[i], options.get('lineColor'), undefined,1654options.get('lineWidth')).append();1655}16561657if (spotRadius && options.get('valueSpots')) {1658valueSpots = options.get('valueSpots');1659if (valueSpots.get === undefined) {1660valueSpots = new RangeMap(valueSpots);1661}1662for (i = 0; i < yvalcount; i++) {1663color = valueSpots.get(yvalues[i]);1664if (color) {1665target.drawCircle(canvasLeft + Math.round((xvalues[i] - this.minx) * (canvasWidth / rangex)),1666canvasTop + Math.round(canvasHeight - (canvasHeight * ((yvalues[i] - this.miny) / rangey))),1667spotRadius, undefined,1668color).append();1669}1670}16711672}1673if (spotRadius && options.get('spotColor') && yvalues[yvallast] !== null) {1674target.drawCircle(canvasLeft + Math.round((xvalues[xvalues.length - 1] - this.minx) * (canvasWidth / rangex)),1675canvasTop + Math.round(canvasHeight - (canvasHeight * ((yvalues[yvallast] - this.miny) / rangey))),1676spotRadius, undefined,1677options.get('spotColor')).append();1678}1679if (this.maxy !== this.minyorg) {1680if (spotRadius && options.get('minSpotColor')) {1681x = xvalues[$.inArray(this.minyorg, yvalues)];1682target.drawCircle(canvasLeft + Math.round((x - this.minx) * (canvasWidth / rangex)),1683canvasTop + Math.round(canvasHeight - (canvasHeight * ((this.minyorg - this.miny) / rangey))),1684spotRadius, undefined,1685options.get('minSpotColor')).append();1686}1687if (spotRadius && options.get('maxSpotColor')) {1688x = xvalues[$.inArray(this.maxyorg, yvalues)];1689target.drawCircle(canvasLeft + Math.round((x - this.minx) * (canvasWidth / rangex)),1690canvasTop + Math.round(canvasHeight - (canvasHeight * ((this.maxyorg - this.miny) / rangey))),1691spotRadius, undefined,1692options.get('maxSpotColor')).append();1693}1694}16951696this.lastShapeId = target.getLastShapeId();1697this.canvasTop = canvasTop;1698target.render();1699}1700});17011702/**1703* Bar charts1704*/1705$.fn.sparkline.bar = bar = createClass($.fn.sparkline._base, barHighlightMixin, {1706type: 'bar',17071708init: function (el, values, options, width, height) {1709var barWidth = parseInt(options.get('barWidth'), 10),1710barSpacing = parseInt(options.get('barSpacing'), 10),1711chartRangeMin = options.get('chartRangeMin'),1712chartRangeMax = options.get('chartRangeMax'),1713chartRangeClip = options.get('chartRangeClip'),1714stackMin = Infinity,1715stackMax = -Infinity,1716isStackString, groupMin, groupMax, stackRanges,1717numValues, i, vlen, range, zeroAxis, xaxisOffset, min, max, clipMin, clipMax,1718stacked, vlist, j, slen, svals, val, yoffset, yMaxCalc, canvasHeightEf;1719bar._super.init.call(this, el, values, options, width, height);17201721// scan values to determine whether to stack bars1722for (i = 0, vlen = values.length; i < vlen; i++) {1723val = values[i];1724isStackString = typeof(val) === 'string' && val.indexOf(':') > -1;1725if (isStackString || $.isArray(val)) {1726stacked = true;1727if (isStackString) {1728val = values[i] = normalizeValues(val.split(':'));1729}1730val = remove(val, null); // min/max will treat null as zero1731groupMin = Math.min.apply(Math, val);1732groupMax = Math.max.apply(Math, val);1733if (groupMin < stackMin) {1734stackMin = groupMin;1735}1736if (groupMax > stackMax) {1737stackMax = groupMax;1738}1739}1740}17411742this.stacked = stacked;1743this.regionShapes = {};1744this.barWidth = barWidth;1745this.barSpacing = barSpacing;1746this.totalBarWidth = barWidth + barSpacing;1747this.width = width = (values.length * barWidth) + ((values.length - 1) * barSpacing);17481749this.initTarget();17501751if (chartRangeClip) {1752clipMin = chartRangeMin === undefined ? -Infinity : chartRangeMin;1753clipMax = chartRangeMax === undefined ? Infinity : chartRangeMax;1754}17551756numValues = [];1757stackRanges = stacked ? [] : numValues;1758var stackTotals = [];1759var stackRangesNeg = [];1760for (i = 0, vlen = values.length; i < vlen; i++) {1761if (stacked) {1762vlist = values[i];1763values[i] = svals = [];1764stackTotals[i] = 0;1765stackRanges[i] = stackRangesNeg[i] = 0;1766for (j = 0, slen = vlist.length; j < slen; j++) {1767val = svals[j] = chartRangeClip ? clipval(vlist[j], clipMin, clipMax) : vlist[j];1768if (val !== null) {1769if (val > 0) {1770stackTotals[i] += val;1771}1772if (stackMin < 0 && stackMax > 0) {1773if (val < 0) {1774stackRangesNeg[i] += Math.abs(val);1775} else {1776stackRanges[i] += val;1777}1778} else {1779stackRanges[i] += Math.abs(val - (val < 0 ? stackMax : stackMin));1780}1781numValues.push(val);1782}1783}1784} else {1785val = chartRangeClip ? clipval(values[i], clipMin, clipMax) : values[i];1786val = values[i] = normalizeValue(val);1787if (val !== null) {1788numValues.push(val);1789}1790}1791}1792this.max = max = Math.max.apply(Math, numValues);1793this.min = min = Math.min.apply(Math, numValues);1794this.stackMax = stackMax = stacked ? Math.max.apply(Math, stackTotals) : max;1795this.stackMin = stackMin = stacked ? Math.min.apply(Math, numValues) : min;17961797if (options.get('chartRangeMin') !== undefined && (options.get('chartRangeClip') || options.get('chartRangeMin') < min)) {1798min = options.get('chartRangeMin');1799}1800if (options.get('chartRangeMax') !== undefined && (options.get('chartRangeClip') || options.get('chartRangeMax') > max)) {1801max = options.get('chartRangeMax');1802}18031804this.zeroAxis = zeroAxis = options.get('zeroAxis', true);1805if (min <= 0 && max >= 0 && zeroAxis) {1806xaxisOffset = 0;1807} else if (zeroAxis == false) {1808xaxisOffset = min;1809} else if (min > 0) {1810xaxisOffset = min;1811} else {1812xaxisOffset = max;1813}1814this.xaxisOffset = xaxisOffset;18151816range = stacked ? (Math.max.apply(Math, stackRanges) + Math.max.apply(Math, stackRangesNeg)) : max - min;18171818// as we plot zero/min values a single pixel line, we add a pixel to all other1819// values - Reduce the effective canvas size to suit1820this.canvasHeightEf = (zeroAxis && min < 0) ? this.canvasHeight - 2 : this.canvasHeight - 1;18211822if (min < xaxisOffset) {1823yMaxCalc = (stacked && max >= 0) ? stackMax : max;1824yoffset = (yMaxCalc - xaxisOffset) / range * this.canvasHeight;1825if (yoffset !== Math.ceil(yoffset)) {1826this.canvasHeightEf -= 2;1827yoffset = Math.ceil(yoffset);1828}1829} else {1830yoffset = this.canvasHeight;1831}1832this.yoffset = yoffset;18331834if ($.isArray(options.get('colorMap'))) {1835this.colorMapByIndex = options.get('colorMap');1836this.colorMapByValue = null;1837} else {1838this.colorMapByIndex = null;1839this.colorMapByValue = options.get('colorMap');1840if (this.colorMapByValue && this.colorMapByValue.get === undefined) {1841this.colorMapByValue = new RangeMap(this.colorMapByValue);1842}1843}18441845this.range = range;1846},18471848getRegion: function (el, x, y) {1849var result = Math.floor(x / this.totalBarWidth);1850return (result < 0 || result >= this.values.length) ? undefined : result;1851},18521853getCurrentRegionFields: function () {1854var currentRegion = this.currentRegion,1855values = ensureArray(this.values[currentRegion]),1856result = [],1857value, i;1858for (i = values.length; i--;) {1859value = values[i];1860result.push({1861isNull: value === null,1862value: value,1863color: this.calcColor(i, value, currentRegion),1864offset: currentRegion1865});1866}1867return result;1868},18691870calcColor: function (stacknum, value, valuenum) {1871var colorMapByIndex = this.colorMapByIndex,1872colorMapByValue = this.colorMapByValue,1873options = this.options,1874color, newColor;1875if (this.stacked) {1876color = options.get('stackedBarColor');1877} else {1878color = (value < 0) ? options.get('negBarColor') : options.get('barColor');1879}1880if (value === 0 && options.get('zeroColor') !== undefined) {1881color = options.get('zeroColor');1882}1883if (colorMapByValue && (newColor = colorMapByValue.get(value))) {1884color = newColor;1885} else if (colorMapByIndex && colorMapByIndex.length > valuenum) {1886color = colorMapByIndex[valuenum];1887}1888return $.isArray(color) ? color[stacknum % color.length] : color;1889},18901891/**1892* Render bar(s) for a region1893*/1894renderRegion: function (valuenum, highlight) {1895var vals = this.values[valuenum],1896options = this.options,1897xaxisOffset = this.xaxisOffset,1898result = [],1899range = this.range,1900stacked = this.stacked,1901target = this.target,1902x = valuenum * this.totalBarWidth,1903canvasHeightEf = this.canvasHeightEf,1904yoffset = this.yoffset,1905y, height, color, isNull, yoffsetNeg, i, valcount, val, minPlotted, allMin;19061907vals = $.isArray(vals) ? vals : [vals];1908valcount = vals.length;1909val = vals[0];1910isNull = all(null, vals);1911allMin = all(xaxisOffset, vals, true);19121913if (isNull) {1914if (options.get('nullColor')) {1915color = highlight ? options.get('nullColor') : this.calcHighlightColor(options.get('nullColor'), options);1916y = (yoffset > 0) ? yoffset - 1 : yoffset;1917return target.drawRect(x, y, this.barWidth - 1, 0, color, color);1918} else {1919return undefined;1920}1921}1922yoffsetNeg = yoffset;1923for (i = 0; i < valcount; i++) {1924val = vals[i];19251926if (stacked && val === xaxisOffset) {1927if (!allMin || minPlotted) {1928continue;1929}1930minPlotted = true;1931}19321933if (range > 0) {1934height = Math.floor(canvasHeightEf * ((Math.abs(val - xaxisOffset) / range))) + 1;1935} else {1936height = 1;1937}1938if (val < xaxisOffset || (val === xaxisOffset && yoffset === 0)) {1939y = yoffsetNeg;1940yoffsetNeg += height;1941} else {1942y = yoffset - height;1943yoffset -= height;1944}1945color = this.calcColor(i, val, valuenum);1946if (highlight) {1947color = this.calcHighlightColor(color, options);1948}1949result.push(target.drawRect(x, y, this.barWidth - 1, height - 1, color, color));1950}1951if (result.length === 1) {1952return result[0];1953}1954return result;1955}1956});19571958/**1959* Tristate charts1960*/1961$.fn.sparkline.tristate = tristate = createClass($.fn.sparkline._base, barHighlightMixin, {1962type: 'tristate',19631964init: function (el, values, options, width, height) {1965var barWidth = parseInt(options.get('barWidth'), 10),1966barSpacing = parseInt(options.get('barSpacing'), 10);1967tristate._super.init.call(this, el, values, options, width, height);19681969this.regionShapes = {};1970this.barWidth = barWidth;1971this.barSpacing = barSpacing;1972this.totalBarWidth = barWidth + barSpacing;1973this.values = $.map(values, Number);1974this.width = width = (values.length * barWidth) + ((values.length - 1) * barSpacing);19751976if ($.isArray(options.get('colorMap'))) {1977this.colorMapByIndex = options.get('colorMap');1978this.colorMapByValue = null;1979} else {1980this.colorMapByIndex = null;1981this.colorMapByValue = options.get('colorMap');1982if (this.colorMapByValue && this.colorMapByValue.get === undefined) {1983this.colorMapByValue = new RangeMap(this.colorMapByValue);1984}1985}1986this.initTarget();1987},19881989getRegion: function (el, x, y) {1990return Math.floor(x / this.totalBarWidth);1991},19921993getCurrentRegionFields: function () {1994var currentRegion = this.currentRegion;1995return {1996isNull: this.values[currentRegion] === undefined,1997value: this.values[currentRegion],1998color: this.calcColor(this.values[currentRegion], currentRegion),1999offset: currentRegion2000};2001},20022003calcColor: function (value, valuenum) {2004var values = this.values,2005options = this.options,2006colorMapByIndex = this.colorMapByIndex,2007colorMapByValue = this.colorMapByValue,2008color, newColor;20092010if (colorMapByValue && (newColor = colorMapByValue.get(value))) {2011color = newColor;2012} else if (colorMapByIndex && colorMapByIndex.length > valuenum) {2013color = colorMapByIndex[valuenum];2014} else if (values[valuenum] < 0) {2015color = options.get('negBarColor');2016} else if (values[valuenum] > 0) {2017color = options.get('posBarColor');2018} else {2019color = options.get('zeroBarColor');2020}2021return color;2022},20232024renderRegion: function (valuenum, highlight) {2025var values = this.values,2026options = this.options,2027target = this.target,2028canvasHeight, height, halfHeight,2029x, y, color;20302031canvasHeight = target.pixelHeight;2032halfHeight = Math.round(canvasHeight / 2);20332034x = valuenum * this.totalBarWidth;2035if (values[valuenum] < 0) {2036y = halfHeight;2037height = halfHeight - 1;2038} else if (values[valuenum] > 0) {2039y = 0;2040height = halfHeight - 1;2041} else {2042y = halfHeight - 1;2043height = 2;2044}2045color = this.calcColor(values[valuenum], valuenum);2046if (color === null) {2047return;2048}2049if (highlight) {2050color = this.calcHighlightColor(color, options);2051}2052return target.drawRect(x, y, this.barWidth - 1, height - 1, color, color);2053}2054});20552056/**2057* Discrete charts2058*/2059$.fn.sparkline.discrete = discrete = createClass($.fn.sparkline._base, barHighlightMixin, {2060type: 'discrete',20612062init: function (el, values, options, width, height) {2063discrete._super.init.call(this, el, values, options, width, height);20642065this.regionShapes = {};2066this.values = values = $.map(values, Number);2067this.min = Math.min.apply(Math, values);2068this.max = Math.max.apply(Math, values);2069this.range = this.max - this.min;2070this.width = width = options.get('width') === 'auto' ? values.length * 2 : this.width;2071this.interval = Math.floor(width / values.length);2072this.itemWidth = width / values.length;2073if (options.get('chartRangeMin') !== undefined && (options.get('chartRangeClip') || options.get('chartRangeMin') < this.min)) {2074this.min = options.get('chartRangeMin');2075}2076if (options.get('chartRangeMax') !== undefined && (options.get('chartRangeClip') || options.get('chartRangeMax') > this.max)) {2077this.max = options.get('chartRangeMax');2078}2079this.initTarget();2080if (this.target) {2081this.lineHeight = options.get('lineHeight') === 'auto' ? Math.round(this.canvasHeight * 0.3) : options.get('lineHeight');2082}2083},20842085getRegion: function (el, x, y) {2086return Math.floor(x / this.itemWidth);2087},20882089getCurrentRegionFields: function () {2090var currentRegion = this.currentRegion;2091return {2092isNull: this.values[currentRegion] === undefined,2093value: this.values[currentRegion],2094offset: currentRegion2095};2096},20972098renderRegion: function (valuenum, highlight) {2099var values = this.values,2100options = this.options,2101min = this.min,2102max = this.max,2103range = this.range,2104interval = this.interval,2105target = this.target,2106canvasHeight = this.canvasHeight,2107lineHeight = this.lineHeight,2108pheight = canvasHeight - lineHeight,2109ytop, val, color, x;21102111val = clipval(values[valuenum], min, max);2112x = valuenum * interval;2113ytop = Math.round(pheight - pheight * ((val - min) / range));2114color = (options.get('thresholdColor') && val < options.get('thresholdValue')) ? options.get('thresholdColor') : options.get('lineColor');2115if (highlight) {2116color = this.calcHighlightColor(color, options);2117}2118return target.drawLine(x, ytop, x, ytop + lineHeight, color);2119}2120});21212122/**2123* Bullet charts2124*/2125$.fn.sparkline.bullet = bullet = createClass($.fn.sparkline._base, {2126type: 'bullet',21272128init: function (el, values, options, width, height) {2129var min, max, vals;2130bullet._super.init.call(this, el, values, options, width, height);21312132// values: target, performance, range1, range2, range32133this.values = values = normalizeValues(values);2134// target or performance could be null2135vals = values.slice();2136vals[0] = vals[0] === null ? vals[2] : vals[0];2137vals[1] = values[1] === null ? vals[2] : vals[1];2138min = Math.min.apply(Math, values);2139max = Math.max.apply(Math, values);2140if (options.get('base') === undefined) {2141min = min < 0 ? min : 0;2142} else {2143min = options.get('base');2144}2145this.min = min;2146this.max = max;2147this.range = max - min;2148this.shapes = {};2149this.valueShapes = {};2150this.regiondata = {};2151this.width = width = options.get('width') === 'auto' ? '4.0em' : width;2152this.target = this.$el.simpledraw(width, height, options.get('composite'));2153if (!values.length) {2154this.disabled = true;2155}2156this.initTarget();2157},21582159getRegion: function (el, x, y) {2160var shapeid = this.target.getShapeAt(el, x, y);2161return (shapeid !== undefined && this.shapes[shapeid] !== undefined) ? this.shapes[shapeid] : undefined;2162},21632164getCurrentRegionFields: function () {2165var currentRegion = this.currentRegion;2166return {2167fieldkey: currentRegion.substr(0, 1),2168value: this.values[currentRegion.substr(1)],2169region: currentRegion2170};2171},21722173changeHighlight: function (highlight) {2174var currentRegion = this.currentRegion,2175shapeid = this.valueShapes[currentRegion],2176shape;2177delete this.shapes[shapeid];2178switch (currentRegion.substr(0, 1)) {2179case 'r':2180shape = this.renderRange(currentRegion.substr(1), highlight);2181break;2182case 'p':2183shape = this.renderPerformance(highlight);2184break;2185case 't':2186shape = this.renderTarget(highlight);2187break;2188}2189this.valueShapes[currentRegion] = shape.id;2190this.shapes[shape.id] = currentRegion;2191this.target.replaceWithShape(shapeid, shape);2192},21932194renderRange: function (rn, highlight) {2195var rangeval = this.values[rn],2196rangewidth = Math.round(this.canvasWidth * ((rangeval - this.min) / this.range)),2197color = this.options.get('rangeColors')[rn - 2];2198if (highlight) {2199color = this.calcHighlightColor(color, this.options);2200}2201return this.target.drawRect(0, 0, rangewidth - 1, this.canvasHeight - 1, color, color);2202},22032204renderPerformance: function (highlight) {2205var perfval = this.values[1],2206perfwidth = Math.round(this.canvasWidth * ((perfval - this.min) / this.range)),2207color = this.options.get('performanceColor');2208if (highlight) {2209color = this.calcHighlightColor(color, this.options);2210}2211return this.target.drawRect(0, Math.round(this.canvasHeight * 0.3), perfwidth - 1,2212Math.round(this.canvasHeight * 0.4) - 1, color, color);2213},22142215renderTarget: function (highlight) {2216var targetval = this.values[0],2217x = Math.round(this.canvasWidth * ((targetval - this.min) / this.range) - (this.options.get('targetWidth') / 2)),2218targettop = Math.round(this.canvasHeight * 0.10),2219targetheight = this.canvasHeight - (targettop * 2),2220color = this.options.get('targetColor');2221if (highlight) {2222color = this.calcHighlightColor(color, this.options);2223}2224return this.target.drawRect(x, targettop, this.options.get('targetWidth') - 1, targetheight - 1, color, color);2225},22262227render: function () {2228var vlen = this.values.length,2229target = this.target,2230i, shape;2231if (!bullet._super.render.call(this)) {2232return;2233}2234for (i = 2; i < vlen; i++) {2235shape = this.renderRange(i).append();2236this.shapes[shape.id] = 'r' + i;2237this.valueShapes['r' + i] = shape.id;2238}2239if (this.values[1] !== null) {2240shape = this.renderPerformance().append();2241this.shapes[shape.id] = 'p1';2242this.valueShapes.p1 = shape.id;2243}2244if (this.values[0] !== null) {2245shape = this.renderTarget().append();2246this.shapes[shape.id] = 't0';2247this.valueShapes.t0 = shape.id;2248}2249target.render();2250}2251});22522253/**2254* Pie charts2255*/2256$.fn.sparkline.pie = pie = createClass($.fn.sparkline._base, {2257type: 'pie',22582259init: function (el, values, options, width, height) {2260var total = 0, i;22612262pie._super.init.call(this, el, values, options, width, height);22632264this.shapes = {}; // map shape ids to value offsets2265this.valueShapes = {}; // maps value offsets to shape ids2266this.values = values = $.map(values, Number);22672268if (options.get('width') === 'auto') {2269this.width = this.height;2270}22712272if (values.length > 0) {2273for (i = values.length; i--;) {2274total += values[i];2275}2276}2277this.total = total;2278this.initTarget();2279this.radius = Math.floor(Math.min(this.canvasWidth, this.canvasHeight) / 2);2280},22812282getRegion: function (el, x, y) {2283var shapeid = this.target.getShapeAt(el, x, y);2284return (shapeid !== undefined && this.shapes[shapeid] !== undefined) ? this.shapes[shapeid] : undefined;2285},22862287getCurrentRegionFields: function () {2288var currentRegion = this.currentRegion;2289return {2290isNull: this.values[currentRegion] === undefined,2291value: this.values[currentRegion],2292percent: this.values[currentRegion] / this.total * 100,2293color: this.options.get('sliceColors')[currentRegion % this.options.get('sliceColors').length],2294offset: currentRegion2295};2296},22972298changeHighlight: function (highlight) {2299var currentRegion = this.currentRegion,2300newslice = this.renderSlice(currentRegion, highlight),2301shapeid = this.valueShapes[currentRegion];2302delete this.shapes[shapeid];2303this.target.replaceWithShape(shapeid, newslice);2304this.valueShapes[currentRegion] = newslice.id;2305this.shapes[newslice.id] = currentRegion;2306},23072308renderSlice: function (valuenum, highlight) {2309var target = this.target,2310options = this.options,2311radius = this.radius,2312borderWidth = options.get('borderWidth'),2313offset = options.get('offset'),2314circle = 2 * Math.PI,2315values = this.values,2316total = this.total,2317next = offset ? (2*Math.PI)*(offset/360) : 0,2318start, end, i, vlen, color;23192320vlen = values.length;2321for (i = 0; i < vlen; i++) {2322start = next;2323end = next;2324if (total > 0) { // avoid divide by zero2325end = next + (circle * (values[i] / total));2326}2327if (valuenum === i) {2328color = options.get('sliceColors')[i % options.get('sliceColors').length];2329if (highlight) {2330color = this.calcHighlightColor(color, options);2331}23322333return target.drawPieSlice(radius, radius, radius - borderWidth, start, end, undefined, color);2334}2335next = end;2336}2337},23382339render: function () {2340var target = this.target,2341values = this.values,2342options = this.options,2343radius = this.radius,2344borderWidth = options.get('borderWidth'),2345shape, i;23462347if (!pie._super.render.call(this)) {2348return;2349}2350if (borderWidth) {2351target.drawCircle(radius, radius, Math.floor(radius - (borderWidth / 2)),2352options.get('borderColor'), undefined, borderWidth).append();2353}2354for (i = values.length; i--;) {2355if (values[i]) { // don't render zero values2356shape = this.renderSlice(i).append();2357this.valueShapes[i] = shape.id; // store just the shapeid2358this.shapes[shape.id] = i;2359}2360}2361target.render();2362}2363});23642365/**2366* Box plots2367*/2368$.fn.sparkline.box = box = createClass($.fn.sparkline._base, {2369type: 'box',23702371init: function (el, values, options, width, height) {2372box._super.init.call(this, el, values, options, width, height);2373this.values = $.map(values, Number);2374this.width = options.get('width') === 'auto' ? '4.0em' : width;2375this.initTarget();2376if (!this.values.length) {2377this.disabled = 1;2378}2379},23802381/**2382* Simulate a single region2383*/2384getRegion: function () {2385return 1;2386},23872388getCurrentRegionFields: function () {2389var result = [2390{ field: 'lq', value: this.quartiles[0] },2391{ field: 'med', value: this.quartiles[1] },2392{ field: 'uq', value: this.quartiles[2] }2393];2394if (this.loutlier !== undefined) {2395result.push({ field: 'lo', value: this.loutlier});2396}2397if (this.routlier !== undefined) {2398result.push({ field: 'ro', value: this.routlier});2399}2400if (this.lwhisker !== undefined) {2401result.push({ field: 'lw', value: this.lwhisker});2402}2403if (this.rwhisker !== undefined) {2404result.push({ field: 'rw', value: this.rwhisker});2405}2406return result;2407},24082409render: function () {2410var target = this.target,2411values = this.values,2412vlen = values.length,2413options = this.options,2414canvasWidth = this.canvasWidth,2415canvasHeight = this.canvasHeight,2416minValue = options.get('chartRangeMin') === undefined ? Math.min.apply(Math, values) : options.get('chartRangeMin'),2417maxValue = options.get('chartRangeMax') === undefined ? Math.max.apply(Math, values) : options.get('chartRangeMax'),2418canvasLeft = 0,2419lwhisker, loutlier, iqr, q1, q2, q3, rwhisker, routlier, i,2420size, unitSize;24212422if (!box._super.render.call(this)) {2423return;2424}24252426if (options.get('raw')) {2427if (options.get('showOutliers') && values.length > 5) {2428loutlier = values[0];2429lwhisker = values[1];2430q1 = values[2];2431q2 = values[3];2432q3 = values[4];2433rwhisker = values[5];2434routlier = values[6];2435} else {2436lwhisker = values[0];2437q1 = values[1];2438q2 = values[2];2439q3 = values[3];2440rwhisker = values[4];2441}2442} else {2443values.sort(function (a, b) { return a - b; });2444q1 = quartile(values, 1);2445q2 = quartile(values, 2);2446q3 = quartile(values, 3);2447iqr = q3 - q1;2448if (options.get('showOutliers')) {2449lwhisker = rwhisker = undefined;2450for (i = 0; i < vlen; i++) {2451if (lwhisker === undefined && values[i] > q1 - (iqr * options.get('outlierIQR'))) {2452lwhisker = values[i];2453}2454if (values[i] < q3 + (iqr * options.get('outlierIQR'))) {2455rwhisker = values[i];2456}2457}2458loutlier = values[0];2459routlier = values[vlen - 1];2460} else {2461lwhisker = values[0];2462rwhisker = values[vlen - 1];2463}2464}2465this.quartiles = [q1, q2, q3];2466this.lwhisker = lwhisker;2467this.rwhisker = rwhisker;2468this.loutlier = loutlier;2469this.routlier = routlier;24702471unitSize = canvasWidth / (maxValue - minValue + 1);2472if (options.get('showOutliers')) {2473canvasLeft = Math.ceil(options.get('spotRadius'));2474canvasWidth -= 2 * Math.ceil(options.get('spotRadius'));2475unitSize = canvasWidth / (maxValue - minValue + 1);2476if (loutlier < lwhisker) {2477target.drawCircle((loutlier - minValue) * unitSize + canvasLeft,2478canvasHeight / 2,2479options.get('spotRadius'),2480options.get('outlierLineColor'),2481options.get('outlierFillColor')).append();2482}2483if (routlier > rwhisker) {2484target.drawCircle((routlier - minValue) * unitSize + canvasLeft,2485canvasHeight / 2,2486options.get('spotRadius'),2487options.get('outlierLineColor'),2488options.get('outlierFillColor')).append();2489}2490}24912492// box2493target.drawRect(2494Math.round((q1 - minValue) * unitSize + canvasLeft),2495Math.round(canvasHeight * 0.1),2496Math.round((q3 - q1) * unitSize),2497Math.round(canvasHeight * 0.8),2498options.get('boxLineColor'),2499options.get('boxFillColor')).append();2500// left whisker2501target.drawLine(2502Math.round((lwhisker - minValue) * unitSize + canvasLeft),2503Math.round(canvasHeight / 2),2504Math.round((q1 - minValue) * unitSize + canvasLeft),2505Math.round(canvasHeight / 2),2506options.get('lineColor')).append();2507target.drawLine(2508Math.round((lwhisker - minValue) * unitSize + canvasLeft),2509Math.round(canvasHeight / 4),2510Math.round((lwhisker - minValue) * unitSize + canvasLeft),2511Math.round(canvasHeight - canvasHeight / 4),2512options.get('whiskerColor')).append();2513// right whisker2514target.drawLine(Math.round((rwhisker - minValue) * unitSize + canvasLeft),2515Math.round(canvasHeight / 2),2516Math.round((q3 - minValue) * unitSize + canvasLeft),2517Math.round(canvasHeight / 2),2518options.get('lineColor')).append();2519target.drawLine(2520Math.round((rwhisker - minValue) * unitSize + canvasLeft),2521Math.round(canvasHeight / 4),2522Math.round((rwhisker - minValue) * unitSize + canvasLeft),2523Math.round(canvasHeight - canvasHeight / 4),2524options.get('whiskerColor')).append();2525// median line2526target.drawLine(2527Math.round((q2 - minValue) * unitSize + canvasLeft),2528Math.round(canvasHeight * 0.1),2529Math.round((q2 - minValue) * unitSize + canvasLeft),2530Math.round(canvasHeight * 0.9),2531options.get('medianColor')).append();2532if (options.get('target')) {2533size = Math.ceil(options.get('spotRadius'));2534target.drawLine(2535Math.round((options.get('target') - minValue) * unitSize + canvasLeft),2536Math.round((canvasHeight / 2) - size),2537Math.round((options.get('target') - minValue) * unitSize + canvasLeft),2538Math.round((canvasHeight / 2) + size),2539options.get('targetColor')).append();2540target.drawLine(2541Math.round((options.get('target') - minValue) * unitSize + canvasLeft - size),2542Math.round(canvasHeight / 2),2543Math.round((options.get('target') - minValue) * unitSize + canvasLeft + size),2544Math.round(canvasHeight / 2),2545options.get('targetColor')).append();2546}2547target.render();2548}2549});25502551// Setup a very simple "virtual canvas" to make drawing the few shapes we need easier2552// This is accessible as $(foo).simpledraw()25532554VShape = createClass({2555init: function (target, id, type, args) {2556this.target = target;2557this.id = id;2558this.type = type;2559this.args = args;2560},2561append: function () {2562this.target.appendShape(this);2563return this;2564}2565});25662567VCanvas_base = createClass({2568_pxregex: /(\d+)(px)?\s*$/i,25692570init: function (width, height, target) {2571if (!width) {2572return;2573}2574this.width = width;2575this.height = height;2576this.target = target;2577this.lastShapeId = null;2578if (target[0]) {2579target = target[0];2580}2581$.data(target, '_jqs_vcanvas', this);2582},25832584drawLine: function (x1, y1, x2, y2, lineColor, lineWidth) {2585return this.drawShape([[x1, y1], [x2, y2]], lineColor, lineWidth);2586},25872588drawShape: function (path, lineColor, fillColor, lineWidth) {2589return this._genShape('Shape', [path, lineColor, fillColor, lineWidth]);2590},25912592drawCircle: function (x, y, radius, lineColor, fillColor, lineWidth) {2593return this._genShape('Circle', [x, y, radius, lineColor, fillColor, lineWidth]);2594},25952596drawPieSlice: function (x, y, radius, startAngle, endAngle, lineColor, fillColor) {2597return this._genShape('PieSlice', [x, y, radius, startAngle, endAngle, lineColor, fillColor]);2598},25992600drawRect: function (x, y, width, height, lineColor, fillColor) {2601return this._genShape('Rect', [x, y, width, height, lineColor, fillColor]);2602},26032604getElement: function () {2605return this.canvas;2606},26072608/**2609* Return the most recently inserted shape id2610*/2611getLastShapeId: function () {2612return this.lastShapeId;2613},26142615/**2616* Clear and reset the canvas2617*/2618reset: function () {2619alert('reset not implemented');2620},26212622_insert: function (el, target) {2623$(target).html(el);2624},26252626/**2627* Calculate the pixel dimensions of the canvas2628*/2629_calculatePixelDims: function (width, height, canvas) {2630// XXX This should probably be a configurable option2631var match;2632match = this._pxregex.exec(height);2633if (match) {2634this.pixelHeight = match[1];2635} else {2636this.pixelHeight = $(canvas).height();2637}2638match = this._pxregex.exec(width);2639if (match) {2640this.pixelWidth = match[1];2641} else {2642this.pixelWidth = $(canvas).width();2643}2644//var ratio = window.hasOwnProperty('devicePixelRatio') ? window.devicePixelRatio : 1;2645//this.pixelWidth *= ratio;2646//this.pixelHeight *= ratio;2647//if enabled comment line 27192648},26492650/**2651* Generate a shape object and id for later rendering2652*/2653_genShape: function (shapetype, shapeargs) {2654var id = shapeCount++;2655shapeargs.unshift(id);2656return new VShape(this, id, shapetype, shapeargs);2657},26582659/**2660* Add a shape to the end of the render queue2661*/2662appendShape: function (shape) {2663alert('appendShape not implemented');2664},26652666/**2667* Replace one shape with another2668*/2669replaceWithShape: function (shapeid, shape) {2670alert('replaceWithShape not implemented');2671},26722673/**2674* Insert one shape after another in the render queue2675*/2676insertAfterShape: function (shapeid, shape) {2677alert('insertAfterShape not implemented');2678},26792680/**2681* Remove a shape from the queue2682*/2683removeShapeId: function (shapeid) {2684alert('removeShapeId not implemented');2685},26862687/**2688* Find a shape at the specified x/y co-ordinates2689*/2690getShapeAt: function (el, x, y) {2691alert('getShapeAt not implemented');2692},26932694/**2695* Render all queued shapes onto the canvas2696*/2697render: function () {2698alert('render not implemented');2699}2700});27012702VCanvas_canvas = createClass(VCanvas_base, {2703init: function (width, height, target, interact) {2704VCanvas_canvas._super.init.call(this, width, height, target);2705this.canvas = document.createElement('canvas');2706if (target[0]) {2707target = target[0];2708}2709$.data(target, '_jqs_vcanvas', this);2710$(this.canvas).css({ display: 'inline-block', width: width, height: height, verticalAlign: 'top' });2711this._insert(this.canvas, target);2712this._calculatePixelDims(width, height, this.canvas);2713this.canvas.width = this.pixelWidth;2714this.canvas.height = this.pixelHeight;2715this.interact = interact;2716this.shapes = {};2717this.shapeseq = [];2718this.currentTargetShapeId = undefined;2719$(this.canvas).css({width: this.pixelWidth, height: this.pixelHeight});2720},27212722_getContext: function (lineColor, fillColor, lineWidth) {2723var context = this.canvas.getContext('2d');2724if (lineColor !== undefined) {2725context.strokeStyle = lineColor;2726}2727context.lineWidth = lineWidth === undefined ? 1 : lineWidth;2728if (fillColor !== undefined) {2729context.fillStyle = fillColor;2730}2731context;2732return context;2733},27342735reset: function () {2736var context = this._getContext();2737context.clearRect(0, 0, this.pixelWidth, this.pixelHeight);2738this.shapes = {};2739this.shapeseq = [];2740this.currentTargetShapeId = undefined;2741},27422743_drawShape: function (shapeid, path, lineColor, fillColor, lineWidth) {2744var context = this._getContext(lineColor, fillColor, lineWidth),2745i, plen;2746context.beginPath();2747context.moveTo(path[0][0] + 0.5, path[0][1] + 0.5);2748for (i = 1, plen = path.length; i < plen; i++) {2749context.lineTo(path[i][0] + 0.5, path[i][1] + 0.5); // the 0.5 offset gives us crisp pixel-width lines2750}2751if (lineColor !== undefined) {2752context.stroke();2753}2754if (fillColor !== undefined) {2755context.fill();2756}2757if (this.targetX !== undefined && this.targetY !== undefined &&2758context.isPointInPath(this.targetX, this.targetY)) {2759this.currentTargetShapeId = shapeid;2760}2761},27622763_drawCircle: function (shapeid, x, y, radius, lineColor, fillColor, lineWidth) {2764var context = this._getContext(lineColor, fillColor, lineWidth);2765context.beginPath();2766context.arc(x, y, radius, 0, 2 * Math.PI, false);2767if (this.targetX !== undefined && this.targetY !== undefined &&2768context.isPointInPath(this.targetX, this.targetY)) {2769this.currentTargetShapeId = shapeid;2770}2771if (lineColor !== undefined) {2772context.stroke();2773}2774if (fillColor !== undefined) {2775context.fill();2776}2777},27782779_drawPieSlice: function (shapeid, x, y, radius, startAngle, endAngle, lineColor, fillColor) {2780var context = this._getContext(lineColor, fillColor);2781context.beginPath();2782context.moveTo(x, y);2783context.arc(x, y, radius, startAngle, endAngle, false);2784context.lineTo(x, y);2785context.closePath();2786if (lineColor !== undefined) {2787context.stroke();2788}2789if (fillColor) {2790context.fill();2791}2792if (this.targetX !== undefined && this.targetY !== undefined &&2793context.isPointInPath(this.targetX, this.targetY)) {2794this.currentTargetShapeId = shapeid;2795}2796},27972798_drawRect: function (shapeid, x, y, width, height, lineColor, fillColor) {2799return this._drawShape(shapeid, [[x, y], [x + width, y], [x + width, y + height], [x, y + height], [x, y]], lineColor, fillColor);2800},28012802appendShape: function (shape) {2803this.shapes[shape.id] = shape;2804this.shapeseq.push(shape.id);2805this.lastShapeId = shape.id;2806return shape.id;2807},28082809replaceWithShape: function (shapeid, shape) {2810var shapeseq = this.shapeseq,2811i;2812this.shapes[shape.id] = shape;2813for (i = shapeseq.length; i--;) {2814if (shapeseq[i] == shapeid) {2815shapeseq[i] = shape.id;2816}2817}2818delete this.shapes[shapeid];2819},28202821replaceWithShapes: function (shapeids, shapes) {2822var shapeseq = this.shapeseq,2823shapemap = {},2824sid, i, first;28252826for (i = shapeids.length; i--;) {2827shapemap[shapeids[i]] = true;2828}2829for (i = shapeseq.length; i--;) {2830sid = shapeseq[i];2831if (shapemap[sid]) {2832shapeseq.splice(i, 1);2833delete this.shapes[sid];2834first = i;2835}2836}2837for (i = shapes.length; i--;) {2838shapeseq.splice(first, 0, shapes[i].id);2839this.shapes[shapes[i].id] = shapes[i];2840}28412842},28432844insertAfterShape: function (shapeid, shape) {2845var shapeseq = this.shapeseq,2846i;2847for (i = shapeseq.length; i--;) {2848if (shapeseq[i] === shapeid) {2849shapeseq.splice(i + 1, 0, shape.id);2850this.shapes[shape.id] = shape;2851return;2852}2853}2854},28552856removeShapeId: function (shapeid) {2857var shapeseq = this.shapeseq,2858i;2859for (i = shapeseq.length; i--;) {2860if (shapeseq[i] === shapeid) {2861shapeseq.splice(i, 1);2862break;2863}2864}2865delete this.shapes[shapeid];2866},28672868getShapeAt: function (el, x, y) {2869this.targetX = x;2870this.targetY = y;2871this.render();2872return this.currentTargetShapeId;2873},28742875render: function () {2876var shapeseq = this.shapeseq,2877shapes = this.shapes,2878shapeCount = shapeseq.length,2879context = this._getContext(),2880shapeid, shape, i;2881context.clearRect(0, 0, this.pixelWidth, this.pixelHeight);2882for (i = 0; i < shapeCount; i++) {2883shapeid = shapeseq[i];2884shape = shapes[shapeid];2885this['_draw' + shape.type].apply(this, shape.args);2886}2887if (!this.interact) {2888// not interactive so no need to keep the shapes array2889this.shapes = {};2890this.shapeseq = [];2891}2892}28932894});28952896VCanvas_vml = createClass(VCanvas_base, {2897init: function (width, height, target) {2898var groupel;2899VCanvas_vml._super.init.call(this, width, height, target);2900if (target[0]) {2901target = target[0];2902}2903$.data(target, '_jqs_vcanvas', this);2904this.canvas = document.createElement('span');2905$(this.canvas).css({ display: 'inline-block', position: 'relative', overflow: 'hidden', width: width, height: height, margin: '0px', padding: '0px', verticalAlign: 'top'});2906this._insert(this.canvas, target);2907this._calculatePixelDims(width, height, this.canvas);2908this.canvas.width = this.pixelWidth;2909this.canvas.height = this.pixelHeight;2910groupel = '<v:group coordorigin="0 0" coordsize="' + this.pixelWidth + ' ' + this.pixelHeight + '"' +2911' style="position:absolute;top:0;left:0;width:' + this.pixelWidth + 'px;height=' + this.pixelHeight + 'px;"></v:group>';2912this.canvas.insertAdjacentHTML('beforeEnd', groupel);2913this.group = $(this.canvas).children()[0];2914this.rendered = false;2915this.prerender = '';2916},29172918_drawShape: function (shapeid, path, lineColor, fillColor, lineWidth) {2919var vpath = [],2920initial, stroke, fill, closed, vel, plen, i;2921for (i = 0, plen = path.length; i < plen; i++) {2922vpath[i] = '' + (path[i][0]) + ',' + (path[i][1]);2923}2924initial = vpath.splice(0, 1);2925lineWidth = lineWidth === undefined ? 1 : lineWidth;2926stroke = lineColor === undefined ? ' stroked="false" ' : ' strokeWeight="' + lineWidth + 'px" strokeColor="' + lineColor + '" ';2927fill = fillColor === undefined ? ' filled="false"' : ' fillColor="' + fillColor + '" filled="true" ';2928closed = vpath[0] === vpath[vpath.length - 1] ? 'x ' : '';2929vel = '<v:shape coordorigin="0 0" coordsize="' + this.pixelWidth + ' ' + this.pixelHeight + '" ' +2930' id="jqsshape' + shapeid + '" ' +2931stroke +2932fill +2933' style="position:absolute;left:0px;top:0px;height:' + this.pixelHeight + 'px;width:' + this.pixelWidth + 'px;padding:0px;margin:0px;" ' +2934' path="m ' + initial + ' l ' + vpath.join(', ') + ' ' + closed + 'e">' +2935' </v:shape>';2936return vel;2937},29382939_drawCircle: function (shapeid, x, y, radius, lineColor, fillColor, lineWidth) {2940var stroke, fill, vel;2941x -= radius;2942y -= radius;2943stroke = lineColor === undefined ? ' stroked="false" ' : ' strokeWeight="' + lineWidth + 'px" strokeColor="' + lineColor + '" ';2944fill = fillColor === undefined ? ' filled="false"' : ' fillColor="' + fillColor + '" filled="true" ';2945vel = '<v:oval ' +2946' id="jqsshape' + shapeid + '" ' +2947stroke +2948fill +2949' style="position:absolute;top:' + y + 'px; left:' + x + 'px; width:' + (radius * 2) + 'px; height:' + (radius * 2) + 'px"></v:oval>';2950return vel;29512952},29532954_drawPieSlice: function (shapeid, x, y, radius, startAngle, endAngle, lineColor, fillColor) {2955var vpath, startx, starty, endx, endy, stroke, fill, vel;2956if (startAngle === endAngle) {2957return ''; // VML seems to have problem when start angle equals end angle.2958}2959if ((endAngle - startAngle) === (2 * Math.PI)) {2960startAngle = 0.0; // VML seems to have a problem when drawing a full circle that doesn't start 02961endAngle = (2 * Math.PI);2962}29632964startx = x + Math.round(Math.cos(startAngle) * radius);2965starty = y + Math.round(Math.sin(startAngle) * radius);2966endx = x + Math.round(Math.cos(endAngle) * radius);2967endy = y + Math.round(Math.sin(endAngle) * radius);29682969if (startx === endx && starty === endy) {2970if ((endAngle - startAngle) < Math.PI) {2971// Prevent very small slices from being mistaken as a whole pie2972return '';2973}2974// essentially going to be the entire circle, so ignore startAngle2975startx = endx = x + radius;2976starty = endy = y;2977}29782979if (startx === endx && starty === endy && (endAngle - startAngle) < Math.PI) {2980return '';2981}29822983vpath = [x - radius, y - radius, x + radius, y + radius, startx, starty, endx, endy];2984stroke = lineColor === undefined ? ' stroked="false" ' : ' strokeWeight="1px" strokeColor="' + lineColor + '" ';2985fill = fillColor === undefined ? ' filled="false"' : ' fillColor="' + fillColor + '" filled="true" ';2986vel = '<v:shape coordorigin="0 0" coordsize="' + this.pixelWidth + ' ' + this.pixelHeight + '" ' +2987' id="jqsshape' + shapeid + '" ' +2988stroke +2989fill +2990' style="position:absolute;left:0px;top:0px;height:' + this.pixelHeight + 'px;width:' + this.pixelWidth + 'px;padding:0px;margin:0px;" ' +2991' path="m ' + x + ',' + y + ' wa ' + vpath.join(', ') + ' x e">' +2992' </v:shape>';2993return vel;2994},29952996_drawRect: function (shapeid, x, y, width, height, lineColor, fillColor) {2997return this._drawShape(shapeid, [[x, y], [x, y + height], [x + width, y + height], [x + width, y], [x, y]], lineColor, fillColor);2998},29993000reset: function () {3001this.group.innerHTML = '';3002},30033004appendShape: function (shape) {3005var vel = this['_draw' + shape.type].apply(this, shape.args);3006if (this.rendered) {3007this.group.insertAdjacentHTML('beforeEnd', vel);3008} else {3009this.prerender += vel;3010}3011this.lastShapeId = shape.id;3012return shape.id;3013},30143015replaceWithShape: function (shapeid, shape) {3016var existing = $('#jqsshape' + shapeid),3017vel = this['_draw' + shape.type].apply(this, shape.args);3018existing[0].outerHTML = vel;3019},30203021replaceWithShapes: function (shapeids, shapes) {3022// replace the first shapeid with all the new shapes then toast the remaining old shapes3023var existing = $('#jqsshape' + shapeids[0]),3024replace = '',3025slen = shapes.length,3026i;3027for (i = 0; i < slen; i++) {3028replace += this['_draw' + shapes[i].type].apply(this, shapes[i].args);3029}3030existing[0].outerHTML = replace;3031for (i = 1; i < shapeids.length; i++) {3032$('#jqsshape' + shapeids[i]).remove();3033}3034},30353036insertAfterShape: function (shapeid, shape) {3037var existing = $('#jqsshape' + shapeid),3038vel = this['_draw' + shape.type].apply(this, shape.args);3039existing[0].insertAdjacentHTML('afterEnd', vel);3040},30413042removeShapeId: function (shapeid) {3043var existing = $('#jqsshape' + shapeid);3044this.group.removeChild(existing[0]);3045},30463047getShapeAt: function (el, x, y) {3048var shapeid = el.id.substr(8);3049return shapeid;3050},30513052render: function () {3053if (!this.rendered) {3054// batch the intial render into a single repaint3055this.group.innerHTML = this.prerender;3056this.rendered = true;3057}3058}3059});30603061}))}(document, Math));30623063