jbrowse-plugin-mafviewer 1.4.2 → 1.4.5

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.
Files changed (100) hide show
  1. package/README.md +1 -1
  2. package/dist/LinearMafDisplay/components/InsertionSequenceDialog/InsertionSequenceDialog.d.ts +14 -0
  3. package/dist/LinearMafDisplay/components/InsertionSequenceDialog/InsertionSequenceDialog.js +69 -0
  4. package/dist/LinearMafDisplay/components/InsertionSequenceDialog/InsertionSequenceDialog.js.map +1 -0
  5. package/dist/LinearMafDisplay/components/LinearMafDisplayComponent.js +4 -4
  6. package/dist/LinearMafDisplay/components/LinearMafDisplayComponent.js.map +1 -1
  7. package/dist/LinearMafDisplay/components/Sidebar/ColorLegend.js +2 -2
  8. package/dist/LinearMafDisplay/components/Sidebar/ColorLegend.js.map +1 -1
  9. package/dist/LinearMafDisplay/components/Sidebar/RectBg.d.ts +1 -1
  10. package/dist/LinearMafDisplay/components/Sidebar/RectBg.js +2 -3
  11. package/dist/LinearMafDisplay/components/Sidebar/RectBg.js.map +1 -1
  12. package/dist/LinearMafDisplay/components/Sidebar/SvgWrapper.js +81 -11
  13. package/dist/LinearMafDisplay/components/Sidebar/SvgWrapper.js.map +1 -1
  14. package/dist/LinearMafDisplay/components/Sidebar/Tree.js +30 -9
  15. package/dist/LinearMafDisplay/components/Sidebar/Tree.js.map +1 -1
  16. package/dist/LinearMafDisplay/components/Sidebar/YScaleBars.d.ts +0 -1
  17. package/dist/LinearMafDisplay/components/Sidebar/YScaleBars.js.map +1 -1
  18. package/dist/LinearMafDisplay/renderSvg.js +1 -1
  19. package/dist/LinearMafDisplay/renderSvg.js.map +1 -1
  20. package/dist/LinearMafDisplay/stateModel.d.ts +69 -3
  21. package/dist/LinearMafDisplay/stateModel.js +96 -1
  22. package/dist/LinearMafDisplay/stateModel.js.map +1 -1
  23. package/dist/LinearMafDisplay/util.d.ts +1 -0
  24. package/dist/LinearMafDisplay/util.js +3 -2
  25. package/dist/LinearMafDisplay/util.js.map +1 -1
  26. package/dist/LinearMafRenderer/LinearMafRenderer.d.ts +4 -7
  27. package/dist/LinearMafRenderer/LinearMafRenderer.js.map +1 -1
  28. package/dist/LinearMafRenderer/components/LinearMafRendering.d.ts +1 -1
  29. package/dist/LinearMafRenderer/components/LinearMafRendering.js +27 -9
  30. package/dist/LinearMafRenderer/components/LinearMafRendering.js.map +1 -1
  31. package/dist/LinearMafRenderer/makeImageData.js +13 -9
  32. package/dist/LinearMafRenderer/makeImageData.js.map +1 -1
  33. package/dist/LinearMafRenderer/rendering/features.d.ts +0 -1
  34. package/dist/LinearMafRenderer/rendering/features.js +1 -14
  35. package/dist/LinearMafRenderer/rendering/features.js.map +1 -1
  36. package/dist/LinearMafRenderer/rendering/insertions.js +8 -6
  37. package/dist/LinearMafRenderer/rendering/insertions.js.map +1 -1
  38. package/dist/LinearMafRenderer/rendering/matches.d.ts +1 -1
  39. package/dist/LinearMafRenderer/rendering/matches.js +3 -15
  40. package/dist/LinearMafRenderer/rendering/matches.js.map +1 -1
  41. package/dist/LinearMafRenderer/rendering/spatialIndex.js +8 -2
  42. package/dist/LinearMafRenderer/rendering/spatialIndex.js.map +1 -1
  43. package/dist/LinearMafRenderer/rendering/text.js +1 -3
  44. package/dist/LinearMafRenderer/rendering/text.js.map +1 -1
  45. package/dist/LinearMafRenderer/rendering/types.d.ts +5 -4
  46. package/dist/jbrowse-plugin-mafviewer.umd.production.min.js +7 -7
  47. package/dist/jbrowse-plugin-mafviewer.umd.production.min.js.map +4 -4
  48. package/dist/util/fastaUtils.js +23 -50
  49. package/dist/util/fastaUtils.js.map +1 -1
  50. package/package.json +9 -9
  51. package/src/LinearMafDisplay/components/InsertionSequenceDialog/InsertionSequenceDialog.tsx +105 -0
  52. package/src/LinearMafDisplay/components/LinearMafDisplayComponent.tsx +4 -4
  53. package/src/LinearMafDisplay/components/Sidebar/ColorLegend.tsx +2 -6
  54. package/src/LinearMafDisplay/components/Sidebar/RectBg.tsx +8 -3
  55. package/src/LinearMafDisplay/components/Sidebar/SvgWrapper.tsx +117 -15
  56. package/src/LinearMafDisplay/components/Sidebar/Tree.tsx +53 -8
  57. package/src/LinearMafDisplay/components/Sidebar/YScaleBars.tsx +0 -1
  58. package/src/LinearMafDisplay/renderSvg.tsx +1 -1
  59. package/src/LinearMafDisplay/stateModel.ts +109 -1
  60. package/src/LinearMafDisplay/util.ts +4 -2
  61. package/src/LinearMafRenderer/LinearMafRenderer.ts +2 -4
  62. package/src/LinearMafRenderer/components/LinearMafRendering.tsx +51 -30
  63. package/src/LinearMafRenderer/makeImageData.ts +21 -27
  64. package/src/LinearMafRenderer/rendering/features.ts +2 -36
  65. package/src/LinearMafRenderer/rendering/insertions.ts +11 -6
  66. package/src/LinearMafRenderer/rendering/matches.ts +2 -27
  67. package/src/LinearMafRenderer/rendering/spatialIndex.ts +9 -2
  68. package/src/LinearMafRenderer/rendering/text.ts +1 -2
  69. package/src/LinearMafRenderer/rendering/types.ts +7 -4
  70. package/src/util/fastaUtils.ts +28 -54
  71. package/dist/BgzipTaffyAdapter/BgzipTaffyAdapter.d.ts +0 -30
  72. package/dist/BgzipTaffyAdapter/BgzipTaffyAdapter.js +0 -253
  73. package/dist/BgzipTaffyAdapter/BgzipTaffyAdapter.js.map +0 -1
  74. package/dist/BgzipTaffyAdapter/configSchema.d.ts +0 -44
  75. package/dist/BgzipTaffyAdapter/configSchema.js +0 -53
  76. package/dist/BgzipTaffyAdapter/configSchema.js.map +0 -1
  77. package/dist/BgzipTaffyAdapter/index.d.ts +0 -2
  78. package/dist/BgzipTaffyAdapter/index.js +0 -11
  79. package/dist/BgzipTaffyAdapter/index.js.map +0 -1
  80. package/dist/BgzipTaffyAdapter/rowInstructions.d.ts +0 -35
  81. package/dist/BgzipTaffyAdapter/rowInstructions.js +0 -55
  82. package/dist/BgzipTaffyAdapter/rowInstructions.js.map +0 -1
  83. package/dist/BgzipTaffyAdapter/types.d.ts +0 -13
  84. package/dist/BgzipTaffyAdapter/types.js +0 -2
  85. package/dist/BgzipTaffyAdapter/types.js.map +0 -1
  86. package/dist/BgzipTaffyAdapter/util.d.ts +0 -1
  87. package/dist/BgzipTaffyAdapter/util.js +0 -22
  88. package/dist/BgzipTaffyAdapter/util.js.map +0 -1
  89. package/dist/BgzipTaffyAdapter/virtualOffset.d.ts +0 -8
  90. package/dist/BgzipTaffyAdapter/virtualOffset.js +0 -23
  91. package/dist/BgzipTaffyAdapter/virtualOffset.js.map +0 -1
  92. package/dist/LinearMafRenderer/components/ReactComponent.d.ts +0 -9
  93. package/dist/LinearMafRenderer/components/ReactComponent.js +0 -47
  94. package/dist/LinearMafRenderer/components/ReactComponent.js.map +0 -1
  95. package/dist/LinearMafRenderer/components/util.d.ts +0 -1
  96. package/dist/LinearMafRenderer/components/util.js +0 -13
  97. package/dist/LinearMafRenderer/components/util.js.map +0 -1
  98. package/dist/out.js +0 -32303
  99. package/dist/out.js.map +0 -7
  100. package/src/LinearMafRenderer/components/util.ts +0 -13
