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