You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@lucene.apache.org by st...@apache.org on 2013/01/24 20:32:41 UTC

svn commit: r1438137 [2/2] - in /lucene/dev/branches/branch_4x: ./ solr/ solr/CHANGES.txt solr/webapp/ solr/webapp/web/js/lib/jquery.sparkline.js

Modified: lucene/dev/branches/branch_4x/solr/webapp/web/js/lib/jquery.sparkline.js
URL: http://svn.apache.org/viewvc/lucene/dev/branches/branch_4x/solr/webapp/web/js/lib/jquery.sparkline.js?rev=1438137&r1=1438136&r2=1438137&view=diff
==============================================================================
--- lucene/dev/branches/branch_4x/solr/webapp/web/js/lib/jquery.sparkline.js (original)
+++ lucene/dev/branches/branch_4x/solr/webapp/web/js/lib/jquery.sparkline.js Thu Jan 24 19:32:40 2013
@@ -2,46 +2,46 @@
 *
 * jquery.sparkline.js
 *
-* v1.6
-* (c) Splunk, Inc 
+* v2.1
+* (c) Splunk, Inc
 * Contact: Gareth Watts (gareth@splunk.com)
 * http://omnipotent.net/jquery.sparkline/
 *
 * Generates inline sparkline charts from data supplied either to the method
 * or inline in HTML
-* 
+*
 * Compatible with Internet Explorer 6.0+ and modern browsers equipped with the canvas tag
 * (Firefox 2.0+, Safari, Opera, etc)
 *
 * License: New BSD License
-* 
-* Copyright (c) 2010, Splunk Inc.
+*
+* Copyright (c) 2012, Splunk Inc.
 * All rights reserved.
-* 
-* Redistribution and use in source and binary forms, with or without modification, 
+*
+* Redistribution and use in source and binary forms, with or without modification,
 * are permitted provided that the following conditions are met:
-* 
-*     * Redistributions of source code must retain the above copyright notice, 
+*
+*     * Redistributions of source code must retain the above copyright notice,
 *       this list of conditions and the following disclaimer.
-*     * Redistributions in binary form must reproduce the above copyright notice, 
-*       this list of conditions and the following disclaimer in the documentation 
+*     * Redistributions in binary form must reproduce the above copyright notice,
+*       this list of conditions and the following disclaimer in the documentation
 *       and/or other materials provided with the distribution.
-*     * Neither the name of Splunk Inc nor the names of its contributors may 
-*       be used to endorse or promote products derived from this software without 
+*     * Neither the name of Splunk Inc nor the names of its contributors may
+*       be used to endorse or promote products derived from this software without
 *       specific prior written permission.
-* 
-* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY 
-* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 
-* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT 
-* SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 
-* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT 
-* OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 
-* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 
-* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
+* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
+* SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
+* OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-* 
 *
-* Usage: 
+*
+* Usage:
 *  $(selector).sparkline(values, options)
 *
 * If values is undefined or set to 'html' then the data values are read from the specified tag:
@@ -86,8 +86,34 @@
 *           another chart over the top - Note that width and height are ignored if an
 *           existing chart is detected.
 *   tagValuesAttribute - Name of tag attribute to check for data values - Defaults to 'values'
-*   enableTagOptions - Whether to check tags for sparkline options 
+*   enableTagOptions - Whether to check tags for sparkline options
 *   tagOptionPrefix - Prefix used for options supplied as tag attributes - Defaults to 'spark'
+*   disableHiddenCheck - If set to true, then the plugin will assume that charts will never be drawn into a
+*           hidden dom element, avoding a browser reflow
+*   disableInteraction - If set to true then all mouseover/click interaction behaviour will be disabled,
+*       making the plugin perform much like it did in 1.x
+*   disableTooltips - If set to true then tooltips will be disabled - Defaults to false (tooltips enabled)
+*   disableHighlight - If set to true then highlighting of selected chart elements on mouseover will be disabled
+*       defaults to false (highlights enabled)
+*   highlightLighten - Factor to lighten/darken highlighted chart values by - Defaults to 1.4 for a 40% increase
+*   tooltipContainer - Specify which DOM element the tooltip should be rendered into - defaults to document.body
+*   tooltipClassname - Optional CSS classname to apply to tooltips - If not specified then a default style will be applied
+*   tooltipOffsetX - How many pixels away from the mouse pointer to render the tooltip on the X axis
+*   tooltipOffsetY - How many pixels away from the mouse pointer to render the tooltip on the r axis
+*   tooltipFormatter  - Optional callback that allows you to override the HTML displayed in the tooltip
+*       callback is given arguments of (sparkline, options, fields)
+*   tooltipChartTitle - If specified then the tooltip uses the string specified by this setting as a title
+*   tooltipFormat - A format string or SPFormat object  (or an array thereof for multiple entries)
+*       to control the format of the tooltip
+*   tooltipPrefix - A string to prepend to each field displayed in a tooltip
+*   tooltipSuffix - A string to append to each field displayed in a tooltip
+*   tooltipSkipNull - If true then null values will not have a tooltip displayed (defaults to true)
+*   tooltipValueLookups - An object or range map to map field values to tooltip strings
+*       (eg. to map -1 to "Lost", 0 to "Draw", and 1 to "Win")
+*   numberFormatter - Optional callback for formatting numbers in tooltips
+*   numberDigitGroupSep - Character to use for group separator in numbers "1,234" - Defaults to ","
+*   numberDecimalMark - Character to use for the decimal point when formatting numbers - Defaults to "."
+*   numberDigitGroupCount - Number of digits between group separator - Defaults to 3
 *
 * There are 7 types of sparkline, selected by supplying a "type" option of 'line' (default),
 * 'bar', 'tristate', 'bullet', 'discrete', 'pie' or 'box'
@@ -97,12 +123,15 @@
 *       maxSpotColor - If set, color of spot at maximum value
 *       spotRadius - Radius in pixels
 *       lineWidth - Width of line in pixels
-*       normalRangeMin 
+*       normalRangeMin
 *       normalRangeMax - If set draws a filled horizontal bar between these two values marking the "normal"
 *                      or expected range of values
 *       normalRangeColor - Color to use for the above bar
 *       drawNormalOnTop - Draw the normal range above the chart fill color if true
 *       defaultPixelsPerValue - Defaults to 3 pixels of width for each value in the chart
+*       highlightSpotColor - The color to use for drawing a highlight spot on mouseover - Set to null to disable
+*       highlightLineColor - The color to use for drawing a highlight line on mouseover - Set to null to disable
+*       valueSpots - Specify which points to draw spots on, and in which color.  Accepts a range map
 *
 *   bar - Bar chart.  Options:
 *       barColor - Color of bars for postive values
@@ -111,7 +140,8 @@
 *       nullColor - Color of bars with null values - Defaults to omitting the bar entirely
 *       barWidth - Width of bars in pixels
 *       colorMap - Optional mappnig of values to colors to override the *BarColor values above
-*                  can be an Array of values to control the color of individual bars
+*                  can be an Array of values to control the color of individual bars or a range map
+*                  to specify colors for individual ranges of values
 *       barSpacing - Gap between bars in pixels
 *       zeroAxis - Centers the y-axis around zero if true
 *
@@ -122,7 +152,8 @@
 *       barWidth - Width of bars in pixels
 *       barSpacing - Gap between bars in pixels
 *       colorMap - Optional mappnig of values to colors to override the *BarColor values above
-*                  can be an Array of values to control the color of individual bars
+*                  can be an Array of values to control the color of individual bars or a range map
+*                  to specify colors for individual ranges of values
 *
 *   discrete - Options:
 *       lineHeight - Height of each line in pixels - Defaults to 30% of the graph height
@@ -139,6 +170,8 @@
 *   pie - Pie chart. Options:
 *       sliceColors - An array of colors to use for pie slices
 *       offset - Angle in degrees to offset the first slice - Try -90 or +90
+*       borderWidth - Width of border to draw around the pie chart, in pixels - Defaults to 0 (no border)
+*       borderColor - Color to use for the pie chart border - Defaults to #000
 *
 *   box - Box plot. Options:
 *       raw - Set to true to supply pre-computed plot points as values
@@ -146,7 +179,7 @@
 *             When set to false you can supply any number of values and the box plot will
 *             be computed for you.  Default is false.
 *       showOutliers - Set to true (default) to display outliers as circles
-*       outlierIRQ - Interquartile range used to determine outliers.  Default 1.5
+*       outlierIQR - Interquartile range used to determine outliers.  Default 1.5
 *       boxLineColor - Outline color of the box
 *       boxFillColor - Fill color for the box
 *       whiskerColor - Line color used for whiskers
@@ -155,9 +188,9 @@
 *       spotRadius - Radius of outlier circles
 *       medianColor - Line color of the median line
 *       target - Draw a target cross hair at the supplied value (default undefined)
-*      
-*   
-*       
+*
+*
+*
 *   Examples:
 *   $('#sparkline1').sparkline(myvalues, { lineColor: '#f00', fillColor: false });
 *   $('.barsparks').sparkline('html', { type:'bar', height:'40px', barWidth:5 });
@@ -167,189 +200,301 @@
 *   $('#pie').sparkline([1,1,2], { type:'pie' });
 */
 
+/*jslint regexp: true, browser: true, jquery: true, white: true, nomen: false, plusplus: false, maxerr: 500, indent: 4 */
 
