gridviz 2.0.28 → 2.0.31

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 (57) hide show
  1. package/dist/gridviz.js +34368 -0
  2. package/dist/gridviz.min.js +1 -1
  3. package/package.json +10 -6
  4. package/src/App.js +654 -0
  5. package/src/BackgroundLayer.js +182 -0
  6. package/src/BackgroundLayerWMS.js +131 -0
  7. package/src/Dataset.js +76 -0
  8. package/src/DatasetComponent.js +103 -0
  9. package/src/GeoCanvas.js +419 -0
  10. package/src/LabelLayer.js +190 -0
  11. package/src/Layer.js +92 -0
  12. package/src/Legend.js +86 -0
  13. package/src/LineLayer.js +150 -0
  14. package/src/Style.js +114 -0
  15. package/src/Tooltip.js +150 -0
  16. package/src/dataset/COGeo.js +24 -0
  17. package/src/dataset/CSVGrid.js +104 -0
  18. package/src/dataset/GeoTIFF.js +127 -0
  19. package/src/dataset/GridTile.js +41 -0
  20. package/src/dataset/ParquetGrid.js +122 -0
  21. package/src/dataset/TiledGrid.js +296 -0
  22. package/src/index.js +177 -0
  23. package/src/legend/ColorCategoryLegend.js +124 -0
  24. package/src/legend/ColorDiscreteLegend.js +105 -0
  25. package/src/legend/ColorLegend.js +125 -0
  26. package/src/legend/SegmentOrientationLegend.js +87 -0
  27. package/src/legend/SegmentWidthLegend.js +97 -0
  28. package/src/legend/SizeLegend.js +141 -0
  29. package/src/style/CompositionStyle.js +300 -0
  30. package/src/style/ContourStyle.js +54 -0
  31. package/src/style/DotDensityStyle.js +156 -0
  32. package/src/style/JoyPlotStyle.js +135 -0
  33. package/src/style/KernelSmoothingOldStyle.js +221 -0
  34. package/src/style/KernelSmoothingStyle.js +115 -0
  35. package/src/style/KernelSmoothingWGLStyle.js +130 -0
  36. package/src/style/LegoStyle.js +112 -0
  37. package/src/style/MosaicStyle.js +107 -0
  38. package/src/style/NinjaStarStyle.js +124 -0
  39. package/src/style/PillarStyle.js +230 -0
  40. package/src/style/SegmentStyle.js +142 -0
  41. package/src/style/ShapeColorSizeStyle.js +126 -0
  42. package/src/style/SideCatStyle.js +172 -0
  43. package/src/style/SideStyle.js +218 -0
  44. package/src/style/SquareColorCatWGLStyle.js +120 -0
  45. package/src/style/SquareColorWGLStyle.js +146 -0
  46. package/src/style/StrokeStyle.js +131 -0
  47. package/src/style/TanakaStyle.js +160 -0
  48. package/src/style/TextStyle.js +132 -0
  49. package/src/utils/Utils.js +35 -0
  50. package/src/utils/WebGLRectangleColoring.js +106 -0
  51. package/src/utils/WebGLSquareColoring.js +118 -0
  52. package/src/utils/WebGLSquareColoringAdvanced.js +230 -0
  53. package/src/utils/WebGLSquareColoringCatAdvanced.js +133 -0
  54. package/src/utils/WebGLSquareColoringKS.js +153 -0
  55. package/src/utils/stretching.js +131 -0
  56. package/src/utils/webGLUtils.js +71 -0
  57. package/src/utils/zoomUtils.js +142 -0
