|
/** Lines **/ |
|
Flotr.addType('lines', { |
|
options: { |
|
show: false, // => setting to true will show lines, false will hide |
|
lineWidth: 2, // => line width in pixels |
|
fill: false, // => true to fill the area from the line to the x axis, false for (transparent) no fill |
|
fillBorder: false, // => draw a border around the fill |
|
fillColor: null, // => fill color |
|
fillOpacity: 0.4, // => opacity of the fill color, set to 1 for a solid fill, 0 hides the fill |
|
steps: false, // => draw steps |
|
stacked: false // => setting to true will show stacked lines, false will show normal lines |
|
}, |
|
|
|
stack : { |
|
values : [] |
|
}, |
|
|
|
/** |
|
* Draws lines series in the canvas element. |
|
* @param {Object} options |
|
*/ |
|
draw : function (options) { |
|
|
|
var |
|
context = options.context, |
|
lineWidth = options.lineWidth, |
|
shadowSize = options.shadowSize, |
|
offset; |
|
|
|
context.save(); |
|
context.lineJoin = 'round'; |
|
|
|
if (shadowSize) { |
|
|
|
context.lineWidth = shadowSize / 2; |
|
offset = lineWidth / 2 + context.lineWidth / 2; |
|
|
|
// @TODO do this instead with a linear gradient |
|
context.strokeStyle = "rgba(0,0,0,0.1)"; |
|
this.plot(options, offset + shadowSize / 2, false); |
|
|
|
context.strokeStyle = "rgba(0,0,0,0.2)"; |
|
this.plot(options, offset, false); |
|
} |
|
|
|
context.lineWidth = lineWidth; |
|
context.strokeStyle = options.color; |
|
|
|
this.plot(options, 0, true); |
|
|
|
context.restore(); |
|
}, |
|
|
|
plot : function (options, shadowOffset, incStack) { |
|
|
|
var |
|
context = options.context, |
|
width = options.width, |
|
height = options.height, |
|
xScale = options.xScale, |
|
yScale = options.yScale, |
|
data = options.data, |
|
stack = options.stacked ? this.stack : false, |
|
length = data.length - 1, |
|
prevx = null, |
|
prevy = null, |
|
zero = yScale(0), |
|
start = null, |
|
x1, x2, y1, y2, stack1, stack2, i; |
|
|
|
if (length < 1) return; |
|
|
|
context.beginPath(); |
|
|
|
for (i = 0; i < length; ++i) { |
|
|
|
// To allow empty values |
|
if (data[i][1] === null || data[i+1][1] === null) { |
|
if (options.fill) { |
|
if (i > 0 && data[i][1]) { |
|
context.stroke(); |
|
fill(); |
|
start = null; |
|
context.closePath(); |
|
context.beginPath(); |
|
} |
|
} |
|
continue; |
|
} |
|
|
|
// Zero is infinity for log scales |
|
// TODO handle zero for logarithmic |
|
// if (xa.options.scaling === 'logarithmic' && (data[i][0] <= 0 || data[i+1][0] <= 0)) continue; |
|
// if (ya.options.scaling === 'logarithmic' && (data[i][1] <= 0 || data[i+1][1] <= 0)) continue; |
|
|
|
x1 = xScale(data[i][0]); |
|
x2 = xScale(data[i+1][0]); |
|
|
|
if (start === null) start = data[i]; |
|
|
|
if (stack) { |
|
|
|
stack1 = stack.values[data[i][0]] || 0; |
|
stack2 = stack.values[data[i+1][0]] || stack.values[data[i][0]] || 0; |
|
|
|
y1 = yScale(data[i][1] + stack1); |
|
y2 = yScale(data[i+1][1] + stack2); |
|
|
|
if(incStack){ |
|
stack.values[data[i][0]] = data[i][1]+stack1; |
|
|
|
if(i == length-1) |
|
stack.values[data[i+1][0]] = data[i+1][1]+stack2; |
|
} |
|
} |
|
else{ |
|
y1 = yScale(data[i][1]); |
|
y2 = yScale(data[i+1][1]); |
|
} |
|
|
|
if ( |
|
(y1 > height && y2 > height) || |
|
(y1 < 0 && y2 < 0) || |
|
(x1 < 0 && x2 < 0) || |
|
(x1 > width && x2 > width) |
|
) continue; |
|
|
|
if((prevx != x1) || (prevy != y1 + shadowOffset)) |
|
context.moveTo(x1, y1 + shadowOffset); |
|
|
|
prevx = x2; |
|
prevy = y2 + shadowOffset; |
|
if (options.steps) { |
|
context.lineTo(prevx + shadowOffset / 2, y1 + shadowOffset); |
|
context.lineTo(prevx + shadowOffset / 2, prevy); |
|
} else { |
|
context.lineTo(prevx, prevy); |
|
} |
|
} |
|
|
|
if (!options.fill || options.fill && !options.fillBorder) context.stroke(); |
|
|
|
fill(); |
|
|
|
function fill () { |
|
// TODO stacked lines |
|
if(!shadowOffset && options.fill && start){ |
|
x1 = xScale(start[0]); |
|
context.fillStyle = options.fillStyle; |
|
context.lineTo(x2, zero); |
|
context.lineTo(x1, zero); |
|
context.lineTo(x1, yScale(start[1])); |
|
context.fill(); |
|
if (options.fillBorder) { |
|
context.stroke(); |
|
} |
|
} |
|
} |
|
|
|
context.closePath(); |
|
}, |
|
|
|
// Perform any pre-render precalculations (this should be run on data first) |
|
// - Pie chart total for calculating measures |
|
// - Stacks for lines and bars |
|
// precalculate : function () { |
|
// } |
|
// |
|
// |
|
// Get any bounds after pre calculation (axis can fetch this if does not have explicit min/max) |
|
// getBounds : function () { |
|
// } |
|
// getMin : function () { |
|
// } |
|
// getMax : function () { |
|
// } |
|
// |
|
// |
|
// Padding around rendered elements |
|
// getPadding : function () { |
|
// } |
|
|
|
extendYRange : function (axis, data, options, lines) { |
|
|
|
var o = axis.options; |
|
|
|
// If stacked and auto-min |
|
if (options.stacked && ((!o.max && o.max !== 0) || (!o.min && o.min !== 0))) { |
|
|
|
var |
|
newmax = axis.max, |
|
newmin = axis.min, |
|
positiveSums = lines.positiveSums || {}, |
|
negativeSums = lines.negativeSums || {}, |
|
x, j; |
|
|
|
for (j = 0; j < data.length; j++) { |
|
|
|
x = data[j][0] + ''; |
|
|
|
// Positive |
|
if (data[j][1] > 0) { |
|
positiveSums[x] = (positiveSums[x] || 0) + data[j][1]; |
|
newmax = Math.max(newmax, positiveSums[x]); |
|
} |
|
|
|
// Negative |
|
else { |
|
negativeSums[x] = (negativeSums[x] || 0) + data[j][1]; |
|
newmin = Math.min(newmin, negativeSums[x]); |
|
} |
|
} |
|
|
|
lines.negativeSums = negativeSums; |
|
lines.positiveSums = positiveSums; |
|
|
|
axis.max = newmax; |
|
axis.min = newmin; |
|
} |
|
|
|
if (options.steps) { |
|
|
|
this.hit = function (options) { |
|
var |
|
data = options.data, |
|
args = options.args, |
|
yScale = options.yScale, |
|
mouse = args[0], |
|
length = data.length, |
|
n = args[1], |
|
x = options.xInverse(mouse.relX), |
|
relY = mouse.relY, |
|
i; |
|
|
|
for (i = 0; i < length - 1; i++) { |
|
if (x >= data[i][0] && x <= data[i+1][0]) { |
|
if (Math.abs(yScale(data[i][1]) - relY) < 8) { |
|
n.x = data[i][0]; |
|
n.y = data[i][1]; |
|
n.index = i; |
|
n.seriesIndex = options.index; |
|
} |
|
break; |
|
} |
|
} |
|
}; |
|
|
|
this.drawHit = function (options) { |
|
var |
|
context = options.context, |
|
args = options.args, |
|
data = options.data, |
|
xScale = options.xScale, |
|
index = args.index, |
|
x = xScale(args.x), |
|
y = options.yScale(args.y), |
|
x2; |
|
|
|
if (data.length - 1 > index) { |
|
x2 = options.xScale(data[index + 1][0]); |
|
context.save(); |
|
context.strokeStyle = options.color; |
|
context.lineWidth = options.lineWidth; |
|
context.beginPath(); |
|
context.moveTo(x, y); |
|
context.lineTo(x2, y); |
|
context.stroke(); |
|
context.closePath(); |
|
context.restore(); |
|
} |
|
}; |
|
|
|
this.clearHit = function (options) { |
|
var |
|
context = options.context, |
|
args = options.args, |
|
data = options.data, |
|
xScale = options.xScale, |
|
width = options.lineWidth, |
|
index = args.index, |
|
x = xScale(args.x), |
|
y = options.yScale(args.y), |
|
x2; |
|
|
|
if (data.length - 1 > index) { |
|
x2 = options.xScale(data[index + 1][0]); |
|
context.clearRect(x - width, y - width, x2 - x + 2 * width, 2 * width); |
|
} |
|
}; |
|
} |
|
} |
|
|
|
}); |
|
|