@jbrowse/wiggle-core 5.0.0-beta.7 → 5.0.0-beta.9

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 (47) hide show
  1. package/README.md +49 -83
  2. package/esm/ScoreAxisMixin.d.ts +99 -0
  3. package/esm/ScoreAxisMixin.js +150 -0
  4. package/esm/ScoreFieldConfigMixin.d.ts +60 -0
  5. package/esm/ScoreFieldConfigMixin.js +25 -0
  6. package/esm/ScorePlotChrome.d.ts +45 -0
  7. package/esm/ScorePlotChrome.js +47 -0
  8. package/esm/ScorePlotSvgFrame.d.ts +27 -0
  9. package/esm/ScorePlotSvgFrame.js +23 -0
  10. package/esm/ScoreScaleMixin.d.ts +65 -99
  11. package/esm/ScoreScaleMixin.js +78 -141
  12. package/esm/SetScoreRulesDialog.d.ts +14 -0
  13. package/esm/SetScoreRulesDialog.js +45 -0
  14. package/esm/WiggleScoreConfigMixin.d.ts +89 -0
  15. package/esm/WiggleScoreConfigMixin.js +69 -0
  16. package/esm/autoscale.d.ts +45 -5
  17. package/esm/autoscale.js +99 -67
  18. package/esm/chrome.d.ts +4 -0
  19. package/esm/chrome.js +7 -0
  20. package/esm/computeYTicks.d.ts +1 -1
  21. package/esm/computeYTicks.js +4 -2
  22. package/esm/dataTypes.d.ts +2 -6
  23. package/esm/gapBreak.d.ts +2 -3
  24. package/esm/gapBreak.js +2 -3
  25. package/esm/index.d.ts +20 -16
  26. package/esm/index.js +17 -13
  27. package/esm/normalize.d.ts +1 -26
  28. package/esm/normalize.js +1 -86
  29. package/esm/pointSizeMenu.d.ts +13 -0
  30. package/esm/pointSizeMenu.js +21 -3
  31. package/esm/renderingBackendTypes.d.ts +12 -3
  32. package/esm/renderingBackendTypes.js +4 -1
  33. package/esm/scale.d.ts +1 -1
  34. package/esm/scoreFieldConfigSchemaFields.d.ts +10 -0
  35. package/esm/scoreFieldConfigSchemaFields.js +11 -0
  36. package/esm/scoreMenuItems.d.ts +19 -7
  37. package/esm/scoreMenuItems.js +64 -29
  38. package/esm/scoreRuleMarks.d.ts +13 -37
  39. package/esm/scoreRuleMarks.js +12 -45
  40. package/esm/transferables.js +3 -8
  41. package/esm/valueScaleConfigSchema.d.ts +211 -0
  42. package/esm/valueScaleConfigSchema.js +240 -0
  43. package/esm/wiggleRenderModes.generated.d.ts +1 -2
  44. package/esm/wiggleRenderModes.generated.js +1 -2
  45. package/package.json +12 -8
  46. package/esm/scoreAxisConfigSchemaFields.d.ts +0 -44
  47. package/esm/scoreAxisConfigSchemaFields.js +0 -54
@@ -0,0 +1,23 @@
1
+ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { PaintLayer } from '@jbrowse/core/util/paintLayer';
3
+ import { axisPlotBox } from '@jbrowse/display-ui';
4
+ import { paintMarkBlocks } from '@jbrowse/render-core/marks';
5
+ /**
6
+ * The SVG-export body of a display with a score axis, mounted through
7
+ * `renderDisplaySvg`, which draws the axis and cross-hatches. Paints `marks`
8
+ * over `regions` inside the plot box, at the export's width and the box's
9
+ * height — the two fields of `renderState` the on-screen canvas sizes
10
+ * differently. `plotGeometry` defaults to the single-plot box `ScorePlotChrome`
11
+ * draws in; multi-wiggle stacks its rows over the full height instead.
12
+ * `children` draw over the plot, in the same untranslated space as the axis.
13
+ */
14
+ export function ScorePlotSvgFrame({ height, canvasWidth, renderBlocks, opts, plotGeometry = axisPlotBox(height), marks, regions, renderState, children, }) {
15
+ const { yTop, plotHeight } = plotGeometry;
16
+ return (_jsxs(_Fragment, { children: [_jsx("g", { transform: `translate(0,${yTop})`, children: _jsx(PaintLayer, { width: canvasWidth, height: plotHeight, opts: opts, paint: ctx => {
17
+ paintMarkBlocks(ctx, marks, regions, renderBlocks, {
18
+ ...renderState,
19
+ canvasWidth,
20
+ canvasHeight: plotHeight,
21
+ });
22
+ } }) }), children] }));
23
+ }
@@ -1,152 +1,112 @@
1
- import type { ScoreAxisConfigModel } from './scoreAxisConfigSchemaFields.ts';
2
- import type { ValueScale, YAxis } from '@jbrowse/display-ui';
1
+ import type { scalesSchema } from './valueScaleConfigSchema.ts';
2
+ import type { ConfigModelForFields } from '@jbrowse/core/configuration';
3
+ import type { ValueScaleRule } from '@jbrowse/display-ui';
3
4
  /**
4
- * The whole of what `ScoreScaleMixin` needs a composing display to be. Exported
5
+ * The whole of what `ScoreScaleMixin` needs a composing display to be: a
6
+ * `scales` object holding the `y` the value-scale factory built. Exported
5
7
  * because it is the mixin's contract and `ScoreScaleMixin.test.ts` pins it:
6
8
  * widen it and the `@ts-expect-error`s there go unused.
7
9
  */
