gridviz 3.0.40 → 3.1.0
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/dist/gridviz.js +1322 -1180
- package/dist/gridviz.min.js +1 -1
- package/package.json +2 -2
- package/src/core/Drawable.js +25 -3
- package/src/core/Map.js +670 -669
- package/src/dataset/TiledGrid.js +133 -21
- package/src/layer/GridLayer.js +195 -194
- package/src/style/Interpolator.js +259 -258
package/src/dataset/TiledGrid.js
CHANGED
|
@@ -5,11 +5,23 @@
|
|
|
5
5
|
|
|
6
6
|
// internal
|
|
7
7
|
import { Dataset } from '../core/Dataset.js'
|
|
8
|
+
import { isVisibleAt } from '../core/Drawable.js'
|
|
8
9
|
//import { monitor, monitorDuration } from '../utils/Utils.js'
|
|
9
10
|
|
|
10
11
|
// external
|
|
11
12
|
import { json, csv } from 'd3-fetch'
|
|
12
13
|
|
|
14
|
+
// The tiles that are loaded are kept, so that going back to a place needs no new request. They are kept as lists of cell objects, which
|
|
15
|
+
// take a lot of memory (the education grid of the Statistical Atlas reached 1.5 GB after some minutes of panning and zooming), so the
|
|
16
|
+
// number of cells kept by all the TiledGrid together is limited (TiledGrid.maxCachedCells): when there are more, the tiles that were used
|
|
17
|
+
// the longest time ago are dropped, and loaded again if the map needs them. The tiles of the view on the screen, and those used in the
|
|
18
|
+
// last KEEP_MS ms, are never dropped.
|
|
19
|
+
const KEEP_MS = 3000
|
|
20
|
+
/** All the tiles kept, of every dataset. */
|
|
21
|
+
const keptTiles = new Set()
|
|
22
|
+
let keptCells = 0
|
|
23
|
+
const now = () => (typeof performance !== 'undefined' ? performance.now() : Date.now())
|
|
24
|
+
|
|
13
25
|
/**
|
|
14
26
|
* A tiled dataset, composed of CSV tiles.
|
|
15
27
|
*
|
|
@@ -85,7 +97,7 @@ export class TiledGrid extends Dataset {
|
|
|
85
97
|
if (!this.map || !this.map.layers) return false
|
|
86
98
|
const z = this.map.getZoom()
|
|
87
99
|
for (const lay of this.map.layers) {
|
|
88
|
-
if (lay
|
|
100
|
+
if (!isVisibleAt(lay, z)) continue
|
|
89
101
|
if (!lay.getDataset) continue
|
|
90
102
|
if (lay.getDataset(z) == this) return true
|
|
91
103
|
}
|
|
@@ -141,8 +153,6 @@ export class TiledGrid extends Dataset {
|
|
|
141
153
|
const yMin = Math.max(tb.yMin, gbYMin)
|
|
142
154
|
const yMax = Math.min(tb.yMax, gbYMax)
|
|
143
155
|
|
|
144
|
-
const totalTiles = (xMax - xMin + 1) * (yMax - yMin + 1)
|
|
145
|
-
let processedTiles = 0
|
|
146
156
|
const tilePromises = []
|
|
147
157
|
|
|
148
158
|
// Iterate over tiles within bounds
|
|
@@ -150,9 +160,13 @@ export class TiledGrid extends Dataset {
|
|
|
150
160
|
for (let yT = yMin; yT <= yMax; yT++) {
|
|
151
161
|
if (!this.cache[xT]) this.cache[xT] = {}
|
|
152
162
|
|
|
153
|
-
// Skip
|
|
154
|
-
|
|
155
|
-
|
|
163
|
+
// Skip the tiles loaded, under way, or that failed recently
|
|
164
|
+
const kept = this.cache[xT][yT]
|
|
165
|
+
const retry =
|
|
166
|
+
kept && kept.failed && !kept.missing && now() - kept.failed >= TiledGrid.retryFailedAfterMs
|
|
167
|
+
if (kept && !retry) {
|
|
168
|
+
// in the view: not to be dropped
|
|
169
|
+
if (kept.cells) kept.lastUsed = now()
|
|
156
170
|
continue
|
|
157
171
|
}
|
|
158
172
|
|
|
@@ -160,18 +174,40 @@ export class TiledGrid extends Dataset {
|
|
|
160
174
|
this.cache[xT][yT] = 'loading'
|
|
161
175
|
|
|
162
176
|
tilePromises.push(
|
|
163
|
-
this.loadTile(xT, yT, signal)
|
|
164
|
-
|
|
177
|
+
this.loadTile(xT, yT, signal).then(
|
|
178
|
+
(tile) => {
|
|
165
179
|
this.cache[xT][yT] = tile
|
|
180
|
+
this._keep(tile)
|
|
166
181
|
|
|
167
182
|
// Check if this is the last tile
|
|
168
|
-
|
|
183
|
+
// (tiles under way for another request count: the one that ends last is the one that draws)
|
|
184
|
+
const isLastTile = !this._anyLoading(xMin, xMax, yMin, yMax)
|
|
169
185
|
this.checkAndRedraw(tile, isLastTile)
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
186
|
+
},
|
|
187
|
+
(error) => {
|
|
188
|
+
if (error && error.name === 'AbortError') {
|
|
189
|
+
// cancelled, not failed: the next request loads it
|
|
190
|
+
delete this.cache[xT][yT]
|
|
191
|
+
return
|
|
192
|
+
}
|
|
193
|
+
// Most of the time a tile that fails is one the server doesn't have (the tiles of a grid are
|
|
194
|
+
// often sparse). It is remembered as failed, and not asked for again at every redraw.
|
|
195
|
+
// A 404 is final ('missing'). Any other failure is tried again after retryFailedAfterMs: a
|
|
196
|
+
// browser doesn't give the status of a response without CORS headers, so a 404 from such a
|
|
197
|
+
// server can't be told from a network error.
|
|
198
|
+
this.cache[xT][yT] = {
|
|
199
|
+
failed: now(),
|
|
200
|
+
missing: /^(404|410)\b/.test(error && error.message),
|
|
201
|
+
}
|
|
202
|
+
// it changes nothing on the map, except when it was waited for to draw
|
|
203
|
+
if (
|
|
204
|
+
this.onlyDrawWhenAllTilesReady &&
|
|
205
|
+
!this._anyLoading(xMin, xMax, yMin, yMax) &&
|
|
206
|
+
this.isShown()
|
|
207
|
+
)
|
|
208
|
+
this.map.redraw()
|
|
209
|
+
}
|
|
210
|
+
)
|
|
175
211
|
)
|
|
176
212
|
}
|
|
177
213
|
}
|
|
@@ -180,6 +216,25 @@ export class TiledGrid extends Dataset {
|
|
|
180
216
|
return this
|
|
181
217
|
}
|
|
182
218
|
|
|
219
|
+
/**
|
|
220
|
+
* Tells if some tiles of a tiling envelope are still being loaded, by this request or any other one.
|
|
221
|
+
*
|
|
222
|
+
* @param {number} xMin
|
|
223
|
+
* @param {number} xMax
|
|
224
|
+
* @param {number} yMin
|
|
225
|
+
* @param {number} yMax
|
|
226
|
+
* @returns {boolean}
|
|
227
|
+
* @private
|
|
228
|
+
*/
|
|
229
|
+
_anyLoading(xMin, xMax, yMin, yMax) {
|
|
230
|
+
for (let xT = xMin; xT <= xMax; xT++) {
|
|
231
|
+
const column = this.cache[xT]
|
|
232
|
+
if (!column) continue
|
|
233
|
+
for (let yT = yMin; yT <= yMax; yT++) if (column[yT] === 'loading') return true
|
|
234
|
+
}
|
|
235
|
+
return false
|
|
236
|
+
}
|
|
237
|
+
|
|
183
238
|
/**
|
|
184
239
|
* Load a tile.
|
|
185
240
|
*
|
|
@@ -220,19 +275,59 @@ export class TiledGrid extends Dataset {
|
|
|
220
275
|
// 1. the dataset belongs to a layer which is visible at the current zoom level
|
|
221
276
|
if (!this.isShown()) return
|
|
222
277
|
|
|
278
|
+
// Waiting for all the tiles: drawn when the last one arrives, wherever it is (the tiles asked for are those of the
|
|
279
|
+
// view and of a margin around it, so the last one to arrive may be outside the view)
|
|
280
|
+
if (this.onlyDrawWhenAllTilesReady) {
|
|
281
|
+
if (isLastTile) this.map.redraw()
|
|
282
|
+
return
|
|
283
|
+
}
|
|
284
|
+
|
|
223
285
|
// Check if tile intersects the current view
|
|
224
286
|
const env = this.map.updateExtentGeo()
|
|
225
287
|
const { xMin, xMax, yMin, yMax } = tile.extGeo
|
|
226
288
|
if (env.xMax <= xMin || env.xMin >= xMax || env.yMax <= yMin || env.yMin >= yMax) return
|
|
227
289
|
|
|
228
290
|
// Trigger redraw
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
291
|
+
this.map.redraw()
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* Counts a tile that was loaded in the cells kept, and drops the tiles used the longest time ago when there are too many.
|
|
296
|
+
*
|
|
297
|
+
* @param {any} tile
|
|
298
|
+
* @private
|
|
299
|
+
*/
|
|
300
|
+
_keep(tile) {
|
|
301
|
+
// a failed tile is not a tile
|
|
302
|
+
if (!tile || typeof tile !== 'object' || !tile.cells) return
|
|
303
|
+
tile.lastUsed = now()
|
|
304
|
+
tile.owner = this
|
|
305
|
+
keptTiles.add(tile)
|
|
306
|
+
keptCells += tile.cells.length
|
|
307
|
+
if (keptCells <= TiledGrid.maxCachedCells) return
|
|
308
|
+
const limit = now() - KEEP_MS
|
|
309
|
+
const unused = [...keptTiles].filter((t) => t.lastUsed < limit).sort((a, b) => a.lastUsed - b.lastUsed)
|
|
310
|
+
for (const t of unused) {
|
|
311
|
+
if (keptCells <= TiledGrid.maxCachedCells) break
|
|
312
|
+
t.owner._drop(t)
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* Forgets a tile: it is loaded again when the map needs it.
|
|
318
|
+
*
|
|
319
|
+
* @param {any} tile
|
|
320
|
+
* @private
|
|
321
|
+
*/
|
|
322
|
+
_drop(tile) {
|
|
323
|
+
const column = this.cache[tile.x]
|
|
324
|
+
if (column && column[tile.y] === tile) {
|
|
325
|
+
delete column[tile.y]
|
|
326
|
+
if (!Object.keys(column).length) delete this.cache[tile.x]
|
|
235
327
|
}
|
|
328
|
+
if (keptTiles.delete(tile)) keptCells -= tile.cells.length
|
|
329
|
+
tile.owner = undefined
|
|
330
|
+
tile.cells = []
|
|
236
331
|
}
|
|
237
332
|
|
|
238
333
|
/**
|
|
@@ -263,7 +358,8 @@ export class TiledGrid extends Dataset {
|
|
|
263
358
|
//get tile
|
|
264
359
|
/** @type {object} */
|
|
265
360
|
const tile = this.cache[xT][yT]
|
|
266
|
-
if (!tile ||
|
|
361
|
+
if (!tile || !tile.cells) continue
|
|
362
|
+
tile.lastUsed = now()
|
|
267
363
|
|
|
268
364
|
//get cells
|
|
269
365
|
//this.cellsViewCache = this.cellsViewCache.concat(tile.cells)
|
|
@@ -280,6 +376,22 @@ export class TiledGrid extends Dataset {
|
|
|
280
376
|
}
|
|
281
377
|
}
|
|
282
378
|
|
|
379
|
+
/**
|
|
380
|
+
* The most cells kept in memory by the tiles of all the TiledGrid together (a cell is an object of the values of a place, some hundred
|
|
381
|
+
* bytes: about 250 MB for a million). It can be changed at any time.
|
|
382
|
+
*
|
|
383
|
+
* @type {number}
|
|
384
|
+
*/
|
|
385
|
+
TiledGrid.maxCachedCells = 1500000
|
|
386
|
+
|
|
387
|
+
/**
|
|
388
|
+
* How long, in ms, a tile that failed to load is remembered, before it is requested again. It doesn't apply to a tile the
|
|
389
|
+
* server answered 404 or 410 for: that one isn't requested again. It can be changed at any time.
|
|
390
|
+
*
|
|
391
|
+
* @type {number}
|
|
392
|
+
*/
|
|
393
|
+
TiledGrid.retryFailedAfterMs = 30000
|
|
394
|
+
|
|
283
395
|
function getGridTile(cells, xT, yT, gridInfo) {
|
|
284
396
|
const tile = {}
|
|
285
397
|
|
package/src/layer/GridLayer.js
CHANGED
|
@@ -1,194 +1,195 @@
|
|
|
1
|
-
//@ts-check
|
|
2
|
-
'use strict'
|
|
3
|
-
|
|
4
|
-
import { Layer } from '../core/Layer.js'
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* @
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
* @param {
|
|
16
|
-
* @param {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
opts
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
* A
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
const
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
if (this.
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
if
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
//
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
if (s.
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
if
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
//
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
lg.
|
|
140
|
-
|
|
141
|
-
//
|
|
142
|
-
//
|
|
143
|
-
|
|
144
|
-
const
|
|
145
|
-
const
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
container.
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
*
|
|
156
|
-
*
|
|
157
|
-
* @
|
|
158
|
-
*
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
*
|
|
166
|
-
*
|
|
167
|
-
* @
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
if (arguments.length ===
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
*
|
|
181
|
-
*
|
|
182
|
-
*
|
|
183
|
-
* @
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
if (key === '
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
}
|
|
1
|
+
//@ts-check
|
|
2
|
+
'use strict'
|
|
3
|
+
|
|
4
|
+
import { Layer } from '../core/Layer.js'
|
|
5
|
+
import { isVisibleAt } from '../core/Drawable.js'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* A layer, which specifies a dataset to be shown with specified styles.
|
|
9
|
+
*
|
|
10
|
+
* @module layer
|
|
11
|
+
* @author Joseph Davies, Julien Gaffuri
|
|
12
|
+
*/
|
|
13
|
+
export class GridLayer extends Layer {
|
|
14
|
+
/**
|
|
15
|
+
* @param {import("../core/Dataset").Dataset|import("../core/MultiResolutionDataset").MultiResolutionDataset} dataset The dataset to show.
|
|
16
|
+
* @param {Array.<import("../core/Style").Style>} styles The styles, ordered in drawing order.
|
|
17
|
+
* @param {{visible?:function(number):boolean,alpha?:function(number):number,blendOperation?:function(number):GlobalCompositeOperation,minPixelsPerCell?:number,cellInfoHTML?:'none'|function(import("../core/Dataset").Cell):string}} opts
|
|
18
|
+
*/
|
|
19
|
+
constructor(dataset, styles, opts = {}) {
|
|
20
|
+
super(opts)
|
|
21
|
+
opts = opts || {}
|
|
22
|
+
|
|
23
|
+
/** @type {import("../core/Dataset").Dataset|import("../core/MultiResolutionDataset").MultiResolutionDataset} */
|
|
24
|
+
this.dataset = dataset
|
|
25
|
+
|
|
26
|
+
/** @type {Array.<import("../core/Style").Style>} */
|
|
27
|
+
this.styles = styles
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* This parameter is used when the dataset is a MultiResolutionDataset.
|
|
31
|
+
* It defines the minimum number of pixels a grid cell should have to select the dataset to display based on its resolution.
|
|
32
|
+
* A low value, means that the map will be more detailled (smaller cells).
|
|
33
|
+
* A high value, means that the map will be less detailled (larger cells).
|
|
34
|
+
* This value should be higher than 1, otherwise it means a grid cell is smaller than the screen resolution.
|
|
35
|
+
* For more complex cell representations that require some more map space, this value should be higher.
|
|
36
|
+
* @type {number} */
|
|
37
|
+
this.minPixelsPerCell = opts.minPixelsPerCell || 3
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* The function returning cell information as HTML.
|
|
41
|
+
* This is typically used for tooltip information.
|
|
42
|
+
* Set to 'none' to disable cell info.
|
|
43
|
+
* @type {'none'|function(import("../core/Dataset").Cell, number):string} */
|
|
44
|
+
this.cellInfoHTML = opts.cellInfoHTML || GridLayer.defaultCellInfoHTML
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** */
|
|
48
|
+
draw(geoCanvas, legend) {
|
|
49
|
+
//get zoom level
|
|
50
|
+
const z = geoCanvas.view.z
|
|
51
|
+
const ctx = geoCanvas.offscreenCtx
|
|
52
|
+
|
|
53
|
+
//get layer dataset component
|
|
54
|
+
/** @type {import('../core/Dataset.js').Dataset|undefined} */
|
|
55
|
+
const dsc = this.getDataset(z)
|
|
56
|
+
if (!dsc) return
|
|
57
|
+
|
|
58
|
+
//launch data download, if necessary
|
|
59
|
+
dsc.getData(geoCanvas.extGeo)
|
|
60
|
+
|
|
61
|
+
//update dataset view cache
|
|
62
|
+
dsc.updateViewCache(geoCanvas.extGeo)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
//set layer alpha and blend mode
|
|
66
|
+
if (this.alpha || this.blendOperation) {
|
|
67
|
+
ctx.save()
|
|
68
|
+
if (this.alpha) ctx.globalAlpha = this.alpha(z)
|
|
69
|
+
if (this.blendOperation) ctx.globalCompositeOperation = this.blendOperation(z)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// tell the legends where they are drawn, so that they can fit it (width:'fill')
|
|
73
|
+
if (legend) for (const s of this.styles) GridLayer.setLegendContainer(legend.node(), s.legends, s['styles'])
|
|
74
|
+
|
|
75
|
+
//draw cells, style by style
|
|
76
|
+
for (const s of this.styles) {
|
|
77
|
+
//check if style is visible
|
|
78
|
+
if (!isVisibleAt(s, z)) continue
|
|
79
|
+
|
|
80
|
+
// set style alpha and blend mode
|
|
81
|
+
//TODO: multiply style alpha by layer alpha ?
|
|
82
|
+
if (s.alpha || s.blendOperation) {
|
|
83
|
+
ctx.save()
|
|
84
|
+
if (s.alpha) ctx.globalAlpha = s.alpha(z)
|
|
85
|
+
if (s.blendOperation) ctx.globalCompositeOperation = s.blendOperation(z)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
//set affin transform to draw with geographical coordinates
|
|
89
|
+
geoCanvas.setCanvasTransform()
|
|
90
|
+
|
|
91
|
+
//draw with style
|
|
92
|
+
s.draw(dsc.getViewCache(), geoCanvas, dsc.getResolution())
|
|
93
|
+
|
|
94
|
+
//draw style filter
|
|
95
|
+
if (s.filterColor) s.drawFilter(geoCanvas)
|
|
96
|
+
|
|
97
|
+
//restore default alpha and blend operation - before style change
|
|
98
|
+
if (s.alpha || s.blendOperation) ctx.restore()
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
//restore default alpha and blend operation - before layer change
|
|
102
|
+
if (this.alpha || this.blendOperation) ctx.restore()
|
|
103
|
+
|
|
104
|
+
//add legend element
|
|
105
|
+
if (legend) {
|
|
106
|
+
for (const s of this.styles) {
|
|
107
|
+
//check if style is visible
|
|
108
|
+
if (!isVisibleAt(s, z)) continue
|
|
109
|
+
GridLayer.addLegends(legend, s.legends, s, geoCanvas)
|
|
110
|
+
|
|
111
|
+
//case for styles of styles, like kernel smoothing
|
|
112
|
+
//TODO do better
|
|
113
|
+
if (s['styles']) {
|
|
114
|
+
for (const s2 of s['styles']) {
|
|
115
|
+
if (!isVisibleAt(s2, z)) continue
|
|
116
|
+
GridLayer.addLegends(legend, s2.legends, s2, geoCanvas)
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/** @private */
|
|
124
|
+
static setLegendContainer(container, legends, subStyles) {
|
|
125
|
+
// legends can be nested in arrays, like in Style.updateLegendsRecursive
|
|
126
|
+
const set = (lg) => {
|
|
127
|
+
if (Array.isArray(lg)) lg.forEach(set)
|
|
128
|
+
else if (lg) lg.container = container
|
|
129
|
+
}
|
|
130
|
+
set(legends)
|
|
131
|
+
for (const s2 of subStyles || []) GridLayer.setLegendContainer(container, s2.legends, undefined)
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/** @private */
|
|
135
|
+
static addLegends(legendComp, lg, style, geoCanvas) {
|
|
136
|
+
if (Array.isArray(lg)) for (const lg_ of lg) this.addLegends(legendComp, lg_, style, geoCanvas)
|
|
137
|
+
else {
|
|
138
|
+
// so that hovering a swatch can highlight the cells of the style on the map
|
|
139
|
+
lg.style_ = style
|
|
140
|
+
lg.redraw = () => (geoCanvas.redraw ? geoCanvas.redraw() : undefined)
|
|
141
|
+
// The legends go in the order they are drawn, after the one placed before them. A hovered legend is
|
|
142
|
+
// still in the container (see Map.redrawNow) and is left where it is: moving it would lose its mouseleave,
|
|
143
|
+
// and appending the others after it would put it above the legends that come before it.
|
|
144
|
+
const container = legendComp.node()
|
|
145
|
+
const el = lg.div.node()
|
|
146
|
+
const previous = container._gridvizLegendCursor
|
|
147
|
+
if (previous) {
|
|
148
|
+
if (el.previousSibling !== previous) previous.after(el)
|
|
149
|
+
} else if (container.firstChild !== el) container.prepend(el)
|
|
150
|
+
container._gridvizLegendCursor = el
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Return the relevant dataset component for a specified zoom.
|
|
156
|
+
*
|
|
157
|
+
* @param {number} z
|
|
158
|
+
* @returns {import("../core/Dataset").Dataset|undefined}
|
|
159
|
+
* */
|
|
160
|
+
getDataset(z) {
|
|
161
|
+
return this.dataset.getDataset(z, this.minPixelsPerCell)
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Set/get style stack.
|
|
166
|
+
*
|
|
167
|
+
* @param {undefined|import("../core/Style").Style|Array.<import("../core/Style").Style>} styles
|
|
168
|
+
* @returns { this | Array.<import("../core/Style").Style> }
|
|
169
|
+
*/
|
|
170
|
+
styles_(styles) {
|
|
171
|
+
if (arguments.length === 0) return this.styles
|
|
172
|
+
if (arguments.length === 1)
|
|
173
|
+
if (Array.isArray(styles)) this.styles = styles
|
|
174
|
+
else this.styles = [styles]
|
|
175
|
+
else this.styles = arguments
|
|
176
|
+
return this
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* The default function returning cell information as HTML.
|
|
181
|
+
* This is typically used for tooltip information.
|
|
182
|
+
*
|
|
183
|
+
* @param {import("../core/Dataset").Cell} cell
|
|
184
|
+
* @returns {string}
|
|
185
|
+
*/
|
|
186
|
+
static defaultCellInfoHTML(cell) {
|
|
187
|
+
const buf = []
|
|
188
|
+
for (const key of Object.keys(cell)) {
|
|
189
|
+
if (key === 'x') continue
|
|
190
|
+
if (key === 'y') continue
|
|
191
|
+
buf.push('<b>', key, '</b>', ' : ', cell[key], '<br>')
|
|
192
|
+
}
|
|
193
|
+
return buf.join('')
|
|
194
|
+
}
|
|
195
|
+
}
|