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/dist/gridviz.js +917 -68
- package/dist/gridviz.min.js +1 -1
- package/package.json +1 -1
- package/src/core/Style.js +1 -1
- package/src/index.js +5 -1
- package/src/legend/TrivariateLegend.js +210 -0
- package/src/style/ImageStyle.js +81 -0
- package/src/style/PillarStyle.js +2 -2
- package/src/style/SegmentStyle.js +1 -1
- package/src/style/ShapeColorSizeStyle.js +28 -0
- package/src/style/SideStyle.js +3 -3
- package/src/style/StrokeStyle.js +47 -15
- package/src/utils/chernoff.js +14 -0
- package/src/utils/trivariate.js +203 -0
- package/src/utils/utils.js +10 -0
package/package.json
CHANGED
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
|
|
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
|
+
}
|
package/src/style/PillarStyle.js
CHANGED
|
@@ -107,7 +107,7 @@ export class PillarStyle extends Style {
|
|
|
107
107
|
//set width
|
|
108
108
|
geoCanvas.ctx.lineWidth = wG
|
|
109
109
|
|
|
110
|
-
//compute cell
|
|
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
|
|
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
|
|
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
|
}
|
package/src/style/SideStyle.js
CHANGED
|
@@ -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}
|
|
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,
|
|
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 =
|
|
135
|
+
const r2 = center ? resolution / 2 : 0
|
|
136
136
|
|
|
137
137
|
//make horizontal sides
|
|
138
138
|
//sort cells by x and y
|
package/src/style/StrokeStyle.js
CHANGED
|
@@ -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
|
|
52
|
+
for (let c of cells) {
|
|
53
53
|
|
|
54
54
|
//color
|
|
55
|
-
const col = this.strokeColor ? this.strokeColor(
|
|
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
|
|
60
|
+
const size = this.size ? this.size(c, resolution, z, viewScale) : resolution
|
|
61
61
|
|
|
62
62
|
//width
|
|
63
|
-
const wi = this.strokeWidth ? this.strokeWidth(
|
|
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(
|
|
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(
|
|
72
|
+
const offset = this.offset(c, resolution, z)
|
|
73
73
|
|
|
74
74
|
if (shape === 'square') {
|
|
75
75
|
//draw square
|
|
76
|
-
const d = resolution * (1 -
|
|
76
|
+
const d = resolution * (1 - size / resolution) * 0.5
|
|
77
77
|
geoCanvas.ctx.beginPath()
|
|
78
|
-
geoCanvas.ctx.rect(
|
|
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(
|
|
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 =
|
|
86
|
+
const s2 = size * 0.5
|
|
87
87
|
geoCanvas.ctx.beginPath()
|
|
88
|
-
geoCanvas.ctx.moveTo(
|
|
89
|
-
geoCanvas.ctx.lineTo(
|
|
90
|
-
geoCanvas.ctx.lineTo(
|
|
91
|
-
geoCanvas.ctx.lineTo(
|
|
92
|
-
geoCanvas.ctx.lineTo(
|
|
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
|
+
}
|