gridviz 3.1.0 → 3.1.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 (46) hide show
  1. package/README.md +18 -15
  2. package/dist/gridviz.js +3829 -2623
  3. package/dist/gridviz.min.js +1 -1
  4. package/package.json +5 -3
  5. package/src/button/Button.js +77 -68
  6. package/src/button/FullscreenButton.js +123 -123
  7. package/src/button/ZoomButtons.js +109 -104
  8. package/src/core/GeoCanvas.js +15 -0
  9. package/src/core/Legend.js +267 -245
  10. package/src/core/Map.js +819 -670
  11. package/src/core/Tooltip.js +191 -185
  12. package/src/index.js +82 -79
  13. package/src/layer/GridLayer.js +4 -3
  14. package/src/layer/LabelLayer.js +7 -4
  15. package/src/legend/ColorCategoryLegend.js +127 -125
  16. package/src/legend/ColorDiscreteLegend.js +116 -114
  17. package/src/legend/ColorLegend.js +33 -11
  18. package/src/legend/OrientationLegend.js +94 -92
  19. package/src/legend/SizeLegend.js +8 -1
  20. package/src/legend/TernaryLegend.js +317 -314
  21. package/src/legend/VennLegend.js +202 -0
  22. package/src/style/CircleColorSizeWebGLStyle.js +249 -0
  23. package/src/style/CompositionStyle.js +1 -1
  24. package/src/style/DotDensityStyle.js +8 -4
  25. package/src/style/ImageStyle.js +1 -1
  26. package/src/style/Interpolator.js +283 -259
  27. package/src/style/IsoFenceStyle.js +1 -1
  28. package/src/style/LegoStyle.js +6 -5
  29. package/src/style/MosaicStyle.js +1 -1
  30. package/src/style/NinjaStarStyle.js +1 -1
  31. package/src/style/PillarStyle.js +1 -1
  32. package/src/style/RandomWalkInterpolator.js +213 -0
  33. package/src/style/ShadingRayStyle.js +16 -2
  34. package/src/style/ShadingStyle.js +80 -72
  35. package/src/style/ShapeColorSizeStyle.js +18 -2
  36. package/src/style/SideCategoryStyle.js +1 -1
  37. package/src/style/SideStyle.js +1 -1
  38. package/src/style/SideTanakaStyle.js +1 -1
  39. package/src/style/SquareColorCategoryWebGLStyle.js +71 -20
  40. package/src/style/SquareColorWebGLStyle.js +7 -4
  41. package/src/style/StrokeStyle.js +1 -1
  42. package/src/style/TextStyle.js +1 -1
  43. package/src/style/TimeSeriesStyle.js +14 -7
  44. package/src/utils/WebGLSquareColoringAdvanced.js +47 -9
  45. package/src/utils/scale.js +20 -0
  46. package/src/utils/webGLUtils.js +52 -0
@@ -2,7 +2,14 @@
2
2
  'use strict'
3
3
 
4
4
  import { Style } from '../core/Style.js'
5
- import { getSharedWebGLCanvas, getProgram, getBuffer, bufferOwner } from '../utils/webGLUtils.js'
5
+ import {
6
+ getSharedWebGLCanvas,
7
+ getProgram,
8
+ getBuffer,
9
+ bufferOwner,
10
+ drawSquaresAsTriangles,
11
+ pointsToSquares,
12
+ } from '../utils/webGLUtils.js'
6
13
  import { color } from 'd3-color'
7
14
 
8
15
  const VERTEX_SHADER = `
@@ -17,6 +24,19 @@ const VERTEX_SHADER = `
17
24
  vi = i;
18
25
  }`
19
26
 
27
+ // the same, for the large squares: they are drawn as triangles, whose corners are placed from the centre (see drawSquaresAsTriangles)
28
+ const VERTEX_SHADER_TRIANGLES = `
29
+ attribute vec2 pos;
30
+ attribute vec2 corner;
31
+ uniform float halfSizeGeo;
32
+ uniform mat3 mat;
33
+ attribute float i;
34
+ varying float vi;
35
+ void main() {
36
+ gl_Position = vec4(mat * vec3(pos + corner * halfSizeGeo, 1.0), 1.0);
37
+ vi = i;
38
+ }`
39
+
20
40
  const FRAGMENT_SHADER = `
