@sqlrooms/deck 0.29.0-rc.7 → 0.29.0-rc.9
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 +2 -1
- package/README.md +31 -6
- package/dist/DeckJsonMap.d.ts +2 -2
- package/dist/DeckJsonMap.d.ts.map +1 -1
- package/dist/DeckJsonMap.js +204 -17
- package/dist/DeckJsonMap.js.map +1 -1
- package/dist/DeckJsonMapSpec.d.ts +36 -36
- package/dist/DeckJsonMapSpec.d.ts.map +1 -1
- package/dist/DeckJsonMapSpec.js +1 -0
- package/dist/DeckJsonMapSpec.js.map +1 -1
- package/dist/MapSettings.d.ts.map +1 -1
- package/dist/MapSettings.js +203 -25
- package/dist/MapSettings.js.map +1 -1
- package/dist/ai.d.ts +55 -8
- package/dist/ai.d.ts.map +1 -1
- package/dist/ai.js +190 -41
- package/dist/ai.js.map +1 -1
- package/dist/block.d.ts +20 -0
- package/dist/block.d.ts.map +1 -0
- package/dist/block.js +119 -0
- package/dist/block.js.map +1 -0
- package/dist/dashboard.d.ts +2 -0
- package/dist/dashboard.d.ts.map +1 -1
- package/dist/dashboard.js +194 -285
- package/dist/dashboard.js.map +1 -1
- package/dist/dashboardConfig.d.ts +11 -3
- package/dist/dashboardConfig.d.ts.map +1 -1
- package/dist/dashboardConfig.js +82 -7
- package/dist/dashboardConfig.js.map +1 -1
- package/dist/index.d.ts +4 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -2
- package/dist/index.js.map +1 -1
- package/dist/json/aggregateWaypoints.d.ts +14 -0
- package/dist/json/aggregateWaypoints.d.ts.map +1 -0
- package/dist/json/aggregateWaypoints.js +190 -0
- package/dist/json/aggregateWaypoints.js.map +1 -0
- package/dist/json/compileColorScale.d.ts +1 -1
- package/dist/json/compileColorScale.d.ts.map +1 -1
- package/dist/json/compileColorScale.js +42 -20
- package/dist/json/compileColorScale.js.map +1 -1
- package/dist/json/createDeckJsonConfiguration.d.ts.map +1 -1
- package/dist/json/createDeckJsonConfiguration.js +106 -8
- package/dist/json/createDeckJsonConfiguration.js.map +1 -1
- package/dist/json/defaultClasses.d.ts +5 -4
- package/dist/json/defaultClasses.d.ts.map +1 -1
- package/dist/json/defaultClasses.js +9 -4
- package/dist/json/defaultClasses.js.map +1 -1
- package/dist/json/extractColorScaleLegends.d.ts.map +1 -1
- package/dist/json/extractColorScaleLegends.js +43 -5
- package/dist/json/extractColorScaleLegends.js.map +1 -1
- package/dist/json/heatmapDefaults.d.ts +8 -0
- package/dist/json/heatmapDefaults.d.ts.map +1 -0
- package/dist/json/heatmapDefaults.js +14 -0
- package/dist/json/heatmapDefaults.js.map +1 -0
- package/dist/json/layerCompatibility.d.ts.map +1 -1
- package/dist/json/layerCompatibility.js +5 -1
- package/dist/json/layerCompatibility.js.map +1 -1
- package/dist/json/layers/DeckColumnLayer.d.ts +12 -0
- package/dist/json/layers/DeckColumnLayer.d.ts.map +1 -0
- package/dist/json/layers/DeckColumnLayer.js +26 -0
- package/dist/json/layers/DeckColumnLayer.js.map +1 -0
- package/dist/json/layers/DeckH3HexagonLayer.d.ts +37 -0
- package/dist/json/layers/DeckH3HexagonLayer.d.ts.map +1 -0
- package/dist/json/layers/DeckH3HexagonLayer.js +96 -0
- package/dist/json/layers/DeckH3HexagonLayer.js.map +1 -0
- package/dist/json/layers/DeckTripsLayer.d.ts +45 -0
- package/dist/json/layers/DeckTripsLayer.d.ts.map +1 -0
- package/dist/json/layers/DeckTripsLayer.js +169 -0
- package/dist/json/layers/DeckTripsLayer.js.map +1 -0
- package/dist/json/layers/index.d.ts +4 -0
- package/dist/json/layers/index.d.ts.map +1 -0
- package/dist/json/layers/index.js +4 -0
- package/dist/json/layers/index.js.map +1 -0
- package/dist/json/rewriteGeoArrowAccessors.d.ts.map +1 -1
- package/dist/json/rewriteGeoArrowAccessors.js +5 -0
- package/dist/json/rewriteGeoArrowAccessors.js.map +1 -1
- package/dist/mapConfigUtils.d.ts +102 -1
- package/dist/mapConfigUtils.d.ts.map +1 -1
- package/dist/mapConfigUtils.js +21 -3
- package/dist/mapConfigUtils.js.map +1 -1
- package/dist/mapLayerConfigUtils.d.ts +15 -0
- package/dist/mapLayerConfigUtils.d.ts.map +1 -1
- package/dist/mapLayerConfigUtils.js +109 -7
- package/dist/mapLayerConfigUtils.js.map +1 -1
- package/dist/prepare/detectGeometryColumn.d.ts +10 -1
- package/dist/prepare/detectGeometryColumn.d.ts.map +1 -1
- package/dist/prepare/detectGeometryColumn.js +48 -6
- package/dist/prepare/detectGeometryColumn.js.map +1 -1
- package/dist/prepare/wkbDecoder.d.ts.map +1 -1
- package/dist/prepare/wkbDecoder.js +88 -1
- package/dist/prepare/wkbDecoder.js.map +1 -1
- package/dist/prepare/wkbParser.d.ts +5 -0
- package/dist/prepare/wkbParser.d.ts.map +1 -1
- package/dist/prepare/wkbParser.js +24 -0
- package/dist/prepare/wkbParser.js.map +1 -1
- package/dist/types.d.ts +14 -4
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/useDeckMapDatasets.d.ts +17 -0
- package/dist/useDeckMapDatasets.d.ts.map +1 -0
- package/dist/useDeckMapDatasets.js +69 -0
- package/dist/useDeckMapDatasets.js.map +1 -0
- package/dist/useDeckMapFitToBounds.d.ts +29 -0
- package/dist/useDeckMapFitToBounds.d.ts.map +1 -0
- package/dist/useDeckMapFitToBounds.js +333 -0
- package/dist/useDeckMapFitToBounds.js.map +1 -0
- package/package.json +7 -6
package/dist/dashboard.js
CHANGED
|
@@ -1,18 +1,116 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import {
|
|
3
|
-
import { escapeId, getColValAsNumber, useStoreWithDuckDb, } from '@sqlrooms/duckdb';
|
|
4
|
-
import { column, sql, useMosaicClient, useStoreWithMosaicDashboard, } from '@sqlrooms/mosaic';
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { column, sql, useMosaicClient, useStoreWithMosaicDashboard, usePanelClientRegistration, } from '@sqlrooms/mosaic';
|
|
5
3
|
import { Button, Tooltip, TooltipContent, TooltipTrigger } from '@sqlrooms/ui';
|
|
6
|
-
import { FocusIcon, MapIcon, SettingsIcon } from 'lucide-react';
|
|
4
|
+
import { AlertTriangleIcon, FocusIcon, MapIcon, SettingsIcon, } from 'lucide-react';
|
|
7
5
|
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
8
6
|
import { DeckMapConfigPopoverEditor } from './DeckMapConfigPopoverEditor';
|
|
9
7
|
import { DeckJsonMap } from './DeckJsonMap';
|
|
10
8
|
import { MapSettingsPanel } from './MapSettings';
|
|
11
9
|
import { MosaicDashboardPanelLayout } from '@sqlrooms/mosaic';
|
|
12
|
-
import { asDeckJsonMapConfig, createDeckMapDashboardDatasetQuery, createDeckMapDashboardDatasets, DECK_MAP_DASHBOARD_PANEL_TYPE, resolveDeckMapDashboardDatasetSource, } from './dashboardConfig';
|
|
13
|
-
import {
|
|
10
|
+
import { asDeckJsonMapConfig, createDeckMapDashboardPanelConfig, createDeckMapDashboardDatasetQuery, createDeckMapDashboardDatasets, DECK_MAP_DASHBOARD_PANEL_TYPE, DEFAULT_DECK_MAP_MAX_DATA_POINTS, resolveDeckMapDashboardDatasetSource, } from './dashboardConfig';
|
|
11
|
+
import { useDeckMapFitToBounds, emitDeckMapDashboardFitRequest, createDeckMapBoundsQuery, } from './useDeckMapFitToBounds';
|
|
12
|
+
export { createDeckMapBoundsQuery };
|
|
13
|
+
import { useDeckMapDatasets } from './useDeckMapDatasets';
|
|
14
14
|
import { createDeckMapDashboardPanelConfigForTable, findGeometryColumn, findLongitudeLatitudeColumns, } from './mapConfigUtils';
|
|
15
|
+
function createEmptyDeckMapDashboardPanelConfig(title = 'Map') {
|
|
16
|
+
return createDeckMapDashboardPanelConfig({
|
|
17
|
+
title,
|
|
18
|
+
spec: {
|
|
19
|
+
initialViewState: { longitude: 0, latitude: 20, zoom: 1.5 },
|
|
20
|
+
layers: [],
|
|
21
|
+
},
|
|
22
|
+
datasets: {},
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Extracts column names from common DuckDB "column not found" error messages
|
|
27
|
+
* and layer geometry incompatibility errors.
|
|
28
|
+
* Returns the column names if detected, or null for unrecognized errors.
|
|
29
|
+
*/
|
|
30
|
+
function parseMissingColumnsFromError(message) {
|
|
31
|
+
// "Referenced column "X" not found in FROM clause!" — may appear multiple times
|
|
32
|
+
const refMatches = [
|
|
33
|
+
...message.matchAll(/Referenced column "([^"]+)" not found/gi),
|
|
34
|
+
];
|
|
35
|
+
if (refMatches.length > 0)
|
|
36
|
+
return refMatches.map((m) => m[1]);
|
|
37
|
+
// "Binder Error: Column "X" does not exist" or "column X not found"
|
|
38
|
+
const colMatch = message.match(/(?:Column|column)\s+"([^"]+)"\s+(?:does not exist|not found)/i);
|
|
39
|
+
if (colMatch)
|
|
40
|
+
return [colMatch[1]];
|
|
41
|
+
// "Geometry column "X" was not found"
|
|
42
|
+
const geomNotFound = message.match(/Geometry column "([^"]+)" was not found/i);
|
|
43
|
+
if (geomNotFound)
|
|
44
|
+
return [geomNotFound[1]];
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Detects geometry type mismatch errors (e.g. polygon layer on point data).
|
|
49
|
+
*/
|
|
50
|
+
function parseGeometryMismatchError(message) {
|
|
51
|
+
const match = message.match(/requires (\w+) geometry .+ but only (\w+) coordinates were found/i);
|
|
52
|
+
if (match)
|
|
53
|
+
return { required: match[1], found: match[2] };
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Detects columns referenced in layer config that are missing from the loaded dataset.
|
|
58
|
+
*/
|
|
59
|
+
function detectMissingColumns(mapConfig, datasetStates) {
|
|
60
|
+
if (!mapConfig?.spec)
|
|
61
|
+
return [];
|
|
62
|
+
const missing = [];
|
|
63
|
+
const spec = mapConfig.spec;
|
|
64
|
+
const layers = Array.isArray(spec.layers) ? spec.layers : [];
|
|
65
|
+
for (const layer of layers) {
|
|
66
|
+
if (!layer || typeof layer !== 'object')
|
|
67
|
+
continue;
|
|
68
|
+
const layerObj = layer;
|
|
69
|
+
const binding = layerObj._sqlroomsBinding;
|
|
70
|
+
const datasetId = binding?.dataset;
|
|
71
|
+
const arrowTable = datasetId
|
|
72
|
+
? datasetStates[datasetId]?.arrowTable
|
|
73
|
+
: undefined;
|
|
74
|
+
if (!arrowTable)
|
|
75
|
+
continue;
|
|
76
|
+
const fieldNames = new Set(arrowTable.schema.fields.map((f) => f.name.toLowerCase()));
|
|
77
|
+
const check = (col) => {
|
|
78
|
+
if (col && !fieldNames.has(col.toLowerCase())) {
|
|
79
|
+
missing.push(col);
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
// Check @@= accessors
|
|
83
|
+
for (const [propName, propValue] of Object.entries(layerObj)) {
|
|
84
|
+
// Skip elevation references when extrusion is disabled
|
|
85
|
+
if (propName === 'getElevation' && !layerObj.extruded)
|
|
86
|
+
continue;
|
|
87
|
+
if (typeof propValue === 'string' && propValue.startsWith('@@=')) {
|
|
88
|
+
check(propValue.slice(3).trim());
|
|
89
|
+
}
|
|
90
|
+
// Check @@function colorScale/scale field references
|
|
91
|
+
if (propValue &&
|
|
92
|
+
typeof propValue === 'object' &&
|
|
93
|
+
'@@function' in propValue) {
|
|
94
|
+
const fn = propValue;
|
|
95
|
+
if (typeof fn.field === 'string') {
|
|
96
|
+
check(fn.field);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return [...new Set(missing)];
|
|
102
|
+
}
|
|
15
103
|
function DeckMapRuntimeIssuePanel({ issue }) {
|
|
104
|
+
if (issue.kind === 'sql-error' || issue.kind === 'render-error') {
|
|
105
|
+
const missingColumns = parseMissingColumnsFromError(issue.message);
|
|
106
|
+
if (missingColumns) {
|
|
107
|
+
return (_jsxs("div", { className: "flex h-full min-h-[200px] flex-col items-center justify-center p-4", children: [_jsx("div", { className: "mb-2 text-center font-semibold", children: "The visualization can't be displayed" }), _jsxs("div", { className: "text-center text-sm", children: [_jsx("span", { children: "Selected columns are missing in the dataset: " }), missingColumns.map((col, idx) => (_jsxs("span", { children: [_jsx("span", { className: "inline-flex items-center rounded-md border border-gray-600 bg-gray-800 px-1 py-0.5 text-xs font-medium text-gray-300", children: col }), ' '] }, idx)))] })] }));
|
|
108
|
+
}
|
|
109
|
+
const geomMismatch = parseGeometryMismatchError(issue.message);
|
|
110
|
+
if (geomMismatch) {
|
|
111
|
+
return (_jsxs("div", { className: "flex h-full min-h-[200px] flex-col items-center justify-center p-4", children: [_jsx("div", { className: "mb-2 text-center font-semibold", children: "The visualization can't be displayed" }), _jsxs("div", { className: "text-center text-sm", children: ["This layer requires ", geomMismatch.required, " geometry, but the current dataset only has ", geomMismatch.found, " coordinates."] })] }));
|
|
112
|
+
}
|
|
113
|
+
}
|
|
16
114
|
const title = issue.kind === 'too-much-data'
|
|
17
115
|
? 'Too much data'
|
|
18
116
|
: issue.kind === 'sql-error'
|
|
@@ -20,35 +118,54 @@ function DeckMapRuntimeIssuePanel({ issue }) {
|
|
|
20
118
|
: 'Unable to display map';
|
|
21
119
|
return (_jsxs("div", { className: "flex h-full min-h-[200px] flex-col items-center justify-center p-4", children: [_jsx("div", { className: "mb-2 text-center font-semibold", children: title }), _jsx("div", { className: "text-center text-sm whitespace-pre-wrap", children: issue.message })] }));
|
|
22
120
|
}
|
|
23
|
-
function DeckMapDashboardDatasetClient({ dashboard, dataset, datasetId,
|
|
121
|
+
function DeckMapDashboardDatasetClient({ dashboard, dataset, datasetId, fitToData, panel, onDatasetState, runtimeIssueContext, runtimeIssueReporter, selectionName, maxRows, }) {
|
|
122
|
+
const datasetFitToData = useMemo(() => (fitToData?.dataset === datasetId ? fitToData : undefined), [datasetId, fitToData]);
|
|
24
123
|
const source = useMemo(() => resolveDeckMapDashboardDatasetSource({
|
|
25
124
|
dashboard,
|
|
26
125
|
panel,
|
|
27
126
|
dataset,
|
|
28
|
-
|
|
127
|
+
fitToData: datasetFitToData,
|
|
128
|
+
}), [dashboard, dataset, datasetFitToData, panel]);
|
|
29
129
|
const query = useCallback((filter) => source
|
|
30
|
-
? createDeckMapDashboardDatasetQuery(source, filter
|
|
31
|
-
|
|
130
|
+
? createDeckMapDashboardDatasetQuery(source, filter, {
|
|
131
|
+
sampleRows: maxRows,
|
|
132
|
+
})
|
|
133
|
+
: createDeckMapDashboardDatasetQuery({ tableName: '__missing_dashboard_map_dataset__' }, filter, { sampleRows: maxRows }), [maxRows, source]);
|
|
134
|
+
const queryError = useCallback((err) => {
|
|
135
|
+
onDatasetState(datasetId, {
|
|
136
|
+
arrowTable: undefined,
|
|
137
|
+
error: err,
|
|
138
|
+
isLoading: false,
|
|
139
|
+
client: null,
|
|
140
|
+
isSampled: false,
|
|
141
|
+
});
|
|
142
|
+
}, [datasetId, onDatasetState]);
|
|
143
|
+
const sourceKey = source?.tableName ?? dashboard.selectedTable ?? '';
|
|
144
|
+
const sourceQueryKey = source?.sqlQuery ?? '';
|
|
32
145
|
const { data, error, isLoading, client } = useMosaicClient({
|
|
33
|
-
id: `${panel.id}:${datasetId}`,
|
|
146
|
+
id: `${panel.id}:${datasetId}:${sourceKey}:${sourceQueryKey}`,
|
|
34
147
|
selectionName,
|
|
35
148
|
query,
|
|
36
|
-
|
|
149
|
+
queryError,
|
|
37
150
|
enabled: Boolean(source),
|
|
38
151
|
runtimeIssueContext,
|
|
39
152
|
runtimeIssueReporter,
|
|
40
153
|
});
|
|
154
|
+
// Register client for panel reset button
|
|
155
|
+
usePanelClientRegistration(dashboard.id, panel.id, client ? [client] : []);
|
|
156
|
+
const isSampled = Boolean(data && data.numRows >= maxRows);
|
|
41
157
|
useEffect(() => {
|
|
42
158
|
onDatasetState(datasetId, {
|
|
43
159
|
arrowTable: data ?? undefined,
|
|
44
160
|
error,
|
|
45
161
|
isLoading,
|
|
46
162
|
client,
|
|
163
|
+
isSampled,
|
|
47
164
|
});
|
|
48
165
|
return () => {
|
|
49
166
|
onDatasetState(datasetId, undefined);
|
|
50
167
|
};
|
|
51
|
-
}, [client, data, datasetId, error, isLoading, onDatasetState]);
|
|
168
|
+
}, [client, data, datasetId, error, isLoading, isSampled, onDatasetState]);
|
|
52
169
|
return null;
|
|
53
170
|
}
|
|
54
171
|
function createPointRadiusPredicate(interaction, coordinate) {
|
|
@@ -70,81 +187,6 @@ function getCoordinate(info) {
|
|
|
70
187
|
function isPickedMapFeature(info) {
|
|
71
188
|
return Boolean(info.picked || info.object || (info.index ?? -1) >= 0);
|
|
72
189
|
}
|
|
73
|
-
function createDeckMapBoundsQuery(options) {
|
|
74
|
-
const { source, fitToData } = options;
|
|
75
|
-
const baseSourceSql = source.sqlQuery
|
|
76
|
-
? `SELECT * FROM (${source.sqlQuery}) AS "__sqlrooms_dashboard_map_source"`
|
|
77
|
-
: `SELECT * FROM ${(source.tableName ?? '')
|
|
78
|
-
.split('.')
|
|
79
|
-
.map(escapeId)
|
|
80
|
-
.join('.')}`;
|
|
81
|
-
const longitudeColumn = escapeId(fitToData.longitudeColumn);
|
|
82
|
-
const latitudeColumn = escapeId(fitToData.latitudeColumn);
|
|
83
|
-
return `
|
|
84
|
-
SELECT
|
|
85
|
-
ST_XMin(extent) AS min_longitude,
|
|
86
|
-
ST_YMin(extent) AS min_latitude,
|
|
87
|
-
ST_XMax(extent) AS max_longitude,
|
|
88
|
-
ST_YMax(extent) AS max_latitude
|
|
89
|
-
FROM (
|
|
90
|
-
SELECT ST_Extent_Agg(ST_Point(${longitudeColumn}, ${latitudeColumn})) AS extent
|
|
91
|
-
FROM (${baseSourceSql}) AS "__sqlrooms_dashboard_map_points"
|
|
92
|
-
WHERE ${longitudeColumn} IS NOT NULL AND ${latitudeColumn} IS NOT NULL
|
|
93
|
-
) AS "__sqlrooms_dashboard_map_extent"
|
|
94
|
-
WHERE extent IS NOT NULL
|
|
95
|
-
`;
|
|
96
|
-
}
|
|
97
|
-
function readBoundsFromExtentResult(result) {
|
|
98
|
-
const minLongitude = getColValAsNumber(result, 'min_longitude');
|
|
99
|
-
const minLatitude = getColValAsNumber(result, 'min_latitude');
|
|
100
|
-
const maxLongitude = getColValAsNumber(result, 'max_longitude');
|
|
101
|
-
const maxLatitude = getColValAsNumber(result, 'max_latitude');
|
|
102
|
-
if (!Number.isFinite(minLongitude) ||
|
|
103
|
-
!Number.isFinite(minLatitude) ||
|
|
104
|
-
!Number.isFinite(maxLongitude) ||
|
|
105
|
-
!Number.isFinite(maxLatitude)) {
|
|
106
|
-
return null;
|
|
107
|
-
}
|
|
108
|
-
return [
|
|
109
|
-
[
|
|
110
|
-
minLongitude === maxLongitude ? minLongitude - 0.01 : minLongitude,
|
|
111
|
-
minLatitude === maxLatitude ? minLatitude - 0.01 : minLatitude,
|
|
112
|
-
],
|
|
113
|
-
[
|
|
114
|
-
minLongitude === maxLongitude ? maxLongitude + 0.01 : maxLongitude,
|
|
115
|
-
minLatitude === maxLatitude ? maxLatitude + 0.01 : maxLatitude,
|
|
116
|
-
],
|
|
117
|
-
];
|
|
118
|
-
}
|
|
119
|
-
function fitViewStateToBounds(options) {
|
|
120
|
-
const { bounds, width, height, padding = 40, maxZoom = 12 } = options;
|
|
121
|
-
const viewport = new WebMercatorViewport({
|
|
122
|
-
width: Math.max(width, 1),
|
|
123
|
-
height: Math.max(height, 1),
|
|
124
|
-
});
|
|
125
|
-
const fitted = viewport.fitBounds([
|
|
126
|
-
[bounds[0][0], bounds[0][1]],
|
|
127
|
-
[bounds[1][0], bounds[1][1]],
|
|
128
|
-
], { padding });
|
|
129
|
-
return {
|
|
130
|
-
longitude: fitted.longitude,
|
|
131
|
-
latitude: fitted.latitude,
|
|
132
|
-
zoom: Math.min(fitted.zoom, maxZoom),
|
|
133
|
-
};
|
|
134
|
-
}
|
|
135
|
-
const deckMapDashboardFitRequestTarget = new EventTarget();
|
|
136
|
-
function emitDeckMapDashboardFitRequest(panelId) {
|
|
137
|
-
deckMapDashboardFitRequestTarget.dispatchEvent(new CustomEvent('fit-view', { detail: { panelId } }));
|
|
138
|
-
}
|
|
139
|
-
function createInitialDeckMapDashboardFitState(key) {
|
|
140
|
-
return {
|
|
141
|
-
key,
|
|
142
|
-
viewState: undefined,
|
|
143
|
-
didAutoFit: false,
|
|
144
|
-
fitRequestVersion: 0,
|
|
145
|
-
handledFitRequestVersion: 0,
|
|
146
|
-
};
|
|
147
|
-
}
|
|
148
190
|
function DeckMapDashboardHeaderActions({ dashboardId, panel, }) {
|
|
149
191
|
const updatePanel = useStoreWithMosaicDashboard((state) => state.mosaicDashboard.updatePanel);
|
|
150
192
|
const mapConfig = asDeckJsonMapConfig(panel.config);
|
|
@@ -169,9 +211,18 @@ function DeckMapDashboardRenderer({ dashboard, dashboardId, panel, selectionName
|
|
|
169
211
|
const getSelection = useStoreWithMosaicDashboard((state) => state.mosaic.getSelection);
|
|
170
212
|
const updatePanel = useStoreWithMosaicDashboard((state) => state.mosaicDashboard.updatePanel);
|
|
171
213
|
const issue = useStoreWithMosaicDashboard((state) => state.mosaicDashboard.getPanelIssue(dashboardId, panel.id));
|
|
172
|
-
const reportPanelIssue = useStoreWithMosaicDashboard((state) => state.mosaicDashboard.reportPanelIssue);
|
|
173
214
|
const clearPanelIssue = useStoreWithMosaicDashboard((state) => state.mosaicDashboard.clearPanelIssue);
|
|
174
|
-
|
|
215
|
+
// Clear runtime issues when the active table or panel config changes so
|
|
216
|
+
// the map can recover (e.g., after switching tables or AI updating the map).
|
|
217
|
+
useEffect(() => {
|
|
218
|
+
clearPanelIssue(dashboardId, panel.id);
|
|
219
|
+
}, [
|
|
220
|
+
clearPanelIssue,
|
|
221
|
+
dashboard.selectedTable,
|
|
222
|
+
dashboardId,
|
|
223
|
+
panel.config,
|
|
224
|
+
panel.id,
|
|
225
|
+
]);
|
|
175
226
|
const isSettingsOpen = Boolean(panel.config.settingsOpen);
|
|
176
227
|
const handleSettingsOpenChange = useCallback((isOpen) => {
|
|
177
228
|
updatePanel(dashboardId, panel.id, {
|
|
@@ -180,33 +231,10 @@ function DeckMapDashboardRenderer({ dashboard, dashboardId, panel, selectionName
|
|
|
180
231
|
}, [dashboardId, panel.config, panel.id, updatePanel]);
|
|
181
232
|
const selection = useMemo(() => getSelection(selectionName, 'crossfilter'), [getSelection, selectionName]);
|
|
182
233
|
const containerRef = useRef(null);
|
|
183
|
-
const
|
|
234
|
+
const deckMapRef = useRef(null);
|
|
184
235
|
const [containerSize, setContainerSize] = useState({ width: 0, height: 0 });
|
|
185
|
-
const
|
|
186
|
-
|
|
187
|
-
const next = { ...current };
|
|
188
|
-
if (state) {
|
|
189
|
-
next[datasetId] = state;
|
|
190
|
-
}
|
|
191
|
-
else {
|
|
192
|
-
delete next[datasetId];
|
|
193
|
-
}
|
|
194
|
-
return next;
|
|
195
|
-
});
|
|
196
|
-
}, []);
|
|
197
|
-
const dataPolicy = useMemo(() => getDeckMapDataPolicy(mapConfig), [mapConfig]);
|
|
198
|
-
const runtimeIssueContext = useMemo(() => ({
|
|
199
|
-
panelId: panel.id,
|
|
200
|
-
chartType: DECK_MAP_DASHBOARD_PANEL_TYPE,
|
|
201
|
-
}), [panel.id]);
|
|
202
|
-
const runtimeIssueReporter = useMemo(() => ({
|
|
203
|
-
reportIssue: (issueToReport) => {
|
|
204
|
-
reportPanelIssue(dashboardId, panel.id, issueToReport);
|
|
205
|
-
},
|
|
206
|
-
clearIssue: () => {
|
|
207
|
-
clearPanelIssue(dashboardId, panel.id);
|
|
208
|
-
},
|
|
209
|
-
}), [clearPanelIssue, dashboardId, panel.id, reportPanelIssue]);
|
|
236
|
+
const [sampledDismissed, setSampledDismissed] = useState(false);
|
|
237
|
+
const { datasetStates, handleDatasetState, runtimeIssueContext, runtimeIssueReporter, handleRenderingError, } = useDeckMapDatasets({ dashboardId, panel });
|
|
210
238
|
useEffect(() => {
|
|
211
239
|
const container = containerRef.current;
|
|
212
240
|
if (!container)
|
|
@@ -223,130 +251,14 @@ function DeckMapDashboardRenderer({ dashboard, dashboardId, panel, selectionName
|
|
|
223
251
|
return () => {
|
|
224
252
|
observer.disconnect();
|
|
225
253
|
};
|
|
226
|
-
}, []);
|
|
227
|
-
const fitToData =
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
: undefined, [dashboard, fitToData, mapConfig?.datasets, panel]);
|
|
235
|
-
const fitToDataKey = useMemo(() => fitToData && fitToDataSource
|
|
236
|
-
? JSON.stringify({
|
|
237
|
-
source: fitToDataSource,
|
|
238
|
-
fitToData,
|
|
239
|
-
})
|
|
240
|
-
: null, [fitToData, fitToDataSource]);
|
|
241
|
-
const fitStateKey = useMemo(() => JSON.stringify({ panelId: panel.id, fitToDataKey }), [fitToDataKey, panel.id]);
|
|
242
|
-
const [fitState, setFitState] = useState(() => createInitialDeckMapDashboardFitState(fitStateKey));
|
|
243
|
-
const activeFitState = fitState.key === fitStateKey
|
|
244
|
-
? fitState
|
|
245
|
-
: createInitialDeckMapDashboardFitState(fitStateKey);
|
|
246
|
-
const { didAutoFit, fitRequestVersion, handledFitRequestVersion, viewState } = activeFitState;
|
|
247
|
-
useEffect(() => {
|
|
248
|
-
const handleFitRequest = (event) => {
|
|
249
|
-
const detail = event.detail;
|
|
250
|
-
if (detail?.panelId === panel.id) {
|
|
251
|
-
setFitState((current) => {
|
|
252
|
-
const scoped = current.key === fitStateKey
|
|
253
|
-
? current
|
|
254
|
-
: createInitialDeckMapDashboardFitState(fitStateKey);
|
|
255
|
-
return {
|
|
256
|
-
...scoped,
|
|
257
|
-
fitRequestVersion: scoped.fitRequestVersion + 1,
|
|
258
|
-
};
|
|
259
|
-
});
|
|
260
|
-
}
|
|
261
|
-
};
|
|
262
|
-
deckMapDashboardFitRequestTarget.addEventListener('fit-view', handleFitRequest);
|
|
263
|
-
return () => {
|
|
264
|
-
deckMapDashboardFitRequestTarget.removeEventListener('fit-view', handleFitRequest);
|
|
265
|
-
};
|
|
266
|
-
}, [fitStateKey, panel.id]);
|
|
267
|
-
useEffect(() => {
|
|
268
|
-
const hasManualFitRequest = fitRequestVersion > handledFitRequestVersion;
|
|
269
|
-
if (!fitToData ||
|
|
270
|
-
!fitToDataSource ||
|
|
271
|
-
containerSize.width <= 0 ||
|
|
272
|
-
containerSize.height <= 0 ||
|
|
273
|
-
(!hasManualFitRequest && didAutoFit)) {
|
|
274
|
-
return;
|
|
275
|
-
}
|
|
276
|
-
let isCancelled = false;
|
|
277
|
-
const fitToDataBounds = async () => {
|
|
278
|
-
try {
|
|
279
|
-
const handle = await executeSql(createDeckMapBoundsQuery({
|
|
280
|
-
source: fitToDataSource,
|
|
281
|
-
fitToData,
|
|
282
|
-
}));
|
|
283
|
-
const result = handle ? await handle : null;
|
|
284
|
-
if (isCancelled || !result) {
|
|
285
|
-
return;
|
|
286
|
-
}
|
|
287
|
-
const bounds = readBoundsFromExtentResult(result);
|
|
288
|
-
if (!bounds) {
|
|
289
|
-
setFitState((current) => {
|
|
290
|
-
const scoped = current.key === fitStateKey
|
|
291
|
-
? current
|
|
292
|
-
: createInitialDeckMapDashboardFitState(fitStateKey);
|
|
293
|
-
return {
|
|
294
|
-
...scoped,
|
|
295
|
-
didAutoFit: true,
|
|
296
|
-
handledFitRequestVersion: fitRequestVersion,
|
|
297
|
-
};
|
|
298
|
-
});
|
|
299
|
-
return;
|
|
300
|
-
}
|
|
301
|
-
const nextViewState = fitViewStateToBounds({
|
|
302
|
-
bounds,
|
|
303
|
-
width: containerSize.width,
|
|
304
|
-
height: containerSize.height,
|
|
305
|
-
padding: fitToData.padding,
|
|
306
|
-
maxZoom: fitToData.maxZoom,
|
|
307
|
-
});
|
|
308
|
-
setFitState((current) => {
|
|
309
|
-
const scoped = current.key === fitStateKey
|
|
310
|
-
? current
|
|
311
|
-
: createInitialDeckMapDashboardFitState(fitStateKey);
|
|
312
|
-
return {
|
|
313
|
-
...scoped,
|
|
314
|
-
viewState: nextViewState,
|
|
315
|
-
didAutoFit: true,
|
|
316
|
-
handledFitRequestVersion: fitRequestVersion,
|
|
317
|
-
};
|
|
318
|
-
});
|
|
319
|
-
}
|
|
320
|
-
catch {
|
|
321
|
-
if (!isCancelled) {
|
|
322
|
-
setFitState((current) => {
|
|
323
|
-
const scoped = current.key === fitStateKey
|
|
324
|
-
? current
|
|
325
|
-
: createInitialDeckMapDashboardFitState(fitStateKey);
|
|
326
|
-
return {
|
|
327
|
-
...scoped,
|
|
328
|
-
didAutoFit: true,
|
|
329
|
-
handledFitRequestVersion: fitRequestVersion,
|
|
330
|
-
};
|
|
331
|
-
});
|
|
332
|
-
}
|
|
333
|
-
}
|
|
334
|
-
};
|
|
335
|
-
void fitToDataBounds();
|
|
336
|
-
return () => {
|
|
337
|
-
isCancelled = true;
|
|
338
|
-
};
|
|
339
|
-
}, [
|
|
340
|
-
containerSize.height,
|
|
341
|
-
containerSize.width,
|
|
342
|
-
didAutoFit,
|
|
343
|
-
executeSql,
|
|
344
|
-
fitRequestVersion,
|
|
345
|
-
fitStateKey,
|
|
346
|
-
fitToData,
|
|
347
|
-
fitToDataSource,
|
|
348
|
-
handledFitRequestVersion,
|
|
349
|
-
]);
|
|
254
|
+
}, [issue]);
|
|
255
|
+
const { fitToData } = useDeckMapFitToBounds({
|
|
256
|
+
panelId: panel.id,
|
|
257
|
+
dashboard,
|
|
258
|
+
panel,
|
|
259
|
+
containerSize,
|
|
260
|
+
deckMapRef,
|
|
261
|
+
});
|
|
350
262
|
const handleBrushEvent = useCallback((info) => {
|
|
351
263
|
const interaction = mapConfig?.interaction;
|
|
352
264
|
if (!interaction) {
|
|
@@ -372,41 +284,35 @@ function DeckMapDashboardRenderer({ dashboard, dashboardId, panel, selectionName
|
|
|
372
284
|
predicate: createPointRadiusPredicate(interaction, coordinate),
|
|
373
285
|
});
|
|
374
286
|
}, [datasetStates, mapConfig?.interaction, selection]);
|
|
375
|
-
const handleViewStateChange = useCallback(({ viewState: nextViewState, interactionState, }) => {
|
|
376
|
-
const hasUserInteraction = Boolean(interactionState &&
|
|
377
|
-
['isDragging', 'isPanning', 'isRotating', 'isZooming'].some((key) => Boolean(interactionState[key])));
|
|
378
|
-
if (fitToData &&
|
|
379
|
-
!didAutoFit &&
|
|
380
|
-
viewState === null &&
|
|
381
|
-
!hasUserInteraction) {
|
|
382
|
-
return;
|
|
383
|
-
}
|
|
384
|
-
setFitState((current) => {
|
|
385
|
-
const scoped = current.key === fitStateKey
|
|
386
|
-
? current
|
|
387
|
-
: createInitialDeckMapDashboardFitState(fitStateKey);
|
|
388
|
-
return {
|
|
389
|
-
...scoped,
|
|
390
|
-
viewState: nextViewState,
|
|
391
|
-
didAutoFit: hasUserInteraction || scoped.didAutoFit || !fitToData,
|
|
392
|
-
};
|
|
393
|
-
});
|
|
394
|
-
}, [didAutoFit, fitStateKey, fitToData, viewState]);
|
|
395
287
|
const settingsContent = (_jsx(MapSettingsPanel, { dashboardId: dashboardId, panel: panel, onClose: () => handleSettingsOpenChange(false) }));
|
|
396
|
-
const
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
288
|
+
const datasetError = useMemo(() => {
|
|
289
|
+
for (const state of Object.values(datasetStates)) {
|
|
290
|
+
if (state.error)
|
|
291
|
+
return state.error;
|
|
292
|
+
}
|
|
293
|
+
return undefined;
|
|
294
|
+
}, [datasetStates]);
|
|
295
|
+
const missingColumns = useMemo(() => detectMissingColumns(mapConfig ?? undefined, datasetStates), [mapConfig, datasetStates]);
|
|
296
|
+
const mapContent = !mapConfig ? (_jsx("div", { className: "text-muted-foreground flex h-full items-center justify-center p-4 text-sm", children: "Invalid map panel config." })) : Object.entries(mapConfig.datasets).length === 0 ? (_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." })) : (_jsxs(_Fragment, { children: [Object.entries(mapConfig.datasets).map(([datasetId, dataset]) => (_jsx(DeckMapDashboardDatasetClient, { dashboard: dashboard, dataset: dataset, datasetId: datasetId, fitToData: fitToData ?? undefined, panel: panel, onDatasetState: handleDatasetState, runtimeIssueContext: runtimeIssueContext, runtimeIssueReporter: runtimeIssueReporter, selectionName: selectionName, maxRows: DEFAULT_DECK_MAP_MAX_DATA_POINTS }, datasetId))), issue ? (_jsx(DeckMapRuntimeIssuePanel, { issue: issue })) : datasetError ? (_jsx(DeckMapRuntimeIssuePanel, { issue: {
|
|
297
|
+
kind: 'sql-error',
|
|
298
|
+
panelId: panel.id,
|
|
299
|
+
chartType: DECK_MAP_DASHBOARD_PANEL_TYPE,
|
|
300
|
+
message: datasetError.message,
|
|
301
|
+
recoverable: true,
|
|
302
|
+
} })) : (_jsxs("div", { ref: containerRef, className: "relative h-full w-full", children: [Object.values(datasetStates).some((s) => s.isSampled) &&
|
|
303
|
+
!sampledDismissed && (_jsxs("div", { className: "bg-background/80 text-muted-foreground absolute top-2 left-2 z-10 flex items-center gap-1.5 rounded px-2 py-1 text-xs shadow", children: [_jsxs("span", { children: ["Data sampled to", ' ', DEFAULT_DECK_MAP_MAX_DATA_POINTS.toLocaleString(), " rows"] }), _jsx("button", { className: "text-muted-foreground/60 hover:text-foreground -mr-0.5 ml-0.5", onClick: () => setSampledDismissed(true), children: "\u00D7" })] })), missingColumns.length > 0 && (_jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: _jsx("button", { className: `absolute left-2 z-10 flex h-5 w-5 items-center justify-center ${Object.values(datasetStates).some((s) => s.isSampled) &&
|
|
304
|
+
!sampledDismissed
|
|
305
|
+
? 'top-9'
|
|
306
|
+
: 'top-2'}`, onClick: () => handleSettingsOpenChange(true), children: _jsx(AlertTriangleIcon, { className: "h-4 w-4 text-amber-500 drop-shadow-sm" }) }) }), _jsxs(TooltipContent, { side: "right", children: [_jsx("p", { className: "text-xs font-medium", children: "Missing columns:" }), _jsx("p", { className: "text-xs", children: missingColumns.join(', ') })] })] })), _jsx(DeckJsonMap, { ref: deckMapRef, className: "h-full w-full", spec: mapConfig.spec, datasets: mapConfig
|
|
307
|
+
? createDeckMapDashboardDatasets(mapConfig, datasetStates)
|
|
308
|
+
: {}, mapStyle: mapConfig.mapStyle, mapProps: mapConfig.mapProps, showLegends: mapConfig.showLegends, onRenderingError: handleRenderingError, deckProps: {
|
|
309
|
+
controller: true,
|
|
310
|
+
...(mapConfig.interaction
|
|
311
|
+
? (mapConfig.interaction.event ?? 'hover') === 'click'
|
|
312
|
+
? { onClick: handleBrushEvent }
|
|
313
|
+
: { onHover: handleBrushEvent }
|
|
314
|
+
: {}),
|
|
315
|
+
} })] }))] }));
|
|
410
316
|
return (_jsx("div", { className: "h-full min-h-0", children: _jsx(MosaicDashboardPanelLayout, { isOpen: isSettingsOpen, onIsOpenChange: handleSettingsOpenChange, settings: settingsContent, content: mapContent }) }));
|
|
411
317
|
}
|
|
412
318
|
export const deckMapDashboardPanelRenderer = {
|
|
@@ -418,15 +324,18 @@ export const deckMapDashboardAddPanelAction = {
|
|
|
418
324
|
type: DECK_MAP_DASHBOARD_PANEL_TYPE,
|
|
419
325
|
label: 'Map',
|
|
420
326
|
icon: MapIcon,
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
327
|
+
createPanel: ({ selectedTable }) => {
|
|
328
|
+
if (selectedTable &&
|
|
329
|
+
(findLongitudeLatitudeColumns(selectedTable) ??
|
|
330
|
+
findGeometryColumn(selectedTable))) {
|
|
331
|
+
return createDeckMapDashboardPanelConfigForTable({
|
|
332
|
+
title: `${selectedTable.tableName} map`,
|
|
333
|
+
tableName: selectedTable.tableName,
|
|
334
|
+
columns: selectedTable.columns,
|
|
335
|
+
tableReference: selectedTable.table,
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
return createEmptyDeckMapDashboardPanelConfig(selectedTable ? `${selectedTable.tableName} map` : 'Map');
|
|
339
|
+
},
|
|
431
340
|
};
|
|
432
341
|
//# sourceMappingURL=dashboard.js.map
|