react-msaview 5.1.1 → 5.2.1
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 +41 -8
- package/bundle/index.js +17 -17
- package/bundle/index.js.LICENSE.txt +1 -1
- package/bundle/index.js.map +1 -1
- package/dist/calculateBlocks.d.ts +4 -10
- package/dist/calculateBlocks.js +12 -20
- package/dist/calculateBlocks.js.map +1 -1
- package/dist/components/ConservationTrack.js +20 -37
- package/dist/components/ConservationTrack.js.map +1 -1
- package/dist/components/MSAView.js +8 -1
- package/dist/components/MSAView.js.map +1 -1
- package/dist/components/ResizeHandles.d.ts +3 -0
- package/dist/components/ResizeHandles.js +60 -105
- package/dist/components/ResizeHandles.js.map +1 -1
- package/dist/components/TextTrack.js +25 -34
- package/dist/components/TextTrack.js.map +1 -1
- package/dist/components/Track.js +1 -1
- package/dist/components/Track.js.map +1 -1
- package/dist/components/VerticalScrollbar.js +2 -2
- package/dist/components/VerticalScrollbar.js.map +1 -1
- package/dist/components/dialogs/ExportSVGDialog.js +20 -20
- package/dist/components/dialogs/ExportSVGDialog.js.map +1 -1
- package/dist/components/dialogs/InterProScanDialog.js +6 -1
- package/dist/components/dialogs/InterProScanDialog.js.map +1 -1
- package/dist/components/dialogs/UserProvidedDomainsDialog.js +1 -2
- package/dist/components/dialogs/UserProvidedDomainsDialog.js.map +1 -1
- package/dist/components/header/ColorSchemeMenu.js +1 -1
- package/dist/components/header/ColorSchemeMenu.js.map +1 -1
- package/dist/components/header/FileMenu.js +3 -2
- package/dist/components/header/FileMenu.js.map +1 -1
- package/dist/components/header/Header.js +1 -1
- package/dist/components/header/HeaderInfoArea.js +28 -8
- package/dist/components/header/HeaderInfoArea.js.map +1 -1
- package/dist/components/header/MSASettingsMenu.js +1 -1
- package/dist/components/header/MSASettingsMenu.js.map +1 -1
- package/dist/components/header/TreeSettingsMenu.js +1 -1
- package/dist/components/header/TreeSettingsMenu.js.map +1 -1
- package/dist/components/header/ZoomControls.js +7 -2
- package/dist/components/header/ZoomControls.js.map +1 -1
- package/dist/components/header/ZoomMenu.js +9 -0
- package/dist/components/header/ZoomMenu.js.map +1 -1
- package/dist/components/header/settingsMenuItems.js +24 -8
- package/dist/components/header/settingsMenuItems.js.map +1 -1
- package/dist/components/minimap/Minimap.js +6 -17
- package/dist/components/minimap/Minimap.js.map +1 -1
- package/dist/components/minimap/MinimapSVG.js +7 -17
- package/dist/components/minimap/MinimapSVG.js.map +1 -1
- package/dist/components/minimap/minimapLayout.d.ts +9 -0
- package/dist/components/minimap/minimapLayout.js +15 -0
- package/dist/components/minimap/minimapLayout.js.map +1 -0
- package/dist/components/msa/MSACanvas.js +6 -1
- package/dist/components/msa/MSACanvas.js.map +1 -1
- package/dist/components/msa/MSACanvasBlock.js +5 -1
- package/dist/components/msa/MSACanvasBlock.js.map +1 -1
- package/dist/components/msa/MSAMouseoverCanvas.js +2 -2
- package/dist/components/msa/MSAMouseoverCanvas.js.map +1 -1
- package/dist/components/msa/getVisibleLeaves.d.ts +6 -0
- package/dist/components/msa/getVisibleLeaves.js +9 -0
- package/dist/components/msa/getVisibleLeaves.js.map +1 -0
- package/dist/components/msa/renderBoxFeatureCanvasBlock.d.ts +2 -1
- package/dist/components/msa/renderBoxFeatureCanvasBlock.js +5 -6
- package/dist/components/msa/renderBoxFeatureCanvasBlock.js.map +1 -1
- package/dist/components/msa/renderMSABlock.d.ts +2 -1
- package/dist/components/msa/renderMSABlock.js +100 -88
- package/dist/components/msa/renderMSABlock.js.map +1 -1
- package/dist/components/msa/renderMSAMouseover.js +5 -4
- package/dist/components/msa/renderMSAMouseover.js.map +1 -1
- package/dist/components/msa/visibleColRange.d.ts +8 -0
- package/dist/components/msa/visibleColRange.js +10 -0
- package/dist/components/msa/visibleColRange.js.map +1 -0
- package/dist/components/renderCtx.d.ts +7 -0
- package/dist/components/renderCtx.js +2 -0
- package/dist/components/renderCtx.js.map +1 -0
- package/dist/components/tracks/renderTracksSvg.d.ts +6 -5
- package/dist/components/tracks/renderTracksSvg.js +13 -5
- package/dist/components/tracks/renderTracksSvg.js.map +1 -1
- package/dist/components/tree/TreeCanvas.js.map +1 -1
- package/dist/components/tree/TreeCanvasBlock.js +2 -10
- package/dist/components/tree/TreeCanvasBlock.js.map +1 -1
- package/dist/components/tree/renderTreeCanvas.d.ts +5 -4
- package/dist/components/tree/renderTreeCanvas.js +18 -26
- package/dist/components/tree/renderTreeCanvas.js.map +1 -1
- package/dist/components/util.d.ts +0 -14
- package/dist/components/util.js +0 -58
- package/dist/components/util.js.map +1 -1
- package/dist/constants.d.ts +4 -0
- package/dist/constants.js +7 -0
- package/dist/constants.js.map +1 -1
- package/dist/flatToTree.js +6 -0
- package/dist/flatToTree.js.map +1 -1
- package/dist/hierarchy.d.ts +3 -1
- package/dist/hierarchy.js +142 -89
- package/dist/hierarchy.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -1
- package/dist/launchInterProScan.js +22 -9
- package/dist/launchInterProScan.js.map +1 -1
- package/dist/measureTextCanvas.js +2 -1
- package/dist/measureTextCanvas.js.map +1 -1
- package/dist/model.d.ts +234 -176
- package/dist/model.js +285 -259
- package/dist/model.js.map +1 -1
- package/dist/parseAsn1.js +8 -2
- package/dist/parseAsn1.js.map +1 -1
- package/dist/renderToSvg.d.ts +1 -1
- package/dist/renderToSvg.js +43 -46
- package/dist/renderToSvg.js.map +1 -1
- package/dist/rowCoordinateCalculations.d.ts +0 -9
- package/dist/rowCoordinateCalculations.js +0 -20
- package/dist/rowCoordinateCalculations.js.map +1 -1
- package/dist/setFontSize.d.ts +3 -0
- package/dist/setFontSize.js +14 -0
- package/dist/setFontSize.js.map +1 -0
- package/dist/useDevicePixelRatio.d.ts +1 -0
- package/dist/useDevicePixelRatio.js +27 -0
- package/dist/useDevicePixelRatio.js.map +1 -0
- package/dist/useWheelScroll.d.ts +3 -1
- package/dist/useWheelScroll.js +64 -22
- package/dist/useWheelScroll.js.map +1 -1
- package/dist/util.d.ts +5 -0
- package/dist/util.js +59 -1
- package/dist/util.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/webpack.js +0 -1
- package/dist/webpack.js.map +1 -1
- package/dist/wheelZoom.d.ts +4 -0
- package/dist/wheelZoom.js +43 -0
- package/dist/wheelZoom.js.map +1 -0
- package/package.json +5 -5
- package/src/calculateBlocks.ts +18 -51
- package/src/components/ConservationTrack.tsx +20 -45
- package/src/components/MSAView.tsx +10 -1
- package/src/components/ResizeHandles.tsx +101 -113
- package/src/components/TextTrack.tsx +31 -38
- package/src/components/Track.tsx +1 -1
- package/src/components/VerticalScrollbar.tsx +2 -2
- package/src/components/dialogs/ExportSVGDialog.tsx +27 -17
- package/src/components/dialogs/InterProScanDialog.tsx +6 -1
- package/src/components/dialogs/UserProvidedDomainsDialog.tsx +1 -2
- package/src/components/header/ColorSchemeMenu.tsx +1 -0
- package/src/components/header/FileMenu.tsx +3 -1
- package/src/components/header/Header.tsx +1 -1
- package/src/components/header/HeaderInfoArea.tsx +32 -7
- package/src/components/header/MSASettingsMenu.tsx +1 -0
- package/src/components/header/TreeSettingsMenu.tsx +1 -0
- package/src/components/header/ZoomControls.tsx +14 -1
- package/src/components/header/ZoomMenu.tsx +10 -0
- package/src/components/header/settingsMenuItems.ts +24 -8
- package/src/components/minimap/Minimap.tsx +6 -20
- package/src/components/minimap/MinimapSVG.tsx +10 -27
- package/src/components/minimap/minimapLayout.ts +17 -0
- package/src/components/msa/MSACanvas.tsx +9 -0
- package/src/components/msa/MSACanvasBlock.tsx +7 -1
- package/src/components/msa/MSAMouseoverCanvas.tsx +4 -3
- package/src/components/msa/getVisibleLeaves.ts +21 -0
- package/src/components/msa/renderBoxFeatureCanvasBlock.ts +9 -9
- package/src/components/msa/renderMSABlock.ts +123 -125
- package/src/components/msa/renderMSAMouseover.ts +5 -4
- package/src/components/msa/visibleColRange.ts +17 -0
- package/src/components/renderCtx.ts +29 -0
- package/src/components/tracks/renderTracksSvg.ts +20 -10
- package/src/components/tree/TreeCanvas.tsx +3 -1
- package/src/components/tree/TreeCanvasBlock.tsx +2 -12
- package/src/components/tree/renderTreeCanvas.ts +27 -30
- package/src/components/util.ts +0 -81
- package/src/constants.ts +8 -0
- package/src/flatToTree.test.ts +30 -0
- package/src/flatToTree.ts +10 -1
- package/src/hierarchy.test.ts +53 -1
- package/src/hierarchy.ts +153 -100
- package/src/index.ts +8 -0
- package/src/launchInterProScan.ts +33 -11
- package/src/measureTextCanvas.ts +3 -1
- package/src/model.ts +359 -306
- package/src/parseAsn1.test.ts +6 -0
- package/src/parseAsn1.ts +11 -3
- package/src/renderToSvg.tsx +99 -95
- package/src/rowCoordinateCalculations.test.ts +0 -51
- package/src/rowCoordinateCalculations.ts +0 -23
- package/src/setFontSize.test.ts +33 -0
- package/src/setFontSize.ts +20 -0
- package/src/useDevicePixelRatio.ts +29 -0
- package/src/useWheelScroll.ts +79 -21
- package/src/util.ts +60 -1
- package/src/version.ts +1 -1
- package/src/webpack.ts +0 -1
- package/src/wheelZoom.test.ts +46 -0
- package/src/wheelZoom.ts +47 -0
|
@@ -1,7 +1,13 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
calcDepthToLeaf,
|
|
3
|
+
forEachDescendant,
|
|
4
|
+
forEachLink,
|
|
5
|
+
} from '../../hierarchy.ts'
|
|
6
|
+
import { setFontSize } from '../../setFontSize.ts'
|
|
2
7
|
|
|
3
8
|
import type { HierarchyNode } from '../../hierarchy.ts'
|
|
4
9
|
import type { MsaViewModel } from '../../model.ts'
|
|
10
|
+
import type { RenderCtx } from '../renderCtx.ts'
|
|
5
11
|
import type { Theme } from '@mui/material'
|
|
6
12
|
|
|
7
13
|
export const padding = 600
|
|
@@ -59,7 +65,7 @@ export function renderTree({
|
|
|
59
65
|
blockSizeYOverride,
|
|
60
66
|
}: {
|
|
61
67
|
offsetY: number
|
|
62
|
-
ctx:
|
|
68
|
+
ctx: RenderCtx
|
|
63
69
|
model: MsaViewModel
|
|
64
70
|
theme: Theme
|
|
65
71
|
maxBranchLen: number
|
|
@@ -67,16 +73,15 @@ export function renderTree({
|
|
|
67
73
|
blockSizeYOverride?: number
|
|
68
74
|
}) {
|
|
69
75
|
const { hierarchy, showBranchLenEffective: showBranchLen, blockSize } = model
|
|
70
|
-
const by = blockSizeYOverride
|
|
76
|
+
const by = blockSizeYOverride ?? blockSize
|
|
71
77
|
ctx.strokeStyle = theme.palette.text.primary
|
|
72
|
-
|
|
73
|
-
const { source, target } = link
|
|
78
|
+
forEachLink(hierarchy, (source, target) => {
|
|
74
79
|
const sy = source.x!
|
|
75
80
|
const ty = target.x!
|
|
76
81
|
const tx = getNodeX(target, showBranchLen, maxBranchLen, maxDepthToLeaf)
|
|
77
82
|
const sx = getNodeX(source, showBranchLen, maxBranchLen, maxDepthToLeaf)
|
|
78
83
|
if (tx === undefined || sx === undefined) {
|
|
79
|
-
|
|
84
|
+
return
|
|
80
85
|
}
|
|
81
86
|
|
|
82
87
|
const y1 = Math.min(sy, ty)
|
|
@@ -91,7 +96,7 @@ export function renderTree({
|
|
|
91
96
|
ctx.lineTo(tx, ty)
|
|
92
97
|
ctx.stroke()
|
|
93
98
|
}
|
|
94
|
-
}
|
|
99
|
+
})
|
|
95
100
|
}
|
|
96
101
|
|
|
97
102
|
export function renderNodeBubbles({
|
|
@@ -103,7 +108,7 @@ export function renderNodeBubbles({
|
|
|
103
108
|
maxDepthToLeaf,
|
|
104
109
|
blockSizeYOverride,
|
|
105
110
|
}: {
|
|
106
|
-
ctx:
|
|
111
|
+
ctx: RenderCtx
|
|
107
112
|
clickMap?: ClickMapIndex
|
|
108
113
|
offsetY: number
|
|
109
114
|
model: MsaViewModel
|
|
@@ -118,11 +123,11 @@ export function renderNodeBubbles({
|
|
|
118
123
|
blockSize,
|
|
119
124
|
marginLeft: ml,
|
|
120
125
|
} = model
|
|
121
|
-
const by = blockSizeYOverride
|
|
122
|
-
|
|
126
|
+
const by = blockSizeYOverride ?? blockSize
|
|
127
|
+
forEachDescendant(hierarchy, node => {
|
|
123
128
|
const x = getNodeX(node, showBranchLen, maxBranchLen, maxDepthToLeaf)
|
|
124
129
|
if (x === undefined) {
|
|
125
|
-
|
|
130
|
+
return
|
|
126
131
|
}
|
|
127
132
|
const { data } = node
|
|
128
133
|
const y = node.x!
|
|
@@ -149,7 +154,7 @@ export function renderNodeBubbles({
|
|
|
149
154
|
name,
|
|
150
155
|
})
|
|
151
156
|
}
|
|
152
|
-
}
|
|
157
|
+
})
|
|
153
158
|
}
|
|
154
159
|
|
|
155
160
|
export function renderTreeLabels({
|
|
@@ -164,7 +169,7 @@ export function renderTreeLabels({
|
|
|
164
169
|
}: {
|
|
165
170
|
model: MsaViewModel
|
|
166
171
|
offsetY: number
|
|
167
|
-
ctx:
|
|
172
|
+
ctx: RenderCtx
|
|
168
173
|
clickMap?: ClickMapIndex
|
|
169
174
|
theme: Theme
|
|
170
175
|
maxBranchLen: number
|
|
@@ -183,8 +188,9 @@ export function renderTreeLabels({
|
|
|
183
188
|
marginLeft,
|
|
184
189
|
leaves,
|
|
185
190
|
noTree,
|
|
191
|
+
labelWidthMap,
|
|
186
192
|
} = model
|
|
187
|
-
const by = blockSizeYOverride
|
|
193
|
+
const by = blockSizeYOverride ?? blockSize
|
|
188
194
|
const emHeight = ctx.measureText('M').width
|
|
189
195
|
if (labelsAlignRight) {
|
|
190
196
|
ctx.textAlign = 'right'
|
|
@@ -204,10 +210,11 @@ export function renderTreeLabels({
|
|
|
204
210
|
const yp = y + fontSize / 4
|
|
205
211
|
let xp = 0
|
|
206
212
|
if (!noTree) {
|
|
207
|
-
xp = getNodeX(node, showBranchLen, maxBranchLen, maxDepthToLeaf)
|
|
213
|
+
xp = getNodeX(node, showBranchLen, maxBranchLen, maxDepthToLeaf) ?? 0
|
|
208
214
|
}
|
|
209
215
|
|
|
210
|
-
const
|
|
216
|
+
const width =
|
|
217
|
+
labelWidthMap.get(name) ?? ctx.measureText(displayName).width
|
|
211
218
|
|
|
212
219
|
ctx.fillStyle = theme.palette.text.primary
|
|
213
220
|
if (labelsAlignRight) {
|
|
@@ -255,7 +262,7 @@ export function renderTreeCanvas({
|
|
|
255
262
|
}: {
|
|
256
263
|
model: MsaViewModel
|
|
257
264
|
offsetY: number
|
|
258
|
-
ctx:
|
|
265
|
+
ctx: RenderCtx
|
|
259
266
|
clickMap?: ClickMapIndex
|
|
260
267
|
theme: Theme
|
|
261
268
|
highResScaleFactorOverride?: number
|
|
@@ -272,27 +279,17 @@ export function renderTreeCanvas({
|
|
|
272
279
|
fontSize,
|
|
273
280
|
showTreeText,
|
|
274
281
|
marginLeft,
|
|
275
|
-
nref,
|
|
276
282
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
277
|
-
rowHeight: _rowHeight, //
|
|
283
|
+
rowHeight: _rowHeight, // read so the draw autorun re-fires after a zoom change
|
|
278
284
|
} = model
|
|
279
285
|
|
|
280
286
|
ctx.resetTransform()
|
|
281
287
|
|
|
282
|
-
|
|
283
|
-
// it in this statement because otherwise it would be an unused variable,
|
|
284
|
-
// we just need to use nref to indicate a redraw in an autorun when canvas
|
|
285
|
-
// ref is updated and in order to convince bundlers like not to delete
|
|
286
|
-
// unused usage with propertyReadSideEffects
|
|
287
|
-
const k =
|
|
288
|
-
nref < 0
|
|
289
|
-
? Number.NEGATIVE_INFINITY
|
|
290
|
-
: highResScaleFactorOverride || highResScaleFactor
|
|
288
|
+
const k = highResScaleFactorOverride ?? highResScaleFactor
|
|
291
289
|
ctx.scale(k, k)
|
|
292
290
|
ctx.translate(marginLeft, -offsetY)
|
|
293
291
|
|
|
294
|
-
|
|
295
|
-
ctx.font = font.replace(/\d+px/, `${fontSize}px`)
|
|
292
|
+
setFontSize(ctx, fontSize)
|
|
296
293
|
|
|
297
294
|
// memoized on the model and shared across the tree/bubble/label passes (and
|
|
298
295
|
// across all tree blocks) rather than re-traversing the hierarchy in each
|
package/src/components/util.ts
CHANGED
|
@@ -1,84 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Given a scale ( bp/px ) and minimum distances (px) between major and minor
|
|
3
|
-
* gridlines, return an object like `{ majorPitch: bp, minorPitch: bp }` giving
|
|
4
|
-
* the gridline pitches to use.
|
|
5
|
-
*/
|
|
6
|
-
export function chooseGridPitch(
|
|
7
|
-
scale: number,
|
|
8
|
-
minMajorPitchPx: number,
|
|
9
|
-
minMinorPitchPx: number,
|
|
10
|
-
) {
|
|
11
|
-
scale = Math.abs(scale)
|
|
12
|
-
const minMajorPitchBp = minMajorPitchPx * scale
|
|
13
|
-
const majorMagnitude = Number.parseInt(
|
|
14
|
-
minMajorPitchBp.toExponential().split(/e/i)[1]!,
|
|
15
|
-
10,
|
|
16
|
-
)
|
|
17
|
-
|
|
18
|
-
let majorPitch = 10 ** majorMagnitude
|
|
19
|
-
while (majorPitch < minMajorPitchBp) {
|
|
20
|
-
majorPitch *= 2
|
|
21
|
-
if (majorPitch >= minMajorPitchBp) {
|
|
22
|
-
break
|
|
23
|
-
}
|
|
24
|
-
majorPitch *= 2.5
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
majorPitch = Math.max(majorPitch, 5)
|
|
28
|
-
|
|
29
|
-
const majorPitchPx = majorPitch / scale
|
|
30
|
-
|
|
31
|
-
let minorPitch = 0
|
|
32
|
-
if (!(majorPitch % 10) && majorPitchPx / 10 >= minMinorPitchPx) {
|
|
33
|
-
minorPitch = majorPitch / 10
|
|
34
|
-
} else if (!(majorPitch % 5) && majorPitchPx / 5 >= minMinorPitchPx) {
|
|
35
|
-
minorPitch = majorPitch / 5
|
|
36
|
-
} else if (!(majorPitch % 2) && majorPitchPx / 2 >= minMinorPitchPx) {
|
|
37
|
-
minorPitch = majorPitch / 2
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
return { majorPitch, minorPitch }
|
|
41
|
-
}
|
|
42
|
-
export function makeTicks(
|
|
43
|
-
start: number,
|
|
44
|
-
end: number,
|
|
45
|
-
bpPerPx: number,
|
|
46
|
-
emitMajor = true,
|
|
47
|
-
emitMinor = true,
|
|
48
|
-
) {
|
|
49
|
-
const gridPitch = chooseGridPitch(bpPerPx, 60, 15)
|
|
50
|
-
|
|
51
|
-
let minBase = start
|
|
52
|
-
let maxBase = end
|
|
53
|
-
|
|
54
|
-
if (bpPerPx < 0) {
|
|
55
|
-
;[minBase, maxBase] = [maxBase, minBase]
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
// add 20px additional on the right and left to allow us to draw the ends of
|
|
59
|
-
// labels that lie a little outside our region
|
|
60
|
-
minBase -= Math.abs(20 * bpPerPx)
|
|
61
|
-
maxBase += Math.abs(20 * bpPerPx) + 1
|
|
62
|
-
|
|
63
|
-
const iterPitch = gridPitch.minorPitch || gridPitch.majorPitch
|
|
64
|
-
let index = 0
|
|
65
|
-
const ticks = []
|
|
66
|
-
for (
|
|
67
|
-
let base = Math.ceil(minBase / iterPitch) * iterPitch;
|
|
68
|
-
base < maxBase;
|
|
69
|
-
base += iterPitch
|
|
70
|
-
) {
|
|
71
|
-
if (emitMinor && base % (gridPitch.majorPitch * 2)) {
|
|
72
|
-
ticks.push({ type: 'minor', base: base - 1, index })
|
|
73
|
-
index += 1
|
|
74
|
-
} else if (emitMajor && !(base % (gridPitch.majorPitch * 2))) {
|
|
75
|
-
ticks.push({ type: 'major', base: base - 1, index })
|
|
76
|
-
index += 1
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
return ticks
|
|
80
|
-
}
|
|
81
|
-
|
|
82
1
|
export function mathPower(num: number): string {
|
|
83
2
|
if (num < 999) {
|
|
84
3
|
return String(num)
|
package/src/constants.ts
CHANGED
|
@@ -10,6 +10,14 @@ export const defaultHideGaps = true
|
|
|
10
10
|
export const defaultAllowedGappyness = 100
|
|
11
11
|
export const defaultSubFeatureRows = false
|
|
12
12
|
export const defaultDrawMsaLetters = true
|
|
13
|
+
export const defaultScrollZoom = false
|
|
14
|
+
|
|
15
|
+
// Zoom limits, px per cell. maxCellSize caps both smooth zoom and the stepwise
|
|
16
|
+
// zoom-in buttons (which otherwise grow unbounded). minColWidth/minRowHeight are
|
|
17
|
+
// the smooth-zoom floors.
|
|
18
|
+
export const minColWidth = 0.2
|
|
19
|
+
export const minRowHeight = 1
|
|
20
|
+
export const maxCellSize = 80
|
|
13
21
|
|
|
14
22
|
// MSA model defaults
|
|
15
23
|
export const defaultBgColor = true
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { describe, expect, test } from 'vitest'
|
|
2
|
+
|
|
3
|
+
import { flatToTree } from './flatToTree.ts'
|
|
4
|
+
|
|
5
|
+
describe('flatToTree', () => {
|
|
6
|
+
test('builds a tree from a flat parent list', () => {
|
|
7
|
+
const root = flatToTree([
|
|
8
|
+
{ id: 0 },
|
|
9
|
+
{ id: 1, parent: 0 },
|
|
10
|
+
{ id: 2, parent: 0 },
|
|
11
|
+
{ id: 3, parent: 1 },
|
|
12
|
+
])
|
|
13
|
+
expect(root.id).toBe('0')
|
|
14
|
+
expect(root.children.map(c => c.id)).toEqual(['1', '2'])
|
|
15
|
+
expect(root.children[0]!.children.map(c => c.id)).toEqual(['3'])
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
test('throws on empty input rather than returning undefined', () => {
|
|
19
|
+
expect(() => flatToTree([])).toThrow(/no root/)
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
test('throws when every node has a parent (cycle) rather than returning undefined', () => {
|
|
23
|
+
expect(() =>
|
|
24
|
+
flatToTree([
|
|
25
|
+
{ id: 0, parent: 1 },
|
|
26
|
+
{ id: 1, parent: 0 },
|
|
27
|
+
]),
|
|
28
|
+
).toThrow(/no root/)
|
|
29
|
+
})
|
|
30
|
+
})
|
package/src/flatToTree.ts
CHANGED
|
@@ -35,5 +35,14 @@ export function flatToTree(items: FlatItem[]): TreeNode {
|
|
|
35
35
|
}
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
-
|
|
38
|
+
if (root === undefined) {
|
|
39
|
+
// no node lacks a (resolvable) parent: empty input, or every node's parent
|
|
40
|
+
// points back into the set (a cycle). returning undefined here would crash
|
|
41
|
+
// cryptically downstream, so fail with a diagnosable message instead
|
|
42
|
+
throw new Error(
|
|
43
|
+
'could not build tree from flat node list: no root found (input was empty or contains a parent cycle)',
|
|
44
|
+
)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return root
|
|
39
48
|
}
|
package/src/hierarchy.test.ts
CHANGED
|
@@ -1,9 +1,36 @@
|
|
|
1
1
|
import { describe, expect, test } from 'vitest'
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
calcDepthToLeaf,
|
|
5
|
+
collapse,
|
|
6
|
+
find,
|
|
7
|
+
findMaxBranchLen,
|
|
8
|
+
hierarchy,
|
|
9
|
+
leaves,
|
|
10
|
+
maxLength,
|
|
11
|
+
setBrLength,
|
|
12
|
+
sort,
|
|
13
|
+
sum,
|
|
14
|
+
} from './hierarchy.ts'
|
|
4
15
|
|
|
5
16
|
import type { NodeWithIds } from './types.ts'
|
|
6
17
|
|
|
18
|
+
// Builds a fully unbalanced "caterpillar" tree of the given depth: each internal
|
|
19
|
+
// node has one leaf child and one internal child. Recursion over this would blow
|
|
20
|
+
// the call stack; the iterative traversals must handle it.
|
|
21
|
+
function makeDeepTree(depth: number): NodeWithIds {
|
|
22
|
+
let node: NodeWithIds = { id: 'leaf0', name: 'leaf0', children: [] }
|
|
23
|
+
for (let i = 1; i <= depth; i++) {
|
|
24
|
+
node = {
|
|
25
|
+
id: `node${i}`,
|
|
26
|
+
name: `node${i}`,
|
|
27
|
+
length: 1,
|
|
28
|
+
children: [{ id: `leaf${i}`, name: `leaf${i}`, children: [] }, node],
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return node
|
|
32
|
+
}
|
|
33
|
+
|
|
7
34
|
function makeTree(): NodeWithIds {
|
|
8
35
|
return {
|
|
9
36
|
id: 'root',
|
|
@@ -118,3 +145,28 @@ describe('sum', () => {
|
|
|
118
145
|
expect(find(h, n => n.data.id === 'A')?.value).toBe(2)
|
|
119
146
|
})
|
|
120
147
|
})
|
|
148
|
+
|
|
149
|
+
describe('deep (caterpillar) trees do not overflow the stack', () => {
|
|
150
|
+
const depth = 100_000
|
|
151
|
+
|
|
152
|
+
test('build, height, leaf count, and ordering', () => {
|
|
153
|
+
const h = hierarchy(makeDeepTree(depth), d => d.children)
|
|
154
|
+
expect(h.height).toBe(depth)
|
|
155
|
+
const l = leaves(h)
|
|
156
|
+
expect(l.length).toBe(depth + 1)
|
|
157
|
+
// each level contributes its own leafN before descending; deepest leaf last
|
|
158
|
+
expect(l[0]!.data.name).toBe(`leaf${depth}`)
|
|
159
|
+
expect(l.at(-1)!.data.name).toBe('leaf0')
|
|
160
|
+
})
|
|
161
|
+
|
|
162
|
+
test('accumulation helpers (sum, find, maxLength, setBrLength, depth)', () => {
|
|
163
|
+
const h = hierarchy(makeDeepTree(depth), d => d.children)
|
|
164
|
+
sum(h, d => (d.children.length > 0 ? 0 : 1))
|
|
165
|
+
expect(h.value).toBe(depth + 1)
|
|
166
|
+
expect(find(h, n => n.data.id === 'leaf0')?.data.name).toBe('leaf0')
|
|
167
|
+
expect(maxLength(h)).toBe(depth)
|
|
168
|
+
expect(calcDepthToLeaf(h)).toBe(depth)
|
|
169
|
+
setBrLength(h, 0, 1)
|
|
170
|
+
expect(findMaxBranchLen(h)).toBe(depth)
|
|
171
|
+
})
|
|
172
|
+
})
|