/** * 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, hlSpotsEnabled, 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 hlSpotsEnabled = options.get('highlightSpotColor') && !options.get('disableInteraction'); if (hlSpotsEnabled || options.get('minSpotColor') || (options.get('spotColor') && yvalues[yvallast] === this.miny)) { canvasHeight -= Math.ceil(spotRadius); } if (hlSpotsEnabled || options.get('maxSpotColor') || (options.get('spotColor') && yvalues[yvallast] === this.maxy)) { canvasHeight -= Math.ceil(spotRadius); canvasTop += Math.ceil(spotRadius); } if (hlSpotsEnabled || ((options.get('minSpotColor') || options.get('maxSpotColor')) && (yvalues[0] === this.miny || yvalues[0] === this.maxy))) { canvasLeft += Math.ceil(spotRadius); canvasWidth -= Math.ceil(spotRadius); } if (hlSpotsEnabled || 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') !== undefined && !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') !== undefined && 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') && yvalues[yvallast] !== null) { 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(); } });