You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
359 lines
12 KiB
359 lines
12 KiB
/* global jQuery */
|
|
|
|
/**
|
|
## jquery.flot.hover.js
|
|
|
|
This plugin is used for mouse hover and tap on a point of plot series.
|
|
It supports the following options:
|
|
```js
|
|
grid: {
|
|
hoverable: false, //to trigger plothover event on mouse hover or tap on a point
|
|
clickable: false //to trigger plotclick event on mouse hover
|
|
}
|
|
```
|
|
|
|
It listens to native mouse move event or click, as well as artificial generated
|
|
tap and touchevent.
|
|
|
|
When the mouse is over a point or a tap on a point is performed, that point or
|
|
the correscponding bar will be highlighted and a "plothover" event will be generated.
|
|
|
|
Custom "touchevent" is triggered when any touch interaction is made. Hover plugin
|
|
handles this events by unhighlighting all of the previously highlighted points and generates
|
|
"plothovercleanup" event to notify any part that is handling plothover (for exemple to cleanup
|
|
the tooltip from webcharts).
|
|
*/
|
|
|
|
(function($) {
|
|
'use strict';
|
|
|
|
var options = {
|
|
grid: {
|
|
hoverable: false,
|
|
clickable: false
|
|
}
|
|
};
|
|
|
|
var browser = $.plot.browser;
|
|
|
|
var eventType = {
|
|
click: 'click',
|
|
hover: 'hover'
|
|
}
|
|
|
|
function init(plot) {
|
|
var lastMouseMoveEvent;
|
|
var highlights = [];
|
|
|
|
function bindEvents(plot, eventHolder) {
|
|
var o = plot.getOptions();
|
|
|
|
if (o.grid.hoverable || o.grid.clickable) {
|
|
eventHolder[0].addEventListener('touchevent', triggerCleanupEvent, false);
|
|
eventHolder[0].addEventListener('tap', generatePlothoverEvent, false);
|
|
}
|
|
|
|
if (o.grid.clickable) {
|
|
eventHolder.bind("click", onClick);
|
|
}
|
|
|
|
if (o.grid.hoverable) {
|
|
eventHolder.bind("mousemove", onMouseMove);
|
|
|
|
// Use bind, rather than .mouseleave, because we officially
|
|
// still support jQuery 1.2.6, which doesn't define a shortcut
|
|
// for mouseenter or mouseleave. This was a bug/oversight that
|
|
// was fixed somewhere around 1.3.x. We can return to using
|
|
// .mouseleave when we drop support for 1.2.6.
|
|
|
|
eventHolder.bind("mouseleave", onMouseLeave);
|
|
}
|
|
}
|
|
|
|
function shutdown(plot, eventHolder) {
|
|
eventHolder[0].removeEventListener('tap', generatePlothoverEvent);
|
|
eventHolder[0].removeEventListener('touchevent', triggerCleanupEvent);
|
|
eventHolder.unbind("mousemove", onMouseMove);
|
|
eventHolder.unbind("mouseleave", onMouseLeave);
|
|
eventHolder.unbind("click", onClick);
|
|
highlights = [];
|
|
}
|
|
|
|
function generatePlothoverEvent(e) {
|
|
var o = plot.getOptions(),
|
|
newEvent = new CustomEvent('mouseevent');
|
|
|
|
//transform from touch event to mouse event format
|
|
newEvent.pageX = e.detail.changedTouches[0].pageX;
|
|
newEvent.pageY = e.detail.changedTouches[0].pageY;
|
|
newEvent.clientX = e.detail.changedTouches[0].clientX;
|
|
newEvent.clientY = e.detail.changedTouches[0].clientY;
|
|
|
|
if (o.grid.hoverable) {
|
|
doTriggerClickHoverEvent(newEvent, eventType.hover, 30);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function doTriggerClickHoverEvent(event, eventType, searchDistance) {
|
|
var series = plot.getData();
|
|
if (event !== undefined &&
|
|
series.length > 0 &&
|
|
series[0].xaxis.c2p !== undefined &&
|
|
series[0].yaxis.c2p !== undefined) {
|
|
var eventToTrigger = "plot" + eventType;
|
|
var seriesFlag = eventType + "able";
|
|
triggerClickHoverEvent(eventToTrigger, event,
|
|
function(i) {
|
|
return series[i][seriesFlag] !== false;
|
|
}, searchDistance);
|
|
}
|
|
}
|
|
|
|
function onMouseMove(e) {
|
|
lastMouseMoveEvent = e;
|
|
plot.getPlaceholder()[0].lastMouseMoveEvent = e;
|
|
doTriggerClickHoverEvent(e, eventType.hover);
|
|
}
|
|
|
|
function onMouseLeave(e) {
|
|
lastMouseMoveEvent = undefined;
|
|
plot.getPlaceholder()[0].lastMouseMoveEvent = undefined;
|
|
triggerClickHoverEvent("plothover", e,
|
|
function(i) {
|
|
return false;
|
|
});
|
|
}
|
|
|
|
function onClick(e) {
|
|
doTriggerClickHoverEvent(e, eventType.click);
|
|
}
|
|
|
|
function triggerCleanupEvent() {
|
|
plot.unhighlight();
|
|
plot.getPlaceholder().trigger('plothovercleanup');
|
|
}
|
|
|
|
// trigger click or hover event (they send the same parameters
|
|
// so we share their code)
|
|
function triggerClickHoverEvent(eventname, event, seriesFilter, searchDistance) {
|
|
var options = plot.getOptions(),
|
|
offset = plot.offset(),
|
|
page = browser.getPageXY(event),
|
|
canvasX = page.X - offset.left,
|
|
canvasY = page.Y - offset.top,
|
|
pos = plot.c2p({
|
|
left: canvasX,
|
|
top: canvasY
|
|
}),
|
|
distance = searchDistance !== undefined ? searchDistance : options.grid.mouseActiveRadius;
|
|
|
|
pos.pageX = page.X;
|
|
pos.pageY = page.Y;
|
|
|
|
var items = plot.findNearbyItems(canvasX, canvasY, seriesFilter, distance);
|
|
var item = items[0];
|
|
|
|
for (let i = 1; i < items.length; ++i) {
|
|
if (item.distance === undefined ||
|
|
items[i].distance < item.distance) {
|
|
item = items[i];
|
|
}
|
|
}
|
|
|
|
if (item) {
|
|
// fill in mouse pos for any listeners out there
|
|
item.pageX = parseInt(item.series.xaxis.p2c(item.datapoint[0]) + offset.left, 10);
|
|
item.pageY = parseInt(item.series.yaxis.p2c(item.datapoint[1]) + offset.top, 10);
|
|
} else {
|
|
item = null;
|
|
}
|
|
|
|
if (options.grid.autoHighlight) {
|
|
// clear auto-highlights
|
|
for (let i = 0; i < highlights.length; ++i) {
|
|
var h = highlights[i];
|
|
if ((h.auto === eventname &&
|
|
!(item && h.series === item.series &&
|
|
h.point[0] === item.datapoint[0] &&
|
|
h.point[1] === item.datapoint[1])) || !item) {
|
|
unhighlight(h.series, h.point);
|
|
}
|
|
}
|
|
|
|
if (item) {
|
|
highlight(item.series, item.datapoint, eventname);
|
|
}
|
|
}
|
|
|
|
plot.getPlaceholder().trigger(eventname, [pos, item, items]);
|
|
}
|
|
|
|
function highlight(s, point, auto) {
|
|
if (typeof s === "number") {
|
|
s = plot.getData()[s];
|
|
}
|
|
|
|
if (typeof point === "number") {
|
|
var ps = s.datapoints.pointsize;
|
|
point = s.datapoints.points.slice(ps * point, ps * (point + 1));
|
|
}
|
|
|
|
var i = indexOfHighlight(s, point);
|
|
if (i === -1) {
|
|
highlights.push({
|
|
series: s,
|
|
point: point,
|
|
auto: auto
|
|
});
|
|
|
|
plot.triggerRedrawOverlay();
|
|
} else if (!auto) {
|
|
highlights[i].auto = false;
|
|
}
|
|
}
|
|
|
|
function unhighlight(s, point) {
|
|
if (s == null && point == null) {
|
|
highlights = [];
|
|
plot.triggerRedrawOverlay();
|
|
return;
|
|
}
|
|
|
|
if (typeof s === "number") {
|
|
s = plot.getData()[s];
|
|
}
|
|
|
|
if (typeof point === "number") {
|
|
var ps = s.datapoints.pointsize;
|
|
point = s.datapoints.points.slice(ps * point, ps * (point + 1));
|
|
}
|
|
|
|
var i = indexOfHighlight(s, point);
|
|
if (i !== -1) {
|
|
highlights.splice(i, 1);
|
|
|
|
plot.triggerRedrawOverlay();
|
|
}
|
|
}
|
|
|
|
function indexOfHighlight(s, p) {
|
|
for (var i = 0; i < highlights.length; ++i) {
|
|
var h = highlights[i];
|
|
if (h.series === s &&
|
|
h.point[0] === p[0] &&
|
|
h.point[1] === p[1]) {
|
|
return i;
|
|
}
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
function processDatapoints() {
|
|
triggerCleanupEvent();
|
|
doTriggerClickHoverEvent(lastMouseMoveEvent, eventType.hover);
|
|
}
|
|
|
|
function setupGrid() {
|
|
doTriggerClickHoverEvent(lastMouseMoveEvent, eventType.hover);
|
|
}
|
|
|
|
function drawOverlay(plot, octx, overlay) {
|
|
var plotOffset = plot.getPlotOffset(),
|
|
i, hi;
|
|
|
|
octx.save();
|
|
octx.translate(plotOffset.left, plotOffset.top);
|
|
for (i = 0; i < highlights.length; ++i) {
|
|
hi = highlights[i];
|
|
|
|
if (hi.series.bars.show) drawBarHighlight(hi.series, hi.point, octx);
|
|
else drawPointHighlight(hi.series, hi.point, octx, plot);
|
|
}
|
|
octx.restore();
|
|
}
|
|
|
|
function drawPointHighlight(series, point, octx, plot) {
|
|
var x = point[0],
|
|
y = point[1],
|
|
axisx = series.xaxis,
|
|
axisy = series.yaxis,
|
|
highlightColor = (typeof series.highlightColor === "string") ? series.highlightColor : $.color.parse(series.color).scale('a', 0.5).toString();
|
|
|
|
if (x < axisx.min || x > axisx.max || y < axisy.min || y > axisy.max) {
|
|
return;
|
|
}
|
|
|
|
var pointRadius = series.points.radius + series.points.lineWidth / 2;
|
|
octx.lineWidth = pointRadius;
|
|
octx.strokeStyle = highlightColor;
|
|
var radius = 1.5 * pointRadius;
|
|
x = axisx.p2c(x);
|
|
y = axisy.p2c(y);
|
|
|
|
octx.beginPath();
|
|
var symbol = series.points.symbol;
|
|
if (symbol === 'circle') {
|
|
octx.arc(x, y, radius, 0, 2 * Math.PI, false);
|
|
} else if (typeof symbol === 'string' && plot.drawSymbol && plot.drawSymbol[symbol]) {
|
|
plot.drawSymbol[symbol](octx, x, y, radius, false);
|
|
}
|
|
|
|
octx.closePath();
|
|
octx.stroke();
|
|
}
|
|
|
|
function drawBarHighlight(series, point, octx) {
|
|
var highlightColor = (typeof series.highlightColor === "string") ? series.highlightColor : $.color.parse(series.color).scale('a', 0.5).toString(),
|
|
fillStyle = highlightColor,
|
|
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;
|
|
}
|
|
|
|
octx.lineWidth = series.bars.lineWidth;
|
|
octx.strokeStyle = highlightColor;
|
|
|
|
var fillTowards = series.bars.fillTowards || 0,
|
|
bottom = fillTowards > series.yaxis.min ? Math.min(series.yaxis.max, fillTowards) : series.yaxis.min;
|
|
|
|
$.plot.drawSeries.drawBar(point[0], point[1], point[2] || bottom, barLeft, barLeft + barWidth,
|
|
function() {
|
|
return fillStyle;
|
|
}, series.xaxis, series.yaxis, octx, series.bars.horizontal, series.bars.lineWidth);
|
|
}
|
|
|
|
function initHover(plot, options) {
|
|
plot.highlight = highlight;
|
|
plot.unhighlight = unhighlight;
|
|
if (options.grid.hoverable || options.grid.clickable) {
|
|
plot.hooks.drawOverlay.push(drawOverlay);
|
|
plot.hooks.processDatapoints.push(processDatapoints);
|
|
plot.hooks.setupGrid.push(setupGrid);
|
|
}
|
|
|
|
lastMouseMoveEvent = plot.getPlaceholder()[0].lastMouseMoveEvent;
|
|
}
|
|
|
|
plot.hooks.bindEvents.push(bindEvents);
|
|
plot.hooks.shutdown.push(shutdown);
|
|
plot.hooks.processOptions.push(initHover);
|
|
}
|
|
|
|
$.plot.plugins.push({
|
|
init: init,
|
|
options: options,
|
|
name: 'hover',
|
|
version: '0.1'
|
|
});
|
|
})(jQuery);
|
|
|