gridviz 3.0.35 → 3.0.36

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 (67) hide show
  1. package/README.md +129 -129
  2. package/dist/gridviz.js +17486 -17486
  3. package/dist/gridviz.min.js +2 -2
  4. package/package.json +73 -73
  5. package/src/button/Button.js +67 -67
  6. package/src/button/FullscreenButton.js +121 -121
  7. package/src/button/ZoomButtons.js +103 -103
  8. package/src/core/Dataset.js +154 -154
  9. package/src/core/Drawable.js +78 -78
  10. package/src/core/GeoCanvas.js +443 -443
  11. package/src/core/Layer.js +23 -23
  12. package/src/core/Legend.js +108 -108
  13. package/src/core/Map.js +568 -568
  14. package/src/core/MultiResolutionDataset.js +93 -93
  15. package/src/core/Style.js +98 -98
  16. package/src/core/Tooltip.js +182 -182
  17. package/src/dataset/COGeo.js +25 -25
  18. package/src/dataset/CSVGrid.js +112 -112
  19. package/src/dataset/GeoTIFF.js +122 -122
  20. package/src/dataset/JSGrid.js +55 -55
  21. package/src/dataset/TiledGrid.js +302 -302
  22. package/src/dataset/worker.js +18 -18
  23. package/src/index.js +79 -79
  24. package/src/layer/BackgroundLayer.js +173 -173
  25. package/src/layer/BackgroundLayerImage.js +81 -81
  26. package/src/layer/BackgroundLayerWMS.js +104 -104
  27. package/src/layer/GeoJSONLayer.js +276 -276
  28. package/src/layer/GridLayer.js +166 -166
  29. package/src/layer/LabelLayer.js +194 -194
  30. package/src/legend/ColorCategoryLegend.js +109 -109
  31. package/src/legend/ColorDiscreteLegend.js +100 -100
  32. package/src/legend/ColorLegend.js +115 -115
  33. package/src/legend/OrientationLegend.js +89 -89
  34. package/src/legend/SizeLegend.js +224 -224
  35. package/src/legend/TernaryLegend.js +302 -302
  36. package/src/style/CompositionStyle.js +291 -291
  37. package/src/style/DotDensityStyle.js +133 -133
  38. package/src/style/ImageStyle.js +100 -100
  39. package/src/style/Interpolator.js +258 -258
  40. package/src/style/IsoFenceStyle.js +217 -217
  41. package/src/style/JoyPlotStyle.js +180 -180
  42. package/src/style/LegoStyle.js +132 -132
  43. package/src/style/MosaicStyle.js +99 -99
  44. package/src/style/NinjaStarStyle.js +102 -102
  45. package/src/style/PillarStyle.js +207 -207
  46. package/src/style/SegmentStyle.js +105 -105
  47. package/src/style/ShadingRayStyle.js +280 -280
  48. package/src/style/ShadingStyle.js +72 -72
  49. package/src/style/ShapeColorSizeStyle.js +147 -147
  50. package/src/style/SideCategoryStyle.js +112 -112
  51. package/src/style/SideStyle.js +217 -217
  52. package/src/style/SideTanakaStyle.js +154 -154
  53. package/src/style/SquareColorCategoryWebGLStyle.js +307 -294
  54. package/src/style/SquareColorWebGLStyle.js +126 -126
  55. package/src/style/StrokeStyle.js +135 -135
  56. package/src/style/TextStyle.js +110 -110
  57. package/src/style/TimeSeriesStyle.js +222 -222
  58. package/src/utils/WebGLRectangleColoring.js +106 -106
  59. package/src/utils/WebGLSquareColoring.js +118 -118
  60. package/src/utils/WebGLSquareColoringAdvanced.js +227 -227
  61. package/src/utils/WebGLSquareColoringKS.js +153 -153
  62. package/src/utils/scale.js +255 -255
  63. package/src/utils/stretching.js +120 -120
  64. package/src/utils/ternary.js +208 -208
  65. package/src/utils/utils.js +125 -125
  66. package/src/utils/webGLUtils.js +73 -73
  67. package/src/utils/zoomUtils.js +142 -142
