PageSourceSearch

https://frontiersemi.com/Designs/admin/js/jquery.sparkline.js

js frontiersemi.com collected 2026-10-10 00:31:08 UTC 123,546 bytes, 3,057 lines download raw bytes

1/**
2*
3* jquery.sparkline.js
4*
5* v2.1.2
6* (c) Splunk, Inc
7* Contact: Gareth Watts ([email protected])
8* http://omnipotent.net/jquery.sparkline/
9*
10* Generates inline sparkline charts from data supplied either to the method
11* or inline in HTML
12*
13* Compatible with Internet Explorer 6.0+ and modern browsers equipped with the canvas tag
14* (Firefox 2.0+, Safari, Opera, etc)
15*
16* License: New BSD License
17*
18* Copyright (c) 2012, Splunk Inc.
19* All rights reserved.
20*
21* Redistribution and use in source and binary forms, with or without modification,
22* are permitted provided that the following conditions are met:
23*
24*     * Redistributions of source code must retain the above copyright notice,
25*       this list of conditions and the following disclaimer.
26*     * Redistributions in binary form must reproduce the above copyright notice,
27*       this list of conditions and the following disclaimer in the documentation
28*       and/or other materials provided with the distribution.
29*     * Neither the name of Splunk Inc nor the names of its contributors may
30*       be used to endorse or promote products derived from this software without
31*       specific prior written permission.
32*
33* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
34* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
35* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
36* SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
37* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
38* OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
39* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
40* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
41* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
42*
43*
44* Usage:
45*  $(selector).sparkline(values, options)
46*
47* If values is undefined or set to 'html' then the data values are read from the specified tag:
48*   <p>Sparkline: <span class="sparkline">1,4,6,6,8,5,3,5</span></p>
49*   $('.sparkline').sparkline();
50* There must be no spaces in the enclosed data set
51*
52* Otherwise values must be an array of numbers or null values
53*    <p>Sparkline: <span id="sparkline1">This text replaced if the browser is compatible</span></p>
54*    $('#sparkline1').sparkline([1,4,6,6,8,5,3,5])
55*    $('#sparkline2').sparkline([1,4,6,null,null,5,3,5])
56*
57* Values can also be specified in an HTML comment, or as a values attribute:
58*    <p>Sparkline: <span class="sparkline"><!--1,4,6,6,8,5,3,5 --></span></p>
59*    <p>Sparkline: <span class="sparkline" values="1,4,6,6,8,5,3,5"></span></p>
60*    $('.sparkline').sparkline();
61*
62* For line charts, x values can also be specified:
63*   <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>
64*    $('#sparkline1').sparkline([ [1,1], [2.7,4], [3.4,6], [5,6], [6,8], [8.7,5], [9,3], [10,5] ])
65*
66* By default, options should be passed in as teh second argument to the sparkline function:
67*   $('.sparkline').sparkline([1,2,3,4], {type: 'bar'})
68*
69* Options can also be set by passing them on the tag itself.  This feature is disabled by default though
70* as there's a slight performance overhead:
71*   $('.sparkline').sparkline([1,2,3,4], {enableTagOptions: true})
72*   <p>Sparkline: <span class="sparkline" sparkType="bar" sparkBarColor="red">loading</span></p>
73* Prefix all options supplied as tag attribute with "spark" (configurable by setting tagOptionPrefix)
74*
75* Supported options:
76*   lineColor - Color of the line used for the chart
77*   fillColor - Color used to fill in the chart - Set to '' or false for a transparent chart
78*   width - Width of the chart - Defaults to 3 times the number of values in pixels
79*   height - Height of the chart - Defaults to the height of the containing element
80*   chartRangeMin - Specify the minimum value to use for the Y range of the chart - Defaults to the minimum value supplied
81*   chartRangeMax - Specify the maximum value to use for the Y range of the chart - Defaults to the maximum value supplied
82*   chartRangeClip - Clip out of range values to the max/min specified by chartRangeMin and chartRangeMax
83*   chartRangeMinX - Specify the minimum value to use for the X range of the chart - Defaults to the minimum value supplied
84*   chartRangeMaxX - Specify the maximum value to use for the X range of the chart - Defaults to the maximum value supplied
85*   composite - If true then don't erase any existing chart attached to the tag, but draw
86*           another chart over the top - Note that width and height are ignored if an
87*           existing chart is detected.
88*   tagValuesAttribute - Name of tag attribute to check for data values - Defaults to 'values'
89*   enableTagOptions - Whether to check tags for sparkline options
90*   tagOptionPrefix - Prefix used for options supplied as tag attributes - Defaults to 'spark'
91*   disableHiddenCheck - If set to true, then the plugin will assume that charts will never be drawn into a
92*           hidden dom element, avoding a browser reflow
93*   disableInteraction - If set to true then all mouseover/click interaction behaviour will be disabled,
94*       making the plugin perform much like it did in 1.x
95*   disableTooltips - If set to true then tooltips will be disabled - Defaults to false (tooltips enabled)
96*   disableHighlight - If set to true then highlighting of selected chart elements on mouseover will be disabled
97*       defaults to false (highlights enabled)
98*   highlightLighten - Factor to lighten/darken highlighted chart values by - Defaults to 1.4 for a 40% increase
99*   tooltipContainer - Specify which DOM element the tooltip should be rendered into - defaults to document.body
100*   tooltipClassname - Optional CSS classname to apply to tooltips - If not specified then a default style will be applied
101*   tooltipOffsetX - How many pixels away from the mouse pointer to render the tooltip on the X axis
102*   tooltipOffsetY - How many pixels away from the mouse pointer to render the tooltip on the r axis
103*   tooltipFormatter  - Optional callback that allows you to override the HTML displayed in the tooltip
104*       callback is given arguments of (sparkline, options, fields)
105*   tooltipChartTitle - If specified then the tooltip uses the string specified by this setting as a title
106*   tooltipFormat - A format string or SPFormat object  (or an array thereof for multiple entries)
107*       to control the format of the tooltip
108*   tooltipPrefix - A string to prepend to each field displayed in a tooltip
109*   tooltipSuffix - A string to append to each field displayed in a tooltip
110*   tooltipSkipNull - If true then null values will not have a tooltip displayed (defaults to true)
111*   tooltipValueLookups - An object or range map to map field values to tooltip strings
112*       (eg. to map -1 to "Lost", 0 to "Draw", and 1 to "Win")
113*   numberFormatter - Optional callback for formatting numbers in tooltips
114*   numberDigitGroupSep - Character to use for group separator in numbers "1,234" - Defaults to ","
115*   numberDecimalMark - Character to use for the decimal point when formatting numbers - Defaults to "."
116*   numberDigitGroupCount - Number of digits between group separator - Defaults to 3
117*
118* There are 7 types of sparkline, selected by supplying a "type" option of 'line' (default),
119* 'bar', 'tristate', 'bullet', 'discrete', 'pie' or 'box'
120*    line - Line chart.  Options:
121*       spotColor - Set to '' to not end each line in a circular spot
122*       minSpotColor - If set, color of spot at minimum value
123*       maxSpotColor - If set, color of spot at maximum value
124*       spotRadius - Radius in pixels
125*       lineWidth - Width of line in pixels
126*       normalRangeMin
127*       normalRangeMax - If set draws a filled horizontal bar between these two values marking the "normal"
128*                      or expected range of values
129*       normalRangeColor - Color to use for the above bar
130*       drawNormalOnTop - Draw the normal range above the chart fill color if true
131*       defaultPixelsPerValue - Defaults to 3 pixels of width for each value in the chart
132*       highlightSpotColor - The color to use for drawing a highlight spot on mouseover - Set to null to disable
133*       highlightLineColor - The color to use for drawing a highlight line on mouseover - Set to null to disable
134*       valueSpots - Specify which points to draw spots on, and in which color.  Accepts a range map
135*
136*   bar - Bar chart.  Options:
137*       barColor - Color of bars for postive values
138*       negBarColor - Color of bars for negative values
139*       zeroColor - Color of bars with zero values
140*       nullColor - Color of bars with null values - Defaults to omitting the bar entirely
141*       barWidth - Width of bars in pixels
142*       colorMap - Optional mappnig of values to colors to override the *BarColor values above
143*                  can be an Array of values to control the color of individual bars or a range map
144*                  to specify colors for individual ranges of values
145*       barSpacing - Gap between bars in pixels
146*       zeroAxis - Centers the y-axis around zero if true
147*
148*   tristate - Charts values of win (>0), lose (<0) or draw (=0)
149*       posBarColor - Color of win values
150*       negBarColor - Color of lose values
151*       zeroBarColor - Color of draw values
152*       barWidth - Width of bars in pixels
153*       barSpacing - Gap between bars in pixels
154*       colorMap - Optional mappnig of values to colors to override the *BarColor values above
155*                  can be an Array of values to control the color of individual bars or a range map
156*                  to specify colors for individual ranges of values
157*
158*   discrete - Options:
159*       lineHeight - Height of each line in pixels - Defaults to 30% of the graph height
160*       thesholdValue - Values less than this value will be drawn using thresholdColor instead of lineColor
161*       thresholdColor
162*
163*   bullet - Values for bullet graphs msut be in the order: target, performance, range1, range2, range3, ...
164*       options:
165*       targetColor - The color of the vertical target marker
166*       targetWidth - The width of the target marker in pixels
167*       performanceColor - The color of the performance measure horizontal bar
168*       rangeColors - Colors to use for each qualitative range background color
169*
170*   pie - Pie chart. Options:
171*       sliceColors - An array of colors to use for pie slices
172*       offset - Angle in degrees to offset the first slice - Try -90 or +90
173*       borderWidth - Width of border to draw around the pie chart, in pixels - Defaults to 0 (no border)
174*       borderColor - Color to use for the pie chart border - Defaults to #000
175*
176*   box - Box plot. Options:
177*       raw - Set to true to supply pre-computed plot points as values
178*             values should be: low_outlier, low_whisker, q1, median, q3, high_whisker, high_outlier
179*             When set to false you can supply any number of values and the box plot will
180*             be computed for you.  Default is false.
181*       showOutliers - Set to true (default) to display outliers as circles
182*       outlierIQR - Interquartile range used to determine outliers.  Default 1.5
183*       boxLineColor - Outline color of the box
184*       boxFillColor - Fill color for the box
185*       whiskerColor - Line color used for whiskers
186*       outlierLineColor - Outline color of outlier circles
187*       outlierFillColor - Fill color of the outlier circles
188*       spotRadius - Radius of outlier circles
189*       medianColor - Line color of the median line
190*       target - Draw a target cross hair at the supplied value (default undefined)
191*
192*
193*
194*   Examples:
195*   $('#sparkline1').sparkline(myvalues, { lineColor: '#f00', fillColor: false });
196*   $('.barsparks').sparkline('html', { type:'bar', height:'40px', barWidth:5 });
197*   $('#tristate').sparkline([1,1,-1,1,0,0,-1], { type:'tristate' }):
198*   $('#discrete').sparkline([1,3,4,5,5,3,4,5], { type:'discrete' });
199*   $('#bullet').sparkline([10,12,12,9,7], { type:'bullet' });
200*   $('#pie').sparkline([1,1,2], { type:'pie' });
201*/
202
203/*jslint regexp: true, browser: true, jquery: true, white: true, nomen: false, plusplus: false, maxerr: 500, indent: 4 */
204
205(function(document, Math, undefined) { // performance/minified-size optimization
206(function(factory) {
207    if(typeof define === 'function' && define.amd) {
208        define(['jquery'], factory);
209    } else if (jQuery && !jQuery.fn.sparkline) {
210        factory(jQuery);
211    }
212}
213(function($) {
214    'use strict';
215
216    var UNSET_OPTION = {},
217        getDefaults, createClass, SPFormat, clipval, quartile, normalizeValue, normalizeValues,
218        remove, isNumber, all, sum, addCSS, ensureArray, formatNumber, RangeMap,
219        MouseHandler, Tooltip, barHighlightMixin,
220        line, bar, tristate, discrete, bullet, pie, box, defaultStyles, initStyles,
221        VShape, VCanvas_base, VCanvas_canvas, VCanvas_vml, pending, shapeCount = 0;
222
223    /**
224     * Default configuration settings
225     */
226    getDefaults = function () {
227        return {
228            // Settings common to most/all chart types
229            common: {
230                type: 'line',
231                lineColor: '#00f',
232                fillColor: '#cdf',
233                defaultPixelsPerValue: 3,
234                width: 'auto',
235                height: 'auto',
236                composite: false,
237                tagValuesAttribute: 'values',
238                tagOptionsPrefix: 'spark',
239                enableTagOptions: false,
240                enableHighlight: true,
241                highlightLighten: 1.4,
242                tooltipSkipNull: true,
243                tooltipPrefix: '',
244                tooltipSuffix: '',
vendor: 12,440 bytes, lines 245-592
245                disableHiddenCheck: false,
246                numberFormatter: false,
247                numberDigitGroupCount: 3,
248                numberDigitGroupSep: ',',
249                numberDecimalMark: '.',
250                disableTooltips: false,
251                disableInteraction: false
252            },
253            // Defaults for line charts
254            line: {
255                spotColor: '#f80',
256                highlightSpotColor: '#5f5',
257                highlightLineColor: '#f22',
258                spotRadius: 1.5,
259                minSpotColor: '#f80',
260                maxSpotColor: '#f80',
261                lineWidth: 1,
262                normalRangeMin: undefined,
263                normalRangeMax: undefined,
264                normalRangeColor: '#ccc',
265                drawNormalOnTop: false,
266                chartRangeMin: undefined,
267                chartRangeMax: undefined,
268                chartRangeMinX: undefined,
269                chartRangeMaxX: undefined,
270                tooltipFormat: new SPFormat('<span style="color: {{color}}">&#9679;</span> {{prefix}}{{y}}{{suffix}}')
271            },
272            // Defaults for bar charts
273            bar: {
274                barColor: '#3366cc',
275                negBarColor: '#f44',
276                stackedBarColor: ['#3366cc', '#dc3912', '#ff9900', '#109618', '#66aa00',
277                    '#dd4477', '#0099c6', '#990099'],
278                zeroColor: undefined,
279                nullColor: undefined,
280                zeroAxis: true,
281                barWidth: 4,
282                barSpacing: 1,
283                chartRangeMax: undefined,
284                chartRangeMin: undefined,
285                chartRangeClip: false,
286                colorMap: undefined,
287                tooltipFormat: new SPFormat('<span style="color: {{color}}">&#9679;</span> {{prefix}}{{value}}{{suffix}}')
288            },
289            // Defaults for tristate charts
290            tristate: {
291                barWidth: 4,
292                barSpacing: 1,
293                posBarColor: '#6f6',
294                negBarColor: '#f44',
295                zeroBarColor: '#999',
296                colorMap: {},
297                tooltipFormat: new SPFormat('<span style="color: {{color}}">&#9679;</span> {{value:map}}'),
298                tooltipValueLookups: { map: { '-1': 'Loss', '0': 'Draw', '1': 'Win' } }
299            },
300            // Defaults for discrete charts
301            discrete: {
302                lineHeight: 'auto',
303                thresholdColor: undefined,
304                thresholdValue: 0,
305                chartRangeMax: undefined,
306                chartRangeMin: undefined,
307                chartRangeClip: false,
308                tooltipFormat: new SPFormat('{{prefix}}{{value}}{{suffix}}')
309            },
310            // Defaults for bullet charts
311            bullet: {
312                targetColor: '#f33',
313                targetWidth: 3, // width of the target bar in pixels
314                performanceColor: '#33f',
315                rangeColors: ['#d3dafe', '#a8b6ff', '#7f94ff'],
316                base: undefined, // set this to a number to change the base start number
317                tooltipFormat: new SPFormat('{{fieldkey:fields}} - {{value}}'),
318                tooltipValueLookups: { fields: {r: 'Range', p: 'Performance', t: 'Target'} }
319            },
320            // Defaults for pie charts
321            pie: {
322                offset: 0,
323                sliceColors: ['#3366cc', '#dc3912', '#ff9900', '#109618', '#66aa00',
324                    '#dd4477', '#0099c6', '#990099'],
325                borderWidth: 0,
326                borderColor: '#000',
327                tooltipFormat: new SPFormat('<span style="color: {{color}}">&#9679;</span> {{value}} ({{percent.1}}%)')
328            },
329            // Defaults for box plots
330            box: {
331                raw: false,
332                boxLineColor: '#000',
333                boxFillColor: '#cdf',
334                whiskerColor: '#000',
335                outlierLineColor: '#333',
336                outlierFillColor: '#fff',
337                medianColor: '#f00',
338                showOutliers: true,
339                outlierIQR: 1.5,
340                spotRadius: 1.5,
341                target: undefined,
342                targetColor: '#4a2',
343                chartRangeMax: undefined,
344                chartRangeMin: undefined,
345                tooltipFormat: new SPFormat('{{field:fields}}: {{value}}'),
346                tooltipFormatFieldlistKey: 'field',
347                tooltipValueLookups: { fields: { lq: 'Lower Quartile', med: 'Median',
348                    uq: 'Upper Quartile', lo: 'Left Outlier', ro: 'Right Outlier',
349                    lw: 'Left Whisker', rw: 'Right Whisker'} }
350            }
351        };
352    };
353
354    // You can have tooltips use a css class other than jqstooltip by specifying tooltipClassname
355    // tooltip modified by westilian:jaman
356    defaultStyles = '.jqstooltip { ' +
357            'position: absolute;' +
358            'left: 0px;' +
359            'top: 0px;' +
360            'display: block;' +
361            'visibility: hidden;' +
362            'background: rgb(0, 0, 0) transparent;' +
363            'background-color: rgba(0,0,0,0.6);' +
364            'filter:progid:DXImageTransform.Microsoft.gradient(startColorstr=#99000000, endColorstr=#99000000);' +
365            '-ms-filter: "progid:DXImageTransform.Microsoft.gradient(startColorstr=#99000000, endColorstr=#99000000)";' +
366            'color: white;' +
367            'font: 10px arial, san serif;' +
368            'text-align: left;' +
369            'white-space: nowrap;' +
370            
371            'border: 1px solid white;' +
372            'z-index: 10000;' +
373            '}' +
374            '.jqsfield { ' +
375            'color: white;' +
376            'padding: 5px 5px 8px 5px;' +
377            'font: 10px arial, san serif;' +
378            'text-align: left;' +
379            '}';
380
381    /**
382     * Utilities
383     */
384
385    createClass = function (/* [baseclass, [mixin, ...]], definition */) {
386        var Class, args;
387        Class = function () {
388            this.init.apply(this, arguments);
389        };
390        if (arguments.length > 1) {
391            if (arguments[0]) {
392                Class.prototype = $.extend(new arguments[0](), arguments[arguments.length - 1]);
393                Class._super = arguments[0].prototype;
394            } else {
395                Class.prototype = arguments[arguments.length - 1];
396            }
397            if (arguments.length > 2) {
398                args = Array.prototype.slice.call(arguments, 1, -1);
399                args.unshift(Class.prototype);
400                $.extend.apply($, args);
401            }
402        } else {
403            Class.prototype = arguments[0];
404        }
405        Class.prototype.cls = Class;
406        return Class;
407    };
408
409    /**
410     * Wraps a format string for tooltips
411     * {{x}}
412     * {{x.2}
413     * {{x:months}}
414     */
415    $.SPFormatClass = SPFormat = createClass({
416        fre: /\{\{([\w.]+?)(:(.+?))?\}\}/g,
417        precre: /(\w+)\.(\d+)/,
418
419        init: function (format, fclass) {
420            this.format = format;
421            this.fclass = fclass;
422        },
423
424        render: function (fieldset, lookups, options) {
425            var self = this,
426                fields = fieldset,
427                match, token, lookupkey, fieldvalue, prec;
428            return this.format.replace(this.fre, function () {
429                var lookup;
430                token = arguments[1];
431                lookupkey = arguments[3];
432                match = self.precre.exec(token);
433                if (match) {
434                    prec = match[2];
435                    token = match[1];
436                } else {
437                    prec = false;
438                }
439                fieldvalue = fields[token];
440                if (fieldvalue === undefined) {
441                    return '';
442                }
443                if (lookupkey && lookups && lookups[lookupkey]) {
444                    lookup = lookups[lookupkey];
445                    if (lookup.get) { // RangeMap
446                        return lookups[lookupkey].get(fieldvalue) || fieldvalue;
447                    } else {
448                        return lookups[lookupkey][fieldvalue] || fieldvalue;
449                    }
450                }
451                if (isNumber(fieldvalue)) {
452                    if (options.get('numberFormatter')) {
453                        fieldvalue = options.get('numberFormatter')(fieldvalue);
454                    } else {
455                        fieldvalue = formatNumber(fieldvalue, prec,
456                            options.get('numberDigitGroupCount'),
457                            options.get('numberDigitGroupSep'),
458                            options.get('numberDecimalMark'));
459                    }
460                }
461                return fieldvalue;
462            });
463        }
464    });
465
466    // convience method to avoid needing the new operator
467    $.spformat = function(format, fclass) {
468        return new SPFormat(format, fclass);
469    };
470
471    clipval = function (val, min, max) {
472        if (val < min) {
473            return min;
474        }
475        if (val > max) {
476            return max;
477        }
478        return val;
479    };
480
481    quartile = function (values, q) {
482        var vl;
483        if (q === 2) {
484            vl = Math.floor(values.length / 2);
485            return values.length % 2 ? values[vl] : (values[vl-1] + values[vl]) / 2;
486        } else {
487            if (values.length % 2 ) { // odd
488                vl = (values.length * q + q) / 4;
489                return vl % 1 ? (values[Math.floor(vl)] + values[Math.floor(vl) - 1]) / 2 : values[vl-1];
490            } else { //even
491                vl = (values.length * q + 2) / 4;
492                return vl % 1 ? (values[Math.floor(vl)] + values[Math.floor(vl) - 1]) / 2 :  values[vl-1];
493
494            }
495        }
496    };
497
498    normalizeValue = function (val) {
499        var nf;
500        switch (val) {
501            case 'undefined':
502                val = undefined;
503                break;
504            case 'null':
505                val = null;
506                break;
507            case 'true':
508                val = true;
509                break;
510            case 'false':
511                val = false;
512                break;
513            default:
514                nf = parseFloat(val);
515                if (val == nf) {
516                    val = nf;
517                }
518        }
519        return val;
520    };
521
522    normalizeValues = function (vals) {
523        var i, result = [];
524        for (i = vals.length; i--;) {
525            result[i] = normalizeValue(vals[i]);
526        }
527        return result;
528    };
529
530    remove = function (vals, filter) {
531        var i, vl, result = [];
532        for (i = 0, vl = vals.length; i < vl; i++) {
533            if (vals[i] !== filter) {
534                result.push(vals[i]);
535            }
536        }
537        return result;
538    };
539
540    isNumber = function (num) {
541        return !isNaN(parseFloat(num)) && isFinite(num);
542    };
543
544    formatNumber = function (num, prec, groupsize, groupsep, decsep) {
545        var p, i;
546        num = (prec === false ? parseFloat(num).toString() : num.toFixed(prec)).split('');
547        p = (p = $.inArray('.', num)) < 0 ? num.length : p;
548        if (p < num.length) {
549            num[p] = decsep;
550        }
551        for (i = p - groupsize; i > 0; i -= groupsize) {
552            num.splice(i, 0, groupsep);
553        }
554        return num.join('');
555    };
556
557    // determine if all values of an array match a value
558    // returns true if the array is empty
559    all = function (val, arr, ignoreNull) {
560        var i;
561        for (i = arr.length; i--; ) {
562            if (ignoreNull && arr[i] === null) continue;
563            if (arr[i] !== val) {
564                return false;
565            }
566        }
567        return true;
568    };
569
570    // sums the numeric values in an array, ignoring other values
571    sum = function (vals) {
572        var total = 0, i;
573        for (i = vals.length; i--;) {
574            total += typeof vals[i] === 'number' ? vals[i] : 0;
575        }
576        return total;
577    };
578
579    ensureArray = function (val) {
580        return $.isArray(val) ? val : [val];
581    };
582
583    // http://paulirish.com/2008/bookmarklet-inject-new-css-rules/
584    addCSS = function(css) {
585        var tag;
586        //if ('\v' == 'v') /* ie only */ {
587        if (document.createStyleSheet) {
588            document.createStyleSheet().cssText = css;
589        } else {
590            tag = document.createElement('style');
591            tag.type = 'text/css';
592            document.getElementsByTagName('head')[0].appendChild(tag);
vendor: 69,154 bytes, lines 592-2346
592
593            tag[(typeof document.body.style.WebkitAppearance == 'string') /* webkit only */ ? 'innerText' : 'innerHTML'] = css;
594        }
595    };
596
597    // Provide a cross-browser interface to a few simple drawing primitives
598    $.fn.simpledraw = function (width, height, useExisting, interact) {
599        var target, mhandler;
600        if (useExisting && (target = this.data('_jqs_vcanvas'))) {
601            return target;
602        }
603
604        if ($.fn.sparkline.canvas === false) {
605            // We've already determined that neither Canvas nor VML are available
606            return false;
607
608        } else if ($.fn.sparkline.canvas === undefined) {
609            // No function defined yet -- need to see if we support Canvas or VML
610            var el = document.createElement('canvas');
611            if (!!(el.getContext && el.getContext('2d'))) {
612                // Canvas is available
613                $.fn.sparkline.canvas = function(width, height, target, interact) {
614                    return new VCanvas_canvas(width, height, target, interact);
615                };
616            } else if (document.namespaces && !document.namespaces.v) {
617                // VML is available
618                document.namespaces.add('v', 'urn:schemas-microsoft-com:vml', '#default#VML');
619                $.fn.sparkline.canvas = function(width, height, target, interact) {
620                    return new VCanvas_vml(width, height, target);
621                };
622            } else {
623                // Neither Canvas nor VML are available
624                $.fn.sparkline.canvas = false;
625                return false;
626            }
627        }
628
629        if (width === undefined) {
630            width = $(this).innerWidth();
631        }
632        if (height === undefined) {
633            height = $(this).innerHeight();
634        }
635
636        target = $.fn.sparkline.canvas(width, height, this, interact);
637
638        mhandler = $(this).data('_jqs_mhandler');
639        if (mhandler) {
640            mhandler.registerCanvas(target);
641        }
642        return target;
643    };
644
645    $.fn.cleardraw = function () {
646        var target = this.data('_jqs_vcanvas');
647        if (target) {
648            target.reset();
649        }
650    };
651
652    $.RangeMapClass = RangeMap = createClass({
653        init: function (map) {
654            var key, range, rangelist = [];
655            for (key in map) {
656                if (map.hasOwnProperty(key) && typeof key === 'string' && key.indexOf(':') > -1) {
657                    range = key.split(':');
658                    range[0] = range[0].length === 0 ? -Infinity : parseFloat(range[0]);
659                    range[1] = range[1].length === 0 ? Infinity : parseFloat(range[1]);
660                    range[2] = map[key];
661                    rangelist.push(range);
662                }
663            }
664            this.map = map;
665            this.rangelist = rangelist || false;
666        },
667
668        get: function (value) {
669            var rangelist = this.rangelist,
670                i, range, result;
671            if ((result = this.map[value]) !== undefined) {
672                return result;
673            }
674            if (rangelist) {
675                for (i = rangelist.length; i--;) {
676                    range = rangelist[i];
677                    if (range[0] <= value && range[1] >= value) {
678                        return range[2];
679                    }
680                }
681            }
682            return undefined;
683        }
684    });
685
686    // Convenience function
687    $.range_map = function(map) {
688        return new RangeMap(map);
689    };
690
691    MouseHandler = createClass({
692        init: function (el, options) {
693            var $el = $(el);
694            this.$el = $el;
695            this.options = options;
696            this.currentPageX = 0;
697            this.currentPageY = 0;
698            this.el = el;
699            this.splist = [];
700            this.tooltip = null;
701            this.over = false;
702            this.displayTooltips = !options.get('disableTooltips');
703            this.highlightEnabled = !options.get('disableHighlight');
704        },
705
706        registerSparkline: function (sp) {
707            this.splist.push(sp);
708            if (this.over) {
709                this.updateDisplay();
710            }
711        },
712
713        registerCanvas: function (canvas) {
714            var $canvas = $(canvas.canvas);
715            this.canvas = canvas;
716            this.$canvas = $canvas;
717            $canvas.mouseenter($.proxy(this.mouseenter, this));
718            $canvas.mouseleave($.proxy(this.mouseleave, this));
719            $canvas.click($.proxy(this.mouseclick, this));
720        },
721
722        reset: function (removeTooltip) {
723            this.splist = [];
724            if (this.tooltip && removeTooltip) {
725                this.tooltip.remove();
726                this.tooltip = undefined;
727            }
728        },
729
730        mouseclick: function (e) {
731            var clickEvent = $.Event('sparklineClick');
732            clickEvent.originalEvent = e;
733            clickEvent.sparklines = this.splist;
734            this.$el.trigger(clickEvent);
735        },
736
737        mouseenter: function (e) {
738            $(document.body).unbind('mousemove.jqs');
739            $(document.body).bind('mousemove.jqs', $.proxy(this.mousemove, this));
740            this.over = true;
741            this.currentPageX = e.pageX;
742            this.currentPageY = e.pageY;
743            this.currentEl = e.target;
744            if (!this.tooltip && this.displayTooltips) {
745                this.tooltip = new Tooltip(this.options);
746                this.tooltip.updatePosition(e.pageX, e.pageY);
747            }
748            this.updateDisplay();
749        },
750
751        mouseleave: function () {
752            $(document.body).unbind('mousemove.jqs');
753            var splist = this.splist,
754                 spcount = splist.length,
755                 needsRefresh = false,
756                 sp, i;
757            this.over = false;
758            this.currentEl = null;
759
760            if (this.tooltip) {
761                this.tooltip.remove();
762                this.tooltip = null;
763            }
764
765            for (i = 0; i < spcount; i++) {
766                sp = splist[i];
767                if (sp.clearRegionHighlight()) {
768                    needsRefresh = true;
769                }
770            }
771
772            if (needsRefresh) {
773                this.canvas.render();
774            }
775        },
776
777        mousemove: function (e) {
778            this.currentPageX = e.pageX;
779            this.currentPageY = e.pageY;
780            this.currentEl = e.target;
781            if (this.tooltip) {
782                this.tooltip.updatePosition(e.pageX, e.pageY);
783            }
784            this.updateDisplay();
785        },
786
787        updateDisplay: function () {
788            var splist = this.splist,
789                 spcount = splist.length,
790                 needsRefresh = false,
791                 offset = this.$canvas.offset(),
792                 localX = this.currentPageX - offset.left,
793                 localY = this.currentPageY - offset.top,
794                 tooltiphtml, sp, i, result, changeEvent;
795            if (!this.over) {
796                return;
797            }
798            for (i = 0; i < spcount; i++) {
799                sp = splist[i];
800                result = sp.setRegionHighlight(this.currentEl, localX, localY);
801                if (result) {
802                    needsRefresh = true;
803                }
804            }
805            if (needsRefresh) {
806                changeEvent = $.Event('sparklineRegionChange');
807                changeEvent.sparklines = this.splist;
808                this.$el.trigger(changeEvent);
809                if (this.tooltip) {
810                    tooltiphtml = '';
811                    for (i = 0; i < spcount; i++) {
812                        sp = splist[i];
813                        tooltiphtml += sp.getCurrentRegionTooltip();
814                    }
815                    this.tooltip.setContent(tooltiphtml);
816                }
817                if (!this.disableHighlight) {
818                    this.canvas.render();
819                }
820            }
821            if (result === null) {
822                this.mouseleave();
823            }
824        }
825    });
826
827
828    Tooltip = createClass({
829        sizeStyle: 'position: static !important;' +
830            'display: block !important;' +
831            'visibility: hidden !important;' +
832            'float: left !important;',
833
834        init: function (options) {
835            var tooltipClassname = options.get('tooltipClassname', 'jqstooltip'),
836                sizetipStyle = this.sizeStyle,
837                offset;
838            this.container = options.get('tooltipContainer') || document.body;
839            this.tooltipOffsetX = options.get('tooltipOffsetX', 10);
840            this.tooltipOffsetY = options.get('tooltipOffsetY', 12);
841            // remove any previous lingering tooltip
842            $('#jqssizetip').remove();
843            $('#jqstooltip').remove();
844            this.sizetip = $('<div/>', {
845                id: 'jqssizetip',
846                style: sizetipStyle,
847                'class': tooltipClassname
848            });
849            this.tooltip = $('<div/>', {
850                id: 'jqstooltip',
851                'class': tooltipClassname
852            }).appendTo(this.container);
853            // account for the container's location
854            offset = this.tooltip.offset();
855            this.offsetLeft = offset.left;
856            this.offsetTop = offset.top;
857            this.hidden = true;
858            $(window).unbind('resize.jqs scroll.jqs');
859            $(window).bind('resize.jqs scroll.jqs', $.proxy(this.updateWindowDims, this));
860            this.updateWindowDims();
861        },
862
863        updateWindowDims: function () {
864            this.scrollTop = $(window).scrollTop();
865            this.scrollLeft = $(window).scrollLeft();
866            this.scrollRight = this.scrollLeft + $(window).width();
867            this.updatePosition();
868        },
869
870        getSize: function (content) {
871            this.sizetip.html(content).appendTo(this.container);
872            this.width = this.sizetip.width() + 1;
873            this.height = this.sizetip.height();
874            this.sizetip.remove();
875        },
876
877        setContent: function (content) {
878            if (!content) {
879                this.tooltip.css('visibility', 'hidden');
880                this.hidden = true;
881                return;
882            }
883            this.getSize(content);
884            this.tooltip.html(content)
885                .css({
886                    'width': this.width,
887                    'height': this.height,
888                    'visibility': 'visible'
889                });
890            if (this.hidden) {
891                this.hidden = false;
892                this.updatePosition();
893            }
894        },
895
896        updatePosition: function (x, y) {
897            if (x === undefined) {
898                if (this.mousex === undefined) {
899                    return;
900                }
901                x = this.mousex - this.offsetLeft;
902                y = this.mousey - this.offsetTop;
903
904            } else {
905                this.mousex = x = x - this.offsetLeft;
906                this.mousey = y = y - this.offsetTop;
907            }
908            if (!this.height || !this.width || this.hidden) {
909                return;
910            }
911
912            y -= this.height + this.tooltipOffsetY;
913            x += this.tooltipOffsetX;
914
915            if (y < this.scrollTop) {
916                y = this.scrollTop;
917            }
918            if (x < this.scrollLeft) {
919                x = this.scrollLeft;
920            } else if (x + this.width > this.scrollRight) {
921                x = this.scrollRight - this.width;
922            }
923
924            this.tooltip.css({
925                'left': x,
926                'top': y
927            });
928        },
929
930        remove: function () {
931            this.tooltip.remove();
932            this.sizetip.remove();
933            this.sizetip = this.tooltip = undefined;
934            $(window).unbind('resize.jqs scroll.jqs');
935        }
936    });
937
938    initStyles = function() {
939        addCSS(defaultStyles);
940    };
941
942    $(initStyles);
943
944    pending = [];
945    $.fn.sparkline = function (userValues, userOptions) {
946        return this.each(function () {
947            var options = new $.fn.sparkline.options(this, userOptions),
948                 $this = $(this),
949                 render, i;
950            render = function () {
951                var values, width, height, tmp, mhandler, sp, vals;
952                if (userValues === 'html' || userValues === undefined) {
953                    vals = this.getAttribute(options.get('tagValuesAttribute'));
954                    if (vals === undefined || vals === null) {
955                        vals = $this.html();
956                    }
957                    values = vals.replace(/(^\s*<!--)|(-->\s*$)|\s+/g, '').split(',');
958                } else {
959                    values = userValues;
960                }
961
962                width = options.get('width') === 'auto' ? values.length * options.get('defaultPixelsPerValue') : options.get('width');
963                if (options.get('height') === 'auto') {
964                    if (!options.get('composite') || !$.data(this, '_jqs_vcanvas')) {
965                        // must be a better way to get the line height
966                        tmp = document.createElement('span');
967                        tmp.innerHTML = 'a';
968                        $this.html(tmp);
969                        height = $(tmp).innerHeight() || $(tmp).height();
970                        $(tmp).remove();
971                        tmp = null;
972                    }
973                } else {
974                    height = options.get('height');
975                }
976
977                if (!options.get('disableInteraction')) {
978                    mhandler = $.data(this, '_jqs_mhandler');
979                    if (!mhandler) {
980                        mhandler = new MouseHandler(this, options);
981                        $.data(this, '_jqs_mhandler', mhandler);
982                    } else if (!options.get('composite')) {
983                        mhandler.reset();
984                    }
985                } else {
986                    mhandler = false;
987                }
988
989                if (options.get('composite') && !$.data(this, '_jqs_vcanvas')) {
990                    if (!$.data(this, '_jqs_errnotify')) {
991                        alert('Attempted to attach a composite sparkline to an element with no existing sparkline');
992                        $.data(this, '_jqs_errnotify', true);
993                    }
994                    return;
995                }
996
997                sp = new $.fn.sparkline[options.get('type')](this, values, options, width, height);
998
999                sp.render();
1000
1001                if (mhandler) {
1002                    mhandler.registerSparkline(sp);
1003                }
1004            };
1005            if (($(this).html() && !options.get('disableHiddenCheck') && $(this).is(':hidden')) || !$(this).parents('body').length) {
1006                if (!options.get('composite') && $.data(this, '_jqs_pending')) {
1007                    // remove any existing references to the element
1008                    for (i = pending.length; i; i--) {
1009                        if (pending[i - 1][0] == this) {
1010                            pending.splice(i - 1, 1);
1011                        }
1012                    }
1013                }
1014                pending.push([this, render]);
1015                $.data(this, '_jqs_pending', true);
1016            } else {
1017                render.call(this);
1018            }
1019        });
1020    };
1021
1022    $.fn.sparkline.defaults = getDefaults();
1023
1024
1025    $.sparkline_display_visible = function () {
1026        var el, i, pl;
1027        var done = [];
1028        for (i = 0, pl = pending.length; i < pl; i++) {
1029            el = pending[i][0];
1030            if ($(el).is(':visible') && !$(el).parents().is(':hidden')) {
1031                pending[i][1].call(el);
1032                $.data(pending[i][0], '_jqs_pending', false);
1033                done.push(i);
1034            } else if (!$(el).closest('html').length && !$.data(el, '_jqs_pending')) {
1035                // element has been inserted and removed from the DOM
1036                // If it was not yet inserted into the dom then the .data request
1037                // will return true.
1038                // removing from the dom causes the data to be removed.
1039                $.data(pending[i][0], '_jqs_pending', false);
1040                done.push(i);
1041            }
1042        }
1043        for (i = done.length; i; i--) {
1044            pending.splice(done[i - 1], 1);
1045        }
1046    };
1047
1048
1049    /**
1050     * User option handler
1051     */
1052    $.fn.sparkline.options = createClass({
1053        init: function (tag, userOptions) {
1054            var extendedOptions, defaults, base, tagOptionType;
1055            this.userOptions = userOptions = userOptions || {};
1056            this.tag = tag;
1057            this.tagValCache = {};
1058            defaults = $.fn.sparkline.defaults;
1059            base = defaults.common;
1060            this.tagOptionsPrefix = userOptions.enableTagOptions && (userOptions.tagOptionsPrefix || base.tagOptionsPrefix);
1061
1062            tagOptionType = this.getTagSetting('type');
1063            if (tagOptionType === UNSET_OPTION) {
1064                extendedOptions = defaults[userOptions.type || base.type];
1065            } else {
1066                extendedOptions = defaults[tagOptionType];
1067            }
1068            this.mergedOptions = $.extend({}, base, extendedOptions, userOptions);
1069        },
1070
1071
1072        getTagSetting: function (key) {
1073            var prefix = this.tagOptionsPrefix,
1074                val, i, pairs, keyval;
1075            if (prefix === false || prefix === undefined) {
1076                return UNSET_OPTION;
1077            }
1078            if (this.tagValCache.hasOwnProperty(key)) {
1079                val = this.tagValCache.key;
1080            } else {
1081                val = this.tag.getAttribute(prefix + key);
1082                if (val === undefined || val === null) {
1083                    val = UNSET_OPTION;
1084                } else if (val.substr(0, 1) === '[') {
1085                    val = val.substr(1, val.length - 2).split(',');
1086                    for (i = val.length; i--;) {
1087                        val[i] = normalizeValue(val[i].replace(/(^\s*)|(\s*$)/g, ''));
1088                    }
1089                } else if (val.substr(0, 1) === '{') {
1090                    pairs = val.substr(1, val.length - 2).split(',');
1091                    val = {};
1092                    for (i = pairs.length; i--;) {
1093                        keyval = pairs[i].split(':', 2);
1094                        val[keyval[0].replace(/(^\s*)|(\s*$)/g, '')] = normalizeValue(keyval[1].replace(/(^\s*)|(\s*$)/g, ''));
1095                    }
1096                } else {
1097                    val = normalizeValue(val);
1098                }
1099                this.tagValCache.key = val;
1100            }
1101            return val;
1102        },
1103
1104        get: function (key, defaultval) {
1105            var tagOption = this.getTagSetting(key),
1106                result;
1107            if (tagOption !== UNSET_OPTION) {
1108                return tagOption;
1109            }
1110            return (result = this.mergedOptions[key]) === undefined ? defaultval : result;
1111        }
1112    });
1113
1114
1115    $.fn.sparkline._base = createClass({
1116        disabled: false,
1117
1118        init: function (el, values, options, width, height) {
1119            this.el = el;
1120            this.$el = $(el);
1121            this.values = values;
1122            this.options = options;
1123            this.width = width;
1124            this.height = height;
1125            this.currentRegion = undefined;
1126        },
1127
1128        /**
1129         * Setup the canvas
1130         */
1131        initTarget: function () {
1132            var interactive = !this.options.get('disableInteraction');
1133            if (!(this.target = this.$el.simpledraw(this.width, this.height, this.options.get('composite'), interactive))) {
1134                this.disabled = true;
1135            } else {
1136                this.canvasWidth = this.target.pixelWidth;
1137                this.canvasHeight = this.target.pixelHeight;
1138            }
1139        },
1140
1141        /**
1142         * Actually render the chart to the canvas
1143         */
1144        render: function () {
1145            if (this.disabled) {
1146                this.el.innerHTML = '';
1147                return false;
1148            }
1149            return true;
1150        },
1151
1152        /**
1153         * Return a region id for a given x/y co-ordinate
1154         */
1155        getRegion: function (x, y) {
1156        },
1157
1158        /**
1159         * Highlight an item based on the moused-over x,y co-ordinate
1160         */
1161        setRegionHighlight: function (el, x, y) {
1162            var currentRegion = this.currentRegion,
1163                highlightEnabled = !this.options.get('disableHighlight'),
1164                newRegion;
1165            if (x > this.canvasWidth || y > this.canvasHeight || x < 0 || y < 0) {
1166                return null;
1167            }
1168            newRegion = this.getRegion(el, x, y);
1169            if (currentRegion !== newRegion) {
1170                if (currentRegion !== undefined && highlightEnabled) {
1171                    this.removeHighlight();
1172                }
1173                this.currentRegion = newRegion;
1174                if (newRegion !== undefined && highlightEnabled) {
1175                    this.renderHighlight();
1176                }
1177                return true;
1178            }
1179            return false;
1180        },
1181
1182        /**
1183         * Reset any currently highlighted item
1184         */
1185        clearRegionHighlight: function () {
1186            if (this.currentRegion !== undefined) {
1187                this.removeHighlight();
1188                this.currentRegion = undefined;
1189                return true;
1190            }
1191            return false;
1192        },
1193
1194        renderHighlight: function () {
1195            this.changeHighlight(true);
1196        },
1197
1198        removeHighlight: function () {
1199            this.changeHighlight(false);
1200        },
1201
1202        changeHighlight: function (highlight)  {},
1203
1204        /**
1205         * Fetch the HTML to display as a tooltip
1206         */
1207        getCurrentRegionTooltip: function () {
1208            var options = this.options,
1209                header = '',
1210                entries = [],
1211                fields, formats, formatlen, fclass, text, i,
1212                showFields, showFieldsKey, newFields, fv,
1213                formatter, format, fieldlen, j;
1214            if (this.currentRegion === undefined) {
1215                return '';
1216            }
1217            fields = this.getCurrentRegionFields();
1218            formatter = options.get('tooltipFormatter');
1219            if (formatter) {
1220                return formatter(this, options, fields);
1221            }
1222            if (options.get('tooltipChartTitle')) {
1223                header += '<div class="jqs jqstitle">' + options.get('tooltipChartTitle') + '</div>\n';
1224            }
1225            formats = this.options.get('tooltipFormat');
1226            if (!formats) {
1227                return '';
1228            }
1229            if (!$.isArray(formats)) {
1230                formats = [formats];
1231            }
1232            if (!$.isArray(fields)) {
1233                fields = [fields];
1234            }
1235            showFields = this.options.get('tooltipFormatFieldlist');
1236            showFieldsKey = this.options.get('tooltipFormatFieldlistKey');
1237            if (showFields && showFieldsKey) {
1238                // user-selected ordering of fields
1239                newFields = [];
1240                for (i = fields.length; i--;) {
1241                    fv = fields[i][showFieldsKey];
1242                    if ((j = $.inArray(fv, showFields)) != -1) {
1243                        newFields[j] = fields[i];
1244                    }
1245                }
1246                fields = newFields;
1247            }
1248            formatlen = formats.length;
1249            fieldlen = fields.length;
1250            for (i = 0; i < formatlen; i++) {
1251                format = formats[i];
1252                if (typeof format === 'string') {
1253                    format = new SPFormat(format);
1254                }
1255                fclass = format.fclass || 'jqsfield';
1256                for (j = 0; j < fieldlen; j++) {
1257                    if (!fields[j].isNull || !options.get('tooltipSkipNull')) {
1258                        $.extend(fields[j], {
1259                            prefix: options.get('tooltipPrefix'),
1260                            suffix: options.get('tooltipSuffix')
1261                        });
1262                        text = format.render(fields[j], options.get('tooltipValueLookups'), options);
1263                        entries.push('<div class="' + fclass + '">' + text + '</div>');
1264                    }
1265                }
1266            }
1267            if (entries.length) {
1268                return header + entries.join('\n');
1269            }
1270            return '';
1271        },
1272
1273        getCurrentRegionFields: function () {},
1274
1275        calcHighlightColor: function (color, options) {
1276            var highlightColor = options.get('highlightColor'),
1277                lighten = options.get('highlightLighten'),
1278                parse, mult, rgbnew, i;
1279            if (highlightColor) {
1280                return highlightColor;
1281            }
1282            if (lighten) {
1283                // extract RGB values
1284                parse = /^#([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);
1285                if (parse) {
1286                    rgbnew = [];
1287                    mult = color.length === 4 ? 16 : 1;
1288                    for (i = 0; i < 3; i++) {
1289                        rgbnew[i] = clipval(Math.round(parseInt(parse[i + 1], 16) * mult * lighten), 0, 255);
1290                    }
1291                    return 'rgb(' + rgbnew.join(',') + ')';
1292                }
1293
1294            }
1295            return color;
1296        }
1297
1298    });
1299
1300    barHighlightMixin = {
1301        changeHighlight: function (highlight) {
1302            var currentRegion = this.currentRegion,
1303                target = this.target,
1304                shapeids = this.regionShapes[currentRegion],
1305                newShapes;
1306            // will be null if the region value was null
1307            if (shapeids) {
1308                newShapes = this.renderRegion(currentRegion, highlight);
1309                if ($.isArray(newShapes) || $.isArray(shapeids)) {
1310                    target.replaceWithShapes(shapeids, newShapes);
1311                    this.regionShapes[currentRegion] = $.map(newShapes, function (newShape) {
1312                        return newShape.id;
1313                    });
1314                } else {
1315                    target.replaceWithShape(shapeids, newShapes);
1316                    this.regionShapes[currentRegion] = newShapes.id;
1317                }
1318            }
1319        },
1320
1321        render: function () {
1322            var values = this.values,
1323                target = this.target,
1324                regionShapes = this.regionShapes,
1325                shapes, ids, i, j;
1326
1327            if (!this.cls._super.render.call(this)) {
1328                return;
1329            }
1330            for (i = values.length; i--;) {
1331                shapes = this.renderRegion(i);
1332                if (shapes) {
1333                    if ($.isArray(shapes)) {
1334                        ids = [];
1335                        for (j = shapes.length; j--;) {
1336                            shapes[j].append();
1337                            ids.push(shapes[j].id);
1338                        }
1339                        regionShapes[i] = ids;
1340                    } else {
1341                        shapes.append();
1342                        regionShapes[i] = shapes.id; // store just the shapeid
1343                    }
1344                } else {
1345                    // null value
1346                    regionShapes[i] = null;
1347                }
1348            }
1349            target.render();
1350        }
1351    };
1352
1353    /**
1354     * Line charts
1355     */
1356    $.fn.sparkline.line = line = createClass($.fn.sparkline._base, {
1357        type: 'line',
1358
1359        init: function (el, values, options, width, height) {
1360            line._super.init.call(this, el, values, options, width, height);
1361            this.vertices = [];
1362            this.regionMap = [];
1363            this.xvalues = [];
1364            this.yvalues = [];
1365            this.yminmax = [];
1366            this.hightlightSpotId = null;
1367            this.lastShapeId = null;
1368            this.initTarget();
1369        },
1370
1371        getRegion: function (el, x, y) {
1372            var i,
1373                regionMap = this.regionMap; // maps regions to value positions
1374            for (i = regionMap.length; i--;) {
1375                if (regionMap[i] !== null && x >= regionMap[i][0] && x <= regionMap[i][1]) {
1376                    return regionMap[i][2];
1377                }
1378            }
1379            return undefined;
1380        },
1381
1382        getCurrentRegionFields: function () {
1383            var currentRegion = this.currentRegion;
1384            return {
1385                isNull: this.yvalues[currentRegion] === null,
1386                x: this.xvalues[currentRegion],
1387                y: this.yvalues[currentRegion],
1388                color: this.options.get('lineColor'),
1389                fillColor: this.options.get('fillColor'),
1390                offset: currentRegion
1391            };
1392        },
1393
1394        renderHighlight: function () {
1395            var currentRegion = this.currentRegion,
1396                target = this.target,
1397                vertex = this.vertices[currentRegion],
1398                options = this.options,
1399                spotRadius = options.get('spotRadius'),
1400                highlightSpotColor = options.get('highlightSpotColor'),
1401                highlightLineColor = options.get('highlightLineColor'),
1402                highlightSpot, highlightLine;
1403
1404            if (!vertex) {
1405                return;
1406            }
1407            if (spotRadius && highlightSpotColor) {
1408                highlightSpot = target.drawCircle(vertex[0], vertex[1],
1409                    spotRadius, undefined, highlightSpotColor);
1410                this.highlightSpotId = highlightSpot.id;
1411                target.insertAfterShape(this.lastShapeId, highlightSpot);
1412            }
1413            if (highlightLineColor) {
1414                highlightLine = target.drawLine(vertex[0], this.canvasTop, vertex[0],
1415                    this.canvasTop + this.canvasHeight, highlightLineColor);
1416                this.highlightLineId = highlightLine.id;
1417                target.insertAfterShape(this.lastShapeId, highlightLine);
1418            }
1419        },
1420
1421        removeHighlight: function () {
1422            var target = this.target;
1423            if (this.highlightSpotId) {
1424                target.removeShapeId(this.highlightSpotId);
1425                this.highlightSpotId = null;
1426            }
1427            if (this.highlightLineId) {
1428                target.removeShapeId(this.highlightLineId);
1429                this.highlightLineId = null;
1430            }
1431        },
1432
1433        scanValues: function () {
1434            var values = this.values,
1435                valcount = values.length,
1436                xvalues = this.xvalues,
1437                yvalues = this.yvalues,
1438                yminmax = this.yminmax,
1439                i, val, isStr, isArray, sp;
1440            for (i = 0; i < valcount; i++) {
1441                val = values[i];
1442                isStr = typeof(values[i]) === 'string';
1443                isArray = typeof(values[i]) === 'object' && values[i] instanceof Array;
1444                sp = isStr && values[i].split(':');
1445                if (isStr && sp.length === 2) { // x:y
1446                    xvalues.push(Number(sp[0]));
1447                    yvalues.push(Number(sp[1]));
1448                    yminmax.push(Number(sp[1]));
1449                } else if (isArray) {
1450                    xvalues.push(val[0]);
1451                    yvalues.push(val[1]);
1452                    yminmax.push(val[1]);
1453                } else {
1454                    xvalues.push(i);
1455                    if (values[i] === null || values[i] === 'null') {
1456                        yvalues.push(null);
1457                    } else {
1458                        yvalues.push(Number(val));
1459                        yminmax.push(Number(val));
1460                    }
1461                }
1462            }
1463            if (this.options.get('xvalues')) {
1464                xvalues = this.options.get('xvalues');
1465            }
1466
1467            this.maxy = this.maxyorg = Math.max.apply(Math, yminmax);
1468            this.miny = this.minyorg = Math.min.apply(Math, yminmax);
1469
1470            this.maxx = Math.max.apply(Math, xvalues);
1471            this.minx = Math.min.apply(Math, xvalues);
1472
1473            this.xvalues = xvalues;
1474            this.yvalues = yvalues;
1475            this.yminmax = yminmax;
1476
1477        },
1478
1479        processRangeOptions: function () {
1480            var options = this.options,
1481                normalRangeMin = options.get('normalRangeMin'),
1482                normalRangeMax = options.get('normalRangeMax');
1483
1484            if (normalRangeMin !== undefined) {
1485                if (normalRangeMin < this.miny) {
1486                    this.miny = normalRangeMin;
1487                }
1488                if (normalRangeMax > this.maxy) {
1489                    this.maxy = normalRangeMax;
1490                }
1491            }
1492            if (options.get('chartRangeMin') !== undefined && (options.get('chartRangeClip') || options.get('chartRangeMin') < this.miny)) {
1493                this.miny = options.get('chartRangeMin');
1494            }
1495            if (options.get('chartRangeMax') !== undefined && (options.get('chartRangeClip') || options.get('chartRangeMax') > this.maxy)) {
1496                this.maxy = options.get('chartRangeMax');
1497            }
1498            if (options.get('chartRangeMinX') !== undefined && (options.get('chartRangeClipX') || options.get('chartRangeMinX') < this.minx)) {
1499                this.minx = options.get('chartRangeMinX');
1500            }
1501            if (options.get('chartRangeMaxX') !== undefined && (options.get('chartRangeClipX') || options.get('chartRangeMaxX') > this.maxx)) {
1502                this.maxx = options.get('chartRangeMaxX');
1503            }
1504
1505        },
1506
1507        drawNormalRange: function (canvasLeft, canvasTop, canvasHeight, canvasWidth, rangey) {
1508            var normalRangeMin = this.options.get('normalRangeMin'),
1509                normalRangeMax = this.options.get('normalRangeMax'),
1510                ytop = canvasTop + Math.round(canvasHeight - (canvasHeight * ((normalRangeMax - this.miny) / rangey))),
1511                height = Math.round((canvasHeight * (normalRangeMax - normalRangeMin)) / rangey);
1512            this.target.drawRect(canvasLeft, ytop, canvasWidth, height, undefined, this.options.get('normalRangeColor')).append();
1513        },
1514
1515        render: function () {
1516            var options = this.options,
1517                target = this.target,
1518                canvasWidth = this.canvasWidth,
1519                canvasHeight = this.canvasHeight,
1520                vertices = this.vertices,
1521                spotRadius = options.get('spotRadius'),
1522                regionMap = this.regionMap,
1523                rangex, rangey, yvallast,
1524                canvasTop, canvasLeft,
1525                vertex, path, paths, x, y, xnext, xpos, xposnext,
1526                last, next, yvalcount, lineShapes, fillShapes, plen,
1527                valueSpots, hlSpotsEnabled, color, xvalues, yvalues, i;
1528
1529            if (!line._super.render.call(this)) {
1530                return;
1531            }
1532
1533            this.scanValues();
1534            this.processRangeOptions();
1535
1536            xvalues = this.xvalues;
1537            yvalues = this.yvalues;
1538
1539            if (!this.yminmax.length || this.yvalues.length < 2) {
1540                // empty or all null valuess
1541                return;
1542            }
1543
1544            canvasTop = canvasLeft = 0;
1545
1546            rangex = this.maxx - this.minx === 0 ? 1 : this.maxx - this.minx;
1547            rangey = this.maxy - this.miny === 0 ? 1 : this.maxy - this.miny;
1548            yvallast = this.yvalues.length - 1;
1549
1550            if (spotRadius && (canvasWidth < (spotRadius * 4) || canvasHeight < (spotRadius * 4))) {
1551                spotRadius = 0;
1552            }
1553            if (spotRadius) {
1554                // adjust the canvas size as required so that spots will fit
1555                hlSpotsEnabled = options.get('highlightSpotColor') &&  !options.get('disableInteraction');
1556                if (hlSpotsEnabled || options.get('minSpotColor') || (options.get('spotColor') && yvalues[yvallast] === this.miny)) {
1557                    canvasHeight -= Math.ceil(spotRadius);
1558                }
1559                if (hlSpotsEnabled || options.get('maxSpotColor') || (options.get('spotColor') && yvalues[yvallast] === this.maxy)) {
1560                    canvasHeight -= Math.ceil(spotRadius);
1561                    canvasTop += Math.ceil(spotRadius);
1562                }
1563                if (hlSpotsEnabled ||
1564                     ((options.get('minSpotColor') || options.get('maxSpotColor')) && (yvalues[0] === this.miny || yvalues[0] === this.maxy))) {
1565                    canvasLeft += Math.ceil(spotRadius);
1566                    canvasWidth -= Math.ceil(spotRadius);
1567                }
1568                if (hlSpotsEnabled || options.get('spotColor') ||
1569                    (options.get('minSpotColor') || options.get('maxSpotColor') &&
1570                        (yvalues[yvallast] === this.miny || yvalues[yvallast] === this.maxy))) {
1571                    canvasWidth -= Math.ceil(spotRadius);
1572                }
1573            }
1574
1575
1576            canvasHeight--;
1577
1578            if (options.get('normalRangeMin') !== undefined && !options.get('drawNormalOnTop')) {
1579                this.drawNormalRange(canvasLeft, canvasTop, canvasHeight, canvasWidth, rangey);
1580            }
1581
1582            path = [];
1583            paths = [path];
1584            last = next = null;
1585            yvalcount = yvalues.length;
1586            for (i = 0; i < yvalcount; i++) {
1587                x = xvalues[i];
1588                xnext = xvalues[i + 1];
1589                y = yvalues[i];
1590                xpos = canvasLeft + Math.round((x - this.minx) * (canvasWidth / rangex));
1591                xposnext = i < yvalcount - 1 ? canvasLeft + Math.round((xnext - this.minx) * (canvasWidth / rangex)) : canvasWidth;
1592                next = xpos + ((xposnext - xpos) / 2);
1593                regionMap[i] = [last || 0, next, i];
1594                last = next;
1595                if (y === null) {
1596                    if (i) {
1597                        if (yvalues[i - 1] !== null) {
1598                            path = [];
1599                            paths.push(path);
1600                        }
1601                        vertices.push(null);
1602                    }
1603                } else {
1604                    if (y < this.miny) {
1605                        y = this.miny;
1606                    }
1607                    if (y > this.maxy) {
1608                        y = this.maxy;
1609                    }
1610                    if (!path.length) {
1611                        // previous value was null
1612                        path.push([xpos, canvasTop + canvasHeight]);
1613                    }
1614                    vertex = [xpos, canvasTop + Math.round(canvasHeight - (canvasHeight * ((y - this.miny) / rangey)))];
1615                    path.push(vertex);
1616                    vertices.push(vertex);
1617                }
1618            }
1619
1620            lineShapes = [];
1621            fillShapes = [];
1622            plen = paths.length;
1623            for (i = 0; i < plen; i++) {
1624                path = paths[i];
1625                if (path.length) {
1626                    if (options.get('fillColor')) {
1627                        path.push([path[path.length - 1][0], (canvasTop + canvasHeight)]);
1628                        fillShapes.push(path.slice(0));
1629                        path.pop();
1630                    }
1631                    // if there's only a single point in this path, then we want to display it
1632                    // as a vertical line which means we keep path[0]  as is
1633                    if (path.length > 2) {
1634                        // else we want the first value
1635                        path[0] = [path[0][0], path[1][1]];
1636                    }
1637                    lineShapes.push(path);
1638                }
1639            }
1640
1641            // draw the fill first, then optionally the normal range, then the line on top of that
1642            plen = fillShapes.length;
1643            for (i = 0; i < plen; i++) {
1644                target.drawShape(fillShapes[i],
1645                    options.get('fillColor'), options.get('fillColor')).append();
1646            }
1647
1648            if (options.get('normalRangeMin') !== undefined && options.get('drawNormalOnTop')) {
1649                this.drawNormalRange(canvasLeft, canvasTop, canvasHeight, canvasWidth, rangey);
1650            }
1651
1652            plen = lineShapes.length;
1653            for (i = 0; i < plen; i++) {
1654                target.drawShape(lineShapes[i], options.get('lineColor'), undefined,
1655                    options.get('lineWidth')).append();
1656            }
1657
1658            if (spotRadius && options.get('valueSpots')) {
1659                valueSpots = options.get('valueSpots');
1660                if (valueSpots.get === undefined) {
1661                    valueSpots = new RangeMap(valueSpots);
1662                }
1663                for (i = 0; i < yvalcount; i++) {
1664                    color = valueSpots.get(yvalues[i]);
1665                    if (color) {
1666                        target.drawCircle(canvasLeft + Math.round((xvalues[i] - this.minx) * (canvasWidth / rangex)),
1667                            canvasTop + Math.round(canvasHeight - (canvasHeight * ((yvalues[i] - this.miny) / rangey))),
1668                            spotRadius, undefined,
1669                            color).append();
1670                    }
1671                }
1672
1673            }
1674            if (spotRadius && options.get('spotColor') && yvalues[yvallast] !== null) {
1675                target.drawCircle(canvasLeft + Math.round((xvalues[xvalues.length - 1] - this.minx) * (canvasWidth / rangex)),
1676                    canvasTop + Math.round(canvasHeight - (canvasHeight * ((yvalues[yvallast] - this.miny) / rangey))),
1677                    spotRadius, undefined,
1678                    options.get('spotColor')).append();
1679            }
1680            if (this.maxy !== this.minyorg) {
1681                if (spotRadius && options.get('minSpotColor')) {
1682                    x = xvalues[$.inArray(this.minyorg, yvalues)];
1683                    target.drawCircle(canvasLeft + Math.round((x - this.minx) * (canvasWidth / rangex)),
1684                        canvasTop + Math.round(canvasHeight - (canvasHeight * ((this.minyorg - this.miny) / rangey))),
1685                        spotRadius, undefined,
1686                        options.get('minSpotColor')).append();
1687                }
1688                if (spotRadius && options.get('maxSpotColor')) {
1689                    x = xvalues[$.inArray(this.maxyorg, yvalues)];
1690                    target.drawCircle(canvasLeft + Math.round((x - this.minx) * (canvasWidth / rangex)),
1691                        canvasTop + Math.round(canvasHeight - (canvasHeight * ((this.maxyorg - this.miny) / rangey))),
1692                        spotRadius, undefined,
1693                        options.get('maxSpotColor')).append();
1694                }
1695            }
1696
1697            this.lastShapeId = target.getLastShapeId();
1698            this.canvasTop = canvasTop;
1699            target.render();
1700        }
1701    });
1702
1703    /**
1704     * Bar charts
1705     */
1706    $.fn.sparkline.bar = bar = createClass($.fn.sparkline._base, barHighlightMixin, {
1707        type: 'bar',
1708
1709        init: function (el, values, options, width, height) {
1710            var barWidth = parseInt(options.get('barWidth'), 10),
1711                barSpacing = parseInt(options.get('barSpacing'), 10),
1712                chartRangeMin = options.get('chartRangeMin'),
1713                chartRangeMax = options.get('chartRangeMax'),
1714                chartRangeClip = options.get('chartRangeClip'),
1715                stackMin = Infinity,
1716                stackMax = -Infinity,
1717                isStackString, groupMin, groupMax, stackRanges,
1718                numValues, i, vlen, range, zeroAxis, xaxisOffset, min, max, clipMin, clipMax,
1719                stacked, vlist, j, slen, svals, val, yoffset, yMaxCalc, canvasHeightEf;
1720            bar._super.init.call(this, el, values, options, width, height);
1721
1722            // scan values to determine whether to stack bars
1723            for (i = 0, vlen = values.length; i < vlen; i++) {
1724                val = values[i];
1725                isStackString = typeof(val) === 'string' && val.indexOf(':') > -1;
1726                if (isStackString || $.isArray(val)) {
1727                    stacked = true;
1728                    if (isStackString) {
1729                        val = values[i] = normalizeValues(val.split(':'));
1730                    }
1731                    val = remove(val, null); // min/max will treat null as zero
1732                    groupMin = Math.min.apply(Math, val);
1733                    groupMax = Math.max.apply(Math, val);
1734                    if (groupMin < stackMin) {
1735                        stackMin = groupMin;
1736                    }
1737                    if (groupMax > stackMax) {
1738                        stackMax = groupMax;
1739                    }
1740                }
1741            }
1742
1743            this.stacked = stacked;
1744            this.regionShapes = {};
1745            this.barWidth = barWidth;
1746            this.barSpacing = barSpacing;
1747            this.totalBarWidth = barWidth + barSpacing;
1748            this.width = width = (values.length * barWidth) + ((values.length - 1) * barSpacing);
1749
1750            this.initTarget();
1751
1752            if (chartRangeClip) {
1753                clipMin = chartRangeMin === undefined ? -Infinity : chartRangeMin;
1754                clipMax = chartRangeMax === undefined ? Infinity : chartRangeMax;
1755            }
1756
1757            numValues = [];
1758            stackRanges = stacked ? [] : numValues;
1759            var stackTotals = [];
1760            var stackRangesNeg = [];
1761            for (i = 0, vlen = values.length; i < vlen; i++) {
1762                if (stacked) {
1763                    vlist = values[i];
1764                    values[i] = svals = [];
1765                    stackTotals[i] = 0;
1766                    stackRanges[i] = stackRangesNeg[i] = 0;
1767                    for (j = 0, slen = vlist.length; j < slen; j++) {
1768                        val = svals[j] = chartRangeClip ? clipval(vlist[j], clipMin, clipMax) : vlist[j];
1769                        if (val !== null) {
1770                            if (val > 0) {
1771                                stackTotals[i] += val;
1772                            }
1773                            if (stackMin < 0 && stackMax > 0) {
1774                                if (val < 0) {
1775                                    stackRangesNeg[i] += Math.abs(val);
1776                                } else {
1777                                    stackRanges[i] += val;
1778                                }
1779                            } else {
1780                                stackRanges[i] += Math.abs(val - (val < 0 ? stackMax : stackMin));
1781                            }
1782                            numValues.push(val);
1783                        }
1784                    }
1785                } else {
1786                    val = chartRangeClip ? clipval(values[i], clipMin, clipMax) : values[i];
1787                    val = values[i] = normalizeValue(val);
1788                    if (val !== null) {
1789                        numValues.push(val);
1790                    }
1791                }
1792            }
1793            this.max = max = Math.max.apply(Math, numValues);
1794            this.min = min = Math.min.apply(Math, numValues);
1795            this.stackMax = stackMax = stacked ? Math.max.apply(Math, stackTotals) : max;
1796            this.stackMin = stackMin = stacked ? Math.min.apply(Math, numValues) : min;
1797
1798            if (options.get('chartRangeMin') !== undefined && (options.get('chartRangeClip') || options.get('chartRangeMin') < min)) {
1799                min = options.get('chartRangeMin');
1800            }
1801            if (options.get('chartRangeMax') !== undefined && (options.get('chartRangeClip') || options.get('chartRangeMax') > max)) {
1802                max = options.get('chartRangeMax');
1803            }
1804
1805            this.zeroAxis = zeroAxis = options.get('zeroAxis', true);
1806            if (min <= 0 && max >= 0 && zeroAxis) {
1807                xaxisOffset = 0;
1808            } else if (zeroAxis == false) {
1809                xaxisOffset = min;
1810            } else if (min > 0) {
1811                xaxisOffset = min;
1812            } else {
1813                xaxisOffset = max;
1814            }
1815            this.xaxisOffset = xaxisOffset;
1816
1817            range = stacked ? (Math.max.apply(Math, stackRanges) + Math.max.apply(Math, stackRangesNeg)) : max - min;
1818
1819            // as we plot zero/min values a single pixel line, we add a pixel to all other
1820            // values - Reduce the effective canvas size to suit
1821            this.canvasHeightEf = (zeroAxis && min < 0) ? this.canvasHeight - 2 : this.canvasHeight - 1;
1822
1823            if (min < xaxisOffset) {
1824                yMaxCalc = (stacked && max >= 0) ? stackMax : max;
1825                yoffset = (yMaxCalc - xaxisOffset) / range * this.canvasHeight;
1826                if (yoffset !== Math.ceil(yoffset)) {
1827                    this.canvasHeightEf -= 2;
1828                    yoffset = Math.ceil(yoffset);
1829                }
1830            } else {
1831                yoffset = this.canvasHeight;
1832            }
1833            this.yoffset = yoffset;
1834
1835            if ($.isArray(options.get('colorMap'))) {
1836                this.colorMapByIndex = options.get('colorMap');
1837                this.colorMapByValue = null;
1838            } else {
1839                this.colorMapByIndex = null;
1840                this.colorMapByValue = options.get('colorMap');
1841                if (this.colorMapByValue && this.colorMapByValue.get === undefined) {
1842                    this.colorMapByValue = new RangeMap(this.colorMapByValue);
1843                }
1844            }
1845
1846            this.range = range;
1847        },
1848
1849        getRegion: function (el, x, y) {
1850            var result = Math.floor(x / this.totalBarWidth);
1851            return (result < 0 || result >= this.values.length) ? undefined : result;
1852        },
1853
1854        getCurrentRegionFields: function () {
1855            var currentRegion = this.currentRegion,
1856                values = ensureArray(this.values[currentRegion]),
1857                result = [],
1858                value, i;
1859            for (i = values.length; i--;) {
1860                value = values[i];
1861                result.push({
1862                    isNull: value === null,
1863                    value: value,
1864                    color: this.calcColor(i, value, currentRegion),
1865                    offset: currentRegion
1866                });
1867            }
1868            return result;
1869        },
1870
1871        calcColor: function (stacknum, value, valuenum) {
1872            var colorMapByIndex = this.colorMapByIndex,
1873                colorMapByValue = this.colorMapByValue,
1874                options = this.options,
1875                color, newColor;
1876            if (this.stacked) {
1877                color = options.get('stackedBarColor');
1878            } else {
1879                color = (value < 0) ? options.get('negBarColor') : options.get('barColor');
1880            }
1881            if (value === 0 && options.get('zeroColor') !== undefined) {
1882                color = options.get('zeroColor');
1883            }
1884            if (colorMapByValue && (newColor = colorMapByValue.get(value))) {
1885                color = newColor;
1886            } else if (colorMapByIndex && colorMapByIndex.length > valuenum) {
1887                color = colorMapByIndex[valuenum];
1888            }
1889            return $.isArray(color) ? color[stacknum % color.length] : color;
1890        },
1891
1892        /**
1893         * Render bar(s) for a region
1894         */
1895        renderRegion: function (valuenum, highlight) {
1896            var vals = this.values[valuenum],
1897                options = this.options,
1898                xaxisOffset = this.xaxisOffset,
1899                result = [],
1900                range = this.range,
1901                stacked = this.stacked,
1902                target = this.target,
1903                x = valuenum * this.totalBarWidth,
1904                canvasHeightEf = this.canvasHeightEf,
1905                yoffset = this.yoffset,
1906                y, height, color, isNull, yoffsetNeg, i, valcount, val, minPlotted, allMin;
1907
1908            vals = $.isArray(vals) ? vals : [vals];
1909            valcount = vals.length;
1910            val = vals[0];
1911            isNull = all(null, vals);
1912            allMin = all(xaxisOffset, vals, true);
1913
1914            if (isNull) {
1915                if (options.get('nullColor')) {
1916                    color = highlight ? options.get('nullColor') : this.calcHighlightColor(options.get('nullColor'), options);
1917                    y = (yoffset > 0) ? yoffset - 1 : yoffset;
1918                    return target.drawRect(x, y, this.barWidth - 1, 0, color, color);
1919                } else {
1920                    return undefined;
1921                }
1922            }
1923            yoffsetNeg = yoffset;
1924            for (i = 0; i < valcount; i++) {
1925                val = vals[i];
1926
1927                if (stacked && val === xaxisOffset) {
1928                    if (!allMin || minPlotted) {
1929                        continue;
1930                    }
1931                    minPlotted = true;
1932                }
1933
1934                if (range > 0) {
1935                    height = Math.floor(canvasHeightEf * ((Math.abs(val - xaxisOffset) / range))) + 1;
1936                } else {
1937                    height = 1;
1938                }
1939                if (val < xaxisOffset || (val === xaxisOffset && yoffset === 0)) {
1940                    y = yoffsetNeg;
1941                    yoffsetNeg += height;
1942                } else {
1943                    y = yoffset - height;
1944                    yoffset -= height;
1945                }
1946                color = this.calcColor(i, val, valuenum);
1947                if (highlight) {
1948                    color = this.calcHighlightColor(color, options);
1949                }
1950                result.push(target.drawRect(x, y, this.barWidth - 1, height - 1, color, color));
1951            }
1952            if (result.length === 1) {
1953                return result[0];
1954            }
1955            return result;
1956        }
1957    });
1958
1959    /**
1960     * Tristate charts
1961     */
1962    $.fn.sparkline.tristate = tristate = createClass($.fn.sparkline._base, barHighlightMixin, {
1963        type: 'tristate',
1964
1965        init: function (el, values, options, width, height) {
1966            var barWidth = parseInt(options.get('barWidth'), 10),
1967                barSpacing = parseInt(options.get('barSpacing'), 10);
1968            tristate._super.init.call(this, el, values, options, width, height);
1969
1970            this.regionShapes = {};
1971            this.barWidth = barWidth;
1972            this.barSpacing = barSpacing;
1973            this.totalBarWidth = barWidth + barSpacing;
1974            this.values = $.map(values, Number);
1975            this.width = width = (values.length * barWidth) + ((values.length - 1) * barSpacing);
1976
1977            if ($.isArray(options.get('colorMap'))) {
1978                this.colorMapByIndex = options.get('colorMap');
1979                this.colorMapByValue = null;
1980            } else {
1981                this.colorMapByIndex = null;
1982                this.colorMapByValue = options.get('colorMap');
1983                if (this.colorMapByValue && this.colorMapByValue.get === undefined) {
1984                    this.colorMapByValue = new RangeMap(this.colorMapByValue);
1985                }
1986            }
1987            this.initTarget();
1988        },
1989
1990        getRegion: function (el, x, y) {
1991            return Math.floor(x / this.totalBarWidth);
1992        },
1993
1994        getCurrentRegionFields: function () {
1995            var currentRegion = this.currentRegion;
1996            return {
1997                isNull: this.values[currentRegion] === undefined,
1998                value: this.values[currentRegion],
1999                color: this.calcColor(this.values[currentRegion], currentRegion),
2000                offset: currentRegion
2001            };
2002        },
2003
2004        calcColor: function (value, valuenum) {
2005            var values = this.values,
2006                options = this.options,
2007                colorMapByIndex = this.colorMapByIndex,
2008                colorMapByValue = this.colorMapByValue,
2009                color, newColor;
2010
2011            if (colorMapByValue && (newColor = colorMapByValue.get(value))) {
2012                color = newColor;
2013            } else if (colorMapByIndex && colorMapByIndex.length > valuenum) {
2014                color = colorMapByIndex[valuenum];
2015            } else if (values[valuenum] < 0) {
2016                color = options.get('negBarColor');
2017            } else if (values[valuenum] > 0) {
2018                color = options.get('posBarColor');
2019            } else {
2020                color = options.get('zeroBarColor');
2021            }
2022            return color;
2023        },
2024
2025        renderRegion: function (valuenum, highlight) {
2026            var values = this.values,
2027                options = this.options,
2028                target = this.target,
2029                canvasHeight, height, halfHeight,
2030                x, y, color;
2031
2032            canvasHeight = target.pixelHeight;
2033            halfHeight = Math.round(canvasHeight / 2);
2034
2035            x = valuenum * this.totalBarWidth;
2036            if (values[valuenum] < 0) {
2037                y = halfHeight;
2038                height = halfHeight - 1;
2039            } else if (values[valuenum] > 0) {
2040                y = 0;
2041                height = halfHeight - 1;
2042            } else {
2043                y = halfHeight - 1;
2044                height = 2;
2045            }
2046            color = this.calcColor(values[valuenum], valuenum);
2047            if (color === null) {
2048                return;
2049            }
2050            if (highlight) {
2051                color = this.calcHighlightColor(color, options);
2052            }
2053            return target.drawRect(x, y, this.barWidth - 1, height - 1, color, color);
2054        }
2055    });
2056
2057    /**
2058     * Discrete charts
2059     */
2060    $.fn.sparkline.discrete = discrete = createClass($.fn.sparkline._base, barHighlightMixin, {
2061        type: 'discrete',
2062
2063        init: function (el, values, options, width, height) {
2064            discrete._super.init.call(this, el, values, options, width, height);
2065
2066            this.regionShapes = {};
2067            this.values = values = $.map(values, Number);
2068            this.min = Math.min.apply(Math, values);
2069            this.max = Math.max.apply(Math, values);
2070            this.range = this.max - this.min;
2071            this.width = width = options.get('width') === 'auto' ? values.length * 2 : this.width;
2072            this.interval = Math.floor(width / values.length);
2073            this.itemWidth = width / values.length;
2074            if (options.get('chartRangeMin') !== undefined && (options.get('chartRangeClip') || options.get('chartRangeMin') < this.min)) {
2075                this.min = options.get('chartRangeMin');
2076            }
2077            if (options.get('chartRangeMax') !== undefined && (options.get('chartRangeClip') || options.get('chartRangeMax') > this.max)) {
2078                this.max = options.get('chartRangeMax');
2079            }
2080            this.initTarget();
2081            if (this.target) {
2082                this.lineHeight = options.get('lineHeight') === 'auto' ? Math.round(this.canvasHeight * 0.3) : options.get('lineHeight');
2083            }
2084        },
2085
2086        getRegion: function (el, x, y) {
2087            return Math.floor(x / this.itemWidth);
2088        },
2089
2090        getCurrentRegionFields: function () {
2091            var currentRegion = this.currentRegion;
2092            return {
2093                isNull: this.values[currentRegion] === undefined,
2094                value: this.values[currentRegion],
2095                offset: currentRegion
2096            };
2097        },
2098
2099        renderRegion: function (valuenum, highlight) {
2100            var values = this.values,
2101                options = this.options,
2102                min = this.min,
2103                max = this.max,
2104                range = this.range,
2105                interval = this.interval,
2106                target = this.target,
2107                canvasHeight = this.canvasHeight,
2108                lineHeight = this.lineHeight,
2109                pheight = canvasHeight - lineHeight,
2110                ytop, val, color, x;
2111
2112            val = clipval(values[valuenum], min, max);
2113            x = valuenum * interval;
2114            ytop = Math.round(pheight - pheight * ((val - min) / range));
2115            color = (options.get('thresholdColor') && val < options.get('thresholdValue')) ? options.get('thresholdColor') : options.get('lineColor');
2116            if (highlight) {
2117                color = this.calcHighlightColor(color, options);
2118            }
2119            return target.drawLine(x, ytop, x, ytop + lineHeight, color);
2120        }
2121    });
2122
2123    /**
2124     * Bullet charts
2125     */
2126    $.fn.sparkline.bullet = bullet = createClass($.fn.sparkline._base, {
2127        type: 'bullet',
2128
2129        init: function (el, values, options, width, height) {
2130            var min, max, vals;
2131            bullet._super.init.call(this, el, values, options, width, height);
2132
2133            // values: target, performance, range1, range2, range3
2134            this.values = values = normalizeValues(values);
2135            // target or performance could be null
2136            vals = values.slice();
2137            vals[0] = vals[0] === null ? vals[2] : vals[0];
2138            vals[1] = values[1] === null ? vals[2] : vals[1];
2139            min = Math.min.apply(Math, values);
2140            max = Math.max.apply(Math, values);
2141            if (options.get('base') === undefined) {
2142                min = min < 0 ? min : 0;
2143            } else {
2144                min = options.get('base');
2145            }
2146            this.min = min;
2147            this.max = max;
2148            this.range = max - min;
2149            this.shapes = {};
2150            this.valueShapes = {};
2151            this.regiondata = {};
2152            this.width = width = options.get('width') === 'auto' ? '4.0em' : width;
2153            this.target = this.$el.simpledraw(width, height, options.get('composite'));
2154            if (!values.length) {
2155                this.disabled = true;
2156            }
2157            this.initTarget();
2158        },
2159
2160        getRegion: function (el, x, y) {
2161            var shapeid = this.target.getShapeAt(el, x, y);
2162            return (shapeid !== undefined && this.shapes[shapeid] !== undefined) ? this.shapes[shapeid] : undefined;
2163        },
2164
2165        getCurrentRegionFields: function () {
2166            var currentRegion = this.currentRegion;
2167            return {
2168                fieldkey: currentRegion.substr(0, 1),
2169                value: this.values[currentRegion.substr(1)],
2170                region: currentRegion
2171            };
2172        },
2173
2174        changeHighlight: function (highlight) {
2175            var currentRegion = this.currentRegion,
2176                shapeid = this.valueShapes[currentRegion],
2177                shape;
2178            delete this.shapes[shapeid];
2179            switch (currentRegion.substr(0, 1)) {
2180                case 'r':
2181                    shape = this.renderRange(currentRegion.substr(1), highlight);
2182                    break;
2183                case 'p':
2184                    shape = this.renderPerformance(highlight);
2185                    break;
2186                case 't':
2187                    shape = this.renderTarget(highlight);
2188                    break;
2189            }
2190            this.valueShapes[currentRegion] = shape.id;
2191            this.shapes[shape.id] = currentRegion;
2192            this.target.replaceWithShape(shapeid, shape);
2193        },
2194
2195        renderRange: function (rn, highlight) {
2196            var rangeval = this.values[rn],
2197                rangewidth = Math.round(this.canvasWidth * ((rangeval - this.min) / this.range)),
2198                color = this.options.get('rangeColors')[rn - 2];
2199            if (highlight) {
2200                color = this.calcHighlightColor(color, this.options);
2201            }
2202            return this.target.drawRect(0, 0, rangewidth - 1, this.canvasHeight - 1, color, color);
2203        },
2204
2205        renderPerformance: function (highlight) {
2206            var perfval = this.values[1],
2207                perfwidth = Math.round(this.canvasWidth * ((perfval - this.min) / this.range)),
2208                color = this.options.get('performanceColor');
2209            if (highlight) {
2210                color = this.calcHighlightColor(color, this.options);
2211            }
2212            return this.target.drawRect(0, Math.round(this.canvasHeight * 0.3), perfwidth - 1,
2213                Math.round(this.canvasHeight * 0.4) - 1, color, color);
2214        },
2215
2216        renderTarget: function (highlight) {
2217            var targetval = this.values[0],
2218                x = Math.round(this.canvasWidth * ((targetval - this.min) / this.range) - (this.options.get('targetWidth') / 2)),
2219                targettop = Math.round(this.canvasHeight * 0.10),
2220                targetheight = this.canvasHeight - (targettop * 2),
2221                color = this.options.get('targetColor');
2222            if (highlight) {
2223                color = this.calcHighlightColor(color, this.options);
2224            }
2225            return this.target.drawRect(x, targettop, this.options.get('targetWidth') - 1, targetheight - 1, color, color);
2226        },
2227
2228        render: function () {
2229            var vlen = this.values.length,
2230                target = this.target,
2231                i, shape;
2232            if (!bullet._super.render.call(this)) {
2233                return;
2234            }
2235            for (i = 2; i < vlen; i++) {
2236                shape = this.renderRange(i).append();
2237                this.shapes[shape.id] = 'r' + i;
2238                this.valueShapes['r' + i] = shape.id;
2239            }
2240            if (this.values[1] !== null) {
2241                shape = this.renderPerformance().append();
2242                this.shapes[shape.id] = 'p1';
2243                this.valueShapes.p1 = shape.id;
2244            }
2245            if (this.values[0] !== null) {
2246                shape = this.renderTarget().append();
2247                this.shapes[shape.id] = 't0';
2248                this.valueShapes.t0 = shape.id;
2249            }
2250            target.render();
2251        }
2252    });
2253
2254    /**
2255     * Pie charts
2256     */
2257    $.fn.sparkline.pie = pie = createClass($.fn.sparkline._base, {
2258        type: 'pie',
2259
2260        init: function (el, values, options, width, height) {
2261            var total = 0, i;
2262
2263            pie._super.init.call(this, el, values, options, width, height);
2264
2265            this.shapes = {}; // map shape ids to value offsets
2266            this.valueShapes = {}; // maps value offsets to shape ids
2267            this.values = values = $.map(values, Number);
2268
2269            if (options.get('width') === 'auto') {
2270                this.width = this.height;
2271            }
2272
2273            if (values.length > 0) {
2274                for (i = values.length; i--;) {
2275                    total += values[i];
2276                }
2277            }
2278            this.total = total;
2279            this.initTarget();
2280            this.radius = Math.floor(Math.min(this.canvasWidth, this.canvasHeight) / 2);
2281        },
2282
2283        getRegion: function (el, x, y) {
2284            var shapeid = this.target.getShapeAt(el, x, y);
2285            return (shapeid !== undefined && this.shapes[shapeid] !== undefined) ? this.shapes[shapeid] : undefined;
2286        },
2287
2288        getCurrentRegionFields: function () {
2289            var currentRegion = this.currentRegion;
2290            return {
2291                isNull: this.values[currentRegion] === undefined,
2292                value: this.values[currentRegion],
2293                percent: this.values[currentRegion] / this.total * 100,
2294                color: this.options.get('sliceColors')[currentRegion % this.options.get('sliceColors').length],
2295                offset: currentRegion
2296            };
2297        },
2298
2299        changeHighlight: function (highlight) {
2300            var currentRegion = this.currentRegion,
2301                 newslice = this.renderSlice(currentRegion, highlight),
2302                 shapeid = this.valueShapes[currentRegion];
2303            delete this.shapes[shapeid];
2304            this.target.replaceWithShape(shapeid, newslice);
2305            this.valueShapes[currentRegion] = newslice.id;
2306            this.shapes[newslice.id] = currentRegion;
2307        },
2308
2309        renderSlice: function (valuenum, highlight) {
2310            var target = this.target,
2311                options = this.options,
2312                radius = this.radius,
2313                borderWidth = options.get('borderWidth'),
2314                offset = options.get('offset'),
2315                circle = 2 * Math.PI,
2316                values = this.values,
2317                total = this.total,
2318                next = offset ? (2*Math.PI)*(offset/360) : 0,
2319                start, end, i, vlen, color;
2320
2321            vlen = values.length;
2322            for (i = 0; i < vlen; i++) {
2323                start = next;
2324                end = next;
2325                if (total > 0) {  // avoid divide by zero
2326                    end = next + (circle * (values[i] / total));
2327                }
2328                if (valuenum === i) {
2329                    color = options.get('sliceColors')[i % options.get('sliceColors').length];
2330                    if (highlight) {
2331                        color = this.calcHighlightColor(color, options);
2332                    }
2333
2334                    return target.drawPieSlice(radius, radius, radius - borderWidth, start, end, undefined, color);
2335                }
2336                next = end;
2337            }
2338        },
2339
2340        render: function () {
2341            var target = this.target,
2342                values = this.values,
2343                options = this.options,
2344                radius = this.radius,
2345                borderWidth = options.get('borderWidth'),
2346                
vendor: 620 bytes, lines 2346-2360
2346shape, i;
2347
2348            if (!pie._super.render.call(this)) {
2349                return;
2350            }
2351            if (borderWidth) {
2352                target.drawCircle(radius, radius, Math.floor(radius - (borderWidth / 2)),
2353                    options.get('borderColor'), undefined, borderWidth).append();
2354            }
2355            for (i = values.length; i--;) {
2356                if (values[i]) { // don't render zero values
2357                    shape = this.renderSlice(i).append();
2358                    this.valueShapes[i] = shape.id; // store just the shapeid
2359                    this.shapes[shape.id] = i;
2360                }
vendor: 27,518 bytes, lines 2360-3057
2360
2361            }
2362            target.render();
2363        }
2364    });
2365
2366    /**
2367     * Box plots
2368     */
2369    $.fn.sparkline.box = box = createClass($.fn.sparkline._base, {
2370        type: 'box',
2371
2372        init: function (el, values, options, width, height) {
2373            box._super.init.call(this, el, values, options, width, height);
2374            this.values = $.map(values, Number);
2375            this.width = options.get('width') === 'auto' ? '4.0em' : width;
2376            this.initTarget();
2377            if (!this.values.length) {
2378                this.disabled = 1;
2379            }
2380        },
2381
2382        /**
2383         * Simulate a single region
2384         */
2385        getRegion: function () {
2386            return 1;
2387        },
2388
2389        getCurrentRegionFields: function () {
2390            var result = [
2391                { field: 'lq', value: this.quartiles[0] },
2392                { field: 'med', value: this.quartiles[1] },
2393                { field: 'uq', value: this.quartiles[2] }
2394            ];
2395            if (this.loutlier !== undefined) {
2396                result.push({ field: 'lo', value: this.loutlier});
2397            }
2398            if (this.routlier !== undefined) {
2399                result.push({ field: 'ro', value: this.routlier});
2400            }
2401            if (this.lwhisker !== undefined) {
2402                result.push({ field: 'lw', value: this.lwhisker});
2403            }
2404            if (this.rwhisker !== undefined) {
2405                result.push({ field: 'rw', value: this.rwhisker});
2406            }
2407            return result;
2408        },
2409
2410        render: function () {
2411            var target = this.target,
2412                values = this.values,
2413                vlen = values.length,
2414                options = this.options,
2415                canvasWidth = this.canvasWidth,
2416                canvasHeight = this.canvasHeight,
2417                minValue = options.get('chartRangeMin') === undefined ? Math.min.apply(Math, values) : options.get('chartRangeMin'),
2418                maxValue = options.get('chartRangeMax') === undefined ? Math.max.apply(Math, values) : options.get('chartRangeMax'),
2419                canvasLeft = 0,
2420                lwhisker, loutlier, iqr, q1, q2, q3, rwhisker, routlier, i,
2421                size, unitSize;
2422
2423            if (!box._super.render.call(this)) {
2424                return;
2425            }
2426
2427            if (options.get('raw')) {
2428                if (options.get('showOutliers') && values.length > 5) {
2429                    loutlier = values[0];
2430                    lwhisker = values[1];
2431                    q1 = values[2];
2432                    q2 = values[3];
2433                    q3 = values[4];
2434                    rwhisker = values[5];
2435                    routlier = values[6];
2436                } else {
2437                    lwhisker = values[0];
2438                    q1 = values[1];
2439                    q2 = values[2];
2440                    q3 = values[3];
2441                    rwhisker = values[4];
2442                }
2443            } else {
2444                values.sort(function (a, b) { return a - b; });
2445                q1 = quartile(values, 1);
2446                q2 = quartile(values, 2);
2447                q3 = quartile(values, 3);
2448                iqr = q3 - q1;
2449                if (options.get('showOutliers')) {
2450                    lwhisker = rwhisker = undefined;
2451                    for (i = 0; i < vlen; i++) {
2452                        if (lwhisker === undefined && values[i] > q1 - (iqr * options.get('outlierIQR'))) {
2453                            lwhisker = values[i];
2454                        }
2455                        if (values[i] < q3 + (iqr * options.get('outlierIQR'))) {
2456                            rwhisker = values[i];
2457                        }
2458                    }
2459                    loutlier = values[0];
2460                    routlier = values[vlen - 1];
2461                } else {
2462                    lwhisker = values[0];
2463                    rwhisker = values[vlen - 1];
2464                }
2465            }
2466            this.quartiles = [q1, q2, q3];
2467            this.lwhisker = lwhisker;
2468            this.rwhisker = rwhisker;
2469            this.loutlier = loutlier;
2470            this.routlier = routlier;
2471
2472            unitSize = canvasWidth / (maxValue - minValue + 1);
2473            if (options.get('showOutliers')) {
2474                canvasLeft = Math.ceil(options.get('spotRadius'));
2475                canvasWidth -= 2 * Math.ceil(options.get('spotRadius'));
2476                unitSize = canvasWidth / (maxValue - minValue + 1);
2477                if (loutlier < lwhisker) {
2478                    target.drawCircle((loutlier - minValue) * unitSize + canvasLeft,
2479                        canvasHeight / 2,
2480                        options.get('spotRadius'),
2481                        options.get('outlierLineColor'),
2482                        options.get('outlierFillColor')).append();
2483                }
2484                if (routlier > rwhisker) {
2485                    target.drawCircle((routlier - minValue) * unitSize + canvasLeft,
2486                        canvasHeight / 2,
2487                        options.get('spotRadius'),
2488                        options.get('outlierLineColor'),
2489                        options.get('outlierFillColor')).append();
2490                }
2491            }
2492
2493            // box
2494            target.drawRect(
2495                Math.round((q1 - minValue) * unitSize + canvasLeft),
2496                Math.round(canvasHeight * 0.1),
2497                Math.round((q3 - q1) * unitSize),
2498                Math.round(canvasHeight * 0.8),
2499                options.get('boxLineColor'),
2500                options.get('boxFillColor')).append();
2501            // left whisker
2502            target.drawLine(
2503                Math.round((lwhisker - minValue) * unitSize + canvasLeft),
2504                Math.round(canvasHeight / 2),
2505                Math.round((q1 - minValue) * unitSize + canvasLeft),
2506                Math.round(canvasHeight / 2),
2507                options.get('lineColor')).append();
2508            target.drawLine(
2509                Math.round((lwhisker - minValue) * unitSize + canvasLeft),
2510                Math.round(canvasHeight / 4),
2511                Math.round((lwhisker - minValue) * unitSize + canvasLeft),
2512                Math.round(canvasHeight - canvasHeight / 4),
2513                options.get('whiskerColor')).append();
2514            // right whisker
2515            target.drawLine(Math.round((rwhisker - minValue) * unitSize + canvasLeft),
2516                Math.round(canvasHeight / 2),
2517                Math.round((q3 - minValue) * unitSize + canvasLeft),
2518                Math.round(canvasHeight / 2),
2519                options.get('lineColor')).append();
2520            target.drawLine(
2521                Math.round((rwhisker - minValue) * unitSize + canvasLeft),
2522                Math.round(canvasHeight / 4),
2523                Math.round((rwhisker - minValue) * unitSize + canvasLeft),
2524                Math.round(canvasHeight - canvasHeight / 4),
2525                options.get('whiskerColor')).append();
2526            // median line
2527            target.drawLine(
2528                Math.round((q2 - minValue) * unitSize + canvasLeft),
2529                Math.round(canvasHeight * 0.1),
2530                Math.round((q2 - minValue) * unitSize + canvasLeft),
2531                Math.round(canvasHeight * 0.9),
2532                options.get('medianColor')).append();
2533            if (options.get('target')) {
2534                size = Math.ceil(options.get('spotRadius'));
2535                target.drawLine(
2536                    Math.round((options.get('target') - minValue) * unitSize + canvasLeft),
2537                    Math.round((canvasHeight / 2) - size),
2538                    Math.round((options.get('target') - minValue) * unitSize + canvasLeft),
2539                    Math.round((canvasHeight / 2) + size),
2540                    options.get('targetColor')).append();
2541                target.drawLine(
2542                    Math.round((options.get('target') - minValue) * unitSize + canvasLeft - size),
2543                    Math.round(canvasHeight / 2),
2544                    Math.round((options.get('target') - minValue) * unitSize + canvasLeft + size),
2545                    Math.round(canvasHeight / 2),
2546                    options.get('targetColor')).append();
2547            }
2548            target.render();
2549        }
2550    });
2551
2552    // Setup a very simple "virtual canvas" to make drawing the few shapes we need easier
2553    // This is accessible as $(foo).simpledraw()
2554
2555    VShape = createClass({
2556        init: function (target, id, type, args) {
2557            this.target = target;
2558            this.id = id;
2559            this.type = type;
2560            this.args = args;
2561        },
2562        append: function () {
2563            this.target.appendShape(this);
2564            return this;
2565        }
2566    });
2567
2568    VCanvas_base = createClass({
2569        _pxregex: /(\d+)(px)?\s*$/i,
2570
2571        init: function (width, height, target) {
2572            if (!width) {
2573                return;
2574            }
2575            this.width = width;
2576            this.height = height;
2577            this.target = target;
2578            this.lastShapeId = null;
2579            if (target[0]) {
2580                target = target[0];
2581            }
2582            $.data(target, '_jqs_vcanvas', this);
2583        },
2584
2585        drawLine: function (x1, y1, x2, y2, lineColor, lineWidth) {
2586            return this.drawShape([[x1, y1], [x2, y2]], lineColor, lineWidth);
2587        },
2588
2589        drawShape: function (path, lineColor, fillColor, lineWidth) {
2590            return this._genShape('Shape', [path, lineColor, fillColor, lineWidth]);
2591        },
2592
2593        drawCircle: function (x, y, radius, lineColor, fillColor, lineWidth) {
2594            return this._genShape('Circle', [x, y, radius, lineColor, fillColor, lineWidth]);
2595        },
2596
2597        drawPieSlice: function (x, y, radius, startAngle, endAngle, lineColor, fillColor) {
2598            return this._genShape('PieSlice', [x, y, radius, startAngle, endAngle, lineColor, fillColor]);
2599        },
2600
2601        drawRect: function (x, y, width, height, lineColor, fillColor) {
2602            return this._genShape('Rect', [x, y, width, height, lineColor, fillColor]);
2603        },
2604
2605        getElement: function () {
2606            return this.canvas;
2607        },
2608
2609        /**
2610         * Return the most recently inserted shape id
2611         */
2612        getLastShapeId: function () {
2613            return this.lastShapeId;
2614        },
2615
2616        /**
2617         * Clear and reset the canvas
2618         */
2619        reset: function () {
2620            alert('reset not implemented');
2621        },
2622
2623        _insert: function (el, target) {
2624            $(target).html(el);
2625        },
2626
2627        /**
2628         * Calculate the pixel dimensions of the canvas
2629         */
2630        _calculatePixelDims: function (width, height, canvas) {
2631            // XXX This should probably be a configurable option
2632            var match;
2633            match = this._pxregex.exec(height);
2634            if (match) {
2635                this.pixelHeight = match[1];
2636            } else {
2637                this.pixelHeight = $(canvas).height();
2638            }
2639            match = this._pxregex.exec(width);
2640            if (match) {
2641                this.pixelWidth = match[1];
2642            } else {
2643                this.pixelWidth = $(canvas).width();
2644            }
2645        },
2646
2647        /**
2648         * Generate a shape object and id for later rendering
2649         */
2650        _genShape: function (shapetype, shapeargs) {
2651            var id = shapeCount++;
2652            shapeargs.unshift(id);
2653            return new VShape(this, id, shapetype, shapeargs);
2654        },
2655
2656        /**
2657         * Add a shape to the end of the render queue
2658         */
2659        appendShape: function (shape) {
2660            alert('appendShape not implemented');
2661        },
2662
2663        /**
2664         * Replace one shape with another
2665         */
2666        replaceWithShape: function (shapeid, shape) {
2667            alert('replaceWithShape not implemented');
2668        },
2669
2670        /**
2671         * Insert one shape after another in the render queue
2672         */
2673        insertAfterShape: function (shapeid, shape) {
2674            alert('insertAfterShape not implemented');
2675        },
2676
2677        /**
2678         * Remove a shape from the queue
2679         */
2680        removeShapeId: function (shapeid) {
2681            alert('removeShapeId not implemented');
2682        },
2683
2684        /**
2685         * Find a shape at the specified x/y co-ordinates
2686         */
2687        getShapeAt: function (el, x, y) {
2688            alert('getShapeAt not implemented');
2689        },
2690
2691        /**
2692         * Render all queued shapes onto the canvas
2693         */
2694        render: function () {
2695            alert('render not implemented');
2696        }
2697    });
2698
2699    VCanvas_canvas = createClass(VCanvas_base, {
2700        init: function (width, height, target, interact) {
2701            VCanvas_canvas._super.init.call(this, width, height, target);
2702            this.canvas = document.createElement('canvas');
2703            if (target[0]) {
2704                target = target[0];
2705            }
2706            $.data(target, '_jqs_vcanvas', this);
2707            $(this.canvas).css({ display: 'inline-block', width: width, height: height, verticalAlign: 'top' });
2708            this._insert(this.canvas, target);
2709            this._calculatePixelDims(width, height, this.canvas);
2710            this.canvas.width = this.pixelWidth;
2711            this.canvas.height = this.pixelHeight;
2712            this.interact = interact;
2713            this.shapes = {};
2714            this.shapeseq = [];
2715            this.currentTargetShapeId = undefined;
2716            $(this.canvas).css({width: this.pixelWidth, height: this.pixelHeight});
2717        },
2718
2719        _getContext: function (lineColor, fillColor, lineWidth) {
2720            var context = this.canvas.getContext('2d');
2721            if (lineColor !== undefined) {
2722                context.strokeStyle = lineColor;
2723            }
2724            context.lineWidth = lineWidth === undefined ? 1 : lineWidth;
2725            if (fillColor !== undefined) {
2726                context.fillStyle = fillColor;
2727            }
2728            return context;
2729        },
2730
2731        reset: function () {
2732            var context = this._getContext();
2733            context.clearRect(0, 0, this.pixelWidth, this.pixelHeight);
2734            this.shapes = {};
2735            this.shapeseq = [];
2736            this.currentTargetShapeId = undefined;
2737        },
2738
2739        _drawShape: function (shapeid, path, lineColor, fillColor, lineWidth) {
2740            var context = this._getContext(lineColor, fillColor, lineWidth),
2741                i, plen;
2742            context.beginPath();
2743            context.moveTo(path[0][0] + 0.5, path[0][1] + 0.5);
2744            for (i = 1, plen = path.length; i < plen; i++) {
2745                context.lineTo(path[i][0] + 0.5, path[i][1] + 0.5); // the 0.5 offset gives us crisp pixel-width lines
2746            }
2747            if (lineColor !== undefined) {
2748                context.stroke();
2749            }
2750            if (fillColor !== undefined) {
2751                context.fill();
2752            }
2753            if (this.targetX !== undefined && this.targetY !== undefined &&
2754                context.isPointInPath(this.targetX, this.targetY)) {
2755                this.currentTargetShapeId = shapeid;
2756            }
2757        },
2758
2759        _drawCircle: function (shapeid, x, y, radius, lineColor, fillColor, lineWidth) {
2760            var context = this._getContext(lineColor, fillColor, lineWidth);
2761            context.beginPath();
2762            context.arc(x, y, radius, 0, 2 * Math.PI, false);
2763            if (this.targetX !== undefined && this.targetY !== undefined &&
2764                context.isPointInPath(this.targetX, this.targetY)) {
2765                this.currentTargetShapeId = shapeid;
2766            }
2767            if (lineColor !== undefined) {
2768                context.stroke();
2769            }
2770            if (fillColor !== undefined) {
2771                context.fill();
2772            }
2773        },
2774
2775        _drawPieSlice: function (shapeid, x, y, radius, startAngle, endAngle, lineColor, fillColor) {
2776            var context = this._getContext(lineColor, fillColor);
2777            context.beginPath();
2778            context.moveTo(x, y);
2779            context.arc(x, y, radius, startAngle, endAngle, false);
2780            context.lineTo(x, y);
2781            context.closePath();
2782            if (lineColor !== undefined) {
2783                context.stroke();
2784            }
2785            if (fillColor) {
2786                context.fill();
2787            }
2788            if (this.targetX !== undefined && this.targetY !== undefined &&
2789                context.isPointInPath(this.targetX, this.targetY)) {
2790                this.currentTargetShapeId = shapeid;
2791            }
2792        },
2793
2794        _drawRect: function (shapeid, x, y, width, height, lineColor, fillColor) {
2795            return this._drawShape(shapeid, [[x, y], [x + width, y], [x + width, y + height], [x, y + height], [x, y]], lineColor, fillColor);
2796        },
2797
2798        appendShape: function (shape) {
2799            this.shapes[shape.id] = shape;
2800            this.shapeseq.push(shape.id);
2801            this.lastShapeId = shape.id;
2802            return shape.id;
2803        },
2804
2805        replaceWithShape: function (shapeid, shape) {
2806            var shapeseq = this.shapeseq,
2807                i;
2808            this.shapes[shape.id] = shape;
2809            for (i = shapeseq.length; i--;) {
2810                if (shapeseq[i] == shapeid) {
2811                    shapeseq[i] = shape.id;
2812                }
2813            }
2814            delete this.shapes[shapeid];
2815        },
2816
2817        replaceWithShapes: function (shapeids, shapes) {
2818            var shapeseq = this.shapeseq,
2819                shapemap = {},
2820                sid, i, first;
2821
2822            for (i = shapeids.length; i--;) {
2823                shapemap[shapeids[i]] = true;
2824            }
2825            for (i = shapeseq.length; i--;) {
2826                sid = shapeseq[i];
2827                if (shapemap[sid]) {
2828                    shapeseq.splice(i, 1);
2829                    delete this.shapes[sid];
2830                    first = i;
2831                }
2832            }
2833            for (i = shapes.length; i--;) {
2834                shapeseq.splice(first, 0, shapes[i].id);
2835                this.shapes[shapes[i].id] = shapes[i];
2836            }
2837
2838        },
2839
2840        insertAfterShape: function (shapeid, shape) {
2841            var shapeseq = this.shapeseq,
2842                i;
2843            for (i = shapeseq.length; i--;) {
2844                if (shapeseq[i] === shapeid) {
2845                    shapeseq.splice(i + 1, 0, shape.id);
2846                    this.shapes[shape.id] = shape;
2847                    return;
2848                }
2849            }
2850        },
2851
2852        removeShapeId: function (shapeid) {
2853            var shapeseq = this.shapeseq,
2854                i;
2855            for (i = shapeseq.length; i--;) {
2856                if (shapeseq[i] === shapeid) {
2857                    shapeseq.splice(i, 1);
2858                    break;
2859                }
2860            }
2861            delete this.shapes[shapeid];
2862        },
2863
2864        getShapeAt: function (el, x, y) {
2865            this.targetX = x;
2866            this.targetY = y;
2867            this.render();
2868            return this.currentTargetShapeId;
2869        },
2870
2871        render: function () {
2872            var shapeseq = this.shapeseq,
2873                shapes = this.shapes,
2874                shapeCount = shapeseq.length,
2875                context = this._getContext(),
2876                shapeid, shape, i;
2877            context.clearRect(0, 0, this.pixelWidth, this.pixelHeight);
2878            for (i = 0; i < shapeCount; i++) {
2879                shapeid = shapeseq[i];
2880                shape = shapes[shapeid];
2881                this['_draw' + shape.type].apply(this, shape.args);
2882            }
2883            if (!this.interact) {
2884                // not interactive so no need to keep the shapes array
2885                this.shapes = {};
2886                this.shapeseq = [];
2887            }
2888        }
2889
2890    });
2891
2892    VCanvas_vml = createClass(VCanvas_base, {
2893        init: function (width, height, target) {
2894            var groupel;
2895            VCanvas_vml._super.init.call(this, width, height, target);
2896            if (target[0]) {
2897                target = target[0];
2898            }
2899            $.data(target, '_jqs_vcanvas', this);
2900            this.canvas = document.createElement('span');
2901            $(this.canvas).css({ display: 'inline-block', position: 'relative', overflow: 'hidden', width: width, height: height, margin: '0px', padding: '0px', verticalAlign: 'top'});
2902            this._insert(this.canvas, target);
2903            this._calculatePixelDims(width, height, this.canvas);
2904            this.canvas.width = this.pixelWidth;
2905            this.canvas.height = this.pixelHeight;
2906            groupel = '<v:group coordorigin="0 0" coordsize="' + this.pixelWidth + ' ' + this.pixelHeight + '"' +
2907                    ' style="position:absolute;top:0;left:0;width:' + this.pixelWidth + 'px;height=' + this.pixelHeight + 'px;"></v:group>';
2908            this.canvas.insertAdjacentHTML('beforeEnd', groupel);
2909            this.group = $(this.canvas).children()[0];
2910            this.rendered = false;
2911            this.prerender = '';
2912        },
2913
2914        _drawShape: function (shapeid, path, lineColor, fillColor, lineWidth) {
2915            var vpath = [],
2916                initial, stroke, fill, closed, vel, plen, i;
2917            for (i = 0, plen = path.length; i < plen; i++) {
2918                vpath[i] = '' + (path[i][0]) + ',' + (path[i][1]);
2919            }
2920            initial = vpath.splice(0, 1);
2921            lineWidth = lineWidth === undefined ? 1 : lineWidth;
2922            stroke = lineColor === undefined ? ' stroked="false" ' : ' strokeWeight="' + lineWidth + 'px" strokeColor="' + lineColor + '" ';
2923            fill = fillColor === undefined ? ' filled="false"' : ' fillColor="' + fillColor + '" filled="true" ';
2924            closed = vpath[0] === vpath[vpath.length - 1] ? 'x ' : '';
2925            vel = '<v:shape coordorigin="0 0" coordsize="' + this.pixelWidth + ' ' + this.pixelHeight + '" ' +
2926                 ' id="jqsshape' + shapeid + '" ' +
2927                 stroke +
2928                 fill +
2929                ' style="position:absolute;left:0px;top:0px;height:' + this.pixelHeight + 'px;width:' + this.pixelWidth + 'px;padding:0px;margin:0px;" ' +
2930                ' path="m ' + initial + ' l ' + vpath.join(', ') + ' ' + closed + 'e">' +
2931                ' </v:shape>';
2932            return vel;
2933        },
2934
2935        _drawCircle: function (shapeid, x, y, radius, lineColor, fillColor, lineWidth) {
2936            var stroke, fill, vel;
2937            x -= radius;
2938            y -= radius;
2939            stroke = lineColor === undefined ? ' stroked="false" ' : ' strokeWeight="' + lineWidth + 'px" strokeColor="' + lineColor + '" ';
2940            fill = fillColor === undefined ? ' filled="false"' : ' fillColor="' + fillColor + '" filled="true" ';
2941            vel = '<v:oval ' +
2942                 ' id="jqsshape' + shapeid + '" ' +
2943                stroke +
2944                fill +
2945                ' style="position:absolute;top:' + y + 'px; left:' + x + 'px; width:' + (radius * 2) + 'px; height:' + (radius * 2) + 'px"></v:oval>';
2946            return vel;
2947
2948        },
2949
2950        _drawPieSlice: function (shapeid, x, y, radius, startAngle, endAngle, lineColor, fillColor) {
2951            var vpath, startx, starty, endx, endy, stroke, fill, vel;
2952            if (startAngle === endAngle) {
2953                return '';  // VML seems to have problem when start angle equals end angle.
2954            }
2955            if ((endAngle - startAngle) === (2 * Math.PI)) {
2956                startAngle = 0.0;  // VML seems to have a problem when drawing a full circle that doesn't start 0
2957                endAngle = (2 * Math.PI);
2958            }
2959
2960            startx = x + Math.round(Math.cos(startAngle) * radius);
2961            starty = y + Math.round(Math.sin(startAngle) * radius);
2962            endx = x + Math.round(Math.cos(endAngle) * radius);
2963            endy = y + Math.round(Math.sin(endAngle) * radius);
2964
2965            if (startx === endx && starty === endy) {
2966                if ((endAngle - startAngle) < Math.PI) {
2967                    // Prevent very small slices from being mistaken as a whole pie
2968                    return '';
2969                }
2970                // essentially going to be the entire circle, so ignore startAngle
2971                startx = endx = x + radius;
2972                starty = endy = y;
2973            }
2974
2975            if (startx === endx && starty === endy && (endAngle - startAngle) < Math.PI) {
2976                return '';
2977            }
2978
2979            vpath = [x - radius, y - radius, x + radius, y + radius, startx, starty, endx, endy];
2980            stroke = lineColor === undefined ? ' stroked="false" ' : ' strokeWeight="1px" strokeColor="' + lineColor + '" ';
2981            fill = fillColor === undefined ? ' filled="false"' : ' fillColor="' + fillColor + '" filled="true" ';
2982            vel = '<v:shape coordorigin="0 0" coordsize="' + this.pixelWidth + ' ' + this.pixelHeight + '" ' +
2983                 ' id="jqsshape' + shapeid + '" ' +
2984                 stroke +
2985                 fill +
2986                ' style="position:absolute;left:0px;top:0px;height:' + this.pixelHeight + 'px;width:' + this.pixelWidth + 'px;padding:0px;margin:0px;" ' +
2987                ' path="m ' + x + ',' + y + ' wa ' + vpath.join(', ') + ' x e">' +
2988                ' </v:shape>';
2989            return vel;
2990        },
2991
2992        _drawRect: function (shapeid, x, y, width, height, lineColor, fillColor) {
2993            return this._drawShape(shapeid, [[x, y], [x, y + height], [x + width, y + height], [x + width, y], [x, y]], lineColor, fillColor);
2994        },
2995
2996        reset: function () {
2997            this.group.innerHTML = '';
2998        },
2999
3000        appendShape: function (shape) {
3001            var vel = this['_draw' + shape.type].apply(this, shape.args);
3002            if (this.rendered) {
3003                this.group.insertAdjacentHTML('beforeEnd', vel);
3004            } else {
3005                this.prerender += vel;
3006            }
3007            this.lastShapeId = shape.id;
3008            return shape.id;
3009        },
3010
3011        replaceWithShape: function (shapeid, shape) {
3012            var existing = $('#jqsshape' + shapeid),
3013                vel = this['_draw' + shape.type].apply(this, shape.args);
3014            existing[0].outerHTML = vel;
3015        },
3016
3017        replaceWithShapes: function (shapeids, shapes) {
3018            // replace the first shapeid with all the new shapes then toast the remaining old shapes
3019            var existing = $('#jqsshape' + shapeids[0]),
3020                replace = '',
3021                slen = shapes.length,
3022                i;
3023            for (i = 0; i < slen; i++) {
3024                replace += this['_draw' + shapes[i].type].apply(this, shapes[i].args);
3025            }
3026            existing[0].outerHTML = replace;
3027            for (i = 1; i < shapeids.length; i++) {
3028                $('#jqsshape' + shapeids[i]).remove();
3029            }
3030        },
3031
3032        insertAfterShape: function (shapeid, shape) {
3033            var existing = $('#jqsshape' + shapeid),
3034                 vel = this['_draw' + shape.type].apply(this, shape.args);
3035            existing[0].insertAdjacentHTML('afterEnd', vel);
3036        },
3037
3038        removeShapeId: function (shapeid) {
3039            var existing = $('#jqsshape' + shapeid);
3040            this.group.removeChild(existing[0]);
3041        },
3042
3043        getShapeAt: function (el, x, y) {
3044            var shapeid = el.id.substr(8);
3045            return shapeid;
3046        },
3047
3048        render: function () {
3049            if (!this.rendered) {
3050                // batch the intial render into a single repaint
3051                this.group.innerHTML = this.prerender;
3052                this.rendered = true;
3053            }
3054        }
3055    });
3056
3057}))}(document, Math));

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.