@jbrowse/wiggle-core 5.0.0-beta.1 → 5.0.0-beta.10

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 (73) hide show
  1. package/README.md +76 -107
  2. package/esm/AutoscaleGroupDialog.d.ts +7 -0
  3. package/esm/AutoscaleGroupDialog.js +26 -0
  4. package/esm/ScoreAxisMixin.d.ts +134 -0
  5. package/esm/ScoreAxisMixin.js +213 -0
  6. package/esm/ScoreFieldConfigMixin.d.ts +72 -0
  7. package/esm/ScoreFieldConfigMixin.js +25 -0
  8. package/esm/ScorePlotChrome.d.ts +45 -0
  9. package/esm/ScorePlotChrome.js +47 -0
  10. package/esm/ScorePlotSvgFrame.d.ts +27 -0
  11. package/esm/ScorePlotSvgFrame.js +23 -0
  12. package/esm/ScoreScaleMixin.d.ts +113 -50
  13. package/esm/ScoreScaleMixin.js +148 -70
  14. package/esm/SetMinMaxDialog.d.ts +3 -2
  15. package/esm/SetMinMaxDialog.js +20 -6
  16. package/esm/SetScoreRulesDialog.d.ts +14 -0
  17. package/esm/SetScoreRulesDialog.js +45 -0
  18. package/esm/WiggleScoreConfigMixin.d.ts +89 -0
  19. package/esm/WiggleScoreConfigMixin.js +54 -0
  20. package/esm/autoscale.d.ts +54 -14
  21. package/esm/autoscale.js +105 -180
  22. package/esm/autoscaleGroup.d.ts +30 -0
  23. package/esm/autoscaleGroup.js +97 -0
  24. package/esm/chrome.d.ts +5 -0
  25. package/esm/chrome.js +7 -0
  26. package/esm/computeYTicks.d.ts +1 -1
  27. package/esm/computeYTicks.js +4 -3
  28. package/esm/constants.d.ts +1 -2
  29. package/esm/constants.js +1 -10
  30. package/esm/dataTypes.d.ts +4 -6
  31. package/esm/displayModel.d.ts +1 -2
  32. package/esm/gapBreak.d.ts +2 -3
  33. package/esm/gapBreak.js +2 -3
  34. package/esm/index.d.ts +24 -25
  35. package/esm/index.js +20 -18
  36. package/esm/normalize.d.ts +1 -26
  37. package/esm/normalize.js +1 -86
  38. package/esm/pointMarker.d.ts +7 -2
  39. package/esm/pointMarker.js +14 -18
  40. package/esm/pointSizeMenu.d.ts +30 -11
  41. package/esm/pointSizeMenu.js +42 -19
  42. package/esm/renderingBackendTypes.d.ts +13 -4
  43. package/esm/renderingBackendTypes.js +4 -1
  44. package/esm/rowLabelOffset.d.ts +8 -0
  45. package/esm/rowLabelOffset.js +16 -0
  46. package/esm/scale.d.ts +12 -8
  47. package/esm/scale.js +27 -14
  48. package/esm/scoreFieldConfigSchemaFields.d.ts +10 -0
  49. package/esm/scoreFieldConfigSchemaFields.js +8 -0
  50. package/esm/scoreMenuItems.d.ts +35 -16
  51. package/esm/scoreMenuItems.js +95 -62
  52. package/esm/scoreRuleMarks.d.ts +14 -39
  53. package/esm/scoreRuleMarks.js +12 -45
  54. package/esm/transferables.js +4 -9
  55. package/esm/valueScaleConfigSchema.d.ts +253 -0
  56. package/esm/valueScaleConfigSchema.js +270 -0
  57. package/esm/visibleStatsDomain.d.ts +20 -8
  58. package/esm/visibleStatsDomain.js +32 -27
  59. package/esm/wiggleRenderModes.generated.d.ts +1 -2
  60. package/esm/wiggleRenderModes.generated.js +1 -2
  61. package/package.json +14 -9
  62. package/esm/CrossHatches.d.ts +0 -11
  63. package/esm/CrossHatches.js +0 -32
  64. package/esm/ScoreRules.d.ts +0 -11
  65. package/esm/ScoreRules.js +0 -37
  66. package/esm/YScaleBar.d.ts +0 -6
  67. package/esm/YScaleBar.js +0 -46
  68. package/esm/YScaleBarOverlay.d.ts +0 -6
  69. package/esm/YScaleBarOverlay.js +0 -24
  70. package/esm/scoreAxisConfigSchemaFields.d.ts +0 -44
  71. package/esm/scoreAxisConfigSchemaFields.js +0 -54
  72. package/esm/yScaleTicks.d.ts +0 -59
  73. package/esm/yScaleTicks.js +0 -59
