@jbrowse/wiggle-core 5.0.0-beta.11 → 5.0.0-beta.13

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 (52) hide show
  1. package/README.md +21 -7
  2. package/esm/ResolutionStepper.d.ts +1 -0
  3. package/esm/ResolutionStepper.js +5 -2
  4. package/esm/ScoreAxisWidget/RangeFields.d.ts +5 -0
  5. package/esm/ScoreAxisWidget/RangeFields.js +70 -0
  6. package/esm/ScoreAxisWidget/ReferenceLines.d.ts +10 -0
  7. package/esm/ScoreAxisWidget/ReferenceLines.js +53 -0
  8. package/esm/ScoreAxisWidget/ResetAxisButton.d.ts +6 -0
  9. package/esm/ScoreAxisWidget/ResetAxisButton.js +20 -0
  10. package/esm/ScoreAxisWidget/ScoreAxisHeading.d.ts +5 -0
  11. package/esm/ScoreAxisWidget/ScoreAxisHeading.js +9 -0
  12. package/esm/ScoreAxisWidget/ScoreAxisWidget.d.ts +5 -0
  13. package/esm/ScoreAxisWidget/ScoreAxisWidget.js +69 -0
  14. package/esm/ScoreAxisWidget/Section.d.ts +5 -0
  15. package/esm/ScoreAxisWidget/Section.js +13 -0
  16. package/esm/ScoreAxisWidget/ShareAxisWith.d.ts +5 -0
  17. package/esm/ScoreAxisWidget/ShareAxisWith.js +32 -0
  18. package/esm/ScoreAxisWidget/constants.d.ts +1 -0
  19. package/esm/ScoreAxisWidget/constants.js +1 -0
  20. package/esm/ScoreAxisWidget/index.d.ts +8 -0
  21. package/esm/ScoreAxisWidget/index.js +22 -0
  22. package/esm/ScoreAxisWidget/stateModel.d.ts +30 -0
  23. package/esm/ScoreAxisWidget/stateModel.js +30 -0
  24. package/esm/ScoreAxisWidget/types.d.ts +10 -0
  25. package/esm/ScoreAxisWidget/types.js +1 -0
  26. package/esm/ScoreAxisWidget/writeThrough.d.ts +2 -0
  27. package/esm/ScoreAxisWidget/writeThrough.js +7 -0
  28. package/esm/ScorePlotChrome.d.ts +1 -0
  29. package/esm/ScorePlotChrome.js +3 -3
  30. package/esm/ScorePlotSvgFrame.d.ts +7 -12
  31. package/esm/ScorePlotSvgFrame.js +6 -14
  32. package/esm/ScoreScaleMixin.d.ts +5 -5
  33. package/esm/ScoreScaleMixin.js +5 -5
  34. package/esm/autoscale.d.ts +12 -8
  35. package/esm/autoscale.js +18 -11
  36. package/esm/chrome.d.ts +0 -1
  37. package/esm/chrome.js +0 -1
  38. package/esm/dataTypes.d.ts +0 -1
  39. package/esm/index.d.ts +2 -2
  40. package/esm/index.js +1 -1
  41. package/esm/scale.js +1 -1
  42. package/esm/scoreMenuItems.d.ts +14 -28
  43. package/esm/scoreMenuItems.js +35 -130
  44. package/esm/valueScaleConfigSchema.d.ts +4 -5
  45. package/esm/valueScaleConfigSchema.js +21 -19
  46. package/package.json +8 -7
  47. package/esm/AutoscaleGroupDialog.d.ts +0 -7
  48. package/esm/AutoscaleGroupDialog.js +0 -26
  49. package/esm/SetMinMaxDialog.d.ts +0 -12
  50. package/esm/SetMinMaxDialog.js +0 -32
  51. package/esm/SetScoreRulesDialog.d.ts +0 -14
  52. package/esm/SetScoreRulesDialog.js +0 -45
package/README.md CHANGED
@@ -12,14 +12,15 @@ Auto-generated from `#api` JSDoc tags in this package. Do not edit by hand.
12
12
  ### autoscaleDomainFromSpans
13
13
 
14
14
  The domain the visible instances autoscale to: at a `quantile` of 1 their
15
- extremes, `stats`; below it `quantileExtent`'s ends, the bottom read off the
16
- `low`s and the top off the `high`s less the zeros where any is positive, so
17
- whiskers open to their spread, a sparse window's empty bins lower no top, and a
18
- window of nothing above 0 keeps the top it has.
15
+ extremes, `stats`; below it those extremes fenced by `quantileExtent`'s ends
16
+ (`fenceOutliers`), the bottom read off the `low`s and the top off the `high`s
17
+ less the zeros where any is positive, so whiskers open to their spread, a sparse
18
+ window's empty bins lower no top, and a window of nothing above 0 keeps the top
19
+ it has.
19
20
 
