UNPKG

@jbrowse/plugin-wiggle

Version:

JBrowse 2 wiggle adapters, tracks, etc.

192 lines (191 loc) 8.43 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.drawXY = drawXY; const configuration_1 = require("@jbrowse/core/configuration"); const util_1 = require("@jbrowse/core/util"); const colord_1 = require("@jbrowse/core/util/colord"); const stopToken_1 = require("@jbrowse/core/util/stopToken"); const util_2 = require("./util"); function lighten(color, amount) { const hslColor = color.toHsl(); const l = hslColor.l * (1 + amount); return (0, colord_1.colord)({ ...hslColor, l: (0, util_1.clamp)(l, 0, 100), }); } function darken(color, amount) { const hslColor = color.toHsl(); const l = hslColor.l * (1 - amount); return (0, colord_1.colord)({ ...hslColor, l: (0, util_1.clamp)(l, 0, 100), }); } const fudgeFactor = 0.3; const clipHeight = 2; function drawXY(ctx, props) { const { features, bpPerPx, regions, scaleOpts, height: unadjustedHeight, config, ticks, displayCrossHatches, offset = 0, colorCallback, inverted, stopToken, } = props; const region = regions[0]; const width = (region.end - region.start) / bpPerPx; const height = unadjustedHeight - offset * 2; const filled = (0, configuration_1.readConfObject)(config, 'filled'); const clipColor = (0, configuration_1.readConfObject)(config, 'clipColor'); const summaryScoreMode = (0, configuration_1.readConfObject)(config, 'summaryScoreMode'); const pivotValue = (0, configuration_1.readConfObject)(config, 'bicolorPivotValue'); const minSize = (0, configuration_1.readConfObject)(config, 'minSize'); const scale = (0, util_2.getScale)({ ...scaleOpts, range: [0, height], inverted }); const originY = (0, util_2.getOrigin)(scaleOpts.scaleType); const domain = scale.domain(); const niceMin = domain[0]; const niceMax = domain[1]; const toY = (n) => (0, util_1.clamp)(height - (scale(n) || 0), 0, height) + offset; const toOrigin = (n) => toY(originY) - toY(n); const getHeight = (n) => (filled ? toOrigin(n) : Math.max(minSize, 1)); let hasClipping = false; let prevLeftPx = Number.NEGATIVE_INFINITY; const reducedFeatures = []; const crossingOrigin = niceMin < pivotValue && niceMax > pivotValue; let start = performance.now(); if (summaryScoreMode === 'whiskers') { let lastCol; let lastMix; start = performance.now(); for (const feature of features.values()) { if (performance.now() - start > 400) { (0, stopToken_1.checkStopToken)(stopToken); start = performance.now(); } const [leftPx, rightPx] = (0, util_1.featureSpanPx)(feature, region, bpPerPx); if (feature.get('summary')) { const w = Math.max(rightPx - leftPx + fudgeFactor, minSize); const max = feature.get('maxScore'); const c = colorCallback(feature, max); const effectiveC = crossingOrigin ? c : c === lastCol ? lastMix : (lastMix = lighten((0, colord_1.colord)(c), 0.4).toHex()); (0, util_2.fillRectCtx)(leftPx, toY(max), w, getHeight(max), ctx, effectiveC); lastCol = c; } } lastMix = undefined; lastCol = undefined; start = performance.now(); for (const feature of features.values()) { if (performance.now() - start > 400) { (0, stopToken_1.checkStopToken)(stopToken); start = performance.now(); } const [leftPx, rightPx] = (0, util_1.featureSpanPx)(feature, region, bpPerPx); const score = feature.get('score'); const max = feature.get('maxScore'); const min = feature.get('minScore'); const summary = feature.get('summary'); const c = colorCallback(feature, score); const effectiveC = crossingOrigin && summary ? c === lastCol ? lastMix : (lastMix = (0, colord_1.colord)(colorCallback(feature, max)) .mix((0, colord_1.colord)(colorCallback(feature, min))) .toString()) : c; const w = Math.max(rightPx - leftPx + fudgeFactor, minSize); if (Math.floor(leftPx) !== Math.floor(prevLeftPx) || rightPx - leftPx > 1) { reducedFeatures.push(feature); prevLeftPx = leftPx; } hasClipping = hasClipping || score < niceMin || score > niceMax; (0, util_2.fillRectCtx)(leftPx, toY(score), w, getHeight(score), ctx, effectiveC); lastCol = c; } lastMix = undefined; lastCol = undefined; start = performance.now(); for (const feature of features.values()) { if (performance.now() - start > 400) { (0, stopToken_1.checkStopToken)(stopToken); start = performance.now(); } const [leftPx, rightPx] = (0, util_1.featureSpanPx)(feature, region, bpPerPx); if (feature.get('summary')) { const min = feature.get('minScore'); const c = colorCallback(feature, min); const w = Math.max(rightPx - leftPx + fudgeFactor, minSize); const effectiveC = crossingOrigin ? c : c === lastCol ? lastMix : (lastMix = darken((0, colord_1.colord)(c), 0.4).toHex()); (0, util_2.fillRectCtx)(leftPx, toY(min), w, getHeight(min), ctx, effectiveC); lastCol = c; } } } else { start = performance.now(); for (const feature of features.values()) { if (performance.now() - start > 400) { (0, stopToken_1.checkStopToken)(stopToken); start = performance.now(); } const [leftPx, rightPx] = (0, util_1.featureSpanPx)(feature, region, bpPerPx); if (Math.floor(leftPx) !== Math.floor(prevLeftPx) || rightPx - leftPx > 1) { reducedFeatures.push(feature); prevLeftPx = leftPx; } const score = feature.get('score'); const c = colorCallback(feature, score); hasClipping = hasClipping || score < niceMin || score > niceMax; const w = Math.max(rightPx - leftPx + fudgeFactor, minSize); if (summaryScoreMode === 'max') { const s = feature.get('summary') ? feature.get('maxScore') : score; (0, util_2.fillRectCtx)(leftPx, toY(s), w, getHeight(s), ctx, c); } else if (summaryScoreMode === 'min') { const s = feature.get('summary') ? feature.get('minScore') : score; (0, util_2.fillRectCtx)(leftPx, toY(s), w, getHeight(s), ctx, c); } else { (0, util_2.fillRectCtx)(leftPx, toY(score), w, getHeight(score), ctx, c); } } } ctx.save(); if (hasClipping) { ctx.fillStyle = clipColor; start = performance.now(); for (const feature of features.values()) { if (performance.now() - start > 400) { (0, stopToken_1.checkStopToken)(stopToken); start = performance.now(); } const [leftPx, rightPx] = (0, util_1.featureSpanPx)(feature, region, bpPerPx); const w = rightPx - leftPx + fudgeFactor; const score = feature.get('score'); if (score > niceMax) { (0, util_2.fillRectCtx)(leftPx, offset, w, clipHeight, ctx); } else if (score < niceMin && scaleOpts.scaleType !== 'log') { (0, util_2.fillRectCtx)(leftPx, unadjustedHeight, w, clipHeight, ctx); } } } ctx.restore(); if (displayCrossHatches) { ctx.lineWidth = 1; ctx.strokeStyle = 'rgba(200,200,200,0.5)'; for (const tick of ticks.values) { ctx.beginPath(); ctx.moveTo(0, Math.round(toY(tick))); ctx.lineTo(width, Math.round(toY(tick))); ctx.stroke(); } } return { reducedFeatures, }; }