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