21
41
  precision highp float;
22
42
  varying float vi;
@@ -31,6 +51,7 @@ const FRAGMENT_SHADER = `
31
51
  // names of the buffers this style fills in the shared context (getBuffer)
32
52
  const POSITION_BUFFER = 'SquareColorCategoryWebGLStyle.pos'
33
53
  const INDEX_BUFFER = 'SquareColorCategoryWebGLStyle.i'
54
+ const CORNER_BUFFER = 'SquareColorCategoryWebGLStyle.corner'
34
55
 
35
56
  /**
36
57
  * Style based on webGL
@@ -79,8 +100,10 @@ export class SquareColorCategoryWebGLStyle extends Style {
79
100
  this.gl = undefined
80
101
  this.program = undefined
81
102
  this.lutTexture = undefined
82
- /** Number of points in the shared buffers, for drawArrays */
103
+ /** Number of cells in the shared buffers, for drawArrays */
83
104
  this.pointsNb = 0
105
+ /** Whether the squares of the shared buffers are drawn as triangles (large squares) or points @private */
106
+ this.asTriangles = false
84
107
 
85
108
  this.x = undefined
86
109
  this.y = undefined
@@ -94,17 +117,19 @@ export class SquareColorCategoryWebGLStyle extends Style {
94
117
  * Binds this style's program, colour lookup texture and uniforms in the shared WebGL
95
118
  * context (getSharedWebGLCanvas): compiled once per context, so a new style instance
96
119
  * (e.g. a setting change rebuilding the style) costs no new context or program.
120
+ * @param {boolean} asTriangles Whether the squares are large, and drawn as triangles instead of points (see drawSquaresAsTriangles).
97
121
  * @returns {boolean} true when the context is new to this style (its data must be re-uploaded)
98
122
  */
99
- init(w, h) {
123
+ init(w, h, asTriangles = false) {
100
124
  this.cvWGL = getSharedWebGLCanvas(w, h)
101
125
  const gl = this.cvWGL.gl
102
126
  const contextChanged = this.gl !== gl
103
127
  if (contextChanged) {
104
128
  this.gl = gl
105
- this.program = getProgram(gl, VERTEX_SHADER, FRAGMENT_SHADER)
106
129
  this.lutTexture = undefined
107
130
  }
131
+ //programs are compiled once per context: choosing one each time costs nothing
132
+ this.program = getProgram(gl, asTriangles ? VERTEX_SHADER_TRIANGLES : VERTEX_SHADER, FRAGMENT_SHADER)
108
133
  gl.useProgram(this.program)
109
134
  this.bindColors()
110
135
  return contextChanged
@@ -143,10 +168,12 @@ export class SquareColorCategoryWebGLStyle extends Style {
143
168
  * Fills the shared position and colour index buffers with the cells to draw.
144
169
  * @returns {number} the number of points uploaded (cells without a category are left out)
145
170
  */
146
- bindVertices(cells, resolution, z, viewScale) {
171
+ bindVertices(cells, resolution, z, viewScale, view, asTriangles = false) {
147
172
  const gl = this.cvWGL.gl
148
- //add vertice and fragment data
173
+ //add vertice and fragment data. The positions are relative to the center of the view, see GeoCanvas.getWebGLTransformCentered
149
174
  const r2 = resolution / 2
175
+ const vx = view.x,
176
+ vy = view.y
150
177
  let c,
151
178
  nb = cells.length
152
179
  const verticesBuffer = new Float32Array(2 * nb)
@@ -167,16 +194,26 @@ export class SquareColorCategoryWebGLStyle extends Style {
167
194
  continue
168
195
  }
169
196
  if (shown && !shown[+i_]) continue
170
- verticesBuffer[2 * n] = c.x + r2
171
- verticesBuffer[2 * n + 1] = c.y + r2
197
+ verticesBuffer[2 * n] = c.x + r2 - vx
198
+ verticesBuffer[2 * n + 1] = c.y + r2 - vy
172
199
  iBuffer[n] = +i_
173
200
  n++
174
201
  }
175
202
 
203
+ let positions = verticesBuffer.subarray(0, 2 * n)
204
+ let indices = iBuffer.subarray(0, n)
205
+ if (asTriangles) {
206
+ //six vertices for each square
207
+ const sq = pointsToSquares(positions, n, [indices])
208
+ positions = sq.positions
209
+ indices = sq.values[0]
210
+ gl.bindBuffer(gl.ARRAY_BUFFER, getBuffer(gl, CORNER_BUFFER))
211
+ gl.bufferData(gl.ARRAY_BUFFER, sq.corners, gl.DYNAMIC_DRAW)
212
+ }
176
213
  gl.bindBuffer(gl.ARRAY_BUFFER, getBuffer(gl, POSITION_BUFFER))
177
- gl.bufferData(gl.ARRAY_BUFFER, verticesBuffer.subarray(0, 2 * n), gl.DYNAMIC_DRAW)
214
+ gl.bufferData(gl.ARRAY_BUFFER, positions, gl.DYNAMIC_DRAW)
178
215
  gl.bindBuffer(gl.ARRAY_BUFFER, getBuffer(gl, INDEX_BUFFER))
179
- gl.bufferData(gl.ARRAY_BUFFER, iBuffer.subarray(0, n), gl.DYNAMIC_DRAW)
216
+ gl.bufferData(gl.ARRAY_BUFFER, indices, gl.DYNAMIC_DRAW)
180
217
  bufferOwner(gl, POSITION_BUFFER, this)
181
218
  return n
182
219
  }
@@ -192,6 +229,12 @@ export class SquareColorCategoryWebGLStyle extends Style {
192
229
  gl.bindBuffer(gl.ARRAY_BUFFER, getBuffer(gl, INDEX_BUFFER))
193
230
  gl.vertexAttribPointer(i, 1, gl.FLOAT, false, 0, 0)
194
231
  gl.enableVertexAttribArray(i)
232
+ if (this.asTriangles) {
233
+ const corner = gl.getAttribLocation(this.program, 'corner')
234
+ gl.bindBuffer(gl.ARRAY_BUFFER, getBuffer(gl, CORNER_BUFFER))
235
+ gl.vertexAttribPointer(corner, 2, gl.FLOAT, false, 0, 0)
236
+ gl.enableVertexAttribArray(corner)
237
+ }
195
238
  }
196
239
 
197
240
  // check if the vertices have to be bound again
@@ -220,14 +263,19 @@ export class SquareColorCategoryWebGLStyle extends Style {
220
263
  //get view scale
221
264
  const viewScale = this.viewScale ? this.viewScale(cells, resolution, z) : undefined
222
265
 
223
- //bind this style's program in the shared WebGL context
224
- const contextChanged = this.init(geoCanvas.w, geoCanvas.h)
266
+ //size of the squares
267
+ const sizeGeo = this.size ? this.size(resolution, z) : resolution + 0.2 * z
268
+ const sizePix = sizeGeo / z
269
+
270
+ //bind this style's program in the shared WebGL context. Large squares are drawn as triangles, small ones as points.
271
+ const asTriangles = drawSquaresAsTriangles(getSharedWebGLCanvas(geoCanvas.w, geoCanvas.h).gl, sizePix)
272
+ const contextChanged = this.init(geoCanvas.w, geoCanvas.h, asTriangles)
225
273
  const gl = this.cvWGL.gl
226
274
  const canvas = this.cvWGL.canvas
227
275
 
228
- //bind sizePix
229
- const sizePix = this.size ? this.size(resolution, z) / z : resolution / z + 0.2
230
- gl.uniform1f(gl.getUniformLocation(this.program, 'sizePix'), 1.0 * sizePix)
276
+ //bind the size
277
+ if (asTriangles) gl.uniform1f(gl.getUniformLocation(this.program, 'halfSizeGeo'), sizeGeo / 2)
278
+ else gl.uniform1f(gl.getUniformLocation(this.program, 'sizePix'), 1.0 * sizePix)
231
279
 
232
280
  // mapContentChanged() is a cheap proxy for "did the content change", based on view
233
281
  // x/y/z and cell count alone. The upload is also needed when the shared buffers
@@ -236,16 +284,18 @@ export class SquareColorCategoryWebGLStyle extends Style {
236
284
  const highlightKey = this.highlightColors ? this.highlightColors.join('|') : ''
237
285
  const highlightChanged = highlightKey !== this.highlightKey
238
286
  this.highlightKey = highlightKey
239
- const contentChanged = this.mapContentChanged(geoCanvas.view, cells.length) || highlightChanged
287
+ const shapeChanged = asTriangles !== this.asTriangles
288
+ this.asTriangles = asTriangles
289
+ const contentChanged = this.mapContentChanged(geoCanvas.view, cells.length) || highlightChanged || shapeChanged
240
290
  if (contentChanged || contextChanged || bufferOwner(gl, POSITION_BUFFER) !== this)
241
- this.pointsNb = this.bindVertices(cells, resolution, z, viewScale)
291
+ this.pointsNb = this.bindVertices(cells, resolution, z, viewScale, geoCanvas.view, asTriangles)
242
292
  this.bindAttributes()
243
293
 
244
294
  //transformation
245
295
  gl.uniformMatrix3fv(
246
296
  gl.getUniformLocation(this.program, 'mat'),
247
297
  false,
248
- new Float32Array(geoCanvas.getWebGLTransform())
298
+ new Float32Array(geoCanvas.getWebGLTransformCentered())
249
299
  )
250
300
 
251
301
  // Clear the color buffer bit
@@ -254,14 +304,15 @@ export class SquareColorCategoryWebGLStyle extends Style {
254
304
 
255
305
  // only the points actually uploaded: cells without a category are left out, and
256
306
  // drawing past the end of the buffer is a WebGL error that draws nothing at all
257
- gl.drawArrays(gl.POINTS, 0, this.pointsNb)
307
+ if (asTriangles) gl.drawArrays(gl.TRIANGLES, 0, 6 * this.pointsNb)
308
+ else gl.drawArrays(gl.POINTS, 0, this.pointsNb)
258
309
 
259
310
  //draw in canvas geo
260
311
  geoCanvas.initCanvasTransform()
261
312
  geoCanvas.offscreenCtx.drawImage(canvas, 0, 0)
262
313
 
263
314
  //update legends
264
- this.updateLegends({ style: this, resolution: resolution, z: z, viewScale: viewScale })
315
+ this.updateLegends({ style: this, resolution: resolution, z: z, viewScale: viewScale, cells: cells })
265
316
  }
266
317
  }
267
318
 
@@ -93,13 +93,16 @@ export class SquareColorWebGLStyle extends Style {
93
93
  }
94
94
 
95
95
  //add vertice and fragment data
96
+ //(the positions are relative to the center of the view, see GeoCanvas.getWebGLTransformCentered)
96
97
  const r2 = resolution / 2
98
+ const vx = geoCanvas.view.x
99
+ const vy = geoCanvas.view.y
97
100
  const verticesBuffer = []
98
101
  const tBuffer = []
99
102
  for (let cell of cells) {
100
103
  const t = this.tFun(cell, resolution, z, viewScale)
101
104
  if (t == null || t == undefined) continue
102
- verticesBuffer.push(cell.x + r2, cell.y + r2)
105
+ verticesBuffer.push(cell.x + r2 - vx, cell.y + r2 - vy)
103
106
  tBuffer.push(t > 1 ? 1 : t < 0 ? 0 : t)
104
107
  }
105
108
 
@@ -110,10 +113,10 @@ export class SquareColorWebGLStyle extends Style {
110
113
  const op = this.opacity ? this.opacity(resolution, z) : undefined
111
114
 
112
115
  //
113
- const wgp = new WebGLSquareColoringAdvanced(cvWGL.gl, this.colors, this.stretching, sizeGeo / z, op)
116
+ const wgp = new WebGLSquareColoringAdvanced(cvWGL.gl, this.colors, this.stretching, sizeGeo / z, op, sizeGeo)
114
117
 
115
118
  //draw
116
- wgp.draw(verticesBuffer, tBuffer, geoCanvas.getWebGLTransform())
119
+ wgp.draw(verticesBuffer, tBuffer, geoCanvas.getWebGLTransformCentered())
117
120
 
118
121
  // draw in canvas geo
119
122
  // NOTE: drawing each tile this way is very inefficient. WebGL is best used with fewer, heavier/larger draw calls.
@@ -121,6 +124,6 @@ export class SquareColorWebGLStyle extends Style {
121
124
  geoCanvas.offscreenCtx.drawImage(cvWGL.canvas, 0, 0)
122
125
 
123
126
  //update legends
124
- this.updateLegends({ style: this, resolution: resolution, z: z, viewScale: viewScale })
127
+ this.updateLegends({ style: this, resolution: resolution, z: z, viewScale: viewScale, cells: cells })
125
128
  }
126
129
  }
@@ -130,6 +130,6 @@ export class StrokeStyle extends Style {
130
130
  }
131
131
 
132
132
  //update legends
133
- this.updateLegends({ style: this, resolution: resolution, z: z, viewScale: viewScale })
133
+ this.updateLegends({ style: this, resolution: resolution, z: z, viewScale: viewScale, cells: cells })
134
134
  }
135
135
  }
@@ -88,7 +88,7 @@ export class TextStyle extends Style {
88
88
  }
89
89
 
90
90
  //update legends
91
- this.updateLegends({ style: this, resolution: resolution, z: z, viewScale: viewScale })
91
+ this.updateLegends({ style: this, resolution: resolution, z: z, viewScale: viewScale, cells: cells })
92
92
  }
93
93
 
94
94
  /**
@@ -44,7 +44,7 @@ export class TimeSeriesStyle extends Style {
44
44
 
45
45
  /** A function returning the width of the line, in geo unit
46
46
  * @type {function(import('../core/Dataset.js').Cell, number, number, object):number} */
47
- this.lineWidth = opts.lineWidth || ((v, r, s, z) => 1.5 * z)
47
+ this.lineWidth = opts.lineWidth || ((c, r, z) => 1.5 * z)
48
48
 
49
49
  /** A function returning the color of the chart.
50
50
  * @type {function(import('../core/Dataset.js').Cell, number, number, object):string} */
@@ -69,11 +69,18 @@ export class TimeSeriesStyle extends Style {
69
69
  //get view scale
70
70
  const viewScale = this.viewScale ? this.viewScale(cells, resolution, z) : undefined
71
71
 
72
+ //the number value of a cell for a column of the time series, undefined for no data.
73
+ //The values of a CSV file are texts: they have to be converted, or "9" is above "10000".
74
+ const getValue = (c, t) => {
75
+ const val = c[t]
76
+ return this.noData(val) ? undefined : +val
77
+ }
78
+
72
79
  //compute cell amplitude
73
80
  const getAmplitude = (c) => {
74
81
  let min, max
75
82
  for (let t of this.ts) {
76
- const val = c[t]
83
+ const val = getValue(c, t)
77
84
  if (val == undefined) continue
78
85
  if (min == undefined || val < min) min = val
79
86
  if (max == undefined || val > max) max = val
@@ -135,7 +142,7 @@ export class TimeSeriesStyle extends Style {
135
142
  } else if (anchY === 'bottom') {
136
143
  //get min
137
144
  for (let t of this.ts) {
138
- const val = +c[t]
145
+ const val = getValue(c, t)
139
146
  if (val == undefined) continue
140
147
  if (val0 == undefined || val < val0) val0 = val
141
148
  }
@@ -143,7 +150,7 @@ export class TimeSeriesStyle extends Style {
143
150
  } else if (anchY === 'top') {
144
151
  //get max
145
152
  for (let t of this.ts) {
146
- const val = +c[t]
153
+ const val = getValue(c, t)
147
154
  if (val == undefined) continue
148
155
  if (val0 == undefined || val > val0) val0 = val
149
156
  }
@@ -152,12 +159,12 @@ export class TimeSeriesStyle extends Style {
152
159
  //get min and max
153
160
  let min, max
154
161
  for (let t of this.ts) {
155
- const val = c[t]
162
+ const val = getValue(c, t)
156
163
  if (val == undefined) continue
157
164
  if (min == undefined || val < min) min = val
158
165
  if (max == undefined || val > max) max = val
159
166
  }
160
- val0 = (+max + +min) * 0.5
167
+ val0 = (max + min) * 0.5
161
168
  y0 = resolution / 2
162
169
  } else {
163
170
  console.log('Unexpected anchorModeY: ' + anchY)
@@ -217,6 +224,6 @@ export class TimeSeriesStyle extends Style {
217
224
  }
218
225
 
219
226
  //update legend, if any
220
- this.updateLegends({ style: this, resolution: resolution, z: z, viewScale: viewScale })
227
+ this.updateLegends({ style: this, resolution: resolution, z: z, viewScale: viewScale, cells: cells })
221
228
  }
222
229
  }
@@ -1,7 +1,7 @@
1
1
  //@ts-check
2
2
  'use strict'
3
3
 
4
- import { getProgram, getBuffer } from './webGLUtils.js'
4
+ import { getProgram, getBuffer, drawSquaresAsTriangles, pointsToSquares } from './webGLUtils.js'
5
5
  import { color } from 'd3-color'
6
6
 
7
7
  const VERTEX_SHADER = `
