@jbrowse/wiggle-core 5.0.0-beta.8 → 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,89 @@
1
+ import type { scalesSchema } from './valueScaleConfigSchema.ts';
2
+ import type { ConfigModelForFields } from '@jbrowse/core/configuration';
3
+ /** The slots its composers each declare, with their own defaults. */
4
+ export declare const wiggleScoreConfigExtraSlots: {
5
+ readonly scatterPointSize: {
6
+ readonly type: 'number';
7
+ readonly defaultValue: 2;
8
+ };
9
+ readonly displayCrossHatches: {
10
+ readonly type: 'boolean';
11
+ readonly defaultValue: false;
12
+ };
13
+ };
14
+ export interface WiggleScoreConfigHost {
15
+ configuration: ConfigModelForFields<{
16
+ scales: ReturnType<typeof scalesSchema>;
17
+ } & typeof wiggleScoreConfigExtraSlots>;
18
+ }
19
+ /**
20
+ * #stateModel WiggleScoreConfigMixin
21
+ * #category display
22
+ *
23
+ * The score-plot config every display with a score axis shares: the axis
24
+ * (`ScoreScaleMixin`), the cross-hatch toggle and the scatter point size. A
25
+ * display plotting one configured field composes `ScoreFieldConfigMixin`,
26
+ * which adds `scoreField`.
27
+ */
28
+ export declare function WiggleScoreConfigMixin(): import("@jbrowse/mobx-state-tree").IModelType<Omit<{}, never>, {
29
+ readonly scaleType: string;
30
+ readonly manualMinScore: number | undefined;
31
+ readonly manualMaxScore: number | undefined;
32
+ readonly defaultScoreDomain: [number | undefined, number | undefined];
33
+ readonly valueScales: import("@jbrowse/display-ui").ValueScale[];
34
+ } & {
35
+ readonly minScoreBound: number | undefined;
36
+ readonly maxScoreBound: number | undefined;
37
+ readonly hasManualScoreBounds: boolean;
38
+ readonly axes: import("@jbrowse/display-ui").YAxis[];
39
+ } & {
40
+ readonly scaleType: string;
41
+ readonly scaleTypeChoices: string[];
42
+ readonly autoscaleType: string | undefined;
43
+ readonly numStdDev: number;
44
+ readonly numQuantile: number;
45
+ readonly symlogConstant: number;
46
+ readonly manualMinScore: number | undefined;
47
+ readonly manualMaxScore: number | undefined;
48
+ readonly scaleTitle: string | undefined;
49
+ readonly scoreRulesDrawn: boolean;
50
+ readonly scoreRules: import("@jbrowse/display-ui").ValueScaleRule[];
51
+ } & {
52
+ setScaleType(scaleType: string): void;
53
+ setAutoscale(val?: string): void;
54
+ setMinScore(val?: number): void;
55
+ setMaxScore(val?: number): void;
56
+ setScoreRules(rules: (number | import("@jbrowse/display-ui").ValueScaleRule)[]): void;
57
+ } & {
58
+ /**
59
+ * #getter
60
+ */
61
+ readonly scatterPointSize: number;
62
+ /**
63
+ * #getter
64
+ * The configured cross-hatch setting the menu toggles; `showCrossHatches`
65
+ * is what draws.
66
+ */
67
+ readonly displayCrossHatches: boolean;
68
+ /**
69
+ * #getter
70
+ * Whether score maps to color instead of height; a display overrides it.
71
+ */
72
+ readonly isDensityMode: boolean;
73
+ } & {
74
+ /**
75
+ * #action
76
+ */
77
+ toggleCrossHatches(): void;
78
+ /**
79
+ * #action
80
+ */
81
+ setScatterPointSize(val?: number): void;
82
+ } & {
83
+ /**
84
+ * #getter
85
+ * Whether the score-axis cross hatches draw: never in density mode, which
86
+ * has no height axis to rule and no toggle in its menu.
87
+ */
88
+ readonly showCrossHatches: boolean;
89
+ }, import("@jbrowse/mobx-state-tree")._NotCustomized, import("@jbrowse/mobx-state-tree")._NotCustomized>;
@@ -0,0 +1,69 @@
1
+ import { getConf, setConf } from '@jbrowse/core/configuration';
2
+ import { types } from '@jbrowse/mobx-state-tree';
3
+ import { ScoreScaleMixin } from './ScoreScaleMixin.js';
4
+ /** The slots its composers each declare, with their own defaults. */
5
+ export const wiggleScoreConfigExtraSlots = {
6
+ scatterPointSize: { type: 'number', defaultValue: 2 },
7
+ displayCrossHatches: { type: 'boolean', defaultValue: false },
8
+ };
9
+ const confNode = (self) => self;
10
+ /**
11
+ * #stateModel WiggleScoreConfigMixin
12
+ * #category display
13
+ *
14
+ * The score-plot config every display with a score axis shares: the axis
15
+ * (`ScoreScaleMixin`), the cross-hatch toggle and the scatter point size. A
16
+ * display plotting one configured field composes `ScoreFieldConfigMixin`,
17
+ * which adds `scoreField`.
18
+ */
19
+ export function WiggleScoreConfigMixin() {
20
+ return types
21
+ .compose('WiggleScoreConfigMixin', ScoreScaleMixin(), types.model({}))
22
+ .views(self => ({
23
+ /**
24
+ * #getter
25
+ */
26
+ get scatterPointSize() {
27
+ return getConf(confNode(self), 'scatterPointSize');
28
+ },
29
+ /**
30
+ * #getter
31
+ * The configured cross-hatch setting the menu toggles; `showCrossHatches`
32
+ * is what draws.
33
+ */
34
+ get displayCrossHatches() {
35
+ return getConf(confNode(self), 'displayCrossHatches');
36
+ },
37
+ /**
38
+ * #getter
39
+ * Whether score maps to color instead of height; a display overrides it.
40
+ */
41
+ get isDensityMode() {
42
+ return false;
43
+ },
44
+ }))
45
+ .actions(self => ({
46
+ /**
47
+ * #action
48
+ */
49
+ toggleCrossHatches() {
50
+ setConf(confNode(self), 'displayCrossHatches', !self.displayCrossHatches);
51
+ },
52
+ /**
53
+ * #action
54
+ */
55
+ setScatterPointSize(val) {
56
+ setConf(confNode(self), 'scatterPointSize', val);
57
+ },
58
+ }))
59
+ .views(self => ({
60
+ /**
61
+ * #getter
62
+ * Whether the score-axis cross hatches draw: never in density mode, which
63
+ * has no height axis to rule and no toggle in its menu.
64
+ */
65
+ get showCrossHatches() {
66
+ return self.displayCrossHatches && !self.isDensityMode;
67
+ },
68
+ }));
69
+ }
@@ -18,6 +18,29 @@ export interface ScoreStats {
18
18
  scoreMean: number;
19
19
  scoreStdDev: number;
20
20
  }
