gridviz 3.0.2 → 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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gridviz",
3
- "version": "3.0.2",
3
+ "version": "3.0.3",
4
4
  "description": "Visualization tool for gridded statistics",
5
5
  "keywords": [
6
6
  "statistics",
package/src/core/Style.js CHANGED
@@ -3,7 +3,7 @@
3
3
 
4
4
  import { Drawable } from "./Drawable.js"
5
5
 
6
- /** @typedef {"square"|"circle"|"diamond"|"donut"|"none"} Shape */
6
+ /** @typedef {"square"|"circle"|"diamond"|"donut"|"triangle_up"|"triangle_down"|"triangle_left"|"triangle_right"|"none"} Shape */
7
7
 
8
8
  /**
9
9
  * viewScale type
package/src/index.js CHANGED
@@ -33,6 +33,7 @@ export { MosaicStyle } from './style/MosaicStyle.js'
33
33
  export { NinjaStarStyle } from './style/NinjaStarStyle.js'
34
34
  export { TimeSeriesStyle } from './style/TimeSeriesStyle.js'
35
35
  export { IsoFenceStyle } from './style/IsoFenceStyle.js'
36
+ export { ImageStyle } from './style/ImageStyle.js'
36
37
 
37
38
  // export additional layers
38
39
  export { GridLayer } from './layer/GridLayer.js'
@@ -47,11 +48,14 @@ export { ColorDiscreteLegend } from './legend/ColorDiscreteLegend.js'
47
48
  export { ColorCategoryLegend } from './legend/ColorCategoryLegend.js'
48
49
  export { SizeLegend, sizeLegend, sizeLegendViewScale, sizeDiscreteLegend, sizeDiscreteViewScaleLegend } from './legend/SizeLegend.js'
49
50
  export { OrientationLegend, orientationLegend } from './legend/OrientationLegend.js'
51
+ export { TrivariateLegend } from './legend/TrivariateLegend.js'
50
52
 
51
53
  // export { goToStraight, zoomTo } from "./utils/zoomUtils"
52
54
  export * from './utils/stretching.js'
53
55
  export * from './utils/scale.js'
54
- export { nice } from './utils/utils.js'
56
+ export * from './utils/trivariate.js'
57
+ export * from './utils/chernoff.js'
58
+ export { nice, loadImage } from './utils/utils.js'
55
59
 
56
60
  import { GeoCanvas } from './core/GeoCanvas.js'
57
61
  export const getParameterByName = GeoCanvas.getParameterByName
@@ -0,0 +1,210 @@
1
+ //@ts-check
2
+ 'use strict'
3
+
4
+ import { select } from 'd3-selection'
5
+ import { Legend } from '../core/Legend.js'
6
+
7
+ /**
8
+ *
9
+ * @module legend
10
+ * @author Julien Gaffuri
11
+ */
12
+ export class TrivariateLegend extends Legend {
13
+ /** @param {Object} opts */
14
+ constructor(opts) {
15
+ super(opts)
16
+ opts = opts || {}
17
+
18
+ //classifier
19
+ this.classifier = opts.classifier
20
+
21
+ this.width = opts.width || 150
22
+ this.selectionColor = this.selectionColor || "red"
23
+ this.tooltip = opts.tooltip
24
+ this.texts = opts.texts
25
+
26
+ this.leftText = opts.leftText || "Category 0"
27
+ this.topText = opts.topText || "Category 1"
28
+ this.rightText = opts.rightText || "Category 2"
29
+
30
+ this.centerCoefficient = opts.centerCoefficient || this.classifier.centerCoefficient
31
+ }
32
+
33
+ /**
34
+ * @param {{} } opts
35
+ */
36
+ update(opts) {
37
+
38
+ //clear
39
+ this.div.selectAll('*').remove()
40
+
41
+ //title
42
+ this.makeTitle()
43
+
44
+ const sqrt3over2 = 0.866025
45
+ const w = this.width, h = w * sqrt3over2
46
+ const classifier = this.classifier
47
+ const selectionColor = this.selectionColor
48
+ const selectionStrokeWidth = 0
49
+ const tt = this.tooltip
50
+ const texts = this.texts || {}
51
+
52
+ const padding = 2
53
+ const fontSize = 12
54
+
55
+ //make svg element
56
+ const svg = this.div
57
+ .append('svg')
58
+ .attr('width', w + selectionStrokeWidth)
59
+ .attr('height', h + 4 * padding + 2 * fontSize)
60
+
61
+ //top label
62
+ svg.append("text").attr("x", w / 2).attr("y", padding + fontSize).text(this.topText).attr("font-size", fontSize)
63
+ .attr("text-anchor", "middle")
64
+ //left label
65
+ svg.append("text").attr("x", 0).attr("y", 3 * padding + 2 * fontSize + h).text(this.leftText).attr("font-size", fontSize)
66
+ .attr("text-anchor", "start")
67
+ //right label
68
+ svg.append("text").attr("x", w).attr("y", 3 * padding + 2 * fontSize + h).text(this.rightText).attr("font-size", fontSize)
69
+ .attr("text-anchor", "end")
70
+
71
+
72
+
73
+ //triangle group
74
+ const g = svg.append("g").attr("transform", "translate(" + (selectionStrokeWidth / 2) + " " + (selectionStrokeWidth / 2 + (2 * padding + fontSize)) + ")")
75
+
76
+ //common function for triangle patches
77
+ const setAttributes = (elt, color, text) => {
78
+ //elt.raise();
79
+ elt.attr('fill', color)
80
+ //.attr("stroke", colorOver)
81
+ //.attr("stroke-width", 0)
82
+ //.attr("stroke-linejoin", "round")
83
+ .on("mouseover", function (e) {
84
+ /*this.parentNode.appendChild(this); select(this).attr("stroke-width", selectionStrokeWidth);*/
85
+ select(this).attr('fill', selectionColor);
86
+ if (!tt || !text) return;
87
+ tt.html(text);
88
+ tt.setPosition(e);
89
+ tt.show()
90
+ })
91
+ .on("mouseout", function () {
92
+ /*select(this).attr("stroke-width", 0);*/
93
+ select(this).attr('fill', color);
94
+ if (tt) tt.hide()
95
+ })
96
+ if (tt && text) elt.on("mousemove", function (e) { tt.setPosition(e) })
97
+ }
98
+
99
+ //const [c0, c1, c2] = classifier.center
100
+
101
+ //trapezium s0
102
+ const t0 = g.append('polygon')
103
+ .attr('points', "0," + h + " " + w / 3 + "," + h + " " + w / 2 + "," + h * 2 / 3 + " " + w / 6 + "," + h * 2 / 3)
104
+ setAttributes(t0, classifier.colors[0], texts["0"])
105
+ //trapezium s1
106
+ const t1 = g.append('polygon')
107
+ .attr('points', w / 2 + ",0 " + w * 2 / 3 + "," + h / 3 + " " + w / 2 + "," + h * 2 / 3 + " " + w / 3 + "," + h / 3)
108
+ setAttributes(t1, classifier.colors[1], texts["1"])
109
+ //trapezium s2
110
+ const t2 = g.append('polygon')
111
+ .attr('points', w + "," + h + " " + w * 5 / 6 + "," + 2 * h / 3 + " " + w / 2 + "," + h * 2 / 3 + " " + w * 2 / 3 + "," + h)
112
+ setAttributes(t2, classifier.colors[2], texts["2"])
113
+ //triangle s0
114
+ const t0_ = g.append('polygon')
115
+ .attr('points', w / 2 + "," + h * 2 / 3 + " " + w * 5 / 6 + "," + h * 2 / 3 + " " + w * 2 / 3 + "," + h / 3)
116
+ setAttributes(t0_, classifier.mixColors[0], texts["12"])
117
+ //triangle s1
118
+ const t1_ = g.append('polygon')
119
+ .attr('points', w / 2 + "," + h * 2 / 3 + " " + w / 3 + "," + h + " " + w * 2 / 3 + "," + h)
120
+ setAttributes(t1_, classifier.mixColors[1], texts["02"])
121
+ //triangle s2
122
+ const t2_ = g.append('polygon')
123
+ .attr('points', w / 2 + "," + h * 2 / 3 + " " + w / 6 + "," + h * 2 / 3 + " " + w / 3 + "," + h / 3)
124
+ setAttributes(t2_, classifier.mixColors[2], texts["01"])
125
+
126
+ //center
127
+ if (this.centerCoefficient) {
128
+ //TODO make it an hexagon !
129
+ const center = g.append('circle')
130
+ .attr('cx', w / 2).attr('cy', h * 2 / 3)
131
+ .attr('r', this.centerCoefficient * h / 3)
132
+ setAttributes(center, classifier.centerColor, texts["center"])
133
+ }
134
+
135
+
136
+ /*
137
+ let middle, left, top, right, left_, bottom_, right_
138
+ if (!this.real) {
139
+
140
+ //0 left triangle
141
+ left = g.append('polygon')
142
+ .attr('points', "0," + h + " " + (w / 3) + "," + h + " " + (w / 6) + "," + (2 * h / 3))
143
+ //1 top triangle
144
+ top = g.append('polygon')
145
+ .attr('points', (w / 3) + "," + (h / 3) + " " + (w * 2 / 3) + "," + (h / 3) + " " + (w / 2) + ",0")
146
+ //2 right triangle
147
+ right = g.append('polygon')
148
+ .attr('points', (w * 2 / 3) + "," + h + " " + w + "," + h + " " + (w * 5 / 6) + "," + (2 * h / 3))
149
+ //middle triangle
150
+ middle = g.append('polygon')
151
+ .attr('points', (w / 2) + "," + (h / 3) + " " + (w / 4) + "," + (h * 5 / 6) + " " + (3 * w / 4) + "," + (h * 5 / 6))
152
+ //01 left trapezium
153
+ left_ = g.append('polygon')
154
+ .attr('points', (w / 6) + "," + (h * 2 / 3) + " " + (w / 4) + "," + (h * 5 / 6) + " " + (w / 2) + "," + (h / 3) + " " + (w / 3) + "," + (h / 3))
155
+ //02 bottom trapezium
156
+ bottom_ = g.append('polygon')
157
+ .attr('points', (w / 3) + "," + (h) + " " + (2 * w / 3) + "," + (h) + " " + (w * 3 / 4) + "," + (h * 5 / 6) + " " + (w / 4) + "," + (h * 5 / 6))
158
+ //12 right trapezium
159
+ right_ = g.append('polygon')
160
+ .attr('points', (w / 2) + "," + (h / 3) + " " + (w * 3 / 4) + "," + (h * 5 / 6) + " " + (w * 5 / 6) + "," + (h * 2 / 3) + " " + (w * 2 / 3) + "," + (h / 3))
161
+
162
+ } else {
163
+
164
+ //middle triangle
165
+ middle = g.append('polygon')
166
+ .attr('points', (w / 2) + ",0 0," + h + " " + w + "," + h)
167
+
168
+ //draw trapezium
169
+ //draw large trapezium first
170
+ for (let i_ = 2; i_ >= 0; i_--) {
171
+ const i = this.classifier.lowIndex[i_]
172
+ const r = this.classifier.lowThreshold[i]
173
+ if (i == 2)
174
+ //01 left trapezium
175
+ left_ = g.append('polygon')
176
+ .attr('points', w / 2 + ",0 0," + h + " " + w * r + "," + h + " " + w * (1 + r) / 2 + "," + r * h)
177
+ else if (i == 1)
178
+ //02 bottom trapezium
179
+ bottom_ = g.append('polygon')
180
+ .attr('points', "0," + h + " " + w + "," + h + " " + w * (1 - r / 2) + "," + h * (1 - r) + " " + r * w / 2 + "," + h * (1 - r))
181
+ else
182
+ //12 right trapezium
183
+ right_ = g.append('polygon')
184
+ .attr('points', w + "," + h + " " + w / 2 + ",0 " + w * (1 - r) / 2 + "," + h * r + " " + w * (1 - r) + "," + h)
185
+ }
186
+
187
+ //draw triangles
188
+ //draw large triangles first
189
+ for (let i_ = 2; i_ >= 0; i_--) {
190
+ const i = this.classifier.highIndex[i_]
191
+ const r = this.classifier.highThreshold[i]
192
+
193
+ if (i == 2)
194
+ //2 right triangle
195
+ right = g.append('polygon')
196
+ .attr('points', w + "," + h + " " + w * r + "," + h + " " + w * (1 + r) / 2 + "," + h * r)
197
+ else if (i == 1)
198
+ //1 top triangle
199
+ top = g.append('polygon')
200
+ .attr('points', (w / 2) + ",0 " + w * r / 2 + "," + h * (1 - r) + " " + w * (1 - r / 2) + "," + h * (1 - r))
201
+ else
202
+ //0 left triangle
203
+ left = g.append('polygon')
204
+ .attr('points', "0," + h + " " + w * (1 - r) + "," + h + " " + w * (1 - r) / 2 + "," + h * r)
205
+ }
206
+
207
+ }*/
208
+
209
+ }
210
+ }
@@ -0,0 +1,81 @@
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 ImageStyle extends Style {
11
+
12
+ /** @param {object} opts */
13
+ constructor(opts) {
14
+ super(opts)
15
+ opts = opts || {}
16
+
17
+ /** A function returning the image code of a cell.
18
+ * @type {function(import('../core/Dataset.js').Cell, number, number, object):string} */
19
+ this.imageCode = opts.imageCode || (() => '') //(c,r,z,vs) => {}
20
+
21
+ /** The dictionnary code -> image
22
+ * @type {object} */
23
+ this.images = opts.images || {}
24
+
25
+ /** The image size in ground meters
26
+ * @type {function(import('../core/Dataset.js').Cell, number, number, object):number} */
27
+ this.size = opts.size || ((cell, resolution) => resolution)
28
+ }
29
+
30
+ /**
31
+ * @param {Array.<import("../core/Dataset.js").Cell>} cells
32
+ * @param {import("../core/GeoCanvas.js").GeoCanvas} geoCanvas
33
+ * @param {number} resolution
34
+ * @override
35
+ */
36
+ draw(cells, geoCanvas, resolution) {
37
+
38
+ //
39
+ const z = geoCanvas.view.z,
40
+ resolutionPix = resolution / z
41
+
42
+ //get view scale
43
+ const viewScale = this.viewScale ? this.viewScale(cells, resolution, z) : undefined
44
+
45
+ //draw in screen coordinates
46
+ geoCanvas.initCanvasTransform()
47
+
48
+ //
49
+ for (let cell of cells) {
50
+
51
+ //get cell image code
52
+ const code = this.imageCode(cell, resolution, z, viewScale)
53
+
54
+ //get image
55
+ const image = this.images[code]
56
+ if (!image) continue
57
+
58
+ //size and position values
59
+ let sizePix = this.size(cell, resolution, z, viewScale) / z
60
+ if (!sizePix) continue
61
+ const d = (resolutionPix - sizePix) / 2
62
+
63
+ try {
64
+ geoCanvas.ctx.drawImage(image, geoCanvas.geoToPixX(cell.x) + d, geoCanvas.geoToPixY(cell.y) + d -resolutionPix, sizePix, sizePix)
65
+
66
+ /*/red color filter
67
+ geoCanvas.ctx.globalCompositeOperation = 'source-in';
68
+ geoCanvas.ctx.fillStyle = 'red';
69
+ geoCanvas.ctx.fillRect(geoCanvas.geoToPixX(cell.x) + d, geoCanvas.geoToPixY(cell.y) + d, sizePix, sizePix);
70
+ geoCanvas.ctx.globalCompositeOperation = 'source-over';*/
71
+ } catch (error) {
72
+ console.error(error)
73
+ }
74
+
75
+ }
76
+
77
+
78
+ //update legends
79
+ this.updateLegends({ style: this, resolution: resolution, z: z, viewScale: viewScale })
80
+ }
81
+ }
@@ -107,7 +107,7 @@ export class PillarStyle extends Style {
107
107
  //set width
108
108
  geoCanvas.ctx.lineWidth = wG
109
109
 
110
- //compute cell centre postition
110
+ //compute cell center postition
111
111
  const cx = cell.x + resolution / 2
112
112
  const cy = cell.y + resolution / 2
113
113
  const ls = hG * this.shadowFactor
@@ -152,7 +152,7 @@ export class PillarStyle extends Style {
152
152
  //TODO use that
153
153
  //const offset = this.offset(c, resolution, z)
154
154
 
155
- //compute cell centre postition
155
+ //compute cell center postition
156
156
  const cx = cell.x + resolution / 2
157
157
  const cy = cell.y + resolution / 2
158
158
 
@@ -84,7 +84,7 @@ export class SegmentStyle extends Style {
84
84
  geoCanvas.ctx.strokeStyle = col
85
85
  geoCanvas.ctx.lineWidth = wG
86
86
 
87
- //compute segment centre postition
87
+ //compute segment center postition
88
88
  const cx = cell.x + resolution / 2 + offset.dx
89
89
  const cy = cell.y + resolution / 2 + offset.dy
90
90
 
@@ -93,6 +93,34 @@ export class ShapeColorSizeStyle extends Style {
93
93
  geoCanvas.ctx.lineTo(c.x + r2 + s2, c.y + r2)
94
94
  geoCanvas.ctx.lineTo(c.x + r2, c.y + r2 - s2)
95
95
  geoCanvas.ctx.fill()
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.fill()
103
+ } else if (shape === 'triangle_down') {
104
+ const dr2 = (size - resolution) / 2
105
+ geoCanvas.ctx.beginPath()
106
+ geoCanvas.ctx.moveTo(c.x - dr2, c.y + resolution + dr2)
107
+ geoCanvas.ctx.lineTo(c.x + r2, c.y - dr2)
108
+ geoCanvas.ctx.lineTo(c.x + resolution + dr2, c.y + resolution + dr2)
109
+ geoCanvas.ctx.fill()
110
+ } else if (shape === 'triangle_left') {
111
+ const dr2 = (size - resolution) / 2
112
+ geoCanvas.ctx.beginPath()
113
+ geoCanvas.ctx.moveTo(c.x + resolution + dr2, c.y + resolution + dr2)
114
+ geoCanvas.ctx.lineTo(c.x - dr2, c.y + r2)
115
+ geoCanvas.ctx.lineTo(c.x + resolution + dr2, c.y - dr2)
116
+ geoCanvas.ctx.fill()
117
+ } else if (shape === 'triangle_right') {
118
+ const dr2 = (size - resolution) / 2
119
+ geoCanvas.ctx.beginPath()
120
+ geoCanvas.ctx.moveTo(c.x - dr2, c.y - dr2)
121
+ geoCanvas.ctx.lineTo(c.x + resolution + dr2, c.y + r2)
122
+ geoCanvas.ctx.lineTo(c.x - dr2, c.y + resolution + dr2)
123
+ geoCanvas.ctx.fill()
96
124
  } else {
97
125
  throw new Error('Unexpected shape:' + shape)
98
126
  }
@@ -125,14 +125,14 @@ export class SideStyle extends Style {
125
125
  * @param {number} resolution The cells resolution
126
126
  * @param {boolean} withHorizontal Set to true to build horizontal sides, false otherwise.
127
127
  * @param {boolean} withVertical Set to true to build vertical sides, false otherwise.
128
- * @param {boolean} centre Set to true so that the side coordinate are those of its center point rather than its left/bottom point (the side x,y coordinates are those of the left point for horizontal sides, and of the bottom point for vertical sides)
128
+ * @param {boolean} center Set to true so that the side coordinate are those of its center point rather than its left/bottom point (the side x,y coordinates are those of the left point for horizontal sides, and of the bottom point for vertical sides)
129
129
  * @returns { Array.<Side> }
130
130
  */
131
- static buildSides(cells, resolution, withHorizontal = true, withVertical = true, centre = true) {
131
+ static buildSides(cells, resolution, withHorizontal = true, withVertical = true, center = true) {
132
132
  /** @type { Array.<Side> } */
133
133
  const sides = []
134
134
 
135
- const r2 = centre ? resolution / 2 : 0
135
+ const r2 = center ? resolution / 2 : 0
136
136
 
137
137
  //make horizontal sides
138
138
  //sort cells by x and y
@@ -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,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
+ }