123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656 |
- /**
- ## jquery.flot.drawSeries.js
- This plugin is used by flot for drawing lines, plots, bars or area.
- ### Public methods
- */
- (function($) {
- "use strict";
- function DrawSeries() {
- function plotLine(datapoints, xoffset, yoffset, axisx, axisy, ctx, steps) {
- var points = datapoints.points,
- ps = datapoints.pointsize,
- prevx = null,
- prevy = null;
- var x1 = 0.0,
- y1 = 0.0,
- x2 = 0.0,
- y2 = 0.0,
- mx = null,
- my = null,
- i = 0;
- ctx.beginPath();
- for (i = ps; i < points.length; i += ps) {
- x1 = points[i - ps];
- y1 = points[i - ps + 1];
- x2 = points[i];
- y2 = points[i + 1];
- if (x1 === null || x2 === null) {
- mx = null;
- my = null;
- continue;
- }
- if(steps){
- if (mx !== null && my !== null) {
- // if middle point exists, transfer p2 -> p1 and p1 -> mp
- x2 = x1;
- y2 = y1;
- x1 = mx;
- y1 = my;
- // 'remove' middle point
- mx = null;
- my = null;
- // subtract pointsize from i to have current point p1 handled again
- i -= ps;
- } else if (y1 !== y2 && x1 !== x2) {
- // create a middle point
- y2 = y1;
- mx = x2;
- my = y1;
- }
- }
- // clip with ymin
- if (y1 <= y2 && y1 < axisy.min) {
- if (y2 < axisy.min) {
- // line segment is outside
- continue;
- }
- // compute new intersection point
- x1 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1;
- y1 = axisy.min;
- } else if (y2 <= y1 && y2 < axisy.min) {
- if (y1 < axisy.min) {
- continue;
- }
- x2 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1;
- y2 = axisy.min;
- }
- // clip with ymax
- if (y1 >= y2 && y1 > axisy.max) {
- if (y2 > axisy.max) {
- continue;
- }
- x1 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1;
- y1 = axisy.max;
- } else if (y2 >= y1 && y2 > axisy.max) {
- if (y1 > axisy.max) {
- continue;
- }
- x2 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1;
- y2 = axisy.max;
- }
- // clip with xmin
- if (x1 <= x2 && x1 < axisx.min) {
- if (x2 < axisx.min) {
- continue;
- }
- y1 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1;
- x1 = axisx.min;
- } else if (x2 <= x1 && x2 < axisx.min) {
- if (x1 < axisx.min) {
- continue;
- }
- y2 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1;
- x2 = axisx.min;
- }
- // clip with xmax
- if (x1 >= x2 && x1 > axisx.max) {
- if (x2 > axisx.max) {
- continue;
- }
- y1 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1;
- x1 = axisx.max;
- } else if (x2 >= x1 && x2 > axisx.max) {
- if (x1 > axisx.max) {
- continue;
- }
- y2 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1;
- x2 = axisx.max;
- }
- if (x1 !== prevx || y1 !== prevy) {
- ctx.moveTo(axisx.p2c(x1) + xoffset, axisy.p2c(y1) + yoffset);
- }
- prevx = x2;
- prevy = y2;
- ctx.lineTo(axisx.p2c(x2) + xoffset, axisy.p2c(y2) + yoffset);
- }
- ctx.stroke();
- }
- function plotLineArea(datapoints, axisx, axisy, fillTowards, ctx, steps) {
- var points = datapoints.points,
- ps = datapoints.pointsize,
- bottom = fillTowards > axisy.min ? Math.min(axisy.max, fillTowards) : axisy.min,
- i = 0,
- ypos = 1,
- areaOpen = false,
- segmentStart = 0,
- segmentEnd = 0,
- mx = null,
- my = null;
- // we process each segment in two turns, first forward
- // direction to sketch out top, then once we hit the
- // end we go backwards to sketch the bottom
- while (true) {
- if (ps > 0 && i > points.length + ps) {
- break;
- }
- i += ps; // ps is negative if going backwards
- var x1 = points[i - ps],
- y1 = points[i - ps + ypos],
- x2 = points[i],
- y2 = points[i + ypos];
- if (ps === -2) {
- /* going backwards and no value for the bottom provided in the series*/
- y1 = y2 = bottom;
- }
- if (areaOpen) {
- if (ps > 0 && x1 != null && x2 == null) {
- // at turning point
- segmentEnd = i;
- ps = -ps;
- ypos = 2;
- continue;
- }
- if (ps < 0 && i === segmentStart + ps) {
- // done with the reverse sweep
- ctx.fill();
- areaOpen = false;
- ps = -ps;
- i = segmentStart = segmentEnd + ps;
- continue;
- }
- }
- if (x1 == null || x2 == null) {
- mx = null;
- my = null;
- continue;
- }
- if(steps){
- if (mx !== null && my !== null) {
- // if middle point exists, transfer p2 -> p1 and p1 -> mp
- x2 = x1;
- y2 = y1;
- x1 = mx;
- y1 = my;
- // 'remove' middle point
- mx = null;
- my = null;
- // subtract pointsize from i to have current point p1 handled again
- i -= ps;
- } else if (y1 !== y2 && x1 !== x2) {
- // create a middle point
- y2 = y1;
- mx = x2;
- my = y1;
- }
- }
- // clip x values
- // clip with xmin
- if (x1 <= x2 && x1 < axisx.min) {
- if (x2 < axisx.min) {
- continue;
- }
- y1 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1;
- x1 = axisx.min;
- } else if (x2 <= x1 && x2 < axisx.min) {
- if (x1 < axisx.min) {
- continue;
- }
- y2 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1;
- x2 = axisx.min;
- }
- // clip with xmax
- if (x1 >= x2 && x1 > axisx.max) {
- if (x2 > axisx.max) {
- continue;
- }
- y1 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1;
- x1 = axisx.max;
- } else if (x2 >= x1 && x2 > axisx.max) {
- if (x1 > axisx.max) {
- continue;
- }
- y2 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1;
- x2 = axisx.max;
- }
- if (!areaOpen) {
- // open area
- ctx.beginPath();
- ctx.moveTo(axisx.p2c(x1), axisy.p2c(bottom));
- areaOpen = true;
- }
- // now first check the case where both is outside
- if (y1 >= axisy.max && y2 >= axisy.max) {
- ctx.lineTo(axisx.p2c(x1), axisy.p2c(axisy.max));
- ctx.lineTo(axisx.p2c(x2), axisy.p2c(axisy.max));
- continue;
- } else if (y1 <= axisy.min && y2 <= axisy.min) {
- ctx.lineTo(axisx.p2c(x1), axisy.p2c(axisy.min));
- ctx.lineTo(axisx.p2c(x2), axisy.p2c(axisy.min));
- continue;
- }
- // else it's a bit more complicated, there might
- // be a flat maxed out rectangle first, then a
- // triangular cutout or reverse; to find these
- // keep track of the current x values
- var x1old = x1,
- x2old = x2;
- // clip the y values, without shortcutting, we
- // go through all cases in turn
- // clip with ymin
- if (y1 <= y2 && y1 < axisy.min && y2 >= axisy.min) {
- x1 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1;
- y1 = axisy.min;
- } else if (y2 <= y1 && y2 < axisy.min && y1 >= axisy.min) {
- x2 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1;
- y2 = axisy.min;
- }
- // clip with ymax
- if (y1 >= y2 && y1 > axisy.max && y2 <= axisy.max) {
- x1 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1;
- y1 = axisy.max;
- } else if (y2 >= y1 && y2 > axisy.max && y1 <= axisy.max) {
- x2 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1;
- y2 = axisy.max;
- }
- // if the x value was changed we got a rectangle
- // to fill
- if (x1 !== x1old) {
- ctx.lineTo(axisx.p2c(x1old), axisy.p2c(y1));
- // it goes to (x1, y1), but we fill that below
- }
- // fill triangular section, this sometimes result
- // in redundant points if (x1, y1) hasn't changed
- // from previous line to, but we just ignore that
- ctx.lineTo(axisx.p2c(x1), axisy.p2c(y1));
- ctx.lineTo(axisx.p2c(x2), axisy.p2c(y2));
- // fill the other rectangle if it's there
- if (x2 !== x2old) {
- ctx.lineTo(axisx.p2c(x2), axisy.p2c(y2));
- ctx.lineTo(axisx.p2c(x2old), axisy.p2c(y2));
- }
- }
- }
- /**
- - drawSeriesLines(series, ctx, plotOffset, plotWidth, plotHeight, drawSymbol, getColorOrGradient)
- This function is used for drawing lines or area fill. In case the series has line decimation function
- attached, before starting to draw, as an optimization the points will first be decimated.
- The series parameter contains the series to be drawn on ctx context. The plotOffset, plotWidth and
- plotHeight are the corresponding parameters of flot used to determine the drawing surface.
- The function getColorOrGradient is used to compute the fill style of lines and area.
- */
- function drawSeriesLines(series, ctx, plotOffset, plotWidth, plotHeight, drawSymbol, getColorOrGradient) {
- ctx.save();
- ctx.translate(plotOffset.left, plotOffset.top);
- ctx.lineJoin = "round";
- if (series.lines.dashes && ctx.setLineDash) {
- ctx.setLineDash(series.lines.dashes);
- }
- var datapoints = {
- format: series.datapoints.format,
- points: series.datapoints.points,
- pointsize: series.datapoints.pointsize
- };
- if (series.decimate) {
- datapoints.points = series.decimate(series, series.xaxis.min, series.xaxis.max, plotWidth, series.yaxis.min, series.yaxis.max, plotHeight);
- }
- var lw = series.lines.lineWidth;
- ctx.lineWidth = lw;
- ctx.strokeStyle = series.color;
- var fillStyle = getFillStyle(series.lines, series.color, 0, plotHeight, getColorOrGradient);
- if (fillStyle) {
- ctx.fillStyle = fillStyle;
- plotLineArea(datapoints, series.xaxis, series.yaxis, series.lines.fillTowards || 0, ctx, series.lines.steps);
- }
- if (lw > 0) {
- plotLine(datapoints, 0, 0, series.xaxis, series.yaxis, ctx, series.lines.steps);
- }
- ctx.restore();
- }
- /**
- - drawSeriesPoints(series, ctx, plotOffset, plotWidth, plotHeight, drawSymbol, getColorOrGradient)
- This function is used for drawing points using a given symbol. In case the series has points decimation
- function attached, before starting to draw, as an optimization the points will first be decimated.
- The series parameter contains the series to be drawn on ctx context. The plotOffset, plotWidth and
- plotHeight are the corresponding parameters of flot used to determine the drawing surface.
- The function drawSymbol is used to compute and draw the symbol chosen for the points.
- */
- function drawSeriesPoints(series, ctx, plotOffset, plotWidth, plotHeight, drawSymbol, getColorOrGradient) {
- function drawCircle(ctx, x, y, radius, shadow, fill) {
- ctx.moveTo(x + radius, y);
- ctx.arc(x, y, radius, 0, shadow ? Math.PI : Math.PI * 2, false);
- }
- drawCircle.fill = true;
- function plotPoints(datapoints, radius, fill, offset, shadow, axisx, axisy, drawSymbolFn) {
- var points = datapoints.points,
- ps = datapoints.pointsize;
- ctx.beginPath();
- for (var i = 0; i < points.length; i += ps) {
- var x = points[i],
- y = points[i + 1];
- if (x == null || x < axisx.min || x > axisx.max || y < axisy.min || y > axisy.max) {
- continue;
- }
- x = axisx.p2c(x);
- y = axisy.p2c(y) + offset;
- drawSymbolFn(ctx, x, y, radius, shadow, fill);
- }
- if (drawSymbolFn.fill && !shadow) {
- ctx.fill();
- }
- ctx.stroke();
- }
- ctx.save();
- ctx.translate(plotOffset.left, plotOffset.top);
- var datapoints = {
- format: series.datapoints.format,
- points: series.datapoints.points,
- pointsize: series.datapoints.pointsize
- };
- if (series.decimatePoints) {
- datapoints.points = series.decimatePoints(series, series.xaxis.min, series.xaxis.max, plotWidth, series.yaxis.min, series.yaxis.max, plotHeight);
- }
- var lw = series.points.lineWidth,
- radius = series.points.radius,
- symbol = series.points.symbol,
- drawSymbolFn;
- if (symbol === 'circle') {
- drawSymbolFn = drawCircle;
- } else if (typeof symbol === 'string' && drawSymbol && drawSymbol[symbol]) {
- drawSymbolFn = drawSymbol[symbol];
- } else if (typeof drawSymbol === 'function') {
- drawSymbolFn = drawSymbol;
- }
- // If the user sets the line width to 0, we change it to a very
- // small value. A line width of 0 seems to force the default of 1.
- if (lw === 0) {
- lw = 0.0001;
- }
- ctx.lineWidth = lw;
- ctx.fillStyle = getFillStyle(series.points, series.color, null, null, getColorOrGradient);
- ctx.strokeStyle = series.color;
- plotPoints(datapoints, radius,
- true, 0, false,
- series.xaxis, series.yaxis, drawSymbolFn);
- ctx.restore();
- }
- function drawBar(x, y, b, barLeft, barRight, fillStyleCallback, axisx, axisy, c, horizontal, lineWidth) {
- var left = x + barLeft,
- right = x + barRight,
- bottom = b, top = y,
- drawLeft, drawRight, drawTop, drawBottom = false,
- tmp;
- drawLeft = drawRight = drawTop = true;
- // in horizontal mode, we start the bar from the left
- // instead of from the bottom so it appears to be
- // horizontal rather than vertical
- if (horizontal) {
- drawBottom = drawRight = drawTop = true;
- drawLeft = false;
- left = b;
- right = x;
- top = y + barLeft;
- bottom = y + barRight;
- // account for negative bars
- if (right < left) {
- tmp = right;
- right = left;
- left = tmp;
- drawLeft = true;
- drawRight = false;
- }
- }
- else {
- drawLeft = drawRight = drawTop = true;
- drawBottom = false;
- left = x + barLeft;
- right = x + barRight;
- bottom = b;
- top = y;
- // account for negative bars
- if (top < bottom) {
- tmp = top;
- top = bottom;
- bottom = tmp;
- drawBottom = true;
- drawTop = false;
- }
- }
- // clip
- if (right < axisx.min || left > axisx.max ||
- top < axisy.min || bottom > axisy.max) {
- return;
- }
- if (left < axisx.min) {
- left = axisx.min;
- drawLeft = false;
- }
- if (right > axisx.max) {
- right = axisx.max;
- drawRight = false;
- }
- if (bottom < axisy.min) {
- bottom = axisy.min;
- drawBottom = false;
- }
- if (top > axisy.max) {
- top = axisy.max;
- drawTop = false;
- }
- left = axisx.p2c(left);
- bottom = axisy.p2c(bottom);
- right = axisx.p2c(right);
- top = axisy.p2c(top);
- // fill the bar
- if (fillStyleCallback) {
- c.fillStyle = fillStyleCallback(bottom, top);
- c.fillRect(left, top, right - left, bottom - top)
- }
- // draw outline
- if (lineWidth > 0 && (drawLeft || drawRight || drawTop || drawBottom)) {
- c.beginPath();
- // FIXME: inline moveTo is buggy with excanvas
- c.moveTo(left, bottom);
- if (drawLeft) {
- c.lineTo(left, top);
- } else {
- c.moveTo(left, top);
- }
- if (drawTop) {
- c.lineTo(right, top);
- } else {
- c.moveTo(right, top);
- }
- if (drawRight) {
- c.lineTo(right, bottom);
- } else {
- c.moveTo(right, bottom);
- }
- if (drawBottom) {
- c.lineTo(left, bottom);
- } else {
- c.moveTo(left, bottom);
- }
- c.stroke();
- }
- }
- /**
- - drawSeriesBars(series, ctx, plotOffset, plotWidth, plotHeight, drawSymbol, getColorOrGradient)
- This function is used for drawing series represented as bars. In case the series has decimation
- function attached, before starting to draw, as an optimization the points will first be decimated.
- The series parameter contains the series to be drawn on ctx context. The plotOffset, plotWidth and
- plotHeight are the corresponding parameters of flot used to determine the drawing surface.
- The function getColorOrGradient is used to compute the fill style of bars.
- */
- function drawSeriesBars(series, ctx, plotOffset, plotWidth, plotHeight, drawSymbol, getColorOrGradient) {
- function plotBars(datapoints, barLeft, barRight, fillStyleCallback, axisx, axisy) {
- var points = datapoints.points,
- ps = datapoints.pointsize,
- fillTowards = series.bars.fillTowards || 0,
- calculatedBottom = fillTowards > axisy.min ? Math.min(axisy.max, fillTowards) : axisy.min;
- for (var i = 0; i < points.length; i += ps) {
- if (points[i] == null) {
- continue;
- }
- // Use third point as bottom if pointsize is 3
- var bottom = ps === 3 ? points[i + 2] : calculatedBottom;
- drawBar(points[i], points[i + 1], bottom, barLeft, barRight, fillStyleCallback, axisx, axisy, ctx, series.bars.horizontal, series.bars.lineWidth);
- }
- }
- ctx.save();
- ctx.translate(plotOffset.left, plotOffset.top);
- var datapoints = {
- format: series.datapoints.format,
- points: series.datapoints.points,
- pointsize: series.datapoints.pointsize
- };
- if (series.decimate) {
- datapoints.points = series.decimate(series, series.xaxis.min, series.xaxis.max, plotWidth);
- }
- ctx.lineWidth = series.bars.lineWidth;
- ctx.strokeStyle = series.color;
- var barLeft;
- var barWidth = series.bars.barWidth[0] || series.bars.barWidth;
- switch (series.bars.align) {
- case "left":
- barLeft = 0;
- break;
- case "right":
- barLeft = -barWidth;
- break;
- default:
- barLeft = -barWidth / 2;
- }
- var fillStyleCallback = series.bars.fill ? function(bottom, top) {
- return getFillStyle(series.bars, series.color, bottom, top, getColorOrGradient);
- } : null;
- plotBars(datapoints, barLeft, barLeft + barWidth, fillStyleCallback, series.xaxis, series.yaxis);
- ctx.restore();
- }
- function getFillStyle(filloptions, seriesColor, bottom, top, getColorOrGradient) {
- var fill = filloptions.fill;
- if (!fill) {
- return null;
- }
- if (filloptions.fillColor) {
- return getColorOrGradient(filloptions.fillColor, bottom, top, seriesColor);
- }
- var c = $.color.parse(seriesColor);
- c.a = typeof fill === "number" ? fill : 0.4;
- c.normalize();
- return c.toString();
- }
- this.drawSeriesLines = drawSeriesLines;
- this.drawSeriesPoints = drawSeriesPoints;
- this.drawSeriesBars = drawSeriesBars;
- this.drawBar = drawBar;
- };
- $.plot.drawSeries = new DrawSeries();
- })(jQuery);
|