20
21
  ```js
21
22
  // type signature
22
- ({ stats, quantile, spans, }: { stats: ScoreStats; quantile: number; spans: ScoreSpan[]; }) => [number, number]
23
+ ({ stats, quantile, zero, spans, }: { stats: ScoreStats; quantile: number; zero: boolean; spans: ScoreSpan[]; }) => [number, number]
23
24
  ```
24
25
 
25
26
  [Source code](https://github.com/GMOD/jbrowse-components/blob/main/packages/wiggle-core/src/autoscale.ts)
@@ -27,11 +28,12 @@ window of nothing above 0 keeps the top it has.
27
28
  ### computeAutoscaleDomain
28
29
 
29
30
  The score domain of the visible feature arrays, following `quantile` as
30
- `scales.y.domainQuantile` says.
31
+ `scales.y.domainQuantile` says, its outliers fenced for an axis that reaches 0
32
+ under `zero`.
31
33
 
32
34
  ```js
33
35
  // type signature
34
- (quantile: number, summaryScoreMode: string, visibleEntries: { data: FeatureArrays; visStart: number; visEnd: number; }[]) => [number, number] | undefined
36
+ (quantile: number, summaryScoreMode: string, visibleEntries: { data: FeatureArrays; visStart: number; visEnd: number; }[], zero: boolean) => [number, number] | undefined
35
37
  ```
36
38
 
37
39
  [Source code](https://github.com/GMOD/jbrowse-components/blob/main/packages/wiggle-core/src/autoscale.ts)
@@ -152,6 +154,18 @@ unless `nice: false` says it is already the one being drawn with.
152
154
 
153
155
  [Source code](https://github.com/GMOD/jbrowse-components/blob/main/packages/wiggle-core/src/scale.ts)
154
156
 
157
+ ### registerScoreAxisWidget
158
+
159
+ Registers the drawer widget the quantitative tracks' "Y axis..." row opens. The
160
+ wiggle plugin calls it once; alignments and the mark display open it by name.
161
+
162
+ ```js
163
+ // type signature
164
+ (pluginManager: PluginManager) => void
165
+ ```
166
+
167
+ [Source code](https://github.com/GMOD/jbrowse-components/blob/main/packages/wiggle-core/src/ScoreAxisWidget/index.ts)
168
+
155
169
  ### resolveSymlogConstant
156
170
 
157
171
  The symlog constant actually used for a domain. `0` (the config default) means
@@ -13,5 +13,6 @@ export declare function makeResolutionSubMenuItem(opts: {
13
13
  onReset: () => void;
14
14
  icon?: React.ElementType;
15
15
  resetTitle?: string;
16
+ trailingItems?: MenuItem[];
16
17
  }): MenuItem;
17
18
  export {};
@@ -39,9 +39,11 @@ const ResolutionStepper = observer(function ResolutionStepper({ getState, onFine
39
39
  });
40
40
  // A "Resolution" submenu whose single row is the inline stepper, so it sits
41
41
  // alongside the other track-menu submenus but keeps the menu open while
42
- // stepping. Callers own the label formatting, step bounds, and step actions.
42
+ // stepping. Callers own the label formatting, step bounds, and step actions;
43
+ // `trailingItems` follow the stepper (wiggle's summary score mode, which reads
44
+ // the same bins).
43
45
  export function makeResolutionSubMenuItem(opts) {
44
- const { getState, onFiner, onCoarser, onReset, icon, resetTitle } = opts;
46
+ const { getState, onFiner, onCoarser, onReset, icon, resetTitle, trailingItems = [], } = opts;
45
47
  return {
46
48
  label: 'Resolution',
47
49
  icon,
@@ -51,6 +53,7 @@ export function makeResolutionSubMenuItem(opts) {
51
53
  type: 'custom',
52
54
  render: () => (_jsx(ResolutionStepper, { getState: getState, onFiner: onFiner, onCoarser: onCoarser, onReset: onReset, resetTitle: resetTitle })),
53
55
  },
56
+ ...trailingItems,
54
57
  ],
55
58
  };
56
59
  }
@@ -0,0 +1,5 @@
1
+ import type { ScoreAxisDisplay } from './types.ts';
2
+ declare const _default: ({ display, }: {
3
+ display: ScoreAxisDisplay;
4
+ }) => import("react").JSX.Element;
5
+ export default _default;
@@ -0,0 +1,70 @@
1
+ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useRef, useState } from 'react';
3
+ import { useFinalUnmount } from '@jbrowse/core/util/hooks';
4
+ import { isAlive } from '@jbrowse/mobx-state-tree';
5
+ import { FormHelperText, TextField, Typography } from '@mui/material';
6
+ import { observer } from 'mobx-react';
7
+ import { toP } from '../formatNumber.js';
8
+ import { writeThrough } from './writeThrough.js';
9
+ const DEBOUNCE_MS = 300;
10
+ // '' is "follow the data"; anything else that isn't a number is held back
11
+ // rather than read as empty, which would unpin the end.
12
+ function parse(text) {
13
+ const trimmed = text.trim();
14
+ return trimmed === '' ? undefined : Number(trimmed);
15
+ }
16
+ function problem(min, max, scaleType) {
17
+ if (Number.isNaN(min) || Number.isNaN(max)) {
18
+ return 'Enter a number, or leave the field empty to follow the data';
19
+ }
20
+ if (min !== undefined && max !== undefined && !(max > min)) {
21
+ return 'Max must be greater than min';
22
+ }
23
+ if (scaleType === 'log' && min !== undefined && min <= 0) {
24
+ return 'A log axis needs a min above 0';
25
+ }
26
+ return undefined;
27
+ }
28
+ function formatRange([lo, hi]) {
29
+ return `${toP(lo, 4)} – ${toP(hi, 4)}`;
30
+ }
31
+ // Typing 190 would otherwise redraw at 1 and 19 on the way; the pair writes
32
+ // once the typing pauses, and at once on blur or when the drawer closes.
33
+ export default observer(function RangeFields({ display, }) {
34
+ const { manualMinScore, manualMaxScore, scaleType, autoscaleRange } = display;
35
+ const [text, setText] = useState({
36
+ min: manualMinScore === undefined ? '' : `${manualMinScore}`,
37
+ max: manualMaxScore === undefined ? '' : `${manualMaxScore}`,
38
+ });
39
+ const latest = useRef(text);
40
+ const timer = useRef(undefined);
41
+ const flush = () => {
42
+ clearTimeout(timer.current);
43
+ timer.current = undefined;
44
+ const min = parse(latest.current.min);
45
+ const max = parse(latest.current.max);
46
+ if (isAlive(display) &&
47
+ problem(min, max, display.scaleType) === undefined &&
48
+ (min !== display.manualMinScore || max !== display.manualMaxScore)) {
49
+ writeThrough(display, () => {
50
+ display.setMinScore(min);
51
+ display.setMaxScore(max);
52
+ });
53
+ }
54
+ };
55
+ useFinalUnmount(flush);
56
+ const edit = (patch) => {
57
+ const next = { ...latest.current, ...patch };
58
+ latest.current = next;
59
+ setText(next);
60
+ clearTimeout(timer.current);
61
+ timer.current = setTimeout(flush, DEBOUNCE_MS);
62
+ };
63
+ const error = problem(parse(text.min), parse(text.max), scaleType);
64
+ return (_jsxs(_Fragment, { children: [_jsx("div", { style: { display: 'flex', gap: 16 }, children: ['min', 'max'].map(end => (_jsx(TextField, { label: end === 'min' ? 'Min' : 'Max', placeholder: "auto", value: text[end], error: error !== undefined, autoComplete: "off", size: "small", variant: "standard", slotProps: {
65
+ inputLabel: { shrink: true },
66
+ htmlInput: { inputMode: 'decimal' },
67
+ }, onChange: event => {
68
+ edit({ [end]: event.target.value });
69
+ }, onBlur: flush }, end))) }), error ? (_jsx(FormHelperText, { error: true, children: error })) : autoscaleRange ? (_jsxs(Typography, { variant: "body2", color: "text.secondary", children: ["Values in view: ", formatRange(autoscaleRange)] })) : null] }));
70
+ });
@@ -0,0 +1,10 @@
1
+ import type { ScoreAxisDisplay } from './types.ts';
2
+ /**
3
+ * `scales.y.rules`, one row per line: its value on the plot's own scale, its
4
+ * label and its colour, an empty colour taking the display's. The lines
5
+ * redraw as soon as every row reads; a half-typed row waits.
6
+ */
7
+ declare const _default: ({ display, }: {
8
+ display: ScoreAxisDisplay;
9
+ }) => import("react").JSX.Element;
10
+ export default _default;
@@ -0,0 +1,53 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useState } from 'react';
3
+ import { isCssColor } from '@jbrowse/core/util/colorBits';
4
+ import DeleteIcon from '@mui/icons-material/Delete';
5
+ import { Button, IconButton, TextField, Typography } from '@mui/material';
6
+ import { observer } from 'mobx-react';
7
+ import { writeThrough } from './writeThrough.js';
8
+ function rowOf({ value, label = '', color = '' }) {
9
+ return { value: String(value), label, color };
10
+ }
11
+ function valueOk({ value }) {
12
+ return value.trim() !== '' && Number.isFinite(Number(value));
13
+ }
14
+ function colorOk({ color }) {
15
+ return color.trim() === '' || isCssColor(color);
16
+ }
17
+ function ruleOf(row) {
18
+ return {
19
+ value: Number(row.value),
20
+ ...(row.label ? { label: row.label } : {}),
21
+ ...(row.color.trim() ? { color: row.color.trim() } : {}),
22
+ };
23
+ }
24
+ /**
25
+ * `scales.y.rules`, one row per line: its value on the plot's own scale, its
26
+ * label and its colour, an empty colour taking the display's. The lines
27
+ * redraw as soon as every row reads; a half-typed row waits.
28
+ */
29
+ export default observer(function ReferenceLines({ display, }) {
30
+ const [rows, setRows] = useState(() => (display.scoreRules ?? []).map(rowOf));
31
+ const commit = (next) => {
32
+ setRows(next);
33
+ if (next.every(row => valueOk(row) && colorOk(row))) {
34
+ writeThrough(display, () => {
35
+ display.setScoreRules?.(next.map(ruleOf));
36
+ });
37
+ }
38
+ };
39
+ const setRow = (index, patch) => {
40
+ commit(rows.map((row, i) => (i === index ? { ...row, ...patch } : row)));
41
+ };
42
+ return (_jsxs("div", { children: [_jsx(Typography, { variant: "body2", children: "Reference lines" }), rows.map((row, index) => (_jsxs("div", { style: { display: 'flex', gap: 8, marginTop: 4 }, children: [_jsx(TextField, { label: "Value", size: "small", variant: "standard", value: row.value, error: !valueOk(row), slotProps: { htmlInput: { inputMode: 'decimal' } }, onChange: event => {
43
+ setRow(index, { value: event.target.value });
44
+ } }), _jsx(TextField, { label: "Label", size: "small", variant: "standard", value: row.label, onChange: event => {
45
+ setRow(index, { label: event.target.value });
46
+ } }), _jsx(TextField, { label: "Color", size: "small", variant: "standard", value: row.color, error: !colorOk(row), onChange: event => {
47
+ setRow(index, { color: event.target.value });
48
+ } }), _jsx(IconButton, { "aria-label": "Remove line", size: "small", onClick: () => {
49
+ commit(rows.filter((_, i) => i !== index));
50
+ }, children: _jsx(DeleteIcon, { fontSize: "small" }) })] }, index))), _jsx(Button, { size: "small", onClick: () => {
51
+ setRows([...rows, { value: '', label: '', color: '' }]);
52
+ }, children: "Add line" })] }));
53
+ });
@@ -0,0 +1,6 @@
1
+ import type { ScoreAxisDisplay } from './types.ts';
2
+ export declare function resetAxis(display: ScoreAxisDisplay): void;
3
+ export default function ResetAxisButton({ display, onReset, }: {
4
+ display: ScoreAxisDisplay;
5
+ onReset: () => void;
6
+ }): import("react").JSX.Element;
@@ -0,0 +1,20 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { setConf } from '@jbrowse/core/configuration';
3
+ import { baseDisplayConfig } from '@jbrowse/core/util/baseDisplayConfig';
4
+ import { Button } from '@mui/material';
5
+ import { writeThrough } from './writeThrough.js';
6
+ // Back to what the config.json says, or to what a session-owned track was
7
+ // added with, so an admin's declared axis survives a reset; slots the base
8
+ // leaves unset take their defaults.
9
+ export function resetAxis(display) {
10
+ const base = baseDisplayConfig(display).scales;
11
+ writeThrough(display, () => {
12
+ setConf(display.configuration, ['scales', 'y'], base?.y ?? {});
13
+ });
14
+ }
15
+ export default function ResetAxisButton({ display, onReset, }) {
16
+ return (_jsx("div", { children: _jsx(Button, { variant: "outlined", size: "small", onClick: () => {
17
+ resetAxis(display);
18
+ onReset();
19
+ }, children: "Reset to defaults" }) }));
20
+ }
@@ -0,0 +1,5 @@
1
+ import type { ScoreAxisWidgetModel } from './stateModel.ts';
2
+ declare const _default: ({ model, }: {
3
+ model: ScoreAxisWidgetModel;
4
+ }) => string;
5
+ export default _default;
@@ -0,0 +1,9 @@
1
+ import { getConf } from '@jbrowse/core/configuration';
2
+ import { getContainingTrack } from '@jbrowse/core/util/mstUtils';
3
+ import { observer } from 'mobx-react';
4
+ export default observer(function ScoreAxisHeading({ model, }) {
5
+ const { display, label } = model;
6
+ return display
7
+ ? `${label} — ${getConf(getContainingTrack(display), 'name')}`
8
+ : label;
9
+ });
@@ -0,0 +1,5 @@
1
+ import type { ScoreAxisWidgetModel } from './stateModel.ts';
2
+ declare const _default: ({ model, }: {
3
+ model: ScoreAxisWidgetModel;
4
+ }) => import("react").JSX.Element;
5
+ export default _default;
@@ -0,0 +1,69 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useState } from 'react';
3
+ import { LabeledCheckbox } from '@jbrowse/core/ui';
4
+ import { makeStyles } from '@jbrowse/core/util/tss-react';
5
+ import { FormControlLabel, FormHelperText, Radio, RadioGroup, Typography, } from '@mui/material';
6
+ import { observer } from 'mobx-react';
7
+ import { VALUE_SCALE_TYPES } from '../valueScaleConfigSchema.js';
8
+ import RangeFields from './RangeFields.js';
9
+ import ReferenceLines from './ReferenceLines.js';
10
+ import ResetAxisButton from './ResetAxisButton.js';
11
+ import Section from './Section.js';
12
+ import ShareAxisWith from './ShareAxisWith.js';
13
+ import { writeThrough } from './writeThrough.js';
14
+ const useStyles = makeStyles()(theme => ({
15
+ root: {
16
+ padding: theme.spacing(2),
17
+ display: 'grid',
18
+ gap: theme.spacing(2.5),
19
+ },
20
+ }));
21
+ const SCALE_TYPE_LABELS = {
22
+ linear: 'Linear',
23
+ log: 'Log',
24
+ symlog: 'Symlog',
25
+ };
26
+ // A density plot maps score to colour and rules no band, so it has no 0 to
27
+ // include and no lines to draw across it.
28
+ function rulesABand(display) {
29
+ return display.scoreRulesDrawn === true;
30
+ }
31
+ const AxisEditor = observer(function AxisEditor({ display, onReset, }) {
32
+ const { classes } = useStyles();
33
+ const { scaleType, manualMinScore, manualMaxScore, domainQuantile } = display;
34
+ const bothPinned = manualMinScore !== undefined && manualMaxScore !== undefined;
35
+ const zeroBlocker = scaleType === 'log'
36
+ ? 'A log axis has no 0'
37
+ : bothPinned
38
+ ? 'Both ends are set'
39
+ : undefined;
40
+ return (_jsxs("div", { className: classes.root, children: [_jsx(Section, { title: "Scale", children: _jsx(RadioGroup, { row: true, value: scaleType, onChange: event => {
41
+ writeThrough(display, () => {
42
+ display.setScaleType(event.target.value);
43
+ });
44
+ }, children: VALUE_SCALE_TYPES.map(type => (_jsx(FormControlLabel, { value: type, control: _jsx(Radio, { size: "small" }), label: SCALE_TYPE_LABELS[type] }, type))) }) }), _jsxs(Section, { title: "Range", children: [_jsx(RangeFields, { display: display }), rulesABand(display) ? (_jsx(LabeledCheckbox, { label: "Include 0", checked: display.scaleZero, disabled: zeroBlocker !== undefined, onChange: zero => {
45
+ writeThrough(display, () => {
46
+ display.setScaleZero(zero);
47
+ });
48
+ } })) : null, _jsx(LabeledCheckbox, { label: "Clip extreme outliers", checked: domainQuantile < 1, disabled: bothPinned, onChange: on => {
49
+ writeThrough(display, () => {
50
+ display.setDomainQuantile(on ? display.clipQuantile : 1);
51
+ });
52
+ } }), _jsx(FormHelperText, { children: bothPinned
53
+ ? 'Both ends are set, so nothing here moves the axis'
54
+ : (zeroBlocker ??
55
+ `Clipping cuts a value that would leave the others under half the axis, and marks it in red`) })] }), _jsx(ShareAxisWith, { display: display }), _jsxs(Section, { title: "Guides", children: [_jsx(LabeledCheckbox, { label: "Grid lines", checked: display.grid, onChange: grid => {
56
+ writeThrough(display, () => {
57
+ display.setGrid(grid);
58
+ });
59
+ } }), rulesABand(display) ? _jsx(ReferenceLines, { display: display }) : null] }), _jsx(ResetAxisButton, { display: display, onReset: onReset })] }));
60
+ });
61
+ // A reset rewrites what the text fields and reference-line rows hold as their
62
+ // own draft, so it remounts them.
63
+ export default observer(function ScoreAxisWidget({ model, }) {
64
+ const [generation, setGeneration] = useState(0);
65
+ const display = model.display;
66
+ return display ? (_jsx(AxisEditor, { display: display, onReset: () => {
67
+ setGeneration(n => n + 1);
68
+ } }, `${display.id}-${generation}`)) : (_jsx(Typography, { color: "text.secondary", style: { padding: 16 }, children: "The track this edited is no longer shown." }));
69
+ });
@@ -0,0 +1,5 @@
1
+ import type { ReactNode } from 'react';
2
+ export default function Section({ title, children, }: {
3
+ title: string;
4
+ children: ReactNode;
5
+ }): import("react").JSX.Element;
@@ -0,0 +1,13 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { makeStyles } from '@jbrowse/core/util/tss-react';
3
+ import { Typography } from '@mui/material';
4
+ const useStyles = makeStyles()(theme => ({
5
+ section: {
6
+ display: 'grid',
7
+ gap: theme.spacing(0.5),
8
+ },
9
+ }));
10
+ export default function Section({ title, children, }) {
11
+ const { classes } = useStyles();
12
+ return (_jsxs("div", { className: classes.section, children: [_jsx(Typography, { variant: "subtitle2", children: title }), children] }));
13
+ }
@@ -0,0 +1,5 @@
1
+ import type { ScoreAxisDisplay } from './types.ts';
2
+ declare const _default: ({ display, }: {
3
+ display: ScoreAxisDisplay;
4
+ }) => import("react").JSX.Element | null;
5
+ export default _default;
@@ -0,0 +1,32 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { LabeledCheckbox } from '@jbrowse/core/ui';
3
+ import { getContainingTrack } from '@jbrowse/core/util/mstUtils';
4
+ import { observer } from 'mobx-react';
5
+ import { autoscalePeers, autoscaleWith } from '../autoscaleGroup.js';
6
+ import Section from './Section.js';
7
+ function sharesAxis(display) {
8
+ return display.setAutoscaleGroup !== undefined;
9
+ }
10
+ // Offered once the view holds another track with a value axis to share. A tick
11
+ // writes the peers' config too, so each one's track saves.
12
+ export default observer(function ShareAxisWith({ display, }) {
13
+ if (!sharesAxis(display)) {
14
+ return null;
15
+ }
16
+ const peers = autoscalePeers(display);
17
+ if (peers.length === 0) {
18
+ return null;
19
+ }
20
+ const group = display.autoscaleGroup;
21
+ const inGroup = (peer) => group !== undefined && peer.autoscaleGroup === group;
22
+ return (_jsx(Section, { title: "Share axis with", children: peers.map(({ display: peer, key, name }) => (_jsx(LabeledCheckbox, { label: name, checked: inGroup(peer), onChange: checked => {
23
+ const chosen = new Set(peers.map(p => p.display).filter(p => p !== peer && inGroup(p)));
24
+ if (checked) {
25
+ chosen.add(peer);
26
+ }
27
+ autoscaleWith(display, peers.map(p => p.display), chosen);
28
+ for (const d of [display, ...peers.map(p => p.display)]) {
29
+ getContainingTrack(d).persistConfigurationNow?.();
30
+ }
31
+ } }, key))) }));
32
+ });
@@ -0,0 +1 @@
1
+ export declare const SCORE_AXIS_WIDGET = "ScoreAxisWidget";
@@ -0,0 +1 @@
1
+ export const SCORE_AXIS_WIDGET = 'ScoreAxisWidget';
@@ -0,0 +1,8 @@
1
+ import type PluginManager from '@jbrowse/core/PluginManager';
2
+ /**
3
+ * #api
4
+ * Registers the drawer widget the quantitative tracks' "Y axis..." row opens.
5
+ * The wiggle plugin calls it once; alignments and the mark display open it by
6
+ * name.
7
+ */
8
+ export declare function registerScoreAxisWidget(pluginManager: PluginManager): void;
@@ -0,0 +1,22 @@
1
+ import { ConfigurationSchema } from '@jbrowse/core/configuration';
2
+ import { WidgetType } from '@jbrowse/core/pluggableElementTypes';
3
+ import { lazyWithPreload } from '@jbrowse/core/util/lazyWithPreload';
4
+ import ScoreAxisHeading from './ScoreAxisHeading.js';
5
+ import { SCORE_AXIS_WIDGET } from './constants.js';
6
+ import { stateModelFactory } from './stateModel.js';
7
+ /**
8
+ * #api
9
+ * Registers the drawer widget the quantitative tracks' "Y axis..." row opens.
10
+ * The wiggle plugin calls it once; alignments and the mark display open it by
11
+ * name.
12
+ */
13
+ export function registerScoreAxisWidget(pluginManager) {
14
+ pluginManager.addWidgetType(() => new WidgetType({
15
+ name: SCORE_AXIS_WIDGET,
16
+ discardOnClose: true,
17
+ HeadingComponent: ScoreAxisHeading,
18
+ configSchema: ConfigurationSchema(SCORE_AXIS_WIDGET, {}),
19
+ stateModel: stateModelFactory(pluginManager),
20
+ ReactComponent: lazyWithPreload(() => import('./ScoreAxisWidget.js')),
21
+ }));
22
+ }
@@ -0,0 +1,30 @@
1
+ import type PluginManager from '@jbrowse/core/PluginManager';
2
+ import type { Instance } from '@jbrowse/mobx-state-tree';
3
+ /**
4
+ * #stateModel ScoreAxisWidget
5
+ * Drawer widget editing one display's value scale, `scales.y`. The display is
6
+ * a safe reference, so hiding its track or closing its view empties it rather
7
+ * than leaving the widget writing to a dead node.
8
+ */
9
+ export declare function stateModelFactory(pluginManager: PluginManager): import("@jbrowse/mobx-state-tree").IModelType<{
10
+ /**
11
+ * #property
12
+ */
13
+ id: import("@jbrowse/mobx-state-tree").IOptionalIType<import("@jbrowse/mobx-state-tree").ISimpleType<string>, [undefined]>;
14
+ /**
15
+ * #property
16
+ */
17
+ type: import("@jbrowse/mobx-state-tree").ILiteralType<"ScoreAxisWidget">;
18
+ /**
19
+ * #property
20
+ */
21
+ display: import("@jbrowse/mobx-state-tree").IMaybe<import("@jbrowse/mobx-state-tree").IReferenceType<import("@jbrowse/mobx-state-tree").IAnyType>>;
22
+ /**
23
+ * #property
24
+ * The name of the menu row that opened it, "Y axis" or "Coverage axis",
25
+ * so the heading says the same.
26
+ */
27
+ label: import("@jbrowse/mobx-state-tree").IOptionalIType<import("@jbrowse/mobx-state-tree").ISimpleType<string>, [undefined]>;
28
+ }, {}, import("@jbrowse/mobx-state-tree")._NotCustomized, import("@jbrowse/mobx-state-tree")._NotCustomized>;
29
+ export interface ScoreAxisWidgetModel extends Instance<ReturnType<typeof stateModelFactory>> {
30
+ }
@@ -0,0 +1,30 @@
1
+ import { ElementId } from '@jbrowse/core/util/types/mst';
2
+ import { types } from '@jbrowse/mobx-state-tree';
3
+ /**
4
+ * #stateModel ScoreAxisWidget
5
+ * Drawer widget editing one display's value scale, `scales.y`. The display is
6
+ * a safe reference, so hiding its track or closing its view empties it rather
7
+ * than leaving the widget writing to a dead node.
8
+ */
9
+ export function stateModelFactory(pluginManager) {
10
+ return types.model('ScoreAxisWidget', {
11
+ /**
12
+ * #property
13
+ */
14
+ id: ElementId,
15
+ /**
16
+ * #property
17
+ */
18
+ type: types.literal('ScoreAxisWidget'),
19
+ /**
20
+ * #property
21
+ */
22
+ display: types.safeReference(pluginManager.pluggableMstType('display', 'stateModel')),
23
+ /**
24
+ * #property
25
+ * The name of the menu row that opened it, "Y axis" or "Coverage axis",
26
+ * so the heading says the same.
27
+ */
28
+ label: types.optional(types.string, 'Y axis'),
29
+ });
30
+ }
@@ -0,0 +1,10 @@
1
+ import type { AutoscalePeer } from '../autoscaleGroup.ts';
2
+ import type { ScoreRulesModel, ScoreScaleModel } from '../scoreMenuItems.ts';
3
+ import type { AnyConfigurationModel } from '@jbrowse/core/configuration';
4
+ /** What the widget edits: a display's value scale and the guides it owns. */
5
+ export type ScoreAxisDisplay = ScoreScaleModel & Partial<ScoreRulesModel> & Partial<AutoscalePeer> & {
6
+ id: string;
7
+ configuration: AnyConfigurationModel;
8
+ grid: boolean;
9
+ setGrid: (grid: boolean) => void;
10
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,2 @@
1
+ import type { ScoreAxisDisplay } from './types.ts';
2
+ export declare function writeThrough(display: ScoreAxisDisplay, edit: () => void): void;
@@ -0,0 +1,7 @@
1
+ import { getContainingTrack } from '@jbrowse/core/util/mstUtils';
2
+ // Each write lands in the session at once rather than after the track's
3
+ // debounced save, so undo steps one edit at a time.
4
+ export function writeThrough(display, edit) {
5
+ edit();
6
+ getContainingTrack(display).persistConfigurationNow?.();
7
+ }
@@ -22,6 +22,7 @@ export interface ScorePlotContextMenuHost<Hit> extends ContextMenuView {
22
22
  }
23
23
  export type ScorePlotChromeModel<Hit, TRegion, TState extends FrameDimensions> = ChromeModel & RenderLifecycleModel<PerRegionRenderingBackend<TRegion, TState>> & {
24
24
  canvasWidthPx: number;
25
+ scrollableHeight: number;
25
26
  setHoveredFeature: (hit?: Hit) => void;
26
27
  selectFeature: (hit: Hit) => void;
27
28
  };
@@ -34,14 +34,14 @@ export const ScorePlotChrome = observer(function ScorePlotChrome({ model, marks,
34
34
  ? { clientX: event.clientX, clientY: event.clientY, hit }
35
35
  : undefined);
36
36
  }
37
- : undefined, children: ({ canvasRef, mouseTracker }) => (_jsx(ScorePlotBody, { canvasRef: canvasRef, mouseTracker: mouseTracker, width: width, plotGeometry: box, tooltip: tooltip, overlay: overlay, indicators: indicators, contextMenu: contextMenu })) }));
37
+ : undefined, children: ({ canvasRef, mouseTracker }) => (_jsx(ScorePlotBody, { model: model, canvasRef: canvasRef, mouseTracker: mouseTracker, width: width, plotGeometry: box, tooltip: tooltip, overlay: overlay, indicators: indicators, contextMenu: contextMenu })) }));
38
38
  });