8
10
  export interface ScoreScaleHost {
9
- configuration: ScoreAxisConfigModel;
11
+ configuration: ConfigModelForFields<{
12
+ scales: ReturnType<typeof scalesSchema>;
13
+ }>;
10
14
  }
11
15
  /**
12
16
  * #stateModel ScoreScaleMixin
13
17
  * #category display
14
- * #crossCuttingMixin Score axis. Nothing — the config slots. Brings `scaleType` / `autoscaleType` / `minScore` / `maxScore` / `manual*` / `*Bound` / `hasManualScoreBounds` / `numStdDev` and their setters, i.e. the whole `ScoreScaleModel` interface the shared score menu and `SetMinMaxDialog` consume
18
+ * #crossCuttingMixin Value scale, written in `scales.y`. `valueScaleSchema` / `scalesSchema`. Brings `ScoreAxisMixin` plus `scaleType` / `scaleTypeChoices` / `autoscaleType` / `numStdDev` / `numQuantile` / `symlogConstant` / `manual*` and their setters, i.e. the whole `ScoreScaleModel` interface the shared score menu and `SetMinMaxDialog` consume
15
19
  *
16
- * The score axis every quantitative display shares: which scale, how to
17
- * autoscale it, and the manual min/max bounds. This is the runtime half of
18
- * {@link ScoreScaleModel} in `scoreMenuItems.ts` — that interface is what the
19
- * shared Score menu, the autoscale/scale submenus and `SetMinMaxDialog` consume,
20
- * and it was already the canonical contract while two displays hand-wrote
21
- * identical implementations of it (`WiggleScoreConfigMixin`, and the alignments
22
- * coverage band). Composing this is now how a display satisfies it, so a new
23
- * score display cannot satisfy it *partially*.
20
+ * The value scale of every quantitative display: wiggle, the multi-wiggle,
21
+ * Manhattan, the alignments coverage band and the mark display each declare
22
+ * `scales.y` through {@link valueScaleSchema} and compose this. It backs
23
+ * {@link ScoreAxisMixin}'s three overridable members off that object and adds
24
+ * the setters that write it, so composing this is how a display satisfies
25
+ * {@link ScoreScaleModel} in `scoreMenuItems.ts` — the interface the shared
26
+ * Score menu, the scale and autoscale submenus and `SetMinMaxDialog` consume.
24
27
  *
25
- * Deliberately just the axis. Colors, `resolution`, cross-hatches and the
26
- * autoscale *computation* stay in `WiggleScoreConfigMixin` / `WiggleCommonMixin`
27
- * — the alignments coverage band shares this axis but none of the rest.
28
- *
29
- * `minScore`/`maxScore` are the **raw** slot values with their
30
- * `Number.MIN_VALUE`/`Number.MAX_VALUE` "unset" sentinels intact, and nothing
31
- * outside this file should want them: `manualMinScore`/`manualMaxScore` are the
32
- * same answer with the sentinel resolved to `undefined`, which is what the
33
- * dialog round-trips and what the menu captions itself with;
34
- * `minScoreBound`/`maxScoreBound` are the resolved bounds, where `undefined`
35
- * means "autoscale this end". Every consumer that computes a domain reads the
36
- * `*Bound` pair.
28
+ * What a display's scale offers follows what it draws, so the members below
29
+ * answer `undefined` where its factory call left them out: `autoscaleType` on
30
+ * Manhattan, whose domain is plain min/max, and `symlogConstant` wherever
31
+ * `symlog` is not among the scale types. `scaleTypeChoices` reads the declared
32
+ * enum back, which is what the scale-type radio offers.
37
33
  *
38
- * A display that declares its value scale elsewhere answers
39
- * `declaredValueScale`, and then the scale type and the pinned bounds come
40
- * from the declaration rather than from `scaleType`/`minScore`/`maxScore`.
41
- * Its setters are the display's to override so the edit lands on the same
42
- * declaration — one owner, whichever it is.
43
- *
44
- * Whether a bound is *configured* is a third question, and `hasManualScoreBounds`
45
- * is the only getter that answers it — the resolved pair cannot, since
46
- * `defaultScoreDomain` is exactly the hook that turns an unset end into a number.
34
+ * Deliberately just the scale. Colors, `resolution`, cross-hatches and the
35
+ * autoscale *computation* stay in `WiggleScoreConfigMixin` / `WiggleCommonMixin`
36
+ * — the alignments coverage band shares this scale but none of the rest.
47
37
  */
