gridviz 3.0.39 → 3.0.41

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gridviz",
3
- "version": "3.0.39",
3
+ "version": "3.0.41",
4
4
  "description": "Visualization tool for gridded statistics",
5
5
  "keywords": [
6
6
  "statistics",
package/src/core/Map.js CHANGED
@@ -245,7 +245,11 @@ export class Map {
245
245
  //remove legend elements: the legends themselves only, not what is inside them
246
246
  //(a legend keeps its content while one of its swatches is hovered, see Style.updateLegends)
247
247
  //except the one being hovered, which would lose its mouseleave (see Legend.highlight)
248
- if (this.legend) this.legend.selectAll(':scope > *:not([data-gridviz-hovered])').remove()
248
+ if (this.legend) {
249
+ this.legend.selectAll(':scope > *:not([data-gridviz-hovered])').remove()
250
+ // the legends of this draw are placed one after the other (see GridLayer.addLegends)
251
+ this.legend.node()._gridvizLegendCursor = null
252
+ }
249
253
 
250
254
  //clear
251
255
  this.geoCanvas.initCanvasTransform()
@@ -10,6 +10,17 @@ import { Dataset } from '../core/Dataset.js'
10
10
  // external
11
11
  import { json, csv } from 'd3-fetch'
12
12
 
13
+ // 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
14
+ // 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
15
+ // number of cells kept by all the TiledGrid together is limited (TiledGrid.maxCachedCells): when there are more, the tiles that were used
16
+ // 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
17
+ // last KEEP_MS ms, are never dropped.
18
+ const KEEP_MS = 3000
19
+ /** All the tiles kept, of every dataset. */
20
+ const keptTiles = new Set()
21
+ let keptCells = 0
22
+ const now = () => (typeof performance !== 'undefined' ? performance.now() : Date.now())
23
+
13
24
  /**
14
25
  * A tiled dataset, composed of CSV tiles.
15
26
  *
@@ -152,6 +163,8 @@ export class TiledGrid extends Dataset {
152
163
 
153
164
  // Skip already loaded tiles or retry failed ones
154
165
  if (this.cache[xT][yT] && this.cache[xT][yT] !== 'failed') {
166
+ // in the view: not to be dropped
167
+ if (typeof this.cache[xT][yT] === 'object') this.cache[xT][yT].lastUsed = now()
155
168
  ++processedTiles
156
169
  continue
157
170
  }
@@ -163,6 +176,7 @@ export class TiledGrid extends Dataset {
163
176
  this.loadTile(xT, yT, signal)
164
177
  .then((tile) => {
165
178
  this.cache[xT][yT] = tile
179
+ this._keep(tile)
166
180
 
167
181
  // Check if this is the last tile
168
182
  const isLastTile = ++processedTiles === totalTiles
@@ -235,6 +249,45 @@ export class TiledGrid extends Dataset {
235
249
  }
236
250
  }
237
251
 
252
+ /**
253
+ * Counts a tile that was loaded in the cells kept, and drops the tiles used the longest time ago when there are too many.
254
+ *
255
+ * @param {any} tile
256
+ * @private
257
+ */
258
+ _keep(tile) {
259
+ // a failed tile is the string 'failed', not a tile
260
+ if (!tile || typeof tile !== 'object' || !tile.cells) return
261
+ tile.lastUsed = now()
262
+ tile.owner = this
263
+ keptTiles.add(tile)
264
+ keptCells += tile.cells.length
265
+ if (keptCells <= TiledGrid.maxCachedCells) return
266
+ const limit = now() - KEEP_MS
267
+ const unused = [...keptTiles].filter((t) => t.lastUsed < limit).sort((a, b) => a.lastUsed - b.lastUsed)
268
+ for (const t of unused) {
269
+ if (keptCells <= TiledGrid.maxCachedCells) break
270
+ t.owner._drop(t)
271
+ }
272
+ }
273
+
274
+ /**
275
+ * Forgets a tile: it is loaded again when the map needs it.
276
+ *
277
+ * @param {any} tile
278
+ * @private
279
+ */
280
+ _drop(tile) {
281
+ const column = this.cache[tile.x]
282
+ if (column && column[tile.y] === tile) {
283
+ delete column[tile.y]
284
+ if (!Object.keys(column).length) delete this.cache[tile.x]
285
+ }
286
+ if (keptTiles.delete(tile)) keptCells -= tile.cells.length
287
+ tile.owner = undefined
288
+ tile.cells = []
289
+ }
290
+
238
291
  /**
239
292
  * Fill the view cache with all cells which are within a geographical envelope.
240
293
  * @abstract
@@ -264,6 +317,7 @@ export class TiledGrid extends Dataset {
264
317
  /** @type {object} */
265
318
  const tile = this.cache[xT][yT]
266
319
  if (!tile || typeof tile === 'string') continue
320
+ tile.lastUsed = now()
267
321
 
268
322
  //get cells
269
323
  //this.cellsViewCache = this.cellsViewCache.concat(tile.cells)
@@ -280,6 +334,14 @@ export class TiledGrid extends Dataset {
280
334
  }
281
335
  }
282
336
 
337
+ /**
338
+ * 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
339
+ * bytes: about 250 MB for a million). It can be changed at any time.
340
+ *
341
+ * @type {number}
342
+ */
343
+ TiledGrid.maxCachedCells = 1500000
344
+
283
345
  function getGridTile(cells, xT, yT, gridInfo) {
284
346
  const tile = {}
285
347
 
@@ -137,8 +137,16 @@ export class GridLayer extends Layer {
137
137
  // so that hovering a swatch can highlight the cells of the style on the map
138
138
  lg.style_ = style
139
139
  lg.redraw = () => (geoCanvas.redraw ? geoCanvas.redraw() : undefined)
140
- // a hovered legend is still in place (see Map.redrawNow): moving it would lose its mouseleave
141
- if (lg.div.node().parentNode !== legendComp.node()) legendComp.node().append(lg.div.node())
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
142
150
  }
143
151
  }
144
152