higlass 1.13.4 → 1.13.6
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/README.md +48 -54
- package/app/globals.d.ts +1 -1
- package/app/missing-types.d.ts +4 -1
- package/app/scripts/AddTrackDialog.jsx +3 -3
- package/app/scripts/AddTrackPositionMenu.jsx +2 -2
- package/app/scripts/Annotations1dTrack.js +1 -1
- package/app/scripts/Annotations2dTrack.js +3 -5
- package/app/scripts/Autocomplete.jsx +14 -21
- package/app/scripts/AxisPixi.js +10 -12
- package/app/scripts/BarTrack.js +3 -3
- package/app/scripts/BedLikeTrack.js +12 -13
- package/app/scripts/Button.jsx +1 -1
- package/app/scripts/CNVIntervalTrack.js +1 -1
- package/app/scripts/CenterTrack.jsx +8 -7
- package/app/scripts/Chromosome2DAnnotations.js +1 -1
- package/app/scripts/Chromosome2DLabels.js +1 -1
- package/app/scripts/ChromosomeGrid.js +49 -38
- package/app/scripts/ChromosomeInfo.js +4 -2
- package/app/scripts/CombinedTrack.js +3 -1
- package/app/scripts/ConfigTrackMenu.jsx +1 -1
- package/app/scripts/ConfigViewMenu.jsx +2 -2
- package/app/scripts/ContextMenuContainer.jsx +1 -2
- package/app/scripts/ContextMenuItem.jsx +1 -0
- package/app/scripts/CrossRule.js +1 -1
- package/app/scripts/CustomTrackDialog.jsx +2 -2
- package/app/scripts/Dialog.jsx +2 -2
- package/app/scripts/DragListeningDiv.jsx +1 -1
- package/app/scripts/DraggableDiv.jsx +2 -3
- package/app/scripts/ExportLinkDialog.jsx +1 -1
- package/app/scripts/GalleryTracks.jsx +77 -78
- package/app/scripts/GenomePositionSearchBox.jsx +10 -9
- package/app/scripts/HeatmapOptions.jsx +8 -3
- package/app/scripts/HeatmapTiledPixiTrack.js +72 -53
- package/app/scripts/HiGlassComponent.jsx +75 -98
- package/app/scripts/Horizontal1dHeatmapTrack.js +1 -1
- package/app/scripts/Horizontal2DDomainsTrack.js +1 -1
- package/app/scripts/HorizontalChromosomeLabels.js +28 -22
- package/app/scripts/HorizontalGeneAnnotationsTrack.js +1 -1
- package/app/scripts/HorizontalHeatmapTrack.js +2 -2
- package/app/scripts/HorizontalMultivecTrack.js +6 -7
- package/app/scripts/HorizontalRule.js +1 -2
- package/app/scripts/HorizontalTiled1DPixiTrack.js +4 -4
- package/app/scripts/HorizontalTiledPlot.jsx +9 -9
- package/app/scripts/LeftTrackModifier.js +4 -0
- package/app/scripts/ListWrapper.jsx +1 -2
- package/app/scripts/MapboxTilesTrack.js +4 -4
- package/app/scripts/Modal.jsx +2 -2
- package/app/scripts/MoveableTrack.jsx +10 -12
- package/app/scripts/NestedContextMenu.jsx +2 -1
- package/app/scripts/OSMTileIdsTrack.js +1 -1
- package/app/scripts/OverlayTrack.js +4 -4
- package/app/scripts/PixiTrack.js +58 -17
- package/app/scripts/PlotTypeChooser.jsx +3 -4
- package/app/scripts/RasterTilesTrack.js +3 -2
- package/app/scripts/SearchField.js +5 -5
- package/app/scripts/SeriesListItems.jsx +3 -4
- package/app/scripts/SeriesListMenu.jsx +81 -11
- package/app/scripts/SeriesListSubmenuMixin.jsx +5 -1
- package/app/scripts/SketchInlinePicker.jsx +2 -2
- package/app/scripts/SortableList.jsx +1 -1
- package/app/scripts/Tiled1DPixiTrack.js +5 -1
- package/app/scripts/TiledPixiTrack.js +221 -76
- package/app/scripts/TiledPlot.jsx +35 -43
- package/app/scripts/TilesetFinder.jsx +12 -4
- package/app/scripts/Track.js +2 -2
- package/app/scripts/TrackArea.jsx +4 -0
- package/app/scripts/TrackControl.jsx +2 -2
- package/app/scripts/TrackRenderer.jsx +30 -31
- package/app/scripts/UnknownPixiTrack.js +1 -1
- package/app/scripts/ValueIntervalTrack.js +1 -1
- package/app/scripts/VerticalRule.js +2 -2
- package/app/scripts/VerticalTiledPlot.jsx +7 -7
- package/app/scripts/ViewConfigEditor.jsx +1 -1
- package/app/scripts/ViewContextMenu.jsx +4 -4
- package/app/scripts/ViewHeader.jsx +6 -7
- package/app/scripts/ViewportTracker2D.js +1 -1
- package/app/scripts/api.js +5 -6
- package/app/scripts/configs/available-track-types.js +1 -1
- package/app/scripts/configs/positions-by-datatype.js +2 -2
- package/app/scripts/configs/themes.js +0 -1
- package/app/scripts/configs/tracks-info-by-type.js +11 -8
- package/app/scripts/configs/tracks-info.js +2 -2
- package/app/scripts/d3-context-menu.js +3 -4
- package/app/scripts/data-fetchers/DataFetcher.js +107 -91
- package/app/scripts/data-fetchers/genbank-fetcher.js +6 -10
- package/app/scripts/data-fetchers/local-tile-fetcher.js +2 -6
- package/app/scripts/gosling-exports.js +29 -0
- package/app/scripts/hglib.jsx +3 -1
- package/app/scripts/hocs/with-modal.jsx +32 -10
- package/app/scripts/hocs/with-pub-sub.js +28 -0
- package/app/scripts/hocs/with-theme.jsx +21 -14
- package/app/scripts/icons.jsx +3 -2
- package/app/scripts/mixwith.js +2 -2
- package/app/scripts/plugins/get-data-fetcher.js +2 -3
- package/app/scripts/services/chrom-info.js +32 -4
- package/app/scripts/services/element-resize-listener.js +2 -2
- package/app/scripts/services/index.js +0 -1
- package/app/scripts/services/tile-proxy.js +370 -282
- package/app/scripts/services/worker.js +36 -34
- package/app/scripts/test-helpers/test-helpers.jsx +3 -3
- package/app/scripts/types.ts +73 -38
- package/app/scripts/utils/DenseDataExtrema1D.js +1 -1
- package/app/scripts/utils/DenseDataExtrema2D.js +2 -1
- package/app/scripts/utils/LruCache.js +3 -2
- package/app/scripts/utils/assert.js +19 -0
- package/app/scripts/utils/background-task-scheduler.js +2 -0
- package/app/scripts/utils/color-domain-to-rgba-array.js +13 -3
- package/app/scripts/utils/color-to-hex.js +1 -1
- package/app/scripts/utils/dict-items.js +1 -0
- package/app/scripts/utils/dict-keys.js +1 -0
- package/app/scripts/utils/dict-values.js +1 -0
- package/app/scripts/utils/expand-combined-tracks.js +11 -7
- package/app/scripts/utils/fake-pub-sub.js +12 -0
- package/app/scripts/utils/fill-in-min-widths.js +47 -21
- package/app/scripts/utils/flatten.js +0 -1
- package/app/scripts/utils/get-aggregation-function.js +1 -1
- package/app/scripts/utils/get-default-track-for-datatype.js +36 -10
- package/app/scripts/utils/get-higlass-components.js +27 -3
- package/app/scripts/utils/get-track-position-by-uid.js +8 -1
- package/app/scripts/utils/get-xylofon.js +12 -9
- package/app/scripts/utils/has-parent.js +5 -5
- package/app/scripts/utils/hex-string-to-int.js +1 -1
- package/app/scripts/utils/interval-tree.js +222 -177
- package/app/scripts/utils/load-chrom-infos.js +4 -1
- package/app/scripts/utils/pixi-text-to-svg.js +5 -9
- package/app/scripts/utils/range-query-2d.js +3 -3
- package/app/scripts/utils/reduce.js +12 -5
- package/app/scripts/utils/segments-to-rows.js +14 -11
- package/app/scripts/utils/show-mouse-position.js +17 -1
- package/app/scripts/utils/svg-line.js +7 -8
- package/app/scripts/utils/type-guards.js +16 -7
- package/app/scripts/utils/visit-positioned-tracks.js +7 -5
- package/app/styles/d3-context-menu.css +0 -1
- package/app/styles/prism.css +1 -0
- package/dist/hglib.js +85885 -85618
- package/dist/hglib.min.js +110 -109
- package/dist/higlass.mjs +86301 -86034
- package/package.json +13 -17
- package/app/scripts/hocs/with-pub-sub.jsx +0 -28
|
@@ -1,110 +1,104 @@
|
|
|
1
|
-
// @ts-nocheck
|
|
2
1
|
import { range } from 'd3-array';
|
|
3
2
|
import slugid from 'slugid';
|
|
4
3
|
|
|
5
|
-
import {
|
|
4
|
+
import { workerFetchTiles, workerSetPix } from './worker';
|
|
6
5
|
|
|
7
|
-
import
|
|
6
|
+
import sleep from '../utils/timeout';
|
|
7
|
+
import tts from '../utils/trim-trailing-slash';
|
|
8
|
+
import {
|
|
9
|
+
isLegacyTilesetInfo,
|
|
10
|
+
isResolutionsTilesetInfo,
|
|
11
|
+
} from '../utils/type-guards';
|
|
8
12
|
|
|
9
13
|
// Config
|
|
10
|
-
import { TILE_FETCH_DEBOUNCE } from '../configs';
|
|
14
|
+
import { TILE_FETCH_DEBOUNCE } from '../configs/primitives';
|
|
11
15
|
|
|
16
|
+
/** @import { PubSub } from 'pub-sub-es' */
|
|
17
|
+
/** @import { Scale, TilesetInfo, TilesRequest } from '../types' */
|
|
18
|
+
/** @import { CompletedTileData, TileResponse, SelectedRowsOptions } from './worker' */
|
|
19
|
+
|
|
20
|
+
/** @type {number} */
|
|
12
21
|
const MAX_FETCH_TILES = 15;
|
|
13
22
|
|
|
14
|
-
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
setPixPool.run(function(params, done) {
|
|
22
|
-
try {
|
|
23
|
-
const array = new Float32Array(params.data);
|
|
24
|
-
const pixData = worker.workerSetPix(
|
|
25
|
-
params.size,
|
|
26
|
-
array,
|
|
27
|
-
params.valueScaleType,
|
|
28
|
-
params.valueScaleDomain,
|
|
29
|
-
params.pseudocount,
|
|
30
|
-
params.colorScale,
|
|
31
|
-
);
|
|
23
|
+
/** @type {string} */
|
|
24
|
+
const sessionId = import.meta.env.DEV ? 'dev' : slugid.nice();
|
|
25
|
+
/** @type {number} */
|
|
26
|
+
export let requestsInFlight = 0;
|
|
27
|
+
/** @type {string | null} */
|
|
28
|
+
export let authHeader = null;
|
|
32
29
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
30
|
+
/**
|
|
31
|
+
* Iterator helper to chunk an array into smaller arrays of a fixed size.
|
|
32
|
+
*
|
|
33
|
+
* @template T
|
|
34
|
+
* @param {Iterable<T>} iterable
|
|
35
|
+
* @param {number} size
|
|
36
|
+
* @returns {Generator<Array<T>, void, unknown>}
|
|
37
|
+
*/
|
|
38
|
+
function* chunkIterable(iterable, size) {
|
|
39
|
+
let chunk = [];
|
|
40
|
+
for (const item of iterable) {
|
|
41
|
+
chunk.push(item);
|
|
42
|
+
if (chunk.length === size) {
|
|
43
|
+
yield chunk;
|
|
44
|
+
chunk = [];
|
|
45
|
+
}
|
|
38
46
|
}
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
const fetchTilesPool = new Pool(10);
|
|
43
|
-
fetchTilesPool.run(function(params, done) {
|
|
44
|
-
try {
|
|
45
|
-
worker.workerGetTiles(params.outUrl, params.server, params.theseTileIds,
|
|
46
|
-
params.authHeader, done);
|
|
47
|
-
// done.transfer({
|
|
48
|
-
// pixData: pixData
|
|
49
|
-
// }, [pixData.buffer]);
|
|
50
|
-
} catch (err) {
|
|
51
|
-
console.log('err:', err);
|
|
47
|
+
if (chunk.length) {
|
|
48
|
+
yield chunk;
|
|
52
49
|
}
|
|
53
|
-
}
|
|
54
|
-
*/
|
|
50
|
+
}
|
|
55
51
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
52
|
+
/**
|
|
53
|
+
* @template T
|
|
54
|
+
* @template U
|
|
55
|
+
* @typedef {{ value: T, resolve: (value: U) => void, reject: (err: unknown) => void }} WithResolvers
|
|
56
|
+
*/
|
|
59
57
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
58
|
+
/**
|
|
59
|
+
* Create a function that batches calls at intervals, with a final debounce.
|
|
60
|
+
*
|
|
61
|
+
* The returned function collects individual items and executes `processBatch` at the specified interval.
|
|
62
|
+
* If additional calls occur after the last batch, a final debounce ensures they are included.
|
|
63
|
+
*
|
|
64
|
+
* @template T
|
|
65
|
+
* @template U
|
|
66
|
+
* @template {Array<unknown>} Args
|
|
67
|
+
*
|
|
68
|
+
* @param {Object} options
|
|
69
|
+
* @param {(items: Array<WithResolvers<T, U>>, ...args: Args) => void} options.processBatch
|
|
70
|
+
* @param {number} options.interval
|
|
71
|
+
* @param {number} options.finalWait
|
|
72
|
+
*/
|
|
73
|
+
function createBatchedExecutor({ processBatch, interval, finalWait }) {
|
|
74
|
+
/** @type {ReturnType<typeof setTimeout> | undefined} */
|
|
75
|
+
let timeout = undefined;
|
|
76
|
+
/** @type {Array<WithResolvers<T, U>>} */
|
|
77
|
+
let pending = [];
|
|
78
|
+
/** @type {number} */
|
|
64
79
|
let blockedCalls = 0;
|
|
65
80
|
|
|
66
|
-
const bundleRequests = (request) => {
|
|
67
|
-
const requestId = requestMapper[request.id];
|
|
68
|
-
|
|
69
|
-
if (requestId && bundledRequest[requestId]) {
|
|
70
|
-
bundledRequest[requestId].ids = bundledRequest[requestId].ids.concat(
|
|
71
|
-
request.ids,
|
|
72
|
-
);
|
|
73
|
-
} else {
|
|
74
|
-
requestMapper[request.id] = bundledRequest.length;
|
|
75
|
-
bundledRequest.push(request);
|
|
76
|
-
}
|
|
77
|
-
};
|
|
78
|
-
|
|
79
81
|
const reset = () => {
|
|
80
|
-
timeout =
|
|
81
|
-
|
|
82
|
-
requestMapper = {};
|
|
82
|
+
timeout = undefined;
|
|
83
|
+
pending = [];
|
|
83
84
|
};
|
|
84
85
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
const callFunc = (request, ...args) => {
|
|
90
|
-
func(
|
|
91
|
-
{
|
|
92
|
-
sessionId,
|
|
93
|
-
requests: bundledRequest,
|
|
94
|
-
},
|
|
95
|
-
...args,
|
|
96
|
-
);
|
|
86
|
+
/** @param {Args} args */
|
|
87
|
+
const callFunc = (...args) => {
|
|
88
|
+
// Flush the "bundle" (of collected items) to the processor
|
|
89
|
+
processBatch(pending, ...args);
|
|
97
90
|
reset();
|
|
98
91
|
};
|
|
99
92
|
|
|
100
|
-
|
|
93
|
+
/** @param {Args} args */
|
|
94
|
+
const debounced = (...args) => {
|
|
101
95
|
const later = () => {
|
|
102
96
|
// Since we throttle and debounce we should check whether there were
|
|
103
97
|
// actually multiple attempts to call this function after the most recent
|
|
104
98
|
// throttled call. If there were no more calls we don't have to call
|
|
105
99
|
// the function again.
|
|
106
100
|
if (blockedCalls > 0) {
|
|
107
|
-
callFunc(
|
|
101
|
+
callFunc(...args);
|
|
108
102
|
blockedCalls = 0;
|
|
109
103
|
}
|
|
110
104
|
};
|
|
@@ -113,25 +107,20 @@ const throttleAndDebounce = (func, interval, finalWait) => {
|
|
|
113
107
|
timeout = setTimeout(later, finalWait);
|
|
114
108
|
};
|
|
115
109
|
|
|
116
|
-
debounced.cancel = () => {
|
|
117
|
-
clearTimeout(timeout);
|
|
118
|
-
reset();
|
|
119
|
-
};
|
|
120
|
-
|
|
121
|
-
debounced.immediate = () => {
|
|
122
|
-
func({
|
|
123
|
-
sessionId,
|
|
124
|
-
requests: bundledRequest,
|
|
125
|
-
});
|
|
126
|
-
};
|
|
127
|
-
|
|
128
110
|
let wait = false;
|
|
129
|
-
|
|
130
|
-
|
|
111
|
+
/**
|
|
112
|
+
* @param {T} value
|
|
113
|
+
* @param {Args} args
|
|
114
|
+
* @returns {Promise<U>}
|
|
115
|
+
*/
|
|
116
|
+
const throttled = (value, ...args) => {
|
|
117
|
+
// Collect items into the current queue any time the caller makes a request
|
|
118
|
+
const { promise, resolve, reject } = Promise.withResolvers();
|
|
119
|
+
pending.push({ value, resolve, reject });
|
|
131
120
|
|
|
132
121
|
if (!wait) {
|
|
133
|
-
callFunc(
|
|
134
|
-
debounced(
|
|
122
|
+
callFunc(...args);
|
|
123
|
+
debounced(...args);
|
|
135
124
|
wait = true;
|
|
136
125
|
blockedCalls = 0;
|
|
137
126
|
setTimeout(() => {
|
|
@@ -140,152 +129,247 @@ const throttleAndDebounce = (func, interval, finalWait) => {
|
|
|
140
129
|
} else {
|
|
141
130
|
blockedCalls++;
|
|
142
131
|
}
|
|
132
|
+
|
|
133
|
+
return promise;
|
|
143
134
|
};
|
|
144
135
|
|
|
145
136
|
return throttled;
|
|
146
|
-
}
|
|
137
|
+
}
|
|
147
138
|
|
|
139
|
+
/** @param {string} newHeader */
|
|
148
140
|
export const setTileProxyAuthHeader = (newHeader) => {
|
|
149
141
|
authHeader = newHeader;
|
|
150
142
|
};
|
|
151
143
|
|
|
144
|
+
/** @returns {string | null} */
|
|
152
145
|
export const getTileProxyAuthHeader = () => authHeader;
|
|
153
146
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
147
|
+
/**
|
|
148
|
+
* Merges an array of request objects by combining requests
|
|
149
|
+
* that share the same `id`, reducing the total number of requests.
|
|
150
|
+
*
|
|
151
|
+
* If multiple requests have the same `id`, their `tileIds` arrays are merged
|
|
152
|
+
* into a single request entry in the output array.
|
|
153
|
+
*
|
|
154
|
+
* @example
|
|
155
|
+
* ```js
|
|
156
|
+
* const requests = [
|
|
157
|
+
* { id: "A", tileIds: ["1", "2"] },
|
|
158
|
+
* { id: "B", tileIds: ["3"] },
|
|
159
|
+
* { id: "A", tileids: ["4", "5"] },
|
|
160
|
+
* ];
|
|
161
|
+
*
|
|
162
|
+
* const bundled = bundleRequests(requests);
|
|
163
|
+
* console.log(bundled);
|
|
164
|
+
* // [
|
|
165
|
+
* // { id: "A", tileIds: ["1", "2", "4", "5"] },
|
|
166
|
+
* // { id: "B", tileIds: ["3"] },
|
|
167
|
+
* // ]
|
|
168
|
+
* ```
|
|
169
|
+
*
|
|
170
|
+
* @template {{ id: string, tileIds: ReadonlyArray<string> }} T
|
|
171
|
+
* @param {Array<T>} requests - The list of requests to bundle
|
|
172
|
+
* @returns {Array<T>} - A new array with merged requests
|
|
173
|
+
*/
|
|
174
|
+
export function bundleRequestsById(requests) {
|
|
175
|
+
/** @type {Array<T>} */
|
|
176
|
+
const bundledRequests = [];
|
|
177
|
+
/** @type {Record<string, number>} */
|
|
178
|
+
const mapper = {};
|
|
157
179
|
|
|
158
|
-
const
|
|
180
|
+
for (const request of requests) {
|
|
181
|
+
if (mapper[request.id] === undefined) {
|
|
182
|
+
mapper[request.id] = bundledRequests.length;
|
|
183
|
+
bundledRequests.push({ ...request, tileIds: [] });
|
|
184
|
+
}
|
|
185
|
+
const bundle = bundledRequests[mapper[request.id]];
|
|
186
|
+
bundle.tileIds = bundle.tileIds.concat(request.tileIds);
|
|
187
|
+
}
|
|
159
188
|
|
|
160
|
-
|
|
161
|
-
|
|
189
|
+
return bundledRequests;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Groups request objects by `server`, merging their `tileIds` and structuring tileset-related
|
|
194
|
+
* data into `body`.
|
|
195
|
+
*
|
|
196
|
+
* **Note:** The first request for each `server` sets the `options` for all grouped requests.
|
|
197
|
+
* Each tileset in `body` also inherits these `options`. A tileset is only added to `body`
|
|
198
|
+
* if the request includes `options`.
|
|
199
|
+
*
|
|
200
|
+
* Trevor (2025-02-20): This follows the original "server bundling" logic. It’s unclear if `body` is
|
|
201
|
+
* actually used in practice. Omitting requests without `options` might be an unintended
|
|
202
|
+
* behavior, but we're maintaining it for now.
|
|
203
|
+
*
|
|
204
|
+
* @example
|
|
205
|
+
* ```js
|
|
206
|
+
* const requests = [
|
|
207
|
+
* { server: "A", tileIds: ["tileset1.1", "tileset2.2"], options: { foo: "bar" } },
|
|
208
|
+
* { server: "B", tileIds: ["tileset3.3"], options: { baz: "qux" } },
|
|
209
|
+
* { server: "A", tileIds: ["tileset1.4"] },
|
|
210
|
+
* ];
|
|
211
|
+
*
|
|
212
|
+
* const bundled = bundleRequestsByServer(requests);
|
|
213
|
+
* console.log(bundled);
|
|
214
|
+
* // [
|
|
215
|
+
* // {
|
|
216
|
+
* // server: "A",
|
|
217
|
+
* // tileIds: ["tileset1.1", "tileset2.2", "tileset1.4"],
|
|
218
|
+
* // options: { foo: "bar" },
|
|
219
|
+
* // body: [
|
|
220
|
+
* // { tilesetUid: "tileset1", tileIds: ["1"], options: { foo: "bar" } },
|
|
221
|
+
* // { tilesetUid: "tileset2", tileIds: ["2"], options: { foo: "bar" } }
|
|
222
|
+
* // ]
|
|
223
|
+
* // },
|
|
224
|
+
* // {
|
|
225
|
+
* // server: "B",
|
|
226
|
+
* // tileIds: ["tileset3.3"],
|
|
227
|
+
* // options: { baz: "qux" },
|
|
228
|
+
* // body: [
|
|
229
|
+
* // { tilesetUid: "tileset3", tileIds: ["3"], options: { baz: "qux" } }
|
|
230
|
+
* // ]
|
|
231
|
+
* // }
|
|
232
|
+
* // ]
|
|
233
|
+
* ```
|
|
234
|
+
*
|
|
235
|
+
* @template {{ tileIds: ReadonlyArray<string>, server: string, options?: Record<string, any> }} T
|
|
236
|
+
* @param {Array<T>} requests - The list of requests to bundle
|
|
237
|
+
* @returns {Array<T & { body: ReadonlyArray<ServerTilesetBody> }> }>} - A new array with merged requests per server
|
|
238
|
+
*/
|
|
239
|
+
export function bundleRequestsByServer(requests) {
|
|
240
|
+
/** @typedef {{ tilesetUid: string, tileIds: Array<string>, options: Record<string, any> }} ServerTilesetBody */
|
|
241
|
+
/** @type {Array<T & { body: Array<ServerTilesetBody> }>} */
|
|
242
|
+
const bundle = [];
|
|
243
|
+
/** @type {Record<string, number>} */
|
|
244
|
+
const mapper = {};
|
|
162
245
|
|
|
163
246
|
// We're converting the array of IDs into an object in order to filter out duplicated requests.
|
|
164
247
|
// In case different instances request the same data it won't be loaded twice.
|
|
165
248
|
for (const request of requests) {
|
|
166
|
-
if (
|
|
167
|
-
|
|
168
|
-
|
|
249
|
+
if (mapper[request.server] === undefined) {
|
|
250
|
+
mapper[request.server] = bundle.length;
|
|
251
|
+
bundle.push({ ...request, tileIds: [], body: [] });
|
|
169
252
|
}
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
253
|
+
const server = bundle[mapper[request.server]];
|
|
254
|
+
server.tileIds = server.tileIds.concat(request.tileIds);
|
|
255
|
+
for (const id of request.tileIds) {
|
|
173
256
|
if (request.options) {
|
|
174
257
|
const firstSepIndex = id.indexOf('.');
|
|
175
|
-
const
|
|
258
|
+
const tilesetUid = id.substring(0, firstSepIndex);
|
|
176
259
|
const tileId = id.substring(firstSepIndex + 1);
|
|
177
|
-
|
|
178
|
-
(t) => t.tilesetUid ===
|
|
260
|
+
let tilesetObject = server.body.find(
|
|
261
|
+
(t) => t.tilesetUid === tilesetUid,
|
|
179
262
|
);
|
|
180
|
-
if (tilesetObject) {
|
|
181
|
-
tilesetObject
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
tilesetUid: tilesetUuid,
|
|
185
|
-
tileIds: [tileId],
|
|
263
|
+
if (!tilesetObject) {
|
|
264
|
+
tilesetObject = {
|
|
265
|
+
tilesetUid: tilesetUid,
|
|
266
|
+
tileIds: [],
|
|
186
267
|
options: request.options,
|
|
187
|
-
}
|
|
268
|
+
};
|
|
269
|
+
server.body.push(tilesetObject);
|
|
188
270
|
}
|
|
271
|
+
tilesetObject.tileIds.push(tileId);
|
|
189
272
|
}
|
|
190
273
|
}
|
|
191
274
|
}
|
|
192
275
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
for (const server of servers) {
|
|
196
|
-
const ids = Object.keys(requestsByServer[server]);
|
|
197
|
-
// console.log('ids:', ids);
|
|
198
|
-
|
|
199
|
-
const requestBody = requestBodyByServer[server];
|
|
200
|
-
|
|
201
|
-
// if we request too many tiles, then the URL can get too long and fail
|
|
202
|
-
// so we'll break up the requests into smaller subsets
|
|
203
|
-
for (let i = 0; i < ids.length; i += MAX_FETCH_TILES) {
|
|
204
|
-
const theseTileIds = ids.slice(
|
|
205
|
-
i,
|
|
206
|
-
i + Math.min(ids.length - i, MAX_FETCH_TILES),
|
|
207
|
-
);
|
|
208
|
-
|
|
209
|
-
const renderParams = theseTileIds.map((x) => `d=${x}`).join('&');
|
|
210
|
-
const outUrl = `${server}/tiles/?${renderParams}&s=${sessionId}`;
|
|
211
|
-
|
|
212
|
-
/* eslint-disable no-loop-func */
|
|
213
|
-
/* eslint-disable no-unused-vars */
|
|
214
|
-
const p = new Promise((resolve, reject) => {
|
|
215
|
-
pubSub.publish('requestSent', outUrl);
|
|
216
|
-
const params = {};
|
|
217
|
-
|
|
218
|
-
params.outUrl = outUrl;
|
|
219
|
-
params.server = server;
|
|
220
|
-
params.theseTileIds = theseTileIds;
|
|
221
|
-
params.authHeader = authHeader;
|
|
222
|
-
|
|
223
|
-
workerGetTiles(
|
|
224
|
-
params.outUrl,
|
|
225
|
-
params.server,
|
|
226
|
-
params.theseTileIds,
|
|
227
|
-
params.authHeader,
|
|
228
|
-
resolve,
|
|
229
|
-
requestBody,
|
|
230
|
-
);
|
|
276
|
+
return bundle;
|
|
277
|
+
}
|
|
231
278
|
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
279
|
+
/**
|
|
280
|
+
* Consolidates requests into a (potentially) smaller, optimized set
|
|
281
|
+
*
|
|
282
|
+
* Requests are first bundled to merge duplicates, then grouped by `server` to
|
|
283
|
+
* consolidate requests targeting the same endpoint. The resulting set is split
|
|
284
|
+
* into smaller batches based on `maxSize`.
|
|
285
|
+
*
|
|
286
|
+
* @template {TilesRequest} T
|
|
287
|
+
* @param {Array<T>} requests - The list of requests to optimize.
|
|
288
|
+
* @param {{ maxSize?: number }} [options] - Configuration options.
|
|
289
|
+
*/
|
|
290
|
+
function* optimizeRequests(requests, { maxSize = MAX_FETCH_TILES } = {}) {
|
|
291
|
+
const byRequestId = bundleRequestsById(requests);
|
|
292
|
+
const byServer = bundleRequestsByServer(byRequestId);
|
|
293
|
+
for (const request of byServer) {
|
|
294
|
+
for (const tileIds of chunkIterable(new Set(request.tileIds), maxSize)) {
|
|
295
|
+
yield { ...request, tileIds };
|
|
243
296
|
}
|
|
244
297
|
}
|
|
298
|
+
}
|
|
245
299
|
|
|
246
|
-
|
|
247
|
-
const tiles = {};
|
|
248
|
-
|
|
249
|
-
// merge back all the tile requests
|
|
250
|
-
for (const data of datas) {
|
|
251
|
-
const tileIds = Object.keys(data);
|
|
300
|
+
/** @typedef {CompletedTileData<TileResponse>} TileData */
|
|
252
301
|
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
302
|
+
/**
|
|
303
|
+
* Collects independent tile responses into a shared index.
|
|
304
|
+
*
|
|
305
|
+
* Allows requests to retrieve associated tiles by server and tile IDs.
|
|
306
|
+
*
|
|
307
|
+
* @param {Array<Record<string, TileData> | void>} responses
|
|
308
|
+
*/
|
|
309
|
+
function indexTiles(responses) {
|
|
310
|
+
/** @type {Record<string, TileData>} */
|
|
311
|
+
const tileMap = {};
|
|
312
|
+
/** @type {(server: string, tileId: string) => string} */
|
|
313
|
+
const keyFor = (server, tileId) => `${server}/${tileId}`;
|
|
314
|
+
|
|
315
|
+
// merge back all the tile requests
|
|
316
|
+
for (const response of responses) {
|
|
317
|
+
if (!response) continue;
|
|
318
|
+
for (const [tileId, tileData] of Object.entries(response)) {
|
|
319
|
+
tileMap[keyFor(tileData.server, tileId)] = response[tileId];
|
|
256
320
|
}
|
|
321
|
+
}
|
|
257
322
|
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
323
|
+
return {
|
|
324
|
+
/**
|
|
325
|
+
* Retrieve data for a specific request from the shared index.
|
|
326
|
+
*
|
|
327
|
+
* @param {{ server: string, tileIds: Array<string> }} request
|
|
328
|
+
*/
|
|
329
|
+
resolveTileDataForRequest(request) {
|
|
330
|
+
/** @type {Record<string, TileData>} */
|
|
331
|
+
const response = {};
|
|
332
|
+
for (const tileId of request.tileIds) {
|
|
333
|
+
const entry = tileMap[keyFor(request.server, tileId)];
|
|
334
|
+
if (entry) response[tileId] = entry;
|
|
266
335
|
}
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
});
|
|
336
|
+
return response;
|
|
337
|
+
},
|
|
338
|
+
};
|
|
271
339
|
}
|
|
272
340
|
|
|
273
341
|
/**
|
|
274
|
-
* Retrieve a set of tiles from the server
|
|
342
|
+
* Retrieve a set of tiles from the server.
|
|
275
343
|
*
|
|
276
|
-
*
|
|
277
|
-
*
|
|
278
|
-
* @param server: A string with the server's url (e.g. "http://127.0.0.1")
|
|
279
|
-
* @param tileIds: The ids of the tiles to fetch (e.g. asdf-sdfs-sdfs.0.0.0)
|
|
344
|
+
* @type {(request: TilesRequest, pubSub: PubSub) => Promise<Record<string, TileData>>}
|
|
280
345
|
*/
|
|
281
|
-
export const fetchTilesDebounced =
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
346
|
+
export const fetchTilesDebounced = createBatchedExecutor({
|
|
347
|
+
/**
|
|
348
|
+
* Fetch and process a batch of tile requests.
|
|
349
|
+
*
|
|
350
|
+
* @param {Array<WithResolvers<TilesRequest, Record<string, TileData>>>} requests
|
|
351
|
+
* @param {PubSub} pubSub
|
|
352
|
+
*/
|
|
353
|
+
processBatch: async (requests, pubSub) => {
|
|
354
|
+
const promises = Array.from(
|
|
355
|
+
optimizeRequests(requests.map((r) => r.value)),
|
|
356
|
+
(request) => workerFetchTiles(request, { authHeader, sessionId, pubSub }),
|
|
357
|
+
);
|
|
358
|
+
const index = indexTiles(await Promise.all(promises));
|
|
359
|
+
for (const request of requests) {
|
|
360
|
+
request.resolve(index.resolveTileDataForRequest(request.value));
|
|
361
|
+
}
|
|
362
|
+
},
|
|
363
|
+
interval: TILE_FETCH_DEBOUNCE,
|
|
364
|
+
finalWait: TILE_FETCH_DEBOUNCE,
|
|
365
|
+
});
|
|
286
366
|
|
|
287
367
|
/**
|
|
288
368
|
* Calculate the zoom level from a list of available resolutions
|
|
369
|
+
*
|
|
370
|
+
* @param {Array<string>} resolutions
|
|
371
|
+
* @param {Scale} scale
|
|
372
|
+
* @returns {number}
|
|
289
373
|
*/
|
|
290
374
|
export const calculateZoomLevelFromResolutions = (resolutions, scale) => {
|
|
291
375
|
const sortedResolutions = resolutions.map((x) => +x).sort((a, b) => b - a);
|
|
@@ -308,8 +392,13 @@ export const calculateZoomLevelFromResolutions = (resolutions, scale) => {
|
|
|
308
392
|
);
|
|
309
393
|
};
|
|
310
394
|
|
|
395
|
+
/**
|
|
396
|
+
* @param {TilesetInfo} tilesetInfo
|
|
397
|
+
* @param {number} zoomLevel
|
|
398
|
+
* @returns {number}
|
|
399
|
+
*/
|
|
311
400
|
export const calculateResolution = (tilesetInfo, zoomLevel) => {
|
|
312
|
-
if (tilesetInfo
|
|
401
|
+
if (isResolutionsTilesetInfo(tilesetInfo)) {
|
|
313
402
|
const sortedResolutions = tilesetInfo.resolutions
|
|
314
403
|
.map((x) => +x)
|
|
315
404
|
.sort((a, b) => b - a);
|
|
@@ -319,7 +408,7 @@ export const calculateResolution = (tilesetInfo, zoomLevel) => {
|
|
|
319
408
|
}
|
|
320
409
|
|
|
321
410
|
const maxWidth = tilesetInfo.max_width;
|
|
322
|
-
const binsPerDimension = +tilesetInfo
|
|
411
|
+
const binsPerDimension = +(tilesetInfo?.bins_per_dimension ?? 256);
|
|
323
412
|
const resolution = maxWidth / (2 ** zoomLevel * binsPerDimension);
|
|
324
413
|
|
|
325
414
|
return resolution;
|
|
@@ -327,6 +416,12 @@ export const calculateResolution = (tilesetInfo, zoomLevel) => {
|
|
|
327
416
|
|
|
328
417
|
/**
|
|
329
418
|
* Calculate the current zoom level.
|
|
419
|
+
*
|
|
420
|
+
* @param {Scale} scale
|
|
421
|
+
* @param {number} minX
|
|
422
|
+
* @param {number} maxX
|
|
423
|
+
* @param {number} binsPerTile
|
|
424
|
+
* @returns {number}
|
|
330
425
|
*/
|
|
331
426
|
export const calculateZoomLevel = (scale, minX, maxX, binsPerTile) => {
|
|
332
427
|
const rangeWidth = scale.range()[1] - scale.range()[0];
|
|
@@ -366,13 +461,13 @@ export const calculateZoomLevel = (scale, minX, maxX, binsPerTile) => {
|
|
|
366
461
|
* Returns the tile position and position within the tile for
|
|
367
462
|
* the given element.
|
|
368
463
|
*
|
|
369
|
-
* @param {
|
|
370
|
-
* @param {
|
|
371
|
-
*
|
|
372
|
-
* @param {
|
|
373
|
-
* @param {
|
|
374
|
-
*
|
|
375
|
-
*
|
|
464
|
+
* @param {TilesetInfo} tilesetInfo - The information about this tileset
|
|
465
|
+
* @param {number} maxDim - The maximum width of the dataset (only used for tilesets without resolutions)
|
|
466
|
+
* @param {number} dataStartPos - The position where the data begins
|
|
467
|
+
* @param {number} zoomLevel - The (integer) current zoomLevel
|
|
468
|
+
* @param {number} position -The position (in absolute coordinates) to caculate the tile and position in tile for
|
|
469
|
+
*
|
|
470
|
+
* @returns {Array<number>}
|
|
376
471
|
*/
|
|
377
472
|
export function calculateTileAndPosInTile(
|
|
378
473
|
tilesetInfo,
|
|
@@ -382,17 +477,20 @@ export function calculateTileAndPosInTile(
|
|
|
382
477
|
position,
|
|
383
478
|
) {
|
|
384
479
|
let tileWidth = null;
|
|
385
|
-
const PIXELS_PER_TILE = tilesetInfo.bins_per_dimension || 256;
|
|
386
480
|
|
|
387
|
-
|
|
388
|
-
|
|
481
|
+
const pixelsPerTile = isLegacyTilesetInfo(tilesetInfo)
|
|
482
|
+
? (tilesetInfo.bins_per_dimension ?? 256)
|
|
483
|
+
: 256;
|
|
484
|
+
|
|
485
|
+
if (!isLegacyTilesetInfo(tilesetInfo)) {
|
|
486
|
+
tileWidth = tilesetInfo.resolutions[zoomLevel] * pixelsPerTile;
|
|
389
487
|
} else {
|
|
390
488
|
tileWidth = maxDim / 2 ** zoomLevel;
|
|
391
489
|
}
|
|
392
490
|
|
|
393
491
|
const tilePos = Math.floor((position - dataStartPos) / tileWidth);
|
|
394
492
|
const posInTile = Math.floor(
|
|
395
|
-
(
|
|
493
|
+
(pixelsPerTile * (position - tilePos * tileWidth)) / tileWidth,
|
|
396
494
|
);
|
|
397
495
|
|
|
398
496
|
return [tilePos, posInTile];
|
|
@@ -408,19 +506,19 @@ export function calculateTileAndPosInTile(
|
|
|
408
506
|
* @param {number} zoomLevel - The zoom level at which to find the tiles (can be
|
|
409
507
|
* calculated using this.calcaulteZoomLevel, but needs to synchronized across
|
|
410
508
|
* both x and y scales so should be calculated externally)
|
|
411
|
-
* @param {
|
|
509
|
+
* @param {Scale} scale - A d3 scale mapping data domain to visible values
|
|
412
510
|
* @param {number} minX - The minimum possible value in the dataset
|
|
413
|
-
* @param {number}
|
|
511
|
+
* @param {number} _maxX - The maximum possible value in the dataset
|
|
414
512
|
* @param {number} maxZoom - The maximum zoom value in this dataset
|
|
415
513
|
* @param {number} maxDim - The largest dimension of the tileset (e.g., width or height)
|
|
416
514
|
* (roughlty equal to 2 ** maxZoom * tileSize * tileResolution)
|
|
417
|
-
* @returns {number
|
|
515
|
+
* @returns {Array<number>} The indices of the tiles that should be visible
|
|
418
516
|
*/
|
|
419
517
|
export const calculateTiles = (
|
|
420
518
|
zoomLevel,
|
|
421
519
|
scale,
|
|
422
520
|
minX,
|
|
423
|
-
|
|
521
|
+
_maxX,
|
|
424
522
|
maxZoom,
|
|
425
523
|
maxDim,
|
|
426
524
|
) => {
|
|
@@ -431,16 +529,8 @@ export const calculateTiles = (
|
|
|
431
529
|
// be calculated according to cumulative width
|
|
432
530
|
|
|
433
531
|
const tileWidth = maxDim / 2 ** zoomLevelFinal;
|
|
434
|
-
// console.log('maxDim:', maxDim);
|
|
435
|
-
|
|
436
532
|
const epsilon = 0.0000001;
|
|
437
533
|
|
|
438
|
-
/*
|
|
439
|
-
console.log('minX:', minX, 'zoomLevel:', zoomLevel);
|
|
440
|
-
console.log('domain:', scale.domain(), scale.domain()[0] - minX,
|
|
441
|
-
((scale.domain()[0] - minX) / tileWidth))
|
|
442
|
-
*/
|
|
443
|
-
|
|
444
534
|
return range(
|
|
445
535
|
Math.max(0, Math.floor((scale.domain()[0] - minX) / tileWidth)),
|
|
446
536
|
Math.min(
|
|
@@ -450,8 +540,13 @@ export const calculateTiles = (
|
|
|
450
540
|
);
|
|
451
541
|
};
|
|
452
542
|
|
|
543
|
+
/**
|
|
544
|
+
* @param {TilesetInfo} tilesetInfo
|
|
545
|
+
* @param {number} zoomLevel
|
|
546
|
+
* @param {number} binsPerTile
|
|
547
|
+
*/
|
|
453
548
|
export const calculateTileWidth = (tilesetInfo, zoomLevel, binsPerTile) => {
|
|
454
|
-
if (tilesetInfo
|
|
549
|
+
if (!isLegacyTilesetInfo(tilesetInfo)) {
|
|
455
550
|
const sortedResolutions = tilesetInfo.resolutions
|
|
456
551
|
.map((x) => +x)
|
|
457
552
|
.sort((a, b) => b - a);
|
|
@@ -465,7 +560,7 @@ export const calculateTileWidth = (tilesetInfo, zoomLevel, binsPerTile) => {
|
|
|
465
560
|
* the minX and maxX values for the region
|
|
466
561
|
*
|
|
467
562
|
* @param {number} resolution - The number of base pairs per bin
|
|
468
|
-
* @param {
|
|
563
|
+
* @param {Scale} scale - The scale to use to calculate the currently visible tiles
|
|
469
564
|
* @param {number} minX - The minimum x position of the tileset
|
|
470
565
|
* @param {number} maxX - The maximum x position of the tileset
|
|
471
566
|
* @param {number=} pixelsPerTile - The number of pixels per tile
|
|
@@ -485,7 +580,7 @@ export const calculateTilesFromResolution = (
|
|
|
485
580
|
// console.log('PIXELS_PER_TILE:', PIXELS_PER_TILE);
|
|
486
581
|
|
|
487
582
|
if (!maxX) {
|
|
488
|
-
maxX = Number.MAX_VALUE;
|
|
583
|
+
maxX = Number.MAX_VALUE;
|
|
489
584
|
}
|
|
490
585
|
|
|
491
586
|
const lowerBound = Math.max(
|
|
@@ -513,21 +608,16 @@ export const calculateTilesFromResolution = (
|
|
|
513
608
|
* Render 2D tile data. Convert the raw values to an array of
|
|
514
609
|
* color values
|
|
515
610
|
*
|
|
516
|
-
* @param
|
|
517
|
-
*
|
|
518
|
-
* @param
|
|
519
|
-
*
|
|
520
|
-
* @param
|
|
521
|
-
* @param
|
|
522
|
-
* @param
|
|
523
|
-
* @param
|
|
524
|
-
* @param
|
|
525
|
-
* @param
|
|
526
|
-
* be mirrored tiles present ignore the upper right values
|
|
527
|
-
* @param ignoreLowerLeft: If this is a tile along the diagonal and there will be
|
|
528
|
-
* mirrored tiles present ignore the lower left values
|
|
529
|
-
* @param {array} zeroValueColor: The color to use for rendering zero data values, [r, g, b, a].
|
|
530
|
-
* @param {object} selectedRowsOptions Rendering options when using a `selectRows` track option.
|
|
611
|
+
* @param {{ mirrored?: boolean, isMirrored?: boolean, tileData: { dense: Float32Array, tilePos: readonly [a: number, b?: number], shape: readonly [number, number] }}} tile
|
|
612
|
+
* @param {"log" | "linear"} valueScaleType - Either 'log' or 'linear'
|
|
613
|
+
* @param {[min: number, max: number]} valueScaleDomain - The domain of the scale (the range is always [254,0])
|
|
614
|
+
* @param {number} pseudocount
|
|
615
|
+
* @param {ReadonlyArray<readonly [r: number, g: number, b: number, a: number]>} colorScale - a 255 x 4 rgba array used as a color scale
|
|
616
|
+
* @param {(x: null | { pixData: Uint8ClampedArray }) => void} finished
|
|
617
|
+
* @param {boolean | undefined} ignoreUpperRight - If this is a tile along the diagonal and there will be mirrored tiles present ignore the upper right values
|
|
618
|
+
* @param {boolean | undefined} ignoreLowerLeft - If this is a tile along the diagonal and there will be mirrored tiles present ignore the lower left values
|
|
619
|
+
* @param {[r: number, g:number, b: number, a: number]} zeroValueColor - The color to use for rendering zero data values
|
|
620
|
+
* @param {Partial<SelectedRowsOptions>} selectedRowsOptions Rendering options when using a `selectRows` track option.
|
|
531
621
|
*/
|
|
532
622
|
export const tileDataToPixData = (
|
|
533
623
|
tile,
|
|
@@ -572,19 +662,13 @@ export const tileDataToPixData = (
|
|
|
572
662
|
if (ignoreLowerLeft) {
|
|
573
663
|
for (let row = 0; row < tileWidth; row++) {
|
|
574
664
|
for (let col = 0; col < row; col++) {
|
|
575
|
-
tile.tileData.dense[row * tileWidth + col] = NaN;
|
|
665
|
+
tile.tileData.dense[row * tileWidth + col] = Number.NaN;
|
|
576
666
|
}
|
|
577
667
|
}
|
|
578
668
|
}
|
|
579
669
|
tile.isMirrored = true;
|
|
580
670
|
}
|
|
581
671
|
|
|
582
|
-
// console.log('tile', tile);
|
|
583
|
-
// clone the tileData so that the original array doesn't get neutered
|
|
584
|
-
// when being passed to the worker script
|
|
585
|
-
// const newTileData = tileData.dense;
|
|
586
|
-
|
|
587
|
-
// comment this and uncomment the code afterwards to enable threading
|
|
588
672
|
const pixData = workerSetPix(
|
|
589
673
|
tileData.dense.length,
|
|
590
674
|
tileData.dense,
|
|
@@ -600,31 +684,35 @@ export const tileDataToPixData = (
|
|
|
600
684
|
);
|
|
601
685
|
|
|
602
686
|
finished({ pixData });
|
|
687
|
+
};
|
|
603
688
|
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
colorScale: colorScale
|
|
614
|
-
};
|
|
689
|
+
/**
|
|
690
|
+
* @template T
|
|
691
|
+
* @overload
|
|
692
|
+
* @param {string | URL} url
|
|
693
|
+
* @param {(err: Error | undefined, value: T | undefined) => void} callback
|
|
694
|
+
* @param {"json"} textOrJson
|
|
695
|
+
* @param {import("pub-sub-es").PubSub} pubSub
|
|
696
|
+
* @returns {Promise<T>}
|
|
697
|
+
*/
|
|
615
698
|
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
;
|
|
625
|
-
*/
|
|
626
|
-
};
|
|
699
|
+
/**
|
|
700
|
+
* @overload
|
|
701
|
+
* @param {string | URL} url
|
|
702
|
+
* @param {(err: Error | undefined, value: string | undefined) => void} callback
|
|
703
|
+
* @param {"text"} textOrJson
|
|
704
|
+
* @param {import("pub-sub-es").PubSub} pubSub
|
|
705
|
+
* @returns {Promise<string>}
|
|
706
|
+
*/
|
|
627
707
|
|
|
708
|
+
/**
|
|
709
|
+
* @template T
|
|
710
|
+
* @param {string | URL} url
|
|
711
|
+
* @param {(err: Error | undefined, value: T | undefined) => void} callback
|
|
712
|
+
* @param {"text" | "json"} textOrJson
|
|
713
|
+
* @param {import("pub-sub-es").PubSub} pubSub
|
|
714
|
+
* @returns {Promise<T>}
|
|
715
|
+
*/
|
|
628
716
|
function fetchEither(url, callback, textOrJson, pubSub) {
|
|
629
717
|
requestsInFlight += 1;
|
|
630
718
|
pubSub.publish('requestSent', url);
|
|
@@ -637,6 +725,7 @@ function fetchEither(url, callback, textOrJson, pubSub) {
|
|
|
637
725
|
} else {
|
|
638
726
|
throw new Error(`fetch either "text" or "json", not "${textOrJson}"`);
|
|
639
727
|
}
|
|
728
|
+
/** @type {Record<string, string>} */
|
|
640
729
|
const headers = {};
|
|
641
730
|
|
|
642
731
|
if (mime) {
|
|
@@ -671,8 +760,9 @@ function fetchEither(url, callback, textOrJson, pubSub) {
|
|
|
671
760
|
/**
|
|
672
761
|
* Send a text request and mark it so that we can tell how many are in flight
|
|
673
762
|
*
|
|
674
|
-
* @param
|
|
675
|
-
* @param
|
|
763
|
+
* @param {string | URL} url
|
|
764
|
+
* @param {(err: Error | undefined, value: string | undefined) => void} callback
|
|
765
|
+
* @param {import("pub-sub-es").PubSub} pubSub
|
|
676
766
|
*/
|
|
677
767
|
function text(url, callback, pubSub) {
|
|
678
768
|
return fetchEither(url, callback, 'text', pubSub);
|
|
@@ -681,15 +771,16 @@ function text(url, callback, pubSub) {
|
|
|
681
771
|
/**
|
|
682
772
|
* Send a JSON request and mark it so that we can tell how many are in flight
|
|
683
773
|
*
|
|
684
|
-
* @
|
|
685
|
-
* @param
|
|
774
|
+
* @template T
|
|
775
|
+
* @param {string} url
|
|
776
|
+
* @param {(err: Error | undefined, value: T | undefined) => void} callback
|
|
777
|
+
* @param {import("pub-sub-es").PubSub} pubSub
|
|
686
778
|
*/
|
|
687
779
|
async function json(url, callback, pubSub) {
|
|
688
780
|
// Fritz: What is going on here? Can someone explain?
|
|
689
781
|
if (url.indexOf('hg19') >= 0) {
|
|
690
782
|
await sleep(1);
|
|
691
783
|
}
|
|
692
|
-
// console.log('url:', url);
|
|
693
784
|
return fetchEither(url, callback, 'json', pubSub);
|
|
694
785
|
}
|
|
695
786
|
|
|
@@ -698,8 +789,10 @@ async function json(url, callback, pubSub) {
|
|
|
698
789
|
*
|
|
699
790
|
* @param {string} server: The server where the data resides
|
|
700
791
|
* @param {string} tilesetUid: The identifier for the dataset
|
|
701
|
-
* @param {
|
|
702
|
-
* @param {
|
|
792
|
+
* @param {(info: Record<string, TilesetInfo>) => void} doneCb: A callback that gets called when the data is retrieved
|
|
793
|
+
* @param {(error: string) => void} errorCb: A callback that gets called when there is an error
|
|
794
|
+
* @param {import("pub-sub-es").PubSub} pubSub
|
|
795
|
+
* @returns {void}
|
|
703
796
|
*/
|
|
704
797
|
export const trackInfo = (server, tilesetUid, doneCb, errorCb, pubSub) => {
|
|
705
798
|
const url = `${tts(server)}/tileset_info/?d=${tilesetUid}&s=${sessionId}`;
|
|
@@ -708,19 +801,14 @@ export const trackInfo = (server, tilesetUid, doneCb, errorCb, pubSub) => {
|
|
|
708
801
|
json(
|
|
709
802
|
url,
|
|
710
803
|
(error, data) => {
|
|
711
|
-
// eslint-disable-line
|
|
712
804
|
pubSub.publish('requestReceived', url);
|
|
713
805
|
if (error) {
|
|
714
|
-
// console.log('error:', error);
|
|
715
|
-
// don't do anything
|
|
716
|
-
// no tileset info just means we can't do anything with this file...
|
|
717
806
|
if (errorCb) {
|
|
718
807
|
errorCb(`Error retrieving tilesetInfo from: ${server}`);
|
|
719
808
|
} else {
|
|
720
809
|
console.warn('Error retrieving: ', url);
|
|
721
810
|
}
|
|
722
811
|
} else {
|
|
723
|
-
// console.log('got data', data);
|
|
724
812
|
doneCb(data);
|
|
725
813
|
}
|
|
726
814
|
},
|