@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.
- package/README.md +28 -13
- package/esm/ResolutionStepper.d.ts +1 -0
- package/esm/ResolutionStepper.js +5 -2
- package/esm/ScoreAxisWidget/RangeFields.d.ts +5 -0
- package/esm/ScoreAxisWidget/RangeFields.js +70 -0
- package/esm/ScoreAxisWidget/ReferenceLines.d.ts +10 -0
- package/esm/ScoreAxisWidget/ReferenceLines.js +53 -0
- package/esm/ScoreAxisWidget/ResetAxisButton.d.ts +7 -0
- package/esm/ScoreAxisWidget/ResetAxisButton.js +22 -0
- package/esm/ScoreAxisWidget/ScoreAxisHeading.d.ts +5 -0
- package/esm/ScoreAxisWidget/ScoreAxisHeading.js +9 -0
- package/esm/ScoreAxisWidget/ScoreAxisWidget.d.ts +5 -0
- package/esm/ScoreAxisWidget/ScoreAxisWidget.js +69 -0
- package/esm/ScoreAxisWidget/Section.d.ts +6 -0
- package/esm/ScoreAxisWidget/Section.js +15 -0
- package/esm/ScoreAxisWidget/ShareAxisWith.d.ts +5 -0
- package/esm/ScoreAxisWidget/ShareAxisWith.js +32 -0
- package/esm/ScoreAxisWidget/constants.d.ts +1 -0
- package/esm/ScoreAxisWidget/constants.js +1 -0
- package/esm/ScoreAxisWidget/index.d.ts +8 -0
- package/esm/ScoreAxisWidget/index.js +22 -0
- package/esm/ScoreAxisWidget/stateModel.d.ts +30 -0
- package/esm/ScoreAxisWidget/stateModel.js +30 -0
- package/esm/ScoreAxisWidget/types.d.ts +10 -0
- package/esm/ScoreAxisWidget/types.js +1 -0
- package/esm/ScoreAxisWidget/writeThrough.d.ts +2 -0
- package/esm/ScoreAxisWidget/writeThrough.js +7 -0
- package/esm/ScorePlotChrome.d.ts +9 -8
- package/esm/ScorePlotChrome.js +16 -8
- package/esm/ScorePlotSvgFrame.d.ts +7 -12
- package/esm/ScorePlotSvgFrame.js +6 -14
- package/esm/ScoreScaleMixin.d.ts +7 -14
- package/esm/ScoreScaleMixin.js +7 -21
- package/esm/WiggleScoreConfigMixin.d.ts +5 -22
- package/esm/WiggleScoreConfigMixin.js +5 -25
- package/esm/{ScoreFieldConfigMixin.d.ts → YFieldConfigMixin.d.ts} +11 -15
- package/esm/YFieldConfigMixin.js +25 -0
- package/esm/autoscale.d.ts +20 -15
- package/esm/autoscale.js +37 -34
- package/esm/chrome.d.ts +1 -3
- package/esm/chrome.js +1 -2
- package/esm/dataTypes.d.ts +2 -1
- package/esm/index.d.ts +7 -8
- package/esm/index.js +5 -6
- package/esm/pointSizeMenu.d.ts +12 -24
- package/esm/pointSizeMenu.js +9 -20
- package/esm/renderingBackendTypes.d.ts +1 -0
- package/esm/renderingBackendTypes.js +1 -1
- package/esm/scale.d.ts +5 -4
- package/esm/scale.js +6 -10
- package/esm/scoreMenuItems.d.ts +14 -28
- package/esm/scoreMenuItems.js +35 -130
- package/esm/valueScaleConfigSchema.d.ts +80 -6
- package/esm/valueScaleConfigSchema.js +67 -27
- package/esm/yFieldConfigSchemaFields.d.ts +10 -0
- package/esm/yFieldConfigSchemaFields.js +8 -0
- package/package.json +11 -10
- package/esm/AutoscaleGroupDialog.d.ts +0 -7
- package/esm/AutoscaleGroupDialog.js +0 -26
- package/esm/ScoreFieldConfigMixin.js +0 -25
- package/esm/SetMinMaxDialog.d.ts +0 -12
- package/esm/SetMinMaxDialog.js +0 -32
- package/esm/SetScoreRulesDialog.d.ts +0 -14
- package/esm/SetScoreRulesDialog.js +0 -45
- package/esm/rowLabelOffset.d.ts +0 -8
- package/esm/rowLabelOffset.js +0 -16
- package/esm/scoreFieldConfigSchemaFields.d.ts +0 -10
- 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
|
|
16
|
-
`
|
|
17
|
-
|
|
18
|
-
window of nothing above 0 keeps the top
|
|
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,
|
|
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
|
|
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
|
|
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
|
|
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
|
-
({
|
|
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
|
-
({
|
|
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)
|
package/esm/ResolutionStepper.js
CHANGED
|
@@ -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,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,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,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,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,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,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
|
+
}
|