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
package/src/renderToSvg.tsx
CHANGED
|
@@ -6,7 +6,7 @@ import { when } from 'mobx'
|
|
|
6
6
|
import { visibleRowRange } from './components/getVisibleLeaves.ts'
|
|
7
7
|
import MinimapSVG from './components/minimap/MinimapSVG.tsx'
|
|
8
8
|
import { legendRows } from './components/msa/legendRows.ts'
|
|
9
|
-
import {
|
|
9
|
+
import { rasterApplies, rasterImageHref } from './components/msa/msaRaster.ts'
|
|
10
10
|
import { renderBoxFeatureCanvasBlock } from './components/msa/renderBoxFeatureCanvasBlock.ts'
|
|
11
11
|
import { renderPersistentHighlights } from './components/msa/renderHighlights.ts'
|
|
12
12
|
import { renderMSABlock } from './components/msa/renderMSABlock.ts'
|
|
@@ -15,141 +15,32 @@ import { headerRunsAcross } from './components/rowpanels/headerLayout.ts'
|
|
|
15
15
|
import { renderRowPanels } from './components/rowpanels/renderRowPanel.ts'
|
|
16
16
|
import { renderAllTracks } from './components/tracks/drawTracks.ts'
|
|
17
17
|
import ScaleBarSVG from './components/tree/ScaleBarSVG.tsx'
|
|
18
|
-
import {
|
|
19
|
-
|
|
20
|
-
cladeHeight,
|
|
21
|
-
cladeInGutter,
|
|
22
|
-
cladeLabelHorizontal,
|
|
23
|
-
cladeLabelOffset,
|
|
24
|
-
} from './components/tree/cladeBrackets.ts'
|
|
18
|
+
import { cladeLabelBoxes } from './components/tree/cladeBrackets.ts'
|
|
19
|
+
import { ellipsize } from './components/tree/ellipsize.ts'
|
|
25
20
|
import { renderTreeCanvas } from './components/tree/renderTreeCanvas.ts'
|
|
26
21
|
import { renderTreeOverview } from './components/tree/renderTreeOverview.ts'
|
|
27
22
|
import { treeScaleBarHeight } from './constants.ts'
|
|
23
|
+
import {
|
|
24
|
+
LEGEND_CHAR_W,
|
|
25
|
+
LEGEND_FONT,
|
|
26
|
+
LEGEND_PAD,
|
|
27
|
+
LEGEND_ROW_H,
|
|
28
|
+
LEGEND_SWATCH,
|
|
29
|
+
LEGEND_TEXT_X,
|
|
30
|
+
exportPageSize,
|
|
31
|
+
getLayout,
|
|
32
|
+
legendHeight,
|
|
33
|
+
} from './exportLayout.ts'
|
|
28
34
|
import { measureTextCanvas } from './measureTextCanvas.ts'
|
|
29
35
|
import { renderToStaticMarkup, svgSafeColors } from './renderToStaticMarkup.ts'
|
|
30
36
|
import { outlineColor } from './util.ts'
|
|
31
37
|
|
|
32
|
-
import type {
|
|
38
|
+
import type { ExportSvgOptions, Layout } from './exportLayout.ts'
|
|
33
39
|
import type { MsaViewModel } from './model.ts'
|
|
34
40
|
import type { Context as ContextType } from '@jbrowse/svgcanvas'
|
|
35
41
|
import type { Theme } from '@mui/material'
|
|
36
42
|
|
|
37
|
-
export
|
|
38
|
-
theme: Theme
|
|
39
|
-
includeMinimap?: boolean
|
|
40
|
-
includeTracks?: boolean
|
|
41
|
-
exportType: 'entire' | 'viewport'
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
// legend geometry, shared by the width calculation and the renderer so the
|
|
45
|
-
// reserved column on the right exactly fits the drawn legends
|
|
46
|
-
const LEGEND_PAD = 8
|
|
47
|
-
const LEGEND_ROW_H = 16
|
|
48
|
-
const LEGEND_SWATCH = 12
|
|
49
|
-
const LEGEND_FONT = 12
|
|
50
|
-
const LEGEND_CHAR_W = 7
|
|
51
|
-
|
|
52
|
-
// the width of a character as a fraction of the font size, close enough for a
|
|
53
|
-
// sans-serif average
|
|
54
|
-
const CHAR_WIDTH = 0.6
|
|
55
|
-
|
|
56
|
-
const LEGEND_MAX_W = 360
|
|
57
|
-
const LEGEND_TEXT_X = LEGEND_PAD + LEGEND_SWATCH + 6
|
|
58
|
-
|
|
59
|
-
function getLegendWidth(rows: LegendRow[]) {
|
|
60
|
-
const maxLen = rows.reduce((a, r) => Math.max(a, r.label.length), 0)
|
|
61
|
-
const w = LEGEND_TEXT_X + LEGEND_PAD + maxLen * LEGEND_CHAR_W
|
|
62
|
-
return Math.min(LEGEND_MAX_W, Math.max(120, w))
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
function legendHeight(rows: LegendRow[]) {
|
|
66
|
-
return LEGEND_PAD * 2 + rows.length * LEGEND_ROW_H
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
// resolved sizes/offsets (in svg user units) for the chosen export, shared by
|
|
70
|
-
// every layer; 'entire' renders the whole alignment unscrolled, 'viewport'
|
|
71
|
-
// mirrors the live scroll position
|
|
72
|
-
interface Layout {
|
|
73
|
-
width: number
|
|
74
|
-
msaAreaWidth: number
|
|
75
|
-
height: number
|
|
76
|
-
contentHeight: number
|
|
77
|
-
trackHeight: number
|
|
78
|
-
// the band the minimap, the row panel headers and the tree overview stacked
|
|
79
|
-
// on the scale bar share across the top
|
|
80
|
-
topHeight: number
|
|
81
|
-
offsetX: number
|
|
82
|
-
offsetY: number
|
|
83
|
-
includeMinimap: boolean
|
|
84
|
-
legendWidth: number
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
function getLayout(model: MsaViewModel, opts: ExportSvgOptions): Layout {
|
|
88
|
-
const {
|
|
89
|
-
scrollX,
|
|
90
|
-
scrollY,
|
|
91
|
-
totalWidth,
|
|
92
|
-
totalHeight,
|
|
93
|
-
effectiveTreeAreaWidth,
|
|
94
|
-
rowPanelsWidth,
|
|
95
|
-
rowPanelsHeaderHeight,
|
|
96
|
-
totalTrackAreaHeight,
|
|
97
|
-
minimapHeight,
|
|
98
|
-
msaAreaHeight,
|
|
99
|
-
msaCanvasWidth,
|
|
100
|
-
showHorizontalScrollbar,
|
|
101
|
-
treeOverviewHeight,
|
|
102
|
-
treeScaleBar,
|
|
103
|
-
} = model
|
|
104
|
-
const trackHeight = opts.includeTracks ? totalTrackAreaHeight : 0
|
|
105
|
-
// the minimap reflects the live viewport scroll position, so it's only
|
|
106
|
-
// meaningful for a viewport export, never for the entire alignment -- and
|
|
107
|
-
// only when the live view shows one at all, since a minimap over an alignment
|
|
108
|
-
// that already fits marks a viewport wider than the bar
|
|
109
|
-
const includeMinimap =
|
|
110
|
-
opts.exportType === 'viewport' &&
|
|
111
|
-
!!opts.includeMinimap &&
|
|
112
|
-
showHorizontalScrollbar
|
|
113
|
-
const rows = legendRows(model.legends)
|
|
114
|
-
const legendWidth = rows.length > 0 ? getLegendWidth(rows) : 0
|
|
115
|
-
const topHeight = Math.max(
|
|
116
|
-
includeMinimap ? minimapHeight : 0,
|
|
117
|
-
rowPanelsHeaderHeight,
|
|
118
|
-
treeOverviewHeight + (treeScaleBar ? treeScaleBarHeight : 0),
|
|
119
|
-
)
|
|
120
|
-
const gutterWidth = effectiveTreeAreaWidth + rowPanelsWidth
|
|
121
|
-
|
|
122
|
-
// width stays content-only; the legend occupies an extra column added at the
|
|
123
|
-
// svg root in MsaSvg. The viewport export takes the alignment canvas's own
|
|
124
|
-
// size rather than the widget's -- the widget box also covers the header, the
|
|
125
|
-
// resize handle and the scrollbars, and rendering that box exports rows and
|
|
126
|
-
// columns the scrollbars hide on screen
|
|
127
|
-
return opts.exportType === 'entire'
|
|
128
|
-
? {
|
|
129
|
-
width: totalWidth + gutterWidth,
|
|
130
|
-
msaAreaWidth: totalWidth,
|
|
131
|
-
height: totalHeight + trackHeight + topHeight,
|
|
132
|
-
contentHeight: totalHeight,
|
|
133
|
-
trackHeight,
|
|
134
|
-
topHeight,
|
|
135
|
-
offsetX: 0,
|
|
136
|
-
offsetY: 0,
|
|
137
|
-
includeMinimap,
|
|
138
|
-
legendWidth,
|
|
139
|
-
}
|
|
140
|
-
: {
|
|
141
|
-
width: msaCanvasWidth + gutterWidth,
|
|
142
|
-
msaAreaWidth: msaCanvasWidth,
|
|
143
|
-
height: msaAreaHeight + topHeight + trackHeight,
|
|
144
|
-
contentHeight: msaAreaHeight,
|
|
145
|
-
trackHeight,
|
|
146
|
-
topHeight,
|
|
147
|
-
offsetX: -scrollX,
|
|
148
|
-
offsetY: -scrollY,
|
|
149
|
-
includeMinimap,
|
|
150
|
-
legendWidth,
|
|
151
|
-
}
|
|
152
|
-
}
|
|
43
|
+
export type { ExportSvgOptions } from './exportLayout.ts'
|
|
153
44
|
|
|
154
45
|
export async function renderToSvg(model: MsaViewModel, opts: ExportSvgOptions) {
|
|
155
46
|
// dataInitialized stays false while the model holds a load error, so waiting
|
|
@@ -173,12 +64,13 @@ export async function renderToSvg(model: MsaViewModel, opts: ExportSvgOptions) {
|
|
|
173
64
|
layers={layers}
|
|
174
65
|
/>,
|
|
175
66
|
)
|
|
67
|
+
// innerHTML serializes as HTML, and an XML parser rejects its
|
|
176
68
|
return svgSafeColors(
|
|
177
69
|
markup.replaceAll(/<g data-layer="([^"]+)"><\/g>/g, (match, id: string) => {
|
|
178
70
|
const ctx = layers.get(id)
|
|
179
71
|
return ctx ? `<g id="${id}">${ctx.getSvg().innerHTML}</g>` : match
|
|
180
72
|
}),
|
|
181
|
-
)
|
|
73
|
+
).replaceAll(' ', ' ')
|
|
182
74
|
}
|
|
183
75
|
|
|
184
76
|
function MsaSvg({
|
|
@@ -196,17 +88,8 @@ function MsaSvg({
|
|
|
196
88
|
}) {
|
|
197
89
|
const { width, trackHeight, includeMinimap, legendWidth, topHeight } = layout
|
|
198
90
|
const { effectiveTreeAreaWidth, rowPanelsWidth } = model
|
|
199
|
-
const totalWidth =
|
|
91
|
+
const { width: totalWidth, height } = exportPageSize(model, layout)
|
|
200
92
|
const legendTop = topHeight
|
|
201
|
-
// a short alignment with many legend entries has a key taller than its rows,
|
|
202
|
-
// and the page has to hold the whole key
|
|
203
|
-
const height =
|
|
204
|
-
legendWidth > 0
|
|
205
|
-
? Math.max(
|
|
206
|
-
layout.height,
|
|
207
|
-
legendTop + legendHeight(legendRows(model.legends)),
|
|
208
|
-
)
|
|
209
|
-
: layout.height
|
|
210
93
|
const props = { Context, model, theme, layout, layers }
|
|
211
94
|
|
|
212
95
|
const body = (
|
|
@@ -227,6 +110,9 @@ function MsaSvg({
|
|
|
227
110
|
xmlns="http://www.w3.org/2000/svg"
|
|
228
111
|
xmlnsXlink="http://www.w3.org/1999/xlink"
|
|
229
112
|
viewBox={`0 0 ${totalWidth} ${height}`}
|
|
113
|
+
// the canvas layers name sans-serif on each glyph; the legend, labels
|
|
114
|
+
// and headers inherit it here instead of an editor's default serif
|
|
115
|
+
fontFamily="sans-serif"
|
|
230
116
|
>
|
|
231
117
|
{/* every layer below draws in the theme's colors, so the page has to be
|
|
232
118
|
the theme's background too -- a hardcoded white one turns a dark-theme
|
|
@@ -359,7 +245,6 @@ function RowPanelHeadersSVG({
|
|
|
359
245
|
const center = effectiveTreeAreaWidth + panel.offsetX + panel.width / 2
|
|
360
246
|
const x = across ? center : center + size / 3
|
|
361
247
|
const room = across ? panel.width : y - LEGEND_PAD
|
|
362
|
-
const maxChars = Math.floor(room / (size * CHAR_WIDTH))
|
|
363
248
|
return size < 5 ? null : (
|
|
364
249
|
<text
|
|
365
250
|
key={panel.id}
|
|
@@ -371,9 +256,10 @@ function RowPanelHeadersSVG({
|
|
|
371
256
|
fontSize={size}
|
|
372
257
|
fill={theme.palette.text.primary}
|
|
373
258
|
>
|
|
374
|
-
{
|
|
375
|
-
|
|
376
|
-
|
|
259
|
+
{
|
|
260
|
+
ellipsize(t => measureTextCanvas(t, size), panel.header, room)
|
|
261
|
+
.text
|
|
262
|
+
}
|
|
377
263
|
</text>
|
|
378
264
|
)
|
|
379
265
|
})}
|
|
@@ -381,21 +267,11 @@ function RowPanelHeadersSVG({
|
|
|
381
267
|
)
|
|
382
268
|
}
|
|
383
269
|
|
|
384
|
-
// A label past the room it has, cut to what fits with an ellipsis. The measure
|
|
385
|
-
// is the one cladeGutterWidth sizes the gutter with, so a label the gutter
|
|
386
|
-
// holds keeps every character.
|
|
387
|
-
function clipCladeLabel(label: string, room: number, fontSize: number) {
|
|
388
|
-
const width = measureTextCanvas(label, fontSize)
|
|
389
|
-
if (width <= room) {
|
|
390
|
-
return label
|
|
391
|
-
}
|
|
392
|
-
const fits = Math.floor((label.length * room) / width)
|
|
393
|
-
return `${label.slice(0, Math.max(1, fits - 1))}…`
|
|
394
|
-
}
|
|
395
|
-
|
|
396
270
|
// Each bracket's label in the gutter beside its bar, where the live view puts
|
|
397
271
|
// it (see CladeLabels). The canvas layer draws the bar; a label longer than the
|
|
398
272
|
// gutter or the clade is clipped, since text that overruns runs off the figure.
|
|
273
|
+
// cladeGutterWidth sizes the gutter with the same measureTextCanvas, so a label
|
|
274
|
+
// the gutter holds keeps every character.
|
|
399
275
|
function CladeLabelsSVG({
|
|
400
276
|
model,
|
|
401
277
|
theme,
|
|
@@ -405,43 +281,32 @@ function CladeLabelsSVG({
|
|
|
405
281
|
theme: Theme
|
|
406
282
|
offsetY: number
|
|
407
283
|
}) {
|
|
408
|
-
const {
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
rowHeight,
|
|
412
|
-
effectiveTreeAreaWidth,
|
|
413
|
-
fontSize,
|
|
414
|
-
} = model
|
|
415
|
-
if (cladeGutterWidth === 0) {
|
|
416
|
-
return null
|
|
417
|
-
}
|
|
418
|
-
const gutterLeft = effectiveTreeAreaWidth - cladeGutterWidth
|
|
419
|
-
return (
|
|
284
|
+
const { fontSize } = model
|
|
285
|
+
const boxes = cladeLabelBoxes(model)
|
|
286
|
+
return boxes.length === 0 ? null : (
|
|
420
287
|
<g id="clade-labels">
|
|
421
|
-
{
|
|
422
|
-
const
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
const top = rows[0] * rowHeight - offsetY
|
|
428
|
-
const height = cladeHeight(clade, rowHeight)
|
|
429
|
-
const horizontal = cladeLabelHorizontal(clade, rowHeight, fontSize)
|
|
430
|
-
const room = horizontal
|
|
431
|
-
? effectiveTreeAreaWidth - left - bracketGap
|
|
432
|
-
: height
|
|
433
|
-
const x = horizontal ? left : left + fontSize
|
|
434
|
-
const y = horizontal ? top + height / 2 + fontSize / 3 : top + height
|
|
288
|
+
{boxes.map(box => {
|
|
289
|
+
const top = box.top - offsetY
|
|
290
|
+
const x = box.horizontal ? box.left : box.left + fontSize
|
|
291
|
+
const y = box.horizontal
|
|
292
|
+
? top + box.height / 2 + fontSize / 3
|
|
293
|
+
: top + box.height
|
|
435
294
|
return (
|
|
436
295
|
<text
|
|
437
|
-
key={
|
|
296
|
+
key={box.key}
|
|
438
297
|
x={x}
|
|
439
298
|
y={y}
|
|
440
|
-
transform={horizontal ? undefined : `rotate(-90 ${x} ${y})`}
|
|
299
|
+
transform={box.horizontal ? undefined : `rotate(-90 ${x} ${y})`}
|
|
441
300
|
fontSize={fontSize}
|
|
442
|
-
fill={
|
|
301
|
+
fill={box.color ?? theme.palette.text.primary}
|
|
443
302
|
>
|
|
444
|
-
{
|
|
303
|
+
{
|
|
304
|
+
ellipsize(
|
|
305
|
+
t => measureTextCanvas(t, fontSize),
|
|
306
|
+
box.label,
|
|
307
|
+
box.horizontal ? box.width : box.height,
|
|
308
|
+
).text
|
|
309
|
+
}
|
|
445
310
|
</text>
|
|
446
311
|
)
|
|
447
312
|
})}
|
|
@@ -464,9 +329,7 @@ function LegendSVG({
|
|
|
464
329
|
const boxHeight = legendHeight(rows)
|
|
465
330
|
// the column is capped at LEGEND_MAX_W, so a name too long for it has to be
|
|
466
331
|
// clipped here -- text that overruns the reserved width runs off the figure
|
|
467
|
-
const
|
|
468
|
-
(width - LEGEND_TEXT_X - LEGEND_PAD) / LEGEND_CHAR_W,
|
|
469
|
-
)
|
|
332
|
+
const room = width - LEGEND_TEXT_X - LEGEND_PAD
|
|
470
333
|
return (
|
|
471
334
|
<g>
|
|
472
335
|
<rect
|
|
@@ -499,9 +362,7 @@ function LegendSVG({
|
|
|
499
362
|
fontWeight={row.color ? undefined : 'bold'}
|
|
500
363
|
fill={theme.palette.text.primary}
|
|
501
364
|
>
|
|
502
|
-
{row.label.
|
|
503
|
-
? `${row.label.slice(0, Math.max(1, maxChars - 1))}…`
|
|
504
|
-
: row.label}
|
|
365
|
+
{ellipsize(t => t.length * LEGEND_CHAR_W, row.label, room).text}
|
|
505
366
|
</text>
|
|
506
367
|
</g>
|
|
507
368
|
)
|
|
@@ -622,9 +483,8 @@ function CoreRendering({ model, theme, layout, Context, layers }: LayerProps) {
|
|
|
622
483
|
}
|
|
623
484
|
|
|
624
485
|
/**
|
|
625
|
-
* The alignment background as a single <image>,
|
|
626
|
-
*
|
|
627
|
-
* that reads back.
|
|
486
|
+
* The alignment background as a single <image>, wherever the live canvas
|
|
487
|
+
* draws the raster.
|
|
628
488
|
*
|
|
629
489
|
* The vector path's <rect> per cell exhausts the heap on a real alignment. The
|
|
630
490
|
* image is one pixel per cell, and image-rendering keeps the upscaled cell
|
|
@@ -640,8 +500,8 @@ function rasterBackground({
|
|
|
640
500
|
layout: Layout
|
|
641
501
|
}) {
|
|
642
502
|
const { contentHeight, offsetX, offsetY, msaAreaWidth } = layout
|
|
643
|
-
const { colWidth, rowHeight
|
|
644
|
-
if (
|
|
503
|
+
const { colWidth, rowHeight } = model
|
|
504
|
+
if (!rasterApplies(model)) {
|
|
645
505
|
return undefined
|
|
646
506
|
}
|
|
647
507
|
const { xStart, xEnd } = visibleColRange({
|
|
@@ -728,7 +588,8 @@ function TrackLabelsSVG({
|
|
|
728
588
|
const top = turnedOnTracks
|
|
729
589
|
.slice(0, i)
|
|
730
590
|
.reduce((acc, t) => acc + t.model.height, 0)
|
|
731
|
-
|
|
591
|
+
// pinned to the top of the track, as the live view's flex row pins it
|
|
592
|
+
return { id, name, size, baseline: top + size * 0.8 }
|
|
732
593
|
})
|
|
733
594
|
return (
|
|
734
595
|
<>
|
package/src/types.ts
CHANGED
package/src/version.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const version = '9.
|
|
1
|
+
export const version = '9.1.0'
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { createJBrowseTheme } from '@jbrowse/core/ui/theme'
|
|
2
|
+
|
|
3
|
+
import type { MSAViewerProps } from './components/MSAViewer.tsx'
|
|
4
|
+
import type { ColorScheme, Highlight } from './types.ts'
|
|
5
|
+
|
|
6
|
+
export function colorSchemeSnapshot(scheme: ColorScheme | undefined) {
|
|
7
|
+
return typeof scheme === 'string'
|
|
8
|
+
? { colorSchemeName: scheme }
|
|
9
|
+
: scheme
|
|
10
|
+
? { customColorScheme: scheme.map }
|
|
11
|
+
: {}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* The `highlightColumns` prop as `highlights`: one `{start, end}` per run of
|
|
16
|
+
* adjacent 1-based alignment columns
|
|
17
|
+
*/
|
|
18
|
+
export function columnHighlights(columns: number[] = []) {
|
|
19
|
+
const sorted = [...new Set(columns)].sort((a, b) => a - b)
|
|
20
|
+
const runs: { start: number; end: number }[] = []
|
|
21
|
+
for (const column of sorted) {
|
|
22
|
+
const last = runs.at(-1)
|
|
23
|
+
if (last?.end === column - 1) {
|
|
24
|
+
last.end = column
|
|
25
|
+
} else {
|
|
26
|
+
runs.push({ start: column, end: column })
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return runs
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function viewerHighlights({
|
|
33
|
+
highlights = [],
|
|
34
|
+
highlightColumns,
|
|
35
|
+
}: Pick<MSAViewerProps, 'highlights' | 'highlightColumns'>): Highlight[] {
|
|
36
|
+
return [...highlights, ...columnHighlights(highlightColumns)]
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function urlLocation(uri: string | undefined) {
|
|
40
|
+
return uri ? { uri, locationType: 'UriLocation' as const } : undefined
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** each filehandle prop, or the URL prop standing in for it */
|
|
44
|
+
export function fileLocations({
|
|
45
|
+
msaFilehandle,
|
|
46
|
+
treeFilehandle,
|
|
47
|
+
gffFilehandle,
|
|
48
|
+
msaUrl,
|
|
49
|
+
treeUrl,
|
|
50
|
+
gffUrl,
|
|
51
|
+
}: MSAViewerProps) {
|
|
52
|
+
return {
|
|
53
|
+
msaFilehandle: msaFilehandle ?? urlLocation(msaUrl),
|
|
54
|
+
treeFilehandle: treeFilehandle ?? urlLocation(treeUrl),
|
|
55
|
+
gffFilehandle: gffFilehandle ?? urlLocation(gffUrl),
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function resolveTheme(theme: MSAViewerProps['theme']) {
|
|
60
|
+
return createJBrowseTheme(
|
|
61
|
+
theme === 'dark'
|
|
62
|
+
? { palette: { mode: 'dark' } }
|
|
63
|
+
: typeof theme === 'object'
|
|
64
|
+
? theme
|
|
65
|
+
: undefined,
|
|
66
|
+
)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* The model snapshot `MSAViewer` creates from its props. The CLI's `--props`
|
|
71
|
+
* reads the same JSON the R and Python widgets send, so a figure exported
|
|
72
|
+
* headlessly draws what the widget drew.
|
|
73
|
+
*/
|
|
74
|
+
export function viewerSnapshot(props: MSAViewerProps) {
|
|
75
|
+
const {
|
|
76
|
+
msa,
|
|
77
|
+
tree,
|
|
78
|
+
gff,
|
|
79
|
+
rowData,
|
|
80
|
+
colorScheme,
|
|
81
|
+
height,
|
|
82
|
+
colWidth,
|
|
83
|
+
rowHeight,
|
|
84
|
+
allowedGappyness,
|
|
85
|
+
highlightColumns,
|
|
86
|
+
highlights,
|
|
87
|
+
features,
|
|
88
|
+
selection,
|
|
89
|
+
clades,
|
|
90
|
+
relativeTo,
|
|
91
|
+
columnTracks,
|
|
92
|
+
residueMappings,
|
|
93
|
+
encodings,
|
|
94
|
+
rowPanels,
|
|
95
|
+
drawTree,
|
|
96
|
+
treeAreaWidth,
|
|
97
|
+
showBranchLen,
|
|
98
|
+
treeOrder,
|
|
99
|
+
treeRoot,
|
|
100
|
+
region,
|
|
101
|
+
residueEncoding,
|
|
102
|
+
} = props
|
|
103
|
+
const { msaFilehandle, treeFilehandle, gffFilehandle } = fileLocations(props)
|
|
104
|
+
return {
|
|
105
|
+
type: 'MsaView' as const,
|
|
106
|
+
...(msa || tree || gff || rowData
|
|
107
|
+
? {
|
|
108
|
+
data: {
|
|
109
|
+
msa: msa ?? '',
|
|
110
|
+
tree: tree ?? '',
|
|
111
|
+
gff,
|
|
112
|
+
// the row table is stored as this JSON string, which is what a
|
|
113
|
+
// shared URL and the size limit already cover
|
|
114
|
+
...(rowData ? { treeMetadata: JSON.stringify(rowData) } : {}),
|
|
115
|
+
},
|
|
116
|
+
}
|
|
117
|
+
: {}),
|
|
118
|
+
...(msaFilehandle ? { msaFilehandle } : {}),
|
|
119
|
+
...(treeFilehandle ? { treeFilehandle } : {}),
|
|
120
|
+
...(gffFilehandle ? { gffFilehandle } : {}),
|
|
121
|
+
...colorSchemeSnapshot(colorScheme),
|
|
122
|
+
...(height ? { height } : {}),
|
|
123
|
+
...(colWidth ? { colWidth } : {}),
|
|
124
|
+
...(rowHeight ? { rowHeight } : {}),
|
|
125
|
+
...(allowedGappyness !== undefined ? { allowedGappyness } : {}),
|
|
126
|
+
...(highlights || highlightColumns
|
|
127
|
+
? { highlights: viewerHighlights({ highlights, highlightColumns }) }
|
|
128
|
+
: {}),
|
|
129
|
+
...(features ? { features } : {}),
|
|
130
|
+
...(selection ? { selection } : {}),
|
|
131
|
+
...(clades ? { clades } : {}),
|
|
132
|
+
...(relativeTo ? { relativeTo } : {}),
|
|
133
|
+
...(columnTracks ? { columnTracks } : {}),
|
|
134
|
+
...(residueMappings ? { residueMappings } : {}),
|
|
135
|
+
...(encodings ? { encodings } : {}),
|
|
136
|
+
...(rowPanels ? { rowPanels } : {}),
|
|
137
|
+
...(drawTree !== undefined ? { drawTree } : {}),
|
|
138
|
+
...(treeAreaWidth ? { treeAreaWidth } : {}),
|
|
139
|
+
...(showBranchLen !== undefined ? { showBranchLen } : {}),
|
|
140
|
+
...(treeOrder ? { treeOrder } : {}),
|
|
141
|
+
...(treeRoot ? { treeRoot } : {}),
|
|
142
|
+
...(region ? { region } : {}),
|
|
143
|
+
...(residueEncoding !== undefined
|
|
144
|
+
? { bgColor: residueEncoding === 'fill' }
|
|
145
|
+
: {}),
|
|
146
|
+
}
|
|
147
|
+
}
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import CascadingMenuButton from '@jbrowse/core/ui/CascadingMenuButton';
|
|
3
|
-
import MoreVert from '@mui/icons-material/MoreVert';
|
|
4
|
-
import RestartAlt from '@mui/icons-material/RestartAlt';
|
|
5
|
-
import { observer } from 'mobx-react';
|
|
6
|
-
const ZoomMenu = observer(function ({ model }) {
|
|
7
|
-
return (React.createElement(CascadingMenuButton, { tooltip: "Fit / zoom options", menuItems: [
|
|
8
|
-
{
|
|
9
|
-
label: 'Fit both vertically/horizontally',
|
|
10
|
-
onClick: () => {
|
|
11
|
-
model.fit();
|
|
12
|
-
},
|
|
13
|
-
},
|
|
14
|
-
{
|
|
15
|
-
label: 'Fit vertically',
|
|
16
|
-
onClick: () => {
|
|
17
|
-
model.fitVertically();
|
|
18
|
-
},
|
|
19
|
-
},
|
|
20
|
-
{
|
|
21
|
-
label: 'Fit horizontally',
|
|
22
|
-
onClick: () => {
|
|
23
|
-
model.fitHorizontally();
|
|
24
|
-
},
|
|
25
|
-
},
|
|
26
|
-
{
|
|
27
|
-
label: 'Reset zoom to default',
|
|
28
|
-
icon: RestartAlt,
|
|
29
|
-
onClick: () => {
|
|
30
|
-
model.resetZoom();
|
|
31
|
-
},
|
|
32
|
-
},
|
|
33
|
-
] },
|
|
34
|
-
React.createElement(MoreVert, null)));
|
|
35
|
-
});
|
|
36
|
-
export default ZoomMenu;
|
|
37
|
-
//# sourceMappingURL=ZoomMenu.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"ZoomMenu.js","sourceRoot":"","sources":["../../../src/components/header/ZoomMenu.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AAEzB,OAAO,mBAAmB,MAAM,sCAAsC,CAAA;AACtE,OAAO,QAAQ,MAAM,8BAA8B,CAAA;AACnD,OAAO,UAAU,MAAM,gCAAgC,CAAA;AACvD,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAA;AAIrC,MAAM,QAAQ,GAAG,QAAQ,CAAC,UAAU,EAAE,KAAK,EAA2B;IACpE,OAAO,CACL,oBAAC,mBAAmB,IAClB,OAAO,EAAC,oBAAoB,EAC5B,SAAS,EAAE;YACT;gBACE,KAAK,EAAE,kCAAkC;gBACzC,OAAO,EAAE,GAAG,EAAE;oBACZ,KAAK,CAAC,GAAG,EAAE,CAAA;gBACb,CAAC;aACF;YACD;gBACE,KAAK,EAAE,gBAAgB;gBACvB,OAAO,EAAE,GAAG,EAAE;oBACZ,KAAK,CAAC,aAAa,EAAE,CAAA;gBACvB,CAAC;aACF;YACD;gBACE,KAAK,EAAE,kBAAkB;gBACzB,OAAO,EAAE,GAAG,EAAE;oBACZ,KAAK,CAAC,eAAe,EAAE,CAAA;gBACzB,CAAC;aACF;YAED;gBACE,KAAK,EAAE,uBAAuB;gBAC9B,IAAI,EAAE,UAAU;gBAChB,OAAO,EAAE,GAAG,EAAE;oBACZ,KAAK,CAAC,SAAS,EAAE,CAAA;gBACnB,CAAC;aACF;SACF;QAED,oBAAC,QAAQ,OAAG,CACQ,CACvB,CAAA;AACH,CAAC,CAAC,CAAA;AAEF,eAAe,QAAQ,CAAA"}
|
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
import React from 'react'
|
|
2
|
-
|
|
3
|
-
import CascadingMenuButton from '@jbrowse/core/ui/CascadingMenuButton'
|
|
4
|
-
import MoreVert from '@mui/icons-material/MoreVert'
|
|
5
|
-
import RestartAlt from '@mui/icons-material/RestartAlt'
|
|
6
|
-
import { observer } from 'mobx-react'
|
|
7
|
-
|
|
8
|
-
import type { MsaViewModel } from '../../model.ts'
|
|
9
|
-
|
|
10
|
-
const ZoomMenu = observer(function ({ model }: { model: MsaViewModel }) {
|
|
11
|
-
return (
|
|
12
|
-
<CascadingMenuButton
|
|
13
|
-
tooltip="Fit / zoom options"
|
|
14
|
-
menuItems={[
|
|
15
|
-
{
|
|
16
|
-
label: 'Fit both vertically/horizontally',
|
|
17
|
-
onClick: () => {
|
|
18
|
-
model.fit()
|
|
19
|
-
},
|
|
20
|
-
},
|
|
21
|
-
{
|
|
22
|
-
label: 'Fit vertically',
|
|
23
|
-
onClick: () => {
|
|
24
|
-
model.fitVertically()
|
|
25
|
-
},
|
|
26
|
-
},
|
|
27
|
-
{
|
|
28
|
-
label: 'Fit horizontally',
|
|
29
|
-
onClick: () => {
|
|
30
|
-
model.fitHorizontally()
|
|
31
|
-
},
|
|
32
|
-
},
|
|
33
|
-
|
|
34
|
-
{
|
|
35
|
-
label: 'Reset zoom to default',
|
|
36
|
-
icon: RestartAlt,
|
|
37
|
-
onClick: () => {
|
|
38
|
-
model.resetZoom()
|
|
39
|
-
},
|
|
40
|
-
},
|
|
41
|
-
]}
|
|
42
|
-
>
|
|
43
|
-
<MoreVert />
|
|
44
|
-
</CascadingMenuButton>
|
|
45
|
-
)
|
|
46
|
-
})
|
|
47
|
-
|
|
48
|
-
export default ZoomMenu
|