@@ -1,120 +1,120 @@
1
- //@ts-check
2
- 'use strict'
3
-
4
- /**
5
- * @module utils
6
- */
7
-
8
- //TODO invert for circular
9
- //TODO use Math.sqrt
10
- //TODO validate
11
-
12
- /**
13
- * Some function [0,1]->[0,1] to stretch range of values.
14
- * @see https://github.com/eurostat/gridviz/blob/master/docs/reference.md#stretching
15
- * @see https://observablehq.com/@jgaffuri/stretching
16
- */
17
-
18
- //identity function
19
- const identity = (t) => t
20
- identity.invert = identity
21
-
22
- /**
23
- * @param {number} base
24
- * @returns {function(number):number}
25
- */
26
- export const exponentialScale = (base = 3) => {
27
- if (base == 0) return identity
28
- const a = Math.exp(base) - 1
29
- const f = (t) => (Math.exp(t * base) - 1) / a
30
- f.invert = (t) => Math.log(a * t + 1) / base
31
- return f
32
- }
33
-
34
- /**
35
- * @param {number} base
36
- * @returns {function(number):number}
37
- */
38
- export const logarithmicScale = (base = 3) => {
39
- if (base == 0) return identity
40
- const a = Math.exp(base),
41
- b = 1 - a
42
- const f = (t) => 1 - Math.log(a + t * b) / base
43
- f.invert = (t) => (Math.exp((1 - t) * base) - a) / b
44
- return f
45
- }
46
-
47
- /**
48
- * @param {number} exponent
49
- * @returns {function(number):number}
50
- */
51
- export const powerScale = (exponent = 3) => {
52
- if (exponent == 1) return identity
53
- //TODO if (exponent == 0.5) return Math.sqrt
54
- const f = (t) => Math.pow(t, exponent)
55
- const a = 1 / exponent
56
- f.invert = (t) => Math.pow(t, a)
57
- return f
58
- }
59
-
60
- /**
61
- * @param {number} exponent
62
- * @returns {function(number):number}
63
- */
64
- export const powerInverseScale = (exponent = 3) => {
65
- if (exponent == 1) return identity
66
- //TODO if (exponent == 2) return t => 1 - Math.sqrt(1 - t)
67
- const a = 1 / exponent
68
- const f = (t) => 1 - Math.pow(1 - t, a)
69
- f.invert = (t) => 1 - Math.pow(1 - t, exponent)
70
- return f
71
- }
72
-
73
- /**
74
- * @param {number} circularity
75
- * @returns {function(number):number}
76
- */
77
- export const circularScale = (circularity = 0.8) => {
78
- if (circularity == 0) return identity
79
- if (circularity == 1) return (t) => Math.sqrt(t * (2 - t))
80
- else {
81
- const a = circularity / (1 - circularity)
82
- return (t) => Math.sqrt(1 / (a * a) + t * (2 / a + 2 - t)) - 1 / a
83
- }
84
- }
85
-
86
- /**
87
- * @param {number} circularity
88
- * @returns {function(number):number}
89
- */
90
- export const circularInverseScale = (circularity = 0.8) => {
91
- if (circularity == 0) return identity
92
- const f = circularScale(circularity)
93
- return (t) => 1 - f(1 - t)
94
- }
95
-
96
- //test
97
- /*
98
- const test = (f, fun, a, err = 1e-12) => {
99
- for (let t = 0; t <= 1; t += 1 / 50) {
100
- const er = t - f.invert(f(t))
101
- if (Math.abs(er) < err) continue
102
- console.log(fun, a, er)
103
- }
104
- }
105
-
106
- for (let fun of [powerScale, powerInverseScale])
107
- for (let exp = -30; exp <= 50; exp += 1) {
108
- if (exp == 0) continue
109
- const f = fun(exp)
110
- test(f, fun, exp)
111
- }
112
-
113
-
114
- for (let fun of [exponentialScale, logarithmicScale])
115
- for (let base = -20; base <= 20; base += 1) {
116
- //if (exp == 0) continue
117
- const f = fun(base)
118
- test(f, fun, base, 1e-10)
119
- }
120
- */
1
+ //@ts-check
2
+ 'use strict'
3
+
4
+ /**
5
+ * @module utils
6
+ */
7
+
8
+ //TODO invert for circular
9
+ //TODO use Math.sqrt
10
+ //TODO validate
11
+
12
+ /**
13
+ * Some function [0,1]->[0,1] to stretch range of values.
14
+ * @see https://github.com/eurostat/gridviz/blob/master/docs/reference.md#stretching
15
+ * @see https://observablehq.com/@jgaffuri/stretching
16
+ */
17
+
18
+ //identity function
19
+ const identity = (t) => t
20
+ identity.invert = identity
21
+
22
+ /**
23
+ * @param {number} base
24
+ * @returns {function(number):number}
25
+ */
26
+ export const exponentialScale = (base = 3) => {
27
+ if (base == 0) return identity
28
+ const a = Math.exp(base) - 1
29
+ const f = (t) => (Math.exp(t * base) - 1) / a
30
+ f.invert = (t) => Math.log(a * t + 1) / base
31
+ return f
32
+ }
33
+
34
+ /**
35
+ * @param {number} base
36
+ * @returns {function(number):number}
37
+ */
38
+ export const logarithmicScale = (base = 3) => {
39
+ if (base == 0) return identity
40
+ const a = Math.exp(base),
41
+ b = 1 - a
42
+ const f = (t) => 1 - Math.log(a + t * b) / base
43
+ f.invert = (t) => (Math.exp((1 - t) * base) - a) / b
44
+ return f
45
+ }
46
+
47
+ /**
48
+ * @param {number} exponent
49
+ * @returns {function(number):number}
50
+ */
51
+ export const powerScale = (exponent = 3) => {
52
+ if (exponent == 1) return identity
53
+ //TODO if (exponent == 0.5) return Math.sqrt
54
+ const f = (t) => Math.pow(t, exponent)
55
+ const a = 1 / exponent
56
+ f.invert = (t) => Math.pow(t, a)
57
+ return f
58
+ }
59
+
60
+ /**
61
+ * @param {number} exponent
62
+ * @returns {function(number):number}
63
+ */
64
+ export const powerInverseScale = (exponent = 3) => {
65
+ if (exponent == 1) return identity
66
+ //TODO if (exponent == 2) return t => 1 - Math.sqrt(1 - t)
67
+ const a = 1 / exponent
68
+ const f = (t) => 1 - Math.pow(1 - t, a)
69
+ f.invert = (t) => 1 - Math.pow(1 - t, exponent)
70
+ return f
71
+ }
72
+
73
+ /**
74
+ * @param {number} circularity
75
+ * @returns {function(number):number}
76
+ */
77
+ export const circularScale = (circularity = 0.8) => {
78
+ if (circularity == 0) return identity
79
+ if (circularity == 1) return (t) => Math.sqrt(t * (2 - t))
80
+ else {
81
+ const a = circularity / (1 - circularity)
82
+ return (t) => Math.sqrt(1 / (a * a) + t * (2 / a + 2 - t)) - 1 / a
83
+ }
84
+ }
85
+
86
+ /**
87
+ * @param {number} circularity
88
+ * @returns {function(number):number}
89
+ */
90
+ export const circularInverseScale = (circularity = 0.8) => {
91
+ if (circularity == 0) return identity
92
+ const f = circularScale(circularity)
93
+ return (t) => 1 - f(1 - t)
94
+ }
95
+
96
+ //test
97
+ /*
98
+ const test = (f, fun, a, err = 1e-12) => {
99
+ for (let t = 0; t <= 1; t += 1 / 50) {
100
+ const er = t - f.invert(f(t))
101
+ if (Math.abs(er) < err) continue
102
+ console.log(fun, a, er)
103
+ }
104
+ }
105
+
106
+ for (let fun of [powerScale, powerInverseScale])
107
+ for (let exp = -30; exp <= 50; exp += 1) {
108
+ if (exp == 0) continue
109
+ const f = fun(exp)
110
+ test(f, fun, exp)
111
+ }
112
+
113
+
114
+ for (let fun of [exponentialScale, logarithmicScale])
115
+ for (let base = -20; base <= 20; base += 1) {
116
+ //if (exp == 0) continue
117
+ const f = fun(base)
118
+ test(f, fun, base, 1e-10)
119
+ }
120
+ */
@@ -1,208 +1,208 @@
1
- //@ts-check
2
- 'use strict'
3
-
4
- import { interpolateLab } from 'd3-interpolate'
5
-
6
- export const ternaryClassifier = (properties, totalFunction, opts = {}) => {
7
- //the three properties
8
- const p0 = properties[0],
9
- p1 = properties[1],
10
- p2 = properties[2]
11
-
12
- //the classifier center point. sum must be equal to 1
13
- const [c0, c1, c2] = opts.center || [1 / 3, 1 / 3, 1 / 3]
14
-
15
- //parameter to decide wether to use mixed classes m0, m1, m2.
16
- const withMixedClasses = opts.withMixedClasses != undefined ? opts.withMixedClasses : true
17
-
18
- //parameter to decide wether to use a central class, and the size of this central class.
19
- //set to 0 or undefined for not showing any central class. Set to 1 for a central class that contains the mix classes
20
- const cc = opts.centerCoefficient ? 1 - opts.centerCoefficient : undefined
21
-
22
- //the output classifier method
23
- const fun = (c) => {
24
- //get total
25
- const tot = totalFunction(c)
26
- if (!tot) return undefined
27
- //compute shares
28
- const [s0, s1, s2] = [+c[p0] / tot, +c[p1] / tot, +c[p2] / tot]
29
-
30
- //class 0
31
- if (s0 >= c0 && s1 <= c1 && s2 <= c2) {
32
- //central class near class 0
33
- if (cc != undefined && (s2 - c2) * (c1 - cc * c1) >= (s1 - cc * c1) * (cc * c2 - c2))
34
- return 'center'
35
- return '0'
36
- }
37
- //class 1
38
- if (s0 <= c0 && s1 >= c1 && s2 <= c2) {
39
- //central class near class 1
40
- if (cc != undefined && (s2 - c2) * (c0 - cc * c0) >= (s0 - cc * c0) * (cc * c2 - c2))
41
- return 'center'
42
- return '1'
43
- }
44
- //class 2
45
- if (s0 <= c0 && s1 <= c1 && s2 >= c2) {
46
- //central class near class 2
47
- if (cc != undefined && (s1 - c1) * (c0 - cc * c0) >= (s0 - cc * c0) * (cc * c1 - c1))
48
- return 'center'
49
- return '2'
50
- }
51
- //middle class 0 - intersection class 1 and 2
52
- if (s0 <= c0 && s1 >= c1 && s2 >= c2) {
53
- //central class
54
- if (cc != undefined && s0 > cc * c0) return 'center'
55
- if (withMixedClasses) return 'm12'
56
- return s1 > s2 ? '1' : '2'
57
- }
58
- //middle class 1 - intersection class 0 and 1
59
- if (s0 >= c0 && s1 <= c1 && s2 >= c2) {
60
- //central class
61
- if (cc != undefined && s1 > cc * c1) return 'center'
62
- if (withMixedClasses) return 'm02'
63
- return s0 > s2 ? '0' : '2'
64
- }
65
- //middle class 2 - intersection class 0 and 1
66
- if (s0 >= c0 && s1 >= c1 && s2 <= c2) {
67
- //central class
68
- if (cc != undefined && s2 > cc * c2) return 'center'
69
- if (withMixedClasses) return 'm01'
70
- return s1 > s0 ? '1' : '0'
71
- }
72
- //should not happen
73
- return 'unknown'
74
- }
75
-
76
- //attach information to output function
77
- fun.center = [c0, c1, c2]
78
- fun.centerCoefficient = opts.centerCoefficient
79
-
80
- return fun
81
- }
82
-
83
- export const ternaryColorClassifier = (properties, totalFunction, colors, opts = {}) => {
84
- //the three colors
85
- const [color0, color1, color2] = colors || ['red', 'green', 'blue']
86
-
87
- //the color interpolation function
88
- const colorInterpolation = opts.colorInterpolation || interpolateLab
89
-
90
- //parameter to decide wether to use mixed classes.
91
- const withMixedClasses = opts.withMixedClasses != undefined ? opts.withMixedClasses : true
92
- //https://d3js.org/d3-interpolate/color
93
- const mixColorFunction = (color1, color2) => colorInterpolation(color1, color2)(0.5)
94
- //the colors corresponding to the mixed classes
95
- const [mixColor0, mixColor1, mixColor2] =
96
- opts.mixedColors || withMixedClasses
97
- ? [
98
- mixColorFunction(color1, color2),
99
- mixColorFunction(color0, color2),
100
- mixColorFunction(color0, color1),
101
- ]
102
- : []
103
-
104
- //the central color, used for the central class, if any. The central class is the class of relatively balanced values, around the center point
105
- const centerColor =
106
- opts.centerColor || colorInterpolation(mixColorFunction(color0, color1), color2)(0.333)
107
-
108
- //make classifier
109
- const classifier = ternaryClassifier(properties, totalFunction, opts)
110
-
111
- //the output color classifier method
112
- const fun = (c) => {
113
- const cla = classifier(c)
114
- if (cla == '0') return color0
115
- if (cla == '1') return color1
116
- if (cla == '2') return color2
117
- if (cla == 'm12') return mixColor0
118
- if (cla == 'm02') return mixColor1
119
- if (cla == 'm01') return mixColor2
120
- if (cla == 'center') return centerColor
121
- return opts.defaultColor || 'black'
122
- }
123
- fun.center = classifier.center
124
- fun.centerCoefficient = opts.centerCoefficient
125
- fun.colors = [color0, color1, color2]
126
- fun.mixColors = [mixColor0, mixColor1, mixColor2]
127
- fun.centerColor = centerColor
128
- fun.classifier = classifier
129
-
130
- return fun
131
- }
132
-
133
-
134
-
135
-
136
- // OLD VERSIONS
137
-
138
- /*
139
- const orderedIndexesDec = arr => [...arr.keys()].sort((a, b) => arr[b] - arr[a]);
140
- const orderedIndexesInc = arr => [...arr.keys()].sort((a, b) => arr[a] - arr[b]);
141
-
142
- export const ternaryClassifier = (properties, totalFunction, opts = {}) => {
143
- const lowThreshold = opts.lowThreshold || [1 / 3, 1 / 3, 1 / 3]
144
- const highThreshold = opts.highThreshold || [2 / 3, 2 / 3, 2 / 3]
145
- const colors = opts.colors || ["red", "green", "blue"]
146
- const colorInterpolation = opts.colorInterpolation || interpolateLab
147
-
148
- //https://d3js.org/d3-interpolate/color
149
- const middleColorFunction = (color1, color2) => colorInterpolation(color1, color2)(0.5)
150
- const middleColors = opts.middleColors || [middleColorFunction(colors[1], colors[2]), middleColorFunction(colors[0], colors[2]), middleColorFunction(colors[0], colors[1])]
151
- const centerColor = opts.centerColor || colorInterpolation(middleColors[2], colors[2])(0.333)
152
-
153
- const high_ = orderedIndexesDec(highThreshold)
154
- const low_ = orderedIndexesInc(lowThreshold)
155
-
156
- const p0 = properties[0], p1 = properties[1], p2 = properties[2]
157
- const fun = c => {
158
- //get total
159
- const tot = totalFunction(c)
160
- if (!tot) return undefined
161
- //compute shares
162
- const shares = [+c[p0] / tot, +c[p1] / tot, +c[p2] / tot]
163
- //return colors
164
- //start first with the extreme high (triangles): from the larger value (small triangle) to the lower value (large triangle)
165
- for (let i of high_) if (shares[i] > highThreshold[i]) return colors[i]
166
- //then draw the extreme low (trapeziums): from the lower value (small trapeziums) to the larger values (large trapezium)
167
- for (let i of low_) if (shares[i] < lowThreshold[i]) return middleColors[i]
168
- //else central color
169
- return centerColor
170
- }
171
- fun.lowThreshold = lowThreshold
172
- fun.highThreshold = highThreshold
173
- fun.colors = colors
174
- fun.middleColors = middleColors
175
- fun.centerColor = centerColor
176
- fun.lowIndex = low_
177
- fun.highIndex = high_
178
- return fun
179
- }
180
- */
181
-
182
- /*
183
- export const ternaryClassifier3 = (properties, totalFunction, opts = {}) => {
184
- const [a01, a12, a20] = opts.thresholds || [1/3, 1/3, 1/3]
185
- const [c0, c1, c2] = opts.colors || ["red", "green", "blue"]
186
- const centerColor = opts.centerColor || "gray"
187
-
188
- const fff = a => a == 1 ? Infinity : a / (1 - a)
189
- const c01 = fff(a01), c12 = fff(a12), c20 = fff(a20)
190
-
191
- const p0 = properties[0], p1 = properties[1], p2 = properties[2]
192
- const fun = c => {
193
- //get total
194
- const tot = totalFunction(c)
195
- if (!tot) return undefined
196
- //compute shares
197
- const [s0, s1, s2] = [+c[p0] / tot, +c[p1] / tot, +c[p2] / tot]
198
- //return colors
199
- if (s0 * c01 > s1 && s0 > s2 * c20) return c0
200
- else if (s1 > s0 * c01 && s1 * c12 > s2) return c1
201
- else if (s2 * c20 > s0 && s2 > s1 * c12) return c2
202
- else return centerColor
203
- }
204
- fun.colors = [c0, c1, c2]
205
- fun.centerColor = centerColor
206
- return fun
207
- }
208
- */
1
+ //@ts-check
2
+ 'use strict'
3
+
4
+ import { interpolateLab } from 'd3-interpolate'
5
+
6
+ export const ternaryClassifier = (properties, totalFunction, opts = {}) => {
7
+ //the three properties
8
+ const p0 = properties[0],
9
+ p1 = properties[1],
10
+ p2 = properties[2]
11
+
12
+ //the classifier center point. sum must be equal to 1
13
+ const [c0, c1, c2] = opts.center || [1 / 3, 1 / 3, 1 / 3]
14
+
15
+ //parameter to decide wether to use mixed classes m0, m1, m2.
16
+ const withMixedClasses = opts.withMixedClasses != undefined ? opts.withMixedClasses : true
17
+
18
+ //parameter to decide wether to use a central class, and the size of this central class.
19
+ //set to 0 or undefined for not showing any central class. Set to 1 for a central class that contains the mix classes
20
+ const cc = opts.centerCoefficient ? 1 - opts.centerCoefficient : undefined
21
+
22
+ //the output classifier method
23
+ const fun = (c) => {
24
+ //get total
25
+ const tot = totalFunction(c)
26
+ if (!tot) return undefined
27
+ //compute shares
28
+ const [s0, s1, s2] = [+c[p0] / tot, +c[p1] / tot, +c[p2] / tot]
29
+
30
+ //class 0
31
+ if (s0 >= c0 && s1 <= c1 && s2 <= c2) {
32
+ //central class near class 0
33
+ if (cc != undefined && (s2 - c2) * (c1 - cc * c1) >= (s1 - cc * c1) * (cc * c2 - c2))
34
+ return 'center'
35
+ return '0'
36
+ }
37
+ //class 1
38
+ if (s0 <= c0 && s1 >= c1 && s2 <= c2) {
39
+ //central class near class 1
40
+ if (cc != undefined && (s2 - c2) * (c0 - cc * c0) >= (s0 - cc * c0) * (cc * c2 - c2))
41
+ return 'center'
42
+ return '1'
43
+ }
44
+ //class 2
45
+ if (s0 <= c0 && s1 <= c1 && s2 >= c2) {
46
+ //central class near class 2
47
+ if (cc != undefined && (s1 - c1) * (c0 - cc * c0) >= (s0 - cc * c0) * (cc * c1 - c1))
48
+ return 'center'
49
+ return '2'
50
+ }
51
+ //middle class 0 - intersection class 1 and 2
52
+ if (s0 <= c0 && s1 >= c1 && s2 >= c2) {
53
+ //central class
54
+ if (cc != undefined && s0 > cc * c0) return 'center'
55
+ if (withMixedClasses) return 'm12'
56
+ return s1 > s2 ? '1' : '2'
57
+ }
58
+ //middle class 1 - intersection class 0 and 1
59
+ if (s0 >= c0 && s1 <= c1 && s2 >= c2) {
60
+ //central class
61
+ if (cc != undefined && s1 > cc * c1) return 'center'
62
+ if (withMixedClasses) return 'm02'
63
+ return s0 > s2 ? '0' : '2'
64
+ }
65
+ //middle class 2 - intersection class 0 and 1
66
+ if (s0 >= c0 && s1 >= c1 && s2 <= c2) {
67
+ //central class
68
+ if (cc != undefined && s2 > cc * c2) return 'center'
69
+ if (withMixedClasses) return 'm01'
70
+ return s1 > s0 ? '1' : '0'
71
+ }
72
+ //should not happen
73
+ return 'unknown'
74
+ }
75
+
76
+ //attach information to output function
77
+ fun.center = [c0, c1, c2]
78
+ fun.centerCoefficient = opts.centerCoefficient
79
+
80
+ return fun
81
+ }
82
+
83
+ export const ternaryColorClassifier = (properties, totalFunction, colors, opts = {}) => {
84
+ //the three colors
85
+ const [color0, color1, color2] = colors || ['red', 'green', 'blue']
86
+
87
+ //the color interpolation function
88
+ const colorInterpolation = opts.colorInterpolation || interpolateLab
89
+
90
+ //parameter to decide wether to use mixed classes.
91
+ const withMixedClasses = opts.withMixedClasses != undefined ? opts.withMixedClasses : true
92
+ //https://d3js.org/d3-interpolate/color
93
+ const mixColorFunction = (color1, color2) => colorInterpolation(color1, color2)(0.5)
94
+ //the colors corresponding to the mixed classes
95
+ const [mixColor0, mixColor1, mixColor2] =
96
+ opts.mixedColors || withMixedClasses
97
+ ? [
98
+ mixColorFunction(color1, color2),
99
+ mixColorFunction(color0, color2),
100
+ mixColorFunction(color0, color1),
101
+ ]
102
+ : []
103
+
104
+ //the central color, used for the central class, if any. The central class is the class of relatively balanced values, around the center point
105
+ const centerColor =
106
+ opts.centerColor || colorInterpolation(mixColorFunction(color0, color1), color2)(0.333)
107
+
108
+ //make classifier
109
+ const classifier = ternaryClassifier(properties, totalFunction, opts)
110
+
111
+ //the output color classifier method
112
+ const fun = (c) => {
113
+ const cla = classifier(c)
114
+ if (cla == '0') return color0
115
+ if (cla == '1') return color1
116
+ if (cla == '2') return color2
117
+ if (cla == 'm12') return mixColor0
118
+ if (cla == 'm02') return mixColor1
119
+ if (cla == 'm01') return mixColor2
120
+ if (cla == 'center') return centerColor
121
+ return opts.defaultColor || 'black'
122
+ }
123
+ fun.center = classifier.center
124
+ fun.centerCoefficient = opts.centerCoefficient
125
+ fun.colors = [color0, color1, color2]
126
+ fun.mixColors = [mixColor0, mixColor1, mixColor2]
127
+ fun.centerColor = centerColor
128
+ fun.classifier = classifier
129
+
130
+ return fun
131
+ }
132
+
133
+
134
+
135
+
136
+ // OLD VERSIONS
137
+
138
+ /*
139
+ const orderedIndexesDec = arr => [...arr.keys()].sort((a, b) => arr[b] - arr[a]);
140
+ const orderedIndexesInc = arr => [...arr.keys()].sort((a, b) => arr[a] - arr[b]);
141
+
142
+ export const ternaryClassifier = (properties, totalFunction, opts = {}) => {
143
+ const lowThreshold = opts.lowThreshold || [1 / 3, 1 / 3, 1 / 3]
144
+ const highThreshold = opts.highThreshold || [2 / 3, 2 / 3, 2 / 3]
145
+ const colors = opts.colors || ["red", "green", "blue"]
146
+ const colorInterpolation = opts.colorInterpolation || interpolateLab
147
+
148
+ //https://d3js.org/d3-interpolate/color
149
+ const middleColorFunction = (color1, color2) => colorInterpolation(color1, color2)(0.5)
150
+ const middleColors = opts.middleColors || [middleColorFunction(colors[1], colors[2]), middleColorFunction(colors[0], colors[2]), middleColorFunction(colors[0], colors[1])]
151
+ const centerColor = opts.centerColor || colorInterpolation(middleColors[2], colors[2])(0.333)
152
+
153
+ const high_ = orderedIndexesDec(highThreshold)
154
+ const low_ = orderedIndexesInc(lowThreshold)
155
+
156
+ const p0 = properties[0], p1 = properties[1], p2 = properties[2]
157
+ const fun = c => {
158
+ //get total
159
+ const tot = totalFunction(c)
160
+ if (!tot) return undefined
161
+ //compute shares
162
+ const shares = [+c[p0] / tot, +c[p1] / tot, +c[p2] / tot]
163
+ //return colors
164
+ //start first with the extreme high (triangles): from the larger value (small triangle) to the lower value (large triangle)
165
+ for (let i of high_) if (shares[i] > highThreshold[i]) return colors[i]
166
+ //then draw the extreme low (trapeziums): from the lower value (small trapeziums) to the larger values (large trapezium)
167
+ for (let i of low_) if (shares[i] < lowThreshold[i]) return middleColors[i]
168
+ //else central color
169
+ return centerColor
170
+ }
171
+ fun.lowThreshold = lowThreshold
172
+ fun.highThreshold = highThreshold
173
+ fun.colors = colors
174
+ fun.middleColors = middleColors
175
+ fun.centerColor = centerColor
176
+ fun.lowIndex = low_
177
+ fun.highIndex = high_
178
+ return fun
179
+ }
180
+ */
181
+
182
+ /*
183
+ export const ternaryClassifier3 = (properties, totalFunction, opts = {}) => {
184
+ const [a01, a12, a20] = opts.thresholds || [1/3, 1/3, 1/3]
185
+ const [c0, c1, c2] = opts.colors || ["red", "green", "blue"]
186
+ const centerColor = opts.centerColor || "gray"
187
+
188
+ const fff = a => a == 1 ? Infinity : a / (1 - a)
189
+ const c01 = fff(a01), c12 = fff(a12), c20 = fff(a20)
190
+
191
+ const p0 = properties[0], p1 = properties[1], p2 = properties[2]
192
+ const fun = c => {
193
+ //get total
194
+ const tot = totalFunction(c)
195
+ if (!tot) return undefined
196
+ //compute shares
197
+ const [s0, s1, s2] = [+c[p0] / tot, +c[p1] / tot, +c[p2] / tot]
198
+ //return colors
199
+ if (s0 * c01 > s1 && s0 > s2 * c20) return c0
200
+ else if (s1 > s0 * c01 && s1 * c12 > s2) return c1
201
+ else if (s2 * c20 > s0 && s2 > s1 * c12) return c2
202
+ else return centerColor
203
+ }
204
+ fun.colors = [c0, c1, c2]
205
+ fun.centerColor = centerColor
206
+ return fun
207
+ }
208
+ */