UNPKG

ideogram

Version:

Chromosome visualization for the web

181 lines (150 loc) 5.26 kB
import {d3, formatSiPrefix} from '../lib'; // import {getShapes} from './draw'; /** * Optional callback, invoked when annotations are loaded */ function onLoadAnnots() { call(this.onLoadAnnotsCallback); } function onBeforeDrawAnnots() { call(this.onBeforeDrawAnnotsCallback); } /** * Optional callback, invoked when annotations are drawn */ function onDrawAnnots() { call(this.onDrawAnnotsCallback); } function hideAnnotTooltip() { d3.selectAll('._ideogramTooltip').transition() .duration(500) // fade out for half second .style('opacity', 0) .style('pointer-events', 'none'); } /** * Starts a timer that, upon expiring, hides the annotation tooltip. * * To enable users to copy tooltip content to their clipboard, a timer is * used to control when the tooltip disappears. It starts when the user's * cursor leaves the annotation or the tooltip. If the user moves the cursor * back over the annot or tooltip after the timer starts and before it expires, * then the timer is cleared. */ function startHideAnnotTooltipTimeout() { const ideo = this; if (ideo.config.showAnnotTooltip === false) { return; } // See "Without this..." note in gene-structure.js const hideMs = ideo.oneTimeDelayTooltipHideMs ?? 250; delete ideo.oneTimeDelayTooltipHideMs; // Hide tooltip after `hideMs` milliseconds ideo.hideAnnotTooltipTimeout = window.setTimeout(function() { hideAnnotTooltip(); }, hideMs); // Enable clients to not show tooltip immediately after clicking gene, // e.g. in related genes kit ideo.isTooltipCooling = true; ideo.hideAnnotTooltipCounter = window.setTimeout(function() { ideo.isTooltipCooling = false; }, 500); } function renderTooltip(tooltip, content, matrix, yOffset, ideo) { const left = ideo.config.orientation === 'horizontal' ? matrix.left + (matrix.width / 2) : matrix.left + matrix.width; tooltip.html(content) .style('opacity', 1) // Make tooltip visible .style('left', left + 'px') .style('top', (matrix.top - yOffset) + 'px') .style('font-family', ideo.config.fontFamily) .style('pointer-events', null) // Prevent bug in clicking chromosome .on('mouseover', function() { clearTimeout(ideo.hideAnnotTooltipTimeout); }) .on('mouseout', function() { ideo.startHideAnnotTooltipTimeout(); }); } function getCoarseBpLength(annot) { const length = Math.abs(annot.stop - annot.start); return formatSiPrefix(length) + 'bp'; } function getContentAndYOffset(annot, includeLength=false) { var content, yOffset, range, displayName; range = 'chr' + annot.chr + ':' + annot.start.toLocaleString(); if (annot.displayCoordinates) { range = annot.displayCoordinates; } else if (annot.length > 0) { // Only show range if stop differs from start range += '-' + annot.stop.toLocaleString(); if (includeLength) range += ' (' + getCoarseBpLength(annot) + ')'; } content = `<span class="_ideoTooltipFooter"><br />${range}</span>`; yOffset = 24; if (annot.name) { displayName = annot.displayName ? annot.displayName : annot.name; content = displayName + content; yOffset += 8; } return [content, yOffset]; } /** * Optional callback, invoked before showing annotation tooltip */ async function onWillShowAnnotTooltip(event, context) { await call(this.onWillShowAnnotTooltipCallback, event, context); } function onDidShowAnnotTooltip() { call(this.onDidShowAnnotTooltipCallback); } /** * Optional callback, invoked on clicking annotation */ function onClickAnnot(annot) { this.prevClickedAnnot = annot; this.hasShownAnnotSinceClick = false; this.onClickAnnotCallback(annot); } // /** Get list of annotation objects by names, e.g. ["BRCA1", "APOE"] */ // function getAnnotsByName(annotNames, ideo) { // return annotNames.map(name => getAnnotByName(name, ideo)); // } /** * Shows a tooltip for the given annotation. * * See notes in startHideAnnotTooltipTimeout about show/hide logic. * * @param annot {Object} Processed annotation object * @param context {Object} "This" of the caller -- an SVG path DOM object */ async function showAnnotTooltip(annot, context) { var content, yOffset, tooltip, ideo = this; if (ideo.config.showAnnotTooltip === false) return; clearTimeout(ideo.hideAnnotTooltipTimeout); if (ideo.onWillShowAnnotTooltipCallback) { annot = await ideo.onWillShowAnnotTooltipCallback(annot, context); } // Enable onWillShowAnnotTooltipCallback to cancel showing tooltip if (annot === null) { hideAnnotTooltip(); return; } ideo.prevTooltipAnnotName = annot.name; tooltip = d3 .select(`#${ideo.config.container.replace('#', '')}_ideogramTooltip`); tooltip.interrupt(); // Stop any in-progress disapperance const includeLength = true; [content, yOffset] = getContentAndYOffset(annot, includeLength); const matrix = context.getBoundingClientRect(); renderTooltip(tooltip, content, matrix, yOffset, ideo); if (ideo.onDidShowAnnotTooltipCallback) { ideo.onDidShowAnnotTooltipCallback(); } } export { onLoadAnnots, onBeforeDrawAnnots, onDrawAnnots, startHideAnnotTooltipTimeout, onWillShowAnnotTooltip, showAnnotTooltip, onClickAnnot, onDidShowAnnotTooltip };