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.
- package/dist/gridviz.js +984 -90
- package/dist/gridviz.min.js +1 -1
- package/package.json +1 -1
- package/src/core/Dataset.js +24 -1
- package/src/core/Map.js +11 -1
- package/src/core/Style.js +8 -2
- package/src/index.js +5 -1
- package/src/layer/GridLayer.js +9 -13
- package/src/legend/SizeLegend.js +6 -6
- 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 +24 -0
- package/src/utils/trivariate.js +203 -0
- package/src/utils/utils.js +10 -0
package/package.json
CHANGED
package/src/core/Dataset.js
CHANGED
|
@@ -17,7 +17,7 @@ export class Dataset {
|
|
|
17
17
|
* @param {import("./Map.js").Map} map The map.
|
|
18
18
|
* @param {string} url The URL of the dataset.
|
|
19
19
|
* @param {number} resolution The dataset resolution, in the CRS geographical unit.
|
|
20
|
-
* @param {{preprocess?:function(Cell):boolean}} opts
|
|
20
|
+
* @param {{preprocess?:function(Cell):boolean, mixedResolution?:function(Cell):number}} opts
|
|
21
21
|
* @abstract
|
|
22
22
|
*/
|
|
23
23
|
constructor(map, url, resolution, opts = {}) {
|
|
@@ -40,6 +40,13 @@ export class Dataset {
|
|
|
40
40
|
* @type {number} */
|
|
41
41
|
this.resolution = resolution
|
|
42
42
|
|
|
43
|
+
/**
|
|
44
|
+
* In case the dataset is a dataset with cells having different resolution,
|
|
45
|
+
* this is the function returning the resolution of each cell.
|
|
46
|
+
* @protected
|
|
47
|
+
* @type {(function(Cell):number )| undefined } */
|
|
48
|
+
this.mixedResolution = opts.mixedResolution
|
|
49
|
+
|
|
43
50
|
/**
|
|
44
51
|
* A preprocess to run on each cell after loading. It can be used to apply some specific treatment before or compute a new column. And also to determine which cells to keep after loading.
|
|
45
52
|
* @type {(function(Cell):boolean )| undefined } */
|
|
@@ -96,6 +103,22 @@ export class Dataset {
|
|
|
96
103
|
}
|
|
97
104
|
return undefined*/
|
|
98
105
|
|
|
106
|
+
//rare case of mixed resolution dataset
|
|
107
|
+
if (this.mixedResolution) {
|
|
108
|
+
for (const c of cells) {
|
|
109
|
+
/** @type {number} */
|
|
110
|
+
const r = this.mixedResolution(c)
|
|
111
|
+
if (posGeo.x < c.x) continue
|
|
112
|
+
else if (c.x + r < posGeo.x) continue
|
|
113
|
+
else if (posGeo.y < c.y) continue
|
|
114
|
+
else if (c.y + r < posGeo.y) continue
|
|
115
|
+
else return c
|
|
116
|
+
}
|
|
117
|
+
return undefined
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
//common case
|
|
121
|
+
|
|
99
122
|
/** @type {number} */
|
|
100
123
|
const r = this.getResolution()
|
|
101
124
|
for (const cell of cells) {
|
package/src/core/Map.js
CHANGED
|
@@ -171,7 +171,8 @@ export class Map {
|
|
|
171
171
|
|
|
172
172
|
//set layer alpha and blend mode
|
|
173
173
|
this.geoCanvas.ctx.globalAlpha = layer.alpha ? layer.alpha(z) : 1.0
|
|
174
|
-
|
|
174
|
+
if (layer.blendOperation)
|
|
175
|
+
this.geoCanvas.ctx.globalCompositeOperation = layer.blendOperation(z)
|
|
175
176
|
|
|
176
177
|
//set affin transform to draw with geographical coordinates
|
|
177
178
|
this.geoCanvas.setCanvasTransform()
|
|
@@ -300,6 +301,15 @@ export class Map {
|
|
|
300
301
|
const cell = dsc.getCellFromPosition(posGeo, dsc.getViewCache())
|
|
301
302
|
//console.log(cell, dsc.resolution)
|
|
302
303
|
if (!cell) return undefined
|
|
304
|
+
|
|
305
|
+
//rare case for a dataset with mixed resolutions
|
|
306
|
+
if (dsc.mixedResolution) {
|
|
307
|
+
const r = dsc.mixedResolution(cell)
|
|
308
|
+
const html = layer.cellInfoHTML(cell, r)
|
|
309
|
+
if (!html) return undefined
|
|
310
|
+
return { cell: cell, html: html, resolution: r }
|
|
311
|
+
}
|
|
312
|
+
|
|
303
313
|
const html = layer.cellInfoHTML(cell, dsc.getResolution())
|
|
304
314
|
if (!html) return undefined
|
|
305
315
|
return { cell: cell, html: html, resolution: dsc.getResolution() }
|
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
|
|
@@ -78,10 +78,16 @@ export class Style extends Drawable {
|
|
|
78
78
|
* @param {object} opts
|
|
79
79
|
* @returns {this} */
|
|
80
80
|
updateLegends(opts) {
|
|
81
|
-
|
|
81
|
+
Style.updateLegendsRecursive(this.legends, opts)
|
|
82
82
|
return this
|
|
83
83
|
}
|
|
84
84
|
|
|
85
|
+
/** @private */
|
|
86
|
+
static updateLegendsRecursive(lg, opts) {
|
|
87
|
+
if (Array.isArray(lg)) for (const lg_ of lg) this.updateLegendsRecursive(lg_, opts)
|
|
88
|
+
else lg.update(opts)
|
|
89
|
+
}
|
|
90
|
+
|
|
85
91
|
/**
|
|
86
92
|
* @param {Array.<import("./Legend").Legend>} legends
|
|
87
93
|
* @returns {this} */
|
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
|
package/src/layer/GridLayer.js
CHANGED
|
@@ -68,7 +68,8 @@ export class GridLayer extends Layer {
|
|
|
68
68
|
//set style alpha and blend mode
|
|
69
69
|
//TODO: multiply by layer alpha ?
|
|
70
70
|
geoCanvas.ctx.globalAlpha = s.alpha ? s.alpha(z) : 1.0
|
|
71
|
-
|
|
71
|
+
if (s.blendOperation)
|
|
72
|
+
geoCanvas.ctx.globalCompositeOperation = s.blendOperation(z)
|
|
72
73
|
|
|
73
74
|
//set affin transform to draw with geographical coordinates
|
|
74
75
|
geoCanvas.setCanvasTransform()
|
|
@@ -86,24 +87,14 @@ export class GridLayer extends Layer {
|
|
|
86
87
|
for (const s of this.styles) {
|
|
87
88
|
//check if style is visible
|
|
88
89
|
if (s.visible && !s.visible(z)) continue
|
|
89
|
-
|
|
90
|
-
//console.log(s, lg)
|
|
91
|
-
//this.legend.append(lg.div)
|
|
92
|
-
//s1.node().appendChild(s2.node())
|
|
93
|
-
legend.node().append(lg.div.node())
|
|
94
|
-
}
|
|
90
|
+
GridLayer.addLegends(legend, s.legends)
|
|
95
91
|
|
|
96
92
|
//case for styles of styles, like kernel smoothing
|
|
97
93
|
//TODO do better
|
|
98
94
|
if (s['styles']) {
|
|
99
95
|
for (const s2 of s['styles']) {
|
|
100
96
|
if (s2.visible && !s2.visible(z)) continue
|
|
101
|
-
|
|
102
|
-
//console.log(s, lg)
|
|
103
|
-
//this.legend.append(lg.div)
|
|
104
|
-
//s1.node().appendChild(s2.node())
|
|
105
|
-
legend.node().append(lg.div.node())
|
|
106
|
-
}
|
|
97
|
+
GridLayer.addLegends(legend, s2.legends)
|
|
107
98
|
}
|
|
108
99
|
}
|
|
109
100
|
}
|
|
@@ -111,6 +102,11 @@ export class GridLayer extends Layer {
|
|
|
111
102
|
|
|
112
103
|
}
|
|
113
104
|
|
|
105
|
+
/** @private */
|
|
106
|
+
static addLegends(legendComp, lg) {
|
|
107
|
+
if (Array.isArray(lg)) for (const lg_ of lg) this.addLegends(legendComp, lg_)
|
|
108
|
+
else legendComp.node().append(lg.div.node())
|
|
109
|
+
}
|
|
114
110
|
|
|
115
111
|
|
|
116
112
|
/**
|
package/src/legend/SizeLegend.js
CHANGED
|
@@ -17,12 +17,12 @@ export class SizeLegend extends Legend {
|
|
|
17
17
|
super(opts)
|
|
18
18
|
opts = opts || {}
|
|
19
19
|
|
|
20
|
-
/** A function returning the text label
|
|
21
|
-
* @type { function(object, Array.<import('../core/Dataset.js').Cell
|
|
20
|
+
/** A function returning the text label, from the view scale and list of cells, resolution and zoom
|
|
21
|
+
* @type { function(object, Array.<import('../core/Dataset.js').Cell>, number, number):(number|string) } */
|
|
22
22
|
this.label = opts.label || undefined
|
|
23
23
|
|
|
24
|
-
/** A function returning the size of the legend symbol
|
|
25
|
-
* @type { function(object):number } */
|
|
24
|
+
/** A function returning the size of the legend symbol, in geo UoM, from the viewscale, resolution and zoom
|
|
25
|
+
* @type { function(object, number, number):number } */
|
|
26
26
|
this.size = opts.size || undefined
|
|
27
27
|
|
|
28
28
|
//symbol
|
|
@@ -52,12 +52,12 @@ export class SizeLegend extends Legend {
|
|
|
52
52
|
this.makeTitle()
|
|
53
53
|
|
|
54
54
|
//get label. May not be a number (!)
|
|
55
|
-
let label = this.label(opts.viewScale, opts.cells)
|
|
55
|
+
let label = this.label(opts.viewScale, opts.cells, opts.resolution, opts.z)
|
|
56
56
|
|
|
57
57
|
//compute size of symbol, in pix
|
|
58
58
|
let sizePix
|
|
59
59
|
if (this.size)
|
|
60
|
-
sizePix = this.size(opts.viewScale) / opts.z
|
|
60
|
+
sizePix = this.size(opts.viewScale, opts.resolution, opts.z) / opts.z
|
|
61
61
|
else
|
|
62
62
|
sizePix = opts.viewScale(+label) / opts.z
|
|
63
63
|
if (!sizePix) return
|
|
@@ -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
|