gridviz 3.0.30 → 3.0.32

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gridviz",
3
- "version": "3.0.30",
3
+ "version": "3.0.32",
4
4
  "description": "Visualization tool for gridded statistics",
5
5
  "keywords": [
6
6
  "statistics",
package/src/core/Map.js CHANGED
@@ -76,6 +76,7 @@ export class Map {
76
76
  this.legend = opts.legendContainer
77
77
  ? select(opts.legendContainer) // Wrap the provided HTML node in a D3 selection
78
78
  : null
79
+ this._legendProvidedByUser = !!opts.legendContainer
79
80
  if (!this.legend) this.initialiseLegend()
80
81
 
81
82
  //tooltip
@@ -536,7 +537,10 @@ export class Map {
536
537
  this.geoCanvas.canvas.remove()
537
538
 
538
539
  // remove legend
539
- this.legend?.remove()
540
+ // only remove legend if gridviz created it
541
+ if (!this._legendProvidedByUser) {
542
+ this.legend?.remove()
543
+ }
540
544
 
541
545
  // remove tooltip
542
546
  this.tooltip.tooltip?.remove()
@@ -42,14 +42,25 @@ export class ShadingStyle extends SideStyle {
42
42
  // compute maximum side value for normalization
43
43
  this.viewScale = sides => max(sides, s => sideValue(s))
44
44
 
45
- const colorTopLeft = opts.colorTopLeft || '255,255,255'
46
- const colorBottomRight = opts.colorBottomRight || '0,0,0'
47
- const scale = opts.scale || (t=>t)
45
+ // get colors
46
+ let colorTopLeft = opts.colorTopLeft || '255,255,255'
47
+ let colorBottomRight = opts.colorBottomRight || '0,0,0'
48
+
49
+ // revert colors (to revert the relief, show depressions as hills)
50
+ const revert = opts.revert == undefined? false : opts.revert
51
+ if(revert) {
52
+ let a = colorTopLeft
53
+ colorTopLeft = colorBottomRight
54
+ colorBottomRight = a
55
+ }
56
+
57
+ //
58
+ const scale = opts.scale
48
59
 
49
60
  this.color = (side, resolution, z, max) => {
50
61
  if (side.v == 0) return
51
62
  let coeff = Math.abs(side.v / max)
52
- coeff = scale(coeff)
63
+ if(scale) coeff = scale(coeff)
53
64
  if ((side.v < 0 && side.or === 'h') || (side.v > 0 && side.or === 'v'))
54
65
  return 'rgba(' + colorTopLeft + ',' + coeff + ')'
55
66
  return 'rgba(' + colorBottomRight + ',' + coeff + ')'
@@ -124,12 +124,17 @@ export class SideStyle extends Style {
124
124
  * @param {boolean} center Set to true so that the side coordinate are those of its center point rather than its left/bottom point (the side x,y coordinates are those of the left point for horizontal sides, and of the bottom point for vertical sides)
125
125
  * @returns { Array.<Side> }
126
126
  */
127
- static buildSides(cells, resolution, withHorizontal = true, withVertical = true, center = true) {
127
+ static buildSides(cells, resolution, withHorizontal = true, withVertical = true, center = true, eps = 0.01) {
128
128
  /** @type { Array.<Side> } */
129
129
  const sides = []
130
130
 
131
+ // half resolution
131
132
  const r2 = center ? resolution / 2 : 0
132
133
 
134
+ // epsilon
135
+ eps = resolution * eps
136
+ const abs = Math.abs
137
+
133
138
  //make horizontal sides
134
139
  //sort cells by x and y
135
140
  cells.sort((c1, c2) => (c2.x == c1.x ? c1.y - c2.y : c1.x - c2.x))
@@ -137,7 +142,7 @@ export class SideStyle extends Style {
137
142
  for (let i = 1; i < cells.length; i++) {
138
143
  let c2 = cells[i]
139
144
 
140
- if (c1.y + resolution == c2.y && c1.x == c2.x)
145
+ if (abs(c1.y + resolution - c2.y) < eps && abs(c1.x - c2.x) < eps)
141
146
  //cells in same column and touch along horizontal side
142
147
  //make shared side
143
148
  sides.push({
@@ -176,7 +181,7 @@ export class SideStyle extends Style {
176
181
  for (let i = 1; i < cells.length; i++) {
177
182
  let c2 = cells[i]
178
183
 
179
- if (c1.x + resolution == c2.x && c1.y == c2.y)
184
+ if (abs(c1.x + resolution - c2.x) < eps && abs(c1.y - c2.y) < eps)
180
185
  //cells in same row and touch along vertical side
181
186
  //make shared side
182
187
  sides.push({
@@ -10,6 +10,8 @@ import { SideStyle } from './SideStyle.js'
10
10
  * @typedef {function(Array.<Side>,number, number):*} SideViewScale */
11
11
 
12
12
  /**
13
+ * @see https://manifold.net/doc/mfd9/example__tanaka_contours.htm
14
+ *
13
15
  * @module style
14
16
  * @author Julien Gaffuri
15
17
  */
@@ -25,7 +27,7 @@ export class SideTanakaStyle extends Style {
25
27
 
26
28
  /** A function returning the width of a cell side, in geo unit
27
29
  * @type {function(Side, number, number, number, object):number} */
28
- this.width = opts.width || ((side, sideValue, resolution, z, sidesScale) => Math.abs(sideValue) * Math.min(2 * z, resolution / 3))
30
+ this.width = opts.width || ((side, sideValue, resolution, z, sidesScale) => Math.abs(sideValue) * z) //Math.min(2 * z, resolution / 3))
29
31
 
30
32
  /** A function returning the length of a cell side, in geo unit
31
33
  * @type {function(Side, number, number, object):number} */
@@ -2,8 +2,8 @@
2
2
  'use strict'
3
3
 
4
4
  import { Style } from '../core/Style.js'
5
- import { makeWebGLCanvas } from '../utils/webGLUtils.js'
6
- import { WebGLSquareColoringCatAdvanced } from '../utils/WebGLSquareColoringCatAdvanced.js'
5
+ import { initShaderProgram, createShader, makeWebGLCanvas } from '../utils/webGLUtils.js'
6
+ import { color } from 'd3-color'
7
7
 
8
8
  /**
9
9
  * Style based on webGL
@@ -21,8 +21,8 @@ export class SquareColorCategoryWebGLStyle extends Style {
21
21
 
22
22
  /**
23
23
  * A function returning the category code of the cell, for coloring.
24
- * @type {function(import('../core/Dataset.js').Cell):string} */
25
- this.code = opts.code
24
+ * @type {function(import('../core/Dataset.js').Cell, number, number, viewScale):string} */
25
+ this.code = opts.code // (c, resolution, z, viewScale) => "code1"
26
26
 
27
27
  /**
28
28
  * The dictionary (code -> color) which gives the color of each category code.
@@ -45,38 +45,105 @@ export class SquareColorCategoryWebGLStyle extends Style {
45
45
  * @type {function(number,number):number} */
46
46
  this.size = opts.size // (resolution, z) => ...
47
47
 
48
- /**
49
- * @private
50
- * @type { WebGLSquareColoringCatAdvanced } */
51
- this.wgp = new WebGLSquareColoringCatAdvanced(this.colors)
48
+ /** * @type {{canvas:HTMLCanvasElement, gl:WebGLRenderingContext, width:number, height:number }}*/
49
+ this.cvWGL = undefined
50
+ this.programm = undefined
51
+
52
+ this.x = undefined
53
+ this.y = undefined
54
+ this.z = undefined
55
+ this.cellsNb = undefined
52
56
  }
53
57
 
54
- /**
55
- * @param {Array.<import("../core/Dataset.js").Cell>} cells
56
- * @param {import("../core/GeoCanvas.js").GeoCanvas} geoCanvas
57
- * @param {number} resolution
58
- */
59
- draw(cells, geoCanvas, resolution) {
60
- //filter
61
- if (this.filter) cells = cells.filter(this.filter)
58
+ init(w, h) {
59
+ this.cvWGL = makeWebGLCanvas(w + '', h + '')
60
+ if (!this.cvWGL) { console.error('No webGL'); return }
62
61
 
63
- //
64
- const z = geoCanvas.view.z
62
+ const gl = this.cvWGL.gl
63
+
64
+ //draw
65
+ const vectorShader = `
66
+ attribute vec2 pos;
67
+ uniform float sizePix;
68
+ uniform mat3 mat;
69
+ attribute float i;
70
+ varying float vi;
71
+ void main() {
72
+ gl_Position = vec4(mat * vec3(pos, 1.0), 1.0);
73
+ gl_PointSize = sizePix;
74
+ vi = i;
75
+ }`
76
+ /** @type {WebGLShader} */
77
+ const vShader = createShader(gl, gl.VERTEX_SHADER, vectorShader)
78
+
79
+ const fragmentShader = `
80
+ precision highp float;
81
+ varying float vi;
82
+ uniform sampler2D colorLUT;
83
+ uniform float lutSize;
84
+ void main(void) {
85
+ float idx = floor(vi + 0.5);
86
+ float u = (idx + 0.5) / lutSize;
87
+ gl_FragColor = texture2D(colorLUT, vec2(u, 0.5));
88
+ }`
89
+ /** @type {WebGLShader} */
90
+ const fShader = createShader(gl, gl.FRAGMENT_SHADER, fragmentShader)
91
+
92
+ /** @type {WebGLProgram} */
93
+ this.program = initShaderProgram(gl, vShader, fShader)
94
+ gl.useProgram(this.program)
95
+ }
96
+
97
+ bindColors() {
98
+ const gl = this.cvWGL.gl
99
+ const lutSize = this.colors.length;
100
+ const lutData = new Uint8Array(lutSize * 4); // RGBA for each entry
65
101
 
102
+ // Fill lutData with your color values (e.g., rainbow, grayscale, etc.)
103
+ for (let i = 0; i < lutSize; i++) {
104
+ const c = color(this.colors[i])
105
+ lutData[i * 4] = +c.r; // R
106
+ lutData[i * 4 + 1] = c.g; // G
107
+ lutData[i * 4 + 2] = c.b; // B
108
+ lutData[i * 4 + 3] = c?.opacity * 255; // A
109
+ }
110
+
111
+ // Create and bind texture
112
+ const lutTexture = gl.createTexture();
113
+ gl.bindTexture(gl.TEXTURE_2D, lutTexture);
114
+ gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, lutSize, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, lutData);
115
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
116
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
117
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
118
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
119
+
120
+ // Get uniform locations
121
+ const uColorLUT = gl.getUniformLocation(this.program, 'colorLUT');
122
+ const uLutSize = gl.getUniformLocation(this.program, 'lutSize');
123
+
124
+ // Set uniform values
125
+ gl.uniform1i(uColorLUT, 0); // Texture unit 0
126
+ gl.uniform1f(uLutSize, lutSize);
127
+
128
+ // Bind the texture to texture unit 0
129
+ gl.activeTexture(gl.TEXTURE0);
130
+ gl.bindTexture(gl.TEXTURE_2D, lutTexture);
131
+ }
132
+
133
+ bindVertices(cells, resolution, z, viewScale) {
134
+ const gl = this.cvWGL.gl
66
135
  //add vertice and fragment data
67
136
  const r2 = resolution / 2
68
- let c,
69
- nb = cells.length
137
+ let c, nb = cells.length
70
138
  const verticesBuffer = []
71
139
  const iBuffer = []
72
140
  for (let i = 0; i < nb; i++) {
73
141
  c = cells[i]
74
- const cat = this.code(c)
142
+ const cat = this.code(c, resolution, z, viewScale)
75
143
  if (cat == undefined) {
76
144
  console.log('Unexpected category: ' + cat)
77
145
  continue
78
146
  }
79
- /** @type {number} */
80
147
  const i_ = this.catToI[cat]
81
148
  if (isNaN(+i_)) {
82
149
  console.log('Unexpected category index: ' + cat + ' ' + i_)
@@ -86,22 +153,143 @@ export class SquareColorCategoryWebGLStyle extends Style {
86
153
  iBuffer.push(+i_)
87
154
  }
88
155
 
156
+ //bind vertice data
157
+ gl.bindBuffer(gl.ARRAY_BUFFER, gl.createBuffer())
158
+ gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(verticesBuffer), gl.STATIC_DRAW)
159
+ const position = gl.getAttribLocation(this.program, 'pos')
160
+ gl.vertexAttribPointer(
161
+ position,
162
+ 2, //numComponents
163
+ gl.FLOAT, //type
164
+ false, //normalise
165
+ 0, //stride
166
+ 0 //offset
167
+ )
168
+ gl.enableVertexAttribArray(position)
169
+
170
+ //i data
171
+ gl.bindBuffer(gl.ARRAY_BUFFER, gl.createBuffer())
172
+ gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(iBuffer), gl.STATIC_DRAW)
173
+ const i = gl.getAttribLocation(this.program, 'i')
174
+ gl.vertexAttribPointer(i, 1, gl.FLOAT, false, 0, 0)
175
+ gl.enableVertexAttribArray(i)
176
+ }
177
+
178
+
179
+
180
+ // check if the vertices have to be bound again
181
+ mapContentChanged(v, cellsNb) {
182
+ if (v.x == this.x && v.y == this.y && v.z == this.z && this.cellsNb == cellsNb) return false
183
+ this.x = v.x
184
+ this.y = v.y
185
+ this.z = v.z
186
+ this.cellsNb = cellsNb
187
+ return true
188
+ }
189
+
190
+
191
+ /**
192
+ * @param {Array.<import("../core/Dataset.js").Cell>} cells
193
+ * @param {import("../core/GeoCanvas.js").GeoCanvas} geoCanvas
194
+ * @param {number} resolution
195
+ */
196
+ draw(cells, geoCanvas, resolution) {
197
+ //filter
198
+ if (this.filter) cells = cells.filter(this.filter)
199
+
200
+ //
201
+ const z = geoCanvas.view.z
202
+
203
+ //get view scale
204
+ const viewScale = this.viewScale ? this.viewScale(cells, resolution, z) : undefined
205
+
89
206
  //create canvas and webgl renderer
90
- const cvWGL = makeWebGLCanvas(geoCanvas.w + '', geoCanvas.h + '')
91
- if (!cvWGL) {
92
- console.error('No webGL')
93
- return
207
+ if (!this.cvWGL || geoCanvas.w != this.cvWGL.width || geoCanvas.h != this.cvWGL.height) {
208
+ this.init(geoCanvas.w, geoCanvas.h)
209
+ this.bindColors()
94
210
  }
211
+ const gl = this.cvWGL.gl
212
+ const canvas = this.cvWGL.canvas
95
213
 
96
- //draw
97
- const sizeGeo = this.size ? this.size(resolution, z) : resolution + 0.2 * z
98
- this.wgp.draw(cvWGL.gl, verticesBuffer, iBuffer, geoCanvas.getWebGLTransform(), sizeGeo / z)
214
+ //bind sizePix
215
+ const sizePix = this.size ? this.size(resolution, z) / z : resolution / z + 0.2
216
+ gl.uniform1f(gl.getUniformLocation(this.program, 'sizePix'), 1.0 * sizePix)
217
+
218
+ //
219
+ if (this.mapContentChanged(geoCanvas.view, cells.length))
220
+ //bind vertices
221
+ this.bindVertices(cells, resolution, z, viewScale)
222
+ //transformation
223
+ gl.uniformMatrix3fv(gl.getUniformLocation(this.program, 'mat'), false, new Float32Array(geoCanvas.getWebGLTransform()))
224
+
225
+ // Enable the depth test
226
+ //gl.enable(gl.DEPTH_TEST);
227
+ // Clear the color buffer bit
228
+ gl.clear(gl.COLOR_BUFFER_BIT)
229
+ // Set the view port
230
+ //gl.viewport(0, 0, cg.w, cg.h);
231
+
232
+ gl.drawArrays(gl.POINTS, 0, cells.length)
99
233
 
100
234
  //draw in canvas geo
101
235
  geoCanvas.initCanvasTransform()
102
- geoCanvas.offscreenCtx.drawImage(cvWGL.canvas, 0, 0)
236
+ geoCanvas.offscreenCtx.drawImage(canvas, 0, 0)
103
237
 
104
238
  //update legends
105
239
  this.updateLegends({ style: this, resolution: resolution, z: z })
106
240
  }
107
241
  }
242
+
243
+
244
+
245
+ // early tests for webgl2 migration
246
+
247
+ /*
248
+ function getVectorShader2() {
249
+ return `
250
+ #version 300 es
251
+ precision highp float;
252
+
253
+ in vec2 pos;
254
+ in int i;
255
+
256
+ uniform float sizePix;
257
+ uniform mat3 mat;
258
+
259
+ flat out int vi;
260
+
261
+ void main() {
262
+ gl_Position = vec4(mat * vec3(pos, 1.0), 1.0);
263
+ gl_PointSize = sizePix;
264
+ vi = i;
265
+ }
266
+ `
267
+ }
268
+
269
+
270
+ function getFragmentShader2(colors) {
271
+
272
+ //prepare fragment shader code
273
+ //declare the uniform and other variables
274
+ const out = []
275
+ out.push('#version 300 es\nprecision highp float;\nflat in int vi;\n')
276
+
277
+ //add color uniforms
278
+ //uniform vec4 colors[12];
279
+ out.push('uniform vec4 colors[')
280
+ out.push(colors.length)
281
+ out.push('];\n')
282
+
283
+ out.push('out vec4 fragColor;\n')
284
+
285
+ //start the main function
286
+ //void main() { fragColor = colors[clamp(vi, 0, 11)]; }
287
+ out.push('void main() { fragColor = colors[vi]; }\n')
288
+
289
+ /** Fragment shader program
290
+ const fshString = out.join('')
291
+ console.log(fshString)
292
+ return fshString
293
+ }
294
+
295
+ */
@@ -53,86 +53,85 @@ export class WebGLSquareColoringAdvanced {
53
53
 
54
54
  //prepare fragment shader code
55
55
  //declare the uniform and other variables
56
- let fshString =
57
- '' +
58
- 'precision mediump float;\n' +
59
- 'varying float vt;\n' +
60
- 'uniform float alpha;\n' +
61
- (() => {
62
- const out = []
63
- for (let i = 0; i < colors.length; i++) out.push('uniform vec4 c' + i + ';\n')
64
- return out.join('')
65
- })() +
66
- //start the main function, apply the stretching of t
67
- 'void main(void) {\n'
68
-
56
+ const fshBuff = []
57
+ fshBuff.push('precision mediump float;')
58
+ fshBuff.push('varying float vt;')
59
+ fshBuff.push('uniform float alpha;')
60
+ for (let i = 0; i < colors.length; i++) fshBuff.push('uniform vec4 c' + i + ';')
61
+ //start the main function
62
+ fshBuff.push('void main(void) {')
63
+
64
+ // apply the stretching of t
69
65
  if (stretching) {
70
66
  if (stretching.fun == 'pow')
71
67
  //sPow = (t, alpha = 3) => Math.pow(t, alpha);
72
- fshString += ' float t = pow(vt, alpha);\n'
68
+ fshBuff.push(' float t = pow(vt, alpha);')
73
69
  else if (stretching.fun == 'powInv')
74
70
  //sPowRev = (t, alpha = 3) => 1 - Math.pow(1 - t, 1 / alpha);
75
- fshString += ' float t = 1.0-pow(1.0-vt, 1.0/alpha);\n'
71
+ fshBuff.push(' float t = 1.0-pow(1.0-vt, 1.0/alpha);')
76
72
  else if (stretching.fun == 'exp')
77
73
  //sExp = (t, alpha = 3) => alpha == 0 ? t : (Math.exp(t * alpha) - 1) / (Math.exp(alpha) - 1);
78
- fshString +=
74
+ fshBuff.push(
79
75
  stretching.alpha == 0
80
76
  ? `float t = vt;`
81
- : ' float t = (exp(vt * alpha) - 1.0) / (exp(alpha) - 1.0);\n'
77
+ : ' float t = (exp(vt * alpha) - 1.0) / (exp(alpha) - 1.0);')
82
78
  else if (stretching.fun == 'log')
83
79
  //sExpRev = (t, alpha = 3) => alpha == 0 ? t : 1 - (1 / alpha) * Math.log(Math.exp(alpha) * (1 - t) + t);
84
- fshString +=
80
+ fshBuff.push(
85
81
  stretching.alpha == 0
86
82
  ? `float t = vt;`
87
- : ' float t = 1.0 - (1.0 / alpha) * log(exp(alpha) * (1.0 - vt) + vt);\n'
83
+ : ' float t = 1.0 - (1.0 / alpha) * log(exp(alpha) * (1.0 - vt) + vt);')
88
84
  else if (stretching.fun == 'circle') {
89
85
  if (stretching.alpha == 0)
90
86
  //if (alpha == 0) return t;
91
- fshString += ' float t = vt;\n'
87
+ fshBuff.push(' float t = vt;')
92
88
  else if (stretching.alpha == 1)
93
89
  // if (alpha == 1) return Math.sqrt(2 * t - t * t);
94
- fshString += ' float t = sqrt(vt * (2.0 - vt));\n'
90
+ fshBuff.push(' float t = sqrt(vt * (2.0 - vt));')
95
91
  else {
96
92
  //const a = alpha / (1 - alpha);
97
93
  //return Math.sqrt(1 / (a * a) + t * (2 / a + 2 - t)) - 1 / a;
98
- fshString +=
99
- ' float a = alpha / (1.0 - alpha);\n' +
100
- ' float t = sqrt(1.0 / (a * a) + vt * ( 2.0/a + 2.0 - vt )) - 1.0 / a;\n'
94
+ fshBuff.push(' float a = alpha / (1.0 - alpha);')
95
+ fshBuff.push(' float t = sqrt(1.0 / (a * a) + vt * ( 2.0/a + 2.0 - vt )) - 1.0 / a;')
101
96
  }
102
97
  } else if (stretching.fun == 'circleInv') {
103
98
  // 1 - sCircleLow(1 - t, alpha)
104
99
  if (stretching.alpha == 0)
105
100
  //if (alpha == 0) return t;
106
- fshString += ' float t = vt;\n'
101
+ fshBuff.push(' float t = vt;')
107
102
  else if (stretching.alpha == 1)
108
103
  // if (alpha == 1) return Math.sqrt(2 * t - t * t);
109
- fshString += ' float t = 1.0 - sqrt((1.0 - vt) * (1.0 + vt));\n'
104
+ fshBuff.push(' float t = 1.0 - sqrt((1.0 - vt) * (1.0 + vt));')
110
105
  else {
111
106
  //const a = alpha / (1 - alpha);
112
107
  //return Math.sqrt(1 / (a * a) + (2 * t) / a + 2 * t - t * t) - 1 / a;
113
- fshString +=
114
- ' float a = alpha / (1.0 - alpha);\n' +
115
- ' float t = 1.0 - sqrt(1.0 / (a * a) + (1.0-vt) * ( 2.0/a + 1.0 + vt )) + 1.0 / a;\n'
108
+ fshBuff.push(' float a = alpha / (1.0 - alpha);')
109
+ fshBuff.push(' float t = 1.0 - sqrt(1.0 / (a * a) + (1.0-vt) * ( 2.0/a + 1.0 + vt )) + 1.0 / a;')
116
110
  }
117
111
  } else {
118
112
  console.error('Unexpected stretching function code: ' + stretching.fun)
119
- fshString += ' float t = vt;\n'
113
+ fshBuff.push(' float t = vt;')
120
114
  }
121
115
  } else {
122
- fshString += ' float t = vt;\n'
116
+ fshBuff.push(' float t = vt;')
123
117
  }
124
118
 
125
119
  //choose initial and final colors, and adjust t value
126
- if (colors.length == 1) fshString += ' vec4 cI=c0;\n vec4 cF=c0;\n'
127
- else if (colors.length == 2) fshString += ' vec4 cI=c0;\n vec4 cF=c1;\n'
128
- else {
120
+ if (colors.length == 1) {
121
+ fshBuff.push(' vec4 cI=c0;')
122
+ fshBuff.push(' vec4 cF=c0;')
123
+ }
124
+ else if (colors.length == 2) {
125
+ fshBuff.push(' vec4 cI=c0;')
126
+ fshBuff.push(' vec4 cF=c1;')
127
+ } else {
129
128
  const nb = colors.length - 1
130
129
  const nbs = nb + '.0'
131
- fshString += ' vec4 cI;\n'
132
- fshString += ' vec4 cF;\n'
133
- fshString += ' if(t<1.0/' + nbs + ') { cI=c0; cF=c1; t=t*' + nbs + '; }\n'
130
+ fshBuff.push(' vec4 cI;')
131
+ fshBuff.push(' vec4 cF;')
132
+ fshBuff.push(' if(t<1.0/' + nbs + ') { cI=c0; cF=c1; t=t*' + nbs + '; }')
134
133
  for (let i = 2; i < nb; i++)
135
- fshString +=
134
+ fshBuff.push(
136
135
  ' else if(t<' +
137
136
  i +
138
137
  '.0/' +
@@ -145,16 +144,17 @@ export class WebGLSquareColoringAdvanced {
145
144
  nbs +
146
145
  '*t-' +
147
146
  (i - 1) +
148
- '.0; }\n'
149
- fshString +=
150
- ' else { cI=c' + (nb - 1) + '; cF=c' + nb + '; t=' + nbs + '*t-' + (nb - 1) + '.0; }\n'
147
+ '.0; }')
148
+ fshBuff.push(' else { cI=c' + (nb - 1) + '; cF=c' + nb + '; t=' + nbs + '*t-' + (nb - 1) + '.0; }')
151
149
  }
152
150
 
153
151
  //one single color
154
- if (colors.length == 1) fshString += ' gl_FragColor = vec4(c0[0], c0[1], c0[2], c0[3]);}\n'
152
+ if (colors.length == 1) fshBuff.push(' gl_FragColor = vec4(c0[0], c0[1], c0[2], c0[3]);}')
155
153
  //set interpolated color, between initial and final one
156
- else fshString += ' gl_FragColor = mix(cI, cF, t);}\n'
154
+ else fshBuff.push(' gl_FragColor = mix(cI, cF, t);}')
157
155
 
156
+ let fshString = fshBuff.join("")
157
+ //let fshString = fshBuff.join("\n")
158
158
  //console.log(fshString)
159
159
 
160
160
  /** @type {WebGLShader} */
@@ -180,12 +180,9 @@ export class WebGLSquareColoringAdvanced {
180
180
  let opacity = c.opacity
181
181
  if (c.opacity == 1 && globalOpacity != undefined) opacity = globalOpacity
182
182
 
183
- gl.uniform4fv(gl.getUniformLocation(this.program, 'c' + i), [
184
- +c.r / 255.0,
185
- +c.g / 255.0,
186
- +c.b / 255.0,
187
- +opacity,
188
- ])
183
+ const colData = [+c.r / 255.0, +c.g / 255.0, +c.b / 255.0, +opacity]
184
+ //console.log(i, colors[i], c, colData)
185
+ gl.uniform4fv(gl.getUniformLocation(this.program, 'c' + i), colData)
189
186
  }
190
187
  }
191
188
 
@@ -151,6 +151,28 @@ export const viewScaleColorQuantile = (opts) => {
151
151
  }
152
152
  }
153
153
 
154
+
155
+ /**
156
+ * Generic function for view scale quantile classifier
157
+ *
158
+ * @param {{ valueFunction:function(import("../core/Dataset").Cell):number, classNumber?:number }} opts
159
+ * @returns {function(Array.<import("../core/Dataset").Cell>):ColorScale}
160
+ */
161
+ export const viewScaleQuantileClassifer = (opts) => {
162
+ const valueFunction = opts.valueFunction // the function that return the cell value from a cell
163
+ const classNumber = opts.classNumber || 12 // the class number
164
+
165
+ const scale = scaleQuantile().range(Array.from( { length: classNumber }, (_, i) => i ))
166
+ return (cells) => {
167
+ scale.domain(cells.map(valueFunction))
168
+ scale.breaks = scale.quantiles()
169
+ return scale
170
+ }
171
+ }
172
+
173
+
174
+
175
+
154
176
  /**
155
177
  * Combine view scale functions
156
178
  *
@@ -5,18 +5,19 @@
5
5
  * @param {string} width
6
6
  * @param {string} height
7
7
  * @param {object} opts
8
- * @returns {{canvas:HTMLCanvasElement, gl:WebGLRenderingContext}}
8
+ * @returns {{canvas:HTMLCanvasElement, gl:WebGLRenderingContext, width:number, height:number }}
9
9
  */
10
10
  export function makeWebGLCanvas(width, height, opts = {}) {
11
11
  const canvas = document.createElement('canvas')
12
12
  canvas.setAttribute('width', width)
13
13
  canvas.setAttribute('height', height)
14
+ const version2 = (opts && +opts.version==2)? "2" : ""
14
15
  /** @type {WebGLRenderingContext} */
15
- const gl = canvas.getContext('webgl', opts)
16
+ const gl = canvas.getContext('webgl' + version2, opts)
16
17
  if (!gl) {
17
- throw new Error('Unable to initialize WebGL. Your browser or machine may not support it.')
18
+ throw new Error('Unable to initialize WebGL'+version2+'. Your browser or machine may not support it.')
18
19
  }
19
- return { canvas: canvas, gl: gl }
20
+ return { canvas: canvas, gl: gl, width: width, height: height }
20
21
  }
21
22
 
22
23
  /**