@@ -0,0 +1,156 @@
1
+ //@ts-check
2
+ 'use strict'
3
+
4
+ import { Style } from '../Style.js'
5
+ import { randomNormal } from 'd3-random'
6
+ import { checkWebGLSupport, makeWebGLCanvas } from '../utils/webGLUtils.js'
7
+ import { WebGLSquareColoring } from '../utils/WebGLSquareColoring.js'
8
+ import { color } from 'd3-color'
9
+ import { monitor, monitorDuration } from '../utils/Utils.js'
10
+
11
+ /**
12
+ *
13
+ * @author Julien Gaffuri
14
+ */
15
+ export class DotDensityStyle extends Style {
16
+ /** @param {object} opts */
17
+ constructor(opts) {
18
+ super(opts)
19
+ opts = opts || {}
20
+
21
+ /** The name of the column/attribute of the tabular data where to retrieve the variable for dot number.
22
+ * @type {string} */
23
+ this.nbCol = opts.nbCol
24
+
25
+ /** A function returning the number of dots for a cell value.
26
+ * @type {function(number,number,import("../Style").Stat,number):number} */
27
+ this.nb = opts.nb || ((v, r, s, zf) => (((0.3 * r * r) / (zf * zf)) * v) / s.max)
28
+
29
+ /** The color of the dots. Same color for all dots within a cell.
30
+ * @type {function(import("../Dataset").Cell):string} */
31
+ this.color = opts.color || (() => '#FF5733')
32
+
33
+ /** A function returning the size of the dots, in geo unit.
34
+ * @type {function(number,number):number} */
35
+ this.dotSize = opts.dotSize //|| ((r, zf) => ...
36
+
37
+ /** A function returning the sigma of the distribution from the resolution, in geo unit.
38
+ * @type {function(number,number):number} */
39
+ this.sigma = opts.sigma //|| ((r,zf) => ...
40
+ }
41
+
42
+ /**
43
+ * Draw cells as text.
44
+ *
45
+ * @param {Array.<import("../Dataset").Cell>} cells
46
+ * @param {number} r
47
+ * @param {import("../GeoCanvas").GeoCanvas} cg
48
+ */
49
+ draw(cells, r, cg) {
50
+ if (monitor) monitorDuration('*** DotDensityStyle draw')
51
+
52
+ //filter
53
+ if (this.filter) cells = cells.filter(this.filter)
54
+
55
+ //zoom factor
56
+ const zf = cg.getZf()
57
+
58
+ let stat
59
+ if (this.nbCol) stat = Style.getStatistics(cells, (c) => c[this.nbCol], true)
60
+ if (!stat) return
61
+
62
+ //size of the dots
63
+ const sGeo = this.dotSize ? this.dotSize(r, zf) : 2 * zf
64
+
65
+ //make random function
66
+ const sig = this.sigma ? this.sigma(r, zf) : r * 0.4
67
+ const rand = randomNormal(0, sig)
68
+
69
+ if (monitor) monitorDuration(' preparation')
70
+
71
+ if (checkWebGLSupport()) {
72
+ //create canvas and webgl renderer
73
+ const cvWGL = makeWebGLCanvas(cg.w + '', cg.h + '')
74
+ if (!cvWGL) {
75
+ console.error('No webGL')
76
+ return
77
+ }
78
+
79
+ //create webGL program
80
+ const prog = new WebGLSquareColoring(cvWGL.gl, sGeo / zf)
81
+
82
+ if (monitor) monitorDuration(' webgl creation')
83
+
84
+ const r2 = r / 2
85
+
86
+ let col, offset, nb, cx, cy, cc
87
+ for (let c of cells) {
88
+ //get color
89
+ col = this.color(c)
90
+ if (!col || col === 'none') continue
91
+
92
+ //get offset
93
+ offset = this.offset(c, r, zf)
94
+
95
+ //number of dots
96
+ nb = this.nb(c[this.nbCol], r, stat, zf)
97
+
98
+ //cell center
99
+ cx = c.x + offset.dx + r2
100
+ cy = c.y + offset.dy + r2
101
+
102
+ //convert color
103
+ cc = color(col)
104
+ if (!cc) return
105
+
106
+ //random points
107
+ for (let i = 0; i <= nb; i++)
108
+ prog.addPointData2(cx + rand(), cy + rand(), cc.r, cc.g, cc.b, cc.opacity)
109
+ }
110
+
111
+ if (monitor) monitorDuration(' data preparation')
112
+
113
+ //draw
114
+ prog.draw(cg.getWebGLTransform())
115
+
116
+ if (monitor) monitorDuration(' webgl drawing')
117
+
118
+ //draw in canvas geo
119
+ cg.initCanvasTransform()
120
+ cg.ctx.drawImage(cvWGL.canvas, 0, 0)
121
+
122
+ if (monitor) monitorDuration(' canvas drawing')
123
+ } else {
124
+ //draw with HTML canvas
125
+
126
+ //draw in geo coordinates
127
+ cg.setCanvasTransform()
128
+
129
+ for (let c of cells) {
130
+ //get color
131
+ const col = this.color(c)
132
+ if (!col || col === 'none') continue
133
+ //set color
134
+ cg.ctx.fillStyle = col
135
+
136
+ //get offset
137
+ const offset = this.offset(c, r, zf)
138
+
139
+ //number of dots
140
+ const nb = this.nb(c[this.nbCol], r, stat, zf)
141
+
142
+ //draw random dots
143
+ const cx = c.x + offset.dx + r / 2,
144
+ cy = c.y + offset.dy + r / 2
145
+ for (let i = 0; i <= nb; i++) {
146
+ cg.ctx.fillRect(cx + rand(), cy + rand(), sGeo, sGeo)
147
+ }
148
+ }
149
+ }
150
+
151
+ //update legends
152
+ this.updateLegends({ style: this, r: r, zf: zf })
153
+
154
+ if (monitor) monitorDuration('*** DotDensityStyle end draw')
155
+ }
156
+ }
@@ -0,0 +1,135 @@
1
+ //@ts-check
2
+ 'use strict'
3
+
4
+ import { Style } from '../Style.js'
5
+
6
+ /**
7
+ *
8
+ * @author Julien Gaffuri
9
+ */
10
+ export class JoyPlotStyle extends Style {
11
+ /** @param {object} opts */
12
+ constructor(opts) {
13
+ super(opts)
14
+ opts = opts || {}
15
+
16
+ /** The cell column where to get the value to represent.
17
+ * @type {string} */
18
+ this.heightCol = opts.heightCol
19
+
20
+ /** A function returning the height of a cell.
21
+ * @type {function(number,number,import("../Style").Stat|undefined,number):number} */
22
+ this.height = opts.height || ((v) => Math.sqrt(v))
23
+
24
+ /**
25
+ * @type {function(number,{min:number, max:number},number,number):string} */
26
+ this.lineColor = opts.lineColor || ((y, ys, r, zf) => '#BBB')
27
+ /**
28
+ * @type {function(number,{min:number, max:number},number,number):number} */
29
+ this.lineWidth = opts.lineWidth || ((y, ys, r, zf) => zf)
30
+ /**
31
+ * @type {function(number,{min:number, max:number},number,number):string} */
32
+ this.fillColor = opts.fillColor || ((y, ys, r, zf) => '#c08c5968')
33
+ }
34
+
35
+ /**
36
+ * Draw cells as squares depending on their value.
37
+ *
38
+ * @param {Array.<import("../Dataset").Cell>} cells
39
+ * @param {number} r
40
+ * @param {import("../GeoCanvas").GeoCanvas} cg
41
+ * */
42
+ draw(cells, r, cg) {
43
+ //filter
44
+ if (this.filter) cells = cells.filter(this.filter)
45
+
46
+ cg.ctx.lineJoin = 'round'
47
+
48
+ //zoom factor
49
+ const zf = cg.getZf()
50
+
51
+ //compute statistics
52
+ const stat = Style.getStatistics(cells, (c) => c[this.heightCol], true)
53
+
54
+ //index cells by y and x
55
+ /** @type {object} */
56
+ const ind = {}
57
+ for (const cell of cells) {
58
+ let row = ind[cell.y]
59
+ if (!row) {
60
+ row = {}
61
+ ind[cell.y] = row
62
+ }
63
+ row[cell.x] = this.height(cell[this.heightCol], r, stat, zf)
64
+ }
65
+
66
+ //compute extent
67
+ const e = cg.extGeo
68
+ if (!e) return
69
+ const xMin = Math.floor(e.xMin / r) * r
70
+ const xMax = Math.floor(e.xMax / r) * r
71
+ const yMin = Math.floor(e.yMin / r) * r
72
+ const yMax = Math.floor(e.yMax / r) * r
73
+
74
+ /** @type {{min:number, max:number}} */
75
+ const ys = { min: yMin, max: yMax }
76
+
77
+ //draw in geo coordinates
78
+ cg.setCanvasTransform()
79
+
80
+ //draw lines, row by row, stating from the top
81
+ for (let y = yMax; y >= yMin; y -= r) {
82
+ //get row
83
+ const row = ind[y]
84
+
85
+ //no row
86
+ if (!row) continue
87
+
88
+ //place first point
89
+ cg.ctx.beginPath()
90
+ cg.ctx.moveTo(xMin - r / 2, y)
91
+
92
+ //store the previous height
93
+ /** @type {number|undefined} */
94
+ let hG_
95
+
96
+ //go through the line cells
97
+ for (let x = xMin; x <= xMax; x += r) {
98
+ //get column value
99
+ /** @type {number} */
100
+ let hG = row[x]
101
+ if (!hG) hG = 0
102
+
103
+ if (hG || hG_) {
104
+ //draw line only when at least one of both values is non-null
105
+ //TODO test bezierCurveTo
106
+ cg.ctx.lineTo(x + r / 2, y + hG)
107
+ } else {
108
+ //else move the point
109
+ cg.ctx.moveTo(x + r / 2, y)
110
+ }
111
+ //store the previous value
112
+ hG_ = hG
113
+ }
114
+
115
+ //last point
116
+ if (hG_) cg.ctx.lineTo(xMax + r / 2, y)
117
+
118
+ //draw fill
119
+ const fc = this.fillColor(y, ys, r, zf)
120
+ if (fc && fc != 'none') {
121
+ cg.ctx.fillStyle = fc
122
+ cg.ctx.fill()
123
+ }
124
+
125
+ //draw line
126
+ const lc = this.lineColor(y, ys, r, zf)
127
+ const lw = this.lineWidth(y, ys, r, zf)
128
+ if (lc && lc != 'none' && lw > 0) {
129
+ cg.ctx.strokeStyle = lc
130
+ cg.ctx.lineWidth = lw
131
+ cg.ctx.stroke()
132
+ }
133
+ }
134
+ }
135
+ }
@@ -0,0 +1,221 @@
1
+ //@ts-check
2
+ 'use strict'
3
+
4
+ import { Style } from '../Style.js'
5
+
6
+ /**
7
+ * A style representing the cell as a smoothed layer, to smoothing local variations and show main trends across space.
8
+ *
9
+ * @author Julien Gaffuri
10
+ */
11
+ export class KernelSmoothingStyle extends Style {
12
+ //bandwidth
13
+ // https://observablehq.com/@uwdata/fast-kde
14
+ // https://observablehq.com/d/3127b6d89ada959f
15
+ //TODO use https://github.com/uwdata/fast-kde ?
16
+ //TODO https://observablehq.com/@sahilchinoy/areal-interpolation-iii ?
17
+
18
+ /** @param {object} opts */
19
+ constructor(opts) {
20
+ super(opts)
21
+ opts = opts || {}
22
+
23
+ /** A function specifying the value to consider for each cell. This is the value to smooth.
24
+ * @type {function(import("../Dataset").Cell):number} */
25
+ this.value = opts.value
26
+
27
+ /** The smoothing parameter, in geo unit. The larger, the more smoothed.
28
+ * @type {function(number,number):number}
29
+ */
30
+ this.sigma = opts.sigma // (r, zf)=>...
31
+
32
+ /** The styles to represent the smoothed cells.
33
+ * @type {Array.<Style>}
34
+ */
35
+ this.styles = opts.styles
36
+ }
37
+
38
+ /**
39
+ * Compute the kernel matrix, that is the matrix of the weights.
40
+ * One quadrant is necessary only, since it is symetrical (along both x and y axes).
41
+ * @param {number} s Sigma (in grid pixel !)
42
+ * @returns {Array.<Array<number>>}
43
+ * @private
44
+ */
45
+ getKernelMatrix(s) {
46
+ //the size of the kernel: lets limit that to ~3 times the standard deviation, as an approximation.
47
+ const kernelSize = Math.floor(3.25 * s)
48
+
49
+ //pre-compute coefficient for gaussian computation, to avoid computing them every time.
50
+ const c2 = 2 * s * s
51
+
52
+ /**
53
+ * The gaussian function. TODO expose that function as a parameter, to use other kernels ?
54
+ * @param {number} x
55
+ * @param {number} y
56
+ * @returns {number}
57
+ */
58
+ const gaussian = (x, y) => (c2 != 0 ? Math.exp(-(x * x + y * y) / c2) : x == 0 && y == 0 ? 1 : 0) //dirac case
59
+
60
+ /** @type {Array.<Array<number>>} */
61
+ const kw = []
62
+ for (let wi = 0; wi <= kernelSize; wi++) {
63
+ /** @type {Array<number>} */
64
+ const col = []
65
+ for (let wj = 0; wj <= kernelSize; wj++) {
66
+ //compute weight at wi,wj
67
+ col.push(gaussian(wi, wj))
68
+ }
69
+ kw.push(col)
70
+ }
71
+
72
+ /*
73
+ //debug: show values
74
+ for (let wj = kernelSize-1; wj >=0; wj--) {
75
+ let st = "";
76
+ for (let wi = 0; wi <= kernelSize; wi++) {
77
+ st += " " + Math.floor(kw[wi][wj].w *1000) /1000
78
+ }
79
+ console.log(st)
80
+ }*/
81
+
82
+ return kw
83
+ }
84
+
85
+ /**
86
+ * Compute kernel smoothing of some cells.
87
+ *
88
+ * @private
89
+ * @param {Array.<import("../Dataset").Cell>} cells The cells to be smoothed.
90
+ * @param {import("../Dataset").Envelope} e Geo envelope to consider.
91
+ * @param {number} r Resolution, in geo unit.
92
+ * @param {number} s Sigma (in grid pixel !)
93
+ * @returns {Array.<import("../Dataset").Cell>} The list of cells, including the initial ones and the ones with smoothed values, in "ksmval" property.
94
+ */
95
+ kernelSmoothing(cells, e, r, s) {
96
+ //filter
97
+ if (this.filter) cells = cells.filter(this.filter)
98
+
99
+ const wThr = 0.01 //gaussian weights below this value will be ignored
100
+
101
+ //compute extent, in grid position
102
+ const xMin = Math.floor(e.xMin / r) * r
103
+ const xMax = Math.floor(e.xMax / r) * r
104
+ const yMin = Math.floor(e.yMin / r) * r
105
+ const yMax = Math.floor(e.yMax / r) * r
106
+
107
+ //compute matrix dimensions
108
+ const nbX = (xMax - xMin) / r + 1
109
+ const nbY = (yMax - yMin) / r + 1
110
+
111
+ //initialise smoothed value to 0
112
+ for (const c of cells) c['ksmval'] = 0
113
+
114
+ //make matrix. add input cells by i/j (grid position)
115
+ const index = {}
116
+ for (const c of cells) {
117
+ // i,j of the cell
118
+ const i = Math.floor((c.x - xMin) / r)
119
+ const j = Math.floor((c.y - yMin) / r)
120
+ if (!index[i]) index[i] = {}
121
+ index[i][j] = c
122
+ }
123
+
124
+ /** Check that the cell i,j is within the frame */
125
+ const isWithinFrame = (i, j) => i >= 0 && i < nbX && j >= 0 && j < nbY
126
+
127
+ //get kernel matrix
128
+ /** @type {Array.<Array.<number>>} */
129
+ const kernelMatrix = this.getKernelMatrix(s)
130
+ const kernelSize = kernelMatrix.length
131
+
132
+ //compute summ of the weights over entire kernel window
133
+ /** @type {number} */
134
+ let sumWeights = 0
135
+ for (let ki = 1 - kernelSize; ki < kernelSize; ki++) {
136
+ for (let kj = 1 - kernelSize; kj < kernelSize; kj++) {
137
+ let w = kernelMatrix[Math.abs(ki)][Math.abs(kj)]
138
+ if (w < wThr) continue
139
+ sumWeights += w
140
+ }
141
+ }
142
+
143
+ /** Add v as a contribution to the cell i,j */
144
+ const addContributionTo = (i, j, v) => {
145
+ //get cell at (i,j)
146
+ const c_ = index[i] ? index[i][j] : undefined
147
+
148
+ if (c_) {
149
+ //cell exists: add contribution
150
+ if (c_.ksmval) c_.ksmval += v
151
+ else c_.ksmval = v
152
+ } else {
153
+ //cell does not exist: create a new one with the smoothed value
154
+ if (!index[i]) index[i] = {}
155
+ index[i][j] = { x: xMin + i * r, y: yMin + j * r, ksmval: v }
156
+ }
157
+ }
158
+
159
+ //compute smoothing, input cell by input cell
160
+ for (const c of cells) {
161
+ /** get value of cell c
162
+ * @type {number} */
163
+ const val = this.value(c)
164
+ if (!val) continue
165
+
166
+ //cell matrix coordinates
167
+ const i = Math.floor((c.x - xMin) / r)
168
+ const j = Math.floor((c.y - yMin) / r)
169
+
170
+ //add contributions to smoothed values
171
+ for (let ki = 1 - kernelSize; ki < kernelSize; ki++) {
172
+ for (let kj = 1 - kernelSize; kj < kernelSize; kj++) {
173
+ //check cell is within the frame
174
+ if (!isWithinFrame(i + ki, j + kj)) continue
175
+
176
+ //get contribution (ki,kj)
177
+ let w = kernelMatrix[Math.abs(ki)][Math.abs(kj)]
178
+ if (!w || w < wThr) continue
179
+ let v = w * val
180
+ if (!v) continue
181
+ v /= sumWeights
182
+ if (!v) continue
183
+
184
+ //add contribution
185
+ addContributionTo(i + ki, j + kj, v)
186
+ }
187
+ }
188
+ }
189
+
190
+ //make output list
191
+ const out = []
192
+ for (let i of Object.keys(index)) for (const j of Object.keys(index[i])) out.push(index[i][j])
193
+
194
+ return out
195
+ }
196
+
197
+ /**
198
+ * Draw the smoothed cells depending on the list of styles specified.
199
+ *
200
+ * @param {Array.<import("../Dataset").Cell>} cells
201
+ * @param {number} r
202
+ * @param {import("../GeoCanvas").GeoCanvas} cg
203
+ */
204
+ draw(cells, r, cg) {
205
+ if (!cells || cells.length == 0) return
206
+
207
+ //get smoothing param in geo unit
208
+ /** @type {number} */
209
+ const sG = this.sigma(r, cg.zf)
210
+
211
+ //apply kernel smoothing
212
+ cells = this.kernelSmoothing(cells, cg.extGeo, r, sG / r)
213
+
214
+ //draw smoothed cells from styles
215
+ for (let s of this.styles) s.draw(cells, r, cg)
216
+
217
+ //update legends
218
+ //for (let s of this.styles)
219
+ // s.updateLegends({ style: s, r: r, zf: cg.getZf() });
220
+ }
221
+ }
@@ -0,0 +1,115 @@
1
+ //@ts-check
2
+ 'use strict'
3
+
4
+ import { Style } from '../Style.js'
5
+ import { density2d } from 'fast-kde'
6
+
7
+ /**
8
+ * A style representing the cell as a smoothed layer, to smoothing local variations and show main trends across space.
9
+ *
10
+ * @author Julien Gaffuri
11
+ */
12
+ export class KernelSmoothingStyle extends Style {
13
+ // https://observablehq.com/d/5dd1cb5e4d21c021
14
+ // https://observablehq.com/@uwdata/fast-kde
15
+ // https://observablehq.com/d/3127b6d89ada959f
16
+ //TODO https://observablehq.com/@sahilchinoy/areal-interpolation-iii ?
17
+
18
+ /** @param {object} opts */
19
+ constructor(opts) {
20
+ super(opts)
21
+ opts = opts || {}
22
+
23
+ /** A function returning the value to consider for each cell. This is the value to be smoothed.
24
+ * @type {function(import("../Dataset").Cell):number} */
25
+ this.value = opts.value
26
+
27
+ /** The smoothing parameter, in geo unit. The larger, the more smoothed.
28
+ * @type {function(number,number):number}
29
+ */
30
+ this.sigma = opts.sigma // (r, zf)=>...
31
+
32
+ /** A factor to adjust the smoothed grid resolution.
33
+ * When set to 1, the smoothed grid is exactly the screen resolution.
34
+ * Set to 2 to degrade the resolution to a factor 2.
35
+ * The higher, the more pixelised and the faster to compute.
36
+ * @type { number }
37
+ */
38
+ this.factor = opts.factor || 2
39
+
40
+ /** A filter function to filter the smoothed cells based on their smoothed value.
41
+ * Return true to keep the cell, false otherwise.
42
+ * @type { function(number):boolean }
43
+ */
44
+ this.filterSm = opts.filterSm
45
+
46
+ /** The name of the attribute where the smoothed value is stored in the output smoothed grid.
47
+ * @type { string }
48
+ */
49
+ this.sCol = opts.sCol || 'ksmval'
50
+
51
+ /** The styles to represent the smoothed grid.
52
+ * @type {Array.<Style>}
53
+ */
54
+ this.styles = opts.styles || []
55
+ }
56
+
57
+ /**
58
+ * Draw the smoothed cells depending on the list of styles specified.
59
+ *
60
+ * @param {Array.<import("../Dataset").Cell>} cells
61
+ * @param {number} r
62
+ * @param {import("../GeoCanvas").GeoCanvas} cg
63
+ */
64
+ draw(cells, r, cg) {
65
+ //filter
66
+ if (this.filter) cells = cells.filter(this.filter)
67
+
68
+ if (!cells || cells.length == 0) return
69
+
70
+ //get smoothing param in geo unit
71
+ /** @type {number} */
72
+ const sG = this.sigma(r, cg.zf)
73
+
74
+ //compute smoothed grid dimensions
75
+ const nbX = Math.ceil(cg.w / this.factor)
76
+ const nbY = Math.ceil(cg.h / this.factor)
77
+ //compute smoothed grid geo extent
78
+ const e_ = [
79
+ [cg.pixToGeoX(0), cg.pixToGeoX(nbX * this.factor)],
80
+ [cg.pixToGeoY(nbY * this.factor), cg.pixToGeoY(0)],
81
+ ]
82
+
83
+ //compute smoothed grid
84
+ let g = density2d(cells, {
85
+ x: (c) => c.x + r / 2,
86
+ y: (c) => c.y + r / 2,
87
+ weight: (c) => this.value(c),
88
+ bins: [nbX, nbY],
89
+ bandwidth: sG,
90
+ extent: e_,
91
+ }).grid()
92
+
93
+ //compute the resolution of the smoothed grid
94
+ const resSmoothed = (e_[0][1] - e_[0][0]) / nbX
95
+
96
+ //make smoothed cells
97
+ cells = []
98
+ for (let ind = 0; ind < g.length; ind++) {
99
+ const v = g[ind]
100
+ if (this.filterSm && !this.filterSm(v)) continue
101
+ const row = Math.floor(ind / nbX)
102
+ const col = ind % nbX
103
+ const c = { x: e_[0][0] + col * resSmoothed, y: e_[1][0] + row * resSmoothed }
104
+ c[this.sCol] = v
105
+ cells.push(c)
106
+ }
107
+
108
+ //draw smoothed cells from styles
109
+ for (let s of this.styles) s.draw(cells, resSmoothed, cg)
110
+
111
+ //update legends
112
+ //for (let s of this.styles)
113
+ // s.updateLegends({ style: s, r: r, zf: cg.getZf() });
114
+ }
115
+ }