jbrowse-plugin-graphgenomeviewer 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/README.md +36 -0
  2. package/dist/GraphGenomeView/components/FeatureDialog.d.ts +5 -0
  3. package/dist/GraphGenomeView/components/FeatureDialog.js +18 -0
  4. package/dist/GraphGenomeView/components/FeatureDialog.js.map +1 -0
  5. package/dist/GraphGenomeView/components/GraphGenomeView.d.ts +6 -0
  6. package/dist/GraphGenomeView/components/GraphGenomeView.js +17 -0
  7. package/dist/GraphGenomeView/components/GraphGenomeView.js.map +1 -0
  8. package/dist/GraphGenomeView/components/GraphPanel.d.ts +6 -0
  9. package/dist/GraphGenomeView/components/GraphPanel.js +17 -0
  10. package/dist/GraphGenomeView/components/GraphPanel.js.map +1 -0
  11. package/dist/GraphGenomeView/components/Header.d.ts +6 -0
  12. package/dist/GraphGenomeView/components/Header.js +59 -0
  13. package/dist/GraphGenomeView/components/Header.js.map +1 -0
  14. package/dist/GraphGenomeView/components/SettingsDialog.d.ts +7 -0
  15. package/dist/GraphGenomeView/components/SettingsDialog.js +24 -0
  16. package/dist/GraphGenomeView/components/SettingsDialog.js.map +1 -0
  17. package/dist/GraphGenomeView/index.d.ts +2 -0
  18. package/dist/GraphGenomeView/index.js +15 -0
  19. package/dist/GraphGenomeView/index.js.map +1 -0
  20. package/dist/GraphGenomeView/model.d.ts +80 -0
  21. package/dist/GraphGenomeView/model.js +96 -0
  22. package/dist/GraphGenomeView/model.js.map +1 -0
  23. package/dist/GraphGenomeView/util.d.ts +1 -0
  24. package/dist/GraphGenomeView/util.js +8 -0
  25. package/dist/GraphGenomeView/util.js.map +1 -0
  26. package/dist/index.d.ts +8 -0
  27. package/dist/index.js +24 -0
  28. package/dist/index.js.map +1 -0
  29. package/dist/jbrowse-plugin-graphgenomeviewer.umd.production.min.js +67 -0
  30. package/dist/jbrowse-plugin-graphgenomeviewer.umd.production.min.js.map +7 -0
  31. package/package.json +63 -0
  32. package/src/GraphGenomeView/components/FeatureDialog.tsx +51 -0
  33. package/src/GraphGenomeView/components/GraphGenomeView.tsx +32 -0
  34. package/src/GraphGenomeView/components/GraphPanel.tsx +37 -0
  35. package/src/GraphGenomeView/components/Header.tsx +90 -0
  36. package/src/GraphGenomeView/components/SettingsDialog.tsx +71 -0
  37. package/src/GraphGenomeView/index.ts +19 -0
  38. package/src/GraphGenomeView/model.ts +110 -0
  39. package/src/GraphGenomeView/util.ts +7 -0
  40. package/src/declare.d.ts +1 -0
  41. package/src/index.ts +28 -0
