@sqlrooms/deck 0.29.0-rc.2 → 0.29.0-rc.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/LICENSE.md +9 -0
- package/README.md +69 -0
- package/dist/DeckJsonMap.d.ts +4 -0
- package/dist/DeckJsonMap.d.ts.map +1 -0
- package/dist/{DeckMap.js → DeckJsonMap.js} +57 -44
- package/dist/DeckJsonMap.js.map +1 -0
- package/dist/DeckJsonMapSpec.d.ts +7617 -0
- package/dist/DeckJsonMapSpec.d.ts.map +1 -0
- package/dist/DeckJsonMapSpec.js +81 -0
- package/dist/DeckJsonMapSpec.js.map +1 -0
- package/dist/DeckMapConfigPopoverEditor.d.ts +8 -0
- package/dist/DeckMapConfigPopoverEditor.d.ts.map +1 -0
- package/dist/DeckMapConfigPopoverEditor.js +43 -0
- package/dist/DeckMapConfigPopoverEditor.js.map +1 -0
- package/dist/createDeckJsonSpecFromDatasets.d.ts +11 -0
- package/dist/createDeckJsonSpecFromDatasets.d.ts.map +1 -0
- package/dist/createDeckJsonSpecFromDatasets.js +85 -0
- package/dist/createDeckJsonSpecFromDatasets.js.map +1 -0
- package/dist/dashboard.d.ts +4 -0
- package/dist/dashboard.d.ts.map +1 -0
- package/dist/dashboard.js +465 -0
- package/dist/dashboard.js.map +1 -0
- package/dist/dashboardConfig.d.ts +51 -0
- package/dist/dashboardConfig.d.ts.map +1 -0
- package/dist/dashboardConfig.js +54 -0
- package/dist/dashboardConfig.js.map +1 -0
- package/dist/datasets/PreparedDatasetStore.d.ts +120 -0
- package/dist/datasets/PreparedDatasetStore.d.ts.map +1 -0
- package/dist/datasets/PreparedDatasetStore.js +262 -0
- package/dist/datasets/PreparedDatasetStore.js.map +1 -0
- package/dist/datasets/helpers.d.ts +57 -0
- package/dist/datasets/helpers.d.ts.map +1 -0
- package/dist/datasets/helpers.js +146 -0
- package/dist/datasets/helpers.js.map +1 -0
- package/dist/datasets/normalizeDatasets.d.ts +8 -2
- package/dist/datasets/normalizeDatasets.d.ts.map +1 -1
- package/dist/datasets/normalizeDatasets.js +33 -48
- package/dist/datasets/normalizeDatasets.js.map +1 -1
- package/dist/datasets/tableAdapter.d.ts +11 -0
- package/dist/datasets/tableAdapter.d.ts.map +1 -0
- package/dist/datasets/tableAdapter.js +11 -0
- package/dist/datasets/tableAdapter.js.map +1 -0
- package/dist/datasets/types.d.ts +40 -0
- package/dist/datasets/types.d.ts.map +1 -0
- package/dist/datasets/types.js +2 -0
- package/dist/datasets/types.js.map +1 -0
- package/dist/datasets/usePreparedDatasetStates.d.ts +16 -0
- package/dist/datasets/usePreparedDatasetStates.d.ts.map +1 -0
- package/dist/datasets/usePreparedDatasetStates.js +59 -0
- package/dist/datasets/usePreparedDatasetStates.js.map +1 -0
- package/dist/index.d.ts +10 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -2
- package/dist/index.js.map +1 -1
- package/dist/json/colorScaleFunction.d.ts +14 -0
- package/dist/json/colorScaleFunction.d.ts.map +1 -0
- package/dist/json/colorScaleFunction.js +34 -0
- package/dist/json/colorScaleFunction.js.map +1 -0
- package/dist/json/compileColorScale.d.ts +4 -27
- package/dist/json/compileColorScale.d.ts.map +1 -1
- package/dist/json/compileColorScale.js +16 -450
- package/dist/json/compileColorScale.js.map +1 -1
- package/dist/json/compileGeoArrowAccessor.d.ts +7 -0
- package/dist/json/compileGeoArrowAccessor.d.ts.map +1 -1
- package/dist/json/compileGeoArrowAccessor.js +68 -2
- package/dist/json/compileGeoArrowAccessor.js.map +1 -1
- package/dist/json/createDeckJsonConfiguration.d.ts.map +1 -1
- package/dist/json/createDeckJsonConfiguration.js +86 -33
- package/dist/json/createDeckJsonConfiguration.js.map +1 -1
- package/dist/json/defaultClasses.d.ts +12 -6
- package/dist/json/defaultClasses.d.ts.map +1 -1
- package/dist/json/defaultClasses.js +7 -1
- package/dist/json/defaultClasses.js.map +1 -1
- package/dist/json/extractColorScaleLegends.d.ts +1 -1
- package/dist/json/extractColorScaleLegends.d.ts.map +1 -1
- package/dist/json/extractColorScaleLegends.js +8 -6
- package/dist/json/extractColorScaleLegends.js.map +1 -1
- package/dist/json/layerCompatibility.d.ts +9 -3
- package/dist/json/layerCompatibility.d.ts.map +1 -1
- package/dist/json/layerCompatibility.js +135 -11
- package/dist/json/layerCompatibility.js.map +1 -1
- package/dist/json/layerConfig.d.ts +7 -3
- package/dist/json/layerConfig.d.ts.map +1 -1
- package/dist/json/layerConfig.js +19 -8
- package/dist/json/layerConfig.js.map +1 -1
- package/dist/prepare/detectGeometryColumn.d.ts.map +1 -1
- package/dist/prepare/detectGeometryColumn.js +1 -1
- package/dist/prepare/detectGeometryColumn.js.map +1 -1
- package/dist/prepare/geoarrow.d.ts.map +1 -1
- package/dist/prepare/geoarrow.js.map +1 -1
- package/dist/prepare/geometryDecoder.d.ts +1 -2
- package/dist/prepare/geometryDecoder.d.ts.map +1 -1
- package/dist/prepare/geometryDecoder.js.map +1 -1
- package/dist/prepare/prepareDeckDataset.d.ts +46 -0
- package/dist/prepare/prepareDeckDataset.d.ts.map +1 -1
- package/dist/prepare/prepareDeckDataset.js +46 -0
- package/dist/prepare/prepareDeckDataset.js.map +1 -1
- package/dist/prepare/toGeoJsonBinary.d.ts.map +1 -1
- package/dist/prepare/toGeoJsonBinary.js +3 -2
- package/dist/prepare/toGeoJsonBinary.js.map +1 -1
- package/dist/prepare/wkbDecoder.d.ts.map +1 -1
- package/dist/prepare/wkbDecoder.js +36 -9
- package/dist/prepare/wkbDecoder.js.map +1 -1
- package/dist/types.d.ts +31 -92
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +6 -1
- package/dist/types.js.map +1 -1
- package/dist/useDeckLayersReadyRedraw.d.ts +13 -0
- package/dist/useDeckLayersReadyRedraw.d.ts.map +1 -0
- package/dist/useDeckLayersReadyRedraw.js +35 -0
- package/dist/useDeckLayersReadyRedraw.js.map +1 -0
- package/package.json +16 -10
- package/dist/ColorScaleLegend.d.ts +0 -8
- package/dist/ColorScaleLegend.d.ts.map +0 -1
- package/dist/ColorScaleLegend.js +0 -11
- package/dist/ColorScaleLegend.js.map +0 -1
- package/dist/DeckMap.d.ts +0 -4
- package/dist/DeckMap.d.ts.map +0 -1
- package/dist/DeckMap.js.map +0 -1
- package/dist/datasets/usePreparedDeckDatasets.d.ts +0 -3
- package/dist/datasets/usePreparedDeckDatasets.d.ts.map +0 -1
- package/dist/datasets/usePreparedDeckDatasets.js +0 -61
- package/dist/datasets/usePreparedDeckDatasets.js.map +0 -1
|
@@ -0,0 +1,465 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { WebMercatorViewport } from '@deck.gl/core';
|
|
3
|
+
import { escapeId, getColValAsNumber, useStoreWithDuckDb, } from '@sqlrooms/duckdb';
|
|
4
|
+
import { column, sql, useMosaicClient, useStoreWithMosaicDashboard, } from '@sqlrooms/mosaic';
|
|
5
|
+
import { Button } from '@sqlrooms/ui';
|
|
6
|
+
import { FocusIcon, MapIcon } from 'lucide-react';
|
|
7
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
8
|
+
import { DeckMapConfigPopoverEditor } from './DeckMapConfigPopoverEditor';
|
|
9
|
+
import { DeckJsonMap } from './DeckJsonMap';
|
|
10
|
+
import { asDeckJsonMapConfig, createDeckMapDashboardDatasetQuery, createDeckMapDashboardDatasets, createDeckMapDashboardPanelConfig, DECK_MAP_DASHBOARD_PANEL_TYPE, resolveDeckMapDashboardDatasetSource, } from './dashboardConfig';
|
|
11
|
+
function DeckMapDashboardDatasetClient({ dashboard, dataset, datasetId, panel, onDatasetState, selectionName, }) {
|
|
12
|
+
const source = useMemo(() => resolveDeckMapDashboardDatasetSource({
|
|
13
|
+
dashboard,
|
|
14
|
+
panel,
|
|
15
|
+
dataset,
|
|
16
|
+
}), [dashboard, dataset, panel]);
|
|
17
|
+
const query = useCallback((filter) => source
|
|
18
|
+
? createDeckMapDashboardDatasetQuery(source, filter)
|
|
19
|
+
: createDeckMapDashboardDatasetQuery({ tableName: '__missing_dashboard_map_dataset__' }, filter), [source]);
|
|
20
|
+
const { data, error, isLoading, client } = useMosaicClient({
|
|
21
|
+
id: `${panel.id}:${datasetId}`,
|
|
22
|
+
selectionName,
|
|
23
|
+
query,
|
|
24
|
+
enabled: Boolean(source),
|
|
25
|
+
});
|
|
26
|
+
useEffect(() => {
|
|
27
|
+
onDatasetState(datasetId, {
|
|
28
|
+
arrowTable: data ?? undefined,
|
|
29
|
+
error,
|
|
30
|
+
isLoading,
|
|
31
|
+
client,
|
|
32
|
+
});
|
|
33
|
+
return () => {
|
|
34
|
+
onDatasetState(datasetId, undefined);
|
|
35
|
+
};
|
|
36
|
+
}, [client, data, datasetId, error, isLoading, onDatasetState]);
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
function createPointRadiusPredicate(interaction, coordinate) {
|
|
40
|
+
const [longitude, latitude] = coordinate;
|
|
41
|
+
const radiusMeters = interaction.radiusMeters ?? 500;
|
|
42
|
+
const metersPerDegree = 111_320;
|
|
43
|
+
const cosLatitude = Math.cos(latitude * (Math.PI / 180));
|
|
44
|
+
const radiusSquared = radiusMeters * radiusMeters;
|
|
45
|
+
return sql `(
|
|
46
|
+
pow((${column(interaction.longitudeColumn)} - ${longitude}) * ${cosLatitude * metersPerDegree}, 2) +
|
|
47
|
+
pow((${column(interaction.latitudeColumn)} - ${latitude}) * ${metersPerDegree}, 2)
|
|
48
|
+
) < ${radiusSquared}`;
|
|
49
|
+
}
|
|
50
|
+
function getCoordinate(info) {
|
|
51
|
+
return info.coordinate && info.coordinate.length >= 2
|
|
52
|
+
? [info.coordinate[0], info.coordinate[1]]
|
|
53
|
+
: undefined;
|
|
54
|
+
}
|
|
55
|
+
function isPickedMapFeature(info) {
|
|
56
|
+
return Boolean(info.picked || info.object || (info.index ?? -1) >= 0);
|
|
57
|
+
}
|
|
58
|
+
function createDeckMapBoundsQuery(options) {
|
|
59
|
+
const { source, fitToData } = options;
|
|
60
|
+
const baseSourceSql = source.sqlQuery
|
|
61
|
+
? `SELECT * FROM (${source.sqlQuery}) AS "__sqlrooms_dashboard_map_source"`
|
|
62
|
+
: `SELECT * FROM ${(source.tableName ?? '')
|
|
63
|
+
.split('.')
|
|
64
|
+
.map(escapeId)
|
|
65
|
+
.join('.')}`;
|
|
66
|
+
const longitudeColumn = escapeId(fitToData.longitudeColumn);
|
|
67
|
+
const latitudeColumn = escapeId(fitToData.latitudeColumn);
|
|
68
|
+
return `
|
|
69
|
+
SELECT
|
|
70
|
+
ST_XMin(extent) AS min_longitude,
|
|
71
|
+
ST_YMin(extent) AS min_latitude,
|
|
72
|
+
ST_XMax(extent) AS max_longitude,
|
|
73
|
+
ST_YMax(extent) AS max_latitude
|
|
74
|
+
FROM (
|
|
75
|
+
SELECT ST_Extent_Agg(ST_Point(${longitudeColumn}, ${latitudeColumn})) AS extent
|
|
76
|
+
FROM (${baseSourceSql}) AS "__sqlrooms_dashboard_map_points"
|
|
77
|
+
WHERE ${longitudeColumn} IS NOT NULL AND ${latitudeColumn} IS NOT NULL
|
|
78
|
+
) AS "__sqlrooms_dashboard_map_extent"
|
|
79
|
+
WHERE extent IS NOT NULL
|
|
80
|
+
`;
|
|
81
|
+
}
|
|
82
|
+
function readBoundsFromExtentResult(result) {
|
|
83
|
+
const minLongitude = getColValAsNumber(result, 'min_longitude');
|
|
84
|
+
const minLatitude = getColValAsNumber(result, 'min_latitude');
|
|
85
|
+
const maxLongitude = getColValAsNumber(result, 'max_longitude');
|
|
86
|
+
const maxLatitude = getColValAsNumber(result, 'max_latitude');
|
|
87
|
+
if (!Number.isFinite(minLongitude) ||
|
|
88
|
+
!Number.isFinite(minLatitude) ||
|
|
89
|
+
!Number.isFinite(maxLongitude) ||
|
|
90
|
+
!Number.isFinite(maxLatitude)) {
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
return [
|
|
94
|
+
[
|
|
95
|
+
minLongitude === maxLongitude ? minLongitude - 0.01 : minLongitude,
|
|
96
|
+
minLatitude === maxLatitude ? minLatitude - 0.01 : minLatitude,
|
|
97
|
+
],
|
|
98
|
+
[
|
|
99
|
+
minLongitude === maxLongitude ? maxLongitude + 0.01 : maxLongitude,
|
|
100
|
+
minLatitude === maxLatitude ? maxLatitude + 0.01 : maxLatitude,
|
|
101
|
+
],
|
|
102
|
+
];
|
|
103
|
+
}
|
|
104
|
+
function fitViewStateToBounds(options) {
|
|
105
|
+
const { bounds, width, height, padding = 40, maxZoom = 12 } = options;
|
|
106
|
+
const viewport = new WebMercatorViewport({
|
|
107
|
+
width: Math.max(width, 1),
|
|
108
|
+
height: Math.max(height, 1),
|
|
109
|
+
});
|
|
110
|
+
const fitted = viewport.fitBounds([
|
|
111
|
+
[bounds[0][0], bounds[0][1]],
|
|
112
|
+
[bounds[1][0], bounds[1][1]],
|
|
113
|
+
], { padding });
|
|
114
|
+
return {
|
|
115
|
+
longitude: fitted.longitude,
|
|
116
|
+
latitude: fitted.latitude,
|
|
117
|
+
zoom: Math.min(fitted.zoom, maxZoom),
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
const deckMapDashboardFitRequestTarget = new EventTarget();
|
|
121
|
+
function emitDeckMapDashboardFitRequest(panelId) {
|
|
122
|
+
deckMapDashboardFitRequestTarget.dispatchEvent(new CustomEvent('fit-view', { detail: { panelId } }));
|
|
123
|
+
}
|
|
124
|
+
function createInitialDeckMapDashboardFitState(key) {
|
|
125
|
+
return {
|
|
126
|
+
key,
|
|
127
|
+
viewState: undefined,
|
|
128
|
+
didAutoFit: false,
|
|
129
|
+
fitRequestVersion: 0,
|
|
130
|
+
handledFitRequestVersion: 0,
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
function DeckMapDashboardHeaderActions({ dashboardId, panel, }) {
|
|
134
|
+
const updatePanel = useStoreWithMosaicDashboard((state) => state.mosaicDashboard.updatePanel);
|
|
135
|
+
const mapConfig = asDeckJsonMapConfig(panel.config);
|
|
136
|
+
const canFitView = Boolean(mapConfig?.fitToData);
|
|
137
|
+
const handleConfigApply = useCallback((nextConfig) => {
|
|
138
|
+
updatePanel(dashboardId, panel.id, {
|
|
139
|
+
config: nextConfig,
|
|
140
|
+
});
|
|
141
|
+
}, [dashboardId, panel.id, updatePanel]);
|
|
142
|
+
return (_jsxs("div", { className: "flex items-center gap-0.5", children: [_jsx(DeckMapConfigPopoverEditor, { value: panel.config, onApply: handleConfigApply }), _jsx(Button, { variant: "ghost", size: "icon", className: "h-6 w-6", title: canFitView
|
|
143
|
+
? 'Fit map view to data'
|
|
144
|
+
: 'Fit view unavailable for this map', disabled: !canFitView, onClick: () => emitDeckMapDashboardFitRequest(panel.id), children: _jsx(FocusIcon, { className: "h-3.5 w-3.5" }) })] }));
|
|
145
|
+
}
|
|
146
|
+
function DeckMapDashboardRenderer({ dashboard, panel, selectionName, }) {
|
|
147
|
+
const mapConfig = asDeckJsonMapConfig(panel.config);
|
|
148
|
+
const getSelection = useStoreWithMosaicDashboard((state) => state.mosaic.getSelection);
|
|
149
|
+
const executeSql = useStoreWithDuckDb((state) => state.db.executeSql);
|
|
150
|
+
const selection = useMemo(() => getSelection(selectionName, 'crossfilter'), [getSelection, selectionName]);
|
|
151
|
+
const containerRef = useRef(null);
|
|
152
|
+
const [datasetStates, setDatasetStates] = useState({});
|
|
153
|
+
const [containerSize, setContainerSize] = useState({ width: 0, height: 0 });
|
|
154
|
+
const handleDatasetState = useCallback((datasetId, state) => {
|
|
155
|
+
setDatasetStates((current) => {
|
|
156
|
+
const next = { ...current };
|
|
157
|
+
if (state) {
|
|
158
|
+
next[datasetId] = state;
|
|
159
|
+
}
|
|
160
|
+
else {
|
|
161
|
+
delete next[datasetId];
|
|
162
|
+
}
|
|
163
|
+
return next;
|
|
164
|
+
});
|
|
165
|
+
}, []);
|
|
166
|
+
useEffect(() => {
|
|
167
|
+
const container = containerRef.current;
|
|
168
|
+
if (!container)
|
|
169
|
+
return;
|
|
170
|
+
const updateSize = () => {
|
|
171
|
+
setContainerSize({
|
|
172
|
+
width: container.clientWidth,
|
|
173
|
+
height: container.clientHeight,
|
|
174
|
+
});
|
|
175
|
+
};
|
|
176
|
+
updateSize();
|
|
177
|
+
const observer = new ResizeObserver(updateSize);
|
|
178
|
+
observer.observe(container);
|
|
179
|
+
return () => {
|
|
180
|
+
observer.disconnect();
|
|
181
|
+
};
|
|
182
|
+
}, []);
|
|
183
|
+
const deckDatasets = useMemo(() => {
|
|
184
|
+
if (!mapConfig) {
|
|
185
|
+
return {};
|
|
186
|
+
}
|
|
187
|
+
return createDeckMapDashboardDatasets(mapConfig, datasetStates);
|
|
188
|
+
}, [datasetStates, mapConfig]);
|
|
189
|
+
const fitToData = mapConfig?.fitToData ?? null;
|
|
190
|
+
const fitToDataSource = useMemo(() => fitToData
|
|
191
|
+
? resolveDeckMapDashboardDatasetSource({
|
|
192
|
+
dashboard,
|
|
193
|
+
panel,
|
|
194
|
+
dataset: mapConfig?.datasets[fitToData.dataset],
|
|
195
|
+
})
|
|
196
|
+
: undefined, [dashboard, fitToData, mapConfig?.datasets, panel]);
|
|
197
|
+
const fitToDataKey = useMemo(() => fitToData && fitToDataSource
|
|
198
|
+
? JSON.stringify({
|
|
199
|
+
source: fitToDataSource,
|
|
200
|
+
fitToData,
|
|
201
|
+
})
|
|
202
|
+
: null, [fitToData, fitToDataSource]);
|
|
203
|
+
const fitStateKey = useMemo(() => JSON.stringify({ panelId: panel.id, fitToDataKey }), [fitToDataKey, panel.id]);
|
|
204
|
+
const [fitState, setFitState] = useState(() => createInitialDeckMapDashboardFitState(fitStateKey));
|
|
205
|
+
const activeFitState = fitState.key === fitStateKey
|
|
206
|
+
? fitState
|
|
207
|
+
: createInitialDeckMapDashboardFitState(fitStateKey);
|
|
208
|
+
const { didAutoFit, fitRequestVersion, handledFitRequestVersion, viewState } = activeFitState;
|
|
209
|
+
useEffect(() => {
|
|
210
|
+
const handleFitRequest = (event) => {
|
|
211
|
+
const detail = event.detail;
|
|
212
|
+
if (detail?.panelId === panel.id) {
|
|
213
|
+
setFitState((current) => {
|
|
214
|
+
const scoped = current.key === fitStateKey
|
|
215
|
+
? current
|
|
216
|
+
: createInitialDeckMapDashboardFitState(fitStateKey);
|
|
217
|
+
return {
|
|
218
|
+
...scoped,
|
|
219
|
+
fitRequestVersion: scoped.fitRequestVersion + 1,
|
|
220
|
+
};
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
};
|
|
224
|
+
deckMapDashboardFitRequestTarget.addEventListener('fit-view', handleFitRequest);
|
|
225
|
+
return () => {
|
|
226
|
+
deckMapDashboardFitRequestTarget.removeEventListener('fit-view', handleFitRequest);
|
|
227
|
+
};
|
|
228
|
+
}, [fitStateKey, panel.id]);
|
|
229
|
+
useEffect(() => {
|
|
230
|
+
const hasManualFitRequest = fitRequestVersion > handledFitRequestVersion;
|
|
231
|
+
if (!fitToData ||
|
|
232
|
+
!fitToDataSource ||
|
|
233
|
+
containerSize.width <= 0 ||
|
|
234
|
+
containerSize.height <= 0 ||
|
|
235
|
+
(!hasManualFitRequest && didAutoFit)) {
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
let isCancelled = false;
|
|
239
|
+
const fitToDataBounds = async () => {
|
|
240
|
+
try {
|
|
241
|
+
const handle = await executeSql(createDeckMapBoundsQuery({
|
|
242
|
+
source: fitToDataSource,
|
|
243
|
+
fitToData,
|
|
244
|
+
}));
|
|
245
|
+
const result = handle ? await handle : null;
|
|
246
|
+
if (isCancelled || !result) {
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
const bounds = readBoundsFromExtentResult(result);
|
|
250
|
+
if (!bounds) {
|
|
251
|
+
setFitState((current) => {
|
|
252
|
+
const scoped = current.key === fitStateKey
|
|
253
|
+
? current
|
|
254
|
+
: createInitialDeckMapDashboardFitState(fitStateKey);
|
|
255
|
+
return {
|
|
256
|
+
...scoped,
|
|
257
|
+
didAutoFit: true,
|
|
258
|
+
handledFitRequestVersion: fitRequestVersion,
|
|
259
|
+
};
|
|
260
|
+
});
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
const nextViewState = fitViewStateToBounds({
|
|
264
|
+
bounds,
|
|
265
|
+
width: containerSize.width,
|
|
266
|
+
height: containerSize.height,
|
|
267
|
+
padding: fitToData.padding,
|
|
268
|
+
maxZoom: fitToData.maxZoom,
|
|
269
|
+
});
|
|
270
|
+
setFitState((current) => {
|
|
271
|
+
const scoped = current.key === fitStateKey
|
|
272
|
+
? current
|
|
273
|
+
: createInitialDeckMapDashboardFitState(fitStateKey);
|
|
274
|
+
return {
|
|
275
|
+
...scoped,
|
|
276
|
+
viewState: nextViewState,
|
|
277
|
+
didAutoFit: true,
|
|
278
|
+
handledFitRequestVersion: fitRequestVersion,
|
|
279
|
+
};
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
catch {
|
|
283
|
+
if (!isCancelled) {
|
|
284
|
+
setFitState((current) => {
|
|
285
|
+
const scoped = current.key === fitStateKey
|
|
286
|
+
? current
|
|
287
|
+
: createInitialDeckMapDashboardFitState(fitStateKey);
|
|
288
|
+
return {
|
|
289
|
+
...scoped,
|
|
290
|
+
didAutoFit: true,
|
|
291
|
+
handledFitRequestVersion: fitRequestVersion,
|
|
292
|
+
};
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
};
|
|
297
|
+
void fitToDataBounds();
|
|
298
|
+
return () => {
|
|
299
|
+
isCancelled = true;
|
|
300
|
+
};
|
|
301
|
+
}, [
|
|
302
|
+
containerSize.height,
|
|
303
|
+
containerSize.width,
|
|
304
|
+
didAutoFit,
|
|
305
|
+
executeSql,
|
|
306
|
+
fitRequestVersion,
|
|
307
|
+
fitStateKey,
|
|
308
|
+
fitToData,
|
|
309
|
+
fitToDataSource,
|
|
310
|
+
handledFitRequestVersion,
|
|
311
|
+
]);
|
|
312
|
+
const handleBrushEvent = useCallback((info) => {
|
|
313
|
+
const interaction = mapConfig?.interaction;
|
|
314
|
+
if (!interaction) {
|
|
315
|
+
return;
|
|
316
|
+
}
|
|
317
|
+
const client = datasetStates[interaction.dataset]?.client;
|
|
318
|
+
if (!client) {
|
|
319
|
+
return;
|
|
320
|
+
}
|
|
321
|
+
const coordinate = getCoordinate(info);
|
|
322
|
+
if (!coordinate) {
|
|
323
|
+
return;
|
|
324
|
+
}
|
|
325
|
+
if (interaction.event !== 'click' && !isPickedMapFeature(info)) {
|
|
326
|
+
selection.reset(selection.clauses.filter((clause) => clause.source === client));
|
|
327
|
+
return;
|
|
328
|
+
}
|
|
329
|
+
const radiusMeters = interaction.radiusMeters ?? 500;
|
|
330
|
+
selection.update({
|
|
331
|
+
source: client,
|
|
332
|
+
clients: new Set([client]),
|
|
333
|
+
value: [coordinate[0], coordinate[1], radiusMeters],
|
|
334
|
+
predicate: createPointRadiusPredicate(interaction, coordinate),
|
|
335
|
+
});
|
|
336
|
+
}, [datasetStates, mapConfig?.interaction, selection]);
|
|
337
|
+
const handleViewStateChange = useCallback(({ viewState: nextViewState, interactionState, }) => {
|
|
338
|
+
const hasUserInteraction = Boolean(interactionState &&
|
|
339
|
+
['isDragging', 'isPanning', 'isRotating', 'isZooming'].some((key) => Boolean(interactionState[key])));
|
|
340
|
+
if (fitToData &&
|
|
341
|
+
!didAutoFit &&
|
|
342
|
+
viewState === null &&
|
|
343
|
+
!hasUserInteraction) {
|
|
344
|
+
return;
|
|
345
|
+
}
|
|
346
|
+
setFitState((current) => {
|
|
347
|
+
const scoped = current.key === fitStateKey
|
|
348
|
+
? current
|
|
349
|
+
: createInitialDeckMapDashboardFitState(fitStateKey);
|
|
350
|
+
return {
|
|
351
|
+
...scoped,
|
|
352
|
+
viewState: nextViewState,
|
|
353
|
+
didAutoFit: hasUserInteraction || scoped.didAutoFit || !fitToData,
|
|
354
|
+
};
|
|
355
|
+
});
|
|
356
|
+
}, [didAutoFit, fitStateKey, fitToData, viewState]);
|
|
357
|
+
if (!mapConfig) {
|
|
358
|
+
return (_jsx("div", { className: "text-muted-foreground flex h-full items-center justify-center p-4 text-sm", children: "Invalid map panel config." }));
|
|
359
|
+
}
|
|
360
|
+
const datasetEntries = Object.entries(mapConfig.datasets);
|
|
361
|
+
if (!datasetEntries.length) {
|
|
362
|
+
return (_jsx("div", { className: "text-muted-foreground flex h-full items-center justify-center p-4 text-sm", children: "Map panels require at least one dataset." }));
|
|
363
|
+
}
|
|
364
|
+
const datasetErrors = Object.entries(datasetStates).filter(([, state]) => state.error);
|
|
365
|
+
const interactionEvent = mapConfig.interaction?.event ?? 'hover';
|
|
366
|
+
const interactionDeckProps = mapConfig.interaction
|
|
367
|
+
? interactionEvent === 'click'
|
|
368
|
+
? { onClick: handleBrushEvent }
|
|
369
|
+
: { onHover: handleBrushEvent }
|
|
370
|
+
: {};
|
|
371
|
+
return (_jsxs("div", { ref: containerRef, className: "relative h-full w-full", children: [datasetEntries.map(([datasetId, dataset]) => (_jsx(DeckMapDashboardDatasetClient, { dashboard: dashboard, dataset: dataset, datasetId: datasetId, panel: panel, onDatasetState: handleDatasetState, selectionName: selectionName }, datasetId))), datasetErrors.length ? (_jsx("div", { className: "bg-background/90 text-destructive absolute inset-x-4 top-4 z-10 rounded-md border p-3 text-sm shadow", children: datasetErrors.map(([datasetId, state]) => (_jsxs("div", { children: ["Failed to load dataset \"", datasetId, "\":", ' ', state.error?.message] }, datasetId))) })) : null, _jsx(DeckJsonMap, { className: "h-full w-full", spec: mapConfig.spec, datasets: deckDatasets, mapStyle: mapConfig.mapStyle, mapProps: mapConfig.mapProps, showLegends: mapConfig.showLegends, deckProps: {
|
|
372
|
+
controller: true,
|
|
373
|
+
...(viewState ? { viewState } : {}),
|
|
374
|
+
onViewStateChange: handleViewStateChange,
|
|
375
|
+
...interactionDeckProps,
|
|
376
|
+
} })] }));
|
|
377
|
+
}
|
|
378
|
+
export const deckMapDashboardPanelRenderer = {
|
|
379
|
+
component: DeckMapDashboardRenderer,
|
|
380
|
+
headerActions: DeckMapDashboardHeaderActions,
|
|
381
|
+
icon: MapIcon,
|
|
382
|
+
};
|
|
383
|
+
const LONGITUDE_COLUMN_NAMES = ['longitude', 'lon', 'lng', 'long', 'x'];
|
|
384
|
+
const LATITUDE_COLUMN_NAMES = ['latitude', 'lat', 'y'];
|
|
385
|
+
function findColumnByName(table, candidates) {
|
|
386
|
+
const candidateSet = new Set(candidates);
|
|
387
|
+
return table.columns.find((column) => candidateSet.has(column.name.toLowerCase()))?.name;
|
|
388
|
+
}
|
|
389
|
+
function findLongitudeLatitudeColumns(table) {
|
|
390
|
+
if (!table)
|
|
391
|
+
return null;
|
|
392
|
+
const longitudeColumn = findColumnByName(table, LONGITUDE_COLUMN_NAMES);
|
|
393
|
+
const latitudeColumn = findColumnByName(table, LATITUDE_COLUMN_NAMES);
|
|
394
|
+
return longitudeColumn && latitudeColumn
|
|
395
|
+
? { longitudeColumn, latitudeColumn }
|
|
396
|
+
: null;
|
|
397
|
+
}
|
|
398
|
+
function quoteSqlIdentifier(identifier) {
|
|
399
|
+
return `"${identifier.replace(/"/g, '""')}"`;
|
|
400
|
+
}
|
|
401
|
+
function quoteTableReference(table) {
|
|
402
|
+
const qualifiedName = table.table;
|
|
403
|
+
return [qualifiedName.database, qualifiedName.schema, qualifiedName.table]
|
|
404
|
+
.filter((part) => Boolean(part))
|
|
405
|
+
.map(quoteSqlIdentifier)
|
|
406
|
+
.join('.');
|
|
407
|
+
}
|
|
408
|
+
function createDeckMapPanelForTable(table) {
|
|
409
|
+
const coordinates = findLongitudeLatitudeColumns(table);
|
|
410
|
+
if (!coordinates)
|
|
411
|
+
return undefined;
|
|
412
|
+
const { longitudeColumn, latitudeColumn } = coordinates;
|
|
413
|
+
const datasetId = table.tableName;
|
|
414
|
+
const geometryColumn = '__sqlrooms_geom';
|
|
415
|
+
const quotedLongitude = quoteSqlIdentifier(longitudeColumn);
|
|
416
|
+
const quotedLatitude = quoteSqlIdentifier(latitudeColumn);
|
|
417
|
+
return createDeckMapDashboardPanelConfig({
|
|
418
|
+
title: `${table.tableName} map`,
|
|
419
|
+
source: { tableName: table.tableName },
|
|
420
|
+
spec: {
|
|
421
|
+
initialViewState: { longitude: 0, latitude: 20, zoom: 1.5 },
|
|
422
|
+
layers: [
|
|
423
|
+
{
|
|
424
|
+
'@@type': 'GeoArrowScatterplotLayer',
|
|
425
|
+
id: datasetId,
|
|
426
|
+
_sqlroomsBinding: { dataset: datasetId },
|
|
427
|
+
filled: true,
|
|
428
|
+
stroked: false,
|
|
429
|
+
pickable: true,
|
|
430
|
+
radiusUnits: 'pixels',
|
|
431
|
+
getRadius: 4,
|
|
432
|
+
getFillColor: [56, 189, 248, 180],
|
|
433
|
+
},
|
|
434
|
+
],
|
|
435
|
+
},
|
|
436
|
+
datasets: {
|
|
437
|
+
[datasetId]: {
|
|
438
|
+
source: {
|
|
439
|
+
sqlQuery: [
|
|
440
|
+
`SELECT *, ST_AsWKB(ST_Point(${quotedLongitude}, ${quotedLatitude})) AS ${quoteSqlIdentifier(geometryColumn)}`,
|
|
441
|
+
`FROM ${quoteTableReference(table)}`,
|
|
442
|
+
`WHERE ${quotedLongitude} IS NOT NULL AND ${quotedLatitude} IS NOT NULL`,
|
|
443
|
+
].join(' '),
|
|
444
|
+
},
|
|
445
|
+
geometryColumn,
|
|
446
|
+
geometryEncodingHint: 'wkb',
|
|
447
|
+
},
|
|
448
|
+
},
|
|
449
|
+
fitToData: {
|
|
450
|
+
dataset: datasetId,
|
|
451
|
+
longitudeColumn,
|
|
452
|
+
latitudeColumn,
|
|
453
|
+
padding: 40,
|
|
454
|
+
maxZoom: 12,
|
|
455
|
+
},
|
|
456
|
+
});
|
|
457
|
+
}
|
|
458
|
+
export const deckMapDashboardAddPanelAction = {
|
|
459
|
+
type: DECK_MAP_DASHBOARD_PANEL_TYPE,
|
|
460
|
+
label: 'Map',
|
|
461
|
+
icon: MapIcon,
|
|
462
|
+
isEnabled: ({ selectedTable }) => Boolean(findLongitudeLatitudeColumns(selectedTable)),
|
|
463
|
+
createPanel: ({ selectedTable }) => selectedTable ? createDeckMapPanelForTable(selectedTable) : undefined,
|
|
464
|
+
};
|
|
465
|
+
//# sourceMappingURL=dashboard.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dashboard.js","sourceRoot":"","sources":["../src/dashboard.tsx"],"names":[],"mappings":"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","sourcesContent":["import {WebMercatorViewport} from '@deck.gl/core';\nimport type {ComponentProps} from 'react';\nimport type DeckGLReact from '@deck.gl/react';\n\ntype DeckProps = ComponentProps<typeof DeckGLReact>;\nimport {\n escapeId,\n getColValAsNumber,\n useStoreWithDuckDb,\n type DataTable,\n} from '@sqlrooms/duckdb';\nimport {\n column,\n type MosaicDashboardEntryType,\n type MosaicDashboardPanelConfigType,\n type MosaicDashboardPanelRenderer,\n type MosaicDashboardPanelRendererProps,\n sql,\n useMosaicClient,\n useStoreWithMosaicDashboard,\n} from '@sqlrooms/mosaic';\nimport {Button} from '@sqlrooms/ui';\nimport type {MosaicClient} from '@uwdata/mosaic-core';\nimport type {Selection} from '@uwdata/mosaic-core';\nimport type {Table as ArrowTable} from 'apache-arrow';\nimport {FocusIcon, MapIcon} from 'lucide-react';\nimport {useCallback, useEffect, useMemo, useRef, useState} from 'react';\nimport {DeckMapConfigPopoverEditor} from './DeckMapConfigPopoverEditor';\nimport {DeckJsonMap} from './DeckJsonMap';\nimport type {DeckJsonMapProps} from './types';\nimport {\n asDeckJsonMapConfig,\n createDeckMapDashboardDatasetQuery,\n createDeckMapDashboardDatasets,\n createDeckMapDashboardPanelConfig,\n DECK_MAP_DASHBOARD_PANEL_TYPE,\n resolveDeckMapDashboardDatasetSource,\n type DeckMapDashboardFitToDataConfig,\n type DeckMapDashboardDatasetClientState,\n type DeckMapDashboardDatasetConfig,\n type DeckMapDashboardInteractionConfig,\n} from './dashboardConfig';\n\nfunction DeckMapDashboardDatasetClient({\n dashboard,\n dataset,\n datasetId,\n panel,\n onDatasetState,\n selectionName,\n}: {\n dashboard: MosaicDashboardEntryType;\n dataset: DeckMapDashboardDatasetConfig;\n datasetId: string;\n panel: MosaicDashboardPanelConfigType;\n onDatasetState: (\n datasetId: string,\n state: DeckMapDashboardDatasetClientState | undefined,\n ) => void;\n selectionName: string;\n}) {\n const source = useMemo(\n () =>\n resolveDeckMapDashboardDatasetSource({\n dashboard,\n panel,\n dataset,\n }),\n [dashboard, dataset, panel],\n );\n const query = useCallback(\n (filter: unknown) =>\n source\n ? createDeckMapDashboardDatasetQuery(source, filter)\n : createDeckMapDashboardDatasetQuery(\n {tableName: '__missing_dashboard_map_dataset__'},\n filter,\n ),\n [source],\n );\n const {data, error, isLoading, client} = useMosaicClient({\n id: `${panel.id}:${datasetId}`,\n selectionName,\n query,\n enabled: Boolean(source),\n });\n\n useEffect(() => {\n onDatasetState(datasetId, {\n arrowTable: data ?? undefined,\n error,\n isLoading,\n client,\n });\n\n return () => {\n onDatasetState(datasetId, undefined);\n };\n }, [client, data, datasetId, error, isLoading, onDatasetState]);\n\n return null;\n}\n\nfunction createPointRadiusPredicate(\n interaction: DeckMapDashboardInteractionConfig,\n coordinate: [number, number],\n) {\n const [longitude, latitude] = coordinate;\n const radiusMeters = interaction.radiusMeters ?? 500;\n const metersPerDegree = 111_320;\n const cosLatitude = Math.cos(latitude * (Math.PI / 180));\n const radiusSquared = radiusMeters * radiusMeters;\n\n return sql`(\n pow((${column(interaction.longitudeColumn)} - ${longitude}) * ${\n cosLatitude * metersPerDegree\n }, 2) +\n pow((${column(interaction.latitudeColumn)} - ${latitude}) * ${\n metersPerDegree\n }, 2)\n ) < ${radiusSquared}`;\n}\n\ntype DeckMapInteractionEvent = {\n coordinate?: number[] | null;\n object?: unknown;\n index?: number;\n picked?: boolean;\n};\n\nfunction getCoordinate(\n info: DeckMapInteractionEvent,\n): [number, number] | undefined {\n return info.coordinate && info.coordinate.length >= 2\n ? [info.coordinate[0]!, info.coordinate[1]!]\n : undefined;\n}\n\nfunction isPickedMapFeature(info: DeckMapInteractionEvent) {\n return Boolean(info.picked || info.object || (info.index ?? -1) >= 0);\n}\n\nfunction createDeckMapBoundsQuery(options: {\n source: {tableName?: string; sqlQuery?: string};\n fitToData: DeckMapDashboardFitToDataConfig;\n}) {\n const {source, fitToData} = options;\n const baseSourceSql = source.sqlQuery\n ? `SELECT * FROM (${source.sqlQuery}) AS \"__sqlrooms_dashboard_map_source\"`\n : `SELECT * FROM ${(source.tableName ?? '')\n .split('.')\n .map(escapeId)\n .join('.')}`;\n const longitudeColumn = escapeId(fitToData.longitudeColumn);\n const latitudeColumn = escapeId(fitToData.latitudeColumn);\n\n return `\n SELECT\n ST_XMin(extent) AS min_longitude,\n ST_YMin(extent) AS min_latitude,\n ST_XMax(extent) AS max_longitude,\n ST_YMax(extent) AS max_latitude\n FROM (\n SELECT ST_Extent_Agg(ST_Point(${longitudeColumn}, ${latitudeColumn})) AS extent\n FROM (${baseSourceSql}) AS \"__sqlrooms_dashboard_map_points\"\n WHERE ${longitudeColumn} IS NOT NULL AND ${latitudeColumn} IS NOT NULL\n ) AS \"__sqlrooms_dashboard_map_extent\"\n WHERE extent IS NOT NULL\n `;\n}\n\nfunction readBoundsFromExtentResult(result: ArrowTable) {\n const minLongitude = getColValAsNumber(result, 'min_longitude');\n const minLatitude = getColValAsNumber(result, 'min_latitude');\n const maxLongitude = getColValAsNumber(result, 'max_longitude');\n const maxLatitude = getColValAsNumber(result, 'max_latitude');\n if (\n !Number.isFinite(minLongitude) ||\n !Number.isFinite(minLatitude) ||\n !Number.isFinite(maxLongitude) ||\n !Number.isFinite(maxLatitude)\n ) {\n return null;\n }\n\n return [\n [\n minLongitude === maxLongitude ? minLongitude - 0.01 : minLongitude,\n minLatitude === maxLatitude ? minLatitude - 0.01 : minLatitude,\n ],\n [\n minLongitude === maxLongitude ? maxLongitude + 0.01 : maxLongitude,\n minLatitude === maxLatitude ? maxLatitude + 0.01 : maxLatitude,\n ],\n ] as const;\n}\n\nfunction fitViewStateToBounds(options: {\n bounds: readonly [readonly [number, number], readonly [number, number]];\n width: number;\n height: number;\n padding?: number;\n maxZoom?: number;\n}) {\n const {bounds, width, height, padding = 40, maxZoom = 12} = options;\n const viewport = new WebMercatorViewport({\n width: Math.max(width, 1),\n height: Math.max(height, 1),\n });\n const fitted = viewport.fitBounds(\n [\n [bounds[0][0], bounds[0][1]],\n [bounds[1][0], bounds[1][1]],\n ],\n {padding},\n ) as WebMercatorViewport & {longitude: number; latitude: number; zoom: number};\n\n return {\n longitude: fitted.longitude,\n latitude: fitted.latitude,\n zoom: Math.min(fitted.zoom, maxZoom),\n };\n}\n\nconst deckMapDashboardFitRequestTarget = new EventTarget();\n\nfunction emitDeckMapDashboardFitRequest(panelId: string) {\n deckMapDashboardFitRequestTarget.dispatchEvent(\n new CustomEvent('fit-view', {detail: {panelId}}),\n );\n}\n\ntype DeckMapDashboardFitState = {\n key: string;\n viewState: DeckProps['viewState'];\n didAutoFit: boolean;\n fitRequestVersion: number;\n handledFitRequestVersion: number;\n};\n\nfunction createInitialDeckMapDashboardFitState(\n key: string,\n): DeckMapDashboardFitState {\n return {\n key,\n viewState: undefined,\n didAutoFit: false,\n fitRequestVersion: 0,\n handledFitRequestVersion: 0,\n };\n}\n\nfunction DeckMapDashboardHeaderActions({\n dashboardId,\n panel,\n}: MosaicDashboardPanelRendererProps) {\n const updatePanel = useStoreWithMosaicDashboard(\n (state) => state.mosaicDashboard.updatePanel,\n );\n const mapConfig = asDeckJsonMapConfig(panel.config);\n const canFitView = Boolean(mapConfig?.fitToData);\n const handleConfigApply = useCallback(\n (nextConfig: Record<string, unknown>) => {\n updatePanel(dashboardId, panel.id, {\n config: nextConfig,\n });\n },\n [dashboardId, panel.id, updatePanel],\n );\n\n return (\n <div className=\"flex items-center gap-0.5\">\n <DeckMapConfigPopoverEditor\n value={panel.config}\n onApply={handleConfigApply}\n />\n <Button\n variant=\"ghost\"\n size=\"icon\"\n className=\"h-6 w-6\"\n title={\n canFitView\n ? 'Fit map view to data'\n : 'Fit view unavailable for this map'\n }\n disabled={!canFitView}\n onClick={() => emitDeckMapDashboardFitRequest(panel.id)}\n >\n <FocusIcon className=\"h-3.5 w-3.5\" />\n </Button>\n </div>\n );\n}\n\nfunction DeckMapDashboardRenderer({\n dashboard,\n panel,\n selectionName,\n}: MosaicDashboardPanelRendererProps) {\n const mapConfig = asDeckJsonMapConfig(panel.config);\n const getSelection = useStoreWithMosaicDashboard(\n (state) => state.mosaic.getSelection,\n );\n const executeSql = useStoreWithDuckDb((state) => state.db.executeSql);\n const selection = useMemo<Selection>(\n () => getSelection(selectionName, 'crossfilter'),\n [getSelection, selectionName],\n );\n const containerRef = useRef<HTMLDivElement | null>(null);\n const [datasetStates, setDatasetStates] = useState<\n Record<string, DeckMapDashboardDatasetClientState>\n >({});\n const [containerSize, setContainerSize] = useState({width: 0, height: 0});\n\n const handleDatasetState = useCallback(\n (\n datasetId: string,\n state: DeckMapDashboardDatasetClientState | undefined,\n ) => {\n setDatasetStates((current) => {\n const next = {...current};\n if (state) {\n next[datasetId] = state;\n } else {\n delete next[datasetId];\n }\n return next;\n });\n },\n [],\n );\n\n useEffect(() => {\n const container = containerRef.current;\n if (!container) return;\n\n const updateSize = () => {\n setContainerSize({\n width: container.clientWidth,\n height: container.clientHeight,\n });\n };\n\n updateSize();\n const observer = new ResizeObserver(updateSize);\n observer.observe(container);\n\n return () => {\n observer.disconnect();\n };\n }, []);\n\n const deckDatasets = useMemo<DeckJsonMapProps['datasets']>(() => {\n if (!mapConfig) {\n return {};\n }\n\n return createDeckMapDashboardDatasets(mapConfig, datasetStates);\n }, [datasetStates, mapConfig]);\n const fitToData = mapConfig?.fitToData ?? null;\n const fitToDataSource = useMemo(\n () =>\n fitToData\n ? resolveDeckMapDashboardDatasetSource({\n dashboard,\n panel,\n dataset: mapConfig?.datasets[fitToData.dataset],\n })\n : undefined,\n [dashboard, fitToData, mapConfig?.datasets, panel],\n );\n const fitToDataKey = useMemo(\n () =>\n fitToData && fitToDataSource\n ? JSON.stringify({\n source: fitToDataSource,\n fitToData,\n })\n : null,\n [fitToData, fitToDataSource],\n );\n const fitStateKey = useMemo(\n () => JSON.stringify({panelId: panel.id, fitToDataKey}),\n [fitToDataKey, panel.id],\n );\n const [fitState, setFitState] = useState<DeckMapDashboardFitState>(() =>\n createInitialDeckMapDashboardFitState(fitStateKey),\n );\n const activeFitState =\n fitState.key === fitStateKey\n ? fitState\n : createInitialDeckMapDashboardFitState(fitStateKey);\n const {didAutoFit, fitRequestVersion, handledFitRequestVersion, viewState} =\n activeFitState;\n\n useEffect(() => {\n const handleFitRequest = (event: Event) => {\n const detail = (event as CustomEvent<{panelId?: string}>).detail;\n if (detail?.panelId === panel.id) {\n setFitState((current) => {\n const scoped =\n current.key === fitStateKey\n ? current\n : createInitialDeckMapDashboardFitState(fitStateKey);\n return {\n ...scoped,\n fitRequestVersion: scoped.fitRequestVersion + 1,\n };\n });\n }\n };\n\n deckMapDashboardFitRequestTarget.addEventListener(\n 'fit-view',\n handleFitRequest,\n );\n return () => {\n deckMapDashboardFitRequestTarget.removeEventListener(\n 'fit-view',\n handleFitRequest,\n );\n };\n }, [fitStateKey, panel.id]);\n\n useEffect(() => {\n const hasManualFitRequest = fitRequestVersion > handledFitRequestVersion;\n if (\n !fitToData ||\n !fitToDataSource ||\n containerSize.width <= 0 ||\n containerSize.height <= 0 ||\n (!hasManualFitRequest && didAutoFit)\n ) {\n return;\n }\n\n let isCancelled = false;\n\n const fitToDataBounds = async () => {\n try {\n const handle = await executeSql(\n createDeckMapBoundsQuery({\n source: fitToDataSource,\n fitToData,\n }),\n );\n const result = handle ? await handle : null;\n if (isCancelled || !result) {\n return;\n }\n\n const bounds = readBoundsFromExtentResult(result);\n if (!bounds) {\n setFitState((current) => {\n const scoped =\n current.key === fitStateKey\n ? current\n : createInitialDeckMapDashboardFitState(fitStateKey);\n return {\n ...scoped,\n didAutoFit: true,\n handledFitRequestVersion: fitRequestVersion,\n };\n });\n return;\n }\n\n const nextViewState = fitViewStateToBounds({\n bounds,\n width: containerSize.width,\n height: containerSize.height,\n padding: fitToData.padding,\n maxZoom: fitToData.maxZoom,\n });\n setFitState((current) => {\n const scoped =\n current.key === fitStateKey\n ? current\n : createInitialDeckMapDashboardFitState(fitStateKey);\n return {\n ...scoped,\n viewState: nextViewState,\n didAutoFit: true,\n handledFitRequestVersion: fitRequestVersion,\n };\n });\n } catch {\n if (!isCancelled) {\n setFitState((current) => {\n const scoped =\n current.key === fitStateKey\n ? current\n : createInitialDeckMapDashboardFitState(fitStateKey);\n return {\n ...scoped,\n didAutoFit: true,\n handledFitRequestVersion: fitRequestVersion,\n };\n });\n }\n }\n };\n\n void fitToDataBounds();\n\n return () => {\n isCancelled = true;\n };\n }, [\n containerSize.height,\n containerSize.width,\n didAutoFit,\n executeSql,\n fitRequestVersion,\n fitStateKey,\n fitToData,\n fitToDataSource,\n handledFitRequestVersion,\n ]);\n\n const handleBrushEvent = useCallback(\n (info: DeckMapInteractionEvent) => {\n const interaction = mapConfig?.interaction;\n if (!interaction) {\n return;\n }\n\n const client = datasetStates[interaction.dataset]?.client as\n | MosaicClient\n | null\n | undefined;\n if (!client) {\n return;\n }\n const coordinate = getCoordinate(info);\n if (!coordinate) {\n return;\n }\n if (interaction.event !== 'click' && !isPickedMapFeature(info)) {\n selection.reset(\n selection.clauses.filter((clause) => clause.source === client),\n );\n return;\n }\n\n const radiusMeters = interaction.radiusMeters ?? 500;\n selection.update({\n source: client,\n clients: new Set([client]),\n value: [coordinate[0], coordinate[1], radiusMeters],\n predicate: createPointRadiusPredicate(interaction, coordinate),\n });\n },\n [datasetStates, mapConfig?.interaction, selection],\n );\n\n const handleViewStateChange = useCallback(\n ({\n viewState: nextViewState,\n interactionState,\n }: {\n viewState: any;\n interactionState?: any;\n }) => {\n const hasUserInteraction = Boolean(\n interactionState &&\n ['isDragging', 'isPanning', 'isRotating', 'isZooming'].some((key) =>\n Boolean(interactionState[key]),\n ),\n );\n if (\n fitToData &&\n !didAutoFit &&\n viewState === null &&\n !hasUserInteraction\n ) {\n return;\n }\n\n setFitState((current) => {\n const scoped =\n current.key === fitStateKey\n ? current\n : createInitialDeckMapDashboardFitState(fitStateKey);\n return {\n ...scoped,\n viewState: nextViewState,\n didAutoFit: hasUserInteraction || scoped.didAutoFit || !fitToData,\n };\n });\n },\n [didAutoFit, fitStateKey, fitToData, viewState],\n );\n\n if (!mapConfig) {\n return (\n <div className=\"text-muted-foreground flex h-full items-center justify-center p-4 text-sm\">\n Invalid map panel config.\n </div>\n );\n }\n\n const datasetEntries = Object.entries(mapConfig.datasets);\n if (!datasetEntries.length) {\n return (\n <div className=\"text-muted-foreground flex h-full items-center justify-center p-4 text-sm\">\n Map panels require at least one dataset.\n </div>\n );\n }\n const datasetErrors = Object.entries(datasetStates).filter(\n ([, state]) => state.error,\n );\n\n const interactionEvent = mapConfig.interaction?.event ?? 'hover';\n const interactionDeckProps: DeckJsonMapProps['deckProps'] =\n mapConfig.interaction\n ? interactionEvent === 'click'\n ? {onClick: handleBrushEvent}\n : {onHover: handleBrushEvent}\n : {};\n\n return (\n <div ref={containerRef} className=\"relative h-full w-full\">\n {datasetEntries.map(([datasetId, dataset]) => (\n <DeckMapDashboardDatasetClient\n key={datasetId}\n dashboard={dashboard}\n dataset={dataset}\n datasetId={datasetId}\n panel={panel}\n onDatasetState={handleDatasetState}\n selectionName={selectionName}\n />\n ))}\n {datasetErrors.length ? (\n <div className=\"bg-background/90 text-destructive absolute inset-x-4 top-4 z-10 rounded-md border p-3 text-sm shadow\">\n {datasetErrors.map(([datasetId, state]) => (\n <div key={datasetId}>\n Failed to load dataset "{datasetId}":{' '}\n {state.error?.message}\n </div>\n ))}\n </div>\n ) : null}\n <DeckJsonMap\n className=\"h-full w-full\"\n spec={mapConfig.spec}\n datasets={deckDatasets}\n mapStyle={mapConfig.mapStyle}\n mapProps={mapConfig.mapProps}\n showLegends={mapConfig.showLegends}\n deckProps={{\n controller: true,\n ...(viewState ? {viewState} : {}),\n onViewStateChange: handleViewStateChange,\n ...interactionDeckProps,\n }}\n />\n </div>\n );\n}\n\nexport const deckMapDashboardPanelRenderer: MosaicDashboardPanelRenderer = {\n component: DeckMapDashboardRenderer,\n headerActions: DeckMapDashboardHeaderActions,\n icon: MapIcon,\n};\n\nconst LONGITUDE_COLUMN_NAMES = ['longitude', 'lon', 'lng', 'long', 'x'];\nconst LATITUDE_COLUMN_NAMES = ['latitude', 'lat', 'y'];\n\nfunction findColumnByName(\n table: DataTable,\n candidates: string[],\n) {\n const candidateSet = new Set(candidates);\n return table.columns.find((column) =>\n candidateSet.has(column.name.toLowerCase()),\n )?.name;\n}\n\nfunction findLongitudeLatitudeColumns(\n table?: DataTable,\n) {\n if (!table) return null;\n const longitudeColumn = findColumnByName(table, LONGITUDE_COLUMN_NAMES);\n const latitudeColumn = findColumnByName(table, LATITUDE_COLUMN_NAMES);\n return longitudeColumn && latitudeColumn\n ? {longitudeColumn, latitudeColumn}\n : null;\n}\n\nfunction quoteSqlIdentifier(identifier: string) {\n return `\"${identifier.replace(/\"/g, '\"\"')}\"`;\n}\n\nfunction quoteTableReference(table: DataTable) {\n const qualifiedName = table.table;\n return [qualifiedName.database, qualifiedName.schema, qualifiedName.table]\n .filter((part): part is string => Boolean(part))\n .map(quoteSqlIdentifier)\n .join('.');\n}\n\nfunction createDeckMapPanelForTable(\n table: DataTable,\n) {\n const coordinates = findLongitudeLatitudeColumns(table);\n if (!coordinates) return undefined;\n\n const {longitudeColumn, latitudeColumn} = coordinates;\n const datasetId = table.tableName;\n const geometryColumn = '__sqlrooms_geom';\n const quotedLongitude = quoteSqlIdentifier(longitudeColumn);\n const quotedLatitude = quoteSqlIdentifier(latitudeColumn);\n\n return createDeckMapDashboardPanelConfig({\n title: `${table.tableName} map`,\n source: {tableName: table.tableName},\n spec: {\n initialViewState: {longitude: 0, latitude: 20, zoom: 1.5},\n layers: [\n {\n '@@type': 'GeoArrowScatterplotLayer',\n id: datasetId,\n _sqlroomsBinding: {dataset: datasetId},\n filled: true,\n stroked: false,\n pickable: true,\n radiusUnits: 'pixels',\n getRadius: 4,\n getFillColor: [56, 189, 248, 180],\n },\n ],\n },\n datasets: {\n [datasetId]: {\n source: {\n sqlQuery: [\n `SELECT *, ST_AsWKB(ST_Point(${quotedLongitude}, ${quotedLatitude})) AS ${quoteSqlIdentifier(geometryColumn)}`,\n `FROM ${quoteTableReference(table)}`,\n `WHERE ${quotedLongitude} IS NOT NULL AND ${quotedLatitude} IS NOT NULL`,\n ].join(' '),\n },\n geometryColumn,\n geometryEncodingHint: 'wkb',\n },\n },\n fitToData: {\n dataset: datasetId,\n longitudeColumn,\n latitudeColumn,\n padding: 40,\n maxZoom: 12,\n },\n });\n}\n\nexport const deckMapDashboardAddPanelAction: import('@sqlrooms/mosaic').MosaicDashboardAddPanelAction =\n {\n type: DECK_MAP_DASHBOARD_PANEL_TYPE,\n label: 'Map',\n icon: MapIcon,\n isEnabled: ({selectedTable}) =>\n Boolean(findLongitudeLatitudeColumns(selectedTable)),\n createPanel: ({selectedTable}) =>\n selectedTable ? createDeckMapPanelForTable(selectedTable) : undefined,\n };\n"]}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { type MosaicDashboardEntryType, type MosaicDashboardPanelConfigType, type MosaicDashboardPanelSourceType } from '@sqlrooms/mosaic';
|
|
2
|
+
import type { Table as ArrowTable } from 'apache-arrow';
|
|
3
|
+
import type { DeckJsonMapProps, DeckSqlDatasetInput } from './types';
|
|
4
|
+
export declare const DECK_MAP_DASHBOARD_PANEL_TYPE = "deck-json-map";
|
|
5
|
+
export type DeckMapDashboardDatasetConfig = Omit<DeckSqlDatasetInput, 'sqlQuery'> & {
|
|
6
|
+
source?: MosaicDashboardPanelSourceType;
|
|
7
|
+
};
|
|
8
|
+
export type DeckMapDashboardInteractionConfig = {
|
|
9
|
+
type: 'point-radius-brush';
|
|
10
|
+
dataset: string;
|
|
11
|
+
longitudeColumn: string;
|
|
12
|
+
latitudeColumn: string;
|
|
13
|
+
radiusMeters?: number;
|
|
14
|
+
event?: 'hover' | 'click';
|
|
15
|
+
};
|
|
16
|
+
export type DeckMapDashboardFitToDataConfig = {
|
|
17
|
+
dataset: string;
|
|
18
|
+
longitudeColumn: string;
|
|
19
|
+
latitudeColumn: string;
|
|
20
|
+
padding?: number;
|
|
21
|
+
maxZoom?: number;
|
|
22
|
+
};
|
|
23
|
+
export type DeckMapDashboardPanelConfig = {
|
|
24
|
+
spec: DeckJsonMapProps['spec'];
|
|
25
|
+
datasets: Record<string, DeckMapDashboardDatasetConfig>;
|
|
26
|
+
mapStyle?: string;
|
|
27
|
+
mapProps?: Record<string, unknown>;
|
|
28
|
+
showLegends?: boolean;
|
|
29
|
+
interaction?: DeckMapDashboardInteractionConfig;
|
|
30
|
+
fitToData?: DeckMapDashboardFitToDataConfig;
|
|
31
|
+
};
|
|
32
|
+
export type CreateDeckMapDashboardPanelConfigOptions = DeckMapDashboardPanelConfig & {
|
|
33
|
+
title?: string;
|
|
34
|
+
source?: MosaicDashboardPanelSourceType;
|
|
35
|
+
};
|
|
36
|
+
export type DeckMapDashboardDatasetClientState = {
|
|
37
|
+
arrowTable?: ArrowTable;
|
|
38
|
+
isLoading: boolean;
|
|
39
|
+
error?: Error;
|
|
40
|
+
client: unknown;
|
|
41
|
+
};
|
|
42
|
+
export declare function asDeckJsonMapConfig(config: Record<string, unknown>): DeckMapDashboardPanelConfig | null;
|
|
43
|
+
export declare function createDeckMapDashboardPanelConfig(options: CreateDeckMapDashboardPanelConfigOptions): MosaicDashboardPanelConfigType;
|
|
44
|
+
export declare function resolveDeckMapDashboardDatasetSource(options: {
|
|
45
|
+
dashboard: MosaicDashboardEntryType;
|
|
46
|
+
panel: MosaicDashboardPanelConfigType;
|
|
47
|
+
dataset?: DeckMapDashboardDatasetConfig;
|
|
48
|
+
}): MosaicDashboardPanelSourceType | undefined;
|
|
49
|
+
export declare function createDeckMapDashboardDatasetQuery(source: MosaicDashboardPanelSourceType, filter: unknown): import("@uwdata/mosaic-sql").SelectQuery;
|
|
50
|
+
export declare function createDeckMapDashboardDatasets(mapConfig: DeckMapDashboardPanelConfig, datasetStates: Record<string, Pick<DeckMapDashboardDatasetClientState, 'arrowTable'>>): DeckJsonMapProps['datasets'];
|
|
51
|
+
//# sourceMappingURL=dashboardConfig.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dashboardConfig.d.ts","sourceRoot":"","sources":["../src/dashboardConfig.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,wBAAwB,EAC7B,KAAK,8BAA8B,EACnC,KAAK,8BAA8B,EAEpC,MAAM,kBAAkB,CAAC;AAG1B,OAAO,KAAK,EAAC,KAAK,IAAI,UAAU,EAAC,MAAM,cAAc,CAAC;AACtD,OAAO,KAAK,EAAC,gBAAgB,EAAE,mBAAmB,EAAC,MAAM,SAAS,CAAC;AAEnE,eAAO,MAAM,6BAA6B,kBAAkB,CAAC;AAE7D,MAAM,MAAM,6BAA6B,GAAG,IAAI,CAC9C,mBAAmB,EACnB,UAAU,CACX,GAAG;IACF,MAAM,CAAC,EAAE,8BAA8B,CAAC;CACzC,CAAC;AAEF,MAAM,MAAM,iCAAiC,GAAG;IAC9C,IAAI,EAAE,oBAAoB,CAAC;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,eAAe,EAAE,MAAM,CAAC;IACxB,cAAc,EAAE,MAAM,CAAC;IACvB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,KAAK,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC;CAC3B,CAAC;AAEF,MAAM,MAAM,+BAA+B,GAAG;IAC5C,OAAO,EAAE,MAAM,CAAC;IAChB,eAAe,EAAE,MAAM,CAAC;IACxB,cAAc,EAAE,MAAM,CAAC;IACvB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF,MAAM,MAAM,2BAA2B,GAAG;IACxC,IAAI,EAAE,gBAAgB,CAAC,MAAM,CAAC,CAAC;IAC/B,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,6BAA6B,CAAC,CAAC;IACxD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACnC,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,WAAW,CAAC,EAAE,iCAAiC,CAAC;IAChD,SAAS,CAAC,EAAE,+BAA+B,CAAC;CAC7C,CAAC;AAEF,MAAM,MAAM,wCAAwC,GAClD,2BAA2B,GAAG;IAC5B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,8BAA8B,CAAC;CACzC,CAAC;AAEJ,MAAM,MAAM,kCAAkC,GAAG;IAC/C,UAAU,CAAC,EAAE,UAAU,CAAC;IACxB,SAAS,EAAE,OAAO,CAAC;IACnB,KAAK,CAAC,EAAE,KAAK,CAAC;IACd,MAAM,EAAE,OAAO,CAAC;CACjB,CAAC;AAEF,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC9B,2BAA2B,GAAG,IAAI,CAWpC;AAED,wBAAgB,iCAAiC,CAC/C,OAAO,EAAE,wCAAwC,GAChD,8BAA8B,CAShC;AAED,wBAAgB,oCAAoC,CAAC,OAAO,EAAE;IAC5D,SAAS,EAAE,wBAAwB,CAAC;IACpC,KAAK,EAAE,8BAA8B,CAAC;IACtC,OAAO,CAAC,EAAE,6BAA6B,CAAC;CACzC,GAAG,8BAA8B,GAAG,SAAS,CAW7C;AAED,wBAAgB,kCAAkC,CAChD,MAAM,EAAE,8BAA8B,EACtC,MAAM,EAAE,OAAO,4CAShB;AAED,wBAAgB,8BAA8B,CAC5C,SAAS,EAAE,2BAA2B,EACtC,aAAa,EAAE,MAAM,CACnB,MAAM,EACN,IAAI,CAAC,kCAAkC,EAAE,YAAY,CAAC,CACvD,GACA,gBAAgB,CAAC,UAAU,CAAC,CAW9B"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { Query, } from '@sqlrooms/mosaic';
|
|
2
|
+
import { createId } from '@paralleldrive/cuid2';
|
|
3
|
+
import { verbatim } from '@uwdata/mosaic-sql';
|
|
4
|
+
export const DECK_MAP_DASHBOARD_PANEL_TYPE = 'deck-json-map';
|
|
5
|
+
export function asDeckJsonMapConfig(config) {
|
|
6
|
+
if (!config.spec ||
|
|
7
|
+
!config.datasets ||
|
|
8
|
+
typeof config.datasets !== 'object' ||
|
|
9
|
+
Array.isArray(config.datasets)) {
|
|
10
|
+
return null;
|
|
11
|
+
}
|
|
12
|
+
return config;
|
|
13
|
+
}
|
|
14
|
+
export function createDeckMapDashboardPanelConfig(options) {
|
|
15
|
+
const { title, source, ...config } = options;
|
|
16
|
+
return {
|
|
17
|
+
id: createId(),
|
|
18
|
+
type: DECK_MAP_DASHBOARD_PANEL_TYPE,
|
|
19
|
+
title: title ?? 'Map',
|
|
20
|
+
...(source ? { source } : {}),
|
|
21
|
+
config: JSON.parse(JSON.stringify(config)),
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
export function resolveDeckMapDashboardDatasetSource(options) {
|
|
25
|
+
const datasetSource = options.dataset?.source;
|
|
26
|
+
if (datasetSource?.sqlQuery || datasetSource?.tableName) {
|
|
27
|
+
return datasetSource;
|
|
28
|
+
}
|
|
29
|
+
if (options.panel.source?.sqlQuery || options.panel.source?.tableName) {
|
|
30
|
+
return options.panel.source;
|
|
31
|
+
}
|
|
32
|
+
return options.dashboard.selectedTable
|
|
33
|
+
? { tableName: options.dashboard.selectedTable }
|
|
34
|
+
: undefined;
|
|
35
|
+
}
|
|
36
|
+
export function createDeckMapDashboardDatasetQuery(source, filter) {
|
|
37
|
+
const query = source.sqlQuery
|
|
38
|
+
? Query.from({
|
|
39
|
+
__dashboard_map_dataset: verbatim(`(${source.sqlQuery})`),
|
|
40
|
+
})
|
|
41
|
+
: Query.from(source.tableName ?? '');
|
|
42
|
+
return query.select('*').where(filter);
|
|
43
|
+
}
|
|
44
|
+
export function createDeckMapDashboardDatasets(mapConfig, datasetStates) {
|
|
45
|
+
return Object.fromEntries(Object.entries(mapConfig.datasets).map(([datasetId, dataset]) => [
|
|
46
|
+
datasetId,
|
|
47
|
+
{
|
|
48
|
+
arrowTable: datasetStates[datasetId]?.arrowTable,
|
|
49
|
+
geometryColumn: dataset.geometryColumn,
|
|
50
|
+
geometryEncodingHint: dataset.geometryEncodingHint,
|
|
51
|
+
},
|
|
52
|
+
]));
|
|
53
|
+
}
|
|
54
|
+
//# sourceMappingURL=dashboardConfig.js.map
|