@sentropic/design-system-vue 0.27.0 → 0.29.0
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/GeoMap.d.ts +182 -0
- package/dist/GeoMap.d.ts.map +1 -0
- package/dist/GeoMap.js +490 -0
- package/dist/GeoMap.js.map +1 -0
- package/dist/LineChart.d.ts +8 -0
- package/dist/LineChart.d.ts.map +1 -1
- package/dist/LineChart.js +55 -7
- package/dist/LineChart.js.map +1 -1
- package/dist/ScatterPlot.d.ts +26 -0
- package/dist/ScatterPlot.d.ts.map +1 -1
- package/dist/ScatterPlot.js +30 -4
- package/dist/ScatterPlot.js.map +1 -1
- package/dist/SelectionChip.d.ts +1 -1
- package/dist/SelectionChip.d.ts.map +1 -1
- package/dist/chartScale.d.ts +12 -0
- package/dist/chartScale.d.ts.map +1 -1
- package/dist/chartScale.js +12 -0
- package/dist/chartScale.js.map +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/styles.css +118 -0
- package/package.json +1 -1
package/dist/GeoMap.d.ts
ADDED
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
export type GeoMapTone = "category1" | "category2" | "category3" | "category4" | "category5" | "category6" | "category7" | "category8";
|
|
2
|
+
/** Coordonnée géographique — même forme que `GeoCoordinate` (dataviz-core). */
|
|
3
|
+
export type GeoMapCoordinate = {
|
|
4
|
+
latitude: number;
|
|
5
|
+
longitude: number;
|
|
6
|
+
};
|
|
7
|
+
/** Emprise géographique — même forme que `GeoBounds` (dataviz-core). */
|
|
8
|
+
export type GeoMapBounds = {
|
|
9
|
+
south: number;
|
|
10
|
+
west: number;
|
|
11
|
+
north: number;
|
|
12
|
+
east: number;
|
|
13
|
+
};
|
|
14
|
+
export type GeoMapProjection = "equirectangular" | "mercator";
|
|
15
|
+
export type GeoMapGeometryType = "Point" | "MultiPoint" | "LineString" | "MultiLineString" | "Polygon" | "MultiPolygon";
|
|
16
|
+
/** Géométrie GeoJSON — même forme que `GeoJsonGeometry` (dataviz-core). */
|
|
17
|
+
export type GeoMapGeometry = {
|
|
18
|
+
type: GeoMapGeometryType;
|
|
19
|
+
coordinates: unknown[];
|
|
20
|
+
};
|
|
21
|
+
/** Entité géographique — sous-ensemble structurel de `GeoJsonFeature` (dataviz-core). */
|
|
22
|
+
export type GeoMapFeature = {
|
|
23
|
+
id: string;
|
|
24
|
+
label?: string;
|
|
25
|
+
value?: number;
|
|
26
|
+
geometry: GeoMapGeometry;
|
|
27
|
+
};
|
|
28
|
+
/** Point géographique — sur-ensemble structurel de `GeoPoint` (dataviz-core). */
|
|
29
|
+
export type GeoMapPoint = GeoMapCoordinate & {
|
|
30
|
+
id?: string;
|
|
31
|
+
label?: string;
|
|
32
|
+
value?: number;
|
|
33
|
+
tone?: GeoMapTone;
|
|
34
|
+
/** Rayon explicite en px (prioritaire sur l'échelle par `value`), borné à 32. */
|
|
35
|
+
r?: number;
|
|
36
|
+
};
|
|
37
|
+
/** Flux géographique — sous-ensemble structurel de `GeoFlowLink` (dataviz-core). */
|
|
38
|
+
export type GeoMapFlow = {
|
|
39
|
+
id?: string;
|
|
40
|
+
label?: string;
|
|
41
|
+
source: GeoMapCoordinate;
|
|
42
|
+
target: GeoMapCoordinate;
|
|
43
|
+
value?: number;
|
|
44
|
+
};
|
|
45
|
+
/** Entités GeoJSON (polygones, lignes, points) ; ton par couche ou cycle de palette par entité. */
|
|
46
|
+
export type GeoMapGeojsonLayer = {
|
|
47
|
+
type: "geojson";
|
|
48
|
+
features: GeoMapFeature[];
|
|
49
|
+
tone?: GeoMapTone;
|
|
50
|
+
label?: string;
|
|
51
|
+
};
|
|
52
|
+
/** Choroplèthe : entités + valeur par id (`regions` dataviz : `key` → `value`) → intensité color-mix. */
|
|
53
|
+
export type GeoMapChoroplethLayer = {
|
|
54
|
+
type: "choropleth";
|
|
55
|
+
features: GeoMapFeature[];
|
|
56
|
+
values: Record<string, number>;
|
|
57
|
+
/** Ton de base de la rampe d'intensité (défaut `category1`). */
|
|
58
|
+
tone?: GeoMapTone;
|
|
59
|
+
label?: string;
|
|
60
|
+
};
|
|
61
|
+
/** Points/épingles : rayon ∝ `value` (bornes `minRadius`/`maxRadius`, défauts 5/14 comme dataviz). */
|
|
62
|
+
export type GeoMapPointsLayer = {
|
|
63
|
+
type: "points";
|
|
64
|
+
points: GeoMapPoint[];
|
|
65
|
+
tone?: GeoMapTone;
|
|
66
|
+
minRadius?: number;
|
|
67
|
+
maxRadius?: number;
|
|
68
|
+
label?: string;
|
|
69
|
+
};
|
|
70
|
+
/** Densité : cercles translucides superposés, rayon/intensité ∝ `value` (poids). */
|
|
71
|
+
export type GeoMapDensityLayer = {
|
|
72
|
+
type: "density";
|
|
73
|
+
points: GeoMapPoint[];
|
|
74
|
+
/** Ton de la nappe (défaut `category3` — parité visuelle dataviz). */
|
|
75
|
+
tone?: GeoMapTone;
|
|
76
|
+
maxRadius?: number;
|
|
77
|
+
label?: string;
|
|
78
|
+
};
|
|
79
|
+
/** Flux : arcs quadratiques source → target, épaisseur ∝ `value` (défaut ton `category1`). */
|
|
80
|
+
export type GeoMapFlowLayer = {
|
|
81
|
+
type: "flow";
|
|
82
|
+
flows: GeoMapFlow[];
|
|
83
|
+
tone?: GeoMapTone;
|
|
84
|
+
label?: string;
|
|
85
|
+
};
|
|
86
|
+
/** Hexbin : binning hexagonal des points (même binning que dataviz-core), intensité ∝ valeur agrégée. */
|
|
87
|
+
export type GeoMapHexbinLayer = {
|
|
88
|
+
type: "hexbin";
|
|
89
|
+
points: GeoMapPoint[];
|
|
90
|
+
/** Taille de cellule en degrés (défaut 1). */
|
|
91
|
+
cellSize?: number;
|
|
92
|
+
tone?: GeoMapTone;
|
|
93
|
+
label?: string;
|
|
94
|
+
};
|
|
95
|
+
/** Clusters : regroupement glouton des points (même algo que dataviz-core), centroïdes marqueurs distinctifs. */
|
|
96
|
+
export type GeoMapClusterLayer = {
|
|
97
|
+
type: "cluster";
|
|
98
|
+
points: GeoMapPoint[];
|
|
99
|
+
/** Rayon de regroupement en degrés (défaut 1). */
|
|
100
|
+
radius?: number;
|
|
101
|
+
tone?: GeoMapTone;
|
|
102
|
+
label?: string;
|
|
103
|
+
};
|
|
104
|
+
export type GeoMapLayer = GeoMapGeojsonLayer | GeoMapChoroplethLayer | GeoMapPointsLayer | GeoMapDensityLayer | GeoMapFlowLayer | GeoMapHexbinLayer | GeoMapClusterLayer;
|
|
105
|
+
export type GeoMapProps = {
|
|
106
|
+
layers: GeoMapLayer[];
|
|
107
|
+
width?: number;
|
|
108
|
+
height?: number;
|
|
109
|
+
projection?: GeoMapProjection;
|
|
110
|
+
/** Emprise explicite ; sinon auto-ajustement sur les données de toutes les couches + marge. */
|
|
111
|
+
bounds?: GeoMapBounds;
|
|
112
|
+
label: string;
|
|
113
|
+
class?: string;
|
|
114
|
+
};
|
|
115
|
+
export declare const GeoMap: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
|
|
116
|
+
layers: {
|
|
117
|
+
type: () => GeoMapLayer[];
|
|
118
|
+
required: true;
|
|
119
|
+
};
|
|
120
|
+
width: {
|
|
121
|
+
type: NumberConstructor;
|
|
122
|
+
default: number;
|
|
123
|
+
};
|
|
124
|
+
height: {
|
|
125
|
+
type: NumberConstructor;
|
|
126
|
+
default: number;
|
|
127
|
+
};
|
|
128
|
+
projection: {
|
|
129
|
+
type: () => GeoMapProjection;
|
|
130
|
+
default: string;
|
|
131
|
+
};
|
|
132
|
+
bounds: {
|
|
133
|
+
type: () => GeoMapBounds;
|
|
134
|
+
default: undefined;
|
|
135
|
+
};
|
|
136
|
+
label: {
|
|
137
|
+
type: StringConstructor;
|
|
138
|
+
required: true;
|
|
139
|
+
};
|
|
140
|
+
class: {
|
|
141
|
+
type: StringConstructor;
|
|
142
|
+
default: undefined;
|
|
143
|
+
};
|
|
144
|
+
}>, () => import("vue").VNode<import("vue").RendererNode, import("vue").RendererElement, {
|
|
145
|
+
[key: string]: any;
|
|
146
|
+
}>, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
|
|
147
|
+
layers: {
|
|
148
|
+
type: () => GeoMapLayer[];
|
|
149
|
+
required: true;
|
|
150
|
+
};
|
|
151
|
+
width: {
|
|
152
|
+
type: NumberConstructor;
|
|
153
|
+
default: number;
|
|
154
|
+
};
|
|
155
|
+
height: {
|
|
156
|
+
type: NumberConstructor;
|
|
157
|
+
default: number;
|
|
158
|
+
};
|
|
159
|
+
projection: {
|
|
160
|
+
type: () => GeoMapProjection;
|
|
161
|
+
default: string;
|
|
162
|
+
};
|
|
163
|
+
bounds: {
|
|
164
|
+
type: () => GeoMapBounds;
|
|
165
|
+
default: undefined;
|
|
166
|
+
};
|
|
167
|
+
label: {
|
|
168
|
+
type: StringConstructor;
|
|
169
|
+
required: true;
|
|
170
|
+
};
|
|
171
|
+
class: {
|
|
172
|
+
type: StringConstructor;
|
|
173
|
+
default: undefined;
|
|
174
|
+
};
|
|
175
|
+
}>> & Readonly<{}>, {
|
|
176
|
+
class: string;
|
|
177
|
+
width: number;
|
|
178
|
+
height: number;
|
|
179
|
+
projection: GeoMapProjection;
|
|
180
|
+
bounds: GeoMapBounds;
|
|
181
|
+
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
|
182
|
+
//# sourceMappingURL=GeoMap.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"GeoMap.d.ts","sourceRoot":"","sources":["../src/GeoMap.ts"],"names":[],"mappings":"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"}
|
package/dist/GeoMap.js
ADDED
|
@@ -0,0 +1,490 @@
|
|
|
1
|
+
import { defineComponent, h } from "vue";
|
|
2
|
+
import { classNames } from "./classNames.js";
|
|
3
|
+
import { chartDataList } from "./chartScale.js";
|
|
4
|
+
const PADDING = 24;
|
|
5
|
+
const MAX_POINT_RADIUS = 32;
|
|
6
|
+
const WORLD = { south: -90, west: -180, north: 90, east: 180 };
|
|
7
|
+
const TONES = [
|
|
8
|
+
"category1",
|
|
9
|
+
"category2",
|
|
10
|
+
"category3",
|
|
11
|
+
"category4",
|
|
12
|
+
"category5",
|
|
13
|
+
"category6",
|
|
14
|
+
"category7",
|
|
15
|
+
"category8",
|
|
16
|
+
];
|
|
17
|
+
const GEOMETRY_TYPES = new Set(["Point", "MultiPoint", "LineString", "MultiLineString", "Polygon", "MultiPolygon"]);
|
|
18
|
+
function isFiniteCoordinate(c) {
|
|
19
|
+
return !!c && Number.isFinite(c.latitude) && Number.isFinite(c.longitude);
|
|
20
|
+
}
|
|
21
|
+
function scaleNumber(value, min, max, start, end) {
|
|
22
|
+
return max === min ? (start + end) / 2 : start + ((value - min) / (max - min)) * (end - start);
|
|
23
|
+
}
|
|
24
|
+
/** Pourcentage color-mix valide (0–100, arrondi). */
|
|
25
|
+
function mixPercent(value) {
|
|
26
|
+
return Math.round(Math.max(0, Math.min(100, value)));
|
|
27
|
+
}
|
|
28
|
+
function coordinatePair(value) {
|
|
29
|
+
if (!Array.isArray(value) || value.length < 2 || Array.isArray(value[0]))
|
|
30
|
+
return undefined;
|
|
31
|
+
const longitude = Number(value[0]);
|
|
32
|
+
const latitude = Number(value[1]);
|
|
33
|
+
if (!Number.isFinite(latitude) || !Number.isFinite(longitude))
|
|
34
|
+
return undefined;
|
|
35
|
+
return { latitude, longitude };
|
|
36
|
+
}
|
|
37
|
+
function collectGeometryCoordinates(value, out) {
|
|
38
|
+
if (!Array.isArray(value))
|
|
39
|
+
return;
|
|
40
|
+
const pair = coordinatePair(value);
|
|
41
|
+
if (pair) {
|
|
42
|
+
out.push(pair);
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
for (const item of value)
|
|
46
|
+
collectGeometryCoordinates(item, out);
|
|
47
|
+
}
|
|
48
|
+
function isGeometry(geometry) {
|
|
49
|
+
return !!geometry && GEOMETRY_TYPES.has(geometry.type) && Array.isArray(geometry.coordinates);
|
|
50
|
+
}
|
|
51
|
+
function layerCoordinates(layer) {
|
|
52
|
+
switch (layer.type) {
|
|
53
|
+
case "geojson":
|
|
54
|
+
case "choropleth": {
|
|
55
|
+
const out = [];
|
|
56
|
+
for (const feature of layer.features ?? []) {
|
|
57
|
+
if (isGeometry(feature.geometry))
|
|
58
|
+
collectGeometryCoordinates(feature.geometry.coordinates, out);
|
|
59
|
+
}
|
|
60
|
+
return out;
|
|
61
|
+
}
|
|
62
|
+
case "points":
|
|
63
|
+
case "density":
|
|
64
|
+
case "hexbin":
|
|
65
|
+
case "cluster":
|
|
66
|
+
return (layer.points ?? []).filter(isFiniteCoordinate);
|
|
67
|
+
case "flow": {
|
|
68
|
+
const out = [];
|
|
69
|
+
for (const flow of layer.flows ?? []) {
|
|
70
|
+
if (isFiniteCoordinate(flow.source))
|
|
71
|
+
out.push(flow.source);
|
|
72
|
+
if (isFiniteCoordinate(flow.target))
|
|
73
|
+
out.push(flow.target);
|
|
74
|
+
}
|
|
75
|
+
return out;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
function validBounds(candidate) {
|
|
80
|
+
return candidate &&
|
|
81
|
+
Number.isFinite(candidate.south) &&
|
|
82
|
+
Number.isFinite(candidate.west) &&
|
|
83
|
+
Number.isFinite(candidate.north) &&
|
|
84
|
+
Number.isFinite(candidate.east) &&
|
|
85
|
+
candidate.north > candidate.south &&
|
|
86
|
+
candidate.east > candidate.west
|
|
87
|
+
? candidate
|
|
88
|
+
: null;
|
|
89
|
+
}
|
|
90
|
+
function fitBounds(all) {
|
|
91
|
+
const coords = all.flatMap(layerCoordinates);
|
|
92
|
+
if (coords.length === 0)
|
|
93
|
+
return WORLD;
|
|
94
|
+
let south = Infinity;
|
|
95
|
+
let west = Infinity;
|
|
96
|
+
let north = -Infinity;
|
|
97
|
+
let east = -Infinity;
|
|
98
|
+
for (const c of coords) {
|
|
99
|
+
if (c.latitude < south)
|
|
100
|
+
south = c.latitude;
|
|
101
|
+
if (c.latitude > north)
|
|
102
|
+
north = c.latitude;
|
|
103
|
+
if (c.longitude < west)
|
|
104
|
+
west = c.longitude;
|
|
105
|
+
if (c.longitude > east)
|
|
106
|
+
east = c.longitude;
|
|
107
|
+
}
|
|
108
|
+
// Marge : 5 % de l'étendue, 1° minimum (point isolé), bornée au monde.
|
|
109
|
+
const latPad = Math.max((north - south) * 0.05, 1);
|
|
110
|
+
const lonPad = Math.max((east - west) * 0.05, 1);
|
|
111
|
+
return {
|
|
112
|
+
south: Math.max(south - latPad, -90),
|
|
113
|
+
west: Math.max(west - lonPad, -180),
|
|
114
|
+
north: Math.min(north + latPad, 90),
|
|
115
|
+
east: Math.min(east + lonPad, 180),
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
function mercatorY(latitude) {
|
|
119
|
+
const clamped = Math.max(-85, Math.min(85, latitude));
|
|
120
|
+
return Math.log(Math.tan(Math.PI / 4 + (clamped * Math.PI) / 360));
|
|
121
|
+
}
|
|
122
|
+
function createProjector(b, projection, width, height) {
|
|
123
|
+
const innerW = Math.max(width - PADDING * 2, 1);
|
|
124
|
+
const innerH = Math.max(height - PADDING * 2, 1);
|
|
125
|
+
const projY = (latitude) => (projection === "mercator" ? mercatorY(latitude) : latitude);
|
|
126
|
+
const top = projY(b.north);
|
|
127
|
+
const bottom = projY(b.south);
|
|
128
|
+
const lonSpan = b.east - b.west || 1;
|
|
129
|
+
const ySpan = top - bottom || 1;
|
|
130
|
+
return (c) => ({
|
|
131
|
+
x: PADDING + ((c.longitude - b.west) / lonSpan) * innerW,
|
|
132
|
+
y: PADDING + ((top - projY(c.latitude)) / ySpan) * innerH,
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
function linePath(coordinates, project) {
|
|
136
|
+
return coordinates
|
|
137
|
+
.map((item, index) => {
|
|
138
|
+
const pair = coordinatePair(item);
|
|
139
|
+
if (!pair)
|
|
140
|
+
return "";
|
|
141
|
+
const p = project(pair);
|
|
142
|
+
return `${index === 0 ? "M" : "L"} ${p.x} ${p.y}`;
|
|
143
|
+
})
|
|
144
|
+
.filter(Boolean)
|
|
145
|
+
.join(" ");
|
|
146
|
+
}
|
|
147
|
+
function ringsPath(coordinates, project) {
|
|
148
|
+
return coordinates
|
|
149
|
+
.map((ring) => {
|
|
150
|
+
const path = Array.isArray(ring) ? linePath(ring, project) : "";
|
|
151
|
+
return path === "" ? "" : `${path} Z`;
|
|
152
|
+
})
|
|
153
|
+
.filter(Boolean)
|
|
154
|
+
.join(" ");
|
|
155
|
+
}
|
|
156
|
+
function geometryPath(geometry, project) {
|
|
157
|
+
switch (geometry.type) {
|
|
158
|
+
case "Point": {
|
|
159
|
+
const pair = coordinatePair(geometry.coordinates);
|
|
160
|
+
if (!pair)
|
|
161
|
+
return "";
|
|
162
|
+
const p = project(pair);
|
|
163
|
+
return `M ${p.x - 5} ${p.y} a 5 5 0 1 0 10 0 a 5 5 0 1 0 -10 0`;
|
|
164
|
+
}
|
|
165
|
+
case "MultiPoint":
|
|
166
|
+
return geometry.coordinates
|
|
167
|
+
.map((c) => geometryPath({ type: "Point", coordinates: c }, project))
|
|
168
|
+
.filter(Boolean)
|
|
169
|
+
.join(" ");
|
|
170
|
+
case "LineString":
|
|
171
|
+
return linePath(geometry.coordinates, project);
|
|
172
|
+
case "MultiLineString":
|
|
173
|
+
return geometry.coordinates
|
|
174
|
+
.map((line) => (Array.isArray(line) ? linePath(line, project) : ""))
|
|
175
|
+
.filter(Boolean)
|
|
176
|
+
.join(" ");
|
|
177
|
+
case "Polygon":
|
|
178
|
+
return ringsPath(geometry.coordinates, project);
|
|
179
|
+
case "MultiPolygon":
|
|
180
|
+
return geometry.coordinates
|
|
181
|
+
.map((polygon) => (Array.isArray(polygon) ? ringsPath(polygon, project) : ""))
|
|
182
|
+
.filter(Boolean)
|
|
183
|
+
.join(" ");
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
function pointRadius(point, min, max, rMin, rMax) {
|
|
187
|
+
if (typeof point.r === "number" && Number.isFinite(point.r) && point.r >= 0) {
|
|
188
|
+
return Math.min(point.r, MAX_POINT_RADIUS);
|
|
189
|
+
}
|
|
190
|
+
return scaleNumber(point.value ?? 1, min, max, rMin, rMax);
|
|
191
|
+
}
|
|
192
|
+
function hexagonPoints(cx, cy, radius) {
|
|
193
|
+
return Array.from({ length: 6 }, (_, index) => {
|
|
194
|
+
const angle = (Math.PI / 3) * index - Math.PI / 6;
|
|
195
|
+
return `${cx + Math.cos(angle) * radius},${cy + Math.sin(angle) * radius}`;
|
|
196
|
+
}).join(" ");
|
|
197
|
+
}
|
|
198
|
+
function flowPath(source, target, project) {
|
|
199
|
+
const a = project(source);
|
|
200
|
+
const b = project(target);
|
|
201
|
+
// Arc quadratique : point de contrôle au milieu, décalé perpendiculairement.
|
|
202
|
+
const mx = (a.x + b.x) / 2;
|
|
203
|
+
const my = (a.y + b.y) / 2;
|
|
204
|
+
const dx = b.x - a.x;
|
|
205
|
+
const dy = b.y - a.y;
|
|
206
|
+
const cx = mx - dy * 0.18;
|
|
207
|
+
const cy = my + dx * 0.18;
|
|
208
|
+
return `M ${a.x} ${a.y} Q ${cx} ${cy} ${b.x} ${b.y}`;
|
|
209
|
+
}
|
|
210
|
+
function binPoints(points, cellSize) {
|
|
211
|
+
const hexHeight = cellSize * (Math.sqrt(3) / 2);
|
|
212
|
+
const bins = new Map();
|
|
213
|
+
for (const point of points) {
|
|
214
|
+
const q = Math.trunc(point.longitude / cellSize);
|
|
215
|
+
const r = Math.trunc(point.latitude / hexHeight);
|
|
216
|
+
const id = `${q}:${r}`;
|
|
217
|
+
const bin = bins.get(id);
|
|
218
|
+
const value = Number.isFinite(point.value) ? point.value : 1;
|
|
219
|
+
if (bin) {
|
|
220
|
+
bin.count += 1;
|
|
221
|
+
bin.value += value;
|
|
222
|
+
}
|
|
223
|
+
else {
|
|
224
|
+
bins.set(id, { id, q, r, center: { latitude: r * hexHeight, longitude: q * cellSize }, count: 1, value });
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
return [...bins.values()];
|
|
228
|
+
}
|
|
229
|
+
function clusterPoints(points, radius) {
|
|
230
|
+
const clusters = [];
|
|
231
|
+
for (const point of points) {
|
|
232
|
+
const cluster = clusters.find((item) => {
|
|
233
|
+
const dLat = item.latitude - point.latitude;
|
|
234
|
+
const dLon = item.longitude - point.longitude;
|
|
235
|
+
return Math.sqrt(dLat * dLat + dLon * dLon) <= radius;
|
|
236
|
+
});
|
|
237
|
+
const value = Number.isFinite(point.value) ? point.value : 1;
|
|
238
|
+
if (!cluster) {
|
|
239
|
+
clusters.push({
|
|
240
|
+
id: `cluster:${clusters.length}`,
|
|
241
|
+
latitude: point.latitude,
|
|
242
|
+
longitude: point.longitude,
|
|
243
|
+
count: 1,
|
|
244
|
+
value,
|
|
245
|
+
});
|
|
246
|
+
continue;
|
|
247
|
+
}
|
|
248
|
+
cluster.latitude = (cluster.latitude * cluster.count + point.latitude) / (cluster.count + 1);
|
|
249
|
+
cluster.longitude = (cluster.longitude * cluster.count + point.longitude) / (cluster.count + 1);
|
|
250
|
+
cluster.count += 1;
|
|
251
|
+
cluster.value += value;
|
|
252
|
+
}
|
|
253
|
+
return clusters;
|
|
254
|
+
}
|
|
255
|
+
function positiveOr(value, fallback) {
|
|
256
|
+
return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : fallback;
|
|
257
|
+
}
|
|
258
|
+
export const GeoMap = defineComponent({
|
|
259
|
+
name: "GeoMap",
|
|
260
|
+
props: {
|
|
261
|
+
layers: { type: Array, required: true },
|
|
262
|
+
width: { type: Number, default: 520 },
|
|
263
|
+
height: { type: Number, default: 320 },
|
|
264
|
+
projection: { type: String, default: "equirectangular" },
|
|
265
|
+
bounds: { type: Object, default: undefined },
|
|
266
|
+
label: { type: String, required: true },
|
|
267
|
+
class: { type: String, default: undefined },
|
|
268
|
+
},
|
|
269
|
+
setup(props, { attrs }) {
|
|
270
|
+
return () => {
|
|
271
|
+
const width = props.width ?? 520;
|
|
272
|
+
const height = props.height ?? 320;
|
|
273
|
+
const projection = props.projection ?? "equirectangular";
|
|
274
|
+
const layers = props.layers ?? [];
|
|
275
|
+
const label = props.label;
|
|
276
|
+
const mapBounds = validBounds(props.bounds) ?? fitBounds(layers);
|
|
277
|
+
const project = createProjector(mapBounds, projection, width, height);
|
|
278
|
+
const dataValueItems = [];
|
|
279
|
+
const rendered = layers.map((layer, layerIndex) => {
|
|
280
|
+
const key = `layer-${layerIndex}`;
|
|
281
|
+
switch (layer.type) {
|
|
282
|
+
case "geojson": {
|
|
283
|
+
const features = (layer.features ?? [])
|
|
284
|
+
.map((feature, index) => {
|
|
285
|
+
if (!isGeometry(feature.geometry))
|
|
286
|
+
return null;
|
|
287
|
+
const d = geometryPath(feature.geometry, project);
|
|
288
|
+
if (d === "")
|
|
289
|
+
return null;
|
|
290
|
+
return {
|
|
291
|
+
key: `${feature.id}-${index}`,
|
|
292
|
+
d,
|
|
293
|
+
tone: layer.tone ?? TONES[index % TONES.length],
|
|
294
|
+
line: feature.geometry.type === "LineString" || feature.geometry.type === "MultiLineString",
|
|
295
|
+
text: `${feature.label ?? feature.id}${feature.value === undefined ? "" : `: ${feature.value}`}`,
|
|
296
|
+
};
|
|
297
|
+
})
|
|
298
|
+
.filter((f) => f !== null);
|
|
299
|
+
dataValueItems.push(`${layer.label ?? "GeoJSON"}: ${features.length} entités`, ...features.map((f) => f.text));
|
|
300
|
+
return h("g", { key, class: "st-geoMap__layer st-geoMap__layer--geojson" }, features.map((feature) => h("path", {
|
|
301
|
+
key: feature.key,
|
|
302
|
+
class: classNames("st-geoMap__feature", `st-geoMap__feature--${feature.tone}`, feature.line && "st-geoMap__feature--line"),
|
|
303
|
+
d: feature.d,
|
|
304
|
+
"fill-rule": "evenodd",
|
|
305
|
+
})));
|
|
306
|
+
}
|
|
307
|
+
case "choropleth": {
|
|
308
|
+
const values = layer.values ?? {};
|
|
309
|
+
const finiteValues = (layer.features ?? [])
|
|
310
|
+
.map((feature) => values[feature.id])
|
|
311
|
+
.filter((v) => Number.isFinite(v));
|
|
312
|
+
const max = Math.max(1, ...finiteValues);
|
|
313
|
+
const tone = layer.tone ?? "category1";
|
|
314
|
+
const regions = (layer.features ?? [])
|
|
315
|
+
.map((feature, index) => {
|
|
316
|
+
if (!isGeometry(feature.geometry))
|
|
317
|
+
return null;
|
|
318
|
+
const d = geometryPath(feature.geometry, project);
|
|
319
|
+
if (d === "")
|
|
320
|
+
return null;
|
|
321
|
+
const value = values[feature.id];
|
|
322
|
+
const valid = Number.isFinite(value);
|
|
323
|
+
return {
|
|
324
|
+
key: `${feature.id}-${index}`,
|
|
325
|
+
d,
|
|
326
|
+
mix: valid ? mixPercent(scaleNumber(value, 0, max, 22, 90)) : null,
|
|
327
|
+
text: valid ? `${feature.label ?? feature.id}: ${value}` : `${feature.label ?? feature.id}`,
|
|
328
|
+
};
|
|
329
|
+
})
|
|
330
|
+
.filter((r) => r !== null);
|
|
331
|
+
dataValueItems.push(`${layer.label ?? "Choroplèthe"}: ${regions.length} régions`, ...regions.map((r) => r.text));
|
|
332
|
+
return h("g", { key, class: "st-geoMap__layer st-geoMap__layer--choropleth" }, regions.map((region) => region.mix === null
|
|
333
|
+
? h("path", {
|
|
334
|
+
key: region.key,
|
|
335
|
+
class: "st-geoMap__region st-geoMap__region--empty",
|
|
336
|
+
d: region.d,
|
|
337
|
+
"fill-rule": "evenodd",
|
|
338
|
+
})
|
|
339
|
+
: h("path", {
|
|
340
|
+
key: region.key,
|
|
341
|
+
class: classNames("st-geoMap__region", `st-geoMap__region--${tone}`),
|
|
342
|
+
d: region.d,
|
|
343
|
+
"fill-rule": "evenodd",
|
|
344
|
+
style: `--st-geoMap-mix: ${region.mix}%`,
|
|
345
|
+
})));
|
|
346
|
+
}
|
|
347
|
+
case "points": {
|
|
348
|
+
const valid = (layer.points ?? []).filter(isFiniteCoordinate);
|
|
349
|
+
const pointValues = valid.map((p) => (Number.isFinite(p.value) ? p.value : 1));
|
|
350
|
+
const min = Math.min(0, ...pointValues);
|
|
351
|
+
const max = Math.max(1, ...pointValues);
|
|
352
|
+
const rMin = positiveOr(layer.minRadius, 5);
|
|
353
|
+
const rMax = positiveOr(layer.maxRadius, 14);
|
|
354
|
+
const marks = valid.map((point, index) => {
|
|
355
|
+
const p = project(point);
|
|
356
|
+
return {
|
|
357
|
+
key: `${point.id ?? index}`,
|
|
358
|
+
cx: p.x,
|
|
359
|
+
cy: p.y,
|
|
360
|
+
r: pointRadius(point, min, max, rMin, rMax),
|
|
361
|
+
tone: point.tone ?? layer.tone ?? TONES[index % TONES.length],
|
|
362
|
+
text: `${point.label ?? point.id ?? `(${point.latitude}, ${point.longitude})`}${point.value === undefined ? "" : `: ${point.value}`}`,
|
|
363
|
+
};
|
|
364
|
+
});
|
|
365
|
+
dataValueItems.push(`${layer.label ?? "Points"}: ${marks.length}`, ...marks.map((m) => m.text));
|
|
366
|
+
return h("g", { key, class: "st-geoMap__layer st-geoMap__layer--points" }, marks.map((mark) => h("circle", {
|
|
367
|
+
key: mark.key,
|
|
368
|
+
class: classNames("st-geoMap__point", `st-geoMap__point--${mark.tone}`),
|
|
369
|
+
cx: mark.cx,
|
|
370
|
+
cy: mark.cy,
|
|
371
|
+
r: mark.r,
|
|
372
|
+
})));
|
|
373
|
+
}
|
|
374
|
+
case "density": {
|
|
375
|
+
const valid = (layer.points ?? []).filter(isFiniteCoordinate);
|
|
376
|
+
const weights = valid.map((p) => (Number.isFinite(p.value) ? p.value : 1));
|
|
377
|
+
const max = Math.max(1, ...weights);
|
|
378
|
+
const rMax = positiveOr(layer.maxRadius, 17);
|
|
379
|
+
const tone = layer.tone ?? "category3";
|
|
380
|
+
const marks = valid.map((point, index) => {
|
|
381
|
+
const p = project(point);
|
|
382
|
+
const weight = Number.isFinite(point.value) ? point.value : 1;
|
|
383
|
+
return {
|
|
384
|
+
key: `${point.id ?? index}`,
|
|
385
|
+
cx: p.x,
|
|
386
|
+
cy: p.y,
|
|
387
|
+
r: scaleNumber(weight, 0, max, 8, rMax),
|
|
388
|
+
mix: mixPercent(scaleNumber(weight, 0, max, 25, 60)),
|
|
389
|
+
text: `(${point.latitude}, ${point.longitude}): ${weight}`,
|
|
390
|
+
};
|
|
391
|
+
});
|
|
392
|
+
dataValueItems.push(`${layer.label ?? "Densité"}: ${marks.length} points`, ...marks.map((m) => m.text));
|
|
393
|
+
return h("g", { key, class: "st-geoMap__layer st-geoMap__layer--density" }, marks.map((mark) => h("circle", {
|
|
394
|
+
key: mark.key,
|
|
395
|
+
class: classNames("st-geoMap__density", `st-geoMap__density--${tone}`),
|
|
396
|
+
cx: mark.cx,
|
|
397
|
+
cy: mark.cy,
|
|
398
|
+
r: mark.r,
|
|
399
|
+
style: `--st-geoMap-mix: ${mark.mix}%`,
|
|
400
|
+
})));
|
|
401
|
+
}
|
|
402
|
+
case "flow": {
|
|
403
|
+
const valid = (layer.flows ?? []).filter((f) => isFiniteCoordinate(f.source) && isFiniteCoordinate(f.target));
|
|
404
|
+
const flowValues = valid.map((f) => (Number.isFinite(f.value) ? f.value : 1));
|
|
405
|
+
const max = Math.max(1, ...flowValues);
|
|
406
|
+
const tone = layer.tone ?? "category1";
|
|
407
|
+
const marks = valid.map((flow, index) => {
|
|
408
|
+
const value = Number.isFinite(flow.value) ? flow.value : 1;
|
|
409
|
+
return {
|
|
410
|
+
key: `${flow.id ?? index}`,
|
|
411
|
+
d: flowPath(flow.source, flow.target, project),
|
|
412
|
+
strokeWidth: scaleNumber(value, 0, max, 2, 9),
|
|
413
|
+
text: `${flow.label ?? `(${flow.source.latitude}, ${flow.source.longitude}) → (${flow.target.latitude}, ${flow.target.longitude})`}: ${value}`,
|
|
414
|
+
};
|
|
415
|
+
});
|
|
416
|
+
dataValueItems.push(`${layer.label ?? "Flux"}: ${marks.length}`, ...marks.map((m) => m.text));
|
|
417
|
+
return h("g", { key, class: "st-geoMap__layer st-geoMap__layer--flow" }, marks.map((mark) => h("path", {
|
|
418
|
+
key: mark.key,
|
|
419
|
+
class: classNames("st-geoMap__flow", `st-geoMap__flow--${tone}`),
|
|
420
|
+
d: mark.d,
|
|
421
|
+
"stroke-width": mark.strokeWidth,
|
|
422
|
+
})));
|
|
423
|
+
}
|
|
424
|
+
case "hexbin": {
|
|
425
|
+
const valid = (layer.points ?? []).filter(isFiniteCoordinate);
|
|
426
|
+
const cellSize = positiveOr(layer.cellSize, 1);
|
|
427
|
+
const bins = binPoints(valid, cellSize);
|
|
428
|
+
const max = Math.max(1, ...bins.map((bin) => bin.value));
|
|
429
|
+
const marks = bins.map((bin, index) => {
|
|
430
|
+
const p = project(bin.center);
|
|
431
|
+
return {
|
|
432
|
+
key: bin.id,
|
|
433
|
+
points: hexagonPoints(p.x, p.y, scaleNumber(bin.value, 0, max, 10, 22)),
|
|
434
|
+
mix: mixPercent(scaleNumber(bin.value, 0, max, 25, 85)),
|
|
435
|
+
tone: layer.tone ?? TONES[index % TONES.length],
|
|
436
|
+
text: `${bin.id}: ${bin.value}`,
|
|
437
|
+
};
|
|
438
|
+
});
|
|
439
|
+
dataValueItems.push(`${layer.label ?? "Hexbin"}: ${marks.length} alvéoles`, ...marks.map((m) => m.text));
|
|
440
|
+
return h("g", { key, class: "st-geoMap__layer st-geoMap__layer--hexbin" }, marks.map((mark) => h("polygon", {
|
|
441
|
+
key: mark.key,
|
|
442
|
+
class: classNames("st-geoMap__hexbin", `st-geoMap__hexbin--${mark.tone}`),
|
|
443
|
+
points: mark.points,
|
|
444
|
+
style: `--st-geoMap-mix: ${mark.mix}%`,
|
|
445
|
+
})));
|
|
446
|
+
}
|
|
447
|
+
case "cluster": {
|
|
448
|
+
const valid = (layer.points ?? []).filter(isFiniteCoordinate);
|
|
449
|
+
const radius = positiveOr(layer.radius, 1);
|
|
450
|
+
const clusters = clusterPoints(valid, radius);
|
|
451
|
+
const max = Math.max(1, ...clusters.map((cluster) => cluster.count));
|
|
452
|
+
const marks = clusters.map((cluster, index) => {
|
|
453
|
+
const p = project(cluster);
|
|
454
|
+
return {
|
|
455
|
+
key: cluster.id,
|
|
456
|
+
cx: p.x,
|
|
457
|
+
cy: p.y,
|
|
458
|
+
r: scaleNumber(cluster.count, 0, max, 8, 24),
|
|
459
|
+
tone: layer.tone ?? TONES[index % TONES.length],
|
|
460
|
+
text: `${cluster.id}: ${cluster.count}`,
|
|
461
|
+
};
|
|
462
|
+
});
|
|
463
|
+
dataValueItems.push(`${layer.label ?? "Clusters"}: ${marks.length}`, ...marks.map((m) => m.text));
|
|
464
|
+
return h("g", { key, class: "st-geoMap__layer st-geoMap__layer--cluster" }, marks.map((mark) => h("g", { key: mark.key, class: classNames("st-geoMap__cluster", `st-geoMap__cluster--${mark.tone}`) }, [
|
|
465
|
+
h("circle", { class: "st-geoMap__clusterDot", cx: mark.cx, cy: mark.cy, r: mark.r }),
|
|
466
|
+
h("circle", { class: "st-geoMap__clusterRing", cx: mark.cx, cy: mark.cy, r: mark.r + 3 }),
|
|
467
|
+
])));
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
});
|
|
471
|
+
return h("div", { ...attrs, class: classNames("st-geoMap", props.class) }, [
|
|
472
|
+
h("div", { class: "st-geoMap__visual", role: "img", "aria-label": label }, [
|
|
473
|
+
h("svg", {
|
|
474
|
+
viewBox: `0 0 ${width} ${height}`,
|
|
475
|
+
preserveAspectRatio: "xMidYMid meet",
|
|
476
|
+
width: "100%",
|
|
477
|
+
height: "100%",
|
|
478
|
+
focusable: "false",
|
|
479
|
+
"aria-hidden": "true",
|
|
480
|
+
}, [
|
|
481
|
+
h("rect", { class: "st-geoMap__frame", x: "0.5", y: "0.5", width: width - 1, height: height - 1, rx: "4" }),
|
|
482
|
+
...rendered,
|
|
483
|
+
]),
|
|
484
|
+
]),
|
|
485
|
+
chartDataList(label, dataValueItems),
|
|
486
|
+
]);
|
|
487
|
+
};
|
|
488
|
+
},
|
|
489
|
+
});
|
|
490
|
+
//# sourceMappingURL=GeoMap.js.map
|