+(function(factory) {
+    if(typeof define === 'function' && define.amd) {
+		define(['jquery'], factory);
+	}
+	else {
+		factory(jQuery);
+	}
+}
 (function($) {
+    'use strict';
+
+    var UNSET_OPTION = {},
+        getDefaults, createClass, SPFormat, clipval, quartile, normalizeValue, normalizeValues,
+        remove, isNumber, all, sum, addCSS, ensureArray, formatNumber, RangeMap,
+        MouseHandler, Tooltip, barHighlightMixin,
+        line, bar, tristate, discrete, bullet, pie, box, defaultStyles, initStyles,
+         VShape, VCanvas_base, VCanvas_canvas, VCanvas_vml, pending, shapeCount = 0;
 
-    /*
+    /**
      * Default configuration settings
      */
-    var defaults = {
-        // Settings common to most/all chart types
-        common: {
-            type : 'line',
-            lineColor : '#00f',
-            fillColor : '#cdf',
-            defaultPixelsPerValue : 3,
-            width : 'auto', 
-            height : 'auto',
-            composite : false,
-            tagValuesAttribute: 'values',
-            tagOptionsPrefix: 'spark',
-            enableTagOptions: false
-        },
-        // Defaults for line charts
-        line: {
-            spotColor : '#f80',
-            spotRadius : 1.5,
-            minSpotColor : '#f80',
-            maxSpotColor : '#f80',
-            lineWidth: 1, 
-            normalRangeMin : undefined,
-            normalRangeMax : undefined,
-            normalRangeColor : '#ccc',
-            drawNormalOnTop: false,
-            chartRangeMin : undefined,
-            chartRangeMax : undefined,
-            chartRangeMinX : undefined,
-            chartRangeMaxX : undefined
-        },
-        // Defaults for bar charts
-        bar: {
-            barColor : '#00f',
-            negBarColor : '#f44',
-            zeroColor: undefined,
-            nullColor: undefined,
-            zeroAxis : undefined,
-            barWidth : 4,
-            barSpacing : 1,
-            chartRangeMax: undefined,
-            chartRangeMin: undefined,
-            chartRangeClip: false,
-            colorMap : undefined
-        },
-        // Defaults for tristate charts
-        tristate: {
-            barWidth : 4,
-            barSpacing : 1,
-            posBarColor: '#6f6',
-            negBarColor : '#f44',
-            zeroBarColor : '#999',
-            colorMap : {}
-        },
-        // Defaults for discrete charts
-        discrete: {
-            lineHeight: 'auto',
-            thresholdColor: undefined,
-            thresholdValue : 0,
-            chartRangeMax: undefined,
-            chartRangeMin: undefined,
-            chartRangeClip: false
-        },
-        // Defaults for bullet charts
-        bullet: {
-            targetColor : 'red',
-            targetWidth : 3, // width of the target bar in pixels
-            performanceColor : 'blue',
-            rangeColors : ['#D3DAFE', '#A8B6FF', '#7F94FF' ],
-            base : undefined // set this to a number to change the base start number
-        },
-        // Defaults for pie charts
-        pie: {
-            sliceColors : ['#f00', '#0f0', '#00f']
-        },
-        // Defaults for box plots
-        box: {
-            raw: false,
-            boxLineColor: 'black',
-            boxFillColor: '#cdf',
-            whiskerColor: 'black',
-            outlierLineColor: '#333',
-            outlierFillColor: 'white',
-            medianColor: 'red',
-            showOutliers: true,
-            outlierIQR: 1.5,
-            spotRadius: 1.5,
-            target: undefined,
-            targetColor: '#4a2',
-            chartRangeMax: undefined,
-            chartRangeMin: undefined
-        }
+    getDefaults = function () {
+        return {
+            // Settings common to most/all chart types
+            common: {
+                type: 'line',
+                lineColor: '#00f',
+                fillColor: '#cdf',
+                defaultPixelsPerValue: 3,
+                width: 'auto',
+                height: 'auto',
+                composite: false,
+                tagValuesAttribute: 'values',
+                tagOptionsPrefix: 'spark',
+                enableTagOptions: false,
+                enableHighlight: true,
+                highlightLighten: 1.4,
+                tooltipSkipNull: true,
+                tooltipPrefix: '',
+                tooltipSuffix: '',
+                disableHiddenCheck: false,
+                numberFormatter: false,
+                numberDigitGroupCount: 3,
+                numberDigitGroupSep: ',',
+                numberDecimalMark: '.',
+                disableTooltips: false,
+                disableInteraction: false
+            },
+            // Defaults for line charts
+            line: {
+                spotColor: '#f80',
+                highlightSpotColor: '#5f5',
+                highlightLineColor: '#f22',
+                spotRadius: 1.5,
+                minSpotColor: '#f80',
+                maxSpotColor: '#f80',
+                lineWidth: 1,
+                normalRangeMin: undefined,
+                normalRangeMax: undefined,
+                normalRangeColor: '#ccc',
+                drawNormalOnTop: false,
+                chartRangeMin: undefined,
+                chartRangeMax: undefined,
+                chartRangeMinX: undefined,
+                chartRangeMaxX: undefined,
+                tooltipFormat: new SPFormat('<span style="color: {{color}}">&#9679;</span> {{prefix}}{{y}}{{suffix}}')
+            },
+            // Defaults for bar charts
+            bar: {
+                barColor: '#3366cc',
+                negBarColor: '#f44',
+                stackedBarColor: ['#3366cc', '#dc3912', '#ff9900', '#109618', '#66aa00',
+                    '#dd4477', '#0099c6', '#990099'],
+                zeroColor: undefined,
+                nullColor: undefined,
+                zeroAxis: true,
+                barWidth: 4,
+                barSpacing: 1,
+                chartRangeMax: undefined,
+                chartRangeMin: undefined,
+                chartRangeClip: false,
+                colorMap: undefined,
+                tooltipFormat: new SPFormat('<span style="color: {{color}}">&#9679;</span> {{prefix}}{{value}}{{suffix}}')
+            },
+            // Defaults for tristate charts
+            tristate: {
+                barWidth: 4,
+                barSpacing: 1,
+                posBarColor: '#6f6',
+                negBarColor: '#f44',
+                zeroBarColor: '#999',
+                colorMap: {},
+                tooltipFormat: new SPFormat('<span style="color: {{color}}">&#9679;</span> {{value:map}}'),
+                tooltipValueLookups: { map: { '-1': 'Loss', '0': 'Draw', '1': 'Win' } }
+            },
+            // Defaults for discrete charts
+            discrete: {
+                lineHeight: 'auto',
+                thresholdColor: undefined,
+                thresholdValue: 0,
+                chartRangeMax: undefined,
+                chartRangeMin: undefined,
+                chartRangeClip: false,
+                tooltipFormat: new SPFormat('{{prefix}}{{value}}{{suffix}}')
+            },
+            // Defaults for bullet charts
+            bullet: {
+                targetColor: '#f33',
+                targetWidth: 3, // width of the target bar in pixels
+                performanceColor: '#33f',
+                rangeColors: ['#d3dafe', '#a8b6ff', '#7f94ff'],
+                base: undefined, // set this to a number to change the base start number
+                tooltipFormat: new SPFormat('{{fieldkey:fields}} - {{value}}'),
+                tooltipValueLookups: { fields: {r: 'Range', p: 'Performance', t: 'Target'} }
+            },
+            // Defaults for pie charts
+            pie: {
+                offset: 0,
+                sliceColors: ['#3366cc', '#dc3912', '#ff9900', '#109618', '#66aa00',
+                    '#dd4477', '#0099c6', '#990099'],
+                borderWidth: 0,
+                borderColor: '#000',
+                tooltipFormat: new SPFormat('<span style="color: {{color}}">&#9679;</span> {{value}} ({{percent.1}}%)')
+            },
+            // Defaults for box plots
+            box: {
+                raw: false,
+                boxLineColor: '#000',
+                boxFillColor: '#cdf',
+                whiskerColor: '#000',
+                outlierLineColor: '#333',
+                outlierFillColor: '#fff',
+                medianColor: '#f00',
+                showOutliers: true,
+                outlierIQR: 1.5,
+                spotRadius: 1.5,
+                target: undefined,
+                targetColor: '#4a2',
+                chartRangeMax: undefined,
+                chartRangeMin: undefined,
+                tooltipFormat: new SPFormat('{{field:fields}}: {{value}}'),
+                tooltipFormatFieldlistKey: 'field',
+                tooltipValueLookups: { fields: { lq: 'Lower Quartile', med: 'Median',
+                    uq: 'Upper Quartile', lo: 'Left Outlier', ro: 'Right Outlier',
+                    lw: 'Left Whisker', rw: 'Right Whisker'} }
+            }
+        };
     };
 
-    // Provide a cross-browser interface to a few simple drawing primitives
-    var VCanvas_base, VCanvas_canvas, VCanvas_vml;
-    $.fn.simpledraw = function(width, height, use_existing) {
-        if (use_existing && this[0].VCanvas) {
-            return this[0].VCanvas;
-        }
-        if (width === undefined) { 
-            width=$(this).innerWidth();
-        }
-        if (height === undefined) {
-            height=$(this).innerHeight();
-        }
-        if ($.browser.hasCanvas) {
-            return new VCanvas_canvas(width, height, this);
-        } else if ($.browser.msie) {
-            return new VCanvas_vml(width, height, this);
+    // You can have tooltips use a css class other than jqstooltip by specifying tooltipClassname
+    defaultStyles = '.jqstooltip { ' +
+            'position: absolute;' +
+            'left: 0px;' +
+            'top: 0px;' +
+            'visibility: hidden;' +
+            'background: rgb(0, 0, 0) transparent;' +
+            'background-color: rgba(0,0,0,0.6);' +
+            'filter:progid:DXImageTransform.Microsoft.gradient(startColorstr=#99000000, endColorstr=#99000000);' +
+            '-ms-filter: "progid:DXImageTransform.Microsoft.gradient(startColorstr=#99000000, endColorstr=#99000000)";' +
+            'color: white;' +
+            'font: 10px arial, san serif;' +
+            'text-align: left;' +
+            'white-space: nowrap;' +
+            'padding: 5px;' +
+            'border: 1px solid white;' +
+            'z-index: 10000;' +
+            '}' +
+            '.jqsfield { ' +
+            'color: white;' +
+            'font: 10px arial, san serif;' +
+            'text-align: left;' +
+            '}';
+
+    /**
+     * Utilities
+     */
+
+    createClass = function (/* [baseclass, [mixin, ...]], definition */) {
+        var Class, args;
+        Class = function () {
+            this.init.apply(this, arguments);
+        };
+        if (arguments.length > 1) {
+            if (arguments[0]) {
+                Class.prototype = $.extend(new arguments[0](), arguments[arguments.length - 1]);
+                Class._super = arguments[0].prototype;
+            } else {
+                Class.prototype = arguments[arguments.length - 1];
+            }
+            if (arguments.length > 2) {
+                args = Array.prototype.slice.call(arguments, 1, -1);
+                args.unshift(Class.prototype);
+                $.extend.apply($, args);
+            }
         } else {
-            return false;
+            Class.prototype = arguments[0];
         }
+        Class.prototype.cls = Class;
+        return Class;
     };
 
-    var pending = [];
-
-
-    $.fn.sparkline = function(uservalues, userOptions) {
-        return this.each(function() {
-            var options = new $.fn.sparkline.options(this, userOptions);
-            var render = function() {
-                var values, width, height;
-                if (uservalues==='html' || uservalues===undefined) {
-                    var vals = this.getAttribute(options.get('tagValuesAttribute'));
-                    if (vals===undefined || vals===null) {
-                        vals = $(this).html();
-                    }
-                    values = vals.replace(/(^\s*<!--)|(-->\s*$)|\s+/g, '').split(',');
+    /**
+     * Wraps a format string for tooltips
+     * {{x}}
+     * {{x.2}
+     * {{x:months}}
+     */
+    $.SPFormatClass = SPFormat = createClass({
+        fre: /\{\{([\w.]+?)(:(.+?))?\}\}/g,
+        precre: /(\w+)\.(\d+)/,
+
+        init: function (format, fclass) {
+            this.format = format;
+            this.fclass = fclass;
+        },
+
+        render: function (fieldset, lookups, options) {
+            var self = this,
+                fields = fieldset,
+                match, token, lookupkey, fieldvalue, prec;
+            return this.format.replace(this.fre, function () {
+                var lookup;
+                token = arguments[1];
+                lookupkey = arguments[3];
+                match = self.precre.exec(token);
+                if (match) {
+                    prec = match[2];
+                    token = match[1];
                 } else {
-                    values = uservalues;
+                    prec = false;
                 }
-
-                width = options.get('width')=='auto' ? values.length*options.get('defaultPixelsPerValue') : options.get('width');
-                if (options.get('height') == 'auto') {
-                    if (!options.get('composite') || !this.VCanvas) {
-                        // must be a better way to get the line height
-                        var tmp = document.createElement('span');
-                        tmp.innerHTML = 'a';
-                        $(this).html(tmp);
-                        height = $(tmp).innerHeight();
-                        $(tmp).remove();
+                fieldvalue = fields[token];
+                if (fieldvalue === undefined) {
+                    return '';
+                }
+                if (lookupkey && lookups && lookups[lookupkey]) {
+                    lookup = lookups[lookupkey];
+                    if (lookup.get) { // RangeMap
+                        return lookups[lookupkey].get(fieldvalue) || fieldvalue;
+                    } else {
+                        return lookups[lookupkey][fieldvalue] || fieldvalue;
+                    }
+                }
+                if (isNumber(fieldvalue)) {
+                    if (options.get('numberFormatter')) {
+                        fieldvalue = options.get('numberFormatter')(fieldvalue);
+                    } else {
+                        fieldvalue = formatNumber(fieldvalue, prec,
+                            options.get('numberDigitGroupCount'),
+                            options.get('numberDigitGroupSep'),
+                            options.get('numberDecimalMark'));
                     }
-                } else {
-                    height = options.get('height');
                 }
+                return fieldvalue;
+            });
+        }
+    });
 
-                $.fn.sparkline[options.get('type')].call(this, values, options, width, height);
-            };
-            // jQuery 1.3.0 completely changed the meaning of :hidden :-/
-            if (($(this).html() && $(this).is(':hidden')) || ($.fn.jquery < "1.3.0" && $(this).parents().is(':hidden')) || !$(this).parents('body').length) {
-                pending.push([this, render]);
-            } else {
-                render.call(this);
-            }
-        });
+    // convience method to avoid needing the new operator
+    $.spformat = function(format, fclass) {
+        return new SPFormat(format, fclass);
     };
 
-    $.fn.sparkline.defaults = defaults;
+    clipval = function (val, min, max) {
+        if (val < min) {
+            return min;
+        }
+        if (val > max) {
+            return max;
+        }
+        return val;
+    };
 
+    quartile = function (values, q) {
+        var vl;
+        if (q === 2) {
+            vl = Math.floor(values.length / 2);
+            return values.length % 2 ? values[vl] : (values[vl-1] + values[vl]) / 2;
+        } else {
+            if (values.length % 2 ) { // odd
+                vl = (values.length * q + q) / 4;
+                return vl % 1 ? (values[Math.floor(vl)] + values[Math.floor(vl) - 1]) / 2 : values[vl-1];
+            } else { //even
+                vl = (values.length * q + 2) / 4;
+                return vl % 1 ? (values[Math.floor(vl)] + values[Math.floor(vl) - 1]) / 2 :  values[vl-1];
 
-    $.sparkline_display_visible = function() {
-        for (var i=pending.length-1; i>=0; i--) {
-            var el = pending[i][0];
-            if ($(el).is(':visible') && !$(el).parents().is(':hidden')) {
-                pending[i][1].call(el);
-                pending.splice(i, 1);
             }
         }
     };
 
-
-    /**
-     * User option handler
-     */
-    var UNSET_OPTION = {};
-    var normalizeValue = function(val) {
-        switch(val) {
+    normalizeValue = function (val) {
+        var nf;
+        switch (val) {
             case 'undefined':
                 val = undefined;
                 break;
@@ -363,775 +508,2225 @@
                 val = false;
                 break;
             default:
-                var nf = parseFloat(val);
+                nf = parseFloat(val);
                 if (val == nf) {
                     val = nf;
                 }
         }
         return val;
     };
-    $.fn.sparkline.options = function(tag, userOptions) {
-        var extendedOptions;
-        this.userOptions = userOptions = userOptions || {};
-        this.tag = tag;
-        this.tagValCache = {};
-        var defaults = $.fn.sparkline.defaults;
-        var base = defaults.common;
-        this.tagOptionsPrefix = userOptions.enableTagOptions && (userOptions.tagOptionsPrefix || base.tagOptionsPrefix);
-            
-        var tagOptionType = this.getTagSetting('type');
-        if (tagOptionType === UNSET_OPTION) {
-            extendedOptions = defaults[userOptions.type || base.type];
-        } else {
-            extendedOptions = defaults[tagOptionType];
+
+    normalizeValues = function (vals) {
+        var i, result = [];
+        for (i = vals.length; i--;) {
+            result[i] = normalizeValue(vals[i]);
         }
-        this.mergedOptions = $.extend({}, base, extendedOptions, userOptions);
+        return result;
     };
 
-
-    $.fn.sparkline.options.prototype.getTagSetting = function(key) {
-        var val, i, prefix = this.tagOptionsPrefix;
-        if (prefix === false || prefix === undefined) {
-            return UNSET_OPTION;
-        }
-        if (this.tagValCache.hasOwnProperty(key)) {
-            val = this.tagValCache.key;
-        } else {
-            val = this.tag.getAttribute(prefix + key);
-            if (val === undefined || val === null) {
-                val = UNSET_OPTION;
-            } else if (val.substr(0, 1) == '[') {
-                val = val.substr(1, val.length-2).split(',');
-                for(i=val.length; i--;) {
-                    val[i] = normalizeValue(val[i].replace(/(^\s*)|(\s*$)/g, ''));
-                }
-            } else if (val.substr(0, 1) == '{') {
-                var pairs= val.substr(1, val.length-2).split(',');
-                val = {};
-                for(i=pairs.length; i--;) {
-                    var keyval = pairs[i].split(':', 2);
-                    val[keyval[0].replace(/(^\s*)|(\s*$)/g, '')] = normalizeValue(keyval[1].replace(/(^\s*)|(\s*$)/g, ''));
-                }
-            } else {
-                val = normalizeValue(val);
+    remove = function (vals, filter) {
+        var i, vl, result = [];
+        for (i = 0, vl = vals.length; i < vl; i++) {
+            if (vals[i] !== filter) {
+                result.push(vals[i]);
             }
-            this.tagValCache.key = val;
         }
-        return val; 
+        return result;
     };
 
-    $.fn.sparkline.options.prototype.get = function(key) {
-        var tagOption = this.getTagSetting(key);
-        if (tagOption !== UNSET_OPTION) {
-            return tagOption;
-        }
-        return this.mergedOptions[key];
+    isNumber = function (num) {
+        return !isNaN(parseFloat(num)) && isFinite(num);
     };
 
-
-    /**
-     * Line charts
-     */
-    $.fn.sparkline.line = function(values, options, width, height) {
-        var xvalues = [], yvalues = [], yminmax = [];
-        for (var i=0; i<values.length; i++) {
-            var val = values[i];
-            var isstr = typeof(values[i])=='string';
-            var isarray = typeof(values[i])=='object' && values[i] instanceof Array;
-            var sp = isstr && values[i].split(':');
-            if (isstr && sp.length == 2) { // x:y
-                xvalues.push(Number(sp[0]));
-                yvalues.push(Number(sp[1]));
-                yminmax.push(Number(sp[1]));
-            } else if (isarray) {
-                xvalues.push(val[0]);
-                yvalues.push(val[1]);
-                yminmax.push(val[1]);
-            } else {
-                xvalues.push(i);
-                if (values[i]===null || values[i]=='null') {
-                    yvalues.push(null);
-                } else {
-                    yvalues.push(Number(val));
-                    yminmax.push(Number(val));
-                }
-            }
+    formatNumber = function (num, prec, groupsize, groupsep, decsep) {
+        var p, i;
+        num = (prec === false ? parseFloat(num).toString() : num.toFixed(prec)).split('');
+        p = (p = $.inArray('.', num)) < 0 ? num.length : p;
+        if (p < num.length) {
+            num[p] = decsep;
         }
-        if (options.get('xvalues')) {
-            xvalues = options.get('xvalues');
+        for (i = p - groupsize; i > 0; i -= groupsize) {
+            num.splice(i, 0, groupsep);
         }
+        return num.join('');
+    };
 
-        var maxy = Math.max.apply(Math, yminmax);
-        var maxyval = maxy;
-        var miny = Math.min.apply(Math, yminmax);
-        var minyval = miny;
+    // determine if all values of an array match a value
+    // returns true if the array is empty
+    all = function (val, arr, ignoreNull) {
+        var i;
+        for (i = arr.length; i--; ) {
+            if (ignoreNull && arr[i] === null) continue;
+            if (arr[i] !== val) {
+                return false;
+            }
+        }
+        return true;
+    };
 
-        var maxx = Math.max.apply(Math, xvalues);
-        var minx = Math.min.apply(Math, xvalues);
+    // sums the numeric values in an array, ignoring other values
+    sum = function (vals) {
+        var total = 0, i;
+        for (i = vals.length; i--;) {
+            total += typeof vals[i] === 'number' ? vals[i] : 0;
+        }
+        return total;
+    };
 
-        var normalRangeMin = options.get('normalRangeMin');
-        var normalRangeMax = options.get('normalRangeMax');
+    ensureArray = function (val) {
+        return $.isArray(val) ? val : [val];
+    };
 
-        if (normalRangeMin!==undefined) {
-            if (normalRangeMin<miny) {
-                miny = normalRangeMin;
-            }
-            if (normalRangeMax>maxy) {
-                maxy = normalRangeMax;
-            }
+    // http://paulirish.com/2008/bookmarklet-inject-new-css-rules/
+    addCSS = function(css) {
+        var tag;
+        //if ('\v' == 'v') /* ie only */ {
+        if (document.createStyleSheet) {
+            document.createStyleSheet().cssText = css;
+        } else {
+            tag = document.createElement('style');
+            tag.type = 'text/css';
+            document.getElementsByTagName('head')[0].appendChild(tag);
+            tag[(typeof document.body.style.WebkitAppearance == 'string') /* webkit only */ ? 'innerText' : 'innerHTML'] = css;
         }
-        if (options.get('chartRangeMin')!==undefined && (options.get('chartRangeClip') ||  options.get('chartRangeMin')<miny)) {
-            miny = options.get('chartRangeMin');
+    };
+
+    // Provide a cross-browser interface to a few simple drawing primitives
+    $.fn.simpledraw = function (width, height, useExisting, interact) {
+        var target, mhandler;
+        if (useExisting && (target = this.data('_jqs_vcanvas'))) {
+            return target;
         }
-        if (options.get('chartRangeMax')!==undefined && (options.get('chartRangeClip') || options.get('chartRangeMax')>maxy)) {
-            maxy = options.get('chartRangeMax');
+        if (width === undefined) {
+            width = $(this).innerWidth();
         }
-        if (options.get('chartRangeMinX')!==undefined && (options.get('chartRangeClipX') || options.get('chartRangeMinX')<minx)) {
-            minx = options.get('chartRangeMinX');
+        if (height === undefined) {
+            height = $(this).innerHeight();
         }
-        if (options.get('chartRangeMaxX')!==undefined && (options.get('chartRangeClipX') || options.get('chartRangeMaxX')>maxx)) {
-            maxx = options.get('chartRangeMaxX');
+        if ($.browser.hasCanvas) {
+            target = new VCanvas_canvas(width, height, this, interact);
+        } else if ($.browser.msie) {
+            target = new VCanvas_vml(width, height, this);
+        } else {
+            return false;
         }
-        var rangex = maxx-minx === 0 ? 1 : maxx-minx;
-        var rangey = maxy-miny === 0 ? 1 : maxy-miny;
-        var vl = yvalues.length-1;
-
-        if (vl<1) {
-            this.innerHTML = '';
-            return;
+        mhandler = $(this).data('_jqs_mhandler');
+        if (mhandler) {
+            mhandler.registerCanvas(target);
         }
+        return target;
+    };
 
-        var target = $(this).simpledraw(width, height, options.get('composite'));
+    $.fn.cleardraw = function () {
+        var target = this.data('_jqs_vcanvas');
         if (target) {
-            var canvas_width = target.pixel_width;
-            var canvas_height = target.pixel_height;
-            var canvas_top = 0;
-            var canvas_left = 0;
+            target.reset();
+        }
+    };
 
-            var spotRadius = options.get('spotRadius');
-            if (spotRadius && (canvas_width < (spotRadius*4) || canvas_height < (spotRadius*4))) {
-                spotRadius = 0;
-            }
-            if (spotRadius) {
-                // adjust the canvas size as required so that spots will fit
-                if (options.get('minSpotColor') || (options.get('spotColor') && yvalues[vl]==miny)) {
-                    canvas_height -= Math.ceil(spotRadius);
-                }
-                if (options.get('maxSpotColor') || (options.get('spotColor') && yvalues[vl]==maxy)) {
-                    canvas_height -= Math.ceil(spotRadius);
-                    canvas_top += Math.ceil(spotRadius);
-                }
-                if (options.get('minSpotColor') || options.get('maxSpotColor') && (yvalues[0]==miny || yvalues[0]==maxy)) {
-                    canvas_left += Math.ceil(spotRadius);
-                    canvas_width -= Math.ceil(spotRadius);
-                }
-                if (options.get('spotColor') || (options.get('minSpotColor') || options.get('maxSpotColor') && (yvalues[vl]==miny||yvalues[vl]==maxy))) {
-                    canvas_width -= Math.ceil(spotRadius);
+    $.RangeMapClass = RangeMap = createClass({
+        init: function (map) {
+            var key, range, rangelist = [];
+            for (key in map) {
+                if (map.hasOwnProperty(key) && typeof key === 'string' && key.indexOf(':') > -1) {
+                    range = key.split(':');
+                    range[0] = range[0].length === 0 ? -Infinity : parseFloat(range[0]);
+                    range[1] = range[1].length === 0 ? Infinity : parseFloat(range[1]);
+                    range[2] = map[key];
+                    rangelist.push(range);
+                }
+            }
+            this.map = map;
+            this.rangelist = rangelist || false;
+        },
+
+        get: function (value) {
+            var rangelist = this.rangelist,
+                i, range, result;
+            if ((result = this.map[value]) !== undefined) {
+                return result;
+            }
+            if (rangelist) {
+                for (i = rangelist.length; i--;) {
+                    range = rangelist[i];
+                    if (range[0] <= value && range[1] >= value) {
+                        return range[2];
+                    }
                 }
             }
+            return undefined;
+        }
+    });
 
+    // Convenience function
+    $.range_map = function(map) {
+        return new RangeMap(map);
+    };
 
-            canvas_height--;
+    MouseHandler = createClass({
+        init: function (el, options) {
+            var $el = $(el);
+            this.$el = $el;
+            this.options = options;
+            this.currentPageX = 0;
+            this.currentPageY = 0;
+            this.el = el;
+            this.splist = [];
+            this.tooltip = null;
+            this.over = false;
+            this.displayTooltips = !options.get('disableTooltips');
+            this.highlightEnabled = !options.get('disableHighlight');
+        },
 
-            var drawNormalRange = function() {
-                if (normalRangeMin!==undefined) {
-                    var ytop = canvas_top+Math.round(canvas_height-(canvas_height*((normalRangeMax-miny)/rangey)));
-                    var height = Math.round((canvas_height*(normalRangeMax-normalRangeMin))/rangey);
-                    target.drawRect(canvas_left, ytop, canvas_width, height, undefined, options.get('normalRangeColor'));
-                }
-            };
+        registerSparkline: function (sp) {
+            this.splist.push(sp);
+            if (this.over) {
+                this.updateDisplay();
+            }
+        },
+
+        registerCanvas: function (canvas) {
+            var $canvas = $(canvas.canvas);
+            this.canvas = canvas;
+            this.$canvas = $canvas;
+            $canvas.mouseenter($.proxy(this.mouseenter, this));
+            $canvas.mouseleave($.proxy(this.mouseleave, this));
+            $canvas.click($.proxy(this.mouseclick, this));
+        },
 
-            if (!options.get('drawNormalOnTop')) {
-                drawNormalRange();
+        reset: function (removeTooltip) {
+            this.splist = [];
+            if (this.tooltip && removeTooltip) {
+                this.tooltip.remove();
+                this.tooltip = undefined;
             }
+        },
 
-            var path = [];
-            var paths = [path];
-            var x, y, vlen=yvalues.length;
-            for(i=0; i<vlen; i++) {
-                x=xvalues[i];
-                y=yvalues[i];
-                if (y===null) {
-                    if (i) {
-                        if (yvalues[i-1]!==null) {
-                            path = [];
-                            paths.push(path);
-                        }
-                    }
-                } else {
-                    if (y < miny) {
-                        y=miny;
-                    }
-                    if (y > maxy) {
-                        y=maxy;
-                    }
-                    if (!path.length) {
-                        // previous value was null
-                        path.push([canvas_left+Math.round((x-minx)*(canvas_width/rangex)), canvas_top+canvas_height]);
-                    }
-                    path.push([canvas_left+Math.round((x-minx)*(canvas_width/rangex)), canvas_top+Math.round(canvas_height-(canvas_height*((y-miny)/rangey)))]);
-                }
+        mouseclick: function (e) {
+            var clickEvent = $.Event('sparklineClick');
+            clickEvent.originalEvent = e;
+            clickEvent.sparklines = this.splist;
+            this.$el.trigger(clickEvent);
+        },
+
+        mouseenter: function (e) {
+            $(document.body).unbind('mousemove.jqs');
+            $(document.body).bind('mousemove.jqs', $.proxy(this.mousemove, this));
+            this.over = true;
+            this.currentPageX = e.pageX;
+            this.currentPageY = e.pageY;
+            this.currentEl = e.target;
+            if (!this.tooltip && this.displayTooltips) {
+                this.tooltip = new Tooltip(this.options);
+                this.tooltip.updatePosition(e.pageX, e.pageY);
             }
-            var lineshapes = [];
-            var fillshapes = [];
-            var plen=paths.length;
-            for(i=0; i<plen; i++) {
-                path = paths[i];
-                if (!path.length) {
-                    continue; // last value was null
-                }
-                if (options.get('fillColor')) {
-                    path.push([path[path.length-1][0], canvas_top+canvas_height-1]);
-                    fillshapes.push(path.slice(0));
-                    path.pop();
-                }
-                // if there's only a single point in this path, then we want to display it as a vertical line
-                // which means we keep path[0]  as is
-                if (path.length>2) {
-                    // else we want the first value 
-                    path[0] = [ path[0][0], path[1][1] ];
-                }
-                lineshapes.push(path);
+            this.updateDisplay();
+        },
+
+        mouseleave: function () {
+            $(document.body).unbind('mousemove.jqs');
+            var splist = this.splist,
+                 spcount = splist.length,
+                 needsRefresh = false,
+                 sp, i;
+            this.over = false;
+            this.currentEl = null;
+
+            if (this.tooltip) {
+                this.tooltip.remove();
+                this.tooltip = null;
             }
 
-            // draw the fill first, then optionally the normal range, then the line on top of that
-            plen = fillshapes.length;
-            for(i=0; i<plen; i++) {
-                target.drawShape(fillshapes[i], undefined, options.get('fillColor'));
+            for (i = 0; i < spcount; i++) {
+                sp = splist[i];
+                if (sp.clearRegionHighlight()) {
+                    needsRefresh = true;
+                }
             }
 
-            if (options.get('drawNormalOnTop')) {
-                drawNormalRange();
+            if (needsRefresh) {
+                this.canvas.render();
             }
+        },
 
-            plen = lineshapes.length;
-            for(i=0; i<plen; i++) {
-                target.drawShape(lineshapes[i], options.get('lineColor'), undefined, options.get('lineWidth'));
+        mousemove: function (e) {
+            this.currentPageX = e.pageX;
+            this.currentPageY = e.pageY;
+            this.currentEl = e.target;
+            if (this.tooltip) {
+                this.tooltip.updatePosition(e.pageX, e.pageY);
             }
-                
-            if (spotRadius && options.get('spotColor')) {
-                target.drawCircle(canvas_left+Math.round(xvalues[xvalues.length-1]*(canvas_width/rangex)),  canvas_top+Math.round(canvas_height-(canvas_height*((yvalues[vl]-miny)/rangey))), spotRadius, undefined, options.get('spotColor'));
+            this.updateDisplay();
+        },
+
+        updateDisplay: function () {
+            var splist = this.splist,
+                 spcount = splist.length,
+                 needsRefresh = false,
+                 offset = this.$canvas.offset(),
+                 localX = this.currentPageX - offset.left,
+                 localY = this.currentPageY - offset.top,
+                 tooltiphtml, sp, i, result, changeEvent;
+            if (!this.over) {
+                return;
             }
-            if (maxy!=minyval) {
-                if (spotRadius && options.get('minSpotColor')) {
-                    x = xvalues[$.inArray(minyval, yvalues)];
-                    target.drawCircle(canvas_left+Math.round((x-minx)*(canvas_width/rangex)),  canvas_top+Math.round(canvas_height-(canvas_height*((minyval-miny)/rangey))), spotRadius, undefined, options.get('minSpotColor'));
+            for (i = 0; i < spcount; i++) {
+                sp = splist[i];
+                result = sp.setRegionHighlight(this.currentEl, localX, localY);
+                if (result) {
+                    needsRefresh = true;
+                }
+            }
+            if (needsRefresh) {
+                changeEvent = $.Event('sparklineRegionChange');
+                changeEvent.sparklines = this.splist;
+                this.$el.trigger(changeEvent);
+                if (this.tooltip) {
+                    tooltiphtml = '';
+                    for (i = 0; i < spcount; i++) {
+                        sp = splist[i];
+                        tooltiphtml += sp.getCurrentRegionTooltip();
+                    }
+                    this.tooltip.setContent(tooltiphtml);
                 }
-                if (spotRadius && options.get('maxSpotColor')) {
-                    x = xvalues[$.inArray(maxyval, yvalues)];
-                    target.drawCircle(canvas_left+Math.round((x-minx)*(canvas_width/rangex)),  canvas_top+Math.round(canvas_height-(canvas_height*((maxyval-miny)/rangey))), spotRadius, undefined, options.get('maxSpotColor'));
+                if (!this.disableHighlight) {
+                    this.canvas.render();
                 }
             }
-
-        } else {
-            // Remove the tag contents if sparklines aren't supported
-            this.innerHTML = '';
+            if (result === null) {
+                this.mouseleave();
+            }
         }
-    };
+    });
+
+
+    Tooltip = createClass({
+        sizeStyle: 'position: static !important;' +
+            'display: block !important;' +
+            'visibility: hidden !important;' +
+            'float: left !important;',
+
+        init: function (options) {
+            var tooltipClassname = options.get('tooltipClassname', 'jqstooltip'),
+                sizetipStyle = this.sizeStyle,
+                offset;
+            this.container = options.get('tooltipContainer') || document.body;
+            this.tooltipOffsetX = options.get('tooltipOffsetX', 10);
+            this.tooltipOffsetY = options.get('tooltipOffsetY', 12);
+            // remove any previous lingering tooltip
+            $('#jqssizetip').remove();
+            $('#jqstooltip').remove();
+            this.sizetip = $('<div/>', {
+                id: 'jqssizetip',
+                style: sizetipStyle,
+                'class': tooltipClassname
+            });
+            this.tooltip = $('<div/>', {
+                id: 'jqstooltip',
+                'class': tooltipClassname
+            }).appendTo(this.container);
+            // account for the container's location
+            offset = this.tooltip.offset();
+            this.offsetLeft = offset.left;
+            this.offsetTop = offset.top;
+            this.hidden = true;
+            $(window).unbind('resize.jqs scroll.jqs');
+            $(window).bind('resize.jqs scroll.jqs', $.proxy(this.updateWindowDims, this));
+            this.updateWindowDims();
+        },
+
+        updateWindowDims: function () {
+            this.scrollTop = $(window).scrollTop();
+            this.scrollLeft = $(window).scrollLeft();
+            this.scrollRight = this.scrollLeft + $(window).width();
+            this.updatePosition();
+        },
+
+        getSize: function (content) {
+            this.sizetip.html(content).appendTo(this.container);
+            this.width = this.sizetip.width() + 1;
+            this.height = this.sizetip.height();
+            this.sizetip.remove();
+        },
+
+        setContent: function (content) {
+            if (!content) {
+                this.tooltip.css('visibility', 'hidden');
+                this.hidden = true;
+                return;
+            }
+            this.getSize(content);
+            this.tooltip.html(content)
+                .css({
+                    'width': this.width,
+                    'height': this.height,
+                    'visibility': 'visible'
+                });
+            if (this.hidden) {
+                this.hidden = false;
+                this.updatePosition();
+            }
+        },
+
+        updatePosition: function (x, y) {
+            if (x === undefined) {
+                if (this.mousex === undefined) {
+                    return;
+                }
+                x = this.mousex - this.offsetLeft;
+                y = this.mousey - this.offsetTop;
 
-    
-    /** 
-     * Bar charts
-     */
-    $.fn.sparkline.bar = function(values, options, width, height) {
-        width = (values.length * options.get('barWidth')) + ((values.length-1) * options.get('barSpacing'));
-        var num_values = [];
-        for(var i=0, vlen=values.length; i<vlen; i++) {
-            if (values[i]=='null' || values[i]===null) {
-                values[i] = null;
             } else {
-                values[i] = Number(values[i]);
-                num_values.push(Number(values[i]));
+                this.mousex = x = x - this.offsetLeft;
+                this.mousey = y = y - this.offsetTop;
+            }
+            if (!this.height || !this.width || this.hidden) {
+                return;
             }
-        }
-        var max = Math.max.apply(Math, num_values),
-            min = Math.min.apply(Math, num_values);
-        if (options.get('chartRangeMin')!==undefined && (options.get('chartRangeClip') || options.get('chartRangeMin')<min)) {
-            min = options.get('chartRangeMin');
-        }
-        if (options.get('chartRangeMax')!==undefined && (options.get('chartRangeClip') || options.get('chartRangeMax')>max)) {
-            max = options.get('chartRangeMax');
-        }
-        var zeroAxis = options.get('zeroAxis');
-        if (zeroAxis === undefined) {
-            zeroAxis = min<0;
-        }
-        var range = max-min === 0 ? 1 : max-min;
 
-        var colorMapByIndex, colorMapByValue;
-        if ($.isArray(options.get('colorMap'))) {
-            colorMapByIndex = options.get('colorMap');
-            colorMapByValue = null;
-        } else {
-            colorMapByIndex = null;
-            colorMapByValue = options.get('colorMap');
-        }
+            y -= this.height + this.tooltipOffsetY;
+            x += this.tooltipOffsetX;
 
-        var target = $(this).simpledraw(width, height, options.get('composite'));
-        if (target) {
-            var color,
-                canvas_height = target.pixel_height,
-                yzero = min<0 && zeroAxis ? canvas_height-Math.round(canvas_height * (Math.abs(min)/range))-1 : canvas_height-1;
-
-            for(i=values.length; i--;) {
-                var x = i*(options.get('barWidth')+options.get('barSpacing')),
-                    y, 
-                    val = values[i];
-                if (val===null) {
-                    if (options.get('nullColor')) {
-                        color = options.get('nullColor');
-                        val = (zeroAxis && min<0) ? 0 : min;
-                        height = 1;
-                        y = (zeroAxis && min<0) ? yzero : canvas_height - height;
-                    } else {
-                        continue;
-                    }
-                } else {
-                    if (val < min) {
-                        val=min;
-                    }
-                    if (val > max) {
-                        val=max;
-                    }
-                    color = (val < 0) ? options.get('negBarColor') : options.get('barColor');
-                    if (zeroAxis && min<0) {
-                        height = Math.round(canvas_height*((Math.abs(val)/range)))+1;
-                        y = (val < 0) ? yzero : yzero-height;
-                    } else {
-                        height = Math.round(canvas_height*((val-min)/range))+1;
-                        y = canvas_height-height;
-                    }
-                    if (val===0 && options.get('zeroColor')!==undefined) {
-                        color = options.get('zeroColor');
-                    }
-                    if (colorMapByValue && colorMapByValue[val]) {
-                        color = colorMapByValue[val];
-                    } else if (colorMapByIndex && colorMapByIndex.length>i) {
-                        color = colorMapByIndex[i];
-                    }
-                    if (color===null) {
-                        continue;
-                    }
-                }
-                target.drawRect(x, y, options.get('barWidth')-1, height-1, color, color);
+            if (y < this.scrollTop) {
+                y = this.scrollTop;
+            }
+            if (x < this.scrollLeft) {
+                x = this.scrollLeft;
+            } else if (x + this.width > this.scrollRight) {
+                x = this.scrollRight - this.width;
             }
-        } else {
-            // Remove the tag contents if sparklines aren't supported
-            this.innerHTML = '';
-        }
-    };
 
+            this.tooltip.css({
+                'left': x,
+                'top': y
+            });
+        },
 
-    /**
-     * Tristate charts
-     */
-    $.fn.sparkline.tristate = function(values, options, width, height) {
-        values = $.map(values, Number);
-        width = (values.length * options.get('barWidth')) + ((values.length-1) * options.get('barSpacing'));
-
-        var colorMapByIndex, colorMapByValue;
-        if ($.isArray(options.get('colorMap'))) {
-            colorMapByIndex = options.get('colorMap');
-            colorMapByValue = null;
-        } else {
-            colorMapByIndex = null;
-            colorMapByValue = options.get('colorMap');
+        remove: function () {
+            this.tooltip.remove();
+            this.sizetip.remove();
+            this.sizetip = this.tooltip = undefined;
+            $(window).unbind('resize.jqs scroll.jqs');
         }
+    });
 
-        var target = $(this).simpledraw(width, height, options.get('composite'));
-        if (target) {
-            var canvas_height = target.pixel_height,
-                half_height = Math.round(canvas_height/2);
+    initStyles = function() {
+        addCSS(defaultStyles);
+    };
 
-            for(var i=values.length; i--;) {
-                var x = i*(options.get('barWidth')+options.get('barSpacing')),
-                    y, color;
-                if (values[i] < 0) {
-                    y = half_height;
-                    height = half_height-1;
-                    color = options.get('negBarColor');
-                } else if (values[i] > 0) {
-                    y = 0;
-                    height = half_height-1;
-                    color = options.get('posBarColor');
+    $(initStyles);
+
+    pending = [];
+    $.fn.sparkline = function (userValues, userOptions) {
+        return this.each(function () {
+            var options = new $.fn.sparkline.options(this, userOptions),
+                 $this = $(this),
+                 render, i;
+            render = function () {
+                var values, width, height, tmp, mhandler, sp, vals;
+                if (userValues === 'html' || userValues === undefined) {
+                    vals = this.getAttribute(options.get('tagValuesAttribute'));
+                    if (vals === undefined || vals === null) {
+                        vals = $this.html();
+                    }
+                    values = vals.replace(/(^\s*<!--)|(-->\s*$)|\s+/g, '').split(',');
                 } else {
-                    y = half_height-1;
-                    height = 2;
-                    color = options.get('zeroBarColor');
-                }
-                if (colorMapByValue && colorMapByValue[values[i]]) {
-                    color = colorMapByValue[values[i]];
-                } else if (colorMapByIndex && colorMapByIndex.length>i) {
-                    color = colorMapByIndex[i];
+                    values = userValues;
                 }
-                if (color===null) {
-                    continue;
+
+                width = options.get('width') === 'auto' ? values.length * options.get('defaultPixelsPerValue') : options.get('width');
+                if (options.get('height') === 'auto') {
+                    if (!options.get('composite') || !$.data(this, '_jqs_vcanvas')) {
+                        // must be a better way to get the line height
+                        tmp = document.createElement('span');
+                        tmp.innerHTML = 'a';
+                        $this.html(tmp);
+                        height = $(tmp).innerHeight() || $(tmp).height();
+                        $(tmp).remove();
+                        tmp = null;
+                    }
+                } else {
+                    height = options.get('height');
                 }
-                target.drawRect(x, y, options.get('barWidth')-1, height-1, color, color);
-            }
-        } else {
-            // Remove the tag contents if sparklines aren't supported
-            this.innerHTML = '';
-        }
-    };
 
+                if (!options.get('disableInteraction')) {
+                    mhandler = $.data(this, '_jqs_mhandler');
+                    if (!mhandler) {
+                        mhandler = new MouseHandler(this, options);
+                        $.data(this, '_jqs_mhandler', mhandler);
+                    } else if (!options.get('composite')) {
+                        mhandler.reset();
+                    }
+                } else {
+                    mhandler = false;
+                }
 
-    /** 
-     * Discrete charts
-     */
-    $.fn.sparkline.discrete = function(values, options, width, height) {
-        values = $.map(values, Number);
-        width = options.get('width')=='auto' ? values.length*2 : width;
-        var interval = Math.floor(width / values.length);
+                if (options.get('composite') && !$.data(this, '_jqs_vcanvas')) {
+                    if (!$.data(this, '_jqs_errnotify')) {
+                        alert('Attempted to attach a composite sparkline to an element with no existing sparkline');
+                        $.data(this, '_jqs_errnotify', true);
+                    }
+                    return;
+                }
 
-        var target = $(this).simpledraw(width, height, options.get('composite'));
-        if (target) {
-            var canvas_height = target.pixel_height,
-                line_height = options.get('lineHeight') == 'auto' ? Math.round(canvas_height * 0.3) : options.get('lineHeight'),
-                pheight = canvas_height - line_height,
-                min = Math.min.apply(Math, values),
-                max = Math.max.apply(Math, values);
-            if (options.get('chartRangeMin')!==undefined && (options.get('chartRangeClip') || options.get('chartRangeMin')<min)) {
-                min = options.get('chartRangeMin');
-            }
-            if (options.get('chartRangeMax')!==undefined && (options.get('chartRangeClip')  || options.get('chartRangeMax')>max)) {
-                max = options.get('chartRangeMax');
-            }
-            var range = max-min;
+                sp = new $.fn.sparkline[options.get('type')](this, values, options, width, height);
 
-            for(var i=values.length; i--;) {
-                var val = values[i];
-                if (val < min) {
-                    val=min;
+                sp.render();
+
+                if (mhandler) {
+                    mhandler.registerSparkline(sp);
                 }
-                if (val > max) {
-                    val=max;
+            };
+            // jQuery 1.3.0 completely changed the meaning of :hidden :-/
+            if (($(this).html() && !options.get('disableHiddenCheck') && $(this).is(':hidden')) || ($.fn.jquery < '1.3.0' && $(this).parents().is(':hidden')) || !$(this).parents('body').length) {
+                if (!options.get('composite') && $.data(this, '_jqs_pending')) {
+                    // remove any existing references to the element
+                    for (i = pending.length; i; i--) {
+                        if (pending[i - 1][0] == this) {
+                            pending.splice(i - 1, 1);
+                        }
+                    }
                 }
-                var x = (i*interval),
-                    ytop = Math.round(pheight-pheight*((val-min)/range));
-                target.drawLine(x, ytop, x, ytop+line_height, (options.get('thresholdColor') && val < options.get('thresholdValue')) ? options.get('thresholdColor') : options.get('lineColor'));
+                pending.push([this, render]);
+                $.data(this, '_jqs_pending', true);
+            } else {
+                render.call(this);
             }
-        }  else {
-            // Remove the tag contents if sparklines aren't supported
-            this.innerHTML = '';
-        }
-                
+        });
     };
 
+    $.fn.sparkline.defaults = getDefaults();
 
-    /**
-     * Bullet charts
-     */
-    $.fn.sparkline.bullet = function(values, options, width, height) {
-        values = $.map(values, Number);
-        // target, performance, range1, range2, range3
-        
-        width = options.get('width')=='auto' ? '4.0em' : width;
-
-        var target = $(this).simpledraw(width, height, options.get('composite'));
-        if (target && values.length>1) {
-            var canvas_width = target.pixel_width-Math.ceil(options.get('targetWidth')/2),
-                canvas_height = target.pixel_height,
-                min = Math.min.apply(Math, values),
-                max = Math.max.apply(Math, values);
 
-            if (options.get('base') === undefined) {
-                min = min < 0 ? min : 0;
-            } else {
-                min = options.get('base');
+    $.sparkline_display_visible = function () {
+        var el, i, pl;
+        var done = [];
+        for (i = 0, pl = pending.length; i < pl; i++) {
+            el = pending[i][0];
+            if ($(el).is(':visible') && !$(el).parents().is(':hidden')) {
+                pending[i][1].call(el);
+                $.data(pending[i][0], '_jqs_pending', false);
+                done.push(i);
+            } else if (!$(el).closest('html').length && !$.data(el, '_jqs_pending')) {
+                // element has been inserted and removed from the DOM
+                // If it was not yet inserted into the dom then the .data request
+                // will return true.
+                // removing from the dom causes the data to be removed.
+                $.data(pending[i][0], '_jqs_pending', false);
+                done.push(i);
             }
-            var range = max-min;
-
-            // draw range values
-            for(var i=2, vlen=values.length; i<vlen; i++) {
-                var rangeval = values[i],
-                    rangewidth = Math.round(canvas_width*((rangeval-min)/range));
-                target.drawRect(0, 0, rangewidth-1, canvas_height-1, options.get('rangeColors')[i-2], options.get('rangeColors')[i-2]);
-            }
-
-            // draw the performance bar
-            var perfval = values[1],
-                perfwidth = Math.round(canvas_width*((perfval-min)/range));
-            target.drawRect(0, Math.round(canvas_height*0.3), perfwidth-1, Math.round(canvas_height*0.4)-1, options.get('performanceColor'), options.get('performanceColor'));
-
-            // draw the target line
-            var targetval = values[0],
-                x = Math.round(canvas_width*((targetval-min)/range)-(options.get('targetWidth')/2)),
-                targettop = Math.round(canvas_height*0.10),
-                targetheight = canvas_height-(targettop*2);
-            target.drawRect(x, targettop, options.get('targetWidth')-1, targetheight-1, options.get('targetColor'), options.get('targetColor'));
-        }  else {
-            // Remove the tag contents if sparklines aren't supported
-            this.innerHTML = '';
+        }
+        for (i = done.length; i; i--) {
+            pending.splice(done[i - 1], 1);
         }
     };
 
 
     /**
-     * Pie charts
+     * User option handler
      */
-    $.fn.sparkline.pie = function(values, options, width, height) {
-        values = $.map(values, Number);
-        width = options.get('width')=='auto' ? height : width;
-
-        var target = $(this).simpledraw(width, height, options.get('composite'));
-        if (target && values.length>1) {
-            var canvas_width = target.pixel_width,
-                canvas_height = target.pixel_height,
-                radius = Math.floor(Math.min(canvas_width, canvas_height)/2),
-                total = 0,
-                next = 0,
-                circle = 2*Math.PI;
-
-            for(var i=values.length; i--;) {
-                total += values[i];
-            }
-
-            if (options.get('offset')) {
-                next += (2*Math.PI)*(options.get('offset')/360);
-            }
-            var vlen = values.length;
-            for(i=0; i<vlen; i++) {
-                var start = next;
-                var end = next;
-                if (total > 0) {  // avoid divide by zero
-                    end = next + (circle*(values[i]/total));
-                }
-                target.drawPieSlice(radius, radius, radius, start, end, undefined, options.get('sliceColors')[i % options.get('sliceColors').length]);
-                next = end;
+    $.fn.sparkline.options = createClass({
+        init: function (tag, userOptions) {
+            var extendedOptions, defaults, base, tagOptionType;
+            this.userOptions = userOptions = userOptions || {};
+            this.tag = tag;
+            this.tagValCache = {};
+            defaults = $.fn.sparkline.defaults;
+            base = defaults.common;
+            this.tagOptionsPrefix = userOptions.enableTagOptions && (userOptions.tagOptionsPrefix || base.tagOptionsPrefix);
+
+            tagOptionType = this.getTagSetting('type');
+            if (tagOptionType === UNSET_OPTION) {
+                extendedOptions = defaults[userOptions.type || base.type];
+            } else {
+                extendedOptions = defaults[tagOptionType];
             }
-        }
-    };
+            this.mergedOptions = $.extend({}, base, extendedOptions, userOptions);
+        },
 
 
+        getTagSetting: function (key) {
+            var prefix = this.tagOptionsPrefix,
+                val, i, pairs, keyval;
+            if (prefix === false || prefix === undefined) {
+                return UNSET_OPTION;
+            }
+            if (this.tagValCache.hasOwnProperty(key)) {
+                val = this.tagValCache.key;
+            } else {
+                val = this.tag.getAttribute(prefix + key);
+                if (val === undefined || val === null) {
+                    val = UNSET_OPTION;
+                } else if (val.substr(0, 1) === '[') {
+                    val = val.substr(1, val.length - 2).split(',');
+                    for (i = val.length; i--;) {
+                        val[i] = normalizeValue(val[i].replace(/(^\s*)|(\s*$)/g, ''));
+                    }
+                } else if (val.substr(0, 1) === '{') {
+                    pairs = val.substr(1, val.length - 2).split(',');
+                    val = {};
+                    for (i = pairs.length; i--;) {
+                        keyval = pairs[i].split(':', 2);
+                        val[keyval[0].replace(/(^\s*)|(\s*$)/g, '')] = normalizeValue(keyval[1].replace(/(^\s*)|(\s*$)/g, ''));
+                    }
+                } else {
+                    val = normalizeValue(val);
+                }
+                this.tagValCache.key = val;
+            }
+            return val;
+        },
+
+        get: function (key, defaultval) {
+            var tagOption = this.getTagSetting(key),
+                result;
+            if (tagOption !== UNSET_OPTION) {
+                return tagOption;
+            }
+            return (result = this.mergedOptions[key]) === undefined ? defaultval : result;
+        }
+    });
+
+
+    $.fn.sparkline._base = createClass({
+        disabled: false,
+
+        init: function (el, values, options, width, height) {
+            this.el = el;
+            this.$el = $(el);
+            this.values = values;
+            this.options = options;
+            this.width = width;
+            this.height = height;
+            this.currentRegion = undefined;
+        },
+
+        /**
+         * Setup the canvas
+         */
+        initTarget: function () {
+            var interactive = !this.options.get('disableInteraction');
+            if (!(this.target = this.$el.simpledraw(this.width, this.height, this.options.get('composite'), interactive))) {
+                this.disabled = true;
+            } else {
+                this.canvasWidth = this.target.pixelWidth;
+                this.canvasHeight = this.target.pixelHeight;
+            }
+        },
+
+        /**
+         * Actually render the chart to the canvas
+         */
+        render: function () {
+            if (this.disabled) {
+                this.el.innerHTML = '';
+                return false;
+            }
+            return true;
+        },
+
+        /**
+         * Return a region id for a given x/y co-ordinate
+         */
+        getRegion: function (x, y) {
+        },
+
+        /**
+         * Highlight an item based on the moused-over x,y co-ordinate
+         */
+        setRegionHighlight: function (el, x, y) {
+            var currentRegion = this.currentRegion,
+                highlightEnabled = !this.options.get('disableHighlight'),
+                newRegion;
+            if (x > this.canvasWidth || y > this.canvasHeight || x < 0 || y < 0) {
+                return null;
+            }
+            newRegion = this.getRegion(el, x, y);
+            if (currentRegion !== newRegion) {
+                if (currentRegion !== undefined && highlightEnabled) {
+                    this.removeHighlight();
+                }
+                this.currentRegion = newRegion;
+                if (newRegion !== undefined && highlightEnabled) {
+                    this.renderHighlight();
+                }
+                return true;
+            }
+            return false;
+        },
+
+        /**
+         * Reset any currently highlighted item
+         */
+        clearRegionHighlight: function () {
+            if (this.currentRegion !== undefined) {
+                this.removeHighlight();
+                this.currentRegion = undefined;
+                return true;
+            }
+            return false;
+        },
+
+        renderHighlight: function () {
+            this.changeHighlight(true);
+        },
+
+        removeHighlight: function () {
+            this.changeHighlight(false);
+        },
+
+        changeHighlight: function (highlight)  {},
+
+        /**
+         * Fetch the HTML to display as a tooltip
+         */
+        getCurrentRegionTooltip: function () {
+            var options = this.options,
+                header = '',
+                entries = [],
+                fields, formats, formatlen, fclass, text, i,
+                showFields, showFieldsKey, newFields, fv,
+                formatter, format, fieldlen, j;
+            if (this.currentRegion === undefined) {
+                return '';
+            }
+            fields = this.getCurrentRegionFields();
+            formatter = options.get('tooltipFormatter');
+            if (formatter) {
+                return formatter(this, options, fields);
+            }
+            if (options.get('tooltipChartTitle')) {
+                header += '<div class="jqs jqstitle">' + options.get('tooltipChartTitle') + '</div>\n';
+            }
+            formats = this.options.get('tooltipFormat');
+            if (!formats) {
+                return '';
+            }
+            if (!$.isArray(formats)) {
+                formats = [formats];
+            }
+            if (!$.isArray(fields)) {
+                fields = [fields];
+            }
+            showFields = this.options.get('tooltipFormatFieldlist');
+            showFieldsKey = this.options.get('tooltipFormatFieldlistKey');
+            if (showFields && showFieldsKey) {
+                // user-selected ordering of fields
+                newFields = [];
+                for (i = fields.length; i--;) {
+                    fv = fields[i][showFieldsKey];
+                    if ((j = $.inArray(fv, showFields)) != -1) {
+                        newFields[j] = fields[i];
+                    }
+                }
+                fields = newFields;
+            }
+            formatlen = formats.length;
+            fieldlen = fields.length;
+            for (i = 0; i < formatlen; i++) {
+                format = formats[i];
+                if (typeof format === 'string') {
+                    format = new SPFormat(format);
+                }
+                fclass = format.fclass || 'jqsfield';
+                for (j = 0; j < fieldlen; j++) {
+                    if (!fields[j].isNull || !options.get('tooltipSkipNull')) {
+                        $.extend(fields[j], {
+                            prefix: options.get('tooltipPrefix'),
+                            suffix: options.get('tooltipSuffix')
+                        });
+                        text = format.render(fields[j], options.get('tooltipValueLookups'), options);
+                        entries.push('<div class="' + fclass + '">' + text + '</div>');
+                    }
+                }
+            }
+            if (entries.length) {
+                return header + entries.join('\n');
+            }
+            return '';
+        },
+
+        getCurrentRegionFields: function () {},
+
+        calcHighlightColor: function (color, options) {
+            var highlightColor = options.get('highlightColor'),
+                lighten = options.get('highlightLighten'),
+                parse, mult, rgbnew, i;
+            if (highlightColor) {
+                return highlightColor;
+            }
+            if (lighten) {
+                // extract RGB values
+                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);
+                if (parse) {
+                    rgbnew = [];
+                    mult = color.length === 4 ? 16 : 1;
+                    for (i = 0; i < 3; i++) {
+                        rgbnew[i] = clipval(Math.round(parseInt(parse[i + 1], 16) * mult * lighten), 0, 255);
+                    }
+                    return 'rgb(' + rgbnew.join(',') + ')';
+                }
+
+            }
+            return color;
+        }
+
+    });
+
+    barHighlightMixin = {
+        changeHighlight: function (highlight) {
+            var currentRegion = this.currentRegion,
+                target = this.target,
+                shapeids = this.regionShapes[currentRegion],
+                newShapes;
+            // will be null if the region value was null
+            if (shapeids) {
+                newShapes = this.renderRegion(currentRegion, highlight);
+                if ($.isArray(newShapes) || $.isArray(shapeids)) {
+                    target.replaceWithShapes(shapeids, newShapes);
+                    this.regionShapes[currentRegion] = $.map(newShapes, function (newShape) {
+                        return newShape.id;
+                    });
+                } else {
+                    target.replaceWithShape(shapeids, newShapes);
+                    this.regionShapes[currentRegion] = newShapes.id;
+                }
+            }
+        },
+
+        render: function () {
+            var values = this.values,
+                target = this.target,
+                regionShapes = this.regionShapes,
+                shapes, ids, i, j;
+
+            if (!this.cls._super.render.call(this)) {
+                return;
+            }
+            for (i = values.length; i--;) {
+                shapes = this.renderRegion(i);
+                if (shapes) {
+                    if ($.isArray(shapes)) {
+                        ids = [];
+                        for (j = shapes.length; j--;) {
+                            shapes[j].append();
+                            ids.push(shapes[j].id);
+                        }
+                        regionShapes[i] = ids;
+                    } else {
+                        shapes.append();
+                        regionShapes[i] = shapes.id; // store just the shapeid
+                    }
+                } else {
+                    // null value
+                    regionShapes[i] = null;
+                }
+            }
+            target.render();
+        }
+    };
+
     /**
-     * Box plots
+     * Line charts
      */
-    var quartile = function(values, q) {
-        if (q==2) {
-            var vl2 = Math.floor(values.length/2);
-            return values.length % 2 ? values[vl2] : (values[vl2]+values[vl2+1])/2;
-        } else {
-            var vl4 = Math.floor(values.length/4);
-            return values.length % 2 ? (values[vl4*q]+values[vl4*q+1])/2 : values[vl4*q];
+    $.fn.sparkline.line = line = createClass($.fn.sparkline._base, {
+        type: 'line',
+
+        init: function (el, values, options, width, height) {
+            line._super.init.call(this, el, values, options, width, height);
+            this.vertices = [];
+            this.regionMap = [];
+            this.xvalues = [];
+            this.yvalues = [];
+            this.yminmax = [];
+            this.hightlightSpotId = null;
+            this.lastShapeId = null;
+            this.initTarget();
+        },
+
+        getRegion: function (el, x, y) {
+            var i,
+                regionMap = this.regionMap; // maps regions to value positions
+            for (i = regionMap.length; i--;) {
+                if (regionMap[i] !== null && x >= regionMap[i][0] && x <= regionMap[i][1]) {
+                    return regionMap[i][2];
+                }
+            }
+            return undefined;
+        },
+
+        getCurrentRegionFields: function () {
+            var currentRegion = this.currentRegion;
+            return {
+                isNull: this.yvalues[currentRegion] === null,
+                x: this.xvalues[currentRegion],
+                y: this.yvalues[currentRegion],
+                color: this.options.get('lineColor'),
+                fillColor: this.options.get('fillColor'),
+                offset: currentRegion
+            };
+        },
+
+        renderHighlight: function () {
+            var currentRegion = this.currentRegion,
+                target = this.target,
+                vertex = this.vertices[currentRegion],
+                options = this.options,
+                spotRadius = options.get('spotRadius'),
+                highlightSpotColor = options.get('highlightSpotColor'),
+                highlightLineColor = options.get('highlightLineColor'),
+                highlightSpot, highlightLine;
+
+            if (!vertex) {
+                return;
+            }
+            if (spotRadius && highlightSpotColor) {
+                highlightSpot = target.drawCircle(vertex[0], vertex[1],
+                    spotRadius, undefined, highlightSpotColor);
+                this.highlightSpotId = highlightSpot.id;
+                target.insertAfterShape(this.lastShapeId, highlightSpot);
+            }
+            if (highlightLineColor) {
+                highlightLine = target.drawLine(vertex[0], this.canvasTop, vertex[0],
+                    this.canvasTop + this.canvasHeight, highlightLineColor);
+                this.highlightLineId = highlightLine.id;
+                target.insertAfterShape(this.lastShapeId, highlightLine);
+            }
+        },
+
+        removeHighlight: function () {
+            var target = this.target;
+            if (this.highlightSpotId) {
+                target.removeShapeId(this.highlightSpotId);
+                this.highlightSpotId = null;
+            }
+            if (this.highlightLineId) {
+                target.removeShapeId(this.highlightLineId);
+                this.highlightLineId = null;
+            }
+        },
+
+        scanValues: function () {
+            var values = this.values,
+                valcount = values.length,
+                xvalues = this.xvalues,
+                yvalues = this.yvalues,
+                yminmax = this.yminmax,
+                i, val, isStr, isArray, sp;
+            for (i = 0; i < valcount; i++) {
+                val = values[i];
+                isStr = typeof(values[i]) === 'string';
+                isArray = typeof(values[i]) === 'object' && values[i] instanceof Array;
+                sp = isStr && values[i].split(':');
+                if (isStr && sp.length === 2) { // x:y
+                    xvalues.push(Number(sp[0]));
+                    yvalues.push(Number(sp[1]));
+                    yminmax.push(Number(sp[1]));
+                } else if (isArray) {
+                    xvalues.push(val[0]);
+                    yvalues.push(val[1]);
+                    yminmax.push(val[1]);
+                } else {
+                    xvalues.push(i);
+                    if (values[i] === null || values[i] === 'null') {
+                        yvalues.push(null);
+                    } else {
+                        yvalues.push(Number(val));
+                        yminmax.push(Number(val));
+                    }
+                }
+            }
+            if (this.options.get('xvalues')) {
+                xvalues = this.options.get('xvalues');
+            }
+
+            this.maxy = this.maxyorg = Math.max.apply(Math, yminmax);
+            this.miny = this.minyorg = Math.min.apply(Math, yminmax);
+
+            this.maxx = Math.max.apply(Math, xvalues);
+            this.minx = Math.min.apply(Math, xvalues);
+
+            this.xvalues = xvalues;
+            this.yvalues = yvalues;
+            this.yminmax = yminmax;
+
+        },
+
+        processRangeOptions: function () {
+            var options = this.options,
+                normalRangeMin = options.get('normalRangeMin'),
+                normalRangeMax = options.get('normalRangeMax');
+
+            if (normalRangeMin !== undefined) {
+                if (normalRangeMin < this.miny) {
+                    this.miny = normalRangeMin;
+                }
+                if (normalRangeMax > this.maxy) {
+                    this.maxy = normalRangeMax;
+                }
+            }
+            if (options.get('chartRangeMin') !== undefined && (options.get('chartRangeClip') || options.get('chartRangeMin') < this.miny)) {
+                this.miny = options.get('chartRangeMin');
+            }
+            if (options.get('chartRangeMax') !== undefined && (options.get('chartRangeClip') || options.get('chartRangeMax') > this.maxy)) {
+                this.maxy = options.get('chartRangeMax');
+            }
+            if (options.get('chartRangeMinX') !== undefined && (options.get('chartRangeClipX') || options.get('chartRangeMinX') < this.minx)) {
+                this.minx = options.get('chartRangeMinX');
+            }
+            if (options.get('chartRangeMaxX') !== undefined && (options.get('chartRangeClipX') || options.get('chartRangeMaxX') > this.maxx)) {
+                this.maxx = options.get('chartRangeMaxX');
+            }
+
+        },
+
+        drawNormalRange: function (canvasLeft, canvasTop, canvasHeight, canvasWidth, rangey) {
+            var normalRangeMin = this.options.get('normalRangeMin'),
+                normalRangeMax = this.options.get('normalRangeMax'),
+                ytop = canvasTop + Math.round(canvasHeight - (canvasHeight * ((normalRangeMax - this.miny) / rangey))),
+                height = Math.round((canvasHeight * (normalRangeMax - normalRangeMin)) / rangey);
+            this.target.drawRect(canvasLeft, ytop, canvasWidth, height, undefined, this.options.get('normalRangeColor')).append();
+        },
+
+        render: function () {
+            var options = this.options,
+                target = this.target,
+                canvasWidth = this.canvasWidth,
+                canvasHeight = this.canvasHeight,
+                vertices = this.vertices,
+                spotRadius = options.get('spotRadius'),
+                regionMap = this.regionMap,
+                rangex, rangey, yvallast,
+                canvasTop, canvasLeft,
+                vertex, path, paths, x, y, xnext, xpos, xposnext,
+                last, next, yvalcount, lineShapes, fillShapes, plen,
+                valueSpots, hlSpotsEnabled, color, xvalues, yvalues, i;
+
+            if (!line._super.render.call(this)) {
+                return;
+            }
+
+            this.scanValues();
+            this.processRangeOptions();
+
+            xvalues = this.xvalues;
+            yvalues = this.yvalues;
+
+            if (!this.yminmax.length || this.yvalues.length < 2) {
+                // empty or all null valuess
+                return;
+            }
+
+            canvasTop = canvasLeft = 0;
+
+            rangex = this.maxx - this.minx === 0 ? 1 : this.maxx - this.minx;
+            rangey = this.maxy - this.miny === 0 ? 1 : this.maxy - this.miny;
+            yvallast = this.yvalues.length - 1;
+
+            if (spotRadius && (canvasWidth < (spotRadius * 4) || canvasHeight < (spotRadius * 4))) {
+                spotRadius = 0;
+            }
+            if (spotRadius) {
+                // adjust the canvas size as required so that spots will fit
+                hlSpotsEnabled = options.get('highlightSpotColor') &&  !options.get('disableInteraction');
+                if (hlSpotsEnabled || options.get('minSpotColor') || (options.get('spotColor') && yvalues[yvallast] === this.miny)) {
+                    canvasHeight -= Math.ceil(spotRadius);
+                }
+                if (hlSpotsEnabled || options.get('maxSpotColor') || (options.get('spotColor') && yvalues[yvallast] === this.maxy)) {
+                    canvasHeight -= Math.ceil(spotRadius);
+                    canvasTop += Math.ceil(spotRadius);
+                }
+                if (hlSpotsEnabled ||
+                     ((options.get('minSpotColor') || options.get('maxSpotColor')) && (yvalues[0] === this.miny || yvalues[0] === this.maxy))) {
+                    canvasLeft += Math.ceil(spotRadius);
+                    canvasWidth -= Math.ceil(spotRadius);
+                }

[... 1676 lines stripped ...]