gridviz 2.1.17 → 3.0.1

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.
Files changed (67) hide show
  1. package/README.md +2 -3
  2. package/dist/gridviz.js +8659 -9294
  3. package/dist/gridviz.min.js +1 -1
  4. package/package.json +3 -2
  5. package/src/button/Button.js +14 -15
  6. package/src/button/FullscreenButton.js +14 -14
  7. package/src/button/ZoomButtons.js +2 -2
  8. package/src/{DatasetComponent.js → core/Dataset.js} +33 -13
  9. package/src/core/Drawable.js +60 -0
  10. package/src/{GeoCanvas.js → core/GeoCanvas.js} +65 -155
  11. package/src/core/Layer.js +24 -0
  12. package/src/{Legend.js → core/Legend.js} +24 -1
  13. package/src/core/Map.js +474 -0
  14. package/src/core/MultiResolutionDataset.js +96 -0
  15. package/src/core/Style.js +84 -0
  16. package/src/dataset/COGeo.js +5 -5
  17. package/src/dataset/CSVGrid.js +43 -41
  18. package/src/dataset/GeoTIFF.js +9 -14
  19. package/src/dataset/{LGrid.js → JSGrid.js} +9 -11
  20. package/src/dataset/TiledGrid.js +68 -45
  21. package/src/index.js +22 -29
  22. package/src/{BackgroundLayer.js → layer/BackgroundLayer.js} +27 -52
  23. package/src/{BackgroundLayerWMS.js → layer/BackgroundLayerWMS.js} +21 -45
  24. package/src/{LineLayer.js → layer/GeoJSONLayer.js} +22 -23
  25. package/src/layer/GridLayer.js +160 -0
  26. package/src/{LabelLayer.js → layer/LabelLayer.js} +25 -23
  27. package/src/legend/ColorCategoryLegend.js +9 -24
  28. package/src/legend/ColorDiscreteLegend.js +20 -24
  29. package/src/legend/ColorLegend.js +21 -33
  30. package/src/legend/OrientationLegend.js +90 -0
  31. package/src/legend/SizeLegend.js +153 -72
  32. package/src/style/CompositionStyle.js +71 -84
  33. package/src/style/ContourStyle.js +2 -36
  34. package/src/style/DotDensityStyle.js +50 -73
  35. package/src/style/IsoFenceStyle.js +59 -70
  36. package/src/style/JoyPlotStyle.js +41 -48
  37. package/src/style/LegoStyle.js +4 -4
  38. package/src/style/MosaicStyle.js +30 -39
  39. package/src/style/NinjaStarStyle.js +45 -68
  40. package/src/style/PillarStyle.js +70 -93
  41. package/src/style/SegmentStyle.js +37 -75
  42. package/src/style/ShapeColorSizeStyle.js +49 -98
  43. package/src/style/SideCategoryStyle.js +112 -0
  44. package/src/style/SideStyle.js +99 -133
  45. package/src/style/{SquareColorCatWGLStyle.js → SquareColorCategoryWebGLStyle.js} +28 -41
  46. package/src/style/{SquareColorWGLStyle.js → SquareColorWebGLStyle.js} +28 -49
  47. package/src/style/StrokeStyle.js +43 -70
  48. package/src/style/TanakaStyle.js +34 -132
  49. package/src/style/TextStyle.js +43 -64
  50. package/src/style/TimeSeriesStyle.js +44 -75
  51. package/src/utils/WebGLSquareColoringAdvanced.js +4 -4
  52. package/src/utils/scale.js +217 -0
  53. package/src/utils/stretching.js +86 -129
  54. package/src/utils/{Utils.js → utils.js} +15 -0
  55. package/src/utils/webGLUtils.js +4 -5
  56. package/src/utils/zoomUtils.js +30 -30
  57. package/src/App.js +0 -760
  58. package/src/Dataset.js +0 -76
  59. package/src/Layer.js +0 -103
  60. package/src/Style.js +0 -131
  61. package/src/dataset/GridTile.js +0 -41
  62. package/src/legend/SegmentOrientationLegend.js +0 -87
  63. package/src/legend/SegmentWidthLegend.js +0 -101
  64. package/src/style/CompositionStyle_.js +0 -293
  65. package/src/style/ShapeColorSizeStyle_.js +0 -108
  66. package/src/style/SideCatStyle.js +0 -172
  67. /package/src/{Tooltip.js → core/Tooltip.js} +0 -0
