gridviz 3.0.41 → 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.
- package/README.md +18 -15
- package/dist/gridviz.js +3227 -1941
- package/dist/gridviz.min.js +1 -1
- package/package.json +6 -4
- package/src/button/Button.js +77 -68
- package/src/button/FullscreenButton.js +123 -123
- package/src/button/ZoomButtons.js +109 -104
- package/src/core/Drawable.js +25 -3
- package/src/core/GeoCanvas.js +15 -0
- package/src/core/Legend.js +267 -245
- package/src/core/Map.js +180 -30
- package/src/core/Tooltip.js +191 -185
- package/src/dataset/TiledGrid.js +74 -24
- package/src/index.js +82 -79
- package/src/layer/GridLayer.js +196 -194
- package/src/layer/LabelLayer.js +7 -4
- package/src/legend/ColorCategoryLegend.js +127 -125
- package/src/legend/ColorDiscreteLegend.js +116 -114
- package/src/legend/ColorLegend.js +33 -11
- package/src/legend/OrientationLegend.js +94 -92
- package/src/legend/SizeLegend.js +8 -1
- package/src/legend/TernaryLegend.js +317 -314
- package/src/legend/VennLegend.js +202 -0
- package/src/style/CircleColorSizeWebGLStyle.js +249 -0
- package/src/style/CompositionStyle.js +1 -1
- package/src/style/DotDensityStyle.js +8 -4
- package/src/style/ImageStyle.js +1 -1
- package/src/style/Interpolator.js +31 -6
- package/src/style/IsoFenceStyle.js +1 -1
- package/src/style/LegoStyle.js +6 -5
- package/src/style/MosaicStyle.js +1 -1
- package/src/style/NinjaStarStyle.js +1 -1
- package/src/style/PillarStyle.js +1 -1
- package/src/style/RandomWalkInterpolator.js +213 -0
- package/src/style/ShadingRayStyle.js +16 -2
- package/src/style/ShadingStyle.js +80 -72
- package/src/style/ShapeColorSizeStyle.js +18 -2
- package/src/style/SideCategoryStyle.js +1 -1
- package/src/style/SideStyle.js +1 -1
- package/src/style/SideTanakaStyle.js +1 -1
- package/src/style/SquareColorCategoryWebGLStyle.js +71 -20
- package/src/style/SquareColorWebGLStyle.js +7 -4
- package/src/style/StrokeStyle.js +1 -1
- package/src/style/TextStyle.js +1 -1
- package/src/style/TimeSeriesStyle.js +14 -7
- package/src/utils/WebGLSquareColoringAdvanced.js +47 -9
- package/src/utils/scale.js +20 -0
- 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 {
|
|
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
|
|
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,
|
|
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,
|
|
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
|
-
//
|
|
224
|
-
const
|
|
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
|
|
229
|
-
|
|
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
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
}
|
package/src/style/StrokeStyle.js
CHANGED
|
@@ -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
|
}
|
package/src/style/TextStyle.js
CHANGED
|
@@ -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 || ((
|
|
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
|
|
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 =
|
|
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 =
|
|
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
|
|
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 = (
|
|
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
|
-
//
|
|
163
|
-
|
|
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,
|
|
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,
|
|
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.
|
|
258
|
+
if (this.asTriangles) gl.drawArrays(gl.TRIANGLES, 0, 6 * n)
|
|
259
|
+
else gl.drawArrays(gl.POINTS, 0, n)
|
|
222
260
|
}
|
|
223
261
|
}
|
package/src/utils/scale.js
CHANGED
|
@@ -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.
|
package/src/utils/webGLUtils.js
CHANGED
|
@@ -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
|
+
}
|