@jbrowse/plugin-wiggle
Version:
JBrowse 2 wiggle adapters, tracks, etc.
152 lines (151 loc) • 6.11 kB
JavaScript
import { readConfObject } from '@jbrowse/core/configuration';
import { clamp } from '@jbrowse/core/util';
import { checkStopToken2, createStopTokenChecker, } from '@jbrowse/core/util/stopToken';
import { WIGGLE_CLIP_HEIGHT, WIGGLE_FUDGE_FACTOR, getScale } from "./util.js";
export function drawLine(ctx, props) {
const { features, regions, bpPerPx, scaleOpts, height: unadjustedHeight, ticks: { values }, displayCrossHatches, colorCallback, config, offset = 0, staticColor, stopToken, lastCheck = createStopTokenChecker(stopToken), } = props;
const region = regions[0];
const regionStart = region.start;
const regionEnd = region.end;
const reversed = region.reversed;
const invBpPerPx = 1 / bpPerPx;
const width = (regionEnd - regionStart) * invBpPerPx;
const height = unadjustedHeight - offset * 2;
const clipColor = readConfObject(config, 'clipColor');
const scale = getScale({ ...scaleOpts, range: [0, height] });
const domain = scale.domain();
const niceMin = domain[0];
const niceMax = domain[1];
const domainSpan = niceMax - niceMin;
const isLog = scaleOpts.scaleType === 'log';
const linearRatio = domainSpan !== 0 ? height / domainSpan : 0;
const log2 = Math.log(2);
const logMin = Math.log(niceMin) / log2;
const logMax = Math.log(niceMax) / log2;
const logSpan = logMax - logMin;
const logRatio = logSpan !== 0 ? height / logSpan : 0;
const toY = isLog
? (n) => clamp(height - (Math.log(n) / log2 - logMin) * logRatio, 0, height) +
offset
: (n) => clamp(height - (n - niceMin) * linearRatio, 0, height) + offset;
let lastVal;
let prevLeftPx = Number.NEGATIVE_INFINITY;
const reducedFeatures = [];
if (staticColor) {
ctx.beginPath();
ctx.strokeStyle = staticColor;
const clippingFeatures = [];
for (const feature of features.values()) {
checkStopToken2(lastCheck);
const fStart = feature.get('start');
const fEnd = feature.get('end');
const leftPx = reversed
? (regionEnd - fEnd) * invBpPerPx
: (fStart - regionStart) * invBpPerPx;
const rightPx = reversed
? (regionEnd - fStart) * invBpPerPx
: (fEnd - regionStart) * invBpPerPx;
if ((leftPx | 0) !== (prevLeftPx | 0) || rightPx - leftPx > 1) {
reducedFeatures.push(feature);
prevLeftPx = leftPx;
}
const score = feature.get('score');
const scoreY = toY(score);
if (score > niceMax) {
clippingFeatures.push({
leftPx,
w: rightPx - leftPx + WIGGLE_FUDGE_FACTOR,
high: true,
});
}
else if (score < niceMin && !isLog) {
clippingFeatures.push({
leftPx,
w: rightPx - leftPx + WIGGLE_FUDGE_FACTOR,
high: false,
});
}
const startY = lastVal !== undefined ? toY(lastVal) : scoreY;
if (!reversed) {
ctx.moveTo(leftPx, startY);
ctx.lineTo(leftPx, scoreY);
ctx.lineTo(rightPx, scoreY);
}
else {
ctx.moveTo(rightPx, startY);
ctx.lineTo(rightPx, scoreY);
ctx.lineTo(leftPx, scoreY);
}
lastVal = score;
}
ctx.stroke();
if (clippingFeatures.length > 0) {
ctx.fillStyle = clipColor;
for (const { leftPx, w, high } of clippingFeatures) {
if (high) {
ctx.fillRect(leftPx, offset, w, WIGGLE_CLIP_HEIGHT);
}
else {
ctx.fillRect(leftPx, height - WIGGLE_CLIP_HEIGHT, w, WIGGLE_CLIP_HEIGHT);
}
}
}
}
else {
for (const feature of features.values()) {
checkStopToken2(lastCheck);
const fStart = feature.get('start');
const fEnd = feature.get('end');
const leftPx = reversed
? (regionEnd - fEnd) * invBpPerPx
: (fStart - regionStart) * invBpPerPx;
const rightPx = reversed
? (regionEnd - fStart) * invBpPerPx
: (fEnd - regionStart) * invBpPerPx;
if ((leftPx | 0) !== (prevLeftPx | 0) || rightPx - leftPx > 1) {
reducedFeatures.push(feature);
prevLeftPx = leftPx;
}
const score = feature.get('score');
const scoreY = toY(score);
const w = rightPx - leftPx + WIGGLE_FUDGE_FACTOR;
const c = colorCallback(feature, score);
ctx.beginPath();
ctx.strokeStyle = c;
const startY = lastVal !== undefined ? toY(lastVal) : scoreY;
if (!reversed) {
ctx.moveTo(leftPx, startY);
ctx.lineTo(leftPx, scoreY);
ctx.lineTo(rightPx, scoreY);
}
else {
ctx.moveTo(rightPx, startY);
ctx.lineTo(rightPx, scoreY);
ctx.lineTo(leftPx, scoreY);
}
ctx.stroke();
lastVal = score;
if (score > niceMax) {
ctx.fillStyle = clipColor;
ctx.fillRect(leftPx, offset, w, WIGGLE_CLIP_HEIGHT);
}
else if (score < niceMin && !isLog) {
ctx.fillStyle = clipColor;
ctx.fillRect(leftPx, height - WIGGLE_CLIP_HEIGHT, w, WIGGLE_CLIP_HEIGHT);
}
}
}
if (displayCrossHatches) {
ctx.lineWidth = 1;
ctx.strokeStyle = 'rgba(200,200,200,0.5)';
for (const tick of values) {
ctx.beginPath();
ctx.moveTo(0, Math.round(toY(tick)));
ctx.lineTo(width, Math.round(toY(tick)));
ctx.stroke();
}
}
return {
reducedFeatures,
};
}