gridviz 2.0.47 → 2.0.49

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": "2.0.47",
3
+ "version": "2.0.49",
4
4
  "description": "Visualization tool for gridded statistics",
5
5
  "keywords": [
6
6
  "statistics",
package/src/App.js CHANGED
@@ -110,11 +110,23 @@ export class App {
110
110
  //update dataset view cache
111
111
  if (strong) dsc.updateViewCache(this.cg.extGeo)
112
112
 
113
+ //set layer alpha and blend mode
114
+ this.cg.ctx.globalAlpha = layer.alpha === undefined ? 1 : layer.alpha
115
+ this.cg.ctx.globalCompositeOperation = layer.blendOperation
116
+
113
117
  //draw cells, style by style
114
118
  if (strong)
115
119
  for (const s of layer.styles) {
120
+ //check if style is visible
121
+ if (!s.visible) continue
116
122
  if (zf > s.maxZoom) continue
117
123
  if (zf < s.minZoom) continue
124
+
125
+ //set style alpha and blend mode
126
+ //TODO: multiply by layer alpha ?
127
+ this.cg.ctx.globalAlpha = s.alpha === undefined ? 1 : s.alpha
128
+ this.cg.ctx.globalCompositeOperation = s.blendOperation
129
+
118
130
  s.draw(dsc.getViewCache(), dsc.getResolution(), this.cg)
119
131
  }
120
132
 
package/src/Layer.js CHANGED
@@ -10,7 +10,7 @@ export class Layer {
10
10
  /**
11
11
  * @param {import("./Dataset").Dataset} dataset The multi resolution dataset to show.
12
12
  * @param {Array.<import("./Style").Style>} styles The styles, ordered in drawing order.
13
- * @param {{visible?:boolean,minZoom?:number,maxZoom?:number,pixNb?:number,cellInfoHTML?:function(import("./Dataset").Cell):string}} opts
13
+ * @param {{visible?:boolean,alpha?:number,blendOperation?:GlobalCompositeOperation,minZoom?:number,maxZoom?:number,pixNb?:number,cellInfoHTML?:function(import("./Dataset").Cell):string}} opts
14
14
  * minZoom: The minimum zoom level when to show the layer. maxZoom: The maximum zoom level when to show the layer
15
15
  */
16
16
  constructor(dataset, styles, opts = {}) {
@@ -23,7 +23,17 @@ export class Layer {
23
23
 
24
24
  /** An attribute to specify if a layer should be drawn or not
25
25
  * @type {boolean} */
26
- this.visible = opts.visible == false ? false : true
26
+ this.visible = opts.visible === false ? false : true
27
+
28
+ /** The alpha of the layer, between 0.0 (fully transparent) and 1.0 (fully opaque).
29
+ * (see CanvasRenderingContext2D: globalAlpha property)
30
+ * @type {number|undefined} */
31
+ this.alpha = opts.alpha
32
+
33
+ /** The blend operation
34
+ * (see CanvasRenderingContext2D: globalCompositeOperation property)
35
+ * @type {GlobalCompositeOperation} */
36
+ this.blendOperation = opts.blendOperation || 'normal'
27
37
 
28
38
  /** The minimum zoom factor: Below this level, the layer is not shown.
29
39
  * @type {number} */
package/src/Style.js CHANGED
@@ -17,7 +17,7 @@
17
17
  export class Style {
18
18
  /**
19
19
  * @abstract
20
- * @param {{filter?:function(import('./Dataset').Cell):boolean,offset?:function(import('./Dataset').Cell,number,number):{dx:number,dy:number},minZoom?:number,maxZoom?:number}} opts
20
+ * @param {{filter?:function(import('./Dataset').Cell):boolean,offset?:function(import('./Dataset').Cell,number,number):{dx:number,dy:number},visible?:boolean,alpha?:number,blendOperation?:GlobalCompositeOperation,minZoom?:number,maxZoom?:number}} opts
21
21
  * minZoom: The minimum zoom level when to show the layer. maxZoom: The maximum zoom level when to show the layer
22
22
  */
23
23
  constructor(opts) {
@@ -33,6 +33,20 @@ export class Style {
33
33
  * @type {function(import('./Dataset').Cell,number,number):{dx:number,dy:number}} */
34
34
  this.offset = opts.offset || ((c, r, zf) => ({ dx: 0, dy: 0 }))
35
35
 
36
+ /** An attribute to specify if a style should be drawn or not
37
+ * @type {boolean} */
38
+ this.visible = opts.visible === false ? false : true
39
+
40
+ /** The alpha of the style, between 0.0 (fully transparent) and 1.0 (fully opaque).
41
+ * (see CanvasRenderingContext2D: globalAlpha property)
42
+ * @type {number|undefined} */
43
+ this.alpha = opts.alpha
44
+
45
+ /** The blend operation
46
+ * (see CanvasRenderingContext2D: globalCompositeOperation property)
47
+ * @type {GlobalCompositeOperation} */
48
+ this.blendOperation = opts.blendOperation || 'normal'
49
+
36
50
  /** The minimum zoom factor: Below this level, the layer is not shown.
37
51
  * @type {number}
38
52
  * */
package/src/index.js CHANGED
@@ -33,6 +33,7 @@ export { SquareColorWGLStyle } from './style/SquareColorWGLStyle.js'
33
33
  export { SquareColorCatWGLStyle } from './style/SquareColorCatWGLStyle.js'
34
34
  export { MosaicStyle } from './style/MosaicStyle.js'
35
35
  export { NinjaStarStyle } from './style/NinjaStarStyle.js'
36
+ export { TimeSeriesStyle } from './style/TimeSeriesStyle.js'
36
37
 
37
38
  // export additional layers
38
39
  export { BackgroundLayer } from './BackgroundLayer.js'
@@ -85,7 +85,8 @@ export class NinjaStarStyle extends Style {
85
85
  const sG2 = s_(cell[this.sizeCol], r, statSize, zf) * r2
86
86
 
87
87
  //get offset
88
- const offset = this.offset(cell, r, zf)
88
+ //TODO use
89
+ //const offset = this.offset(cell, r, zf)
89
90
 
90
91
  //center position
91
92
  const cx = cell.x + r2
@@ -0,0 +1,207 @@
1
+ //@ts-check
2
+ 'use strict'
3
+
4
+ import { Style } from '../Style.js'
5
+
6
+ /** @typedef {"first"|"bottom"|"center"|"top"|"last"} AnchorModeYEnum */
7
+
8
+ /**
9
+ * Show cell as timeseries chart
10
+ * Can be used for sparkline map of https://datagistips.hypotheses.org/488
11
+ *
12
+ * @author Julien Gaffuri
13
+ */
14
+ export class TimeSeriesStyle extends Style {
15
+ /** @param {object} opts */
16
+ constructor(opts) {
17
+ super(opts)
18
+ opts = opts || {}
19
+
20
+ /** The columns of the time series, ordered in chronological order.
21
+ * @type {Array.<string>} */
22
+ this.ts = opts.ts
23
+
24
+ //x
25
+ /** in geo unit
26
+ * @type {function(import("../Dataset.js").Cell,number,number):number} */
27
+ this.offsetX = opts.offsetX || ((c, r, zf) => 0)
28
+ /** @type {function(import("../Dataset.js").Cell,number,number):number} */
29
+ this.width = opts.width || ((c, r, zf) => r)
30
+
31
+ //y
32
+ /** in geo unit
33
+ * @type {function(import("../Dataset.js").Cell,number,number):number} */
34
+ this.offsetY = opts.offsetY || ((c, r, zf) => 0)
35
+ /** @type {function(import("../Dataset.js").Cell,number,number):number} */
36
+ this.height = opts.height || ((c, r, zf) => r)
37
+ /** @type {function(import("../Dataset.js").Cell,number,number):AnchorModeYEnum} */
38
+ this.anchorModeY = opts.anchorModeY || ((c, r, zf) => "center")
39
+
40
+
41
+ /**
42
+ * @type {string} */
43
+ this.lineWidthCol = opts.lineWidthCol
44
+
45
+ /** A function returning the width of the line, in geo unit
46
+ * @type {function(number,number,import("../Style.js").Stat|undefined,number):number} */
47
+ this.lineWidth = opts.lineWidth || ((v, r, s, zf) => 1.5 * zf)
48
+
49
+
50
+ /**
51
+ * @type {string} */
52
+ this.colorCol = opts.colorCol
53
+
54
+ /** A function returning the color of the cell.
55
+ * @type {function(number,number,import("../Style.js").Stat|undefined,number):string} */
56
+ this.color = opts.color || ((v, r, s, zf) => 'black')
57
+
58
+ }
59
+
60
+ /**
61
+ * Draw cells as text.
62
+ *
63
+ * @param {Array.<import("../Dataset.js").Cell>} cells
64
+ * @param {number} r
65
+ * @param {import("../GeoCanvas.js").GeoCanvas} cg
66
+ */
67
+ draw(cells, r, cg) {
68
+
69
+ //filter
70
+ if (this.filter) cells = cells.filter(this.filter)
71
+
72
+ //zoom factor
73
+ const zf = cg.getZf()
74
+
75
+ let statWidth
76
+ if (this.lineWidthCol) {
77
+ //and compute size variable statistics
78
+ statWidth = Style.getStatistics(cells, (c) => c[this.lineWidthCol], true)
79
+ }
80
+
81
+ let statColor
82
+ if (this.colorCol) {
83
+ //compute color variable statistics
84
+ statColor = Style.getStatistics(cells, (c) => c[this.colorCol], true)
85
+ }
86
+
87
+ //compute cell amplitude
88
+ const getAmplitude = c => {
89
+ let min, max
90
+ for (let t of this.ts) {
91
+ const val = c[t];
92
+ if (val == undefined) continue
93
+ if (min == undefined || val < min) min = val
94
+ if (max == undefined || val > max) max = val
95
+ }
96
+ if (min == undefined) return undefined
97
+ return max - min
98
+ }
99
+
100
+ //compute max amplitude
101
+ let ampMax
102
+ for (let c of cells) {
103
+ const amp = getAmplitude(c)
104
+ if (amp == undefined) continue
105
+ if (ampMax == undefined || amp > ampMax) ampMax = amp
106
+ }
107
+ if (!ampMax) return
108
+
109
+ const nb = this.ts.length
110
+
111
+ //draw with HTML canvas
112
+ //in geo coordinates
113
+ cg.setCanvasTransform()
114
+
115
+ cg.ctx.lineCap = 'butt'
116
+ for (let c of cells) {
117
+
118
+ //line width
119
+ /** @type {number|undefined} */
120
+ const wG = this.lineWidth ? this.lineWidth(c[this.lineWidthCol], r, statWidth, zf) : undefined
121
+ if (!wG || wG < 0) continue
122
+
123
+ //line color
124
+ /** @type {string|undefined} */
125
+ const col = this.color ? this.color(c[this.colorCol], r, statColor, zf) : undefined
126
+ if (!col) continue
127
+
128
+
129
+ //x
130
+ const offX = this.offsetX ? this.offsetX(c, r, zf) : 0
131
+ if (offX == undefined || isNaN(offX)) continue
132
+ const w = this.width ? this.width(c, r, zf) : r
133
+ if (w == undefined || isNaN(w)) continue
134
+
135
+ //y
136
+ const offY = this.offsetY ? this.offsetY(c, r, zf) : 0
137
+ if (offY == undefined || isNaN(offY)) continue
138
+ const h = this.height ? this.height(c, r, zf) : r
139
+ if (h == undefined || isNaN(h)) continue
140
+ const anchY = this.anchorModeY ? this.anchorModeY(c, r, zf) : "center"
141
+ if (!anchY) continue
142
+
143
+ cg.ctx.lineWidth = wG
144
+ cg.ctx.strokeStyle = col
145
+
146
+ //compute anchor Y figures
147
+ let val0, y0
148
+ if (anchY === "first") {
149
+ //get first value
150
+ val0 = c[this.ts[0]]
151
+ y0 = 0
152
+ } else if (anchY === "last") {
153
+ //get last value
154
+ val0 = c[this.ts[this.ts.length - 1]]
155
+ y0 = 0
156
+ } else if (anchY === "bottom") {
157
+ //get min
158
+ for (let t of this.ts) {
159
+ const val = +c[t];
160
+ if (val == undefined) continue
161
+ if (val0 == undefined || val < val0) val0 = val
162
+ }
163
+ y0 = 0
164
+ } else if (anchY === "top") {
165
+ //get max
166
+ for (let t of this.ts) {
167
+ const val = +c[t];
168
+ if (val == undefined) continue
169
+ if (val0 == undefined || val > val0) val0 = val
170
+ }
171
+ y0 = r
172
+ } else if (anchY === "center") {
173
+ //get min and max
174
+ let min, max
175
+ for (let t of this.ts) {
176
+ const val = c[t];
177
+ if (val == undefined) continue
178
+ if (min == undefined || val < min) min = val
179
+ if (max == undefined || val > max) max = val
180
+ }
181
+ val0 = (+max + +min) * 0.5
182
+ y0 = r / 2
183
+ } else {
184
+ console.log("Unexpected anchorModeY: " + anchY)
185
+ continue;
186
+ }
187
+
188
+ if (val0 == undefined || isNaN(val0)) continue
189
+
190
+ //draw line
191
+ cg.ctx.beginPath()
192
+ const sX = w / (nb - 1)
193
+ for (let i = 0; i < nb; i++) {
194
+ const val = c[this.ts[i]]
195
+ if (val == undefined) break
196
+ if (i == 0)
197
+ cg.ctx.moveTo(c.x + i * sX + offX, c.y + y0 + (val - val0) * h / ampMax + offY)
198
+ else
199
+ cg.ctx.lineTo(c.x + i * sX + offX, c.y + y0 + (val - val0) * h / ampMax + offY)
200
+ }
201
+
202
+ cg.ctx.stroke()
203
+
204
+ }
205
+
206
+ }
207
+ }