@jbrowse/plugin-linear-genome-view 5.0.0-beta.11 → 5.0.0-beta.15
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.
- package/esm/FeatureTrack/configSchema.d.ts +2 -1
- package/esm/FeatureTrack/configSchema.js +2 -1
- package/esm/LinearGenomeView/afterAttach.js +1 -23
- package/esm/LinearGenomeView/components/ExportSvgDialog.d.ts +6 -2
- package/esm/LinearGenomeView/components/ExportSvgDialog.js +31 -7
- package/esm/LinearGenomeView/components/HeaderPanControls.d.ts +3 -3
- package/esm/LinearGenomeView/components/HeaderPanControls.js +4 -2
- package/esm/LinearGenomeView/components/HeaderSearchBoxes.js +4 -1
- package/esm/LinearGenomeView/components/HighlightBand.d.ts +3 -2
- package/esm/LinearGenomeView/components/HighlightBand.js +4 -2
- package/esm/LinearGenomeView/components/HighlightChip.d.ts +3 -2
- package/esm/LinearGenomeView/components/HighlightChip.js +4 -2
- package/esm/LinearGenomeView/components/LinearGenomeView.js +5 -12
- package/esm/LinearGenomeView/components/LinearGenomeViewContainer.js +7 -9
- package/esm/LinearGenomeView/components/MiniControls.js +8 -3
- package/esm/LinearGenomeView/components/OverviewHighlightBand.d.ts +3 -2
- package/esm/LinearGenomeView/components/OverviewHighlightBand.js +4 -2
- package/esm/LinearGenomeView/components/RegionWidthEditorDialog.d.ts +3 -2
- package/esm/LinearGenomeView/components/RegionWidthEditorDialog.js +4 -4
- package/esm/LinearGenomeView/components/ScalebarRefNameLabels.js +4 -4
- package/esm/LinearGenomeView/components/SearchPanelForm.d.ts +3 -2
- package/esm/LinearGenomeView/components/SearchPanelForm.js +4 -2
- package/esm/LinearGenomeView/components/SearchResultsDialog.d.ts +4 -9
- package/esm/LinearGenomeView/components/SearchResultsDialog.js +7 -4
- package/esm/LinearGenomeView/components/SearchResultsTable.d.ts +3 -8
- package/esm/LinearGenomeView/components/SearchResultsTable.js +4 -33
- package/esm/LinearGenomeView/components/StrandCheckboxes.d.ts +3 -2
- package/esm/LinearGenomeView/components/StrandCheckboxes.js +4 -2
- package/esm/LinearGenomeView/components/TrackContainer.js +1 -1
- package/esm/LinearGenomeView/components/TrackLabel.js +4 -1
- package/esm/LinearGenomeView/components/TrackLabelMenu.js +8 -12
- package/esm/LinearGenomeView/components/useRangeSelect.js +3 -2
- package/esm/LinearGenomeView/components/useSearchBoxPrefs.d.ts +3 -1
- package/esm/LinearGenomeView/components/useSearchBoxPrefs.js +4 -2
- package/esm/LinearGenomeView/components/useSideScroll.js +11 -12
- package/esm/LinearGenomeView/lazyDialogs.d.ts +17 -2
- package/esm/LinearGenomeView/menuItems.js +32 -24
- package/esm/LinearGenomeView/model.d.ts +121 -65
- package/esm/LinearGenomeView/model.js +143 -135
- package/esm/LinearGenomeView/rExport/exportLinearView.d.ts +13 -0
- package/esm/LinearGenomeView/rExport/exportLinearView.js +98 -0
- package/esm/LinearGenomeView/rExport/rScript.d.ts +75 -0
- package/esm/LinearGenomeView/rExport/rScript.js +464 -0
- package/esm/LinearGenomeView/svgcomponents/SVGLinearGenomeView.js +4 -7
- package/esm/LinearGenomeView/svgcomponents/SVGStackedRow.d.ts +1 -2
- package/esm/LinearGenomeView/svgcomponents/SVGStackedRow.js +2 -2
- package/esm/LinearGenomeView/svgcomponents/SVGTrackLabel.d.ts +1 -2
- package/esm/LinearGenomeView/svgcomponents/SVGTrackLabel.js +5 -10
- package/esm/LinearGenomeView/svgcomponents/SVGTracks.d.ts +1 -2
- package/esm/LinearGenomeView/svgcomponents/SVGTracks.js +5 -5
- package/esm/LinearGenomeView/svgcomponents/SVGView.d.ts +1 -2
- package/esm/LinearGenomeView/svgcomponents/SVGView.js +3 -3
- package/esm/LinearGenomeView/svgcomponents/renderViewTracks.d.ts +24 -21
- package/esm/LinearGenomeView/svgcomponents/renderViewTracks.js +42 -29
- package/esm/LinearGenomeView/svgcomponents/useViewSvgFigure.js +32 -17
- package/esm/LinearGenomeView/svgcomponents/util.d.ts +16 -13
- package/esm/LinearGenomeView/svgcomponents/util.js +10 -19
- package/esm/LinearGenomeView/util.d.ts +42 -0
- package/esm/LinearGenomeView/util.js +81 -0
- package/esm/MultiLevelRubberband/MultiLevelRubberband.js +2 -2
- package/esm/displayKitTests/perRegionTestEnv.d.ts +45 -12
- package/esm/index.d.ts +4 -2
- package/esm/index.js +3 -1
- package/esm/searchUtils.js +1 -0
- package/esm/shared/RubberbandSpan.d.ts +3 -2
- package/esm/shared/RubberbandSpan.js +4 -2
- package/esm/shared/coordLabels.d.ts +6 -6
- package/esm/shared/coordLabels.js +7 -6
- package/package.json +10 -10
|
@@ -0,0 +1,464 @@
|
|
|
1
|
+
import { expandAssemblySequence, expandSidecarShorthand, refNameAliasAdapterType, } from '@jbrowse/core/assemblyManager/expandAssemblyConfigShorthand';
|
|
2
|
+
import { resolveUri } from '@jbrowse/core/util';
|
|
3
|
+
/**
|
|
4
|
+
* Each display ggjbrowse reads, by the reader that reads it, and the adapters
|
|
5
|
+
* it opens (the slot holding the file and the extension the reader knows it
|
|
6
|
+
* by).
|
|
7
|
+
*/
|
|
8
|
+
const READERS = {
|
|
9
|
+
LinearBasicDisplay: {
|
|
10
|
+
call: 'jbrowse_features',
|
|
11
|
+
adapters: {
|
|
12
|
+
Gff3TabixAdapter: { slot: 'gffGzLocation', ext: /\.gff3?\.gz$/ },
|
|
13
|
+
Gff3Adapter: { slot: 'gffLocation', ext: /\.gff3?$/ },
|
|
14
|
+
GtfAdapter: { slot: 'gtfLocation', ext: /\.gtf$/ },
|
|
15
|
+
BedTabixAdapter: { slot: 'bedGzLocation', ext: /\.bed\.gz$/ },
|
|
16
|
+
BedAdapter: { slot: 'bedLocation', ext: /\.bed$/ },
|
|
17
|
+
BigBedAdapter: { slot: 'bigBedLocation', ext: /\.(bb|bigbed)$/ },
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
LinearVariantDisplay: {
|
|
21
|
+
call: 'jbrowse_features',
|
|
22
|
+
adapters: {
|
|
23
|
+
VcfTabixAdapter: { slot: 'vcfGzLocation', ext: /\.vcf\.gz$/ },
|
|
24
|
+
VcfAdapter: { slot: 'vcfLocation', ext: /\.vcf$/ },
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
LinearMultiSampleVariantDisplay: {
|
|
28
|
+
call: 'jbrowse_variants',
|
|
29
|
+
adapters: {
|
|
30
|
+
VcfTabixAdapter: { slot: 'vcfGzLocation', ext: /\.vcf\.gz$/ },
|
|
31
|
+
VcfAdapter: { slot: 'vcfLocation', ext: /\.vcf$/ },
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
LinearWiggleDisplay: {
|
|
35
|
+
call: 'jbrowse_wiggle',
|
|
36
|
+
adapters: {
|
|
37
|
+
BigWigAdapter: { slot: 'bigWigLocation', ext: /$/ },
|
|
38
|
+
MultiWiggleAdapter: {},
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
LinearAlignmentsDisplay: {
|
|
42
|
+
call: 'jbrowse_alignments',
|
|
43
|
+
adapters: {
|
|
44
|
+
BamAdapter: { slot: 'bamLocation', ext: /(?<!\.cram)$/ },
|
|
45
|
+
CramAdapter: { slot: 'cramLocation', ext: /\.cram$/ },
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
/** The ggjbrowse release whose readers and `jbrowse_stack` the script calls. */
|
|
50
|
+
export const GGJBROWSE_VERSION = '0.10.0';
|
|
51
|
+
const INSTALL = 'remotes::install_github("GMOD/ggjbrowse")';
|
|
52
|
+
function isSet(value) {
|
|
53
|
+
return !(value === undefined ||
|
|
54
|
+
value === null ||
|
|
55
|
+
value === '' ||
|
|
56
|
+
(Array.isArray(value) && value.length === 0) ||
|
|
57
|
+
(typeof value === 'object' &&
|
|
58
|
+
!Array.isArray(value) &&
|
|
59
|
+
Object.keys(value).length === 0));
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* A resolved config writes an unset slot as `''`, `[]` or `{}`, which a reader
|
|
63
|
+
* would take for a setting. Keeps `false` and `0`, which are values.
|
|
64
|
+
*/
|
|
65
|
+
export function withoutUnset(value) {
|
|
66
|
+
if (Array.isArray(value)) {
|
|
67
|
+
return value.map(withoutUnset);
|
|
68
|
+
}
|
|
69
|
+
if (value && typeof value === 'object') {
|
|
70
|
+
return Object.fromEntries(Object.entries(value)
|
|
71
|
+
.map(([k, v]) => [k, withoutUnset(v)])
|
|
72
|
+
.filter(([, v]) => isSet(v)));
|
|
73
|
+
}
|
|
74
|
+
return value;
|
|
75
|
+
}
|
|
76
|
+
export function rStr(s) {
|
|
77
|
+
return JSON.stringify(s);
|
|
78
|
+
}
|
|
79
|
+
const RESERVED = new Set([
|
|
80
|
+
'if',
|
|
81
|
+
'else',
|
|
82
|
+
'repeat',
|
|
83
|
+
'while',
|
|
84
|
+
'function',
|
|
85
|
+
'for',
|
|
86
|
+
'next',
|
|
87
|
+
'break',
|
|
88
|
+
'in',
|
|
89
|
+
'TRUE',
|
|
90
|
+
'FALSE',
|
|
91
|
+
'NULL',
|
|
92
|
+
'Inf',
|
|
93
|
+
'NaN',
|
|
94
|
+
'NA',
|
|
95
|
+
]);
|
|
96
|
+
function isIdentifier(s) {
|
|
97
|
+
return /^[A-Za-z.][A-Za-z0-9._]*$/.test(s) && !RESERVED.has(s);
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* A config value as the R literal ggjbrowse hands the engine: a `list` per
|
|
101
|
+
* object, `c()` per vector of scalars, `list()` per vector of objects and per
|
|
102
|
+
* empty vector, which jsonlite would otherwise write as `{}`.
|
|
103
|
+
*/
|
|
104
|
+
export function rValue(value) {
|
|
105
|
+
if (value === null || value === undefined) {
|
|
106
|
+
return 'NULL';
|
|
107
|
+
}
|
|
108
|
+
if (typeof value === 'string') {
|
|
109
|
+
return rStr(value);
|
|
110
|
+
}
|
|
111
|
+
if (typeof value === 'number' || typeof value === 'boolean') {
|
|
112
|
+
return typeof value === 'number' ? String(value) : value ? 'TRUE' : 'FALSE';
|
|
113
|
+
}
|
|
114
|
+
if (Array.isArray(value)) {
|
|
115
|
+
const items = value.map(rValue).join(', ');
|
|
116
|
+
return value.length && value.every(v => typeof v !== 'object')
|
|
117
|
+
? `c(${items})`
|
|
118
|
+
: `list(${items})`;
|
|
119
|
+
}
|
|
120
|
+
return `list(${Object.entries(value)
|
|
121
|
+
.map(([k, v]) => `${isIdentifier(k) ? k : rStr(k)} = ${rValue(v)}`)
|
|
122
|
+
.join(', ')})`;
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* The path or URL a JBrowse file location points at, as R opens it: a `uri`
|
|
126
|
+
* resolved against its `baseUri` as the browser's fetch resolved it, a
|
|
127
|
+
* `file://` URL as its path, or a `localPath`.
|
|
128
|
+
*/
|
|
129
|
+
export function locationPath(location) {
|
|
130
|
+
const loc = location;
|
|
131
|
+
if (!loc?.uri) {
|
|
132
|
+
return loc?.localPath;
|
|
133
|
+
}
|
|
134
|
+
const resolved = resolveUri({ uri: loc.uri, baseUri: loc.baseUri });
|
|
135
|
+
return resolved.startsWith('file://')
|
|
136
|
+
? decodeURIComponent(new URL(resolved).pathname)
|
|
137
|
+
: resolved;
|
|
138
|
+
}
|
|
139
|
+
function isLocation(v) {
|
|
140
|
+
return (typeof v === 'object' &&
|
|
141
|
+
v !== null &&
|
|
142
|
+
!Array.isArray(v) &&
|
|
143
|
+
(typeof v.uri === 'string' ||
|
|
144
|
+
typeof v.localPath === 'string'));
|
|
145
|
+
}
|
|
146
|
+
/** An adapter config with each location a path or URL R opens, each path also pushed onto `uris`. */
|
|
147
|
+
function openable(value, uris) {
|
|
148
|
+
if (isLocation(value)) {
|
|
149
|
+
const uri = locationPath(value);
|
|
150
|
+
if (uri) {
|
|
151
|
+
uris.push(uri);
|
|
152
|
+
}
|
|
153
|
+
return { uri };
|
|
154
|
+
}
|
|
155
|
+
if (Array.isArray(value)) {
|
|
156
|
+
return value.map(v => openable(v, uris));
|
|
157
|
+
}
|
|
158
|
+
if (value && typeof value === 'object') {
|
|
159
|
+
return Object.fromEntries(Object.entries(value).map(([k, v]) => [k, openable(v, uris)]));
|
|
160
|
+
}
|
|
161
|
+
return value;
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* The file argument of a track's reader: its path where ggjbrowse would choose
|
|
165
|
+
* the track's adapter for it from the extension, and the adapter config itself
|
|
166
|
+
* where the adapter carries a setting or the name hides its format, so every
|
|
167
|
+
* setting reaches the engine as the browser has it.
|
|
168
|
+
*/
|
|
169
|
+
function fileArgument(adapters, adapter) {
|
|
170
|
+
const type = String(adapter.type);
|
|
171
|
+
const known = adapters[type];
|
|
172
|
+
if (!known) {
|
|
173
|
+
return { skipped: `${type} has no R reader for this display` };
|
|
174
|
+
}
|
|
175
|
+
const uris = [];
|
|
176
|
+
const located = withoutUnset(openable(adapter, uris));
|
|
177
|
+
if (uris.length === 0) {
|
|
178
|
+
return { skipped: `${type} names no file` };
|
|
179
|
+
}
|
|
180
|
+
if (uris.some(u => u.startsWith('blob:'))) {
|
|
181
|
+
return {
|
|
182
|
+
skipped: 'it reads a file opened in the browser, which R cannot reach',
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
const file = known.slot
|
|
186
|
+
? (locationPath(adapter[known.slot]) ??
|
|
187
|
+
(typeof adapter.uri === 'string' ? adapter.uri : undefined))
|
|
188
|
+
: undefined;
|
|
189
|
+
const index = adapter.index;
|
|
190
|
+
const indexPath = locationPath(index?.location) ?? locationPath(adapter.craiLocation);
|
|
191
|
+
const besideSuffix = type === 'CramAdapter' ? '.crai' : type === 'BamAdapter' ? '.bai' : '.tbi';
|
|
192
|
+
const plainSlots = new Set([
|
|
193
|
+
'type',
|
|
194
|
+
'uri',
|
|
195
|
+
'index',
|
|
196
|
+
'craiLocation',
|
|
197
|
+
known.slot,
|
|
198
|
+
]);
|
|
199
|
+
const plain = file !== undefined &&
|
|
200
|
+
known.ext?.test(file.replace(/\?.*$/, '').toLowerCase()) &&
|
|
201
|
+
Object.keys(located).every(k => plainSlots.has(k)) &&
|
|
202
|
+
Object.keys(index ?? {}).every(k => k === 'location' || k === 'indexType') &&
|
|
203
|
+
(index?.indexType === 'CSI') === !!indexPath?.endsWith('.csi');
|
|
204
|
+
return plain
|
|
205
|
+
? {
|
|
206
|
+
file: rStr(file),
|
|
207
|
+
index: indexPath && indexPath !== `${file}${besideSuffix}`
|
|
208
|
+
? indexPath
|
|
209
|
+
: undefined,
|
|
210
|
+
uris,
|
|
211
|
+
}
|
|
212
|
+
: { file: rValue(located), uris };
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Each sequence adapter `jbrowse_assembly()` builds from a path, by the
|
|
216
|
+
* extension ggjbrowse reads it off, and the index each takes beside it.
|
|
217
|
+
*/
|
|
218
|
+
const SEQUENCE_ADAPTERS = {
|
|
219
|
+
TwoBitAdapter: { slot: 'twoBitLocation', ext: /\.2bit$/, beside: {} },
|
|
220
|
+
BgzipFastaAdapter: {
|
|
221
|
+
slot: 'fastaLocation',
|
|
222
|
+
ext: /\.gz$/,
|
|
223
|
+
beside: { faiLocation: '.fai', gziLocation: '.gzi' },
|
|
224
|
+
},
|
|
225
|
+
IndexedFastaAdapter: {
|
|
226
|
+
slot: 'fastaLocation',
|
|
227
|
+
ext: /^(?!.*\.(2bit|gz)$)/,
|
|
228
|
+
beside: { faiLocation: '.fai' },
|
|
229
|
+
},
|
|
230
|
+
};
|
|
231
|
+
/** The path of a sequence adapter `jbrowse_assembly()` rebuilds from that path alone. */
|
|
232
|
+
function plainSequence(adapter) {
|
|
233
|
+
const { type, uri, baseUri, ...slots } = adapter;
|
|
234
|
+
if (type === undefined && typeof uri === 'string') {
|
|
235
|
+
return Object.keys(slots).length
|
|
236
|
+
? undefined
|
|
237
|
+
: locationPath({ uri, baseUri });
|
|
238
|
+
}
|
|
239
|
+
const known = SEQUENCE_ADAPTERS[String(type)];
|
|
240
|
+
const file = known && uri === undefined && locationPath(slots[known.slot]);
|
|
241
|
+
return file &&
|
|
242
|
+
known.ext.test(file.replace(/\?.*$/, '').toLowerCase()) &&
|
|
243
|
+
Object.entries(slots).every(([k, v]) => k === known.slot ||
|
|
244
|
+
(known.beside[k] !== undefined &&
|
|
245
|
+
locationPath(v) === `${file}${known.beside[k]}`))
|
|
246
|
+
? file
|
|
247
|
+
: undefined;
|
|
248
|
+
}
|
|
249
|
+
/** The path of an alias adapter ggjbrowse reads as that path alone. */
|
|
250
|
+
function plainAliases(adapter) {
|
|
251
|
+
const { type, uri, baseUri, location, ...rest } = adapter;
|
|
252
|
+
const file = typeof uri === 'string'
|
|
253
|
+
? locationPath({ uri, baseUri })
|
|
254
|
+
: locationPath(location);
|
|
255
|
+
return file &&
|
|
256
|
+
Object.keys(rest).length === 0 &&
|
|
257
|
+
type === refNameAliasAdapterType(file)
|
|
258
|
+
? file
|
|
259
|
+
: undefined;
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* The view's assembly as the R value every reader takes: `jbrowse_assembly()`
|
|
263
|
+
* of its sequence and alias files where ggjbrowse builds the same config from
|
|
264
|
+
* their paths, and otherwise the config itself, its locations paths R opens.
|
|
265
|
+
*/
|
|
266
|
+
export function assemblyArgument(snapshot) {
|
|
267
|
+
const config = expandAssemblySequence(snapshot);
|
|
268
|
+
const name = String(config.name);
|
|
269
|
+
const sequence = withoutUnset(config.sequence?.adapter);
|
|
270
|
+
if (!sequence) {
|
|
271
|
+
return { skipped: 'the assembly names no sequence' };
|
|
272
|
+
}
|
|
273
|
+
const aliasConfig = config.refNameAliases;
|
|
274
|
+
const aliases = aliasConfig
|
|
275
|
+
? withoutUnset(expandSidecarShorthand(aliasConfig, refNameAliasAdapterType).adapter)
|
|
276
|
+
: undefined;
|
|
277
|
+
const uris = [];
|
|
278
|
+
const located = {
|
|
279
|
+
name,
|
|
280
|
+
sequence: { adapter: openable(sequence, uris) },
|
|
281
|
+
...(aliases
|
|
282
|
+
? { refNameAliases: { adapter: openable(aliases, uris) } }
|
|
283
|
+
: {}),
|
|
284
|
+
};
|
|
285
|
+
if (uris.some(u => u.startsWith('blob:'))) {
|
|
286
|
+
return {
|
|
287
|
+
skipped: 'the assembly reads a file opened in the browser, which R cannot reach',
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
const sequenceFile = plainSequence(sequence);
|
|
291
|
+
const aliasFile = aliases && plainAliases(aliases);
|
|
292
|
+
return sequenceFile && (!aliases || aliasFile)
|
|
293
|
+
? {
|
|
294
|
+
value: `jbrowse_assembly(${[
|
|
295
|
+
rStr(sequenceFile),
|
|
296
|
+
...(aliasFile ? [rStr(aliasFile)] : []),
|
|
297
|
+
`name = ${rStr(name)}`,
|
|
298
|
+
].join(', ')})`,
|
|
299
|
+
}
|
|
300
|
+
: { value: rValue(located) };
|
|
301
|
+
}
|
|
302
|
+
/** A region as JBrowse's location box writes it, 1-based and inclusive. */
|
|
303
|
+
export function locusOf(refName, start, end) {
|
|
304
|
+
const n = (x) => Math.round(x).toLocaleString('en-US');
|
|
305
|
+
return `${refName}:${n(start + 1)}-${n(end)}`;
|
|
306
|
+
}
|
|
307
|
+
function lociLiteral(loci) {
|
|
308
|
+
return loci.length === 1
|
|
309
|
+
? rStr(loci[0])
|
|
310
|
+
: `c(${loci.map(l => rStr(l)).join(', ')})`;
|
|
311
|
+
}
|
|
312
|
+
/** An R-safe name per track id, unique within the script. */
|
|
313
|
+
function trackNames(tracks) {
|
|
314
|
+
const taken = new Set(['locus', 'width', 'assembly', 'fig']);
|
|
315
|
+
return tracks.map(track => {
|
|
316
|
+
let base = track.trackId
|
|
317
|
+
.replaceAll(/[^a-zA-Z0-9]/g, '_')
|
|
318
|
+
.replace(/^(?=[\d_])/, 'x');
|
|
319
|
+
if (!isIdentifier(base)) {
|
|
320
|
+
base = `${base}_track`;
|
|
321
|
+
}
|
|
322
|
+
let name = base;
|
|
323
|
+
for (let n = 2; taken.has(name); n++) {
|
|
324
|
+
name = `${base}_${n}`;
|
|
325
|
+
}
|
|
326
|
+
taken.add(name);
|
|
327
|
+
return name;
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
/** What the figure cannot show of a track whose settings ggjbrowse reads. */
|
|
331
|
+
function settingNotes(track) {
|
|
332
|
+
const color = track.display.color;
|
|
333
|
+
return [
|
|
334
|
+
...(color?.field === 'mateRefName'
|
|
335
|
+
? [
|
|
336
|
+
'color: mateRefName colors each contig by its hash, where the browser orders them by the assembly',
|
|
337
|
+
]
|
|
338
|
+
: []),
|
|
339
|
+
];
|
|
340
|
+
}
|
|
341
|
+
/**
|
|
342
|
+
* One R script for a view's tracks over its regions: a ggjbrowse reader per
|
|
343
|
+
* track, reading each as its display does, then `jbrowse_stack()` drawing
|
|
344
|
+
* them as the view stacks them. A track ggjbrowse cannot read lands in the
|
|
345
|
+
* header instead of stopping the figure: a script that quietly differs from
|
|
346
|
+
* its view says so.
|
|
347
|
+
*/
|
|
348
|
+
export function exportTracksToR({ tracks, regions, assembly: assemblySnapshot, bpPerPx, out = 'figure.png', notes: viewNotes = [], }) {
|
|
349
|
+
const notes = [...viewNotes];
|
|
350
|
+
const assembly = assemblySnapshot && assemblyArgument(assemblySnapshot);
|
|
351
|
+
if (assembly && 'skipped' in assembly) {
|
|
352
|
+
notes.push(`assembly: ${assembly.skipped}`);
|
|
353
|
+
}
|
|
354
|
+
const assemblyValue = assembly && 'value' in assembly ? assembly.value : undefined;
|
|
355
|
+
const loci = regions.map(r => locusOf(r.refName, r.start, r.end));
|
|
356
|
+
const totalBp = regions.reduce((sum, r) => sum + r.end - r.start, 0);
|
|
357
|
+
const viewPx = bpPerPx ? Math.round(totalBp / bpPerPx) : undefined;
|
|
358
|
+
const widthReaders = [];
|
|
359
|
+
const names = trackNames(tracks);
|
|
360
|
+
const reads = [];
|
|
361
|
+
const drawn = [];
|
|
362
|
+
let alignments;
|
|
363
|
+
tracks.forEach((track, i) => {
|
|
364
|
+
const reader = READERS[track.displayType];
|
|
365
|
+
if (!reader) {
|
|
366
|
+
notes.push(`${track.trackName}: ${track.displayType} has no R reader in ggjbrowse yet`);
|
|
367
|
+
return;
|
|
368
|
+
}
|
|
369
|
+
const source = fileArgument(reader.adapters, track.adapter);
|
|
370
|
+
if ('skipped' in source) {
|
|
371
|
+
notes.push(`${track.trackName}: ${source.skipped}`);
|
|
372
|
+
return;
|
|
373
|
+
}
|
|
374
|
+
const { heightMode, height } = track;
|
|
375
|
+
const display = reader.call === 'jbrowse_features'
|
|
376
|
+
? {
|
|
377
|
+
...track.display,
|
|
378
|
+
...(heightMode === undefined ? {} : { heightMode }),
|
|
379
|
+
...(height === undefined ? {} : { height }),
|
|
380
|
+
}
|
|
381
|
+
: track.display;
|
|
382
|
+
const width = viewPx !== undefined;
|
|
383
|
+
const args = [
|
|
384
|
+
...(Object.keys(display).length ? [`display = ${rValue(display)}`] : []),
|
|
385
|
+
...(width ? ['width = width'] : []),
|
|
386
|
+
...(assemblyValue ? ['assembly = assembly'] : []),
|
|
387
|
+
...(source.index ? [`index = ${rStr(source.index)}`] : []),
|
|
388
|
+
];
|
|
389
|
+
const name = names[i];
|
|
390
|
+
if (width) {
|
|
391
|
+
widthReaders.push(name);
|
|
392
|
+
}
|
|
393
|
+
reads.push([
|
|
394
|
+
`# ${track.trackName}`,
|
|
395
|
+
`${name} <- ${reader.call}(${source.file},`,
|
|
396
|
+
` ${['locus', ...args].join(',\n ')})`,
|
|
397
|
+
].join('\n'));
|
|
398
|
+
drawn.push(name);
|
|
399
|
+
if (reader.call === 'jbrowse_alignments') {
|
|
400
|
+
alignments ??= name;
|
|
401
|
+
}
|
|
402
|
+
notes.push(...[
|
|
403
|
+
...(source.uris.some(u => u.includes('://') && u.includes('?'))
|
|
404
|
+
? [
|
|
405
|
+
'source: a URL carries its query string, credential included, so the script is not one to share',
|
|
406
|
+
]
|
|
407
|
+
: []),
|
|
408
|
+
...settingNotes(track),
|
|
409
|
+
].map(n => `${track.trackName}: ${n}`));
|
|
410
|
+
});
|
|
411
|
+
if (drawn.length === 0) {
|
|
412
|
+
return { drawn: 0, script: undefined, notes };
|
|
413
|
+
}
|
|
414
|
+
const script = [
|
|
415
|
+
"# Generated by JBrowse 2: the view's tracks, read by ggjbrowse, JBrowse's",
|
|
416
|
+
'# own engine in R, and drawn as the view stacks them.',
|
|
417
|
+
`# Install ggjbrowse with ${INSTALL}`,
|
|
418
|
+
'#',
|
|
419
|
+
'# Each reader returns the tables its track draws; jbrowse_stack() draws',
|
|
420
|
+
'# them. Restyle every band with &, or draw one alone and add to it:',
|
|
421
|
+
'# fig & ggplot2::theme_classic()',
|
|
422
|
+
`# ${alignments ? `autoplot(${alignments}, "pileup")` : `autoplot(${drawn[0]})`} + ggplot2::theme_classic()`,
|
|
423
|
+
...(notes.length
|
|
424
|
+
? [
|
|
425
|
+
'#',
|
|
426
|
+
'# What this figure does not show:',
|
|
427
|
+
...notes.map(n => `# - ${n}`),
|
|
428
|
+
]
|
|
429
|
+
: []),
|
|
430
|
+
'',
|
|
431
|
+
'if (',
|
|
432
|
+
' !requireNamespace("ggjbrowse", quietly = TRUE) ||',
|
|
433
|
+
` packageVersion("ggjbrowse") < ${rStr(GGJBROWSE_VERSION)}`,
|
|
434
|
+
') {',
|
|
435
|
+
` stop("this script needs ggjbrowse ${GGJBROWSE_VERSION} or later: ${INSTALL.replaceAll('"', '\\"')}")`,
|
|
436
|
+
'}',
|
|
437
|
+
'library(ggjbrowse)',
|
|
438
|
+
'',
|
|
439
|
+
`locus <- ${lociLiteral(loci)}`,
|
|
440
|
+
...(assemblyValue
|
|
441
|
+
? [
|
|
442
|
+
"# The view's assembly: the reference every reader reads against, and",
|
|
443
|
+
'# the aliases that name each contig as each file spells it.',
|
|
444
|
+
`assembly <- ${assemblyValue}`,
|
|
445
|
+
]
|
|
446
|
+
: []),
|
|
447
|
+
...(widthReaders.length
|
|
448
|
+
? [
|
|
449
|
+
"# The view's pixels across its regions. A smaller number lays the tracks",
|
|
450
|
+
'# out as a narrower browser window would, for a narrower figure with',
|
|
451
|
+
'# fewer labels at the same size.',
|
|
452
|
+
`width <- ${viewPx}`,
|
|
453
|
+
]
|
|
454
|
+
: []),
|
|
455
|
+
'',
|
|
456
|
+
reads.join('\n\n'),
|
|
457
|
+
'',
|
|
458
|
+
`fig <- jbrowse_stack(${drawn.join(', ')})`,
|
|
459
|
+
'print(fig)',
|
|
460
|
+
`ggplot2::ggsave(${rStr(out)}, fig, width = jbrowse_width(fig), height = jbrowse_height(fig))`,
|
|
461
|
+
'',
|
|
462
|
+
].join('\n');
|
|
463
|
+
return { drawn: drawn.length, script, notes };
|
|
464
|
+
}
|
|
@@ -27,20 +27,17 @@ export async function renderToSvg(model, opts) {
|
|
|
27
27
|
textHeight = defaultTextHeight(fontSize), rulerHeight = 34, trackLabels = 'offset', themeName, fontFamily, showGridlines = false, Wrapper, } = opts;
|
|
28
28
|
const session = getSession(model);
|
|
29
29
|
const theme = session.getActiveThemeOptions?.(themeName);
|
|
30
|
-
// owns the
|
|
31
|
-
//
|
|
30
|
+
// owns the ordering this used to spell out by hand: the stack's height and
|
|
31
|
+
// legend gutter measured after the awaits. Every display's `renderSvg` owns its
|
|
32
32
|
// own readiness wait — an LGV display through `renderDisplaySvg`'s
|
|
33
33
|
// `awaitSvgReady`, a non-LGV one (dotplot, synteny, circular) by calling that
|
|
34
34
|
// itself.
|
|
35
35
|
const { tracks, displayResults, tracksHeight, legendWidth, skippedTracks } = await renderViewTracks({
|
|
36
36
|
view: model,
|
|
37
|
-
opts,
|
|
37
|
+
opts: { ...opts, fontSize },
|
|
38
38
|
theme,
|
|
39
39
|
textHeight,
|
|
40
40
|
trackLabels,
|
|
41
|
-
// the standalone export is the one with room to give: it widens its
|
|
42
|
-
// canvas below so a legend sits beside the plot rather than over it
|
|
43
|
-
reserveLegendWidth: true,
|
|
44
41
|
});
|
|
45
42
|
notifySkippedSvgTracks(session, skippedTracks);
|
|
46
43
|
// read after the displays' waits, and handed to SVGHeader rather than read
|
|
@@ -72,6 +69,6 @@ export async function renderToSvg(model, opts) {
|
|
|
72
69
|
// header is the full one: cytoband overview and total-bp scalebar as
|
|
73
70
|
// well as the assembly name and ruler. `tracksTop` is the height
|
|
74
71
|
// getHeaderLayout gave it.
|
|
75
|
-
header: _jsx(SVGHeader, { model: model, fontSize: fontSize, rulerHeight: rulerHeight, layout: headerLayout, showCytobands: effectiveShowCytobands }), fontSize: fontSize, textHeight: textHeight, trackLabels: trackLabels, trackLabelOffset: trackLabelOffset, contentTop: tracksTop, tracksHeight: tracksHeight, showGridlines: showGridlines, leftBuffer: exportMargin
|
|
72
|
+
header: _jsx(SVGHeader, { model: model, fontSize: fontSize, rulerHeight: rulerHeight, layout: headerLayout, showCytobands: effectiveShowCytobands }), fontSize: fontSize, textHeight: textHeight, trackLabels: trackLabels, trackLabelOffset: trackLabelOffset, contentTop: tracksTop, tracksHeight: tracksHeight, showGridlines: showGridlines, leftBuffer: exportMargin }) })),
|
|
76
73
|
});
|
|
77
74
|
}
|
|
@@ -7,7 +7,7 @@ import type { TrackLabelMode } from '@jbrowse/display-kit/types';
|
|
|
7
7
|
* reserves `getRowHeaderLayout().bandHeight` above it for the header to draw
|
|
8
8
|
* into, and the row is `rulerHeight + rendered.tracksHeight` tall below it.
|
|
9
9
|
*/
|
|
10
|
-
export default function SVGStackedRow({ view, rendered, top, margin, fontSize, textHeight, rulerHeight, trackLabels, trackLabelOffset, showGridlines,
|
|
10
|
+
export default function SVGStackedRow({ view, rendered, top, margin, fontSize, textHeight, rulerHeight, trackLabels, trackLabelOffset, showGridlines, showAssemblyName, showScalebar, }: {
|
|
11
11
|
view: LinearGenomeViewModel;
|
|
12
12
|
rendered: {
|
|
13
13
|
displayResults: SvgDisplayResult[];
|
|
@@ -21,7 +21,6 @@ export default function SVGStackedRow({ view, rendered, top, margin, fontSize, t
|
|
|
21
21
|
trackLabels: TrackLabelMode;
|
|
22
22
|
trackLabelOffset: number;
|
|
23
23
|
showGridlines: boolean;
|
|
24
|
-
legendWidth?: number;
|
|
25
24
|
showAssemblyName?: boolean;
|
|
26
25
|
showScalebar?: boolean;
|
|
27
26
|
}): import("react").JSX.Element;
|
|
@@ -7,6 +7,6 @@ import SVGView from './SVGView.js';
|
|
|
7
7
|
* reserves `getRowHeaderLayout().bandHeight` above it for the header to draw
|
|
8
8
|
* into, and the row is `rulerHeight + rendered.tracksHeight` tall below it.
|
|
9
9
|
*/
|
|
10
|
-
export default function SVGStackedRow({ view, rendered, top, margin, fontSize, textHeight, rulerHeight, trackLabels, trackLabelOffset, showGridlines,
|
|
11
|
-
return (_jsx("g", { transform: `translate(${margin} ${top})`, children: _jsx(SVGView, { view: view, displayResults: rendered.displayResults, header: _jsx(SVGRowHeader, { view: view, fontSize: fontSize, rulerHeight: rulerHeight, showAssemblyName: showAssemblyName, showScalebar: showScalebar }), fontSize: fontSize, textHeight: textHeight, trackLabels: trackLabels, trackLabelOffset: trackLabelOffset, contentTop: rulerHeight, tracksHeight: rendered.tracksHeight, showGridlines: showGridlines, leftBuffer: margin
|
|
10
|
+
export default function SVGStackedRow({ view, rendered, top, margin, fontSize, textHeight, rulerHeight, trackLabels, trackLabelOffset, showGridlines, showAssemblyName, showScalebar, }) {
|
|
11
|
+
return (_jsx("g", { transform: `translate(${margin} ${top})`, children: _jsx(SVGView, { view: view, displayResults: rendered.displayResults, header: _jsx(SVGRowHeader, { view: view, fontSize: fontSize, rulerHeight: rulerHeight, showAssemblyName: showAssemblyName, showScalebar: showScalebar }), fontSize: fontSize, textHeight: textHeight, trackLabels: trackLabels, trackLabelOffset: trackLabelOffset, contentTop: rulerHeight, tracksHeight: rendered.tracksHeight, showGridlines: showGridlines, leftBuffer: margin }) }));
|
|
12
12
|
}
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import type { TrackLabelMode } from '../types.ts';
|
|
2
|
-
export default function SVGTrackLabel({ trackLabels, trackName, fontSize, textHeight, trackLabelOffset, x,
|
|
2
|
+
export default function SVGTrackLabel({ trackLabels, trackName, fontSize, textHeight, trackLabelOffset, x, }: {
|
|
3
3
|
trackName: string;
|
|
4
4
|
trackLabels: TrackLabelMode;
|
|
5
5
|
fontSize: number;
|
|
6
6
|
textHeight: number;
|
|
7
7
|
trackLabelOffset: number;
|
|
8
8
|
x: number;
|
|
9
|
-
raised?: boolean;
|
|
10
9
|
}): import("react").JSX.Element | null;
|
|
@@ -4,24 +4,20 @@ import { stripAlpha } from '@jbrowse/core/util';
|
|
|
4
4
|
import { TRACK_LABEL_GAP, insetLabelBaselineY, offsetLabelBaselineY, } from './util.js';
|
|
5
5
|
// Where the label sits, per mode. 'left' right-aligns in the gutter
|
|
6
6
|
// trackLabelLeftOffset reserved (hence the shared TRACK_LABEL_GAP), beside the
|
|
7
|
-
// first row
|
|
8
|
-
// gutter beside its rows. The other modes hang off the leftmost visible content
|
|
7
|
+
// first row. The other modes hang off the leftmost visible content
|
|
9
8
|
// at `x`, either just above the track body ('offset') or inset over it
|
|
10
9
|
// ('overlapping').
|
|
11
10
|
//
|
|
12
11
|
// Every mode is placed on its alphabetic baseline (the SVG default, so no
|
|
13
|
-
// dominantBaseline is emitted at all): 'offset'
|
|
14
|
-
// descenders land a known distance above the track body — the band is
|
|
12
|
+
// dominantBaseline is emitted at all): 'offset' so its descenders land a known distance above the track body — the band is
|
|
15
13
|
// `textHeight` tall, and offsetLabelBaselineY owns that arithmetic — and
|
|
16
14
|
// 'left'/'overlapping' so their ascenders stay inside the track box they align
|
|
17
15
|
// with.
|
|
18
|
-
function labelPosition({ trackLabels, trackLabelOffset, textHeight, fontSize, x,
|
|
16
|
+
function labelPosition({ trackLabels, trackLabelOffset, textHeight, fontSize, x, }) {
|
|
19
17
|
if (trackLabels === 'left') {
|
|
20
18
|
return {
|
|
21
19
|
x: trackLabelOffset - TRACK_LABEL_GAP,
|
|
22
|
-
y:
|
|
23
|
-
? offsetLabelBaselineY(textHeight, fontSize)
|
|
24
|
-
: insetLabelBaselineY(fontSize),
|
|
20
|
+
y: insetLabelBaselineY(fontSize),
|
|
25
21
|
textAnchor: 'end',
|
|
26
22
|
};
|
|
27
23
|
}
|
|
@@ -34,7 +30,7 @@ function labelPosition({ trackLabels, trackLabelOffset, textHeight, fontSize, x,
|
|
|
34
30
|
// stay inside it (at y=0 they rose into the track above)
|
|
35
31
|
return { x: x + 5, y: insetLabelBaselineY(fontSize) };
|
|
36
32
|
}
|
|
37
|
-
export default function SVGTrackLabel({ trackLabels, trackName, fontSize, textHeight, trackLabelOffset, x,
|
|
33
|
+
export default function SVGTrackLabel({ trackLabels, trackName, fontSize, textHeight, trackLabelOffset, x, }) {
|
|
38
34
|
const palette = usePalette();
|
|
39
35
|
if (trackLabels === 'hidden') {
|
|
40
36
|
return null;
|
|
@@ -45,7 +41,6 @@ export default function SVGTrackLabel({ trackLabels, trackName, fontSize, textHe
|
|
|
45
41
|
textHeight,
|
|
46
42
|
fontSize,
|
|
47
43
|
x,
|
|
48
|
-
raised,
|
|
49
44
|
});
|
|
50
45
|
// 'overlapping' prints over the track's own data, which the on-screen label
|
|
51
46
|
// clears with a paper box, so it carries the halo text over a plot needs
|
|
@@ -2,7 +2,7 @@ import type { LinearGenomeViewModel } from '../index.ts';
|
|
|
2
2
|
import type { TrackLabelMode } from '../types.ts';
|
|
3
3
|
import type { SvgDisplayResult } from './util.ts';
|
|
4
4
|
type LGV = LinearGenomeViewModel;
|
|
5
|
-
export default function SVGTracks({ displayResults, model, textHeight, fontSize, trackLabels, trackLabelOffset, leftBuffer,
|
|
5
|
+
export default function SVGTracks({ displayResults, model, textHeight, fontSize, trackLabels, trackLabelOffset, leftBuffer, }: {
|
|
6
6
|
displayResults: SvgDisplayResult[];
|
|
7
7
|
model: LGV;
|
|
8
8
|
textHeight: number;
|
|
@@ -10,6 +10,5 @@ export default function SVGTracks({ displayResults, model, textHeight, fontSize,
|
|
|
10
10
|
trackLabels: TrackLabelMode;
|
|
11
11
|
trackLabelOffset: number;
|
|
12
12
|
leftBuffer: number;
|
|
13
|
-
legendWidth: number;
|
|
14
13
|
}): import("react").JSX.Element;
|
|
15
14
|
export {};
|
|
@@ -3,20 +3,20 @@ import { SvgClipRect } from '@jbrowse/core/svg/SvgExport';
|
|
|
3
3
|
import { svgNodeId } from '@jbrowse/core/svg/svgId';
|
|
4
4
|
import { svgTrackName } from '@jbrowse/core/svg/trackNames';
|
|
5
5
|
import { getSession } from '@jbrowse/core/util';
|
|
6
|
-
import {
|
|
6
|
+
import { SvgLegend } from '@jbrowse/display-kit/renderDisplaySvg';
|
|
7
7
|
import SVGRegionSeparators from './SVGRegionSeparators.js';
|
|
8
8
|
import SVGTrackLabel from './SVGTrackLabel.js';
|
|
9
|
-
import { labelOffset,
|
|
10
|
-
export default function SVGTracks({ displayResults, model, textHeight, fontSize, trackLabels, trackLabelOffset, leftBuffer,
|
|
9
|
+
import { labelOffset, trackBoxOffsets, trackLabelMode } from './util.js';
|
|
10
|
+
export default function SVGTracks({ displayResults, model, textHeight, fontSize, trackLabels, trackLabelOffset, leftBuffer, }) {
|
|
11
11
|
const session = getSession(model);
|
|
12
12
|
const x = Math.max(-model.offsetPx, 0);
|
|
13
13
|
const offsets = trackBoxOffsets(displayResults.map(r => r.track), trackLabels, textHeight);
|
|
14
|
-
return (_jsx(_Fragment, { children: displayResults.map(({ track, result }, i) => {
|
|
14
|
+
return (_jsx(_Fragment, { children: displayResults.map(({ track, result, legendHeight }, i) => {
|
|
15
15
|
const conf = track.configuration;
|
|
16
16
|
const trackName = svgTrackName(track, session);
|
|
17
17
|
const display = track.displays[0];
|
|
18
18
|
const currentOffset = offsets[i];
|
|
19
19
|
const textOffset = labelOffset(track, trackLabels, textHeight);
|
|
20
|
-
return (_jsxs("g", { transform: `translate(0 ${currentOffset})`, children: [_jsx(SvgClipRect, { id: `track-clip-${svgNodeId(model)}-${conf.trackId}`, x: -leftBuffer, y: textOffset, width: model.width + trackLabelOffset + leftBuffer
|
|
20
|
+
return (_jsxs("g", { transform: `translate(0 ${currentOffset})`, children: [_jsx(SvgClipRect, { id: `track-clip-${svgNodeId(model)}-${conf.trackId}`, x: -leftBuffer, y: textOffset, width: model.width + trackLabelOffset + leftBuffer, height: display.height, children: _jsxs("g", { transform: `translate(${trackLabelOffset} ${textOffset})`, children: [result, display.regionTooLarge ? null : (_jsx(SVGRegionSeparators, { model: model, height: display.height }))] }) }), legendHeight === undefined ? null : (_jsx("g", { transform: `translate(${trackLabelOffset} ${textOffset})`, children: _jsx(SvgLegend, { model: display, width: model.width, height: legendHeight }) })), _jsx(SVGTrackLabel, { trackName: trackName, fontSize: fontSize, textHeight: textHeight, trackLabels: trackLabelMode(track, trackLabels), trackLabelOffset: trackLabelOffset, x: x + trackLabelOffset })] }, conf.trackId));
|
|
21
21
|
}) }));
|
|
22
22
|
}
|
|
@@ -2,7 +2,7 @@ import type { LinearGenomeViewModel } from '../index.ts';
|
|
|
2
2
|
import type { TrackLabelMode } from '../types.ts';
|
|
3
3
|
import type { SvgDisplayResult } from './util.ts';
|
|
4
4
|
import type { ReactNode } from 'react';
|
|
5
|
-
export default function SVGView({ view, displayResults, header, fontSize, textHeight, trackLabels, trackLabelOffset, contentTop, tracksHeight, showGridlines, leftBuffer,
|
|
5
|
+
export default function SVGView({ view, displayResults, header, fontSize, textHeight, trackLabels, trackLabelOffset, contentTop, tracksHeight, showGridlines, leftBuffer, }: {
|
|
6
6
|
view: LinearGenomeViewModel;
|
|
7
7
|
displayResults: SvgDisplayResult[];
|
|
8
8
|
header: ReactNode;
|
|
@@ -14,5 +14,4 @@ export default function SVGView({ view, displayResults, header, fontSize, textHe
|
|
|
14
14
|
tracksHeight: number;
|
|
15
15
|
showGridlines: boolean;
|
|
16
16
|
leftBuffer: number;
|
|
17
|
-
legendWidth?: number;
|
|
18
17
|
}): import("react").JSX.Element;
|
|
@@ -7,7 +7,7 @@ import SVGTracks from './SVGTracks.js';
|
|
|
7
7
|
// Shared by all three exports that stack an LGV — the standalone one, and the
|
|
8
8
|
// linear-synteny and breakpoint-split rows — so their per-view layout can't
|
|
9
9
|
// drift. It did drift while the standalone export kept its own copy of these
|
|
10
|
-
// four groups
|
|
11
|
-
export default function SVGView({ view, displayResults, header, fontSize, textHeight, trackLabels, trackLabelOffset, contentTop, tracksHeight, showGridlines, leftBuffer,
|
|
12
|
-
return (_jsxs(_Fragment, { children: [_jsx("g", { transform: `translate(${trackLabelOffset})`, children: header }), showGridlines ? (_jsx("g", { transform: `translate(${trackLabelOffset} ${contentTop})`, children: _jsx(SVGGridlines, { model: view, height: tracksHeight }) })) : null, _jsx("g", { transform: `translate(0 ${contentTop})`, children: _jsx(SVGTracks, { textHeight: textHeight, trackLabels: trackLabels, fontSize: fontSize, model: view, displayResults: displayResults, trackLabelOffset: trackLabelOffset, leftBuffer: leftBuffer
|
|
10
|
+
// four groups.
|
|
11
|
+
export default function SVGView({ view, displayResults, header, fontSize, textHeight, trackLabels, trackLabelOffset, contentTop, tracksHeight, showGridlines, leftBuffer, }) {
|
|
12
|
+
return (_jsxs(_Fragment, { children: [_jsx("g", { transform: `translate(${trackLabelOffset})`, children: header }), showGridlines ? (_jsx("g", { transform: `translate(${trackLabelOffset} ${contentTop})`, children: _jsx(SVGGridlines, { model: view, height: tracksHeight }) })) : null, _jsx("g", { transform: `translate(0 ${contentTop})`, children: _jsx(SVGTracks, { textHeight: textHeight, trackLabels: trackLabels, fontSize: fontSize, model: view, displayResults: displayResults, trackLabelOffset: trackLabelOffset, leftBuffer: leftBuffer }) }), _jsx("g", { transform: `translate(${trackLabelOffset} ${contentTop})`, children: _jsx(SVGHighlightsOverlay, { model: view, tracksHeight: tracksHeight }) })] }));
|
|
13
13
|
}
|