package/package.json ADDED
@@ -0,0 +1,63 @@
1
+ {
2
+ "version": "1.0.0",
3
+ "name": "jbrowse-plugin-graphgenomeviewer",
4
+ "keywords": [
5
+ "jbrowse",
6
+ "jbrowse2"
7
+ ],
8
+ "main": "dist/index.js",
9
+ "files": [
10
+ "dist",
11
+ "src"
12
+ ],
13
+ "scripts": {
14
+ "test": "vitest",
15
+ "clean": "rimraf dist",
16
+ "format": "prettier --write .",
17
+ "prebuild": "npm run clean",
18
+ "start": "node esbuild.mjs",
19
+ "build": "tsc && NODE_ENV=production node esbuild.mjs",
20
+ "lint": "eslint --report-unused-disable-directives --max-warnings 0",
21
+ "prepack": "npm run build",
22
+ "postversion": "git push --follow-tags"
23
+ },
24
+ "dependencies": {
25
+ "@mui/icons-material": "^6.4.1",
26
+ "graphgenomeviewer": "^5.0.4"
27
+ },
28
+ "devDependencies": {
29
+ "@emotion/react": "^11.13.0",
30
+ "@eslint/compat": "^1.1.1",
31
+ "@fal-works/esbuild-plugin-global-externals": "^2.1.2",
32
+ "@jbrowse/core": "^3.0.1",
33
+ "@jbrowse/plugin-linear-genome-view": "^3.0.1",
34
+ "@mui/material": "^6.1.5",
35
+ "@mui/system": "^6.1.5",
36
+ "@mui/x-data-grid": "^7.3.0",
37
+ "@types/node": "^22.5.0",
38
+ "@types/react": "^19.0.2",
39
+ "@typescript-eslint/eslint-plugin": "^8.1.0",
40
+ "@typescript-eslint/parser": "^8.1.0",
41
+ "esbuild": "^0.25.0",
42
+ "eslint": "^9.0.0",
43
+ "eslint-plugin-import": "^2.31.0",
44
+ "eslint-plugin-react": "^7.20.3",
45
+ "eslint-plugin-react-hooks": "^5.0.0",
46
+ "eslint-plugin-react-refresh": "^0.4.11",
47
+ "eslint-plugin-unicorn": "^56.0.0",
48
+ "mobx": "^6.0.0",
49
+ "mobx-react": "^9.0.1",
50
+ "mobx-state-tree": "5.4.2",
51
+ "prettier": "^3.2.5",
52
+ "pretty-bytes": "^6.1.1",
53
+ "react": "^19.0.0",
54
+ "rimraf": "^6.0.1",
55
+ "rxjs": "^7.0.0",
56
+ "tss-react": "^4.9.4",
57
+ "typescript": "^5.3.3",
58
+ "typescript-eslint": "^8.1.0",
59
+ "vitest": "^3.0.5"
60
+ },
61
+ "author": "Colin <colin.diesh@gmail.com>",
62
+ "license": "MIT"
63
+ }
@@ -0,0 +1,51 @@
1
+ import React from 'react'
2
+
3
+ import { Dialog } from '@jbrowse/core/ui'
4
+
5
+ export default function FeatureDialog({
6
+ data,
7
+ onClose,
8
+ }: {
9
+ data: Record<string, unknown>
10
+ onClose: () => void
11
+ }) {
12
+ return (
13
+ <Dialog open title="Feature details" onClose={onClose}>
14
+ <div>Attributes</div>
15
+ {Object.entries(data)
16
+ .filter(
17
+ entry => !['source', 'target', 'linkNum', 'tags'].includes(entry[0]),
18
+ )
19
+ .map(([key, value]) => (
20
+ <div
21
+ key={`${key}_${value}`}
22
+ style={{ display: 'flex', maxHeight: 150, margin: 3 }}
23
+ >
24
+ <div style={{ backgroundColor: '#dda', minWidth: 100 }}>{key}</div>
25
+ <div style={{ wordBreak: 'break-word', overflow: 'auto' }}>
26
+ {String(value)}
27
+ </div>
28
+ </div>
29
+ ))}
30
+ <hr />
31
+ {data.tags && Object.keys(data.tags).length > 0 ? (
32
+ <>
33
+ <div>Tags</div>
34
+ {Object.entries(data.tags).map(([key, value]) => (
35
+ <div
36
+ key={`${key}_${value}`}
37
+ style={{ display: 'flex', maxHeight: 150, margin: 3 }}
38
+ >
39
+ <div style={{ backgroundColor: '#dda', minWidth: 100 }}>
40
+ {key}
41
+ </div>
42
+ <div style={{ wordBreak: 'break-word', overflow: 'auto' }}>
43
+ {String(value)}
44
+ </div>
45
+ </div>
46
+ ))}
47
+ </>
48
+ ) : null}
49
+ </Dialog>
50
+ )
51
+ }
@@ -0,0 +1,32 @@
1
+ import React from 'react'
2
+
3
+ import { ErrorMessage } from '@jbrowse/core/ui'
4
+ import { observer } from 'mobx-react'
5
+
6
+ import GraphPanel from './GraphPanel'
7
+ import Header from './Header'
8
+
9
+ import type { GraphGenomeViewModel } from '../model'
10
+
11
+ const GraphGenomeView = observer(function ({
12
+ model,
13
+ }: {
14
+ model: GraphGenomeViewModel
15
+ }) {
16
+ const { error } = model
17
+ return (
18
+ <div
19
+ style={{
20
+ padding: 10,
21
+ }}
22
+ >
23
+ <Header model={model} />
24
+ <div>
25
+ {error ? <ErrorMessage error={error} /> : null}
26
+ <GraphPanel model={model} />
27
+ </div>
28
+ </div>
29
+ )
30
+ })
31
+
32
+ export default GraphGenomeView
@@ -0,0 +1,37 @@
1
+ import React, { useState } from 'react'
2
+
3
+ import { Graph } from 'graphgenomeviewer'
4
+ import { observer } from 'mobx-react'
5
+
6
+ import FeatureDialog from './FeatureDialog'
7
+
8
+ import type { GraphGenomeViewModel } from '../model'
9
+
10
+ const GraphPanel = observer(function ({
11
+ model,
12
+ }: {
13
+ model: GraphGenomeViewModel
14
+ }) {
15
+ const { graph } = model
16
+ const [featureData, setFeatureData] = useState<Record<string, unknown>>()
17
+ return graph ? (
18
+ <>
19
+ {featureData ? (
20
+ <FeatureDialog
21
+ data={featureData}
22
+ onClose={() => {
23
+ setFeatureData(undefined)
24
+ }}
25
+ />
26
+ ) : null}
27
+ <Graph
28
+ graph={graph}
29
+ onFeatureClick={data => {
30
+ setFeatureData(data)
31
+ }}
32
+ />
33
+ </>
34
+ ) : null
35
+ })
36
+
37
+ export default GraphPanel
@@ -0,0 +1,90 @@
1
+ import React, { useEffect, useState } from 'react'
2
+
3
+ import { FileSelector } from '@jbrowse/core/ui'
4
+ import { FileLocation, getSession } from '@jbrowse/core/util'
5
+ import { openLocation } from '@jbrowse/core/util/io'
6
+ import { Button, IconButton, TextField } from '@mui/material'
7
+ import { observer } from 'mobx-react'
8
+
9
+ import { Settings } from '@mui/icons-material'
10
+
11
+ import SettingsDialog from './SettingsDialog'
12
+
13
+ import type { GraphGenomeViewModel } from '../model'
14
+
15
+ const Header = observer(function ({ model }: { model: GraphGenomeViewModel }) {
16
+ const { mode } = model
17
+ const [val, setVal] = useState('GRCh38#chr21:1000-2000')
18
+ const [loc, setLoc] = useState<FileLocation>()
19
+ useEffect(() => {
20
+ if (!loc) {
21
+ return
22
+ }
23
+ if ('uri' in loc && loc.uri) {
24
+ model.setGfaUrl(loc.uri)
25
+ } else {
26
+ // eslint-disable-next-line @typescript-eslint/no-floating-promises
27
+ ;(async () => {
28
+ try {
29
+ model.setError(undefined)
30
+ const data = await openLocation(loc).readFile('utf8')
31
+ model.setResultData(data)
32
+ } catch (e) {
33
+ console.error(e)
34
+ model.setError(e)
35
+ }
36
+ })()
37
+ }
38
+ })
39
+ return (
40
+ <div style={{ display: 'flex' }}>
41
+ {mode === 'files' ? (
42
+ <div style={{ maxWidth: 500 }}>
43
+ <FileSelector
44
+ name="GFA path"
45
+ inline
46
+ location={loc}
47
+ setLocation={loc => {
48
+ setLoc(loc)
49
+ }}
50
+ />
51
+ </div>
52
+ ) : (
53
+ <div>
54
+ <TextField
55
+ type="text"
56
+ variant="outlined"
57
+ margin="normal"
58
+ size="small"
59
+ value={val}
60
+ style={{ minWidth: 500 }}
61
+ onChange={event => {
62
+ setVal(event.target.value)
63
+ }}
64
+ />
65
+ <Button variant="contained" type="submit">
66
+ Submit
67
+ </Button>
68
+ </div>
69
+ )}
70
+ <div style={{ flexGrow: 1 }} />
71
+ <div>
72
+ <IconButton
73
+ onClick={() => {
74
+ getSession(model).queueDialog(handleClose => [
75
+ SettingsDialog,
76
+ {
77
+ handleClose,
78
+ model,
79
+ },
80
+ ])
81
+ }}
82
+ >
83
+ <Settings />
84
+ </IconButton>
85
+ </div>
86
+ </div>
87
+ )
88
+ })
89
+
90
+ export default Header
@@ -0,0 +1,71 @@
1
+ import React from 'react'
2
+
3
+ import { Dialog } from '@jbrowse/core/ui'
4
+ import {
5
+ Button,
6
+ DialogActions,
7
+ DialogContent,
8
+ FormControl,
9
+ FormControlLabel,
10
+ Radio,
11
+ RadioGroup,
12
+ } from '@mui/material'
13
+ import { observer } from 'mobx-react'
14
+
15
+ import type { GraphGenomeViewModel } from '../model'
16
+
17
+ const SettingsDialog = observer(function ({
18
+ model,
19
+ handleClose,
20
+ }: {
21
+ model: GraphGenomeViewModel
22
+ handleClose: () => void
23
+ }) {
24
+ const { mode } = model
25
+ return (
26
+ <Dialog open maxWidth="xl" title="Settings" onClose={handleClose}>
27
+ <DialogContent style={{ minWidth: 800 }}>
28
+ <FormControl component="fieldset">
29
+ <RadioGroup
30
+ value={mode}
31
+ onChange={event => {
32
+ model.setMode(event.target.value)
33
+ }}
34
+ >
35
+ <FormControlLabel
36
+ value="files"
37
+ control={<Radio />}
38
+ label="Use GFA files"
39
+ />
40
+ <FormControlLabel
41
+ value="server"
42
+ control={<Radio />}
43
+ label="Use GFA server"
44
+ />
45
+ </RadioGroup>
46
+ </FormControl>
47
+ </DialogContent>
48
+ <DialogActions>
49
+ <Button
50
+ variant="contained"
51
+ onClick={() => {
52
+ handleClose()
53
+ }}
54
+ >
55
+ Submit
56
+ </Button>
57
+ <Button
58
+ variant="contained"
59
+ color="secondary"
60
+ onClick={() => {
61
+ handleClose()
62
+ }}
63
+ >
64
+ Cancel
65
+ </Button>
66
+ </DialogActions>
67
+ </Dialog>
68
+ )
69
+ })
70
+
71
+ export default SettingsDialog
@@ -0,0 +1,19 @@
1
+ import { lazy } from 'react'
2
+
3
+ import PluginManager from '@jbrowse/core/PluginManager'
4
+ import { ViewType } from '@jbrowse/core/pluggableElementTypes'
5
+
6
+ import stateModelF from './model'
7
+
8
+ const ReactComponent = lazy(() => import('./components/GraphGenomeView'))
9
+
10
+ export default function ProteinViewF(pluginManager: PluginManager) {
11
+ pluginManager.addViewType(() => {
12
+ return new ViewType({
13
+ name: 'GraphGenomeView',
14
+ displayName: 'Graph genome view',
15
+ stateModel: stateModelF(),
16
+ ReactComponent,
17
+ })
18
+ })
19
+ }
@@ -0,0 +1,110 @@
1
+ import { BaseViewModel } from '@jbrowse/core/pluggableElementTypes'
2
+ import { parseGFA } from 'graphgenomeviewer'
3
+ import { autorun } from 'mobx'
4
+ import { addDisposer, types } from 'mobx-state-tree'
5
+
6
+ import { myfetchtext } from './util'
7
+
8
+ import type { Instance } from 'mobx-state-tree'
9
+
10
+ export default function stateModelFactory() {
11
+ return types.compose(
12
+ 'GraphGenomeView',
13
+ BaseViewModel,
14
+ types
15
+ .model({
16
+ /**
17
+ * #property
18
+ */
19
+ type: 'GraphGenomeView',
20
+
21
+ /**
22
+ * #property
23
+ */
24
+ genomicRegionLocString: types.maybe(types.string),
25
+
26
+ /**
27
+ * #property
28
+ */
29
+ gfaUrl: types.maybe(types.string),
30
+ /**
31
+ * #property
32
+ */
33
+ mode: 'files',
34
+ })
35
+ .volatile(() => ({
36
+ /**
37
+ * #volatile
38
+ */
39
+ resultData: undefined as string | undefined,
40
+ /**
41
+ * #volatile
42
+ */
43
+ error: undefined as unknown,
44
+ }))
45
+ .views(self => ({
46
+ /**
47
+ * #getter
48
+ */
49
+ get graph() {
50
+ return self.resultData ? parseGFA(self.resultData) : undefined
51
+ },
52
+ }))
53
+ .actions(self => ({
54
+ /**
55
+ * #action
56
+ */
57
+ setMode(arg: string) {
58
+ self.mode = arg
59
+ },
60
+ /**
61
+ * #action
62
+ */
63
+ setError(arg: unknown) {
64
+ self.error = arg
65
+ },
66
+ /**
67
+ * #action
68
+ */
69
+ setResultData(arg: string) {
70
+ self.resultData = arg
71
+ },
72
+ /**
73
+ * #action
74
+ */
75
+ setGenomicRegionLocString(arg: string) {
76
+ self.genomicRegionLocString = arg
77
+ },
78
+ /**
79
+ * #action
80
+ */
81
+ setGfaUrl(url: string) {
82
+ self.gfaUrl = url
83
+ },
84
+ }))
85
+ .actions(self => ({
86
+ afterAttach() {
87
+ addDisposer(
88
+ self,
89
+ autorun(async () => {
90
+ if (!self.gfaUrl) {
91
+ return
92
+ }
93
+ try {
94
+ self.setError(undefined)
95
+ const url = self.gfaUrl
96
+ const data = await myfetchtext(url)
97
+ self.setResultData(data)
98
+ } catch (e) {
99
+ self.setError(e)
100
+ console.error(e)
101
+ }
102
+ }),
103
+ )
104
+ },
105
+ })),
106
+ )
107
+ }
108
+
109
+ export type GraphGenomeViewStateModel = ReturnType<typeof stateModelFactory>
110
+ export type GraphGenomeViewModel = Instance<GraphGenomeViewStateModel>
@@ -0,0 +1,7 @@
1
+ export async function myfetchtext(url: string) {
2
+ const res = await fetch(url)
3
+ if (!res.ok) {
4
+ throw new Error(`HTTP ${res.status} fetching ${url}`)
5
+ }
6
+ return res.text()
7
+ }
@@ -0,0 +1 @@
1
+ declare module '*.json'
package/src/index.ts ADDED
@@ -0,0 +1,28 @@
1
+ import Plugin from '@jbrowse/core/Plugin'
2
+ import PluginManager from '@jbrowse/core/PluginManager'
3
+ import { AbstractSessionModel, isAbstractMenuManager } from '@jbrowse/core/util'
4
+ import HubIcon from '@mui/icons-material/Hub'
5
+
6
+ import { version } from '../package.json'
7
+ import GraphGenomeViewF from './GraphGenomeView'
8
+
9
+ export default class GraphGenomeViewer extends Plugin {
10
+ name = 'GraphGenomeViewer'
11
+ version = version
12
+
13
+ install(pluginManager: PluginManager) {
14
+ GraphGenomeViewF(pluginManager)
15
+ }
16
+
17
+ configure(pluginManager: PluginManager) {
18
+ if (isAbstractMenuManager(pluginManager.rootModel)) {
19
+ pluginManager.rootModel.appendToSubMenu(['Add'], {
20
+ label: 'Graph genome viewer',
21
+ icon: HubIcon,
22
+ onClick: (session: AbstractSessionModel) => {
23
+ session.addView('GraphGenomeView', {})
24
+ },
25
+ })
26
+ }
27
+ }
28
+ }