21
+ /**
22
+ * #api
23
+ * One block's worth of values to fold into a domain, whatever packed them: a
24
+ * wiggle source's interleaved `featurePositions` and its three summary arrays,
25
+ * or a mark layer's separate `x`/`x2` and its one `y` lane. `starts[i * stride]`
26
+ * and `ends[i * stride + endOffset]` give instance `i`'s span; `low`, `high` and
27
+ * `avg` give the two ends of its value and the one the mean is taken over, which
28
+ * are the same array wherever the packer ships a single scalar.
29
+ */
30
+ export interface ScoreSpan {
31
+ count: number;
32
+ starts: Uint32Array;
33
+ ends: Uint32Array;
34
+ stride: number;
35
+ endOffset: number;
36
+ low: Float32Array;
37
+ high: Float32Array;
38
+ avg: Float32Array;
39
+ visStart?: number;
40
+ visEnd?: number;
41
+ }
42
+ /** The wiggle packer's arrays as a span, under a summary mode. */
43
+ export declare function datasetSpan({ data, visStart, visEnd }: Dataset, summaryScoreMode: string): ScoreSpan;
21
44
  /**
22
45
  * #api
23
46
  * Per-feature scalar score array for a summary mode: the min/max summary array
@@ -28,16 +51,33 @@ export declare function getEffectiveScores(data: {
28
51
  featureMinScores: Float32Array;
29
52
  featureMaxScores: Float32Array;
30
53
  }, summaryScoreMode: string): Float32Array<ArrayBufferLike>;
54
+ export declare function computeSpanStats(spans: ScoreSpan[]): ScoreStats | undefined;
55
+ /** `computeSpanStats` over the wiggle packer's datasets. */
31
56
  export declare function computeScoreStats(summaryScoreMode: string, datasets: Dataset[]): ScoreStats | undefined;
