src/js/libs/jquery.sparkline.js
branchpopcorn-port
changeset 523 8dfeaec4724b
equal deleted inserted replaced
521:df716b99c5bb 523:8dfeaec4724b
       
     1 /**
       
     2 *
       
     3 * jquery.sparkline.js
       
     4 *
       
     5 * v1.6
       
     6 * (c) Splunk, Inc 
       
     7 * Contact: Gareth Watts (gareth@splunk.com)
       
     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) 2010, 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 *
       
    92 * There are 7 types of sparkline, selected by supplying a "type" option of 'line' (default),
       
    93 * 'bar', 'tristate', 'bullet', 'discrete', 'pie' or 'box'
       
    94 *    line - Line chart.  Options:
       
    95 *       spotColor - Set to '' to not end each line in a circular spot
       
    96 *       minSpotColor - If set, color of spot at minimum value
       
    97 *       maxSpotColor - If set, color of spot at maximum value
       
    98 *       spotRadius - Radius in pixels
       
    99 *       lineWidth - Width of line in pixels
       
   100 *       normalRangeMin 
       
   101 *       normalRangeMax - If set draws a filled horizontal bar between these two values marking the "normal"
       
   102 *                      or expected range of values
       
   103 *       normalRangeColor - Color to use for the above bar
       
   104 *       drawNormalOnTop - Draw the normal range above the chart fill color if true
       
   105 *       defaultPixelsPerValue - Defaults to 3 pixels of width for each value in the chart
       
   106 *
       
   107 *   bar - Bar chart.  Options:
       
   108 *       barColor - Color of bars for postive values
       
   109 *       negBarColor - Color of bars for negative values
       
   110 *       zeroColor - Color of bars with zero values
       
   111 *       nullColor - Color of bars with null values - Defaults to omitting the bar entirely
       
   112 *       barWidth - Width of bars in pixels
       
   113 *       colorMap - Optional mappnig of values to colors to override the *BarColor values above
       
   114 *                  can be an Array of values to control the color of individual bars
       
   115 *       barSpacing - Gap between bars in pixels
       
   116 *       zeroAxis - Centers the y-axis around zero if true
       
   117 *
       
   118 *   tristate - Charts values of win (>0), lose (<0) or draw (=0)
       
   119 *       posBarColor - Color of win values
       
   120 *       negBarColor - Color of lose values
       
   121 *       zeroBarColor - Color of draw values
       
   122 *       barWidth - Width of bars in pixels
       
   123 *       barSpacing - Gap between bars in pixels
       
   124 *       colorMap - Optional mappnig of values to colors to override the *BarColor values above
       
   125 *                  can be an Array of values to control the color of individual bars
       
   126 *
       
   127 *   discrete - Options:
       
   128 *       lineHeight - Height of each line in pixels - Defaults to 30% of the graph height
       
   129 *       thesholdValue - Values less than this value will be drawn using thresholdColor instead of lineColor
       
   130 *       thresholdColor
       
   131 *
       
   132 *   bullet - Values for bullet graphs msut be in the order: target, performance, range1, range2, range3, ...
       
   133 *       options:
       
   134 *       targetColor - The color of the vertical target marker
       
   135 *       targetWidth - The width of the target marker in pixels
       
   136 *       performanceColor - The color of the performance measure horizontal bar
       
   137 *       rangeColors - Colors to use for each qualitative range background color
       
   138 *
       
   139 *   pie - Pie chart. Options:
       
   140 *       sliceColors - An array of colors to use for pie slices
       
   141 *       offset - Angle in degrees to offset the first slice - Try -90 or +90
       
   142 *
       
   143 *   box - Box plot. Options:
       
   144 *       raw - Set to true to supply pre-computed plot points as values
       
   145 *             values should be: low_outlier, low_whisker, q1, median, q3, high_whisker, high_outlier
       
   146 *             When set to false you can supply any number of values and the box plot will
       
   147 *             be computed for you.  Default is false.
       
   148 *       showOutliers - Set to true (default) to display outliers as circles
       
   149 *       outlierIRQ - Interquartile range used to determine outliers.  Default 1.5
       
   150 *       boxLineColor - Outline color of the box
       
   151 *       boxFillColor - Fill color for the box
       
   152 *       whiskerColor - Line color used for whiskers
       
   153 *       outlierLineColor - Outline color of outlier circles
       
   154 *       outlierFillColor - Fill color of the outlier circles
       
   155 *       spotRadius - Radius of outlier circles
       
   156 *       medianColor - Line color of the median line
       
   157 *       target - Draw a target cross hair at the supplied value (default undefined)
       
   158 *      
       
   159 *   
       
   160 *       
       
   161 *   Examples:
       
   162 *   $('#sparkline1').sparkline(myvalues, { lineColor: '#f00', fillColor: false });
       
   163 *   $('.barsparks').sparkline('html', { type:'bar', height:'40px', barWidth:5 });
       
   164 *   $('#tristate').sparkline([1,1,-1,1,0,0,-1], { type:'tristate' }):
       
   165 *   $('#discrete').sparkline([1,3,4,5,5,3,4,5], { type:'discrete' });
       
   166 *   $('#bullet').sparkline([10,12,12,9,7], { type:'bullet' });
       
   167 *   $('#pie').sparkline([1,1,2], { type:'pie' });
       
   168 */
       
   169 
       
   170 
       
   171 (function($) {
       
   172 
       
   173     /*
       
   174      * Default configuration settings
       
   175      */
       
   176     var defaults = {
       
   177         // Settings common to most/all chart types
       
   178         common: {
       
   179             type : 'line',
       
   180             lineColor : '#00f',
       
   181             fillColor : '#cdf',
       
   182             defaultPixelsPerValue : 3,
       
   183             width : 'auto', 
       
   184             height : 'auto',
       
   185             composite : false,
       
   186             tagValuesAttribute: 'values',
       
   187             tagOptionsPrefix: 'spark',
       
   188             enableTagOptions: false
       
   189         },
       
   190         // Defaults for line charts
       
   191         line: {
       
   192             spotColor : '#f80',
       
   193             spotRadius : 1.5,
       
   194             minSpotColor : '#f80',
       
   195             maxSpotColor : '#f80',
       
   196             lineWidth: 1, 
       
   197             normalRangeMin : undefined,
       
   198             normalRangeMax : undefined,
       
   199             normalRangeColor : '#ccc',
       
   200             drawNormalOnTop: false,
       
   201             chartRangeMin : undefined,
       
   202             chartRangeMax : undefined,
       
   203             chartRangeMinX : undefined,
       
   204             chartRangeMaxX : undefined
       
   205         },
       
   206         // Defaults for bar charts
       
   207         bar: {
       
   208             barColor : '#00f',
       
   209             negBarColor : '#f44',
       
   210             zeroColor: undefined,
       
   211             nullColor: undefined,
       
   212             zeroAxis : undefined,
       
   213             barWidth : 4,
       
   214             barSpacing : 1,
       
   215             chartRangeMax: undefined,
       
   216             chartRangeMin: undefined,
       
   217             chartRangeClip: false,
       
   218             colorMap : undefined
       
   219         },
       
   220         // Defaults for tristate charts
       
   221         tristate: {
       
   222             barWidth : 4,
       
   223             barSpacing : 1,
       
   224             posBarColor: '#6f6',
       
   225             negBarColor : '#f44',
       
   226             zeroBarColor : '#999',
       
   227             colorMap : {}
       
   228         },
       
   229         // Defaults for discrete charts
       
   230         discrete: {
       
   231             lineHeight: 'auto',
       
   232             thresholdColor: undefined,
       
   233             thresholdValue : 0,
       
   234             chartRangeMax: undefined,
       
   235             chartRangeMin: undefined,
       
   236             chartRangeClip: false
       
   237         },
       
   238         // Defaults for bullet charts
       
   239         bullet: {
       
   240             targetColor : 'red',
       
   241             targetWidth : 3, // width of the target bar in pixels
       
   242             performanceColor : 'blue',
       
   243             rangeColors : ['#D3DAFE', '#A8B6FF', '#7F94FF' ],
       
   244             base : undefined // set this to a number to change the base start number
       
   245         },
       
   246         // Defaults for pie charts
       
   247         pie: {
       
   248             sliceColors : ['#f00', '#0f0', '#00f']
       
   249         },
       
   250         // Defaults for box plots
       
   251         box: {
       
   252             raw: false,
       
   253             boxLineColor: 'black',
       
   254             boxFillColor: '#cdf',
       
   255             whiskerColor: 'black',
       
   256             outlierLineColor: '#333',
       
   257             outlierFillColor: 'white',
       
   258             medianColor: 'red',
       
   259             showOutliers: true,
       
   260             outlierIQR: 1.5,
       
   261             spotRadius: 1.5,
       
   262             target: undefined,
       
   263             targetColor: '#4a2',
       
   264             chartRangeMax: undefined,
       
   265             chartRangeMin: undefined
       
   266         }
       
   267     };
       
   268 
       
   269     // Provide a cross-browser interface to a few simple drawing primitives
       
   270     var VCanvas_base, VCanvas_canvas, VCanvas_vml;
       
   271     $.fn.simpledraw = function(width, height, use_existing) {
       
   272         if (use_existing && this[0].VCanvas) {
       
   273             return this[0].VCanvas;
       
   274         }
       
   275         if (width === undefined) { 
       
   276             width=$(this).innerWidth();
       
   277         }
       
   278         if (height === undefined) {
       
   279             height=$(this).innerHeight();
       
   280         }
       
   281         if ($.browser.hasCanvas) {
       
   282             return new VCanvas_canvas(width, height, this);
       
   283         } else if ($.browser.msie) {
       
   284             return new VCanvas_vml(width, height, this);
       
   285         } else {
       
   286             return false;
       
   287         }
       
   288     };
       
   289 
       
   290     var pending = [];
       
   291 
       
   292 
       
   293     $.fn.sparkline = function(uservalues, userOptions) {
       
   294         return this.each(function() {
       
   295             var options = new $.fn.sparkline.options(this, userOptions);
       
   296             var render = function() {
       
   297                 var values, width, height;
       
   298                 if (uservalues==='html' || uservalues===undefined) {
       
   299                     var vals = this.getAttribute(options.get('tagValuesAttribute'));
       
   300                     if (vals===undefined || vals===null) {
       
   301                         vals = $(this).html();
       
   302                     }
       
   303                     values = vals.replace(/(^\s*<!--)|(-->\s*$)|\s+/g, '').split(',');
       
   304                 } else {
       
   305                     values = uservalues;
       
   306                 }
       
   307 
       
   308                 width = options.get('width')=='auto' ? values.length*options.get('defaultPixelsPerValue') : options.get('width');
       
   309                 if (options.get('height') == 'auto') {
       
   310                     if (!options.get('composite') || !this.VCanvas) {
       
   311                         // must be a better way to get the line height
       
   312                         var tmp = document.createElement('span');
       
   313                         tmp.innerHTML = 'a';
       
   314                         $(this).html(tmp);
       
   315                         height = $(tmp).innerHeight();
       
   316                         $(tmp).remove();
       
   317                     }
       
   318                 } else {
       
   319                     height = options.get('height');
       
   320                 }
       
   321 
       
   322                 $.fn.sparkline[options.get('type')].call(this, values, options, width, height);
       
   323             };
       
   324             // jQuery 1.3.0 completely changed the meaning of :hidden :-/
       
   325             if (($(this).html() && $(this).is(':hidden')) || ($.fn.jquery < "1.3.0" && $(this).parents().is(':hidden')) || !$(this).parents('body').length) {
       
   326                 pending.push([this, render]);
       
   327             } else {
       
   328                 render.call(this);
       
   329             }
       
   330         });
       
   331     };
       
   332 
       
   333     $.fn.sparkline.defaults = defaults;
       
   334 
       
   335 
       
   336     $.sparkline_display_visible = function() {
       
   337         for (var i=pending.length-1; i>=0; i--) {
       
   338             var el = pending[i][0];
       
   339             if ($(el).is(':visible') && !$(el).parents().is(':hidden')) {
       
   340                 pending[i][1].call(el);
       
   341                 pending.splice(i, 1);
       
   342             }
       
   343         }
       
   344     };
       
   345 
       
   346 
       
   347     /**
       
   348      * User option handler
       
   349      */
       
   350     var UNSET_OPTION = {};
       
   351     var normalizeValue = function(val) {
       
   352         switch(val) {
       
   353             case 'undefined':
       
   354                 val = undefined;
       
   355                 break;
       
   356             case 'null':
       
   357                 val = null;
       
   358                 break;
       
   359             case 'true':
       
   360                 val = true;
       
   361                 break;
       
   362             case 'false':
       
   363                 val = false;
       
   364                 break;
       
   365             default:
       
   366                 var nf = parseFloat(val);
       
   367                 if (val == nf) {
       
   368                     val = nf;
       
   369                 }
       
   370         }
       
   371         return val;
       
   372     };
       
   373     $.fn.sparkline.options = function(tag, userOptions) {
       
   374         var extendedOptions;
       
   375         this.userOptions = userOptions = userOptions || {};
       
   376         this.tag = tag;
       
   377         this.tagValCache = {};
       
   378         var defaults = $.fn.sparkline.defaults;
       
   379         var base = defaults.common;
       
   380         this.tagOptionsPrefix = userOptions.enableTagOptions && (userOptions.tagOptionsPrefix || base.tagOptionsPrefix);
       
   381             
       
   382         var tagOptionType = this.getTagSetting('type');
       
   383         if (tagOptionType === UNSET_OPTION) {
       
   384             extendedOptions = defaults[userOptions.type || base.type];
       
   385         } else {
       
   386             extendedOptions = defaults[tagOptionType];
       
   387         }
       
   388         this.mergedOptions = $.extend({}, base, extendedOptions, userOptions);
       
   389     };
       
   390 
       
   391 
       
   392     $.fn.sparkline.options.prototype.getTagSetting = function(key) {
       
   393         var val, i, prefix = this.tagOptionsPrefix;
       
   394         if (prefix === false || prefix === undefined) {
       
   395             return UNSET_OPTION;
       
   396         }
       
   397         if (this.tagValCache.hasOwnProperty(key)) {
       
   398             val = this.tagValCache.key;
       
   399         } else {
       
   400             val = this.tag.getAttribute(prefix + key);
       
   401             if (val === undefined || val === null) {
       
   402                 val = UNSET_OPTION;
       
   403             } else if (val.substr(0, 1) == '[') {
       
   404                 val = val.substr(1, val.length-2).split(',');
       
   405                 for(i=val.length; i--;) {
       
   406                     val[i] = normalizeValue(val[i].replace(/(^\s*)|(\s*$)/g, ''));
       
   407                 }
       
   408             } else if (val.substr(0, 1) == '{') {
       
   409                 var pairs= val.substr(1, val.length-2).split(',');
       
   410                 val = {};
       
   411                 for(i=pairs.length; i--;) {
       
   412                     var keyval = pairs[i].split(':', 2);
       
   413                     val[keyval[0].replace(/(^\s*)|(\s*$)/g, '')] = normalizeValue(keyval[1].replace(/(^\s*)|(\s*$)/g, ''));
       
   414                 }
       
   415             } else {
       
   416                 val = normalizeValue(val);
       
   417             }
       
   418             this.tagValCache.key = val;
       
   419         }
       
   420         return val; 
       
   421     };
       
   422 
       
   423     $.fn.sparkline.options.prototype.get = function(key) {
       
   424         var tagOption = this.getTagSetting(key);
       
   425         if (tagOption !== UNSET_OPTION) {
       
   426             return tagOption;
       
   427         }
       
   428         return this.mergedOptions[key];
       
   429     };
       
   430 
       
   431 
       
   432     /**
       
   433      * Line charts
       
   434      */
       
   435     $.fn.sparkline.line = function(values, options, width, height) {
       
   436         var xvalues = [], yvalues = [], yminmax = [];
       
   437         for (var i=0; i<values.length; i++) {
       
   438             var val = values[i];
       
   439             var isstr = typeof(values[i])=='string';
       
   440             var isarray = typeof(values[i])=='object' && values[i] instanceof Array;
       
   441             var sp = isstr && values[i].split(':');
       
   442             if (isstr && sp.length == 2) { // x:y
       
   443                 xvalues.push(Number(sp[0]));
       
   444                 yvalues.push(Number(sp[1]));
       
   445                 yminmax.push(Number(sp[1]));
       
   446             } else if (isarray) {
       
   447                 xvalues.push(val[0]);
       
   448                 yvalues.push(val[1]);
       
   449                 yminmax.push(val[1]);
       
   450             } else {
       
   451                 xvalues.push(i);
       
   452                 if (values[i]===null || values[i]=='null') {
       
   453                     yvalues.push(null);
       
   454                 } else {
       
   455                     yvalues.push(Number(val));
       
   456                     yminmax.push(Number(val));
       
   457                 }
       
   458             }
       
   459         }
       
   460         if (options.get('xvalues')) {
       
   461             xvalues = options.get('xvalues');
       
   462         }
       
   463 
       
   464         var maxy = Math.max.apply(Math, yminmax);
       
   465         var maxyval = maxy;
       
   466         var miny = Math.min.apply(Math, yminmax);
       
   467         var minyval = miny;
       
   468 
       
   469         var maxx = Math.max.apply(Math, xvalues);
       
   470         var minx = Math.min.apply(Math, xvalues);
       
   471 
       
   472         var normalRangeMin = options.get('normalRangeMin');
       
   473         var normalRangeMax = options.get('normalRangeMax');
       
   474 
       
   475         if (normalRangeMin!==undefined) {
       
   476             if (normalRangeMin<miny) {
       
   477                 miny = normalRangeMin;
       
   478             }
       
   479             if (normalRangeMax>maxy) {
       
   480                 maxy = normalRangeMax;
       
   481             }
       
   482         }
       
   483         if (options.get('chartRangeMin')!==undefined && (options.get('chartRangeClip') ||  options.get('chartRangeMin')<miny)) {
       
   484             miny = options.get('chartRangeMin');
       
   485         }
       
   486         if (options.get('chartRangeMax')!==undefined && (options.get('chartRangeClip') || options.get('chartRangeMax')>maxy)) {
       
   487             maxy = options.get('chartRangeMax');
       
   488         }
       
   489         if (options.get('chartRangeMinX')!==undefined && (options.get('chartRangeClipX') || options.get('chartRangeMinX')<minx)) {
       
   490             minx = options.get('chartRangeMinX');
       
   491         }
       
   492         if (options.get('chartRangeMaxX')!==undefined && (options.get('chartRangeClipX') || options.get('chartRangeMaxX')>maxx)) {
       
   493             maxx = options.get('chartRangeMaxX');
       
   494         }
       
   495         var rangex = maxx-minx === 0 ? 1 : maxx-minx;
       
   496         var rangey = maxy-miny === 0 ? 1 : maxy-miny;
       
   497         var vl = yvalues.length-1;
       
   498 
       
   499         if (vl<1) {
       
   500             this.innerHTML = '';
       
   501             return;
       
   502         }
       
   503 
       
   504         var target = $(this).simpledraw(width, height, options.get('composite'));
       
   505         if (target) {
       
   506             var canvas_width = target.pixel_width;
       
   507             var canvas_height = target.pixel_height;
       
   508             var canvas_top = 0;
       
   509             var canvas_left = 0;
       
   510 
       
   511             var spotRadius = options.get('spotRadius');
       
   512             if (spotRadius && (canvas_width < (spotRadius*4) || canvas_height < (spotRadius*4))) {
       
   513                 spotRadius = 0;
       
   514             }
       
   515             if (spotRadius) {
       
   516                 // adjust the canvas size as required so that spots will fit
       
   517                 if (options.get('minSpotColor') || (options.get('spotColor') && yvalues[vl]==miny)) {
       
   518                     canvas_height -= Math.ceil(spotRadius);
       
   519                 }
       
   520                 if (options.get('maxSpotColor') || (options.get('spotColor') && yvalues[vl]==maxy)) {
       
   521                     canvas_height -= Math.ceil(spotRadius);
       
   522                     canvas_top += Math.ceil(spotRadius);
       
   523                 }
       
   524                 if (options.get('minSpotColor') || options.get('maxSpotColor') && (yvalues[0]==miny || yvalues[0]==maxy)) {
       
   525                     canvas_left += Math.ceil(spotRadius);
       
   526                     canvas_width -= Math.ceil(spotRadius);
       
   527                 }
       
   528                 if (options.get('spotColor') || (options.get('minSpotColor') || options.get('maxSpotColor') && (yvalues[vl]==miny||yvalues[vl]==maxy))) {
       
   529                     canvas_width -= Math.ceil(spotRadius);
       
   530                 }
       
   531             }
       
   532 
       
   533 
       
   534             canvas_height--;
       
   535 
       
   536             var drawNormalRange = function() {
       
   537                 if (normalRangeMin!==undefined) {
       
   538                     var ytop = canvas_top+Math.round(canvas_height-(canvas_height*((normalRangeMax-miny)/rangey)));
       
   539                     var height = Math.round((canvas_height*(normalRangeMax-normalRangeMin))/rangey);
       
   540                     target.drawRect(canvas_left, ytop, canvas_width, height, undefined, options.get('normalRangeColor'));
       
   541                 }
       
   542             };
       
   543 
       
   544             if (!options.get('drawNormalOnTop')) {
       
   545                 drawNormalRange();
       
   546             }
       
   547 
       
   548             var path = [];
       
   549             var paths = [path];
       
   550             var x, y, vlen=yvalues.length;
       
   551             for(i=0; i<vlen; i++) {
       
   552                 x=xvalues[i];
       
   553                 y=yvalues[i];
       
   554                 if (y===null) {
       
   555                     if (i) {
       
   556                         if (yvalues[i-1]!==null) {
       
   557                             path = [];
       
   558                             paths.push(path);
       
   559                         }
       
   560                     }
       
   561                 } else {
       
   562                     if (y < miny) {
       
   563                         y=miny;
       
   564                     }
       
   565                     if (y > maxy) {
       
   566                         y=maxy;
       
   567                     }
       
   568                     if (!path.length) {
       
   569                         // previous value was null
       
   570                         path.push([canvas_left+Math.round((x-minx)*(canvas_width/rangex)), canvas_top+canvas_height]);
       
   571                     }
       
   572                     path.push([canvas_left+Math.round((x-minx)*(canvas_width/rangex)), canvas_top+Math.round(canvas_height-(canvas_height*((y-miny)/rangey)))]);
       
   573                 }
       
   574             }
       
   575             var lineshapes = [];
       
   576             var fillshapes = [];
       
   577             var plen=paths.length;
       
   578             for(i=0; i<plen; i++) {
       
   579                 path = paths[i];
       
   580                 if (!path.length) {
       
   581                     continue; // last value was null
       
   582                 }
       
   583                 if (options.get('fillColor')) {
       
   584                     path.push([path[path.length-1][0], canvas_top+canvas_height-1]);
       
   585                     fillshapes.push(path.slice(0));
       
   586                     path.pop();
       
   587                 }
       
   588                 // if there's only a single point in this path, then we want to display it as a vertical line
       
   589                 // which means we keep path[0]  as is
       
   590                 if (path.length>2) {
       
   591                     // else we want the first value 
       
   592                     path[0] = [ path[0][0], path[1][1] ];
       
   593                 }
       
   594                 lineshapes.push(path);
       
   595             }
       
   596 
       
   597             // draw the fill first, then optionally the normal range, then the line on top of that
       
   598             plen = fillshapes.length;
       
   599             for(i=0; i<plen; i++) {
       
   600                 target.drawShape(fillshapes[i], undefined, options.get('fillColor'));
       
   601             }
       
   602 
       
   603             if (options.get('drawNormalOnTop')) {
       
   604                 drawNormalRange();
       
   605             }
       
   606 
       
   607             plen = lineshapes.length;
       
   608             for(i=0; i<plen; i++) {
       
   609                 target.drawShape(lineshapes[i], options.get('lineColor'), undefined, options.get('lineWidth'));
       
   610             }
       
   611                 
       
   612             if (spotRadius && options.get('spotColor')) {
       
   613                 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'));
       
   614             }
       
   615             if (maxy!=minyval) {
       
   616                 if (spotRadius && options.get('minSpotColor')) {
       
   617                     x = xvalues[$.inArray(minyval, yvalues)];
       
   618                     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'));
       
   619                 }
       
   620                 if (spotRadius && options.get('maxSpotColor')) {
       
   621                     x = xvalues[$.inArray(maxyval, yvalues)];
       
   622                     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'));
       
   623                 }
       
   624             }
       
   625 
       
   626         } else {
       
   627             // Remove the tag contents if sparklines aren't supported
       
   628             this.innerHTML = '';
       
   629         }
       
   630     };
       
   631 
       
   632     
       
   633     /** 
       
   634      * Bar charts
       
   635      */
       
   636     $.fn.sparkline.bar = function(values, options, width, height) {
       
   637         width = (values.length * options.get('barWidth')) + ((values.length-1) * options.get('barSpacing'));
       
   638         var num_values = [];
       
   639         for(var i=0, vlen=values.length; i<vlen; i++) {
       
   640             if (values[i]=='null' || values[i]===null) {
       
   641                 values[i] = null;
       
   642             } else {
       
   643                 values[i] = Number(values[i]);
       
   644                 num_values.push(Number(values[i]));
       
   645             }
       
   646         }
       
   647         var max = Math.max.apply(Math, num_values),
       
   648             min = Math.min.apply(Math, num_values);
       
   649         if (options.get('chartRangeMin')!==undefined && (options.get('chartRangeClip') || options.get('chartRangeMin')<min)) {
       
   650             min = options.get('chartRangeMin');
       
   651         }
       
   652         if (options.get('chartRangeMax')!==undefined && (options.get('chartRangeClip') || options.get('chartRangeMax')>max)) {
       
   653             max = options.get('chartRangeMax');
       
   654         }
       
   655         var zeroAxis = options.get('zeroAxis');
       
   656         if (zeroAxis === undefined) {
       
   657             zeroAxis = min<0;
       
   658         }
       
   659         var range = max-min === 0 ? 1 : max-min;
       
   660 
       
   661         var colorMapByIndex, colorMapByValue;
       
   662         if ($.isArray(options.get('colorMap'))) {
       
   663             colorMapByIndex = options.get('colorMap');
       
   664             colorMapByValue = null;
       
   665         } else {
       
   666             colorMapByIndex = null;
       
   667             colorMapByValue = options.get('colorMap');
       
   668         }
       
   669 
       
   670         var target = $(this).simpledraw(width, height, options.get('composite'));
       
   671         if (target) {
       
   672             var color,
       
   673                 canvas_height = target.pixel_height,
       
   674                 yzero = min<0 && zeroAxis ? canvas_height-Math.round(canvas_height * (Math.abs(min)/range))-1 : canvas_height-1;
       
   675 
       
   676             for(i=values.length; i--;) {
       
   677                 var x = i*(options.get('barWidth')+options.get('barSpacing')),
       
   678                     y, 
       
   679                     val = values[i];
       
   680                 if (val===null) {
       
   681                     if (options.get('nullColor')) {
       
   682                         color = options.get('nullColor');
       
   683                         val = (zeroAxis && min<0) ? 0 : min;
       
   684                         height = 1;
       
   685                         y = (zeroAxis && min<0) ? yzero : canvas_height - height;
       
   686                     } else {
       
   687                         continue;
       
   688                     }
       
   689                 } else {
       
   690                     if (val < min) {
       
   691                         val=min;
       
   692                     }
       
   693                     if (val > max) {
       
   694                         val=max;
       
   695                     }
       
   696                     color = (val < 0) ? options.get('negBarColor') : options.get('barColor');
       
   697                     if (zeroAxis && min<0) {
       
   698                         height = Math.round(canvas_height*((Math.abs(val)/range)))+1;
       
   699                         y = (val < 0) ? yzero : yzero-height;
       
   700                     } else {
       
   701                         height = Math.round(canvas_height*((val-min)/range))+1;
       
   702                         y = canvas_height-height;
       
   703                     }
       
   704                     if (val===0 && options.get('zeroColor')!==undefined) {
       
   705                         color = options.get('zeroColor');
       
   706                     }
       
   707                     if (colorMapByValue && colorMapByValue[val]) {
       
   708                         color = colorMapByValue[val];
       
   709                     } else if (colorMapByIndex && colorMapByIndex.length>i) {
       
   710                         color = colorMapByIndex[i];
       
   711                     }
       
   712                     if (color===null) {
       
   713                         continue;
       
   714                     }
       
   715                 }
       
   716                 target.drawRect(x, y, options.get('barWidth')-1, height-1, color, color);
       
   717             }
       
   718         } else {
       
   719             // Remove the tag contents if sparklines aren't supported
       
   720             this.innerHTML = '';
       
   721         }
       
   722     };
       
   723 
       
   724 
       
   725     /**
       
   726      * Tristate charts
       
   727      */
       
   728     $.fn.sparkline.tristate = function(values, options, width, height) {
       
   729         values = $.map(values, Number);
       
   730         width = (values.length * options.get('barWidth')) + ((values.length-1) * options.get('barSpacing'));
       
   731 
       
   732         var colorMapByIndex, colorMapByValue;
       
   733         if ($.isArray(options.get('colorMap'))) {
       
   734             colorMapByIndex = options.get('colorMap');
       
   735             colorMapByValue = null;
       
   736         } else {
       
   737             colorMapByIndex = null;
       
   738             colorMapByValue = options.get('colorMap');
       
   739         }
       
   740 
       
   741         var target = $(this).simpledraw(width, height, options.get('composite'));
       
   742         if (target) {
       
   743             var canvas_height = target.pixel_height,
       
   744                 half_height = Math.round(canvas_height/2);
       
   745 
       
   746             for(var i=values.length; i--;) {
       
   747                 var x = i*(options.get('barWidth')+options.get('barSpacing')),
       
   748                     y, color;
       
   749                 if (values[i] < 0) {
       
   750                     y = half_height;
       
   751                     height = half_height-1;
       
   752                     color = options.get('negBarColor');
       
   753                 } else if (values[i] > 0) {
       
   754                     y = 0;
       
   755                     height = half_height-1;
       
   756                     color = options.get('posBarColor');
       
   757                 } else {
       
   758                     y = half_height-1;
       
   759                     height = 2;
       
   760                     color = options.get('zeroBarColor');
       
   761                 }
       
   762                 if (colorMapByValue && colorMapByValue[values[i]]) {
       
   763                     color = colorMapByValue[values[i]];
       
   764                 } else if (colorMapByIndex && colorMapByIndex.length>i) {
       
   765                     color = colorMapByIndex[i];
       
   766                 }
       
   767                 if (color===null) {
       
   768                     continue;
       
   769                 }
       
   770                 target.drawRect(x, y, options.get('barWidth')-1, height-1, color, color);
       
   771             }
       
   772         } else {
       
   773             // Remove the tag contents if sparklines aren't supported
       
   774             this.innerHTML = '';
       
   775         }
       
   776     };
       
   777 
       
   778 
       
   779     /** 
       
   780      * Discrete charts
       
   781      */
       
   782     $.fn.sparkline.discrete = function(values, options, width, height) {
       
   783         values = $.map(values, Number);
       
   784         width = options.get('width')=='auto' ? values.length*2 : width;
       
   785         var interval = Math.floor(width / values.length);
       
   786 
       
   787         var target = $(this).simpledraw(width, height, options.get('composite'));
       
   788         if (target) {
       
   789             var canvas_height = target.pixel_height,
       
   790                 line_height = options.get('lineHeight') == 'auto' ? Math.round(canvas_height * 0.3) : options.get('lineHeight'),
       
   791                 pheight = canvas_height - line_height,
       
   792                 min = Math.min.apply(Math, values),
       
   793                 max = Math.max.apply(Math, values);
       
   794             if (options.get('chartRangeMin')!==undefined && (options.get('chartRangeClip') || options.get('chartRangeMin')<min)) {
       
   795                 min = options.get('chartRangeMin');
       
   796             }
       
   797             if (options.get('chartRangeMax')!==undefined && (options.get('chartRangeClip')  || options.get('chartRangeMax')>max)) {
       
   798                 max = options.get('chartRangeMax');
       
   799             }
       
   800             var range = max-min;
       
   801 
       
   802             for(var i=values.length; i--;) {
       
   803                 var val = values[i];
       
   804                 if (val < min) {
       
   805                     val=min;
       
   806                 }
       
   807                 if (val > max) {
       
   808                     val=max;
       
   809                 }
       
   810                 var x = (i*interval),
       
   811                     ytop = Math.round(pheight-pheight*((val-min)/range));
       
   812                 target.drawLine(x, ytop, x, ytop+line_height, (options.get('thresholdColor') && val < options.get('thresholdValue')) ? options.get('thresholdColor') : options.get('lineColor'));
       
   813             }
       
   814         }  else {
       
   815             // Remove the tag contents if sparklines aren't supported
       
   816             this.innerHTML = '';
       
   817         }
       
   818                 
       
   819     };
       
   820 
       
   821 
       
   822     /**
       
   823      * Bullet charts
       
   824      */
       
   825     $.fn.sparkline.bullet = function(values, options, width, height) {
       
   826         values = $.map(values, Number);
       
   827         // target, performance, range1, range2, range3
       
   828         
       
   829         width = options.get('width')=='auto' ? '4.0em' : width;
       
   830 
       
   831         var target = $(this).simpledraw(width, height, options.get('composite'));
       
   832         if (target && values.length>1) {
       
   833             var canvas_width = target.pixel_width-Math.ceil(options.get('targetWidth')/2),
       
   834                 canvas_height = target.pixel_height,
       
   835                 min = Math.min.apply(Math, values),
       
   836                 max = Math.max.apply(Math, values);
       
   837 
       
   838             if (options.get('base') === undefined) {
       
   839                 min = min < 0 ? min : 0;
       
   840             } else {
       
   841                 min = options.get('base');
       
   842             }
       
   843             var range = max-min;
       
   844 
       
   845             // draw range values
       
   846             for(var i=2, vlen=values.length; i<vlen; i++) {
       
   847                 var rangeval = values[i],
       
   848                     rangewidth = Math.round(canvas_width*((rangeval-min)/range));
       
   849                 target.drawRect(0, 0, rangewidth-1, canvas_height-1, options.get('rangeColors')[i-2], options.get('rangeColors')[i-2]);
       
   850             }
       
   851 
       
   852             // draw the performance bar
       
   853             var perfval = values[1],
       
   854                 perfwidth = Math.round(canvas_width*((perfval-min)/range));
       
   855             target.drawRect(0, Math.round(canvas_height*0.3), perfwidth-1, Math.round(canvas_height*0.4)-1, options.get('performanceColor'), options.get('performanceColor'));
       
   856 
       
   857             // draw the target line
       
   858             var targetval = values[0],
       
   859                 x = Math.round(canvas_width*((targetval-min)/range)-(options.get('targetWidth')/2)),
       
   860                 targettop = Math.round(canvas_height*0.10),
       
   861                 targetheight = canvas_height-(targettop*2);
       
   862             target.drawRect(x, targettop, options.get('targetWidth')-1, targetheight-1, options.get('targetColor'), options.get('targetColor'));
       
   863         }  else {
       
   864             // Remove the tag contents if sparklines aren't supported
       
   865             this.innerHTML = '';
       
   866         }
       
   867     };
       
   868 
       
   869 
       
   870     /**
       
   871      * Pie charts
       
   872      */
       
   873     $.fn.sparkline.pie = function(values, options, width, height) {
       
   874         values = $.map(values, Number);
       
   875         width = options.get('width')=='auto' ? height : width;
       
   876 
       
   877         var target = $(this).simpledraw(width, height, options.get('composite'));
       
   878         if (target && values.length>1) {
       
   879             var canvas_width = target.pixel_width,
       
   880                 canvas_height = target.pixel_height,
       
   881                 radius = Math.floor(Math.min(canvas_width, canvas_height)/2),
       
   882                 total = 0,
       
   883                 next = 0,
       
   884                 circle = 2*Math.PI;
       
   885 
       
   886             for(var i=values.length; i--;) {
       
   887                 total += values[i];
       
   888             }
       
   889 
       
   890             if (options.get('offset')) {
       
   891                 next += (2*Math.PI)*(options.get('offset')/360);
       
   892             }
       
   893             var vlen = values.length;
       
   894             for(i=0; i<vlen; i++) {
       
   895                 var start = next;
       
   896                 var end = next;
       
   897                 if (total > 0) {  // avoid divide by zero
       
   898                     end = next + (circle*(values[i]/total));
       
   899                 }
       
   900                 target.drawPieSlice(radius, radius, radius, start, end, undefined, options.get('sliceColors')[i % options.get('sliceColors').length]);
       
   901                 next = end;
       
   902             }
       
   903         }
       
   904     };
       
   905 
       
   906 
       
   907     /**
       
   908      * Box plots
       
   909      */
       
   910     var quartile = function(values, q) {
       
   911         if (q==2) {
       
   912             var vl2 = Math.floor(values.length/2);
       
   913             return values.length % 2 ? values[vl2] : (values[vl2]+values[vl2+1])/2;
       
   914         } else {
       
   915             var vl4 = Math.floor(values.length/4);
       
   916             return values.length % 2 ? (values[vl4*q]+values[vl4*q+1])/2 : values[vl4*q];
       
   917         }
       
   918     };
       
   919 
       
   920     $.fn.sparkline.box = function(values, options, width, height) {
       
   921         values = $.map(values, Number);
       
   922         width = options.get('width')=='auto' ? '4.0em' : width;
       
   923 
       
   924         var minvalue = options.get('chartRangeMin')===undefined ? Math.min.apply(Math, values) : options.get('chartRangeMin'),
       
   925             maxvalue = options.get('chartRangeMax')===undefined ? Math.max.apply(Math, values) : options.get('chartRangeMax'),
       
   926             target = $(this).simpledraw(width, height, options.get('composite')),
       
   927             vlen = values.length,
       
   928             lwhisker, loutlier, q1, q2, q3, rwhisker, routlier;
       
   929 
       
   930         if (target && values.length>1) {
       
   931             var canvas_width = target.pixel_width,
       
   932                 canvas_height = target.pixel_height;
       
   933             if (options.get('raw')) {
       
   934                 if (options.get('showOutliers') && values.length>5) {
       
   935                     loutlier=values[0]; lwhisker=values[1]; q1=values[2]; q2=values[3]; q3=values[4]; rwhisker=values[5]; routlier=values[6];
       
   936                 } else {
       
   937                     lwhisker=values[0]; q1=values[1]; q2=values[2]; q3=values[3]; rwhisker=values[4];
       
   938                 }
       
   939             } else {
       
   940                 values.sort(function(a, b) { return a-b; });
       
   941                 q1 = quartile(values, 1);
       
   942                 q2 = quartile(values, 2);
       
   943                 q3 = quartile(values, 3);
       
   944                 var iqr = q3-q1;
       
   945                 if (options.get('showOutliers')) {
       
   946                     lwhisker=undefined; rwhisker=undefined;
       
   947                     for(var i=0; i<vlen; i++) {
       
   948                         if (lwhisker===undefined && values[i] > q1-(iqr*options.get('outlierIQR'))) {
       
   949                             lwhisker = values[i];
       
   950                         }
       
   951                         if (values[i] < q3+(iqr*options.get('outlierIQR'))) {
       
   952                             rwhisker = values[i];
       
   953                         }
       
   954                     }
       
   955                     loutlier = values[0];
       
   956                     routlier = values[vlen-1];
       
   957                 } else {
       
   958                     lwhisker = values[0];
       
   959                     rwhisker = values[vlen-1];
       
   960                 }
       
   961             }
       
   962 
       
   963             var unitsize = canvas_width / (maxvalue-minvalue+1),
       
   964                 canvas_left = 0;
       
   965             if (options.get('showOutliers')) {
       
   966                 canvas_left = Math.ceil(options.get('spotRadius'));
       
   967                 canvas_width -= 2*Math.ceil(options.get('spotRadius'));
       
   968                 unitsize = canvas_width / (maxvalue-minvalue+1);
       
   969                 if (loutlier < lwhisker) {
       
   970                     target.drawCircle((loutlier-minvalue)*unitsize+canvas_left, canvas_height/2, options.get('spotRadius'), options.get('outlierLineColor'), options.get('outlierFillColor'));
       
   971                 }
       
   972                 if (routlier > rwhisker) {
       
   973                     target.drawCircle((routlier-minvalue)*unitsize+canvas_left, canvas_height/2, options.get('spotRadius'), options.get('outlierLineColor'), options.get('outlierFillColor'));
       
   974                 }
       
   975             }
       
   976 
       
   977             // box
       
   978             target.drawRect(
       
   979                 Math.round((q1-minvalue)*unitsize+canvas_left),
       
   980                 Math.round(canvas_height*0.1),
       
   981                 Math.round((q3-q1)*unitsize), 
       
   982                 Math.round(canvas_height*0.8), 
       
   983                 options.get('boxLineColor'), 
       
   984                 options.get('boxFillColor'));
       
   985             // left whisker
       
   986             target.drawLine(
       
   987                 Math.round((lwhisker-minvalue)*unitsize+canvas_left), 
       
   988                 Math.round(canvas_height/2), 
       
   989                 Math.round((q1-minvalue)*unitsize+canvas_left), 
       
   990                 Math.round(canvas_height/2), 
       
   991                 options.get('lineColor'));
       
   992             target.drawLine(
       
   993                 Math.round((lwhisker-minvalue)*unitsize+canvas_left), 
       
   994                 Math.round(canvas_height/4), 
       
   995                 Math.round((lwhisker-minvalue)*unitsize+canvas_left), 
       
   996                 Math.round(canvas_height-canvas_height/4), 
       
   997                 options.get('whiskerColor'));
       
   998             // right whisker
       
   999             target.drawLine(Math.round((rwhisker-minvalue)*unitsize+canvas_left), 
       
  1000                 Math.round(canvas_height/2), 
       
  1001                 Math.round((q3-minvalue)*unitsize+canvas_left), 
       
  1002                 Math.round(canvas_height/2), 
       
  1003                 options.get('lineColor'));
       
  1004             target.drawLine(
       
  1005                 Math.round((rwhisker-minvalue)*unitsize+canvas_left), 
       
  1006                 Math.round(canvas_height/4), 
       
  1007                 Math.round((rwhisker-minvalue)*unitsize+canvas_left), 
       
  1008                 Math.round(canvas_height-canvas_height/4), 
       
  1009                 options.get('whiskerColor'));
       
  1010             // median line
       
  1011             target.drawLine(
       
  1012                 Math.round((q2-minvalue)*unitsize+canvas_left), 
       
  1013                 Math.round(canvas_height*0.1),
       
  1014                 Math.round((q2-minvalue)*unitsize+canvas_left), 
       
  1015                 Math.round(canvas_height*0.9),
       
  1016                 options.get('medianColor'));
       
  1017             if (options.get('target')) {
       
  1018                 var size = Math.ceil(options.get('spotRadius'));
       
  1019                 target.drawLine(
       
  1020                     Math.round((options.get('target')-minvalue)*unitsize+canvas_left), 
       
  1021                     Math.round((canvas_height/2)-size), 
       
  1022                     Math.round((options.get('target')-minvalue)*unitsize+canvas_left), 
       
  1023                     Math.round((canvas_height/2)+size), 
       
  1024                     options.get('targetColor'));
       
  1025                 target.drawLine(
       
  1026                     Math.round((options.get('target')-minvalue)*unitsize+canvas_left-size), 
       
  1027                     Math.round(canvas_height/2), 
       
  1028                     Math.round((options.get('target')-minvalue)*unitsize+canvas_left+size), 
       
  1029                     Math.round(canvas_height/2), 
       
  1030                     options.get('targetColor'));
       
  1031             }
       
  1032         }  else {
       
  1033             // Remove the tag contents if sparklines aren't supported
       
  1034             this.innerHTML = '';
       
  1035         }
       
  1036     };
       
  1037 
       
  1038 
       
  1039     // Setup a very simple "virtual canvas" to make drawing the few shapes we need easier
       
  1040     // This is accessible as $(foo).simpledraw()
       
  1041 
       
  1042     if ($.browser.msie && !document.namespaces.v) {
       
  1043         document.namespaces.add('v', 'urn:schemas-microsoft-com:vml', '#default#VML');
       
  1044     }
       
  1045 
       
  1046     if ($.browser.hasCanvas === undefined) {
       
  1047         var t = document.createElement('canvas');
       
  1048         $.browser.hasCanvas = t.getContext!==undefined;
       
  1049     }
       
  1050 
       
  1051     VCanvas_base = function(width, height, target) {
       
  1052     };
       
  1053 
       
  1054     VCanvas_base.prototype = {
       
  1055         init : function(width, height, target) {
       
  1056             this.width = width;
       
  1057             this.height = height;
       
  1058             this.target = target;
       
  1059             if (target[0]) {
       
  1060                 target=target[0];
       
  1061             }
       
  1062             target.VCanvas = this;
       
  1063         },
       
  1064 
       
  1065         drawShape : function(path, lineColor, fillColor, lineWidth) {
       
  1066             alert('drawShape not implemented');
       
  1067         },
       
  1068 
       
  1069         drawLine : function(x1, y1, x2, y2, lineColor, lineWidth) {
       
  1070             return this.drawShape([ [x1,y1], [x2,y2] ], lineColor, lineWidth);
       
  1071         },
       
  1072 
       
  1073         drawCircle : function(x, y, radius, lineColor, fillColor) {
       
  1074             alert('drawCircle not implemented');
       
  1075         },
       
  1076 
       
  1077         drawPieSlice : function(x, y, radius, startAngle, endAngle, lineColor, fillColor) {
       
  1078             alert('drawPieSlice not implemented');
       
  1079         },
       
  1080 
       
  1081         drawRect : function(x, y, width, height, lineColor, fillColor) {
       
  1082             alert('drawRect not implemented');
       
  1083         },
       
  1084 
       
  1085         getElement : function() {
       
  1086             return this.canvas;
       
  1087         },
       
  1088 
       
  1089         _insert : function(el, target) {
       
  1090             $(target).html(el);
       
  1091         }
       
  1092     };
       
  1093 
       
  1094     VCanvas_canvas = function(width, height, target) {
       
  1095         return this.init(width, height, target);
       
  1096     };
       
  1097 
       
  1098     VCanvas_canvas.prototype = $.extend(new VCanvas_base(), {
       
  1099         _super : VCanvas_base.prototype,
       
  1100 
       
  1101         init : function(width, height, target) {
       
  1102             this._super.init(width, height, target);
       
  1103             this.canvas = document.createElement('canvas');
       
  1104             if (target[0]) {
       
  1105                 target=target[0];
       
  1106             }
       
  1107             target.VCanvas = this;
       
  1108             $(this.canvas).css({ display:'inline-block', width:width, height:height, verticalAlign:'top' });
       
  1109             this._insert(this.canvas, target);
       
  1110             this.pixel_height = $(this.canvas).height();
       
  1111             this.pixel_width = $(this.canvas).width();
       
  1112             this.canvas.width = this.pixel_width;
       
  1113             this.canvas.height = this.pixel_height;
       
  1114             $(this.canvas).css({width: this.pixel_width, height: this.pixel_height});
       
  1115         },
       
  1116 
       
  1117         _getContext : function(lineColor, fillColor, lineWidth) {
       
  1118             var context = this.canvas.getContext('2d');
       
  1119             if (lineColor !== undefined) {
       
  1120                 context.strokeStyle = lineColor;
       
  1121             }
       
  1122             context.lineWidth = lineWidth===undefined ? 1 : lineWidth;
       
  1123             if (fillColor !== undefined) {
       
  1124                 context.fillStyle = fillColor;
       
  1125             }
       
  1126             return context;
       
  1127         },
       
  1128 
       
  1129         drawShape : function(path, lineColor, fillColor, lineWidth) {
       
  1130             var context = this._getContext(lineColor, fillColor, lineWidth);
       
  1131             context.beginPath();
       
  1132             context.moveTo(path[0][0]+0.5, path[0][1]+0.5);
       
  1133             for(var i=1, plen=path.length; i<plen; i++) {
       
  1134                 context.lineTo(path[i][0]+0.5, path[i][1]+0.5); // the 0.5 offset gives us crisp pixel-width lines
       
  1135             }
       
  1136             if (lineColor !== undefined) {
       
  1137                 context.stroke();
       
  1138             }
       
  1139             if (fillColor !== undefined) {
       
  1140                 context.fill();
       
  1141             }
       
  1142         },
       
  1143 
       
  1144         drawCircle : function(x, y, radius, lineColor, fillColor) {
       
  1145             var context = this._getContext(lineColor, fillColor);
       
  1146             context.beginPath();
       
  1147             context.arc(x, y, radius, 0, 2*Math.PI, false);
       
  1148             if (lineColor !== undefined) {
       
  1149                 context.stroke();
       
  1150             }
       
  1151             if (fillColor !== undefined) {
       
  1152                 context.fill();
       
  1153             }
       
  1154         }, 
       
  1155 
       
  1156         drawPieSlice : function(x, y, radius, startAngle, endAngle, lineColor, fillColor) {
       
  1157             var context = this._getContext(lineColor, fillColor);
       
  1158             context.beginPath();
       
  1159             context.moveTo(x, y);
       
  1160             context.arc(x, y, radius, startAngle, endAngle, false);
       
  1161             context.lineTo(x, y);
       
  1162             context.closePath();
       
  1163             if (lineColor !== undefined) {
       
  1164                 context.stroke();
       
  1165             }
       
  1166             if (fillColor) {
       
  1167                 context.fill();
       
  1168             }
       
  1169         },
       
  1170 
       
  1171         drawRect : function(x, y, width, height, lineColor, fillColor) {
       
  1172             return this.drawShape([ [x,y], [x+width, y], [x+width, y+height], [x, y+height], [x, y] ], lineColor, fillColor);
       
  1173         }
       
  1174         
       
  1175     });
       
  1176 
       
  1177     VCanvas_vml = function(width, height, target) {
       
  1178         return this.init(width, height, target);
       
  1179     };
       
  1180 
       
  1181     VCanvas_vml.prototype = $.extend(new VCanvas_base(), {
       
  1182         _super : VCanvas_base.prototype,
       
  1183 
       
  1184         init : function(width, height, target) {
       
  1185             this._super.init(width, height, target);
       
  1186             if (target[0]) {
       
  1187                 target=target[0];
       
  1188             }
       
  1189             target.VCanvas = this;
       
  1190             this.canvas = document.createElement('span');
       
  1191             $(this.canvas).css({ display:'inline-block', position: 'relative', overflow:'hidden', width:width, height:height, margin:'0px', padding:'0px', verticalAlign: 'top'});
       
  1192             this._insert(this.canvas, target);
       
  1193             this.pixel_height = $(this.canvas).height();
       
  1194             this.pixel_width = $(this.canvas).width();
       
  1195             this.canvas.width = this.pixel_width;
       
  1196             this.canvas.height = this.pixel_height;
       
  1197             var groupel = '<v:group coordorigin="0 0" coordsize="'+this.pixel_width+' '+this.pixel_height+'"' +
       
  1198                     ' style="position:absolute;top:0;left:0;width:'+this.pixel_width+'px;height='+this.pixel_height+'px;"></v:group>';
       
  1199             this.canvas.insertAdjacentHTML('beforeEnd', groupel);
       
  1200             this.group = $(this.canvas).children()[0];
       
  1201         },
       
  1202 
       
  1203         drawShape : function(path, lineColor, fillColor, lineWidth) {
       
  1204             var vpath = [];
       
  1205             for(var i=0, plen=path.length; i<plen; i++) {
       
  1206                 vpath[i] = ''+(path[i][0])+','+(path[i][1]);
       
  1207             }
       
  1208             var initial = vpath.splice(0,1);
       
  1209             lineWidth = lineWidth === undefined ? 1 : lineWidth;
       
  1210             var stroke = lineColor === undefined ? ' stroked="false" ' : ' strokeWeight="'+lineWidth+'" strokeColor="'+lineColor+'" ';
       
  1211             var fill = fillColor === undefined ? ' filled="false"' : ' fillColor="'+fillColor+'" filled="true" ';
       
  1212             var closed = vpath[0] == vpath[vpath.length-1] ? 'x ' : '';
       
  1213             var vel = '<v:shape coordorigin="0 0" coordsize="'+this.pixel_width+' '+this.pixel_height+'" ' +
       
  1214                  stroke +
       
  1215                  fill +
       
  1216                 ' style="position:absolute;left:0px;top:0px;height:'+this.pixel_height+'px;width:'+this.pixel_width+'px;padding:0px;margin:0px;" ' +
       
  1217                 ' path="m '+initial+' l '+vpath.join(', ')+' '+closed+'e">' +
       
  1218                 ' </v:shape>';
       
  1219              this.group.insertAdjacentHTML('beforeEnd', vel);
       
  1220         },
       
  1221 
       
  1222         drawCircle : function(x, y, radius, lineColor, fillColor) {
       
  1223             x -= radius+1;
       
  1224             y -= radius+1;
       
  1225             var stroke = lineColor === undefined ? ' stroked="false" ' : ' strokeWeight="1" strokeColor="'+lineColor+'" ';
       
  1226             var fill = fillColor === undefined ? ' filled="false"' : ' fillColor="'+fillColor+'" filled="true" ';
       
  1227             var vel = '<v:oval ' +
       
  1228                 stroke +
       
  1229                 fill +
       
  1230                 ' style="position:absolute;top:'+y+'px; left:'+x+'px; width:'+(radius*2)+'px; height:'+(radius*2)+'px"></v:oval>';
       
  1231             this.group.insertAdjacentHTML('beforeEnd', vel);
       
  1232             
       
  1233         },
       
  1234         
       
  1235         drawPieSlice : function(x, y, radius, startAngle, endAngle, lineColor, fillColor) {
       
  1236             if (startAngle == endAngle) {
       
  1237                 return;  // VML seems to have problem when start angle equals end angle.
       
  1238             }
       
  1239             if ((endAngle - startAngle) == (2*Math.PI)) {
       
  1240                 startAngle = 0.0;  // VML seems to have a problem when drawing a full circle that doesn't start 0
       
  1241                 endAngle = (2*Math.PI);
       
  1242             }
       
  1243 
       
  1244             var startx = x + Math.round(Math.cos(startAngle) * radius);
       
  1245             var starty = y + Math.round(Math.sin(startAngle) * radius);
       
  1246             var endx = x + Math.round(Math.cos(endAngle) * radius);
       
  1247             var endy = y + Math.round(Math.sin(endAngle) * radius);
       
  1248 
       
  1249             // Prevent very small slices from being mistaken as a whole pie
       
  1250             if (startx==endx && starty==endy && (endAngle-startAngle) < Math.PI) {
       
  1251                 return;
       
  1252             }
       
  1253 
       
  1254             var vpath = [  x-radius, y-radius, x+radius, y+radius, startx, starty, endx, endy ]; 
       
  1255             var stroke = lineColor === undefined ? ' stroked="false" ' : ' strokeWeight="1" strokeColor="'+lineColor+'" ';
       
  1256             var fill = fillColor === undefined ? ' filled="false"' : ' fillColor="'+fillColor+'" filled="true" ';
       
  1257             var vel = '<v:shape coordorigin="0 0" coordsize="'+this.pixel_width+' '+this.pixel_height+'" ' +
       
  1258                  stroke +
       
  1259                  fill +
       
  1260                 ' style="position:absolute;left:0px;top:0px;height:'+this.pixel_height+'px;width:'+this.pixel_width+'px;padding:0px;margin:0px;" ' +
       
  1261                 ' path="m '+x+','+y+' wa '+vpath.join(', ')+' x e">' +
       
  1262                 ' </v:shape>';
       
  1263              this.group.insertAdjacentHTML('beforeEnd', vel);
       
  1264         },
       
  1265 
       
  1266         drawRect : function(x, y, width, height, lineColor, fillColor) {
       
  1267             return this.drawShape( [ [x, y], [x, y+height], [x+width, y+height], [x+width, y], [x, y] ], lineColor, fillColor);
       
  1268         }
       
  1269     });
       
  1270 
       
  1271 })(jQuery);