/** * 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; } });