39
- const ScorePlotBody = observer(function ScorePlotBody({ canvasRef, mouseTracker, width, plotGeometry, tooltip, overlay, indicators, contextMenu, }) {
39
+ const ScorePlotBody = observer(function ScorePlotBody({ model, canvasRef, mouseTracker, width, plotGeometry, tooltip, overlay, indicators, contextMenu, }) {
40
40
  return (_jsxs(_Fragment, { children: [_jsx("canvas", { ref: canvasRef, style: {
41
41
  width,
42
42
  height: plotGeometry.plotHeight,
43
43
  position: 'absolute',
44
44
  left: 0,
45
45
  top: plotGeometry.yTop,
46
- } }), overlay?.(plotGeometry), _jsx(PointerLayer, { mouseTracker: mouseTracker, children: tooltip }), indicators ? (_jsx(BottomRightIndicators, { children: indicators() })) : null, contextMenu ? _jsx(DisplayContextMenu, { model: contextMenu }) : null] }));
46
+ } }), overlay?.(plotGeometry), _jsx(PointerLayer, { mouseTracker: mouseTracker, children: tooltip }), indicators ? (_jsx(BottomRightIndicators, { scrollableHeight: model.scrollableHeight, children: indicators() })) : null, contextMenu ? _jsx(DisplayContextMenu, { model: contextMenu }) : null] }));
47
47
  });
