@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/esm/autoscale.js CHANGED
@@ -1,19 +1,20 @@
1
- // Default autoscale modes shared by wiggle / multi-wiggle. The alignments
2
- // coverage band exposes only a subset and a dynamic σ value, so it passes its
3
- // own option list.
4
- //
5
- // Here rather than beside the menu that renders it because a `[value, label]`
6
- // table is the one thing a menu holds that something outside the app needs to
7
- // read: the website's figure recipes name these labels in a click path, and a
8
- // module importing React or a lazy .tsx cannot be loaded by the node script
9
- // that builds them. A leaf module makes the recipe import the label instead of
10
- // retyping it, which is the difference between a copy that drifts and one that
11
- // cannot.
12
- export const DEFAULT_AUTOSCALE_OPTIONS = [
13
- ['local', 'Local'],
14
- ['localpercentile', 'Local (99th percentile)'],
15
- ['localsd', 'Local ± 3σ'],
16
- ];
1
+ import { quantileExtent } from '@jbrowse/core/util/quantileExtent';
2
+ /** The wiggle packer's arrays as a span, under a summary mode. */
3
+ export function datasetSpan({ data, visStart, visEnd }, summaryScoreMode) {
4
+ const { low, high } = boundArrays(summaryScoreMode);
5
+ return {
6
+ count: data.numFeatures,
7
+ starts: data.featurePositions,
8
+ ends: data.featurePositions,
9
+ stride: 2,
10
+ endOffset: 1,
11
+ low: low(data),
12
+ high: high(data),
13
+ visStart,
14
+ visEnd,
15
+ sortedBins: true,
16
+ };
17
+ }
17
18
  /**
18
19
  * #api
19
20
  * Per-feature scalar score array for a summary mode: the min/max summary array
@@ -32,9 +33,8 @@ function overlaps(fStart, fEnd, visStart, visEnd) {
32
33
  }
33
34
  // Which per-feature array each end of the domain comes from. Whiskers spreads
34
35
  // the two ends across the min/max summary arrays; every other mode draws both
35
- // from a single scalar. One table because `computeScoreStats` and the
36
- // percentile histogram have to agree — a domain whose extent and whose clipped
37
- // bound were measured off different arrays is a domain that clips its own data.
36
+ // from a single scalar. One table because the extent and the clipped bound have
37
+ // to read the same arrays, or a domain clips its own data.
38
38
  function boundArrays(summaryScoreMode) {
39
39
  const useWhiskers = summaryScoreMode === 'whiskers';
40
40
  return {
@@ -46,18 +46,15 @@ function boundArrays(summaryScoreMode) {
46
46
  : getEffectiveScores(data, summaryScoreMode),
47
47
  };
48
48
  }
49
- // Bin count for the approximate percentile histogram. 1024 bins gives ~0.1%
50
- // resolution on the domain, which is finer than any autoscale needs and keeps
51
- // the second pass O(n) with a fixed, trivial allocation.
52
- const NUM_HISTOGRAM_BINS = 1024;
53
- // First index whose feature STARTS at or after `bp`. `featurePositions` is
54
- // sorted by start — the same property `findFeatureAtBp` binary-searches on.
55
- function lowerBoundByStart(featurePositions, numFeatures, bp) {
49
+ // First index whose instance STARTS at or after `bp`. `starts` is sorted by
50
+ // start — the same property `findFeatureAtBp` binary-searches on.
51
+ function lowerBoundByStart(span, bp) {
52
+ const { starts, stride } = span;
56
53
  let lo = 0;
57
- let hi = numFeatures;
54
+ let hi = span.count;
58
55
  while (lo < hi) {
59
56
  const mid = (lo + hi) >> 1;
60
- if (featurePositions[mid * 2] >= bp) {
57
+ if (starts[mid * stride] >= bp) {
61
58
  hi = mid;
62
59
  }
63
60
  else {
@@ -67,186 +64,114 @@ function lowerBoundByStart(featurePositions, numFeatures, bp) {
67
64
  return lo;
68
65
  }
69
66
  /**
70
- * The half-open index range that can overlap `[visStart, visEnd)`.
71
- *
72
- * A fetch covers `bufferedVisibleRegions` — the viewport plus half a screen on
73
- * each side — so roughly half of what these passes walk is off-screen, and
74
- * `localpercentile` (the default) walks it two or three times over. Both bounds
75
- * come from a binary search on the sorted starts instead.
76
- *
77
- * The upper bound needs nothing but that sortedness: a feature starting at or
78
- * after `visEnd` cannot reach back into the window. The lower bound also leans
79
- * on wiggle features being non-overlapping bins — bigWig summary levels and
80
- * bedGraph both are — walking back over any run that does reach in. The callers
81
- * still test `overlaps` per feature inside the range, so a dataset that broke
82
- * that assumption could only lose a long early feature, never gain one.
67
+ * The half-open index range that can overlap `[visStart, visEnd)`. Over
68
+ * `sortedBins` a binary search bounds it, walking back over any bin that
69
+ * reaches into the window; any other span is walked whole, and every caller
70
+ * tests `overlaps` per instance inside the range.
83
71
  */
