@lgv/visualization-map 0.0.1 → 0.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 +6 -5
- package/src/configuration.js +3 -2
- package/src/layout/heat.js +18 -14
- package/src/visualization/heatmap.js +6 -10
- package/src/visualization/index.js +123 -9
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lgv/visualization-map",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.3",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "ES6 d3.js core visualization scaffold object and utilities for maps.",
|
|
6
6
|
"main": "src/index.js",
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
"build": "webpack build --config webpack.prod.js",
|
|
13
13
|
"coverage": "c8 --reporter=html --reporter=text ava --node-arguments='--experimental-json-modules'",
|
|
14
14
|
"start": "webpack serve --config webpack.dev.js",
|
|
15
|
-
"startdocker": "webpack serve --config webpack.dev.js --host 0.0.0.0 --
|
|
15
|
+
"startdocker": "webpack serve --config webpack.dev.js --host 0.0.0.0 --allowed-hosts all",
|
|
16
16
|
"test": "npx ava --verbose --serial --timeout 1m --node-arguments='--experimental-json-modules'"
|
|
17
17
|
},
|
|
18
18
|
"repository": {
|
|
@@ -36,13 +36,14 @@
|
|
|
36
36
|
"css-loader": "^6.7.2",
|
|
37
37
|
"html-webpack-plugin": "^5.3.2",
|
|
38
38
|
"style-loader": "^3.3.1",
|
|
39
|
-
"webpack
|
|
40
|
-
"webpack-
|
|
39
|
+
"webpack": "^5.86.0",
|
|
40
|
+
"webpack-cli": "^5.1.4",
|
|
41
|
+
"webpack-dev-server": "^4.15.1"
|
|
41
42
|
},
|
|
42
43
|
"dependencies": {
|
|
43
44
|
"@deck.gl/core": "^8.8.20",
|
|
44
45
|
"@deck.gl/layers": "^8.8.20",
|
|
45
|
-
"@lgv/visualization-chart": "^0.3.
|
|
46
|
+
"@lgv/visualization-chart": "^0.3.21",
|
|
46
47
|
"@msrvida/vega-deck.gl": "^3.3.4",
|
|
47
48
|
"leaflet": "^1.9.2",
|
|
48
49
|
"vega": "^5.22.1"
|
package/src/configuration.js
CHANGED
|
@@ -6,12 +6,13 @@ const configuration = {
|
|
|
6
6
|
};
|
|
7
7
|
|
|
8
8
|
const configurationColor = {
|
|
9
|
-
p1: ["#
|
|
9
|
+
p1: ["#0a86ca", "#0a86ca"],
|
|
10
|
+
p2: ["#F7BBB5", "#F37765", "#EF4223", "#992B20", "#37150E"]
|
|
10
11
|
};
|
|
11
12
|
|
|
12
13
|
const configurationLayout = {
|
|
13
14
|
height: process.env.LGV_HEIGHT || 400,
|
|
14
|
-
tileserver: process.env.LGV_TILESERVER || "https://
|
|
15
|
+
tileserver: process.env.LGV_TILESERVER || "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png",
|
|
15
16
|
width: process.env.LGV_WIDTH || 400
|
|
16
17
|
}
|
|
17
18
|
|
package/src/layout/heat.js
CHANGED
|
@@ -30,21 +30,25 @@ class HeatLayout extends MapData {
|
|
|
30
30
|
|
|
31
31
|
if (this.map && data.type == "FeatureCollection") {
|
|
32
32
|
|
|
33
|
-
|
|
34
|
-
result = data.features.map(d => {
|
|
35
|
-
|
|
36
|
-
// convert to xy
|
|
37
|
-
let xy = this.map.latLngToContainerPoint(L.latLng(d.geometry.coordinates[1], d.geometry.coordinates[0]));
|
|
33
|
+
let bounds = this.map.getBounds();
|
|
38
34
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
35
|
+
// project to xy
|
|
36
|
+
result = data.features
|
|
37
|
+
.filter(d => d.geometry.coordinates[1] >= bounds._southWest.lat && d.geometry.coordinates[1] <= bounds._northEast.lat && d.geometry.coordinates[0] >= bounds._southWest.lng && d.geometry.coordinates[0] <= bounds._northEast.lng)
|
|
38
|
+
.map(d => {
|
|
39
|
+
|
|
40
|
+
// convert to xy
|
|
41
|
+
let xy = this.map.latLngToContainerPoint(L.latLng(d.geometry.coordinates[1], d.geometry.coordinates[0]));
|
|
42
|
+
|
|
43
|
+
return {
|
|
44
|
+
count: d.properties.count,
|
|
45
|
+
id: d.properties.id,
|
|
46
|
+
label: d.properties.name,
|
|
47
|
+
x: xy.x,
|
|
48
|
+
y: xy.y
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
});
|
|
48
52
|
|
|
49
53
|
}
|
|
50
54
|
|
|
@@ -11,7 +11,7 @@ import { HeatLayout as HL } from "../layout/heat.js";
|
|
|
11
11
|
import { configuration, configurationColor, configurationLayout } from "../configuration.js";
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
|
-
*
|
|
14
|
+
* Heatmap is a map abstraction for a heatmap visualization.
|
|
15
15
|
* @param {object} data - usually array; occasionally object
|
|
16
16
|
* @param {string} label - identifer for chart brand
|
|
17
17
|
* @param {float} height - specified height of chart
|
|
@@ -43,7 +43,7 @@ class Heatmap extends VisualizationMap {
|
|
|
43
43
|
let isHighDensityDisplay = window.devicePixelRatio > 1 && window.innerWidth * window.devicePixelRatio > 3360 && window.innerHeight * window.devicePixelRatio > 1942;
|
|
44
44
|
|
|
45
45
|
// set starting size
|
|
46
|
-
let base = isHighDensityDisplay ? this.unit * 1.75 : this.unit *
|
|
46
|
+
let base = isHighDensityDisplay ? this.unit * 1.75 : this.unit * 2;
|
|
47
47
|
let mark = base;
|
|
48
48
|
|
|
49
49
|
// determine mark size
|
|
@@ -72,12 +72,7 @@ class Heatmap extends VisualizationMap {
|
|
|
72
72
|
/**
|
|
73
73
|
* Generate main visualization, i.e. everything that sits inside the map.
|
|
74
74
|
*/
|
|
75
|
-
generateChart(
|
|
76
|
-
|
|
77
|
-
// process data
|
|
78
|
-
// update data object now that map exists
|
|
79
|
-
this.Data.map = this.map;
|
|
80
|
-
this.data = this.Data.update;
|
|
75
|
+
generateChart() {
|
|
81
76
|
|
|
82
77
|
// generate vega specification
|
|
83
78
|
let spec = this.generateSpecification();
|
|
@@ -123,11 +118,11 @@ class Heatmap extends VisualizationMap {
|
|
|
123
118
|
return {
|
|
124
119
|
width: this.width,
|
|
125
120
|
height: this.height,
|
|
126
|
-
data: [{ name: this.name, values: this.data }],
|
|
121
|
+
data: [{ name: this.name, values: this.isXY ? this.data.results : this.data }],
|
|
127
122
|
scales: [
|
|
128
123
|
{
|
|
129
124
|
name: "color",
|
|
130
|
-
type: "
|
|
125
|
+
type: "linear",
|
|
131
126
|
domain: { data: this.name, field: "count" },
|
|
132
127
|
range: { scheme: this.name, count: configurationColor.p1.length }
|
|
133
128
|
}
|
|
@@ -143,6 +138,7 @@ class Heatmap extends VisualizationMap {
|
|
|
143
138
|
y: { field: "y" },
|
|
144
139
|
size: { value: this.mark },
|
|
145
140
|
fill: { scale: "color", field: "count" },
|
|
141
|
+
opacity: { value: 1 }
|
|
146
142
|
},
|
|
147
143
|
},
|
|
148
144
|
}
|
|
@@ -21,8 +21,16 @@ class VisualizationMap extends LinearGrid {
|
|
|
21
21
|
// initialize inheritance
|
|
22
22
|
super(data, width, height, MapData, label, name);
|
|
23
23
|
|
|
24
|
+
// determine type of data object
|
|
25
|
+
this.determineDataType();
|
|
26
|
+
|
|
27
|
+
// determine x/y center
|
|
28
|
+
let x = this.isFeatureCollection ? mean(this.data.features, d => d.geometry.coordinates[1]) : (this.isPoint ? this.data.coordinates[1] : (this.isXY ? mean([this.data.metadata.bounds[0][1],this.data.metadata.bounds[1][1]]) : 0));
|
|
29
|
+
|
|
30
|
+
let y = this.isFeatureCollection ? mean(this.data.features, d => d.geometry.coordinates[0]) : (this.isPoint ? this.data.coordinates[0] : (this.isXY ? mean([this.data.metadata.bounds[0][0],this.data.metadata.bounds[1][0]]) : 0));
|
|
31
|
+
|
|
24
32
|
// update self
|
|
25
|
-
this.center = [
|
|
33
|
+
this.center = [x,y];
|
|
26
34
|
this.map = null;
|
|
27
35
|
this.tileserver = configurationLayout.tileserver;
|
|
28
36
|
this.zoom = 1;
|
|
@@ -30,12 +38,46 @@ class VisualizationMap extends LinearGrid {
|
|
|
30
38
|
}
|
|
31
39
|
|
|
32
40
|
/**
|
|
33
|
-
*
|
|
41
|
+
* Attach event handlers for map events.
|
|
34
42
|
*/
|
|
35
|
-
|
|
43
|
+
attachEvents() {
|
|
44
|
+
this.map.on("moveend", e => {
|
|
45
|
+
|
|
46
|
+
// update dimensions on class
|
|
47
|
+
let containerSize = this.map.getSize();
|
|
48
|
+
this.heightSpecified = containerSize.y;
|
|
49
|
+
this.widthSpecified = containerSize.x;
|
|
50
|
+
|
|
51
|
+
// process data
|
|
52
|
+
// update data object now that map exists
|
|
53
|
+
this.Data.map = this.map;
|
|
54
|
+
if (this.Data.data) this.data = this.Data.update;
|
|
55
|
+
|
|
56
|
+
// render overlay
|
|
57
|
+
this.generateChart();
|
|
58
|
+
|
|
59
|
+
// broadcast event
|
|
60
|
+
this.container.dispatch("map-change", {
|
|
61
|
+
bubbles: true
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
});
|
|
65
|
+
}
|
|
36
66
|
|
|
37
|
-
|
|
67
|
+
/**
|
|
68
|
+
* Generate main visualization, i.e. everything that sits inside the svg.
|
|
69
|
+
*/
|
|
70
|
+
determineDataType() {
|
|
71
|
+
this.isFeatureCollection = this.data && this.data.features;
|
|
72
|
+
this.isPoint = this.data && this.data.coordinates && this.data.type.lower() == "point";
|
|
73
|
+
this.isXY = this.data && this.data.metadata && this.data.results;
|
|
74
|
+
}
|
|
38
75
|
|
|
76
|
+
/**
|
|
77
|
+
* Generate main visualization, i.e. everything that sits inside the svg.
|
|
78
|
+
*/
|
|
79
|
+
generateChart() {
|
|
80
|
+
// empty method for specific map visualizations to overwrite
|
|
39
81
|
}
|
|
40
82
|
|
|
41
83
|
/**
|
|
@@ -43,21 +85,59 @@ class VisualizationMap extends LinearGrid {
|
|
|
43
85
|
*/
|
|
44
86
|
generateCore() {
|
|
45
87
|
|
|
46
|
-
//
|
|
47
|
-
|
|
88
|
+
// pull dom element styles
|
|
89
|
+
let styles = window.getComputedStyle(this.container.node());
|
|
90
|
+
let height = parseFloat(styles.height.split("px")[0]);
|
|
91
|
+
let width = parseFloat(styles.width.split("px")[0]);
|
|
92
|
+
|
|
93
|
+
// check for unset width or height
|
|
94
|
+
if (height == 0 || width == 0) {
|
|
95
|
+
|
|
96
|
+
// add dom element attribute
|
|
97
|
+
this.container.node().setAttribute(`data-${this.branding}`, this.name);
|
|
98
|
+
|
|
99
|
+
// generate css for account for unset dimension(s)
|
|
100
|
+
let property = `[data-${this.branding}="${this.name}"]`;
|
|
101
|
+
let value = width == 0 && height == 0 ? `{ height: ${this.height}px; width: ${this.width}px; }` : (width == 0 ? `{ width: ${this.width}px; }` : `{ height: ${this.height}px; }`);
|
|
102
|
+
|
|
103
|
+
// generate stylesheet and insert in document
|
|
104
|
+
let style = document.createElement("style");
|
|
105
|
+
style.type = "text/css";
|
|
106
|
+
style.append(document.createTextNode(`${property} ${value}`));
|
|
107
|
+
document.head.append(style);
|
|
108
|
+
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// update stored dimensions
|
|
112
|
+
if (height > 0) this.heightSpecified = height;
|
|
113
|
+
if (width > 0) this.widthSpecified = width;
|
|
114
|
+
|
|
115
|
+
// initialize map
|
|
48
116
|
this.map = L.map(this.container.node(), {
|
|
49
117
|
center: this.center,
|
|
50
118
|
zoom: this.zoom
|
|
51
119
|
});
|
|
52
120
|
|
|
53
121
|
// add handlers for leaflet events
|
|
54
|
-
this.
|
|
122
|
+
this.attachEvents();
|
|
55
123
|
|
|
56
124
|
// update tile server
|
|
57
125
|
L.tileLayer(this.tileserver).addTo(this.map);
|
|
58
126
|
|
|
59
|
-
//
|
|
60
|
-
this.
|
|
127
|
+
// determine bounding box
|
|
128
|
+
let bounds = (this.isFeatureCollection || this.isPoint) ? L.geoJSON(this.Data.source).getBounds() : (this.isXY ? this.data.metadata.bounds : this.map.getBounds());
|
|
129
|
+
|
|
130
|
+
// fit to bounds
|
|
131
|
+
if (this.isFeatureCollection || this.isXY) {
|
|
132
|
+
// several data points
|
|
133
|
+
this.map.fitBounds(bounds);
|
|
134
|
+
} else if (this.isPoint) {
|
|
135
|
+
// zoom out if only provided single data point
|
|
136
|
+
this.map.setView(bounds.getCenter(), this.map.getMaxZoom() / 2);
|
|
137
|
+
} else {
|
|
138
|
+
// no geo data provided so center world
|
|
139
|
+
this.map.setView(bounds.getCenter());
|
|
140
|
+
}
|
|
61
141
|
|
|
62
142
|
}
|
|
63
143
|
|
|
@@ -74,6 +154,40 @@ class VisualizationMap extends LinearGrid {
|
|
|
74
154
|
|
|
75
155
|
}
|
|
76
156
|
|
|
157
|
+
/**
|
|
158
|
+
* Update visualization.
|
|
159
|
+
* @param {object} data - geojson
|
|
160
|
+
* @param {float} height - specified height of chart
|
|
161
|
+
* @param {float} width - specified width of chart
|
|
162
|
+
*/
|
|
163
|
+
update(data, width, height) {
|
|
164
|
+
|
|
165
|
+
// update self
|
|
166
|
+
this.heightSpecified = height || this.heightSpecified;
|
|
167
|
+
this.widthSpecified = width || this.widthSpecified;
|
|
168
|
+
|
|
169
|
+
// if layout data object
|
|
170
|
+
if (this.Data.height && this.Data.width) {
|
|
171
|
+
|
|
172
|
+
// update layout attributes
|
|
173
|
+
this.Data.height = this.heightSpecified;
|
|
174
|
+
this.Data.width = this.widthSpecified;
|
|
175
|
+
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// recalculate values
|
|
179
|
+
this.Data.source = data;
|
|
180
|
+
this.Data.data = this.Data.update;
|
|
181
|
+
this.data = this.Data.data;
|
|
182
|
+
|
|
183
|
+
// determine type of data object
|
|
184
|
+
this.determineDataType();
|
|
185
|
+
|
|
186
|
+
// generate visualization
|
|
187
|
+
this.generateChart();
|
|
188
|
+
|
|
189
|
+
}
|
|
190
|
+
|
|
77
191
|
}
|
|
78
192
|
|
|
79
193
|
export { VisualizationMap };
|