gridviz 3.0.1 → 3.0.3

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 (58) hide show
  1. package/README.md +36 -33
  2. package/dist/gridviz.js +1300 -200
  3. package/dist/gridviz.min.js +1 -1
  4. package/package.json +4 -3
  5. package/src/button/Button.js +1 -0
  6. package/src/button/FullscreenButton.js +1 -0
  7. package/src/button/ZoomButtons.js +1 -0
  8. package/src/core/Dataset.js +1 -0
  9. package/src/core/Drawable.js +1 -0
  10. package/src/core/GeoCanvas.js +1 -0
  11. package/src/core/Layer.js +1 -0
  12. package/src/core/Legend.js +1 -0
  13. package/src/core/Map.js +1 -0
  14. package/src/core/MultiResolutionDataset.js +1 -0
  15. package/src/core/Style.js +11 -2
  16. package/src/core/Tooltip.js +1 -0
  17. package/src/dataset/COGeo.js +1 -0
  18. package/src/dataset/CSVGrid.js +1 -0
  19. package/src/dataset/GeoTIFF.js +1 -0
  20. package/src/dataset/JSGrid.js +1 -0
  21. package/src/dataset/TiledGrid.js +1 -0
  22. package/src/index.js +7 -3
  23. package/src/layer/BackgroundLayer.js +1 -0
  24. package/src/layer/BackgroundLayerWMS.js +1 -0
  25. package/src/layer/GeoJSONLayer.js +1 -0
  26. package/src/layer/GridLayer.js +1 -0
  27. package/src/layer/LabelLayer.js +1 -0
  28. package/src/legend/ColorCategoryLegend.js +1 -0
  29. package/src/legend/ColorDiscreteLegend.js +2 -0
  30. package/src/legend/ColorLegend.js +1 -0
  31. package/src/legend/OrientationLegend.js +1 -0
  32. package/src/legend/SizeLegend.js +4 -2
  33. package/src/legend/TrivariateLegend.js +210 -0
  34. package/src/style/CompositionStyle.js +1 -0
  35. package/src/style/ContourStyle.js +1 -0
  36. package/src/style/DotDensityStyle.js +1 -0
  37. package/src/style/ImageStyle.js +81 -0
  38. package/src/style/IsoFenceStyle.js +24 -72
  39. package/src/style/JoyPlotStyle.js +1 -1
  40. package/src/style/LegoStyle.js +22 -24
  41. package/src/style/MosaicStyle.js +1 -0
  42. package/src/style/NinjaStarStyle.js +1 -1
  43. package/src/style/PillarStyle.js +3 -2
  44. package/src/style/SegmentStyle.js +2 -1
  45. package/src/style/ShapeColorSizeStyle.js +29 -0
  46. package/src/style/SideCategoryStyle.js +1 -0
  47. package/src/style/SideStyle.js +50 -27
  48. package/src/style/SquareColorCategoryWebGLStyle.js +4 -3
  49. package/src/style/SquareColorWebGLStyle.js +1 -0
  50. package/src/style/StrokeStyle.js +48 -16
  51. package/src/style/TanakaStyle.js +11 -5
  52. package/src/style/TextStyle.js +1 -1
  53. package/src/style/TimeSeriesStyle.js +1 -0
  54. package/src/utils/chernoff.js +14 -0
  55. package/src/utils/scale.js +22 -11
  56. package/src/utils/stretching.js +4 -0
  57. package/src/utils/trivariate.js +203 -0
  58. package/src/utils/utils.js +11 -0
@@ -4,7 +4,7 @@
4
4
  import { Style } from '../core/Style.js'
5
5
 
6
6
  /**
7
- *
7
+ * @module style
8
8
  * @author Julien Gaffuri
9
9
  */