84
- function visibleIndexRange(featurePositions, numFeatures, visStart, visEnd) {
85
- if (visStart === undefined || visEnd === undefined) {
86
- return { from: 0, to: numFeatures };
72
+ function visibleIndexRange(span) {
73
+ const { visStart, visEnd, ends, stride, endOffset, sortedBins } = span;
74
+ if (!sortedBins || visStart === undefined || visEnd === undefined) {
75
+ return { from: 0, to: span.count };
87
76
  }
88
- const to = lowerBoundByStart(featurePositions, numFeatures, visEnd);
89
- let from = lowerBoundByStart(featurePositions, numFeatures, visStart);
90
- while (from > 0 && featurePositions[(from - 1) * 2 + 1] > visStart) {
77
+ const to = lowerBoundByStart(span, visEnd);
78
+ let from = lowerBoundByStart(span, visStart);
79
+ while (from > 0 && ends[(from - 1) * stride + endOffset] > visStart) {
91
80
  from--;
92
81
  }
93
82
  return { from, to };
94
83
  }
95
- // Min/max/mean/stddev of the visible features for a summary mode, in one pass.
96
- // Exported (not #api — internal plumbing shared with the wiggle displays) so a
97
- // caller needing both a domain and the raw extent computes the stats once and
98
- // feeds them to autoscaleDomainFromStats instead of walking the arrays twice.
99
- export function computeScoreStats(summaryScoreMode, datasets) {
100
- const { low, high } = boundArrays(summaryScoreMode);
84
+ // Half-open overlap test against the span's own window, for the drawn
85
+ // instances the index range admits.
86
+ function spanOverlaps(span, i) {
87
+ const { visStart, visEnd, starts, ends, stride, endOffset, drawnKeys } = span;
88
+ return ((drawnKeys === undefined || drawnKeys[span.row?.[i] ?? 0] === 1) &&
89
+ (visStart === undefined ||
90
+ visEnd === undefined ||
91
+ overlaps(starts[i * stride], ends[i * stride + endOffset], visStart, visEnd)));
92
+ }
93
+ // The extremes of the visible instances, in one pass. Exported (not #api —
94
+ // internal plumbing shared with the quantitative displays) so a caller needing
95
+ // both a domain and the raw extent computes them once and feeds them to
96
+ // `autoscaleDomainFromSpans` instead of walking the arrays twice.
97
+ export function computeSpanStats(spans) {
101
98
  let min = Infinity;
102
99
  let max = -Infinity;
103
- let sum = 0;
104
- let sumSq = 0;
105
- let count = 0;
106
- for (const { data, visStart, visEnd } of datasets) {
107
- const { featureScores, featurePositions, numFeatures } = data;
108
- // Selecting the arrays once per dataset keeps the mode check out of the
109
- // per-feature loop.
110
- const minScores = low(data);
111
- const maxScores = high(data);
112
- const { from, to } = visibleIndexRange(featurePositions, numFeatures, visStart, visEnd);
100
+ for (const span of spans) {
101
+ const { low, high } = span;
102
+ const { from, to } = visibleIndexRange(span);
113
103
  for (let i = from; i < to; i++) {
114
- if (visStart !== undefined &&
115
- visEnd !== undefined &&
116
- !overlaps(featurePositions[i * 2], featurePositions[i * 2 + 1], visStart, visEnd)) {
104
+ if (!spanOverlaps(span, i)) {
117
105
  continue;
118
106
  }
119
- min = Math.min(min, minScores[i]);
120
- max = Math.max(max, maxScores[i]);
121
- // Mean/stddev always use featureScores (the average) regardless of
122
- // summaryScoreMode; min/max for the domain bounds come from the mode-
123
- // selected arrays above. Intentional: sd-based autoscale centers on the
124
- // average-value distribution even in whiskers/min/max summary modes.
125
- const avg = featureScores[i];
126
- sum += avg;
127
- sumSq += avg * avg;
128
- count++;
107
+ // Non-finite values are skipped rather than folded in: a wig file may
108
+ // carry a NaN, and one of them poisons min/max, collapsing the whole
109
+ // domain to the [0, 1] stub the callers fall back to.
110
+ const lo = low[i];
111
+ if (Number.isFinite(lo)) {
112
+ min = Math.min(min, lo);
113
+ }
114
+ const hi = high[i];
115
+ if (Number.isFinite(hi)) {
116
+ max = Math.max(max, hi);
117
+ }
129
118
  }
130
119
  }
131
- if (count === 0 || !Number.isFinite(min) || !Number.isFinite(max)) {
132
- return undefined;
133
- }
134
- const mean = sum / count;
135
- const stdDev = Math.sqrt(Math.max(0, sumSq / count - mean * mean));
136
- return { scoreMin: min, scoreMax: max, scoreMean: mean, scoreStdDev: stdDev };
120
+ return Number.isFinite(min) && Number.isFinite(max)
121
+ ? { scoreMin: min, scoreMax: max }
122
+ : undefined;
123
+ }
124
+ /** `computeSpanStats` over the wiggle packer's datasets. */
125
+ export function computeScoreStats(summaryScoreMode, datasets) {
126
+ return computeSpanStats(datasets.map(d => datasetSpan(d, summaryScoreMode)));
137
127
  }
138
128
  /**
139
129
  * #api
140
- * Converts score stats into a `[min, max]` domain, applying std-dev
141
- * expansion for the `localsd` autoscale type.
130
+ * The domain the visible instances autoscale to: at a `quantile` of 1 their
131
+ * extremes, `stats`; below it `quantileExtent`'s ends, the bottom read off
132
+ * the `low`s and the top off the `high`s less the zeros where any is
133
+ * positive, so whiskers open to their spread, a sparse window's empty bins
134
+ * lower no top, and a window of nothing above 0 keeps the top it has.
142
135
  */
143
- export function domainFromStats(stats, autoscaleType, numStdDev) {
144
- if (autoscaleType === 'localsd') {
145
- const { scoreMean, scoreStdDev, scoreMin } = stats;
146
- return [
147
- scoreMin >= 0 ? 0 : scoreMean - numStdDev * scoreStdDev,
148
- scoreMean + numStdDev * scoreStdDev,
149
- ];
136
+ export function autoscaleDomainFromSpans({ stats, quantile, spans, }) {
137
+ if (quantile >= 1) {
138
+ return [stats.scoreMin, stats.scoreMax];
150
139
  }
151
- return [stats.scoreMin, stats.scoreMax];
152
- }
153
- // The `quantile`-th percentile magnitude of one signed side of the score
154
- // distribution: features are filtered to a single sign (`positiveSide`), their
155
- // magnitudes binned over `[0, maxMag]`, and the magnitude below which `quantile`
156
- // of that side's mass falls is returned — clipping the outermost `1 - quantile`
157
- // as outliers. Returns 0 when the side is empty. A fixed histogram keeps this an
158
- // O(n) pass with no sort, approximate to ~1/NUM_HISTOGRAM_BINS of maxMag, far
159
- // finer than the display needs.
160
- function sideMagnitudePercentile(datasets, scoresFor, positiveSide, maxMag, quantile) {
161
- if (maxMag <= 0) {
162
- return 0;
163
- }
164
- const bins = new Int32Array(NUM_HISTOGRAM_BINS);
165
- const scale = NUM_HISTOGRAM_BINS / maxMag;
166
- let count = 0;
167
- for (const { data, visStart, visEnd } of datasets) {
168
- const { featurePositions, numFeatures } = data;
169
- const scores = scoresFor(data);
170
- const { from, to } = visibleIndexRange(featurePositions, numFeatures, visStart, visEnd);
140
+ const lows = [];
141
+ const highs = [];
142
+ for (const span of spans) {
143
+ const { low, high } = span;
144
+ const { from, to } = visibleIndexRange(span);
171
145
  for (let i = from; i < to; i++) {
172
- if (visStart !== undefined &&
173
- visEnd !== undefined &&
174
- !overlaps(featurePositions[i * 2], featurePositions[i * 2 + 1], visStart, visEnd)) {
146
+ if (!spanOverlaps(span, i)) {
175
147
  continue;
176
148
  }
177
- const mag = positiveSide ? scores[i] : -scores[i];
178
- if (mag > 0) {
179
- const bin = Math.min(NUM_HISTOGRAM_BINS - 1, Math.floor(mag * scale));
180
- bins[bin]++;
181
- count++;
182
- }
149
+ lows.push(low[i]);
150
+ highs.push(high[i]);
183
151
  }
184
152
  }
185
- if (count === 0) {
186
- return 0;
187
- }
188
- const target = quantile * count;
189
- let cumulative = 0;
190
- for (let bin = 0; bin < NUM_HISTOGRAM_BINS; bin++) {
191
- cumulative += bins[bin];
192
- // +1 so the target bin is included rather than clipped.
193
- if (cumulative >= target) {
194
- return ((bin + 1) / NUM_HISTOGRAM_BINS) * maxMag;
195
- }
196
- }
197
- return maxMag;
198
- }
199
- // Builds a `[low, high]` domain by clipping each side of the score distribution
200
- // to its central `quantile` fraction (e.g. 0.99 → clip the outermost 1% of each
201
- // sign). Unlike localsd it makes no normality assumption, so it stays robust on
202
- // the heavily skewed score distributions typical of coverage/wiggle data.
203
- //
204
- // The two signs are clipped INDEPENDENTLY, anchored at 0. A single combined
205
- // percentile spends its whole budget on the dominant side, so on strongly
206
- // one-sided signed data (e.g. phyloP: mostly-positive conservation with a
207
- // sparse, small negative tail) the minority tail's 1st percentile lands at or
208
- // above 0 and the negative extent collapses to a flat band. Measuring each
209
- // side's percentile from 0 outward keeps a small-but-real opposite tail visible.
210
- function percentileDomainFromHistogram(stats, summaryScoreMode, quantile, datasets) {
211
- const { scoreMin, scoreMax } = stats;
212
- if (scoreMax - scoreMin <= 0) {
213
- return [scoreMin, scoreMax];
214
- }
215
- const arrays = boundArrays(summaryScoreMode);
216
- const high = scoreMax > 0
217
- ? sideMagnitudePercentile(datasets, arrays.high, true, scoreMax, quantile)
218
- : 0;
219
- const negExtent = scoreMin < 0
220
- ? sideMagnitudePercentile(datasets, arrays.low, false, -scoreMin, quantile)
221
- : 0;
222
- // Anchor the low bound at 0 for all-positive data (matching localsd's origin
223
- // convention); otherwise extend it to the negative side's clipped extent.
224
- return [scoreMin < 0 ? -negExtent : 0, high];
153
+ const tops = highs.some(v => v > 0) ? highs.filter(v => v !== 0) : highs;
154
+ const min = quantileExtent(lows, lows.length, quantile)[0];
155
+ const max = quantileExtent(tops, tops.length, quantile)[1];
156
+ return [Number.isFinite(min) ? min : 0, Number.isFinite(max) ? max : 0];
225
157
  }
226
- // Turns already-computed stats into the displayed domain for the `local` /
227
- // `localsd` / `localpercentile` autoscale types. `localpercentile` re-walks the
228
- // entries to build its histogram; the other types read the stats alone.
229
- // computeAutoscaleDomain (#api) is the one-shot form.
230
- export function autoscaleDomainFromStats({ stats, autoscaleType, summaryScoreMode, numStdDev, numQuantile = 0.99, visibleEntries, }) {
231
- return autoscaleType === 'localpercentile'
232
- ? percentileDomainFromHistogram(stats, summaryScoreMode, numQuantile, visibleEntries)
233
- : domainFromStats(stats, autoscaleType, numStdDev);
158
+ /** `autoscaleDomainFromSpans` over the wiggle packer's datasets. */
159
+ export function autoscaleDomainFromStats({ stats, quantile, summaryScoreMode, visibleEntries, }) {
160
+ return autoscaleDomainFromSpans({
161
+ stats,
162
+ quantile,
163
+ spans: visibleEntries.map(d => datasetSpan(d, summaryScoreMode)),
164
+ });
234
165
  }
235
166
  /**
236
167
  * #api
237
- * Computes a score domain from the visible feature arrays for the `local` /
238
- * `localsd` / `localpercentile` autoscale types.
168
+ * The score domain of the visible feature arrays, following `quantile` as
169
+ * `scales.y.domainQuantile` says.
239
170
  */
240
- export function computeAutoscaleDomain(autoscaleType, summaryScoreMode, numStdDev, visibleEntries, numQuantile = 0.99) {
241
- const stats = computeScoreStats(summaryScoreMode, visibleEntries);
171
+ export function computeAutoscaleDomain(quantile, summaryScoreMode, visibleEntries) {
172
+ const spans = visibleEntries.map(d => datasetSpan(d, summaryScoreMode));
173
+ const stats = computeSpanStats(spans);
242
174
  return stats
243
- ? autoscaleDomainFromStats({
244
- stats,
245
- autoscaleType,
246
- summaryScoreMode,
247
- numStdDev,
248
- numQuantile,
249
- visibleEntries,
250
- })
175
+ ? autoscaleDomainFromSpans({ stats, quantile, spans })
251
176
  : undefined;
252
177
  }
@@ -0,0 +1,30 @@
1
+ import type { IAnyStateTreeNode } from '@jbrowse/mobx-state-tree';
2
+ /** A display whose value scale can autoscale beside others. */
3
+ export interface AutoscaleGroupMember {
4
+ autoscaleGroup: string | undefined;
5
+ autoscaleRange: [number, number] | undefined;
6
+ }
7
+ /** A member the score menu can move into or out of a group. */
8
+ export interface AutoscalePeer extends AutoscaleGroupMember {
9
+ setAutoscaleGroup: (group?: string) => void;
10
+ }
11
+ /**
12
+ * Every display in `node`'s view whose value scale names `group`, `node`
13
+ * among them.
14
+ */
15
+ export declare function autoscaleGroupMembers(node: IAnyStateTreeNode, group: string): AutoscaleGroupMember[];
16
+ /** The span every defined range covers; undefined where none is. */
17
+ export declare function unionRanges(ranges: readonly ([number, number] | undefined)[]): [number, number] | undefined;
18
+ /** The other displays in `node`'s view with a value scale, by track name. */
19
+ export declare function autoscalePeers(node: IAnyStateTreeNode): {
20
+ display: AutoscalePeer;
21
+ key: string;
22
+ name: string;
23
+ }[];
24
+ /**
25
+ * Put `self` and `chosen` in one group, and take the group off every peer that
26
+ * held it and is not chosen. The group keeps `self`'s name, or a chosen
27
+ * peer's, and is named afresh where neither has one; choosing nobody takes
28
+ * `self` out of its group.
29
+ */
30
+ export declare function autoscaleWith(self: AutoscalePeer, peers: readonly AutoscalePeer[], chosen: ReadonlySet<AutoscalePeer>): void;
@@ -0,0 +1,97 @@
1
+ import { getConf } from '@jbrowse/core/configuration';
2
+ import { getContainingTrack, getContainingView, } from '@jbrowse/core/util/mstUtils';
3
+ import { hasParent, isStateTreeNode } from '@jbrowse/mobx-state-tree';
4
+ function isMember(thing) {
5
+ return (typeof thing === 'object' &&
6
+ thing !== null &&
7
+ 'autoscaleGroup' in thing &&
8
+ 'autoscaleRange' in thing);
9
+ }
10
+ function isPeer(thing) {
11
+ return isMember(thing) && 'setAutoscaleGroup' in thing;
12
+ }
13
+ function displaysOf(track) {
14
+ return typeof track === 'object' &&
15
+ track !== null &&
16
+ 'displays' in track &&
17
+ Array.isArray(track.displays)
18
+ ? track.displays
19
+ : [];
20
+ }
21
+ function containingView(node) {
22
+ if (!isStateTreeNode(node) || !hasParent(node)) {
23
+ return undefined;
24
+ }
25
+ try {
26
+ return getContainingView(node);
27
+ }
28
+ catch {
29
+ return undefined;
30
+ }
31
+ }
32
+ // A display standing outside any view, as a unit test builds one, shares its
33
+ // axis with nothing.
34
+ function viewDisplays(node) {
35
+ const view = containingView(node);
36
+ const tracks = view && 'tracks' in view && Array.isArray(view.tracks) ? view.tracks : [];
37
+ return tracks.flatMap(displaysOf);
38
+ }
39
+ /**
40
+ * Every display in `node`'s view whose value scale names `group`, `node`
41
+ * among them.
42
+ */
43
+ export function autoscaleGroupMembers(node, group) {
44
+ return viewDisplays(node).filter((display) => isMember(display) && display.autoscaleGroup === group);
45
+ }
46
+ /** The span every defined range covers; undefined where none is. */
47
+ export function unionRanges(ranges) {
48
+ let union;
49
+ for (const range of ranges) {
50
+ if (range) {
51
+ union = union
52
+ ? [Math.min(union[0], range[0]), Math.max(union[1], range[1])]
53
+ : [range[0], range[1]];
54
+ }
55
+ }
56
+ return union;
57
+ }
58
+ /** The other displays in `node`'s view with a value scale, by track name. */
59
+ export function autoscalePeers(node) {
60
+ return viewDisplays(node).flatMap(display => {
61
+ if (!isPeer(display) || display === node) {
62
+ return [];
63
+ }
64
+ const track = getContainingTrack(display);
65
+ return [{ display, key: track.id, name: String(getConf(track, 'name')) }];
66
+ });
67
+ }
68
+ function freeGroupName(taken) {
69
+ let n = 1;
70
+ while (taken.has(`group${n}`)) {
71
+ n++;
72
+ }
73
+ return `group${n}`;
74
+ }
75
+ /**
76
+ * Put `self` and `chosen` in one group, and take the group off every peer that
77
+ * held it and is not chosen. The group keeps `self`'s name, or a chosen
78
+ * peer's, and is named afresh where neither has one; choosing nobody takes
79
+ * `self` out of its group.
80
+ */
81
+ export function autoscaleWith(self, peers, chosen) {
82
+ const previous = self.autoscaleGroup;
83
+ const group = chosen.size === 0
84
+ ? undefined
85
+ : (previous ??
86
+ [...chosen].find(p => p.autoscaleGroup)?.autoscaleGroup ??
87
+ freeGroupName(new Set(peers.map(p => p.autoscaleGroup))));
88
+ self.setAutoscaleGroup(group);
89
+ for (const peer of peers) {
90
+ if (chosen.has(peer)) {
91
+ peer.setAutoscaleGroup(group);
92
+ }
93
+ else if (group !== undefined && peer.autoscaleGroup === previous) {
94
+ peer.setAutoscaleGroup(undefined);
95
+ }
96
+ }
97
+ }
@@ -0,0 +1,5 @@
1
+ export { AxisGutter, CrossHatchLines, CrossHatches, ScoreDomainCaption, ScoreRuleLines, ScoreRules, YScaleBar, YScaleBarOverlay, } from '@jbrowse/display-ui';
2
+ export { makeResolutionSubMenuItem } from './ResolutionStepper.tsx';
3
+ export { makePointSizeSubMenu, pointSizeAccess } from './pointSizeMenu.tsx';
4
+ export type { PointSizeAccess } from './pointSizeMenu.tsx';
5
+ export { default as SetMinMaxDialog } from './SetMinMaxDialog.tsx';
package/esm/chrome.js ADDED
@@ -0,0 +1,7 @@
1
+ // What draws a score plot, kept out of the package entry so the entry's score
2
+ // math and config mixins stay real in the RPC worker. A display imports these;
3
+ // an adapter or an RPC method never does.
4
+ export { AxisGutter, CrossHatchLines, CrossHatches, ScoreDomainCaption, ScoreRuleLines, ScoreRules, YScaleBar, YScaleBarOverlay, } from '@jbrowse/display-ui';
5
+ export { makeResolutionSubMenuItem } from './ResolutionStepper.js';
6
+ export { makePointSizeSubMenu, pointSizeAccess } from './pointSizeMenu.js';
7
+ export { default as SetMinMaxDialog } from './SetMinMaxDialog.js';
@@ -1,4 +1,4 @@
1
- import type { YScaleTicks } from './yScaleTicks.ts';
1
+ import type { YScaleTicks } from '@jbrowse/display-ui/yScaleTicks';
2
2
  export declare function computeYTicks(opts: {
3
3
  height: number;
4
4
  domain: [number, number] | number[] | undefined;
@@ -1,6 +1,6 @@
1
- import { YSCALEBAR_LABEL_OFFSET } from './constants.js';
1
+ import { YSCALEBAR_LABEL_OFFSET } from '@jbrowse/display-ui/yAxisConstants';
2
+ import { axisPlotBox } from '@jbrowse/display-ui/yScaleTicks';
2
3
  import { getScale } from './scale.js';
3
- import { axisPlotBox } from './yScaleTicks.js';
4
4
  // Builds Y-axis tick positions for a wiggle-family display: tick values come
5
5
  // from d3's scale.ticks(4) at normal heights, or fall back to the domain
6
6
  // endpoints for short tracks / when the user opts into minimal ticks.
@@ -18,7 +18,8 @@ export function computeYTicks(opts) {
18
18
  const { yTop, yBottom } = axisPlotBox(height, offset);
19
19
  // A window whose scores are all one value (an all-zero coverage stretch) has
20
20
  // a domain of no width. d3 answers the midpoint for every input there while
21
- // `makeScoreNormalizer` answers 0, and the renderer is the one to agree with.
21
+ // `makeScoreNormalizer` puts that value on the baseline, and the renderer is
22
+ // the one to agree with.
22
23
  if (domainMin === domainMax) {
23
24
  return { items: [{ value: domainMin, y: yBottom }], yTop, yBottom };
24
25
  }
@@ -1,2 +1 @@
1
- export declare const YSCALEBAR_LABEL_OFFSET = 5;
2
- export declare const ONSCREEN_AXIS_LEFT_PX = 50;
1
+ export { YSCALEBAR_LABEL_OFFSET } from '@jbrowse/display-ui/yAxisConstants';
package/esm/constants.js CHANGED
@@ -1,10 +1 @@
1
- // Vertical inset the single-wiggle family reserves above and below the plot so
2
- // the top/bottom y-axis labels aren't clipped. Lives in its own module because
3
- // both index.ts (which re-exports computeYTicks) and computeYTicks.ts need it —
4
- // importing it from index.ts would close a cycle.
5
- export const YSCALEBAR_LABEL_OFFSET = 5;
6
- // Left indent of the on-screen y-axis overlay (single-wiggle / manhattan) and of
7
- // multi-wiggle's per-row scalebars. The axis is right-oriented on screen, so its
8
- // labels grow into the plot from here; the indent keeps them clear of the track
9
- // label. Export uses its own anchor (the content left edge).
10
- export const ONSCREEN_AXIS_LEFT_PX = 50;
1
+ export { YSCALEBAR_LABEL_OFFSET } from '@jbrowse/display-ui/yAxisConstants';
@@ -1,3 +1,4 @@
1
+ import type { ZoomRange } from '@jbrowse/core/data_adapters/BaseAdapter/zoomRange';
1
2
  export interface SourceInfo {
2
3
  name: string;
3
4
  color?: string;
@@ -13,14 +14,11 @@ export interface WiggleFeatureArrays {
13
14
  featureMaxScores: Float32Array;
14
15
  numFeatures: number;
15
16
  hasSummaryScores: boolean;
16
- posFeaturePositions: Uint32Array;
17
- posFeatureScores: Float32Array;
18
- posNumFeatures: number;
19
- negFeaturePositions: Uint32Array;
20
- negFeatureScores: Float32Array;
21
- negNumFeatures: number;
22
17
  }
23
18
  export type WiggleSourceData = SourceInfo & WiggleFeatureArrays;
24
19
  export interface WiggleDataResult {
25
20
  sources: WiggleSourceData[];
21
+ zoomRange?: ZoomRange;
22
+ /** The bounds the adapter's values lie in by definition (`getValueDomain`). */
23
+ valueDomain?: [number, number];
26
24
  }
@@ -1,7 +1,7 @@
1
1
  import type { WiggleDataResult } from './dataTypes.ts';
2
2
  import type { WiggleRenderingBackend } from './renderingBackendTypes.ts';
3
- import type { YScaleTicks } from './yScaleTicks.ts';
4
3
  import type { RegionHost } from '@jbrowse/display-kit/regionHost';
4
+ import type { YScaleTicks } from '@jbrowse/display-ui';
5
5
  import type { DisplayPhase } from '@jbrowse/render-core/displayPhase';
6
6
  import type { RenderLifecycleModel } from '@jbrowse/render-core/useRenderingBackend';
7
7
  export interface WiggleGpuDisplayModel<TRenderingBackend = WiggleRenderingBackend, TData = WiggleDataResult> extends RenderLifecycleModel<TRenderingBackend> {
@@ -20,5 +20,4 @@ export interface WiggleGpuDisplayModel<TRenderingBackend = WiggleRenderingBacken
20
20
  regionTooLargeReason: string;
21
21
  zoomCanReleaseGate: boolean;
22
22
  forceLoad: () => void;
23
- showCrossHatches: boolean;
24
23
  }
package/esm/gapBreak.d.ts CHANGED
@@ -5,9 +5,8 @@
5
5
  * draw. Returns `Infinity` when there is nothing to decide, so a caller can
6
6
  * always compare against it unguarded.
7
7
  *
8
- * The limit is a multiple of the series' own *mean* spacing rather than an
9
- * absolute distance, which is what makes one number work across zoom levels and
10
- * data types: reduced BigWig bins get wider as you zoom out, so any fixed bp
8
+ * The limit is a multiple of the series' *mean* spacing rather than an
9
+ * absolute distance, so one number works across zoom levels and data types: reduced BigWig bins get wider as you zoom out, so any fixed bp
11
10
  * threshold would be either useless at one end or destructive at the other. It
12
11
  * also lets the same rule serve a bp axis (wiggle's linecenter) and a px one —
13
12
  * the caller picks the space, this only cares that the units are consistent.
package/esm/gapBreak.js CHANGED
@@ -5,9 +5,8 @@
5
5
  * draw. Returns `Infinity` when there is nothing to decide, so a caller can
6
6
  * always compare against it unguarded.
7
7
  *
8
- * The limit is a multiple of the series' own *mean* spacing rather than an
9
- * absolute distance, which is what makes one number work across zoom levels and
10
- * data types: reduced BigWig bins get wider as you zoom out, so any fixed bp
8
+ * The limit is a multiple of the series' *mean* spacing rather than an
9
+ * absolute distance, so one number works across zoom levels and data types: reduced BigWig bins get wider as you zoom out, so any fixed bp
11
10
  * threshold would be either useless at one end or destructive at the other. It
12
11
  * also lets the same rule serve a bp axis (wiggle's linecenter) and a px one —
13
12
  * the caller picks the space, this only cares that the units are consistent.