gridviz 3.0.2 → 3.0.4

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.
@@ -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
  }
@@ -0,0 +1,24 @@
1
+ //@ts-check
2
+ 'use strict'
3
+
4
+ import { loadImage } from '../utils/utils.js'
5
+
6
+ //
7
+
8
+ /**
9
+ * Load chernoff images
10
+ * See for example: https://github.com/jgaffuri/chernoff-faces
11
+ * @param {object} out
12
+ * @param {string} baseURL
13
+ * @param {function|undefined} callback
14
+ * @returns
15
+ */
16
+ export async function loadChernoffFacesImages(out, baseURL, callback = undefined) {
17
+ out = out || {}
18
+ for (let s of ["f", "m"])
19
+ for (let a of ["y", "m", "o"])
20
+ for (let i = 0; i <= 2; i++)
21
+ out[s + a + i] = await loadImage(baseURL + (s + a + i) + ".png")
22
+ if (callback) callback()
23
+ return out
24
+ }
@@ -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,6 +29,16 @@ 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
+
41
+
32
42
 
33
43
  /*
34
44
  export let monitor = false