gridviz 2.1.13 → 2.1.15

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 (62) hide show
  1. package/README.md +124 -125
  2. package/dist/gridviz.js +8900 -8368
  3. package/dist/gridviz.min.js +1 -1
  4. package/package.json +77 -77
  5. package/src/App.js +760 -758
  6. package/src/BackgroundLayer.js +182 -182
  7. package/src/BackgroundLayerWMS.js +131 -131
  8. package/src/Dataset.js +76 -76
  9. package/src/DatasetComponent.js +113 -113
  10. package/src/GeoCanvas.js +420 -420
  11. package/src/LabelLayer.js +190 -190
  12. package/src/Layer.js +103 -103
  13. package/src/Legend.js +86 -86
  14. package/src/LineLayer.js +150 -150
  15. package/src/Style.js +131 -129
  16. package/src/Tooltip.js +182 -180
  17. package/src/button/Button.js +66 -54
  18. package/src/button/FullscreenButton.js +120 -98
  19. package/src/button/ZoomButtons.js +95 -72
  20. package/src/dataset/COGeo.js +24 -24
  21. package/src/dataset/CSVGrid.js +104 -104
  22. package/src/dataset/GeoTIFF.js +127 -127
  23. package/src/dataset/GridTile.js +41 -41
  24. package/src/dataset/LGrid.js +54 -54
  25. package/src/dataset/TiledGrid.js +264 -264
  26. package/src/index.js +73 -71
  27. package/src/legend/ColorCategoryLegend.js +124 -124
  28. package/src/legend/ColorDiscreteLegend.js +103 -103
  29. package/src/legend/ColorLegend.js +128 -128
  30. package/src/legend/SegmentOrientationLegend.js +87 -87
  31. package/src/legend/SegmentWidthLegend.js +101 -101
  32. package/src/legend/SizeLegend.js +141 -141
  33. package/src/style/CompositionStyle.js +300 -300
  34. package/src/style/CompositionStyle_.js +293 -0
  35. package/src/style/ContourStyle.js +54 -54
  36. package/src/style/DotDensityStyle.js +156 -156
  37. package/src/style/IsoFenceStyle.js +262 -262
  38. package/src/style/JoyPlotStyle.js +135 -135
  39. package/src/style/LegoStyle.js +112 -112
  40. package/src/style/MosaicStyle.js +107 -107
  41. package/src/style/NinjaStarStyle.js +125 -125
  42. package/src/style/PillarStyle.js +230 -230
  43. package/src/style/SegmentStyle.js +142 -142
  44. package/src/style/ShapeColorSizeStyle.js +152 -152
  45. package/src/style/ShapeColorSizeStyle_.js +108 -0
  46. package/src/style/SideCatStyle.js +172 -172
  47. package/src/style/SideStyle.js +216 -216
  48. package/src/style/SquareColorCatWGLStyle.js +120 -120
  49. package/src/style/SquareColorWGLStyle.js +146 -146
  50. package/src/style/StrokeStyle.js +131 -131
  51. package/src/style/TanakaStyle.js +160 -160
  52. package/src/style/TextStyle.js +132 -132
  53. package/src/style/TimeSeriesStyle.js +259 -259
  54. package/src/utils/Utils.js +35 -35
  55. package/src/utils/WebGLRectangleColoring.js +106 -106
  56. package/src/utils/WebGLSquareColoring.js +118 -118
  57. package/src/utils/WebGLSquareColoringAdvanced.js +230 -230
  58. package/src/utils/WebGLSquareColoringCatAdvanced.js +133 -133
  59. package/src/utils/WebGLSquareColoringKS.js +153 -153
  60. package/src/utils/stretching.js +154 -131
  61. package/src/utils/webGLUtils.js +71 -71
  62. package/src/utils/zoomUtils.js +142 -142
