@jbrowse/core 5.0.0-beta.3 → 5.0.0-beta.7

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 (110) hide show
  1. package/README.md +278 -250
  2. package/esm/PluginManager.js +1 -1
  3. package/esm/ReExports/publicUi.d.ts +1 -1
  4. package/esm/ReExports/publicUi.js +1 -1
  5. package/esm/ReExports/publicUtil.d.ts +1 -1
  6. package/esm/ReExports/publicUtil.js +1 -1
  7. package/esm/ReExports/workerNamespaceNames.js +3 -9
  8. package/esm/assemblyManager/assemblyConfigSchema.d.ts +2 -1
  9. package/esm/assemblyManager/assemblyConfigSchema.js +2 -1
  10. package/esm/assemblyManager/expandAssemblyConfigShorthand.d.ts +15 -15
  11. package/esm/assemblyManager/expandAssemblyConfigShorthand.js +34 -18
  12. package/esm/configuration/configurationSchema.d.ts +1 -2
  13. package/esm/configuration/configurationSchema.js +3 -8
  14. package/esm/configuration/configurationSlot.d.ts +0 -47
  15. package/esm/configuration/configurationSlot.js +4 -54
  16. package/esm/configuration/fillLocations.d.ts +14 -0
  17. package/esm/configuration/fillLocations.js +14 -0
  18. package/esm/configuration/fullConfSnapshot.d.ts +0 -15
  19. package/esm/configuration/fullConfSnapshot.js +0 -28
  20. package/esm/configuration/getConf.d.ts +4 -34
  21. package/esm/configuration/getConf.js +3 -38
  22. package/esm/configuration/index.d.ts +3 -4
  23. package/esm/configuration/index.js +3 -2
  24. package/esm/configuration/schemaRegistry.d.ts +1 -2
  25. package/esm/configuration/schemaRegistry.js +1 -2
  26. package/esm/configuration/slotFacade.d.ts +1 -16
  27. package/esm/configuration/slotFacade.js +1 -2
  28. package/esm/configuration/tabixShorthand.js +3 -3
  29. package/esm/configuration/types.d.ts +4 -16
  30. package/esm/data_adapters/CytobandAdapter/configSchema.js +3 -4
  31. package/esm/pluggableElementTypes/DisplayType.d.ts +5 -3
  32. package/esm/pluggableElementTypes/DisplayType.js +3 -1
  33. package/esm/pluggableElementTypes/models/BaseTrackModel.d.ts +30 -0
  34. package/esm/pluggableElementTypes/models/BaseTrackModel.js +45 -17
  35. package/esm/rpc/RpcRegistry.d.ts +7 -1
  36. package/esm/rpc/coreRpcMethods.d.ts +1 -0
  37. package/esm/rpc/coreRpcMethods.js +1 -0
  38. package/esm/rpc/methods/CoreEncodeFeatures.d.ts +15 -0
  39. package/esm/rpc/methods/CoreEncodeFeatures.js +62 -0
  40. package/esm/svg/svgId.d.ts +1 -1
  41. package/esm/svg/svgId.js +1 -1
  42. package/esm/ui/LegendSwatchGlyph.js +26 -21
  43. package/esm/ui/MenuTypes.d.ts +5 -52
  44. package/esm/ui/SizeSliderRow.d.ts +1 -3
  45. package/esm/ui/SizeSliderRow.js +4 -6
  46. package/esm/ui/SvgColorLegend.d.ts +9 -3
  47. package/esm/ui/SvgColorLegend.js +74 -33
  48. package/esm/ui/colorScale.d.ts +64 -0
  49. package/esm/ui/colorScale.js +41 -0
  50. package/esm/ui/colors.d.ts +11 -0
  51. package/esm/ui/colors.js +7 -0
  52. package/esm/ui/index.d.ts +5 -7
  53. package/esm/ui/index.js +3 -13
  54. package/esm/ui/legendMenuItem.d.ts +5 -16
  55. package/esm/ui/legendMenuItem.js +3 -14
  56. package/esm/ui/legendSpec.d.ts +11 -5
  57. package/esm/ui/legendSpec.js +8 -11
  58. package/esm/ui/makeSizeMenu.d.ts +0 -6
  59. package/esm/ui/makeSizeMenu.js +6 -30
  60. package/esm/ui/measureLegendText.js +1 -1
  61. package/esm/ui/menuItemAdornment.d.ts +9 -19
  62. package/esm/ui/menuItemAdornment.js +10 -31
  63. package/esm/ui/menuItems.d.ts +2 -6
  64. package/esm/ui/menuItems.js +0 -4
  65. package/esm/ui/palette.js +2 -1
  66. package/esm/ui/toggleMenuItems.d.ts +6 -13
  67. package/esm/ui/toggleMenuItems.js +13 -29
  68. package/esm/util/colorRamp.d.ts +3 -3
  69. package/esm/util/colorRamp.js +2 -2
  70. package/esm/util/featureTransforms.d.ts +15 -0
  71. package/esm/util/featureTransforms.js +342 -0
  72. package/esm/util/freezeDeep.d.ts +8 -10
  73. package/esm/util/freezeDeep.js +8 -10
  74. package/esm/util/glyphNames.d.ts +8 -0
  75. package/esm/util/glyphNames.js +12 -0
  76. package/esm/util/index.d.ts +0 -1
  77. package/esm/util/index.js +0 -1
  78. package/esm/util/markEncoding.d.ts +76 -0
  79. package/esm/util/markEncoding.js +436 -0
  80. package/esm/util/markEncodingTypes.d.ts +331 -0
  81. package/esm/util/markEncodingTypes.js +8 -0
  82. package/esm/util/renderToStaticMarkup.js +1 -1
  83. package/esm/util/starFusion.d.ts +26 -0
  84. package/esm/util/starFusion.js +37 -0
  85. package/esm/util/trackConfigDelta.d.ts +4 -8
  86. package/esm/util/trackConfigDelta.js +2 -4
  87. package/esm/util/tracks.d.ts +7 -1
  88. package/esm/util/tracks.js +72 -7
  89. package/esm/util/types/index.d.ts +12 -4
  90. package/esm/util/whenViewSettled.d.ts +40 -6
  91. package/esm/util/whenViewSettled.js +41 -5
  92. package/package.json +13 -3
  93. package/esm/configuration/promotableDefaults.d.ts +0 -234
  94. package/esm/configuration/promotableDefaults.js +0 -395
  95. package/esm/configuration/promotablePin.d.ts +0 -65
  96. package/esm/configuration/promotablePin.js +0 -1
  97. package/esm/configuration/promotableResolve.d.ts +0 -108
  98. package/esm/configuration/promotableResolve.js +0 -117
  99. package/esm/configuration/promotableSlots.d.ts +0 -12
  100. package/esm/configuration/promotableSlots.js +0 -45
  101. package/esm/configuration/slotShape.d.ts +0 -12
  102. package/esm/configuration/slotShape.js +0 -84
  103. package/esm/ui/PinAdornment.d.ts +0 -5
  104. package/esm/ui/PinAdornment.js +0 -67
  105. package/esm/ui/SvgGradientLegend.d.ts +0 -26
  106. package/esm/ui/SvgGradientLegend.js +0 -26
  107. package/esm/ui/promotablePinCoverage.d.ts +0 -58
  108. package/esm/ui/promotablePinCoverage.js +0 -124
  109. package/esm/util/openDisplays.d.ts +0 -23
  110. package/esm/util/openDisplays.js +0 -79
