| /** |
| * |
| * jquery.sparkline.js |
| * |
| * v2.0 |
| * (c) Splunk, Inc |
| * Contact: Gareth Watts (gareth@splunk.com) |
| * http://omnipotent.net/jquery.sparkline/ |
| * |
| * Generates inline sparkline charts from data supplied either to the method |
| * or inline in HTML |
| * |
| * Compatible with Internet Explorer 6.0+ and modern browsers equipped with the canvas tag |
| * (Firefox 2.0+, Safari, Opera, etc) |
| * |
| * License: New BSD License |
| * |
| * Copyright (c) 2012, Splunk Inc. |
| * All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without modification, |
| * are permitted provided that the following conditions are met: |
| * |
| * * Redistributions of source code must retain the above copyright notice, |
| * this list of conditions and the following disclaimer. |
| * * Redistributions in binary form must reproduce the above copyright notice, |
| * this list of conditions and the following disclaimer in the documentation |
| * and/or other materials provided with the distribution. |
| * * Neither the name of Splunk Inc nor the names of its contributors may |
| * be used to endorse or promote products derived from this software without |
| * specific prior written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY |
| * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES |
| * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT |
| * SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT |
| * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) |
| * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, |
| * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
| * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| * |
| * |
| * Usage: |
| * $(selector).sparkline(values, options) |
| * |
| * If values is undefined or set to 'html' then the data values are read from the specified tag: |
| * <p>Sparkline: <span class="sparkline">1,4,6,6,8,5,3,5</span></p> |
| * $('.sparkline').sparkline(); |
| * There must be no spaces in the enclosed data set |
| * |
| * Otherwise values must be an array of numbers or null values |
| * <p>Sparkline: <span id="sparkline1">This text replaced if the browser is compatible</span></p> |
| * $('#sparkline1').sparkline([1,4,6,6,8,5,3,5]) |
| * $('#sparkline2').sparkline([1,4,6,null,null,5,3,5]) |
| * |
| * Values can also be specified in an HTML comment, or as a values attribute: |
| * <p>Sparkline: <span class="sparkline"><!--1,4,6,6,8,5,3,5 --></span></p> |
| * <p>Sparkline: <span class="sparkline" values="1,4,6,6,8,5,3,5"></span></p> |
| * $('.sparkline').sparkline(); |
| * |
| * For line charts, x values can also be specified: |
| * <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> |
| * $('#sparkline1').sparkline([ [1,1], [2.7,4], [3.4,6], [5,6], [6,8], [8.7,5], [9,3], [10,5] ]) |
| * |
| * By default, options should be passed in as teh second argument to the sparkline function: |
| * $('.sparkline').sparkline([1,2,3,4], {type: 'bar'}) |
| * |
| * Options can also be set by passing them on the tag itself. This feature is disabled by default though |
| * as there's a slight performance overhead: |
| * $('.sparkline').sparkline([1,2,3,4], {enableTagOptions: true}) |
| * <p>Sparkline: <span class="sparkline" sparkType="bar" sparkBarColor="red">loading</span></p> |
| * Prefix all options supplied as tag attribute with "spark" (configurable by setting tagOptionPrefix) |
| * |
| * Supported options: |
| * lineColor - Color of the line used for the chart |
| * fillColor - Color used to fill in the chart - Set to '' or false for a transparent chart |
| * width - Width of the chart - Defaults to 3 times the number of values in pixels |
| * height - Height of the chart - Defaults to the height of the containing element |
| * chartRangeMin - Specify the minimum value to use for the Y range of the chart - Defaults to the minimum value supplied |
| * chartRangeMax - Specify the maximum value to use for the Y range of the chart - Defaults to the maximum value supplied |
| * chartRangeClip - Clip out of range values to the max/min specified by chartRangeMin and chartRangeMax |
| * chartRangeMinX - Specify the minimum value to use for the X range of the chart - Defaults to the minimum value supplied |
| * chartRangeMaxX - Specify the maximum value to use for the X range of the chart - Defaults to the maximum value supplied |
| * composite - If true then don't erase any existing chart attached to the tag, but draw |
| * another chart over the top - Note that width and height are ignored if an |
| * existing chart is detected. |
| * tagValuesAttribute - Name of tag attribute to check for data values - Defaults to 'values' |
| * enableTagOptions - Whether to check tags for sparkline options |
| * tagOptionPrefix - Prefix used for options supplied as tag attributes - Defaults to 'spark' |
| * disableHiddenCheck - If set to true, then the plugin will assume that charts will never be drawn into a |
| * hidden dom element, avoding a browser reflow |
| * disableInteraction - If set to true then all mouseover/click interaction behaviour will be disabled, |
| * making the plugin perform much like it did in 1.x |
| * disableTooltips - If set to true then tooltips will be disabled - Defaults to false (tooltips enabled) |
| * disableHighlight - If set to true then highlighting of selected chart elements on mouseover will be disabled |
| * defaults to false (highlights enabled) |
| * highlightLighten - Factor to lighten/darken highlighted chart values by - Defaults to 1.4 for a 40% increase |
| * tooltipContainer - Specify which DOM element the tooltip should be rendered into - defaults to document.body |
| * tooltipClassname - Optional CSS classname to apply to tooltips - If not specified then a default style will be applied |
| * tooltipOffsetX - How many pixels away from the mouse pointer to render the tooltip on the X axis |
| * tooltipOffsetY - How many pixels away from the mouse pointer to render the tooltip on the r axis |
| * tooltipFormatter - Optional callback that allows you to override the HTML displayed in the tooltip |
| * callback is given arguments of (sparkline, options, fields) |
| * tooltipChartTitle - If specified then the tooltip uses the string specified by this setting as a title |
| * tooltipFormat - A format string or SPFormat object (or an array thereof for multiple entries) |
| * to control the format of the tooltip |
| * tooltipPrefix - A string to prepend to each field displayed in a tooltip |
| * tooltipSuffix - A string to append to each field displayed in a tooltip |
| * tooltipSkipNull - If true then null values will not have a tooltip displayed (defaults to true) |
| * tooltipValueLookups - An object or range map to map field values to tooltip strings |
| * (eg. to map -1 to "Lost", 0 to "Draw", and 1 to "Win") |
| * numberFormatter - Optional callback for formatting numbers in tooltips |
| * numberDigitGroupSep - Character to use for group separator in numbers "1,234" - Defaults to "," |
| * numberDecimalMark - Character to use for the decimal point when formatting numbers - Defaults to "." |
| * numberDigitGroupCount - Number of digits between group separator - Defaults to 3 |
| * |
| * There are 7 types of sparkline, selected by supplying a "type" option of 'line' (default), |
| * 'bar', 'tristate', 'bullet', 'discrete', 'pie' or 'box' |
| * line - Line chart. Options: |
| * spotColor - Set to '' to not end each line in a circular spot |
| * minSpotColor - If set, color of spot at minimum value |
| * maxSpotColor - If set, color of spot at maximum value |
| * spotRadius - Radius in pixels |
| * lineWidth - Width of line in pixels |
| * normalRangeMin |
| * normalRangeMax - If set draws a filled horizontal bar between these two values marking the "normal" |
| * or expected range of values |
| * normalRangeColor - Color to use for the above bar |
| * drawNormalOnTop - Draw the normal range above the chart fill color if true |
| * defaultPixelsPerValue - Defaults to 3 pixels of width for each value in the chart |
| * highlightSpotColor - The color to use for drawing a highlight spot on mouseover - Set to null to disable |
| * highlightLineColor - The color to use for drawing a highlight line on mouseover - Set to null to disable |
| * valueSpots - Specify which points to draw spots on, and in which color. Accepts a range map |
| * |
| * bar - Bar chart. Options: |
| * barColor - Color of bars for postive values |
| * negBarColor - Color of bars for negative values |
| * zeroColor - Color of bars with zero values |
| * nullColor - Color of bars with null values - Defaults to omitting the bar entirely |
| * barWidth - Width of bars in pixels |
| * colorMap - Optional mappnig of values to colors to override the *BarColor values above |
| * can be an Array of values to control the color of individual bars or a range map |
| * to specify colors for individual ranges of values |
| * barSpacing - Gap between bars in pixels |
| * zeroAxis - Centers the y-axis around zero if true |
| * |
| * tristate - Charts values of win (>0), lose (<0) or draw (=0) |
| * posBarColor - Color of win values |
| * negBarColor - Color of lose values |
| * zeroBarColor - Color of draw values |
| * barWidth - Width of bars in pixels |
| * barSpacing - Gap between bars in pixels |
| * colorMap - Optional mappnig of values to colors to override the *BarColor values above |
| * can be an Array of values to control the color of individual bars or a range map |
| * to specify colors for individual ranges of values |
| * |
| * discrete - Options: |
| * lineHeight - Height of each line in pixels - Defaults to 30% of the graph height |
| * thesholdValue - Values less than this value will be drawn using thresholdColor instead of lineColor |
| * thresholdColor |
| * |
| * bullet - Values for bullet graphs msut be in the order: target, performance, range1, range2, range3, ... |
| * options: |
| * targetColor - The color of the vertical target marker |
| * targetWidth - The width of the target marker in pixels |
| * performanceColor - The color of the performance measure horizontal bar |
| * rangeColors - Colors to use for each qualitative range background color |
| * |
| * pie - Pie chart. Options: |
| * sliceColors - An array of colors to use for pie slices |
| * offset - Angle in degrees to offset the first slice - Try -90 or +90 |
| * borderWidth - Width of border to draw around the pie chart, in pixels - Defaults to 0 (no border) |
| * borderColor - Color to use for the pie chart border - Defaults to #000 |
| * |
| * box - Box plot. Options: |
| * raw - Set to true to supply pre-computed plot points as values |
| * values should be: low_outlier, low_whisker, q1, median, q3, high_whisker, high_outlier |
| * When set to false you can supply any number of values and the box plot will |
| * be computed for you. Default is false. |
| * showOutliers - Set to true (default) to display outliers as circles |
| * outlierIRQ - Interquartile range used to determine outliers. Default 1.5 |
| * boxLineColor - Outline color of the box |
| * boxFillColor - Fill color for the box |
| * whiskerColor - Line color used for whiskers |
| * outlierLineColor - Outline color of outlier circles |
| * outlierFillColor - Fill color of the outlier circles |
| * spotRadius - Radius of outlier circles |
| * medianColor - Line color of the median line |
| * target - Draw a target cross hair at the supplied value (default undefined) |
| * |
| * |
| * |
| * Examples: |
| * $('#sparkline1').sparkline(myvalues, { lineColor: '#f00', fillColor: false }); |
| * $('.barsparks').sparkline('html', { type:'bar', height:'40px', barWidth:5 }); |
| * $('#tristate').sparkline([1,1,-1,1,0,0,-1], { type:'tristate' }): |
| * $('#discrete').sparkline([1,3,4,5,5,3,4,5], { type:'discrete' }); |
| * $('#bullet').sparkline([10,12,12,9,7], { type:'bullet' }); |
| * $('#pie').sparkline([1,1,2], { type:'pie' }); |
| */ |
| |
| /*jslint regexp: true, browser: true, jquery: true, white: true, nomen: false, plusplus: false, maxerr: 500, indent: 4 */ |
| |
| (function ($) { |
| 'use strict'; |
| |
| var UNSET_OPTION = {}, |
| getDefaults, createClass, SPFormat, clipval, quartile, normalizeValue, normalizeValues, |
| remove, isNumber, all, sum, addCSS, ensureArray, formatNumber, RangeMap, |
| MouseHandler, Tooltip, barHighlightMixin, |
| line, bar, tristate, discrete, bullet, pie, box, defaultStyles, initStyles, |
| VShape, VCanvas_base, VCanvas_canvas, VCanvas_vml, pending, shapeCount = 0; |
| |
| /** |
| * Default configuration settings |
| */ |
| getDefaults = function () { |
| return { |
| // Settings common to most/all chart types |
| common: { |
| type: 'line', |
| lineColor: '#00f', |
| fillColor: '#cdf', |
| defaultPixelsPerValue: 3, |
| width: 'auto', |
| height: 'auto', |
| composite: false, |
| tagValuesAttribute: 'values', |
| tagOptionsPrefix: 'spark', |
| enableTagOptions: false, |
| enableHighlight: true, |
| highlightLighten: 1.4, |
| tooltipSkipNull: true, |
| tooltipPrefix: '', |
| tooltipSuffix: '', |
| disableHiddenCheck: false, |
| numberFormatter: false, |
| numberDigitGroupCount: 3, |
| numberDigitGroupSep: ',', |
| numberDecimalMark: '.', |
| disableTooltips: false, |
| disableInteraction: false |
| }, |
| // Defaults for line charts |
| line: { |
| spotColor: '#f80', |
| highlightSpotColor: '#5f5', |
| highlightLineColor: '#f22', |
| spotRadius: 1.5, |
| minSpotColor: '#f80', |
| maxSpotColor: '#f80', |
| lineWidth: 1, |
| normalRangeMin: undefined, |
| normalRangeMax: undefined, |
| normalRangeColor: '#ccc', |
| drawNormalOnTop: false, |
| chartRangeMin: undefined, |
| chartRangeMax: undefined, |
| chartRangeMinX: undefined, |
| chartRangeMaxX: undefined, |
| tooltipFormat: new SPFormat('<span style="color: {{color}}">●</span> {{prefix}}{{y}}{{suffix}}') |
| }, |
| // Defaults for bar charts |
| bar: { |
| barColor: '#3366cc', |
| negBarColor: '#f44', |
| stackedBarColor: ['#3366cc', '#dc3912', '#ff9900', '#109618', '#66aa00', |
| '#dd4477', '#0099c6', '#990099'], |
| zeroColor: undefined, |
| nullColor: undefined, |
| zeroAxis: true, |
| barWidth: 4, |
| barSpacing: 1, |
| chartRangeMax: undefined, |
| chartRangeMin: undefined, |
| chartRangeClip: false, |
| colorMap: undefined, |
| tooltipFormat: new SPFormat('<span style="color: {{color}}">●</span> {{prefix}}{{value}}{{suffix}}') |
| }, |
| // Defaults for tristate charts |
| tristate: { |
| barWidth: 4, |
| barSpacing: 1, |
| posBarColor: '#6f6', |
| negBarColor: '#f44', |
| zeroBarColor: '#999', |
| colorMap: {}, |
| tooltipFormat: new SPFormat('<span style="color: {{color}}">●</span> {{value:map}}'), |
| tooltipValueLookups: { map: { '-1': 'Loss', '0': 'Draw', '1': 'Win' } } |
| }, |
| // Defaults for discrete charts |
| discrete: { |
| lineHeight: 'auto', |
| thresholdColor: undefined, |
| thresholdValue: 0, |
| chartRangeMax: undefined, |
| chartRangeMin: undefined, |
| chartRangeClip: false, |
| tooltipFormat: new SPFormat('{{prefix}}{{value}}{{suffix}}') |
| }, |
| // Defaults for bullet charts |
| bullet: { |
| targetColor: '#f33', |
| targetWidth: 3, // width of the target bar in pixels |
| performanceColor: '#33f', |
| rangeColors: ['#d3dafe', '#a8b6ff', '#7f94ff'], |
| base: undefined, // set this to a number to change the base start number |
| tooltipFormat: new SPFormat('{{fieldkey:fields}} - {{value}}'), |
| tooltipValueLookups: { fields: {r: 'Range', p: 'Performance', t: 'Target'} } |
| }, |
| // Defaults for pie charts |
| pie: { |
| offset: 0, |
| sliceColors: ['#3366cc', '#dc3912', '#ff9900', '#109618', '#66aa00', |
| '#dd4477', '#0099c6', '#990099'], |
| borderWidth: 0, |
| borderColor: '#000', |
| tooltipFormat: new SPFormat('<span style="color: {{color}}">●</span> {{value}} ({{percent.1}}%)') |
| }, |
| // Defaults for box plots |
| box: { |
| raw: false, |
| boxLineColor: '#000', |
| boxFillColor: '#cdf', |
| whiskerColor: '#000', |
| outlierLineColor: '#333', |
| outlierFillColor: '#fff', |
| medianColor: '#f00', |
| showOutliers: true, |
| outlierIQR: 1.5, |
| spotRadius: 1.5, |
| target: undefined, |
| targetColor: '#4a2', |
| chartRangeMax: undefined, |
| chartRangeMin: undefined, |
| tooltipFormat: new SPFormat('{{field:fields}}: {{value}}'), |
| tooltipFormatFieldlistKey: 'field', |
| tooltipValueLookups: { fields: { lq: 'Lower Quartile', med: 'Median', |
| uq: 'Upper Quartile', lo: 'Left Outlier', ro: 'Right Outlier', |
| lw: 'Left Whisker', rw: 'Right Whisker'} } |
| } |
| }; |
| }; |
| |
| // You can have tooltips use a css class other than jqstooltip by specifying tooltipClassname |
| defaultStyles = '.jqstooltip { ' + |
| 'position: absolute;' + |
| 'left: 0px;' + |
| 'top: 0px;' + |
| 'visibility: hidden;' + |
| 'background: rgb(0, 0, 0) transparent;' + |
| 'background-color: rgba(0,0,0,0.6);' + |
| 'filter:progid:DXImageTransform.Microsoft.gradient(startColorstr=#99000000, endColorstr=#99000000);' + |
| '-ms-filter: "progid:DXImageTransform.Microsoft.gradient(startColorstr=#99000000, endColorstr=#99000000)";' + |
| 'color: white;' + |
| 'font: 10px arial, san serif;' + |
| 'text-align: left;' + |
| 'white-space: nowrap;' + |
| 'padding: 5px;' + |
| 'border: 1px solid white;' + |
| '}' + |
| '.jqsfield { ' + |
| 'color: white;' + |
| 'font: 10px arial, san serif;' + |
| 'text-align: left;' + |
| '}'; |
| |
| initStyles = function() { |
| addCSS(defaultStyles); |
| }; |
| |
| $(initStyles); |
| |
| /** |
| * Utilities |
| */ |
| |
| createClass = function (/* [baseclass, [mixin, ...]], definition */) { |
| var Class, args; |
| Class = function () { |
| this.init.apply(this, arguments); |
| }; |
| if (arguments.length > 1) { |
| if (arguments[0]) { |
| Class.prototype = $.extend(new arguments[0](), arguments[arguments.length - 1]); |
| Class._super = arguments[0].prototype; |
| } else { |
| Class.prototype = arguments[arguments.length - 1]; |
| } |
| if (arguments.length > 2) { |
| args = Array.prototype.slice.call(arguments, 1, -1); |
| args.unshift(Class.prototype); |
| $.extend.apply($, args); |
| } |
| } else { |
| Class.prototype = arguments[0]; |
| } |
| Class.prototype.cls = Class; |
| return Class; |
| }; |
| |
| /** |
| * Wraps a format string for tooltips |
| * {{x}} |
| * {{x.2} |
| * {{x:months}} |
| */ |
| $.SPFormatClass = SPFormat = createClass({ |
| fre: /\{\{([\w.]+?)(:(.+?))?\}\}/g, |
| precre: /(\w+)\.(\d+)/, |
| |
| init: function (format, fclass) { |
| this.format = format; |
| this.fclass = fclass; |
| }, |
| |
| render: function (fieldset, lookups, options) { |
| var self = this, |
| fields = fieldset, |
| match, token, lookupkey, fieldvalue, prec; |
| return this.format.replace(this.fre, function () { |
| var lookup; |
| token = arguments[1]; |
| lookupkey = arguments[3]; |
| match = self.precre.exec(token); |
| if (match) { |
| prec = match[2]; |
| token = match[1]; |
| } else { |
| prec = false; |
| } |
| fieldvalue = fields[token]; |
| if (fieldvalue === undefined) { |
| return ''; |
| } |
| if (lookupkey && lookups && lookups[lookupkey]) { |
| lookup = lookups[lookupkey]; |
| if (lookup.get) { // RangeMap |
| return lookups[lookupkey].get(fieldvalue) || fieldvalue; |
| } else { |
| return lookups[lookupkey][fieldvalue] || fieldvalue; |
| } |
| } |
| if (isNumber(fieldvalue)) { |
| if (options.get('numberFormatter')) { |
| fieldvalue = options.get('numberFormatter')(fieldvalue); |
| } else { |
| fieldvalue = formatNumber(fieldvalue, prec, |
| options.get('numberDigitGroupCount'), |
| options.get('numberDigitGroupSep'), |
| options.get('numberDecimalMark')); |
| } |
| } |
| return fieldvalue; |
| }); |
| } |
| }); |
| |
| // convience method to avoid needing the new operator |
| $.spformat = function(format, fclass) { |
| return new SPFormat(format, fclass); |
| }; |
| |
| clipval = function (val, min, max) { |
| if (val < min) { |
| return min; |
| } |
| if (val > max) { |
| return max; |
| } |
| return val; |
| }; |
| |
| quartile = function (values, q) { |
| var vl; |
| if (q === 2) { |
| vl = Math.floor(values.length / 2); |
| return values.length % 2 ? values[vl] : (values[vl] + values[vl + 1]) / 2; |
| } else { |
| vl = Math.floor(values.length / 4); |
| return values.length % 2 ? (values[vl * q] + values[vl * q + 1]) / 2 : values[vl * q]; |
| } |
| }; |
| |
| normalizeValue = function (val) { |
| var nf; |
| switch (val) { |
| case 'undefined': |
| val = undefined; |
| break; |
| case 'null': |
| val = null; |
| break; |
| case 'true': |
| val = true; |
| break; |
| case 'false': |
| val = false; |
| break; |
| default: |
| nf = parseFloat(val); |
| if (val == nf) { |
| val = nf; |
| } |
| } |
| return val; |
| }; |
| |
| normalizeValues = function (vals) { |
| var i, result = []; |
| for (i = vals.length; i--;) { |
| result[i] = normalizeValue(vals[i]); |
| } |
| return result; |
| }; |
| |
| remove = function (vals, filter) { |
| var i, vl, result = []; |
| for (i = 0, vl = vals.length; i < vl; i++) { |
| if (vals[i] !== filter) { |
| result.push(vals[i]); |
| } |
| } |
| return result; |
| }; |
| |
| isNumber = function (num) { |
| return !isNaN(parseFloat(num)) && isFinite(num); |
| }; |
| |
| formatNumber = function (num, prec, groupsize, groupsep, decsep) { |
| var p, i; |
| num = (prec === false ? parseFloat(num).toString() : num.toFixed(prec)).split(''); |
| p = (p = $.inArray('.', num)) < 0 ? num.length : p; |
| if (p < num.length) { |
| num[p] = decsep; |
| } |
| for (i = p - groupsize; i > 0; i -= groupsize) { |
| num.splice(i, 0, groupsep); |
| } |
| return num.join(''); |
| }; |
| |
| // determine if all values of an array match a value |
| // returns true if the array is empty |
| all = function (val, arr, ignoreNull) { |
| var i; |
| for (i = arr.length; i--; ) { |
| if (arr[i] !== val || (!ignoreNull && val === null)) { |
| return false; |
| } |
| } |
| return true; |
| }; |
| |
| // sums the numeric values in an array, ignoring other values |
| sum = function (vals) { |
| var total = 0, i; |
| for (i = vals.length; i--;) { |
| total += typeof vals[i] === 'number' ? vals[i] : 0; |
| } |
| return total; |
| }; |
| |
| ensureArray = function (val) { |
| return $.isArray(val) ? val : [val]; |
| }; |
| |
| // http://paulirish.com/2008/bookmarklet-inject-new-css-rules/ |
| addCSS = function(css) { |
| var tag; |
| //if ('\v' == 'v') /* ie only */ { |
| if (document.createStyleSheet) { |
| document.createStyleSheet().cssText = css; |
| } else { |
| tag = document.createElement('style'); |
| tag.type = 'text/css'; |
| document.getElementsByTagName('head')[0].appendChild(tag); |
| tag[(typeof document.body.style.WebkitAppearance == 'string') /* webkit only */ ? 'innerText' : 'innerHTML'] = css; |
| } |
| }; |
| |
| // Provide a cross-browser interface to a few simple drawing primitives |
| $.fn.simpledraw = function (width, height, useExisting, interact) { |
| var target, mhandler; |
| if (useExisting && (target = this.data('_jqs_vcanvas'))) { |
| return target; |
| } |
| if (width === undefined) { |
| width = $(this).innerWidth(); |
| } |
| if (height === undefined) { |
| height = $(this).innerHeight(); |
| } |
| if ($.browser.hasCanvas) { |
| target = new VCanvas_canvas(width, height, this, interact); |
| } else if ($.browser.msie) { |
| target = new VCanvas_vml(width, height, this); |
| } else { |
| return false; |
| } |
| mhandler = $(this).data('_jqs_mhandler'); |
| if (mhandler) { |
| mhandler.registerCanvas(target); |
| } |
| return target; |
| }; |
| |
| $.fn.cleardraw = function () { |
| var target = this.data('_jqs_vcanvas'); |
| if (target) { |
| target.reset(); |
| } |
| }; |
| |
| $.RangeMapClass = RangeMap = createClass({ |
| init: function (map) { |
| var key, range, rangelist = []; |
| for (key in map) { |
| if (map.hasOwnProperty(key) && typeof key === 'string' && key.indexOf(':') > -1) { |
| range = key.split(':'); |
| range[0] = range[0].length === 0 ? -Infinity : parseFloat(range[0]); |
| range[1] = range[1].length === 0 ? Infinity : parseFloat(range[1]); |
| range[2] = map[key]; |
| rangelist.push(range); |
| } |
| } |
| this.map = map; |
| this.rangelist = rangelist || false; |
| }, |
| |
| get: function (value) { |
| var rangelist = this.rangelist, |
| i, range, result; |
| if ((result = this.map[value]) !== undefined) { |
| return result; |
| } |
| if (rangelist) { |
| for (i = rangelist.length; i--;) { |
| range = rangelist[i]; |
| if (range[0] <= value && range[1] >= value) { |
| return range[2]; |
| } |
| } |
| } |
| return undefined; |
| } |
| }); |
| |
| // Convenience function |
| $.range_map = function(map) { |
| return new RangeMap(map); |
| }; |
| |
| MouseHandler = createClass({ |
| init: function (el, options) { |
| var $el = $(el); |
| this.$el = $el; |
| this.options = options; |
| this.currentPageX = 0; |
| this.currentPageY = 0; |
| this.el = el; |
| this.splist = []; |
| this.tooltip = null; |
| this.over = false; |
| this.displayTooltips = !options.get('disableTooltips'); |
| this.highlightEnabled = !options.get('disableHighlight'); |
| }, |
| |
| registerSparkline: function (sp) { |
| this.splist.push(sp); |
| if (this.over) { |
| this.updateDisplay(); |
| } |
| }, |
| |
| registerCanvas: function (canvas) { |
| var $canvas = $(canvas.canvas); |
| this.canvas = canvas; |
| this.$canvas = $canvas; |
| $canvas.mouseenter($.proxy(this.mouseenter, this)); |
| $canvas.mouseleave($.proxy(this.mouseleave, this)); |
| $canvas.click($.proxy(this.mouseclick, this)); |
| }, |
| |
| reset: function (removeTooltip) { |
| this.splist = []; |
| if (this.tooltip && removeTooltip) { |
| this.tooltip.remove(); |
| this.tooltip = undefined; |
| } |
| }, |
| |
| mouseclick: function (e) { |
| var clickEvent = $.Event('sparklineClick'); |
| clickEvent.originalEvent = e; |
| clickEvent.sparklines = this.splist; |
| this.$el.trigger(clickEvent); |
| }, |
| |
| mouseenter: function (e) { |
| $(document.body).unbind('mousemove.jqs'); |
| $(document.body).bind('mousemove.jqs', $.proxy(this.mousemove, this)); |
| this.over = true; |
| this.currentPageX = e.pageX; |
| this.currentPageY = e.pageY; |
| this.currentEl = e.target; |
| if (!this.tooltip && this.displayTooltips) { |
| this.tooltip = new Tooltip(this.options); |
| this.tooltip.updatePosition(e.pageX, e.pageY); |
| } |
| this.updateDisplay(); |
| }, |
| |
| mouseleave: function () { |
| $(document.body).unbind('mousemove.jqs'); |
| var splist = this.splist, |
| spcount = splist.length, |
| needsRefresh = false, |
| sp, i; |
| this.over = false; |
| this.currentEl = null; |
| |
| if (this.tooltip) { |
| this.tooltip.remove(); |
| this.tooltip = null; |
| } |
| |
| for (i = 0; i < spcount; i++) { |
| sp = splist[i]; |
| if (sp.clearRegionHighlight()) { |
| needsRefresh = true; |
| } |
| } |
| |
| if (needsRefresh) { |
| this.canvas.render(); |
| } |
| }, |
| |
| mousemove: function (e) { |
| this.currentPageX = e.pageX; |
| this.currentPageY = e.pageY; |
| this.currentEl = e.target; |
| if (this.tooltip) { |
| this.tooltip.updatePosition(e.pageX, e.pageY); |
| } |
| this.updateDisplay(); |
| }, |
| |
| updateDisplay: function () { |
| var splist = this.splist, |
| spcount = splist.length, |
| needsRefresh = false, |
| offset = this.$canvas.offset(), |
| localX = this.currentPageX - offset.left, |
| localY = this.currentPageY - offset.top, |
| tooltiphtml, sp, i, result, changeEvent; |
| if (!this.over) { |
| return; |
| } |
| for (i = 0; i < spcount; i++) { |
| sp = splist[i]; |
| result = sp.setRegionHighlight(this.currentEl, localX, localY); |
| if (result) { |
| needsRefresh = true; |
| } |
| } |
| if (needsRefresh) { |
| changeEvent = $.Event('sparklineRegionChange'); |
| changeEvent.sparklines = this.splist; |
| this.$el.trigger(changeEvent); |
| if (this.tooltip) { |
| tooltiphtml = ''; |
| for (i = 0; i < spcount; i++) { |
| sp = splist[i]; |
| tooltiphtml += sp.getCurrentRegionTooltip(); |
| } |
| this.tooltip.setContent(tooltiphtml); |
| } |
| if (!this.disableHighlight) { |
| this.canvas.render(); |
| } |
| } |
| if (result === null) { |
| this.mouseleave(); |
| } |
| } |
| }); |
| |
| |
| Tooltip = createClass({ |
| sizeStyle: 'position: static !important;' + |
| 'display: block !important;' + |
| 'visibility: hidden !important;' + |
| 'float: left !important;', |
| |
| init: function (options) { |
| var tooltipClassname = options.get('tooltipClassname', 'jqstooltip'), |
| sizetipStyle = this.sizeStyle, |
| offset; |
| this.container = options.get('tooltipContainer') || document.body; |
| this.tooltipOffsetX = options.get('tooltipOffsetX', 10); |
| this.tooltipOffsetY = options.get('tooltipOffsetY', 12); |
| // remove any previous lingering tooltip |
| $('#jqssizetip').remove(); |
| $('#jqstooltip').remove(); |
| this.sizetip = $('<div/>', { |
| id: 'jqssizetip', |
| style: sizetipStyle, |
| 'class': tooltipClassname |
| }); |
| this.tooltip = $('<div/>', { |
| id: 'jqstooltip', |
| 'class': tooltipClassname |
| }).appendTo(this.container); |
| // account for the container's location |
| offset = this.tooltip.offset(); |
| this.offsetLeft = offset.left; |
| this.offsetTop = offset.top; |
| this.hidden = true; |
| $(window).unbind('resize.jqs scroll.jqs'); |
| $(window).bind('resize.jqs scroll.jqs', $.proxy(this.updateWindowDims, this)); |
| this.updateWindowDims(); |
| }, |
| |
| updateWindowDims: function () { |
| this.scrollTop = $(window).scrollTop(); |
| this.scrollLeft = $(window).scrollLeft(); |
| this.scrollRight = this.scrollLeft + $(window).width(); |
| this.updatePosition(); |
| }, |
| |
| getSize: function (content) { |
| this.sizetip.html(content).appendTo(this.container); |
| this.width = this.sizetip.width() + 1; |
| this.height = this.sizetip.height(); |
| this.sizetip.remove(); |
| }, |
| |
| setContent: function (content) { |
| if (!content) { |
| this.tooltip.css('visibility', 'hidden'); |
| this.hidden = true; |
| return; |
| } |
| this.getSize(content); |
| this.tooltip.html(content) |
| .css({ |
| 'width': this.width, |
| 'height': this.height, |
| 'visibility': 'visible' |
| }); |
| if (this.hidden) { |
| this.hidden = false; |
| this.updatePosition(); |
| } |
| }, |
| |
| updatePosition: function (x, y) { |
| if (x === undefined) { |
| if (this.mousex === undefined) { |
| return; |
| } |
| x = this.mousex - this.offsetLeft; |
| y = this.mousey - this.offsetTop; |
| |
| } else { |
| this.mousex = x = x - this.offsetLeft; |
| this.mousey = y = y - this.offsetTop; |
| } |
| if (!this.height || !this.width || this.hidden) { |
| return; |
| } |
| |
| y -= this.height + this.tooltipOffsetY; |
| x += this.tooltipOffsetX; |
| |
| if (y < this.scrollTop) { |
| y = this.scrollTop; |
| } |
| if (x < this.scrollLeft) { |
| x = this.scrollLeft; |
| } else if (x + this.width > this.scrollRight) { |
| x = this.scrollRight - this.width; |
| } |
| |
| this.tooltip.css({ |
| 'left': x, |
| 'top': y |
| }); |
| }, |
| |
| remove: function () { |
| this.tooltip.remove(); |
| this.sizetip.remove(); |
| this.sizetip = this.tooltip = undefined; |
| $(window).unbind('resize.jqs scroll.jqs'); |
| } |
| }); |
| |
| pending = []; |
| $.fn.sparkline = function (userValues, userOptions) { |
| return this.each(function () { |
| var options = new $.fn.sparkline.options(this, userOptions), |
| $this = $(this), |
| render, i; |
| render = function () { |
| var values, width, height, tmp, mhandler, sp, vals; |
| if (userValues === 'html' || userValues === undefined) { |
| vals = this.getAttribute(options.get('tagValuesAttribute')); |
| if (vals === undefined || vals === null) { |
| vals = $this.html(); |
| } |
| values = vals.replace(/(^\s*<!--)|(-->\s*$)|\s+/g, '').split(','); |
| } else { |
| values = userValues; |
| } |
| |
| width = options.get('width') === 'auto' ? values.length * options.get('defaultPixelsPerValue') : options.get('width'); |
| if (options.get('height') === 'auto') { |
| if (!options.get('composite') || !$.data(this, '_jqs_vcanvas')) { |
| // must be a better way to get the line height |
| tmp = document.createElement('span'); |
| tmp.innerHTML = 'a'; |
| $this.html(tmp); |
| height = $(tmp).innerHeight() || $(tmp).height(); |
| $(tmp).remove(); |
| tmp = null; |
| } |
| } else { |
| height = options.get('height'); |
| } |
| |
| if (!options.get('disableInteraction')) { |
| mhandler = $.data(this, '_jqs_mhandler'); |
| if (!mhandler) { |
| mhandler = new MouseHandler(this, options); |
| $.data(this, '_jqs_mhandler', mhandler); |
| } else if (!options.get('composite')) { |
| mhandler.reset(); |
| } |
| } else { |
| mhandler = false; |
| } |
| |
| if (options.get('composite') && !$.data(this, '_jqs_vcanvas')) { |
| if (!$.data(this, '_jqs_errnotify')) { |
| alert('Attempted to attach a composite sparkline to an element with no existing sparkline'); |
| $.data(this, '_jqs_errnotify', true); |
| } |
| return; |
| } |
| |
| sp = new $.fn.sparkline[options.get('type')](this, values, options, width, height); |
| |
| sp.render(); |
| |
| if (mhandler) { |
| mhandler.registerSparkline(sp); |
| } |
| }; |
| // jQuery 1.3.0 completely changed the meaning of :hidden :-/ |
| if (($(this).html() && !options.get('disableHiddenCheck') && $(this).is(':hidden')) || ($.fn.jquery < '1.3.0' && $(this).parents().is(':hidden')) || !$(this).parents('body').length) { |
| if (!options.get('composite') && $.data(this, '_jqs_pending')) { |
| // remove any existing references to the element |
| for (i = pending.length; i; i--) { |
| if (pending[i - 1][0] == this) { |
| pending.splice(i - 1, 1); |
| } |
| } |
| } |
| pending.push([this, render]); |
| $.data(this, '_jqs_pending', true); |
| } else { |
| render.call(this); |
| } |
| }); |
| }; |
| |
| $.fn.sparkline.defaults = getDefaults(); |
| |
| |
| $.sparkline_display_visible = function () { |
| var el, i, pl; |
| var done = []; |
| for (i = 0, pl = pending.length; i < pl; i++) { |
| el = pending[i][0]; |
| if ($(el).is(':visible') && !$(el).parents().is(':hidden')) { |
| pending[i][1].call(el); |
| $.data(pending[i][0], '_jqs_pending', false); |
| done.push(i); |
| } else if (!$(el).closest('html').length && !$.data(el, '_jqs_pending')) { |
| // element has been inserted and removed from the DOM |
| // If it was not yet inserted into the dom then the .data request |
| // will return true. |
| // removing from the dom causes the data to be removed. |
| $.data(pending[i][0], '_jqs_pending', false); |
| done.push(i); |
| } |
| } |
| for (i = done.length; i; i--) { |
| pending.splice(done[i - 1], 1); |
| } |
| }; |
| |
| |
| /** |
| * User option handler |
| */ |
| $.fn.sparkline.options = createClass({ |
| init: function (tag, userOptions) { |
| var extendedOptions, defaults, base, tagOptionType; |
| this.userOptions = userOptions = userOptions || {}; |
| this.tag = tag; |
| this.tagValCache = {}; |
| defaults = $.fn.sparkline.defaults; |
| base = defaults.common; |
| this.tagOptionsPrefix = userOptions.enableTagOptions && (userOptions.tagOptionsPrefix || base.tagOptionsPrefix); |
| |
| tagOptionType = this.getTagSetting('type'); |
| if (tagOptionType === UNSET_OPTION) { |
| extendedOptions = defaults[userOptions.type || base.type]; |
| } else { |
| extendedOptions = defaults[tagOptionType]; |
| } |
| this.mergedOptions = $.extend({}, base, extendedOptions, userOptions); |
| }, |
| |
| |
| getTagSetting: function (key) { |
| var prefix = this.tagOptionsPrefix, |
| val, i, pairs, keyval; |
| if (prefix === false || prefix === undefined) { |
| return UNSET_OPTION; |
| } |
| if (this.tagValCache.hasOwnProperty(key)) { |
| val = this.tagValCache.key; |
| } else { |
| val = this.tag.getAttribute(prefix + key); |
| if (val === undefined || val === null) { |
| val = UNSET_OPTION; |
| } else if (val.substr(0, 1) === '[') { |
| val = val.substr(1, val.length - 2).split(','); |
| for (i = val.length; i--;) { |
| val[i] = normalizeValue(val[i].replace(/(^\s*)|(\s*$)/g, '')); |
| } |
| } else if (val.substr(0, 1) === '{') { |
| pairs = val.substr(1, val.length - 2).split(','); |
| val = {}; |
| for (i = pairs.length; i--;) { |
| keyval = pairs[i].split(':', 2); |
| val[keyval[0].replace(/(^\s*)|(\s*$)/g, '')] = normalizeValue(keyval[1].replace(/(^\s*)|(\s*$)/g, '')); |
| } |
| } else { |
| val = normalizeValue(val); |
| } |
| this.tagValCache.key = val; |
| } |
| return val; |
| }, |
| |
| get: function (key, defaultval) { |
| var tagOption = this.getTagSetting(key), |
| result; |
| if (tagOption !== UNSET_OPTION) { |
| return tagOption; |
| } |
| return (result = this.mergedOptions[key]) === undefined ? defaultval : result; |
| } |
| }); |
| |
| |
| $.fn.sparkline._base = createClass({ |
| disabled: false, |
| |
| init: function (el, values, options, width, height) { |
| this.el = el; |
| this.$el = $(el); |
| this.values = values; |
| this.options = options; |
| this.width = width; |
| this.height = height; |
| this.currentRegion = undefined; |
| }, |
| |
| /** |
| * Setup the canvas |
| */ |
| initTarget: function () { |
| var interactive = !this.options.get('disableInteraction'); |
| if (!(this.target = this.$el.simpledraw(this.width, this.height, this.options.get('composite'), interactive))) { |
| this.disabled = true; |
| } else { |
| this.canvasWidth = this.target.pixelWidth; |
| this.canvasHeight = this.target.pixelHeight; |
| } |
| }, |
| |
| /** |
| * Actually render the chart to the canvas |
| */ |
| render: function () { |
| if (this.disabled) { |
| this.el.innerHTML = ''; |
| return false; |
| } |
| return true; |
| }, |
| |
| /** |
| * Return a region id for a given x/y co-ordinate |
| */ |
| getRegion: function (x, y) { |
| }, |
| |
| /** |
| * Highlight an item based on the moused-over x,y co-ordinate |
| */ |
| setRegionHighlight: function (el, x, y) { |
| var currentRegion = this.currentRegion, |
| highlightEnabled = !this.options.get('disableHighlight'), |
| newRegion; |
| if (x > this.canvasWidth || y > this.canvasHeight || x < 0 || y < 0) { |
| return null; |
| } |
| newRegion = this.getRegion(el, x, y); |
| if (currentRegion !== newRegion) { |
| if (currentRegion !== undefined && highlightEnabled) { |
| this.removeHighlight(); |
| } |
| this.currentRegion = newRegion; |
| if (newRegion !== undefined && highlightEnabled) { |
| this.renderHighlight(); |
| } |
| return true; |
| } |
| return false; |
| }, |
| |
| /** |
| * Reset any currently highlighted item |
| */ |
| clearRegionHighlight: function () { |
| if (this.currentRegion !== undefined) { |
| this.removeHighlight(); |
| this.currentRegion = undefined; |
| return true; |
| } |
| return false; |
| }, |
| |
| renderHighlight: function () { |
| this.changeHighlight(true); |
| }, |
| |
| removeHighlight: function () { |
| this.changeHighlight(false); |
| }, |
| |
| changeHighlight: function (highlight) {}, |
| |
| /** |
| * Fetch the HTML to display as a tooltip |
| */ |
| getCurrentRegionTooltip: function () { |
| var options = this.options, |
| header = '', |
| entries = [], |
| fields, formats, formatlen, fclass, text, i, |
| showFields, showFieldsKey, newFields, fv, |
| formatter, format, fieldlen, j; |
| if (this.currentRegion === undefined) { |
| return ''; |
| } |
| fields = this.getCurrentRegionFields(); |
| formatter = options.get('tooltipFormatter'); |
| if (formatter) { |
| return formatter(this, options, fields); |
| } |
| if (options.get('tooltipChartTitle')) { |
| header += '<div class="jqs jqstitle">' + options.get('tooltipChartTitle') + '</div>\n'; |
| } |
| formats = this.options.get('tooltipFormat'); |
| if (!formats) { |
| return ''; |
| } |
| if (!$.isArray(formats)) { |
| formats = [formats]; |
| } |
| if (!$.isArray(fields)) { |
| fields = [fields]; |
| } |
| showFields = this.options.get('tooltipFormatFieldlist'); |
| showFieldsKey = this.options.get('tooltipFormatFieldlistKey'); |
| if (showFields && showFieldsKey) { |
| // user-selected ordering of fields |
| newFields = []; |
| for (i = fields.length; i--;) { |
| fv = fields[i][showFieldsKey]; |
| if ((j = $.inArray(fv, showFields)) != -1) { |
| newFields[j] = fields[i]; |
| } |
| } |
| fields = newFields; |
| } |
| formatlen = formats.length; |
| fieldlen = fields.length; |
| for (i = 0; i < formatlen; i++) { |
| format = formats[i]; |
| if (typeof format === 'string') { |
| format = new SPFormat(format); |
| } |
| fclass = format.fclass || 'jqsfield'; |
| for (j = 0; j < fieldlen; j++) { |
| if (!fields[j].isNull || !options.get('tooltipSkipNull')) { |
| $.extend(fields[j], { |
| prefix: options.get('tooltipPrefix'), |
| suffix: options.get('tooltipSuffix') |
| }); |
| text = format.render(fields[j], options.get('tooltipValueLookups'), options); |
| entries.push('<div class="' + fclass + '">' + text + '</div>'); |
| } |
| } |
| } |
| if (entries.length) { |
| return header + entries.join('\n'); |
| } |
| return ''; |
| }, |
| |
| getCurrentRegionFields: function () {}, |
| |
| calcHighlightColor: function (color, options) { |
| var highlightColor = options.get('highlightColor'), |
| lighten = options.get('highlightLighten'), |
| parse, mult, rgbnew, i; |
| if (highlightColor) { |
| return highlightColor; |
| } |
| if (lighten) { |
| // extract RGB values |
| 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); |
| if (parse) { |
| rgbnew = []; |
| mult = color.length === 4 ? 16 : 1; |
| for (i = 0; i < 3; i++) { |
| rgbnew[i] = clipval(Math.round(parseInt(parse[i + 1], 16) * mult * lighten), 0, 255); |
| } |
| return 'rgb(' + rgbnew.join(',') + ')'; |
| } |
| |
| } |
| return color; |
| } |
| |
| }); |
| |
| barHighlightMixin = { |
| changeHighlight: function (highlight) { |
| var currentRegion = this.currentRegion, |
| target = this.target, |
| shapeids = this.regionShapes[currentRegion], |
| newShapes; |
| // will be null if the region value was null |
| if (shapeids) { |
| newShapes = this.renderRegion(currentRegion, highlight); |
| if ($.isArray(newShapes) || $.isArray(shapeids)) { |
| target.replaceWithShapes(shapeids, newShapes); |
| this.regionShapes[currentRegion] = $.map(newShapes, function (newShape) { |
| return newShape.id; |
| }); |
| } else { |
| target.replaceWithShape(shapeids, newShapes); |
| this.regionShapes[currentRegion] = newShapes.id; |
| } |
| } |
| }, |
| |
| render: function () { |
| var values = this.values, |
| target = this.target, |
| regionShapes = this.regionShapes, |
| shapes, ids, i, j; |
| |
| if (!this.cls._super.render.call(this)) { |
| return; |
| } |
| for (i = values.length; i--;) { |
| shapes = this.renderRegion(i); |
| if (shapes) { |
| if ($.isArray(shapes)) { |
| ids = []; |
| for (j = shapes.length; j--;) { |
| shapes[j].append(); |
| ids.push(shapes[j].id); |
| } |
| regionShapes[i] = ids; |
| } else { |
| shapes.append(); |
| regionShapes[i] = shapes.id; // store just the shapeid |
| } |
| } else { |
| // null value |
| regionShapes[i] = null; |
| } |
| } |
| target.render(); |
| } |
| }; |
| |
| /** |
| * Line charts |
| */ |
| $.fn.sparkline.line = line = createClass($.fn.sparkline._base, { |
| type: 'line', |
| |
| init: function (el, values, options, width, height) { |
| line._super.init.call(this, el, values, options, width, height); |
| this.vertices = []; |
| this.regionMap = []; |
| this.xvalues = []; |
| this.yvalues = []; |
| this.yminmax = []; |
| this.hightlightSpotId = null; |
| this.lastShapeId = null; |
| this.initTarget(); |
| }, |
| |
| getRegion: function (el, x, y) { |
| var i, |
| regionMap = this.regionMap; // maps regions to value positions |
| for (i = regionMap.length; i--;) { |
| if (regionMap[i] !== null && x >= regionMap[i][0] && x <= regionMap[i][1]) { |
| return regionMap[i][2]; |
| } |
| } |
| return undefined; |
| }, |
| |
| getCurrentRegionFields: function () { |
| var currentRegion = this.currentRegion; |
| return { |
| isNull: this.yvalues[currentRegion] === null, |
| x: this.xvalues[currentRegion], |
| y: this.yvalues[currentRegion], |
| color: this.options.get('lineColor'), |
| fillColor: this.options.get('fillColor'), |
| offset: currentRegion |
| }; |
| }, |
| |
| renderHighlight: function () { |
| var currentRegion = this.currentRegion, |
| target = this.target, |
| vertex = this.vertices[currentRegion], |
| options = this.options, |
| spotRadius = options.get('spotRadius'), |
| highlightSpotColor = options.get('highlightSpotColor'), |
| highlightLineColor = options.get('highlightLineColor'), |
| highlightSpot, highlightLine; |
| |
| if (!vertex) { |
| return; |
| } |
| if (spotRadius && highlightSpotColor) { |
| highlightSpot = target.drawCircle(vertex[0], vertex[1], |
| spotRadius, undefined, highlightSpotColor); |
| this.highlightSpotId = highlightSpot.id; |
| target.insertAfterShape(this.lastShapeId, highlightSpot); |
| } |
| if (highlightLineColor) { |
| highlightLine = target.drawLine(vertex[0], this.canvasTop, vertex[0], |
| this.canvasTop + this.canvasHeight, highlightLineColor); |
| this.highlightLineId = highlightLine.id; |
| target.insertAfterShape(this.lastShapeId, highlightLine); |
| } |
| }, |
| |
| removeHighlight: function () { |
| var target = this.target; |
| if (this.highlightSpotId) { |
| target.removeShapeId(this.highlightSpotId); |
| this.highlightSpotId = null; |
| } |
| if (this.highlightLineId) { |
| target.removeShapeId(this.highlightLineId); |
| this.highlightLineId = null; |
| } |
| }, |
| |
| scanValues: function () { |
| var values = this.values, |
| valcount = values.length, |
| xvalues = this.xvalues, |
| yvalues = this.yvalues, |
| yminmax = this.yminmax, |
| i, val, isStr, isArray, sp; |
| for (i = 0; i < valcount; i++) { |
| val = values[i]; |
| isStr = typeof(values[i]) === 'string'; |
| isArray = typeof(values[i]) === 'object' && values[i] instanceof Array; |
| sp = isStr && values[i].split(':'); |
| if (isStr && sp.length === 2) { // x:y |
| xvalues.push(Number(sp[0])); |
| yvalues.push(Number(sp[1])); |
| yminmax.push(Number(sp[1])); |
| } else if (isArray) { |
| xvalues.push(val[0]); |
| yvalues.push(val[1]); |
| yminmax.push(val[1]); |
| } else { |
| xvalues.push(i); |
| if (values[i] === null || values[i] === 'null') { |
| yvalues.push(null); |
| } else { |
| yvalues.push(Number(val)); |
| yminmax.push(Number(val)); |
| } |
| } |
| } |
| if (this.options.get('xvalues')) { |
| xvalues = this.options.get('xvalues'); |
| } |
| |
| this.maxy = this.maxyorg = Math.max.apply(Math, yminmax); |
| this.miny = this.minyorg = Math.min.apply(Math, yminmax); |
| |
| this.maxx = Math.max.apply(Math, xvalues); |
| this.minx = Math.min.apply(Math, xvalues); |
| |
| this.xvalues = xvalues; |
| this.yvalues = yvalues; |
| this.yminmax = yminmax; |
| |
| }, |
| |
| processRangeOptions: function () { |
| var options = this.options, |
| normalRangeMin = options.get('normalRangeMin'), |
| normalRangeMax = options.get('normalRangeMax'); |
| |
| if (normalRangeMin !== undefined) { |
| if (normalRangeMin < this.miny) { |
| this.miny = normalRangeMin; |
| } |
| if (normalRangeMax > this.maxy) { |
| this.maxy = normalRangeMax; |
| } |
| } |
| if (options.get('chartRangeMin') !== undefined && (options.get('chartRangeClip') || options.get('chartRangeMin') < this.miny)) { |
| this.miny = options.get('chartRangeMin'); |
| } |
| if (options.get('chartRangeMax') !== undefined && (options.get('chartRangeClip') || options.get('chartRangeMax') > this.maxy)) { |
| this.maxy = options.get('chartRangeMax'); |
| } |
| if (options.get('chartRangeMinX') !== undefined && (options.get('chartRangeClipX') || options.get('chartRangeMinX') < this.minx)) { |
| this.minx = options.get('chartRangeMinX'); |
| } |
| if (options.get('chartRangeMaxX') !== undefined && (options.get('chartRangeClipX') || options.get('chartRangeMaxX') > this.maxx)) { |
| this.maxx = options.get('chartRangeMaxX'); |
| } |
| |
| }, |
| |
| drawNormalRange: function (canvasLeft, canvasTop, canvasHeight, canvasWidth, rangey) { |
| var normalRangeMin = this.options.get('normalRangeMin'), |
| normalRangeMax = this.options.get('normalRangeMax'), |
| ytop = canvasTop + Math.round(canvasHeight - (canvasHeight * ((normalRangeMax - this.miny) / rangey))), |
| height = Math.round((canvasHeight * (normalRangeMax - normalRangeMin)) / rangey); |
| this.target.drawRect(canvasLeft, ytop, canvasWidth, height, undefined, this.options.get('normalRangeColor')).append(); |
| }, |
| |
| render: function () { |
| var options = this.options, |
| target = this.target, |
| canvasWidth = this.canvasWidth, |
| canvasHeight = this.canvasHeight, |
| vertices = this.vertices, |
| spotRadius = options.get('spotRadius'), |
| regionMap = this.regionMap, |
| rangex, rangey, yvallast, |
| canvasTop, canvasLeft, |
| vertex, path, paths, x, y, xnext, xpos, xposnext, |
| last, next, yvalcount, lineShapes, fillShapes, plen, |
| valueSpots, color, xvalues, yvalues, i; |
| |
| if (!line._super.render.call(this)) { |
| return; |
| } |
| |
| this.scanValues(); |
| this.processRangeOptions(); |
| |
| xvalues = this.xvalues; |
| yvalues = this.yvalues; |
| |
| if (!this.yminmax.length || this.yvalues.length < 2) { |
| // empty or all null valuess |
| return; |
| } |
| |
| canvasTop = canvasLeft = 0; |
| |
| rangex = this.maxx - this.minx === 0 ? 1 : this.maxx - this.minx; |
| rangey = this.maxy - this.miny === 0 ? 1 : this.maxy - this.miny; |
| yvallast = this.yvalues.length - 1; |
| |
| if (spotRadius && (canvasWidth < (spotRadius * 4) || canvasHeight < (spotRadius * 4))) { |
| spotRadius = 0; |
| } |
| if (spotRadius) { |
| // adjust the canvas size as required so that spots will fit |
| if (options.get('minSpotColor') || (options.get('spotColor') && yvalues[yvallast] === this.miny)) { |
| canvasHeight -= Math.ceil(spotRadius); |
| } |
| if (options.get('maxSpotColor') || (options.get('spotColor') && yvalues[yvallast] === this.maxy)) { |
| canvasHeight -= Math.ceil(spotRadius); |
| canvasTop += Math.ceil(spotRadius); |
| } |
| if ((options.get('minSpotColor') || options.get('maxSpotColor')) && (yvalues[0] === this.miny || yvalues[0] === this.maxy)) { |
| canvasLeft += Math.ceil(spotRadius); |
| canvasWidth -= Math.ceil(spotRadius); |
| } |
| if (options.get('spotColor') || |
| (options.get('minSpotColor') || options.get('maxSpotColor') && |
| (yvalues[yvallast] === this.miny || yvalues[yvallast] === this.maxy))) { |
| canvasWidth -= Math.ceil(spotRadius); |
| } |
| } |
| |
| |
| canvasHeight--; |
| |
| if (options.get('normalRangeMin') && !options.get('drawNormalOnTop')) { |
| this.drawNormalRange(canvasLeft, canvasTop, canvasHeight, canvasWidth, rangey); |
| } |
| |
| path = []; |
| paths = [path]; |
| last = next = null; |
| yvalcount = yvalues.length; |
| for (i = 0; i < yvalcount; i++) { |
| x = xvalues[i]; |
| xnext = xvalues[i + 1]; |
| y = yvalues[i]; |
| xpos = canvasLeft + Math.round((x - this.minx) * (canvasWidth / rangex)); |
| xposnext = i < yvalcount - 1 ? canvasLeft + Math.round((xnext - this.minx) * (canvasWidth / rangex)) : canvasWidth; |
| next = xpos + ((xposnext - xpos) / 2); |
| regionMap[i] = [last || 0, next, i]; |
| last = next; |
| if (y === null) { |
| if (i) { |
| if (yvalues[i - 1] !== null) { |
| path = []; |
| paths.push(path); |
| vertices.push(null); |
| } |
| } |
| } else { |
| if (y < this.miny) { |
| y = this.miny; |
| } |
| if (y > this.maxy) { |
| y = this.maxy; |
| } |
| if (!path.length) { |
| // previous value was null |
| path.push([xpos, canvasTop + canvasHeight]); |
| } |
| vertex = [xpos, canvasTop + Math.round(canvasHeight - (canvasHeight * ((y - this.miny) / rangey)))]; |
| path.push(vertex); |
| vertices.push(vertex); |
| } |
| } |
| |
| lineShapes = []; |
| fillShapes = []; |
| plen = paths.length; |
| for (i = 0; i < plen; i++) { |
| path = paths[i]; |
| if (path.length) { |
| if (options.get('fillColor')) { |
| path.push([path[path.length - 1][0], (canvasTop + canvasHeight)]); |
| fillShapes.push(path.slice(0)); |
| path.pop(); |
| } |
| // if there's only a single point in this path, then we want to display it |
| // as a vertical line which means we keep path[0] as is |
| if (path.length > 2) { |
| // else we want the first value |
| path[0] = [path[0][0], path[1][1]]; |
| } |
| lineShapes.push(path); |
| } |
| } |
| |
| // draw the fill first, then optionally the normal range, then the line on top of that |
| plen = fillShapes.length; |
| for (i = 0; i < plen; i++) { |
| target.drawShape(fillShapes[i], |
| options.get('fillColor'), options.get('fillColor')).append(); |
| } |
| |
| if (options.get('normalRangeMin') && options.get('drawNormalOnTop')) { |
| this.drawNormalRange(canvasLeft, canvasTop, canvasHeight, canvasWidth, rangey); |
| } |
| |
| plen = lineShapes.length; |
| for (i = 0; i < plen; i++) { |
| target.drawShape(lineShapes[i], options.get('lineColor'), undefined, |
| options.get('lineWidth')).append(); |
| } |
| |
| if (spotRadius && options.get('valueSpots')) { |
| valueSpots = options.get('valueSpots'); |
| if (valueSpots.get === undefined) { |
| valueSpots = new RangeMap(valueSpots); |
| } |
| for (i = 0; i < yvalcount; i++) { |
| color = valueSpots.get(yvalues[i]); |
| if (color) { |
| target.drawCircle(canvasLeft + Math.round((xvalues[i] - this.minx) * (canvasWidth / rangex)), |
| canvasTop + Math.round(canvasHeight - (canvasHeight * ((yvalues[i] - this.miny) / rangey))), |
| spotRadius, undefined, |
| color).append(); |
| } |
| } |
| |
| } |
| if (spotRadius && options.get('spotColor')) { |
| target.drawCircle(canvasLeft + Math.round((xvalues[xvalues.length - 1] - this.minx) * (canvasWidth / rangex)), |
| canvasTop + Math.round(canvasHeight - (canvasHeight * ((yvalues[yvallast] - this.miny) / rangey))), |
| spotRadius, undefined, |
| options.get('spotColor')).append(); |
| } |
| if (this.maxy !== this.minyorg) { |
| if (spotRadius && options.get('minSpotColor')) { |
| x = xvalues[$.inArray(this.minyorg, yvalues)]; |
| target.drawCircle(canvasLeft + Math.round((x - this.minx) * (canvasWidth / rangex)), |
| canvasTop + Math.round(canvasHeight - (canvasHeight * ((this.minyorg - this.miny) / rangey))), |
| spotRadius, undefined, |
| options.get('minSpotColor')).append(); |
| } |
| if (spotRadius && options.get('maxSpotColor')) { |
| x = xvalues[$.inArray(this.maxyorg, yvalues)]; |
| target.drawCircle(canvasLeft + Math.round((x - this.minx) * (canvasWidth / rangex)), |
| canvasTop + Math.round(canvasHeight - (canvasHeight * ((this.maxyorg - this.miny) / rangey))), |
| spotRadius, undefined, |
| options.get('maxSpotColor')).append(); |
| } |
| } |
| |
| this.lastShapeId = target.getLastShapeId(); |
| this.canvasTop = canvasTop; |
| target.render(); |
| } |
| }); |
| |
| /** |
| * Bar charts |
| */ |
| $.fn.sparkline.bar = bar = createClass($.fn.sparkline._base, barHighlightMixin, { |
| type: 'bar', |
| |
| init: function (el, values, options, width, height) { |
| var barWidth = parseInt(options.get('barWidth'), 10), |
| barSpacing = parseInt(options.get('barSpacing'), 10), |
| chartRangeMin = options.get('chartRangeMin'), |
| chartRangeMax = options.get('chartRangeMax'), |
| chartRangeClip = options.get('chartRangeClip'), |
| stackMin = Infinity, |
| stackMax = -Infinity, |
| isStackString, groupMin, groupMax, stackRanges, |
| numValues, i, vlen, range, zeroAxis, xaxisOffset, min, max, clipMin, clipMax, |
| stacked, vlist, j, slen, svals, val, yoffset, yMaxCalc, canvasHeightEf; |
| bar._super.init.call(this, el, values, options, width, height); |
| |
| // scan values to determine whether to stack bars |
| for (i = 0, vlen = values.length; i < vlen; i++) { |
| val = values[i]; |
| isStackString = typeof(val) === 'string' && val.indexOf(':') > -1; |
| if (isStackString || $.isArray(val)) { |
| stacked = true; |
| if (isStackString) { |
| val = values[i] = normalizeValues(val.split(':')); |
| } |
| val = remove(val, null); // min/max will treat null as zero |
| groupMin = Math.min.apply(Math, val); |
| groupMax = Math.max.apply(Math, val); |
| if (groupMin < stackMin) { |
| stackMin = groupMin; |
| } |
| if (groupMax > stackMax) { |
| stackMax = groupMax; |
| } |
| } |
| } |
| |
| this.stacked = stacked; |
| this.regionShapes = {}; |
| this.barWidth = barWidth; |
| this.barSpacing = barSpacing; |
| this.totalBarWidth = barWidth + barSpacing; |
| this.width = width = (values.length * barWidth) + ((values.length - 1) * barSpacing); |
| |
| this.initTarget(); |
| |
| if (chartRangeClip) { |
| clipMin = chartRangeMin === undefined ? -Infinity : chartRangeMin; |
| clipMax = chartRangeMax === undefined ? Infinity : chartRangeMax; |
| } |
| |
| numValues = []; |
| stackRanges = stacked ? [] : numValues; |
| var stackTotals = []; |
| var stackRangesNeg = []; |
| for (i = 0, vlen = values.length; i < vlen; i++) { |
| if (stacked) { |
| vlist = values[i]; |
| values[i] = svals = []; |
| stackTotals[i] = 0; |
| stackRanges[i] = stackRangesNeg[i] = 0; |
| for (j = 0, slen = vlist.length; j < slen; j++) { |
| val = svals[j] = chartRangeClip ? clipval(vlist[j], clipMin, clipMax) : vlist[j]; |
| if (val !== null) { |
| if (val > 0) { |
| stackTotals[i] += val; |
| } |
| if (stackMin < 0 && stackMax > 0) { |
| if (val < 0) { |
| stackRangesNeg[i] += Math.abs(val); |
| } else { |
| stackRanges[i] += val; |
| } |
| } else { |
| stackRanges[i] += Math.abs(val - (val < 0 ? stackMax : stackMin)); |
| } |
| numValues.push(val); |
| } |
| } |
| } else { |
| val = chartRangeClip ? clipval(values[i], clipMin, clipMax) : values[i]; |
| val = values[i] = normalizeValue(val); |
| if (val !== null) { |
| numValues.push(val); |
| } |
| } |
| } |
| this.max = max = Math.max.apply(Math, numValues); |
| this.min = min = Math.min.apply(Math, numValues); |
| this.stackMax = stackMax = stacked ? Math.max.apply(Math, stackTotals) : max; |
| this.stackMin = stackMin = stacked ? Math.min.apply(Math, numValues) : min; |
| |
| if (options.get('chartRangeMin') !== undefined && (options.get('chartRangeClip') || options.get('chartRangeMin') < min)) { |
| min = options.get('chartRangeMin'); |
| } |
| if (options.get('chartRangeMax') !== undefined && (options.get('chartRangeClip') || options.get('chartRangeMax') > max)) { |
| max = options.get('chartRangeMax'); |
| } |
| |
| this.zeroAxis = zeroAxis = options.get('zeroAxis', true); |
| if (min <= 0 && max >= 0 && zeroAxis) { |
| xaxisOffset = 0; |
| } else if (zeroAxis == false) { |
| xaxisOffset = min; |
| } else if (min > 0) { |
| xaxisOffset = min; |
| } else { |
| xaxisOffset = max; |
| } |
| this.xaxisOffset = xaxisOffset; |
| |
| range = stacked ? (Math.max.apply(Math, stackRanges) + Math.max.apply(Math, stackRangesNeg)) : max - min; |
| |
| // as we plot zero/min values a single pixel line, we add a pixel to all other |
| // values - Reduce the effective canvas size to suit |
| this.canvasHeightEf = (zeroAxis && min < 0) ? this.canvasHeight - 2 : this.canvasHeight - 1; |
| |
| if (min < xaxisOffset) { |
| yMaxCalc = (stacked && max >= 0) ? stackMax : max; |
| yoffset = (yMaxCalc - xaxisOffset) / range * this.canvasHeight; |
| if (yoffset !== Math.ceil(yoffset)) { |
| this.canvasHeightEf -= 2; |
| yoffset = Math.ceil(yoffset); |
| } |
| } else { |
| yoffset = this.canvasHeight; |
| } |
| this.yoffset = yoffset; |
| |
| if ($.isArray(options.get('colorMap'))) { |
| this.colorMapByIndex = options.get('colorMap'); |
| this.colorMapByValue = null; |
| } else { |
| this.colorMapByIndex = null; |
| this.colorMapByValue = options.get('colorMap'); |
| if (this.colorMapByValue && this.colorMapByValue.get === undefined) { |
| this.colorMapByValue = new RangeMap(this.colorMapByValue); |
| } |
| } |
| |
| this.range = range; |
| }, |
| |
| getRegion: function (el, x, y) { |
| var result = Math.floor(x / this.totalBarWidth); |
| return (result < 0 || result >= this.values.length) ? undefined : result; |
| }, |
| |
| getCurrentRegionFields: function () { |
| var currentRegion = this.currentRegion, |
| values = ensureArray(this.values[currentRegion]), |
| result = [], |
| value, i; |
| for (i = values.length; i--;) { |
| value = values[i]; |
| result.push({ |
| isNull: value === null, |
| value: value, |
| color: this.calcColor(i, value, currentRegion), |
| offset: currentRegion |
| }); |
| } |
| return result; |
| }, |
| |
| calcColor: function (stacknum, value, valuenum) { |
| var colorMapByIndex = this.colorMapByIndex, |
| colorMapByValue = this.colorMapByValue, |
| options = this.options, |
| color, newColor; |
| if (this.stacked) { |
| color = options.get('stackedBarColor'); |
| } else { |
| color = (value < 0) ? options.get('negBarColor') : options.get('barColor'); |
| } |
| if (value === 0 && options.get('zeroColor') !== undefined) { |
| color = options.get('zeroColor'); |
| } |
| if (colorMapByValue && (newColor = colorMapByValue.get(value))) { |
| color = newColor; |
| } else if (colorMapByIndex && colorMapByIndex.length > valuenum) { |
| color = colorMapByIndex[valuenum]; |
| } |
| return $.isArray(color) ? color[stacknum % color.length] : color; |
| }, |
| |
| /** |
| * Render bar(s) for a region |
| */ |
| renderRegion: function (valuenum, highlight) { |
| var vals = this.values[valuenum], |
| options = this.options, |
| xaxisOffset = this.xaxisOffset, |
| result = [], |
| range = this.range, |
| stacked = this.stacked, |
| target = this.target, |
| x = valuenum * this.totalBarWidth, |
| canvasHeightEf = this.canvasHeightEf, |
| yoffset = this.yoffset, |
| y, height, color, isNull, yoffsetNeg, i, valcount, val, minPlotted, allMin; |
| |
| vals = $.isArray(vals) ? vals : [vals]; |
| valcount = vals.length; |
| val = vals[0]; |
| isNull = all(null, vals); |
| allMin = all(xaxisOffset, vals, true); |
| |
| if (isNull) { |
| if (options.get('nullColor')) { |
| color = highlight ? options.get('nullColor') : this.calcHighlightColor(options.get('nullColor'), options); |
| y = (yoffset > 0) ? yoffset - 1 : yoffset; |
| return target.drawRect(x, y, this.barWidth - 1, 0, color, color); |
| } else { |
| return undefined; |
| } |
| } |
| yoffsetNeg = yoffset; |
| for (i = 0; i < valcount; i++) { |
| val = vals[i]; |
| |
| if (stacked && val === xaxisOffset) { |
| if (!allMin || minPlotted) { |
| continue; |
| } |
| minPlotted = true; |
| } |
| |
| if (range > 0) { |
| height = Math.floor(canvasHeightEf * ((Math.abs(val - xaxisOffset) / range))) + 1; |
| } else { |
| height = 1; |
| } |
| if (val < xaxisOffset || (val === xaxisOffset && yoffset === 0)) { |
| y = yoffsetNeg; |
| yoffsetNeg += height; |
| } else { |
| y = yoffset - height; |
| yoffset -= height; |
| } |
| color = this.calcColor(i, val, valuenum); |
| if (highlight) { |
| color = this.calcHighlightColor(color, options); |
| } |
| result.push(target.drawRect(x, y, this.barWidth - 1, height - 1, color, color)); |
| } |
| if (result.length === 1) { |
| return result[0]; |
| } |
| return result; |
| } |
| }); |
| |
| /** |
| * Tristate charts |
| */ |
| $.fn.sparkline.tristate = tristate = createClass($.fn.sparkline._base, barHighlightMixin, { |
| type: 'tristate', |
| |
| init: function (el, values, options, width, height) { |
| var barWidth = parseInt(options.get('barWidth'), 10), |
| barSpacing = parseInt(options.get('barSpacing'), 10); |
| tristate._super.init.call(this, el, values, options, width, height); |
| |
| this.regionShapes = {}; |
| this.barWidth = barWidth; |
| this.barSpacing = barSpacing; |
| this.totalBarWidth = barWidth + barSpacing; |
| this.values = $.map(values, Number); |
| this.width = width = (values.length * barWidth) + ((values.length - 1) * barSpacing); |
| |
| if ($.isArray(options.get('colorMap'))) { |
| this.colorMapByIndex = options.get('colorMap'); |
| this.colorMapByValue = null; |
| } else { |
| this.colorMapByIndex = null; |
| this.colorMapByValue = options.get('colorMap'); |
| if (this.colorMapByValue && this.colorMapByValue.get === undefined) { |
| this.colorMapByValue = new RangeMap(this.colorMapByValue); |
| } |
| } |
| this.initTarget(); |
| }, |
| |
| getRegion: function (el, x, y) { |
| return Math.floor(x / this.totalBarWidth); |
| }, |
| |
| getCurrentRegionFields: function () { |
| var currentRegion = this.currentRegion; |
| return { |
| isNull: this.values[currentRegion] === undefined, |
| value: this.values[currentRegion], |
| color: this.calcColor(this.values[currentRegion], currentRegion), |
| offset: currentRegion |
| }; |
| }, |
| |
| calcColor: function (value, valuenum) { |
| var values = this.values, |
| options = this.options, |
| colorMapByIndex = this.colorMapByIndex, |
| colorMapByValue = this.colorMapByValue, |
| color, newColor; |
| |
| if (colorMapByValue && (newColor = colorMapByValue.get(value))) { |
| color = newColor; |
| } else if (colorMapByIndex && colorMapByIndex.length > valuenum) { |
| color = colorMapByIndex[valuenum]; |
| } else if (values[valuenum] < 0) { |
| color = options.get('negBarColor'); |
| } else if (values[valuenum] > 0) { |
| color = options.get('posBarColor'); |
| } else { |
| color = options.get('zeroBarColor'); |
| } |
| return color; |
| }, |
| |
| renderRegion: function (valuenum, highlight) { |
| var values = this.values, |
| options = this.options, |
| target = this.target, |
| canvasHeight, height, halfHeight, |
| x, y, color; |
| |
| canvasHeight = target.pixelHeight; |
| halfHeight = Math.round(canvasHeight / 2); |
| |
| x = valuenum * this.totalBarWidth; |
| if (values[valuenum] < 0) { |
| y = halfHeight; |
| height = halfHeight - 1; |
| } else if (values[valuenum] > 0) { |
| y = 0; |
| height = halfHeight - 1; |
| } else { |
| y = halfHeight - 1; |
| height = 2; |
| } |
| color = this.calcColor(values[valuenum], valuenum); |
| if (color === null) { |
| return; |
| } |
| if (highlight) { |
| color = this.calcHighlightColor(color, options); |
| } |
| return target.drawRect(x, y, this.barWidth - 1, height - 1, color, color); |
| } |
| }); |
| |
| /** |
| * Discrete charts |
| */ |
| $.fn.sparkline.discrete = discrete = createClass($.fn.sparkline._base, barHighlightMixin, { |
| type: 'discrete', |
| |
| init: function (el, values, options, width, height) { |
| discrete._super.init.call(this, el, values, options, width, height); |
| |
| this.regionShapes = {}; |
| this.values = values = $.map(values, Number); |
| this.min = Math.min.apply(Math, values); |
| this.max = Math.max.apply(Math, values); |
| this.range = this.max - this.min; |
| this.width = width = options.get('width') === 'auto' ? values.length * 2 : this.width; |
| this.interval = Math.floor(width / values.length); |
| this.itemWidth = width / values.length; |
| if (options.get('chartRangeMin') !== undefined && (options.get('chartRangeClip') || options.get('chartRangeMin') < this.min)) { |
| this.min = options.get('chartRangeMin'); |
| } |
| if (options.get('chartRangeMax') !== undefined && (options.get('chartRangeClip') || options.get('chartRangeMax') > this.max)) { |
| this.max = options.get('chartRangeMax'); |
| } |
| this.initTarget(); |
| if (this.target) { |
| this.lineHeight = options.get('lineHeight') === 'auto' ? Math.round(this.canvasHeight * 0.3) : options.get('lineHeight'); |
| } |
| }, |
| |
| getRegion: function (el, x, y) { |
| return Math.floor(x / this.itemWidth); |
| }, |
| |
| getCurrentRegionFields: function () { |
| var currentRegion = this.currentRegion; |
| return { |
| isNull: this.values[currentRegion] === undefined, |
| value: this.values[currentRegion], |
| offset: currentRegion |
| }; |
| }, |
| |
| renderRegion: function (valuenum, highlight) { |
| var values = this.values, |
| options = this.options, |
| min = this.min, |
| max = this.max, |
| range = this.range, |
| interval = this.interval, |
| target = this.target, |
| canvasHeight = this.canvasHeight, |
| lineHeight = this.lineHeight, |
| pheight = canvasHeight - lineHeight, |
| ytop, val, color, x; |
| |
| val = clipval(values[valuenum], min, max); |
| x = valuenum * interval; |
| ytop = Math.round(pheight - pheight * ((val - min) / range)); |
| color = (options.get('thresholdColor') && val < options.get('thresholdValue')) ? options.get('thresholdColor') : options.get('lineColor'); |
| if (highlight) { |
| color = this.calcHighlightColor(color, options); |
| } |
| return target.drawLine(x, ytop, x, ytop + lineHeight, color); |
| } |
| }); |
| |
| /** |
| * Bullet charts |
| */ |
| $.fn.sparkline.bullet = bullet = createClass($.fn.sparkline._base, { |
| type: 'bullet', |
| |
| init: function (el, values, options, width, height) { |
| var min, max; |
| bullet._super.init.call(this, el, values, options, width, height); |
| |
| // values: target, performance, range1, range2, range3 |
| values = $.map(values, Number); |
| min = Math.min.apply(Math, values); |
| max = Math.max.apply(Math, values); |
| if (options.get('base') === undefined) { |
| min = min < 0 ? min : 0; |
| } else { |
| min = options.get('base'); |
| } |
| this.min = min; |
| this.max = max; |
| this.range = max - min; |
| this.shapes = {}; |
| this.valueShapes = {}; |
| this.regiondata = {}; |
| this.width = width = options.get('width') === 'auto' ? '4.0em' : width; |
| this.target = this.$el.simpledraw(width, height, options.get('composite')); |
| if (!values.length) { |
| this.disabled = true; |
| } |
| this.initTarget(); |
| }, |
| |
| getRegion: function (el, x, y) { |
| var shapeid = this.target.getShapeAt(el, x, y); |
| return (shapeid !== undefined && this.shapes[shapeid] !== undefined) ? this.shapes[shapeid] : undefined; |
| }, |
| |
| getCurrentRegionFields: function () { |
| var currentRegion = this.currentRegion; |
| return { |
| fieldkey: currentRegion.substr(0, 1), |
| value: this.values[currentRegion.substr(1)], |
| region: currentRegion |
| }; |
| }, |
| |
| changeHighlight: function (highlight) { |
| var currentRegion = this.currentRegion, |
| shapeid = this.valueShapes[currentRegion], |
| shape; |
| delete this.shapes[shapeid]; |
| switch (currentRegion.substr(0, 1)) { |
| case 'r': |
| shape = this.renderRange(currentRegion.substr(1), highlight); |
| break; |
| case 'p': |
| shape = this.renderPerformance(highlight); |
| break; |
| case 't': |
| shape = this.renderTarget(highlight); |
| break; |
| } |
| this.valueShapes[currentRegion] = shape.id; |
| this.shapes[shape.id] = currentRegion; |
| this.target.replaceWithShape(shapeid, shape); |
| }, |
| |
| renderRange: function (rn, highlight) { |
| var rangeval = this.values[rn], |
| rangewidth = Math.round(this.canvasWidth * ((rangeval - this.min) / this.range)), |
| color = this.options.get('rangeColors')[rn - 2]; |
| if (highlight) { |
| color = this.calcHighlightColor(color, this.options); |
| } |
| return this.target.drawRect(0, 0, rangewidth - 1, this.canvasHeight - 1, color, color); |
| }, |
| |
| renderPerformance: function (highlight) { |
| var perfval = this.values[1], |
| perfwidth = Math.round(this.canvasWidth * ((perfval - this.min) / this.range)), |
| color = this.options.get('performanceColor'); |
| if (highlight) { |
| color = this.calcHighlightColor(color, this.options); |
| } |
| return this.target.drawRect(0, Math.round(this.canvasHeight * 0.3), perfwidth - 1, |
| Math.round(this.canvasHeight * 0.4) - 1, color, color); |
| }, |
| |
| renderTarget: function (highlight) { |
| var targetval = this.values[0], |
| x = Math.round(this.canvasWidth * ((targetval - this.min) / this.range) - (this.options.get('targetWidth') / 2)), |
| targettop = Math.round(this.canvasHeight * 0.10), |
| targetheight = this.canvasHeight - (targettop * 2), |
| color = this.options.get('targetColor'); |
| if (highlight) { |
| color = this.calcHighlightColor(color, this.options); |
| } |
| return this.target.drawRect(x, targettop, this.options.get('targetWidth') - 1, targetheight - 1, color, color); |
| }, |
| |
| render: function () { |
| var vlen = this.values.length, |
| target = this.target, |
| i, shape; |
| if (!bullet._super.render.call(this)) { |
| return; |
| } |
| for (i = 2; i < vlen; i++) { |
| shape = this.renderRange(i).append(); |
| this.shapes[shape.id] = 'r' + i; |
| this.valueShapes['r' + i] = shape.id; |
| } |
| shape = this.renderPerformance().append(); |
| this.shapes[shape.id] = 'p1'; |
| this.valueShapes.p1 = shape.id; |
| shape = this.renderTarget().append(); |
| this.shapes[shape.id] = 't0'; |
| this.valueShapes.t0 = shape.id; |
| target.render(); |
| } |
| }); |
| |
| /** |
| * Pie charts |
| */ |
| $.fn.sparkline.pie = pie = createClass($.fn.sparkline._base, { |
| type: 'pie', |
| |
| init: function (el, values, options, width, height) { |
| var total = 0, i; |
| |
| pie._super.init.call(this, el, values, options, width, height); |
| |
| this.shapes = {}; // map shape ids to value offsets |
| this.valueShapes = {}; // maps value offsets to shape ids |
| this.values = values = $.map(values, Number); |
| |
| if (options.get('width') === 'auto') { |
| this.width = this.height; |
| } |
| |
| if (values.length > 0) { |
| for (i = values.length; i--;) { |
| total += values[i]; |
| } |
| } |
| this.total = total; |
| this.initTarget(); |
| this.radius = Math.floor(Math.min(this.canvasWidth, this.canvasHeight) / 2); |
| }, |
| |
| getRegion: function (el, x, y) { |
| var shapeid = this.target.getShapeAt(el, x, y); |
| return (shapeid !== undefined && this.shapes[shapeid] !== undefined) ? this.shapes[shapeid] : undefined; |
| }, |
| |
| getCurrentRegionFields: function () { |
| var currentRegion = this.currentRegion; |
| return { |
| isNull: this.values[currentRegion] === undefined, |
| value: this.values[currentRegion], |
| percent: this.values[currentRegion] / this.total * 100, |
| color: this.options.get('sliceColors')[currentRegion % this.options.get('sliceColors').length], |
| offset: currentRegion |
| }; |
| }, |
| |
| changeHighlight: function (highlight) { |
| var currentRegion = this.currentRegion, |
| newslice = this.renderSlice(currentRegion, highlight), |
| shapeid = this.valueShapes[currentRegion]; |
| delete this.shapes[shapeid]; |
| this.target.replaceWithShape(shapeid, newslice); |
| this.valueShapes[currentRegion] = newslice.id; |
| this.shapes[newslice.id] = currentRegion; |
| }, |
| |
| renderSlice: function (valuenum, highlight) { |
| var target = this.target, |
| options = this.options, |
| radius = this.radius, |
| borderWidth = options.get('borderWidth'), |
| offset = options.get('offset'), |
| circle = 2 * Math.PI, |
| values = this.values, |
| total = this.total, |
| next = offset ? (2*Math.PI)*(offset/360) : 0, |
| start, end, i, vlen, color; |
| |
| vlen = values.length; |
| for (i = 0; i < vlen; i++) { |
| start = next; |
| end = next; |
| if (total > 0) { // avoid divide by zero |
| end = next + (circle * (values[i] / total)); |
| } |
| if (valuenum === i) { |
| color = options.get('sliceColors')[i % options.get('sliceColors').length]; |
| if (highlight) { |
| color = this.calcHighlightColor(color, options); |
| } |
| |
| return target.drawPieSlice(radius, radius, radius - borderWidth, start, end, undefined, color); |
| } |
| next = end; |
| } |
| }, |
| |
| render: function () { |
| var target = this.target, |
| values = this.values, |
| options = this.options, |
| radius = this.radius, |
| borderWidth = options.get('borderWidth'), |
| shape, i; |
| |
| if (!pie._super.render.call(this)) { |
| return; |
| } |
| if (borderWidth) { |
| target.drawCircle(radius, radius, Math.floor(radius - (borderWidth / 2)), |
| options.get('borderColor'), undefined, borderWidth).append(); |
| } |
| for (i = values.length; i--;) { |
| shape = this.renderSlice(i).append(); |
| this.valueShapes[i] = shape.id; // store just the shapeid |
| this.shapes[shape.id] = i; |
| } |
| target.render(); |
| } |
| }); |
| |
| /** |
| * Box plots |
| */ |
| $.fn.sparkline.box = box = createClass($.fn.sparkline._base, { |
| type: 'box', |
| |
| init: function (el, values, options, width, height) { |
| box._super.init.call(this, el, values, options, width, height); |
| this.values = $.map(values, Number); |
| this.width = options.get('width') === 'auto' ? '4.0em' : width; |
| this.initTarget(); |
| if (!this.values.length) { |
| this.disabled = 1; |
| } |
| }, |
| |
| /** |
| * Simulate a single region |
| */ |
| getRegion: function () { |
| return 1; |
| }, |
| |
| getCurrentRegionFields: function () { |
| var result = [ |
| { field: 'lq', value: this.quartiles[0] }, |
| { field: 'med', value: this.quartiles[1] }, |
| { field: 'uq', value: this.quartiles[2] }, |
| { field: 'lo', value: this.loutlier }, |
| { field: 'ro', value: this.routlier } |
| ]; |
| if (this.lwhisker !== undefined) { |
| result.push({ field: 'lw', value: this.lwhisker}); |
| } |
| if (this.rwhisker !== undefined) { |
| result.push({ field: 'rw', value: this.rwhisker}); |
| } |
| return result; |
| }, |
| |
| render: function () { |
| var target = this.target, |
| values = this.values, |
| vlen = values.length, |
| options = this.options, |
| canvasWidth = this.canvasWidth, |
| canvasHeight = this.canvasHeight, |
| minValue = options.get('chartRangeMin') === undefined ? Math.min.apply(Math, values) : options.get('chartRangeMin'), |
| maxValue = options.get('chartRangeMax') === undefined ? Math.max.apply(Math, values) : options.get('chartRangeMax'), |
| canvasLeft = 0, |
| lwhisker, loutlier, iqr, q1, q2, q3, rwhisker, routlier, i, |
| size, unitSize; |
| |
| if (!box._super.render.call(this)) { |
| return; |
| } |
| |
| if (options.get('raw')) { |
| if (options.get('showOutliers') && values.length > 5) { |
| loutlier = values[0]; |
| lwhisker = values[1]; |
| q1 = values[2]; |
| q2 = values[3]; |
| q3 = values[4]; |
| rwhisker = values[5]; |
| routlier = values[6]; |
| } else { |
| lwhisker = values[0]; |
| q1 = values[1]; |
| q2 = values[2]; |
| q3 = values[3]; |
| rwhisker = values[4]; |
| } |
| } else { |
| values.sort(function (a, b) { return a - b; }); |
| q1 = quartile(values, 1); |
| q2 = quartile(values, 2); |
| q3 = quartile(values, 3); |
| iqr = q3 - q1; |
| if (options.get('showOutliers')) { |
| lwhisker = rwhisker = undefined; |
| for (i = 0; i < vlen; i++) { |
| if (lwhisker === undefined && values[i] > q1 - (iqr * options.get('outlierIQR'))) { |
| lwhisker = values[i]; |
| } |
| if (values[i] < q3 + (iqr * options.get('outlierIQR'))) { |
| rwhisker = values[i]; |
| } |
| } |
| loutlier = values[0]; |
| routlier = values[vlen - 1]; |
| } else { |
| lwhisker = values[0]; |
| rwhisker = values[vlen - 1]; |
| } |
| } |
| this.quartiles = [q1, q2, q3]; |
| this.lwhisker = lwhisker; |
| this.rwhisker = rwhisker; |
| this.loutlier = loutlier; |
| this.routlier = routlier; |
| |
| unitSize = canvasWidth / (maxValue - minValue + 1); |
| if (options.get('showOutliers')) { |
| canvasLeft = Math.ceil(options.get('spotRadius')); |
| canvasWidth -= 2 * Math.ceil(options.get('spotRadius')); |
| unitSize = canvasWidth / (maxValue - minValue + 1); |
| if (loutlier < lwhisker) { |
| target.drawCircle((loutlier - minValue) * unitSize + canvasLeft, |
| canvasHeight / 2, |
| options.get('spotRadius'), |
| options.get('outlierLineColor'), |
| options.get('outlierFillColor')).append(); |
| } |
| if (routlier > rwhisker) { |
| target.drawCircle((routlier - minValue) * unitSize + canvasLeft, |
| canvasHeight / 2, |
| options.get('spotRadius'), |
| options.get('outlierLineColor'), |
| options.get('outlierFillColor')).append(); |
| } |
| } |
| |
| // box |
| target.drawRect( |
| Math.round((q1 - minValue) * unitSize + canvasLeft), |
| Math.round(canvasHeight * 0.1), |
| Math.round((q3 - q1) * unitSize), |
| Math.round(canvasHeight * 0.8), |
| options.get('boxLineColor'), |
| options.get('boxFillColor')).append(); |
| // left whisker |
| target.drawLine( |
| Math.round((lwhisker - minValue) * unitSize + canvasLeft), |
| Math.round(canvasHeight / 2), |
| Math.round((q1 - minValue) * unitSize + canvasLeft), |
| Math.round(canvasHeight / 2), |
| options.get('lineColor')).append(); |
| target.drawLine( |
| Math.round((lwhisker - minValue) * unitSize + canvasLeft), |
| Math.round(canvasHeight / 4), |
| Math.round((lwhisker - minValue) * unitSize + canvasLeft), |
| Math.round(canvasHeight - canvasHeight / 4), |
| options.get('whiskerColor')).append(); |
| // right whisker |
| target.drawLine(Math.round((rwhisker - minValue) * unitSize + canvasLeft), |
| Math.round(canvasHeight / 2), |
| Math.round((q3 - minValue) * unitSize + canvasLeft), |
| Math.round(canvasHeight / 2), |
| options.get('lineColor')).append(); |
| target.drawLine( |
| Math.round((rwhisker - minValue) * unitSize + canvasLeft), |
| Math.round(canvasHeight / 4), |
| Math.round((rwhisker - minValue) * unitSize + canvasLeft), |
| Math.round(canvasHeight - canvasHeight / 4), |
| options.get('whiskerColor')).append(); |
| // median line |
| target.drawLine( |
| Math.round((q2 - minValue) * unitSize + canvasLeft), |
| Math.round(canvasHeight * 0.1), |
| Math.round((q2 - minValue) * unitSize + canvasLeft), |
| Math.round(canvasHeight * 0.9), |
| options.get('medianColor')).append(); |
| if (options.get('target')) { |
| size = Math.ceil(options.get('spotRadius')); |
| target.drawLine( |
| Math.round((options.get('target') - minValue) * unitSize + canvasLeft), |
| Math.round((canvasHeight / 2) - size), |
| Math.round((options.get('target') - minValue) * unitSize + canvasLeft), |
| Math.round((canvasHeight / 2) + size), |
| options.get('targetColor')).append(); |
| target.drawLine( |
| Math.round((options.get('target') - minValue) * unitSize + canvasLeft - size), |
| Math.round(canvasHeight / 2), |
| Math.round((options.get('target') - minValue) * unitSize + canvasLeft + size), |
| Math.round(canvasHeight / 2), |
| options.get('targetColor')).append(); |
| } |
| target.render(); |
| } |
| }); |
| |
| // Setup a very simple "virtual canvas" to make drawing the few shapes we need easier |
| // This is accessible as $(foo).simpledraw() |
| |
| if ($.browser.msie && !document.namespaces.v) { |
| document.namespaces.add('v', 'urn:schemas-microsoft-com:vml', '#default#VML'); |
| } |
| |
| if ($.browser.hasCanvas === undefined) { |
| $.browser.hasCanvas = document.createElement('canvas').getContext !== undefined; |
| } |
| |
| VShape = createClass({ |
| init: function (target, id, type, args) { |
| this.target = target; |
| this.id = id; |
| this.type = type; |
| this.args = args; |
| }, |
| append: function () { |
| this.target.appendShape(this); |
| return this; |
| } |
| }); |
| |
| VCanvas_base = createClass({ |
| _pxregex: /(\d+)(px)?\s*$/i, |
| |
| init: function (width, height, target) { |
| if (!width) { |
| return; |
| } |
| this.width = width; |
| this.height = height; |
| this.target = target; |
| this.lastShapeId = null; |
| if (target[0]) { |
| target = target[0]; |
| } |
| $.data(target, '_jqs_vcanvas', this); |
| }, |
| |
| drawLine: function (x1, y1, x2, y2, lineColor, lineWidth) { |
| return this.drawShape([[x1, y1], [x2, y2]], lineColor, lineWidth); |
| }, |
| |
| drawShape: function (path, lineColor, fillColor, lineWidth) { |
| return this._genShape('Shape', [path, lineColor, fillColor, lineWidth]); |
| }, |
| |
| drawCircle: function (x, y, radius, lineColor, fillColor, lineWidth) { |
| return this._genShape('Circle', [x, y, radius, lineColor, fillColor, lineWidth]); |
| }, |
| |
| drawPieSlice: function (x, y, radius, startAngle, endAngle, lineColor, fillColor) { |
| return this._genShape('PieSlice', [x, y, radius, startAngle, endAngle, lineColor, fillColor]); |
| }, |
| |
| drawRect: function (x, y, width, height, lineColor, fillColor) { |
| return this._genShape('Rect', [x, y, width, height, lineColor, fillColor]); |
| }, |
| |
| getElement: function () { |
| return this.canvas; |
| }, |
| |
| /** |
| * Return the most recently inserted shape id |
| */ |
| getLastShapeId: function () { |
| return this.lastShapeId; |
| }, |
| |
| /** |
| * Clear and reset the canvas |
| */ |
| reset: function () { |
| alert('reset not implemented'); |
| }, |
| |
| _insert: function (el, target) { |
| $(target).html(el); |
| }, |
| |
| /** |
| * Calculate the pixel dimensions of the canvas |
| */ |
| _calculatePixelDims: function (width, height, canvas) { |
| // XXX This should probably be a configurable option |
| var match; |
| match = this._pxregex.exec(height); |
| if (match) { |
| this.pixelHeight = match[1]; |
| } else { |
| this.pixelHeight = $(canvas).height(); |
| } |
| match = this._pxregex.exec(width); |
| if (match) { |
| this.pixelWidth = match[1]; |
| } else { |
| this.pixelWidth = $(canvas).width(); |
| } |
| }, |
| |
| /** |
| * Generate a shape object and id for later rendering |
| */ |
| _genShape: function (shapetype, shapeargs) { |
| var id = shapeCount++; |
| shapeargs.unshift(id); |
| return new VShape(this, id, shapetype, shapeargs); |
| }, |
| |
| /** |
| * Add a shape to the end of the render queue |
| */ |
| appendShape: function (shape) { |
| alert('appendShape not implemented'); |
| }, |
| |
| /** |
| * Replace one shape with another |
| */ |
| replaceWithShape: function (shapeid, shape) { |
| alert('replaceWithShape not implemented'); |
| }, |
| |
| /** |
| * Insert one shape after another in the render queue |
| */ |
| insertAfterShape: function (shapeid, shape) { |
| alert('insertAfterShape not implemented'); |
| }, |
| |
| /** |
| * Remove a shape from the queue |
| */ |
| removeShapeId: function (shapeid) { |
| alert('removeShapeId not implemented'); |
| }, |
| |
| /** |
| * Find a shape at the specified x/y co-ordinates |
| */ |
| getShapeAt: function (el, x, y) { |
| alert('getShapeAt not implemented'); |
| }, |
| |
| /** |
| * Render all queued shapes onto the canvas |
| */ |
| render: function () { |
| alert('render not implemented'); |
| } |
| }); |
| |
| VCanvas_canvas = createClass(VCanvas_base, { |
| init: function (width, height, target, interact) { |
| VCanvas_canvas._super.init.call(this, width, height, target); |
| this.canvas = document.createElement('canvas'); |
| if (target[0]) { |
| target = target[0]; |
| } |
| $.data(target, '_jqs_vcanvas', this); |
| $(this.canvas).css({ display: 'inline-block', width: width, height: height, verticalAlign: 'top' }); |
| this._insert(this.canvas, target); |
| this._calculatePixelDims(width, height, this.canvas); |
| this.canvas.width = this.pixelWidth; |
| this.canvas.height = this.pixelHeight; |
| this.interact = interact; |
| this.shapes = {}; |
| this.shapeseq = []; |
| this.currentTargetShapeId = undefined; |
| $(this.canvas).css({width: this.pixelWidth, height: this.pixelHeight}); |
| }, |
| |
| _getContext: function (lineColor, fillColor, lineWidth) { |
| var context = this.canvas.getContext('2d'); |
| if (lineColor !== undefined) { |
| context.strokeStyle = lineColor; |
| } |
| context.lineWidth = lineWidth === undefined ? 1 : lineWidth; |
| if (fillColor !== undefined) { |
| context.fillStyle = fillColor; |
| } |
| return context; |
| }, |
| |
| reset: function () { |
| var context = this._getContext(); |
| context.clearRect(0, 0, this.pixelWidth, this.pixelHeight); |
| this.shapes = {}; |
| this.shapeseq = []; |
| this.currentTargetShapeId = undefined; |
| }, |
| |
| _drawShape: function (shapeid, path, lineColor, fillColor, lineWidth) { |
| var context = this._getContext(lineColor, fillColor, lineWidth), |
| i, plen; |
| context.beginPath(); |
| context.moveTo(path[0][0] + 0.5, path[0][1] + 0.5); |
| for (i = 1, plen = path.length; i < plen; i++) { |
| context.lineTo(path[i][0] + 0.5, path[i][1] + 0.5); // the 0.5 offset gives us crisp pixel-width lines |
| } |
| if (lineColor !== undefined) { |
| context.stroke(); |
| } |
| if (fillColor !== undefined) { |
| context.fill(); |
| } |
| if (this.targetX !== undefined && this.targetY !== undefined && |
| context.isPointInPath(this.targetX, this.targetY)) { |
| this.currentTargetShapeId = shapeid; |
| } |
| }, |
| |
| _drawCircle: function (shapeid, x, y, radius, lineColor, fillColor, lineWidth) { |
| var context = this._getContext(lineColor, fillColor, lineWidth); |
| context.beginPath(); |
| context.arc(x, y, radius, 0, 2 * Math.PI, false); |
| if (this.targetX !== undefined && this.targetY !== undefined && |
| context.isPointInPath(this.targetX, this.targetY)) { |
| this.currentTargetShapeId = shapeid; |
| } |
| if (lineColor !== undefined) { |
| context.stroke(); |
| } |
| if (fillColor !== undefined) { |
| context.fill(); |
| } |
| }, |
| |
| _drawPieSlice: function (shapeid, x, y, radius, startAngle, endAngle, lineColor, fillColor) { |
| var context = this._getContext(lineColor, fillColor); |
| context.beginPath(); |
| context.moveTo(x, y); |
| context.arc(x, y, radius, startAngle, endAngle, false); |
| context.lineTo(x, y); |
| context.closePath(); |
| if (lineColor !== undefined) { |
| context.stroke(); |
| } |
| if (fillColor) { |
| context.fill(); |
| } |
| if (this.targetX !== undefined && this.targetY !== undefined && |
| context.isPointInPath(this.targetX, this.targetY)) { |
| this.currentTargetShapeId = shapeid; |
| } |
| }, |
| |
| _drawRect: function (shapeid, x, y, width, height, lineColor, fillColor) { |
| return this._drawShape(shapeid, [[x, y], [x + width, y], [x + width, y + height], [x, y + height], [x, y]], lineColor, fillColor); |
| }, |
| |
| appendShape: function (shape) { |
| this.shapes[shape.id] = shape; |
| this.shapeseq.push(shape.id); |
| this.lastShapeId = shape.id; |
| return shape.id; |
| }, |
| |
| replaceWithShape: function (shapeid, shape) { |
| var shapeseq = this.shapeseq, |
| i; |
| this.shapes[shape.id] = shape; |
| for (i = shapeseq.length; i--;) { |
| if (shapeseq[i] == shapeid) { |
| shapeseq[i] = shape.id; |
| } |
| } |
| delete this.shapes[shapeid]; |
| }, |
| |
| replaceWithShapes: function (shapeids, shapes) { |
| var shapeseq = this.shapeseq, |
| shapemap = {}, |
| sid, i, first; |
| |
| for (i = shapeids.length; i--;) { |
| shapemap[shapeids[i]] = true; |
| } |
| for (i = shapeseq.length; i--;) { |
| sid = shapeseq[i]; |
| if (shapemap[sid]) { |
| shapeseq.splice(i, 1); |
| delete this.shapes[sid]; |
| first = i; |
| } |
| } |
| for (i = shapes.length; i--;) { |
| shapeseq.splice(first, 0, shapes[i].id); |
| this.shapes[shapes[i].id] = shapes[i]; |
| } |
| |
| }, |
| |
| insertAfterShape: function (shapeid, shape) { |
| var shapeseq = this.shapeseq, |
| i; |
| for (i = shapeseq.length; i--;) { |
| if (shapeseq[i] === shapeid) { |
| shapeseq.splice(i + 1, 0, shape.id); |
| this.shapes[shape.id] = shape; |
| return; |
| } |
| } |
| }, |
| |
| removeShapeId: function (shapeid) { |
| var shapeseq = this.shapeseq, |
| i; |
| for (i = shapeseq.length; i--;) { |
| if (shapeseq[i] === shapeid) { |
| shapeseq.splice(i, 1); |
| break; |
| } |
| } |
| delete this.shapes[shapeid]; |
| }, |
| |
| getShapeAt: function (el, x, y) { |
| this.targetX = x; |
| this.targetY = y; |
| this.render(); |
| return this.currentTargetShapeId; |
| }, |
| |
| render: function () { |
| var shapeseq = this.shapeseq, |
| shapes = this.shapes, |
| shapeCount = shapeseq.length, |
| context = this._getContext(), |
| shapeid, shape, i; |
| context.clearRect(0, 0, this.pixelWidth, this.pixelHeight); |
| for (i = 0; i < shapeCount; i++) { |
| shapeid = shapeseq[i]; |
| shape = shapes[shapeid]; |
| this['_draw' + shape.type].apply(this, shape.args); |
| } |
| if (!this.interact) { |
| // not interactive so no need to keep the shapes array |
| this.shapes = {}; |
| this.shapeseq = []; |
| } |
| } |
| |
| }); |
| |
| VCanvas_vml = createClass(VCanvas_base, { |
| init: function (width, height, target) { |
| var groupel; |
| VCanvas_vml._super.init.call(this, width, height, target); |
| if (target[0]) { |
| target = target[0]; |
| } |
| $.data(target, '_jqs_vcanvas', this); |
| this.canvas = document.createElement('span'); |
| $(this.canvas).css({ display: 'inline-block', position: 'relative', overflow: 'hidden', width: width, height: height, margin: '0px', padding: '0px', verticalAlign: 'top'}); |
| this._insert(this.canvas, target); |
| this._calculatePixelDims(width, height, this.canvas); |
| this.canvas.width = this.pixelWidth; |
| this.canvas.height = this.pixelHeight; |
| groupel = '<v:group coordorigin="0 0" coordsize="' + this.pixelWidth + ' ' + this.pixelHeight + '"' + |
| ' style="position:absolute;top:0;left:0;width:' + this.pixelWidth + 'px;height=' + this.pixelHeight + 'px;"></v:group>'; |
| this.canvas.insertAdjacentHTML('beforeEnd', groupel); |
| this.group = $(this.canvas).children()[0]; |
| this.rendered = false; |
| this.prerender = ''; |
| }, |
| |
| _drawShape: function (shapeid, path, lineColor, fillColor, lineWidth) { |
| var vpath = [], |
| initial, stroke, fill, closed, vel, plen, i; |
| for (i = 0, plen = path.length; i < plen; i++) { |
| vpath[i] = '' + (path[i][0]) + ',' + (path[i][1]); |
| } |
| initial = vpath.splice(0, 1); |
| lineWidth = lineWidth === undefined ? 1 : lineWidth; |
| stroke = lineColor === undefined ? ' stroked="false" ' : ' strokeWeight="' + lineWidth + 'px" strokeColor="' + lineColor + '" '; |
| fill = fillColor === undefined ? ' filled="false"' : ' fillColor="' + fillColor + '" filled="true" '; |
| closed = vpath[0] === vpath[vpath.length - 1] ? 'x ' : ''; |
| vel = '<v:shape coordorigin="0 0" coordsize="' + this.pixelWidth + ' ' + this.pixelHeight + '" ' + |
| ' id="jqsshape' + shapeid + '" ' + |
| stroke + |
| fill + |
| ' style="position:absolute;left:0px;top:0px;height:' + this.pixelHeight + 'px;width:' + this.pixelWidth + 'px;padding:0px;margin:0px;" ' + |
| ' path="m ' + initial + ' l ' + vpath.join(', ') + ' ' + closed + 'e">' + |
| ' </v:shape>'; |
| return vel; |
| }, |
| |
| _drawCircle: function (shapeid, x, y, radius, lineColor, fillColor, lineWidth) { |
| var stroke, fill, vel; |
| x -= radius; |
| y -= radius; |
| stroke = lineColor === undefined ? ' stroked="false" ' : ' strokeWeight="' + lineWidth + 'px" strokeColor="' + lineColor + '" '; |
| fill = fillColor === undefined ? ' filled="false"' : ' fillColor="' + fillColor + '" filled="true" '; |
| vel = '<v:oval ' + |
| ' id="jqsshape' + shapeid + '" ' + |
| stroke + |
| fill + |
| ' style="position:absolute;top:' + y + 'px; left:' + x + 'px; width:' + (radius * 2) + 'px; height:' + (radius * 2) + 'px"></v:oval>'; |
| return vel; |
| |
| }, |
| |
| _drawPieSlice: function (shapeid, x, y, radius, startAngle, endAngle, lineColor, fillColor) { |
| var vpath, startx, starty, endx, endy, stroke, fill, vel; |
| if (startAngle === endAngle) { |
| return; // VML seems to have problem when start angle equals end angle. |
| } |
| if ((endAngle - startAngle) === (2 * Math.PI)) { |
| startAngle = 0.0; // VML seems to have a problem when drawing a full circle that doesn't start 0 |
| endAngle = (2 * Math.PI); |
| } |
| |
| startx = x + Math.round(Math.cos(startAngle) * radius); |
| starty = y + Math.round(Math.sin(startAngle) * radius); |
| endx = x + Math.round(Math.cos(endAngle) * radius); |
| endy = y + Math.round(Math.sin(endAngle) * radius); |
| |
| // Prevent very small slices from being mistaken as a whole pie |
| if (startx === endx && starty === endy && (endAngle - startAngle) < Math.PI) { |
| return; |
| } |
| |
| vpath = [x - radius, y - radius, x + radius, y + radius, startx, starty, endx, endy]; |
| stroke = lineColor === undefined ? ' stroked="false" ' : ' strokeWeight="1px" strokeColor="' + lineColor + '" '; |
| fill = fillColor === undefined ? ' filled="false"' : ' fillColor="' + fillColor + '" filled="true" '; |
| vel = '<v:shape coordorigin="0 0" coordsize="' + this.pixelWidth + ' ' + this.pixelHeight + '" ' + |
| ' id="jqsshape' + shapeid + '" ' + |
| stroke + |
| fill + |
| ' style="position:absolute;left:0px;top:0px;height:' + this.pixelHeight + 'px;width:' + this.pixelWidth + 'px;padding:0px;margin:0px;" ' + |
| ' path="m ' + x + ',' + y + ' wa ' + vpath.join(', ') + ' x e">' + |
| ' </v:shape>'; |
| return vel; |
| }, |
| |
| _drawRect: function (shapeid, x, y, width, height, lineColor, fillColor) { |
| return this._drawShape(shapeid, [[x, y], [x, y + height], [x + width, y + height], [x + width, y], [x, y]], lineColor, fillColor); |
| }, |
| |
| reset: function () { |
| this.group.innerHTML = ''; |
| }, |
| |
| appendShape: function (shape) { |
| var vel = this['_draw' + shape.type].apply(this, shape.args); |
| if (this.rendered) { |
| this.group.insertAdjacentHTML('beforeEnd', vel); |
| } else { |
| this.prerender += vel; |
| } |
| this.lastShapeId = shape.id; |
| return shape.id; |
| }, |
| |
| replaceWithShape: function (shapeid, shape) { |
| var existing = $('#jqsshape' + shapeid), |
| vel = this['_draw' + shape.type].apply(this, shape.args); |
| existing[0].outerHTML = vel; |
| }, |
| |
| replaceWithShapes: function (shapeids, shapes) { |
| // replace the first shapeid with all the new shapes then toast the remaining old shapes |
| var existing = $('#jqsshape' + shapeids[0]), |
| replace = '', |
| slen = shapes.length, |
| i; |
| for (i = 0; i < slen; i++) { |
| replace += this['_draw' + shapes[i].type].apply(this, shapes[i].args); |
| } |
| existing[0].outerHTML = replace; |
| for (i = 1; i < shapeids.length; i++) { |
| $('#jqsshape' + shapeids[i]).remove(); |
| } |
| }, |
| |
| insertAfterShape: function (shapeid, shape) { |
| var existing = $('#jqsshape' + shapeid), |
| vel = this['_draw' + shape.type].apply(this, shape.args); |
| existing[0].insertAdjacentHTML('afterEnd', vel); |
| }, |
| |
| removeShapeId: function (shapeid) { |
| var existing = $('#jqsshape' + shapeid); |
| this.group.removeChild(existing[0]); |
| }, |
| |
| getShapeAt: function (el, x, y) { |
| var shapeid = el.id.substr(8); |
| return shapeid; |
| }, |
| |
| render: function () { |
| if (!this.rendered) { |
| // batch the intial render into a single repaint |
| this.group.innerHTML = this.prerender; |
| this.rendered = true; |
| } |
| } |
| }); |
| |
| |
| })(jQuery); |