48
38
  export declare function ScoreScaleMixin(): import("@jbrowse/mobx-state-tree").IModelType<{}, {
49
- /**
50
- * #getter
51
- * Overridable hook: a value scale the display declares somewhere other
52
- * than these slots — the mark display's `encoding.y`. Where it answers,
53
- * it is the owner and the slots below stand in only for the ends it
54
- * leaves open, so a scale is read from one place whichever place that
55
- * is. Default none, which is every display whose axis IS these slots.
56
- */
57
- readonly declaredValueScale: {
58
- scaleType?: string;
59
- domain?: [number | undefined, number | undefined];
60
- } | undefined;
39
+ readonly scaleType: string;
40
+ readonly manualMinScore: number | undefined;
41
+ readonly manualMaxScore: number | undefined;
42
+ readonly defaultScoreDomain: [number | undefined, number | undefined];
43
+ readonly valueScales: import("@jbrowse/display-ui").ValueScale[];
44
+ } & {
45
+ readonly minScoreBound: number | undefined;
46
+ readonly maxScoreBound: number | undefined;
47
+ readonly hasManualScoreBounds: boolean;
48
+ readonly axes: import("@jbrowse/display-ui").YAxis[];
49
+ } & {
61
50
  /**
62
51
  * #getter
63
52
  */
64
53
  readonly scaleType: string;
65
54
  /**
66
55
  * #getter
56
+ * The scale types this display's own enum admits, which is what the
57
+ * scale-type radio offers; a display with one draws no radio.
67
58
  */
68
- readonly autoscaleType: string;
59
+ readonly scaleTypeChoices: string[];
69
60
  /**
70
61
  * #getter
62
+ * `undefined` on a display whose domain consults no autoscale mode.
71
63
  */
72
- readonly numStdDev: number;
64
+ readonly autoscaleType: string | undefined;
73
65
  /**
74
66
  * #getter
75
- * Raw slot value, sentinel intact — see the class comment.
76
67
  */
77
- readonly minScore: number;
68
+ readonly numStdDev: number;
78
69
  /**
79
70
  * #getter
80
- * Raw slot value, sentinel intact — see the class comment.
81
71
  */
82
- readonly maxScore: number;
72
+ readonly numQuantile: number;
83
73
  /**
84
74
  * #getter
85
- * Overridable hook: what each end of the domain falls back to where the
86
- * config leaves its bound unset. `[undefined, undefined]` — the default —
87
- * means autoscale both ends, which is right for a track whose scores have
88
- * no absolute meaning (a bigwig's units are its own).
89
- *
90
- * A display whose scores are bounded *by construction* overrides it, so
91
- * the axis stops being a function of what happens to be on screen: GC
92
- * content is a fraction, so 0 and 1 are its real limits and mean the same
93
- * thing at every locus. Autoscaled, the same GC value drew at different
94
- * heights depending on where the user had panned, and the track could not
95
- * be read across loci.
96
- *
97
- * A hook rather than a config default because the answer can depend on
98
- * display state — GC's does, on `gcMode` — and rather than each display
99
- * re-resolving the sentinels below, which is the one thing that must not
100
- * be duplicated: config bounds still win, precisely because they are
101
- * checked before this is consulted.
75
+ * Raw slot; `0` means "derive from the domain". Resolve it with
76
+ * `resolveSymlogConstant` once the domain is known.
102
77
  */
103
- readonly defaultScoreDomain: [number | undefined, number | undefined];
78
+ readonly symlogConstant: number;
104
79
  /**
105
80
  * #getter
106
- * The lower bound the config really sets, `undefined` at the sentinel.
81
+ * The lower bound the config pins, `undefined` where it pins none.
107
82
  */
108
83
  readonly manualMinScore: number | undefined;
109
84
  /**
110
85
  * #getter
111
- * The upper bound the config really sets, `undefined` at the sentinel.
86
+ * The upper bound the config pins, `undefined` where it pins none.
112
87
  */
113
88
  readonly manualMaxScore: number | undefined;
114
89
  /**
115
90
  * #getter
116
- * Resolved lower bound; `undefined` means autoscale this end.
117
- */
118
- readonly minScoreBound: number | undefined;
119
- /**
120
- * #getter
121
- * Resolved upper bound; `undefined` means autoscale this end.
122
- */
123
- readonly maxScoreBound: number | undefined;
124
- /**
125
- * #getter
126
- * Whether the user has pinned either end, which is a different question
127
- * from whether either end resolved to a number: `defaultScoreDomain` fills
128
- * the sentinels in, so a GC content track answers yes to the second with
129
- * nothing configured. The score menu asks this one — it gates the "Clear
130
- * manual min/max" row, and a Clear that writes the sentinels already
131
- * there is a row that does nothing and never goes away.
91
+ * `scales.y.title` as written, `undefined` while unset or on a display
92
+ * whose scale declares no title.
132
93
  */
133
- readonly hasManualScoreBounds: boolean;
94
+ readonly scaleTitle: string | undefined;
134
95
  /**
135
96
  * #getter
136
- * Overridable hook (default none): the scales this display draws its y
137
- * through. A display that answers it gets an axis per band of each,
138
- * with its cross-hatches, placed by `DisplayChrome` and
139
- * `renderDisplaySvg`, and the ticks derived below.
97
+ * Whether this display draws `scales.y.rules`, which is whether the
98
+ * score menu offers the reference lines: its scale declares them, and
99
+ * a scale it places y through rules a band for them to cross, which a
100
+ * density plot's colour-mapped rows and a colour ramp do not.
140
101
  */
141
- readonly valueScales: ValueScale[];
142
- } & {
102
+ readonly scoreRulesDrawn: boolean;
143
103
  /**
144
104
  * #getter
145
- * The axes, one per declared scale whose domain resolved: where each
146
- * tick lands in the band's own pixel space, through `computeYTicks`
147
- * unless the scale brought its own ladder.
105
+ * `scales.y.rules`, read off the live nodes: a snapshot strips a slot at
106
+ * its default, and a rule at 0 is one. Empty on a display whose scale
107
+ * declares no rules.
148
108
  */
149
- readonly axes: YAxis[];
109
+ readonly scoreRules: ValueScaleRule[];
150
110
  } & {
151
111
  /**
152
112
  * #action
@@ -164,4 +124,10 @@ export declare function ScoreScaleMixin(): import("@jbrowse/mobx-state-tree").IM
164
124
  * #action
165
125
  */
166
126
  setMaxScore(val?: number): void;
127
+ /**
128
+ * #action
129
+ * Replaces `scales.y.rules` whole, each entry in a form the config
130
+ * takes: a number, or `{ value, color, label }`.
131
+ */
132
+ setScoreRules(rules: (number | ValueScaleRule)[]): void;
167
133
  }, import("@jbrowse/mobx-state-tree")._NotCustomized, import("@jbrowse/mobx-state-tree")._NotCustomized>;
