Skip to content

Instantly share code, notes, and snippets.

@exlted
Last active February 8, 2019 19:17
Show Gist options
  • Save exlted/9934451a8fa602485a387a0d82006dee to your computer and use it in GitHub Desktop.
Save exlted/9934451a8fa602485a387a0d82006dee to your computer and use it in GitHub Desktop.
Initial work on extending the Flot Value Labels plugin to allow for hover and click events to fire on the tooltips.
/**
* Value Labels Plugin for flot.
* https://github.com/winne27/flot-valuelabels
* https://github.com/winne27/flot-valuelabels/wiki
*
* Implemented some new options (useDecimalComma, showMinValue, showMaxValue)
* changed some default values: align now defaults to center, hideSame now defaults to false
* by Werner Schäffer, October 2014
*
* Using canvas.fillText instead of divs, which is better for printing - by Leonardo Eloy, March 2010.
* Tested with Flot 0.6 and JQuery 1.3.2.
*
* Original homepage: http://sites.google.com/site/petrsstuff/projects/flotvallab
* Released under the MIT license by Petr Blahos, December 2009.
*/
(function($) {
"use strict";
var options = {
series: {
valueLabels: {
show: false,
showTextLabel: false,
showMaxValue: false,
showMinValue: false,
showLastValue: false, // Use this to show the label only for the last value in the series
labelFormatter: function(v) {
return v;
},
// Format the label value to what you want
align: 'center', // can also be 'left' or 'right'
valign: 'above', // can also be 'below', 'middle' or 'bottom'
valignMin: 'below', // can also be 'above', 'middle' or 'bottom'
valignMax: 'above', // can also be 'below', 'middle' or 'bottom'
horizAlign: 'insideMax', // can also be 'outside', 'insideCenter' or 'insideZero'
alwaysLabelAbove: false,
xoffset: 0,
yoffset: 0,
rotate: 0,
useDecimalComma: false,
decimals: false,
hideZero: false,
hideSame: false, // Hide consecutive labels of the same value
reverseAlignBelowZero: false, // reverse align and offset for values below 0
showShadow: false, // false to not use canvas text shadow effect
shadowColor: false, // false = use ctx default
useBackground: false, // set label into box with background color
backgroundColor: '#cccccc', // set backgroundColor like #FFCC00 or darkred
fontcolor: '#222222', // set backgroundColor like #FFCC00 or darkred
useBorder: false, // use a broder arround the label
borderColor: '#999999'
}
}
};
function init(plot) {
var labelInfo = [];
var baseHoverHandler = [];
var baseClickHandler = [];
plot.hooks.draw.push(function(plot, ctx) {
// keep a running total between series for stacked bars.
var stacked = {};
var t;
var x;
var xx;
var x_bb;
var x_pos;
var xdelta;
var y;
var yy;
var y_bb;
var y_pos;
var ydelta;
var valignWork;
var horizAlignWork;
var notShowAll;
var doWork;
var val;
var actAlign = 'left';
var addstack;
var height;
var width;
var bot;
var compDelta;
var textBaseline;
var pointDelta;
labelInfo = [];
$.each(plot.getData(), function(ii, series) {
if (!series.valueLabels.show && !series.stack) return;
var showLastValue = series.valueLabels.showLastValue;
var showMaxValue = series.valueLabels.showMaxValue;
var showMinValue = series.valueLabels.showMinValue;
var showTextLabel = series.valueLabels.showTextLabel;
var labelFormatter = series.valueLabels.labelFormatter;
var xoffset = series.valueLabels.xoffset;
var yoffset = series.valueLabels.yoffset;
var xoffsetMin = series.valueLabels.xoffsetMin || xoffset;
var yoffsetMin = series.valueLabels.yoffsetMin || yoffset;
var xoffsetMax = series.valueLabels.xoffsetMax || xoffset;
var yoffsetMax = series.valueLabels.yoffsetMax || yoffset;
var xoffsetLast = series.valueLabels.xoffsetLast || xoffset;
var yoffsetLast = series.valueLabels.yoffsetLast || yoffset;
var valign = series.valueLabels.valign;
var valignLast = series.valueLabels.valignLast || valign;
var valignMin = series.valueLabels.valignMin;
var valignMax = series.valueLabels.valignMax;
var align = series.valueLabels.align;
var rotate = series.valueLabels.rotate || 0;
var horizAlign = series.valueLabels.horizAlign;
var horizAlignMin = series.valueLabels.horizAlignMin || horizAlign;
var horizAlignMax = series.valueLabels.horizAlignMax || horizAlign;
var horizAlignLast = series.valueLabels.horizAlignLast || horizAlign;
var fontcolor = series.valueLabels.fontcolor || '#222222';
var shadowColor = series.valueLabels.shadowColor;
var font = series.valueLabels.font || series.xaxis.font || '9pt san-serif';
var hideZero = series.valueLabels.hideZero;
var hideSame = series.valueLabels.hideSame;
var reverseAlignBelowZero = series.valueLabels.reverseAlignBelowZero;
var showShadow = series.valueLabels.showShadow;
var useDecimalComma = series.valueLabels.useDecimalComma;
var stackedbar = series.stack;
var decimals = series.valueLabels.decimals;
var useBackground = series.valueLabels.useBackground;
var backgroundColor = series.valueLabels.backgroundColor;
var useBorder = series.valueLabels.useBorder;
var borderColor = series.valueLabels.borderColor;
var order = series.bars.order || 0;
var alwaysLabelAbove = series.valueLabels.alwaysLabelAbove;
// Workaround, since Flot doesn't set this value anymore
series.seriesIndex = ii;
var last_val = null;
var last_x = -1000;
var last_y = -1000;
var xCategories = series.xaxis.options.mode == 'categories';
var yCategories = series.yaxis.options.mode == 'categories';
pointDelta = (series.points.show) ? series.points.radius - series.points.lineWidth / 2 : 0;
if ((showMinValue || showMaxValue) && typeof(series.data[0]) != 'undefined') {
series.data[0][0] = +series.data[0][0];
series.data[0][1] = +series.data[0][1];
var xMin = +series.data[0][0];
var xMax = +series.data[0][0];
var yMin = +series.data[0][1];
var yMax = +series.data[0][1];
for (var i = 1; i < series.data.length; ++i) {
series.data[i][0] = +series.data[i][0];
series.data[i][1] = +series.data[i][1];
if (+series.data[i][0] < xMin) xMin = +series.data[i][0];
if (+series.data[i][0] > xMax) xMax = +series.data[i][0];
if (+series.data[i][1] < yMin) yMin = +series.data[i][1];
if (+series.data[i][1] > yMax) yMax = +series.data[i][1];
}
} else {
showMinValue = false;
showMaxValue = false;
for (var i = 0; i < series.data.length; ++i) {
series.data[i][0] = +series.data[i][0];
series.data[i][1] = +series.data[i][1];
}
}
notShowAll = showMinValue || showMaxValue || showLastValue;
for (var i = 0; i < series.data.length; ++i) {
if (series.data[i] === null) continue;
x = series.data[i][0],
y = series.data[i][1];
var yPos = y;
if(alwaysLabelAbove && y < 0){
yPos = 0;
}
if (showTextLabel && series.data[i].length > 2) {
t = series.data[i][2];
} else {
t = false;
}
if (notShowAll) {
doWork = false;
if (showMinValue && yMin == y && !series.bars.horizontal) {
doWork = true;
xdelta = xoffsetMin;
ydelta = yoffsetMin;
valignWork = valignMin;
showMinValue = false;
}
else if (showMinValue && xMin == x && series.bars.horizontal) {
doWork = true;
xdelta = xoffsetMin;
ydelta = yoffsetMin;
horizAlignWork = horizAlignMin;
showMinValue = false;
} else if (showMaxValue && yMax == y && !series.bars.horizontal) {
doWork = true;
xdelta = xoffsetMax;
ydelta = yoffsetMax;
valignWork = valignMax;
showMaxValue = false;
} else if (showMaxValue && xMax == x && series.bars.horizontal) {
doWork = true;
xdelta = xoffsetMax;
ydelta = yoffsetMax;
horizAlignWork = horizAlignMax;
showMaxValue = false;
} else if (showLastValue && i == series.data.length - 1 && !series.bars.horizontal) {
doWork = true;
xdelta = xoffsetLast;
ydelta = yoffsetLast;
valignWork = valignLast;
} else if (showLastValue && i == series.data.length - 1 && series.bars.horizontal) {
doWork = true;
xdelta = xoffsetLast;
ydelta = yoffsetLast;
horizAlignWork = horizAlignLast;
}
if (!doWork) continue;
} else if (reverseAlignBelowZero && y < 0 && !series.bars.horizontal) {
xdelta = xoffset;
ydelta = -1 * yoffset;
if (valign == 'above') {
valign = 'below';
} else if (valign == 'below') {
valign = 'above';
}
valignWork = valign;
} else {
xdelta = xoffset;
ydelta = yoffset;
valignWork = valign;
horizAlignWork = horizAlign;
}
// for backward compability
if (valignWork == 'top') {
valignWork = 'above';
}
if (xCategories) {
x = series.xaxis.categories[x];
}
if (yCategories) {
y = series.yaxis.categories[y];
}
if (x < series.xaxis.min || x > series.xaxis.max || (y < series.yaxis.min && !alwaysLabelAbove) || y > series.yaxis.max) continue;
if (t !== false) {
val = t;
} else {
val = (series.bars.horizontal) ? x : y;
if (val == null) {
val = ''
}
if (val === 0 && (hideZero || stackedbar)) continue;
if (decimals !== false) {
val = parseFloat(val).toFixed(decimals);
}
}
if (series.valueLabels.valueLabelFunc) {
val = series.valueLabels.valueLabelFunc({
series: series,
seriesIndex: ii,
index: i
});
}
val = "" + val;
val = labelFormatter(val, {series: series, point: series.data[i]});
if (!hideSame || val != last_val || i == series.data.length - 1) {
// if bar is too small to show value inside, show it outside
if (series.bars.horizontal) {
ctx.font = font;
compDelta = (useBorder || useBackground) ? 10 : 6;
if (Math.abs(series.xaxis.p2c(x) - series.xaxis.p2c(0)) < ctx.measureText(val).width + Math.abs(xdelta) + compDelta) {
if (horizAlignWork != 'outside') {
xdelta = -1 * xdelta;
horizAlignWork = 'outside';
}
}
}
if (useDecimalComma) {
val = val.toString().replace('.', ',');
}
// add up y axis for stacked series
addstack = 0;
if (stackedbar) {
var stackedIndex = x + '-' + order;
if (!stacked[stackedIndex]) {
stacked[stackedIndex] = 0.0;
}
addstack = stacked[stackedIndex];
stacked[stackedIndex] = stacked[stackedIndex] + y;
if (!series.valueLabels.show) continue;
}
xx = series.xaxis.p2c(x) + plot.getPlotOffset().left;
yy = series.yaxis.p2c(yPos + addstack) + plot.getPlotOffset().top;
if (!hideSame || Math.abs(yy - last_y) > 20 || last_x < xx) {
last_val = val;
last_x = xx + val.length * 8;
last_y = yy;
if (series.bars.horizontal) {
y_pos = yy;
textBaseline = 'middle';
if (x >= 0) {
if (horizAlignWork == 'outside') {
actAlign = 'left';
xdelta = xdelta + 4;
} else if (horizAlignWork == 'insideMax') {
actAlign = 'right';
xdelta = xdelta - 4;
} else if (horizAlignWork == 'insideCenter') {
actAlign = 'center';
xx = plot.getPlotOffset().left + series.xaxis.p2c(0) + (series.xaxis.p2c(x) - series.xaxis.p2c(0)) / 2 + xdelta;
} else if (horizAlignWork == 'insideZero') {
actAlign = 'left';
xx = plot.getPlotOffset().left + series.xaxis.p2c(0) + 3 + xdelta;
}
} else {
if (horizAlignWork == 'outside') {
actAlign = 'right';
xdelta = xdelta - 4;
} else if (horizAlignWork == 'insideMax') {
actAlign = 'left';
xdelta = xdelta + 4;
} else if (horizAlignWork == 'insideCenter') {
actAlign = 'center';
xx = plot.getPlotOffset().left + series.xaxis.p2c(0) + (series.xaxis.p2c(x) - series.xaxis.p2c(0)) / 2 + xdelta;
} else if (horizAlignWork == 'insideZero') {
actAlign = 'right';
xx = plot.getPlotOffset().left + series.xaxis.p2c(0) - 4 + xdelta;
}
}
x_pos = xx + xdelta;
} else {
if (valignWork == 'bottom') {
textBaseline = 'bottom';
yy = plot.getPlotOffset().top + plot.height();
} else if (valignWork == 'middle') {
textBaseline = 'middle';
bot = plot.getPlotOffset().top + plot.height();
yy = (bot + yy) / 2;
} else if (valignWork == 'below') {
textBaseline = 'top';
ydelta = ydelta + 4 + pointDelta;
} else if (valignWork == 'above') {
textBaseline = 'bottom';
ydelta = ydelta - 2 - pointDelta;
}
x_pos = xx + xdelta;
y_pos = yy + ydelta;
// If the value is on the top of the canvas, we need
// to push it down a little
if (yy <= 0) y_pos = y_pos + 16;
// The same happens with the x axis
if (xx >= plot.width() + plot.getPlotOffset().left) {
x_pos = plot.width() + plot.getPlotOffset().left + xdelta - 3;
actAlign = 'right';
} else {
actAlign = align;
}
}
ctx.font = font;
// Calculate width/height/x_bb/y_bb anyway so we can handle hover and click events nicely
var cached_y_pos = y_pos;
var cached_x_pos = x_pos;
width = ctx.measureText(val).width + 5;
if (width % 2 == 1) {
width++;
}
height = parseInt(font, 10) + 7;
if (textBaseline == 'top') {
y_bb = y_pos;
y_pos = y_pos + 3;
} else if (textBaseline == 'bottom') {
y_bb = y_pos - height - 2;
y_pos = y_pos - 2;
} else if (textBaseline == 'middle') {
y_bb = y_pos - (height + 1) / 2;
y_pos = y_pos + 1;
}
if (actAlign == 'right') {
x_bb = x_pos - width + 1;
x_pos = x_pos - 2;
} else if (actAlign == 'left') {
x_bb = x_pos;
x_pos = x_pos + 3;
} else {
x_bb = x_pos - width / 2;
}
if (useBorder || useBackground) {
ctx.shadowOffsetX = 0;
ctx.shadowOffsetY = 0;
ctx.shadowBlur = 0;
if (useBorder) {
ctx.strokeStyle = borderColor;
ctx.strokeRect(x_bb, y_bb, width, height);
}
if (useBackground) {
ctx.fillStyle = backgroundColor;
ctx.fillRect(x_bb, y_bb, width, height);
}
} else {
y_pos = cached_y_pos;
x_pos = cached_x_pos;
}
ctx.fillStyle = fontcolor;
ctx.save();
if (showShadow) {
ctx.shadowOffsetX = 0;
ctx.shadowOffsetY = 0;
ctx.shadowBlur = 1.5;
ctx.shadowColor = shadowColor;
} else {
ctx.shadowBlur = 0;
}
ctx.translate(x_pos, y_pos);
if (rotate != 0) {
ctx.rotate(rotate * Math.PI / 180);
}
ctx.textAlign = actAlign;
ctx.textBaseline = textBaseline;
ctx.fillText(val, 0, 0);
ctx.restore();
// Store off info about drawn labels so we can check for hover/click events later
var drawInfo = {
x_bb: x_bb,
y_bb: y_bb,
width: width,
height: height,
index: i,
series: ii
};
labelInfo.push(drawInfo);
}
}
}
});
});
plot.hooks.clickHoverFindNearby.push(
function findNearbyLabel(canvasX, canvasY){
var item = null;
var data = plot.getData();
var canvasOffset = $(plot.getCanvas()).offset();
var plotOffset = plot.offset();
canvasX += plotOffset.left;
canvasX -= canvasOffset.left;
canvasY += plotOffset.top;
canvasY -= canvasOffset.top;
if(labelInfo){
for (var j = 0; j < labelInfo.length && !item; j += 1) {
var di = labelInfo[j];
if(canvasX < di.x_bb + di.width && canvasX > di.x_bb
&& canvasY < di.y_bb + di.height && canvasY > di.y_bb){
item = di;
}
}
}
if(item){
var i = item.series;
var j = item.index;
var ps = data[i].datapoints.pointsize;
return { datapoint: data[i].datapoints.points.slice(j * ps, (j + 1) * ps),
dataIndex: j,
series: data[i],
seriesIndex: i };
}
return null;
}
);
}
$.plot.plugins.push({
init: init,
options: options,
name: 'valueLabels',
version: '2.2.0'
});
})(jQuery);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment