@jbrowse/plugin-linear-genome-view 5.0.0-beta.16 → 5.0.0-beta.17

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.
@@ -0,0 +1,18 @@
1
+ /**
2
+ * #config LinearGenomeViewAxis
3
+ * #category view
4
+ * The linear genome view's `axis`: the ruler along its top, whose ticks
5
+ * `grid` carries down through the tracks.
6
+ *
7
+ * #example
8
+ * ```js
9
+ * { type: 'LinearGenomeView', axis: { grid: false } }
10
+ * ```
11
+ */
12
+ export declare const linearGenomeViewAxisConfigSchema: import("@jbrowse/core/configuration/configurationSchema").ConfigurationSchemaType<{
13
+ readonly grid: {
14
+ type: "boolean";
15
+ } & {
16
+ defaultValue: boolean;
17
+ };
18
+ }, import("@jbrowse/core/configuration/configurationSchema").ConfigurationSchemaOptions<undefined, undefined, undefined, import("@jbrowse/core/configuration").ConfigurationSchemaRequirement<Partial<Record<string, string[]>>, string>, any>>;
@@ -0,0 +1,22 @@
1
+ import { ConfigurationSchema } from '@jbrowse/core/configuration';
2
+ /**
3
+ * #config LinearGenomeViewAxis
4
+ * #category view
5
+ * The linear genome view's `axis`: the ruler along its top, whose ticks
6
+ * `grid` carries down through the tracks.
7
+ *
8
+ * #example
9
+ * ```js
10
+ * { type: 'LinearGenomeView', axis: { grid: false } }
11
+ * ```
12
+ */
13
+ export const linearGenomeViewAxisConfigSchema = ConfigurationSchema('LinearGenomeViewAxis', {
14
+ /**
15
+ * #slot
16
+ */
17
+ grid: {
18
+ type: 'boolean',
19
+ defaultValue: true,
20
+ description: "carry the ruler's ticks down through the tracks",
21
+ },
22
+ }, { closed: true });
@@ -2,14 +2,13 @@ import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { useStyleTheme } from '@jbrowse/core/ui/PaletteContext';
3
3
  import { getSession } from '@jbrowse/core/util';
4
4
  import CloseIcon from '@mui/icons-material/Close';
5
+ import EditIcon from '@mui/icons-material/Edit';
5
6
  import VisibilityOffIcon from '@mui/icons-material/VisibilityOff';
6
7
  import { observer } from 'mobx-react';
7
8
  import HighlightBand from './HighlightBand.js';
8
9
  import HighlightChip from './HighlightChip.js';
10
+ import HighlightLabelDialog from './HighlightLabelDialog.js';
9
11
  import { getHighlightColor } from './util.js';
10
- // A band narrower than the link-icon chip clips it into a smudge, so below this
11
- // width the chip is dropped (the band itself still draws).
12
- const CHIP_MIN_WIDTH = 24;
13
12
  const Highlight = observer(function Highlight({ model, highlight, }) {
14
13
  const theme = useStyleTheme();
15
14
  const coords = model.getHighlightCoords(highlight);
@@ -17,7 +16,24 @@ const Highlight = observer(function Highlight({ model, highlight, }) {
17
16
  const label = getSession(model).highlightLabelsVisible
18
17
  ? highlight.label
19
18
  : undefined;
20
- return coords ? (_jsx(HighlightBand, { coords: coords, background: bandColor.toRgbString(), label: label, gaps: model.nonGenomicTrackBands(), children: coords.width >= CHIP_MIN_WIDTH ? (_jsx(HighlightChip, { color: bandColor, label: label, tooltip: highlight.label ?? 'Highlighted region', menuItems: [
19
+ return coords ? (_jsx(HighlightBand, { coords: coords, background: bandColor.toRgbString(), label: label, gaps: model.nonGenomicTrackBands(), children: _jsx(HighlightChip, { color: bandColor, label: label, tooltip: highlight.label ?? 'Highlighted region', menuItems: [
20
+ {
21
+ label: 'Edit label...',
22
+ icon: EditIcon,
23
+ onClick: () => {
24
+ const session = getSession(model);
25
+ session.queueDialog(handleClose => [
26
+ HighlightLabelDialog,
27
+ {
28
+ label: highlight.label,
29
+ onSubmit: (label) => {
30
+ session.updateHighlight(highlight, { label });
31
+ },
32
+ handleClose,
33
+ },
34
+ ]);
35
+ },
36
+ },
21
37
  {
22
38
  label: 'Dismiss highlight',
23
39
  icon: CloseIcon,
@@ -33,6 +49,6 @@ const Highlight = observer(function Highlight({ model, highlight, }) {
33
49
  },
34
50
  },
35
51
  ...model.highlightMenuItems(highlight),
36
- ] })) : null })) : null;
52
+ ] }) })) : null;
37
53
  });
38
54
  export default Highlight;
@@ -1,4 +1,4 @@
1
- import { jsx as _jsx } from "react/jsx-runtime";
1
+ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { makeStyles } from '@jbrowse/core/util/tss-react';
3
3
  import { Typography } from '@mui/material';
4
4
  import { observer } from 'mobx-react';
@@ -16,10 +16,15 @@ const useStyles = makeStyles()(theme => ({
16
16
  zIndex: 1,
17
17
  pointerEvents: 'none',
18
18
  },
19
+ // a sibling of the band, so a label wider than a narrow band runs past its
20
+ // right edge. No `top`: its static position is the band's top edge, where
21
+ // the first track's TrackLabel (zIndex 200) would otherwise cover it
19
22
  overlay: {
20
23
  position: 'absolute',
21
- top: 0,
22
24
  left: 3,
25
+ zIndex: 201,
26
+ pointerEvents: 'none',
27
+ whiteSpace: 'nowrap',
23
28
  },
24
29
  label: {
25
30
  color: theme.palette.text.primary,
@@ -35,20 +40,20 @@ export function gapMask(gaps) {
35
40
  return `linear-gradient(to bottom, ${stops.join(', ')})`;
36
41
  }
37
42
  // OverviewHighlight stays separate (different bpToPx API). A non-interactive
38
- // colored band with an
39
- // optional top label; when `children` (the highlight chip) is passed it renders
40
- // that instead of the plain label. Visibility is toggled from the view menu
43
+ // colored band with an optional top label; when `children` (the highlight
44
+ // chip) is passed it renders that instead of the plain label. Visibility is
45
+ // toggled from the view menu
41
46
  const HighlightBand = observer(function HighlightBand({ coords, background, label, gaps, children, }) {
42
47
  const { classes } = useStyles();
43
48
  const content = children ??
44
49
  (label ? (_jsx(Typography, { variant: "caption", className: classes.label, noWrap: true, children: label })) : null);
45
- return (_jsx("div", { "data-testid": "highlight-band", className: classes.highlight, style: {
46
- transform: `translateX(${coords.left}px)`,
47
- width: coords.width,
48
- background,
49
- ...(gaps?.length
50
- ? { maskImage: gapMask(gaps), WebkitMaskImage: gapMask(gaps) }
51
- : undefined),
52
- }, children: coords.width > 3 && content ? (_jsx("div", { className: classes.overlay, children: content })) : null }));
50
+ return (_jsxs(_Fragment, { children: [_jsx("div", { "data-testid": "highlight-band", className: classes.highlight, style: {
51
+ transform: `translateX(${coords.left}px)`,
52
+ width: coords.width,
53
+ background,
54
+ ...(gaps?.length
55
+ ? { maskImage: gapMask(gaps), WebkitMaskImage: gapMask(gaps) }
56
+ : undefined),
57
+ } }), content ? (_jsx("div", { className: classes.overlay, style: { transform: `translateX(${coords.left}px)` }, children: content })) : null] }));
53
58
  });
54
59
  export default HighlightBand;
@@ -3,13 +3,18 @@ import CascadingMenuButton from '@jbrowse/core/ui/CascadingMenuButton';
3
3
  import LinkIcon from '@mui/icons-material/Link';
4
4
  import { Box, Tooltip, Typography } from '@mui/material';
5
5
  import { observer } from 'mobx-react';
6
- // Interactive chip drawn inside a highlight band: a link icon tinted to the
7
- // band color plus an optional inline label, wrapped in a context menu. Sets its
8
- // own pointer-events since the band is click-through.
6
+ // Interactive chip at a highlight band's top: a link icon tinted to the band
7
+ // color plus an optional inline label, wrapped in a context menu. Its own
8
+ // background keeps it legible over the first track's label, and it sets its own
9
+ // pointer-events since the band is click-through.
9
10
  const HighlightChip = observer(function HighlightChip({ color, label, tooltip, menuItems, }) {
10
11
  // hide the icon color when the band is fully transparent ("label-only"
11
12
  // highlight); otherwise bump to 0.8 alpha so the chip stays legible
12
13
  const chipAlpha = color.alpha() === 0 ? 0 : 0.8;
13
- return (_jsx(Box, { sx: { pointerEvents: 'auto' }, children: _jsx(CascadingMenuButton, { "data-testid": "highlight-chip", menuItems: menuItems, children: _jsx(Tooltip, { title: tooltip, arrow: true, children: _jsxs(Box, { sx: { display: 'flex', alignItems: 'center', gap: 0.5 }, children: [_jsx(LinkIcon, { fontSize: "small", sx: { color: color.alpha(chipAlpha).toRgbString() } }), label ? (_jsx(Typography, { variant: "caption", noWrap: true, children: label })) : null] }) }) }) }));
14
+ return (_jsx(Box, { sx: {
15
+ pointerEvents: 'auto',
16
+ bgcolor: 'background.paper',
17
+ borderRadius: 1,
18
+ }, children: _jsx(CascadingMenuButton, { "data-testid": "highlight-chip", menuItems: menuItems, children: _jsx(Tooltip, { title: tooltip, arrow: true, children: _jsxs(Box, { sx: { display: 'flex', alignItems: 'center', gap: 0.5 }, children: [_jsx(LinkIcon, { fontSize: "small", sx: { color: color.alpha(chipAlpha).toRgbString() } }), label ? (_jsx(Typography, { variant: "caption", noWrap: true, children: label })) : null] }) }) }) }));
14
19
  });
15
20
  export default HighlightChip;
@@ -0,0 +1,6 @@
1
+ declare const HighlightLabelDialog: ({ label, onSubmit, handleClose, }: {
2
+ label?: string;
3
+ onSubmit: (label: string | undefined) => void;
4
+ handleClose: () => void;
5
+ }) => import("react").JSX.Element;
6
+ export default HighlightLabelDialog;
@@ -0,0 +1,18 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { useState } from 'react';
3
+ import { SubmitDialog } from '@jbrowse/core/ui';
4
+ import { TextField } from '@mui/material';
5
+ import { observer } from 'mobx-react';
6
+ // Names a highlight. The label is written on the band and in every view that
7
+ // draws the session's highlights, such as an alignment's column or a
8
+ // structure's residue; an empty label leaves the highlight unnamed.
9
+ const HighlightLabelDialog = observer(function HighlightLabelDialog({ label, onSubmit, handleClose, }) {
10
+ const [val, setVal] = useState(label ?? '');
11
+ return (_jsx(SubmitDialog, { open: true, maxWidth: "xs", fullWidth: true, title: "Edit highlight label", onCancel: handleClose, onSubmit: () => {
12
+ onSubmit(val.trim() || undefined);
13
+ handleClose();
14
+ }, children: _jsx(TextField, { label: "Label", autoFocus: true, fullWidth: true, variant: "outlined", value: val, onChange: event => {
15
+ setVal(event.target.value);
16
+ } }) }));
17
+ });
18
+ export default HighlightLabelDialog;
@@ -1,8 +1,8 @@
1
1
  import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { getFillProps } from '@jbrowse/core/util';
3
3
  // One full-height SVG highlight band with an optional label at the top,
4
- // mirroring the on-screen HighlightBand div. The label is hidden on bands too
5
- // narrow to fit it.
4
+ // mirroring the on-screen HighlightBand div: a label wider than its band runs
5
+ // past the band's right edge.
6
6
  export default function SVGHighlightBand({ coords, height, color, label, labelColor, }) {
7
- return (_jsxs(_Fragment, { children: [_jsx("rect", { x: coords.left, y: 0, width: coords.width, height: height, ...getFillProps(color) }), label && coords.width > 3 ? (_jsx("text", { x: coords.left + 3, y: 2, fontSize: 11, dominantBaseline: "hanging", ...getFillProps(labelColor), children: label })) : null] }));
7
+ return (_jsxs(_Fragment, { children: [_jsx("rect", { x: coords.left, y: 0, width: coords.width, height: height, ...getFillProps(color) }), label ? (_jsx("text", { x: coords.left + 3, y: 2, fontSize: 11, dominantBaseline: "hanging", ...getFillProps(labelColor), children: label })) : null] }));
8
8
  }
@@ -40,13 +40,13 @@ const TracksContainer = observer(function TracksContainer({ children, model, })
40
40
  const { classes } = useStyles();
41
41
  const { pluginManager } = getEnv(model);
42
42
  const sideScroll = useSideScroll(model);
43
- const { showGridlines, showCenterLine } = model;
43
+ const { axisGrid: showGridlines, showCenterLine } = model;
44
44
  const ref = useRef(null);
45
45
  // shift-drag range select over the whole tracks area. This is intentionally
46
46
  // a separate range-select instance from the one inside <Rubberband> (the
47
47
  // scalebar control); don't dedupe them — they cover different regions.
48
48
  const range = useRangeSelect(ref, model, true);
49
- return (_jsxs("div", { ref: ref, "data-testid": "tracksContainer", className: classes.tracksContainer, onPointerDown: sideScroll.pointerDown, onClickCapture: sideScroll.clickCapture, onMouseDown: range.mouseDown, onMouseMove: event => {
49
+ return (_jsxs("div", { ref: ref, "data-testid": "tracksContainer", className: classes.tracksContainer, ...sideScroll, onMouseDown: range.mouseDown, onMouseMove: event => {
50
50
  range.mouseMove(event);
51
51
  }, onMouseLeave: () => {
52
52
  range.mouseOut();
@@ -15,6 +15,7 @@ import type React from 'react';
15
15
  * effect, so a release that lands before React commits still finds them.
16
16
  */
17
17
  export declare function useSideScroll(model: LinearGenomeViewModel): {
18
- pointerDown: (event: React.PointerEvent) => void;
19
- clickCapture: (event: React.MouseEvent) => void;
18
+ onPointerDownCapture: (event: React.PointerEvent) => void;
19
+ onPointerDown: (event: React.PointerEvent) => void;
20
+ onClickCapture: (event: React.MouseEvent) => void;
20
21
  };
@@ -10,7 +10,7 @@ const PAN_CLICK_THRESHOLD_PX = 4;
10
10
  // as `data-gesture-owner`. `data-pan-dragging` is present from press to
11
11
  // release; `data-pan-moved` is set once the press travels past the threshold
12
12
  // or becomes a pinch, and cleared by the next press, so it still answers for
13
- // the click that follows the release: `clickCapture` swallows that click
13
+ // the click that follows the release: `onClickCapture` swallows that click
14
14
  // before any track sees it.
15
15
  const PAN_DRAGGING_ATTR = 'data-pan-dragging';
16
16
  const PAN_MOVED_ATTR = 'data-pan-moved';
@@ -36,16 +36,21 @@ export function useSideScroll(model) {
36
36
  useEffect(() => () => {
37
37
  gestureRef.current?.cancel();
38
38
  }, []);
39
- function pointerDown(event) {
39
+ // The marker outlives the gesture that set it, so every press clears it, in
40
+ // the capture phase: a track that keeps its own press and stops it bubbling
41
+ // (multiway's lane slide, maf's drag select) would otherwise leave the last
42
+ // pan's marker standing to swallow its click.
43
+ function onPointerDownCapture(event) {
44
+ if (!gestureRef.current) {
45
+ event.currentTarget.removeAttribute(PAN_MOVED_ATTR);
46
+ }
47
+ }
48
+ function onPointerDown(event) {
40
49
  const host = event.currentTarget;
41
50
  if (gestureRef.current) {
42
51
  gestureRef.current.join(event);
43
52
  return;
44
53
  }
45
- // Cleared for EVERY press, ahead of the returns below: the marker outlives
46
- // the gesture that set it, and a stale true is what the next reader of
47
- // this DOM contract would inherit.
48
- host.removeAttribute(PAN_MOVED_ATTR);
49
54
  if (event.shiftKey || event.button !== 0 || !event.isPrimary) {
50
55
  return;
51
56
  }
@@ -165,10 +170,10 @@ export function useSideScroll(model) {
165
170
  // The browser fires `click` after a release on whatever the press and the
166
171
  // release share, which over a track is its canvas, so a pan that ended on a
167
172
  // gene would open that gene's details.
168
- function clickCapture(event) {
173
+ function onClickCapture(event) {
169
174
  if (event.currentTarget.hasAttribute(PAN_MOVED_ATTR)) {
170
175
  event.stopPropagation();
171
176
  }
172
177
  }
173
- return { pointerDown, clickCapture };
178
+ return { onPointerDownCapture, onPointerDown, onClickCapture };
174
179
  }
@@ -1,2 +1,2 @@
1
1
  import type { InitState } from './types.ts';
2
- export declare const lgvLaunchKeys: import("@jbrowse/core/util/withLaunchInput").LaunchKeyRegistration<InitState, "bpPerPx" | "offsetPx" | "showCytobandsSetting">;
2
+ export declare const lgvLaunchKeys: import("@jbrowse/core/util/withLaunchInput").LaunchKeyRegistration<InitState, "bpPerPx" | "colorByCDS" | "offsetPx" | "showCytobandsSetting">;
@@ -4,10 +4,11 @@ import { defineLaunchKeys } from '@jbrowse/core/util/withLaunchInput';
4
4
  // key that partitions as a typo.
5
5
  //
6
6
  // `bpPerPx`/`offsetPx` are v4's viewport spelling from before it was stored as
7
- // a window, and `showCytobandsSetting` its cytoband toggle. None is a declared
8
- // property, so the partition would read them as typos; the model's own
9
- // preProcessSnapshot still converts them, so a URL or saved spec naming them
10
- // goes on working. Share links carry all three, so they stay.
7
+ // a window, `showCytobandsSetting` its cytoband toggle and `colorByCDS` its
8
+ // frame coloring. None is a declared property, so the partition would read them
9
+ // as typos; the model's own preProcessSnapshot still converts them, so a URL or
10
+ // saved spec naming them goes on working. Share links carry all four, so they
11
+ // stay.
11
12
  //
12
13
  // #launchKeys LinearGenomeView — the URL parameters page renders this list
13
14
  // rather than restating it; each of these keys has its own `&param=` section
@@ -23,5 +24,5 @@ export const lgvLaunchKeys = defineLaunchKeys()({
23
24
  tracks: { kind: 'trackEntries' },
24
25
  highlight: { kind: 'launch' },
25
26
  }, {
26
- passThrough: ['bpPerPx', 'offsetPx', 'showCytobandsSetting'],
27
+ passThrough: ['bpPerPx', 'offsetPx', 'showCytobandsSetting', 'colorByCDS'],
27
28
  });
@@ -229,9 +229,9 @@ export function buildMenuItems(self) {
229
229
  {
230
230
  label: 'Guidelines',
231
231
  type: 'checkbox',
232
- checked: self.showGridlines,
232
+ checked: self.axisGrid,
233
233
  onClick: () => {
234
- self.setShowGridlines(!self.showGridlines);
234
+ self.setAxisGrid(!self.axisGrid);
235
235
  },
236
236
  },
237
237
  {
@@ -254,9 +254,9 @@ export function buildMenuItems(self) {
254
254
  {
255
255
  label: 'CDS reading frame colors',
256
256
  type: 'checkbox',
257
- checked: self.colorByCDS,
257
+ checked: self.colorByFrame,
258
258
  onClick: () => {
259
- self.setColorByCDS(!self.colorByCDS);
259
+ self.setColorByFrame(!self.colorByFrame);
260
260
  },
261
261
  },
262
262
  {
@@ -55,7 +55,7 @@ export interface HighlightRegion {
55
55
  * views: [
56
56
  * {
57
57
  * type: 'LinearGenomeView',
58
- * colorByCDS: true,
58
+ * colorByFrame: true,
59
59
  * assembly: 'hg38',
60
60
  * loc: 'chr1:1,000,000-1,100,000',
61
61
  * tracks: ['genes', 'alignments'],
@@ -65,7 +65,7 @@ export interface HighlightRegion {
65
65
  * ```
66
66
  * `assembly`, `loc`, `tracks`, `tracklist`, `nav` and `highlight` need
67
67
  * on-attach resolution and are captured into the `launch` property below;
68
- * `colorByCDS`, `showAminoAcids`, `showCenterLine` and `trackLabels` are plain
68
+ * `colorByFrame`, `showAminoAcids`, `showCenterLine` and `trackLabels` are plain
69
69
  * view props MST restores natively. Both are written the same way.
70
70
  * At runtime the same model is driven imperatively — every property and action
71
71
  * below is reachable on `viewState.session.views[0]`:
@@ -80,7 +80,7 @@ export declare function stateModelFactory(pluginManager: PluginManager): import(
80
80
  id: import("@jbrowse/mobx-state-tree").IOptionalIType<import("@jbrowse/mobx-state-tree").ISimpleType<string>, [undefined]>;
81
81
  displayName: import("@jbrowse/mobx-state-tree").IMaybe<import("@jbrowse/mobx-state-tree").ISimpleType<string>>;
82
82
  minimized: import("@jbrowse/mobx-state-tree").IOptionalIType<import("@jbrowse/mobx-state-tree").ISimpleType<boolean>, [undefined]>;
83
- }, "colorByCDS" | "displayedRegions" | "hideHeader" | "hideHeaderOverview" | "hideNoTracksActive" | "id" | "launch" | "legacyBpPerPx" | "scalebarOnly" | "showAminoAcids" | "showCenterLine" | "showCytobands" | "showGridlines" | "showTrackOutlines" | "trackLabels" | "trackSelectorType" | "tracks" | "type" | "windowStartBp" | "windowWidthBp"> & {
83
+ }, "axis" | "colorByFrame" | "displayedRegions" | "hideHeader" | "hideHeaderOverview" | "hideNoTracksActive" | "id" | "launch" | "legacyBpPerPx" | "scalebarOnly" | "showAminoAcids" | "showCenterLine" | "showCytobands" | "showTrackOutlines" | "trackLabels" | "trackSelectorType" | "tracks" | "type" | "windowStartBp" | "windowWidthBp"> & {
84
84
  id: import("@jbrowse/mobx-state-tree").IOptionalIType<import("@jbrowse/mobx-state-tree").ISimpleType<string>, [undefined]>;
85
85
  type: import("@jbrowse/mobx-state-tree").IType<string | undefined, string, string>;
86
86
  windowStartBp: import("@jbrowse/mobx-state-tree").IOptionalIType<import("@jbrowse/mobx-state-tree").ISimpleType<number>, [undefined]>;
@@ -95,8 +95,14 @@ export declare function stateModelFactory(pluginManager: PluginManager): import(
95
95
  showCenterLine: import("@jbrowse/mobx-state-tree").IOptionalIType<import("@jbrowse/mobx-state-tree").ISimpleType<boolean>, [undefined]>;
96
96
  showCytobands: import("@jbrowse/mobx-state-tree").IOptionalIType<import("@jbrowse/mobx-state-tree").ISimpleType<boolean>, [undefined]>;
97
97
  trackLabels: import("@jbrowse/mobx-state-tree").IOptionalIType<import("@jbrowse/mobx-state-tree").ISimpleType<string>, [undefined]>;
98
- showGridlines: import("@jbrowse/mobx-state-tree").IOptionalIType<import("@jbrowse/mobx-state-tree").ISimpleType<boolean>, [undefined]>;
99
- colorByCDS: import("@jbrowse/mobx-state-tree").IOptionalIType<import("@jbrowse/mobx-state-tree").ISimpleType<boolean>, [undefined]>;
98
+ axis: import("@jbrowse/mobx-state-tree").IOptionalIType<import("@jbrowse/core/configuration/configurationSchema").ConfigurationSchemaType<{
99
+ readonly grid: {
100
+ type: "boolean";
101
+ } & {
102
+ defaultValue: boolean;
103
+ };
104
+ }, import("@jbrowse/core/configuration/configurationSchema").ConfigurationSchemaOptions<undefined, undefined, undefined, import("@jbrowse/core/configuration").ConfigurationSchemaRequirement<Partial<Record<string, string[]>>, string>, any>>, [undefined]>;
105
+ colorByFrame: import("@jbrowse/mobx-state-tree").IOptionalIType<import("@jbrowse/mobx-state-tree").ISimpleType<boolean>, [undefined]>;
100
106
  showAminoAcids: import("@jbrowse/mobx-state-tree").IOptionalIType<import("@jbrowse/mobx-state-tree").ISimpleType<boolean>, [undefined]>;
101
107
  showTrackOutlines: import("@jbrowse/mobx-state-tree").IOptionalIType<import("@jbrowse/mobx-state-tree").ISimpleType<boolean>, [undefined]>;
102
108
  scalebarOnly: import("@jbrowse/mobx-state-tree").IOptionalIType<import("@jbrowse/mobx-state-tree").ISimpleType<boolean>, [undefined]>;
@@ -184,6 +190,11 @@ export declare function stateModelFactory(pluginManager: PluginManager): import(
184
190
  */
185
191
  volatileGuides: VolatileGuide[];
186
192
  } & {
193
+ /**
194
+ * #getter
195
+ * `axis.grid`: whether the ruler's ticks run down through the tracks.
196
+ */
197
+ readonly axisGrid: boolean;
187
198
  /**
188
199
  * #getter
189
200
  * the launch state that still has something to apply — the gate every
@@ -857,7 +868,7 @@ export declare function stateModelFactory(pluginManager: PluginManager): import(
857
868
  /**
858
869
  * #action
859
870
  */
860
- setColorByCDS(flag: boolean): void;
871
+ setColorByFrame(flag: boolean): void;
861
872
  /**
862
873
  * #action
863
874
  */
@@ -916,7 +927,7 @@ export declare function stateModelFactory(pluginManager: PluginManager): import(
916
927
  /**
917
928
  * #action
918
929
  */
919
- setShowGridlines(b: boolean): void;
930
+ setAxisGrid(grid: boolean): void;
920
931
  /**
921
932
  * #action
922
933
  * set temporary vertical guides (e.g., for LD display hover)
@@ -1808,7 +1819,7 @@ export declare function stateModelFactory(pluginManager: PluginManager): import(
1808
1819
  id: import("@jbrowse/mobx-state-tree").IOptionalIType<import("@jbrowse/mobx-state-tree").ISimpleType<string>, [undefined]>;
1809
1820
  displayName: import("@jbrowse/mobx-state-tree").IMaybe<import("@jbrowse/mobx-state-tree").ISimpleType<string>>;
1810
1821
  minimized: import("@jbrowse/mobx-state-tree").IOptionalIType<import("@jbrowse/mobx-state-tree").ISimpleType<boolean>, [undefined]>;
1811
- }, "colorByCDS" | "displayedRegions" | "hideHeader" | "hideHeaderOverview" | "hideNoTracksActive" | "id" | "launch" | "legacyBpPerPx" | "scalebarOnly" | "showAminoAcids" | "showCenterLine" | "showCytobands" | "showGridlines" | "showTrackOutlines" | "trackLabels" | "trackSelectorType" | "tracks" | "type" | "windowStartBp" | "windowWidthBp"> & {
1822
+ }, "axis" | "colorByFrame" | "displayedRegions" | "hideHeader" | "hideHeaderOverview" | "hideNoTracksActive" | "id" | "launch" | "legacyBpPerPx" | "scalebarOnly" | "showAminoAcids" | "showCenterLine" | "showCytobands" | "showTrackOutlines" | "trackLabels" | "trackSelectorType" | "tracks" | "type" | "windowStartBp" | "windowWidthBp"> & {
1812
1823
  id: import("@jbrowse/mobx-state-tree").IOptionalIType<import("@jbrowse/mobx-state-tree").ISimpleType<string>, [undefined]>;
1813
1824
  type: import("@jbrowse/mobx-state-tree").IType<string | undefined, string, string>;
1814
1825
  windowStartBp: import("@jbrowse/mobx-state-tree").IOptionalIType<import("@jbrowse/mobx-state-tree").ISimpleType<number>, [undefined]>;
@@ -1823,8 +1834,14 @@ export declare function stateModelFactory(pluginManager: PluginManager): import(
1823
1834
  showCenterLine: import("@jbrowse/mobx-state-tree").IOptionalIType<import("@jbrowse/mobx-state-tree").ISimpleType<boolean>, [undefined]>;
1824
1835
  showCytobands: import("@jbrowse/mobx-state-tree").IOptionalIType<import("@jbrowse/mobx-state-tree").ISimpleType<boolean>, [undefined]>;
1825
1836
  trackLabels: import("@jbrowse/mobx-state-tree").IOptionalIType<import("@jbrowse/mobx-state-tree").ISimpleType<string>, [undefined]>;
1826
- showGridlines: import("@jbrowse/mobx-state-tree").IOptionalIType<import("@jbrowse/mobx-state-tree").ISimpleType<boolean>, [undefined]>;
1827
- colorByCDS: import("@jbrowse/mobx-state-tree").IOptionalIType<import("@jbrowse/mobx-state-tree").ISimpleType<boolean>, [undefined]>;
1837
+ axis: import("@jbrowse/mobx-state-tree").IOptionalIType<import("@jbrowse/core/configuration/configurationSchema").ConfigurationSchemaType<{
1838
+ readonly grid: {
1839
+ type: "boolean";
1840
+ } & {
1841
+ defaultValue: boolean;
1842
+ };
1843
+ }, import("@jbrowse/core/configuration/configurationSchema").ConfigurationSchemaOptions<undefined, undefined, undefined, import("@jbrowse/core/configuration").ConfigurationSchemaRequirement<Partial<Record<string, string[]>>, string>, any>>, [undefined]>;
1844
+ colorByFrame: import("@jbrowse/mobx-state-tree").IOptionalIType<import("@jbrowse/mobx-state-tree").ISimpleType<boolean>, [undefined]>;
1828
1845
  showAminoAcids: import("@jbrowse/mobx-state-tree").IOptionalIType<import("@jbrowse/mobx-state-tree").ISimpleType<boolean>, [undefined]>;
1829
1846
  showTrackOutlines: import("@jbrowse/mobx-state-tree").IOptionalIType<import("@jbrowse/mobx-state-tree").ISimpleType<boolean>, [undefined]>;
1830
1847
  scalebarOnly: import("@jbrowse/mobx-state-tree").IOptionalIType<import("@jbrowse/mobx-state-tree").ISimpleType<boolean>, [undefined]>;
@@ -1912,6 +1929,11 @@ export declare function stateModelFactory(pluginManager: PluginManager): import(
1912
1929
  */
1913
1930
  volatileGuides: VolatileGuide[];
1914
1931
  } & {
1932
+ /**
1933
+ * #getter
1934
+ * `axis.grid`: whether the ruler's ticks run down through the tracks.
1935
+ */
1936
+ readonly axisGrid: boolean;
1915
1937
  /**
1916
1938
  * #getter
1917
1939
  * the launch state that still has something to apply — the gate every
@@ -2585,7 +2607,7 @@ export declare function stateModelFactory(pluginManager: PluginManager): import(
2585
2607
  /**
2586
2608
  * #action
2587
2609
  */
2588
- setColorByCDS(flag: boolean): void;
2610
+ setColorByFrame(flag: boolean): void;
2589
2611
  /**
2590
2612
  * #action
2591
2613
  */
@@ -2644,7 +2666,7 @@ export declare function stateModelFactory(pluginManager: PluginManager): import(
2644
2666
  /**
2645
2667
  * #action
2646
2668
  */
2647
- setShowGridlines(b: boolean): void;
2669
+ setAxisGrid(grid: boolean): void;
2648
2670
  /**
2649
2671
  * #action
2650
2672
  * set temporary vertical guides (e.g., for LD display hover)
@@ -3532,7 +3554,7 @@ export declare function stateModelFactory(pluginManager: PluginManager): import(
3532
3554
  } & {
3533
3555
  afterCreate(): void;
3534
3556
  afterAttach(): void;
3535
- }, import("@jbrowse/mobx-state-tree")._NotCustomized, import("@jbrowse/mobx-state-tree")._NotCustomized>, InitState, "bpPerPx" | "offsetPx" | "showCytobandsSetting">, {
3557
+ }, import("@jbrowse/mobx-state-tree")._NotCustomized, import("@jbrowse/mobx-state-tree")._NotCustomized>, InitState, "bpPerPx" | "colorByCDS" | "offsetPx" | "showCytobandsSetting">, {
3536
3558
  displayName: string | undefined;
3537
3559
  minimized: boolean;
3538
3560
  id: string;
@@ -3548,8 +3570,8 @@ export declare function stateModelFactory(pluginManager: PluginManager): import(
3548
3570
  trackSelectorType: "hierarchical";
3549
3571
  showCytobands: boolean;
3550
3572
  trackLabels: string;
3551
- showGridlines: boolean;
3552
- colorByCDS: boolean;
3573
+ axis: import("@jbrowse/mobx-state-tree").ModelSnapshotType<Record<string, any>>;
3574
+ colorByFrame: boolean;
3553
3575
  showAminoAcids: boolean;
3554
3576
  showTrackOutlines: boolean;
3555
3577
  scalebarOnly: boolean;
@@ -1,5 +1,5 @@
1
1
  import { canonicalLocString } from '@jbrowse/core/TextSearch/places';
2
- import { getConf } from '@jbrowse/core/configuration';
2
+ import { getConf, readConfObject } from '@jbrowse/core/configuration';
3
3
  import { BaseViewModel } from '@jbrowse/core/pluggableElementTypes/models';
4
4
  import { exportViewSvg } from '@jbrowse/core/svg/exportViewSvg';
5
5
  import { assembleLocString, clamp, getBpDisplayStr, getNotificationSink, getSession, isSessionModelWithWidgets, localStorageGetBoolean, localStorageGetItem, localStorageSetBoolean, localStorageSetItem, springAnimate, sum, } from '@jbrowse/core/util';
@@ -15,10 +15,11 @@ import { assemblyErrorMessage, computeViewStatus, viewLoading, } from '@jbrowse/
15
15
  import { pendingLaunch, withLaunchInput, } from '@jbrowse/core/util/withLaunchInput';
16
16
  import { MIN_DISPLAY_HEIGHT } from '@jbrowse/display-kit/const';
17
17
  import { contentRightEdgePx } from '@jbrowse/display-kit/regionHost';
18
- import { cast, getParent, hasParent, isAlive, types, } from '@jbrowse/mobx-state-tree';
18
+ import { cast, getParent, getSnapshot, hasParent, isAlive, types, } from '@jbrowse/mobx-state-tree';
19
19
  import { observable, when } from 'mobx';
20
20
  import { handleSelectedRegion, isOpenInView, navToOption, } from '../searchUtils.js';
21
21
  import { doAfterAttach } from './afterAttach.js';
22
+ import { linearGenomeViewAxisConfigSchema } from './axisConfigSchema.js';
22
23
  import { getCytobands, shouldSwapTracks } from './components/util.js';
23
24
  import { HEADER_BAR_HEIGHT, HEADER_OVERVIEW_HEIGHT, MIN_BP_PER_PX, MINIMIZED_TRACK_HEIGHT, MINIMUM_BLOCK_WIDTH, RESIZE_ALL_HANDLE_HEIGHT, RESIZE_HANDLE_HEIGHT, SCALE_BAR_HEIGHT, SHOW_ALL_REGIONS_FILL, TRACK_OUTLINE_BORDER, TRACK_TOP_GAP, } from './consts.js';
24
25
  import { planFlight } from './flyTo.js';
@@ -154,7 +155,7 @@ function isTrackSelectorVisible(self) {
154
155
  * views: [
155
156
  * {
156
157
  * type: 'LinearGenomeView',
157
- * colorByCDS: true,
158
+ * colorByFrame: true,
158
159
  * assembly: 'hg38',
159
160
  * loc: 'chr1:1,000,000-1,100,000',
160
161
  * tracks: ['genes', 'alignments'],
@@ -164,7 +165,7 @@ function isTrackSelectorVisible(self) {
164
165
  * ```
165
166
  * `assembly`, `loc`, `tracks`, `tracklist`, `nav` and `highlight` need
166
167
  * on-attach resolution and are captured into the `launch` property below;
167
- * `colorByCDS`, `showAminoAcids`, `showCenterLine` and `trackLabels` are plain
168
+ * `colorByFrame`, `showAminoAcids`, `showCenterLine` and `trackLabels` are plain
168
169
  * view props MST restores natively. Both are written the same way.
169
170
  * At runtime the same model is driven imperatively — every property and action
170
171
  * below is reachable on `viewState.session.views[0]`:
@@ -267,7 +268,7 @@ export function stateModelFactory(pluginManager) {
267
268
  /**
268
269
  * #property
269
270
  * show the "center line". A personal preference: never in a session
270
- * snapshot. It, showCytobands, trackLabels, colorByCDS, showAminoAcids
271
+ * snapshot. It, showCytobands, trackLabels, colorByFrame, showAminoAcids
271
272
  * and showTrackOutlines start from this browser's last choice, which
272
273
  * their setters record.
273
274
  */
@@ -291,20 +292,21 @@ export function stateModelFactory(pluginManager) {
291
292
  trackLabels: types.optional(types.string, () => localStorageGetItem('lgv-trackLabels') ?? ''),
292
293
  /**
293
294
  * #property
294
- * show the "gridlines" in the track area
295
+ * The ruler's guide, a [](/docs/config/lineargenomeviewaxis) object:
296
+ * `{ grid: false }` stops its ticks running down through the tracks.
295
297
  */
296
- showGridlines: types.stripDefault(types.boolean, true),
298
+ axis: types.stripDefault(linearGenomeViewAxisConfigSchema, {}),
297
299
  /**
298
300
  * #property
299
301
  * color CDS segments by reading frame
300
302
  */
301
- colorByCDS: types.optional(types.boolean, () => localStorageGetBoolean('lgv-colorByCDS', false)),
303
+ colorByFrame: types.optional(types.boolean, () => localStorageGetBoolean('lgv-colorByFrame', false)),
302
304
  /**
303
305
  * #property
304
306
  * draw translated codons on coding features once zoomed in far enough:
305
307
  * an alternating per-codon shading, and the amino acid letters on top of
306
- * it at base-level zoom. Independent of `colorByCDS`, which only recolors
307
- * the segments by frame.
308
+ * it at base-level zoom. Independent of `colorByFrame`, which only
309
+ * recolors the segments by frame.
308
310
  */
309
311
  showAminoAcids: types.optional(types.boolean, () => localStorageGetBoolean('lgv-showAminoAcids', true)),
310
312
  /**
@@ -406,6 +408,13 @@ export function stateModelFactory(pluginManager) {
406
408
  };
407
409
  })
408
410
  .views(self => ({
411
+ /**
412
+ * #getter
413
+ * `axis.grid`: whether the ruler's ticks run down through the tracks.
414
+ */
415
+ get axisGrid() {
416
+ return readConfObject(self.axis, 'grid');
417
+ },
409
418
  // The viewport in pixels, derived from the window it frames — declared
410
419
  // ahead of every other getter so that the ~300 reads of `self.bpPerPx` /
411
420
  // `self.offsetPx` across the codebase go on resolving by name. Only the
@@ -1053,9 +1062,9 @@ export function stateModelFactory(pluginManager) {
1053
1062
  /**
1054
1063
  * #action
1055
1064
  */
1056
- setColorByCDS(flag) {
1057
- self.colorByCDS = flag;
1058
- localStorageSetBoolean('lgv-colorByCDS', flag);
1065
+ setColorByFrame(flag) {
1066
+ self.colorByFrame = flag;
1067
+ localStorageSetBoolean('lgv-colorByFrame', flag);
1059
1068
  },
1060
1069
  /**
1061
1070
  * #action
@@ -1171,8 +1180,8 @@ export function stateModelFactory(pluginManager) {
1171
1180
  /**
1172
1181
  * #action
1173
1182
  */
1174
- setShowGridlines(b) {
1175
- self.showGridlines = b;
1183
+ setAxisGrid(grid) {
1184
+ self.axis = cast({ ...getSnapshot(self.axis), grid });
1176
1185
  },
1177
1186
  /**
1178
1187
  * #action
@@ -2891,12 +2900,14 @@ export function stateModelFactory(pluginManager) {
2891
2900
  return snap;
2892
2901
  }
2893
2902
  // v4's `showCytobandsSetting` persists as the bare `showCytobands` prop
2894
- // (the capability-gated getter is `effectiveShowCytobands`).
2895
- const { showCytobandsSetting, offsetPx, bpPerPx, ...rest } = snap;
2903
+ // (the capability-gated getter is `effectiveShowCytobands`), and v4's
2904
+ // `colorByCDS` as `colorByFrame`.
2905
+ const { showCytobandsSetting, colorByCDS, offsetPx, bpPerPx, ...rest } = snap;
2896
2906
  return {
2897
2907
  ...(showCytobandsSetting !== undefined
2898
2908
  ? { showCytobands: showCytobandsSetting }
2899
2909
  : {}),
2910
+ ...(colorByCDS !== undefined ? { colorByFrame: colorByCDS } : {}),
2900
2911
  // The viewport used to persist as pixels. Half of that converts
2901
2912
  // exactly and needs no width — the left edge in bp is offsetPx *
2902
2913
  // bpPerPx — and half of it cannot convert at all, because the width
@@ -126,7 +126,7 @@ const FrozenSvgFigure = memo(function FrozenSvgFigure({ view, snapshot, }) {
126
126
  * (hidden sections, collapses, height overrides: `groupStateKey`), which are
127
127
  * volatile and in no snapshot; **the reload counter**, since a Retry or a Force
128
128
  * load leaves every snapshot as it was while the figure has an error or a
129
- * too-large note to replace; **the view's coloring toggles** (`colorByCDS`,
129
+ * too-large note to replace; **the view's coloring toggles** (`colorByFrame`,
130
130
  * `showAminoAcids`), named one by one because the view's own snapshot moves
131
131
  * every pan frame; **the width**, since a resize can widen the canvas without
132
132
  * moving the locus; **the theme**, since each display bakes colors into its own
@@ -172,7 +172,7 @@ function figureKey(view, themeName) {
172
172
  display?.reloadCounter,
173
173
  ];
174
174
  }),
175
- view.colorByCDS,
175
+ view.colorByFrame,
176
176
  view.showAminoAcids,
177
177
  session.getActiveThemeOptions?.(themeName),
178
178
  session.highlightsVisible ? view.highlights : false,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jbrowse/plugin-linear-genome-view",
3
- "version": "5.0.0-beta.16",
3
+ "version": "5.0.0-beta.17",
4
4
  "type": "module",
5
5
  "description": "JBrowse 2 linear genome view",
6
6
  "keywords": [
@@ -26,10 +26,10 @@
26
26
  "@mui/material": "^9.5.0",
27
27
  "mobx": "^7.0.6",
28
28
  "mobx-react": "^10.0.2",
29
- "@jbrowse/core": "^5.0.0-beta.16",
30
- "@jbrowse/display-kit": "^5.0.0-beta.16",
31
- "@jbrowse/display-ui": "^5.0.0-beta.16",
32
- "@jbrowse/render-core": "^5.0.0-beta.16"
29
+ "@jbrowse/core": "^5.0.0-beta.17",
30
+ "@jbrowse/display-ui": "^5.0.0-beta.17",
31
+ "@jbrowse/render-core": "^5.0.0-beta.17",
32
+ "@jbrowse/display-kit": "^5.0.0-beta.17"
33
33
  },
34
34
  "peerDependencies": {
35
35
  "react": ">=19.0.0",
@@ -40,7 +40,7 @@
40
40
  },
41
41
  "sideEffects": false,
42
42
  "devDependencies": {
43
- "@jbrowse/display-test-utils": "^5.0.0-beta.16"
43
+ "@jbrowse/display-test-utils": "^5.0.0-beta.17"
44
44
  },
45
45
  "scripts": {
46
46
  "prebuild": "pnpm clean",