jbrowse-plugin-graphgenomeviewer 1.0.0 → 3.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +674 -0
- package/README.md +202 -24
- package/dist/chunks/AddTrackWorkflow-Y3OBHM33.js +2 -0
- package/dist/chunks/AddTrackWorkflow-Y3OBHM33.js.map +7 -0
- package/dist/chunks/GbzBaseSyntenyAdapter-EFIOI2SN.js +8 -0
- package/dist/chunks/GbzBaseSyntenyAdapter-EFIOI2SN.js.map +7 -0
- package/dist/chunks/GraphGenomeView-UD5WCV3Q.js +14 -0
- package/dist/chunks/GraphGenomeView-UD5WCV3Q.js.map +7 -0
- package/dist/chunks/MinigraphBubbleAdapter-B75VQK7T.js +2 -0
- package/dist/chunks/MinigraphBubbleAdapter-B75VQK7T.js.map +7 -0
- package/dist/chunks/RgfaTabixAdapter-IT7I5RR3.js +3 -0
- package/dist/chunks/RgfaTabixAdapter-IT7I5RR3.js.map +7 -0
- package/dist/chunks/bandage-layout-FIOZ7B3E.js +1660 -0
- package/dist/chunks/bandage-layout-FIOZ7B3E.js.map +7 -0
- package/dist/chunks/chunk-D7XFR2IV.js +2 -0
- package/dist/chunks/chunk-D7XFR2IV.js.map +7 -0
- package/dist/chunks/chunk-F4C3BEPV.js +2 -0
- package/dist/chunks/chunk-F4C3BEPV.js.map +7 -0
- package/dist/chunks/chunk-FCWXYIFC.js +2 -0
- package/dist/chunks/chunk-FCWXYIFC.js.map +7 -0
- package/dist/chunks/chunk-FY3BTKY4.js +2 -0
- package/dist/chunks/chunk-FY3BTKY4.js.map +7 -0
- package/dist/chunks/chunk-J4OLMLS5.js +2 -0
- package/dist/chunks/chunk-J4OLMLS5.js.map +7 -0
- package/dist/chunks/chunk-JCG7RLMB.js +2 -0
- package/dist/chunks/chunk-JCG7RLMB.js.map +7 -0
- package/dist/chunks/chunk-KWAXLUPQ.js +2 -0
- package/dist/chunks/chunk-KWAXLUPQ.js.map +7 -0
- package/dist/chunks/chunk-LGKM2PZH.js +2 -0
- package/dist/chunks/chunk-LGKM2PZH.js.map +7 -0
- package/dist/chunks/chunk-TN6FK5UD.js +2 -0
- package/dist/chunks/chunk-TN6FK5UD.js.map +7 -0
- package/dist/chunks/chunk-X3X74OGB.js +2 -0
- package/dist/chunks/chunk-X3X74OGB.js.map +7 -0
- package/dist/chunks/chunk-XEJ47DLD.js +2 -0
- package/dist/chunks/chunk-XEJ47DLD.js.map +7 -0
- package/dist/chunks/chunk-ZNGDWZ6Z.js +2 -0
- package/dist/chunks/chunk-ZNGDWZ6Z.js.map +7 -0
- package/dist/chunks/executeDiagonalize-XMPEDHVZ.js +2 -0
- package/dist/chunks/executeDiagonalize-XMPEDHVZ.js.map +7 -0
- package/dist/jbrowse-plugin-graphgenomeviewer.esm.js +3 -0
- package/dist/jbrowse-plugin-graphgenomeviewer.esm.js.map +7 -0
- package/package.json +94 -49
- package/src/GbzBaseSyntenyAdapter/GbzBaseSyntenyAdapter.test.ts +427 -0
- package/src/GbzBaseSyntenyAdapter/GbzBaseSyntenyAdapter.ts +461 -0
- package/src/GbzBaseSyntenyAdapter/configSchema.ts +181 -0
- package/src/GbzBaseSyntenyAdapter/index.ts +27 -0
- package/src/GbzBaseSyntenyAdapter/test_data/micb-kir3dl1.gbz.db +0 -0
- package/src/GbzBaseSyntenyAdapter/test_data/micb-kir3dl1.haplotype-index.db +0 -0
- package/src/GetSubgraph.test.ts +193 -0
- package/src/GetSubgraph.ts +93 -0
- package/src/GraphAddTrackWorkflow/AddTrackWorkflow.tsx +167 -0
- package/src/GraphAddTrackWorkflow/buildTrackConfig.test.ts +150 -0
- package/src/GraphAddTrackWorkflow/buildTrackConfig.ts +114 -0
- package/src/GraphAddTrackWorkflow/index.ts +20 -0
- package/src/GraphComputeLayout.ts +46 -0
- package/src/GraphGenomeView/anchoredNodes.ts +101 -0
- package/src/GraphGenomeView/bubbleSpreads.ts +102 -0
- package/src/GraphGenomeView/bubbles/bubbleHalos.test.ts +58 -0
- package/src/GraphGenomeView/bubbles/bubbleHalos.ts +149 -0
- package/src/GraphGenomeView/bubbles/bubbleRoutes.test.ts +141 -0
- package/src/GraphGenomeView/bubbles/bubblesFromGraph.test.ts +239 -0
- package/src/GraphGenomeView/bubbles/bubblesFromGraph.ts +210 -0
- package/src/GraphGenomeView/bubbles/classifyBubble.test.ts +172 -0
- package/src/GraphGenomeView/bubbles/classifyBubble.ts +138 -0
- package/src/GraphGenomeView/bubbles/popBubble.test.ts +73 -0
- package/src/GraphGenomeView/bubbles/popBubble.ts +26 -0
- package/src/GraphGenomeView/colorSchemes.ts +36 -0
- package/src/GraphGenomeView/components/BubbleHalos.tsx +162 -0
- package/src/GraphGenomeView/components/BubbleOverlay.tsx +181 -0
- package/src/GraphGenomeView/components/ColorSchemeSelect.tsx +41 -0
- package/src/GraphGenomeView/components/GenePins.tsx +107 -0
- package/src/GraphGenomeView/components/GraphCanvas.tsx +759 -0
- package/src/GraphGenomeView/components/GraphGenomeView.test.tsx +77 -0
- package/src/GraphGenomeView/components/GraphGenomeView.tsx +18 -17
- package/src/GraphGenomeView/components/GraphLoadStatus.tsx +84 -0
- package/src/GraphGenomeView/components/GraphSettingsDialog.tsx +327 -0
- package/src/GraphGenomeView/components/GraphStats.tsx +82 -0
- package/src/GraphGenomeView/components/GraphToolbar.tsx +84 -0
- package/src/GraphGenomeView/components/ImportForm.tsx +124 -0
- package/src/GraphGenomeView/components/LabelChip.tsx +65 -0
- package/src/GraphGenomeView/components/LayoutSelect.tsx +56 -0
- package/src/GraphGenomeView/components/RepeatSelect.tsx +51 -0
- package/src/GraphGenomeView/components/SettingsMenu.tsx +82 -0
- package/src/GraphGenomeView/components/SubgraphContextSelect.tsx +75 -0
- package/src/GraphGenomeView/components/SubgraphHaplotypesField.tsx +86 -0
- package/src/GraphGenomeView/components/WalkRowsOverlay.tsx +252 -0
- package/src/GraphGenomeView/components/WalkSelect.tsx +51 -0
- package/src/GraphGenomeView/components/overlayLabels.test.ts +56 -0
- package/src/GraphGenomeView/components/overlayLabels.ts +75 -0
- package/src/GraphGenomeView/deletionEdges.test.ts +198 -0
- package/src/GraphGenomeView/deletionEdges.ts +217 -0
- package/src/GraphGenomeView/genes/geneFeatures.ts +107 -0
- package/src/GraphGenomeView/genes/genePins.ts +130 -0
- package/src/GraphGenomeView/genes/genes.test.ts +153 -0
- package/src/GraphGenomeView/gfa/gfaConverter.test.ts +394 -0
- package/src/GraphGenomeView/gfa/gfaConverter.ts +235 -0
- package/src/GraphGenomeView/graphLabels.budget.test.ts +36 -0
- package/src/GraphGenomeView/graphLabels.test.ts +548 -0
- package/src/GraphGenomeView/graphLabels.ts +640 -0
- package/src/GraphGenomeView/index.ts +29 -7
- package/src/GraphGenomeView/laneRamp.test.ts +97 -0
- package/src/GraphGenomeView/laneRamp.ts +87 -0
- package/src/GraphGenomeView/launchKeys.test.ts +63 -0
- package/src/GraphGenomeView/layout/anchoredLayout.test.ts +213 -0
- package/src/GraphGenomeView/layout/anchoredLayout.ts +80 -0
- package/src/GraphGenomeView/layout/bubbleCrossing.test.ts +190 -0
- package/src/GraphGenomeView/layout/drawnScale.ts +199 -0
- package/src/GraphGenomeView/layout/mergeRuns.test.ts +100 -0
- package/src/GraphGenomeView/layout/mergeRuns.ts +203 -0
- package/src/GraphGenomeView/layout/orderedLayout.test.ts +212 -0
- package/src/GraphGenomeView/layout/orderedLayout.ts +396 -0
- package/src/GraphGenomeView/layout/orientToReference.test.ts +110 -0
- package/src/GraphGenomeView/layout/orientToReference.ts +79 -0
- package/src/GraphGenomeView/layout/placeOffReference.ts +147 -0
- package/src/GraphGenomeView/layout/referenceSeeds.test.ts +79 -0
- package/src/GraphGenomeView/layout/referenceSeeds.ts +89 -0
- package/src/GraphGenomeView/layout/rowSpacing.ts +5 -0
- package/src/GraphGenomeView/layout/sampleRowLayout.test.ts +318 -0
- package/src/GraphGenomeView/layout/sampleRowLayout.ts +134 -0
- package/src/GraphGenomeView/layout/variantMapLayout.test.ts +59 -0
- package/src/GraphGenomeView/layout/variantMapLayout.ts +20 -0
- package/src/GraphGenomeView/layout/walkRowLayout.ts +54 -0
- package/src/GraphGenomeView/layout/walkRows.test.ts +178 -0
- package/src/GraphGenomeView/layout/walkRows.ts +177 -0
- package/src/GraphGenomeView/layoutModes.ts +131 -0
- package/src/GraphGenomeView/model.test.ts +2696 -0
- package/src/GraphGenomeView/model.ts +2672 -94
- package/src/GraphGenomeView/nodeWidths.ts +67 -0
- package/src/GraphGenomeView/pathAnchoring.test.ts +252 -0
- package/src/GraphGenomeView/pathAnchoring.ts +164 -0
- package/src/GraphGenomeView/pathColors.test.ts +83 -0
- package/src/GraphGenomeView/pathColors.ts +122 -0
- package/src/GraphGenomeView/referenceSpan.test.ts +84 -0
- package/src/GraphGenomeView/referenceSpan.ts +133 -0
- package/src/GraphGenomeView/renderPasses.bench.ts +172 -0
- package/src/GraphGenomeView/renderPipeline.test.ts +226 -0
- package/src/GraphGenomeView/renderer/Canvas2DRenderer.test.ts +205 -0
- package/src/GraphGenomeView/renderer/Canvas2DRenderer.ts +246 -0
- package/src/GraphGenomeView/renderer/GeometryBuilder.test.ts +723 -0
- package/src/GraphGenomeView/renderer/GeometryBuilder.ts +882 -0
- package/src/GraphGenomeView/renderer/GraphRenderer.ts +13 -0
- package/src/GraphGenomeView/renderer/colorBits.ts +43 -0
- package/src/GraphGenomeView/renderer/recordingCanvas.ts +64 -0
- package/src/GraphGenomeView/renderer/types.ts +84 -0
- package/src/GraphGenomeView/renderer/walkFade.test.ts +100 -0
- package/src/GraphGenomeView/repeats/repeatFeatures.test.ts +114 -0
- package/src/GraphGenomeView/repeats/repeatFeatures.ts +202 -0
- package/src/GraphGenomeView/repeats/test_data/abca7_trgt_walks.json +575 -0
- package/src/GraphGenomeView/repeats/walkCalls.test.ts +127 -0
- package/src/GraphGenomeView/repeats/walkCalls.ts +132 -0
- package/src/GraphGenomeView/stress.test.ts +214 -0
- package/src/GraphGenomeView/subgraphLoad.test.ts +245 -0
- package/src/GraphGenomeView/types.ts +141 -0
- package/src/GraphGenomeView/util/SpatialIndex.test.ts +73 -0
- package/src/GraphGenomeView/util/SpatialIndex.ts +312 -0
- package/src/GraphGenomeView/util/edgeCurves.test.ts +93 -0
- package/src/GraphGenomeView/util/edgeCurves.ts +94 -0
- package/src/GraphGenomeView/util/geometry.test.ts +447 -0
- package/src/GraphGenomeView/util/geometry.ts +656 -0
- package/src/GraphGenomeView/util/hitDetection.test.ts +307 -0
- package/src/GraphGenomeView/util/hitDetection.ts +330 -0
- package/src/GraphGenomeView/util/wheelZoom.test.ts +40 -0
- package/src/GraphGenomeView/util/wheelZoom.ts +26 -0
- package/src/GraphGenomeView/walkHighlight.test.ts +54 -0
- package/src/GraphGenomeView/walkHighlight.ts +56 -0
- package/src/LaunchGraphGenomeView.ts +46 -0
- package/src/MinigraphBubbleAdapter/MinigraphBubbleAdapter.test.ts +139 -0
- package/src/MinigraphBubbleAdapter/MinigraphBubbleAdapter.ts +86 -0
- package/src/MinigraphBubbleAdapter/bubbleLine.ts +92 -0
- package/src/MinigraphBubbleAdapter/configSchema.ts +109 -0
- package/src/MinigraphBubbleAdapter/index.ts +22 -0
- package/src/MinigraphBubbleAdapter/test_data/hprc_bubbles_mhc.bed.gz +0 -0
- package/src/MinigraphBubbleAdapter/test_data/hprc_bubbles_mhc.bed.gz.tbi +0 -0
- package/src/RgfaTabixAdapter/RgfaTabixAdapter.test.ts +285 -0
- package/src/RgfaTabixAdapter/RgfaTabixAdapter.ts +194 -0
- package/src/RgfaTabixAdapter/bubbleTier.test.ts +84 -0
- package/src/RgfaTabixAdapter/configSchema.ts +142 -0
- package/src/RgfaTabixAdapter/index.ts +24 -0
- package/src/RgfaTabixAdapter/rgfaBed.test.ts +158 -0
- package/src/RgfaTabixAdapter/rgfaBed.ts +156 -0
- package/src/RgfaTabixAdapter/test_data/bubble_tier_chrY.links.bed.gz +0 -0
- package/src/RgfaTabixAdapter/test_data/bubble_tier_chrY.links.bed.gz.tbi +0 -0
- package/src/RgfaTabixAdapter/test_data/bubble_tier_chrY.segs.bed.gz +0 -0
- package/src/RgfaTabixAdapter/test_data/bubble_tier_chrY.segs.bed.gz.tbi +0 -0
- package/src/RgfaTabixAdapter/test_data/pggb_ecoli.links.bed.gz +0 -0
- package/src/RgfaTabixAdapter/test_data/pggb_ecoli.links.bed.gz.tbi +0 -0
- package/src/RgfaTabixAdapter/test_data/pggb_ecoli.segs.bed.gz +0 -0
- package/src/RgfaTabixAdapter/test_data/pggb_ecoli.segs.bed.gz.tbi +0 -0
- package/src/RgfaTabixAdapter/test_data/rgfa_ecoli.links.bed.gz +0 -0
- package/src/RgfaTabixAdapter/test_data/rgfa_ecoli.links.bed.gz.tbi +0 -0
- package/src/RgfaTabixAdapter/test_data/rgfa_ecoli.segs.bed.gz +0 -0
- package/src/RgfaTabixAdapter/test_data/rgfa_ecoli.segs.bed.gz.tbi +0 -0
- package/src/alleleProjection/projectAlleles.test.ts +75 -0
- package/src/alleleProjection/projectAlleles.ts +190 -0
- package/src/bandage/README.md +121 -0
- package/src/bandage/bandage-layout.d.ts +14 -0
- package/src/bandage/bandage-layout.js +0 -0
- package/src/bandage/native/CMakeLists.txt +45 -0
- package/src/bandage/native/include/graph.h +188 -0
- package/src/bandage/native/include/graphlayout.h +74 -0
- package/src/bandage/native/include/settings.h +86 -0
- package/src/bandage/native/include/types.h +56 -0
- package/src/bandage/native/src/bindings.cpp +164 -0
- package/src/bandage/native/src/graphlayout.cpp +621 -0
- package/src/bandage/resizableDecode.test.ts +58 -0
- package/src/gfa-core/gfaParser.bench.ts +55 -0
- package/src/gfa-core/gfaParser.test.ts +168 -0
- package/src/gfa-core/gfaParser.ts +237 -0
- package/src/gfa-core/index.ts +10 -0
- package/src/hoverSync/GraphNodeHighlight.tsx +62 -0
- package/src/hoverSync/graphViewHighlights.test.ts +158 -0
- package/src/hoverSync/graphViewHighlights.ts +119 -0
- package/src/hoverSync/index.test.tsx +36 -0
- package/src/hoverSync/index.tsx +19 -0
- package/src/hoverSync/lgvHover.test.ts +202 -0
- package/src/hoverSync/lgvHover.ts +136 -0
- package/src/index.ts +46 -15
- package/src/launchFromGraph/contributors.test.ts +197 -0
- package/src/launchFromGraph/contributors.ts +0 -0
- package/src/launchFromGraph/graphMenuItems.ts +211 -0
- package/src/launchFromGraph/launchFromGraph.test.ts +489 -0
- package/src/launchFromGraph/launchFromGraph.ts +265 -0
- package/src/launchFromGraph/launchTracks.test.ts +72 -0
- package/src/launchFromGraph/launchTracks.ts +50 -0
- package/src/launchFromGraph/linearViewTarget.test.ts +82 -0
- package/src/launchFromGraph/linearViewTarget.ts +75 -0
- package/src/launchFromGraph/syntenyTracks.ts +47 -0
- package/src/launchSubgraph/index.ts +152 -0
- package/src/launchSubgraph/launchSubgraph.test.ts +264 -0
- package/src/launchSubgraph/launchSubgraphView.ts +128 -0
- package/src/launchSubgraph/linearViewMenuItems.test.ts +176 -0
- package/src/launchSubgraph/linearViewMenuItems.ts +107 -0
- package/src/launchSubgraph/multiWayLaunch.test.ts +289 -0
- package/src/launchSubgraph/subgraphMenuItems.ts +73 -0
- package/src/launchSubgraph/subgraphTracks.test.ts +165 -0
- package/src/launchSubgraph/subgraphTracks.ts +151 -0
- package/src/launchSubgraph/testEnv.ts +258 -0
- package/src/loadBandage.ts +22 -0
- package/src/panSNTabix.test.ts +72 -0
- package/src/panSNTabix.ts +149 -0
- package/src/pansn.ts +106 -0
- package/src/pluginName.ts +3 -0
- package/src/synteny/ComparativeAdapterBase.ts +77 -0
- package/src/synteny/SyntenyFeature.ts +116 -0
- package/src/synteny/clipFeatureToRegion.test.ts +329 -0
- package/src/synteny/clipFeatureToRegion.ts +167 -0
- package/src/version.ts +1 -0
- package/dist/GraphGenomeView/components/FeatureDialog.d.ts +0 -5
- package/dist/GraphGenomeView/components/FeatureDialog.js +0 -18
- package/dist/GraphGenomeView/components/FeatureDialog.js.map +0 -1
- package/dist/GraphGenomeView/components/GraphGenomeView.d.ts +0 -6
- package/dist/GraphGenomeView/components/GraphGenomeView.js +0 -17
- package/dist/GraphGenomeView/components/GraphGenomeView.js.map +0 -1
- package/dist/GraphGenomeView/components/GraphPanel.d.ts +0 -6
- package/dist/GraphGenomeView/components/GraphPanel.js +0 -17
- package/dist/GraphGenomeView/components/GraphPanel.js.map +0 -1
- package/dist/GraphGenomeView/components/Header.d.ts +0 -6
- package/dist/GraphGenomeView/components/Header.js +0 -59
- package/dist/GraphGenomeView/components/Header.js.map +0 -1
- package/dist/GraphGenomeView/components/SettingsDialog.d.ts +0 -7
- package/dist/GraphGenomeView/components/SettingsDialog.js +0 -24
- package/dist/GraphGenomeView/components/SettingsDialog.js.map +0 -1
- package/dist/GraphGenomeView/index.d.ts +0 -2
- package/dist/GraphGenomeView/index.js +0 -15
- package/dist/GraphGenomeView/index.js.map +0 -1
- package/dist/GraphGenomeView/model.d.ts +0 -80
- package/dist/GraphGenomeView/model.js +0 -96
- package/dist/GraphGenomeView/model.js.map +0 -1
- package/dist/GraphGenomeView/util.d.ts +0 -1
- package/dist/GraphGenomeView/util.js +0 -8
- package/dist/GraphGenomeView/util.js.map +0 -1
- package/dist/index.d.ts +0 -8
- package/dist/index.js +0 -24
- package/dist/index.js.map +0 -1
- package/dist/jbrowse-plugin-graphgenomeviewer.umd.production.min.js +0 -67
- package/dist/jbrowse-plugin-graphgenomeviewer.umd.production.min.js.map +0 -7
- package/src/GraphGenomeView/components/FeatureDialog.tsx +0 -51
- package/src/GraphGenomeView/components/GraphPanel.tsx +0 -37
- package/src/GraphGenomeView/components/Header.tsx +0 -90
- package/src/GraphGenomeView/components/SettingsDialog.tsx +0 -71
- package/src/GraphGenomeView/util.ts +0 -7
- package/src/declare.d.ts +0 -1
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
import { Canvas2DRenderer } from './Canvas2DRenderer'
|
|
2
|
+
import { buildGeometry } from './GeometryBuilder'
|
|
3
|
+
import { recordingCanvas } from './recordingCanvas'
|
|
4
|
+
|
|
5
|
+
import type { TransformUniform } from './types'
|
|
6
|
+
|
|
7
|
+
// isotropic: one scale for both axes, which is every layout but the row ones
|
|
8
|
+
const iso = (scale = 1) => ({ scaleX: scale, scaleY: scale })
|
|
9
|
+
|
|
10
|
+
function makeRenderer() {
|
|
11
|
+
const { canvas, strokes, fills, lineWidths, points } = recordingCanvas()
|
|
12
|
+
return {
|
|
13
|
+
renderer: new Canvas2DRenderer(canvas),
|
|
14
|
+
strokes,
|
|
15
|
+
fills,
|
|
16
|
+
lineWidths,
|
|
17
|
+
points,
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const TRANSFORM: TransformUniform = {
|
|
22
|
+
scaleX: 1,
|
|
23
|
+
scaleY: 1,
|
|
24
|
+
translateX: 0,
|
|
25
|
+
translateY: 0,
|
|
26
|
+
dpr: 1,
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// three nodes in a row wired A -> B -> C, so edge 0 and edge 1 are distinct
|
|
30
|
+
// strokes and a highlight on one must not touch the other
|
|
31
|
+
const nodes = [
|
|
32
|
+
{ id: 'A+', name: 'A', length: 10, depth: 1 },
|
|
33
|
+
{ id: 'B+', name: 'B', length: 10, depth: 1 },
|
|
34
|
+
{ id: 'C+', name: 'C', length: 10, depth: 1 },
|
|
35
|
+
]
|
|
36
|
+
|
|
37
|
+
function batchOf2Edges() {
|
|
38
|
+
return buildGeometry({
|
|
39
|
+
nodePositions: {
|
|
40
|
+
'A+': [
|
|
41
|
+
{ x: 0, y: 0 },
|
|
42
|
+
{ x: 10, y: 0 },
|
|
43
|
+
],
|
|
44
|
+
'B+': [
|
|
45
|
+
{ x: 40, y: 0 },
|
|
46
|
+
{ x: 50, y: 0 },
|
|
47
|
+
],
|
|
48
|
+
'C+': [
|
|
49
|
+
{ x: 80, y: 0 },
|
|
50
|
+
{ x: 90, y: 0 },
|
|
51
|
+
],
|
|
52
|
+
},
|
|
53
|
+
graph: {
|
|
54
|
+
name: 'test',
|
|
55
|
+
nodes,
|
|
56
|
+
edges: [
|
|
57
|
+
{ from: 'A+', to: 'B+' },
|
|
58
|
+
{ from: 'B+', to: 'C+' },
|
|
59
|
+
],
|
|
60
|
+
},
|
|
61
|
+
nodeById: new Map(nodes.map(n => [n.id, n])),
|
|
62
|
+
colorScheme: 'uniform',
|
|
63
|
+
contigThickness: 10,
|
|
64
|
+
connectorThickness: 4,
|
|
65
|
+
drawPaths: false,
|
|
66
|
+
axis: iso(),
|
|
67
|
+
})
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function channels(rgba: string) {
|
|
71
|
+
const m = /rgba\((\d+),(\d+),(\d+)/.exec(rgba)
|
|
72
|
+
return [Number(m?.[1]), Number(m?.[2]), Number(m?.[3])]
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function expectBrighter(a: string, b: string) {
|
|
76
|
+
const [r, g, bl] = channels(a)
|
|
77
|
+
const [br, bg, bb] = channels(b)
|
|
78
|
+
expect(r).toBeGreaterThan(br!)
|
|
79
|
+
expect(g).toBeGreaterThan(bg!)
|
|
80
|
+
expect(bl).toBeGreaterThan(bb!)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Two edges of one colour and weight, three nodes of one colour: a drawing is
|
|
84
|
+
// mostly runs of one paint, and each run is one path. This is the whole
|
|
85
|
+
// performance story of the renderer, so it is pinned exactly.
|
|
86
|
+
test('strokes every edge of one paint as one path, and every node likewise', () => {
|
|
87
|
+
const { renderer, strokes } = makeRenderer()
|
|
88
|
+
renderer.uploadGeometry(batchOf2Edges())
|
|
89
|
+
renderer.updateTransform(TRANSFORM)
|
|
90
|
+
renderer.render([1, 1, 1, 1])
|
|
91
|
+
|
|
92
|
+
expect(strokes).toHaveLength(2)
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
test('a highlighted edge is stroked brighter, apart from its neighbour', () => {
|
|
96
|
+
const { renderer, strokes } = makeRenderer()
|
|
97
|
+
renderer.uploadGeometry(batchOf2Edges())
|
|
98
|
+
renderer.updateTransform(TRANSFORM)
|
|
99
|
+
renderer.render([1, 1, 1, 1])
|
|
100
|
+
const [edges, nodeStroke] = strokes as [string, string]
|
|
101
|
+
|
|
102
|
+
strokes.length = 0
|
|
103
|
+
renderer.setEdgeHighlight(1, 1.6)
|
|
104
|
+
renderer.render([1, 1, 1, 1])
|
|
105
|
+
|
|
106
|
+
// edge 0 in its own path, then the nodes, then edge 1 on top of everything
|
|
107
|
+
expect(strokes).toEqual([edges, expect.any(String), nodeStroke])
|
|
108
|
+
expect(strokes[1]).not.toBe(edges)
|
|
109
|
+
expectBrighter(strokes[1]!, edges)
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
test('clearing the highlight restores the base stroke', () => {
|
|
113
|
+
const { renderer, strokes } = makeRenderer()
|
|
114
|
+
renderer.uploadGeometry(batchOf2Edges())
|
|
115
|
+
renderer.updateTransform(TRANSFORM)
|
|
116
|
+
renderer.setEdgeHighlight(0, 1.6)
|
|
117
|
+
renderer.render([1, 1, 1, 1])
|
|
118
|
+
expect(strokes).toHaveLength(3)
|
|
119
|
+
|
|
120
|
+
strokes.length = 0
|
|
121
|
+
renderer.setEdgeHighlight(null, 1.6)
|
|
122
|
+
renderer.render([1, 1, 1, 1])
|
|
123
|
+
|
|
124
|
+
expect(strokes).toHaveLength(2)
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
// A rebuild renumbers the strokes, so an edge captured against the old batch
|
|
128
|
+
// could brighten an unrelated one. The model re-applies the current hover after
|
|
129
|
+
// every upload; the renderer's job is not to keep pointing at a stale run.
|
|
130
|
+
test('uploading a new batch drops the previous edge highlight', () => {
|
|
131
|
+
const { renderer, strokes } = makeRenderer()
|
|
132
|
+
renderer.uploadGeometry(batchOf2Edges())
|
|
133
|
+
renderer.updateTransform(TRANSFORM)
|
|
134
|
+
renderer.setEdgeHighlight(1, 1.6)
|
|
135
|
+
renderer.render([1, 1, 1, 1])
|
|
136
|
+
expect(strokes).toHaveLength(3)
|
|
137
|
+
|
|
138
|
+
strokes.length = 0
|
|
139
|
+
renderer.uploadGeometry(batchOf2Edges())
|
|
140
|
+
renderer.render([1, 1, 1, 1])
|
|
141
|
+
|
|
142
|
+
expect(strokes).toHaveLength(2)
|
|
143
|
+
})
|
|
144
|
+
|
|
145
|
+
// Node highlights are keyed by id rather than by position in the batch, so
|
|
146
|
+
// they survive a rebuild on their own and the hovered node lands on top.
|
|
147
|
+
test('a highlighted node is stroked brighter, last, and survives a rebuild', () => {
|
|
148
|
+
const { renderer, strokes } = makeRenderer()
|
|
149
|
+
renderer.uploadGeometry(batchOf2Edges())
|
|
150
|
+
renderer.updateTransform(TRANSFORM)
|
|
151
|
+
renderer.render([1, 1, 1, 1])
|
|
152
|
+
const nodeStroke = strokes[1]!
|
|
153
|
+
|
|
154
|
+
strokes.length = 0
|
|
155
|
+
renderer.setNodeHighlights(new Map([['B+', 1.4]]))
|
|
156
|
+
renderer.render([1, 1, 1, 1])
|
|
157
|
+
expect(strokes).toHaveLength(3)
|
|
158
|
+
expect(strokes[1]).toBe(nodeStroke)
|
|
159
|
+
expectBrighter(strokes[2]!, nodeStroke)
|
|
160
|
+
|
|
161
|
+
strokes.length = 0
|
|
162
|
+
renderer.uploadGeometry(batchOf2Edges())
|
|
163
|
+
renderer.render([1, 1, 1, 1])
|
|
164
|
+
expect(strokes).toHaveLength(3)
|
|
165
|
+
})
|
|
166
|
+
|
|
167
|
+
// A thickness is quoted in CSS pixels and expanded AFTER the transform, so it
|
|
168
|
+
// is the one term the dpr-scaled transform does not reach. Left alone, every
|
|
169
|
+
// tube, connector and arrowhead came out 1/dpr of its weight on a hidpi
|
|
170
|
+
// display, with the positions between them correct.
|
|
171
|
+
//
|
|
172
|
+
// Asserted as "twice the backing-store pixels at twice the ratio", which is the
|
|
173
|
+
// same drawing in css px. The whole point is that a figure does not change
|
|
174
|
+
// weight with the machine it is opened on.
|
|
175
|
+
describe('a thickness is css pixels, whatever the device ratio', () => {
|
|
176
|
+
function drawn(dpr: number) {
|
|
177
|
+
const { renderer, points, lineWidths } = makeRenderer()
|
|
178
|
+
renderer.uploadGeometry(batchOf2Edges())
|
|
179
|
+
renderer.updateTransform({ ...TRANSFORM, scaleX: dpr, scaleY: dpr, dpr })
|
|
180
|
+
renderer.render([1, 1, 1, 1])
|
|
181
|
+
const xs = points.map(p => p.x)
|
|
182
|
+
return {
|
|
183
|
+
spanPx: Math.max(...xs) - Math.min(...xs),
|
|
184
|
+
edgeWidth: lineWidths[0]!,
|
|
185
|
+
nodeWidth: lineWidths[1]!,
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
test('positions go through the transform', () => {
|
|
190
|
+
// three 10-unit nodes over a 90-unit span
|
|
191
|
+
expect(drawn(1).spanPx).toBeCloseTo(90, 5)
|
|
192
|
+
expect(drawn(2).spanPx).toBeCloseTo(180, 5)
|
|
193
|
+
})
|
|
194
|
+
|
|
195
|
+
test('a node stroke scales its width with the ratio', () => {
|
|
196
|
+
expect(drawn(1).nodeWidth).toBeCloseTo(10, 5)
|
|
197
|
+
expect(drawn(2).nodeWidth).toBeCloseTo(20, 5)
|
|
198
|
+
})
|
|
199
|
+
|
|
200
|
+
test('an edge stroke scales its width with the ratio', () => {
|
|
201
|
+
// connectorThickness 4 is a half-width of 2, so a 4 px stroke at ratio 1
|
|
202
|
+
expect(drawn(1).edgeWidth).toBeCloseTo(4, 5)
|
|
203
|
+
expect(drawn(2).edgeWidth).toBeCloseTo(8, 5)
|
|
204
|
+
})
|
|
205
|
+
})
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
import {
|
|
2
|
+
abgrToCssRgba,
|
|
3
|
+
normalizedRgbToCssRgba,
|
|
4
|
+
} from '@jbrowse/core/util/colorBits'
|
|
5
|
+
import { syncCanvasSize } from '@jbrowse/render-core/canvas2dUtils'
|
|
6
|
+
import { Canvas2DRenderingBackendBase } from '@jbrowse/render-core/renderingBackendBase'
|
|
7
|
+
|
|
8
|
+
import { brightenAbgr } from './colorBits'
|
|
9
|
+
|
|
10
|
+
import type { RenderBatch, Renderer, TransformUniform } from './types'
|
|
11
|
+
|
|
12
|
+
// Everything is drawn as a handful of paths, one per distinct (colour, weight),
|
|
13
|
+
// rather than one path per thing. A drawing is mostly runs of one colour, and
|
|
14
|
+
// Canvas2D's cost is per path verb and per fill or stroke, not per pixel: the
|
|
15
|
+
// same 15k-node graph took 414 ms a frame as one fill per mesh triangle and 11
|
|
16
|
+
// ms as batched strokes (agent-docs/GRAPH_SCALE_AND_LOD.md). Highlighted things
|
|
17
|
+
// are drawn last, in their own paths, so they sit on top of what they brighten.
|
|
18
|
+
//
|
|
19
|
+
// Extends the shared Canvas2D base rather than standing alone, which is how
|
|
20
|
+
// `setErrorHandler` arrives — `useRenderingBackend` requires it. The base's
|
|
21
|
+
// no-op implementation is the correct one here: Canvas2D allocates no GPU
|
|
22
|
+
// resources, so there is no OOM channel to forward.
|
|
23
|
+
export class Canvas2DRenderer
|
|
24
|
+
extends Canvas2DRenderingBackendBase
|
|
25
|
+
implements Renderer
|
|
26
|
+
{
|
|
27
|
+
private transform: TransformUniform | null = null
|
|
28
|
+
private batch: RenderBatch | null = null
|
|
29
|
+
private nodeHighlights: ReadonlyMap<string, number> = new Map()
|
|
30
|
+
private highlightedEdge: number | null = null
|
|
31
|
+
private highlightFactor = 1
|
|
32
|
+
|
|
33
|
+
// render-core's, not a local `width * devicePixelRatio`: it clamps the
|
|
34
|
+
// backing store at MAX_CANVAS_DIM_PX, reads the ratio through `getDpr()` so
|
|
35
|
+
// this agrees with the transform the model builds, and writes the css size
|
|
36
|
+
// independently of the backing size.
|
|
37
|
+
resize(width: number, height: number) {
|
|
38
|
+
syncCanvasSize(this.ctx.canvas, width, height)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
uploadGeometry(batch: RenderBatch) {
|
|
42
|
+
this.batch = batch
|
|
43
|
+
// A rebuild renumbers the strokes, so the old edge no longer addresses the
|
|
44
|
+
// same run; the model re-applies the current hover against the new batch.
|
|
45
|
+
this.highlightedEdge = null
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
setNodeHighlights(factors: ReadonlyMap<string, number>) {
|
|
49
|
+
this.nodeHighlights = factors
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
setEdgeHighlight(edgeIndex: number | null, factor: number) {
|
|
53
|
+
this.highlightedEdge = edgeIndex
|
|
54
|
+
this.highlightFactor = factor
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
updateTransform(transform: TransformUniform) {
|
|
58
|
+
this.transform = transform
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
render(clearColor: [number, number, number, number]) {
|
|
62
|
+
const t = this.transform
|
|
63
|
+
if (!t) {
|
|
64
|
+
return
|
|
65
|
+
}
|
|
66
|
+
const ctx = this.ctx
|
|
67
|
+
const { width, height } = ctx.canvas
|
|
68
|
+
|
|
69
|
+
ctx.fillStyle = normalizedRgbToCssRgba(
|
|
70
|
+
[clearColor[0], clearColor[1], clearColor[2]],
|
|
71
|
+
clearColor[3],
|
|
72
|
+
)
|
|
73
|
+
ctx.fillRect(0, 0, width, height)
|
|
74
|
+
|
|
75
|
+
const batch = this.batch
|
|
76
|
+
if (!batch) {
|
|
77
|
+
return
|
|
78
|
+
}
|
|
79
|
+
ctx.lineCap = 'round'
|
|
80
|
+
ctx.lineJoin = 'round'
|
|
81
|
+
this.renderEdges(batch)
|
|
82
|
+
this.renderNodes(batch)
|
|
83
|
+
this.renderArrows(batch)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
private renderEdges(batch: RenderBatch) {
|
|
87
|
+
const t = this.transform!
|
|
88
|
+
const ctx = this.ctx
|
|
89
|
+
const hl =
|
|
90
|
+
this.highlightedEdge === null
|
|
91
|
+
? undefined
|
|
92
|
+
: batch.edgeCurveRuns.get(this.highlightedEdge)
|
|
93
|
+
for (const { color, items } of groupByPaint(batch.edgeCurves, (e, i) => ({
|
|
94
|
+
color: inRun(hl, i)
|
|
95
|
+
? brightenAbgr(e.color, this.highlightFactor)
|
|
96
|
+
: e.color,
|
|
97
|
+
weight: e.thickness,
|
|
98
|
+
last: inRun(hl, i),
|
|
99
|
+
}))) {
|
|
100
|
+
ctx.strokeStyle = abgrToCssRgba(color)
|
|
101
|
+
// thickness is the half-width in css px, so the stroke is twice it at
|
|
102
|
+
// the device ratio the transform already carries
|
|
103
|
+
ctx.lineWidth = items[0]!.thickness * 2 * t.dpr
|
|
104
|
+
ctx.beginPath()
|
|
105
|
+
for (const e of items) {
|
|
106
|
+
const first = e.curves[0]!
|
|
107
|
+
ctx.moveTo(
|
|
108
|
+
first.x0 * t.scaleX + t.translateX,
|
|
109
|
+
first.y0 * t.scaleY + t.translateY,
|
|
110
|
+
)
|
|
111
|
+
for (const c of e.curves) {
|
|
112
|
+
ctx.bezierCurveTo(
|
|
113
|
+
c.cx0 * t.scaleX + t.translateX,
|
|
114
|
+
c.cy0 * t.scaleY + t.translateY,
|
|
115
|
+
c.cx1 * t.scaleX + t.translateX,
|
|
116
|
+
c.cy1 * t.scaleY + t.translateY,
|
|
117
|
+
c.x1 * t.scaleX + t.translateX,
|
|
118
|
+
c.y1 * t.scaleY + t.translateY,
|
|
119
|
+
)
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
ctx.stroke()
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
private renderNodes(batch: RenderBatch) {
|
|
127
|
+
const t = this.transform!
|
|
128
|
+
const ctx = this.ctx
|
|
129
|
+
// stroke index -> brighten factor, for the nodes the model lifted
|
|
130
|
+
const lifted = new Map<number, number>()
|
|
131
|
+
for (const [nodeId, factor] of this.nodeHighlights) {
|
|
132
|
+
const run = batch.nodeStrokeRuns.get(nodeId)
|
|
133
|
+
if (run) {
|
|
134
|
+
for (let i = run.start; i < run.start + run.count; i++) {
|
|
135
|
+
lifted.set(i, factor)
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
for (const { color, items } of groupByPaint(batch.nodeStrokes, (s, i) => {
|
|
140
|
+
const factor = lifted.get(i)
|
|
141
|
+
return {
|
|
142
|
+
color: factor === undefined ? s.color : brightenAbgr(s.color, factor),
|
|
143
|
+
weight: s.thickness,
|
|
144
|
+
last: factor !== undefined,
|
|
145
|
+
}
|
|
146
|
+
})) {
|
|
147
|
+
ctx.strokeStyle = abgrToCssRgba(color)
|
|
148
|
+
ctx.lineWidth = items[0]!.thickness * 2 * t.dpr
|
|
149
|
+
ctx.beginPath()
|
|
150
|
+
for (const s of items) {
|
|
151
|
+
const p0 = s.points[0]!
|
|
152
|
+
ctx.moveTo(
|
|
153
|
+
p0.x * t.scaleX + t.translateX,
|
|
154
|
+
p0.y * t.scaleY + t.translateY,
|
|
155
|
+
)
|
|
156
|
+
for (let i = 1, l = s.points.length; i < l; i++) {
|
|
157
|
+
const p = s.points[i]!
|
|
158
|
+
ctx.lineTo(
|
|
159
|
+
p.x * t.scaleX + t.translateX,
|
|
160
|
+
p.y * t.scaleY + t.translateY,
|
|
161
|
+
)
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
ctx.stroke()
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
private renderArrows(batch: RenderBatch) {
|
|
169
|
+
const t = this.transform!
|
|
170
|
+
const ctx = this.ctx
|
|
171
|
+
const hl =
|
|
172
|
+
this.highlightedEdge === null
|
|
173
|
+
? undefined
|
|
174
|
+
: batch.arrowRuns.get(this.highlightedEdge)
|
|
175
|
+
for (const { color, items } of groupByPaint(batch.arrows, (a, i) => ({
|
|
176
|
+
color: inRun(hl, i)
|
|
177
|
+
? brightenAbgr(a.color, this.highlightFactor)
|
|
178
|
+
: a.color,
|
|
179
|
+
weight: 0,
|
|
180
|
+
last: inRun(hl, i),
|
|
181
|
+
}))) {
|
|
182
|
+
ctx.fillStyle = abgrToCssRgba(color)
|
|
183
|
+
ctx.beginPath()
|
|
184
|
+
for (const a of items) {
|
|
185
|
+
// The tip sits on the edge's end; the two barbs are `size` css px back
|
|
186
|
+
// along either side of the tangent, expanded after the transform so
|
|
187
|
+
// the head is the same size at every zoom.
|
|
188
|
+
const tipX = a.x * t.scaleX + t.translateX
|
|
189
|
+
const tipY = a.y * t.scaleY + t.translateY
|
|
190
|
+
const reach = a.size * t.dpr
|
|
191
|
+
ctx.moveTo(tipX, tipY)
|
|
192
|
+
ctx.lineTo(
|
|
193
|
+
tipX - Math.cos(a.angle - 0.5) * reach,
|
|
194
|
+
tipY - Math.sin(a.angle - 0.5) * reach,
|
|
195
|
+
)
|
|
196
|
+
ctx.lineTo(
|
|
197
|
+
tipX - Math.cos(a.angle + 0.5) * reach,
|
|
198
|
+
tipY - Math.sin(a.angle + 0.5) * reach,
|
|
199
|
+
)
|
|
200
|
+
ctx.closePath()
|
|
201
|
+
}
|
|
202
|
+
ctx.fill()
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
override dispose() {
|
|
207
|
+
super.dispose()
|
|
208
|
+
this.batch = null
|
|
209
|
+
this.nodeHighlights = new Map()
|
|
210
|
+
this.highlightedEdge = null
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function inRun(run: { start: number; count: number } | undefined, i: number) {
|
|
215
|
+
return run !== undefined && i >= run.start && i < run.start + run.count
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
interface Paint {
|
|
219
|
+
color: number
|
|
220
|
+
weight: number
|
|
221
|
+
// drawn after everything else, so a highlight lands on top of its neighbours
|
|
222
|
+
last: boolean
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// Items bucketed by what they are painted with, in first-seen order, with the
|
|
226
|
+
// `last` ones after the rest.
|
|
227
|
+
function groupByPaint<T>(
|
|
228
|
+
items: T[],
|
|
229
|
+
paintOf: (item: T, index: number) => Paint,
|
|
230
|
+
) {
|
|
231
|
+
const groups = new Map<string, { color: number; items: T[] }>()
|
|
232
|
+
const deferred = new Map<string, { color: number; items: T[] }>()
|
|
233
|
+
for (let i = 0, l = items.length; i < l; i++) {
|
|
234
|
+
const item = items[i]!
|
|
235
|
+
const { color, weight, last } = paintOf(item, i)
|
|
236
|
+
const into = last ? deferred : groups
|
|
237
|
+
const key = `${color}:${weight}`
|
|
238
|
+
const group = into.get(key)
|
|
239
|
+
if (group) {
|
|
240
|
+
group.items.push(item)
|
|
241
|
+
} else {
|
|
242
|
+
into.set(key, { color, items: [item] })
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
return [...groups.values(), ...deferred.values()]
|
|
246
|
+
}
|