10
10
  export class StrokeStyle extends Style {
@@ -49,50 +49,82 @@ export class StrokeStyle extends Style {
49
49
  const viewScale = this.viewScale ? this.viewScale(cells, resolution, z) : undefined
50
50
 
51
51
  const r2 = resolution * 0.5
52
- for (let cell of cells) {
52
+ for (let c of cells) {
53
53
 
54
54
  //color
55
- const col = this.strokeColor ? this.strokeColor(cell, resolution, z, viewScale) : undefined
55
+ const col = this.strokeColor ? this.strokeColor(c, resolution, z, viewScale) : undefined
56
56
  if (!col || col === 'none') continue
57
57
  geoCanvas.ctx.strokeStyle = col
58
58
 
59
59
  //size - in geo unit
60
- const sG = this.size ? this.size(cell, resolution, z, viewScale) : resolution
60
+ const size = this.size ? this.size(c, resolution, z, viewScale) : resolution
61
61
 
62
62
  //width
63
- const wi = this.strokeWidth ? this.strokeWidth(cell, resolution, z, viewScale) : 1 * z
63
+ const wi = this.strokeWidth ? this.strokeWidth(c, resolution, z, viewScale) : 1 * z
64
64
  if (!wi || wi <= 0) continue
65
65
  geoCanvas.ctx.lineWidth = wi
66
66
 
67
67
  //shape
68
- const shape = this.shape ? this.shape(cell, resolution, z, viewScale) : 'square'
68
+ const shape = this.shape ? this.shape(c, resolution, z, viewScale) : 'square'
69
69
  if (shape === 'none') continue
70
70
 
71
71
  //get offset
72
- const offset = this.offset(cell, resolution, z)
72
+ const offset = this.offset(c, resolution, z)
73
73
 
74
74
  if (shape === 'square') {
75
75
  //draw square
76
- const d = resolution * (1 - sG / resolution) * 0.5
76
+ const d = resolution * (1 - size / resolution) * 0.5
77
77
  geoCanvas.ctx.beginPath()
78
- geoCanvas.ctx.rect(cell.x + d + offset.dx, cell.y + d + offset.dy, sG, sG)
78
+ geoCanvas.ctx.rect(c.x + d + offset.dx, c.y + d + offset.dy, size, size)
79
79
  geoCanvas.ctx.stroke()
80
80
  } else if (shape === 'circle') {
81
81
  //draw circle
82
82
  geoCanvas.ctx.beginPath()
83
- geoCanvas.ctx.arc(cell.x + r2 + offset.dx, cell.y + r2 + offset.dy, sG * 0.5, 0, 2 * Math.PI, false)
83
+ geoCanvas.ctx.arc(c.x + r2 + offset.dx, c.y + r2 + offset.dy, size * 0.5, 0, 2 * Math.PI, false)
84
84
  geoCanvas.ctx.stroke()
85
85
  } else if (shape === 'diamond') {
86
- const s2 = sG * 0.5
86
+ const s2 = size * 0.5
87
87
  geoCanvas.ctx.beginPath()
88
- geoCanvas.ctx.moveTo(cell.x + r2 - s2, cell.y + r2)
89
- geoCanvas.ctx.lineTo(cell.x + r2, cell.y + r2 + s2)
90
- geoCanvas.ctx.lineTo(cell.x + r2 + s2, cell.y + r2)
91
- geoCanvas.ctx.lineTo(cell.x + r2, cell.y + r2 - s2)
92
- geoCanvas.ctx.lineTo(cell.x + r2 - s2, cell.y + r2)
88
+ geoCanvas.ctx.moveTo(c.x + r2 - s2, c.y + r2)
89
+ geoCanvas.ctx.lineTo(c.x + r2, c.y + r2 + s2)
90
+ geoCanvas.ctx.lineTo(c.x + r2 + s2, c.y + r2)
91
+ geoCanvas.ctx.lineTo(c.x + r2, c.y + r2 - s2)
92
+ geoCanvas.ctx.lineTo(c.x + r2 - s2, c.y + r2)
93
93
  geoCanvas.ctx.stroke()
94
94
  } else if (shape === 'donut') {
95
95
  console.error('Not implemented')
96
+ } else if (shape === 'triangle_up') {
97
+ const dr2 = (size - resolution) / 2
98
+ geoCanvas.ctx.beginPath()
99
+ geoCanvas.ctx.moveTo(c.x - dr2, c.y - dr2)
100
+ geoCanvas.ctx.lineTo(c.x + r2, c.y + resolution + dr2)
101
+ geoCanvas.ctx.lineTo(c.x + resolution + dr2, c.y - dr2)
102
+ geoCanvas.ctx.closePath()
103
+ geoCanvas.ctx.stroke()
104
+ } else if (shape === 'triangle_down') {
105
+ const dr2 = (size - resolution) / 2
106
+ geoCanvas.ctx.beginPath()
107
+ geoCanvas.ctx.moveTo(c.x - dr2, c.y + resolution + dr2)
108
+ geoCanvas.ctx.lineTo(c.x + r2, c.y - dr2)
109
+ geoCanvas.ctx.lineTo(c.x + resolution + dr2, c.y + resolution + dr2)
110
+ geoCanvas.ctx.closePath()
111
+ geoCanvas.ctx.stroke()
112
+ } else if (shape === 'triangle_left') {
113
+ const dr2 = (size - resolution) / 2
114
+ geoCanvas.ctx.beginPath()
115
+ geoCanvas.ctx.moveTo(c.x + resolution + dr2, c.y + resolution + dr2)
116
+ geoCanvas.ctx.lineTo(c.x - dr2, c.y + r2)
117
+ geoCanvas.ctx.lineTo(c.x + resolution + dr2, c.y - dr2)
118
+ geoCanvas.ctx.closePath()
119
+ geoCanvas.ctx.stroke()
120
+ } else if (shape === 'triangle_right') {
121
+ const dr2 = (size - resolution) / 2
122
+ geoCanvas.ctx.beginPath()
123
+ geoCanvas.ctx.moveTo(c.x - dr2, c.y - dr2)
124
+ geoCanvas.ctx.lineTo(c.x + resolution + dr2, c.y + r2)
125
+ geoCanvas.ctx.lineTo(c.x - dr2, c.y + resolution + dr2)
126
+ geoCanvas.ctx.closePath()
127
+ geoCanvas.ctx.stroke()
96
128
  } else {
97
129
  throw new Error('Unexpected shape:' + shape)
98
130
  }
@@ -8,6 +8,7 @@ import { classifier as clFun, colorClassifier as cclFun } from '../utils/scale.j
8
8
  /**
9
9
  * @see https://manifold.net/doc/mfd9/example__tanaka_contours.htm
10
10
  *
11
+ * @module style
11
12
  * @author Julien Gaffuri
12
13
  */
13
14
  export class TanakaStyle {
@@ -32,15 +33,20 @@ export class TanakaStyle {
32
33
  return Math.min(minWG + (sideValue - 1) * step, maxWG)
33
34
  })
34
35
 
35
- //make classifiers
36
+ //make classifier
36
37
  const classifier = clFun(breaks)
37
- const colorClassifier = cclFun(breaks, colors)
38
+ //make colors table
39
+ const colorsDict = {};
40
+ for (let i = 0; i < colors.length; i++) colorsDict[i + ""] = colors[i]
38
41
 
39
- const cellStyle = new SquareColorCategoryWebGLStyle({ color: (cell) => colorClassifier(value(cell)) })
42
+ const cellStyle = new SquareColorCategoryWebGLStyle({
43
+ code: cell => classifier(value(cell)),
44
+ color: colorsDict
45
+ })
40
46
 
41
47
  const getSideValue = (side) => {
42
- const cl1 = side.c1 ? classifier(value(side.c1)) : 0
43
- const cl2 = side.c2 ? classifier(value(side.c2)) : 0
48
+ const cl1 = side.c1 ? classifier(value(side.c1)) : -1
49
+ const cl2 = side.c2 ? classifier(value(side.c2)) : -1
44
50
  return cl1 - cl2
45
51
  }
46
52
 
@@ -4,7 +4,7 @@
4
4
  import { Style } from '../core/Style.js'
5
5
 
6
6
  /**
7
- *
7
+ * @module style
8
8
  * @author Julien Gaffuri
9
9
  */
10
10
  export class TextStyle extends Style {
@@ -9,6 +9,7 @@ import { Style } from '../core/Style.js'
9
9
  * Show cell as timeseries chart
10
10
  * Can be used for sparkline map of https://datagistips.hypotheses.org/488
11
11
  *
12
+ * @module style
12
13
  * @author Julien Gaffuri
13
14
  */
14
15
  export class TimeSeriesStyle extends Style {
@@ -0,0 +1,14 @@
1
+ //@ts-check
2
+ 'use strict'
3
+
4
+ import { loadImage } from '../utils/utils.js'
5
+
6
+ //load chernoff images
7
+ export async function loadChernoffFacesImages(out, baseURL, callback = undefined) {
8
+ out = out || {}
9
+ for (let s of ["f", "m"])
10
+ for (let a of ["y", "m", "o"])
11
+ for (let i = 0; i <= 2; i++)
12
+ out[s + a + i] = await loadImage(baseURL + (s + a + i) + ".png")
13
+ if (callback) callback()
14
+ }
@@ -13,6 +13,10 @@ import { scaleQuantile } from "d3-scale"
13
13
  * @typedef {function(number):string} ColorScale */
14
14
 
15
15
 
16
+ /**
17
+ * @module utils
18
+ */
19
+
16
20
 
17
21
  /**
18
22
  * Generic function for view scale - continuous or discrete
@@ -29,9 +33,9 @@ export const viewScale = (opts) => {
29
33
  const range_ = opts.range
30
34
  const domain_ = opts.domain
31
35
  const classNumber = opts.classNumber
32
- return (cells, r, z) => {
36
+ return (cells, resolution, z) => {
33
37
  const domain = domain_ || [minValue, max(cells, valueFunction)]
34
- const range = range_ || [minSizePix * z, r * maxSizeFactor]
38
+ const range = range_ || [minSizePix * z, resolution * maxSizeFactor]
35
39
  const domainSize = domain[1] - domain[0], domainMin = domain[0]
36
40
  const rangeSize = range[1] - range[0], rangeMin = range[0]
37
41
  return t => {
@@ -61,9 +65,9 @@ export const viewScaleQuantile = (opts) => {
61
65
  const minSizePix = opts.minSizePix || 1
62
66
  const maxSizeFactor = opts.maxSizeFactor || 1
63
67
  const scale = scaleQuantile()
64
- return (cells, r, z) => {
68
+ return (cells, resolution, z) => {
65
69
  scale.domain(cells.map(valueFunction))
66
- const minSizeGeo = minSizePix * z, maxSizeGeo = r * maxSizeFactor
70
+ const minSizeGeo = minSizePix * z, maxSizeGeo = resolution * maxSizeFactor
67
71
  scale.range(Array.from({ length: classNumber }, (_, i) => minSizeGeo + i * (maxSizeGeo - minSizeGeo) / (classNumber - 1)))
68
72
  scale.breaks = scale.quantiles()
69
73
  scale.values = scale.range()
@@ -87,9 +91,10 @@ export const viewScaleColor = (opts) => {
87
91
  const stretching = opts.stretching
88
92
  let colorScale = opts.colorScale || (() => "purple")
89
93
 
90
- //case of discrete colors
91
- if (opts.colors)
92
- colorScale = t => opts.colors[t == 1 ? opts.colors.length - 1 : Math.floor(t * opts.colors.length)]
94
+ //discrete colors case: build continuous color scale from discrete ones.
95
+ const nbClass = opts.colors?.length
96
+ if (opts.colors && nbClass)
97
+ colorScale = t => opts.colors[t == 1 ? nbClass - 1 : Math.floor(t * nbClass)]
93
98
 
94
99
  return (cells) => {
95
100
  if (cells.length == 0 || !cells) return
@@ -109,6 +114,12 @@ export const viewScaleColor = (opts) => {
109
114
  if (stretching) t = stretching.invert(t)
110
115
  return domain[0] + t * domainSize
111
116
  }
117
+ //discrete colors: return the breaks
118
+ if (opts.colors && nbClass) {
119
+ scale.breaks = []
120
+ for (let i = 1; i < nbClass; i++)
121
+ scale.breaks.push(scale.invert(i / nbClass))
122
+ }
112
123
 
113
124
  return scale;
114
125
  }
@@ -145,16 +156,16 @@ export const viewScaleColorQuantile = (opts) => {
145
156
 
146
157
 
147
158
  /**
148
- * combine view scale functions
159
+ * Combine view scale functions
149
160
  *
150
161
  * @param {*} obj
151
- * @returns
162
+ * @returns {function}
152
163
  */
153
164
  export const viewScaleCombination = (obj) => {
154
165
  //obj: prop and a function to call
155
- return (cells, r, z) => {
166
+ return (cells, resolution, z) => {
156
167
  const out = {}
157
- for (const p in obj) { out[p] = obj[p](cells, r, z) }
168
+ for (const p in obj) { out[p] = obj[p](cells, resolution, z) }
158
169
  return out
159
170
  }
160
171
  }
@@ -1,6 +1,10 @@
1
1
  //@ts-check
2
2
  'use strict'
3
3
 
4
+ /**
5
+ * @module utils
6
+ */
7
+
4
8
 
5
9
  //TODO invert for circular
6
10
  //TODO use Math.sqrt
@@ -0,0 +1,203 @@
1
+ //@ts-check
2
+ 'use strict'
3
+
4
+ import { interpolateLab } from "d3-interpolate"
5
+
6
+
7
+
8
+ export const trivariateClassifier = (properties, totalFunction, opts = {}) => {
9
+
10
+ //the three properties
11
+ const p0 = properties[0], p1 = properties[1], p2 = properties[2]
12
+
13
+ //the classifier center point. sum must be equal to 1
14
+ const [c0, c1, c2] = opts.center || [1 / 3, 1 / 3, 1 / 3]
15
+
16
+ //parameter to decide wether to use mixed classes m0, m1, m2.
17
+ const withMixedClasses = opts.withMixedClasses != undefined ? opts.withMixedClasses : true
18
+
19
+ //paramerter decide wether to use a central class, and the size of this central class.
20
+ //set to 0 or undefined for not showing any central class. Set to 1 for a central class that contains the mix classes
21
+ const cc = opts.centerCoefficient ? 1 - opts.centerCoefficient : undefined
22
+
23
+ //the output classifier method
24
+ const fun = c => {
25
+ //get total
26
+ const tot = totalFunction(c)
27
+ if (!tot) return undefined
28
+ //compute shares
29
+ const [s0, s1, s2] = [+c[p0] / tot, +c[p1] / tot, +c[p2] / tot]
30
+
31
+ //class 0
32
+ if (s0 >= c0 && s1 <= c1 && s2 <= c2) {
33
+ //central class near class 0
34
+ if (cc != undefined && (s2 - c2) * (c1 - cc * c1) >= (s1 - cc * c1) * (cc * c2 - c2))
35
+ return "center"
36
+ return "0"
37
+ }
38
+ //class 1
39
+ if (s0 <= c0 && s1 >= c1 && s2 <= c2) {
40
+ //central class near class 1
41
+ if (cc != undefined && (s2 - c2) * (c0 - cc * c0) >= (s0 - cc * c0) * (cc * c2 - c2))
42
+ return "center"
43
+ return "1"
44
+ }
45
+ //class 2
46
+ if (s0 <= c0 && s1 <= c1 && s2 >= c2) {
47
+ //central class near class 2
48
+ if (cc != undefined && (s1 - c1) * (c0 - cc * c0) >= (s0 - cc * c0) * (cc * c1 - c1))
49
+ return "center"
50
+ return "2"
51
+ }
52
+ //middle class 0 - intersection class 1 and 2
53
+ if (s0 <= c0 && s1 >= c1 && s2 >= c2) {
54
+ //central class
55
+ if (cc != undefined && s0 > cc * c0) return "center"
56
+ if (withMixedClasses) return "m0"
57
+ return s1 > s2 ? "1" : "2"
58
+ }
59
+ //middle class 1 - intersection class 0 and 1
60
+ if (s0 >= c0 && s1 <= c1 && s2 >= c2) {
61
+ //central class
62
+ if (cc != undefined && s1 > cc * c1) return "center"
63
+ if (withMixedClasses) return "m1"
64
+ return s0 > s2 ? "0" : "2"
65
+ }
66
+ //middle class 2 - intersection class 0 and 1
67
+ if (s0 >= c0 && s1 >= c1 && s2 <= c2) {
68
+ //central class
69
+ if (cc != undefined && s2 > cc * c2) return "center"
70
+ if (withMixedClasses) return "m2"
71
+ return s1 > s0 ? "1" : "0"
72
+ }
73
+ //should not happen
74
+ return "unknown"
75
+ }
76
+
77
+ //attach information to output function
78
+ fun.center = [c0, c1, c2]
79
+ fun.centerCoefficient = opts.centerCoefficient
80
+
81
+ return fun
82
+ }
83
+
84
+
85
+
86
+ export const trivariateColorClassifier = (properties, totalFunction, colors, opts = {}) => {
87
+
88
+ //the three colors
89
+ const [color0, color1, color2] = colors || ["red", "green", "blue"]
90
+
91
+ //the color interpolation function
92
+ const colorInterpolation = opts.colorInterpolation || interpolateLab
93
+
94
+ //parameter to decide wether to use mixed classes.
95
+ const withMixedClasses = opts.withMixedClasses != undefined ? opts.withMixedClasses : true
96
+ //https://d3js.org/d3-interpolate/color
97
+ const mixColorFunction = (color1, color2) => colorInterpolation(color1, color2)(0.5)
98
+ //the colors corresponding to the mixed classes
99
+ const [mixColor0, mixColor1, mixColor2] = opts.mixedColors || withMixedClasses ? [mixColorFunction(color1, color2), mixColorFunction(color0, color2), mixColorFunction(color0, color1)] : []
100
+
101
+ //the central color, used for the central class, if any. The central class is the class of relatively balanced values, around the center point
102
+ const centerColor = opts.centerColor || colorInterpolation(mixColorFunction(color0, color1), color2)(0.333)
103
+
104
+ //make classifier
105
+ const classifier = trivariateClassifier(properties, totalFunction, opts)
106
+
107
+ //the output color classifier method
108
+ const fun = c => {
109
+ const cla = classifier(c)
110
+ if (cla == "0") return color0
111
+ if (cla == "1") return color1
112
+ if (cla == "2") return color2
113
+ if (cla == "m0") return mixColor0
114
+ if (cla == "m1") return mixColor1
115
+ if (cla == "m2") return mixColor2
116
+ if (cla == "center") return centerColor
117
+ return opts.defaultColor || "black"
118
+ }
119
+ fun.center = classifier.center
120
+ fun.centerCoefficient = opts.centerCoefficient
121
+ fun.colors = [color0, color1, color2]
122
+ fun.mixColors = [mixColor0, mixColor1, mixColor2]
123
+ fun.centerColor = centerColor
124
+
125
+ return fun
126
+ }
127
+
128
+
129
+
130
+
131
+
132
+
133
+ /*
134
+ const orderedIndexesDec = arr => [...arr.keys()].sort((a, b) => arr[b] - arr[a]);
135
+ const orderedIndexesInc = arr => [...arr.keys()].sort((a, b) => arr[a] - arr[b]);
136
+
137
+ export const trivariateClassifier = (properties, totalFunction, opts = {}) => {
138
+ const lowThreshold = opts.lowThreshold || [1 / 3, 1 / 3, 1 / 3]
139
+ const highThreshold = opts.highThreshold || [2 / 3, 2 / 3, 2 / 3]
140
+ const colors = opts.colors || ["red", "green", "blue"]
141
+ const colorInterpolation = opts.colorInterpolation || interpolateLab
142
+
143
+ //https://d3js.org/d3-interpolate/color
144
+ const middleColorFunction = (color1, color2) => colorInterpolation(color1, color2)(0.5)
145
+ const middleColors = opts.middleColors || [middleColorFunction(colors[1], colors[2]), middleColorFunction(colors[0], colors[2]), middleColorFunction(colors[0], colors[1])]
146
+ const centerColor = opts.centerColor || colorInterpolation(middleColors[2], colors[2])(0.333)
147
+
148
+ const high_ = orderedIndexesDec(highThreshold)
149
+ const low_ = orderedIndexesInc(lowThreshold)
150
+
151
+ const p0 = properties[0], p1 = properties[1], p2 = properties[2]
152
+ const fun = c => {
153
+ //get total
154
+ const tot = totalFunction(c)
155
+ if (!tot) return undefined
156
+ //compute shares
157
+ const shares = [+c[p0] / tot, +c[p1] / tot, +c[p2] / tot]
158
+ //return colors
159
+ //start first with the extreme high (triangles): from the larger value (small triangle) to the lower value (large triangle)
160
+ for (let i of high_) if (shares[i] > highThreshold[i]) return colors[i]
161
+ //then draw the extreme low (trapeziums): from the lower value (small trapeziums) to the larger values (large trapezium)
162
+ for (let i of low_) if (shares[i] < lowThreshold[i]) return middleColors[i]
163
+ //else central color
164
+ return centerColor
165
+ }
166
+ fun.lowThreshold = lowThreshold
167
+ fun.highThreshold = highThreshold
168
+ fun.colors = colors
169
+ fun.middleColors = middleColors
170
+ fun.centerColor = centerColor
171
+ fun.lowIndex = low_
172
+ fun.highIndex = high_
173
+ return fun
174
+ }
175
+ */
176
+
177
+ /*
178
+ export const trivariateClassifier3 = (properties, totalFunction, opts = {}) => {
179
+ const [a01, a12, a20] = opts.thresholds || [1/3, 1/3, 1/3]
180
+ const [c0, c1, c2] = opts.colors || ["red", "green", "blue"]
181
+ const centerColor = opts.centerColor || "gray"
182
+
183
+ const fff = a => a == 1 ? Infinity : a / (1 - a)
184
+ const c01 = fff(a01), c12 = fff(a12), c20 = fff(a20)
185
+
186
+ const p0 = properties[0], p1 = properties[1], p2 = properties[2]
187
+ const fun = c => {
188
+ //get total
189
+ const tot = totalFunction(c)
190
+ if (!tot) return undefined
191
+ //compute shares
192
+ const [s0, s1, s2] = [+c[p0] / tot, +c[p1] / tot, +c[p2] / tot]
193
+ //return colors
194
+ if (s0 * c01 > s1 && s0 > s2 * c20) return c0
195
+ else if (s1 > s0 * c01 && s1 * c12 > s2) return c1
196
+ else if (s2 * c20 > s0 && s2 > s1 * c12) return c2
197
+ else return centerColor
198
+ }
199
+ fun.colors = [c0, c1, c2]
200
+ fun.centerColor = centerColor
201
+ return fun
202
+ }
203
+ */
@@ -29,8 +29,18 @@ export function nice(v, multiples = [8, 6, 5, 4, 2.5, 2]) {
29
29
  }
30
30
 
31
31
 
32
+ export function loadImage(src) {
33
+ return new Promise((resolve, reject) => {
34
+ const img = new Image();
35
+ img.onload = function () { resolve(img); };
36
+ img.onerror = function () { reject(new Error('Error loading image')); };
37
+ img.src = src;
38
+ });
39
+ }
40
+
32
41
 
33
42
 
43
+ /*
34
44
  export let monitor = false
35
45
 
36
46
  let previousDate
@@ -48,3 +58,4 @@ export function monitorDuration(message) {
48
58
  previousDate = nowDate
49
59
  console.log(d, message)
50
60
  }
61
+ */