package/README.md CHANGED
@@ -1,6 +1,7 @@
1
1
  # @jbrowse/wiggle-core
2
2
 
3
- Shared scale and autoscale utilities for wiggle and coverage displays
3
+ Score-axis scale, autoscale, config mixins and plot chrome shared by wiggle,
4
+ Manhattan, mark and coverage displays
4
5
 
5
6
  <!-- API_DOCS_START -->
6
7
 
@@ -8,14 +9,29 @@ Shared scale and autoscale utilities for wiggle and coverage displays
8
9
 
9
10
  Auto-generated from `#api` JSDoc tags in this package. Do not edit by hand.
10
11
 
12
+ ### autoscaleDomainFromSpans
13
+
14
+ The domain the visible instances autoscale to: at a `quantile` of 1 their
15
+ extremes, `stats`; below it `quantileExtent`'s ends, the bottom read off the
16
+ `low`s and the top off the `high`s less the zeros where any is positive, so
17
+ whiskers open to their spread, a sparse window's empty bins lower no top, and a
18
+ window of nothing above 0 keeps the top it has.
19
+
20
+ ```js
21
+ // type signature
22
+ ({ stats, quantile, spans, }: { stats: ScoreStats; quantile: number; spans: ScoreSpan[]; }) => [number, number]
23
+ ```
24
+
25
+ [Source code](https://github.com/GMOD/jbrowse-components/blob/main/packages/wiggle-core/src/autoscale.ts)
26
+
11
27
  ### computeAutoscaleDomain
12
28
 
13
- Computes a score domain from the visible feature arrays for the `local` /
14
- `localsd` / `localpercentile` autoscale types.
29
+ The score domain of the visible feature arrays, following `quantile` as
30
+ `scales.y.domainQuantile` says.
15
31
 
16
32
  ```js
17
33
  // type signature
18
- (autoscaleType: string, summaryScoreMode: string, numStdDev: number, visibleEntries: {…}[], numQuantile?: number) => [...] | undefined
34
+ (quantile: number, summaryScoreMode: string, visibleEntries: { data: FeatureArrays; visStart: number; visEnd: number; }[]) => [number, number] | undefined
19
35
  ```
20
36
 
21
37
  [Source code](https://github.com/GMOD/jbrowse-components/blob/main/packages/wiggle-core/src/autoscale.ts)
@@ -50,18 +66,6 @@ exactly what it always got.
50
66
 
51
67
  [Source code](https://github.com/GMOD/jbrowse-components/blob/main/packages/wiggle-core/src/gapBreak.ts)
52
68
 
53
- ### domainFromStats
54
-
55
- Converts score stats into a `[min, max]` domain, applying std-dev expansion for
56
- the `localsd` autoscale type.
57
-
58
- ```js
59
- // type signature
60
- (stats: ScoreStats, autoscaleType: string, numStdDev: number) => [number, number]
61
- ```
62
-
63
- [Source code](https://github.com/GMOD/jbrowse-components/blob/main/packages/wiggle-core/src/autoscale.ts)
64
-
65
69
  ### gapBreakLimit
66
70
 
67
71
  How far apart two consecutive points of an interpolated (point-to-point) line
@@ -69,12 +73,12 @@ may be before the span between them counts as a hole rather than a segment to
69
73
  draw. Returns `Infinity` when there is nothing to decide, so a caller can always
70
74
  compare against it unguarded.
71
75
 
72
- The limit is a multiple of the series' own _mean_ spacing rather than an
73
- absolute distance, which is what makes one number work across zoom levels and
74
- data types: reduced BigWig bins get wider as you zoom out, so any fixed bp
75
- threshold would be either useless at one end or destructive at the other. It
76
- also lets the same rule serve a bp axis (wiggle's linecenter) and a px one — the
77
- caller picks the space, this only cares that the units are consistent.
76
+ The limit is a multiple of the series' _mean_ spacing rather than an absolute
77
+ distance, so one number works across zoom levels and data types: reduced BigWig
78
+ bins get wider as you zoom out, so any fixed bp threshold would be either
79
+ useless at one end or destructive at the other. It also lets the same rule serve
80
+ a bp axis (wiggle's linecenter) and a px one — the caller picks the space, this
81
+ only cares that the units are consistent.
78
82
 
79
83
  Mean, not median: it is O(1) from the endpoints, and it errs the safe way. A
80
84
  series' holes inflate their own mean, which raises the limit and so breaks
@@ -107,16 +111,19 @@ Per-feature scalar score array for a summary mode: the min/max summary array for
107
111
 
108
112
  ### getNiceDomain
109
113
 
110
- Rounds a domain to "nice" endpoints, clamped to the origin. An end given an
111
- explicit `bounds` value keeps that value exactly — only an autoscaled end is
112
- rounded. A log scale's floor still outranks a bound it cannot hold.
114
+ Rounds a domain to "nice" endpoints. `zero` reaches a linear or symlog domain to
115
+ 0 (`scales.y.zero`, ADR-182); a log domain has no 0 and floors at 1 instead. An
116
+ end given an explicit `bounds` value keeps that value exactly — only an
117
+ autoscaled end is rounded. A log scale's floor still outranks a bound it cannot
118
+ hold.
113
119
 
114
- The result never descends: a bound that would put `min` above `max` widens the
115
- other end instead, so no consumer has to guess what a backwards domain means.
120
+ The result never descends and never collapses: a bound that would put `min`
121
+ above `max` widens the other end instead, and one value in view widens away from
122
+ itself, so no consumer has to guess what a backwards or a flat domain means.
116
123
 
117
124
  ```js
118
125
  // type signature
119
- ({ scaleType, domain, bounds, }: { scaleType: string; domain: readonly [number, number]; bounds: readonly [number | undefined, number | undefined]; }) => [number, number]
126
+ ({…}: { scaleType: string; domain: readonly [number, number]; bounds: readonly [number | undefined, number | undefined]; zero: boolean; }) => [number, number]
120
127
  ```
121
128
 
122
129
  [Source code](https://github.com/GMOD/jbrowse-components/blob/main/packages/wiggle-core/src/scale.ts)
@@ -145,41 +152,6 @@ unless `nice: false` says it is already the one being drawn with.
145
152
 
146
153
  [Source code](https://github.com/GMOD/jbrowse-components/blob/main/packages/wiggle-core/src/scale.ts)
147
154
 
148
- ### makeScoreNormalizer
149
-
150
- Returns a loop-hoistable function normalizing a score to [0,1].
151
-
152
- `symlogConstant` is only read for `SCALE_TYPE_SYMLOG`, and is expected to be
153
- already resolved by resolveSymlogConstant — the shader gets the same resolved
154
- number as a uniform, so the "auto" rule lives on this side only and the two
155
- backends compare like for like.
156
-
157
- A range that is zero **or negative** normalizes everything to 0, which is the
158
- rule `scoreScale.slang` spells as `range <= 0.0`. Testing `range === 0` here
159
- instead let a descending domain through to a negative `1 / range`, so every
160
- score saturated to 1 on this side while the shader flattened them all to 0 — one
161
- view, opposite plots. `normalizeScoreParity.test.ts` sweeps the two.
162
-
163
- ```js
164
- // type signature
165
- (min: number, max: number, scaleType: WiggleScaleType, symlogConstant?: number) => (score: number) => number
166
- ```
167
-
168
- [Source code](https://github.com/GMOD/jbrowse-components/blob/main/packages/wiggle-core/src/normalize.ts)
169
-
170
- ### parseScoreRules
171
-
172
- Normalizes whatever a `scoreRules` config slot holds into `ScoreRule`s, dropping
173
- entries that are not usable. Config is user-authored JSON, so a bare number, a
174
- missing value or a non-numeric one all have to survive being read.
175
-
176
- ```js
177
- // type signature
178
- (value: unknown) => ScoreRule[]
179
- ```
180
-
181
- [Source code](https://github.com/GMOD/jbrowse-components/blob/main/packages/wiggle-core/src/scoreRuleMarks.ts)
182
-
183
155
  ### resolveSymlogConstant
184
156
 
185
157
  The symlog constant actually used for a domain. `0` (the config default) means
@@ -200,30 +172,6 @@ rather than hard-coded.
200
172
 
201
173
  [Source code](https://github.com/GMOD/jbrowse-components/blob/main/packages/wiggle-core/src/normalize.ts)
202
174
 
203
- ### scaleTypeFromString
204
-
205
- Maps the `'log'`/`'symlog'`/`'linear'` string to the numeric `WiggleScaleType`.
206
-
207
- ```js
208
- // type signature
209
- (scaleType: string) => WiggleScaleType
210
- ```
211
-
212
- [Source code](https://github.com/GMOD/jbrowse-components/blob/main/packages/wiggle-core/src/normalize.ts)
213
-
214
- ### ScoreRule
215
-
216
- One horizontal rule across a score plot, at a score the user chose.
217
-
218
- `label` is free text and carries no meaning this package assigns. That is
219
- deliberate: the obvious use is reading a coverage or CNV track against copy
220
- number, and there is no ploidy JBrowse could assume on the user's behalf. A
221
- whole-genome triplication is not diploid, plenty of genomes are not diploid to
222
- begin with, and a cancer sample can be neither — so "2 copies" is a claim only
223
- the person looking at the track can make.
224
-
225
- [Source code](https://github.com/GMOD/jbrowse-components/blob/main/packages/wiggle-core/src/scoreRuleMarks.ts)
226
-
227
175
  ### scoreRuleMarks
228
176
 
229
177
  Screen y for each rule that falls inside the plotted domain, dropping the rest.
@@ -240,28 +188,50 @@ because the axis need not be linear: on a log or symlog track, placing a rule at
240
188
  read against is not.
241
189
 
242
190
  `box` is likewise the caller's own — hand it the same `{yTop, yBottom}` the
243
- display's ticks were built with (a `YScaleTicks` satisfies it). Recomputing a
244
- box here would silently disagree with any band that lays its axis out
191
+ display's ticks were built with (a `YScaleTicks` satisfies it). A box recomputed
192
+ here would place rules off the ticks of any band that lays its axis out
245
193
  differently, and the alignments coverage band does.
246
194
 
247
195
  ```js
248
196
  // type signature
249
- ({…}: { rules: readonly ScoreRule[]; domain: [number, number] | undefined; box: { yTop: number; yBottom: number; }; normalize: (score: number) => number; }) => ScoreRuleMark[]
197
+ ({…}: { rules: readonly ValueScaleRule[]; domain: [number, number] | undefined; box: { yTop: number; yBottom: number; }; normalize: (score: number) => number; }) => ScoreRuleMark[]
250
198
  ```
251
199
 
252
200
  [Source code](https://github.com/GMOD/jbrowse-components/blob/main/packages/wiggle-core/src/scoreRuleMarks.ts)
253
201
 
202
+ ### ScoreSpan
203
+
204
+ One block's worth of values to fold into a domain, whatever packed them: a
205
+ wiggle source's interleaved `featurePositions` and its summary arrays, or a mark
206
+ layer's separate `x`/`x2` and its one `y` lane. `starts[i * stride]` and
207
+ `ends[i * stride + endOffset]` give instance `i`'s span, and `low` and `high`
208
+ the two ends of its value, one array where the packer ships a single scalar.
209
+
210
+ [Source code](https://github.com/GMOD/jbrowse-components/blob/main/packages/wiggle-core/src/autoscale.ts)
211
+
254
212
  ### visibleStatsDomain
255
213
 
256
- The visible score domain four displays derive identically: walk the settled
257
- blocks, accumulate the stats of what each one shows, and nice-round the
258
- autoscaled range inside the configured bounds. `undefined` while there is
259
- nothing to scale against — no data, a hidden band, or a view that has not
260
- initialized — which every caller distinguishes from a domain.
214
+ visibleStatsRange nice-rounded inside the configured bounds: the domain of a
215
+ display whose range is its own alone.
261
216
 
262
217
  ```js
263
218
  // type signature
264
- <Payload, Item, Stats>({ active, view, payloadFor, itemsFor, accumulate, range, bounds, scaleType, }: VisibleStatsDomainSpec<Payload, Item, Stats>) => [number, number] | undefined
219
+ <Payload, Item, Stats>({ bounds, scaleType, zero, ...spec }: VisibleStatsDomainSpec<Payload, Item, Stats>) => [number, number] | undefined
220
+ ```
221
+
222
+ [Source code](https://github.com/GMOD/jbrowse-components/blob/main/packages/wiggle-core/src/visibleStatsDomain.ts)
223
+
224
+ ### visibleStatsRange
225
+
226
+ The raw autoscaled range the displays with a value scale derive identically:
227
+ walk the settled blocks, accumulate the stats of what each one shows, and reduce
228
+ them to `[min, max]` before any bound or nice-rounding. `undefined` while there
229
+ is nothing to scale against — no data, a hidden band, or a view that has not
230
+ initialized — which every caller distinguishes from a range.
231
+
232
+ ```js
233
+ // type signature
234
+ <Payload, Item, Stats>({ active, view, payloadFor, itemsFor, accumulate, range, }: VisibleStatsRangeSpec<Payload, Item, Stats>) => [number, number] | undefined
265
235
  ```
266
236
 
267
237
  [Source code](https://github.com/GMOD/jbrowse-components/blob/main/packages/wiggle-core/src/visibleStatsDomain.ts)
@@ -270,17 +240,16 @@ initialized — which every caller distinguishes from a domain.
270
240
 
271
241
  Widens an autoscaled range so every configured rule stays on the axis.
272
242
 
273
- Without this a rule silently disappears in exactly the window that makes it
274
- worth having. Autoscale follows the visible data, so over a homozygous deletion
275
- a coverage domain collapses to about `[0, 1]` and a rule at the diploid depth
276
- falls outside it — and "2 copies would be up there" is the most informative
277
- thing that view can say. The reader has no menu to check either: `scoreRules` is
278
- set by whoever wrote the config, so a rule that vanishes leaves nothing behind
279
- to notice.
280
-
281
- Applied to the raw range, before `getNiceDomain` takes the `minScore` /
282
- `maxScore` bounds. Those still win: a rule outside an explicitly bounded axis is
283
- one the config asked not to be shown, and it drops as before.
243
+ Without this a rule is dropped from the plot in the views where it is most
244
+ useful. Autoscale follows the visible data, so over a homozygous deletion a
245
+ coverage domain collapses to about `[0, 1]` and a rule at the diploid depth
246
+ falls outside it, though the rule's position above the data is the most
247
+ informative mark in that view, and a rule that vanishes leaves nothing behind to
248
+ notice.
249
+
250
+ Applied to the raw range, before `getNiceDomain` takes the `scales.y` domain
251
+ bounds. Those still win: a rule outside an explicitly bounded axis is one the
252
+ config asked not to be shown, and it drops as before.
284
253
 
285
254
  ```js
286
255
  // type signature
@@ -0,0 +1,7 @@
1
+ import type { AutoscalePeer } from './autoscaleGroup.ts';
2
+ import type { IStateTreeNode } from '@jbrowse/mobx-state-tree';
3
+ declare const _default: ({ model, handleClose, }: {
4
+ model: AutoscalePeer & IStateTreeNode;
5
+ handleClose: () => void;
6
+ }) => import("react").JSX.Element;
7
+ export default _default;
@@ -0,0 +1,26 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useState } from 'react';
3
+ import { LabeledCheckbox, SubmitDialog } from '@jbrowse/core/ui';
4
+ import { Typography } from '@mui/material';
5
+ import { observer } from 'mobx-react';
6
+ import { autoscalePeers, autoscaleWith } from './autoscaleGroup.js';
7
+ export default observer(function AutoscaleGroupDialog({ model, handleClose, }) {
8
+ const peers = autoscalePeers(model);
9
+ const group = model.autoscaleGroup;
10
+ const [chosen, setChosen] = useState(() => new Set(group === undefined
11
+ ? []
12
+ : peers.map(p => p.display).filter(d => d.autoscaleGroup === group)));
13
+ return (_jsxs(SubmitDialog, { open: true, title: "Autoscale with other tracks", submitText: "Apply", onCancel: handleClose, onSubmit: () => {
14
+ autoscaleWith(model, peers.map(p => p.display), chosen);
15
+ handleClose();
16
+ }, children: [_jsx(Typography, { color: "text.secondary", children: "The tracks ticked here share this track's axis: each end nobody pinned spans all of their data, and follows it as the view moves." }), peers.map(({ display, key, name }) => (_jsx("div", { children: _jsx(LabeledCheckbox, { checked: chosen.has(display), label: name, onChange: checked => {
17
+ const next = new Set(chosen);
18
+ if (checked) {
19
+ next.add(display);
20
+ }
21
+ else {
22
+ next.delete(display);
23
+ }
24
+ setChosen(next);
25
+ } }) }, key)))] }));
26
+ });
@@ -0,0 +1,134 @@
1
+ import type { ValueScale, YAxis } from '@jbrowse/display-ui';
2
+ /**
3
+ * #stateModel ScoreAxisMixin
4
+ * #category display
5
+ * Not a cross-cutting mixin any more: {@link ScoreScaleMixin} is its one
6
+ * composer, and a display takes the axis by taking that. The two stay apart
7
+ * because the halves answer different questions — this one derives, the other
8
+ * says where the answer is written.
9
+ *
10
+ * The axis contract apart from where it is written down. `scaleType`,
11
+ * `manualMinScore` and `manualMaxScore` are declared here with neutral answers
12
+ * and a composing display overrides them, so the bounds, the "is anything
13
+ * pinned" question, the ticks and the cross-hatches derive the same way
14
+ * wherever the declaration lives. {@link ScoreScaleMixin} is the one composer
15
+ * in the tree, backing them with the `scales.y` object every quantitative
16
+ * display declares; the split stays because the derived half is what a display
17
+ * writing its scale down somewhere else would answer.
18
+ *
19
+ * The setters are not here. A display cannot set what it has not said where to
20
+ * put, so `setScaleType`/`setMinScore`/`setMaxScore` belong to whichever
21
+ * composer owns the declaration — one owner, whichever it is.
22
+ */
23
+ export declare function ScoreAxisMixin(): import("@jbrowse/mobx-state-tree").IModelType<{}, {
24
+ /**
25
+ * #getter
26
+ * Overridable: where this display's scale type is written down.
27
+ */
28
+ readonly scaleType: string;
29
+ /**
30
+ * #getter
31
+ * Overridable: where `scales.y.zero` is written down, whether an
32
+ * autoscaled linear or symlog axis reaches 0.
33
+ */
34
+ readonly scaleZero: boolean;
35
+ /**
36
+ * #getter
37
+ * Overridable: the lower bound the config really pins, `undefined`
38
+ * where nothing does.
39
+ */
40
+ readonly manualMinScore: number | undefined;
41
+ /**
42
+ * #getter
43
+ * Overridable: the upper bound the config really pins.
44
+ */
45
+ readonly manualMaxScore: number | undefined;
46
+ /**
47
+ * #getter
48
+ * Overridable hook: what each end of the domain falls back to where the
49
+ * config leaves its bound unset. `[undefined, undefined]` — the default —
50
+ * means autoscale both ends, which is right for a track whose scores have
51
+ * no absolute meaning (a bigwig's units are its own).
52
+ *
53
+ * A display whose scores are bounded *by construction* overrides it, so
54
+ * the axis stops being a function of what happens to be on screen: GC
55
+ * content is a fraction, so 0 and 1 are its real limits and mean the same
56
+ * thing at every locus. Autoscaled, the same GC value drew at different
57
+ * heights depending on where the user had panned, and the track could not
58
+ * be read across loci.
59
+ *
60
+ * A hook rather than a config default because the answer can depend on
61
+ * the data — the wiggle display reads the bounds its adapter declares,
62
+ * a GC content track's [0, 1] — and rather than each display
63
+ * re-resolving the sentinels, which is the one thing that must not be
64
+ * duplicated: config bounds still win, precisely because they are checked
65
+ * before this is consulted.
66
+ */
67
+ readonly defaultScoreDomain: [number | undefined, number | undefined];
68
+ /**
69
+ * #getter
70
+ * Overridable hook (default none): the scales this display draws its y
71
+ * through. A display that answers it gets an axis per band of each,
72
+ * with its cross-hatches, placed by `DisplayChrome` and
73
+ * `renderDisplaySvg`, and the ticks derived below.
74
+ */
75
+ readonly valueScales: ValueScale[];
76
+ /**
77
+ * #getter
78
+ * Overridable: the group this display's unpinned ends autoscale in,
79
+ * beside every display in its view naming the same one.
80
+ */
81
+ readonly autoscaleGroup: string | undefined;
82
+ /**
83
+ * #getter
84
+ * Overridable: what this display's own data spans, before any bound,
85
+ * nice-rounding or group widens it; what a group unions.
86
+ */
87
+ readonly autoscaleRange: [number, number] | undefined;
88
+ } & {
89
+ /**
90
+ * #getter
91
+ * Overridable: whether the autoscaled domain reaches 0, `scaleZero`
92
+ * unless the display maps its score to something with no axis.
93
+ */
94
+ readonly axisReachesZero: boolean;
95
+ } & {
96
+ /**
97
+ * #getter
98
+ * The domain an autoscaled axis draws: `autoscaleRange`, widened to
99
+ * every range its `autoscaleGroup` holds, nice-rounded inside this
100
+ * display's own bounds. Each member unions the others' own ranges and
101
+ * never their domains, so a pinned end stays the display's that pinned
102
+ * it. `undefined` while this display has nothing of its own to scale.
103
+ */
104
+ readonly autoscaledDomain: [number, number] | undefined;
105
+ /**
106
+ * #getter
107
+ * Resolved lower bound; `undefined` means autoscale this end.
108
+ */
109
+ readonly minScoreBound: number | undefined;
110
+ /**
111
+ * #getter
112
+ * Resolved upper bound; `undefined` means autoscale this end.
113
+ */
114
+ readonly maxScoreBound: number | undefined;
115
+ /**
116
+ * #getter
117
+ * Whether the user has pinned either end, which is a different question
118
+ * from whether either end resolved to a number: `defaultScoreDomain` fills
119
+ * the unset ends in, so a GC content track answers yes to the second with
120
+ * nothing configured. The score menu asks this one — it captions the
121
+ * min/max row with the range in force, and a caption off the resolved
122
+ * pair named a range on every freshly opened GC content track.
123
+ */
124
+ readonly hasManualScoreBounds: boolean;
125
+ /**
126
+ * #getter
127
+ * The axes, one per declared scale whose domain resolved: where each
128
+ * tick lands in the band's own pixel space, through `computeYTicks`
129
+ * unless the scale brought its own ladder, and where each of the
130
+ * scale's `rules` inside the domain lands in that same box, through the
131
+ * scale type the renderer places its values by.
132
+ */
133
+ readonly axes: YAxis[];
134
+ }, import("@jbrowse/mobx-state-tree")._NotCustomized, import("@jbrowse/mobx-state-tree")._NotCustomized>;
@@ -0,0 +1,213 @@
1
+ import { types } from '@jbrowse/mobx-state-tree';
2
+ import { scaleTypeCode } from '@jbrowse/render-core/scoreScale';
3
+ import { autoscaleGroupMembers, unionRanges } from './autoscaleGroup.js';
4
+ import { computeYTicks } from './computeYTicks.js';
5
+ import { makeScoreNormalizer, resolveSymlogConstant } from './normalize.js';
6
+ import { getNiceDomain } from './scale.js';
7
+ import { scoreRuleMarks } from './scoreRuleMarks.js';
8
+ /**
9
+ * #stateModel ScoreAxisMixin
10
+ * #category display
11
+ * Not a cross-cutting mixin any more: {@link ScoreScaleMixin} is its one
12
+ * composer, and a display takes the axis by taking that. The two stay apart
13
+ * because the halves answer different questions — this one derives, the other
14
+ * says where the answer is written.
15
+ *
16
+ * The axis contract apart from where it is written down. `scaleType`,
17
+ * `manualMinScore` and `manualMaxScore` are declared here with neutral answers
18
+ * and a composing display overrides them, so the bounds, the "is anything
19
+ * pinned" question, the ticks and the cross-hatches derive the same way
20
+ * wherever the declaration lives. {@link ScoreScaleMixin} is the one composer
21
+ * in the tree, backing them with the `scales.y` object every quantitative
22
+ * display declares; the split stays because the derived half is what a display
23
+ * writing its scale down somewhere else would answer.
24
+ *
25
+ * The setters are not here. A display cannot set what it has not said where to
26
+ * put, so `setScaleType`/`setMinScore`/`setMaxScore` belong to whichever
27
+ * composer owns the declaration — one owner, whichever it is.
28
+ */
29
+ export function ScoreAxisMixin() {
30
+ return types
31
+ .model('ScoreAxisMixin', {})
32
+ .views(() => ({
33
+ /**
34
+ * #getter
35
+ * Overridable: where this display's scale type is written down.
36
+ */
37
+ get scaleType() {
38
+ return 'linear';
39
+ },
40
+ /**
41
+ * #getter
42
+ * Overridable: where `scales.y.zero` is written down, whether an
43
+ * autoscaled linear or symlog axis reaches 0.
44
+ */
45
+ get scaleZero() {
46
+ return true;
47
+ },
48
+ /**
49
+ * #getter
50
+ * Overridable: the lower bound the config really pins, `undefined`
51
+ * where nothing does.
52
+ */
53
+ get manualMinScore() {
54
+ return undefined;
55
+ },
56
+ /**
57
+ * #getter
58
+ * Overridable: the upper bound the config really pins.
59
+ */
60
+ get manualMaxScore() {
61
+ return undefined;
62
+ },
63
+ /**
64
+ * #getter
65
+ * Overridable hook: what each end of the domain falls back to where the
66
+ * config leaves its bound unset. `[undefined, undefined]` — the default —
67
+ * means autoscale both ends, which is right for a track whose scores have
68
+ * no absolute meaning (a bigwig's units are its own).
69
+ *
70
+ * A display whose scores are bounded *by construction* overrides it, so
71
+ * the axis stops being a function of what happens to be on screen: GC
72
+ * content is a fraction, so 0 and 1 are its real limits and mean the same
73
+ * thing at every locus. Autoscaled, the same GC value drew at different
74
+ * heights depending on where the user had panned, and the track could not
75
+ * be read across loci.
76
+ *
77
+ * A hook rather than a config default because the answer can depend on
78
+ * the data — the wiggle display reads the bounds its adapter declares,
79
+ * a GC content track's [0, 1] — and rather than each display
80
+ * re-resolving the sentinels, which is the one thing that must not be
81
+ * duplicated: config bounds still win, precisely because they are checked
82
+ * before this is consulted.
83
+ */
84
+ get defaultScoreDomain() {
85
+ return [undefined, undefined];
86
+ },
87
+ /**
88
+ * #getter
89
+ * Overridable hook (default none): the scales this display draws its y
90
+ * through. A display that answers it gets an axis per band of each,
91
+ * with its cross-hatches, placed by `DisplayChrome` and
92
+ * `renderDisplaySvg`, and the ticks derived below.
93
+ */
94
+ get valueScales() {
95
+ return [];
96
+ },
97
+ /**
98
+ * #getter
99
+ * Overridable: the group this display's unpinned ends autoscale in,
100
+ * beside every display in its view naming the same one.
101
+ */
102
+ get autoscaleGroup() {
103
+ return undefined;
104
+ },
105
+ /**
106
+ * #getter
107
+ * Overridable: what this display's own data spans, before any bound,
108
+ * nice-rounding or group widens it; what a group unions.
109
+ */
110
+ get autoscaleRange() {
111
+ return undefined;
112
+ },
113
+ }))
114
+ .views(self => ({
115
+ /**
116
+ * #getter
117
+ * Overridable: whether the autoscaled domain reaches 0, `scaleZero`
118
+ * unless the display maps its score to something with no axis.
119
+ */
120
+ get axisReachesZero() {
121
+ return self.scaleZero;
122
+ },
123
+ }))
124
+ .views(self => ({
125
+ /**
126
+ * #getter
127
+ * The domain an autoscaled axis draws: `autoscaleRange`, widened to
128
+ * every range its `autoscaleGroup` holds, nice-rounded inside this
129
+ * display's own bounds. Each member unions the others' own ranges and
130
+ * never their domains, so a pinned end stays the display's that pinned
131
+ * it. `undefined` while this display has nothing of its own to scale.
132
+ */
133
+ get autoscaledDomain() {
134
+ const own = self.autoscaleRange;
135
+ const group = self.autoscaleGroup;
136
+ const range = own && group
137
+ ? unionRanges([
138
+ own,
139
+ ...autoscaleGroupMembers(self, group).map(m => m.autoscaleRange),
140
+ ])
141
+ : own;
142
+ return range
143
+ ? getNiceDomain({
144
+ domain: range,
145
+ bounds: [this.minScoreBound, this.maxScoreBound],
146
+ scaleType: self.scaleType,
147
+ zero: self.axisReachesZero,
148
+ })
149
+ : undefined;
150
+ },
151
+ /**
152
+ * #getter
153
+ * Resolved lower bound; `undefined` means autoscale this end.
154
+ */
155
+ get minScoreBound() {
156
+ return self.manualMinScore ?? self.defaultScoreDomain[0];
157
+ },
158
+ /**
159
+ * #getter
160
+ * Resolved upper bound; `undefined` means autoscale this end.
161
+ */
162
+ get maxScoreBound() {
163
+ return self.manualMaxScore ?? self.defaultScoreDomain[1];
164
+ },
165
+ /**
166
+ * #getter
167
+ * Whether the user has pinned either end, which is a different question
168
+ * from whether either end resolved to a number: `defaultScoreDomain` fills
169
+ * the unset ends in, so a GC content track answers yes to the second with
170
+ * nothing configured. The score menu asks this one — it captions the
171
+ * min/max row with the range in force, and a caption off the resolved
172
+ * pair named a range on every freshly opened GC content track.
173
+ */
174
+ get hasManualScoreBounds() {
175
+ return (self.manualMinScore !== undefined || self.manualMaxScore !== undefined);
176
+ },
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, and where each of the
182
+ * scale's `rules` inside the domain lands in that same box, through the
183
+ * scale type the renderer places its values by.
184
+ */
185
+ get axes() {
186
+ return self.valueScales.flatMap(scale => {
187
+ const { domain, rules = [] } = scale;
188
+ const ticks = scale.ticks ??
189
+ computeYTicks({
190
+ height: scale.height,
191
+ offset: scale.offset,
192
+ domain,
193
+ scaleType: scale.scaleType,
194
+ minimalTicks: scale.minimalTicks ?? false,
195
+ symlogConstant: scale.symlogConstant,
196
+ });
197
+ if (!domain || !ticks) {
198
+ return [];
199
+ }
200
+ const [min, max] = domain;
201
+ const ruleMarks = rules.length > 0
202
+ ? scoreRuleMarks({
203
+ rules,
204
+ domain,
205
+ box: ticks,
206
+ normalize: makeScoreNormalizer(min, max, scaleTypeCode(scale.scaleType), resolveSymlogConstant(min, max, scale.symlogConstant ?? 0)),
207
+ })
208
+ : [];
209
+ return [{ ...scale, domain, ticks, ruleMarks }];
210
+ });
211
+ },
212
+ }));
213
+ }