@@ -1,21 +1,16 @@
1
- import type { SvgExportable } from '@jbrowse/core/svg/svgReady';
2
- import type { LgvSvgBodyProps } from '@jbrowse/display-kit/renderDisplaySvg';
1
+ import type { LgvSvgBodyProps, LgvSvgExportable } from '@jbrowse/display-kit/renderDisplaySvg';
3
2
  import type { Mark, MarkFrame } from '@jbrowse/render-core/marks';
4
3
  import type React from 'react';
5
- export interface ScorePlotSvgModel extends SvgExportable {
6
- id: string;
7
- height: number;
8
- }
4
+ export type ScorePlotSvgModel = LgvSvgExportable;
9
5
  /**
10
6
  * The SVG-export body of a display with a score axis, mounted through
11
7
  * `renderDisplaySvg`, which draws the axis and cross-hatches. Paints `marks`
12
- * over `regions` inside the plot box, at the export's width and the box's
13
- * height — the two fields of `renderState` the on-screen canvas sizes
14
- * differently. `plotGeometry` defaults to the single-plot box `ScorePlotChrome`
15
- * draws in; multi-wiggle stacks its rows over the full height instead.
16
- * `children` draw over the plot, in the same untranslated space as the axis.
8
+ * over `regions` inside the plot box. `plotGeometry` defaults to the
9
+ * single-plot box `ScorePlotChrome` draws in; multi-wiggle stacks its rows over
10
+ * the full height instead. `children` draw over the plot, in the same
11
+ * untranslated space as the axis.
17
12
  */
18
- export declare function ScorePlotSvgFrame<TRegion, TState extends MarkFrame>({ height, canvasWidth, renderBlocks, opts, plotGeometry, marks, regions, renderState, children, }: LgvSvgBodyProps<ScorePlotSvgModel> & {
13
+ export declare function ScorePlotSvgFrame<TRegion, TState extends MarkFrame>({ height, canvasWidth, renderBlocks, opts, plotGeometry, marks, regions, renderState, children, }: Pick<LgvSvgBodyProps<unknown>, 'height' | 'canvasWidth' | 'renderBlocks' | 'opts'> & {
19
14
  plotGeometry?: {
20
15
  yTop: number;
21
16
  plotHeight: number;