@jbrowse/plugin-circular-view 2.3.4 → 2.4.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 (90) hide show
  1. package/dist/BaseChordDisplay/components/Loading.js.map +1 -1
  2. package/dist/BaseChordDisplay/index.d.ts +2 -2
  3. package/dist/BaseChordDisplay/index.js +4 -4
  4. package/dist/BaseChordDisplay/index.js.map +1 -1
  5. package/dist/BaseChordDisplay/models/{baseChordDisplayConfig.js → configSchema.js} +1 -1
  6. package/dist/BaseChordDisplay/models/configSchema.js.map +1 -0
  7. package/dist/BaseChordDisplay/models/{BaseChordDisplayModel.d.ts → model.d.ts} +12 -0
  8. package/dist/BaseChordDisplay/models/{BaseChordDisplayModel.js → model.js} +32 -22
  9. package/dist/BaseChordDisplay/models/model.js.map +1 -0
  10. package/dist/BaseChordDisplay/models/renderReaction.d.ts +1 -1
  11. package/dist/BaseChordDisplay/models/renderReaction.js +4 -5
  12. package/dist/BaseChordDisplay/models/renderReaction.js.map +1 -1
  13. package/dist/CircularView/components/CircularView.js +8 -17
  14. package/dist/CircularView/components/CircularView.js.map +1 -1
  15. package/dist/CircularView/components/Controls.d.ts +2 -2
  16. package/dist/CircularView/components/Controls.js +60 -24
  17. package/dist/CircularView/components/Controls.js.map +1 -1
  18. package/dist/CircularView/components/ExportSvgDialog.d.ts +8 -0
  19. package/dist/CircularView/components/ExportSvgDialog.js +76 -0
  20. package/dist/CircularView/components/ExportSvgDialog.js.map +1 -0
  21. package/dist/CircularView/components/Ruler.d.ts +2 -2
  22. package/dist/CircularView/components/Ruler.js +10 -14
  23. package/dist/CircularView/components/Ruler.js.map +1 -1
  24. package/dist/CircularView/models/CircularView.d.ts +30 -26
  25. package/dist/CircularView/models/CircularView.js +61 -17
  26. package/dist/CircularView/models/CircularView.js.map +1 -1
  27. package/dist/CircularView/models/slices.d.ts +23 -10
  28. package/dist/CircularView/models/slices.js +10 -7
  29. package/dist/CircularView/models/slices.js.map +1 -1
  30. package/dist/CircularView/svgcomponents/SVGBackground.d.ts +6 -0
  31. package/dist/CircularView/svgcomponents/SVGBackground.js +13 -0
  32. package/dist/CircularView/svgcomponents/SVGBackground.js.map +1 -0
  33. package/dist/CircularView/svgcomponents/SVGCircularView.d.ts +4 -0
  34. package/dist/CircularView/svgcomponents/SVGCircularView.js +40 -0
  35. package/dist/CircularView/svgcomponents/SVGCircularView.js.map +1 -0
  36. package/esm/BaseChordDisplay/components/Loading.js.map +1 -1
  37. package/esm/BaseChordDisplay/index.d.ts +2 -2
  38. package/esm/BaseChordDisplay/index.js +2 -2
  39. package/esm/BaseChordDisplay/index.js.map +1 -1
  40. package/esm/BaseChordDisplay/models/{baseChordDisplayConfig.js → configSchema.js} +1 -1
  41. package/esm/BaseChordDisplay/models/configSchema.js.map +1 -0
  42. package/esm/BaseChordDisplay/models/{BaseChordDisplayModel.d.ts → model.d.ts} +12 -0
  43. package/esm/BaseChordDisplay/models/{BaseChordDisplayModel.js → model.js} +33 -23
  44. package/esm/BaseChordDisplay/models/model.js.map +1 -0
  45. package/esm/BaseChordDisplay/models/renderReaction.d.ts +1 -1
  46. package/esm/BaseChordDisplay/models/renderReaction.js +4 -5
  47. package/esm/BaseChordDisplay/models/renderReaction.js.map +1 -1
  48. package/esm/CircularView/components/CircularView.js +8 -17
  49. package/esm/CircularView/components/CircularView.js.map +1 -1
  50. package/esm/CircularView/components/Controls.d.ts +2 -2
  51. package/esm/CircularView/components/Controls.js +37 -24
  52. package/esm/CircularView/components/Controls.js.map +1 -1
  53. package/esm/CircularView/components/ExportSvgDialog.d.ts +8 -0
  54. package/esm/CircularView/components/ExportSvgDialog.js +50 -0
  55. package/esm/CircularView/components/ExportSvgDialog.js.map +1 -0
  56. package/esm/CircularView/components/Ruler.d.ts +2 -2
  57. package/esm/CircularView/components/Ruler.js +10 -14
  58. package/esm/CircularView/components/Ruler.js.map +1 -1
  59. package/esm/CircularView/models/CircularView.d.ts +30 -26
  60. package/esm/CircularView/models/CircularView.js +58 -17
  61. package/esm/CircularView/models/CircularView.js.map +1 -1
  62. package/esm/CircularView/models/slices.d.ts +23 -10
  63. package/esm/CircularView/models/slices.js +10 -7
  64. package/esm/CircularView/models/slices.js.map +1 -1
  65. package/esm/CircularView/svgcomponents/SVGBackground.d.ts +6 -0
  66. package/esm/CircularView/svgcomponents/SVGBackground.js +7 -0
  67. package/esm/CircularView/svgcomponents/SVGBackground.js.map +1 -0
  68. package/esm/CircularView/svgcomponents/SVGCircularView.d.ts +4 -0
  69. package/esm/CircularView/svgcomponents/SVGCircularView.js +33 -0
  70. package/esm/CircularView/svgcomponents/SVGCircularView.js.map +1 -0
  71. package/package.json +5 -3
  72. package/src/BaseChordDisplay/components/Loading.tsx +1 -0
  73. package/src/BaseChordDisplay/index.ts +2 -2
  74. package/src/BaseChordDisplay/models/{BaseChordDisplayModel.ts → model.tsx} +46 -24
  75. package/src/BaseChordDisplay/models/renderReaction.ts +4 -7
  76. package/src/CircularView/components/CircularView.tsx +11 -25
  77. package/src/CircularView/components/Controls.tsx +41 -28
  78. package/src/CircularView/components/ExportSvgDialog.tsx +132 -0
  79. package/src/CircularView/components/Ruler.tsx +193 -182
  80. package/src/CircularView/models/CircularView.ts +78 -40
  81. package/src/CircularView/models/slices.ts +32 -12
  82. package/src/CircularView/svgcomponents/SVGBackground.tsx +21 -0
  83. package/src/CircularView/svgcomponents/SVGCircularView.tsx +58 -0
  84. package/dist/BaseChordDisplay/models/BaseChordDisplayModel.js.map +0 -1
  85. package/dist/BaseChordDisplay/models/baseChordDisplayConfig.js.map +0 -1
  86. package/esm/BaseChordDisplay/models/BaseChordDisplayModel.js.map +0 -1
  87. package/esm/BaseChordDisplay/models/baseChordDisplayConfig.js.map +0 -1
  88. /package/dist/BaseChordDisplay/models/{baseChordDisplayConfig.d.ts → configSchema.d.ts} +0 -0
  89. /package/esm/BaseChordDisplay/models/{baseChordDisplayConfig.d.ts → configSchema.d.ts} +0 -0
  90. /package/src/BaseChordDisplay/models/{baseChordDisplayConfig.ts → configSchema.ts} +0 -0
