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