client/client/modules/render/tracks/comparison/renderer.js (306 lines of code) (raw):
import * as PIXI from 'pixi.js-legacy';
import {CachedTrackRendererWithVerticalScroll} from '../../core';
import {ColorProcessor, PixiTextSize} from '../../utilities';
class ComparisonAlignmentRenderer extends CachedTrackRendererWithVerticalScroll {
get config() {
return this._config;
}
constructor(config, options, targetContext, track) {
super(track);
this._config = config;
this.options = options;
this._hoveringGraphics = new PIXI.Graphics();
this.targetContext = targetContext;
this.container.addChild(this._verticalScroll);
this.dataContainer.addChild(this._hoveringGraphics);
this.initializeCentralLine();
}
scroll(viewport, yDelta) {
this.hoverItem(null);
super.scroll(viewport, yDelta);
}
translateContainer(viewport, cache) {
super.translateContainer(viewport, cache);
this.hoverItem(null);
}
hoverItem(hoveredItem, viewport) {
const changed = this.hoveredItem !== hoveredItem;
if (changed && this._hoveringGraphics) {
this._hoveringGraphics.clear();
if (hoveredItem) {
this.renderHoveredAlignment(
viewport,
hoveredItem.alignment,
hoveredItem.level,
);
}
}
this.hoveredItem = hoveredItem;
return changed;
}
checkPosition(viewport, cache, position) {
if (
!!this.dataContainer &&
this.renderingInfo &&
this.renderingInfo.length > 0
) {
const {x, y} = position;
const [item] = this.renderingInfo.filter(info => info.start <= x &&
info.end >= x &&
info.y1 <= y &&
info.y2 >= y
);
if (item) {
return item;
}
}
return null;
}
rebuildContainer(viewport, cache) {
super.rebuildContainer(viewport, cache);
this.dataContainer.removeChildren();
this._hoveringGraphics.clear();
this._hoveringGraphics = new PIXI.Graphics();
if (
cache.data !== null &&
cache.data !== undefined &&
cache.data.length > 0
) {
const alignments = cache.data;
const featureCoords = this.targetContext.featureCoords;
const levels = [];
let maxLevels = 0;
const height = this.config.sequence.height;
const levelHeight = height + 2 * this.config.sequence.margin;
for (let i = 0; i < alignments.length; i++) {
const alignment = alignments[i];
const { queryStart, queryEnd } = alignment;
const { start, end } = featureCoords;
let startPx = viewport.project.brushBP2pixel(start);
let endPx = viewport.project.brushBP2pixel(end);
if (queryStart > 0) {
startPx -= (
this.config.sequence.notAligned.width +
this.config.sequence.notAligned.margin +
PixiTextSize.getTextSize(
`${queryStart}`,
this.config.sequence.notAligned.label
).width
);
}
if (queryEnd > 0) {
endPx += (
this.config.sequence.notAligned.width +
this.config.sequence.notAligned.margin +
PixiTextSize.getTextSize(
`${queryEnd}`,
this.config.sequence.notAligned.label
).width
);
}
endPx += this.config.strandMarker.width;
startPx -= this.config.sequence.margin;
endPx += this.config.sequence.margin;
const size = endPx - startPx;
const intersections = levels.filter(level => {
const {
start: levelStart,
end: levelEnd
} = level;
const levelSize = levelEnd - levelStart;
const s = Math.min(levelStart, startPx);
const e = Math.max(levelEnd, endPx);
return (e - s) < levelSize + size;
});
const minimumLevel = Math.min(
...intersections.map(intersection => intersection.level),
-1
);
const maximumLevel = Math.max(
...intersections.map(intersection => intersection.level),
-1
);
let level = maximumLevel + 1;
if (minimumLevel > 0) {
level = minimumLevel - 1;
}
levels.push({
start: startPx,
end: endPx,
level,
y1: level * levelHeight,
y2: (level + 1) * levelHeight,
alignment
});
maxLevels = Math.max(maxLevels, level);
this.renderAlignment(viewport, alignment, level);
}
this.renderingInfo = levels.slice();
this._actualHeight = levelHeight * (maxLevels + 1);
} else {
this.renderingInfo = [];
this._actualHeight = this._height;
}
this.dataContainer.addChild(this._hoveringGraphics);
this.scroll(viewport, null);
}
renderStrandMarker (graphics, x, direction, options) {
const {
y,
height,
minimum,
maximum,
baseColor,
highlighted
} = options;
if (x < minimum || x > maximum) {
return;
}
x = Math.min(maximum, Math.max(minimum, x));
graphics
.lineStyle(0, baseColor, 0)
.beginFill(highlighted ? ColorProcessor.darkenColor(baseColor, 0.1) : baseColor, 1)
.moveTo(
x,
y
)
.lineTo(
x + direction * this.config.strandMarker.width,
y + Math.round(height / 2.0),
)
.lineTo(
x,
y + height
)
.endFill();
}
renderMatch (graphics, x1, x2, options) {
const {
y,
height,
minimum,
maximum,
baseColor,
highlighted = false
} = options;
if (x2 < minimum || x1 > maximum) {
return;
}
x1 = Math.min(maximum, Math.max(minimum, x1));
x2 = Math.min(maximum, Math.max(minimum, x2));
graphics
.lineStyle(0, baseColor, 0)
.beginFill(highlighted ? ColorProcessor.darkenColor(baseColor, 0.1) : baseColor, 1)
.drawRect(
x1,
y,
x2 - x1,
height
)
.endFill();
}
getAlignmentRenderingInfo (viewport, alignment, level) {
if (!alignment) {
return undefined;
}
const {
diff: btop,
queryLength
} = alignment;
const featureCoords = this.targetContext.featureCoords;
let {
targetStart,
targetEnd,
queryStart,
queryEnd,
} = alignment;
if (featureCoords) {
targetStart = featureCoords.start;
targetEnd = featureCoords.end;
}
if (!targetStart || !targetEnd) {
return undefined;
}
const height = this.config.sequence.height;
const levelHeight = height + 2 * this.config.sequence.margin;
const y = level * levelHeight + this.config.sequence.margin;
const start = Math.min(targetStart, targetEnd);
const end = Math.max(targetStart, targetEnd);
const startPx = viewport.project.brushBP2pixel(start);
const width = viewport.project.brushBP2pixel(end) - startPx;
const bpWidth = viewport.factor;
let baseColor = this.config.colors && this.config.colors.base
? this.config.colors.base
: this.config.sequence.color;
const renderMarkers = this.config.sequence.markersThresholdWidth < width;
if (!renderMarkers && this.config.sequence.collapsedColor) {
baseColor = this.config.sequence.collapsedColor;
}
const minimum = -viewport.canvasSize;
const maximum = 2 * viewport.canvasSize;
return {
y,
height,
levelHeight,
minimum,
maximum,
bpWidth,
baseColor,
start,
startPx,
width: Math.max(width, 1),
queryStart,
queryEnd,
btop,
queryLength,
renderMarkers
};
}
renderAlignment (viewport, alignment, level) {
const options = this.getAlignmentRenderingInfo(viewport, alignment, level);
if (!options) {
return false;
}
const {
bpWidth,
startPx,
width,
} = options;
const graphics = new PIXI.Graphics();
this.dataContainer.addChild(graphics);
this.renderStrandMarker(
graphics,
(startPx + width + bpWidth / 2.0),
1,
options
);
this.renderMatch(
graphics,
startPx - bpWidth / 2.0,
startPx + width + bpWidth / 2.0,
options
);
return true;
}
renderHoveredAlignment (viewport, alignment, level) {
const options = this.getAlignmentRenderingInfo(viewport, alignment, level);
if (!options || !this._hoveringGraphics) {
return false;
}
const {
bpWidth,
startPx,
width
} = options;
options.highlighted = true;
this._hoveringGraphics.clear();
this.renderStrandMarker(
this._hoveringGraphics,
(startPx + width + bpWidth / 2.0),
1,
options
);
this.renderMatch(
this._hoveringGraphics,
startPx - bpWidth / 2.0,
startPx + width + bpWidth / 2.0,
options
);
return true;
}
render (viewport, cache, isRedraw, showCenterLine) {
if (!isRedraw) {
this.scroll(viewport, 0, cache);
}
super.render(viewport, cache, isRedraw, showCenterLine);
}
}
export default ComparisonAlignmentRenderer;