@@ -0,0 +1,331 @@
1
+ /**
2
+ * #api
3
+ * Where a channel's value comes from: a feature field name, read natively
4
+ * (`feature.get(name)`), or a `jexl:` expression over `feature` — the opt-in
5
+ * escape, three orders of magnitude slower per feature.
6
+ */
7
+ export type FieldRef = string;
8
+ /**
9
+ * #api
10
+ * The ramp a continuous colour scale samples: a named ramp, or evenly spaced
11
+ * CSS colour stops.
12
+ */
13
+ export type RampRef = 'viridis' | string[];
14
+ /**
15
+ * #api
16
+ * A field bound to a categorical scale: each distinct value takes one entry
17
+ * of the channel's range — a palette entry for `color`, a glyph name for
18
+ * `glyph`. With a `domain`, the listed values take the range in that order
19
+ * and whatever else the region meets follows, sorted; without one each value
20
+ * derives its entry from itself (an integer takes the slot it names, anything
21
+ * else hashes in), so every region agrees on a value it shares with another at
22
+ * the cost of an occasional collision, and `domain` is the way to spend the
23
+ * range deliberately.
24
+ */
25
+ export interface CategoricalRef {
26
+ field: FieldRef;
27
+ scale: 'categorical';
28
+ domain?: (string | number)[];
29
+ }
30
+ /**
31
+ * #api
32
+ * How a mark's `color` channel resolves. A CSS colour or a `jexl:` expression
33
+ * returning one paints per feature with no scale; the two object forms bind a
34
+ * field to a scale, which is what a legend can describe. A continuous scale
35
+ * reads the field through `domain` (the region's own extremes when absent)
36
+ * into `ramp`, and there a listed `domain` is what pins the answer across a
37
+ * whole view.
38
+ */
39
+ export type ColorEncoding = string | (CategoricalRef & {
40
+ palette?: string[];
41
+ }) | {
42
+ field: FieldRef;
43
+ scale: 'linear' | 'log';
44
+ domain?: [number, number];
45
+ ramp?: RampRef;
46
+ };
47
+ /**
48
+ * #api
49
+ * How a mark's `y` channel resolves: a feature field (or `jexl:` expression)
50
+ * read on a linear scale over the loaded regions' extremes, or the same field
51
+ * with the scale spelled out — `log` to read the domain logarithmically,
52
+ * `domain` to pin `[min, max]` instead of autoscaling. The declaration is the
53
+ * one owner: the display's axis, its ticks and the shader's placement are all
54
+ * resolved from it.
55
+ *
56
+ * `resolve` is which axis the mark reads. `shared` — the default — folds it
57
+ * into the display's one y domain with every other mark. `independent` gives
58
+ * it a domain folded from its layers alone and a second axis on the right, so
59
+ * a coverage run and the raw features can share a plot; one mark per display
60
+ * may ask for it.
61
+ */
62
+ export type ValueEncoding = FieldRef | {
63
+ field: FieldRef;
64
+ scale?: 'linear' | 'log';
65
+ domain?: [number, number];
66
+ resolve?: 'shared' | 'independent';
67
+ };
68
+ export type GlyphName = 'disc' | 'triangle' | 'diamond';
69
+ /**
70
+ * #api
71
+ * A {@link GlyphName}, a `jexl:` expression over `feature` returning one, or
72
+ * a field bound to a categorical scale whose `range` lists the glyph names
73
+ * handed out — the three glyphs, in order, when absent.
74
+ */
75
+ export type GlyphEncoding = GlyphName | `jexl:${string}` | (CategoricalRef & {
76
+ range?: GlyphName[];
77
+ });
78
+ /**
79
+ * #api
80
+ * The declared mapping from a feature's fields to a mark's channels. `x`
81
+ * defaults to `start` and `x2` to `end`; a mark that plots no value leaves `y`
82
+ * off. `glyph` is read by the `point` shape alone, `row` — an integer field,
83
+ * 0 where missing — by the `span` shape, which stacks a feature on the band
84
+ * it names.
85
+ */
86
+ export interface MarkEncoding {
87
+ x?: FieldRef;
88
+ x2?: FieldRef;
89
+ y?: ValueEncoding;
90
+ row?: FieldRef;
91
+ color?: ColorEncoding;
92
+ glyph?: GlyphEncoding;
93
+ }
94
+ /**
95
+ * #api
96
+ * The lanes a caller asks the encoder to fill, beyond `x`, `x2` and
97
+ * `featureIndex`, which every payload carries: a shape's channels, and
98
+ * `index` for the Flatbush a hover reads. A lane not asked for is neither
99
+ * allocated nor transferred, and a caller that never hovers declines the
100
+ * index, which is most of the encoder's cost after the walk.
101
+ */
102
+ export type LaneName = 'y' | 'color' | 'colorValue' | 'glyph' | 'row' | 'index';
103
+ /**
104
+ * #api
105
+ * The scale a colour channel was resolved through, as the legend reads it —
106
+ * the same table the colours in the payload came from, so the key cannot
107
+ * disagree with the painting.
108
+ */
109
+ export type ColorScaleTable = {
110
+ kind: 'categorical';
111
+ field: string;
112
+ entries: {
113
+ label: string;
114
+ color: number;
115
+ }[];
116
+ } | {
117
+ kind: 'ramp';
118
+ field: string;
119
+ scale: 'linear' | 'log';
120
+ /** What the ramp spans here: the declared domain, else `extent`. */
121
+ domain: [number, number];
122
+ /** Whether `domain` was declared, and so already agrees across regions. */
123
+ pinned: boolean;
124
+ /** This region's own extremes of the field, what a display unions. */
125
+ extent: [number, number];
126
+ lut: Uint8Array;
127
+ };
128
+ /**
129
+ * #api
130
+ * The scale a glyph channel was resolved through: which glyph each value of
131
+ * the field took.
132
+ */
133
+ export interface GlyphScaleTable {
134
+ kind: 'glyph';
135
+ field: string;
136
+ entries: {
137
+ label: string;
138
+ glyph: GlyphName;
139
+ }[];
140
+ }
141
+ /**
142
+ * #api
143
+ * Any channel's scale table; the kind names the channel.
144
+ */
145
+ export type ScaleTable = ColorScaleTable | GlyphScaleTable;
146
+ /**
147
+ * #api
148
+ * One encoding's channels over one region's features, dense and
149
+ * index-aligned: instance `i` of every array is the same feature, and
150
+ * `featureIndex[i]` says which one of the input list it was. A lane is
151
+ * present when the caller asked for it ({@link LaneName}); {@link Encoded}
152
+ * is this type with a known lane set required.
153
+ */
154
+ export interface EncodedChannels {
155
+ count: number;
156
+ /**
157
+ * Features the walk left out: one whose `x`, `x2` or `y` read as missing
158
+ * or not a number. A mistyped field skips every feature, so a display
159
+ * reads this beside `count` to say so.
160
+ */
161
+ skipped: number;
162
+ x: Uint32Array;
163
+ x2: Uint32Array;
164
+ featureIndex: Uint32Array;
165
+ y?: Float32Array;
166
+ color?: Uint32Array;
167
+ /**
168
+ * The raw values of a ramp colour channel, for a caller that named the
169
+ * `colorValue` lane: the scale then resolves on the main thread against a
170
+ * domain unioned over the loaded regions, and `scale.extent` is this
171
+ * region's contribution to it.
172
+ */
173
+ colorValue?: Float32Array;
174
+ glyph?: Uint8Array;
175
+ row?: Uint32Array;
176
+ /** The finite `y` extremes, `Infinity`/`-Infinity` when nothing plotted. */
177
+ yMin: number;
178
+ yMax: number;
179
+ /** A Flatbush over (x, y, x2, y), when `index` was asked for and `count` is not 0. */
180
+ flatbushData?: ArrayBuffer;
181
+ /** The colour channel's table, when `color` is a scale. */
182
+ scale?: ColorScaleTable;
183
+ /** The glyph channel's table, when `glyph` is a scale. */
184
+ glyphScale?: GlyphScaleTable;
185
+ }
186
+ /**
187
+ * #api
188
+ * {@link EncodedChannels} with the lanes in `L` present — what
189
+ * `encodeFeatures` answers a caller that named them.
190
+ */
191
+ export type Encoded<L extends LaneName> = EncodedChannels & Required<Pick<EncodedChannels, Exclude<L, 'index'>>>;
192
+ /**
193
+ * #api
194
+ * One layer of a `CoreEncodeFeatures` request: the encoding to evaluate and
195
+ * the lanes the display's shape reads.
196
+ */
197
+ export interface LayerRequest {
198
+ encoding: MarkEncoding;
199
+ lanes: LaneName[];
200
+ /** This layer's own steps, run after the request's shared ones. */
201
+ transform?: TransformStep[];
202
+ }
203
+ /**
204
+ * #api
205
+ * What `CoreEncodeFeatures` answers for one region: `layers[i]` is the
206
+ * request's `layers[i]` over the region's features, so a display's mark
207
+ * list indexes straight into it.
208
+ */
209
+ export interface EncodedFeaturesResult {
210
+ layers: EncodedChannels[];
211
+ bytes?: number;
212
+ }
213
+ /**
214
+ * #api
215
+ * Keep the features a `jexl:` expression over `feature` admits.
216
+ */
217
+ export interface FilterStep {
218
+ type: 'filter';
219
+ expr: string;
220
+ }
221
+ /**
222
+ * #api
223
+ * Write a `jexl:` expression's value over `feature` into the field `as` of
224
+ * every feature.
225
+ */
226
+ export interface FormulaStep {
227
+ type: 'formula';
228
+ expr: string;
229
+ as: string;
230
+ }
231
+ /**
232
+ * #api
233
+ * Snap every feature to the genome-aligned bin of `step` bp its `field`
234
+ * (`start` by default) falls in, writing the bin's edges over the fields
235
+ * `as` names — `start` and `end` by default, so an `aggregate` grouped by
236
+ * those counts per bin and the bar spans the bin.
237
+ */
238
+ export interface BinStep {
239
+ type: 'bin';
240
+ step: number;
241
+ field?: FieldRef;
242
+ as?: [string, string];
243
+ }
244
+ /**
245
+ * #api
246
+ * One summary over a group: `count` needs no field; `sum`, `mean`, `min` and
247
+ * `max` read one, skipping values that are not numbers. The output field is
248
+ * `as`, else `count` or `<op>_<field>`.
249
+ */
250
+ export interface AggregateOp {
251
+ op: 'count' | 'sum' | 'mean' | 'min' | 'max';
252
+ field?: FieldRef;
253
+ as?: string;
254
+ }
255
+ /**
256
+ * #api
257
+ * One feature per distinct `groupby` value set (one for the whole region
258
+ * with none), spanning its members' extent, carrying the group's fields and
259
+ * every `ops` entry.
260
+ */
261
+ export interface AggregateStep {
262
+ type: 'aggregate';
263
+ groupby?: FieldRef[];
264
+ ops: AggregateOp[];
265
+ }
266
+ /**
267
+ * #api
268
+ * Replace the features with runs of constant depth: how many of them overlap
269
+ * each stretch of the region, in a field `as` (`coverage` by default), with
270
+ * the stretches nothing overlaps left out.
271
+ */
272
+ export interface CoverageStep {
273
+ type: 'coverage';
274
+ as?: string;
275
+ }
276
+ /**
277
+ * #api
278
+ * Fan each feature out into one feature per element of an array-valued field
279
+ * — `subfeatures`, so a gene answers its transcripts and a transcript its
280
+ * exons. Each answer reads the element's own fields first and the feature it
281
+ * came from for everything else, so an exon still knows its gene's name and
282
+ * strand. A feature whose field holds no array drops out unless `keepEmpty`
283
+ * says otherwise.
284
+ */
285
+ export interface FlattenStep {
286
+ type: 'flatten';
287
+ field?: FieldRef;
288
+ /** Written with the element's position in its parent's array. */
289
+ index?: string;
290
+ keepEmpty?: boolean;
291
+ }
292
+ /**
293
+ * #api
294
+ * Assign every feature the lowest row on which it overlaps nothing already
295
+ * there — greedy first fit in start order, the packing a pileup is — and
296
+ * write it to the field `as` (`row`). `fields` names the interval read
297
+ * (`start`, `end`); `padding` is bp of clearance kept between two features
298
+ * sharing a row; `groupby` packs each distinct value set on its own rows,
299
+ * each numbered from 0. The answer is the input in start order, so a `span`
300
+ * encoding `row` stacks it.
301
+ */
302
+ export interface StackStep {
303
+ type: 'stack';
304
+ as?: string;
305
+ fields?: [string, string];
306
+ padding?: number;
307
+ groupby?: FieldRef[];
308
+ }
309
+ /**
310
+ * #api
311
+ * One step over the features before a layer is encoded, named by `type` the
312
+ * way GenomeSpy spells a transform; every step runs in order and the next
313
+ * reads what the last answered.
314
+ */
315
+ export type TransformStep = FilterStep | FormulaStep | FlattenStep | BinStep | AggregateStep | CoverageStep | StackStep;
316
+ export type CoreEncodeFeaturesArgs = {
317
+ adapterConfig: Record<string, unknown>;
318
+ region: {
319
+ refName: string;
320
+ start: number;
321
+ end: number;
322
+ assemblyName: string;
323
+ };
324
+ layers: LayerRequest[];
325
+ /** The steps over the features, in order, before every layer encodes. */
326
+ transform?: TransformStep[];
327
+ /** Sugar for leading `filter` steps: `jexl:`-prefixed expressions. */
328
+ filters?: string[];
329
+ byteLimit?: number;
330
+ sequenceAdapter?: Record<string, unknown>;
331
+ };
@@ -0,0 +1,8 @@
1
+ // The declared shape of a mark encoding and what evaluating one produces —
2
+ // split from markEncoding.ts because that file's runtime half reaches
3
+ // render-core's whole HAL/color-ramp graph for encodeFeatures' native scale
4
+ // tables, and RpcRegistry.ts needs only these types for CoreEncodeFeatures'
5
+ // wire shape. Importing them from markEncoding.ts would carry that graph into
6
+ // every leaf that reaches the RPC registry. scripts/moduleClosure.test.ts
7
+ // holds the ceiling.
8
+ export {};
@@ -50,7 +50,7 @@ export function renderToStaticMarkup(node) {
50
50
  // they cannot parse, so the alpha is stripped rather than left to break the
51
51
  // whole shape. It is *dropped*, not converted, so a translucent color exports
52
52
  // fully opaque: components that need transparency to survive set a separate
53
- // opacity attribute instead (`CrossHatches`, `MultiWiggleOverlayLines`).
53
+ // opacity attribute instead (`CrossHatches`, `RowSeparatorLines`).
54
54
  //
55
55
  // Rewriting `fill="rgba(r,g,b,a)"` into `fill="rgb(r,g,b)" fill-opacity="a"`
56
56
  // would preserve the appearance and let those two drop their workarounds, but
@@ -0,0 +1,26 @@
1
+ export type FusionBreakpoint = {
2
+ refName: string;
3
+ start: number;
4
+ end: number;
5
+ strand?: number;
6
+ /** which way the sequence this end keeps runs from it: 1 right, -1 left, 0 unknown */
7
+ mateDirection: number;
8
+ };
9
+ /**
10
+ * The column names of a STAR-Fusion `fusion_predictions.tsv` header line. The
11
+ * leading `#` is not universal across STAR-Fusion versions and wrappers, so it
12
+ * is stripped when present. Throws a named error for a header without the two
13
+ * breakpoint columns, which is the wrong file or the wrong File Type guess.
14
+ */
15
+ export declare function starFusionColumns(header: string | undefined): string[];
16
+ /**
17
+ * A STAR-Fusion `refName:pos:strand` breakpoint as an interbase feature, read
18
+ * from the RIGHT because a refName may contain colons (GRCh38's
19
+ * `HLA-A*01:01:01:01`). The position is 1-based in the file. The strand is the
20
+ * gene's transcription strand, and the fusion transcript keeps the donor's
21
+ * sequence 5' of the junction and the acceptor's 3' of it, so `mateDirection`
22
+ * is the strand negated for a donor and taken as read for an acceptor: the
23
+ * convention `parseSvAlt` states for a breakend and the paired-arc display
24
+ * draws its ticks from.
25
+ */
26
+ export declare function parseStarFusionBreakpoint(str: string, isDonor: boolean): FusionBreakpoint;
@@ -0,0 +1,37 @@
1
+ /**
2
+ * The column names of a STAR-Fusion `fusion_predictions.tsv` header line. The
3
+ * leading `#` is not universal across STAR-Fusion versions and wrappers, so it
4
+ * is stripped when present. Throws a named error for a header without the two
5
+ * breakpoint columns, which is the wrong file or the wrong File Type guess.
6
+ */
7
+ export function starFusionColumns(header) {
8
+ const columns = (header?.startsWith('#') ? header.slice(1) : header)?.split('\t') ?? [];
9
+ const missing = ['LeftBreakpoint', 'RightBreakpoint'].filter(c => !columns.includes(c));
10
+ if (missing.length) {
11
+ throw new Error(`Not a STAR-Fusion file: no ${missing.join(' or ')} column. Found: ${columns.slice(0, 6).join(', ')}`);
12
+ }
13
+ return columns;
14
+ }
15
+ /**
16
+ * A STAR-Fusion `refName:pos:strand` breakpoint as an interbase feature, read
17
+ * from the RIGHT because a refName may contain colons (GRCh38's
18
+ * `HLA-A*01:01:01:01`). The position is 1-based in the file. The strand is the
19
+ * gene's transcription strand, and the fusion transcript keeps the donor's
20
+ * sequence 5' of the junction and the acceptor's 3' of it, so `mateDirection`
21
+ * is the strand negated for a donor and taken as read for an acceptor: the
22
+ * convention `parseSvAlt` states for a breakend and the paired-arc display
23
+ * draws its ticks from.
24
+ */
25
+ export function parseStarFusionBreakpoint(str, isDonor) {
26
+ const parts = str.split(':');
27
+ const strandStr = parts.length >= 3 ? parts.pop() : undefined;
28
+ const pos = parts.length >= 2 ? +parts.pop() : Number.NaN;
29
+ const strand = strandStr === '+' ? 1 : strandStr === '-' ? -1 : undefined;
30
+ return {
31
+ refName: parts.join(':'),
32
+ start: pos - 1,
33
+ end: pos,
34
+ strand,
35
+ mateDirection: strand === undefined ? 0 : isDonor ? -strand : strand,
36
+ };
37
+ }
@@ -22,10 +22,8 @@
22
22
  * What it is NOT free to conflate is a removal with a reset. Both diff to an
23
23
  * empty delta, and `SessionTracks.updateTrackConfiguration` used to read that as
24
24
  * "the edit netted back to the base" and revert the track's working copy — which
25
- * undid a removal on screen 400ms after it landed. Promotable slots made that
26
- * reachable rather than rare: a slider row's reset unsets its slot, and an
27
- * admin config is free to set one. That branch now keeps the working copy; see
28
- * the comment there.
25
+ * undid a removal on screen 400ms after it landed. That branch now keeps the
26
+ * working copy; see the comment there.
29
27
  *
30
28
  * `displays` is merged by `displayId` so an edit to one display doesn't pin the
31
29
  * others. Nested config objects (e.g. `adapter`) recurse. Any other array (value
@@ -58,10 +56,8 @@ export declare function mergeTrackConfig(base: Record<string, unknown>, delta: R
58
56
  /** A single overridden slot: the dotted `path`, its base `from` and edited `to`. */
59
57
  export interface TrackConfigChange {
60
58
  /**
61
- * Where the setting lives, and — for a row a UI can revert — its **address**:
62
- * product-core's `resetDisplayDefault` parses a promoted display-type
63
- * default's path back into the type and slot it clears. So a producer that
64
- * wants a friendlier heading sets `label` and leaves this alone.
59
+ * Where the setting lives. A producer that wants a friendlier heading sets
60
+ * `label` and leaves this alone.
65
61
  */
66
62
  path: string[];
67
63
  from: Json;
@@ -22,10 +22,8 @@
22
22
  * What it is NOT free to conflate is a removal with a reset. Both diff to an
23
23
  * empty delta, and `SessionTracks.updateTrackConfiguration` used to read that as
24
24
  * "the edit netted back to the base" and revert the track's working copy — which
25
- * undid a removal on screen 400ms after it landed. Promotable slots made that
26
- * reachable rather than rare: a slider row's reset unsets its slot, and an
27
- * admin config is free to set one. That branch now keeps the working copy; see
28
- * the comment there.
25
+ * undid a removal on screen 400ms after it landed. That branch now keeps the
26
+ * working copy; see the comment there.
29
27
  *
30
28
  * `displays` is merged by `displayId` so an edit to one display doesn't pin the
31
29
  * others. Nested config objects (e.g. `adapter`) recurse. Any other array (value
@@ -383,11 +383,17 @@ export declare function viewDisplayNames(pluginManager: PluginManager, viewType:
383
383
  * so the pickers agree on it.
384
384
  */
385
385
  export declare function viewCanDisplayTrack(pluginManager: PluginManager, viewDisplays: Set<string>, trackType: string): boolean;
386
- export declare function pickDisplayForView({ declaredDisplays, requestedType, trackDisplayTypes, viewDisplayTypes, }: {
386
+ export declare function pickDisplayForView({ declaredDisplays, requestedType, trackDisplayTypes, viewDisplayTypes, preferredDisplayTypes, }: {
387
387
  declaredDisplays: DisplayConfSnapshot[];
388
388
  requestedType: string | undefined;
389
389
  trackDisplayTypes: string[];
390
390
  viewDisplayTypes: string[];
391
+ /**
392
+ * The display types the view registered as its own, ahead of those it
393
+ * inherits through `extendedName`: a variant track on the circular view
394
+ * draws its chords, not the linear display the view also accepts as a ring.
395
+ */
396
+ preferredDisplayTypes?: Set<string>;
391
397
  }): {
392
398
  type: string;
393
399
  conf: DisplayConfSnapshot | undefined;
@@ -564,10 +564,31 @@ export function viewCanDisplayTrack(pluginManager, viewDisplays, trackType) {
564
564
  .getTrackType(trackType)
565
565
  .displayTypes.some(d => viewDisplays.has(d.name)));
566
566
  }
567
- export function pickDisplayForView({ declaredDisplays, requestedType, trackDisplayTypes, viewDisplayTypes, }) {
567
+ export function pickDisplayForView({ declaredDisplays, requestedType, trackDisplayTypes, viewDisplayTypes, preferredDisplayTypes = new Set(), }) {
568
568
  const supported = new Set(viewDisplayTypes);
569
- const type = requestedType ??
570
- declaredDisplays.find(d => supported.has(d.type))?.type ??
569
+ // A requested type is CHECKED, not taken on faith. It used to pass straight
570
+ // through: an unsupported or misspelled one produced a synthesized
571
+ // `<trackId>-<type>` displayId that no config declares, and the display
572
+ // configuration reference then resolved that dangling id back to the track's
573
+ // DEFAULT display by type — so a caller that asked for read arcs got the
574
+ // pileup, and every layer reported success. The caller turns this
575
+ // `undefined` into an error naming what the track can be drawn as.
576
+ //
577
+ // Callers pass a CANONICAL name (resolveTrackDisplayChoice resolves an alias
578
+ // first), because these two lists hold canonical names only.
579
+ if (requestedType !== undefined) {
580
+ return supported.has(requestedType) &&
581
+ trackDisplayTypes.includes(requestedType)
582
+ ? {
583
+ type: requestedType,
584
+ conf: declaredDisplays.find(d => d.type === requestedType),
585
+ }
586
+ : undefined;
587
+ }
588
+ const declared = declaredDisplays.filter(d => supported.has(d.type));
589
+ const type = declared.find(d => preferredDisplayTypes.has(d.type))?.type ??
590
+ declared[0]?.type ??
591
+ trackDisplayTypes.find(name => preferredDisplayTypes.has(name)) ??
571
592
  trackDisplayTypes.find(name => supported.has(name));
572
593
  return type === undefined
573
594
  ? undefined
@@ -637,14 +658,32 @@ function resolveTrackDisplayChoice(self, trackId, displayInitialSnapshot, inline
637
658
  // getContainingView already resolves to for everything else beneath it.
638
659
  const view = isViewModel(self) ? self : getContainingView(self);
639
660
  const viewType = pluginManager.getViewType(view.type);
661
+ const trackDisplayTypes = trackType.displayTypes.map(d => d.name);
662
+ const viewDisplayTypes = viewType.displayTypes.map(d => d.name);
663
+ // An alias first, so a session spec or a caller naming a pre-consolidation
664
+ // display type (`LinearPileupDisplay`) reaches the type that replaced it
665
+ // instead of being refused — and so the membership checks below, which are
666
+ // over canonical names, ask the right question.
667
+ const requested = displayInitialSnapshot.type;
668
+ const requestedType = requested === undefined
669
+ ? undefined
670
+ : pluginManager.resolveDisplayTypeRecord(requested)?.name;
640
671
  const picked = pickDisplayForView({
641
672
  declaredDisplays: conf.displays ?? [],
642
- requestedType: displayInitialSnapshot.type,
643
- trackDisplayTypes: trackType.displayTypes.map(d => d.name),
644
- viewDisplayTypes: viewType.displayTypes.map(d => d.name),
673
+ requestedType,
674
+ trackDisplayTypes,
675
+ viewDisplayTypes,
676
+ preferredDisplayTypes: new Set(viewType.displayTypes
677
+ .filter(d => d.viewType === view.type)
678
+ .map(d => d.name)),
645
679
  });
646
680
  if (!picked) {
647
- throw new Error(`Could not find a compatible display for view type ${view.type}`);
681
+ const drawable = trackDisplayTypes.filter(name => viewDisplayTypes.includes(name));
682
+ throw new Error(requested === undefined
683
+ ? `Could not find a compatible display for view type ${view.type}`
684
+ : `Track "${trackId}" cannot be shown as "${requested}" in a ${view.type}. ${drawable.length > 0
685
+ ? `It takes: ${drawable.join(', ')}`
686
+ : 'This view draws none of its display types'}`);
648
687
  }
649
688
  return { rawConf, conf, trackType, picked };
650
689
  }
@@ -727,6 +766,32 @@ inlineConf) {
727
766
  // took the message away with it.
728
767
  const displaySettings = Object.fromEntries(Object.entries(displayInitialSnapshot).filter(([key]) => key !== 'type'));
729
768
  const report = track.applyDisplaySettings(displaySettings);
769
+ // A key that reached NOTHING, told apart from the ones `unapplied` holds
770
+ // legitimately. `unapplied` means "not a config slot", which covers both an
771
+ // MST display prop the snapshot spread above already applied and a
772
+ // misspelling that did nothing at all — so reporting the list wholesale
773
+ // called a correct spec broken, and reporting none of it let
774
+ // `{ trackId, colorSchem: 'x' }` load a plausible track with the setting
775
+ // silently missing. The display's own model type says which is which.
776
+ // Asked of the display NODE, not of its model type: `type.properties` sees
777
+ // declared props and misses everything else a key can legitimately reach —
778
+ // a volatile the snapshot spread above just set (`resolution` on the GC
779
+ // content display is one) reads as unreached and gets reported over a
780
+ // correct call.
781
+ // `unapplied` holds three kinds of entry, and only one of them means the
782
+ // key did nothing at all: applyDisplaySettings annotates `type` and the
783
+ // "a setter exists, pass allowSetters" case in parentheses, and pushes a
784
+ // BARE key when the display has neither a slot nor a setter for it. So the
785
+ // bare ones are the candidates, and the node itself settles the rest — the
786
+ // snapshot spread above may already have landed the key as a prop or a
787
+ // volatile, which is what `resolution` on the GC content display is.
788
+ const drawn = track.displays[0];
789
+ const reachedNothing = drawn
790
+ ? report.unapplied.filter(entry => !entry.includes(' (') && !(entry in drawn))
791
+ : [];
792
+ if (reachedNothing.length > 0) {
793
+ session.notifyError(`Track "${trackId}" ignored ${reachedNothing.join(', ')}: neither a setting the ${displayType} accepts nor one of its properties`);
794
+ }
730
795
  if (report.failed.length) {
731
796
  session.notifyError(`Track "${trackId}" rejected ${report.failed
732
797
  .map(f => `${f.key}: ${f.error}`)
@@ -1,6 +1,6 @@
1
1
  import type { ViewSnapshotInput } from '../../PluginManager.ts';
2
2
  import type TextSearchManager from '../../TextSearch/TextSearchManager.ts';
3
- import type { AnyConfigurationModel, ResolvableDisplay } from '../../configuration/index.ts';
3
+ import type { AnyConfigurationModel } from '../../configuration/index.ts';
4
4
  import type { BaseInternetAccountModel } from '../../pluggableElementTypes/models/index.ts';
5
5
  import type { PluginDefinition } from '../../pluginDefinitions.ts';
6
6
  import type RpcManager from '../../rpc/RpcManager.ts';
@@ -82,8 +82,6 @@ export interface AbstractSessionModel extends AbstractViewContainer, RenderingSe
82
82
  setPreferenceOverride: (key: string, value: unknown) => void;
83
83
  clearPreferenceOverrides: () => void;
84
84
  setScrollZoom: (flag: boolean) => void;
85
- getDisplayTypeDefault: (displayType: string, slot: string) => unknown;
86
- setDisplayTypeDefault: (displayType: string, slot: string, value: unknown) => void;
87
85
  hovered: unknown;
88
86
  setHovered: (arg: unknown) => void;
89
87
  setFocusedViewId?: (id: string) => void;
@@ -425,7 +423,10 @@ export interface TrackContainer {
425
423
  configuration: {
426
424
  trackId: string;
427
425
  };
428
- displays: ResolvableDisplay[];
426
+ displays: {
427
+ type: string;
428
+ configuration: AnyConfigurationModel;
429
+ }[];
429
430
  }[];
430
431
  assemblyNames?: string[];
431
432
  /**
@@ -479,6 +480,13 @@ export interface AbstractViewModel {
479
480
  * the view itself.
480
481
  */
481
482
  trackContainerFor?: (id: string) => TrackContainer | undefined;
483
+ /**
484
+ * the node a display in this view lays its regions out against, where that
485
+ * is not the view's own box — the circular view's strip. A display's
486
+ * foundation reads its host through `containingHost`, which answers this
487
+ * before the view itself.
488
+ */
489
+ regionHost?: unknown;
482
490
  /**
483
491
  * every track container this view owns INSTEAD of a `tracks` array of its
484
492
  * own. The synteny view is the only one: its tracks hang off the levels, one