@@ -8,69 +8,41 @@ export function processFeaturesToFasta({ regions, showAllLetters, samples, featu
8
8
  const region = regions[0];
9
9
  const sampleToRowMap = new Map(samples.map((s, i) => [s.id, i]));
10
10
  const rlen = region.end - region.start;
11
- const outputRows = samples.map(() => '-'.repeat(rlen));
11
+ // Use character arrays instead of strings for O(1) mutations
12
+ const outputRowsArrays = samples.map(() => new Array(rlen).fill('-'));
12
13
  for (const feature of features.values()) {
13
14
  const leftCoord = feature.get('start');
14
15
  const vals = feature.get('alignments');
15
16
  const seq = feature.get('seq');
16
17
  for (const [sample, val] of Object.entries(vals)) {
17
- const origAlignment = val.seq;
18
- const alignment = origAlignment;
18
+ const alignment = val.seq;
19
19
  const row = sampleToRowMap.get(sample);
20
20
  if (row === undefined) {
21
21
  continue;
22
22
  }
23
- // gaps
23
+ const rowArray = outputRowsArrays[row];
24
+ // Single-pass processing: handle gaps, matches, and mismatches together
24
25
  for (let i = 0, o = 0, l = alignment.length; i < l; i++) {
25
26
  if (seq[i] !== '-') {
26
- if (alignment[i] === '-') {
27
- const l = leftCoord + o - region.start;
28
- if (l >= 0 && l < rlen) {
29
- outputRows[row] =
30
- outputRows[row].slice(0, l) +
31
- '-' +
32
- outputRows[row].slice(l + 1);
27
+ const c = alignment[i];
28
+ const pos = leftCoord + o - region.start;
29
+ if (pos >= 0 && pos < rlen) {
30
+ if (c === '-') {
31
+ // Gap
32
+ rowArray[pos] = '-';
33
33
  }
34
- }
35
- o++;
36
- }
37
- }
38
- if (!showAllLetters) {
39
- // matches
40
- for (let i = 0, o = 0, l = alignment.length; i < l; i++) {
41
- if (seq[i] !== '-') {
42
- const c = alignment[i];
43
- const l = leftCoord + o - region.start;
44
- if (l >= 0 && l < rlen) {
45
- if (seq[i] === c && c !== '-' && c !== ' ') {
46
- outputRows[row] =
47
- outputRows[row].slice(0, l) +
48
- '.' +
49
- outputRows[row].slice(l + 1);
34
+ else if (c !== ' ') {
35
+ if (showAllLetters) {
36
+ // Show all letters mode: write character directly
37
+ rowArray[pos] = c;
50
38
  }
51
- }
52
- o++;
53
- }
54
- }
55
- }
56
- // mismatches
57
- for (let i = 0, o = 0, l = alignment.length; i < l; i++) {
58
- const c = alignment[i];
59
- if (seq[i] !== '-') {
60
- if (c !== '-') {
61
- const l = leftCoord + o - region.start;
62
- if (l >= 0 && l < rlen) {
63
- if (seq[i] !== c && c !== ' ') {
64
- outputRows[row] =
65
- outputRows[row].slice(0, l) +
66
- c +
67
- outputRows[row].slice(l + 1);
39
+ else if (seq[i] === c) {
40
+ // Match: use dot notation
41
+ rowArray[pos] = '.';
68
42
  }
69
- else if (showAllLetters) {
70
- outputRows[row] =
71
- outputRows[row].slice(0, l) +
72
- c +
73
- outputRows[row].slice(l + 1);
43
+ else {
44
+ // Mismatch: write character
45
+ rowArray[pos] = c;
74
46
  }
75
47
  }
76
48
  }
@@ -79,6 +51,7 @@ export function processFeaturesToFasta({ regions, showAllLetters, samples, featu
79
51
  }
80
52
  }
81
53
  }
82
- return outputRows;
54
+ // Convert character arrays back to strings
55
+ return outputRowsArrays.map(arr => arr.join(''));
83
56
  }
84
57
  //# sourceMappingURL=fastaUtils.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"fastaUtils.js","sourceRoot":"","sources":["../../src/util/fastaUtils.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"fastaUtils.js","sourceRoot":"","sources":["../../src/util/fastaUtils.ts"],"names":[],"mappings":"AAKA;;;;;GAKG;AACH,MAAM,UAAU,sBAAsB,CAAC,EACrC,OAAO,EACP,cAAc,EACd,OAAO,EACP,QAAQ,GAQT;IACC,MAAM,MAAM,GAAG,OAAO,CAAC,CAAC,CAAE,CAAA;IAC1B,MAAM,cAAc,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAA;IAChE,MAAM,IAAI,GAAG,MAAM,CAAC,GAAG,GAAG,MAAM,CAAC,KAAK,CAAA;IAEtC,6DAA6D;IAC7D,MAAM,gBAAgB,GAAG,OAAO,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,IAAI,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAA;IAErE,KAAK,MAAM,OAAO,IAAI,QAAQ,CAAC,MAAM,EAAE,EAAE,CAAC;QACxC,MAAM,SAAS,GAAG,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,CAAA;QACtC,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,YAAY,CAAoC,CAAA;QACzE,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,CAAA;QAE9B,KAAK,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;YACjD,MAAM,SAAS,GAAG,GAAG,CAAC,GAAG,CAAA;YACzB,MAAM,GAAG,GAAG,cAAc,CAAC,GAAG,CAAC,MAAM,CAAC,CAAA;YACtC,IAAI,GAAG,KAAK,SAAS,EAAE,CAAC;gBACtB,SAAQ;YACV,CAAC;YAED,MAAM,QAAQ,GAAG,gBAAgB,CAAC,GAAG,CAAE,CAAA;YAEvC,wEAAwE;YACxE,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,SAAS,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;gBACxD,IAAI,GAAG,CAAC,CAAC,CAAC,KAAK,GAAG,EAAE,CAAC;oBACnB,MAAM,CAAC,GAAG,SAAS,CAAC,CAAC,CAAC,CAAA;oBACtB,MAAM,GAAG,GAAG,SAAS,GAAG,CAAC,GAAG,MAAM,CAAC,KAAK,CAAA;oBAExC,IAAI,GAAG,IAAI,CAAC,IAAI,GAAG,GAAG,IAAI,EAAE,CAAC;wBAC3B,IAAI,CAAC,KAAK,GAAG,EAAE,CAAC;4BACd,MAAM;4BACN,QAAQ,CAAC,GAAG,CAAC,GAAG,GAAG,CAAA;wBACrB,CAAC;6BAAM,IAAI,CAAC,KAAK,GAAG,EAAE,CAAC;4BACrB,IAAI,cAAc,EAAE,CAAC;gCACnB,kDAAkD;gCAClD,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;4BACnB,CAAC;iCAAM,IAAI,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,EAAE,CAAC;gCACxB,0BAA0B;gCAC1B,QAAQ,CAAC,GAAG,CAAC,GAAG,GAAG,CAAA;4BACrB,CAAC;iCAAM,CAAC;gCACN,4BAA4B;gCAC5B,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;4BACnB,CAAC;wBACH,CAAC;oBACH,CAAC;oBACD,CAAC,EAAE,CAAA;gBACL,CAAC;YACH,CAAC;QACH,CAAC;IACH,CAAC;IAED,2CAA2C;IAC3C,OAAO,gBAAgB,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAA;AAClD,CAAC"}
package/package.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "1.4.2",
2
+ "version": "1.4.5",
3
3
  "license": "MIT",
4
4
  "name": "jbrowse-plugin-mafviewer",
5
5
  "keywords": [
@@ -18,10 +18,10 @@
18
18
  "start": "node esbuild.mjs",
19
19
  "test": "vitest",
20
20
  "format": "prettier --write .",
21
- "prebuild": "npm run clean",
21
+ "prebuild": "yarn clean",
22
22
  "build": "tsc && NODE_ENV=production node esbuild.mjs",
23
23
  "lint": "eslint --report-unused-disable-directives --max-warnings 0",
24
- "prepack": "npm run build",
24
+ "preversion": "yarn lint && yarn build",
25
25
  "postversion": "git push --follow-tags"
26
26
  },
27
27
  "devDependencies": {
@@ -37,15 +37,15 @@
37
37
  "@mui/x-data-grid": "^8.2.0",
38
38
  "@types/d3-array": "^3.2.1",
39
39
  "@types/d3-hierarchy": "^3.1.7",
40
- "@types/node": "^22.15.16",
40
+ "@types/node": "^24.7.0",
41
41
  "@types/react": "^19.0.1",
42
42
  "chalk": "^5.3.0",
43
- "esbuild": "^0.25.0",
43
+ "esbuild": "^0.27.0",
44
44
  "eslint": "^9.17.0",
45
45
  "eslint-plugin-import": "^2.31.0",
46
46
  "eslint-plugin-react": "^7.20.3",
47
- "eslint-plugin-react-hooks": "^5.1.0",
48
- "eslint-plugin-unicorn": "^60.0.0",
47
+ "eslint-plugin-react-hooks": "^7.0.1",
48
+ "eslint-plugin-unicorn": "^62.0.0",
49
49
  "mobx": "^6.0.0",
50
50
  "mobx-react": "^9.0.1",
51
51
  "mobx-state-tree": "^5.4.1",
@@ -59,10 +59,10 @@
59
59
  "tss-react": "^4.9.18",
60
60
  "typescript": "^5.1.6",
61
61
  "typescript-eslint": "^8.18.0",
62
- "vitest": "^3.2.1"
62
+ "vitest": "^4.0.5"
63
63
  },
64
64
  "dependencies": {
65
- "@gmod/bgzf-filehandle": "^4.0.0",
65
+ "@gmod/bgzf-filehandle": "^5.0.2",
66
66
  "abortable-promise-cache": "^1.5.0",
67
67
  "buffer": "^6.0.3",
68
68
  "d3-array": "^3.2.4",
@@ -0,0 +1,105 @@
1
+ import React, { useState } from 'react'
2
+
3
+ import { Dialog } from '@jbrowse/core/ui'
4
+ import { Button, DialogActions, DialogContent, TextField } from '@mui/material'
5
+ import { observer } from 'mobx-react'
6
+ import { makeStyles } from 'tss-react/mui'
7
+
8
+ const useStyles = makeStyles()({
9
+ dialogContent: {
10
+ width: '60em',
11
+ },
12
+ textAreaInput: {
13
+ fontFamily: 'monospace',
14
+ whiteSpace: 'pre',
15
+ overflowX: 'auto',
16
+ },
17
+ })
18
+
19
+ const InsertionSequenceDialog = observer(function ({
20
+ onClose,
21
+ model,
22
+ insertionData,
23
+ }: {
24
+ onClose: () => void
25
+ model: {
26
+ showAsUpperCase: boolean
27
+ }
28
+ insertionData: {
29
+ sequence: string
30
+ sampleLabel: string
31
+ chr: string
32
+ pos: number
33
+ }
34
+ }) {
35
+ const { classes } = useStyles()
36
+ const [copied, setCopied] = useState(false)
37
+ const { sequence, sampleLabel, chr, pos } = insertionData
38
+ const { showAsUpperCase } = model
39
+ const displaySequence = showAsUpperCase
40
+ ? sequence.toUpperCase()
41
+ : sequence.toLowerCase()
42
+
43
+ return (
44
+ <Dialog
45
+ open
46
+ onClose={onClose}
47
+ title={`Insertion Sequence (${sequence.length}bp)`}
48
+ maxWidth="lg"
49
+ >
50
+ <DialogContent>
51
+ <div style={{ marginBottom: 16 }}>
52
+ <strong>Sample:</strong> {sampleLabel}
53
+ <br />
54
+ <strong>Position:</strong> {chr}:{pos.toLocaleString('en-US')}
55
+ <br />
56
+ <strong>Length:</strong> {sequence.length}bp
57
+ </div>
58
+ <TextField
59
+ variant="outlined"
60
+ multiline
61
+ minRows={3}
62
+ maxRows={10}
63
+ className={classes.dialogContent}
64
+ fullWidth
65
+ value={displaySequence}
66
+ slotProps={{
67
+ input: {
68
+ readOnly: true,
69
+ classes: {
70
+ input: classes.textAreaInput,
71
+ },
72
+ },
73
+ }}
74
+ />
75
+ </DialogContent>
76
+ <DialogActions>
77
+ <Button
78
+ variant="contained"
79
+ color="primary"
80
+ onClick={() => {
81
+ // eslint-disable-next-line @typescript-eslint/no-floating-promises
82
+ ;(async () => {
83
+ try {
84
+ await navigator.clipboard.writeText(displaySequence)
85
+ setCopied(true)
86
+ setTimeout(() => {
87
+ setCopied(false)
88
+ }, 1000)
89
+ } catch (e) {
90
+ console.error(e)
91
+ }
92
+ })()
93
+ }}
94
+ >
95
+ {copied ? 'Copied!' : 'Copy to Clipboard'}
96
+ </Button>
97
+ <Button color="secondary" variant="contained" onClick={onClose}>
98
+ Close
99
+ </Button>
100
+ </DialogActions>
101
+ </Dialog>
102
+ )
103
+ })
104
+
105
+ export default InsertionSequenceDialog
@@ -1,4 +1,4 @@
1
- import React, { useEffect, useRef, useState } from 'react'
1
+ import React, { useCallback, useEffect, useRef, useState } from 'react'
2
2
 
3
3
  import { Menu } from '@jbrowse/core/ui'
4
4
  import { getContainingView, getEnv } from '@jbrowse/core/util'
@@ -102,11 +102,11 @@ const LinearMafDisplay = observer(function (props: {
102
102
  }
103
103
 
104
104
  // Function to clear the selection box
105
- const clearSelectionBox = () => {
105
+ const clearSelectionBox = useCallback(() => {
106
106
  setShowSelectionBox(false)
107
107
  setDragStartX(undefined)
108
108
  setDragEndX(undefined)
109
- }
109
+ }, [])
110
110
 
111
111
  // Add keydown event handler to clear selection box when Escape key is pressed
112
112
  useEffect(() => {
@@ -155,7 +155,7 @@ const LinearMafDisplay = observer(function (props: {
155
155
  }}
156
156
  >
157
157
  <BaseLinearDisplayComponent {...props} />
158
- <YScaleBars model={model} />
158
+ {model.showSidebar ? <YScaleBars model={model} /> : null}
159
159
  {mouseY && mouseX && sources && !contextCoord && !showSequenceDialog ? (
160
160
  <div style={{ position: 'relative' }}>
161
161
  <Crosshairs
@@ -17,6 +17,7 @@ const ColorLegend = observer(function ({
17
17
  canDisplayLabel,
18
18
  totalHeight,
19
19
  treeWidth,
20
+ sidebarWidth,
20
21
  samples = [],
21
22
  rowHeight,
22
23
  svgFontSize,
@@ -25,12 +26,7 @@ const ColorLegend = observer(function ({
25
26
 
26
27
  return (
27
28
  <>
28
- <RectBg
29
- y={0}
30
- x={0}
31
- width={labelWidth + 5 + treeWidth}
32
- height={totalHeight}
33
- />
29
+ <RectBg y={0} x={0} width={sidebarWidth} height={totalHeight} />
34
30
  <Tree model={model} />
35
31
  <g transform={`translate(${treeWidth + 5},0)`}>
36
32
  {samples.map((sample, idx) => (
@@ -1,14 +1,19 @@
1
1
  import React from 'react'
2
2
 
3
- const RectBg = (props: {
3
+ const RectBg = ({
4
+ x,
5
+ y,
6
+ width,
7
+ height,
8
+ color = 'rgb(255,255,255,0.5)',
9
+ }: {
4
10
  x: number
5
11
  y: number
6
12
  width: number
7
13
  height: number
8
14
  color?: string
9
15
  }) => {
10
- const { color = 'rgb(255,255,255,0.5)' } = props
11
- return <rect {...props} fill={color} />
16
+ return <rect x={x} y={y} width={width} height={height} fill={color} />
12
17
  }
13
18
 
14
19
  export default RectBg
@@ -1,11 +1,30 @@
1
- import React from 'react'
1
+ import React, { useEffect, useRef } from 'react'
2
2
 
3
+ import { ResizeHandle } from '@jbrowse/core/ui'
3
4
  import { getContainingView } from '@jbrowse/core/util'
5
+ import { autorun } from 'mobx'
4
6
  import { observer } from 'mobx-react'
7
+ import { isAlive } from 'mobx-state-tree'
8
+ import { makeStyles } from 'tss-react/mui'
5
9
 
6
10
  import type { LinearMafDisplayModel } from '../../stateModel'
7
11
  import type { LinearGenomeViewModel } from '@jbrowse/plugin-linear-genome-view'
8
12
 
13
+ const useStyles = makeStyles()({
14
+ resizeHandle: {
15
+ position: 'absolute',
16
+ top: 0,
17
+ height: '100%',
18
+ width: 4,
19
+ zIndex: 1001,
20
+ background: 'transparent',
21
+ cursor: 'col-resize',
22
+ '&:hover': {
23
+ background: 'rgba(0,0,0,0.2)',
24
+ },
25
+ },
26
+ })
27
+
9
28
  const SvgWrapper = observer(function ({
10
29
  children,
11
30
  model,
@@ -15,25 +34,108 @@ const SvgWrapper = observer(function ({
15
34
  children: React.ReactNode
16
35
  exportSVG?: boolean
17
36
  }) {
37
+ const { classes } = useStyles()
38
+ const mouseoverRef = useRef<HTMLCanvasElement>(null)
39
+
40
+ useEffect(() => {
41
+ const ctx = mouseoverRef.current?.getContext('2d')
42
+ return ctx
43
+ ? autorun(() => {
44
+ if (isAlive(model)) {
45
+ const {
46
+ totalHeight,
47
+ leafMap,
48
+ rowHeight,
49
+ highlightedRowNames,
50
+ hoveredTreeNode,
51
+ } = model
52
+ const { width: viewWidth } = getContainingView(
53
+ model,
54
+ ) as LinearGenomeViewModel
55
+
56
+ ctx.resetTransform()
57
+ ctx.clearRect(0, 0, viewWidth, totalHeight)
58
+
59
+ if (highlightedRowNames) {
60
+ ctx.fillStyle = 'rgba(255,165,0,0.2)'
61
+ const halfRowHeight = rowHeight / 2
62
+ for (const name of highlightedRowNames) {
63
+ const leaf = leafMap.get(name)
64
+ if (leaf) {
65
+ ctx.fillRect(0, leaf.x! - halfRowHeight, viewWidth, rowHeight)
66
+ }
67
+ }
68
+
69
+ // Draw orange dot at hovered tree node
70
+ if (hoveredTreeNode) {
71
+ ctx.fillStyle = 'rgba(255,165,0,0.8)'
72
+ ctx.beginPath()
73
+ ctx.arc(hoveredTreeNode.y, hoveredTreeNode.x, 4, 0, 2 * Math.PI)
74
+ ctx.fill()
75
+ ctx.strokeStyle = 'rgba(255,140,0,1)'
76
+ ctx.lineWidth = 1
77
+ ctx.stroke()
78
+ }
79
+ }
80
+ }
81
+ })
82
+ : undefined
83
+ }, [model])
84
+
18
85
  if (exportSVG) {
19
86
  return <>{children}</>
20
87
  } else {
21
- const { totalHeight } = model
88
+ const { totalHeight, treeWidth, hierarchy } = model
22
89
  const { width } = getContainingView(model) as LinearGenomeViewModel
23
90
  return (
24
- <svg
25
- style={{
26
- position: 'absolute',
27
- userSelect: 'none',
28
- top: 0,
29
- left: 0,
30
- pointerEvents: 'none',
31
- height: totalHeight,
32
- width,
33
- }}
34
- >
35
- {children}
36
- </svg>
91
+ <>
92
+ <svg
93
+ style={{
94
+ position: 'absolute',
95
+ userSelect: 'none',
96
+ top: 0,
97
+ left: 0,
98
+ pointerEvents: 'none',
99
+ height: totalHeight,
100
+ width,
101
+ }}
102
+ >
103
+ {children}
104
+ </svg>
105
+ <canvas
106
+ ref={mouseoverRef}
107
+ width={width}
108
+ height={totalHeight}
109
+ style={{
110
+ position: 'absolute',
111
+ top: 0,
112
+ left: 0,
113
+ width,
114
+ height: totalHeight,
115
+ zIndex: 1000,
116
+ pointerEvents: 'none',
117
+ }}
118
+ />
119
+ {hierarchy ? (
120
+ <div
121
+ onMouseDown={e => {
122
+ e.stopPropagation()
123
+ }}
124
+ >
125
+ <ResizeHandle
126
+ onDrag={distance => {
127
+ model.setTreeAreaWidth(
128
+ Math.max(20, model.treeAreaWidth + distance),
129
+ )
130
+ return undefined
131
+ }}
132
+ className={classes.resizeHandle}
133
+ style={{ left: treeWidth }}
134
+ vertical
135
+ />
136
+ </div>
137
+ ) : null}
138
+ </>
37
139
  )
38
140
  }
39
141
  })
@@ -1,20 +1,48 @@
1
- import React from 'react'
1
+ import React, { useCallback, useMemo } from 'react'
2
2
 
3
3
  import { observer } from 'mobx-react'
4
4
 
5
5
  import type { LinearMafDisplayModel } from '../../stateModel'
6
+ import type { NodeWithIdsAndLength } from '../../types'
7
+ import type { HierarchyNode } from 'd3-hierarchy'
8
+
9
+ const hitboxStyle = {
10
+ pointerEvents: 'all',
11
+ cursor: 'pointer',
12
+ strokeWidth: 8,
13
+ stroke: 'transparent',
14
+ } as const
6
15
 
7
16
  const Tree = observer(function ({ model }: { model: LinearMafDisplayModel }) {
8
17
  const {
9
- // this is needed for redrawing after zoom change, similar to react-msaview
10
- // renderTreeCanvas
18
+ // rowHeight is needed for redrawing after zoom change
11
19
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
12
20
  rowHeight: _rowHeight,
13
-
21
+ treeAreaWidth,
14
22
  hierarchy,
15
23
  showBranchLen,
24
+ nodeDescendantNames,
16
25
  } = model
17
26
 
27
+ const clearHighlight = useCallback(() => {
28
+ model.setHighlightedRowNames(undefined)
29
+ }, [model])
30
+
31
+ const nodeHandlers = useMemo(() => {
32
+ const handlers = new Map<HierarchyNode<NodeWithIdsAndLength>, () => void>()
33
+ if (hierarchy) {
34
+ for (const node of hierarchy.descendants()) {
35
+ handlers.set(node, () => {
36
+ model.setHighlightedRowNames(nodeDescendantNames.get(node), {
37
+ x: node.x!,
38
+ y: node.y!,
39
+ })
40
+ })
41
+ }
42
+ }
43
+ return handlers
44
+ }, [model, hierarchy, nodeDescendantNames, treeAreaWidth])
45
+
18
46
  return (
19
47
  <>
20
48
  {hierarchy
@@ -27,13 +55,30 @@ const Tree = observer(function ({ model }: { model: LinearMafDisplayModel }) {
27
55
  // @ts-expect-error
28
56
  const sx = showBranchLen ? source.len : source.y
29
57
 
30
- // 1d line intersection to check if line crosses block at all, this is
31
- // an optimization that allows us to skip drawing most tree links
32
- // outside the block
33
58
  return (
34
- <React.Fragment key={[sy, ty, tx, sx].join('-')}>
59
+ <React.Fragment key={`${treeAreaWidth}-${sy}-${ty}-${tx}-${sx}`}>
60
+ {/* Visible lines */}
35
61
  <line stroke="black" x1={sx} y1={sy} x2={sx} y2={ty} />
36
62
  <line stroke="black" x1={sx} y1={ty} x2={tx} y2={ty} />
63
+ {/* Invisible hitbox lines */}
64
+ <line
65
+ x1={sx}
66
+ y1={sy}
67
+ x2={sx}
68
+ y2={ty}
69
+ style={hitboxStyle}
70
+ onMouseEnter={nodeHandlers.get(source)}
71
+ onMouseLeave={clearHighlight}
72
+ />
73
+ <line
74
+ x1={sx}
75
+ y1={ty}
76
+ x2={tx}
77
+ y2={ty}
78
+ style={hitboxStyle}
79
+ onMouseEnter={nodeHandlers.get(target)}
80
+ onMouseLeave={clearHighlight}
81
+ />
37
82
  </React.Fragment>
38
83
  )
39
84
  })
@@ -9,7 +9,6 @@ import type { LinearMafDisplayModel } from '../../stateModel'
9
9
 
10
10
  export const YScaleBars = observer(function (props: {
11
11
  model: LinearMafDisplayModel
12
- orientation?: string
13
12
  exportSVG?: boolean
14
13
  }) {
15
14
  const { model } = props
@@ -28,7 +28,7 @@ export async function renderSvg(
28
28
  <g clipPath={`url(#${clipid})`}>
29
29
  <g id="snpcov">{await superRenderSvg(opts)}</g>
30
30
  <g transform={`translate(${Math.max(-offsetPx, 0)})`}>
31
- <YScaleBars model={self} orientation="left" exportSVG />
31
+ <YScaleBars model={self} exportSVG />
32
32
  </g>
33
33
  </g>
34
34
  </>