@@ -19,6 +19,22 @@ const VERTEX_SHADER = `
19
19
  }
20
20
  `
21
21
 
22
+ // the same, for the large squares: they are drawn as triangles, whose corners are placed from the centre (see drawSquaresAsTriangles)
23
+ const VERTEX_SHADER_TRIANGLES = `
24
+ attribute vec2 pos;
25
+ attribute vec2 corner;
26
+ uniform float halfSizeGeo;
27
+ uniform mat3 mat;
28
+
29
+ attribute float t;
30
+ varying float vt;
31
+
32
+ void main() {
33
+ gl_Position = vec4(mat * vec3(pos + corner * halfSizeGeo, 1.0), 1.0);
34
+ vt = t;
35
+ }
36
+ `
37
+
22
38
  /**
23
39
  * Everything to easily draw colored squares with webGL.
24
40
  * All the same size, but different fill color.
@@ -38,10 +54,15 @@ export class WebGLSquareColoringAdvanced {
38
54
  * @param {{fun:string,alpha:number}} stretching
39
55
  * @param {number} sizePix
40
56
  * @param {number|undefined} globalOpacity
57
+ * @param {number|undefined} sizeGeo The size of the squares, in geographical unit. Needed to draw the large squares (see drawSquaresAsTriangles).
41
58
  */
