@jbrowse/plugin-canvas 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/esm/CanvasFeatureRenderer/CanvasFeatureRenderer.d.ts +6 -0
- package/esm/CanvasFeatureRenderer/CanvasFeatureRenderer.js +30 -0
- package/esm/CanvasFeatureRenderer/CanvasFeatureRendering.d.ts +17 -0
- package/esm/CanvasFeatureRenderer/CanvasFeatureRendering.js +192 -0
- package/esm/CanvasFeatureRenderer/configSchema.d.ts +136 -0
- package/esm/CanvasFeatureRenderer/configSchema.js +152 -0
- package/esm/CanvasFeatureRenderer/configSchema2.d.ts +136 -0
- package/esm/CanvasFeatureRenderer/configSchema2.js +5 -0
- package/esm/CanvasFeatureRenderer/doAll.d.ts +21 -0
- package/esm/CanvasFeatureRenderer/doAll.js +42 -0
- package/esm/CanvasFeatureRenderer/drawChevrons.d.ts +1 -0
- package/esm/CanvasFeatureRenderer/drawChevrons.js +25 -0
- package/esm/CanvasFeatureRenderer/drawFeature.d.ts +3 -0
- package/esm/CanvasFeatureRenderer/drawFeature.js +27 -0
- package/esm/CanvasFeatureRenderer/filterSubparts.d.ts +5 -0
- package/esm/CanvasFeatureRenderer/filterSubparts.js +92 -0
- package/esm/CanvasFeatureRenderer/floatingLabels.d.ts +23 -0
- package/esm/CanvasFeatureRenderer/floatingLabels.js +65 -0
- package/esm/CanvasFeatureRenderer/glyphs/box.d.ts +2 -0
- package/esm/CanvasFeatureRenderer/glyphs/box.js +87 -0
- package/esm/CanvasFeatureRenderer/glyphs/cds.d.ts +2 -0
- package/esm/CanvasFeatureRenderer/glyphs/cds.js +93 -0
- package/esm/CanvasFeatureRenderer/glyphs/childGlyphs.d.ts +5 -0
- package/esm/CanvasFeatureRenderer/glyphs/childGlyphs.js +18 -0
- package/esm/CanvasFeatureRenderer/glyphs/glyphUtils.d.ts +14 -0
- package/esm/CanvasFeatureRenderer/glyphs/glyphUtils.js +125 -0
- package/esm/CanvasFeatureRenderer/glyphs/index.d.ts +9 -0
- package/esm/CanvasFeatureRenderer/glyphs/index.js +29 -0
- package/esm/CanvasFeatureRenderer/glyphs/matureProteinRegion.d.ts +2 -0
- package/esm/CanvasFeatureRenderer/glyphs/matureProteinRegion.js +121 -0
- package/esm/CanvasFeatureRenderer/glyphs/processed.d.ts +2 -0
- package/esm/CanvasFeatureRenderer/glyphs/processed.js +53 -0
- package/esm/CanvasFeatureRenderer/glyphs/repeatRegion.d.ts +2 -0
- package/esm/CanvasFeatureRenderer/glyphs/repeatRegion.js +104 -0
- package/esm/CanvasFeatureRenderer/glyphs/segments.d.ts +2 -0
- package/esm/CanvasFeatureRenderer/glyphs/segments.js +65 -0
- package/esm/CanvasFeatureRenderer/glyphs/subfeatures.d.ts +2 -0
- package/esm/CanvasFeatureRenderer/glyphs/subfeatures.js +132 -0
- package/esm/CanvasFeatureRenderer/index.d.ts +3 -0
- package/esm/CanvasFeatureRenderer/index.js +23 -0
- package/esm/CanvasFeatureRenderer/labelUtils.d.ts +9 -0
- package/esm/CanvasFeatureRenderer/labelUtils.js +35 -0
- package/esm/CanvasFeatureRenderer/layout/index.d.ts +3 -0
- package/esm/CanvasFeatureRenderer/layout/index.js +3 -0
- package/esm/CanvasFeatureRenderer/layout/layoutFeature.d.ts +14 -0
- package/esm/CanvasFeatureRenderer/layout/layoutFeature.js +40 -0
- package/esm/CanvasFeatureRenderer/layout/layoutFeatures.d.ts +13 -0
- package/esm/CanvasFeatureRenderer/layout/layoutFeatures.js +59 -0
- package/esm/CanvasFeatureRenderer/layout/layoutUtils.d.ts +19 -0
- package/esm/CanvasFeatureRenderer/layout/layoutUtils.js +129 -0
- package/esm/CanvasFeatureRenderer/makeImageData.d.ts +14 -0
- package/esm/CanvasFeatureRenderer/makeImageData.js +90 -0
- package/esm/CanvasFeatureRenderer/peptides/aggregateAminoAcids.d.ts +8 -0
- package/esm/CanvasFeatureRenderer/peptides/aggregateAminoAcids.js +45 -0
- package/esm/CanvasFeatureRenderer/peptides/drawCDSBackground.d.ts +16 -0
- package/esm/CanvasFeatureRenderer/peptides/drawCDSBackground.js +24 -0
- package/esm/CanvasFeatureRenderer/peptides/drawPeptidesOnCDS.d.ts +15 -0
- package/esm/CanvasFeatureRenderer/peptides/drawPeptidesOnCDS.js +28 -0
- package/esm/CanvasFeatureRenderer/peptides/index.d.ts +5 -0
- package/esm/CanvasFeatureRenderer/peptides/index.js +5 -0
- package/esm/CanvasFeatureRenderer/peptides/peptideUtils.d.ts +5 -0
- package/esm/CanvasFeatureRenderer/peptides/peptideUtils.js +129 -0
- package/esm/CanvasFeatureRenderer/peptides/prepareAminoAcidData.d.ts +9 -0
- package/esm/CanvasFeatureRenderer/peptides/prepareAminoAcidData.js +5 -0
- package/esm/CanvasFeatureRenderer/renderConfig.d.ts +26 -0
- package/esm/CanvasFeatureRenderer/renderConfig.js +35 -0
- package/esm/CanvasFeatureRenderer/types.d.ts +97 -0
- package/esm/CanvasFeatureRenderer/types.js +1 -0
- package/esm/CanvasFeatureRenderer/util.d.ts +22 -0
- package/esm/CanvasFeatureRenderer/util.js +50 -0
- package/esm/CanvasFeatureRenderer/zoomThresholds.d.ts +4 -0
- package/esm/CanvasFeatureRenderer/zoomThresholds.js +8 -0
- package/esm/glyphs/index.d.ts +2 -0
- package/esm/glyphs/index.js +2 -0
- package/esm/index.d.ts +6 -0
- package/esm/index.js +10 -0
- package/package.json +53 -0
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { readCachedConfig } from "../renderConfig.js";
|
|
2
|
+
import { getBoxColor, getStrokeColor, isOffScreen, isUTR } from "../util.js";
|
|
3
|
+
import { drawStrandArrow, getStrandArrowPadding } from "./glyphUtils.js";
|
|
4
|
+
const UTR_HEIGHT_FRACTION = 0.65;
|
|
5
|
+
export const boxGlyph = {
|
|
6
|
+
type: 'Box',
|
|
7
|
+
match(feature) {
|
|
8
|
+
const type = feature.get('type');
|
|
9
|
+
if (type === 'CDS') {
|
|
10
|
+
return false;
|
|
11
|
+
}
|
|
12
|
+
const subfeatures = feature.get('subfeatures');
|
|
13
|
+
return !subfeatures?.length;
|
|
14
|
+
},
|
|
15
|
+
layout(args) {
|
|
16
|
+
const { feature, bpPerPx, reversed, configContext } = args;
|
|
17
|
+
const { config, displayMode, featureHeight } = configContext;
|
|
18
|
+
const height = readCachedConfig(featureHeight, config, 'height', feature);
|
|
19
|
+
const baseHeight = displayMode === 'compact' ? height / 2 : height;
|
|
20
|
+
const width = (feature.get('end') - feature.get('start')) / bpPerPx;
|
|
21
|
+
const isTopLevel = !feature.parent?.();
|
|
22
|
+
const strand = feature.get('strand');
|
|
23
|
+
const arrowPadding = isTopLevel
|
|
24
|
+
? getStrandArrowPadding(strand, reversed)
|
|
25
|
+
: { left: 0, right: 0 };
|
|
26
|
+
return {
|
|
27
|
+
feature,
|
|
28
|
+
glyphType: 'Box',
|
|
29
|
+
x: 0,
|
|
30
|
+
y: 0,
|
|
31
|
+
width,
|
|
32
|
+
height: baseHeight,
|
|
33
|
+
totalLayoutHeight: baseHeight,
|
|
34
|
+
totalLayoutWidth: width + arrowPadding.left + arrowPadding.right,
|
|
35
|
+
leftPadding: arrowPadding.left,
|
|
36
|
+
children: [],
|
|
37
|
+
};
|
|
38
|
+
},
|
|
39
|
+
draw(ctx, layout, dc) {
|
|
40
|
+
const { feature } = layout;
|
|
41
|
+
const { region, bpPerPx, configContext, theme, canvasWidth } = dc;
|
|
42
|
+
const { config } = configContext;
|
|
43
|
+
const colorByCDS = dc.colorByCDS ?? false;
|
|
44
|
+
const { start, end } = region;
|
|
45
|
+
const screenWidth = Math.ceil((end - start) / bpPerPx);
|
|
46
|
+
const width = layout.width;
|
|
47
|
+
const left = layout.x;
|
|
48
|
+
if (isOffScreen(left, width, canvasWidth) ||
|
|
49
|
+
(feature.parent?.() && feature.get('type') === 'intron')) {
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
let top = layout.y;
|
|
53
|
+
let height = layout.height;
|
|
54
|
+
if (isUTR(feature)) {
|
|
55
|
+
top += ((1 - UTR_HEIGHT_FRACTION) / 2) * height;
|
|
56
|
+
height *= UTR_HEIGHT_FRACTION;
|
|
57
|
+
}
|
|
58
|
+
const leftWithinBlock = Math.max(left, 0);
|
|
59
|
+
const diff = leftWithinBlock - left;
|
|
60
|
+
const widthWithinBlock = Math.max(2, Math.min(width - diff, screenWidth));
|
|
61
|
+
const fill = getBoxColor({
|
|
62
|
+
feature,
|
|
63
|
+
config,
|
|
64
|
+
configContext,
|
|
65
|
+
colorByCDS,
|
|
66
|
+
theme,
|
|
67
|
+
});
|
|
68
|
+
const stroke = readCachedConfig(configContext.outline, config, 'outline', feature);
|
|
69
|
+
ctx.fillStyle = fill;
|
|
70
|
+
ctx.fillRect(leftWithinBlock, top, widthWithinBlock, height);
|
|
71
|
+
if (stroke) {
|
|
72
|
+
ctx.strokeStyle = stroke;
|
|
73
|
+
ctx.lineWidth = 1;
|
|
74
|
+
ctx.strokeRect(leftWithinBlock, top, widthWithinBlock, height);
|
|
75
|
+
}
|
|
76
|
+
const isTopLevel = !feature.parent?.();
|
|
77
|
+
if (isTopLevel) {
|
|
78
|
+
const strokeColor = getStrokeColor({
|
|
79
|
+
feature,
|
|
80
|
+
config,
|
|
81
|
+
configContext,
|
|
82
|
+
theme,
|
|
83
|
+
});
|
|
84
|
+
drawStrandArrow(ctx, layout, dc, strokeColor);
|
|
85
|
+
}
|
|
86
|
+
},
|
|
87
|
+
};
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { drawCDSBackground } from "../peptides/drawCDSBackground.js";
|
|
2
|
+
import { drawPeptidesOnCDS } from "../peptides/drawPeptidesOnCDS.js";
|
|
3
|
+
import { prepareAminoAcidData } from "../peptides/prepareAminoAcidData.js";
|
|
4
|
+
import { readCachedConfig } from "../renderConfig.js";
|
|
5
|
+
import { getBoxColor, isOffScreen } from "../util.js";
|
|
6
|
+
import { shouldRenderPeptideBackground, shouldRenderPeptideText, } from "../zoomThresholds.js";
|
|
7
|
+
import { boxGlyph } from "./box.js";
|
|
8
|
+
export const cdsGlyph = {
|
|
9
|
+
type: 'CDS',
|
|
10
|
+
match(feature) {
|
|
11
|
+
return feature.get('type') === 'CDS';
|
|
12
|
+
},
|
|
13
|
+
layout(args) {
|
|
14
|
+
const { feature, bpPerPx, configContext } = args;
|
|
15
|
+
const { config, displayMode, featureHeight } = configContext;
|
|
16
|
+
const height = readCachedConfig(featureHeight, config, 'height', feature);
|
|
17
|
+
const baseHeight = displayMode === 'compact' ? height / 2 : height;
|
|
18
|
+
const width = (feature.get('end') - feature.get('start')) / bpPerPx;
|
|
19
|
+
return {
|
|
20
|
+
feature,
|
|
21
|
+
glyphType: 'CDS',
|
|
22
|
+
x: 0,
|
|
23
|
+
y: 0,
|
|
24
|
+
width,
|
|
25
|
+
height: baseHeight,
|
|
26
|
+
totalLayoutHeight: baseHeight,
|
|
27
|
+
totalLayoutWidth: width,
|
|
28
|
+
leftPadding: 0,
|
|
29
|
+
children: [],
|
|
30
|
+
};
|
|
31
|
+
},
|
|
32
|
+
draw(ctx, layout, dc) {
|
|
33
|
+
const { feature } = layout;
|
|
34
|
+
const { region, bpPerPx, configContext, theme, canvasWidth, peptideDataMap, } = dc;
|
|
35
|
+
const { config } = configContext;
|
|
36
|
+
const colorByCDS = dc.colorByCDS ?? false;
|
|
37
|
+
const reversed = region.reversed ?? false;
|
|
38
|
+
const featureStart = feature.get('start');
|
|
39
|
+
const featureEnd = feature.get('end');
|
|
40
|
+
const strand = feature.get('strand');
|
|
41
|
+
const width = layout.width;
|
|
42
|
+
const left = layout.x;
|
|
43
|
+
const top = layout.y;
|
|
44
|
+
const height = layout.height;
|
|
45
|
+
if (isOffScreen(left, width, canvasWidth)) {
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
const parent = feature.parent?.() ?? feature;
|
|
49
|
+
const peptideData = peptideDataMap?.get(parent.id());
|
|
50
|
+
const protein = peptideData?.protein;
|
|
51
|
+
const doRenderBackground = shouldRenderPeptideBackground(bpPerPx) && colorByCDS && !!protein;
|
|
52
|
+
if (doRenderBackground) {
|
|
53
|
+
const aggregatedAminoAcids = prepareAminoAcidData(parent, protein, featureStart, featureEnd, strand);
|
|
54
|
+
const baseColor = getBoxColor({
|
|
55
|
+
feature,
|
|
56
|
+
config,
|
|
57
|
+
configContext,
|
|
58
|
+
colorByCDS,
|
|
59
|
+
theme,
|
|
60
|
+
});
|
|
61
|
+
drawCDSBackground({
|
|
62
|
+
ctx,
|
|
63
|
+
aggregatedAminoAcids,
|
|
64
|
+
baseColor,
|
|
65
|
+
left,
|
|
66
|
+
top,
|
|
67
|
+
width,
|
|
68
|
+
height,
|
|
69
|
+
bpPerPx,
|
|
70
|
+
strand,
|
|
71
|
+
reversed,
|
|
72
|
+
canvasWidth,
|
|
73
|
+
});
|
|
74
|
+
if (shouldRenderPeptideText(bpPerPx)) {
|
|
75
|
+
drawPeptidesOnCDS({
|
|
76
|
+
ctx,
|
|
77
|
+
aggregatedAminoAcids,
|
|
78
|
+
left,
|
|
79
|
+
top,
|
|
80
|
+
width,
|
|
81
|
+
height,
|
|
82
|
+
bpPerPx,
|
|
83
|
+
strand,
|
|
84
|
+
reversed,
|
|
85
|
+
canvasWidth,
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
boxGlyph.draw(ctx, { ...layout, glyphType: 'Box' }, dc);
|
|
91
|
+
}
|
|
92
|
+
},
|
|
93
|
+
};
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { RenderConfigContext } from '../renderConfig.ts';
|
|
2
|
+
import type { Glyph } from '../types.ts';
|
|
3
|
+
import type { Feature } from '@jbrowse/core/util';
|
|
4
|
+
export declare const childGlyphs: Glyph[];
|
|
5
|
+
export declare function findChildGlyph(feature: Feature, configContext: RenderConfigContext): Glyph;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { boxGlyph } from "./box.js";
|
|
2
|
+
import { cdsGlyph } from "./cds.js";
|
|
3
|
+
import { findGlyph } from "./index.js";
|
|
4
|
+
import { matureProteinRegionGlyph } from "./matureProteinRegion.js";
|
|
5
|
+
import { processedTranscriptGlyph } from "./processed.js";
|
|
6
|
+
import { repeatRegionGlyph } from "./repeatRegion.js";
|
|
7
|
+
import { segmentsGlyph } from "./segments.js";
|
|
8
|
+
export const childGlyphs = [
|
|
9
|
+
matureProteinRegionGlyph,
|
|
10
|
+
repeatRegionGlyph,
|
|
11
|
+
cdsGlyph,
|
|
12
|
+
processedTranscriptGlyph,
|
|
13
|
+
segmentsGlyph,
|
|
14
|
+
boxGlyph,
|
|
15
|
+
];
|
|
16
|
+
export function findChildGlyph(feature, configContext) {
|
|
17
|
+
return findGlyph(feature, configContext, childGlyphs);
|
|
18
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { DrawContext, FeatureLayout, Glyph, LayoutArgs } from '../types.ts';
|
|
2
|
+
import type { Feature } from '@jbrowse/core/util';
|
|
3
|
+
export declare const STRAND_ARROW_WIDTH = 8;
|
|
4
|
+
export declare function getStrandArrowPadding(strand: number, reversed: boolean): {
|
|
5
|
+
left: number;
|
|
6
|
+
right: number;
|
|
7
|
+
visualSide: string | null;
|
|
8
|
+
width: number;
|
|
9
|
+
};
|
|
10
|
+
export declare function layoutChild(child: Feature, parentFeature: Feature, args: LayoutArgs, glyphType?: string): FeatureLayout;
|
|
11
|
+
export declare function drawConnectingLine(ctx: CanvasRenderingContext2D, left: number, top: number, width: number, height: number, color: string): void;
|
|
12
|
+
export declare function drawStrandArrowAtPosition(ctx: CanvasRenderingContext2D, left: number, centerY: number, width: number, strand: number, reversed: boolean, color: string): void;
|
|
13
|
+
export declare function drawStrandArrow(ctx: CanvasRenderingContext2D, layout: FeatureLayout, dc: DrawContext, color: string): void;
|
|
14
|
+
export declare function drawSegmentedFeature(ctx: CanvasRenderingContext2D, layout: FeatureLayout, dc: DrawContext, boxGlyph: Glyph, cdsGlyph: Glyph): void;
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { drawChevrons } from "../drawChevrons.js";
|
|
2
|
+
import { readCachedConfig } from "../renderConfig.js";
|
|
3
|
+
import { getStrokeColor, isOffScreen } from "../util.js";
|
|
4
|
+
export const STRAND_ARROW_WIDTH = 8;
|
|
5
|
+
export function getStrandArrowPadding(strand, reversed) {
|
|
6
|
+
const reverseFlip = reversed ? -1 : 1;
|
|
7
|
+
const effectiveStrand = strand * reverseFlip;
|
|
8
|
+
return {
|
|
9
|
+
left: effectiveStrand === -1 ? STRAND_ARROW_WIDTH : 0,
|
|
10
|
+
right: effectiveStrand === 1 ? STRAND_ARROW_WIDTH : 0,
|
|
11
|
+
visualSide: effectiveStrand === -1 ? 'left' : effectiveStrand === 1 ? 'right' : null,
|
|
12
|
+
width: strand ? STRAND_ARROW_WIDTH : 0,
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
export function layoutChild(child, parentFeature, args, glyphType = 'Box') {
|
|
16
|
+
const { bpPerPx, reversed, configContext } = args;
|
|
17
|
+
const { config, displayMode, featureHeight } = configContext;
|
|
18
|
+
const heightPx = readCachedConfig(featureHeight, config, 'height', child);
|
|
19
|
+
const baseHeightPx = displayMode === 'compact' ? heightPx / 2 : heightPx;
|
|
20
|
+
const childStart = child.get('start');
|
|
21
|
+
const childEnd = child.get('end');
|
|
22
|
+
const parentStart = parentFeature.get('start');
|
|
23
|
+
const parentEnd = parentFeature.get('end');
|
|
24
|
+
const widthPx = (childEnd - childStart) / bpPerPx;
|
|
25
|
+
const offsetBp = reversed ? parentEnd - childEnd : childStart - parentStart;
|
|
26
|
+
const xRelativePx = offsetBp / bpPerPx;
|
|
27
|
+
return {
|
|
28
|
+
feature: child,
|
|
29
|
+
glyphType: glyphType,
|
|
30
|
+
x: xRelativePx,
|
|
31
|
+
y: 0,
|
|
32
|
+
width: widthPx,
|
|
33
|
+
height: baseHeightPx,
|
|
34
|
+
totalLayoutHeight: baseHeightPx,
|
|
35
|
+
totalLayoutWidth: widthPx,
|
|
36
|
+
leftPadding: 0,
|
|
37
|
+
children: [],
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
export function drawConnectingLine(ctx, left, top, width, height, color) {
|
|
41
|
+
const centerY = top + height / 2;
|
|
42
|
+
ctx.strokeStyle = color;
|
|
43
|
+
ctx.lineWidth = 1;
|
|
44
|
+
ctx.beginPath();
|
|
45
|
+
ctx.moveTo(left, centerY);
|
|
46
|
+
ctx.lineTo(left + width, centerY);
|
|
47
|
+
ctx.stroke();
|
|
48
|
+
}
|
|
49
|
+
export function drawStrandArrowAtPosition(ctx, left, centerY, width, strand, reversed, color) {
|
|
50
|
+
const arrowSize = 5;
|
|
51
|
+
const reverseFlip = reversed ? -1 : 1;
|
|
52
|
+
const arrowOffset = 7 * strand * reverseFlip;
|
|
53
|
+
const arrowX = strand * reverseFlip === -1
|
|
54
|
+
? left
|
|
55
|
+
: strand * reverseFlip === 1
|
|
56
|
+
? left + width
|
|
57
|
+
: null;
|
|
58
|
+
if (arrowX !== null) {
|
|
59
|
+
ctx.strokeStyle = color;
|
|
60
|
+
ctx.lineWidth = 1;
|
|
61
|
+
ctx.beginPath();
|
|
62
|
+
ctx.moveTo(arrowX, centerY);
|
|
63
|
+
ctx.lineTo(arrowX + arrowOffset, centerY);
|
|
64
|
+
ctx.stroke();
|
|
65
|
+
ctx.fillStyle = color;
|
|
66
|
+
ctx.beginPath();
|
|
67
|
+
ctx.moveTo(arrowX + arrowOffset / 2, centerY - arrowSize / 2);
|
|
68
|
+
ctx.lineTo(arrowX + arrowOffset / 2, centerY + arrowSize / 2);
|
|
69
|
+
ctx.lineTo(arrowX + arrowOffset, centerY);
|
|
70
|
+
ctx.closePath();
|
|
71
|
+
ctx.fill();
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
export function drawStrandArrow(ctx, layout, dc, color) {
|
|
75
|
+
const { feature } = layout;
|
|
76
|
+
const { region, canvasWidth } = dc;
|
|
77
|
+
const reversed = region.reversed ?? false;
|
|
78
|
+
const left = layout.x;
|
|
79
|
+
const width = layout.width;
|
|
80
|
+
if (isOffScreen(left, width, canvasWidth)) {
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
const strand = feature.get('strand');
|
|
84
|
+
if (!strand) {
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
const centerY = layout.y + layout.height / 2;
|
|
88
|
+
drawStrandArrowAtPosition(ctx, left, centerY, width, strand, reversed, color);
|
|
89
|
+
}
|
|
90
|
+
export function drawSegmentedFeature(ctx, layout, dc, boxGlyph, cdsGlyph) {
|
|
91
|
+
const { feature, children } = layout;
|
|
92
|
+
const { region, configContext, theme, canvasWidth } = dc;
|
|
93
|
+
const { config, displayDirectionalChevrons } = configContext;
|
|
94
|
+
const reversed = region.reversed ?? false;
|
|
95
|
+
const left = layout.x;
|
|
96
|
+
const width = layout.width;
|
|
97
|
+
const top = layout.y;
|
|
98
|
+
const height = layout.height;
|
|
99
|
+
if (isOffScreen(left, width, canvasWidth)) {
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
const strokeColor = getStrokeColor({
|
|
103
|
+
feature,
|
|
104
|
+
config,
|
|
105
|
+
configContext,
|
|
106
|
+
theme,
|
|
107
|
+
});
|
|
108
|
+
drawConnectingLine(ctx, left, top, width, height, strokeColor);
|
|
109
|
+
if (displayDirectionalChevrons) {
|
|
110
|
+
const strand = feature.get('strand');
|
|
111
|
+
if (strand) {
|
|
112
|
+
const effectiveStrand = reversed ? -strand : strand;
|
|
113
|
+
drawChevrons(ctx, left, top + height / 2, width, effectiveStrand, strokeColor);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
for (const childLayout of children) {
|
|
117
|
+
if (childLayout.glyphType === 'CDS') {
|
|
118
|
+
cdsGlyph.draw(ctx, childLayout, dc);
|
|
119
|
+
}
|
|
120
|
+
else {
|
|
121
|
+
boxGlyph.draw(ctx, childLayout, dc);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
drawStrandArrow(ctx, layout, dc, strokeColor);
|
|
125
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { RenderConfigContext } from '../renderConfig.ts';
|
|
2
|
+
import type { Glyph } from '../types.ts';
|
|
3
|
+
import type PluginManager from '@jbrowse/core/PluginManager';
|
|
4
|
+
import type GlyphType from '@jbrowse/core/pluggableElementTypes/GlyphType';
|
|
5
|
+
import type { Feature } from '@jbrowse/core/util';
|
|
6
|
+
export declare const builtinGlyphs: Glyph[];
|
|
7
|
+
export declare function findGlyph(feature: Feature, configContext: RenderConfigContext, glyphs?: Glyph[]): Glyph;
|
|
8
|
+
export declare function findPluggableGlyph(feature: Feature, pluginManager?: PluginManager): GlyphType | undefined;
|
|
9
|
+
export { boxGlyph } from './box.ts';
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { boxGlyph } from "./box.js";
|
|
2
|
+
import { cdsGlyph } from "./cds.js";
|
|
3
|
+
import { matureProteinRegionGlyph } from "./matureProteinRegion.js";
|
|
4
|
+
import { processedTranscriptGlyph } from "./processed.js";
|
|
5
|
+
import { repeatRegionGlyph } from "./repeatRegion.js";
|
|
6
|
+
import { segmentsGlyph } from "./segments.js";
|
|
7
|
+
import { subfeaturesGlyph } from "./subfeatures.js";
|
|
8
|
+
export const builtinGlyphs = [
|
|
9
|
+
matureProteinRegionGlyph,
|
|
10
|
+
repeatRegionGlyph,
|
|
11
|
+
cdsGlyph,
|
|
12
|
+
processedTranscriptGlyph,
|
|
13
|
+
subfeaturesGlyph,
|
|
14
|
+
segmentsGlyph,
|
|
15
|
+
boxGlyph,
|
|
16
|
+
];
|
|
17
|
+
export function findGlyph(feature, configContext, glyphs = builtinGlyphs) {
|
|
18
|
+
const match = glyphs.find(g => g.match(feature, configContext));
|
|
19
|
+
return match ?? boxGlyph;
|
|
20
|
+
}
|
|
21
|
+
export function findPluggableGlyph(feature, pluginManager) {
|
|
22
|
+
if (!pluginManager) {
|
|
23
|
+
return undefined;
|
|
24
|
+
}
|
|
25
|
+
const glyphTypes = pluginManager.getGlyphTypes();
|
|
26
|
+
const sortedGlyphs = [...glyphTypes].sort((a, b) => b.priority - a.priority);
|
|
27
|
+
return sortedGlyphs.find(glyph => glyph.match?.(feature));
|
|
28
|
+
}
|
|
29
|
+
export { boxGlyph } from "./box.js";
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { readCachedConfig } from "../renderConfig.js";
|
|
2
|
+
import { isOffScreen } from "../util.js";
|
|
3
|
+
import { drawStrandArrowAtPosition, layoutChild } from "./glyphUtils.js";
|
|
4
|
+
const MATURE_PROTEIN_COLORS = [
|
|
5
|
+
'#1f77b4',
|
|
6
|
+
'#ff7f0e',
|
|
7
|
+
'#2ca02c',
|
|
8
|
+
'#d62728',
|
|
9
|
+
'#9467bd',
|
|
10
|
+
'#8c564b',
|
|
11
|
+
'#e377c2',
|
|
12
|
+
'#7f7f7f',
|
|
13
|
+
'#bcbd22',
|
|
14
|
+
'#17becf',
|
|
15
|
+
'#aec7e8',
|
|
16
|
+
'#ffbb78',
|
|
17
|
+
];
|
|
18
|
+
const MATURE_PROTEIN_TYPES = new Set([
|
|
19
|
+
'mature_protein_region_of_CDS',
|
|
20
|
+
'mature_protein_region',
|
|
21
|
+
]);
|
|
22
|
+
function getMatureProteinChildren(feature) {
|
|
23
|
+
const subfeatures = feature.get('subfeatures');
|
|
24
|
+
return (subfeatures?.filter(sub => MATURE_PROTEIN_TYPES.has(sub.get('type'))) ?? []);
|
|
25
|
+
}
|
|
26
|
+
function sortByPosition(children) {
|
|
27
|
+
return [...children].sort((a, b) => {
|
|
28
|
+
const aStart = a.feature.get('start');
|
|
29
|
+
const bStart = b.feature.get('start');
|
|
30
|
+
if (aStart !== bStart) {
|
|
31
|
+
return aStart - bStart;
|
|
32
|
+
}
|
|
33
|
+
return b.feature.get('end') - a.feature.get('end');
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
export const matureProteinRegionGlyph = {
|
|
37
|
+
type: 'MatureProteinRegion',
|
|
38
|
+
hasIndexableChildren: true,
|
|
39
|
+
match(feature) {
|
|
40
|
+
if (feature.get('type') !== 'CDS') {
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
return getMatureProteinChildren(feature).length > 0;
|
|
44
|
+
},
|
|
45
|
+
getSubfeatureMouseover(feature) {
|
|
46
|
+
const product = feature.get('product');
|
|
47
|
+
const proteinId = feature.get('protein_id');
|
|
48
|
+
const parts = [];
|
|
49
|
+
if (product) {
|
|
50
|
+
parts.push(product);
|
|
51
|
+
}
|
|
52
|
+
if (proteinId) {
|
|
53
|
+
parts.push(`(${proteinId})`);
|
|
54
|
+
}
|
|
55
|
+
return parts.length > 0 ? parts.join(' ') : undefined;
|
|
56
|
+
},
|
|
57
|
+
layout(args) {
|
|
58
|
+
const { feature, bpPerPx, configContext } = args;
|
|
59
|
+
const { config, displayMode, featureHeight, subfeatureLabels } = configContext;
|
|
60
|
+
const start = feature.get('start');
|
|
61
|
+
const end = feature.get('end');
|
|
62
|
+
const heightPx = readCachedConfig(featureHeight, config, 'height', feature);
|
|
63
|
+
const baseHeightPx = displayMode === 'compact' ? heightPx / 2 : heightPx;
|
|
64
|
+
const widthPx = (end - start) / bpPerPx;
|
|
65
|
+
const matureProteins = getMatureProteinChildren(feature);
|
|
66
|
+
const children = matureProteins.map(child => layoutChild(child, feature, args));
|
|
67
|
+
const sortedChildren = sortByPosition(children);
|
|
68
|
+
const numRows = Math.max(1, sortedChildren.length);
|
|
69
|
+
const perRowMultiplier = subfeatureLabels === 'below' ? 2 : 1;
|
|
70
|
+
const rowHeight = baseHeightPx * perRowMultiplier;
|
|
71
|
+
const totalHeight = rowHeight * numRows;
|
|
72
|
+
const padding = 1;
|
|
73
|
+
const boxHeight = subfeatureLabels === 'below'
|
|
74
|
+
? Math.floor(rowHeight / 2) - padding
|
|
75
|
+
: rowHeight - padding * 2;
|
|
76
|
+
for (const [i, sortedChild] of sortedChildren.entries()) {
|
|
77
|
+
const child = sortedChild;
|
|
78
|
+
child.y = i * rowHeight + padding;
|
|
79
|
+
child.height = boxHeight;
|
|
80
|
+
child.totalLayoutHeight = rowHeight;
|
|
81
|
+
}
|
|
82
|
+
return {
|
|
83
|
+
feature,
|
|
84
|
+
glyphType: 'MatureProteinRegion',
|
|
85
|
+
x: 0,
|
|
86
|
+
y: 0,
|
|
87
|
+
width: widthPx,
|
|
88
|
+
height: totalHeight,
|
|
89
|
+
totalLayoutHeight: totalHeight,
|
|
90
|
+
totalLayoutWidth: widthPx,
|
|
91
|
+
leftPadding: 0,
|
|
92
|
+
children: sortedChildren,
|
|
93
|
+
};
|
|
94
|
+
},
|
|
95
|
+
draw(ctx, layout, dc) {
|
|
96
|
+
const { children } = layout;
|
|
97
|
+
const { region, theme, canvasWidth } = dc;
|
|
98
|
+
const reversed = region.reversed ?? false;
|
|
99
|
+
const arrowColor = theme.palette.text.secondary;
|
|
100
|
+
for (const [i, childLayout] of children.entries()) {
|
|
101
|
+
const color = MATURE_PROTEIN_COLORS[i % MATURE_PROTEIN_COLORS.length];
|
|
102
|
+
drawMatureProteinBox(ctx, childLayout, canvasWidth, color, reversed, arrowColor);
|
|
103
|
+
}
|
|
104
|
+
},
|
|
105
|
+
};
|
|
106
|
+
function drawMatureProteinBox(ctx, childLayout, canvasWidth, color, reversed, arrowColor) {
|
|
107
|
+
const { x: left, y: boxTop, width, height: boxHeight, feature } = childLayout;
|
|
108
|
+
if (isOffScreen(left, width, canvasWidth)) {
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
ctx.fillStyle = color;
|
|
112
|
+
ctx.fillRect(left, boxTop, width, boxHeight);
|
|
113
|
+
ctx.strokeStyle = 'rgba(0,0,0,0.3)';
|
|
114
|
+
ctx.lineWidth = 1;
|
|
115
|
+
ctx.strokeRect(left, boxTop, width, boxHeight);
|
|
116
|
+
const strand = feature.get('strand');
|
|
117
|
+
if (strand) {
|
|
118
|
+
const centerY = boxTop + boxHeight / 2;
|
|
119
|
+
drawStrandArrowAtPosition(ctx, left, centerY, width, strand, reversed, arrowColor);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { getSubparts } from "../filterSubparts.js";
|
|
2
|
+
import { readCachedConfig } from "../renderConfig.js";
|
|
3
|
+
import { boxGlyph } from "./box.js";
|
|
4
|
+
import { cdsGlyph } from "./cds.js";
|
|
5
|
+
import { drawSegmentedFeature, getStrandArrowPadding, layoutChild, } from "./glyphUtils.js";
|
|
6
|
+
export const processedTranscriptGlyph = {
|
|
7
|
+
type: 'ProcessedTranscript',
|
|
8
|
+
match(feature, configContext) {
|
|
9
|
+
const type = feature.get('type');
|
|
10
|
+
const subfeatures = feature.get('subfeatures');
|
|
11
|
+
if (!subfeatures?.length) {
|
|
12
|
+
return false;
|
|
13
|
+
}
|
|
14
|
+
const { transcriptTypes } = configContext;
|
|
15
|
+
if (!transcriptTypes.includes(type)) {
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
const hasCDS = subfeatures.some((f) => f.get('type') === 'CDS');
|
|
19
|
+
return hasCDS;
|
|
20
|
+
},
|
|
21
|
+
layout(args) {
|
|
22
|
+
const { feature, bpPerPx, reversed, configContext } = args;
|
|
23
|
+
const { config, displayMode, featureHeight } = configContext;
|
|
24
|
+
const start = feature.get('start');
|
|
25
|
+
const end = feature.get('end');
|
|
26
|
+
const heightPx = readCachedConfig(featureHeight, config, 'height', feature);
|
|
27
|
+
const baseHeightPx = displayMode === 'compact' ? heightPx / 2 : heightPx;
|
|
28
|
+
const widthPx = (end - start) / bpPerPx;
|
|
29
|
+
const strand = feature.get('strand');
|
|
30
|
+
const arrowPadding = getStrandArrowPadding(strand, reversed);
|
|
31
|
+
const subparts = getSubparts(feature, config);
|
|
32
|
+
const children = subparts.map(child => {
|
|
33
|
+
const childType = child.get('type');
|
|
34
|
+
const glyphType = childType === 'CDS' ? 'CDS' : 'Box';
|
|
35
|
+
return layoutChild(child, feature, args, glyphType);
|
|
36
|
+
});
|
|
37
|
+
return {
|
|
38
|
+
feature,
|
|
39
|
+
glyphType: 'ProcessedTranscript',
|
|
40
|
+
x: 0,
|
|
41
|
+
y: 0,
|
|
42
|
+
width: widthPx,
|
|
43
|
+
height: baseHeightPx,
|
|
44
|
+
totalLayoutHeight: baseHeightPx,
|
|
45
|
+
totalLayoutWidth: widthPx + arrowPadding.left + arrowPadding.right,
|
|
46
|
+
leftPadding: arrowPadding.left,
|
|
47
|
+
children,
|
|
48
|
+
};
|
|
49
|
+
},
|
|
50
|
+
draw(ctx, layout, dc) {
|
|
51
|
+
drawSegmentedFeature(ctx, layout, dc, boxGlyph, cdsGlyph);
|
|
52
|
+
},
|
|
53
|
+
};
|