@@ -1,198 +1,127 @@
1
- import { getConf, setConf } from '@jbrowse/core/configuration';
2
- import { types } from '@jbrowse/mobx-state-tree';
3
- import { computeYTicks } from './computeYTicks.js';
1
+ import { getConf, getSlotDefinition, readConfObject, setConf, slotChoices, } from '@jbrowse/core/configuration';
2
+ import { rulesABand } from '@jbrowse/display-ui/axisPlacement';
3
+ import { ScoreAxisMixin } from './ScoreAxisMixin.js';
4
4
  // The mixin composes onto a display that declares this, not the other way
5
5
  // round, so its own `self` isn't typed with it. Cast once, narrowed to the
6
- // field table beside it rather than `AnyConfigurationModel`, which is what keeps
7
- // the slot names below checked.
6
+ // factory's schema rather than `AnyConfigurationModel`, which is what keeps the
7
+ // member names below checked.
8
8
  const confNode = (self) => self;
9
9
  /**
10
10
  * #stateModel ScoreScaleMixin
11
11
  * #category display
12
- * #crossCuttingMixin Score axis. Nothing — the config slots. Brings `scaleType` / `autoscaleType` / `minScore` / `maxScore` / `manual*` / `*Bound` / `hasManualScoreBounds` / `numStdDev` and their setters, i.e. the whole `ScoreScaleModel` interface the shared score menu and `SetMinMaxDialog` consume
12
+ * #crossCuttingMixin Value scale, written in `scales.y`. `valueScaleSchema` / `scalesSchema`. Brings `ScoreAxisMixin` plus `scaleType` / `scaleTypeChoices` / `autoscaleType` / `numStdDev` / `numQuantile` / `symlogConstant` / `manual*` and their setters, i.e. the whole `ScoreScaleModel` interface the shared score menu and `SetMinMaxDialog` consume
13
13
  *
14
- * The score axis every quantitative display shares: which scale, how to
15
- * autoscale it, and the manual min/max bounds. This is the runtime half of
16
- * {@link ScoreScaleModel} in `scoreMenuItems.ts` — that interface is what the
17
- * shared Score menu, the autoscale/scale submenus and `SetMinMaxDialog` consume,
18
- * and it was already the canonical contract while two displays hand-wrote
19
- * identical implementations of it (`WiggleScoreConfigMixin`, and the alignments
20
- * coverage band). Composing this is now how a display satisfies it, so a new
21
- * score display cannot satisfy it *partially*.
14
+ * The value scale of every quantitative display: wiggle, the multi-wiggle,
15
+ * Manhattan, the alignments coverage band and the mark display each declare
16
+ * `scales.y` through {@link valueScaleSchema} and compose this. It backs
17
+ * {@link ScoreAxisMixin}'s three overridable members off that object and adds
18
+ * the setters that write it, so composing this is how a display satisfies
19
+ * {@link ScoreScaleModel} in `scoreMenuItems.ts` — the interface the shared
20
+ * Score menu, the scale and autoscale submenus and `SetMinMaxDialog` consume.
22
21
  *
23
- * Deliberately just the axis. Colors, `resolution`, cross-hatches and the
24
- * autoscale *computation* stay in `WiggleScoreConfigMixin` / `WiggleCommonMixin`
25
- * — the alignments coverage band shares this axis but none of the rest.
26
- *
27
- * `minScore`/`maxScore` are the **raw** slot values with their
28
- * `Number.MIN_VALUE`/`Number.MAX_VALUE` "unset" sentinels intact, and nothing
29
- * outside this file should want them: `manualMinScore`/`manualMaxScore` are the
30
- * same answer with the sentinel resolved to `undefined`, which is what the
31
- * dialog round-trips and what the menu captions itself with;
32
- * `minScoreBound`/`maxScoreBound` are the resolved bounds, where `undefined`
33
- * means "autoscale this end". Every consumer that computes a domain reads the
34
- * `*Bound` pair.
35
- *
36
- * A display that declares its value scale elsewhere answers
37
- * `declaredValueScale`, and then the scale type and the pinned bounds come
38
- * from the declaration rather than from `scaleType`/`minScore`/`maxScore`.
39
- * Its setters are the display's to override so the edit lands on the same
40
- * declaration — one owner, whichever it is.
22
+ * What a display's scale offers follows what it draws, so the members below
23
+ * answer `undefined` where its factory call left them out: `autoscaleType` on
24
+ * Manhattan, whose domain is plain min/max, and `symlogConstant` wherever
25
+ * `symlog` is not among the scale types. `scaleTypeChoices` reads the declared
26
+ * enum back, which is what the scale-type radio offers.
41
27
  *
42
- * Whether a bound is *configured* is a third question, and `hasManualScoreBounds`
43
- * is the only getter that answers it — the resolved pair cannot, since
44
- * `defaultScoreDomain` is exactly the hook that turns an unset end into a number.
28
+ * Deliberately just the scale. Colors, `resolution`, cross-hatches and the
29
+ * autoscale *computation* stay in `WiggleScoreConfigMixin` / `WiggleCommonMixin`
30
+ * — the alignments coverage band shares this scale but none of the rest.
45
31
  */
