@jbrowse/wiggle-core 5.0.0-beta.10 → 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.
- package/README.md +21 -7
- 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 +6 -0
- package/esm/ScoreAxisWidget/ResetAxisButton.js +20 -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 +5 -0
- package/esm/ScoreAxisWidget/Section.js +13 -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 +1 -0
- package/esm/ScorePlotChrome.js +3 -3
- package/esm/ScorePlotSvgFrame.d.ts +7 -12
- package/esm/ScorePlotSvgFrame.js +6 -14
- package/esm/ScoreScaleMixin.d.ts +5 -5
- package/esm/ScoreScaleMixin.js +5 -5
- package/esm/autoscale.d.ts +12 -8
- package/esm/autoscale.js +18 -11
- package/esm/chrome.d.ts +0 -1
- package/esm/chrome.js +0 -1
- package/esm/dataTypes.d.ts +0 -1
- package/esm/index.d.ts +2 -2
- package/esm/index.js +1 -1
- package/esm/scale.js +1 -1
- package/esm/scoreMenuItems.d.ts +14 -28
- package/esm/scoreMenuItems.js +35 -130
- package/esm/valueScaleConfigSchema.d.ts +4 -5
- package/esm/valueScaleConfigSchema.js +21 -19
- package/esm/visibleStatsDomain.d.ts +0 -1
- package/package.json +11 -8
- package/esm/AutoscaleGroupDialog.d.ts +0 -7
- package/esm/AutoscaleGroupDialog.js +0 -26
- 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/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, 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
|
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 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,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,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 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,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,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
|
+
}
|
package/esm/ScorePlotChrome.d.ts
CHANGED
|
@@ -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
|
};
|
package/esm/ScorePlotChrome.js
CHANGED
|
@@ -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 {
|
|
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
|
|
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
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
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<
|
|
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;
|