gridviz 2.0.47 → 2.0.48

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.48",
4
4
  "description": "Visualization tool for gridded statistics",
5
5
  "keywords": [
6
6
  "statistics",
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
+ }