@@ -1,8 +1,8 @@
1
- import React from 'react'
1
+ import React, { useState } from 'react'
2
2
  import { observer } from 'mobx-react'
3
3
  import { IconButton } from '@mui/material'
4
4
  import { makeStyles } from 'tss-react/mui'
5
- import { grey } from '@mui/material/colors'
5
+ import JBrowseMenu from '@jbrowse/core/ui/Menu'
6
6
 
7
7
  // icons
8
8
  import ZoomOutIcon from '@mui/icons-material/ZoomOut'
@@ -11,96 +11,109 @@ import RotateLeftIcon from '@mui/icons-material/RotateLeft'
11
11
  import RotateRightIcon from '@mui/icons-material/RotateRight'
12
12
  import LockOpenIcon from '@mui/icons-material/LockOpen'
13
13
  import LockIcon from '@mui/icons-material/Lock'
14
+ import PhotoCamera from '@mui/icons-material/PhotoCamera'
15
+ import MoreVert from '@mui/icons-material/MoreVert'
14
16
  import { TrackSelector as TrackSelectorIcon } from '@jbrowse/core/ui/Icons'
15
17
 
16
18
  // locals
17
19
  import { CircularViewModel } from '../models/CircularView'
20
+ import { getSession } from '@jbrowse/core/util'
21
+ import ExportSvgDlg from './ExportSvgDialog'
18
22
 
