gridviz 3.0.19 → 3.0.21

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 (55) hide show
  1. package/README.md +128 -128
  2. package/dist/gridviz.js +7875 -7857
  3. package/dist/gridviz.min.js +1 -1
  4. package/package.json +74 -74
  5. package/src/button/ZoomButtons.js +101 -101
  6. package/src/core/Dataset.js +154 -154
  7. package/src/core/Drawable.js +78 -60
  8. package/src/core/GeoCanvas.js +430 -430
  9. package/src/core/Layer.js +23 -23
  10. package/src/core/Legend.js +108 -108
  11. package/src/core/Map.js +516 -516
  12. package/src/core/MultiResolutionDataset.js +93 -93
  13. package/src/core/Style.js +98 -98
  14. package/src/dataset/CSVGrid.js +112 -112
  15. package/src/dataset/GeoTIFF.js +122 -122
  16. package/src/dataset/JSGrid.js +55 -55
  17. package/src/dataset/TiledGrid.js +302 -302
  18. package/src/dataset/worker.js +18 -18
  19. package/src/index.js +77 -77
  20. package/src/layer/BackgroundLayer.js +162 -162
  21. package/src/layer/BackgroundLayerImage.js +81 -81
  22. package/src/layer/BackgroundLayerWMS.js +104 -104
  23. package/src/layer/GeoJSONLayer.js +214 -214
  24. package/src/layer/GridLayer.js +148 -148
  25. package/src/layer/LabelLayer.js +194 -194
  26. package/src/legend/ColorCategoryLegend.js +109 -109
  27. package/src/legend/ColorDiscreteLegend.js +100 -100
  28. package/src/legend/ColorLegend.js +115 -115
  29. package/src/legend/OrientationLegend.js +89 -89
  30. package/src/legend/SizeLegend.js +224 -224
  31. package/src/legend/TernaryLegend.js +302 -302
  32. package/src/style/CompositionStyle.js +291 -291
  33. package/src/style/DotDensityStyle.js +133 -133
  34. package/src/style/ImageStyle.js +100 -100
  35. package/src/style/IsoFenceStyle.js +217 -217
  36. package/src/style/JoyPlotStyle.js +127 -127
  37. package/src/style/LegoStyle.js +110 -110
  38. package/src/style/MosaicStyle.js +99 -99
  39. package/src/style/NinjaStarStyle.js +102 -102
  40. package/src/style/PillarStyle.js +207 -207
  41. package/src/style/SegmentStyle.js +105 -105
  42. package/src/style/ShapeColorSizeStyle.js +147 -147
  43. package/src/style/SideCategoryStyle.js +112 -112
  44. package/src/style/SideStyle.js +212 -212
  45. package/src/style/SquareColorCategoryWebGLStyle.js +107 -107
  46. package/src/style/SquareColorWebGLStyle.js +126 -126
  47. package/src/style/StrokeStyle.js +135 -135
  48. package/src/style/TanakaStyle.js +69 -69
  49. package/src/style/TextStyle.js +110 -110
  50. package/src/style/TimeSeriesStyle.js +222 -222
  51. package/src/utils/scale.js +218 -218
  52. package/src/utils/stretching.js +120 -120
  53. package/src/utils/ternary.js +208 -208
  54. package/src/utils/utils.js +58 -58
  55. package/src/utils/webGLUtils.js +72 -72
