UNPKG

higlass

Version:

HiGlass Hi-C / genomic / large data viewer

211 lines (180 loc) 6.9 kB
import { globalPubSub } from 'pub-sub-es'; import hexStrToInt from './hex-string-to-int'; // Configs import GLOBALS from '../configs/globals'; const COLOR = 0xaaaaaa; const ALPHA = 1.0; /** * @typedef MouseTrackOptions * @property {string=} mousePositionColor - Color of the mouse position. * @property {number=} mousePositionAlpha - Alpha of the mouse position. */ /** * Actual interface for initializing to show the mouse location * * @param {import('pub-sub-es').PubSub} pubSub - PubSub service. * @param {Array<import('pub-sub-es').Subscription>} pubSubs - Subscribed PubSub events. * @param {MouseTrackOptions} options - Track options. * @param {() => [import('../types').Scale, import('../types').Scale]} getScales - Getter for the track's X and Y scales. * @param {() => [number, number]} getPosition - Getter for the track's position. * @param {() => [number, number]} getDimensions - Getter for the track's dimensions. * @param {() => boolean} getIsFlipped - Getter determining if a track has been * flipped from horizontal to vertical. * @param {boolean} is2d - If `true` draw both dimensions of the mouse location. * @param {boolean} isGlobal - If `true` local and global events will trigger * the mouse position drawing. * @return {import('pixi.js').Graphics} - PIXI graphics the mouse location is drawn on. */ const showMousePosition = ( pubSub, pubSubs, options, getScales, getPosition, getDimensions, getIsFlipped, is2d, isGlobal, ) => { pubSub.publish('app.animateOnMouseMove', true); const color = options.mousePositionColor ? hexStrToInt(options.mousePositionColor) : COLOR; const alpha = options.mousePositionAlpha || ALPHA; // Graphics for cursor position const graphics = new GLOBALS.PIXI.Graphics(); // This clears the mouse position graphics, i.e., the mouse position will not // be visible afterwards. const clearGraphics = () => { graphics.clear(); }; /** * Draw 1D mouse location (cross) hair onto the PIXI graphics. * * @param {number} mousePos - One dimension of the mouse location (integer). * @param {boolean=} isHorizontal - If `true` the dimension to be drawn is * horizontal. * @param {boolean=} isNoClear If `true` do not clear the graphics. * @return {void} */ const drawMousePosition = (mousePos, isHorizontal, isNoClear) => { if (!isNoClear) clearGraphics(); graphics.lineStyle(1, color, alpha); if (isHorizontal) { const addition = is2d ? getPosition()[0] : 0; graphics.moveTo(0, mousePos); graphics.lineTo(getDimensions()[0] + addition, mousePos); } else { const addition = is2d ? getPosition()[1] : 0; graphics.moveTo(mousePos, 0); graphics.lineTo(mousePos, getDimensions()[1] + addition); } }; /** * @typedef NoHoveredTracksEvent * @property {true} noHoveredTracks - If `true` no tracks are hovered. * @property {false=} isFromVerticalTrack - If `true` the event is from a vertical track. */ /** * @typedef TrackEvent * @property {false=} noHoveredTracks - If `true` no tracks are hovered. * @property {boolean} isFromVerticalTrack - If `true` the event is from a vertical track. * @property {boolean} isFrom2dTrack - If `true` the event is from a 2D track. * @property {number} dataY - Y position of the mouse. * @property {number} dataX - X position of the mouse. */ /** * Mouse move handler * * @param {Event & (NoHoveredTracksEvent | TrackEvent)} event - Event object. */ const mouseMoveHandler = (event) => { if (event.noHoveredTracks) { clearGraphics(); return graphics; } let x; let y; if (event.isFromVerticalTrack) { x = event.dataY; y = event.dataY; } else { x = event.dataX; y = event.isFrom2dTrack ? event.dataY : event.dataX; } // 2d or central tracks are not offset and rather rely on a mask, i.e., the // top left *visible* position is *not* [0,0] but given by `getPosition()`. const offset = is2d ? getPosition() : [0, 0]; // `getIsFlipped()` is `true` when a horizontal track has been flipped by 90 // degree, i.e., is a vertical track. const mousePos = getIsFlipped() ? getScales()[0](y) + offset[1] : getScales()[0](x) + offset[0]; drawMousePosition(mousePos); // Also draw the second dimension if (is2d) drawMousePosition(getScales()[1](y) + offset[1], true, true); return graphics; }; pubSubs.push(pubSub.subscribe('app.mouseMove', mouseMoveHandler)); pubSubs.push(pubSub.subscribe('app.mouseLeave', clearGraphics)); pubSubs.push(pubSub.subscribe('blur', clearGraphics)); if (isGlobal) { pubSubs.push(globalPubSub.subscribe('higlass.mouseMove', mouseMoveHandler)); } return graphics; }; /** * @typedef ClassContext * @property {import('pixi.js').Container=} pForeground * @property {import('pixi.js').Container=} pMasked * @property {import('pixi.js').Container=} pMain * @property {() => import('../types').Scale} xScale * @property {() => import('../types').Scale} yScale * @property {() => [number, number]} getPosition * @property {() => [number, number]} getDimensions * @property {import('pub-sub-es').PubSub} pubSub * @property {Array<import('pub-sub-es').Subscription>} pubSubs * @property {(prop: 'flipText') => () => boolean} getProp * @property {{}} options */ /** * Public API for showing the mouse location. * * @description * This is just a convenience wrapper to avoid code duplication. * `showMousePosition` is the actual function and could be called from within * each class as well. * * @param {ClassContext} context - Class context, i.e., `this`. * @param {Boolean} is2d - If `true` both dimensions of the mouse location * should be shown. E.g., on a central track. * @param {Boolean} isGlobal - If `true` local and global events will trigger * the mouse position drawing. * @return {Function} - Method to remove graphics showing the mouse location. */ const setupShowMousePosition = (context, is2d = false, isGlobal = false) => { const scene = is2d ? context.pMasked : context.pForeground || context.pMain; if (!scene) { throw new Error( 'setupShowMousePosition: No scene found. Please make sure to call this method after the scene has been initialized.', ); } /** @type {() => [import('../types').Scale, import('../types').Scale]} */ const getScales = () => [context.xScale(), context.yScale()]; const graphics = showMousePosition( context.pubSub, context.pubSubs, context.options, getScales, context.getPosition.bind(context), context.getDimensions.bind(context), context.getProp('flipText'), is2d, isGlobal, ); scene.addChild(graphics); return () => { scene.removeChild(graphics); }; }; export default setupShowMousePosition;