19
- const useStyles = makeStyles()({
20
- iconButton: {
21
- padding: '4px',
22
- margin: '0 2px 0 2px',
23
- },
23
+ const useStyles = makeStyles()(theme => ({
24
24
  controls: {
25
- overflow: 'hidden',
26
- whiteSpace: 'nowrap',
27
25
  position: 'absolute',
28
- background: grey[200],
29
- boxSizing: 'border-box',
30
- borderRight: '1px solid #a2a2a2',
31
- borderBottom: '1px solid #a2a2a2',
26
+ borderRight: `1px solid ${theme.palette.divider}`,
27
+ borderBottom: `1px solid ${theme.palette.divider}`,
32
28
  left: 0,
33
29
  top: 0,
34
30
  },
35
- })
31
+ }))
36
32
 
37
- const Controls = observer(function ({ model }: { model: CircularViewModel }) {
33
+ export default observer(function ({ model }: { model: CircularViewModel }) {
38
34
  const { classes } = useStyles()
35
+ const [anchorEl, setAnchorEl] = useState<HTMLElement | null>(null)
39
36
  return (
40
37
  <div className={classes.controls}>
41
38
  <IconButton
42
39
  onClick={model.zoomOutButton}
43
- className={classes.iconButton}
44
40
  title={model.lockedFitToWindow ? 'unlock to zoom out' : 'zoom out'}
45
41
  disabled={model.atMaxBpPerPx || model.lockedFitToWindow}
46
- color="secondary"
47
42
  >
48
43
  <ZoomOutIcon />
49
44
  </IconButton>
50
45
 
51
46
  <IconButton
52
47
  onClick={model.zoomInButton}
53
- className={classes.iconButton}
54
- title="zoom in"
55
48
  disabled={model.atMinBpPerPx}
56
- color="secondary"
49
+ title="zoom in"
57
50
  >
58
51
  <ZoomInIcon />
59
52
  </IconButton>
60
53
 
61
54
  <IconButton
62
55
  onClick={model.rotateCounterClockwiseButton}
63
- className={classes.iconButton}
64
56
  title="rotate counter-clockwise"
65
- color="secondary"
66
57
  >
67
58
  <RotateLeftIcon />
68
59
  </IconButton>
69
60
 
70
61
  <IconButton
71
62
  onClick={model.rotateClockwiseButton}
72
- className={classes.iconButton}
73
63
  title="rotate clockwise"
74
- color="secondary"
75
64
  >
76
65
  <RotateRightIcon />
77
66
  </IconButton>
78
67
 
79
68
  <IconButton
80
69
  onClick={model.toggleFitToWindowLock}
81
- className={classes.iconButton}
82
70
  title={
83
71
  model.lockedFitToWindow
84
72
  ? 'locked model to window size'
85
73
  : 'unlocked model to zoom further'
86
74
  }
87
75
  disabled={model.tooSmallToLock}
88
- color="secondary"
89
76
  >
90
77
  {model.lockedFitToWindow ? <LockIcon /> : <LockOpenIcon />}
91
78
  </IconButton>
92
79
 
80
+ <IconButton onClick={event => setAnchorEl(event.currentTarget)}>
81
+ <MoreVert />
82
+ </IconButton>
83
+
93
84
  {model.hideTrackSelectorButton ? null : (
94
85
  <IconButton
95
86
  onClick={model.activateTrackSelector}
96
87
  title="Open track selector"
97
88
  data-testid="circular_track_select"
98
- color="secondary"
99
89
  >
100
90
  <TrackSelectorIcon />
101
91
  </IconButton>
102
92
  )}
93
+
94
+ {anchorEl ? (
95
+ <JBrowseMenu
96
+ anchorEl={anchorEl}
97
+ menuItems={[
98
+ {
99
+ label: 'Export SVG',
100
+ icon: PhotoCamera,
101
+ onClick: () => {
102
+ getSession(model).queueDialog(handleClose => [
103
+ ExportSvgDlg,
104
+ { model, handleClose },
105
+ ])
106
+ },
107
+ },
108
+ ]}
109
+ onMenuItemClick={(_event, callback) => {
110
+ callback()
111
+ setAnchorEl(null)
112
+ }}
113
+ open={Boolean(anchorEl)}
114
+ onClose={() => setAnchorEl(null)}
115
+ />
116
+ ) : null}
103
117
  </div>
104
118
  )
105
119
  })
106
- export default Controls
@@ -0,0 +1,132 @@
1
+ import React, { useState } from 'react'
2
+ import {
3
+ Button,
4
+ Checkbox,
5
+ CircularProgress,
6
+ DialogActions,
7
+ DialogContent,
8
+ FormControlLabel,
9
+ MenuItem,
10
+ TextField,
11
+ Typography,
12
+ } from '@mui/material'
13
+ import { Dialog, ErrorMessage } from '@jbrowse/core/ui'
14
+
15
+ // locals
16
+ import { ExportSvgOptions } from '../models/CircularView'
17
+ import { getSession, useLocalStorage } from '@jbrowse/core/util'
18
+
19
+ function LoadingMessage() {
20
+ return (
21
+ <div>
22
+ <CircularProgress size={20} style={{ marginRight: 20 }} />
23
+ <Typography display="inline">Creating SVG</Typography>
24
+ </div>
25
+ )
26
+ }
27
+
28
+ function useSvgLocal<T>(key: string, val: T) {
29
+ return useLocalStorage('svg-' + key, val)
30
+ }
31
+
32
+ export default function ExportSvgDlg({
33
+ model,
34
+ handleClose,
35
+ }: {
36
+ model: { exportSvg(opts: ExportSvgOptions): Promise<void> }
37
+ handleClose: () => void
38
+ }) {
39
+ const session = getSession(model)
40
+ const offscreenCanvas = typeof OffscreenCanvas !== 'undefined'
41
+ const [rasterizeLayers, setRasterizeLayers] = useState(offscreenCanvas)
42
+ const [loading, setLoading] = useState(false)
43
+ const [error, setError] = useState<unknown>()
44
+ const [filename, setFilename] = useSvgLocal('file', 'jbrowse.svg')
45
+ const [themeName, setThemeName] = useSvgLocal(
46
+ 'theme',
47
+ session.themeName || 'default',
48
+ )
49
+ return (
50
+ <Dialog open onClose={handleClose} title="Export SVG">
51
+ <DialogContent>
52
+ {error ? (
53
+ <ErrorMessage error={error} />
54
+ ) : loading ? (
55
+ <LoadingMessage />
56
+ ) : null}
57
+ <TextField
58
+ helperText="filename"
59
+ value={filename}
60
+ onChange={event => setFilename(event.target.value)}
61
+ />
62
+ <br />
63
+
64
+ {session.allThemes ? (
65
+ <TextField
66
+ select
67
+ label="Theme"
68
+ value={themeName}
69
+ onChange={event => setThemeName(event.target.value)}
70
+ >
71
+ {Object.entries(session.allThemes()).map(([key, val]) => (
72
+ <MenuItem key={key} value={key}>
73
+ {
74
+ // @ts-ignore
75
+ val.name || '(Unknown name)'
76
+ }
77
+ </MenuItem>
78
+ ))}
79
+ </TextField>
80
+ ) : null}
81
+ {offscreenCanvas ? (
82
+ <FormControlLabel
83
+ control={
84
+ <Checkbox
85
+ checked={rasterizeLayers}
86
+ onChange={() => setRasterizeLayers(val => !val)}
87
+ />
88
+ }
89
+ label="Rasterize canvas based tracks? File may be much larger if this is turned off"
90
+ />
91
+ ) : (
92
+ <Typography>
93
+ Note: rasterizing layers not yet supported in this browser, so SVG
94
+ size may be large
95
+ </Typography>
96
+ )}
97
+ </DialogContent>
98
+ <DialogActions>
99
+ <Button
100
+ variant="contained"
101
+ color="secondary"
102
+ onClick={() => handleClose()}
103
+ >
104
+ Cancel
105
+ </Button>
106
+ <Button
107
+ variant="contained"
108
+ color="primary"
109
+ type="submit"
110
+ onClick={async () => {
111
+ setLoading(true)
112
+ setError(undefined)
113
+ try {
114
+ await model.exportSvg({
115
+ rasterizeLayers,
116
+ filename,
117
+ themeName,
118
+ })
119
+ handleClose()
120
+ } catch (e) {
121
+ console.error(e)
122
+ setError(e)
123
+ setLoading(false)
124
+ }
125
+ }}
126
+ >
127
+ Submit
128
+ </Button>
129
+ </DialogActions>
130
+ </Dialog>
131
+ )
132
+ }
@@ -11,7 +11,11 @@ import { useTheme } from '@mui/material/styles'
11
11
  import { makeStyles } from 'tss-react/mui'
12
12
 
13
13
  // locals
14
- import { Slice } from '../models/slices'
14
+ import {
15
+ Slice,
16
+ SliceElidedRegion,
17
+ SliceNonElidedRegion,
18
+ } from '../models/slices'
15
19
  import { CircularViewModel } from '../models/CircularView'
16
20
 
17
21
  const useStyles = makeStyles()({
@@ -51,128 +55,116 @@ function sliceArcPath(
51
55
  ].join(' ')
52
56
  }
53
57
 
54
- const ElisionRulerArc = observer(
55
- ({ model, slice }: { model: CircularViewModel; slice: Slice }) => {
56
- const theme = useTheme()
57
- const { radiusPx: modelRadiusPx } = model
58
- const radiusPx = modelRadiusPx + 1
59
- const { endRadians, startRadians, region } = slice
60
- const startXY = polarToCartesian(radiusPx, startRadians)
61
- const endXY = polarToCartesian(radiusPx, endRadians)
62
- const widthPx = (endRadians - startRadians) * radiusPx
63
- const largeArc = endRadians - startRadians > Math.PI ? '1' : '0'
64
- // TODO: draw the elision
65
- const centerRadians = (endRadians + startRadians) / 2
66
- const regionCountString = `[${Number(
67
- // @ts-ignore
68
- region.regions.length,
69
- ).toLocaleString()}]`
70
- return (
71
- <>
72
- <RulerLabel
73
- text={regionCountString}
74
- view={model}
75
- maxWidthPx={widthPx}
76
- radians={centerRadians}
77
- radiusPx={radiusPx}
78
- title={`${Number(
79
- // @ts-ignore
80
- region.regions.length,
81
- ).toLocaleString()} more regions`}
82
- color={theme.palette.text.primary}
83
- />
84
- <path
85
- d={[
86
- 'M',
87
- ...startXY,
88
- 'A',
89
- radiusPx,
90
- radiusPx,
91
- '0',
92
- largeArc,
93
- '1',
94
- ...endXY,
95
- ].join(' ')}
96
- stroke={theme.palette.text.secondary}
97
- strokeWidth={2}
98
- strokeDasharray="2,2"
99
- fill="none"
100
- />
101
- </>
102
- )
103
- },
104
- )
58
+ const ElisionRulerArc = observer(function ({
59
+ model,
60
+ slice,
61
+ region,
62
+ }: {
63
+ model: CircularViewModel
64
+ slice: Slice
65
+ region: SliceElidedRegion
66
+ }) {
67
+ const theme = useTheme()
68
+ const { radiusPx: modelRadiusPx } = model
69
+ const radiusPx = modelRadiusPx + 1
70
+ const { endRadians, startRadians } = slice
71
+ const startXY = polarToCartesian(radiusPx, startRadians)
72
+ const endXY = polarToCartesian(radiusPx, endRadians)
73
+ const widthPx = (endRadians - startRadians) * radiusPx
74
+ const largeArc = endRadians - startRadians > Math.PI ? '1' : '0'
75
+ // TODO: draw the elision
76
+ const centerRadians = (endRadians + startRadians) / 2
77
+ const regionCountString = `[${Number(
78
+ // @ts-ignore
79
+ region.regions.length,
80
+ ).toLocaleString()}]`
81
+ return (
82
+ <>
83
+ <RulerLabel
84
+ text={regionCountString}
85
+ view={model}
86
+ maxWidthPx={widthPx}
87
+ radians={centerRadians}
88
+ radiusPx={radiusPx}
89
+ title={`${Number(
90
+ // @ts-ignore
91
+ region.regions.length,
92
+ ).toLocaleString()} more regions`}
93
+ color={theme.palette.text.primary}
94
+ />
95
+ <path
96
+ d={[
97
+ 'M',
98
+ ...startXY,
99
+ 'A',
100
+ radiusPx,
101
+ radiusPx,
102
+ '0',
103
+ largeArc,
104
+ '1',
105
+ ...endXY,
106
+ ].join(' ')}
107
+ stroke={theme.palette.text.secondary}
108
+ strokeWidth={2}
109
+ strokeDasharray="2,2"
110
+ fill="none"
111
+ />
112
+ </>
113
+ )
114
+ })
105
115
 
106
- const RulerLabel = observer(
107
- ({
108
- view,
109
- text,
110
- maxWidthPx,
111
- radians,
112
- radiusPx,
113
- title,
114
- color,
115
- }: {
116
- view: CircularViewModel
117
- text: string
118
- maxWidthPx: number
119
- radiusPx: number
120
- radians: number
121
- title?: string
122
- color: string
123
- }) => {
124
- const { classes } = useStyles()
125
- const textXY = polarToCartesian(radiusPx + 5, radians)
126
- if (!text) {
127
- return null
128
- }
116
+ const RulerLabel = observer(function ({
117
+ view,
118
+ text,
119
+ maxWidthPx,
120
+ radians,
121
+ radiusPx,
122
+ title,
123
+ color,
124
+ }: {
125
+ view: CircularViewModel
126
+ text: string
127
+ maxWidthPx: number
128
+ radiusPx: number
129
+ radians: number
130
+ title?: string
131
+ color: string
132
+ }) {
133
+ const { classes } = useStyles()
134
+ const textXY = polarToCartesian(radiusPx + 5, radians)
135
+ if (!text) {
136
+ return null
137
+ }
129
138
 
130
- if (text.length * 6.5 < maxWidthPx) {
131
- // text is rotated parallel to the ruler arc
132
- return (
133
- <text
134
- x={0}
135
- y={0}
136
- className={classes.rulerLabel}
137
- textAnchor="middle"
138
- dominantBaseline="baseline"
139
- transform={`translate(${textXY}) rotate(${radToDeg(radians) + 90})`}
140
- style={{ fill: color }}
141
- >
142
- {text}
143
- <title>{title || text}</title>
144
- </text>
145
- )
146
- }
147
- if (maxWidthPx > 4) {
148
- // text is rotated perpendicular to the ruler arc
149
- const overallRotation = radToDeg(
150
- radians + view.offsetRadians - Math.PI / 2,
151
- )
152
- if (overallRotation >= 180) {
153
- return (
154
- <text
155
- x={0}
156
- y={0}
157
- className={classes.rulerLabel}
158
- textAnchor="start"
159
- dominantBaseline="middle"
160
- transform={`translate(${textXY}) rotate(${radToDeg(radians)})`}
161
- style={{ fill: color }}
162
- >
163
- {text}
164
- <title>{title || text}</title>
165
- </text>
166
- )
167
- }
139
+ if (text.length * 6.5 < maxWidthPx) {
140
+ // text is rotated parallel to the ruler arc
141
+ return (
142
+ <text
143
+ x={0}
144
+ y={0}
145
+ className={classes.rulerLabel}
146
+ textAnchor="middle"
147
+ dominantBaseline="baseline"
148
+ transform={`translate(${textXY}) rotate(${radToDeg(radians) + 90})`}
149
+ style={{ fill: color }}
150
+ >
151
+ {text}
152
+ <title>{title || text}</title>
153
+ </text>
154
+ )
155
+ }
156
+ if (maxWidthPx > 4) {
157
+ // text is rotated perpendicular to the ruler arc
158
+ const overallRotation = radToDeg(radians + view.offsetRadians - Math.PI / 2)
159
+ if (overallRotation >= 180) {
168
160
  return (
169
161
  <text
170
162
  x={0}
171
163
  y={0}
172
164
  className={classes.rulerLabel}
173
- textAnchor="end"
165
+ textAnchor="start"
174
166
  dominantBaseline="middle"
175
- transform={`translate(${textXY}) rotate(${radToDeg(radians) + 180})`}
167
+ transform={`translate(${textXY}) rotate(${radToDeg(radians)})`}
176
168
  style={{ fill: color }}
177
169
  >
178
170
  {text}
@@ -180,81 +172,100 @@ const RulerLabel = observer(
180
172
  </text>
181
173
  )
182
174
  }
175
+ return (
176
+ <text
177
+ x={0}
178
+ y={0}
179
+ className={classes.rulerLabel}
180
+ textAnchor="end"
181
+ dominantBaseline="middle"
182
+ transform={`translate(${textXY}) rotate(${radToDeg(radians) + 180})`}
183
+ style={{ fill: color }}
184
+ >
185
+ {text}
186
+ <title>{title || text}</title>
187
+ </text>
188
+ )
189
+ }
183
190
 
184
- // if you get here there is no room for the text at all
185
- return null
186
- },
187
- )
191
+ // if you get here there is no room for the text at all
192
+ return null
193
+ })
188
194
 
189
- const RegionRulerArc = observer(
190
- ({ model, slice }: { model: CircularViewModel; slice: Slice }) => {
191
- const theme = useTheme()
192
- const { radiusPx } = model
193
- const { region, endRadians, startRadians } = slice
194
- const centerRadians = (endRadians + startRadians) / 2
195
- const widthPx = (endRadians - startRadians) * radiusPx
196
- const session = getSession(model)
197
- let color
198
- const assembly = session.assemblyManager.get(slice.region.assemblyName)
199
- if (assembly) {
200
- color = assembly.getRefNameColor(region.refName)
201
- }
202
- if (color) {
203
- try {
204
- color = makeContrasting(color, theme.palette.background.paper)
205
- } catch (error) {
206
- color = theme.palette.text.primary
207
- }
208
- } else {
195
+ const RegionRulerArc = observer(function ({
196
+ model,
197
+ slice,
198
+ region,
199
+ }: {
200
+ model: CircularViewModel
201
+ slice: Slice
202
+ region: SliceNonElidedRegion
203
+ }) {
204
+ const theme = useTheme()
205
+ const { radiusPx } = model
206
+ const { endRadians, startRadians } = slice
207
+ const centerRadians = (endRadians + startRadians) / 2
208
+ const widthPx = (endRadians - startRadians) * radiusPx
209
+ const session = getSession(model)
210
+ let color
211
+ const assembly = session.assemblyManager.get(region.assemblyName)
212
+ if (assembly) {
213
+ color = assembly.getRefNameColor(region.refName)
214
+ }
215
+ if (color) {
216
+ try {
217
+ color = makeContrasting(color, theme.palette.background.paper)
218
+ } catch (error) {
209
219
  color = theme.palette.text.primary
210
220
  }
221
+ } else {
222
+ color = theme.palette.text.primary
223
+ }
211
224
 
212
- // TODO: slice flipping
213
- return (
214
- <>
215
- <RulerLabel
216
- text={region.refName}
217
- view={model}
218
- maxWidthPx={widthPx}
219
- radians={centerRadians}
220
- radiusPx={radiusPx}
221
- color={color}
222
- />
223
- <path
224
- d={sliceArcPath(slice, radiusPx + 1, region.start, region.end)}
225
- stroke={color}
226
- strokeWidth={2}
227
- fill="none"
228
- >
229
- <title>{region.refName}</title>
230
- </path>
231
- </>
232
- )
233
- },
234
- )
225
+ // TODO: slice flipping
226
+ return (
227
+ <>
228
+ <RulerLabel
229
+ text={region.refName}
230
+ view={model}
231
+ maxWidthPx={widthPx}
232
+ radians={centerRadians}
233
+ radiusPx={radiusPx}
234
+ color={color}
235
+ />
236
+ <path
237
+ d={sliceArcPath(slice, radiusPx + 1, region.start, region.end)}
238
+ stroke={color}
239
+ strokeWidth={2}
240
+ fill="none"
241
+ />
242
+ </>
243
+ )
244
+ })
235
245
 
236
- const CircularRuler = observer(
237
- ({ model, slice }: { model: CircularViewModel; slice: Slice }) => {
238
- if (slice.region.elided) {
239
- return (
240
- <ElisionRulerArc
241
- key={
242
- /* @ts-ignore */
243
- assembleLocString(slice.region.regions[0])
244
- }
245
- model={model}
246
- slice={slice}
247
- />
248
- )
249
- }
246
+ export default observer(function ({
247
+ model,
248
+ slice,
249
+ }: {
250
+ model: CircularViewModel
251
+ slice: Slice
252
+ }) {
253
+ if (slice.region.elided) {
250
254
  return (
251
- <RegionRulerArc
252
- key={assembleLocString(slice.region)}
255
+ <ElisionRulerArc
256
+ key={assembleLocString(slice.region.regions[0])}
253
257
  model={model}
258
+ region={slice.region}
254
259
  slice={slice}
255
260
  />
256
261
  )
257
- },
258
- )
259
-
260
- export default CircularRuler
262
+ }
263
+ return (
264
+ <RegionRulerArc
265
+ key={assembleLocString(slice.region)}
266
+ region={slice.region}
267
+ model={model}
268
+ slice={slice}
269
+ />
270
+ )
271
+ })