react-msaview 6.4.2 → 7.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/README.md +6 -5
- package/bundle/index.js +100 -101
- package/bundle/index.js.map +4 -4
- package/dist/components/Loading.js +2 -2
- package/dist/components/Loading.js.map +1 -1
- package/dist/components/MSAViewer.d.ts +10 -2
- package/dist/components/MSAViewer.js +90 -2
- package/dist/components/MSAViewer.js.map +1 -1
- package/dist/components/ResizeHandles.d.ts +3 -3
- package/dist/components/ResizeHandles.js +28 -21
- package/dist/components/ResizeHandles.js.map +1 -1
- package/dist/components/dialogs/{InterProScanDialog.d.ts → AnnotationFileDialog.d.ts} +2 -2
- package/dist/components/dialogs/AnnotationFileDialog.js +92 -0
- package/dist/components/dialogs/AnnotationFileDialog.js.map +1 -0
- package/dist/components/dialogs/FeatureDialog.js +13 -9
- package/dist/components/dialogs/FeatureDialog.js.map +1 -1
- package/dist/components/header/Header.js +4 -0
- package/dist/components/header/Header.js.map +1 -1
- package/dist/components/header/HeaderStatusArea.js +0 -2
- package/dist/components/header/HeaderStatusArea.js.map +1 -1
- package/dist/components/header/LoadWarnings.d.ts +6 -0
- package/dist/components/header/LoadWarnings.js +36 -0
- package/dist/components/header/LoadWarnings.js.map +1 -0
- package/dist/components/header/MultiAlignmentSelector.js +0 -0
- package/dist/components/header/MultiAlignmentSelector.js.map +1 -1
- package/dist/components/header/TreeSettingsMenu.js +9 -1
- package/dist/components/header/TreeSettingsMenu.js.map +1 -1
- package/dist/components/header/UnshareableDataWarning.d.ts +6 -0
- package/dist/components/header/UnshareableDataWarning.js +42 -0
- package/dist/components/header/UnshareableDataWarning.js.map +1 -0
- package/dist/components/header/getAnnotationsMenu.js +14 -14
- package/dist/components/header/getAnnotationsMenu.js.map +1 -1
- package/dist/components/import/ImportForm.js +1 -1
- package/dist/components/import/ImportForm.js.map +1 -1
- package/dist/components/import/ImportFormExamples.js +2 -6
- package/dist/components/import/ImportFormExamples.js.map +1 -1
- package/dist/components/import/util.js +3 -0
- package/dist/components/import/util.js.map +1 -1
- package/dist/components/msa/MSACanvasBlock.js +4 -5
- package/dist/components/msa/MSACanvasBlock.js.map +1 -1
- package/dist/components/msa/msaRaster.d.ts +13 -3
- package/dist/components/msa/msaRaster.js +173 -61
- package/dist/components/msa/msaRaster.js.map +1 -1
- package/dist/components/msa/renderHighlights.d.ts +39 -1
- package/dist/components/msa/renderHighlights.js +71 -6
- package/dist/components/msa/renderHighlights.js.map +1 -1
- package/dist/components/msa/renderMSABlock.d.ts +1 -2
- package/dist/components/msa/renderMSABlock.js +24 -28
- package/dist/components/msa/renderMSABlock.js.map +1 -1
- package/dist/components/msa/renderMSAMouseover.js +9 -30
- package/dist/components/msa/renderMSAMouseover.js.map +1 -1
- package/dist/components/renderCtx.d.ts +1 -1
- package/dist/components/tracks/TrackBlocks.js +10 -11
- package/dist/components/tracks/TrackBlocks.js.map +1 -1
- package/dist/components/tracks/drawTracks.d.ts +26 -8
- package/dist/components/tracks/drawTracks.js +123 -13
- package/dist/components/tracks/drawTracks.js.map +1 -1
- package/dist/components/tree/TreeCanvas.js +2 -3
- package/dist/components/tree/TreeCanvas.js.map +1 -1
- package/dist/components/tree/TreeCanvasBlock.js +4 -2
- package/dist/components/tree/TreeCanvasBlock.js.map +1 -1
- package/dist/components/tree/TreeRuler.js +13 -3
- package/dist/components/tree/TreeRuler.js.map +1 -1
- package/dist/components/tree/renderTreeCanvas.d.ts +0 -1
- package/dist/components/tree/renderTreeCanvas.js +70 -30
- package/dist/components/tree/renderTreeCanvas.js.map +1 -1
- package/dist/components/tree/scaleBar.d.ts +10 -0
- package/dist/components/tree/scaleBar.js +24 -0
- package/dist/components/tree/scaleBar.js.map +1 -0
- package/dist/constants.d.ts +3 -1
- package/dist/constants.js +18 -1
- package/dist/constants.js.map +1 -1
- package/dist/fetchUtils.d.ts +7 -7
- package/dist/fetchUtils.js +4 -32
- package/dist/fetchUtils.js.map +1 -1
- package/dist/hierarchy.d.ts +2 -1
- package/dist/hierarchy.js +16 -10
- package/dist/hierarchy.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +8 -2
- package/dist/index.js.map +1 -1
- package/dist/model/DataModel.js +5 -2
- package/dist/model/DataModel.js.map +1 -1
- package/dist/model.d.ts +454 -177
- package/dist/model.js +1180 -511
- package/dist/model.js.map +1 -1
- package/dist/renderToStaticMarkup.d.ts +11 -0
- package/dist/renderToStaticMarkup.js +30 -7
- package/dist/renderToStaticMarkup.js.map +1 -1
- package/dist/renderToSvg.js +25 -19
- package/dist/renderToSvg.js.map +1 -1
- package/dist/rowCoordinateCalculations.d.ts +25 -25
- package/dist/rowCoordinateCalculations.js +12 -36
- package/dist/rowCoordinateCalculations.js.map +1 -1
- package/dist/seqPosToGlobalCol.js +3 -1
- package/dist/seqPosToGlobalCol.js.map +1 -1
- package/dist/svgTestUtil.d.ts +144 -82
- package/dist/types.d.ts +97 -3
- package/dist/util.d.ts +11 -4
- package/dist/util.js +30 -10
- package/dist/util.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/wuss.d.ts +14 -0
- package/dist/wuss.js +55 -0
- package/dist/wuss.js.map +1 -0
- package/package.json +10 -8
- package/src/components/Loading.tsx +12 -2
- package/src/components/MSAViewer.tsx +106 -2
- package/src/components/ResizeHandles.tsx +31 -31
- package/src/components/dialogs/AnnotationFileDialog.tsx +162 -0
- package/src/components/dialogs/FeatureDialog.tsx +13 -9
- package/src/components/header/Header.tsx +4 -0
- package/src/components/header/HeaderStatusArea.tsx +0 -5
- package/src/components/header/LoadWarnings.tsx +49 -0
- package/src/components/header/MultiAlignmentSelector.tsx +0 -0
- package/src/components/header/TreeSettingsMenu.tsx +9 -1
- package/src/components/header/UnshareableDataWarning.tsx +58 -0
- package/src/components/header/getAnnotationsMenu.ts +16 -17
- package/src/components/import/ImportForm.tsx +2 -2
- package/src/components/import/ImportFormExamples.tsx +2 -6
- package/src/components/import/util.ts +3 -0
- package/src/components/msa/MSACanvasBlock.tsx +4 -5
- package/src/components/msa/msaRaster.ts +216 -59
- package/src/components/msa/renderHighlights.ts +103 -5
- package/src/components/msa/renderMSABlock.ts +25 -36
- package/src/components/msa/renderMSAMouseover.ts +9 -39
- package/src/components/renderCtx.ts +1 -0
- package/src/components/tracks/TrackBlocks.tsx +10 -13
- package/src/components/tracks/drawTracks.ts +165 -17
- package/src/components/tree/TreeCanvas.tsx +2 -3
- package/src/components/tree/TreeCanvasBlock.tsx +4 -2
- package/src/components/tree/TreeRuler.tsx +38 -3
- package/src/components/tree/renderTreeCanvas.ts +79 -35
- package/src/components/tree/scaleBar.ts +23 -0
- package/src/constants.ts +20 -1
- package/src/fetchUtils.ts +7 -40
- package/src/hierarchy.ts +20 -11
- package/src/index.ts +18 -2
- package/src/model/DataModel.ts +6 -2
- package/src/model.ts +1352 -616
- package/src/rawImports.d.ts +9 -0
- package/src/renderToStaticMarkup.ts +34 -7
- package/src/renderToSvg.tsx +33 -27
- package/src/rowCoordinateCalculations.ts +15 -59
- package/src/seqPosToGlobalCol.ts +3 -1
- package/src/types.ts +131 -4
- package/src/util.ts +31 -14
- package/src/version.ts +1 -1
- package/src/wuss.ts +62 -0
- package/dist/components/dialogs/InterProScanDialog.js +0 -137
- package/dist/components/dialogs/InterProScanDialog.js.map +0 -1
- package/dist/components/dialogs/InterProScanFileDialog.d.ts +0 -7
- package/dist/components/dialogs/InterProScanFileDialog.js +0 -64
- package/dist/components/dialogs/InterProScanFileDialog.js.map +0 -1
- package/dist/launchInterProScan.d.ts +0 -13
- package/dist/launchInterProScan.js +0 -110
- package/dist/launchInterProScan.js.map +0 -1
- package/dist/useColorContrast.d.ts +0 -10
- package/dist/useColorContrast.js +0 -13
- package/dist/useColorContrast.js.map +0 -1
- package/src/clustalX.test.ts +0 -74
- package/src/colSpace.test.ts +0 -67
- package/src/collapseLogic.test.ts +0 -115
- package/src/colorSchemes.docs.test.ts +0 -57
- package/src/columnCounts.test.ts +0 -73
- package/src/columnStats.test.ts +0 -94
- package/src/columnTracks.test.ts +0 -97
- package/src/components/dialogs/InterProScanDialog.tsx +0 -214
- package/src/components/dialogs/InterProScanFileDialog.tsx +0 -138
- package/src/components/minimap/minimapLayout.test.ts +0 -71
- package/src/components/msa/msaRaster.test.ts +0 -145
- package/src/components/msa/renderBoxFeatureCanvasBlock.test.ts +0 -51
- package/src/components/tree/renderTreeCanvas.test.ts +0 -296
- package/src/dataInitialized.test.ts +0 -42
- package/src/domainBands.test.ts +0 -122
- package/src/domainLetterColor.test.ts +0 -80
- package/src/exportFileName.test.ts +0 -40
- package/src/fetchUtils.test.ts +0 -83
- package/src/flatToTree.test.ts +0 -51
- package/src/hierarchy.test.ts +0 -228
- package/src/highlights.test.ts +0 -70
- package/src/impgRender.test.tsx +0 -68
- package/src/largeTree.test.ts +0 -67
- package/src/launchInterProScan.ts +0 -167
- package/src/loadErrorDisplay.test.tsx +0 -102
- package/src/modelFilehandleLoaders.test.ts +0 -137
- package/src/modelReset.test.ts +0 -120
- package/src/neighborJoining.test.ts +0 -164
- package/src/parseAsn1.test.ts +0 -36
- package/src/propertyConservation.test.ts +0 -36
- package/src/renderColumnTracksSvg.test.tsx +0 -108
- package/src/renderDomainsSvg.test.tsx +0 -118
- package/src/renderHighlightsSvg.test.tsx +0 -85
- package/src/renderMinimapSvg.test.tsx +0 -62
- package/src/renderRasterSvg.test.tsx +0 -144
- package/src/renderSequenceLogoSvg.test.tsx +0 -165
- package/src/renderToSvg.test.tsx +0 -65
- package/src/reset.test.ts +0 -61
- package/src/rowCoordinateCalculations.test.ts +0 -211
- package/src/rowSpace.test.ts +0 -46
- package/src/segmentOverlay.test.ts +0 -70
- package/src/seqPosToGlobalCol.test.ts +0 -59
- package/src/sequenceLogo.test.ts +0 -88
- package/src/setFontSize.test.ts +0 -33
- package/src/stripDefault.test.ts +0 -48
- package/src/stripDefaultSnapshot.test.ts +0 -87
- package/src/treeMetadata.test.ts +0 -39
- package/src/useCanvasAutorun.test.tsx +0 -98
- package/src/useColorContrast.ts +0 -18
- package/src/viewChrome.test.tsx +0 -134
- package/src/wheelZoom.test.ts +0 -46
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
// Vite resolves a `?raw` import to the file's text. The lib does not build with
|
|
2
|
+
// Vite, but realResidueMapping.test.ts reads the examples package's alignment
|
|
3
|
+
// files that way (they are files, not string constants), so tsc has to be told
|
|
4
|
+
// what the suffix means. tsconfig.build.json excludes this, and tsc emits
|
|
5
|
+
// nothing for a .d.ts anyway, so it stays out of the published types.
|
|
6
|
+
declare module '*?raw' {
|
|
7
|
+
const content: string
|
|
8
|
+
export default content
|
|
9
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { colord } from 'colord'
|
|
1
2
|
import { flushSync } from 'react-dom'
|
|
2
3
|
import { createRoot } from 'react-dom/client'
|
|
3
4
|
|
|
@@ -37,11 +38,37 @@ export function renderToStaticMarkup(node: React.ReactElement) {
|
|
|
37
38
|
// export ever taken. The markup is a string by here and needs no DOM.
|
|
38
39
|
root.unmount()
|
|
39
40
|
}
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
41
|
+
return html
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const tag = /<[a-zA-Z][^>]*>/g
|
|
45
|
+
const colorAttr = /\b(fill|stroke|stop-color)="([^"]+)"/g
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Rewrites every color an SVG 1.1 presentation attribute cannot hold.
|
|
49
|
+
*
|
|
50
|
+
* `fill`, `stroke` and `stop-color` take a <color>, which is a keyword, a hex
|
|
51
|
+
* triplet or rgb() -- not rgba(), which MUI palette values are full of, and not
|
|
52
|
+
* hsl(), which the percent-identity scheme colors with. Illustrator and older
|
|
53
|
+
* Inkscape drop an element whose fill they cannot parse, so the minimap thumb
|
|
54
|
+
* came out opaque and the identity coloring came out missing. Alpha moves to
|
|
55
|
+
* the matching -opacity attribute, unless the element already carries one.
|
|
56
|
+
*/
|
|
57
|
+
export function svgSafeColors(html: string) {
|
|
58
|
+
return html.replaceAll(tag, el =>
|
|
59
|
+
el.replaceAll(colorAttr, (match, attr: string, value: string) => {
|
|
60
|
+
if (!/^(rgba|hsl)/i.test(value)) {
|
|
61
|
+
return match
|
|
62
|
+
}
|
|
63
|
+
const color = colord(value)
|
|
64
|
+
if (!color.isValid()) {
|
|
65
|
+
return match
|
|
66
|
+
}
|
|
67
|
+
const alpha = color.alpha()
|
|
68
|
+
const hex = color.alpha(1).toHex()
|
|
69
|
+
return alpha === 1 || el.includes(`${attr}-opacity=`)
|
|
70
|
+
? `${attr}="${hex}"`
|
|
71
|
+
: `${attr}="${hex}" ${attr}-opacity="${alpha}"`
|
|
72
|
+
}),
|
|
73
|
+
)
|
|
47
74
|
}
|
package/src/renderToSvg.tsx
CHANGED
|
@@ -7,13 +7,12 @@ import { visibleRowRange } from './components/getVisibleLeaves.ts'
|
|
|
7
7
|
import MinimapSVG from './components/minimap/MinimapSVG.tsx'
|
|
8
8
|
import { rasterImageHref, rasterSupported } from './components/msa/msaRaster.ts'
|
|
9
9
|
import { renderBoxFeatureCanvasBlock } from './components/msa/renderBoxFeatureCanvasBlock.ts'
|
|
10
|
-
import {
|
|
10
|
+
import { renderPersistentHighlights } from './components/msa/renderHighlights.ts'
|
|
11
11
|
import { renderMSABlock } from './components/msa/renderMSABlock.ts'
|
|
12
12
|
import { visibleColRange } from './components/msa/visibleColRange.ts'
|
|
13
13
|
import { renderAllTracks } from './components/tracks/drawTracks.ts'
|
|
14
14
|
import { renderTreeCanvas } from './components/tree/renderTreeCanvas.ts'
|
|
15
|
-
import { renderToStaticMarkup } from './renderToStaticMarkup.ts'
|
|
16
|
-
import { colorContrast } from './util.ts'
|
|
15
|
+
import { renderToStaticMarkup, svgSafeColors } from './renderToStaticMarkup.ts'
|
|
17
16
|
|
|
18
17
|
import type { MsaViewModel } from './model.ts'
|
|
19
18
|
import type { Context as ContextType } from '@jbrowse/svgcanvas'
|
|
@@ -131,14 +130,26 @@ export async function renderToSvg(model: MsaViewModel, opts: ExportSvgOptions) {
|
|
|
131
130
|
throw model.error
|
|
132
131
|
}
|
|
133
132
|
const { Context } = await import('@jbrowse/svgcanvas')
|
|
134
|
-
|
|
133
|
+
// Each svgcanvas layer is already serialized SVG, and React would have to
|
|
134
|
+
// parse it into a second DOM only for the export to serialize it back out.
|
|
135
|
+
// The layers register themselves here instead, and their markup is spliced
|
|
136
|
+
// into the page as strings.
|
|
137
|
+
const layers = new Map<string, ContextType>()
|
|
138
|
+
const markup = renderToStaticMarkup(
|
|
135
139
|
<MsaSvg
|
|
136
140
|
model={model}
|
|
137
141
|
theme={opts.theme}
|
|
138
142
|
Context={Context}
|
|
139
143
|
layout={getLayout(model, opts)}
|
|
144
|
+
layers={layers}
|
|
140
145
|
/>,
|
|
141
146
|
)
|
|
147
|
+
return svgSafeColors(
|
|
148
|
+
markup.replaceAll(/<g data-layer="([^"]+)"><\/g>/g, (match, id: string) => {
|
|
149
|
+
const ctx = layers.get(id)
|
|
150
|
+
return ctx ? `<g>${ctx.getSvg().innerHTML}</g>` : match
|
|
151
|
+
}),
|
|
152
|
+
)
|
|
142
153
|
}
|
|
143
154
|
|
|
144
155
|
function MsaSvg({
|
|
@@ -146,11 +157,13 @@ function MsaSvg({
|
|
|
146
157
|
theme,
|
|
147
158
|
Context,
|
|
148
159
|
layout,
|
|
160
|
+
layers,
|
|
149
161
|
}: {
|
|
150
162
|
model: MsaViewModel
|
|
151
163
|
theme: Theme
|
|
152
164
|
Context: typeof ContextType
|
|
153
165
|
layout: Layout
|
|
166
|
+
layers: LayerMap
|
|
154
167
|
}) {
|
|
155
168
|
const { width, trackHeight, includeMinimap, legendWidth } = layout
|
|
156
169
|
const { treeAreaWidth, minimapHeight, visibleDomainTypes } = model
|
|
@@ -162,8 +175,7 @@ function MsaSvg({
|
|
|
162
175
|
legendWidth > 0
|
|
163
176
|
? Math.max(layout.height, legendTop + legendHeight(visibleDomainTypes))
|
|
164
177
|
: layout.height
|
|
165
|
-
const
|
|
166
|
-
const props = { Context, model, theme, layout, contrastScheme }
|
|
178
|
+
const props = { Context, model, theme, layout, layers }
|
|
167
179
|
|
|
168
180
|
const body = (
|
|
169
181
|
<>
|
|
@@ -269,21 +281,17 @@ function LegendSVG({
|
|
|
269
281
|
)
|
|
270
282
|
}
|
|
271
283
|
|
|
284
|
+
type LayerMap = Map<string, ContextType>
|
|
285
|
+
|
|
272
286
|
interface LayerProps {
|
|
273
287
|
model: MsaViewModel
|
|
274
288
|
theme: Theme
|
|
275
289
|
layout: Layout
|
|
276
290
|
Context: typeof ContextType
|
|
277
|
-
|
|
291
|
+
layers: LayerMap
|
|
278
292
|
}
|
|
279
293
|
|
|
280
|
-
function CoreRendering({
|
|
281
|
-
model,
|
|
282
|
-
theme,
|
|
283
|
-
layout,
|
|
284
|
-
Context,
|
|
285
|
-
contrastScheme,
|
|
286
|
-
}: LayerProps) {
|
|
294
|
+
function CoreRendering({ model, theme, layout, Context, layers }: LayerProps) {
|
|
287
295
|
const { contentHeight, offsetX, offsetY, msaAreaWidth } = layout
|
|
288
296
|
const { treeAreaWidth, id } = model
|
|
289
297
|
|
|
@@ -315,7 +323,6 @@ function CoreRendering({
|
|
|
315
323
|
model,
|
|
316
324
|
theme,
|
|
317
325
|
ctx: msaCtx,
|
|
318
|
-
contrastScheme,
|
|
319
326
|
offsetX,
|
|
320
327
|
offsetY,
|
|
321
328
|
blockSizeXOverride: msaAreaWidth,
|
|
@@ -324,7 +331,7 @@ function CoreRendering({
|
|
|
324
331
|
rasterTiles: !!raster,
|
|
325
332
|
})
|
|
326
333
|
msaCtx.resetTransform()
|
|
327
|
-
|
|
334
|
+
renderPersistentHighlights({
|
|
328
335
|
ctx: msaCtx,
|
|
329
336
|
model,
|
|
330
337
|
theme,
|
|
@@ -341,6 +348,7 @@ function CoreRendering({
|
|
|
341
348
|
width={treeAreaWidth}
|
|
342
349
|
height={contentHeight}
|
|
343
350
|
ctx={treeCtx}
|
|
351
|
+
layers={layers}
|
|
344
352
|
/>
|
|
345
353
|
<ClipGroup
|
|
346
354
|
clipId={`msa-${id}`}
|
|
@@ -348,6 +356,7 @@ function CoreRendering({
|
|
|
348
356
|
height={contentHeight}
|
|
349
357
|
transform={`translate(${treeAreaWidth} 0)`}
|
|
350
358
|
ctx={msaCtx}
|
|
359
|
+
layers={layers}
|
|
351
360
|
underlay={raster}
|
|
352
361
|
/>
|
|
353
362
|
</>
|
|
@@ -412,13 +421,7 @@ function rasterBackground({
|
|
|
412
421
|
) : undefined
|
|
413
422
|
}
|
|
414
423
|
|
|
415
|
-
function TrackRendering({
|
|
416
|
-
model,
|
|
417
|
-
theme,
|
|
418
|
-
layout,
|
|
419
|
-
Context,
|
|
420
|
-
contrastScheme,
|
|
421
|
-
}: LayerProps) {
|
|
424
|
+
function TrackRendering({ model, theme, layout, Context, layers }: LayerProps) {
|
|
422
425
|
const { trackHeight, offsetX, msaAreaWidth } = layout
|
|
423
426
|
const { treeAreaWidth, id } = model
|
|
424
427
|
|
|
@@ -426,7 +429,6 @@ function TrackRendering({
|
|
|
426
429
|
renderAllTracks({
|
|
427
430
|
model,
|
|
428
431
|
ctx,
|
|
429
|
-
contrastScheme,
|
|
430
432
|
theme,
|
|
431
433
|
offsetX,
|
|
432
434
|
blockSizeXOverride: msaAreaWidth,
|
|
@@ -442,6 +444,7 @@ function TrackRendering({
|
|
|
442
444
|
width={msaAreaWidth}
|
|
443
445
|
height={trackHeight}
|
|
444
446
|
ctx={ctx}
|
|
447
|
+
layers={layers}
|
|
445
448
|
/>
|
|
446
449
|
</g>
|
|
447
450
|
</>
|
|
@@ -487,8 +490,8 @@ function TrackLabelsSVG({
|
|
|
487
490
|
)
|
|
488
491
|
}
|
|
489
492
|
|
|
490
|
-
// Clips a svgcanvas Context to its box and
|
|
491
|
-
//
|
|
493
|
+
// Clips a svgcanvas Context to its box and leaves a placeholder for renderToSvg
|
|
494
|
+
// to splice the layer's own markup into. `underlay` draws beneath that markup,
|
|
492
495
|
// inside the same clip, for the parts of a layer React emits directly.
|
|
493
496
|
function ClipGroup({
|
|
494
497
|
clipId,
|
|
@@ -496,6 +499,7 @@ function ClipGroup({
|
|
|
496
499
|
height,
|
|
497
500
|
transform,
|
|
498
501
|
ctx,
|
|
502
|
+
layers,
|
|
499
503
|
underlay,
|
|
500
504
|
}: {
|
|
501
505
|
clipId: string
|
|
@@ -503,8 +507,10 @@ function ClipGroup({
|
|
|
503
507
|
height: number
|
|
504
508
|
transform?: string
|
|
505
509
|
ctx: ContextType
|
|
510
|
+
layers: LayerMap
|
|
506
511
|
underlay?: React.ReactNode
|
|
507
512
|
}) {
|
|
513
|
+
layers.set(clipId, ctx)
|
|
508
514
|
return (
|
|
509
515
|
<>
|
|
510
516
|
<defs>
|
|
@@ -514,7 +520,7 @@ function ClipGroup({
|
|
|
514
520
|
</defs>
|
|
515
521
|
<g clipPath={`url(#${clipId})`} transform={transform}>
|
|
516
522
|
{underlay}
|
|
517
|
-
<g
|
|
523
|
+
<g data-layer={clipId} />
|
|
518
524
|
</g>
|
|
519
525
|
</>
|
|
520
526
|
)
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import { isBlank } from './util.ts'
|
|
2
|
-
|
|
3
1
|
/**
|
|
4
2
|
* MSA Coordinate Systems:
|
|
5
3
|
*
|
|
@@ -93,65 +91,23 @@ export function visibleColsBefore(blanks: number[], globalCol: number) {
|
|
|
93
91
|
}
|
|
94
92
|
|
|
95
93
|
/**
|
|
96
|
-
*
|
|
97
|
-
*
|
|
94
|
+
* The ungapped position a row holds at a global column, or undefined when that
|
|
95
|
+
* column is a gap in the row or past its end.
|
|
98
96
|
*
|
|
99
|
-
*
|
|
100
|
-
*
|
|
101
|
-
*
|
|
102
|
-
*
|
|
97
|
+
* A binary search of the row's seqPos index (ascending), not a scan of the row:
|
|
98
|
+
* this answers on every mouse move, and scanning a 30k-column row per event is
|
|
99
|
+
* the whole frame.
|
|
100
|
+
*
|
|
101
|
+
* @param index - The row's seqPos -> global column index (buildSeqPosIndex)
|
|
102
|
+
* @param globalCol - The global column index in the full MSA
|
|
103
103
|
*/
|
|
104
|
-
export function
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
seq: string
|
|
110
|
-
blanks: number[]
|
|
111
|
-
visibleCol: number
|
|
112
|
-
}) {
|
|
113
|
-
// First convert the visible column to global column
|
|
114
|
-
const globalCol = visibleColToGlobalCol(blanks, visibleCol)
|
|
115
|
-
const seqLen = seq.length
|
|
116
|
-
|
|
117
|
-
// Check if the position in the sequence is a gap
|
|
118
|
-
if (globalCol < seqLen && isBlank(seq[globalCol])) {
|
|
104
|
+
export function seqPosOfGlobalCol(
|
|
105
|
+
index: Int32Array | undefined,
|
|
106
|
+
globalCol: number,
|
|
107
|
+
) {
|
|
108
|
+
if (!index) {
|
|
119
109
|
return undefined
|
|
120
110
|
}
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
let seqPos = 0
|
|
124
|
-
for (let i = 0; i < globalCol && i < seqLen; i++) {
|
|
125
|
-
if (!isBlank(seq[i])) {
|
|
126
|
-
seqPos++
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
return globalCol < seqLen ? seqPos : undefined
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
/**
|
|
134
|
-
* Convert a visible column to a row-specific sequence position, with row lookup.
|
|
135
|
-
*
|
|
136
|
-
* @param rowName - The name of the row
|
|
137
|
-
* @param visibleCol - The visible column index
|
|
138
|
-
* @param rowMap - Map from row name to sequence string
|
|
139
|
-
* @param blanks - Sorted array of global column indices that are hidden
|
|
140
|
-
* @returns The sequence position, or undefined if row not found or position is a gap
|
|
141
|
-
*/
|
|
142
|
-
export function visibleColToSeqPosForRow({
|
|
143
|
-
rowName,
|
|
144
|
-
visibleCol,
|
|
145
|
-
rowMap,
|
|
146
|
-
blanks,
|
|
147
|
-
}: {
|
|
148
|
-
rowName: string
|
|
149
|
-
visibleCol: number
|
|
150
|
-
rowMap: Map<string, string>
|
|
151
|
-
blanks: number[]
|
|
152
|
-
}) {
|
|
153
|
-
const seq = rowMap.get(rowName)
|
|
154
|
-
return seq !== undefined
|
|
155
|
-
? visibleColToSeqPos({ seq, visibleCol, blanks })
|
|
156
|
-
: undefined
|
|
111
|
+
const seqPos = partitionPoint(index.length, i => index[i]! >= globalCol)
|
|
112
|
+
return index[seqPos] === globalCol ? seqPos : undefined
|
|
157
113
|
}
|
package/src/seqPosToGlobalCol.ts
CHANGED
|
@@ -19,5 +19,7 @@ export function buildSeqPosIndex(row: string) {
|
|
|
19
19
|
index[n++] = col
|
|
20
20
|
}
|
|
21
21
|
}
|
|
22
|
-
|
|
22
|
+
// slice, not subarray: a subarray keeps the whole row-length buffer alive,
|
|
23
|
+
// and a gappy alignment's indexes are held for as long as the parse is
|
|
24
|
+
return index.slice(0, n)
|
|
23
25
|
}
|
package/src/types.ts
CHANGED
|
@@ -8,7 +8,7 @@ export interface Accession {
|
|
|
8
8
|
// which renderer draws a track's content. Every track kind draws into the same
|
|
9
9
|
// per-column coordinate space, so the kind picks the draw function rather than
|
|
10
10
|
// the geometry -- see drawTracks.ts, which dispatches on it.
|
|
11
|
-
export type TrackKind = 'text' | 'bar' | 'logo'
|
|
11
|
+
export type TrackKind = 'text' | 'bar' | 'logo' | 'arc' | 'ruler'
|
|
12
12
|
|
|
13
13
|
export interface BasicTrackModel {
|
|
14
14
|
id: string
|
|
@@ -16,6 +16,8 @@ export interface BasicTrackModel {
|
|
|
16
16
|
associatedRowName?: string
|
|
17
17
|
height: number
|
|
18
18
|
kind: TrackKind
|
|
19
|
+
// hidden until the user asks for it, unless they have already chosen
|
|
20
|
+
defaultOff?: boolean
|
|
19
21
|
}
|
|
20
22
|
|
|
21
23
|
export interface TextTrackModel extends BasicTrackModel {
|
|
@@ -29,20 +31,45 @@ export interface BarTrackModel extends BasicTrackModel {
|
|
|
29
31
|
barColor?: string
|
|
30
32
|
}
|
|
31
33
|
|
|
34
|
+
// a pair of columns joined by an arc, already resolved to 0-based visible
|
|
35
|
+
// columns -- what the renderer consumes
|
|
36
|
+
export interface Arc {
|
|
37
|
+
start: number
|
|
38
|
+
end: number
|
|
39
|
+
color?: string
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// a track that joins pairs of columns with arcs (RNA base pairs, disulfide
|
|
43
|
+
// bonds, residue contacts)
|
|
44
|
+
export interface ArcTrackModel extends BasicTrackModel {
|
|
45
|
+
arcs?: Arc[]
|
|
46
|
+
arcColor?: string
|
|
47
|
+
}
|
|
48
|
+
|
|
32
49
|
export interface BasicTrack {
|
|
33
50
|
ReactComponent: React.FC<any>
|
|
34
|
-
model: TextTrackModel & BarTrackModel
|
|
51
|
+
model: TextTrackModel & BarTrackModel & ArcTrackModel
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// one arc of an arc track, in the snapshot's own coordinates: 1-based and
|
|
55
|
+
// inclusive, alignment columns unless the track names a `row`
|
|
56
|
+
export interface ArcSpec {
|
|
57
|
+
start: number
|
|
58
|
+
end: number
|
|
59
|
+
color?: string
|
|
35
60
|
}
|
|
36
61
|
|
|
37
62
|
// a track supplied as data in the snapshot rather than computed from the
|
|
38
63
|
// alignment. `values` (bar) or `data` (text) index alignment columns, or the
|
|
39
|
-
// 1-based residues of `row` when it names one
|
|
64
|
+
// 1-based residues of `row` when it names one; `arcs` (arc) name two such
|
|
65
|
+
// positions each
|
|
40
66
|
export interface ColumnTrackSpec {
|
|
41
67
|
id: string
|
|
42
68
|
name: string
|
|
43
|
-
kind: 'bar' | 'text'
|
|
69
|
+
kind: 'bar' | 'text' | 'arc'
|
|
44
70
|
values?: number[]
|
|
45
71
|
data?: string
|
|
72
|
+
arcs?: ArcSpec[]
|
|
46
73
|
max?: number
|
|
47
74
|
color?: string
|
|
48
75
|
colors?: Record<string, string>
|
|
@@ -50,6 +77,106 @@ export interface ColumnTrackSpec {
|
|
|
50
77
|
height?: number
|
|
51
78
|
}
|
|
52
79
|
|
|
80
|
+
// One contiguous run where a row's residues and a structure's line up 1:1.
|
|
81
|
+
// Segment-shaped because SIFTS is: a dozen numbers cover what a per-residue
|
|
82
|
+
// array would spend kilobytes on, and it makes the refusal rule structural
|
|
83
|
+
// rather than a vocabulary -- a position no segment covers is unmapped, and
|
|
84
|
+
// there is no status field to disagree with.
|
|
85
|
+
export interface ResidueSegment {
|
|
86
|
+
rowStart: number
|
|
87
|
+
rowEnd: number
|
|
88
|
+
structStart: number
|
|
89
|
+
structEnd: number
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// the structure half of a mapping. `id` is whatever the producer calls the
|
|
93
|
+
// entry (a PDB id, an AlphaFold accession); `asymId` names the chain, which is
|
|
94
|
+
// what distinguishes two mappings onto the same entry
|
|
95
|
+
export interface MappedStructure {
|
|
96
|
+
id: string
|
|
97
|
+
kind?: 'experimental' | 'predicted'
|
|
98
|
+
asymId?: string
|
|
99
|
+
url?: string
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Which residue of which structure a row's residue is, as data. Computed by
|
|
104
|
+
* whatever knows how -- SIFTS, an AlphaFold model, a curator -- and carried in
|
|
105
|
+
* the snapshot, because the viewer cannot work it out: matching a row to a
|
|
106
|
+
* structure by sequence equality fails for a construct with an expression tag,
|
|
107
|
+
* a truncation, an engineered residue, or a row that is a subsequence of the
|
|
108
|
+
* entry, and it fails in the direction that looks like it worked.
|
|
109
|
+
*
|
|
110
|
+
* Positions are 1-based and inclusive on both sides, as GFF and `highlights`
|
|
111
|
+
* are. Structure positions are `label_seq_id`, the index into the entity's
|
|
112
|
+
* SEQRES; author numbering carries insertion codes and stays out.
|
|
113
|
+
*
|
|
114
|
+
* `unobserved` is in structure positions: present in SEQRES with no
|
|
115
|
+
* coordinates. Worth distinguishing from unmapped, because "the
|
|
116
|
+
* crystallographer could not see it" and "this protein does not have that
|
|
117
|
+
* residue" mean different things to a reader.
|
|
118
|
+
*/
|
|
119
|
+
export interface ResidueMapping {
|
|
120
|
+
row: string
|
|
121
|
+
accession?: string
|
|
122
|
+
structure: MappedStructure
|
|
123
|
+
segments: ResidueSegment[]
|
|
124
|
+
unobserved?: [number, number][]
|
|
125
|
+
/**
|
|
126
|
+
* ungapped length of the row this was computed against. Optional, and worth
|
|
127
|
+
* setting: a mapping outlives the alignment it was made for, and pointing at
|
|
128
|
+
* residue 58 of a sequence that has since been re-aligned, revised or swapped
|
|
129
|
+
* is the failure this whole layer exists to avoid. Declared here, the viewer
|
|
130
|
+
* can notice and refuse instead of answering from stale arithmetic.
|
|
131
|
+
*/
|
|
132
|
+
rowLength?: number
|
|
133
|
+
generated?: {
|
|
134
|
+
by?: string
|
|
135
|
+
date?: string
|
|
136
|
+
sourceSha256?: string
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// why a mapping, or one segment of it, is not being used. Refusing silently
|
|
141
|
+
// leaves a host unable to tell "no structure for this row" from "the mapping
|
|
142
|
+
// no longer matches what is loaded", which are different problems with
|
|
143
|
+
// different fixes.
|
|
144
|
+
export interface ResidueMappingProblem {
|
|
145
|
+
row: string
|
|
146
|
+
structureId: string
|
|
147
|
+
/**
|
|
148
|
+
* `mapping` means the whole mapping is out: the evidence is about the row it
|
|
149
|
+
* claims, so nothing it says can be trusted. `segment` takes only that
|
|
150
|
+
* segment, since the rest of the mapping still describes residues that exist.
|
|
151
|
+
*/
|
|
152
|
+
scope: 'mapping' | 'segment'
|
|
153
|
+
reason: string
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// what a lookup gives back: the structure residue a row residue is, or the row
|
|
157
|
+
// residue a structure residue is. `observed` is false for a residue the
|
|
158
|
+
// structure declares but did not resolve.
|
|
159
|
+
export interface StructureResidue {
|
|
160
|
+
structure: MappedStructure
|
|
161
|
+
position: number
|
|
162
|
+
observed: boolean
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
export interface RowResidue {
|
|
166
|
+
rowName: string
|
|
167
|
+
seqPos: number
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* One loaded document that the snapshot leaves behind, and how big it is.
|
|
172
|
+
* `what` names it the way the import form does, since that is where the reader
|
|
173
|
+
* would go to load it differently.
|
|
174
|
+
*/
|
|
175
|
+
export interface UnshareableData {
|
|
176
|
+
what: 'alignment' | 'tree' | 'annotations' | 'row metadata' | 'data tracks'
|
|
177
|
+
bytes: number
|
|
178
|
+
}
|
|
179
|
+
|
|
53
180
|
// the overlay annotation itself lives in msa-parsers, alongside the adapters
|
|
54
181
|
// that build it. TidyDomainAnnotation is its former name, kept because
|
|
55
182
|
// downstream plugins name it in their emitted declarations.
|
package/src/util.ts
CHANGED
|
@@ -36,14 +36,37 @@ export function transform<T>(
|
|
|
36
36
|
return Object.fromEntries(Object.entries(obj).map(cb))
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
39
|
+
const contrastTextCaches = new WeakMap<Theme, Map<string, string>>()
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Readable letter color over a given cell color, memoized per theme.
|
|
43
|
+
*
|
|
44
|
+
* A dynamic scheme colors a cell from its column, so there is no letter->color
|
|
45
|
+
* table to precompute a contrast table from; asking per cell color covers the
|
|
46
|
+
* dynamic schemes, the static ones and a track's own `colors` alike. An
|
|
47
|
+
* uncolored cell sits on the theme's background, where the theme's own text
|
|
48
|
+
* color is what stays readable -- `black` turned every gap and every uncolored
|
|
49
|
+
* cell invisible in the dark theme.
|
|
50
|
+
*/
|
|
51
|
+
export function contrastTextFn(theme: Theme) {
|
|
52
|
+
let cache = contrastTextCaches.get(theme)
|
|
53
|
+
if (!cache) {
|
|
54
|
+
cache = new Map()
|
|
55
|
+
contrastTextCaches.set(theme, cache)
|
|
56
|
+
}
|
|
57
|
+
const fallback = theme.palette.text.primary
|
|
58
|
+
return (color: string | undefined) => {
|
|
59
|
+
if (!color) {
|
|
60
|
+
return fallback
|
|
61
|
+
}
|
|
62
|
+
const hit = cache.get(color)
|
|
63
|
+
if (hit !== undefined) {
|
|
64
|
+
return hit
|
|
65
|
+
}
|
|
66
|
+
const out = theme.palette.getContrastText(colord(color).toHex())
|
|
67
|
+
cache.set(color, out)
|
|
68
|
+
return out
|
|
69
|
+
}
|
|
47
70
|
}
|
|
48
71
|
|
|
49
72
|
export function dropBlanks<T>(blanks: number[], arr: T[]) {
|
|
@@ -110,9 +133,3 @@ export function computeRowInsertions(blanks: number[], seq: string) {
|
|
|
110
133
|
export function len(a: { end: number; start: number }) {
|
|
111
134
|
return a.end - a.start
|
|
112
135
|
}
|
|
113
|
-
|
|
114
|
-
// https://sonnhammer.sbc.su.se/Stockholm.html
|
|
115
|
-
// gaps can be a . or - in stockholm
|
|
116
|
-
export function isBlank(s?: string) {
|
|
117
|
-
return s === '-' || s === '.'
|
|
118
|
-
}
|
package/src/version.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const version = '
|
|
1
|
+
export const version = '7.0.0'
|
package/src/wuss.ts
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
// WUSS notation (Rfam's `#=GC SS_cons`) names a consensus secondary structure
|
|
2
|
+
// by pairing columns. Nested helices use bracket pairs -- <>, (), [], {} --
|
|
3
|
+
// each type kept on its own stack, and a pseudoknot, which by definition
|
|
4
|
+
// crosses a helix it cannot nest inside, uses a letter pair instead: `A` opens
|
|
5
|
+
// and `a` closes, `B`/`b` for a second one. Everything else (. , _ - : ~) is
|
|
6
|
+
// unpaired.
|
|
7
|
+
|
|
8
|
+
const closeToOpen: Record<string, string> = {
|
|
9
|
+
'>': '<',
|
|
10
|
+
')': '(',
|
|
11
|
+
']': '[',
|
|
12
|
+
'}': '{',
|
|
13
|
+
}
|
|
14
|
+
const opens = new Set(Object.values(closeToOpen))
|
|
15
|
+
|
|
16
|
+
export interface BasePair {
|
|
17
|
+
/** 0-based column */
|
|
18
|
+
start: number
|
|
19
|
+
/** 0-based column */
|
|
20
|
+
end: number
|
|
21
|
+
/** the pair crosses a helix rather than nesting inside one */
|
|
22
|
+
pseudoknot: boolean
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* The base pairs a WUSS string encodes, as 0-based column indexes. Unmatched
|
|
27
|
+
* brackets are dropped rather than throwing: a hand-edited or truncated
|
|
28
|
+
* alignment should still draw the helices it does close.
|
|
29
|
+
*/
|
|
30
|
+
export function parseWuss(ss: string): BasePair[] {
|
|
31
|
+
const stacks = new Map<string, number[]>()
|
|
32
|
+
const pairs: BasePair[] = []
|
|
33
|
+
const push = (key: string, col: number) => {
|
|
34
|
+
const stack = stacks.get(key)
|
|
35
|
+
if (stack) {
|
|
36
|
+
stack.push(col)
|
|
37
|
+
} else {
|
|
38
|
+
stacks.set(key, [col])
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
const pop = (key: string, col: number, pseudoknot: boolean) => {
|
|
42
|
+
const start = stacks.get(key)?.pop()
|
|
43
|
+
if (start !== undefined) {
|
|
44
|
+
pairs.push({ start, end: col, pseudoknot })
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
for (let col = 0; col < ss.length; col++) {
|
|
49
|
+
const ch = ss[col]!
|
|
50
|
+
const opener = closeToOpen[ch]
|
|
51
|
+
if (opens.has(ch)) {
|
|
52
|
+
push(ch, col)
|
|
53
|
+
} else if (opener) {
|
|
54
|
+
pop(opener, col, false)
|
|
55
|
+
} else if (ch >= 'A' && ch <= 'Z') {
|
|
56
|
+
push(ch, col)
|
|
57
|
+
} else if (ch >= 'a' && ch <= 'z') {
|
|
58
|
+
pop(ch.toUpperCase(), col, true)
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return pairs.sort((a, b) => a.start - b.start)
|
|
62
|
+
}
|