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

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 (68) hide show
  1. package/README.md +28 -13
  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 +7 -0
  9. package/esm/ScoreAxisWidget/ResetAxisButton.js +22 -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 +6 -0
  15. package/esm/ScoreAxisWidget/Section.js +15 -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 +9 -8
  29. package/esm/ScorePlotChrome.js +16 -8
  30. package/esm/ScorePlotSvgFrame.d.ts +7 -12
  31. package/esm/ScorePlotSvgFrame.js +6 -14
  32. package/esm/ScoreScaleMixin.d.ts +7 -14
  33. package/esm/ScoreScaleMixin.js +7 -21
  34. package/esm/WiggleScoreConfigMixin.d.ts +5 -22
  35. package/esm/WiggleScoreConfigMixin.js +5 -25
  36. package/esm/{ScoreFieldConfigMixin.d.ts → YFieldConfigMixin.d.ts} +11 -15
  37. package/esm/YFieldConfigMixin.js +25 -0
  38. package/esm/autoscale.d.ts +20 -15
  39. package/esm/autoscale.js +37 -34
  40. package/esm/chrome.d.ts +1 -3
  41. package/esm/chrome.js +1 -2
  42. package/esm/dataTypes.d.ts +2 -1
  43. package/esm/index.d.ts +7 -8
  44. package/esm/index.js +5 -6
  45. package/esm/pointSizeMenu.d.ts +12 -24
  46. package/esm/pointSizeMenu.js +9 -20
  47. package/esm/renderingBackendTypes.d.ts +1 -0
  48. package/esm/renderingBackendTypes.js +1 -1
  49. package/esm/scale.d.ts +5 -4
  50. package/esm/scale.js +6 -10
  51. package/esm/scoreMenuItems.d.ts +14 -28
  52. package/esm/scoreMenuItems.js +35 -130
  53. package/esm/valueScaleConfigSchema.d.ts +80 -6
  54. package/esm/valueScaleConfigSchema.js +67 -27
  55. package/esm/yFieldConfigSchemaFields.d.ts +10 -0
  56. package/esm/yFieldConfigSchemaFields.js +8 -0
  57. package/package.json +11 -10
  58. package/esm/AutoscaleGroupDialog.d.ts +0 -7
  59. package/esm/AutoscaleGroupDialog.js +0 -26
  60. package/esm/ScoreFieldConfigMixin.js +0 -25
  61. package/esm/SetMinMaxDialog.d.ts +0 -12
  62. package/esm/SetMinMaxDialog.js +0 -32
  63. package/esm/SetScoreRulesDialog.d.ts +0 -14
  64. package/esm/SetScoreRulesDialog.js +0 -45
  65. package/esm/rowLabelOffset.d.ts +0 -8
  66. package/esm/rowLabelOffset.js +0 -16
  67. package/esm/scoreFieldConfigSchemaFields.d.ts +0 -10
  68. package/esm/scoreFieldConfigSchemaFields.js +0 -8
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, aggregate: string, visibleEntries: { data: FeatureArrays; visStart: number; visEnd: number; }[], zero: boolean, extent?: string) => [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)
@@ -99,12 +101,12 @@ against, so there is nothing to call a hole.
99
101
 
100
102
  ### getEffectiveScores
101
103
 
102
- Per-feature scalar score array for a summary mode: the min/max summary array for
104
+ Per-feature scalar score array for an aggregate: the min/max summary array for
103
105
  `'min'`/`'max'`, otherwise the average score.
104
106
 
105
107
  ```js
106
108
  // type signature
107
- (data: { featureScores: Float32Array<…>; featureMinScores: Float32Array<ArrayBufferLike>; featureMaxScores: Float32Array<...>; }, summaryScoreMode: string) => Float32Array<...>
109
+ (data: { featureScores: Float32Array<ArrayBufferLike>; featureMinScores: Float32Array<ArrayBufferLike>; featureMaxScores: Float32Array<ArrayBufferLike>; }, aggregate: string) => Float32Array<ArrayBufferLike>
108
110
  ```
109
111
 