32
57
  /**
33
58
  * #api
34
- * Converts score stats into a `[min, max]` domain, applying std-dev
35
- * expansion for the `localsd` autoscale type.
59
+ * Converts score stats into a `[min, max]` domain, applying std-dev expansion
60
+ * for the `localsd` autoscale type. An `undefined` mode is a display whose
61
+ * scale declares none, and takes the plain extremes.
36
62
  */
37
- export declare function domainFromStats(stats: ScoreStats, autoscaleType: string, numStdDev: number): [number, number];
63
+ export declare function domainFromStats(stats: ScoreStats, autoscaleType: string | undefined, numStdDev: number): [number, number];
64
+ /**
65
+ * #api
66
+ * Already-computed stats to the displayed domain, for the `local` / `localsd` /
67
+ * `localpercentile` autoscale modes. `localpercentile` re-walks the spans to
68
+ * build its histogram; the other modes read the stats alone.
69
+ */
70
+ export declare function autoscaleDomainFromSpans({ stats, autoscaleType, numStdDev, numQuantile, spans, }: {
71
+ stats: ScoreStats;
72
+ autoscaleType: string | undefined;
73
+ numStdDev: number;
74
+ numQuantile?: number;
75
+ spans: ScoreSpan[];
76
+ }): [number, number];
77
+ /** `autoscaleDomainFromSpans` over the wiggle packer's datasets. */
38
78
  export declare function autoscaleDomainFromStats({ stats, autoscaleType, summaryScoreMode, numStdDev, numQuantile, visibleEntries, }: {
39
79
  stats: ScoreStats;
40
- autoscaleType: string;
80
+ autoscaleType: string | undefined;
41
81
  summaryScoreMode: string;
42
82
  numStdDev: number;
43
83
  numQuantile?: number;
@@ -48,7 +88,7 @@ export declare function autoscaleDomainFromStats({ stats, autoscaleType, summary
48
88
  * Computes a score domain from the visible feature arrays for the `local` /
49
89
  * `localsd` / `localpercentile` autoscale types.
50
90
  */
51
- export declare function computeAutoscaleDomain(autoscaleType: string, summaryScoreMode: string, numStdDev: number, visibleEntries: {
91
+ export declare function computeAutoscaleDomain(autoscaleType: string | undefined, summaryScoreMode: string, numStdDev: number, visibleEntries: {
52
92
  data: FeatureArrays;
53
93
  visStart: number;
54
94
  visEnd: number;
package/esm/autoscale.js CHANGED
@@ -14,6 +14,22 @@ export const DEFAULT_AUTOSCALE_OPTIONS = [
14
14
  ['localpercentile', 'Local (99th percentile)'],
15
15
  ['localsd', 'Local ± 3σ'],
16
16
  ];
17
+ /** The wiggle packer's arrays as a span, under a summary mode. */
18
+ export function datasetSpan({ data, visStart, visEnd }, summaryScoreMode) {
19
+ const { low, high } = boundArrays(summaryScoreMode);
20
+ return {
21
+ count: data.numFeatures,
22
+ starts: data.featurePositions,
23
+ ends: data.featurePositions,
24
+ stride: 2,
25
+ endOffset: 1,
26
+ low: low(data),
27
+ high: high(data),
28
+ avg: data.featureScores,
29
+ visStart,
30
+ visEnd,
31
+ };
32
+ }
17
33
  /**
18
34
  * #api
19
35
  * Per-feature scalar score array for a summary mode: the min/max summary array
@@ -50,14 +66,15 @@ function boundArrays(summaryScoreMode) {
50
66
  // resolution on the domain, which is finer than any autoscale needs and keeps
51
67
  // the second pass O(n) with a fixed, trivial allocation.
52
68
  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) {
69
+ // First index whose instance STARTS at or after `bp`. `starts` is sorted by
70
+ // start — the same property `findFeatureAtBp` binary-searches on.
71
+ function lowerBoundByStart(span, bp) {
72
+ const { starts, stride } = span;
56
73
  let lo = 0;
57
- let hi = numFeatures;
74
+ let hi = span.count;
58
75
  while (lo < hi) {
59
76
  const mid = (lo + hi) >> 1;
60
- if (featurePositions[mid * 2] >= bp) {
77
+ if (starts[mid * stride] >= bp) {
61
78
  hi = mid;
62
79
  }
63
80
  else {
@@ -81,60 +98,62 @@ function lowerBoundByStart(featurePositions, numFeatures, bp) {
81
98
  * still test `overlaps` per feature inside the range, so a dataset that broke
82
99
  * that assumption could only lose a long early feature, never gain one.
83
100
  */
84
- function visibleIndexRange(featurePositions, numFeatures, visStart, visEnd) {
101
+ function visibleIndexRange(span) {
102
+ const { visStart, visEnd, ends, stride, endOffset } = span;
85
103
  if (visStart === undefined || visEnd === undefined) {
86
- return { from: 0, to: numFeatures };
104
+ return { from: 0, to: span.count };
87
105
  }
88
- const to = lowerBoundByStart(featurePositions, numFeatures, visEnd);
89
- let from = lowerBoundByStart(featurePositions, numFeatures, visStart);
90
- while (from > 0 && featurePositions[(from - 1) * 2 + 1] > visStart) {
106
+ const to = lowerBoundByStart(span, visEnd);
107
+ let from = lowerBoundByStart(span, visStart);
108
+ while (from > 0 && ends[(from - 1) * stride + endOffset] > visStart) {
91
109
  from--;
92
110
  }
93
111
  return { from, to };
94
112
  }
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);
113
+ // Half-open overlap test against the span's own window, for the instances the
114
+ // index range admits.
115
+ function spanOverlaps(span, i) {
116
+ const { visStart, visEnd, starts, ends, stride, endOffset } = span;
117
+ return (visStart === undefined ||
118
+ visEnd === undefined ||
119
+ overlaps(starts[i * stride], ends[i * stride + endOffset], visStart, visEnd));
120
+ }
121
+ // Min/max/mean/stddev of the visible instances, in one pass. Exported (not #api
122
+ // — internal plumbing shared with the quantitative displays) so a caller needing
123
+ // both a domain and the raw extent computes the stats once and feeds them to
124
+ // `autoscaleDomainFromSpans` instead of walking the arrays twice.
125
+ export function computeSpanStats(spans) {
101
126
  let min = Infinity;
102
127
  let max = -Infinity;
103
128
  let sum = 0;
104
129
  let sumSq = 0;
105
130
  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);
131
+ for (const span of spans) {
132
+ const { low, high, avg } = span;
133
+ const { from, to } = visibleIndexRange(span);
113
134
  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)) {
135
+ if (!spanOverlaps(span, i)) {
117
136
  continue;
118
137
  }
119
- // Non-finite scores are skipped rather than folded in: a wig file may
138
+ // Non-finite values are skipped rather than folded in: a wig file may
120
139
  // carry a NaN, and one of them poisons min/max and mean alike, collapsing
121
140
  // the whole domain to the [0, 1] stub the callers fall back to.
122
- const lo = minScores[i];
141
+ const lo = low[i];
123
142
  if (Number.isFinite(lo)) {
124
143
  min = Math.min(min, lo);
125
144
  }
126
- const hi = maxScores[i];
145
+ const hi = high[i];
127
146
  if (Number.isFinite(hi)) {
128
147
  max = Math.max(max, hi);
129
148
  }
130
- // Mean/stddev always use featureScores (the average) regardless of
131
- // summaryScoreMode; min/max for the domain bounds come from the mode-
132
- // selected arrays above. Intentional: sd-based autoscale centers on the
133
- // average-value distribution even in whiskers/min/max summary modes.
134
- const avg = featureScores[i];
135
- if (Number.isFinite(avg)) {
136
- sum += avg;
137
- sumSq += avg * avg;
149
+ // Mean/stddev always come from `avg` — the average score on the wiggle
150
+ // side — while min/max come from the mode-selected pair. Intentional:
151
+ // sd-based autoscale centers on the average-value distribution even in
152
+ // whiskers/min/max summary modes.
153
+ const mid = avg[i];
154
+ if (Number.isFinite(mid)) {
155
+ sum += mid;
156
+ sumSq += mid * mid;
138
157
  count++;
139
158
  }
140
159
  }
@@ -146,10 +165,15 @@ export function computeScoreStats(summaryScoreMode, datasets) {
146
165
  const stdDev = Math.sqrt(Math.max(0, sumSq / count - mean * mean));
147
166
  return { scoreMin: min, scoreMax: max, scoreMean: mean, scoreStdDev: stdDev };
148
167
  }
168
+ /** `computeSpanStats` over the wiggle packer's datasets. */
169
+ export function computeScoreStats(summaryScoreMode, datasets) {
170
+ return computeSpanStats(datasets.map(d => datasetSpan(d, summaryScoreMode)));
171
+ }
149
172
  /**
150
173
  * #api
151
- * Converts score stats into a `[min, max]` domain, applying std-dev
152
- * expansion for the `localsd` autoscale type.
174
+ * Converts score stats into a `[min, max]` domain, applying std-dev expansion
175
+ * for the `localsd` autoscale type. An `undefined` mode is a display whose
176
+ * scale declares none, and takes the plain extremes.
153
177
  */
154
178
  export function domainFromStats(stats, autoscaleType, numStdDev) {
155
179
  if (autoscaleType === 'localsd') {
@@ -161,31 +185,28 @@ export function domainFromStats(stats, autoscaleType, numStdDev) {
161
185
  }
162
186
  return [stats.scoreMin, stats.scoreMax];
163
187
  }
164
- // The `quantile`-th percentile magnitude of one signed side of the score
165
- // distribution: features are filtered to a single sign (`positiveSide`), their
188
+ // The `quantile`-th percentile magnitude of one signed side of the value
189
+ // distribution: instances are filtered to a single sign (`positiveSide`), their
166
190
  // magnitudes binned over `[0, maxMag]`, and the magnitude below which `quantile`
167
191
  // of that side's mass falls is returned — clipping the outermost `1 - quantile`
168
192
  // as outliers. Returns 0 when the side is empty. A fixed histogram keeps this an
169
193
  // O(n) pass with no sort, approximate to ~1/NUM_HISTOGRAM_BINS of maxMag, far
170
194
  // finer than the display needs.
171
- function sideMagnitudePercentile(datasets, scoresFor, positiveSide, maxMag, quantile) {
195
+ function sideMagnitudePercentile(spans, valuesFor, positiveSide, maxMag, quantile) {
172
196
  if (maxMag <= 0) {
173
197
  return 0;
174
198
  }
175
199
  const bins = new Int32Array(NUM_HISTOGRAM_BINS);
176
200
  const scale = NUM_HISTOGRAM_BINS / maxMag;
177
201
  let count = 0;
178
- for (const { data, visStart, visEnd } of datasets) {
179
- const { featurePositions, numFeatures } = data;
180
- const scores = scoresFor(data);
181
- const { from, to } = visibleIndexRange(featurePositions, numFeatures, visStart, visEnd);
202
+ for (const span of spans) {
203
+ const values = valuesFor(span);
204
+ const { from, to } = visibleIndexRange(span);
182
205
  for (let i = from; i < to; i++) {
183
- if (visStart !== undefined &&
184
- visEnd !== undefined &&
185
- !overlaps(featurePositions[i * 2], featurePositions[i * 2 + 1], visStart, visEnd)) {
206
+ if (!spanOverlaps(span, i)) {
186
207
  continue;
187
208
  }
188
- const mag = positiveSide ? scores[i] : -scores[i];
209
+ const mag = positiveSide ? values[i] : -values[i];
189
210
  if (mag > 0) {
190
211
  const bin = Math.min(NUM_HISTOGRAM_BINS - 1, Math.floor(mag * scale));
191
212
  bins[bin]++;
@@ -207,10 +228,10 @@ function sideMagnitudePercentile(datasets, scoresFor, positiveSide, maxMag, quan
207
228
  }
208
229
  return maxMag;
209
230
  }
210
- // Builds a `[low, high]` domain by clipping each side of the score distribution
211
- // to its central `quantile` fraction (e.g. 0.99 → clip the outermost 1% of each
231
+ // Builds a `[low, high]` domain by clipping each side of the value distribution
232
+ // to its central `quantile` fraction (e.g. 0.99 -> clip the outermost 1% of each
212
233
  // sign). Unlike localsd it makes no normality assumption, so it stays robust on
213
- // the heavily skewed score distributions typical of coverage/wiggle data.
234
+ // the heavily skewed distributions typical of coverage/wiggle data.
214
235
  //
215
236
  // The two signs are clipped INDEPENDENTLY, anchored at 0. A single combined
216
237
  // percentile spends its whole budget on the dominant side, so on strongly
@@ -218,46 +239,57 @@ function sideMagnitudePercentile(datasets, scoresFor, positiveSide, maxMag, quan
218
239
  // sparse, small negative tail) the minority tail's 1st percentile lands at or
219
240
  // above 0 and the negative extent collapses to a flat band. Measuring each
220
241
  // side's percentile from 0 outward keeps a small-but-real opposite tail visible.
221
- function percentileDomainFromHistogram(stats, summaryScoreMode, quantile, datasets) {
242
+ function percentileDomainFromSpans(stats, quantile, spans) {
222
243
  const { scoreMin, scoreMax } = stats;
223
244
  if (scoreMax - scoreMin <= 0) {
224
245
  return [scoreMin, scoreMax];
225
246
  }
226
- const arrays = boundArrays(summaryScoreMode);
227
247
  const high = scoreMax > 0
228
- ? sideMagnitudePercentile(datasets, arrays.high, true, scoreMax, quantile)
248
+ ? sideMagnitudePercentile(spans, span => span.high, true, scoreMax, quantile)
229
249
  : 0;
230
250
  const negExtent = scoreMin < 0
231
- ? sideMagnitudePercentile(datasets, arrays.low, false, -scoreMin, quantile)
251
+ ? sideMagnitudePercentile(spans, span => span.low, false, -scoreMin, quantile)
232
252
  : 0;
233
253
  // Anchor the low bound at 0 for all-positive data (matching localsd's origin
234
254
  // convention); otherwise extend it to the negative side's clipped extent.
235
255
  return [scoreMin < 0 ? -negExtent : 0, high];
236
256
  }
237
- // Turns already-computed stats into the displayed domain for the `local` /
238
- // `localsd` / `localpercentile` autoscale types. `localpercentile` re-walks the
239
- // entries to build its histogram; the other types read the stats alone.
240
- // computeAutoscaleDomain (#api) is the one-shot form.
241
- export function autoscaleDomainFromStats({ stats, autoscaleType, summaryScoreMode, numStdDev, numQuantile = 0.99, visibleEntries, }) {
257
+ /**
258
+ * #api
259
+ * Already-computed stats to the displayed domain, for the `local` / `localsd` /
260
+ * `localpercentile` autoscale modes. `localpercentile` re-walks the spans to
261
+ * build its histogram; the other modes read the stats alone.
262
+ */
263
+ export function autoscaleDomainFromSpans({ stats, autoscaleType, numStdDev, numQuantile = 0.99, spans, }) {
242
264
  return autoscaleType === 'localpercentile'
243
- ? percentileDomainFromHistogram(stats, summaryScoreMode, numQuantile, visibleEntries)
265
+ ? percentileDomainFromSpans(stats, numQuantile, spans)
244
266
  : domainFromStats(stats, autoscaleType, numStdDev);
245
267
  }
268
+ /** `autoscaleDomainFromSpans` over the wiggle packer's datasets. */
269
+ export function autoscaleDomainFromStats({ stats, autoscaleType, summaryScoreMode, numStdDev, numQuantile = 0.99, visibleEntries, }) {
270
+ return autoscaleDomainFromSpans({
271
+ stats,
272
+ autoscaleType,
273
+ numStdDev,
274
+ numQuantile,
275
+ spans: visibleEntries.map(d => datasetSpan(d, summaryScoreMode)),
276
+ });
277
+ }
246
278
  /**
247
279
  * #api
248
280
  * Computes a score domain from the visible feature arrays for the `local` /
249
281
  * `localsd` / `localpercentile` autoscale types.
250
282
  */
251
283
  export function computeAutoscaleDomain(autoscaleType, summaryScoreMode, numStdDev, visibleEntries, numQuantile = 0.99) {
252
- const stats = computeScoreStats(summaryScoreMode, visibleEntries);
284
+ const spans = visibleEntries.map(d => datasetSpan(d, summaryScoreMode));
285
+ const stats = computeSpanStats(spans);
253
286
  return stats
254
- ? autoscaleDomainFromStats({
287
+ ? autoscaleDomainFromSpans({
255
288
  stats,
256
289
  autoscaleType,
257
- summaryScoreMode,
258
290
  numStdDev,
259
291
  numQuantile,
260
- visibleEntries,
292
+ spans,
261
293
  })
262
294
  : undefined;
263
295
  }
@@ -0,0 +1,4 @@
1
+ export { AxisGutter, CrossHatchLines, CrossHatches, ScoreDomainCaption, ScoreRuleLines, ScoreRules, YScaleBar, YScaleBarOverlay, } from '@jbrowse/display-ui';
2
+ export { makeResolutionSubMenuItem } from './ResolutionStepper.tsx';
3
+ export { makePointSizeSubMenu, makeScatterPointSizeMenuItem, } from './pointSizeMenu.tsx';
4
+ 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, makeScatterPointSizeMenuItem, } from './pointSizeMenu.js';
7
+ export { default as SetMinMaxDialog } from './SetMinMaxDialog.js';
@@ -1,4 +1,4 @@
1
- import type { YScaleTicks } from '@jbrowse/display-ui';
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,4 +1,5 @@
1
- import { YSCALEBAR_LABEL_OFFSET, axisPlotBox } from '@jbrowse/display-ui';
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
4
  // Builds Y-axis tick positions for a wiggle-family display: tick values come
4
5
  // from d3's scale.ticks(4) at normal heights, or fall back to the domain
@@ -17,7 +18,8 @@ export function computeYTicks(opts) {
17
18
  const { yTop, yBottom } = axisPlotBox(height, offset);
18
19
  // A window whose scores are all one value (an all-zero coverage stretch) has
19
20
  // a domain of no width. d3 answers the midpoint for every input there while
20
- // `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.
21
23
  if (domainMin === domainMax) {
22
24
  return { items: [{ value: domainMin, y: yBottom }], yTop, yBottom };
23
25
  }
@@ -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,9 @@ 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;
26
22
  }
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.