275 lines
7.9 KiB
JavaScript
275 lines
7.9 KiB
JavaScript
/** Bars **/
|
|
Flotr.addType('bars', {
|
|
|
|
options: {
|
|
show: false, // => setting to true will show bars, false will hide
|
|
lineWidth: 2, // => in pixels
|
|
barWidth: 1, // => in units of the x axis
|
|
fill: true, // => true to fill the area from the line to the x axis, false for (transparent) no fill
|
|
fillColor: null, // => fill color
|
|
fillOpacity: 0.4, // => opacity of the fill color, set to 1 for a solid fill, 0 hides the fill
|
|
horizontal: false, // => horizontal bars (x and y inverted)
|
|
stacked: false, // => stacked bar charts
|
|
centered: true, // => center the bars to their x axis value
|
|
topPadding: 0.1 // => top padding in percent
|
|
},
|
|
|
|
stack : {
|
|
positive : [],
|
|
negative : [],
|
|
_positive : [], // Shadow
|
|
_negative : [] // Shadow
|
|
},
|
|
|
|
draw : function (options) {
|
|
var
|
|
context = options.context;
|
|
|
|
context.save();
|
|
context.lineJoin = 'miter';
|
|
// @TODO linewidth not interpreted the right way.
|
|
context.lineWidth = options.lineWidth;
|
|
context.strokeStyle = options.color;
|
|
if (options.fill) context.fillStyle = options.fillStyle;
|
|
|
|
this.plot(options);
|
|
|
|
context.restore();
|
|
},
|
|
|
|
plot : function (options) {
|
|
|
|
var
|
|
data = options.data,
|
|
context = options.context,
|
|
shadowSize = options.shadowSize,
|
|
i, geometry, left, top, width, height;
|
|
|
|
if (data.length < 1) return;
|
|
|
|
this.translate(context, options.horizontal);
|
|
|
|
for (i = 0; i < data.length; i++) {
|
|
|
|
geometry = this.getBarGeometry(data[i][0], data[i][1], options);
|
|
if (geometry === null) continue;
|
|
|
|
left = geometry.left;
|
|
top = geometry.top;
|
|
width = geometry.width;
|
|
height = geometry.height;
|
|
|
|
if (options.fill) context.fillRect(left, top, width, height);
|
|
if (shadowSize) {
|
|
context.save();
|
|
context.fillStyle = 'rgba(0,0,0,0.05)';
|
|
context.fillRect(left + shadowSize, top + shadowSize, width, height);
|
|
context.restore();
|
|
}
|
|
if (options.lineWidth) {
|
|
context.strokeRect(left, top, width, height);
|
|
}
|
|
}
|
|
},
|
|
|
|
translate : function (context, horizontal) {
|
|
if (horizontal) {
|
|
context.rotate(-Math.PI / 2);
|
|
context.scale(-1, 1);
|
|
}
|
|
},
|
|
|
|
getBarGeometry : function (x, y, options) {
|
|
|
|
var
|
|
horizontal = options.horizontal,
|
|
barWidth = options.barWidth,
|
|
centered = options.centered,
|
|
stack = options.stacked ? this.stack : false,
|
|
lineWidth = options.lineWidth,
|
|
bisection = centered ? barWidth / 2 : 0,
|
|
xScale = horizontal ? options.yScale : options.xScale,
|
|
yScale = horizontal ? options.xScale : options.yScale,
|
|
xValue = horizontal ? y : x,
|
|
yValue = horizontal ? x : y,
|
|
stackOffset = 0,
|
|
stackValue, left, right, top, bottom;
|
|
|
|
// Stacked bars
|
|
if (stack) {
|
|
stackValue = yValue > 0 ? stack.positive : stack.negative;
|
|
stackOffset = stackValue[xValue] || stackOffset;
|
|
stackValue[xValue] = stackOffset + yValue;
|
|
}
|
|
|
|
left = xScale(xValue - bisection);
|
|
right = xScale(xValue + barWidth - bisection);
|
|
top = yScale(yValue + stackOffset);
|
|
bottom = yScale(stackOffset);
|
|
|
|
// TODO for test passing... probably looks better without this
|
|
if (bottom < 0) bottom = 0;
|
|
|
|
// TODO Skipping...
|
|
// if (right < xa.min || left > xa.max || top < ya.min || bottom > ya.max) continue;
|
|
|
|
return (x === null || y === null) ? null : {
|
|
x : xValue,
|
|
y : yValue,
|
|
xScale : xScale,
|
|
yScale : yScale,
|
|
top : top,
|
|
left : Math.min(left, right) - lineWidth / 2,
|
|
width : Math.abs(right - left) - lineWidth,
|
|
height : bottom - top
|
|
};
|
|
},
|
|
|
|
hit : function (options) {
|
|
var
|
|
data = options.data,
|
|
args = options.args,
|
|
mouse = args[0],
|
|
n = args[1],
|
|
x = mouse.x,
|
|
y = mouse.y,
|
|
hitGeometry = this.getBarGeometry(x, y, options),
|
|
width = hitGeometry.width / 2,
|
|
left = hitGeometry.left,
|
|
geometry, i;
|
|
|
|
for (i = data.length; i--;) {
|
|
geometry = this.getBarGeometry(data[i][0], data[i][1], options);
|
|
if (geometry.y > hitGeometry.y && Math.abs(left - geometry.left) < width) {
|
|
n.x = data[i][0];
|
|
n.y = data[i][1];
|
|
n.index = i;
|
|
n.seriesIndex = options.index;
|
|
}
|
|
}
|
|
},
|
|
|
|
drawHit : function (options) {
|
|
// TODO hits for stacked bars; implement using calculateStack option?
|
|
var
|
|
context = options.context,
|
|
args = options.args,
|
|
geometry = this.getBarGeometry(args.x, args.y, options),
|
|
left = geometry.left,
|
|
top = geometry.top,
|
|
width = geometry.width,
|
|
height = geometry.height;
|
|
|
|
context.save();
|
|
context.strokeStyle = options.color;
|
|
context.lineWidth = options.lineWidth;
|
|
this.translate(context, options.horizontal);
|
|
|
|
// Draw highlight
|
|
context.beginPath();
|
|
context.moveTo(left, top + height);
|
|
context.lineTo(left, top);
|
|
context.lineTo(left + width, top);
|
|
context.lineTo(left + width, top + height);
|
|
if (options.fill) {
|
|
context.fillStyle = options.fillStyle;
|
|
context.fill();
|
|
}
|
|
context.stroke();
|
|
context.closePath();
|
|
|
|
context.restore();
|
|
},
|
|
|
|
clearHit: function (options) {
|
|
var
|
|
context = options.context,
|
|
args = options.args,
|
|
geometry = this.getBarGeometry(args.x, args.y, options),
|
|
left = geometry.left,
|
|
width = geometry.width,
|
|
top = geometry.top,
|
|
height = geometry.height,
|
|
lineWidth = 2 * options.lineWidth;
|
|
|
|
context.save();
|
|
this.translate(context, options.horizontal);
|
|
context.clearRect(
|
|
left - lineWidth,
|
|
Math.min(top, top + height) - lineWidth,
|
|
width + 2 * lineWidth,
|
|
Math.abs(height) + 2 * lineWidth
|
|
);
|
|
context.restore();
|
|
},
|
|
|
|
extendXRange : function (axis, data, options, bars) {
|
|
this._extendRange(axis, data, options, bars);
|
|
},
|
|
|
|
extendYRange : function (axis, data, options, bars) {
|
|
this._extendRange(axis, data, options, bars);
|
|
},
|
|
_extendRange: function (axis, data, options, bars) {
|
|
|
|
var
|
|
max = axis.options.max;
|
|
|
|
if (_.isNumber(max) || _.isString(max)) return;
|
|
|
|
var
|
|
newmin = axis.min,
|
|
newmax = axis.max,
|
|
horizontal = options.horizontal,
|
|
orientation = axis.orientation,
|
|
positiveSums = this.positiveSums || {},
|
|
negativeSums = this.negativeSums || {},
|
|
value, datum, index, j;
|
|
|
|
// Sides of bars
|
|
if ((orientation == 1 && !horizontal) || (orientation == -1 && horizontal)) {
|
|
if (options.centered) {
|
|
newmax = Math.max(axis.datamax + options.barWidth, newmax);
|
|
newmin = Math.min(axis.datamin - options.barWidth, newmin);
|
|
}
|
|
}
|
|
|
|
if (options.stacked &&
|
|
((orientation == 1 && horizontal) || (orientation == -1 && !horizontal))){
|
|
|
|
for (j = data.length; j--;) {
|
|
value = data[j][(orientation == 1 ? 1 : 0)]+'';
|
|
datum = data[j][(orientation == 1 ? 0 : 1)];
|
|
|
|
// Positive
|
|
if (datum > 0) {
|
|
positiveSums[value] = (positiveSums[value] || 0) + datum;
|
|
newmax = Math.max(newmax, positiveSums[value]);
|
|
}
|
|
|
|
// Negative
|
|
else {
|
|
negativeSums[value] = (negativeSums[value] || 0) + datum;
|
|
newmin = Math.min(newmin, negativeSums[value]);
|
|
}
|
|
}
|
|
}
|
|
|
|
// End of bars
|
|
if ((orientation == 1 && horizontal) || (orientation == -1 && !horizontal)) {
|
|
if (options.topPadding && (axis.max === axis.datamax || (options.stacked && this.stackMax !== newmax))) {
|
|
newmax += options.topPadding * (newmax - newmin);
|
|
}
|
|
}
|
|
|
|
this.stackMin = newmin;
|
|
this.stackMax = newmax;
|
|
this.negativeSums = negativeSums;
|
|
this.positiveSums = positiveSums;
|
|
|
|
axis.max = newmax;
|
|
axis.min = newmin;
|
|
}
|
|
|
|
});
|