react-msaview 9.0.0 → 9.1.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/README.md +19 -10
- package/bundle/index.js +74 -74
- package/bundle/index.js.map +4 -4
- package/dist/colorSchemes.js +1 -3
- package/dist/colorSchemes.js.map +1 -1
- package/dist/columnCounts.d.ts +0 -1
- package/dist/columnCounts.js +1 -1
- package/dist/columnCounts.js.map +1 -1
- package/dist/components/Loading.js +5 -5
- package/dist/components/Loading.js.map +1 -1
- package/dist/components/MSAViewer.d.ts +1891 -4
- package/dist/components/MSAViewer.js +144 -201
- package/dist/components/MSAViewer.js.map +1 -1
- package/dist/components/ResizeHandles.js +3 -2
- package/dist/components/ResizeHandles.js.map +1 -1
- package/dist/components/Track.js +2 -1
- package/dist/components/Track.js.map +1 -1
- package/dist/components/controls.d.ts +11 -0
- package/dist/components/controls.js +48 -0
- package/dist/components/controls.js.map +1 -0
- package/dist/components/dialogs/AnnotationFileDialog.js +5 -16
- package/dist/components/dialogs/AnnotationFileDialog.js.map +1 -1
- package/dist/components/dialogs/ExportSVGDialog.js +5 -7
- package/dist/components/dialogs/ExportSVGDialog.js.map +1 -1
- package/dist/components/dialogs/HelpDialog.d.ts +6 -0
- package/dist/components/dialogs/HelpDialog.js +30 -0
- package/dist/components/dialogs/HelpDialog.js.map +1 -0
- package/dist/components/header/FileMenu.js +33 -7
- package/dist/components/header/FileMenu.js.map +1 -1
- package/dist/components/header/Header.js +4 -6
- package/dist/components/header/Header.js.map +1 -1
- package/dist/components/header/TreeSettingsMenu.d.ts +1 -1
- package/dist/components/header/TreeSettingsMenu.js +1 -32
- package/dist/components/header/TreeSettingsMenu.js.map +1 -1
- package/dist/components/header/ZoomControls.js +28 -44
- package/dist/components/header/ZoomControls.js.map +1 -1
- package/dist/components/header/settingsMenuItems.d.ts +1 -0
- package/dist/components/header/settingsMenuItems.js +44 -0
- package/dist/components/header/settingsMenuItems.js.map +1 -1
- package/dist/components/import/data/seq2.d.ts +0 -1
- package/dist/components/import/data/seq2.js +0 -17
- package/dist/components/import/data/seq2.js.map +1 -1
- package/dist/components/msa/MSACanvas.js +2 -1
- package/dist/components/msa/MSACanvas.js.map +1 -1
- package/dist/components/msa/MSACanvasBlock.js +2 -5
- package/dist/components/msa/MSACanvasBlock.js.map +1 -1
- package/dist/components/msa/drawFeatureSpans.d.ts +50 -11
- package/dist/components/msa/drawFeatureSpans.js +99 -56
- package/dist/components/msa/drawFeatureSpans.js.map +1 -1
- package/dist/components/msa/msaRaster.d.ts +14 -1
- package/dist/components/msa/msaRaster.js +19 -9
- package/dist/components/msa/msaRaster.js.map +1 -1
- package/dist/components/msa/renderBoxFeatureCanvasBlock.d.ts +9 -0
- package/dist/components/msa/renderBoxFeatureCanvasBlock.js +19 -8
- package/dist/components/msa/renderBoxFeatureCanvasBlock.js.map +1 -1
- package/dist/components/msa/renderHighlights.d.ts +2 -2
- package/dist/components/msa/renderHighlights.js +2 -3
- package/dist/components/msa/renderHighlights.js.map +1 -1
- package/dist/components/msa/renderMSAMouseover.js +2 -1
- package/dist/components/msa/renderMSAMouseover.js.map +1 -1
- package/dist/components/renderCtx.d.ts +1 -1
- package/dist/components/rowpanels/renderFeaturePanel.d.ts +5 -1
- package/dist/components/rowpanels/renderFeaturePanel.js +12 -5
- package/dist/components/rowpanels/renderFeaturePanel.js.map +1 -1
- package/dist/components/tracks/drawTracks.d.ts +1 -53
- package/dist/components/tracks/drawTracks.js +5 -5
- package/dist/components/tracks/drawTracks.js.map +1 -1
- package/dist/components/tree/CladeLabels.d.ts +1 -1
- package/dist/components/tree/CladeLabels.js +22 -34
- package/dist/components/tree/CladeLabels.js.map +1 -1
- package/dist/components/tree/cladeBrackets.d.ts +17 -10
- package/dist/components/tree/cladeBrackets.js +33 -3
- package/dist/components/tree/cladeBrackets.js.map +1 -1
- package/dist/components/tree/renderTreeCanvas.js +2 -8
- package/dist/components/tree/renderTreeCanvas.js.map +1 -1
- package/dist/components/tree/renderTreeOverview.js +4 -11
- package/dist/components/tree/renderTreeOverview.js.map +1 -1
- package/dist/element.js +3 -6
- package/dist/element.js.map +1 -1
- package/dist/exportLayout.d.ts +39 -0
- package/dist/exportLayout.js +78 -0
- package/dist/exportLayout.js.map +1 -0
- package/dist/headlessRenderEnv.d.ts +12 -39
- package/dist/headlessRenderEnv.js +18 -12
- package/dist/headlessRenderEnv.js.map +1 -1
- package/dist/hierarchy.d.ts +5 -8
- package/dist/hierarchy.js +5 -79
- package/dist/hierarchy.js.map +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.js +4 -1
- package/dist/index.js.map +1 -1
- package/dist/measureTextCanvas.d.ts +7 -0
- package/dist/measureTextCanvas.js +8 -6
- package/dist/measureTextCanvas.js.map +1 -1
- package/dist/model/treeModel.d.ts +0 -5
- package/dist/model/treeModel.js +0 -7
- package/dist/model/treeModel.js.map +1 -1
- package/dist/model.d.ts +38 -44
- package/dist/model.js +153 -112
- package/dist/model.js.map +1 -1
- package/dist/renderToSvg.d.ts +2 -7
- package/dist/renderToSvg.js +32 -120
- package/dist/renderToSvg.js.map +1 -1
- package/dist/svgTestUtil.d.ts +12 -20
- package/dist/types.d.ts +0 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/viewerSnapshot.d.ts +169 -0
- package/dist/viewerSnapshot.js +100 -0
- package/dist/viewerSnapshot.js.map +1 -0
- package/package.json +3 -3
- package/src/colorSchemes.ts +1 -4
- package/src/columnCounts.ts +1 -1
- package/src/components/Loading.tsx +19 -12
- package/src/components/MSAViewer.tsx +275 -251
- package/src/components/ResizeHandles.tsx +9 -2
- package/src/components/Track.tsx +2 -1
- package/src/components/controls.ts +61 -0
- package/src/components/dialogs/AnnotationFileDialog.tsx +5 -21
- package/src/components/dialogs/ExportSVGDialog.tsx +8 -19
- package/src/components/dialogs/HelpDialog.tsx +79 -0
- package/src/components/header/FileMenu.tsx +33 -7
- package/src/components/header/Header.tsx +4 -6
- package/src/components/header/TreeSettingsMenu.tsx +2 -32
- package/src/components/header/ZoomControls.tsx +30 -66
- package/src/components/header/settingsMenuItems.ts +59 -0
- package/src/components/import/data/seq2.ts +0 -18
- package/src/components/msa/MSACanvas.tsx +2 -1
- package/src/components/msa/MSACanvasBlock.tsx +2 -6
- package/src/components/msa/drawFeatureSpans.ts +160 -79
- package/src/components/msa/msaRaster.ts +27 -9
- package/src/components/msa/renderBoxFeatureCanvasBlock.ts +39 -24
- package/src/components/msa/renderHighlights.ts +2 -3
- package/src/components/msa/renderMSAMouseover.ts +5 -1
- package/src/components/renderCtx.ts +0 -1
- package/src/components/rowpanels/renderFeaturePanel.ts +26 -7
- package/src/components/tracks/drawTracks.ts +5 -5
- package/src/components/tree/CladeLabels.tsx +30 -54
- package/src/components/tree/cladeBrackets.ts +50 -3
- package/src/components/tree/renderTreeCanvas.ts +2 -13
- package/src/components/tree/renderTreeOverview.ts +7 -14
- package/src/element.ts +3 -7
- package/src/exportLayout.ts +134 -0
- package/src/headlessRenderEnv.ts +24 -11
- package/src/hierarchy.ts +5 -88
- package/src/index.ts +5 -5
- package/src/measureTextCanvas.ts +8 -6
- package/src/model/treeModel.ts +0 -7
- package/src/model.ts +170 -122
- package/src/renderToSvg.tsx +55 -194
- package/src/types.ts +0 -1
- package/src/version.ts +1 -1
- package/src/viewerSnapshot.ts +147 -0
- package/dist/components/header/ZoomMenu.d.ts +0 -6
- package/dist/components/header/ZoomMenu.js +0 -37
- package/dist/components/header/ZoomMenu.js.map +0 -1
- package/src/components/header/ZoomMenu.tsx +0 -48
|
@@ -37,6 +37,42 @@ export interface SpanLayout {
|
|
|
37
37
|
headRise?: (height: number) => number
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
+
/**
|
|
41
|
+
* What a renderer hands the span mark: its rows, where a band lands in them,
|
|
42
|
+
* its x mapping, the colors and the label each feature takes.
|
|
43
|
+
*/
|
|
44
|
+
export interface SpanSource<T extends SpanBand> {
|
|
45
|
+
rows: SpanRow<T>[]
|
|
46
|
+
layout: SpanLayout
|
|
47
|
+
xOf: (band: T) => [number, number]
|
|
48
|
+
colors: Map<Annotation, { fill: string; stroke: string }>
|
|
49
|
+
labelOf?: (band: T) => string | undefined
|
|
50
|
+
labelDrawn?: (band: T) => void
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** a span as drawn: a box, or an arrow's outline where it carries a strand */
|
|
54
|
+
export interface SpanShape<T extends SpanBand> {
|
|
55
|
+
band: T
|
|
56
|
+
fill: string
|
|
57
|
+
stroke: string
|
|
58
|
+
box?: { x: number; y: number; w: number; h: number }
|
|
59
|
+
outline?: [number, number][]
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export interface SpanLabel {
|
|
63
|
+
text: string
|
|
64
|
+
x: number
|
|
65
|
+
y: number
|
|
66
|
+
fill: string
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** one row's shapes, then the labels drawn over them at `fontSize` */
|
|
70
|
+
export interface SpanMarkRow<T extends SpanBand> {
|
|
71
|
+
shapes: SpanShape<T>[]
|
|
72
|
+
labels: SpanLabel[]
|
|
73
|
+
fontSize: number
|
|
74
|
+
}
|
|
75
|
+
|
|
40
76
|
/** the rows of `bandsByRow` that `leaves` covers, in display order */
|
|
41
77
|
export function spanRows<T extends SpanBand>(
|
|
42
78
|
leaves: HierarchyNode<NodeWithIdsAndLength>[],
|
|
@@ -53,56 +89,49 @@ export function spanRows<T extends SpanBand>(
|
|
|
53
89
|
}
|
|
54
90
|
|
|
55
91
|
/**
|
|
56
|
-
* The span mark, in whatever x space
|
|
57
|
-
* columns for the domain overlay, panel pixels for a `features` row
|
|
58
|
-
* feature with a strand
|
|
59
|
-
*
|
|
92
|
+
* The span mark as geometry, in whatever x space `xOf` maps its bands into:
|
|
93
|
+
* alignment columns for the domain overlay, panel pixels for a `features` row
|
|
94
|
+
* panel. A feature with a strand is an arrow, one without a box, and a label
|
|
95
|
+
* lands inside the span wherever `measureText` says the text fits.
|
|
60
96
|
*
|
|
61
|
-
* `
|
|
62
|
-
* the context is translated so that `y - rowHeight` is the row's top edge.
|
|
97
|
+
* `y - rowHeight` is the row's top edge in the coordinates `rows` gives.
|
|
63
98
|
*/
|
|
64
|
-
export function
|
|
65
|
-
ctx,
|
|
99
|
+
export function featureSpanMarks<T extends SpanBand>({
|
|
66
100
|
rows,
|
|
67
101
|
layout,
|
|
68
102
|
xOf,
|
|
69
103
|
colors,
|
|
70
104
|
labelOf,
|
|
71
105
|
labelDrawn,
|
|
72
|
-
|
|
106
|
+
measureText,
|
|
73
107
|
xMin,
|
|
74
108
|
xMax,
|
|
75
|
-
}: {
|
|
76
|
-
|
|
77
|
-
rows: SpanRow<T>[]
|
|
78
|
-
layout: SpanLayout
|
|
79
|
-
xOf: (band: T) => [number, number]
|
|
80
|
-
colors: Map<Annotation, { fill: string; stroke: string }>
|
|
81
|
-
labelOf?: (band: T) => string | undefined
|
|
82
|
-
labelDrawn?: (band: T) => void
|
|
83
|
-
contrastText: (color: string) => string
|
|
109
|
+
}: SpanSource<T> & {
|
|
110
|
+
measureText: (text: string, fontSize: number) => number
|
|
84
111
|
xMin: number
|
|
85
112
|
xMax: number
|
|
86
|
-
}) {
|
|
87
|
-
|
|
88
|
-
// longest-first so a nested one lands on top, and drawing each label with its
|
|
89
|
-
// own span put the child's box over the parent's text: `S` covered `RBD`, and
|
|
90
|
-
// `RBD` read as `RB`. Reused across rows rather than allocated per row.
|
|
91
|
-
const placed: {
|
|
92
|
-
band: T
|
|
93
|
-
xStart: number
|
|
94
|
-
w: number
|
|
95
|
-
t: number
|
|
96
|
-
head: number
|
|
97
|
-
fill: string
|
|
98
|
-
}[] = []
|
|
99
|
-
|
|
113
|
+
}): SpanMarkRow<T>[] {
|
|
114
|
+
const out: SpanMarkRow<T>[] = []
|
|
100
115
|
for (const { y, bands } of rows) {
|
|
101
116
|
const h = layout.height(bands[0]!.laneCount)
|
|
102
117
|
const headLen = layout.headLength(h)
|
|
103
118
|
const rise = layout.headRise?.(h) ?? 0
|
|
104
119
|
const labelled = labelOf !== undefined && h >= minFeatureLabelHeight
|
|
105
|
-
|
|
120
|
+
const fontSize = Math.min(h - 2, 11)
|
|
121
|
+
const shapes: SpanShape<T>[] = []
|
|
122
|
+
const labels: SpanLabel[] = []
|
|
123
|
+
// one row's spans are drawn before any of its labels. Bands are ordered
|
|
124
|
+
// longest-first so a nested one lands on top, and drawing each label with
|
|
125
|
+
// its own span put the child's box over the parent's text: `S` covered
|
|
126
|
+
// `RBD`, and `RBD` read as `RB`.
|
|
127
|
+
const placed: {
|
|
128
|
+
band: T
|
|
129
|
+
xStart: number
|
|
130
|
+
w: number
|
|
131
|
+
t: number
|
|
132
|
+
head: number
|
|
133
|
+
fill: string
|
|
134
|
+
}[] = []
|
|
106
135
|
|
|
107
136
|
for (const band of bands) {
|
|
108
137
|
const [xStart, xEnd] = xOf(band)
|
|
@@ -112,55 +141,111 @@ export function drawFeatureSpans<T extends SpanBand>({
|
|
|
112
141
|
}
|
|
113
142
|
const t = layout.top(y, band.lane, h)
|
|
114
143
|
const { fill, stroke } = colors.get(band.annotation)!
|
|
115
|
-
ctx.fillStyle = fill
|
|
116
|
-
ctx.strokeStyle = stroke
|
|
117
144
|
const { strand } = band.annotation
|
|
118
145
|
const head = strand === undefined ? 0 : Math.min(headLen, w)
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
146
|
+
shapes.push(
|
|
147
|
+
strand === undefined
|
|
148
|
+
? { band, fill, stroke, box: { x: xStart, y: t, w, h } }
|
|
149
|
+
: {
|
|
150
|
+
band,
|
|
151
|
+
fill,
|
|
152
|
+
stroke,
|
|
153
|
+
outline: geneArrow({ x: xStart, t, w, h, head, rise, strand }),
|
|
154
|
+
},
|
|
155
|
+
)
|
|
125
156
|
if (labelled) {
|
|
126
157
|
placed.push({ band, xStart, w, t, head, fill })
|
|
127
158
|
}
|
|
128
159
|
}
|
|
129
160
|
|
|
130
|
-
|
|
131
|
-
|
|
161
|
+
for (const { band, xStart, w, t, head, fill } of placed) {
|
|
162
|
+
// asking for the label here rather than in the pass above keeps the
|
|
163
|
+
// once-per-block segment numbering in drawing order
|
|
164
|
+
const text = labelOf!(band)
|
|
165
|
+
// the head narrows to a point, so only about half of it holds text
|
|
166
|
+
if (
|
|
167
|
+
text !== undefined &&
|
|
168
|
+
measureText(text, fontSize) + 2 <= w - head / 2
|
|
169
|
+
) {
|
|
170
|
+
labelDrawn?.(band)
|
|
171
|
+
labels.push({ text, x: xStart + w / 2, y: t + h / 2, fill })
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
out.push({ shapes, labels, fontSize })
|
|
175
|
+
}
|
|
176
|
+
return out
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* The span mark, drawn into `ctx` from `featureSpanMarks`, with the context
|
|
181
|
+
* translated so that `y - rowHeight` is the row's top edge.
|
|
182
|
+
*/
|
|
183
|
+
export function drawFeatureSpans<T extends SpanBand>({
|
|
184
|
+
ctx,
|
|
185
|
+
contrastText,
|
|
186
|
+
...source
|
|
187
|
+
}: SpanSource<T> & {
|
|
188
|
+
ctx: RenderCtx
|
|
189
|
+
contrastText: (color: string) => string
|
|
190
|
+
xMin: number
|
|
191
|
+
xMax: number
|
|
192
|
+
}) {
|
|
193
|
+
let font = ''
|
|
194
|
+
const setFont = (fontSize: number) => {
|
|
195
|
+
const next = `${fontSize}px sans-serif`
|
|
196
|
+
if (next !== font) {
|
|
197
|
+
ctx.font = next
|
|
198
|
+
font = next
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
const rows = featureSpanMarks({
|
|
202
|
+
...source,
|
|
203
|
+
measureText: (text, fontSize) => {
|
|
204
|
+
setFont(fontSize)
|
|
205
|
+
return ctx.measureText(text).width
|
|
206
|
+
},
|
|
207
|
+
})
|
|
208
|
+
for (const { shapes, labels, fontSize } of rows) {
|
|
209
|
+
for (const { fill, stroke, box, outline } of shapes) {
|
|
210
|
+
ctx.fillStyle = fill
|
|
211
|
+
ctx.strokeStyle = stroke
|
|
212
|
+
if (box) {
|
|
213
|
+
ctx.fillRect(box.x, box.y, box.w, box.h)
|
|
214
|
+
ctx.strokeRect(box.x, box.y, box.w, box.h)
|
|
215
|
+
} else if (outline) {
|
|
216
|
+
ctx.beginPath()
|
|
217
|
+
ctx.moveTo(...outline[0]!)
|
|
218
|
+
for (let i = 1; i < outline.length; i++) {
|
|
219
|
+
ctx.lineTo(...outline[i]!)
|
|
220
|
+
}
|
|
221
|
+
ctx.closePath()
|
|
222
|
+
ctx.fill()
|
|
223
|
+
ctx.stroke()
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
if (labels.length > 0) {
|
|
227
|
+
setFont(fontSize)
|
|
132
228
|
ctx.textAlign = 'center'
|
|
133
229
|
ctx.textBaseline = 'middle'
|
|
134
|
-
for (const {
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
const label = labelOf!(band)
|
|
138
|
-
// the head narrows to a point, so only about half of it holds text
|
|
139
|
-
if (
|
|
140
|
-
label !== undefined &&
|
|
141
|
-
ctx.measureText(label).width + 2 <= w - head / 2
|
|
142
|
-
) {
|
|
143
|
-
labelDrawn?.(band)
|
|
144
|
-
ctx.fillStyle = contrastText(fill)
|
|
145
|
-
ctx.fillText(label, xStart + w / 2, t + h / 2)
|
|
146
|
-
}
|
|
230
|
+
for (const { text, x, y, fill } of labels) {
|
|
231
|
+
ctx.fillStyle = contrastText(fill)
|
|
232
|
+
ctx.fillText(text, x, y)
|
|
147
233
|
}
|
|
148
234
|
}
|
|
149
235
|
}
|
|
150
236
|
}
|
|
151
237
|
|
|
152
238
|
/**
|
|
153
|
-
* A gene arrow,
|
|
154
|
-
* last `head` pixels *of* the feature, tapering to a point at its end, and
|
|
155
|
-
* feature shorter than the head is all head. The glyph therefore covers its
|
|
239
|
+
* A gene arrow's outline, the way gggenes and gggenomes draw one: the head is
|
|
240
|
+
* the last `head` pixels *of* the feature, tapering to a point at its end, and
|
|
241
|
+
* a feature shorter than the head is all head. The glyph therefore covers its
|
|
156
242
|
* start..end span and nothing else, so adjacent genes butt together instead of
|
|
157
243
|
* biting triangles out of each other, a neighbour never draws over a label, and
|
|
158
244
|
* a gene 20 columns wide reads as 20 columns wide.
|
|
159
245
|
*
|
|
160
246
|
* `rise` lifts the head above the band, for a bar too thin to taper.
|
|
161
247
|
*/
|
|
162
|
-
function
|
|
163
|
-
ctx,
|
|
248
|
+
function geneArrow({
|
|
164
249
|
x,
|
|
165
250
|
t,
|
|
166
251
|
w,
|
|
@@ -169,7 +254,6 @@ function drawGeneArrow({
|
|
|
169
254
|
rise,
|
|
170
255
|
strand,
|
|
171
256
|
}: {
|
|
172
|
-
ctx: RenderCtx
|
|
173
257
|
x: number
|
|
174
258
|
t: number
|
|
175
259
|
w: number
|
|
@@ -177,29 +261,26 @@ function drawGeneArrow({
|
|
|
177
261
|
head: number
|
|
178
262
|
rise: number
|
|
179
263
|
strand: number
|
|
180
|
-
}) {
|
|
264
|
+
}): [number, number][] {
|
|
181
265
|
const tip = strand > 0 ? x + w : x
|
|
182
266
|
const tail = strand > 0 ? x : x + w
|
|
183
267
|
const flange = strand > 0 ? tip - head : tip + head
|
|
184
268
|
const bottom = t + h
|
|
185
269
|
const crown = t - rise
|
|
186
270
|
const point = (crown + bottom) / 2
|
|
187
|
-
ctx.beginPath()
|
|
188
271
|
if (head >= w) {
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
ctx.lineTo(flange, t)
|
|
195
|
-
if (rise > 0) {
|
|
196
|
-
ctx.lineTo(flange, crown)
|
|
197
|
-
}
|
|
198
|
-
ctx.lineTo(tip, point)
|
|
199
|
-
ctx.lineTo(flange, bottom)
|
|
200
|
-
ctx.lineTo(tail, bottom)
|
|
272
|
+
return [
|
|
273
|
+
[tail, crown],
|
|
274
|
+
[tip, point],
|
|
275
|
+
[tail, bottom],
|
|
276
|
+
]
|
|
201
277
|
}
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
278
|
+
return [
|
|
279
|
+
[tail, t],
|
|
280
|
+
[flange, t],
|
|
281
|
+
...(rise > 0 ? [[flange, crown] as [number, number]] : []),
|
|
282
|
+
[tip, point],
|
|
283
|
+
[flange, bottom],
|
|
284
|
+
[tail, bottom],
|
|
285
|
+
]
|
|
205
286
|
}
|
|
@@ -242,17 +242,30 @@ function usableRasterCtx(ctx: unknown): ctx is RasterCtx {
|
|
|
242
242
|
)
|
|
243
243
|
}
|
|
244
244
|
|
|
245
|
-
|
|
245
|
+
/**
|
|
246
|
+
* A canvas off the page, an OffscreenCanvas where there is one, with its 2d
|
|
247
|
+
* context, or undefined where the context fails `usable` (jsdom has none).
|
|
248
|
+
*/
|
|
249
|
+
export function makeOffscreenCanvas<Ctx>(
|
|
250
|
+
width: number,
|
|
251
|
+
height: number,
|
|
252
|
+
usable: (ctx: unknown) => ctx is Ctx,
|
|
253
|
+
settings?: CanvasRenderingContext2DSettings,
|
|
254
|
+
) {
|
|
246
255
|
const canvas =
|
|
247
256
|
typeof OffscreenCanvas !== 'undefined'
|
|
248
257
|
? new OffscreenCanvas(width, height)
|
|
249
258
|
: typeof document !== 'undefined'
|
|
250
259
|
? Object.assign(document.createElement('canvas'), { width, height })
|
|
251
260
|
: undefined
|
|
252
|
-
const ctx = canvas?.getContext('2d',
|
|
253
|
-
return
|
|
254
|
-
|
|
255
|
-
|
|
261
|
+
const ctx = canvas?.getContext('2d', settings)
|
|
262
|
+
return usable(ctx) ? { canvas: canvas as RasterCanvas, ctx } : undefined
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
function makeRasterCanvas(width: number, height: number) {
|
|
266
|
+
return makeOffscreenCanvas(width, height, usableRasterCtx, {
|
|
267
|
+
willReadFrequently: true,
|
|
268
|
+
})
|
|
256
269
|
}
|
|
257
270
|
|
|
258
271
|
function paint(
|
|
@@ -272,11 +285,16 @@ function paint(
|
|
|
272
285
|
|
|
273
286
|
let supported: boolean | undefined
|
|
274
287
|
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
288
|
+
/**
|
|
289
|
+
* Whether the alignment background comes from the raster: true with
|
|
290
|
+
* background coloring on, the domain overlay off, and a 2d context that reads
|
|
291
|
+
* back, which jsdom lacks. renderMSABlock paints each cell otherwise.
|
|
292
|
+
*/
|
|
293
|
+
export function rasterApplies(
|
|
294
|
+
model: Pick<MsaViewModel, 'actuallyShowDomains' | 'bgColor'>,
|
|
295
|
+
) {
|
|
278
296
|
supported ??= !!makeRasterCanvas(1, 1)
|
|
279
|
-
return supported
|
|
297
|
+
return !model.actuallyShowDomains && model.bgColor && supported
|
|
280
298
|
}
|
|
281
299
|
|
|
282
300
|
interface RasterCache {
|
|
@@ -3,9 +3,11 @@ import { contrastTextFn } from '../../util.ts'
|
|
|
3
3
|
import { getVisibleLeaves } from '../getVisibleLeaves.ts'
|
|
4
4
|
import { drawFeatureSpans, spanRows } from './drawFeatureSpans.ts'
|
|
5
5
|
|
|
6
|
+
import type { HierarchyNode } from '../../hierarchy.ts'
|
|
6
7
|
import type { MsaViewModel } from '../../model.ts'
|
|
7
|
-
import type { DomainBand } from '../../types.ts'
|
|
8
|
+
import type { DomainBand, NodeWithIdsAndLength } from '../../types.ts'
|
|
8
9
|
import type { RenderCtx } from '../renderCtx.ts'
|
|
10
|
+
import type { SpanSource } from './drawFeatureSpans.ts'
|
|
9
11
|
import type { Theme } from '@mui/material'
|
|
10
12
|
|
|
11
13
|
export function renderBoxFeatureCanvasBlock({
|
|
@@ -27,11 +29,41 @@ export function renderBoxFeatureCanvasBlock({
|
|
|
27
29
|
blockSizeXOverride?: number
|
|
28
30
|
blockSizeYOverride?: number
|
|
29
31
|
}) {
|
|
32
|
+
const { blockSize, rowHeight, highResScaleFactor, actuallyShowDomains } =
|
|
33
|
+
model
|
|
34
|
+
if (!actuallyShowDomains) {
|
|
35
|
+
return
|
|
36
|
+
}
|
|
37
|
+
const k = highResScaleFactorOverride ?? highResScaleFactor
|
|
38
|
+
const bx = blockSizeXOverride ?? blockSize
|
|
39
|
+
const by = blockSizeYOverride ?? blockSize
|
|
40
|
+
ctx.resetTransform()
|
|
41
|
+
ctx.scale(k, k)
|
|
42
|
+
ctx.translate(-offsetX, rowHeight / 2 - offsetY)
|
|
43
|
+
|
|
44
|
+
drawFeatureSpans<DomainBand>({
|
|
45
|
+
ctx,
|
|
46
|
+
...domainOverlaySource(
|
|
47
|
+
model,
|
|
48
|
+
getVisibleLeaves({ model, offsetY, blockSizeY: by }),
|
|
49
|
+
),
|
|
50
|
+
contrastText: contrastTextFn(theme),
|
|
51
|
+
xMin: offsetX,
|
|
52
|
+
xMax: offsetX + bx,
|
|
53
|
+
})
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* The domain overlay's spans over `leaves`, in alignment pixels: filled rows,
|
|
58
|
+
* an underline bar, or thin sub-rows, as `domainUnderline` and
|
|
59
|
+
* `subFeatureRows` decide.
|
|
60
|
+
*/
|
|
61
|
+
export function domainOverlaySource(
|
|
62
|
+
model: MsaViewModel,
|
|
63
|
+
leaves: HierarchyNode<NodeWithIdsAndLength>[],
|
|
64
|
+
): SpanSource<DomainBand> {
|
|
30
65
|
const {
|
|
31
|
-
blockSize,
|
|
32
66
|
rowHeight,
|
|
33
|
-
highResScaleFactor,
|
|
34
|
-
actuallyShowDomains,
|
|
35
67
|
subFeatureRows,
|
|
36
68
|
colWidth,
|
|
37
69
|
featureColors,
|
|
@@ -41,16 +73,6 @@ export function renderBoxFeatureCanvasBlock({
|
|
|
41
73
|
domainUnderline,
|
|
42
74
|
domainBands,
|
|
43
75
|
} = model
|
|
44
|
-
if (!actuallyShowDomains) {
|
|
45
|
-
return
|
|
46
|
-
}
|
|
47
|
-
const k = highResScaleFactorOverride ?? highResScaleFactor
|
|
48
|
-
const bx = blockSizeXOverride ?? blockSize
|
|
49
|
-
const by = blockSizeYOverride ?? blockSize
|
|
50
|
-
ctx.resetTransform()
|
|
51
|
-
ctx.scale(k, k)
|
|
52
|
-
ctx.translate(-offsetX, rowHeight / 2 - offsetY)
|
|
53
|
-
|
|
54
76
|
// the plain and underline modes give every band the same height; a sub-row
|
|
55
77
|
// band is thinner, and how thin depends on how many lanes its row needs
|
|
56
78
|
const barHeight = domainUnderline
|
|
@@ -67,12 +89,8 @@ export function renderBoxFeatureCanvasBlock({
|
|
|
67
89
|
// carrying the gene model are often not the first ones in the alignment
|
|
68
90
|
const labelled = drawSegmentLabels ? new Set<string>() : undefined
|
|
69
91
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
rows: spanRows(
|
|
73
|
-
getVisibleLeaves({ model, offsetY, blockSizeY: by }),
|
|
74
|
-
domainBands,
|
|
75
|
-
),
|
|
92
|
+
return {
|
|
93
|
+
rows: spanRows(leaves, domainBands),
|
|
76
94
|
layout: {
|
|
77
95
|
// sub-rows are thin, but a row whose lanes would spill onto the row below
|
|
78
96
|
// shares out the row height between them instead
|
|
@@ -98,8 +116,5 @@ export function renderBoxFeatureCanvasBlock({
|
|
|
98
116
|
? segmentLabels.get(band.annotation.accession)
|
|
99
117
|
: undefined),
|
|
100
118
|
labelDrawn: band => labelled?.add(band.annotation.accession),
|
|
101
|
-
|
|
102
|
-
xMin: offsetX,
|
|
103
|
-
xMax: offsetX + bx,
|
|
104
|
-
})
|
|
119
|
+
}
|
|
105
120
|
}
|
|
@@ -215,8 +215,8 @@ export function renderSelection({
|
|
|
215
215
|
|
|
216
216
|
/**
|
|
217
217
|
* The overlay parts that come from the snapshot: the reference row's tint, a
|
|
218
|
-
* bordered band per run of highlighted columns
|
|
219
|
-
*
|
|
218
|
+
* bordered band per run of highlighted columns and the `highlights` layer. The
|
|
219
|
+
* live overlay draws these under the selected block and its hover and click
|
|
220
220
|
* bands; the SVG export draws only these.
|
|
221
221
|
*/
|
|
222
222
|
export function renderPersistentHighlights({
|
|
@@ -255,5 +255,4 @@ export function renderPersistentHighlights({
|
|
|
255
255
|
}
|
|
256
256
|
|
|
257
257
|
renderHighlights({ ctx, model, theme, offsetX, offsetY, width, height })
|
|
258
|
-
renderSelection({ ctx, model, offsetX, offsetY, height })
|
|
259
258
|
}
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { clickColor, hoverColor, multiRowHoverColor } from '../overlayColors.ts'
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
renderPersistentHighlights,
|
|
4
|
+
renderSelection,
|
|
5
|
+
} from './renderHighlights.ts'
|
|
3
6
|
|
|
4
7
|
import type { MsaViewModel } from '../../model.ts'
|
|
5
8
|
import type { Theme } from '@mui/material'
|
|
@@ -49,6 +52,7 @@ export function renderMouseover({
|
|
|
49
52
|
width,
|
|
50
53
|
height,
|
|
51
54
|
})
|
|
55
|
+
renderSelection({ ctx, model, offsetX: -scrollX, offsetY: -scrollY, height })
|
|
52
56
|
|
|
53
57
|
// every tip under a hovered tree node stays lit
|
|
54
58
|
ctx.fillStyle = multiRowHoverColor
|
|
@@ -2,8 +2,14 @@ import { contrastTextFn } from '../../util.ts'
|
|
|
2
2
|
import { getVisibleLeaves } from '../getVisibleLeaves.ts'
|
|
3
3
|
import { drawFeatureSpans, spanRows } from '../msa/drawFeatureSpans.ts'
|
|
4
4
|
|
|
5
|
+
import type { HierarchyNode } from '../../hierarchy.ts'
|
|
5
6
|
import type { MsaViewModel } from '../../model.ts'
|
|
6
|
-
import type {
|
|
7
|
+
import type {
|
|
8
|
+
NodeWithIdsAndLength,
|
|
9
|
+
ResolvedFeaturePanel,
|
|
10
|
+
RowPanelSpan,
|
|
11
|
+
} from '../../types.ts'
|
|
12
|
+
import type { SpanSource } from '../msa/drawFeatureSpans.ts'
|
|
7
13
|
import type { RenderCtx } from '../renderCtx.ts'
|
|
8
14
|
import type { Theme } from '@mui/material'
|
|
9
15
|
|
|
@@ -46,10 +52,26 @@ export function renderFeaturePanel({
|
|
|
46
52
|
|
|
47
53
|
drawFeatureSpans<RowPanelSpan>({
|
|
48
54
|
ctx,
|
|
49
|
-
|
|
55
|
+
...featurePanelSource(
|
|
56
|
+
model,
|
|
57
|
+
panel,
|
|
50
58
|
getVisibleLeaves({ model, offsetY, blockSizeY: by }),
|
|
51
|
-
panel.spans,
|
|
52
59
|
),
|
|
60
|
+
contrastText: contrastTextFn(theme),
|
|
61
|
+
xMin: 0,
|
|
62
|
+
xMax: panel.width,
|
|
63
|
+
})
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** a features panel's spans over `leaves`, in panel pixels, a lane apiece */
|
|
67
|
+
export function featurePanelSource(
|
|
68
|
+
model: MsaViewModel,
|
|
69
|
+
panel: ResolvedFeaturePanel,
|
|
70
|
+
leaves: HierarchyNode<NodeWithIdsAndLength>[],
|
|
71
|
+
): SpanSource<RowPanelSpan> {
|
|
72
|
+
const { rowHeight } = model
|
|
73
|
+
return {
|
|
74
|
+
rows: spanRows(leaves, panel.spans),
|
|
53
75
|
layout: {
|
|
54
76
|
height: laneCount => Math.max(1, rowHeight / laneCount - rowPadding),
|
|
55
77
|
top: (y, lane, h) =>
|
|
@@ -59,8 +81,5 @@ export function renderFeaturePanel({
|
|
|
59
81
|
xOf: span => [span.xStart, span.xEnd],
|
|
60
82
|
colors: panel.colors,
|
|
61
83
|
labelOf: span => panel.labels?.get(span.annotation),
|
|
62
|
-
|
|
63
|
-
xMin: 0,
|
|
64
|
-
xMax: panel.width,
|
|
65
|
-
})
|
|
84
|
+
}
|
|
66
85
|
}
|
|
@@ -9,7 +9,7 @@ import type { Arc, BasicTrack } from '../../types.ts'
|
|
|
9
9
|
import type { RenderCtx } from '../renderCtx.ts'
|
|
10
10
|
import type { Theme } from '@mui/material'
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
function drawConservationBars({
|
|
13
13
|
ctx,
|
|
14
14
|
values,
|
|
15
15
|
color,
|
|
@@ -55,7 +55,7 @@ export function barTrackValues(model: MsaViewModel, trackId: string) {
|
|
|
55
55
|
|
|
56
56
|
// An arc's apex scales with the square root of its span. Contact-map spans skew
|
|
57
57
|
// short, and a linear scale flattens them along the baseline.
|
|
58
|
-
|
|
58
|
+
function drawArcs({
|
|
59
59
|
ctx,
|
|
60
60
|
arcs,
|
|
61
61
|
color,
|
|
@@ -103,7 +103,7 @@ export function drawArcs({
|
|
|
103
103
|
}
|
|
104
104
|
}
|
|
105
105
|
|
|
106
|
-
|
|
106
|
+
function drawTextTrackContent({
|
|
107
107
|
ctx,
|
|
108
108
|
data,
|
|
109
109
|
colorScheme,
|
|
@@ -176,7 +176,7 @@ export function rulerCols(xStart: number, end: number, colWidth: number) {
|
|
|
176
176
|
const RULER_FONT_SIZE = 10
|
|
177
177
|
const TICK_HEIGHT = 4
|
|
178
178
|
|
|
179
|
-
|
|
179
|
+
function drawColumnRuler({
|
|
180
180
|
ctx,
|
|
181
181
|
numColumns,
|
|
182
182
|
label,
|
|
@@ -229,7 +229,7 @@ export function drawColumnRuler({
|
|
|
229
229
|
const CAP_HEIGHT_RATIO = 0.72
|
|
230
230
|
const MIN_LETTER_PX = 3
|
|
231
231
|
|
|
232
|
-
|
|
232
|
+
function drawSequenceLogo({
|
|
233
233
|
ctx,
|
|
234
234
|
colStats,
|
|
235
235
|
colorScheme,
|