package/src/App.js DELETED
@@ -1,760 +0,0 @@
1
- //@ts-check
2
- 'use strict'
3
-
4
- // internal imports
5
- import { GeoCanvas } from './GeoCanvas.js'
6
- import { Layer } from './Layer.js'
7
- import { Dataset } from './Dataset.js'
8
- import { Tooltip } from './Tooltip.js'
9
- import { CSVGrid } from './dataset/CSVGrid.js'
10
- import { LGrid } from './dataset/LGrid.js'
11
- import { TiledGrid } from './dataset/TiledGrid.js'
12
- import { BackgroundLayer } from './BackgroundLayer.js'
13
- import { BackgroundLayerWMS } from './BackgroundLayerWMS.js'
14
- import { LabelLayer } from './LabelLayer.js'
15
- import { LineLayer } from './LineLayer.js'
16
- import { monitor, monitorDuration } from './utils/Utils.js'
17
- import { ZoomButtons } from './button/ZoomButtons.js'
18
- import { FullscreenButton } from './button/FullscreenButton.js'
19
-
20
- // external imports
21
- import { select } from 'd3-selection'
22
-
23
- /**
24
- * A gridviz application.
25
- *
26
- * @author Joseph Davies, Julien Gaffuri
27
- */
28
- export class App {
29
- /**
30
- * @param {HTMLDivElement} container
31
- * @param {object} opts
32
- */
33
- constructor(container, opts) {
34
- opts = opts || {}
35
-
36
- /**
37
- * The layers.
38
- * @type {Array.<Layer>}
39
- * */
40
- this.layers = []
41
-
42
- //get container element
43
- this.container = container || document.getElementById('gridviz')
44
- if (!this.container) {
45
- console.error('Cannot find gridviz container element.')
46
- return
47
- }
48
-
49
- //https://css-tricks.com/absolute-positioning-inside-relative-positioning/
50
- this.container.style.position = "relative"; // container element must have relative positioning
51
-
52
- //set dimensions
53
- /** @type {number} */
54
- this.w = opts.w || this.container.offsetWidth
55
- /** @type {number} */
56
- this.h = opts.h || this.container.offsetHeight
57
-
58
- //create canvas element if user doesnt specify one
59
- /** @type {HTMLCanvasElement} */
60
- let canvas = opts.canvas || null
61
- if (!canvas) {
62
- canvas = document.createElement('canvas')
63
- canvas.setAttribute('width', '' + this.w)
64
- canvas.setAttribute('height', '' + this.h)
65
- this.container.appendChild(canvas)
66
- }
67
-
68
- /** Make geo canvas
69
- * @type {GeoCanvas}
70
- * @private */
71
- this.cg = new GeoCanvas(canvas, undefined, 1, opts)
72
- this.cg.redraw = (strong = true) => {
73
- if (monitor) monitorDuration('Start redraw')
74
- //console.log("?x=" + this.cg.getCenter().x + "&y=" + this.cg.getCenter().y + "&z=" + this.cg.getZf())
75
-
76
- //remove legend elements
77
- if (this.legend && strong) this.legend.selectAll('*').remove()
78
-
79
- //clear
80
- this.cg.initCanvasTransform()
81
- this.cg.clear(this.cg.backgroundColor)
82
-
83
- const zf = this.getZoomFactor()
84
- this.updateExtentGeo()
85
-
86
- //go through the background layers
87
- if (this.showBgLayers)
88
- for (const layer of this.bgLayers) {
89
- //check if layer is visible
90
- if (!layer.visible) continue
91
- if (zf > layer.maxZoom) continue
92
- if (zf < layer.minZoom) continue
93
-
94
- //draw layer
95
- layer.draw(this.cg)
96
- }
97
-
98
- //go through the layers
99
- for (const layer of this.layers) {
100
- //check if layer is visible
101
- if (!layer.visible) continue
102
- if (zf > layer.maxZoom) continue
103
- if (zf < layer.minZoom) continue
104
-
105
- //get layer dataset component
106
- /** @type {import('./DatasetComponent').DatasetComponent|undefined} */
107
- const dsc = layer.getDatasetComponent(zf)
108
- if (!dsc) continue
109
-
110
- //launch data download, if necessary
111
- if (strong)
112
- dsc.getData(this.cg.extGeo, () => {
113
- this.cg.redraw()
114
- })
115
-
116
- //update dataset view cache
117
- if (strong) dsc.updateViewCache(this.cg.extGeo)
118
-
119
- //set layer alpha and blend mode
120
- this.cg.ctx.globalAlpha = layer.alpha ? layer.alpha(zf) : 1.0
121
- this.cg.ctx.globalCompositeOperation = layer.blendOperation(zf)
122
-
123
- //draw cells, style by style
124
- if (strong)
125
- for (const s of layer.styles) {
126
- //check if style is visible
127
- if (!s.visible) continue
128
- if (zf > s.maxZoom) continue
129
- if (zf < s.minZoom) continue
130
-
131
- //set style alpha and blend mode
132
- //TODO: multiply by layer alpha ?
133
- this.cg.ctx.globalAlpha = s.alpha ? s.alpha(zf) : 1.0
134
- this.cg.ctx.globalCompositeOperation = s.blendOperation(zf)
135
-
136
- s.draw(dsc.getViewCache(), dsc.getResolution(), this.cg)
137
- }
138
-
139
- //add legend element
140
- if (this.legend && strong) {
141
- for (const s of layer.styles) {
142
- if (zf > s.maxZoom) continue
143
- if (zf < s.minZoom) continue
144
- for (const lg of s.legends) {
145
- //console.log(s, lg)
146
- //this.legend.append(lg.div)
147
- //s1.node().appendChild(s2.node())
148
- this.legend.node().append(lg.div.node())
149
- }
150
-
151
- //case for styles of styles, like kernel smoothing
152
- //TODO do better
153
- if (s['styles']) {
154
- for (const s2 of s.styles) {
155
- if (zf > s2.maxZoom) continue
156
- if (zf < s2.minZoom) continue
157
- for (const lg of s2.legends) {
158
- //console.log(s, lg)
159
- //this.legend.append(lg.div)
160
- //s1.node().appendChild(s2.node())
161
- this.legend.node().append(lg.div.node())
162
- }
163
- }
164
- }
165
- }
166
- }
167
-
168
- //restore default alpha and blend operation
169
- this.cg.ctx.globalAlpha = 1.0
170
- this.cg.ctx.globalCompositeOperation = this.defaultGlobalCompositeOperation
171
- }
172
-
173
- //draw boundary layer
174
- //if (strong)
175
- if (this.showBoundaries && this.boundaryLayer) this.boundaryLayer.draw(this.cg)
176
-
177
- //draw label layer
178
- //if (strong)
179
- if (this.showLabels && this.labelLayer) this.labelLayer.draw(this.cg)
180
-
181
- //
182
- this.canvasSave = null
183
-
184
- if (monitor) monitorDuration('End redraw')
185
-
186
- // listen for resize events on the App's container and handle them
187
- this.defineResizeObserver(this.container, canvas)
188
-
189
- return this
190
- }
191
-
192
- /** @type {Array.<BackgroundLayer|BackgroundLayerWMS>} */
193
- this.bgLayers = []
194
- /** @type {boolean} */
195
- this.showBgLayers = true
196
-
197
- /** @type {LabelLayer | undefined} */
198
- this.labelLayer = undefined
199
- /** @type {boolean} */
200
- this.showLabels = true
201
-
202
- /** @type {LineLayer | undefined} */
203
- this.boundaryLayer = undefined
204
- /** @type {boolean} */
205
- this.showBoundaries = true
206
-
207
- // legend div
208
- this.legendDivId = opts.legendDivId || 'gvizLegend'
209
- this.legend = select('#' + this.legendDivId)
210
- if (this.legend.empty()) {
211
- this.legend = select(
212
- this.container.id && this.container.id != '' ? '#' + this.container.id : 'body'
213
- )
214
- .append('div')
215
- .attr('id', this.legendDivId)
216
- .style('position', 'absolute')
217
- .style('width', 'auto')
218
- .style('height', 'auto')
219
- .style('background', '#FFFFFF')
220
- //.style("padding", this.padding)
221
- .style('border', '0px')
222
- //.style('border-radius', '5px')
223
- .style('box-shadow', '3px 3px 3px grey, -3px -3px 3px #ddd')
224
- .style('font-family', 'Helvetica, Arial, sans-serif')
225
- .style('bottom', '15px')
226
- .style('right', '15px')
227
- //hide
228
- //.style("visibility", "hidden")
229
- }
230
-
231
- //tooltip
232
-
233
- // set App container as default parent element for tooltip
234
- if (!opts.tooltip) opts.tooltip = {}
235
- if (!opts.tooltip.parentElement) opts.tooltip.parentElement = this.container
236
-
237
- /**
238
- * @private
239
- * @type {Tooltip} */
240
- this.tooltip = new Tooltip(opts.tooltip)
241
-
242
- /** @param {MouseEvent} e */
243
- const focusCell = (e) => {
244
- //compute mouse geo position
245
- const mousePositionGeo = {
246
- x: this.cg.pixToGeoX(e.offsetX + this.tooltip.xMouseOffset),
247
- y: this.cg.pixToGeoY(e.offsetY + this.tooltip.yMouseOffset),
248
- }
249
- /** @type {{cell:import('./Dataset').Cell,html:string,resolution:number} | undefined} */
250
- const focus = this.getCellFocusInfo(mousePositionGeo)
251
-
252
- // transparent background (e.g. leaflet) 'red painting' fix
253
- if (opts.transparentBackground) {
254
- if (focus) {
255
- this.tooltip.html(focus.html)
256
- this.tooltip.setPosition(e)
257
- this.tooltip.show()
258
- } else {
259
- this.tooltip.hide()
260
- }
261
- this.canvasSave = document.createElement('canvas')
262
- this.canvasSave.setAttribute('width', '' + this.w)
263
- this.canvasSave.setAttribute('height', '' + this.h)
264
- this.canvasSave.getContext('2d').drawImage(this.cg.canvas, 0, 0)
265
- this.cg.initCanvasTransform()
266
- return
267
- }
268
-
269
- if (focus) {
270
- this.tooltip.html(focus.html)
271
- this.tooltip.setPosition(e)
272
- this.tooltip.show()
273
-
274
- //show cell position as a rectangle
275
- if (!this.canvasSave) {
276
- this.canvasSave = document.createElement('canvas')
277
- this.canvasSave.setAttribute('width', '' + this.w)
278
- this.canvasSave.setAttribute('height', '' + this.h)
279
- this.canvasSave.getContext('2d').drawImage(this.cg.canvas, 0, 0)
280
- } else {
281
- this.cg.ctx.drawImage(this.canvasSave, 0, 0)
282
- }
283
-
284
- //draw image saved + draw rectangle
285
- const rectWPix = this.selectionRectangleWidthPix
286
- ? this.selectionRectangleWidthPix(focus.resolution, this.getZoomFactor())
287
- : 4
288
- this.cg.initCanvasTransform()
289
- this.cg.ctx.strokeStyle = this.selectionRectangleColor
290
- this.cg.ctx.lineWidth = rectWPix
291
- this.cg.ctx.beginPath()
292
-
293
- this.cg.ctx.rect(
294
- this.cg.geoToPixX(focus.cell.x) - rectWPix / 2,
295
- this.cg.geoToPixY(focus.cell.y) + rectWPix / 2,
296
- focus.resolution / this.getZoomFactor() + rectWPix,
297
- -focus.resolution / this.getZoomFactor() - rectWPix
298
- )
299
- this.cg.ctx.stroke()
300
- } else {
301
- this.tooltip.hide()
302
- if (this.canvasSave) this.cg.ctx.drawImage(this.canvasSave, 0, 0)
303
- }
304
- }
305
-
306
- // add event listeners to container
307
- this.mouseOverHandler = (e) => focusCell(e)
308
- this.mouseMoveHandler = (e) => focusCell(e)
309
- this.mouseOutHandler = (e) => this.tooltip.hide()
310
- this.cg.canvas.addEventListener('mouseover', this.mouseOverHandler)
311
- this.cg.canvas.addEventListener('mousemove', this.mouseMoveHandler)
312
- this.cg.canvas.addEventListener('mouseout', this.mouseOutHandler)
313
-
314
- // add extra logic to onZoomStartFun
315
- this.cg.onZoomStartFun = (e) => {
316
- if (opts.onZoomStartFun) opts.onZoomStartFun(e)
317
- this.tooltip.hide()
318
- }
319
-
320
- //for mouse over
321
- /**
322
- * @private
323
- * @type {HTMLCanvasElement|null} */
324
- this.canvasSave = null
325
-
326
- this.selectionRectangleColor = opts.selectionRectangleColor || 'red'
327
- this.selectionRectangleWidthPix = opts.selectionRectangleWidthPix || (() => 4) //(r,zf) => {}
328
-
329
- //
330
- //canvas.addEventListener("keydown", e => { console.log(arguments) });
331
-
332
- //set default globalCompositeOperation
333
- this.defaultGlobalCompositeOperation =
334
- opts.defaultGlobalCompositeOperation || this.cg.ctx.globalCompositeOperation
335
- }
336
-
337
- /**
338
- * @param {number} marginPx
339
- * @returns {import('./Dataset').Envelope}
340
- * @public
341
- */
342
- updateExtentGeo(marginPx = 20) {
343
- return this.cg.updateExtentGeo(marginPx)
344
- }
345
-
346
- /**
347
- * Return the cell HTML info at a given geo position.
348
- * This is usefull for user interactions, to show this info where the user clicks for example.
349
- *
350
- * @param {{x:number,y:number}} posGeo
351
- * @returns {{cell:import('./Dataset').Cell,html:string,resolution:number} | undefined}
352
- * @protected
353
- */
354
- getCellFocusInfo(posGeo) {
355
- //go through the layers, starting from top
356
- const zf = this.getZoomFactor()
357
- for (let i = this.layers.length - 1; i >= 0; i--) {
358
- /** @type {Layer} */
359
- const layer = this.layers[i]
360
- if (!layer.visible) continue
361
- if (!layer.cellInfoHTML) continue
362
- //if (layer.cellInfoHTML === 'none') continue
363
- const dsc = layer.getDatasetComponent(zf)
364
- if (!dsc) continue
365
-
366
- //get cell at mouse position
367
- /** @type {import('./Dataset').Cell|undefined} */
368
- const cell = dsc.getCellFromPosition(posGeo, dsc.getViewCache())
369
- //console.log(cell, dsc.resolution)
370
- if (!cell) return undefined
371
- const html = layer.cellInfoHTML(cell, dsc.getResolution())
372
- if (!html) return undefined
373
- return { cell: cell, html: html, resolution: dsc.getResolution() }
374
- }
375
- }
376
-
377
- //getters and setters
378
-
379
- /** @returns {{x:number,y:number}} */
380
- getGeoCenter() {
381
- return this.cg.getCenter()
382
- }
383
- /** @param {{x:number,y:number}} val @returns {this} */
384
- setGeoCenter(val) {
385
- this.cg.setCenter(val)
386
- return this
387
- }
388
-
389
- /** @returns {number} */
390
- getZoomFactor() {
391
- return this.cg.getZf()
392
- }
393
- /** @param {number} val @returns {this} */
394
- setZoomFactor(val) {
395
- this.cg.setZf(val)
396
- return this
397
- }
398
-
399
- /** @returns {Array.<number>} */
400
- getZoomFactorExtent() {
401
- return this.cg.getZfExtent()
402
- }
403
- /** @param {Array.<number>} val @returns {this} */
404
- setZoomFactorExtent(val) {
405
- this.cg.setZfExtent(val)
406
- return this
407
- }
408
-
409
- /** @returns {string} */
410
- getBackgroundColor() {
411
- return this.cg.backgroundColor
412
- }
413
- /** @param {string} val @returns {this} */
414
- setBackgroundColor(val) {
415
- this.cg.backgroundColor = val
416
- return this
417
- }
418
-
419
- /** @returns {LineLayer | undefined} */
420
- getBoundaryLayer() {
421
- return this.boundaryLayer
422
- }
423
- /** @param {object} opts @returns {this} */
424
- setBoundaryLayer(opts) {
425
- this.boundaryLayer = new LineLayer(opts)
426
- return this
427
- }
428
-
429
- /** @returns {LabelLayer | undefined} */
430
- getLabelLayer() {
431
- return this.labelLayer
432
- }
433
- /** @param {object} opts @returns {this} */
434
- setLabelLayer(opts) {
435
- this.labelLayer = new LabelLayer(opts)
436
- return this
437
- }
438
-
439
- /** @returns {this} */
440
- redraw() {
441
- this.cg.redraw()
442
- return this
443
- }
444
-
445
- /**
446
- * Add a layer to the app.
447
- *
448
- * @param {Dataset} dataset The dataset of the layer
449
- * @param {Array.<import('./Style').Style>} styles The styles of the layer
450
- * @param {{visible?:boolean,minZoom?:number,maxZoom?:number,pixNb?:number,cellInfoHTML?:function(import('./Dataset').Cell):string}} opts The layer options.
451
- * @returns {this}
452
- */
453
- addLayerFromDataset(dataset, styles, opts) {
454
- const lay = new Layer(dataset, styles, opts)
455
- this.layers.push(lay)
456
- return this
457
- }
458
-
459
- //dataset creation
460
-
461
- /**
462
- * Make a local grid dataset.
463
- *
464
- * @param {number} resolution The dataset resolution in geographical unit.
465
- * @param {Array} cells The cells.
466
- * @param {object=} opts The parameters of the dataset.
467
- * @returns {Dataset}
468
- */
469
- makeLGridDataset(resolution, cells, opts) {
470
- return new Dataset([new LGrid(resolution, cells)], [], opts)
471
- }
472
-
473
- /**
474
- * Make a CSV grid dataset.
475
- *
476
- * @param {string} url The URL of the dataset.
477
- * @param {number} resolution The dataset resolution in geographical unit.
478
- * @param {object=} opts The parameters of the dataset.
479
- * @returns {Dataset}
480
- */
481
- makeCSVGridDataset(url, resolution, opts) {
482
- return new Dataset(
483
- [
484
- new CSVGrid(url, resolution, opts).getData(undefined, () => {
485
- this.cg.redraw()
486
- }),
487
- ],
488
- [],
489
- opts
490
- )
491
- }
492
-
493
- /**
494
- * Make a tiled grid dataset.
495
- *
496
- * @param {string} url
497
- * @param {{preprocess?:function(import('./Dataset').Cell):boolean}} opts
498
- * @returns {Dataset}
499
- */
500
- makeTiledGridDataset(url, opts) {
501
- return new Dataset(
502
- [
503
- new TiledGrid(url, this, opts).loadInfo(() => {
504
- this.cg.redraw()
505
- }),
506
- ],
507
- [],
508
- opts
509
- )
510
- }
511
-
512
- //multi scale dataset creation
513
-
514
- /**
515
- * Make a multi scale CSV grid dataset.
516
- *
517
- * @param {Array.<number>} resolutions
518
- * @param {function(number):string} resToURL
519
- * @param {{preprocess?:function(import('./Dataset').Cell):boolean}} opts
520
- * @returns {Dataset}
521
- */
522
- makeMultiScaleCSVGridDataset(resolutions, resToURL, opts) {
523
- return Dataset.make(
524
- resolutions,
525
- (res) =>
526
- new CSVGrid(resToURL(res), res, opts).getData(undefined, () => {
527
- this.cg.redraw()
528
- }),
529
- opts
530
- )
531
- }
532
-
533
- //tiled multiscale
534
-
535
- /**
536
- * Make a multi scale tiled grid dataset.
537
- *
538
- * @param {Array.<number>} resolutions
539
- * @param {function(number):string} resToURL
540
- * @param {{preprocess?:function(import('./Dataset').Cell):boolean}} opts
541
- * @returns {Dataset}
542
- */
543
- makeMultiScaleTiledGridDataset(resolutions, resToURL, opts) {
544
- return Dataset.make(
545
- resolutions,
546
- (res) =>
547
- new TiledGrid(resToURL(res), this, opts).loadInfo(() => {
548
- this.cg.redraw()
549
- }),
550
- opts
551
- )
552
- }
553
-
554
- // direct layer creation
555
-
556
- /**
557
- * Add a layer from a CSV grid dataset.
558
- *
559
- * @param {string} url The URL of the dataset.
560
- * @param {number} resolution The dataset resolution in geographical unit.
561
- * @param {Array.<import('./Style').Style>} styles The styles, ordered in drawing order.
562
- * @param {object=} opts The parameters of the dataset and layer.
563
- * @returns {this}
564
- */
565
- addCSVGridLayer(url, resolution, styles, opts) {
566
- const ds = this.makeCSVGridDataset(url, resolution, opts)
567
- return this.addLayerFromDataset(ds, styles, opts)
568
- }
569
-
570
- /**
571
- *
572
- * @param {string} url
573
- * @param {Array.<import('./Style').Style>} styles
574
- * @param {{visible?:boolean,minZoom?:number,maxZoom?:number,pixNb?:number,cellInfoHTML?:function(import('./Dataset').Cell):string, preprocess?:function(import('./Dataset').Cell):boolean}} opts
575
- * @returns {this}
576
- */
577
- addTiledGridLayer(url, styles, opts) {
578
- const ds = this.makeTiledGridDataset(url, opts)
579
- return this.addLayerFromDataset(ds, styles, opts)
580
- }
581
-
582
- /**
583
- * Add a layer from a CSV grid dataset.
584
- *
585
- * @param {Array.<number>} resolutions
586
- * @param {function(number):string} resToURL
587
- * @param {Array.<import('./Style').Style>} styles The styles, ordered in drawing order.
588
- * @param {object=} opts The parameters of the dataset and layer.
589
- * @returns {this}
590
- */
591
- addMultiScaleCSVGridLayer(resolutions, resToURL, styles, opts) {
592
- const ds = this.makeMultiScaleCSVGridDataset(resolutions, resToURL, opts)
593
- return this.addLayerFromDataset(ds, styles, opts)
594
- }
595
-
596
- /**
597
- * @param {Array.<number>} resolutions
598
- * @param {function(number):string} resToURL
599
- * @param {Array.<import('./Style').Style>} styles
600
- * @param {{visible?:boolean,minZoom?:number,maxZoom?:number,pixNb?:number,cellInfoHTML?:function(import('./Dataset').Cell):string, preprocess?:function(import('./Dataset').Cell):boolean}} opts
601
- * @returns {this}
602
- */
603
- addMultiScaleTiledGridLayer(resolutions, resToURL, styles, opts) {
604
- const ds = this.makeMultiScaleTiledGridDataset(resolutions, resToURL, opts)
605
- return this.addLayerFromDataset(ds, styles, opts)
606
- }
607
-
608
- /**
609
- * Add a background layer to the app.
610
- *
611
- * @param {object} opts
612
- * @returns {this}
613
- */
614
- addBackgroundLayer(opts) {
615
- this.bgLayers.push(new BackgroundLayer(opts))
616
- this.redraw()
617
- return this
618
- }
619
-
620
- /**
621
- * Add a WMS background layer to the app.
622
- *
623
- * @param {object} opts
624
- * @returns {this}
625
- */
626
- addBackgroundLayerWMS(opts) {
627
- this.bgLayers.push(new BackgroundLayerWMS(opts))
628
- this.redraw()
629
- return this
630
- }
631
-
632
- /**
633
- *
634
- * @param {string} id
635
- * @param {object} opts
636
- * @returns {this}
637
- */
638
- addZoomSlider(id, opts) {
639
- this.cg.addZoomSlider(id, opts)
640
- return this
641
- }
642
-
643
- /**
644
- * Adds a set of zoom buttons to the app
645
- *
646
- * @param {object} opts
647
- * @returns {this}
648
- */
649
- addZoomButtons(opts) {
650
- // * opts.id
651
- // * opts.onZoom - custom event handler function
652
- // * opts.x
653
- // * opts.y
654
- // * opts.delta - zoom delta applied on each click
655
-
656
- this.zoomButtons = new ZoomButtons({
657
- app: this,
658
- id: opts?.id || 'gridviz-zoom-buttons',
659
- class: opts?.class,
660
- x: opts?.x,
661
- y: opts?.y,
662
- onZoom: opts?.onZoom,
663
- delta: opts?.delta || 0.2
664
- })
665
-
666
- return this
667
- }
668
-
669
- /**
670
- * Adds a fullscreen toggle button to the app
671
- *
672
- * @param {object} opts
673
- * @returns {this}
674
- */
675
- addFullscreenButton(opts) {
676
- // * opts.app - the gridviz app
677
- // * opts.id
678
- // * opts.x
679
- // * opts.y
680
-
681
- this.fullscreenButton = new FullscreenButton({
682
- app: this,
683
- id: opts?.id || 'gridviz-fullscreen-button',
684
- class: opts?.class,
685
- x: opts?.x,
686
- y: opts?.y
687
- })
688
-
689
- return this
690
- }
691
-
692
- /** @returns {this} */
693
- setViewFromURL() {
694
- this.cg.setViewFromURL()
695
- return this
696
- }
697
-
698
- /**
699
- * @description Add a resize event observer to the Apps container and update the canvas accordingly
700
- * @param {HTMLDivElement} container The App's container element
701
- * @param {HTMLCanvasElement} canvas The App canvas element
702
- * @memberof App
703
- */
704
- defineResizeObserver(container, canvas) {
705
- // listen to resize events
706
- const resizeObserver = new ResizeObserver((entries) => {
707
- // make sure canvas has been built
708
- if (container.clientWidth > 0 && container.clientHeight > 0) {
709
- // make sure we dont exceed loop limit first
710
- // see: https://stackoverflow.com/questions/49384120/resizeobserver-loop-limit-exceeded
711
- window.requestAnimationFrame(() => {
712
- if (!Array.isArray(entries) || !entries.length) {
713
- return
714
- }
715
- // update the app and canvas size
716
- if (this.h !== container.clientHeight || this.w !== container.clientWidth) {
717
- this.h = container.clientHeight
718
- this.w = container.clientWidth
719
- this.cg.h = container.clientHeight
720
- this.cg.w = container.clientWidth
721
- canvas.setAttribute('width', '' + this.w)
722
- canvas.setAttribute('height', '' + this.h)
723
- this.redraw()
724
-
725
- //update button positions
726
- // if (this.zoomButtons) this.zoomButtons.node.style.left = this.w - 50 + 'px'
727
- // if (this.fullscreenButton) this.fullscreenButton.node.style.left = this.w - 50 + 'px'
728
- }
729
- })
730
- }
731
- })
732
-
733
- resizeObserver.observe(container)
734
- }
735
-
736
- /**
737
- * @description Destroy the app and it's event listeners
738
- * This should significantly reduce the memory used when creating and destroying gridviz app instances (for example in leaflet-gridviz)
739
- * @memberof App
740
- */
741
- destroy() {
742
- // clear layers
743
- this.layers = []
744
- this.bgLayers = []
745
-
746
- // remove event listeners from container
747
- this.container.removeEventListener('mouseover', this.mouseOverHandler)
748
- this.container.removeEventListener('mousemove', this.mouseMoveHandler)
749
- this.container.removeEventListener('mouseout', this.mouseOutHandler)
750
-
751
- // remove canvas
752
- this.cg.canvas.remove()
753
-
754
- // remove legend
755
- this.legend?.remove()
756
-
757
- // remove tooltip
758
- this.tooltip.tooltip?.remove()
759
- }
760
- }