46
32
  export function ScoreScaleMixin() {
47
- return types
48
- .model('ScoreScaleMixin', {})
33
+ return ScoreAxisMixin()
49
34
  .views(self => ({
50
35
  /**
51
36
  * #getter
52
- * Overridable hook: a value scale the display declares somewhere other
53
- * than these slots — the mark display's `encoding.y`. Where it answers,
54
- * it is the owner and the slots below stand in only for the ends it
55
- * leaves open, so a scale is read from one place whichever place that
56
- * is. Default none, which is every display whose axis IS these slots.
57
37
  */
58
- get declaredValueScale() {
59
- return undefined;
38
+ get scaleType() {
39
+ return getConf(confNode(self), ['scales', 'y', 'type']);
60
40
  },
61
41
  /**
62
42
  * #getter
43
+ * The scale types this display's own enum admits, which is what the
44
+ * scale-type radio offers; a display with one draws no radio.
63
45
  */
64
- get scaleType() {
65
- return (this.declaredValueScale?.scaleType ??
66
- getConf(confNode(self), 'scaleType'));
46
+ get scaleTypeChoices() {
47
+ return (slotChoices(getSlotDefinition(confNode(self).configuration.scales.y, 'type')) ?? []);
67
48
  },
68
49
  /**
69
50
  * #getter
51
+ * `undefined` on a display whose domain consults no autoscale mode.
70
52
  */
71
53
  get autoscaleType() {
72
- return getConf(confNode(self), 'autoscale');
54
+ return getConf(confNode(self), ['scales', 'y', 'autoscale']);
73
55
  },
74
56
  /**
75
57
  * #getter
76
58
  */
77
59
  get numStdDev() {
78
- return getConf(confNode(self), 'numStdDev');
60
+ return getConf(confNode(self), ['scales', 'y', 'numStdDev']);
79
61
  },
80
62
  /**
81
63
  * #getter
82
- * Raw slot value, sentinel intact — see the class comment.
83
64
  */
84
- get minScore() {
85
- return getConf(confNode(self), 'minScore');
65
+ get numQuantile() {
66
+ return getConf(confNode(self), ['scales', 'y', 'numQuantile']);
86
67
  },
87
68
  /**
88
69
  * #getter
89
- * Raw slot value, sentinel intact — see the class comment.
70
+ * Raw slot; `0` means "derive from the domain". Resolve it with
71
+ * `resolveSymlogConstant` once the domain is known.
90
72
  */
91
- get maxScore() {
92
- return getConf(confNode(self), 'maxScore');
73
+ get symlogConstant() {
74
+ return getConf(confNode(self), ['scales', 'y', 'symlogConstant']);
93
75
  },
94
76
  /**
95
77
  * #getter
96
- * Overridable hook: what each end of the domain falls back to where the
97
- * config leaves its bound unset. `[undefined, undefined]` — the default —
98
- * means autoscale both ends, which is right for a track whose scores have
99
- * no absolute meaning (a bigwig's units are its own).
100
- *
101
- * A display whose scores are bounded *by construction* overrides it, so
102
- * the axis stops being a function of what happens to be on screen: GC
103
- * content is a fraction, so 0 and 1 are its real limits and mean the same
104
- * thing at every locus. Autoscaled, the same GC value drew at different
105
- * heights depending on where the user had panned, and the track could not
106
- * be read across loci.
107
- *
108
- * A hook rather than a config default because the answer can depend on
109
- * display state — GC's does, on `gcMode` — and rather than each display
110
- * re-resolving the sentinels below, which is the one thing that must not
111
- * be duplicated: config bounds still win, precisely because they are
112
- * checked before this is consulted.
113
- */
114
- get defaultScoreDomain() {
115
- return [undefined, undefined];
116
- },
117
- /**
118
- * #getter
119
- * The lower bound the config really sets, `undefined` at the sentinel.
78
+ * The lower bound the config pins, `undefined` where it pins none.
120
79
  */
121
80
  get manualMinScore() {
122
- const declared = this.declaredValueScale?.domain?.[0];
123
- if (declared !== undefined) {
124
- return declared;
125
- }
126
- return this.minScore === Number.MIN_VALUE ? undefined : this.minScore;
81
+ return getConf(confNode(self), ['scales', 'y', 'domainMin']);
127
82
  },
128
83
  /**
129
84
  * #getter
130
- * The upper bound the config really sets, `undefined` at the sentinel.
85
+ * The upper bound the config pins, `undefined` where it pins none.
131
86
  */
132
87
  get manualMaxScore() {
133
- const declared = this.declaredValueScale?.domain?.[1];
134
- if (declared !== undefined) {
135
- return declared;
136
- }
137
- return this.maxScore === Number.MAX_VALUE ? undefined : this.maxScore;
88
+ return getConf(confNode(self), ['scales', 'y', 'domainMax']);
138
89
  },
139
90
  /**
140
91
  * #getter
141
- * Resolved lower bound; `undefined` means autoscale this end.
92
+ * `scales.y.title` as written, `undefined` while unset or on a display
93
+ * whose scale declares no title.
142
94
  */
143
- get minScoreBound() {
144
- return this.manualMinScore ?? this.defaultScoreDomain[0];
95
+ get scaleTitle() {
96
+ return getConf(confNode(self), ['scales', 'y', 'title']);
145
97
  },
146
98
  /**
147
99
  * #getter
148
- * Resolved upper bound; `undefined` means autoscale this end.
100
+ * Whether this display draws `scales.y.rules`, which is whether the
101
+ * score menu offers the reference lines: its scale declares them, and
102
+ * a scale it places y through rules a band for them to cross, which a
103
+ * density plot's colour-mapped rows and a colour ramp do not.
149
104
  */
150
- get maxScoreBound() {
151
- return this.manualMaxScore ?? this.defaultScoreDomain[1];
105
+ get scoreRulesDrawn() {
106
+ return (confNode(self).configuration.scales.y.rules !== undefined &&
107
+ self.valueScales.some(rulesABand));
152
108
  },
153
109
  /**
154
110
  * #getter
155
- * Whether the user has pinned either end, which is a different question
156
- * from whether either end resolved to a number: `defaultScoreDomain` fills
157
- * the sentinels in, so a GC content track answers yes to the second with
158
- * nothing configured. The score menu asks this one — it gates the "Clear
159
- * manual min/max" row, and a Clear that writes the sentinels already
160
- * there is a row that does nothing and never goes away.
161
- */
162
- get hasManualScoreBounds() {
163
- return (this.manualMinScore !== undefined || this.manualMaxScore !== undefined);
164
- },
165
- /**
166
- * #getter
167
- * Overridable hook (default none): the scales this display draws its y
168
- * through. A display that answers it gets an axis per band of each,
169
- * with its cross-hatches, placed by `DisplayChrome` and
170
- * `renderDisplaySvg`, and the ticks derived below.
171
- */
172
- get valueScales() {
173
- return [];
174
- },
175
- }))
176
- .views(self => ({
177
- /**
178
- * #getter
179
- * The axes, one per declared scale whose domain resolved: where each
180
- * tick lands in the band's own pixel space, through `computeYTicks`
181
- * unless the scale brought its own ladder.
182
- */
183
- get axes() {
184
- return self.valueScales.flatMap(scale => {
185
- const { domain } = scale;
186
- const ticks = scale.ticks ??
187
- computeYTicks({
188
- height: scale.height,
189
- offset: scale.offset,
190
- domain,
191
- scaleType: scale.scaleType,
192
- minimalTicks: scale.minimalTicks ?? false,
193
- symlogConstant: scale.symlogConstant,
194
- });
195
- return domain && ticks ? [{ ...scale, domain, ticks }] : [];
111
+ * `scales.y.rules`, read off the live nodes: a snapshot strips a slot at
112
+ * its default, and a rule at 0 is one. Empty on a display whose scale
113
+ * declares no rules.
114
+ */
115
+ get scoreRules() {
116
+ const { rules = [] } = confNode(self).configuration.scales.y;
117
+ return rules.map((rule) => {
118
+ const label = readConfObject(rule, 'label');
119
+ const color = readConfObject(rule, 'color');
120
+ return {
121
+ value: readConfObject(rule, 'value'),
122
+ ...(color ? { color } : {}),
123
+ ...(label ? { label } : {}),
124
+ };
196
125
  });
197
126
  },
198
127
  }))
@@ -201,25 +130,33 @@ export function ScoreScaleMixin() {
201
130
  * #action
202
131
  */
203
132
  setScaleType(scaleType) {
204
- setConf(confNode(self), 'scaleType', scaleType);
133
+ setConf(confNode(self), ['scales', 'y', 'type'], scaleType);
205
134
  },
206
135
  /**
207
136
  * #action
208
137
  */
209
138
  setAutoscale(val) {
210
- setConf(confNode(self), 'autoscale', val);
139
+ setConf(confNode(self), ['scales', 'y', 'autoscale'], val);
211
140
  },
212
141
  /**
213
142
  * #action
214
143
  */
215
144
  setMinScore(val) {
216
- setConf(confNode(self), 'minScore', val);
145
+ setConf(confNode(self), ['scales', 'y', 'domainMin'], val);
217
146
  },
218
147
  /**
219
148
  * #action
220
149
  */
221
150
  setMaxScore(val) {
222
- setConf(confNode(self), 'maxScore', val);
151
+ setConf(confNode(self), ['scales', 'y', 'domainMax'], val);
152
+ },
153
+ /**
154
+ * #action
155
+ * Replaces `scales.y.rules` whole, each entry in a form the config
156
+ * takes: a number, or `{ value, color, label }`.
157
+ */
158
+ setScoreRules(rules) {
159
+ setConf(confNode(self), ['scales', 'y', 'rules'], rules);
223
160
  },
224
161
  }));
225
162
  }
@@ -0,0 +1,14 @@
1
+ import type { ValueScaleRule } from '@jbrowse/display-ui';
2
+ /**
3
+ * The score menu's editor for `scales.y.rules`: one row per reference line,
4
+ * its value on the plot's own scale, its label and its colour. An empty colour
5
+ * is the display's default, and removing every row removes every line.
6
+ */
7
+ declare const _default: ({ model, handleClose, }: {
8
+ model: {
9
+ scoreRules: ValueScaleRule[];
10
+ setScoreRules: (rules: ValueScaleRule[]) => void;
11
+ };
12
+ handleClose: () => void;
13
+ }) => import("react").JSX.Element;
14
+ export default _default;
@@ -0,0 +1,45 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useState } from 'react';
3
+ import { SubmitDialog } from '@jbrowse/core/ui';
4
+ import { isCssColor } from '@jbrowse/core/util/colorBits';
5
+ import DeleteIcon from '@mui/icons-material/Delete';
6
+ import { Button, IconButton, TextField, Typography } from '@mui/material';
7
+ import { observer } from 'mobx-react';
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
+ /**
18
+ * The score menu's editor for `scales.y.rules`: one row per reference line,
19
+ * its value on the plot's own scale, its label and its colour. An empty colour
20
+ * is the display's default, and removing every row removes every line.
21
+ */
22
+ export default observer(function SetScoreRulesDialog({ model, handleClose, }) {
23
+ const [rows, setRows] = useState(() => model.scoreRules.map(rowOf));
24
+ const setRow = (index, patch) => {
25
+ setRows(rows.map((row, i) => (i === index ? { ...row, ...patch } : row)));
26
+ };
27
+ return (_jsxs(SubmitDialog, { open: true, title: "Reference lines", submitDisabled: !rows.every(row => valueOk(row) && colorOk(row)), onCancel: handleClose, onSubmit: () => {
28
+ model.setScoreRules(rows.map(row => ({
29
+ value: Number(row.value),
30
+ ...(row.label ? { label: row.label } : {}),
31
+ ...(row.color.trim() ? { color: row.color.trim() } : {}),
32
+ })));
33
+ handleClose();
34
+ }, children: [_jsx(Typography, { children: "A horizontal line at each value, on the same scale as the plot" }), rows.map((row, index) => (_jsxs("div", { style: { display: 'flex', gap: 8, marginTop: 8 }, children: [_jsx(TextField, { label: "Value", value: row.value, error: !valueOk(row), onChange: event => {
35
+ setRow(index, { value: event.target.value });
36
+ } }), _jsx(TextField, { label: "Label", value: row.label, onChange: event => {
37
+ setRow(index, { label: event.target.value });
38
+ } }), _jsx(TextField, { label: "Color", value: row.color, error: !colorOk(row), onChange: event => {
39
+ setRow(index, { color: event.target.value });
40
+ } }), _jsx(IconButton, { "aria-label": "remove line", onClick: () => {
41
+ setRows(rows.filter((_, i) => i !== index));
42
+ }, children: _jsx(DeleteIcon, {}) })] }, index))), _jsx(Button, { onClick: () => {
43
+ setRows([...rows, { value: '', label: '', color: '' }]);
44
+ }, children: "Add line" })] }));
45
+ });