* 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
* 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)
* 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)
* 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
*
'use strict';
var UNSET_OPTION = {},
- getDefaults, createClass, SPFormat, clipval, quartile, normalizeValue, normalizeValues,
- remove, isNumber, all, sum, addCSS, ensureArray, formatNumber, RangeMap,
+ getDefaults, createClass, clipval, quartile, normalizeValue, normalizeValues,
+ remove, isNumber, all, sum, addCSS, ensureArray, formatNumber,
MouseHandler, Tooltip, barHighlightMixin,
bar, defaultStyles, initStyles,
VShape, VCanvas_base, VCanvas_canvas, pending, shapeCount = 0;
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: ',',
bar: {
barColor: '#3366cc',
negBarColor: '#f44',
- stackedBarColor: ['#3366cc', '#dc3912', '#ff9900', '#109618', '#66aa00',
- '#dd4477', '#0099c6', '#990099'],
zeroColor: undefined,
nullColor: undefined,
zeroAxis: true,
chartRangeMax: undefined,
chartRangeMin: undefined,
chartRangeClip: false,
- colorMap: undefined,
- tooltipFormat: new SPFormat('<span style="color: {{color}}">●</span> {{prefix}}{{value}}{{suffix}}')
+ tooltipFormat: '',
},
};
};
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;
}
};
- $.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);
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';
height = $(tmp).innerHeight() || $(tmp).height();
$(tmp).remove();
tmp = null;
- }
} else {
height = options.get('height');
}
if (!mhandler) {
mhandler = new MouseHandler(this, options);
$.data(this, '_jqs_mhandler', mhandler);
- } else if (!options.get('composite')) {
+ } else {
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();
mhandler.registerSparkline(sp);
}
};
- if (($(this).html() && !options.get('disableHiddenCheck') && $(this).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);
- }
+ render.call(this);
});
};
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);
*/
$.fn.sparkline.options = createClass({
init: function (tag, userOptions) {
- var extendedOptions, defaults, base, tagOptionType;
+ var extendedOptions, defaults, base;
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];
- }
+ extendedOptions = defaults[userOptions.type || base.type];
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;
- }
+ var result;
return (result = this.mergedOptions[key]) === undefined ? defaultval : result;
}
});
*/
initTarget: function () {
var interactive = !this.options.get('disableInteraction');
- if (!(this.target = this.$el.simpledraw(this.width, this.height, this.options.get('composite'), interactive))) {
+ if (!(this.target = this.$el.simpledraw(this.width, this.height, false, interactive))) {
this.disabled = true;
} else {
this.canvasWidth = this.target.pixelWidth;
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')) {
chartRangeMin = options.get('chartRangeMin'),
chartRangeMax = options.get('chartRangeMax'),
chartRangeClip = options.get('chartRangeClip'),
- stackMin = Infinity,
- stackMax = -Infinity,
- isStackString, groupMin, groupMax, stackRanges,
+ groupMin, groupMax,
numValues, i, vlen, range, zeroAxis, xaxisOffset, min, max, clipMin, clipMax,
- stacked, vlist, j, slen, svals, val, yoffset, yMaxCalc, canvasHeightEf;
+ 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;
}
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');
}
this.xaxisOffset = xaxisOffset;
- range = stacked ? (Math.max.apply(Math, stackRanges) + Math.max.apply(Math, stackRangesNeg)) : max - min;
+ range = 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;
+ yMaxCalc = max;
yoffset = (yMaxCalc - xaxisOffset) / range * this.canvasHeight;
if (yoffset !== Math.ceil(yoffset)) {
this.canvasHeightEf -= 2;
}
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;
},
},
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 {
+ var color, newColor,
+ options = this.options;
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;
+ return color;
},
/**
xaxisOffset = this.xaxisOffset,
result = [],
range = this.range,
- stacked = this.stacked,
target = this.target,
x = valuenum * this.totalBarWidth,
canvasHeightEf = this.canvasHeightEf,
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 {
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]);
},
}
},
- _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);
},