14a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair/* Flot plugin for showing crosshairs when the mouse hovers over the plot.
24a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
34a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan SinclairCopyright (c) 2007-2014 IOLA and Ole Laursen.
44a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan SinclairLicensed under the MIT license.
54a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
64a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan SinclairThe plugin supports these options:
74a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
84a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair	crosshair: {
94a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair		mode: null or "x" or "y" or "xy"
104a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair		color: color
114a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair		lineWidth: number
124a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair	}
134a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
144a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan SinclairSet the mode to one of "x", "y" or "xy". The "x" mode enables a vertical
154a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclaircrosshair that lets you trace the values on the x axis, "y" enables a
164a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclairhorizontal crosshair and "xy" enables them both. "color" is the color of the
174a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclaircrosshair (default is "rgba(170, 0, 0, 0.80)"), "lineWidth" is the width of
184a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclairthe drawn lines (default is 1).
194a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
204a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan SinclairThe plugin also adds four public methods:
214a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
224a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  - setCrosshair( pos )
234a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
244a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    Set the position of the crosshair. Note that this is cleared if the user
254a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    moves the mouse. "pos" is in coordinates of the plot and should be on the
264a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    form { x: xpos, y: ypos } (you can use x2/x3/... if you're using multiple
274a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    axes), which is coincidentally the same format as what you get from a
284a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    "plothover" event. If "pos" is null, the crosshair is cleared.
294a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
304a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  - clearCrosshair()
314a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
324a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    Clear the crosshair.
334a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
344a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  - lockCrosshair(pos)
354a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
364a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    Cause the crosshair to lock to the current location, no longer updating if
374a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    the user moves the mouse. Optionally supply a position (passed on to
384a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    setCrosshair()) to move it to.
394a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
404a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    Example usage:
414a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
424a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair	var myFlot = $.plot( $("#graph"), ..., { crosshair: { mode: "x" } } };
434a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair	$("#graph").bind( "plothover", function ( evt, position, item ) {
444a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair		if ( item ) {
454a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair			// Lock the crosshair to the data point being hovered
464a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair			myFlot.lockCrosshair({
474a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair				x: item.datapoint[ 0 ],
484a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair				y: item.datapoint[ 1 ]
494a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair			});
504a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair		} else {
514a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair			// Return normal crosshair operation
524a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair			myFlot.unlockCrosshair();
534a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair		}
544a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair	});
554a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
564a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair  - unlockCrosshair()
574a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
584a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    Free the crosshair to move again after locking it.
594a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair*/
604a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
614a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair(function ($) {
624a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    var options = {
634a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair        crosshair: {
644a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair            mode: null, // one of null, "x", "y" or "xy",
654a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair            color: "rgba(170, 0, 0, 0.80)",
664a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair            lineWidth: 1
674a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair        }
684a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    };
694a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
704a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    function init(plot) {
714a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair        // position of crosshair in pixels
724a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair        var crosshair = { x: -1, y: -1, locked: false };
734a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
744a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair        plot.setCrosshair = function setCrosshair(pos) {
754a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair            if (!pos)
764a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair                crosshair.x = -1;
774a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair            else {
784a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair                var o = plot.p2c(pos);
794a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair                crosshair.x = Math.max(0, Math.min(o.left, plot.width()));
804a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair                crosshair.y = Math.max(0, Math.min(o.top, plot.height()));
814a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair            }
824a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
834a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair            plot.triggerRedrawOverlay();
844a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair        };
854a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
864a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair        plot.clearCrosshair = plot.setCrosshair; // passes null for pos
874a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
884a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair        plot.lockCrosshair = function lockCrosshair(pos) {
894a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair            if (pos)
904a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair                plot.setCrosshair(pos);
914a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair            crosshair.locked = true;
924a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair        };
934a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
944a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair        plot.unlockCrosshair = function unlockCrosshair() {
954a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair            crosshair.locked = false;
964a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair        };
974a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
984a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair        function onMouseOut(e) {
994a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair            if (crosshair.locked)
1004a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair                return;
1014a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
1024a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair            if (crosshair.x != -1) {
1034a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair                crosshair.x = -1;
1044a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair                plot.triggerRedrawOverlay();
1054a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair            }
1064a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair        }
1074a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
1084a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair        function onMouseMove(e) {
1094a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair            if (crosshair.locked)
1104a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair                return;
1114a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
1124a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair            if (plot.getSelection && plot.getSelection()) {
1134a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair                crosshair.x = -1; // hide the crosshair while selecting
1144a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair                return;
1154a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair            }
1164a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
1174a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair            var offset = plot.offset();
1184a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair            crosshair.x = Math.max(0, Math.min(e.pageX - offset.left, plot.width()));
1194a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair            crosshair.y = Math.max(0, Math.min(e.pageY - offset.top, plot.height()));
1204a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair            plot.triggerRedrawOverlay();
1214a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair        }
1224a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
1234a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair        plot.hooks.bindEvents.push(function (plot, eventHolder) {
1244a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair            if (!plot.getOptions().crosshair.mode)
1254a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair                return;
1264a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
1274a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair            eventHolder.mouseout(onMouseOut);
1284a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair            eventHolder.mousemove(onMouseMove);
1294a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair        });
1304a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
1314a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair        plot.hooks.drawOverlay.push(function (plot, ctx) {
1324a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair            var c = plot.getOptions().crosshair;
1334a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair            if (!c.mode)
1344a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair                return;
1354a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
1364a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair            var plotOffset = plot.getPlotOffset();
1374a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
1384a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair            ctx.save();
1394a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair            ctx.translate(plotOffset.left, plotOffset.top);
1404a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
1414a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair            if (crosshair.x != -1) {
1424a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair                var adj = plot.getOptions().crosshair.lineWidth % 2 ? 0.5 : 0;
1434a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
1444a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair                ctx.strokeStyle = c.color;
1454a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair                ctx.lineWidth = c.lineWidth;
1464a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair                ctx.lineJoin = "round";
1474a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
1484a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair                ctx.beginPath();
1494a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair                if (c.mode.indexOf("x") != -1) {
1504a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair                    var drawX = Math.floor(crosshair.x) + adj;
1514a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair                    ctx.moveTo(drawX, 0);
1524a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair                    ctx.lineTo(drawX, plot.height());
1534a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair                }
1544a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair                if (c.mode.indexOf("y") != -1) {
1554a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair                    var drawY = Math.floor(crosshair.y) + adj;
1564a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair                    ctx.moveTo(0, drawY);
1574a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair                    ctx.lineTo(plot.width(), drawY);
1584a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair                }
1594a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair                ctx.stroke();
1604a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair            }
1614a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair            ctx.restore();
1624a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair        });
1634a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
1644a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair        plot.hooks.shutdown.push(function (plot, eventHolder) {
1654a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair            eventHolder.unbind("mouseout", onMouseOut);
1664a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair            eventHolder.unbind("mousemove", onMouseMove);
1674a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair        });
1684a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    }
1694a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair
1704a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    $.plot.plugins.push({
1714a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair        init: init,
1724a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair        options: options,
1734a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair        name: 'crosshair',
1744a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair        version: '1.0'
1754a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair    });
1764a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair})(jQuery);
177