7 * Contact: Gareth Watts (gareth@splunk.com)
8 * http://omnipotent.net/jquery.sparkline/
10 * Generates inline sparkline charts from data supplied either to the method
13 * Compatible with Internet Explorer 6.0+ and modern browsers equipped with the canvas tag
14 * (Firefox 2.0+, Safari, Opera, etc)
16 * License: New BSD License
18 * Copyright (c) 2012, Splunk Inc.
19 * All rights reserved.
21 * Redistribution and use in source and binary forms, with or without modification,
22 * are permitted provided that the following conditions are met:
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.
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.
45 * $(selector).sparkline(values, options)
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
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])
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();
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] ])
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'})
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)
76 * lineColor - Color of the line used for the chart
77 * fillColor - Color used to fill in the chart - Set to '' or false for a transparent chart
78 * width - Width of the chart - Defaults to 3 times the number of values in pixels
79 * height - Height of the chart - Defaults to the height of the containing element
80 * chartRangeMin - Specify the minimum value to use for the Y range of the chart - Defaults to the minimum value supplied
81 * chartRangeMax - Specify the maximum value to use for the Y range of the chart - Defaults to the maximum value supplied
82 * chartRangeClip - Clip out of range values to the max/min specified by chartRangeMin and chartRangeMax
83 * chartRangeMinX - Specify the minimum value to use for the X range of the chart - Defaults to the minimum value supplied
84 * chartRangeMaxX - Specify the maximum value to use for the X range of the chart - Defaults to the maximum value supplied
85 * composite - If true then don't erase any existing chart attached to the tag, but draw
86 * another chart over the top - Note that width and height are ignored if an
87 * existing chart is detected.
88 * tagValuesAttribute - Name of tag attribute to check for data values - Defaults to 'values'
89 * enableTagOptions - Whether to check tags for sparkline options
90 * tagOptionPrefix - Prefix used for options supplied as tag attributes - Defaults to 'spark'
91 * disableHiddenCheck - If set to true, then the plugin will assume that charts will never be drawn into a
92 * hidden dom element, avoding a browser reflow
93 * disableInteraction - If set to true then all mouseover/click interaction behaviour will be disabled,
94 * making the plugin perform much like it did in 1.x
95 * disableTooltips - If set to true then tooltips will be disabled - Defaults to false (tooltips enabled)
96 * disableHighlight - If set to true then highlighting of selected chart elements on mouseover will be disabled
97 * defaults to false (highlights enabled)
98 * highlightLighten - Factor to lighten/darken highlighted chart values by - Defaults to 1.4 for a 40% increase
99 * tooltipContainer - Specify which DOM element the tooltip should be rendered into - defaults to document.body
100 * tooltipClassname - Optional CSS classname to apply to tooltips - If not specified then a default style will be applied
101 * tooltipOffsetX - How many pixels away from the mouse pointer to render the tooltip on the X axis
102 * tooltipOffsetY - How many pixels away from the mouse pointer to render the tooltip on the r axis
103 * tooltipFormatter - Optional callback that allows you to override the HTML displayed in the tooltip
104 * callback is given arguments of (sparkline, options, fields)
105 * tooltipChartTitle - If specified then the tooltip uses the string specified by this setting as a title
106 * tooltipFormat - A format string or SPFormat object (or an array thereof for multiple entries)
107 * to control the format of the tooltip
108 * tooltipPrefix - A string to prepend to each field displayed in a tooltip
109 * tooltipSuffix - A string to append to each field displayed in a tooltip
110 * tooltipSkipNull - If true then null values will not have a tooltip displayed (defaults to true)
111 * tooltipValueLookups - An object or range map to map field values to tooltip strings
112 * (eg. to map -1 to "Lost", 0 to "Draw", and 1 to "Win")
113 * numberFormatter - Optional callback for formatting numbers in tooltips
114 * numberDigitGroupSep - Character to use for group separator in numbers "1,234" - Defaults to ","
115 * numberDecimalMark - Character to use for the decimal point when formatting numbers - Defaults to "."
116 * numberDigitGroupCount - Number of digits between group separator - Defaults to 3
118 * There are 7 types of sparkline, selected by supplying a "type" option of 'line' (default),
119 * 'bar', 'tristate', 'bullet', 'discrete', 'pie' or 'box'
120 * line - Line chart. Options:
121 * spotColor - Set to '' to not end each line in a circular spot
122 * minSpotColor - If set, color of spot at minimum value
123 * maxSpotColor - If set, color of spot at maximum value
124 * spotRadius - Radius in pixels
125 * lineWidth - Width of line in pixels
127 * normalRangeMax - If set draws a filled horizontal bar between these two values marking the "normal"
128 * or expected range of values
129 * normalRangeColor - Color to use for the above bar
130 * drawNormalOnTop - Draw the normal range above the chart fill color if true
131 * defaultPixelsPerValue - Defaults to 3 pixels of width for each value in the chart
132 * highlightSpotColor - The color to use for drawing a highlight spot on mouseover - Set to null to disable
133 * highlightLineColor - The color to use for drawing a highlight line on mouseover - Set to null to disable
134 * valueSpots - Specify which points to draw spots on, and in which color. Accepts a range map
136 * bar - Bar chart. Options:
137 * barColor - Color of bars for postive values
138 * negBarColor - Color of bars for negative values
139 * zeroColor - Color of bars with zero values
140 * nullColor - Color of bars with null values - Defaults to omitting the bar entirely
141 * barWidth - Width of bars in pixels
142 * colorMap - Optional mappnig of values to colors to override the *BarColor values above
143 * can be an Array of values to control the color of individual bars or a range map
144 * to specify colors for individual ranges of values
145 * barSpacing - Gap between bars in pixels
146 * zeroAxis - Centers the y-axis around zero if true
148 * tristate - Charts values of win (>0), lose (<0) or draw (=0)
149 * posBarColor - Color of win values
150 * negBarColor - Color of lose values
151 * zeroBarColor - Color of draw values
152 * barWidth - Width of bars in pixels
153 * barSpacing - Gap between bars in pixels
154 * colorMap - Optional mappnig of values to colors to override the *BarColor values above
155 * can be an Array of values to control the color of individual bars or a range map
156 * to specify colors for individual ranges of values
158 * discrete - Options:
159 * lineHeight - Height of each line in pixels - Defaults to 30% of the graph height
160 * thesholdValue - Values less than this value will be drawn using thresholdColor instead of lineColor
163 * bullet - Values for bullet graphs msut be in the order: target, performance, range1, range2, range3, ...
165 * targetColor - The color of the vertical target marker
166 * targetWidth - The width of the target marker in pixels
167 * performanceColor - The color of the performance measure horizontal bar
168 * rangeColors - Colors to use for each qualitative range background color
170 * pie - Pie chart. Options:
171 * sliceColors - An array of colors to use for pie slices
172 * offset - Angle in degrees to offset the first slice - Try -90 or +90
173 * borderWidth - Width of border to draw around the pie chart, in pixels - Defaults to 0 (no border)
174 * borderColor - Color to use for the pie chart border - Defaults to #000
176 * box - Box plot. Options:
177 * raw - Set to true to supply pre-computed plot points as values
178 * values should be: low_outlier, low_whisker, q1, median, q3, high_whisker, high_outlier
179 * When set to false you can supply any number of values and the box plot will
180 * be computed for you. Default is false.
181 * showOutliers - Set to true (default) to display outliers as circles
182 * outlierIQR - Interquartile range used to determine outliers. Default 1.5
183 * boxLineColor - Outline color of the box
184 * boxFillColor - Fill color for the box
185 * whiskerColor - Line color used for whiskers
186 * outlierLineColor - Outline color of outlier circles
187 * outlierFillColor - Fill color of the outlier circles
188 * spotRadius - Radius of outlier circles
189 * medianColor - Line color of the median line
190 * target - Draw a target cross hair at the supplied value (default undefined)
195 * $('#sparkline1').sparkline(myvalues, { lineColor: '#f00', fillColor: false });
196 * $('.barsparks').sparkline('html', { type:'bar', height:'40px', barWidth:5 });
197 * $('#tristate').sparkline([1,1,-1,1,0,0,-1], { type:'tristate' }):
198 * $('#discrete').sparkline([1,3,4,5,5,3,4,5], { type:'discrete' });
199 * $('#bullet').sparkline([10,12,12,9,7], { type:'bullet' });
200 * $('#pie').sparkline([1,1,2], { type:'pie' });
203 /*jslint regexp: true, browser: true, jquery: true, white: true, nomen: false, plusplus: false, maxerr: 500, indent: 4 */
205 (function(document
, Math
, undefined) { // performance/minified-size optimization
207 if(typeof define
=== 'function' && define
.amd
) {
208 define(['jquery'], factory
);
209 } else if (jQuery
&& !jQuery
.fn
.sparkline
) {
216 var UNSET_OPTION
= {},
217 getDefaults
, createClass
, SPFormat
, clipval
, quartile
, normalizeValue
, normalizeValues
,
218 remove
, isNumber
, all
, sum
, addCSS
, ensureArray
, formatNumber
, RangeMap
,
219 MouseHandler
, Tooltip
, barHighlightMixin
,
220 line
, bar
, tristate
, discrete
, bullet
, pie
, box
, defaultStyles
, initStyles
,
221 VShape
, VCanvas_base
, VCanvas_canvas
, VCanvas_vml
, pending
, shapeCount
= 0;
224 * Default configuration settings
226 getDefaults = function () {
228 // Settings common to most/all chart types
233 defaultPixelsPerValue
: 3,
237 tagValuesAttribute
: 'values',
238 tagOptionsPrefix
: 'spark',
239 enableTagOptions
: false,
240 enableHighlight
: true,
241 highlightLighten
: 1.4,
242 tooltipSkipNull
: true,
245 disableHiddenCheck
: false,
246 numberFormatter
: false,
247 numberDigitGroupCount
: 3,
248 numberDigitGroupSep
: ',',
249 numberDecimalMark
: '.',
250 disableTooltips
: false,
251 disableInteraction
: false
253 // Defaults for line charts
256 highlightSpotColor
: '#5f5',
257 highlightLineColor
: '#f22',
259 minSpotColor
: '#f80',
260 maxSpotColor
: '#f80',
262 normalRangeMin
: undefined,
263 normalRangeMax
: undefined,
264 normalRangeColor
: '#ccc',
265 drawNormalOnTop
: false,
266 chartRangeMin
: undefined,
267 chartRangeMax
: undefined,
268 chartRangeMinX
: undefined,
269 chartRangeMaxX
: undefined,
270 tooltipFormat
: new SPFormat('<span style="color: {{color}}">●</span> {{prefix}}{{y}}{{suffix}}')
272 // Defaults for bar charts
276 stackedBarColor
: ['#3366cc', '#dc3912', '#ff9900', '#109618', '#66aa00',
277 '#dd4477', '#0099c6', '#990099'],
278 zeroColor
: undefined,
279 nullColor
: undefined,
283 chartRangeMax
: undefined,
284 chartRangeMin
: undefined,
285 chartRangeClip
: false,
287 tooltipFormat
: new SPFormat('<span style="color: {{color}}">●</span> {{prefix}}{{value}}{{suffix}}')
289 // Defaults for tristate charts
295 zeroBarColor
: '#999',
297 tooltipFormat
: new SPFormat('<span style="color: {{color}}">●</span> {{value:map}}'),
298 tooltipValueLookups
: { map
: { '-1': 'Loss', '0': 'Draw', '1': 'Win' } }
300 // Defaults for discrete charts
303 thresholdColor
: undefined,
305 chartRangeMax
: undefined,
306 chartRangeMin
: undefined,
307 chartRangeClip
: false,
308 tooltipFormat
: new SPFormat('{{prefix}}{{value}}{{suffix}}')
310 // Defaults for bullet charts
313 targetWidth
: 3, // width of the target bar in pixels
314 performanceColor
: '#33f',
315 rangeColors
: ['#d3dafe', '#a8b6ff', '#7f94ff'],
316 base
: undefined, // set this to a number to change the base start number
317 tooltipFormat
: new SPFormat('{{fieldkey:fields}} - {{value}}'),
318 tooltipValueLookups
: { fields
: {r
: 'Range', p
: 'Performance', t
: 'Target'} }
320 // Defaults for pie charts
323 sliceColors
: ['#3366cc', '#dc3912', '#ff9900', '#109618', '#66aa00',
324 '#dd4477', '#0099c6', '#990099'],
327 tooltipFormat
: new SPFormat('<span style="color: {{color}}">●</span> {{value}} ({{percent.1}}%)')
329 // Defaults for box plots
332 boxLineColor
: '#000',
333 boxFillColor
: '#cdf',
334 whiskerColor
: '#000',
335 outlierLineColor
: '#333',
336 outlierFillColor
: '#fff',
343 chartRangeMax
: undefined,
344 chartRangeMin
: undefined,
345 tooltipFormat
: new SPFormat('{{field:fields}}: {{value}}'),
346 tooltipFormatFieldlistKey
: 'field',
347 tooltipValueLookups
: { fields
: { lq
: 'Lower Quartile', med
: 'Median',
348 uq
: 'Upper Quartile', lo
: 'Left Outlier', ro
: 'Right Outlier',
349 lw
: 'Left Whisker', rw
: 'Right Whisker'} }
354 // You can have tooltips use a css class other than jqstooltip by specifying tooltipClassname
355 defaultStyles
= '.jqstooltip { ' +
356 'position: absolute;' +
359 'visibility: hidden;' +
360 'background: rgb(0, 0, 0) transparent;' +
361 'background-color: rgba(0,0,0,0.6);' +
362 'filter:progid:DXImageTransform.Microsoft.gradient(startColorstr=#99000000, endColorstr=#99000000);' +
363 '-ms-filter: "progid:DXImageTransform.Microsoft.gradient(startColorstr=#99000000, endColorstr=#99000000)";' +
365 'font: 10px arial, san serif;' +
366 'text-align: left;' +
367 'white-space: nowrap;' +
369 'border: 1px solid white;' +
374 'font: 10px arial, san serif;' +
375 'text-align: left;' +
382 createClass = function (/* [baseclass, [mixin, ...]], definition */) {
384 Class = function () {
385 this.init
.apply(this, arguments
);
387 if (arguments
.length
> 1) {
389 Class
.prototype = $.extend(new arguments
[0](), arguments
[arguments
.length
- 1]);
390 Class
._super
= arguments
[0].prototype;
392 Class
.prototype = arguments
[arguments
.length
- 1];
394 if (arguments
.length
> 2) {
395 args
= Array
.prototype.slice
.call(arguments
, 1, -1);
396 args
.unshift(Class
.prototype);
397 $.extend
.apply($, args
);
400 Class
.prototype = arguments
[0];
402 Class
.prototype.cls
= Class
;
407 * Wraps a format string for tooltips
412 $.SPFormatClass
= SPFormat
= createClass({
413 fre
: /\{\{([\w.]+?)(:(.+?))?\}\}/g,
414 precre
: /(\w+)\.(\d+)/,
416 init: function (format
, fclass
) {
417 this.format
= format
;
418 this.fclass
= fclass
;
421 render: function (fieldset
, lookups
, options
) {
424 match
, token
, lookupkey
, fieldvalue
, prec
;
425 return this.format
.replace(this.fre
, function () {
427 token
= arguments
[1];
428 lookupkey
= arguments
[3];
429 match
= self
.precre
.exec(token
);
436 fieldvalue
= fields
[token
];
437 if (fieldvalue
=== undefined) {
440 if (lookupkey
&& lookups
&& lookups
[lookupkey
]) {
441 lookup
= lookups
[lookupkey
];
442 if (lookup
.get) { // RangeMap
443 return lookups
[lookupkey
].get(fieldvalue
) || fieldvalue
;
445 return lookups
[lookupkey
][fieldvalue
] || fieldvalue
;
448 if (isNumber(fieldvalue
)) {
449 if (options
.get('numberFormatter')) {
450 fieldvalue
= options
.get('numberFormatter')(fieldvalue
);
452 fieldvalue
= formatNumber(fieldvalue
, prec
,
453 options
.get('numberDigitGroupCount'),
454 options
.get('numberDigitGroupSep'),
455 options
.get('numberDecimalMark'));
463 // convience method to avoid needing the new operator
464 $.spformat = function(format
, fclass
) {
465 return new SPFormat(format
, fclass
);
468 clipval = function (val
, min
, max
) {
478 quartile = function (values
, q
) {
481 vl
= Math
.floor(values
.length
/ 2);
482 return values
.length
% 2 ? values
[vl
] : (values
[vl
-1] + values
[vl
]) / 2;
484 if (values
.length
% 2 ) { // odd
485 vl
= (values
.length
* q
+ q
) / 4;
486 return vl
% 1 ? (values
[Math
.floor(vl
)] + values
[Math
.floor(vl
) - 1]) / 2 : values
[vl
-1];
488 vl
= (values
.length
* q
+ 2) / 4;
489 return vl
% 1 ? (values
[Math
.floor(vl
)] + values
[Math
.floor(vl
) - 1]) / 2 : values
[vl
-1];
495 normalizeValue = function (val
) {
511 nf
= parseFloat(val
);
519 normalizeValues = function (vals
) {
521 for (i
= vals
.length
; i
--;) {
522 result
[i
] = normalizeValue(vals
[i
]);
527 remove = function (vals
, filter
) {
528 var i
, vl
, result
= [];
529 for (i
= 0, vl
= vals
.length
; i
< vl
; i
++) {
530 if (vals
[i
] !== filter
) {
531 result
.push(vals
[i
]);
537 isNumber = function (num
) {
538 return !isNaN(parseFloat(num
)) && isFinite(num
);
541 formatNumber = function (num
, prec
, groupsize
, groupsep
, decsep
) {
543 num
= (prec
=== false ? parseFloat(num
).toString() : num
.toFixed(prec
)).split('');
544 p
= (p
= $.inArray('.', num
)) < 0 ? num
.length
: p
;
545 if (p
< num
.length
) {
548 for (i
= p
- groupsize
; i
> 0; i
-= groupsize
) {
549 num
.splice(i
, 0, groupsep
);
554 // determine if all values of an array match a value
555 // returns true if the array is empty
556 all = function (val
, arr
, ignoreNull
) {
558 for (i
= arr
.length
; i
--; ) {
559 if (ignoreNull
&& arr
[i
] === null) continue;
560 if (arr
[i
] !== val
) {
567 // sums the numeric values in an array, ignoring other values
568 sum = function (vals
) {
570 for (i
= vals
.length
; i
--;) {
571 total
+= typeof vals
[i
] === 'number' ? vals
[i
] : 0;
576 ensureArray = function (val
) {
577 return $.isArray(val
) ? val
: [val
];
580 // http://paulirish.com/2008/bookmarklet-inject-new-css-rules/
581 addCSS = function(css
) {
583 //if ('\v' == 'v') /* ie only */ {
584 if (document
.createStyleSheet
) {
585 document
.createStyleSheet().cssText
= css
;
587 tag
= document
.createElement('style');
588 tag
.type
= 'text/css';
589 document
.getElementsByTagName('head')[0].appendChild(tag
);
590 tag
[(typeof document
.body
.style
.WebkitAppearance
== 'string') /* webkit only */ ? 'innerText' : 'innerHTML'] = css
;
594 // Provide a cross-browser interface to a few simple drawing primitives
595 $.fn
.simpledraw = function (width
, height
, useExisting
, interact
) {
596 var target
, mhandler
;
597 if (useExisting
&& (target
= this.data('_jqs_vcanvas'))) {
601 if ($.fn
.sparkline
.canvas
=== false) {
602 // We've already determined that neither Canvas nor VML are available
605 } else if ($.fn
.sparkline
.canvas
=== undefined) {
606 // No function defined yet -- need to see if we support Canvas or VML
607 var el
= document
.createElement('canvas');
608 if (!!(el
.getContext
&& el
.getContext('2d'))) {
609 // Canvas is available
610 $.fn
.sparkline
.canvas = function(width
, height
, target
, interact
) {
611 return new VCanvas_canvas(width
, height
, target
, interact
);
613 } else if (document
.namespaces
&& !document
.namespaces
.v
) {
615 document
.namespaces
.add('v', 'urn:schemas-microsoft-com:vml', '#default#VML');
616 $.fn
.sparkline
.canvas = function(width
, height
, target
, interact
) {
617 return new VCanvas_vml(width
, height
, target
);
620 // Neither Canvas nor VML are available
621 $.fn
.sparkline
.canvas
= false;
626 if (width
=== undefined) {
627 width
= $(this).innerWidth();
629 if (height
=== undefined) {
630 height
= $(this).innerHeight();
633 target
= $.fn
.sparkline
.canvas(width
, height
, this, interact
);
635 mhandler
= $(this).data('_jqs_mhandler');
637 mhandler
.registerCanvas(target
);
642 $.fn
.cleardraw = function () {
643 var target
= this.data('_jqs_vcanvas');
649 $.RangeMapClass
= RangeMap
= createClass({
650 init: function (map
) {
651 var key
, range
, rangelist
= [];
653 if (map
.hasOwnProperty(key
) && typeof key
=== 'string' && key
.indexOf(':') > -1) {
654 range
= key
.split(':');
655 range
[0] = range
[0].length
=== 0 ? -Infinity
: parseFloat(range
[0]);
656 range
[1] = range
[1].length
=== 0 ? Infinity
: parseFloat(range
[1]);
658 rangelist
.push(range
);
662 this.rangelist
= rangelist
|| false;
665 get: function (value
) {
666 var rangelist
= this.rangelist
,
668 if ((result
= this.map
[value
]) !== undefined) {
672 for (i
= rangelist
.length
; i
--;) {
673 range
= rangelist
[i
];
674 if (range
[0] <= value
&& range
[1] >= value
) {
683 // Convenience function
684 $.range_map = function(map
) {
685 return new RangeMap(map
);
688 MouseHandler
= createClass({
689 init: function (el
, options
) {
692 this.options
= options
;
693 this.currentPageX
= 0;
694 this.currentPageY
= 0;
699 this.displayTooltips
= !options
.get('disableTooltips');
700 this.highlightEnabled
= !options
.get('disableHighlight');
703 registerSparkline: function (sp
) {
704 this.splist
.push(sp
);
706 this.updateDisplay();
710 registerCanvas: function (canvas
) {
711 var $canvas
= $(canvas
.canvas
);
712 this.canvas
= canvas
;
713 this.$canvas
= $canvas
;
714 $canvas
.mouseenter($.proxy(this.mouseenter
, this));
715 $canvas
.mouseleave($.proxy(this.mouseleave
, this));
716 $canvas
.click($.proxy(this.mouseclick
, this));
719 reset: function (removeTooltip
) {
721 if (this.tooltip
&& removeTooltip
) {
722 this.tooltip
.remove();
723 this.tooltip
= undefined;
727 mouseclick: function (e
) {
728 var clickEvent
= $.Event('sparklineClick');
729 clickEvent
.originalEvent
= e
;
730 clickEvent
.sparklines
= this.splist
;
731 this.$el
.trigger(clickEvent
);
734 mouseenter: function (e
) {
735 $(document
.body
).unbind('mousemove.jqs');
736 $(document
.body
).bind('mousemove.jqs', $.proxy(this.mousemove
, this));
738 this.currentPageX
= e
.pageX
;
739 this.currentPageY
= e
.pageY
;
740 this.currentEl
= e
.target
;
741 if (!this.tooltip
&& this.displayTooltips
) {
742 this.tooltip
= new Tooltip(this.options
);
743 this.tooltip
.updatePosition(e
.pageX
, e
.pageY
);
745 this.updateDisplay();
748 mouseleave: function () {
749 $(document
.body
).unbind('mousemove.jqs');
750 var splist
= this.splist
,
751 spcount
= splist
.length
,
752 needsRefresh
= false,
755 this.currentEl
= null;
758 this.tooltip
.remove();
762 for (i
= 0; i
< spcount
; i
++) {
764 if (sp
.clearRegionHighlight()) {
770 this.canvas
.render();
774 mousemove: function (e
) {
775 this.currentPageX
= e
.pageX
;
776 this.currentPageY
= e
.pageY
;
777 this.currentEl
= e
.target
;
779 this.tooltip
.updatePosition(e
.pageX
, e
.pageY
);
781 this.updateDisplay();
784 updateDisplay: function () {
785 var splist
= this.splist
,
786 spcount
= splist
.length
,
787 needsRefresh
= false,
788 offset
= this.$canvas
.offset(),
789 localX
= this.currentPageX
- offset
.left
,
790 localY
= this.currentPageY
- offset
.top
,
791 tooltiphtml
, sp
, i
, result
, changeEvent
;
795 for (i
= 0; i
< spcount
; i
++) {
797 result
= sp
.setRegionHighlight(this.currentEl
, localX
, localY
);
803 changeEvent
= $.Event('sparklineRegionChange');
804 changeEvent
.sparklines
= this.splist
;
805 this.$el
.trigger(changeEvent
);
808 for (i
= 0; i
< spcount
; i
++) {
810 tooltiphtml
+= sp
.getCurrentRegionTooltip();
812 this.tooltip
.setContent(tooltiphtml
);
814 if (!this.disableHighlight
) {
815 this.canvas
.render();
818 if (result
=== null) {
825 Tooltip
= createClass({
826 sizeStyle
: 'position: static !important;' +
827 'display: block !important;' +
828 'visibility: hidden !important;' +
829 'float: left !important;',
831 init: function (options
) {
832 var tooltipClassname
= options
.get('tooltipClassname', 'jqstooltip'),
833 sizetipStyle
= this.sizeStyle
,
835 this.container
= options
.get('tooltipContainer') || document
.body
;
836 this.tooltipOffsetX
= options
.get('tooltipOffsetX', 10);
837 this.tooltipOffsetY
= options
.get('tooltipOffsetY', 12);
838 // remove any previous lingering tooltip
839 $('#jqssizetip').remove();
840 $('#jqstooltip').remove();
841 this.sizetip
= $('<div/>', {
844 'class': tooltipClassname
846 this.tooltip
= $('<div/>', {
848 'class': tooltipClassname
849 }).appendTo(this.container
);
850 // account for the container's location
851 offset
= this.tooltip
.offset();
852 this.offsetLeft
= offset
.left
;
853 this.offsetTop
= offset
.top
;
855 $(window
).unbind('resize.jqs scroll.jqs');
856 $(window
).bind('resize.jqs scroll.jqs', $.proxy(this.updateWindowDims
, this));
857 this.updateWindowDims();
860 updateWindowDims: function () {
861 this.scrollTop
= $(window
).scrollTop();
862 this.scrollLeft
= $(window
).scrollLeft();
863 this.scrollRight
= this.scrollLeft
+ $(window
).width();
864 this.updatePosition();
867 getSize: function (content
) {
868 this.sizetip
.html(content
).appendTo(this.container
);
869 this.width
= this.sizetip
.width() + 1;
870 this.height
= this.sizetip
.height();
871 this.sizetip
.remove();
874 setContent: function (content
) {
876 this.tooltip
.css('visibility', 'hidden');
880 this.getSize(content
);
881 this.tooltip
.html(content
)
884 'height': this.height
,
885 'visibility': 'visible'
889 this.updatePosition();
893 updatePosition: function (x
, y
) {
894 if (x
=== undefined) {
895 if (this.mousex
=== undefined) {
898 x
= this.mousex
- this.offsetLeft
;
899 y
= this.mousey
- this.offsetTop
;
902 this.mousex
= x
= x
- this.offsetLeft
;
903 this.mousey
= y
= y
- this.offsetTop
;
905 if (!this.height
|| !this.width
|| this.hidden
) {
909 y
-= this.height
+ this.tooltipOffsetY
;
910 x
+= this.tooltipOffsetX
;
912 if (y
< this.scrollTop
) {
915 if (x
< this.scrollLeft
) {
917 } else if (x
+ this.width
> this.scrollRight
) {
918 x
= this.scrollRight
- this.width
;
927 remove: function () {
928 this.tooltip
.remove();
929 this.sizetip
.remove();
930 this.sizetip
= this.tooltip
= undefined;
931 $(window
).unbind('resize.jqs scroll.jqs');
935 initStyles = function() {
936 addCSS(defaultStyles
);
942 $.fn
.sparkline = function (userValues
, userOptions
) {
943 return this.each(function () {
944 var options
= new $.fn
.sparkline
.options(this, userOptions
),
947 render = function () {
948 var values
, width
, height
, tmp
, mhandler
, sp
, vals
;
949 if (userValues
=== 'html' || userValues
=== undefined) {
950 vals
= this.getAttribute(options
.get('tagValuesAttribute'));
951 if (vals
=== undefined || vals
=== null) {
954 values
= vals
.replace(/(^\s*<!--)|(-->\s*$)|\s+/g, '').split(',');
959 width
= options
.get('width') === 'auto' ? values
.length
* options
.get('defaultPixelsPerValue') : options
.get('width');
960 if (options
.get('height') === 'auto') {
961 if (!options
.get('composite') || !$.data(this, '_jqs_vcanvas')) {
962 // must be a better way to get the line height
963 tmp
= document
.createElement('span');
966 height
= $(tmp
).innerHeight() || $(tmp
).height();
971 height
= options
.get('height');
974 if (!options
.get('disableInteraction')) {
975 mhandler
= $.data(this, '_jqs_mhandler');
977 mhandler
= new MouseHandler(this, options
);
978 $.data(this, '_jqs_mhandler', mhandler
);
979 } else if (!options
.get('composite')) {
986 if (options
.get('composite') && !$.data(this, '_jqs_vcanvas')) {
987 if (!$.data(this, '_jqs_errnotify')) {
988 alert('Attempted to attach a composite sparkline to an element with no existing sparkline');
989 $.data(this, '_jqs_errnotify', true);
994 sp
= new $.fn
.sparkline
[options
.get('type')](this, values
, options
, width
, height
);
999 mhandler
.registerSparkline(sp
);
1002 if (($(this).html() && !options
.get('disableHiddenCheck') && $(this).is(':hidden')) || !$(this).parents('body').length
) {
1003 if (!options
.get('composite') && $.data(this, '_jqs_pending')) {
1004 // remove any existing references to the element
1005 for (i
= pending
.length
; i
; i
--) {
1006 if (pending
[i
- 1][0] == this) {
1007 pending
.splice(i
- 1, 1);
1011 pending
.push([this, render
]);
1012 $.data(this, '_jqs_pending', true);
1019 $.fn
.sparkline
.defaults
= getDefaults();
1022 $.sparkline_display_visible = function () {
1025 for (i
= 0, pl
= pending
.length
; i
< pl
; i
++) {
1027 if ($(el
).is(':visible') && !$(el
).parents().is(':hidden')) {
1028 pending
[i
][1].call(el
);
1029 $.data(pending
[i
][0], '_jqs_pending', false);
1031 } else if (!$(el
).closest('html').length
&& !$.data(el
, '_jqs_pending')) {
1032 // element has been inserted and removed from the DOM
1033 // If it was not yet inserted into the dom then the .data request
1034 // will return true.
1035 // removing from the dom causes the data to be removed.
1036 $.data(pending
[i
][0], '_jqs_pending', false);
1040 for (i
= done
.length
; i
; i
--) {
1041 pending
.splice(done
[i
- 1], 1);
1047 * User option handler
1049 $.fn
.sparkline
.options
= createClass({
1050 init: function (tag
, userOptions
) {
1051 var extendedOptions
, defaults
, base
, tagOptionType
;
1052 this.userOptions
= userOptions
= userOptions
|| {};
1054 this.tagValCache
= {};
1055 defaults
= $.fn
.sparkline
.defaults
;
1056 base
= defaults
.common
;
1057 this.tagOptionsPrefix
= userOptions
.enableTagOptions
&& (userOptions
.tagOptionsPrefix
|| base
.tagOptionsPrefix
);
1059 tagOptionType
= this.getTagSetting('type');
1060 if (tagOptionType
=== UNSET_OPTION
) {
1061 extendedOptions
= defaults
[userOptions
.type
|| base
.type
];
1063 extendedOptions
= defaults
[tagOptionType
];
1065 this.mergedOptions
= $.extend({}, base
, extendedOptions
, userOptions
);
1069 getTagSetting: function (key
) {
1070 var prefix
= this.tagOptionsPrefix
,
1071 val
, i
, pairs
, keyval
;
1072 if (prefix
=== false || prefix
=== undefined) {
1073 return UNSET_OPTION
;
1075 if (this.tagValCache
.hasOwnProperty(key
)) {
1076 val
= this.tagValCache
.key
;
1078 val
= this.tag
.getAttribute(prefix
+ key
);
1079 if (val
=== undefined || val
=== null) {
1081 } else if (val
.substr(0, 1) === '[') {
1082 val
= val
.substr(1, val
.length
- 2).split(',');
1083 for (i
= val
.length
; i
--;) {
1084 val
[i
] = normalizeValue(val
[i
].replace(/(^\s*)|(\s*$)/g, ''));
1086 } else if (val
.substr(0, 1) === '{') {
1087 pairs
= val
.substr(1, val
.length
- 2).split(',');
1089 for (i
= pairs
.length
; i
--;) {
1090 keyval
= pairs
[i
].split(':', 2);
1091 val
[keyval
[0].replace(/(^\s*)|(\s*$)/g, '')] = normalizeValue(keyval
[1].replace(/(^\s*)|(\s*$)/g, ''));
1094 val
= normalizeValue(val
);
1096 this.tagValCache
.key
= val
;
1101 get: function (key
, defaultval
) {
1102 var tagOption
= this.getTagSetting(key
),
1104 if (tagOption
!== UNSET_OPTION
) {
1107 return (result
= this.mergedOptions
[key
]) === undefined ? defaultval
: result
;
1112 $.fn
.sparkline
._base
= createClass({
1115 init: function (el
, values
, options
, width
, height
) {
1118 this.values
= values
;
1119 this.options
= options
;
1121 this.height
= height
;
1122 this.currentRegion
= undefined;
1128 initTarget: function () {
1129 var interactive
= !this.options
.get('disableInteraction');
1130 if (!(this.target
= this.$el
.simpledraw(this.width
, this.height
, this.options
.get('composite'), interactive
))) {
1131 this.disabled
= true;
1133 this.canvasWidth
= this.target
.pixelWidth
;
1134 this.canvasHeight
= this.target
.pixelHeight
;
1139 * Actually render the chart to the canvas
1141 render: function () {
1142 if (this.disabled
) {
1143 this.el
.innerHTML
= '';
1150 * Return a region id for a given x/y co-ordinate
1152 getRegion: function (x
, y
) {
1156 * Highlight an item based on the moused-over x,y co-ordinate
1158 setRegionHighlight: function (el
, x
, y
) {
1159 var currentRegion
= this.currentRegion
,
1160 highlightEnabled
= !this.options
.get('disableHighlight'),
1162 if (x
> this.canvasWidth
|| y
> this.canvasHeight
|| x
< 0 || y
< 0) {
1165 newRegion
= this.getRegion(el
, x
, y
);
1166 if (currentRegion
!== newRegion
) {
1167 if (currentRegion
!== undefined && highlightEnabled
) {
1168 this.removeHighlight();
1170 this.currentRegion
= newRegion
;
1171 if (newRegion
!== undefined && highlightEnabled
) {
1172 this.renderHighlight();
1180 * Reset any currently highlighted item
1182 clearRegionHighlight: function () {
1183 if (this.currentRegion
!== undefined) {
1184 this.removeHighlight();
1185 this.currentRegion
= undefined;
1191 renderHighlight: function () {
1192 this.changeHighlight(true);
1195 removeHighlight: function () {
1196 this.changeHighlight(false);
1199 changeHighlight: function (highlight
) {},
1202 * Fetch the HTML to display as a tooltip
1204 getCurrentRegionTooltip: function () {
1205 var options
= this.options
,
1208 fields
, formats
, formatlen
, fclass
, text
, i
,
1209 showFields
, showFieldsKey
, newFields
, fv
,
1210 formatter
, format
, fieldlen
, j
;
1211 if (this.currentRegion
=== undefined) {
1214 fields
= this.getCurrentRegionFields();
1215 formatter
= options
.get('tooltipFormatter');
1217 return formatter(this, options
, fields
);
1219 if (options
.get('tooltipChartTitle')) {
1220 header
+= '<div class="jqs jqstitle">' + options
.get('tooltipChartTitle') + '</div>\n';
1222 formats
= this.options
.get('tooltipFormat');
1226 if (!$.isArray(formats
)) {
1227 formats
= [formats
];
1229 if (!$.isArray(fields
)) {
1232 showFields
= this.options
.get('tooltipFormatFieldlist');
1233 showFieldsKey
= this.options
.get('tooltipFormatFieldlistKey');
1234 if (showFields
&& showFieldsKey
) {
1235 // user-selected ordering of fields
1237 for (i
= fields
.length
; i
--;) {
1238 fv
= fields
[i
][showFieldsKey
];
1239 if ((j
= $.inArray(fv
, showFields
)) != -1) {
1240 newFields
[j
] = fields
[i
];
1245 formatlen
= formats
.length
;
1246 fieldlen
= fields
.length
;
1247 for (i
= 0; i
< formatlen
; i
++) {
1248 format
= formats
[i
];
1249 if (typeof format
=== 'string') {
1250 format
= new SPFormat(format
);
1252 fclass
= format
.fclass
|| 'jqsfield';
1253 for (j
= 0; j
< fieldlen
; j
++) {
1254 if (!fields
[j
].isNull
|| !options
.get('tooltipSkipNull')) {
1255 $.extend(fields
[j
], {
1256 prefix
: options
.get('tooltipPrefix'),
1257 suffix
: options
.get('tooltipSuffix')
1259 text
= format
.render(fields
[j
], options
.get('tooltipValueLookups'), options
);
1260 entries
.push('<div class="' + fclass
+ '">' + text
+ '</div>');
1264 if (entries
.length
) {
1265 return header
+ entries
.join('\n');
1270 getCurrentRegionFields: function () {},
1272 calcHighlightColor: function (color
, options
) {
1273 var highlightColor
= options
.get('highlightColor'),
1274 lighten
= options
.get('highlightLighten'),
1275 parse
, mult
, rgbnew
, i
;
1276 if (highlightColor
) {
1277 return highlightColor
;
1280 // extract RGB values
1281 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
);
1284 mult
= color
.length
=== 4 ? 16 : 1;
1285 for (i
= 0; i
< 3; i
++) {
1286 rgbnew
[i
] = clipval(Math
.round(parseInt(parse
[i
+ 1], 16) * mult
* lighten
), 0, 255);
1288 return 'rgb(' + rgbnew
.join(',') + ')';
1297 barHighlightMixin
= {
1298 changeHighlight: function (highlight
) {
1299 var currentRegion
= this.currentRegion
,
1300 target
= this.target
,
1301 shapeids
= this.regionShapes
[currentRegion
],
1303 // will be null if the region value was null
1305 newShapes
= this.renderRegion(currentRegion
, highlight
);
1306 if ($.isArray(newShapes
) || $.isArray(shapeids
)) {
1307 target
.replaceWithShapes(shapeids
, newShapes
);
1308 this.regionShapes
[currentRegion
] = $.map(newShapes
, function (newShape
) {
1312 target
.replaceWithShape(shapeids
, newShapes
);
1313 this.regionShapes
[currentRegion
] = newShapes
.id
;
1318 render: function () {
1319 var values
= this.values
,
1320 target
= this.target
,
1321 regionShapes
= this.regionShapes
,
1324 if (!this.cls
._super
.render
.call(this)) {
1327 for (i
= values
.length
; i
--;) {
1328 shapes
= this.renderRegion(i
);
1330 if ($.isArray(shapes
)) {
1332 for (j
= shapes
.length
; j
--;) {
1334 ids
.push(shapes
[j
].id
);
1336 regionShapes
[i
] = ids
;
1339 regionShapes
[i
] = shapes
.id
; // store just the shapeid
1343 regionShapes
[i
] = null;
1353 $.fn
.sparkline
.line
= line
= createClass($.fn
.sparkline
._base
, {
1356 init: function (el
, values
, options
, width
, height
) {
1357 line
._super
.init
.call(this, el
, values
, options
, width
, height
);
1359 this.regionMap
= [];
1363 this.hightlightSpotId
= null;
1364 this.lastShapeId
= null;
1368 getRegion: function (el
, x
, y
) {
1370 regionMap
= this.regionMap
; // maps regions to value positions
1371 for (i
= regionMap
.length
; i
--;) {
1372 if (regionMap
[i
] !== null && x
>= regionMap
[i
][0] && x
<= regionMap
[i
][1]) {
1373 return regionMap
[i
][2];
1379 getCurrentRegionFields: function () {
1380 var currentRegion
= this.currentRegion
;
1382 isNull
: this.yvalues
[currentRegion
] === null,
1383 x
: this.xvalues
[currentRegion
],
1384 y
: this.yvalues
[currentRegion
],
1385 color
: this.options
.get('lineColor'),
1386 fillColor
: this.options
.get('fillColor'),
1387 offset
: currentRegion
1391 renderHighlight: function () {
1392 var currentRegion
= this.currentRegion
,
1393 target
= this.target
,
1394 vertex
= this.vertices
[currentRegion
],
1395 options
= this.options
,
1396 spotRadius
= options
.get('spotRadius'),
1397 highlightSpotColor
= options
.get('highlightSpotColor'),
1398 highlightLineColor
= options
.get('highlightLineColor'),
1399 highlightSpot
, highlightLine
;
1404 if (spotRadius
&& highlightSpotColor
) {
1405 highlightSpot
= target
.drawCircle(vertex
[0], vertex
[1],
1406 spotRadius
, undefined, highlightSpotColor
);
1407 this.highlightSpotId
= highlightSpot
.id
;
1408 target
.insertAfterShape(this.lastShapeId
, highlightSpot
);
1410 if (highlightLineColor
) {
1411 highlightLine
= target
.drawLine(vertex
[0], this.canvasTop
, vertex
[0],
1412 this.canvasTop
+ this.canvasHeight
, highlightLineColor
);
1413 this.highlightLineId
= highlightLine
.id
;
1414 target
.insertAfterShape(this.lastShapeId
, highlightLine
);
1418 removeHighlight: function () {
1419 var target
= this.target
;
1420 if (this.highlightSpotId
) {
1421 target
.removeShapeId(this.highlightSpotId
);
1422 this.highlightSpotId
= null;
1424 if (this.highlightLineId
) {
1425 target
.removeShapeId(this.highlightLineId
);
1426 this.highlightLineId
= null;
1430 scanValues: function () {
1431 var values
= this.values
,
1432 valcount
= values
.length
,
1433 xvalues
= this.xvalues
,
1434 yvalues
= this.yvalues
,
1435 yminmax
= this.yminmax
,
1436 i
, val
, isStr
, isArray
, sp
;
1437 for (i
= 0; i
< valcount
; i
++) {
1439 isStr
= typeof(values
[i
]) === 'string';
1440 isArray
= typeof(values
[i
]) === 'object' && values
[i
] instanceof Array
;
1441 sp
= isStr
&& values
[i
].split(':');
1442 if (isStr
&& sp
.length
=== 2) { // x:y
1443 xvalues
.push(Number(sp
[0]));
1444 yvalues
.push(Number(sp
[1]));
1445 yminmax
.push(Number(sp
[1]));
1446 } else if (isArray
) {
1447 xvalues
.push(val
[0]);
1448 yvalues
.push(val
[1]);
1449 yminmax
.push(val
[1]);
1452 if (values
[i
] === null || values
[i
] === 'null') {
1455 yvalues
.push(Number(val
));
1456 yminmax
.push(Number(val
));
1460 if (this.options
.get('xvalues')) {
1461 xvalues
= this.options
.get('xvalues');
1464 this.maxy
= this.maxyorg
= Math
.max
.apply(Math
, yminmax
);
1465 this.miny
= this.minyorg
= Math
.min
.apply(Math
, yminmax
);
1467 this.maxx
= Math
.max
.apply(Math
, xvalues
);
1468 this.minx
= Math
.min
.apply(Math
, xvalues
);
1470 this.xvalues
= xvalues
;
1471 this.yvalues
= yvalues
;
1472 this.yminmax
= yminmax
;
1476 processRangeOptions: function () {
1477 var options
= this.options
,
1478 normalRangeMin
= options
.get('normalRangeMin'),
1479 normalRangeMax
= options
.get('normalRangeMax');
1481 if (normalRangeMin
!== undefined) {
1482 if (normalRangeMin
< this.miny
) {
1483 this.miny
= normalRangeMin
;
1485 if (normalRangeMax
> this.maxy
) {
1486 this.maxy
= normalRangeMax
;
1489 if (options
.get('chartRangeMin') !== undefined && (options
.get('chartRangeClip') || options
.get('chartRangeMin') < this.miny
)) {
1490 this.miny
= options
.get('chartRangeMin');
1492 if (options
.get('chartRangeMax') !== undefined && (options
.get('chartRangeClip') || options
.get('chartRangeMax') > this.maxy
)) {
1493 this.maxy
= options
.get('chartRangeMax');
1495 if (options
.get('chartRangeMinX') !== undefined && (options
.get('chartRangeClipX') || options
.get('chartRangeMinX') < this.minx
)) {
1496 this.minx
= options
.get('chartRangeMinX');
1498 if (options
.get('chartRangeMaxX') !== undefined && (options
.get('chartRangeClipX') || options
.get('chartRangeMaxX') > this.maxx
)) {
1499 this.maxx
= options
.get('chartRangeMaxX');
1504 drawNormalRange: function (canvasLeft
, canvasTop
, canvasHeight
, canvasWidth
, rangey
) {
1505 var normalRangeMin
= this.options
.get('normalRangeMin'),
1506 normalRangeMax
= this.options
.get('normalRangeMax'),
1507 ytop
= canvasTop
+ Math
.round(canvasHeight
- (canvasHeight
* ((normalRangeMax
- this.miny
) / rangey
))),
1508 height
= Math
.round((canvasHeight
* (normalRangeMax
- normalRangeMin
)) / rangey
);
1509 this.target
.drawRect(canvasLeft
, ytop
, canvasWidth
, height
, undefined, this.options
.get('normalRangeColor')).append();
1512 render: function () {
1513 var options
= this.options
,
1514 target
= this.target
,
1515 canvasWidth
= this.canvasWidth
,
1516 canvasHeight
= this.canvasHeight
,
1517 vertices
= this.vertices
,
1518 spotRadius
= options
.get('spotRadius'),
1519 regionMap
= this.regionMap
,
1520 rangex
, rangey
, yvallast
,
1521 canvasTop
, canvasLeft
,
1522 vertex
, path
, paths
, x
, y
, xnext
, xpos
, xposnext
,
1523 last
, next
, yvalcount
, lineShapes
, fillShapes
, plen
,
1524 valueSpots
, hlSpotsEnabled
, color
, xvalues
, yvalues
, i
;
1526 if (!line
._super
.render
.call(this)) {
1531 this.processRangeOptions();
1533 xvalues
= this.xvalues
;
1534 yvalues
= this.yvalues
;
1536 if (!this.yminmax
.length
|| this.yvalues
.length
< 2) {
1537 // empty or all null valuess
1541 canvasTop
= canvasLeft
= 0;
1543 rangex
= this.maxx
- this.minx
=== 0 ? 1 : this.maxx
- this.minx
;
1544 rangey
= this.maxy
- this.miny
=== 0 ? 1 : this.maxy
- this.miny
;
1545 yvallast
= this.yvalues
.length
- 1;
1547 if (spotRadius
&& (canvasWidth
< (spotRadius
* 4) || canvasHeight
< (spotRadius
* 4))) {
1551 // adjust the canvas size as required so that spots will fit
1552 hlSpotsEnabled
= options
.get('highlightSpotColor') && !options
.get('disableInteraction');
1553 if (hlSpotsEnabled
|| options
.get('minSpotColor') || (options
.get('spotColor') && yvalues
[yvallast
] === this.miny
)) {
1554 canvasHeight
-= Math
.ceil(spotRadius
);
1556 if (hlSpotsEnabled
|| options
.get('maxSpotColor') || (options
.get('spotColor') && yvalues
[yvallast
] === this.maxy
)) {
1557 canvasHeight
-= Math
.ceil(spotRadius
);
1558 canvasTop
+= Math
.ceil(spotRadius
);
1560 if (hlSpotsEnabled
||
1561 ((options
.get('minSpotColor') || options
.get('maxSpotColor')) && (yvalues
[0] === this.miny
|| yvalues
[0] === this.maxy
))) {
1562 canvasLeft
+= Math
.ceil(spotRadius
);
1563 canvasWidth
-= Math
.ceil(spotRadius
);
1565 if (hlSpotsEnabled
|| options
.get('spotColor') ||
1566 (options
.get('minSpotColor') || options
.get('maxSpotColor') &&
1567 (yvalues
[yvallast
] === this.miny
|| yvalues
[yvallast
] === this.maxy
))) {
1568 canvasWidth
-= Math
.ceil(spotRadius
);
1575 if (options
.get('normalRangeMin') !== undefined && !options
.get('drawNormalOnTop')) {
1576 this.drawNormalRange(canvasLeft
, canvasTop
, canvasHeight
, canvasWidth
, rangey
);
1582 yvalcount
= yvalues
.length
;
1583 for (i
= 0; i
< yvalcount
; i
++) {
1585 xnext
= xvalues
[i
+ 1];
1587 xpos
= canvasLeft
+ Math
.round((x
- this.minx
) * (canvasWidth
/ rangex
));
1588 xposnext
= i
< yvalcount
- 1 ? canvasLeft
+ Math
.round((xnext
- this.minx
) * (canvasWidth
/ rangex
)) : canvasWidth
;
1589 next
= xpos
+ ((xposnext
- xpos
) / 2);
1590 regionMap
[i
] = [last
|| 0, next
, i
];
1594 if (yvalues
[i
- 1] !== null) {
1598 vertices
.push(null);
1601 if (y
< this.miny
) {
1604 if (y
> this.maxy
) {
1608 // previous value was null
1609 path
.push([xpos
, canvasTop
+ canvasHeight
]);
1611 vertex
= [xpos
, canvasTop
+ Math
.round(canvasHeight
- (canvasHeight
* ((y
- this.miny
) / rangey
)))];
1613 vertices
.push(vertex
);
1619 plen
= paths
.length
;
1620 for (i
= 0; i
< plen
; i
++) {
1623 if (options
.get('fillColor')) {
1624 path
.push([path
[path
.length
- 1][0], (canvasTop
+ canvasHeight
)]);
1625 fillShapes
.push(path
.slice(0));
1628 // if there's only a single point in this path, then we want to display it
1629 // as a vertical line which means we keep path[0] as is
1630 if (path
.length
> 2) {
1631 // else we want the first value
1632 path
[0] = [path
[0][0], path
[1][1]];
1634 lineShapes
.push(path
);
1638 // draw the fill first, then optionally the normal range, then the line on top of that
1639 plen
= fillShapes
.length
;
1640 for (i
= 0; i
< plen
; i
++) {
1641 target
.drawShape(fillShapes
[i
],
1642 options
.get('fillColor'), options
.get('fillColor')).append();
1645 if (options
.get('normalRangeMin') !== undefined && options
.get('drawNormalOnTop')) {
1646 this.drawNormalRange(canvasLeft
, canvasTop
, canvasHeight
, canvasWidth
, rangey
);
1649 plen
= lineShapes
.length
;
1650 for (i
= 0; i
< plen
; i
++) {
1651 target
.drawShape(lineShapes
[i
], options
.get('lineColor'), undefined,
1652 options
.get('lineWidth')).append();
1655 if (spotRadius
&& options
.get('valueSpots')) {
1656 valueSpots
= options
.get('valueSpots');
1657 if (valueSpots
.get === undefined) {
1658 valueSpots
= new RangeMap(valueSpots
);
1660 for (i
= 0; i
< yvalcount
; i
++) {
1661 color
= valueSpots
.get(yvalues
[i
]);
1663 target
.drawCircle(canvasLeft
+ Math
.round((xvalues
[i
] - this.minx
) * (canvasWidth
/ rangex
)),
1664 canvasTop
+ Math
.round(canvasHeight
- (canvasHeight
* ((yvalues
[i
] - this.miny
) / rangey
))),
1665 spotRadius
, undefined,
1671 if (spotRadius
&& options
.get('spotColor') && yvalues
[yvallast
] !== null) {
1672 target
.drawCircle(canvasLeft
+ Math
.round((xvalues
[xvalues
.length
- 1] - this.minx
) * (canvasWidth
/ rangex
)),
1673 canvasTop
+ Math
.round(canvasHeight
- (canvasHeight
* ((yvalues
[yvallast
] - this.miny
) / rangey
))),
1674 spotRadius
, undefined,
1675 options
.get('spotColor')).append();
1677 if (this.maxy
!== this.minyorg
) {
1678 if (spotRadius
&& options
.get('minSpotColor')) {
1679 x
= xvalues
[$.inArray(this.minyorg
, yvalues
)];
1680 target
.drawCircle(canvasLeft
+ Math
.round((x
- this.minx
) * (canvasWidth
/ rangex
)),
1681 canvasTop
+ Math
.round(canvasHeight
- (canvasHeight
* ((this.minyorg
- this.miny
) / rangey
))),
1682 spotRadius
, undefined,
1683 options
.get('minSpotColor')).append();
1685 if (spotRadius
&& options
.get('maxSpotColor')) {
1686 x
= xvalues
[$.inArray(this.maxyorg
, yvalues
)];
1687 target
.drawCircle(canvasLeft
+ Math
.round((x
- this.minx
) * (canvasWidth
/ rangex
)),
1688 canvasTop
+ Math
.round(canvasHeight
- (canvasHeight
* ((this.maxyorg
- this.miny
) / rangey
))),
1689 spotRadius
, undefined,
1690 options
.get('maxSpotColor')).append();
1694 this.lastShapeId
= target
.getLastShapeId();
1695 this.canvasTop
= canvasTop
;
1703 $.fn
.sparkline
.bar
= bar
= createClass($.fn
.sparkline
._base
, barHighlightMixin
, {
1706 init: function (el
, values
, options
, width
, height
) {
1707 var barWidth
= parseInt(options
.get('barWidth'), 10),
1708 barSpacing
= parseInt(options
.get('barSpacing'), 10),
1709 chartRangeMin
= options
.get('chartRangeMin'),
1710 chartRangeMax
= options
.get('chartRangeMax'),
1711 chartRangeClip
= options
.get('chartRangeClip'),
1712 stackMin
= Infinity
,
1713 stackMax
= -Infinity
,
1714 isStackString
, groupMin
, groupMax
, stackRanges
,
1715 numValues
, i
, vlen
, range
, zeroAxis
, xaxisOffset
, min
, max
, clipMin
, clipMax
,
1716 stacked
, vlist
, j
, slen
, svals
, val
, yoffset
, yMaxCalc
, canvasHeightEf
;
1717 bar
._super
.init
.call(this, el
, values
, options
, width
, height
);
1719 // scan values to determine whether to stack bars
1720 for (i
= 0, vlen
= values
.length
; i
< vlen
; i
++) {
1722 isStackString
= typeof(val
) === 'string' && val
.indexOf(':') > -1;
1723 if (isStackString
|| $.isArray(val
)) {
1725 if (isStackString
) {
1726 val
= values
[i
] = normalizeValues(val
.split(':'));
1728 val
= remove(val
, null); // min/max will treat null as zero
1729 groupMin
= Math
.min
.apply(Math
, val
);
1730 groupMax
= Math
.max
.apply(Math
, val
);
1731 if (groupMin
< stackMin
) {
1732 stackMin
= groupMin
;
1734 if (groupMax
> stackMax
) {
1735 stackMax
= groupMax
;
1740 this.stacked
= stacked
;
1741 this.regionShapes
= {};
1742 this.barWidth
= barWidth
;
1743 this.barSpacing
= barSpacing
;
1744 this.totalBarWidth
= barWidth
+ barSpacing
;
1745 this.width
= width
= (values
.length
* barWidth
) + ((values
.length
- 1) * barSpacing
);
1749 if (chartRangeClip
) {
1750 clipMin
= chartRangeMin
=== undefined ? -Infinity
: chartRangeMin
;
1751 clipMax
= chartRangeMax
=== undefined ? Infinity
: chartRangeMax
;
1755 stackRanges
= stacked
? [] : numValues
;
1756 var stackTotals
= [];
1757 var stackRangesNeg
= [];
1758 for (i
= 0, vlen
= values
.length
; i
< vlen
; i
++) {
1761 values
[i
] = svals
= [];
1763 stackRanges
[i
] = stackRangesNeg
[i
] = 0;
1764 for (j
= 0, slen
= vlist
.length
; j
< slen
; j
++) {
1765 val
= svals
[j
] = chartRangeClip
? clipval(vlist
[j
], clipMin
, clipMax
) : vlist
[j
];
1768 stackTotals
[i
] += val
;
1770 if (stackMin
< 0 && stackMax
> 0) {
1772 stackRangesNeg
[i
] += Math
.abs(val
);
1774 stackRanges
[i
] += val
;
1777 stackRanges
[i
] += Math
.abs(val
- (val
< 0 ? stackMax
: stackMin
));
1779 numValues
.push(val
);
1783 val
= chartRangeClip
? clipval(values
[i
], clipMin
, clipMax
) : values
[i
];
1784 val
= values
[i
] = normalizeValue(val
);
1786 numValues
.push(val
);
1790 this.max
= max
= Math
.max
.apply(Math
, numValues
);
1791 this.min
= min
= Math
.min
.apply(Math
, numValues
);
1792 this.stackMax
= stackMax
= stacked
? Math
.max
.apply(Math
, stackTotals
) : max
;
1793 this.stackMin
= stackMin
= stacked
? Math
.min
.apply(Math
, numValues
) : min
;
1795 if (options
.get('chartRangeMin') !== undefined && (options
.get('chartRangeClip') || options
.get('chartRangeMin') < min
)) {
1796 min
= options
.get('chartRangeMin');
1798 if (options
.get('chartRangeMax') !== undefined && (options
.get('chartRangeClip') || options
.get('chartRangeMax') > max
)) {
1799 max
= options
.get('chartRangeMax');
1802 this.zeroAxis
= zeroAxis
= options
.get('zeroAxis', true);
1803 if (min
<= 0 && max
>= 0 && zeroAxis
) {
1805 } else if (zeroAxis
== false) {
1807 } else if (min
> 0) {
1812 this.xaxisOffset
= xaxisOffset
;
1814 range
= stacked
? (Math
.max
.apply(Math
, stackRanges
) + Math
.max
.apply(Math
, stackRangesNeg
)) : max
- min
;
1816 // as we plot zero/min values a single pixel line, we add a pixel to all other
1817 // values - Reduce the effective canvas size to suit
1818 this.canvasHeightEf
= (zeroAxis
&& min
< 0) ? this.canvasHeight
- 2 : this.canvasHeight
- 1;
1820 if (min
< xaxisOffset
) {
1821 yMaxCalc
= (stacked
&& max
>= 0) ? stackMax
: max
;
1822 yoffset
= (yMaxCalc
- xaxisOffset
) / range
* this.canvasHeight
;
1823 if (yoffset
!== Math
.ceil(yoffset
)) {
1824 this.canvasHeightEf
-= 2;
1825 yoffset
= Math
.ceil(yoffset
);
1828 yoffset
= this.canvasHeight
;
1830 this.yoffset
= yoffset
;
1832 if ($.isArray(options
.get('colorMap'))) {
1833 this.colorMapByIndex
= options
.get('colorMap');
1834 this.colorMapByValue
= null;
1836 this.colorMapByIndex
= null;
1837 this.colorMapByValue
= options
.get('colorMap');
1838 if (this.colorMapByValue
&& this.colorMapByValue
.get === undefined) {
1839 this.colorMapByValue
= new RangeMap(this.colorMapByValue
);
1846 getRegion: function (el
, x
, y
) {
1847 var result
= Math
.floor(x
/ this.totalBarWidth
);
1848 return (result
< 0 || result
>= this.values
.length
) ? undefined : result
;
1851 getCurrentRegionFields: function () {
1852 var currentRegion
= this.currentRegion
,
1853 values
= ensureArray(this.values
[currentRegion
]),
1856 for (i
= values
.length
; i
--;) {
1859 isNull
: value
=== null,
1861 color
: this.calcColor(i
, value
, currentRegion
),
1862 offset
: currentRegion
1868 calcColor: function (stacknum
, value
, valuenum
) {
1869 var colorMapByIndex
= this.colorMapByIndex
,
1870 colorMapByValue
= this.colorMapByValue
,
1871 options
= this.options
,
1874 color
= options
.get('stackedBarColor');
1876 color
= (value
< 0) ? options
.get('negBarColor') : options
.get('barColor');
1878 if (value
=== 0 && options
.get('zeroColor') !== undefined) {
1879 color
= options
.get('zeroColor');
1881 if (colorMapByValue
&& (newColor
= colorMapByValue
.get(value
))) {
1883 } else if (colorMapByIndex
&& colorMapByIndex
.length
> valuenum
) {
1884 color
= colorMapByIndex
[valuenum
];
1886 return $.isArray(color
) ? color
[stacknum
% color
.length
] : color
;
1890 * Render bar(s) for a region
1892 renderRegion: function (valuenum
, highlight
) {
1893 var vals
= this.values
[valuenum
],
1894 options
= this.options
,
1895 xaxisOffset
= this.xaxisOffset
,
1898 stacked
= this.stacked
,
1899 target
= this.target
,
1900 x
= valuenum
* this.totalBarWidth
,
1901 canvasHeightEf
= this.canvasHeightEf
,
1902 yoffset
= this.yoffset
,
1903 y
, height
, color
, isNull
, yoffsetNeg
, i
, valcount
, val
, minPlotted
, allMin
;
1905 vals
= $.isArray(vals
) ? vals
: [vals
];
1906 valcount
= vals
.length
;
1908 isNull
= all(null, vals
);
1909 allMin
= all(xaxisOffset
, vals
, true);
1912 if (options
.get('nullColor')) {
1913 color
= highlight
? options
.get('nullColor') : this.calcHighlightColor(options
.get('nullColor'), options
);
1914 y
= (yoffset
> 0) ? yoffset
- 1 : yoffset
;
1915 return target
.drawRect(x
, y
, this.barWidth
- 1, 0, color
, color
);
1920 yoffsetNeg
= yoffset
;
1921 for (i
= 0; i
< valcount
; i
++) {
1924 if (stacked
&& val
=== xaxisOffset
) {
1925 if (!allMin
|| minPlotted
) {
1932 height
= Math
.floor(canvasHeightEf
* ((Math
.abs(val
- xaxisOffset
) / range
))) + 1;
1936 if (val
< xaxisOffset
|| (val
=== xaxisOffset
&& yoffset
=== 0)) {
1938 yoffsetNeg
+= height
;
1940 y
= yoffset
- height
;
1943 color
= this.calcColor(i
, val
, valuenum
);
1945 color
= this.calcHighlightColor(color
, options
);
1947 result
.push(target
.drawRect(x
, y
, this.barWidth
- 1, height
- 1, color
, color
));
1949 if (result
.length
=== 1) {
1959 $.fn
.sparkline
.tristate
= tristate
= createClass($.fn
.sparkline
._base
, barHighlightMixin
, {
1962 init: function (el
, values
, options
, width
, height
) {
1963 var barWidth
= parseInt(options
.get('barWidth'), 10),
1964 barSpacing
= parseInt(options
.get('barSpacing'), 10);
1965 tristate
._super
.init
.call(this, el
, values
, options
, width
, height
);
1967 this.regionShapes
= {};
1968 this.barWidth
= barWidth
;
1969 this.barSpacing
= barSpacing
;
1970 this.totalBarWidth
= barWidth
+ barSpacing
;
1971 this.values
= $.map(values
, Number
);
1972 this.width
= width
= (values
.length
* barWidth
) + ((values
.length
- 1) * barSpacing
);
1974 if ($.isArray(options
.get('colorMap'))) {
1975 this.colorMapByIndex
= options
.get('colorMap');
1976 this.colorMapByValue
= null;
1978 this.colorMapByIndex
= null;
1979 this.colorMapByValue
= options
.get('colorMap');
1980 if (this.colorMapByValue
&& this.colorMapByValue
.get === undefined) {
1981 this.colorMapByValue
= new RangeMap(this.colorMapByValue
);
1987 getRegion: function (el
, x
, y
) {
1988 return Math
.floor(x
/ this.totalBarWidth
);
1991 getCurrentRegionFields: function () {
1992 var currentRegion
= this.currentRegion
;
1994 isNull
: this.values
[currentRegion
] === undefined,
1995 value
: this.values
[currentRegion
],
1996 color
: this.calcColor(this.values
[currentRegion
], currentRegion
),
1997 offset
: currentRegion
2001 calcColor: function (value
, valuenum
) {
2002 var values
= this.values
,
2003 options
= this.options
,
2004 colorMapByIndex
= this.colorMapByIndex
,
2005 colorMapByValue
= this.colorMapByValue
,
2008 if (colorMapByValue
&& (newColor
= colorMapByValue
.get(value
))) {
2010 } else if (colorMapByIndex
&& colorMapByIndex
.length
> valuenum
) {
2011 color
= colorMapByIndex
[valuenum
];
2012 } else if (values
[valuenum
] < 0) {
2013 color
= options
.get('negBarColor');
2014 } else if (values
[valuenum
] > 0) {
2015 color
= options
.get('posBarColor');
2017 color
= options
.get('zeroBarColor');
2022 renderRegion: function (valuenum
, highlight
) {
2023 var values
= this.values
,
2024 options
= this.options
,
2025 target
= this.target
,
2026 canvasHeight
, height
, halfHeight
,
2029 canvasHeight
= target
.pixelHeight
;
2030 halfHeight
= Math
.round(canvasHeight
/ 2);
2032 x
= valuenum
* this.totalBarWidth
;
2033 if (values
[valuenum
] < 0) {
2035 height
= halfHeight
- 1;
2036 } else if (values
[valuenum
] > 0) {
2038 height
= halfHeight
- 1;
2043 color
= this.calcColor(values
[valuenum
], valuenum
);
2044 if (color
=== null) {
2048 color
= this.calcHighlightColor(color
, options
);
2050 return target
.drawRect(x
, y
, this.barWidth
- 1, height
- 1, color
, color
);
2057 $.fn
.sparkline
.discrete
= discrete
= createClass($.fn
.sparkline
._base
, barHighlightMixin
, {
2060 init: function (el
, values
, options
, width
, height
) {
2061 discrete
._super
.init
.call(this, el
, values
, options
, width
, height
);
2063 this.regionShapes
= {};
2064 this.values
= values
= $.map(values
, Number
);
2065 this.min
= Math
.min
.apply(Math
, values
);
2066 this.max
= Math
.max
.apply(Math
, values
);
2067 this.range
= this.max
- this.min
;
2068 this.width
= width
= options
.get('width') === 'auto' ? values
.length
* 2 : this.width
;
2069 this.interval
= Math
.floor(width
/ values
.length
);
2070 this.itemWidth
= width
/ values
.length
;
2071 if (options
.get('chartRangeMin') !== undefined && (options
.get('chartRangeClip') || options
.get('chartRangeMin') < this.min
)) {
2072 this.min
= options
.get('chartRangeMin');
2074 if (options
.get('chartRangeMax') !== undefined && (options
.get('chartRangeClip') || options
.get('chartRangeMax') > this.max
)) {
2075 this.max
= options
.get('chartRangeMax');
2079 this.lineHeight
= options
.get('lineHeight') === 'auto' ? Math
.round(this.canvasHeight
* 0.3) : options
.get('lineHeight');
2083 getRegion: function (el
, x
, y
) {
2084 return Math
.floor(x
/ this.itemWidth
);
2087 getCurrentRegionFields: function () {
2088 var currentRegion
= this.currentRegion
;
2090 isNull
: this.values
[currentRegion
] === undefined,
2091 value
: this.values
[currentRegion
],
2092 offset
: currentRegion
2096 renderRegion: function (valuenum
, highlight
) {
2097 var values
= this.values
,
2098 options
= this.options
,
2102 interval
= this.interval
,
2103 target
= this.target
,
2104 canvasHeight
= this.canvasHeight
,
2105 lineHeight
= this.lineHeight
,
2106 pheight
= canvasHeight
- lineHeight
,
2107 ytop
, val
, color
, x
;
2109 val
= clipval(values
[valuenum
], min
, max
);
2110 x
= valuenum
* interval
;
2111 ytop
= Math
.round(pheight
- pheight
* ((val
- min
) / range
));
2112 color
= (options
.get('thresholdColor') && val
< options
.get('thresholdValue')) ? options
.get('thresholdColor') : options
.get('lineColor');
2114 color
= this.calcHighlightColor(color
, options
);
2116 return target
.drawLine(x
, ytop
, x
, ytop
+ lineHeight
, color
);
2123 $.fn
.sparkline
.bullet
= bullet
= createClass($.fn
.sparkline
._base
, {
2126 init: function (el
, values
, options
, width
, height
) {
2128 bullet
._super
.init
.call(this, el
, values
, options
, width
, height
);
2130 // values: target, performance, range1, range2, range3
2131 this.values
= values
= normalizeValues(values
);
2132 // target or performance could be null
2133 vals
= values
.slice();
2134 vals
[0] = vals
[0] === null ? vals
[2] : vals
[0];
2135 vals
[1] = values
[1] === null ? vals
[2] : vals
[1];
2136 min
= Math
.min
.apply(Math
, values
);
2137 max
= Math
.max
.apply(Math
, values
);
2138 if (options
.get('base') === undefined) {
2139 min
= min
< 0 ? min
: 0;
2141 min
= options
.get('base');
2145 this.range
= max
- min
;
2147 this.valueShapes
= {};
2148 this.regiondata
= {};
2149 this.width
= width
= options
.get('width') === 'auto' ? '4.0em' : width
;
2150 this.target
= this.$el
.simpledraw(width
, height
, options
.get('composite'));
2151 if (!values
.length
) {
2152 this.disabled
= true;
2157 getRegion: function (el
, x
, y
) {
2158 var shapeid
= this.target
.getShapeAt(el
, x
, y
);
2159 return (shapeid
!== undefined && this.shapes
[shapeid
] !== undefined) ? this.shapes
[shapeid
] : undefined;
2162 getCurrentRegionFields: function () {
2163 var currentRegion
= this.currentRegion
;
2165 fieldkey
: currentRegion
.substr(0, 1),
2166 value
: this.values
[currentRegion
.substr(1)],
2167 region
: currentRegion
2171 changeHighlight: function (highlight
) {
2172 var currentRegion
= this.currentRegion
,
2173 shapeid
= this.valueShapes
[currentRegion
],
2175 delete this.shapes
[shapeid
];
2176 switch (currentRegion
.substr(0, 1)) {
2178 shape
= this.renderRange(currentRegion
.substr(1), highlight
);
2181 shape
= this.renderPerformance(highlight
);
2184 shape
= this.renderTarget(highlight
);
2187 this.valueShapes
[currentRegion
] = shape
.id
;
2188 this.shapes
[shape
.id
] = currentRegion
;
2189 this.target
.replaceWithShape(shapeid
, shape
);
2192 renderRange: function (rn
, highlight
) {
2193 var rangeval
= this.values
[rn
],
2194 rangewidth
= Math
.round(this.canvasWidth
* ((rangeval
- this.min
) / this.range
)),
2195 color
= this.options
.get('rangeColors')[rn
- 2];
2197 color
= this.calcHighlightColor(color
, this.options
);
2199 return this.target
.drawRect(0, 0, rangewidth
- 1, this.canvasHeight
- 1, color
, color
);
2202 renderPerformance: function (highlight
) {
2203 var perfval
= this.values
[1],
2204 perfwidth
= Math
.round(this.canvasWidth
* ((perfval
- this.min
) / this.range
)),
2205 color
= this.options
.get('performanceColor');
2207 color
= this.calcHighlightColor(color
, this.options
);
2209 return this.target
.drawRect(0, Math
.round(this.canvasHeight
* 0.3), perfwidth
- 1,
2210 Math
.round(this.canvasHeight
* 0.4) - 1, color
, color
);
2213 renderTarget: function (highlight
) {
2214 var targetval
= this.values
[0],
2215 x
= Math
.round(this.canvasWidth
* ((targetval
- this.min
) / this.range
) - (this.options
.get('targetWidth') / 2)),
2216 targettop
= Math
.round(this.canvasHeight
* 0.10),
2217 targetheight
= this.canvasHeight
- (targettop
* 2),
2218 color
= this.options
.get('targetColor');
2220 color
= this.calcHighlightColor(color
, this.options
);
2222 return this.target
.drawRect(x
, targettop
, this.options
.get('targetWidth') - 1, targetheight
- 1, color
, color
);
2225 render: function () {
2226 var vlen
= this.values
.length
,
2227 target
= this.target
,
2229 if (!bullet
._super
.render
.call(this)) {
2232 for (i
= 2; i
< vlen
; i
++) {
2233 shape
= this.renderRange(i
).append();
2234 this.shapes
[shape
.id
] = 'r' + i
;
2235 this.valueShapes
['r' + i
] = shape
.id
;
2237 if (this.values
[1] !== null) {
2238 shape
= this.renderPerformance().append();
2239 this.shapes
[shape
.id
] = 'p1';
2240 this.valueShapes
.p1
= shape
.id
;
2242 if (this.values
[0] !== null) {
2243 shape
= this.renderTarget().append();
2244 this.shapes
[shape
.id
] = 't0';
2245 this.valueShapes
.t0
= shape
.id
;
2254 $.fn
.sparkline
.pie
= pie
= createClass($.fn
.sparkline
._base
, {
2257 init: function (el
, values
, options
, width
, height
) {
2260 pie
._super
.init
.call(this, el
, values
, options
, width
, height
);
2262 this.shapes
= {}; // map shape ids to value offsets
2263 this.valueShapes
= {}; // maps value offsets to shape ids
2264 this.values
= values
= $.map(values
, Number
);
2266 if (options
.get('width') === 'auto') {
2267 this.width
= this.height
;
2270 if (values
.length
> 0) {
2271 for (i
= values
.length
; i
--;) {
2277 this.radius
= Math
.floor(Math
.min(this.canvasWidth
, this.canvasHeight
) / 2);
2280 getRegion: function (el
, x
, y
) {
2281 var shapeid
= this.target
.getShapeAt(el
, x
, y
);
2282 return (shapeid
!== undefined && this.shapes
[shapeid
] !== undefined) ? this.shapes
[shapeid
] : undefined;
2285 getCurrentRegionFields: function () {
2286 var currentRegion
= this.currentRegion
;
2288 isNull
: this.values
[currentRegion
] === undefined,
2289 value
: this.values
[currentRegion
],
2290 percent
: this.values
[currentRegion
] / this.total
* 100,
2291 color
: this.options
.get('sliceColors')[currentRegion
% this.options
.get('sliceColors').length
],
2292 offset
: currentRegion
2296 changeHighlight: function (highlight
) {
2297 var currentRegion
= this.currentRegion
,
2298 newslice
= this.renderSlice(currentRegion
, highlight
),
2299 shapeid
= this.valueShapes
[currentRegion
];
2300 delete this.shapes
[shapeid
];
2301 this.target
.replaceWithShape(shapeid
, newslice
);
2302 this.valueShapes
[currentRegion
] = newslice
.id
;
2303 this.shapes
[newslice
.id
] = currentRegion
;
2306 renderSlice: function (valuenum
, highlight
) {
2307 var target
= this.target
,
2308 options
= this.options
,
2309 radius
= this.radius
,
2310 borderWidth
= options
.get('borderWidth'),
2311 offset
= options
.get('offset'),
2312 circle
= 2 * Math
.PI
,
2313 values
= this.values
,
2315 next
= offset
? (2*Math
.PI
)*(offset
/360) : 0,
2316 start
, end
, i
, vlen
, color
;
2318 vlen
= values
.length
;
2319 for (i
= 0; i
< vlen
; i
++) {
2322 if (total
> 0) { // avoid divide by zero
2323 end
= next
+ (circle
* (values
[i
] / total
));
2325 if (valuenum
=== i
) {
2326 color
= options
.get('sliceColors')[i
% options
.get('sliceColors').length
];
2328 color
= this.calcHighlightColor(color
, options
);
2331 return target
.drawPieSlice(radius
, radius
, radius
- borderWidth
, start
, end
, undefined, color
);
2337 render: function () {
2338 var target
= this.target
,
2339 values
= this.values
,
2340 options
= this.options
,
2341 radius
= this.radius
,
2342 borderWidth
= options
.get('borderWidth'),
2345 if (!pie
._super
.render
.call(this)) {
2349 target
.drawCircle(radius
, radius
, Math
.floor(radius
- (borderWidth
/ 2)),
2350 options
.get('borderColor'), undefined, borderWidth
).append();
2352 for (i
= values
.length
; i
--;) {
2353 if (values
[i
]) { // don't render zero values
2354 shape
= this.renderSlice(i
).append();
2355 this.valueShapes
[i
] = shape
.id
; // store just the shapeid
2356 this.shapes
[shape
.id
] = i
;
2366 $.fn
.sparkline
.box
= box
= createClass($.fn
.sparkline
._base
, {
2369 init: function (el
, values
, options
, width
, height
) {
2370 box
._super
.init
.call(this, el
, values
, options
, width
, height
);
2371 this.values
= $.map(values
, Number
);
2372 this.width
= options
.get('width') === 'auto' ? '4.0em' : width
;
2374 if (!this.values
.length
) {
2380 * Simulate a single region
2382 getRegion: function () {
2386 getCurrentRegionFields: function () {
2388 { field
: 'lq', value
: this.quartiles
[0] },
2389 { field
: 'med', value
: this.quartiles
[1] },
2390 { field
: 'uq', value
: this.quartiles
[2] }
2392 if (this.loutlier
!== undefined) {
2393 result
.push({ field
: 'lo', value
: this.loutlier
});
2395 if (this.routlier
!== undefined) {
2396 result
.push({ field
: 'ro', value
: this.routlier
});
2398 if (this.lwhisker
!== undefined) {
2399 result
.push({ field
: 'lw', value
: this.lwhisker
});
2401 if (this.rwhisker
!== undefined) {
2402 result
.push({ field
: 'rw', value
: this.rwhisker
});
2407 render: function () {
2408 var target
= this.target
,
2409 values
= this.values
,
2410 vlen
= values
.length
,
2411 options
= this.options
,
2412 canvasWidth
= this.canvasWidth
,
2413 canvasHeight
= this.canvasHeight
,
2414 minValue
= options
.get('chartRangeMin') === undefined ? Math
.min
.apply(Math
, values
) : options
.get('chartRangeMin'),
2415 maxValue
= options
.get('chartRangeMax') === undefined ? Math
.max
.apply(Math
, values
) : options
.get('chartRangeMax'),
2417 lwhisker
, loutlier
, iqr
, q1
, q2
, q3
, rwhisker
, routlier
, i
,
2420 if (!box
._super
.render
.call(this)) {
2424 if (options
.get('raw')) {
2425 if (options
.get('showOutliers') && values
.length
> 5) {
2426 loutlier
= values
[0];
2427 lwhisker
= values
[1];
2431 rwhisker
= values
[5];
2432 routlier
= values
[6];
2434 lwhisker
= values
[0];
2438 rwhisker
= values
[4];
2441 values
.sort(function (a
, b
) { return a
- b
; });
2442 q1
= quartile(values
, 1);
2443 q2
= quartile(values
, 2);
2444 q3
= quartile(values
, 3);
2446 if (options
.get('showOutliers')) {
2447 lwhisker
= rwhisker
= undefined;
2448 for (i
= 0; i
< vlen
; i
++) {
2449 if (lwhisker
=== undefined && values
[i
] > q1
- (iqr
* options
.get('outlierIQR'))) {
2450 lwhisker
= values
[i
];
2452 if (values
[i
] < q3
+ (iqr
* options
.get('outlierIQR'))) {
2453 rwhisker
= values
[i
];
2456 loutlier
= values
[0];
2457 routlier
= values
[vlen
- 1];
2459 lwhisker
= values
[0];
2460 rwhisker
= values
[vlen
- 1];
2463 this.quartiles
= [q1
, q2
, q3
];
2464 this.lwhisker
= lwhisker
;
2465 this.rwhisker
= rwhisker
;
2466 this.loutlier
= loutlier
;
2467 this.routlier
= routlier
;
2469 unitSize
= canvasWidth
/ (maxValue
- minValue
+ 1);
2470 if (options
.get('showOutliers')) {
2471 canvasLeft
= Math
.ceil(options
.get('spotRadius'));
2472 canvasWidth
-= 2 * Math
.ceil(options
.get('spotRadius'));
2473 unitSize
= canvasWidth
/ (maxValue
- minValue
+ 1);
2474 if (loutlier
< lwhisker
) {
2475 target
.drawCircle((loutlier
- minValue
) * unitSize
+ canvasLeft
,
2477 options
.get('spotRadius'),
2478 options
.get('outlierLineColor'),
2479 options
.get('outlierFillColor')).append();
2481 if (routlier
> rwhisker
) {
2482 target
.drawCircle((routlier
- minValue
) * unitSize
+ canvasLeft
,
2484 options
.get('spotRadius'),
2485 options
.get('outlierLineColor'),
2486 options
.get('outlierFillColor')).append();
2492 Math
.round((q1
- minValue
) * unitSize
+ canvasLeft
),
2493 Math
.round(canvasHeight
* 0.1),
2494 Math
.round((q3
- q1
) * unitSize
),
2495 Math
.round(canvasHeight
* 0.8),
2496 options
.get('boxLineColor'),
2497 options
.get('boxFillColor')).append();
2500 Math
.round((lwhisker
- minValue
) * unitSize
+ canvasLeft
),
2501 Math
.round(canvasHeight
/ 2),
2502 Math
.round((q1
- minValue
) * unitSize
+ canvasLeft
),
2503 Math
.round(canvasHeight
/ 2),
2504 options
.get('lineColor')).append();
2506 Math
.round((lwhisker
- minValue
) * unitSize
+ canvasLeft
),
2507 Math
.round(canvasHeight
/ 4),
2508 Math
.round((lwhisker
- minValue
) * unitSize
+ canvasLeft
),
2509 Math
.round(canvasHeight
- canvasHeight
/ 4),
2510 options
.get('whiskerColor')).append();
2512 target
.drawLine(Math
.round((rwhisker
- minValue
) * unitSize
+ canvasLeft
),
2513 Math
.round(canvasHeight
/ 2),
2514 Math
.round((q3
- minValue
) * unitSize
+ canvasLeft
),
2515 Math
.round(canvasHeight
/ 2),
2516 options
.get('lineColor')).append();
2518 Math
.round((rwhisker
- minValue
) * unitSize
+ canvasLeft
),
2519 Math
.round(canvasHeight
/ 4),
2520 Math
.round((rwhisker
- minValue
) * unitSize
+ canvasLeft
),
2521 Math
.round(canvasHeight
- canvasHeight
/ 4),
2522 options
.get('whiskerColor')).append();
2525 Math
.round((q2
- minValue
) * unitSize
+ canvasLeft
),
2526 Math
.round(canvasHeight
* 0.1),
2527 Math
.round((q2
- minValue
) * unitSize
+ canvasLeft
),
2528 Math
.round(canvasHeight
* 0.9),
2529 options
.get('medianColor')).append();
2530 if (options
.get('target')) {
2531 size
= Math
.ceil(options
.get('spotRadius'));
2533 Math
.round((options
.get('target') - minValue
) * unitSize
+ canvasLeft
),
2534 Math
.round((canvasHeight
/ 2) - size
),
2535 Math
.round((options
.get('target') - minValue
) * unitSize
+ canvasLeft
),
2536 Math
.round((canvasHeight
/ 2) + size
),
2537 options
.get('targetColor')).append();
2539 Math
.round((options
.get('target') - minValue
) * unitSize
+ canvasLeft
- size
),
2540 Math
.round(canvasHeight
/ 2),
2541 Math
.round((options
.get('target') - minValue
) * unitSize
+ canvasLeft
+ size
),
2542 Math
.round(canvasHeight
/ 2),
2543 options
.get('targetColor')).append();
2549 // Setup a very simple "virtual canvas" to make drawing the few shapes we need easier
2550 // This is accessible as $(foo).simpledraw()
2552 VShape
= createClass({
2553 init: function (target
, id
, type
, args
) {
2554 this.target
= target
;
2559 append: function () {
2560 this.target
.appendShape(this);
2565 VCanvas_base
= createClass({
2566 _pxregex
: /(\d+)(px)?\s*$/i,
2568 init: function (width
, height
, target
) {
2573 this.height
= height
;
2574 this.target
= target
;
2575 this.lastShapeId
= null;
2579 $.data(target
, '_jqs_vcanvas', this);
2582 drawLine: function (x1
, y1
, x2
, y2
, lineColor
, lineWidth
) {
2583 return this.drawShape([[x1
, y1
], [x2
, y2
]], lineColor
, lineWidth
);
2586 drawShape: function (path
, lineColor
, fillColor
, lineWidth
) {
2587 return this._genShape('Shape', [path
, lineColor
, fillColor
, lineWidth
]);
2590 drawCircle: function (x
, y
, radius
, lineColor
, fillColor
, lineWidth
) {
2591 return this._genShape('Circle', [x
, y
, radius
, lineColor
, fillColor
, lineWidth
]);
2594 drawPieSlice: function (x
, y
, radius
, startAngle
, endAngle
, lineColor
, fillColor
) {
2595 return this._genShape('PieSlice', [x
, y
, radius
, startAngle
, endAngle
, lineColor
, fillColor
]);
2598 drawRect: function (x
, y
, width
, height
, lineColor
, fillColor
) {
2599 return this._genShape('Rect', [x
, y
, width
, height
, lineColor
, fillColor
]);
2602 getElement: function () {
2607 * Return the most recently inserted shape id
2609 getLastShapeId: function () {
2610 return this.lastShapeId
;
2614 * Clear and reset the canvas
2616 reset: function () {
2617 alert('reset not implemented');
2620 _insert: function (el
, target
) {
2625 * Calculate the pixel dimensions of the canvas
2627 _calculatePixelDims: function (width
, height
, canvas
) {
2628 // XXX This should probably be a configurable option
2630 match
= this._pxregex
.exec(height
);
2632 this.pixelHeight
= match
[1];
2634 this.pixelHeight
= $(canvas
).height();
2636 match
= this._pxregex
.exec(width
);
2638 this.pixelWidth
= match
[1];
2640 this.pixelWidth
= $(canvas
).width();
2645 * Generate a shape object and id for later rendering
2647 _genShape: function (shapetype
, shapeargs
) {
2648 var id
= shapeCount
++;
2649 shapeargs
.unshift(id
);
2650 return new VShape(this, id
, shapetype
, shapeargs
);
2654 * Add a shape to the end of the render queue
2656 appendShape: function (shape
) {
2657 alert('appendShape not implemented');
2661 * Replace one shape with another
2663 replaceWithShape: function (shapeid
, shape
) {
2664 alert('replaceWithShape not implemented');
2668 * Insert one shape after another in the render queue
2670 insertAfterShape: function (shapeid
, shape
) {
2671 alert('insertAfterShape not implemented');
2675 * Remove a shape from the queue
2677 removeShapeId: function (shapeid
) {
2678 alert('removeShapeId not implemented');
2682 * Find a shape at the specified x/y co-ordinates
2684 getShapeAt: function (el
, x
, y
) {
2685 alert('getShapeAt not implemented');
2689 * Render all queued shapes onto the canvas
2691 render: function () {
2692 alert('render not implemented');
2696 VCanvas_canvas
= createClass(VCanvas_base
, {
2697 init: function (width
, height
, target
, interact
) {
2698 VCanvas_canvas
._super
.init
.call(this, width
, height
, target
);
2699 this.canvas
= document
.createElement('canvas');
2703 $.data(target
, '_jqs_vcanvas', this);
2704 $(this.canvas
).css({ display
: 'inline-block', width
: width
, height
: height
, verticalAlign
: 'top' });
2705 this._insert(this.canvas
, target
);
2706 this._calculatePixelDims(width
, height
, this.canvas
);
2707 this.canvas
.width
= this.pixelWidth
;
2708 this.canvas
.height
= this.pixelHeight
;
2709 this.interact
= interact
;
2712 this.currentTargetShapeId
= undefined;
2713 $(this.canvas
).css({width
: this.pixelWidth
, height
: this.pixelHeight
});
2716 _getContext: function (lineColor
, fillColor
, lineWidth
) {
2717 var context
= this.canvas
.getContext('2d');
2718 if (lineColor
!== undefined) {
2719 context
.strokeStyle
= lineColor
;
2721 context
.lineWidth
= lineWidth
=== undefined ? 1 : lineWidth
;
2722 if (fillColor
!== undefined) {
2723 context
.fillStyle
= fillColor
;
2728 reset: function () {
2729 var context
= this._getContext();
2730 context
.clearRect(0, 0, this.pixelWidth
, this.pixelHeight
);
2733 this.currentTargetShapeId
= undefined;
2736 _drawShape: function (shapeid
, path
, lineColor
, fillColor
, lineWidth
) {
2737 var context
= this._getContext(lineColor
, fillColor
, lineWidth
),
2739 context
.beginPath();
2740 context
.moveTo(path
[0][0] + 0.5, path
[0][1] + 0.5);
2741 for (i
= 1, plen
= path
.length
; i
< plen
; i
++) {
2742 context
.lineTo(path
[i
][0] + 0.5, path
[i
][1] + 0.5); // the 0.5 offset gives us crisp pixel-width lines
2744 if (lineColor
!== undefined) {
2747 if (fillColor
!== undefined) {
2750 if (this.targetX
!== undefined && this.targetY
!== undefined &&
2751 context
.isPointInPath(this.targetX
, this.targetY
)) {
2752 this.currentTargetShapeId
= shapeid
;
2756 _drawCircle: function (shapeid
, x
, y
, radius
, lineColor
, fillColor
, lineWidth
) {
2757 var context
= this._getContext(lineColor
, fillColor
, lineWidth
);
2758 context
.beginPath();
2759 context
.arc(x
, y
, radius
, 0, 2 * Math
.PI
, false);
2760 if (this.targetX
!== undefined && this.targetY
!== undefined &&
2761 context
.isPointInPath(this.targetX
, this.targetY
)) {
2762 this.currentTargetShapeId
= shapeid
;
2764 if (lineColor
!== undefined) {
2767 if (fillColor
!== undefined) {
2772 _drawPieSlice: function (shapeid
, x
, y
, radius
, startAngle
, endAngle
, lineColor
, fillColor
) {
2773 var context
= this._getContext(lineColor
, fillColor
);
2774 context
.beginPath();
2775 context
.moveTo(x
, y
);
2776 context
.arc(x
, y
, radius
, startAngle
, endAngle
, false);
2777 context
.lineTo(x
, y
);
2778 context
.closePath();
2779 if (lineColor
!== undefined) {
2785 if (this.targetX
!== undefined && this.targetY
!== undefined &&
2786 context
.isPointInPath(this.targetX
, this.targetY
)) {
2787 this.currentTargetShapeId
= shapeid
;
2791 _drawRect: function (shapeid
, x
, y
, width
, height
, lineColor
, fillColor
) {
2792 return this._drawShape(shapeid
, [[x
, y
], [x
+ width
, y
], [x
+ width
, y
+ height
], [x
, y
+ height
], [x
, y
]], lineColor
, fillColor
);
2795 appendShape: function (shape
) {
2796 this.shapes
[shape
.id
] = shape
;
2797 this.shapeseq
.push(shape
.id
);
2798 this.lastShapeId
= shape
.id
;
2802 replaceWithShape: function (shapeid
, shape
) {
2803 var shapeseq
= this.shapeseq
,
2805 this.shapes
[shape
.id
] = shape
;
2806 for (i
= shapeseq
.length
; i
--;) {
2807 if (shapeseq
[i
] == shapeid
) {
2808 shapeseq
[i
] = shape
.id
;
2811 delete this.shapes
[shapeid
];
2814 replaceWithShapes: function (shapeids
, shapes
) {
2815 var shapeseq
= this.shapeseq
,
2819 for (i
= shapeids
.length
; i
--;) {
2820 shapemap
[shapeids
[i
]] = true;
2822 for (i
= shapeseq
.length
; i
--;) {
2824 if (shapemap
[sid
]) {
2825 shapeseq
.splice(i
, 1);
2826 delete this.shapes
[sid
];
2830 for (i
= shapes
.length
; i
--;) {
2831 shapeseq
.splice(first
, 0, shapes
[i
].id
);
2832 this.shapes
[shapes
[i
].id
] = shapes
[i
];
2837 insertAfterShape: function (shapeid
, shape
) {
2838 var shapeseq
= this.shapeseq
,
2840 for (i
= shapeseq
.length
; i
--;) {
2841 if (shapeseq
[i
] === shapeid
) {
2842 shapeseq
.splice(i
+ 1, 0, shape
.id
);
2843 this.shapes
[shape
.id
] = shape
;
2849 removeShapeId: function (shapeid
) {
2850 var shapeseq
= this.shapeseq
,
2852 for (i
= shapeseq
.length
; i
--;) {
2853 if (shapeseq
[i
] === shapeid
) {
2854 shapeseq
.splice(i
, 1);
2858 delete this.shapes
[shapeid
];
2861 getShapeAt: function (el
, x
, y
) {
2865 return this.currentTargetShapeId
;
2868 render: function () {
2869 var shapeseq
= this.shapeseq
,
2870 shapes
= this.shapes
,
2871 shapeCount
= shapeseq
.length
,
2872 context
= this._getContext(),
2874 context
.clearRect(0, 0, this.pixelWidth
, this.pixelHeight
);
2875 for (i
= 0; i
< shapeCount
; i
++) {
2876 shapeid
= shapeseq
[i
];
2877 shape
= shapes
[shapeid
];
2878 this['_draw' + shape
.type
].apply(this, shape
.args
);
2880 if (!this.interact
) {
2881 // not interactive so no need to keep the shapes array
2889 VCanvas_vml
= createClass(VCanvas_base
, {
2890 init: function (width
, height
, target
) {
2892 VCanvas_vml
._super
.init
.call(this, width
, height
, target
);
2896 $.data(target
, '_jqs_vcanvas', this);
2897 this.canvas
= document
.createElement('span');
2898 $(this.canvas
).css({ display
: 'inline-block', position
: 'relative', overflow
: 'hidden', width
: width
, height
: height
, margin
: '0px', padding
: '0px', verticalAlign
: 'top'});
2899 this._insert(this.canvas
, target
);
2900 this._calculatePixelDims(width
, height
, this.canvas
);
2901 this.canvas
.width
= this.pixelWidth
;
2902 this.canvas
.height
= this.pixelHeight
;
2903 groupel
= '<v:group coordorigin="0 0" coordsize="' + this.pixelWidth
+ ' ' + this.pixelHeight
+ '"' +
2904 ' style="position:absolute;top:0;left:0;width:' + this.pixelWidth
+ 'px;height=' + this.pixelHeight
+ 'px;"></v:group>';
2905 this.canvas
.insertAdjacentHTML('beforeEnd', groupel
);
2906 this.group
= $(this.canvas
).children()[0];
2907 this.rendered
= false;
2908 this.prerender
= '';
2911 _drawShape: function (shapeid
, path
, lineColor
, fillColor
, lineWidth
) {
2913 initial
, stroke
, fill
, closed
, vel
, plen
, i
;
2914 for (i
= 0, plen
= path
.length
; i
< plen
; i
++) {
2915 vpath
[i
] = '' + (path
[i
][0]) + ',' + (path
[i
][1]);
2917 initial
= vpath
.splice(0, 1);
2918 lineWidth
= lineWidth
=== undefined ? 1 : lineWidth
;
2919 stroke
= lineColor
=== undefined ? ' stroked="false" ' : ' strokeWeight="' + lineWidth
+ 'px" strokeColor="' + lineColor
+ '" ';
2920 fill
= fillColor
=== undefined ? ' filled="false"' : ' fillColor="' + fillColor
+ '" filled="true" ';
2921 closed
= vpath
[0] === vpath
[vpath
.length
- 1] ? 'x ' : '';
2922 vel
= '<v:shape coordorigin="0 0" coordsize="' + this.pixelWidth
+ ' ' + this.pixelHeight
+ '" ' +
2923 ' id="jqsshape' + shapeid
+ '" ' +
2926 ' style="position:absolute;left:0px;top:0px;height:' + this.pixelHeight
+ 'px;width:' + this.pixelWidth
+ 'px;padding:0px;margin:0px;" ' +
2927 ' path="m ' + initial
+ ' l ' + vpath
.join(', ') + ' ' + closed
+ 'e">' +
2932 _drawCircle: function (shapeid
, x
, y
, radius
, lineColor
, fillColor
, lineWidth
) {
2933 var stroke
, fill
, vel
;
2936 stroke
= lineColor
=== undefined ? ' stroked="false" ' : ' strokeWeight="' + lineWidth
+ 'px" strokeColor="' + lineColor
+ '" ';
2937 fill
= fillColor
=== undefined ? ' filled="false"' : ' fillColor="' + fillColor
+ '" filled="true" ';
2939 ' id="jqsshape' + shapeid
+ '" ' +
2942 ' style="position:absolute;top:' + y
+ 'px; left:' + x
+ 'px; width:' + (radius
* 2) + 'px; height:' + (radius
* 2) + 'px"></v:oval>';
2947 _drawPieSlice: function (shapeid
, x
, y
, radius
, startAngle
, endAngle
, lineColor
, fillColor
) {
2948 var vpath
, startx
, starty
, endx
, endy
, stroke
, fill
, vel
;
2949 if (startAngle
=== endAngle
) {
2950 return ''; // VML seems to have problem when start angle equals end angle.
2952 if ((endAngle
- startAngle
) === (2 * Math
.PI
)) {
2953 startAngle
= 0.0; // VML seems to have a problem when drawing a full circle that doesn't start 0
2954 endAngle
= (2 * Math
.PI
);
2957 startx
= x
+ Math
.round(Math
.cos(startAngle
) * radius
);
2958 starty
= y
+ Math
.round(Math
.sin(startAngle
) * radius
);
2959 endx
= x
+ Math
.round(Math
.cos(endAngle
) * radius
);
2960 endy
= y
+ Math
.round(Math
.sin(endAngle
) * radius
);
2962 if (startx
=== endx
&& starty
=== endy
) {
2963 if ((endAngle
- startAngle
) < Math
.PI
) {
2964 // Prevent very small slices from being mistaken as a whole pie
2967 // essentially going to be the entire circle, so ignore startAngle
2968 startx
= endx
= x
+ radius
;
2972 if (startx
=== endx
&& starty
=== endy
&& (endAngle
- startAngle
) < Math
.PI
) {
2976 vpath
= [x
- radius
, y
- radius
, x
+ radius
, y
+ radius
, startx
, starty
, endx
, endy
];
2977 stroke
= lineColor
=== undefined ? ' stroked="false" ' : ' strokeWeight="1px" strokeColor="' + lineColor
+ '" ';
2978 fill
= fillColor
=== undefined ? ' filled="false"' : ' fillColor="' + fillColor
+ '" filled="true" ';
2979 vel
= '<v:shape coordorigin="0 0" coordsize="' + this.pixelWidth
+ ' ' + this.pixelHeight
+ '" ' +
2980 ' id="jqsshape' + shapeid
+ '" ' +
2983 ' style="position:absolute;left:0px;top:0px;height:' + this.pixelHeight
+ 'px;width:' + this.pixelWidth
+ 'px;padding:0px;margin:0px;" ' +
2984 ' path="m ' + x
+ ',' + y
+ ' wa ' + vpath
.join(', ') + ' x e">' +
2989 _drawRect: function (shapeid
, x
, y
, width
, height
, lineColor
, fillColor
) {
2990 return this._drawShape(shapeid
, [[x
, y
], [x
, y
+ height
], [x
+ width
, y
+ height
], [x
+ width
, y
], [x
, y
]], lineColor
, fillColor
);
2993 reset: function () {
2994 this.group
.innerHTML
= '';
2997 appendShape: function (shape
) {
2998 var vel
= this['_draw' + shape
.type
].apply(this, shape
.args
);
2999 if (this.rendered
) {
3000 this.group
.insertAdjacentHTML('beforeEnd', vel
);
3002 this.prerender
+= vel
;
3004 this.lastShapeId
= shape
.id
;
3008 replaceWithShape: function (shapeid
, shape
) {
3009 var existing
= $('#jqsshape' + shapeid
),
3010 vel
= this['_draw' + shape
.type
].apply(this, shape
.args
);
3011 existing
[0].outerHTML
= vel
;
3014 replaceWithShapes: function (shapeids
, shapes
) {
3015 // replace the first shapeid with all the new shapes then toast the remaining old shapes
3016 var existing
= $('#jqsshape' + shapeids
[0]),
3018 slen
= shapes
.length
,
3020 for (i
= 0; i
< slen
; i
++) {
3021 replace
+= this['_draw' + shapes
[i
].type
].apply(this, shapes
[i
].args
);
3023 existing
[0].outerHTML
= replace
;
3024 for (i
= 1; i
< shapeids
.length
; i
++) {
3025 $('#jqsshape' + shapeids
[i
]).remove();
3029 insertAfterShape: function (shapeid
, shape
) {
3030 var existing
= $('#jqsshape' + shapeid
),
3031 vel
= this['_draw' + shape
.type
].apply(this, shape
.args
);
3032 existing
[0].insertAdjacentHTML('afterEnd', vel
);
3035 removeShapeId: function (shapeid
) {
3036 var existing
= $('#jqsshape' + shapeid
);
3037 this.group
.removeChild(existing
[0]);
3040 getShapeAt: function (el
, x
, y
) {
3041 var shapeid
= el
.id
.substr(8);
3045 render: function () {
3046 if (!this.rendered
) {
3047 // batch the intial render into a single repaint
3048 this.group
.innerHTML
= this.prerender
;
3049 this.rendered
= true;
3054 }))}(document
, Math
));