@@ -1,110 +1,110 @@
1
- //@ts-check
2
- 'use strict'
3
-
4
- import { Style } from '../core/Style.js'
5
-
6
- /**
7
- * @module style
8
- * @author Julien Gaffuri
9
- */
10
- export class TextStyle extends Style {
11
- /** @param {object} opts */
12
- constructor(opts) {
13
- super(opts)
14
- opts = opts || {}
15
-
16
- /** A function returning the text of a cell.
17
- * @type {function(import('../core/Dataset.js').Cell, number, number, object):string} */
18
- this.text = opts.text || (() => 'X') //(c,r,z,vs) => {}
19
-
20
- /** A function returning the color of the cell.
21
- * @type {function(import('../core/Dataset.js').Cell, number, number, object):string} */
22
- this.color = opts.color || (() => 'black') //(c,r,z,vs) => {}
23
-
24
- /** A function returning the font size of a cell in geo unit.
25
- * @type {function(import('../core/Dataset.js').Cell, number, number,object):number} */
26
- this.fontSize = opts.fontSize || ((cell, resolution) => resolution) //(c,r,z,vs) => {}
27
-
28
- /** The text font family.
29
- * @type {function(import('../core/Dataset.js').Cell, number, number, object):string} */
30
- this.fontFamily = opts.fontFamily || (() => 'Arial')
31
-
32
- /** The text font weight.
33
- * @type {function(import('../core/Dataset.js').Cell, number, number, object):string} */
34
- this.fontWeight = opts.fontWeight || (() => 'bold')
35
- }
36
-
37
- /**
38
- * Draw cells as text.
39
- *
40
- * @param {Array.<import("../core/Dataset").Cell>} cells
41
- * @param {import("../core/GeoCanvas").GeoCanvas} geoCanvas
42
- * @param {number} resolution
43
- */
44
- draw(cells, geoCanvas, resolution) {
45
- //filter
46
- if (this.filter) cells = cells.filter(this.filter)
47
-
48
- //
49
- const z = geoCanvas.view.z
50
- const ctx = geoCanvas.offscreenCtx
51
- //get view scale
52
- const viewScale = this.viewScale ? this.viewScale(cells, resolution, z) : undefined
53
-
54
- //draw with HTML canvas
55
- //in screen coordinates
56
- geoCanvas.initCanvasTransform()
57
-
58
- for (let cell of cells) {
59
- //get cell text
60
- const text = this.text ? this.text(cell, resolution, z, viewScale) : undefined
61
- if (text == undefined || text == null || text + '' === '') continue
62
-
63
- //color
64
- const col = this.color ? this.color(cell, resolution, z, viewScale) : undefined
65
- if (!col) continue
66
- ctx.fillStyle = col
67
-
68
- //font size
69
- //size - in pixel unit
70
- const fontSizePix = this.fontSize(cell, resolution, z, viewScale) / z
71
- if (!fontSizePix) continue
72
-
73
- //set font
74
- const fontFamily = this.fontFamily ? this.fontFamily(cell, resolution, z, viewScale) : 'Arial'
75
- const fontWeight = this.fontWeight ? this.fontWeight(cell, resolution, z, viewScale) : 'bold'
76
- ctx.font = fontWeight + ' ' + fontSizePix + 'px ' + fontFamily
77
-
78
- //get offset
79
- const offset = this.offset(cell, resolution, z)
80
-
81
- //text position
82
- ctx.textAlign = 'center'
83
- const tx = geoCanvas.geoToPixX(cell.x + resolution * 0.5 + offset.dx)
84
- const ty = geoCanvas.geoToPixY(cell.y + resolution * 0.5 + offset.dy) + fontSizePix * 0.3 //it should be 0.5 but 0.3 seems to work better
85
-
86
- //draw the text
87
- ctx.fillText(text, tx, ty)
88
- }
89
-
90
- //update legends
91
- this.updateLegends({ style: this, resolution: resolution, z: z, viewScale: viewScale })
92
- }
93
-
94
- /**
95
- * Build a function [0,1]->string for characters legend
96
- *
97
- * @param {Array.<string>} chars
98
- * @param {(function(number):number)|undefined} scale
99
- * @returns {function(number):string}
100
- */
101
- static textScale(chars, scale = undefined) {
102
- const nb = chars.length
103
- return (t) => {
104
- if (scale) t = scale(t)
105
- if (t == 0) return ''
106
- if (t >= 1) return chars[nb - 1]
107
- return chars[Math.floor(t * nb)]
108
- }
109
- }
110
- }
1
+ //@ts-check
2
+ 'use strict'
3
+
4
+ import { Style } from '../core/Style.js'
5
+
6
+ /**
7
+ * @module style
8
+ * @author Julien Gaffuri
9
+ */
10
+ export class TextStyle extends Style {
11
+ /** @param {object} opts */
12
+ constructor(opts) {
13
+ super(opts)
14
+ opts = opts || {}
15
+
16
+ /** A function returning the text of a cell.
17
+ * @type {function(import('../core/Dataset.js').Cell, number, number, object):string} */
18
+ this.text = opts.text || (() => 'X') //(c,r,z,vs) => {}
19
+
20
+ /** A function returning the color of the cell.
21
+ * @type {function(import('../core/Dataset.js').Cell, number, number, object):string} */
22
+ this.color = opts.color || (() => 'black') //(c,r,z,vs) => {}
23
+
24
+ /** A function returning the font size of a cell in geo unit.
25
+ * @type {function(import('../core/Dataset.js').Cell, number, number,object):number} */
26
+ this.fontSize = opts.fontSize || ((cell, resolution) => resolution) //(c,r,z,vs) => {}
27
+
28
+ /** The text font family.
29
+ * @type {function(import('../core/Dataset.js').Cell, number, number, object):string} */
30
+ this.fontFamily = opts.fontFamily || (() => 'Arial')
31
+
32
+ /** The text font weight.
33
+ * @type {function(import('../core/Dataset.js').Cell, number, number, object):string} */
34
+ this.fontWeight = opts.fontWeight || (() => 'bold')
35
+ }
36
+
37
+ /**
38
+ * Draw cells as text.
39
+ *
40
+ * @param {Array.<import("../core/Dataset").Cell>} cells
41
+ * @param {import("../core/GeoCanvas").GeoCanvas} geoCanvas
42
+ * @param {number} resolution
43
+ */
44
+ draw(cells, geoCanvas, resolution) {
45
+ //filter
46
+ if (this.filter) cells = cells.filter(this.filter)
47
+
48
+ //
49
+ const z = geoCanvas.view.z
50
+ const ctx = geoCanvas.offscreenCtx
51
+ //get view scale
52
+ const viewScale = this.viewScale ? this.viewScale(cells, resolution, z) : undefined
53
+
54
+ //draw with HTML canvas
55
+ //in screen coordinates
56
+ geoCanvas.initCanvasTransform()
57
+
58
+ for (let cell of cells) {
59
+ //get cell text
60
+ const text = this.text ? this.text(cell, resolution, z, viewScale) : undefined
61
+ if (text == undefined || text == null || text + '' === '') continue
62
+
63
+ //color
64
+ const col = this.color ? this.color(cell, resolution, z, viewScale) : undefined
65
+ if (!col) continue
66
+ ctx.fillStyle = col
67
+
68
+ //font size
69
+ //size - in pixel unit
70
+ const fontSizePix = this.fontSize(cell, resolution, z, viewScale) / z
71
+ if (!fontSizePix) continue
72
+
73
+ //set font
74
+ const fontFamily = this.fontFamily ? this.fontFamily(cell, resolution, z, viewScale) : 'Arial'
75
+ const fontWeight = this.fontWeight ? this.fontWeight(cell, resolution, z, viewScale) : 'bold'
76
+ ctx.font = fontWeight + ' ' + fontSizePix + 'px ' + fontFamily
77
+
78
+ //get offset
79
+ const offset = this.offset(cell, resolution, z)
80
+
81
+ //text position
82
+ ctx.textAlign = 'center'
83
+ const tx = geoCanvas.geoToPixX(cell.x + resolution * 0.5 + offset.dx)
84
+ const ty = geoCanvas.geoToPixY(cell.y + resolution * 0.5 + offset.dy) + fontSizePix * 0.3 //it should be 0.5 but 0.3 seems to work better
85
+
86
+ //draw the text
87
+ ctx.fillText(text, tx, ty)
88
+ }
89
+
90
+ //update legends
91
+ this.updateLegends({ style: this, resolution: resolution, z: z, viewScale: viewScale })
92
+ }
93
+
94
+ /**
95
+ * Build a function [0,1]->string for characters legend
96
+ *
97
+ * @param {Array.<string>} chars
98
+ * @param {(function(number):number)|undefined} scale
99
+ * @returns {function(number):string}
100
+ */
101
+ static textScale(chars, scale = undefined) {
102
+ const nb = chars.length
103
+ return (t) => {
104
+ if (scale) t = scale(t)
105
+ if (t == 0) return ''
106
+ if (t >= 1) return chars[nb - 1]
107
+ return chars[Math.floor(t * nb)]
108
+ }
109
+ }
110
+ }
@@ -1,222 +1,222 @@
1
- //@ts-check
2
- 'use strict'
3
-
4
- import { Style } from '../core/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
- * @module style
13
- * @author Julien Gaffuri
14
- */
15
- export class TimeSeriesStyle extends Style {
16
- /** @param {object} opts */
17
- constructor(opts) {
18
- super(opts)
19
- opts = opts || {}
20
-
21
- /** The columns of the time series, ordered in chronological order.
22
- * @type {Array.<string>} */
23
- this.ts = opts.ts
24
-
25
- /** A function specifying when a value should be considered as "no data" and thus not ignored. The line will have a break at these values.
26
- * @type {function(string):boolean} */
27
- this.noData = opts.noData || ((v) => v === undefined || v == '' || v === null || isNaN(+v))
28
-
29
- //x
30
- /** in geo unit
31
- * @type {function(import("../core/Dataset.js").Cell,number,number):number} */
32
- this.offsetX = opts.offsetX || ((c, r, z) => 0)
33
- /** @type {function(import("../core/Dataset.js").Cell,number,number):number} */
34
- this.width = opts.width || ((c, r, z) => r)
35
-
36
- //y
37
- /** in geo unit
38
- * @type {function(import("../core/Dataset.js").Cell,number,number):number} */
39
- this.offsetY = opts.offsetY || ((c, r, z) => 0)
40
- /** @type {function(import("../core/Dataset.js").Cell,number,number):number} */
41
- this.height = opts.height || ((c, r, z) => r)
42
- /** @type {function(import("../core/Dataset.js").Cell,number,number):AnchorModeYEnum} */
43
- this.anchorModeY = opts.anchorModeY || ((c, r, z) => 'center')
44
-
45
- /** A function returning the width of the line, in geo unit
46
- * @type {function(import('../core/Dataset.js').Cell, number, number, object):number} */
47
- this.lineWidth = opts.lineWidth || ((v, r, s, z) => 1.5 * z)
48
-
49
- /** A function returning the color of the chart.
50
- * @type {function(import('../core/Dataset.js').Cell, number, number, object):string} */
51
- this.color = opts.color || (() => 'black') //(c,r,z,vs) => {}
52
- }
53
-
54
- /**
55
- * Draw cells as text.
56
- *
57
- * @param {Array.<import("../core/Dataset.js").Cell>} cells
58
- * @param {import("../core/GeoCanvas.js").GeoCanvas} geoCanvas
59
- * @param {number} resolution
60
- */
61
- draw(cells, geoCanvas, resolution) {
62
- //filter
63
- if (this.filter) cells = cells.filter(this.filter)
64
-
65
- //
66
- const z = geoCanvas.view.z
67
- const ctx = geoCanvas.offscreenCtx
68
-
69
- //get view scale
70
- const viewScale = this.viewScale ? this.viewScale(cells, resolution, z) : undefined
71
-
72
- //compute cell amplitude
73
- const getAmplitude = (c) => {
74
- let min, max
75
- for (let t of this.ts) {
76
- const val = c[t]
77
- if (val == undefined) continue
78
- if (min == undefined || val < min) min = val
79
- if (max == undefined || val > max) max = val
80
- }
81
- if (min == undefined) return undefined
82
- return max - min
83
- }
84
-
85
- //compute max amplitude
86
- let ampMax
87
- for (let c of cells) {
88
- const amp = getAmplitude(c)
89
- if (amp == undefined) continue
90
- if (ampMax == undefined || amp > ampMax) ampMax = amp
91
- }
92
- if (!ampMax) return
93
-
94
- const nb = this.ts.length
95
-
96
- ctx.lineCap = 'butt'
97
- for (let c of cells) {
98
- //line width
99
- /** @type {number|undefined} */
100
- const wG = this.lineWidth ? this.lineWidth(c, resolution, z, viewScale) : undefined
101
- if (!wG || wG < 0) continue
102
-
103
- //line color
104
- /** @type {string|undefined} */
105
- const col = this.color ? this.color(c, resolution, z, viewScale) : undefined
106
- if (!col) continue
107
-
108
- //x
109
- const offX = this.offsetX ? this.offsetX(c, resolution, z) : 0
110
- if (offX == undefined || isNaN(offX)) continue
111
- const w = this.width ? this.width(c, resolution, z) : resolution
112
- if (w == undefined || isNaN(w)) continue
113
-
114
- //y
115
- const offY = this.offsetY ? this.offsetY(c, resolution, z) : 0
116
- if (offY == undefined || isNaN(offY)) continue
117
- const h = this.height ? this.height(c, resolution, z) : resolution
118
- if (h == undefined || isNaN(h)) continue
119
- const anchY = this.anchorModeY ? this.anchorModeY(c, resolution, z) : 'center'
120
- if (!anchY) continue
121
-
122
- ctx.lineWidth = wG
123
- ctx.strokeStyle = col
124
-
125
- //compute anchor Y figures
126
- let val0, y0
127
- if (anchY === 'first') {
128
- //get first value
129
- val0 = c[this.ts[0]]
130
- y0 = 0
131
- } else if (anchY === 'last') {
132
- //get last value
133
- val0 = c[this.ts[this.ts.length - 1]]
134
- y0 = 0
135
- } else if (anchY === 'bottom') {
136
- //get min
137
- for (let t of this.ts) {
138
- const val = +c[t]
139
- if (val == undefined) continue
140
- if (val0 == undefined || val < val0) val0 = val
141
- }
142
- y0 = 0
143
- } else if (anchY === 'top') {
144
- //get max
145
- for (let t of this.ts) {
146
- const val = +c[t]
147
- if (val == undefined) continue
148
- if (val0 == undefined || val > val0) val0 = val
149
- }
150
- y0 = resolution
151
- } else if (anchY === 'center') {
152
- //get min and max
153
- let min, max
154
- for (let t of this.ts) {
155
- const val = c[t]
156
- if (val == undefined) continue
157
- if (min == undefined || val < min) min = val
158
- if (max == undefined || val > max) max = val
159
- }
160
- val0 = (+max + +min) * 0.5
161
- y0 = resolution / 2
162
- } else {
163
- console.log('Unexpected anchorModeY: ' + anchY)
164
- continue
165
- }
166
-
167
- /*/draw line
168
- if (val0 == undefined || isNaN(val0)) continue
169
- cg.ctx.beginPath()
170
- const sX = w / (nb - 1)
171
- for (let i = 0; i < nb; i++) {
172
- const val = c[this.ts[i]]
173
- if (val == undefined || isNaN(val)) break
174
- if (i == 0)
175
- cg.ctx.moveTo(c.x + i * sX + offX, c.y + y0 + (val - val0) * h / ampMax + offY)
176
- else
177
- cg.ctx.lineTo(c.x + i * sX + offX, c.y + y0 + (val - val0) * h / ampMax + offY)
178
- }
179
- cg.ctx.stroke()*/
180
-
181
- //draw line, segment by segment
182
- const sX = w / (nb - 1)
183
-
184
- //handle first point
185
- let v0 = c[this.ts[0]]
186
- if (!this.noData(v0)) {
187
- ctx.beginPath()
188
- ctx.moveTo(c.x + offX, c.y + y0 + ((v0 - val0) * h) / ampMax + offY)
189
- }
190
- //console.log(v0, isNaN(v0))
191
-
192
- let v1
193
- for (let i = 1; i < nb; i++) {
194
- v1 = c[this.ts[i]]
195
-
196
- //draw segment from v0 to v1
197
-
198
- //both points 'no data'
199
- if (this.noData(v0) && this.noData(v1)) {
200
- //second point 'no data'
201
- } else if (!this.noData(v0) && this.noData(v1)) {
202
- ctx.stroke()
203
-
204
- //first point 'no data'
205
- } else if (this.noData(v0) && !this.noData(v1)) {
206
- ctx.beginPath()
207
- ctx.moveTo(c.x + i * sX + offX, c.y + y0 + ((v1 - val0) * h) / ampMax + offY)
208
-
209
- //both points have data: trace line
210
- } else {
211
- ctx.lineTo(c.x + i * sX + offX, c.y + y0 + ((v1 - val0) * h) / ampMax + offY)
212
- //if it is the last point, stroke
213
- if (i == nb - 1) ctx.stroke()
214
- }
215
- v0 = v1
216
- }
217
- }
218
-
219
- //update legend, if any
220
- this.updateLegends({ style: this, resolution: resolution, z: z, viewScale: viewScale })
221
- }
222
- }
1
+ //@ts-check
2
+ 'use strict'
3
+
4
+ import { Style } from '../core/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
+ * @module style
13
+ * @author Julien Gaffuri
14
+ */
15
+ export class TimeSeriesStyle extends Style {
16
+ /** @param {object} opts */
17
+ constructor(opts) {
18
+ super(opts)
19
+ opts = opts || {}
20
+
21
+ /** The columns of the time series, ordered in chronological order.
22
+ * @type {Array.<string>} */
23
+ this.ts = opts.ts
24
+
25
+ /** A function specifying when a value should be considered as "no data" and thus not ignored. The line will have a break at these values.
26
+ * @type {function(string):boolean} */
27
+ this.noData = opts.noData || ((v) => v === undefined || v == '' || v === null || isNaN(+v))
28
+
29
+ //x
30
+ /** in geo unit
31
+ * @type {function(import("../core/Dataset.js").Cell,number,number):number} */
32
+ this.offsetX = opts.offsetX || ((c, r, z) => 0)
33
+ /** @type {function(import("../core/Dataset.js").Cell,number,number):number} */
34
+ this.width = opts.width || ((c, r, z) => r)
35
+
36
+ //y
37
+ /** in geo unit
38
+ * @type {function(import("../core/Dataset.js").Cell,number,number):number} */
39
+ this.offsetY = opts.offsetY || ((c, r, z) => 0)
40
+ /** @type {function(import("../core/Dataset.js").Cell,number,number):number} */
41
+ this.height = opts.height || ((c, r, z) => r)
42
+ /** @type {function(import("../core/Dataset.js").Cell,number,number):AnchorModeYEnum} */
43
+ this.anchorModeY = opts.anchorModeY || ((c, r, z) => 'center')
44
+
45
+ /** A function returning the width of the line, in geo unit
46
+ * @type {function(import('../core/Dataset.js').Cell, number, number, object):number} */
47
+ this.lineWidth = opts.lineWidth || ((v, r, s, z) => 1.5 * z)
48
+
49
+ /** A function returning the color of the chart.
50
+ * @type {function(import('../core/Dataset.js').Cell, number, number, object):string} */
51
+ this.color = opts.color || (() => 'black') //(c,r,z,vs) => {}
52
+ }
53
+
54
+ /**
55
+ * Draw cells as text.
56
+ *
57
+ * @param {Array.<import("../core/Dataset.js").Cell>} cells
58
+ * @param {import("../core/GeoCanvas.js").GeoCanvas} geoCanvas
59
+ * @param {number} resolution
60
+ */
61
+ draw(cells, geoCanvas, resolution) {
62
+ //filter
63
+ if (this.filter) cells = cells.filter(this.filter)
64
+
65
+ //
66
+ const z = geoCanvas.view.z
67
+ const ctx = geoCanvas.offscreenCtx
68
+
69
+ //get view scale
70
+ const viewScale = this.viewScale ? this.viewScale(cells, resolution, z) : undefined
71
+
72
+ //compute cell amplitude
73
+ const getAmplitude = (c) => {
74
+ let min, max
75
+ for (let t of this.ts) {
76
+ const val = c[t]
77
+ if (val == undefined) continue
78
+ if (min == undefined || val < min) min = val
79
+ if (max == undefined || val > max) max = val
80
+ }
81
+ if (min == undefined) return undefined
82
+ return max - min
83
+ }
84
+
85
+ //compute max amplitude
86
+ let ampMax
87
+ for (let c of cells) {
88
+ const amp = getAmplitude(c)
89
+ if (amp == undefined) continue
90
+ if (ampMax == undefined || amp > ampMax) ampMax = amp
91
+ }
92
+ if (!ampMax) return
93
+
94
+ const nb = this.ts.length
95
+
96
+ ctx.lineCap = 'butt'
97
+ for (let c of cells) {
98
+ //line width
99
+ /** @type {number|undefined} */
100
+ const wG = this.lineWidth ? this.lineWidth(c, resolution, z, viewScale) : undefined
101
+ if (!wG || wG < 0) continue
102
+
103
+ //line color
104
+ /** @type {string|undefined} */
105
+ const col = this.color ? this.color(c, resolution, z, viewScale) : undefined
106
+ if (!col) continue
107
+
108
+ //x
109
+ const offX = this.offsetX ? this.offsetX(c, resolution, z) : 0
110
+ if (offX == undefined || isNaN(offX)) continue
111
+ const w = this.width ? this.width(c, resolution, z) : resolution
112
+ if (w == undefined || isNaN(w)) continue
113
+
114
+ //y
115
+ const offY = this.offsetY ? this.offsetY(c, resolution, z) : 0
116
+ if (offY == undefined || isNaN(offY)) continue
117
+ const h = this.height ? this.height(c, resolution, z) : resolution
118
+ if (h == undefined || isNaN(h)) continue
119
+ const anchY = this.anchorModeY ? this.anchorModeY(c, resolution, z) : 'center'
120
+ if (!anchY) continue
121
+
122
+ ctx.lineWidth = wG
123
+ ctx.strokeStyle = col
124
+
125
+ //compute anchor Y figures
126
+ let val0, y0
127
+ if (anchY === 'first') {
128
+ //get first value
129
+ val0 = c[this.ts[0]]
130
+ y0 = 0
131
+ } else if (anchY === 'last') {
132
+ //get last value
133
+ val0 = c[this.ts[this.ts.length - 1]]
134
+ y0 = 0
135
+ } else if (anchY === 'bottom') {
136
+ //get min
137
+ for (let t of this.ts) {
138
+ const val = +c[t]
139
+ if (val == undefined) continue
140
+ if (val0 == undefined || val < val0) val0 = val
141
+ }
142
+ y0 = 0
143
+ } else if (anchY === 'top') {
144
+ //get max
145
+ for (let t of this.ts) {
146
+ const val = +c[t]
147
+ if (val == undefined) continue
148
+ if (val0 == undefined || val > val0) val0 = val
149
+ }
150
+ y0 = resolution
151
+ } else if (anchY === 'center') {
152
+ //get min and max
153
+ let min, max
154
+ for (let t of this.ts) {
155
+ const val = c[t]
156
+ if (val == undefined) continue
157
+ if (min == undefined || val < min) min = val
158
+ if (max == undefined || val > max) max = val
159
+ }
160
+ val0 = (+max + +min) * 0.5
161
+ y0 = resolution / 2
162
+ } else {
163
+ console.log('Unexpected anchorModeY: ' + anchY)
164
+ continue
165
+ }
166
+
167
+ /*/draw line
168
+ if (val0 == undefined || isNaN(val0)) continue
169
+ cg.ctx.beginPath()
170
+ const sX = w / (nb - 1)
171
+ for (let i = 0; i < nb; i++) {
172
+ const val = c[this.ts[i]]
173
+ if (val == undefined || isNaN(val)) break
174
+ if (i == 0)
175
+ cg.ctx.moveTo(c.x + i * sX + offX, c.y + y0 + (val - val0) * h / ampMax + offY)
176
+ else
177
+ cg.ctx.lineTo(c.x + i * sX + offX, c.y + y0 + (val - val0) * h / ampMax + offY)
178
+ }
179
+ cg.ctx.stroke()*/
180
+
181
+ //draw line, segment by segment
182
+ const sX = w / (nb - 1)
183
+
184
+ //handle first point
185
+ let v0 = c[this.ts[0]]
186
+ if (!this.noData(v0)) {
187
+ ctx.beginPath()
188
+ ctx.moveTo(c.x + offX, c.y + y0 + ((v0 - val0) * h) / ampMax + offY)
189
+ }
190
+ //console.log(v0, isNaN(v0))
191
+
192
+ let v1
193
+ for (let i = 1; i < nb; i++) {
194
+ v1 = c[this.ts[i]]
195
+
196
+ //draw segment from v0 to v1
197
+
198
+ //both points 'no data'
199
+ if (this.noData(v0) && this.noData(v1)) {
200
+ //second point 'no data'
201
+ } else if (!this.noData(v0) && this.noData(v1)) {
202
+ ctx.stroke()
203
+
204
+ //first point 'no data'
205
+ } else if (this.noData(v0) && !this.noData(v1)) {
206
+ ctx.beginPath()
207
+ ctx.moveTo(c.x + i * sX + offX, c.y + y0 + ((v1 - val0) * h) / ampMax + offY)
208
+
209
+ //both points have data: trace line
210
+ } else {
211
+ ctx.lineTo(c.x + i * sX + offX, c.y + y0 + ((v1 - val0) * h) / ampMax + offY)
212
+ //if it is the last point, stroke
213
+ if (i == nb - 1) ctx.stroke()
214
+ }
215
+ v0 = v1
216
+ }
217
+ }
218
+
219
+ //update legend, if any
220
+ this.updateLegends({ style: this, resolution: resolution, z: z, viewScale: viewScale })
221
+ }
222
+ }