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