42
- constructor(gl, colors, stretching, sizePix = 10, globalOpacity = undefined) {
59
+ constructor(gl, colors, stretching, sizePix = 10, globalOpacity = undefined, sizeGeo = undefined) {
43
60
  /** @type {WebGLRenderingContext} */
44
61
  this.gl = gl
62
+
63
+ /** Large squares are drawn as triangles: a point is limited in size by the GPU, and dropped when its centre is out of the view.
64
+ * @type {boolean} */
65
+ this.asTriangles = sizeGeo !== undefined && drawSquaresAsTriangles(gl, sizePix)
45
66
  //gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, true);
46
67
  //gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
47
68
  //gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
@@ -154,14 +175,14 @@ export class WebGLSquareColoringAdvanced {
154
175
 
155
176
  // compiled once per context and fragment source (colours, stretching), then reused
156
177
  /** @type {WebGLProgram} */
157
- this.program = getProgram(gl, VERTEX_SHADER, fshString)
178
+ this.program = getProgram(gl, this.asTriangles ? VERTEX_SHADER_TRIANGLES : VERTEX_SHADER, fshString)
158
179
  gl.useProgram(this.program)
159
180
 
160
181
  //set uniforms
161
182
 
162
- //sizePix
163
- //TODO: bug here. Seems to be limited to some threshold value (around 250).
164
- gl.uniform1f(gl.getUniformLocation(this.program, 'sizePix'), 1.0 * sizePix)
183
+ //size of the squares
184
+ if (this.asTriangles) gl.uniform1f(gl.getUniformLocation(this.program, 'halfSizeGeo'), sizeGeo / 2)
185
+ else gl.uniform1f(gl.getUniformLocation(this.program, 'sizePix'), 1.0 * sizePix)
165
186
 
166
187
  //stretching alpha factor
167
188
  gl.uniform1f(gl.getUniformLocation(this.program, 'alpha'), stretching ? 1.0 * stretching.alpha : 0.0)
@@ -186,9 +207,25 @@ export class WebGLSquareColoringAdvanced {
186
207
 
187
208
  gl.useProgram(program)
188
209
 
210
+ //squares as triangles, when they are large: six vertices for each one
211
+ let n = verticesBuffer.length / 2
212
+ let vertices = new Float32Array(verticesBuffer)
213
+ let tValues = new Float32Array(tBuffer)
214
+ if (this.asTriangles) {
215
+ const sq = pointsToSquares(verticesBuffer, n, [tBuffer])
216
+ vertices = sq.positions
217
+ tValues = sq.values[0]
218
+
219
+ gl.bindBuffer(gl.ARRAY_BUFFER, getBuffer(gl, 'WebGLSquareColoringAdvanced.corner'))
220
+ gl.bufferData(gl.ARRAY_BUFFER, sq.corners, gl.DYNAMIC_DRAW)
221
+ const corner = gl.getAttribLocation(program, 'corner')
222
+ gl.vertexAttribPointer(corner, 2, gl.FLOAT, false, 0, 0)
223
+ gl.enableVertexAttribArray(corner)
224
+ }
225
+
189
226
  //vertice data, in a buffer reused from draw to draw
190
227
  gl.bindBuffer(gl.ARRAY_BUFFER, getBuffer(gl, 'WebGLSquareColoringAdvanced.pos'))
191
- gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(verticesBuffer), gl.DYNAMIC_DRAW)
228
+ gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.DYNAMIC_DRAW)
192
229
  const position = gl.getAttribLocation(program, 'pos')
193
230
  gl.vertexAttribPointer(
194
231
  position,
@@ -202,7 +239,7 @@ export class WebGLSquareColoringAdvanced {
202
239
 
203
240
  //t data
204
241
  gl.bindBuffer(gl.ARRAY_BUFFER, getBuffer(gl, 'WebGLSquareColoringAdvanced.t'))
205
- gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(tBuffer), gl.DYNAMIC_DRAW)
242
+ gl.bufferData(gl.ARRAY_BUFFER, tValues, gl.DYNAMIC_DRAW)
206
243
  const t = gl.getAttribLocation(program, 't')
207
244
  gl.vertexAttribPointer(t, 1, gl.FLOAT, false, 0, 0)
208
245
  gl.enableVertexAttribArray(t)
@@ -218,6 +255,7 @@ export class WebGLSquareColoringAdvanced {
218
255
  // Set the view port
219
256
  //gl.viewport(0, 0, cg.w, cg.h);
220
257
 
221
- gl.drawArrays(gl.POINTS, 0, verticesBuffer.length / 2)
258
+ if (this.asTriangles) gl.drawArrays(gl.TRIANGLES, 0, 6 * n)
259
+ else gl.drawArrays(gl.POINTS, 0, n)
222
260
  }
223
261
  }
@@ -220,6 +220,26 @@ export const viewScaleCombination = (obj) => {
220
220
  }
221
221
 
222
222
 
223
+ /**
224
+ * The class limits of a classification in classes of equal width on a stretched scale: the values of the domain which a
225
+ * stretching function (see stretching.js) maps to 1/n, 2/n... (n-1)/n.
226
+ * It turns a stretching, which is continuous, into the breaks needed by the styles which classify the values,
227
+ * for example LegoStyle.get, or the classifiers of this module.
228
+ *
229
+ * @param {number} min The lowest value of the domain
230
+ * @param {number} max The highest value of the domain
231
+ * @param {number} classNumber The number of classes
232
+ * @param {function(number):number} stretching A stretching function with its invert function, such as powerScale(0.5) or logarithmicScale(-7). None: the classes have the same width.
233
+ * @returns {Array.<number>} The classNumber - 1 limits between the classes, in increasing order
234
+ */
235
+ export function stretchedBreaks(min, max, classNumber, stretching = undefined) {
236
+ const invert = stretching ? stretching.invert : (t) => t
237
+ if (!invert) throw new Error('The stretching function has no invert function')
238
+ const breaks = []
239
+ for (let i = 1; i < classNumber; i++) breaks.push(min + (max - min) * invert(i / classNumber))
240
+ return breaks
241
+ }
242
+
223
243
  /**
224
244
  * Return a classifier function from break values.
225
245
  * The classifier function returns the class id (from 0 to breaks.length) from a value to classifiy.
@@ -176,3 +176,55 @@ export function checkWebGLSupport() {
176
176
  return false
177
177
  }
178
178
  }
179
+
180
+ /**
181
+ * From this size, in pixels, the squares of the WebGL styles are drawn as triangles instead of points.
182
+ *
183
+ * A point (gl_PointSize) is limited by the GPU (gl.ALIASED_POINT_SIZE_RANGE, a few hundred pixels on some GPUs), and it is
184
+ * dropped as soon as its centre is out of the view, even when the square is still partly in it. Both leave gaps in the grid
185
+ * when the cells are large, that is when the map is zoomed in. Triangles have neither limit. There are few cells in the view
186
+ * then, so their higher cost doesn't matter, and points stay faster for the small cells.
187
+ */
188
+ export const MIN_SQUARE_SIZE_AS_TRIANGLES = 24
189
+
190
+ /**
191
+ * Tells if squares of this size (in pixels) must be drawn as triangles rather than points.
192
+ *
193
+ * @param {WebGLRenderingContext} gl
194
+ * @param {number} sizePix
195
+ * @returns {boolean}
196
+ */
197
+ export function drawSquaresAsTriangles(gl, sizePix) {
198
+ const range = gl.getParameter(gl.ALIASED_POINT_SIZE_RANGE)
199
+ const max = range ? range[1] : 1
200
+ return sizePix > Math.min(max, MIN_SQUARE_SIZE_AS_TRIANGLES)
201
+ }
202
+
203
+ // the corners of a square, in units of its half size, as two triangles
204
+ const SQUARE_CORNERS = [-1, -1, 1, -1, 1, 1, -1, -1, 1, 1, -1, 1]
205
+
206
+ /**
207
+ * Turns points into squares made of two triangles each (6 vertices), to be drawn with gl.TRIANGLES.
208
+ * The vertex shader places the corners with `pos + corner * halfSize`.
209
+ *
210
+ * @param {ArrayLike<number>} positions The x,y of the n points (2*n numbers).
211
+ * @param {number} n The number of points.
212
+ * @param {Array.<ArrayLike<number>>} values Some values for each point (n numbers each), repeated for the 6 vertices of its square.
213
+ * @returns {{positions:Float32Array, corners:Float32Array, values:Array.<Float32Array>}}
214
+ */
215
+ export function pointsToSquares(positions, n, values = []) {
216
+ const pos = new Float32Array(12 * n)
217
+ const corners = new Float32Array(12 * n)
218
+ const vals = values.map(() => new Float32Array(6 * n))
219
+ for (let i = 0; i < n; i++) {
220
+ for (let k = 0; k < 6; k++) {
221
+ const o = 12 * i + 2 * k
222
+ pos[o] = positions[2 * i]
223
+ pos[o + 1] = positions[2 * i + 1]
224
+ corners[o] = SQUARE_CORNERS[2 * k]
225
+ corners[o + 1] = SQUARE_CORNERS[2 * k + 1]
226
+ for (let v = 0; v < values.length; v++) vals[v][6 * i + k] = values[v][i]
227
+ }
228
+ }
229
+ return { positions: pos, corners: corners, values: vals }
230
+ }