@jbrowse/plugin-linear-genome-view
Version:
JBrowse 2 linear genome view
108 lines (107 loc) • 4.19 kB
JavaScript
import { clamp } from '@jbrowse/core/util';
export function collectLayoutsFromRenderings(renderings) {
const layoutMaps = [];
for (const [, rendering] of renderings) {
if (rendering.layout?.getRectangles) {
layoutMaps.push(rendering.layout.getRectangles());
}
}
return layoutMaps;
}
function calculateFeatureLeftPx(view, assembly, refName, left, right, bpPerPx) {
const canonicalRefName = assembly?.getCanonicalRefName(refName) || refName;
const leftBpPx = view.bpToPx({
refName: canonicalRefName,
coord: left,
})?.offsetPx;
const rightBpPx = view.bpToPx({
refName: canonicalRefName,
coord: right,
})?.offsetPx;
if (leftBpPx !== undefined) {
const rightEstimate = rightBpPx !== undefined ? rightBpPx : leftBpPx + (right - left) / bpPerPx;
return Math.min(leftBpPx, rightEstimate);
}
else if (rightBpPx !== undefined) {
const leftEstimate = rightBpPx - (right - left) / bpPerPx;
return Math.min(leftEstimate, rightBpPx);
}
return undefined;
}
function calculateMultiRegionLeftPx(view, assembly, refName, left, right) {
const canonicalRefName = assembly?.getCanonicalRefName(refName) || refName;
const visibleRegions = view.displayedRegions.filter(r => r.refName === canonicalRefName && r.start < right && r.end > left);
if (visibleRegions.length === 0) {
return undefined;
}
let minLeftPx = Infinity;
for (const region of visibleRegions) {
const regionStart = Math.max(left, region.start);
const regionEnd = Math.min(right, region.end);
const startPx = view.bpToPx({
refName: canonicalRefName,
coord: regionStart,
})?.offsetPx;
const endPx = view.bpToPx({
refName: canonicalRefName,
coord: regionEnd,
})?.offsetPx;
if (startPx !== undefined && endPx !== undefined) {
minLeftPx = Math.min(minLeftPx, startPx, endPx);
}
}
if (minLeftPx === Infinity) {
return undefined;
}
return minLeftPx;
}
function getFeatureLeftPx(view, assembly, refName, left, right, bpPerPx) {
return (calculateFeatureLeftPx(view, assembly, refName, left, right, bpPerPx) ??
calculateMultiRegionLeftPx(view, assembly, refName, left, right));
}
export function deduplicateFeatureLabels(layoutFeatures, view, assembly, bpPerPx) {
const featureLabels = new Map();
for (const [key, val] of layoutFeatures.entries()) {
if (!val?.[4]) {
continue;
}
const [, topPx, , , feature] = val;
const { refName, floatingLabels, totalFeatureHeight, actualTopPx, featureWidth, featureStartBp, featureEndBp, } = feature;
const effectiveTopPx = actualTopPx ?? topPx;
if (!floatingLabels ||
floatingLabels.length === 0 ||
!totalFeatureHeight ||
featureWidth === undefined ||
featureStartBp === undefined ||
featureEndBp === undefined) {
continue;
}
const leftPx = getFeatureLeftPx(view, assembly, refName, featureStartBp, featureEndBp, bpPerPx);
if (leftPx === undefined) {
continue;
}
const floorLeftPx = Math.floor(leftPx);
const existing = featureLabels.get(key);
if (!existing || floorLeftPx < existing.leftPx) {
featureLabels.set(key, {
leftPx: floorLeftPx,
topPx: effectiveTopPx,
totalFeatureHeight,
floatingLabels,
featureWidth,
});
}
}
return featureLabels;
}
export function calculateFloatingLabelPosition(featureLeftPx, featureRightPx, labelWidth, offsetPx) {
const featureWidth = featureRightPx - featureLeftPx;
if (labelWidth > featureWidth) {
return Math.round(featureLeftPx - offsetPx);
}
const viewportLeft = Math.max(0, offsetPx);
const leftPx = Math.max(featureLeftPx, viewportLeft);
const naturalX = leftPx - offsetPx;
const maxX = featureRightPx - offsetPx - labelWidth;
return Math.round(clamp(naturalX, 0, maxX));
}