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.
@@ -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.visible && !lay.visible(z)) continue
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 already loaded tiles or retry failed ones
154
- if (this.cache[xT][yT] && this.cache[xT][yT] !== 'failed') {
155
- ++processedTiles
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
- .then((tile) => {
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
- const isLastTile = ++processedTiles === totalTiles
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
- .catch(() => {
172
- this.cache[xT][yT] = 'failed'
173
- ++processedTiles
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
- if (this.onlyDrawWhenAllTilesReady) {
230
- if (isLastTile) {
231
- this.map.redraw()
232
- }
233
- } else {
234
- this.map.redraw()
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 || typeof tile === 'string') continue
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
 
@@ -1,194 +1,195 @@
1
- //@ts-check
2
- 'use strict'
3
-
4
- import { Layer } from '../core/Layer.js'
5
-
6
- /**
7
- * A layer, which specifies a dataset to be shown with specified styles.
8
- *
9
- * @module layer
10
- * @author Joseph Davies, Julien Gaffuri
11
- */
12
- export class GridLayer extends Layer {
13
- /**
14
- * @param {import("../core/Dataset").Dataset|import("../core/MultiResolutionDataset").MultiResolutionDataset} dataset The dataset to show.
15
- * @param {Array.<import("../core/Style").Style>} styles The styles, ordered in drawing order.
16
- * @param {{visible?:function(number):boolean,alpha?:function(number):number,blendOperation?:function(number):GlobalCompositeOperation,minPixelsPerCell?:number,cellInfoHTML?:'none'|function(import("../core/Dataset").Cell):string}} opts
17
- */
18
- constructor(dataset, styles, opts = {}) {
19
- super(opts)
20
- opts = opts || {}
21
-
22
- /** @type {import("../core/Dataset").Dataset|import("../core/MultiResolutionDataset").MultiResolutionDataset} */
23
- this.dataset = dataset
24
-
25
- /** @type {Array.<import("../core/Style").Style>} */
26
- this.styles = styles
27
-
28
- /**
29
- * This parameter is used when the dataset is a MultiResolutionDataset.
30
- * It defines the minimum number of pixels a grid cell should have to select the dataset to display based on its resolution.
31
- * A low value, means that the map will be more detailled (smaller cells).
32
- * A high value, means that the map will be less detailled (larger cells).
33
- * This value should be higher than 1, otherwise it means a grid cell is smaller than the screen resolution.
34
- * For more complex cell representations that require some more map space, this value should be higher.
35
- * @type {number} */
36
- this.minPixelsPerCell = opts.minPixelsPerCell || 3
37
-
38
- /**
39
- * The function returning cell information as HTML.
40
- * This is typically used for tooltip information.
41
- * Set to 'none' to disable cell info.
42
- * @type {'none'|function(import("../core/Dataset").Cell, number):string} */
43
- this.cellInfoHTML = opts.cellInfoHTML || GridLayer.defaultCellInfoHTML
44
- }
45
-
46
- /** */
47
- draw(geoCanvas, legend) {
48
- //get zoom level
49
- const z = geoCanvas.view.z
50
- const ctx = geoCanvas.offscreenCtx
51
-
52
- //get layer dataset component
53
- /** @type {import('../core/Dataset.js').Dataset|undefined} */
54
- const dsc = this.getDataset(z)
55
- if (!dsc) return
56
-
57
- //launch data download, if necessary
58
- dsc.getData(geoCanvas.extGeo)
59
-
60
- //update dataset view cache
61
- dsc.updateViewCache(geoCanvas.extGeo)
62
-
63
-
64
- //set layer alpha and blend mode
65
- if (this.alpha || this.blendOperation) {
66
- ctx.save()
67
- if (this.alpha) ctx.globalAlpha = this.alpha(z)
68
- if (this.blendOperation) ctx.globalCompositeOperation = this.blendOperation(z)
69
- }
70
-
71
- // tell the legends where they are drawn, so that they can fit it (width:'fill')
72
- if (legend) for (const s of this.styles) GridLayer.setLegendContainer(legend.node(), s.legends, s['styles'])
73
-
74
- //draw cells, style by style
75
- for (const s of this.styles) {
76
- //check if style is visible
77
- if (s.visible && !s.visible(z)) continue
78
-
79
- // set style alpha and blend mode
80
- //TODO: multiply style alpha by layer alpha ?
81
- if (s.alpha || s.blendOperation) {
82
- ctx.save()
83
- if (s.alpha) ctx.globalAlpha = s.alpha(z)
84
- if (s.blendOperation) ctx.globalCompositeOperation = s.blendOperation(z)
85
- }
86
-
87
- //set affin transform to draw with geographical coordinates
88
- geoCanvas.setCanvasTransform()
89
-
90
- //draw with style
91
- s.draw(dsc.getViewCache(), geoCanvas, dsc.getResolution())
92
-
93
- //draw style filter
94
- if (s.filterColor) s.drawFilter(geoCanvas)
95
-
96
- //restore default alpha and blend operation - before style change
97
- if (s.alpha || s.blendOperation) ctx.restore()
98
- }
99
-
100
- //restore default alpha and blend operation - before layer change
101
- if (this.alpha || this.blendOperation) ctx.restore()
102
-
103
- //add legend element
104
- if (legend) {
105
- for (const s of this.styles) {
106
- //check if style is visible
107
- if (s.visible && !s.visible(z)) continue
108
- GridLayer.addLegends(legend, s.legends, s, geoCanvas)
109
-
110
- //case for styles of styles, like kernel smoothing
111
- //TODO do better
112
- if (s['styles']) {
113
- for (const s2 of s['styles']) {
114
- if (s2.visible && !s2.visible(z)) continue
115
- GridLayer.addLegends(legend, s2.legends, s2, geoCanvas)
116
- }
117
- }
118
- }
119
- }
120
- }
121
-
122
- /** @private */
123
- static setLegendContainer(container, legends, subStyles) {
124
- // legends can be nested in arrays, like in Style.updateLegendsRecursive
125
- const set = (lg) => {
126
- if (Array.isArray(lg)) lg.forEach(set)
127
- else if (lg) lg.container = container
128
- }
129
- set(legends)
130
- for (const s2 of subStyles || []) GridLayer.setLegendContainer(container, s2.legends, undefined)
131
- }
132
-
133
- /** @private */
134
- static addLegends(legendComp, lg, style, geoCanvas) {
135
- if (Array.isArray(lg)) for (const lg_ of lg) this.addLegends(legendComp, lg_, style, geoCanvas)
136
- else {
137
- // so that hovering a swatch can highlight the cells of the style on the map
138
- lg.style_ = style
139
- lg.redraw = () => (geoCanvas.redraw ? geoCanvas.redraw() : undefined)
140
- // The legends go in the order they are drawn, after the one placed before them. A hovered legend is
141
- // still in the container (see Map.redrawNow) and is left where it is: moving it would lose its mouseleave,
142
- // and appending the others after it would put it above the legends that come before it.
143
- const container = legendComp.node()
144
- const el = lg.div.node()
145
- const previous = container._gridvizLegendCursor
146
- if (previous) {
147
- if (el.previousSibling !== previous) previous.after(el)
148
- } else if (container.firstChild !== el) container.prepend(el)
149
- container._gridvizLegendCursor = el
150
- }
151
- }
152
-
153
- /**
154
- * Return the relevant dataset component for a specified zoom.
155
- *
156
- * @param {number} z
157
- * @returns {import("../core/Dataset").Dataset|undefined}
158
- * */
159
- getDataset(z) {
160
- return this.dataset.getDataset(z, this.minPixelsPerCell)
161
- }
162
-
163
- /**
164
- * Set/get style stack.
165
- *
166
- * @param {undefined|import("../core/Style").Style|Array.<import("../core/Style").Style>} styles
167
- * @returns { this | Array.<import("../core/Style").Style> }
168
- */
169
- styles_(styles) {
170
- if (arguments.length === 0) return this.styles
171
- if (arguments.length === 1)
172
- if (Array.isArray(styles)) this.styles = styles
173
- else this.styles = [styles]
174
- else this.styles = arguments
175
- return this
176
- }
177
-
178
- /**
179
- * The default function returning cell information as HTML.
180
- * This is typically used for tooltip information.
181
- *
182
- * @param {import("../core/Dataset").Cell} cell
183
- * @returns {string}
184
- */
185
- static defaultCellInfoHTML(cell) {
186
- const buf = []
187
- for (const key of Object.keys(cell)) {
188
- if (key === 'x') continue
189
- if (key === 'y') continue
190
- buf.push('<b>', key, '</b>', ' : ', cell[key], '<br>')
191
- }
192
- return buf.join('')
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
+ }