110
112
  [Source code](https://github.com/GMOD/jbrowse-components/blob/main/packages/wiggle-core/src/autoscale.ts)
@@ -112,10 +114,11 @@ Per-feature scalar score array for a summary mode: the min/max summary array for
112
114
  ### getNiceDomain
113
115
 
114
116
  Rounds a domain to "nice" endpoints. `zero` reaches a linear or symlog domain to
115
- 0 (`scales.y.zero`, ADR-182); a log domain has no 0 and floors at 1 instead. An
117
+ 0 (`scales.y.zero`, ADR-182); a log domain has no 0, so a min at or below it
118
+ floors to a positive value and a positive min stays where the data put it. An
116
119
  end given an explicit `bounds` value keeps that value exactly — only an
117
120
  autoscaled end is rounded. A log scale's floor still outranks a bound it cannot
118
- hold.
121
+ hold, which the `log-floor` rule reports.
119
122
 
120
123
  The result never descends and never collapses: a bound that would put `min`
121
124
  above `max` widens the other end instead, and one value in view widens away from
@@ -123,7 +126,7 @@ itself, so no consumer has to guess what a backwards or a flat domain means.
123
126
 
124
127
  ```js
125
128
  // type signature
126
- ({…}: { scaleType: string; domain: readonly [number, number]; bounds: readonly [number | undefined, number | undefined]; zero: boolean; }) => [number, number]
129
+ ({ scaleType, domain, bounds, zero, }: { scaleType: string; domain: readonly [number, number]; bounds: readonly [number | undefined, number | undefined]; zero: boolean; }) => [number, number]
127
130
  ```
128
131
 
129
132
  [Source code](https://github.com/GMOD/jbrowse-components/blob/main/packages/wiggle-core/src/scale.ts)
@@ -152,6 +155,18 @@ unless `nice: false` says it is already the one being drawn with.
152
155
 
153
156
  [Source code](https://github.com/GMOD/jbrowse-components/blob/main/packages/wiggle-core/src/scale.ts)
154
157
 
158
+ ### registerScoreAxisWidget
159
+
160
+ Registers the drawer widget the quantitative tracks' "Y axis..." row opens. The
161
+ wiggle plugin calls it once; alignments and the mark display open it by name.
162
+
163
+ ```js
164
+ // type signature
165
+ (pluginManager: PluginManager) => void
166
+ ```
167
+
168
+ [Source code](https://github.com/GMOD/jbrowse-components/blob/main/packages/wiggle-core/src/ScoreAxisWidget/index.ts)
169
+
155
170
  ### resolveSymlogConstant
156
171
 
157
172
  The symlog constant actually used for a domain. `0` (the config default) means
@@ -194,7 +209,7 @@ differently, and the alignments coverage band does.
194
209
 
195
210
  ```js
196
211
  // type signature
197
- ({…}: { rules: readonly ValueScaleRule[]; domain: [number, number] | undefined; box: { yTop: number; yBottom: number; }; normalize: (score: number) => number; }) => ScoreRuleMark[]
212
+ ({ rules, domain, box, normalize, }: { rules: readonly ValueScaleRule[]; domain: [number, number] | undefined; box: { yTop: number; yBottom: number; }; normalize: (score: number) => number; }) => ScoreRuleMark[]
198
213
  ```
199
214
 
200
215
  [Source code](https://github.com/GMOD/jbrowse-components/blob/main/packages/wiggle-core/src/scoreRuleMarks.ts)
@@ -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 aggregate, 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 color, an empty color 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 color, an empty color 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,7 @@
1
+ import type { ScoreAxisDisplay } from './types.ts';
2
+ export declare function resetAxis(display: ScoreAxisDisplay): void;
3
+ declare const ResetAxisButton: ({ display, onReset, }: {
4
+ display: ScoreAxisDisplay;
5
+ onReset: () => void;
6
+ }) => import("react").JSX.Element;
7
+ export default ResetAxisButton;
@@ -0,0 +1,22 @@
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 { observer } from 'mobx-react';
6
+ import { writeThrough } from './writeThrough.js';
7
+ // Back to what the config.json says, or to what a session-owned track was
8
+ // added with, so an admin's declared axis survives a reset; slots the base
9
+ // leaves unset take their defaults.
10
+ export function resetAxis(display) {
11
+ const base = baseDisplayConfig(display).scales;
12
+ writeThrough(display, () => {
13
+ setConf(display.configuration, ['scales', 'y'], base?.y ?? {});
14
+ });
15
+ }
16
+ const ResetAxisButton = observer(function ResetAxisButton({ display, onReset, }) {
17
+ return (_jsx("div", { children: _jsx(Button, { variant: "outlined", size: "small", onClick: () => {
18
+ resetAxis(display);
19
+ onReset();
20
+ }, children: "Reset to defaults" }) }));
21
+ });
22
+ export default ResetAxisButton;
@@ -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 color 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,6 @@
1
+ import type { ReactNode } from 'react';
2
+ declare const Section: ({ title, children, }: {
3
+ title: string;
4
+ children: ReactNode;
5
+ }) => import("react").JSX.Element;
6
+ export default Section;
@@ -0,0 +1,15 @@
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
+ import { observer } from 'mobx-react';
5
+ const useStyles = makeStyles()(theme => ({
6
+ section: {
7
+ display: 'grid',
8
+ gap: theme.spacing(0.5),
9
+ },
10
+ }));
11
+ const Section = observer(function Section({ title, children, }) {
12
+ const { classes } = useStyles();
13
+ return (_jsxs("div", { className: classes.section, children: [_jsx(Typography, { variant: "subtitle2", children: title }), children] }));
14
+ });
15
+ export default Section;
@@ -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
+ }