@@ -1,259 +1,259 @@
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
- /** 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.
25
- * @type {function(string):boolean} */
26
- this.noData = opts.noData || ((v) => v === undefined || v == "" || v === null || isNaN(+v))
27
-
28
- //x
29
- /** in geo unit
30
- * @type {function(import("../Dataset.js").Cell,number,number):number} */
31
- this.offsetX = opts.offsetX || ((c, r, zf) => 0)
32
- /** @type {function(import("../Dataset.js").Cell,number,number):number} */
33
- this.width = opts.width || ((c, r, zf) => r)
34
-
35
- //y
36
- /** in geo unit
37
- * @type {function(import("../Dataset.js").Cell,number,number):number} */
38
- this.offsetY = opts.offsetY || ((c, r, zf) => 0)
39
- /** @type {function(import("../Dataset.js").Cell,number,number):number} */
40
- this.height = opts.height || ((c, r, zf) => r)
41
- /** @type {function(import("../Dataset.js").Cell,number,number):AnchorModeYEnum} */
42
- this.anchorModeY = opts.anchorModeY || ((c, r, zf) => "center")
43
-
44
-
45
- /**
46
- * @type {string} */
47
- this.lineWidthCol = opts.lineWidthCol
48
-
49
- /** A function returning the width of the line, in geo unit
50
- * @type {function(number,number,import("../Style.js").Stat|undefined,number):number} */
51
- this.lineWidth = opts.lineWidth || ((v, r, s, zf) => 1.5 * zf)
52
-
53
- /**
54
- * @type {string} */
55
- this.colorCol = opts.colorCol
56
-
57
- /** A function returning the color of the cell.
58
- * @type {function(number,number,import("../Style.js").Stat|undefined,number):string} */
59
- this.color = opts.color || ((v, r, s, zf) => 'black')
60
-
61
- }
62
-
63
- /**
64
- * Draw cells as text.
65
- *
66
- * @param {Array.<import("../Dataset.js").Cell>} cells
67
- * @param {number} r
68
- * @param {import("../GeoCanvas.js").GeoCanvas} cg
69
- */
70
- draw(cells, r, cg) {
71
-
72
- //filter
73
- if (this.filter) cells = cells.filter(this.filter)
74
-
75
- //zoom factor
76
- const zf = cg.getZf()
77
-
78
- let statWidth
79
- if (this.lineWidthCol) {
80
- //and compute size variable statistics
81
- statWidth = Style.getStatistics(cells, (c) => c[this.lineWidthCol], true)
82
- }
83
-
84
- let statColor
85
- if (this.colorCol) {
86
- //compute color variable statistics
87
- statColor = Style.getStatistics(cells, (c) => c[this.colorCol], true)
88
- }
89
-
90
- //compute cell amplitude
91
- const getAmplitude = c => {
92
- let min, max
93
- for (let t of this.ts) {
94
- const val = c[t];
95
- if (val == undefined) continue
96
- if (min == undefined || val < min) min = val
97
- if (max == undefined || val > max) max = val
98
- }
99
- if (min == undefined) return undefined
100
- return max - min
101
- }
102
-
103
- //compute max amplitude
104
- let ampMax
105
- for (let c of cells) {
106
- const amp = getAmplitude(c)
107
- if (amp == undefined) continue
108
- if (ampMax == undefined || amp > ampMax) ampMax = amp
109
- }
110
- if (!ampMax) return
111
-
112
- const nb = this.ts.length
113
-
114
- //draw with HTML canvas
115
- //in geo coordinates
116
- cg.setCanvasTransform()
117
-
118
- cg.ctx.lineCap = "butt"
119
- for (let c of cells) {
120
-
121
- //line width
122
- /** @type {number|undefined} */
123
- const wG = this.lineWidth ? this.lineWidth(c[this.lineWidthCol], r, statWidth, zf) : undefined
124
- if (!wG || wG < 0) continue
125
-
126
- //line color
127
- /** @type {string|undefined} */
128
- const col = this.color ? this.color(c[this.colorCol], r, statColor, zf) : undefined
129
- if (!col) continue
130
-
131
-
132
- //x
133
- const offX = this.offsetX ? this.offsetX(c, r, zf) : 0
134
- if (offX == undefined || isNaN(offX)) continue
135
- const w = this.width ? this.width(c, r, zf) : r
136
- if (w == undefined || isNaN(w)) continue
137
-
138
- //y
139
- const offY = this.offsetY ? this.offsetY(c, r, zf) : 0
140
- if (offY == undefined || isNaN(offY)) continue
141
- const h = this.height ? this.height(c, r, zf) : r
142
- if (h == undefined || isNaN(h)) continue
143
- const anchY = this.anchorModeY ? this.anchorModeY(c, r, zf) : "center"
144
- if (!anchY) continue
145
-
146
- cg.ctx.lineWidth = wG
147
- cg.ctx.strokeStyle = col
148
-
149
- //compute anchor Y figures
150
- let val0, y0
151
- if (anchY === "first") {
152
- //get first value
153
- val0 = c[this.ts[0]]
154
- y0 = 0
155
- } else if (anchY === "last") {
156
- //get last value
157
- val0 = c[this.ts[this.ts.length - 1]]
158
- y0 = 0
159
- } else if (anchY === "bottom") {
160
- //get min
161
- for (let t of this.ts) {
162
- const val = +c[t];
163
- if (val == undefined) continue
164
- if (val0 == undefined || val < val0) val0 = val
165
- }
166
- y0 = 0
167
- } else if (anchY === "top") {
168
- //get max
169
- for (let t of this.ts) {
170
- const val = +c[t];
171
- if (val == undefined) continue
172
- if (val0 == undefined || val > val0) val0 = val
173
- }
174
- y0 = r
175
- } else if (anchY === "center") {
176
- //get min and max
177
- let min, max
178
- for (let t of this.ts) {
179
- const val = c[t];
180
- if (val == undefined) continue
181
- if (min == undefined || val < min) min = val
182
- if (max == undefined || val > max) max = val
183
- }
184
- val0 = (+max + +min) * 0.5
185
- y0 = r / 2
186
- } else {
187
- console.log("Unexpected anchorModeY: " + anchY)
188
- continue;
189
- }
190
-
191
- /*/draw line
192
- if (val0 == undefined || isNaN(val0)) continue
193
- cg.ctx.beginPath()
194
- const sX = w / (nb - 1)
195
- for (let i = 0; i < nb; i++) {
196
- const val = c[this.ts[i]]
197
- if (val == undefined || isNaN(val)) break
198
- if (i == 0)
199
- cg.ctx.moveTo(c.x + i * sX + offX, c.y + y0 + (val - val0) * h / ampMax + offY)
200
- else
201
- cg.ctx.lineTo(c.x + i * sX + offX, c.y + y0 + (val - val0) * h / ampMax + offY)
202
- }
203
- cg.ctx.stroke()*/
204
-
205
-
206
- //draw line, segment by segment
207
- const sX = w / (nb - 1)
208
-
209
- //handle first point
210
- let v0 = c[this.ts[0]]
211
- if (!this.noData(v0)) {
212
- cg.ctx.beginPath()
213
- cg.ctx.moveTo(c.x + offX, c.y + y0 + (v0 - val0) * h / ampMax + offY)
214
- }
215
- //console.log(v0, isNaN(v0))
216
-
217
- let v1
218
- for (let i = 1; i < nb; i++) {
219
- v1 = c[this.ts[i]]
220
-
221
- //draw segment from v0 to v1
222
-
223
- //both points 'no data'
224
- if (this.noData(v0) && this.noData(v1)) {
225
-
226
- //second point 'no data'
227
- } else if (!this.noData(v0) && this.noData(v1)) {
228
- cg.ctx.stroke()
229
-
230
- //first point 'no data'
231
- } else if (this.noData(v0) && !this.noData(v1)) {
232
- cg.ctx.beginPath()
233
- cg.ctx.moveTo(c.x + i * sX + offX, c.y + y0 + (v1 - val0) * h / ampMax + offY)
234
-
235
- //both points have data: trace line
236
- } else {
237
- cg.ctx.lineTo(c.x + i * sX + offX, c.y + y0 + (v1 - val0) * h / ampMax + offY)
238
- //if it is the last point, stroke
239
- if (i == nb - 1) cg.ctx.stroke()
240
- }
241
- v0 = v1
242
- }
243
-
244
- }
245
-
246
- //update legend, if any
247
- this.updateLegends({
248
- widthFun: this.lineWidth,
249
- r: r,
250
- zf: zf,
251
- sColor: statColor,
252
- //sLength: statLength,
253
- sWidth: statWidth,
254
- })
255
-
256
- }
257
-
258
- }
259
-
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
+ /** 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.
25
+ * @type {function(string):boolean} */
26
+ this.noData = opts.noData || ((v) => v === undefined || v == "" || v === null || isNaN(+v))
27
+
28
+ //x
29
+ /** in geo unit
30
+ * @type {function(import("../Dataset.js").Cell,number,number):number} */
31
+ this.offsetX = opts.offsetX || ((c, r, zf) => 0)
32
+ /** @type {function(import("../Dataset.js").Cell,number,number):number} */
33
+ this.width = opts.width || ((c, r, zf) => r)
34
+
35
+ //y
36
+ /** in geo unit
37
+ * @type {function(import("../Dataset.js").Cell,number,number):number} */
38
+ this.offsetY = opts.offsetY || ((c, r, zf) => 0)
39
+ /** @type {function(import("../Dataset.js").Cell,number,number):number} */
40
+ this.height = opts.height || ((c, r, zf) => r)
41
+ /** @type {function(import("../Dataset.js").Cell,number,number):AnchorModeYEnum} */
42
+ this.anchorModeY = opts.anchorModeY || ((c, r, zf) => "center")
43
+
44
+
45
+ /**
46
+ * @type {string} */
47
+ this.lineWidthCol = opts.lineWidthCol
48
+
49
+ /** A function returning the width of the line, in geo unit
50
+ * @type {function(number,number,import("../Style.js").Stat|undefined,number):number} */
51
+ this.lineWidth = opts.lineWidth || ((v, r, s, zf) => 1.5 * zf)
52
+
53
+ /**
54
+ * @type {string} */
55
+ this.colorCol = opts.colorCol
56
+
57
+ /** A function returning the color of the cell.
58
+ * @type {function(number,number,import("../Style.js").Stat|undefined,number):string} */
59
+ this.color = opts.color || ((v, r, s, zf) => 'black')
60
+
61
+ }
62
+
63
+ /**
64
+ * Draw cells as text.
65
+ *
66
+ * @param {Array.<import("../Dataset.js").Cell>} cells
67
+ * @param {number} r
68
+ * @param {import("../GeoCanvas.js").GeoCanvas} cg
69
+ */
70
+ draw(cells, r, cg) {
71
+
72
+ //filter
73
+ if (this.filter) cells = cells.filter(this.filter)
74
+
75
+ //zoom factor
76
+ const zf = cg.getZf()
77
+
78
+ let statWidth
79
+ if (this.lineWidthCol) {
80
+ //and compute size variable statistics
81
+ statWidth = Style.getStatistics(cells, (c) => c[this.lineWidthCol], true)
82
+ }
83
+
84
+ let statColor
85
+ if (this.colorCol) {
86
+ //compute color variable statistics
87
+ statColor = Style.getStatistics(cells, (c) => c[this.colorCol], true)
88
+ }
89
+
90
+ //compute cell amplitude
91
+ const getAmplitude = c => {
92
+ let min, max
93
+ for (let t of this.ts) {
94
+ const val = c[t];
95
+ if (val == undefined) continue
96
+ if (min == undefined || val < min) min = val
97
+ if (max == undefined || val > max) max = val
98
+ }
99
+ if (min == undefined) return undefined
100
+ return max - min
101
+ }
102
+
103
+ //compute max amplitude
104
+ let ampMax
105
+ for (let c of cells) {
106
+ const amp = getAmplitude(c)
107
+ if (amp == undefined) continue
108
+ if (ampMax == undefined || amp > ampMax) ampMax = amp
109
+ }
110
+ if (!ampMax) return
111
+
112
+ const nb = this.ts.length
113
+
114
+ //draw with HTML canvas
115
+ //in geo coordinates
116
+ cg.setCanvasTransform()
117
+
118
+ cg.ctx.lineCap = "butt"
119
+ for (let c of cells) {
120
+
121
+ //line width
122
+ /** @type {number|undefined} */
123
+ const wG = this.lineWidth ? this.lineWidth(c[this.lineWidthCol], r, statWidth, zf) : undefined
124
+ if (!wG || wG < 0) continue
125
+
126
+ //line color
127
+ /** @type {string|undefined} */
128
+ const col = this.color ? this.color(c[this.colorCol], r, statColor, zf) : undefined
129
+ if (!col) continue
130
+
131
+
132
+ //x
133
+ const offX = this.offsetX ? this.offsetX(c, r, zf) : 0
134
+ if (offX == undefined || isNaN(offX)) continue
135
+ const w = this.width ? this.width(c, r, zf) : r
136
+ if (w == undefined || isNaN(w)) continue
137
+
138
+ //y
139
+ const offY = this.offsetY ? this.offsetY(c, r, zf) : 0
140
+ if (offY == undefined || isNaN(offY)) continue
141
+ const h = this.height ? this.height(c, r, zf) : r
142
+ if (h == undefined || isNaN(h)) continue
143
+ const anchY = this.anchorModeY ? this.anchorModeY(c, r, zf) : "center"
144
+ if (!anchY) continue
145
+
146
+ cg.ctx.lineWidth = wG
147
+ cg.ctx.strokeStyle = col
148
+
149
+ //compute anchor Y figures
150
+ let val0, y0
151
+ if (anchY === "first") {
152
+ //get first value
153
+ val0 = c[this.ts[0]]
154
+ y0 = 0
155
+ } else if (anchY === "last") {
156
+ //get last value
157
+ val0 = c[this.ts[this.ts.length - 1]]
158
+ y0 = 0
159
+ } else if (anchY === "bottom") {
160
+ //get min
161
+ for (let t of this.ts) {
162
+ const val = +c[t];
163
+ if (val == undefined) continue
164
+ if (val0 == undefined || val < val0) val0 = val
165
+ }
166
+ y0 = 0
167
+ } else if (anchY === "top") {
168
+ //get max
169
+ for (let t of this.ts) {
170
+ const val = +c[t];
171
+ if (val == undefined) continue
172
+ if (val0 == undefined || val > val0) val0 = val
173
+ }
174
+ y0 = r
175
+ } else if (anchY === "center") {
176
+ //get min and max
177
+ let min, max
178
+ for (let t of this.ts) {
179
+ const val = c[t];
180
+ if (val == undefined) continue
181
+ if (min == undefined || val < min) min = val
182
+ if (max == undefined || val > max) max = val
183
+ }
184
+ val0 = (+max + +min) * 0.5
185
+ y0 = r / 2
186
+ } else {
187
+ console.log("Unexpected anchorModeY: " + anchY)
188
+ continue;
189
+ }
190
+
191
+ /*/draw line
192
+ if (val0 == undefined || isNaN(val0)) continue
193
+ cg.ctx.beginPath()
194
+ const sX = w / (nb - 1)
195
+ for (let i = 0; i < nb; i++) {
196
+ const val = c[this.ts[i]]
197
+ if (val == undefined || isNaN(val)) break
198
+ if (i == 0)
199
+ cg.ctx.moveTo(c.x + i * sX + offX, c.y + y0 + (val - val0) * h / ampMax + offY)
200
+ else
201
+ cg.ctx.lineTo(c.x + i * sX + offX, c.y + y0 + (val - val0) * h / ampMax + offY)
202
+ }
203
+ cg.ctx.stroke()*/
204
+
205
+
206
+ //draw line, segment by segment
207
+ const sX = w / (nb - 1)
208
+
209
+ //handle first point
210
+ let v0 = c[this.ts[0]]
211
+ if (!this.noData(v0)) {
212
+ cg.ctx.beginPath()
213
+ cg.ctx.moveTo(c.x + offX, c.y + y0 + (v0 - val0) * h / ampMax + offY)
214
+ }
215
+ //console.log(v0, isNaN(v0))
216
+
217
+ let v1
218
+ for (let i = 1; i < nb; i++) {
219
+ v1 = c[this.ts[i]]
220
+
221
+ //draw segment from v0 to v1
222
+
223
+ //both points 'no data'
224
+ if (this.noData(v0) && this.noData(v1)) {
225
+
226
+ //second point 'no data'
227
+ } else if (!this.noData(v0) && this.noData(v1)) {
228
+ cg.ctx.stroke()
229
+
230
+ //first point 'no data'
231
+ } else if (this.noData(v0) && !this.noData(v1)) {
232
+ cg.ctx.beginPath()
233
+ cg.ctx.moveTo(c.x + i * sX + offX, c.y + y0 + (v1 - val0) * h / ampMax + offY)
234
+
235
+ //both points have data: trace line
236
+ } else {
237
+ cg.ctx.lineTo(c.x + i * sX + offX, c.y + y0 + (v1 - val0) * h / ampMax + offY)
238
+ //if it is the last point, stroke
239
+ if (i == nb - 1) cg.ctx.stroke()
240
+ }
241
+ v0 = v1
242
+ }
243
+
244
+ }
245
+
246
+ //update legend, if any
247
+ this.updateLegends({
248
+ widthFun: this.lineWidth,
249
+ r: r,
250
+ zf: zf,
251
+ sColor: statColor,
252
+ //sLength: statLength,
253
+ sWidth: statWidth,
254
+ })
255
+
256
+ }
257
+
258
+ }
259
+
@@ -1,35 +1,35 @@
1
- //@ts-check
2
- 'use strict'
3
-
4
- /**
5
- * Get the class id from a value and class break values
6
- *
7
- * @param {number} v the value
8
- * @param {Array.<number>} breaks the breaks
9
- * @returns The class id, from 0 to breaks.length
10
- */
11
- export function getClass(v, breaks) {
12
- if (!breaks) return
13
- if (breaks.length == 0) return 0
14
- if (v <= breaks[0]) return 0
15
- for (let i = 1; i < breaks.length; i++) if (breaks[i - 1] < v && v <= breaks[i]) return i
16
- return breaks.length
17
- }
18
-
19
- export let monitor = false
20
-
21
- let previousDate
22
- export function monitorDuration(message) {
23
- const nowDate = Date.now()
24
-
25
- //first call
26
- if (!previousDate) {
27
- previousDate = nowDate
28
- console.log(previousDate, message)
29
- return
30
- }
31
-
32
- const d = nowDate - previousDate
33
- previousDate = nowDate
34
- console.log(d, message)
35
- }
1
+ //@ts-check
2
+ 'use strict'
3
+
4
+ /**
5
+ * Get the class id from a value and class break values
6
+ *
7
+ * @param {number} v the value
8
+ * @param {Array.<number>} breaks the breaks
9
+ * @returns The class id, from 0 to breaks.length
10
+ */
11
+ export function getClass(v, breaks) {
12
+ if (!breaks) return
13
+ if (breaks.length == 0) return 0
14
+ if (v <= breaks[0]) return 0
15
+ for (let i = 1; i < breaks.length; i++) if (breaks[i - 1] < v && v <= breaks[i]) return i
16
+ return breaks.length
17
+ }
18
+
19
+ export let monitor = false
20
+
21
+ let previousDate
22
+ export function monitorDuration(message) {
23
+ const nowDate = Date.now()
24
+
25
+ //first call
26
+ if (!previousDate) {
27
+ previousDate = nowDate
28
+ console.log(previousDate, message)
29
+ return
30
+ }
31
+
32
+ const d = nowDate - previousDate
33
+ previousDate = nowDate
34
+ console.log(d, message)
35
+ }