@sqlrooms/deck 0.29.0-rc.11 → 0.29.0-rc.13

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.
Files changed (154) hide show
  1. package/README.md +193 -35
  2. package/dist/BlockMapSettings.d.ts.map +1 -1
  3. package/dist/BlockMapSettings.js +4 -16
  4. package/dist/BlockMapSettings.js.map +1 -1
  5. package/dist/DashboardMapSettings.d.ts.map +1 -1
  6. package/dist/DashboardMapSettings.js +7 -10
  7. package/dist/DashboardMapSettings.js.map +1 -1
  8. package/dist/DeckJsonMap.d.ts.map +1 -1
  9. package/dist/DeckJsonMap.js +75 -23
  10. package/dist/DeckJsonMap.js.map +1 -1
  11. package/dist/DeckJsonMapSpec.d.ts +41 -0
  12. package/dist/DeckJsonMapSpec.d.ts.map +1 -1
  13. package/dist/DeckJsonMapSpec.js +2 -0
  14. package/dist/DeckJsonMapSpec.js.map +1 -1
  15. package/dist/DeckMapBasemapSelect.d.ts +9 -0
  16. package/dist/DeckMapBasemapSelect.d.ts.map +1 -0
  17. package/dist/DeckMapBasemapSelect.js +20 -0
  18. package/dist/DeckMapBasemapSelect.js.map +1 -0
  19. package/dist/DeckMapDefaultStylesProvider.d.ts +13 -19
  20. package/dist/DeckMapDefaultStylesProvider.d.ts.map +1 -1
  21. package/dist/DeckMapDefaultStylesProvider.js +11 -18
  22. package/dist/DeckMapDefaultStylesProvider.js.map +1 -1
  23. package/dist/DeckMapSurface.d.ts +1 -1
  24. package/dist/DeckMapSurface.js +1 -1
  25. package/dist/DeckMapSurface.js.map +1 -1
  26. package/dist/DeckMapsSlice.d.ts +5 -0
  27. package/dist/DeckMapsSlice.d.ts.map +1 -1
  28. package/dist/DeckMapsSlice.js +6 -3
  29. package/dist/DeckMapsSlice.js.map +1 -1
  30. package/dist/MapSettings.d.ts +14 -0
  31. package/dist/MapSettings.d.ts.map +1 -1
  32. package/dist/MapSettings.js +548 -192
  33. package/dist/MapSettings.js.map +1 -1
  34. package/dist/MapSettingsControls.d.ts +8 -1
  35. package/dist/MapSettingsControls.d.ts.map +1 -1
  36. package/dist/MapSettingsControls.js +24 -2
  37. package/dist/MapSettingsControls.js.map +1 -1
  38. package/dist/ai.d.ts.map +1 -1
  39. package/dist/ai.js +20 -12
  40. package/dist/ai.js.map +1 -1
  41. package/dist/aiNormalize.d.ts.map +1 -1
  42. package/dist/aiNormalize.js +2 -1
  43. package/dist/aiNormalize.js.map +1 -1
  44. package/dist/basemap.d.ts +23 -0
  45. package/dist/basemap.d.ts.map +1 -0
  46. package/dist/basemap.js +23 -0
  47. package/dist/basemap.js.map +1 -0
  48. package/dist/block.d.ts +1 -1
  49. package/dist/block.d.ts.map +1 -1
  50. package/dist/block.js +13 -9
  51. package/dist/block.js.map +1 -1
  52. package/dist/createOrUpdateDeckMapResource.d.ts +6 -2
  53. package/dist/createOrUpdateDeckMapResource.d.ts.map +1 -1
  54. package/dist/createOrUpdateDeckMapResource.js +57 -4
  55. package/dist/createOrUpdateDeckMapResource.js.map +1 -1
  56. package/dist/datasets/PreparedDatasetStore.d.ts +3 -4
  57. package/dist/datasets/PreparedDatasetStore.d.ts.map +1 -1
  58. package/dist/datasets/PreparedDatasetStore.js +4 -8
  59. package/dist/datasets/PreparedDatasetStore.js.map +1 -1
  60. package/dist/datasets/helpers.d.ts +39 -2
  61. package/dist/datasets/helpers.d.ts.map +1 -1
  62. package/dist/datasets/helpers.js +86 -3
  63. package/dist/datasets/helpers.js.map +1 -1
  64. package/dist/datasets/usePreparedDatasetStates.d.ts.map +1 -1
  65. package/dist/datasets/usePreparedDatasetStates.js +12 -8
  66. package/dist/datasets/usePreparedDatasetStates.js.map +1 -1
  67. package/dist/index.d.ts +6 -3
  68. package/dist/index.d.ts.map +1 -1
  69. package/dist/index.js +5 -2
  70. package/dist/index.js.map +1 -1
  71. package/dist/json/colorScaleFunction.d.ts +12 -4
  72. package/dist/json/colorScaleFunction.d.ts.map +1 -1
  73. package/dist/json/colorScaleFunction.js.map +1 -1
  74. package/dist/json/compileColorScale.d.ts +3 -2
  75. package/dist/json/compileColorScale.d.ts.map +1 -1
  76. package/dist/json/compileColorScale.js +16 -1
  77. package/dist/json/compileColorScale.js.map +1 -1
  78. package/dist/json/createDeckJsonConfiguration.d.ts.map +1 -1
  79. package/dist/json/createDeckJsonConfiguration.js +235 -175
  80. package/dist/json/createDeckJsonConfiguration.js.map +1 -1
  81. package/dist/json/defaultClasses.d.ts.map +1 -1
  82. package/dist/json/defaultClasses.js +3 -3
  83. package/dist/json/defaultClasses.js.map +1 -1
  84. package/dist/json/extractColorScaleLegends.d.ts.map +1 -1
  85. package/dist/json/extractColorScaleLegends.js +41 -26
  86. package/dist/json/extractColorScaleLegends.js.map +1 -1
  87. package/dist/json/heatmapDefaults.d.ts +17 -7
  88. package/dist/json/heatmapDefaults.d.ts.map +1 -1
  89. package/dist/json/heatmapDefaults.js +54 -12
  90. package/dist/json/heatmapDefaults.js.map +1 -1
  91. package/dist/json/layers/DeckHeatmapLayer.d.ts +32 -0
  92. package/dist/json/layers/DeckHeatmapLayer.d.ts.map +1 -0
  93. package/dist/json/layers/DeckHeatmapLayer.js +69 -0
  94. package/dist/json/layers/DeckHeatmapLayer.js.map +1 -0
  95. package/dist/json/layers/index.d.ts +1 -0
  96. package/dist/json/layers/index.d.ts.map +1 -1
  97. package/dist/json/layers/index.js +1 -0
  98. package/dist/json/layers/index.js.map +1 -1
  99. package/dist/json/rewriteGeoArrowAccessors.d.ts.map +1 -1
  100. package/dist/json/rewriteGeoArrowAccessors.js +13 -5
  101. package/dist/json/rewriteGeoArrowAccessors.js.map +1 -1
  102. package/dist/json/scaleFunction.d.ts +5 -8
  103. package/dist/json/scaleFunction.d.ts.map +1 -1
  104. package/dist/json/scaleFunction.js +30 -5
  105. package/dist/json/scaleFunction.js.map +1 -1
  106. package/dist/mapAiConfig.d.ts +6 -0
  107. package/dist/mapAiConfig.d.ts.map +1 -1
  108. package/dist/mapAiConfig.js +7 -0
  109. package/dist/mapAiConfig.js.map +1 -1
  110. package/dist/mapAiSharedInstructions.d.ts +1 -1
  111. package/dist/mapAiSharedInstructions.d.ts.map +1 -1
  112. package/dist/mapAiSharedInstructions.js +4 -3
  113. package/dist/mapAiSharedInstructions.js.map +1 -1
  114. package/dist/mapConfig.d.ts +6 -1
  115. package/dist/mapConfig.d.ts.map +1 -1
  116. package/dist/mapConfig.js +28 -3
  117. package/dist/mapConfig.js.map +1 -1
  118. package/dist/mapConfigUtils.d.ts +22 -0
  119. package/dist/mapConfigUtils.d.ts.map +1 -1
  120. package/dist/mapConfigUtils.js +152 -5
  121. package/dist/mapConfigUtils.js.map +1 -1
  122. package/dist/mapLayerConfigUtils.d.ts +55 -0
  123. package/dist/mapLayerConfigUtils.d.ts.map +1 -1
  124. package/dist/mapLayerConfigUtils.js +222 -11
  125. package/dist/mapLayerConfigUtils.js.map +1 -1
  126. package/dist/mapResourceAuthoring.d.ts +1 -1
  127. package/dist/mapResourceAuthoring.d.ts.map +1 -1
  128. package/dist/mapResourceAuthoring.js +14 -7
  129. package/dist/mapResourceAuthoring.js.map +1 -1
  130. package/dist/mapStyles.d.ts +16 -0
  131. package/dist/mapStyles.d.ts.map +1 -0
  132. package/dist/mapStyles.js +32 -0
  133. package/dist/mapStyles.js.map +1 -0
  134. package/dist/prepare/prepareDeckDataset.d.ts.map +1 -1
  135. package/dist/prepare/prepareDeckDataset.js +1 -0
  136. package/dist/prepare/prepareDeckDataset.js.map +1 -1
  137. package/dist/prepare/types.d.ts +3 -0
  138. package/dist/prepare/types.d.ts.map +1 -1
  139. package/dist/prepare/types.js.map +1 -1
  140. package/dist/prepare/wkbDecoder.d.ts.map +1 -1
  141. package/dist/prepare/wkbDecoder.js +62 -16
  142. package/dist/prepare/wkbDecoder.js.map +1 -1
  143. package/dist/prepare/wkbParser.d.ts +2 -0
  144. package/dist/prepare/wkbParser.d.ts.map +1 -1
  145. package/dist/prepare/wkbParser.js +1 -0
  146. package/dist/prepare/wkbParser.js.map +1 -1
  147. package/dist/protomapsStyles.d.ts +18 -0
  148. package/dist/protomapsStyles.d.ts.map +1 -0
  149. package/dist/protomapsStyles.js +38 -0
  150. package/dist/protomapsStyles.js.map +1 -0
  151. package/dist/types.d.ts +13 -0
  152. package/dist/types.d.ts.map +1 -1
  153. package/dist/types.js.map +1 -1
  154. package/package.json +11 -9
@@ -1,43 +1,51 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
- import { useCallback, useMemo, useState } from 'react';
2
+ import { useCallback, useEffect, useMemo, useRef, useState, } from 'react';
3
3
  import { getTableIdentity, resolveTableReference, } from '@sqlrooms/duckdb';
4
- import { binnedNumericSchemes, categoricalSchemes, continuousDivergingSchemes, continuousSequentialSchemes, continuousSequentialInterpolators, parseColorString, } from '@sqlrooms/color-scales';
5
- import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, Slider, ScrollArea, SettingsPanelHeader, Switch, } from '@sqlrooms/ui';
4
+ import { binnedNumericSchemes, categoricalSchemeColors, categoricalSchemes, continuousDivergingInterpolators, continuousDivergingSchemes, continuousSequentialSchemes, continuousSequentialInterpolators, parseColorString, } from '@sqlrooms/color-scales';
5
+ import { cn, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, Slider, ScrollArea, SettingsPanelHeader, Switch, Tabs, TabsContent, TabsList, TabsTrigger, Tooltip, TooltipContent, TooltipTrigger, } from '@sqlrooms/ui';
6
+ import { AlertTriangleIcon } from 'lucide-react';
7
+ import { DeckMapBasemapSelect } from './DeckMapBasemapSelect';
6
8
  import { isDeckMapTableDatasetSource } from './mapConfig';
7
- import { clearDeckMapLayerColorScale, createDeckMapLayerColorScale, DECK_MAP_COLOR_SCALE_TYPE_OPTIONS, DECK_MAP_LAYER_TYPE_OPTIONS, getDeckMapColorAccessorOptions, getDeckMapLayerDatasetId, getDeckMapLayerColorScale, getDeckMapLayerRecords, setDeckMapLayerColorScale, setDeckMapLayerGeometryColumn, setDeckMapLayerHexagonColumn, setDeckMapLayerArcColumns, setDeckMapLayerTimestampColumn, setDeckMapLayerType, updateDeckMapLayer, usesGeometryColumnSetting, usesH3ColumnSetting, usesArcColumnSetting, usesRadiusSetting, usesColumnRadiusSetting, usesTripsSettings, usesExtrusionSettings, } from './mapLayerConfigUtils';
8
- import { DeckMapCodeViewerPanel, DeckMapCodeViewToggleButton, DeckMapColumnSelector as ColumnSelector, DeckMapColumnsProvider as ColumnsProvider, DeckMapSettingsField as Field, DeckMapTableSelector as DataTableSelector, } from './MapSettingsControls';
9
+ import { clearDeckMapLayerColorScale, createDeckMapLayerColorScale, DECK_MAP_COLOR_SCALE_TYPE_OPTIONS, DECK_MAP_DEFAULT_LAYER_COLOR, DECK_MAP_DEFAULT_STROKE_COLOR, DECK_MAP_LAYER_TYPE_OPTIONS, deckMapRgbaToHex, getDeckMapColorAccessorOptions, getDeckMapLayerDatasetId, getDeckMapLayerColorScale, getDeckMapLayerExtruded, getDeckMapLayerFlatColor, getDeckMapLayerRecords, setDeckMapLayerFlatColor, setDeckMapLayerGeometryColumn, setDeckMapLayerHexagonColumn, setDeckMapLayerArcColumns, setDeckMapLayerTimestampColumn, setDeckMapLayerType, setDeckMapLayerColumnRadius, updateDeckMapLayer, usesGeometryColumnSetting, usesH3ColumnSetting, usesArcColumnSetting, usesRadiusSetting, usesColumnRadiusSetting, usesTripsSettings, usesExtrusionSettings, usesStrokeSetting, usesStrokeExtrusionWarning, getDeckMapLayerStrokeDefault, getDeckMapColorScaleOpacity, getDeckMapLayerChannelOpacityPercent, detachDeckMapLayerOpacity, replaceDeckMapLayerColorScalesWithFlat, replaceDeckMapLayerColorScaleWithFlat, } from './mapLayerConfigUtils';
10
+ import { DeckMapCodeViewerPanel, DeckMapCodeViewToggleButton, DeckMapColumnSelector as ColumnSelector, DeckMapColumnsProvider as ColumnsProvider, DeckMapSettingsField as Field, DeckMapTableSelector as DataTableSelector, filterDeckMapColumns, isDeckMapCategoricalColorColumn, } from './MapSettingsControls';
9
11
  import { regenerateMapConfigForTable } from './mapConfigUtils';
10
12
  import { useDeckMapDatasetSchema } from './useDeckMapDatasetSchema';
11
- const HEATMAP_COLOR_STEPS = 6;
13
+ import { detectHeatmapScheme, heatmapSchemeToColorRange, } from './json/heatmapDefaults';
12
14
  const EMPTY_COLUMNS = [];
13
- function schemeToColorRange(scheme) {
14
- const interpolator = continuousSequentialInterpolators[scheme];
15
- if (!interpolator) {
16
- return continuousSequentialInterpolators.Viridis
17
- ? Array.from({ length: HEATMAP_COLOR_STEPS }, (_, i) => parseColorString(continuousSequentialInterpolators.Viridis(i / (HEATMAP_COLOR_STEPS - 1))))
18
- : [];
15
+ function getColorScaleColumnKind(type) {
16
+ return type === 'categorical' ? 'categorical' : 'quantitative';
17
+ }
18
+ /** Color-scale type matching the field's column kind. */
19
+ function resolveColorScaleTypeForField(columns, field, preferredType) {
20
+ if (!field)
21
+ return preferredType;
22
+ const column = columns.find((candidate) => candidate.name === field);
23
+ if (!column?.type)
24
+ return preferredType;
25
+ // String/boolean/binary fields only work with categorical scales.
26
+ if (isDeckMapCategoricalColorColumn(column)) {
27
+ return 'categorical';
19
28
  }
20
- return Array.from({ length: HEATMAP_COLOR_STEPS }, (_, i) => parseColorString(interpolator(i / (HEATMAP_COLOR_STEPS - 1))));
29
+ return preferredType;
21
30
  }
22
- function detectHeatmapScheme(colorRange) {
23
- if (!Array.isArray(colorRange) || colorRange.length === 0)
24
- return 'Viridis';
25
- for (const scheme of continuousSequentialSchemes) {
26
- const sampled = schemeToColorRange(scheme);
27
- if (sampled.length === colorRange.length) {
28
- const matches = sampled.every((color, idx) => {
29
- const actual = colorRange[idx];
30
- if (!Array.isArray(actual))
31
- return false;
32
- return (Math.abs(color[0] - actual[0]) < 2 &&
33
- Math.abs(color[1] - actual[1]) < 2 &&
34
- Math.abs(color[2] - actual[2]) < 2);
35
- });
36
- if (matches)
37
- return scheme;
38
- }
31
+ /** First selectable color-scale field for this type. */
32
+ function getDefaultColorScaleField(columns, type, preferred) {
33
+ const colorable = filterDeckMapColumns(columns, 'colorable');
34
+ if (preferred && colorable.some((column) => column.name === preferred)) {
35
+ return preferred;
39
36
  }
40
- return 'Viridis';
37
+ const matchingKind = filterDeckMapColumns(colorable, getColorScaleColumnKind(type));
38
+ return matchingKind[0]?.name ?? colorable[0]?.name;
39
+ }
40
+ /** Pick field first, then coerce scale type to match that field. */
41
+ export function resolveColorScaleFieldAndType(columns, preferredType, preferredField) {
42
+ const field = getDefaultColorScaleField(columns, preferredType, preferredField);
43
+ if (!field)
44
+ return undefined;
45
+ return {
46
+ field,
47
+ type: resolveColorScaleTypeForField(columns, field, preferredType),
48
+ };
41
49
  }
42
50
  function getSchemeOptions(type) {
43
51
  if (type === 'categorical') {
@@ -52,10 +60,296 @@ function getSchemeOptions(type) {
52
60
  // Quantile/quantize/threshold: ColorBrewer only (Viridis → sequential).
53
61
  return binnedNumericSchemes;
54
62
  }
55
- export const DeckMapSettingsPanel = ({ title, selectedTable, config, tables, onClose, onTableChange, onTitleChange, onConfigChange, readOnly, }) => {
63
+ const SCHEME_PREVIEW_STEPS = 24;
64
+ /** Sample CSS colors for scheme dropdown previews. */
65
+ function getSchemePreviewColors(scheme, type) {
66
+ if (type === 'categorical') {
67
+ const colors = categoricalSchemeColors[scheme];
68
+ return colors ? [...colors] : [];
69
+ }
70
+ const interpolator = type === 'diverging'
71
+ ? continuousDivergingInterpolators[scheme]
72
+ : (continuousSequentialInterpolators[scheme] ??
73
+ continuousDivergingInterpolators[scheme]);
74
+ if (!interpolator)
75
+ return [];
76
+ return Array.from({ length: SCHEME_PREVIEW_STEPS }, (_, i) => interpolator(i / (SCHEME_PREVIEW_STEPS - 1)));
77
+ }
78
+ /** Edge-to-edge CSS gradient for a scheme (no fringe gaps). */
79
+ function getSchemePreviewGradient(colors, type) {
80
+ if (colors.length === 0)
81
+ return undefined;
82
+ if (type === 'categorical') {
83
+ const stops = colors.flatMap((color, index) => {
84
+ const start = (index / colors.length) * 100;
85
+ const end = ((index + 1) / colors.length) * 100;
86
+ return [`${color} ${start}%`, `${color} ${end}%`];
87
+ });
88
+ return `linear-gradient(to right, ${stops.join(', ')})`;
89
+ }
90
+ return `linear-gradient(to right, ${colors.join(', ')})`;
91
+ }
92
+ const ColorSchemeOptionLabel = ({ scheme, type }) => {
93
+ const colors = getSchemePreviewColors(scheme, type);
94
+ const gradient = getSchemePreviewGradient(colors, type);
95
+ return (_jsxs("span", { className: "flex min-w-0 flex-1 items-center gap-2", children: [gradient ? (_jsx("span", { "aria-hidden": true, className: "h-3 w-20 shrink-0 overflow-hidden rounded-sm", style: { backgroundImage: gradient } })) : null, _jsx("span", { className: "truncate", children: scheme })] }));
96
+ };
97
+ /** Compact color swatch; native picker is visually hidden. */
98
+ const ColorSwatchInput = ({ value, onChange, disabled, 'aria-label': ariaLabel }) => (_jsx("label", { className: cn('border-input relative h-5 w-9 shrink-0 overflow-hidden rounded-[3px] border', 'ring-offset-background focus-within:ring-ring focus-within:ring-1 focus-within:ring-offset-1', disabled ? 'cursor-not-allowed opacity-50' : 'cursor-pointer'), style: { backgroundColor: value }, children: _jsx("input", { type: "color", "aria-label": ariaLabel, value: value, disabled: disabled, className: "absolute inset-0 h-full w-full cursor-pointer opacity-0 disabled:cursor-not-allowed", onChange: (event) => onChange(event.target.value) }) }));
99
+ function opacityPercentToAlpha(percent) {
100
+ return Math.round((Math.max(0, Math.min(100, percent)) / 100) * 255);
101
+ }
102
+ function getLayerOpacityPercent(layer) {
103
+ const opacity = layer?.opacity;
104
+ if (typeof opacity === 'number' && Number.isFinite(opacity)) {
105
+ return Math.round(Math.max(0, Math.min(1, opacity)) * 100);
106
+ }
107
+ return 100;
108
+ }
109
+ const SettingsSliderField = ({ label, children }) => (_jsx(Field, { label: label, children: _jsx("div", { className: "pt-0.5", children: children }) }));
110
+ const AppearanceOpacitySlider = ({ valuePercent, onChange, disabled }) => (_jsx(SettingsSliderField, { label: `Opacity: ${valuePercent}%`, children: _jsx(Slider, { min: 0, max: 100, step: 1, value: [valuePercent], disabled: disabled, onValueChange: (values) => onChange(values[0] ?? valuePercent) }) }));
111
+ const AppearanceColorChannel = ({ accessor, layer, columns, mapConfig, layerIndex, applyConfig, lastColorScaleFieldsRef, readOnly, enabled, onEnabledChange, enableLabel = 'Enabled', defaultFlatColor = DECK_MAP_DEFAULT_LAYER_COLOR, widthPixels, onWidthChange, widthLabel = 'Width', radiusValue, onRadiusChange, radiusLabel = 'Radius', radiusUnit = '', radiusMin = 0.1, radiusMax = 50, radiusStep = 0.1, extrusionDisablesStroke = false, }) => {
112
+ const colorScale = getDeckMapLayerColorScale(layer, accessor);
113
+ const flatColor = getDeckMapLayerFlatColor(layer, accessor) ?? [
114
+ ...defaultFlatColor,
115
+ ];
116
+ const colorScaleType = colorScale?.type ?? 'sequential';
117
+ const schemeOptions = getSchemeOptions(colorScaleType);
118
+ // Don't read lastColorScaleFieldsRef during render (react-hooks/refs).
119
+ const defaultField = getDefaultColorScaleField(columns, colorScaleType);
120
+ const showControls = enabled !== false;
121
+ const opacityPercent = getDeckMapLayerChannelOpacityPercent(layer, accessor, flatColor[3] ?? 255);
122
+ const updateColorScale = (patch) => {
123
+ const preferredField = patch.field ??
124
+ colorScale?.field ??
125
+ lastColorScaleFieldsRef.current[accessor];
126
+ const resolved = resolveColorScaleFieldAndType(columns, patch.type ?? colorScale?.type ?? 'sequential', preferredField);
127
+ if (!resolved)
128
+ return;
129
+ const { field, type } = resolved;
130
+ lastColorScaleFieldsRef.current[accessor] = field;
131
+ const scheme = patch.scheme ??
132
+ (patch.type && patch.type !== colorScale?.type
133
+ ? undefined
134
+ : type !== colorScale?.type
135
+ ? undefined
136
+ : colorScale?.scheme);
137
+ applyConfig(updateDeckMapLayer(mapConfig, layerIndex, (nextLayer) => {
138
+ const detached = detachDeckMapLayerOpacity(nextLayer);
139
+ const existingScale = getDeckMapLayerColorScale(detached, accessor);
140
+ const currentFlat = getDeckMapLayerFlatColor(detached, accessor) ?? flatColor;
141
+ return {
142
+ ...detached,
143
+ [accessor]: createDeckMapLayerColorScale({
144
+ field,
145
+ type,
146
+ scheme,
147
+ title: field,
148
+ opacity: existingScale
149
+ ? getDeckMapColorScaleOpacity(existingScale)
150
+ : (currentFlat[3] ?? 255) / 255,
151
+ }),
152
+ };
153
+ }));
154
+ };
155
+ return (_jsxs("div", { className: "flex flex-col gap-2", children: [onEnabledChange ? (_jsxs("div", { className: "flex items-center justify-between gap-2", children: [_jsxs("span", { className: "flex items-center gap-1 text-xs font-medium", children: [enableLabel, extrusionDisablesStroke ? (_jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: _jsx("button", { type: "button", className: "inline-flex", "aria-label": "no strokes with extrusion enabled", children: _jsx(AlertTriangleIcon, { className: "h-3.5 w-3.5 text-amber-500" }) }) }), _jsx(TooltipContent, { children: "no strokes with extrusion enabled" })] })) : null] }), _jsx(Switch, { checked: enabled !== false, onCheckedChange: onEnabledChange, "aria-label": enableLabel })] })) : null, showControls && onWidthChange && widthPixels !== undefined ? (_jsx(SettingsSliderField, { label: `${widthLabel}: ${Number(widthPixels.toFixed(1))}px`, children: _jsx(Slider, { min: 0.1, max: 20, step: 0.1, value: [widthPixels], onValueChange: (values) => onWidthChange(values[0] ?? 1) }) })) : null, showControls && onRadiusChange && radiusValue !== undefined ? (_jsx(SettingsSliderField, { label: `${radiusLabel}: ${radiusStep < 1 ? Number(radiusValue.toFixed(1)) : radiusValue}${radiusUnit}`, children: _jsx(Slider, { min: radiusMin, max: radiusMax, step: radiusStep, value: [radiusValue], onValueChange: (values) => onRadiusChange(values[0] ?? radiusValue) }) })) : null, showControls ? (_jsxs(_Fragment, { children: [_jsxs("div", { className: "flex items-center justify-between gap-2", children: [_jsx("span", { className: "text-xs font-medium", children: "Color scale" }), _jsx(Switch, { checked: Boolean(colorScale), disabled: readOnly || (!colorScale && !defaultField), "aria-label": "Color scale", onCheckedChange: (checked) => {
156
+ if (checked) {
157
+ updateColorScale({});
158
+ return;
159
+ }
160
+ if (colorScale?.field) {
161
+ lastColorScaleFieldsRef.current[accessor] = colorScale.field;
162
+ }
163
+ applyConfig(clearDeckMapLayerColorScale(mapConfig, layerIndex, accessor));
164
+ } })] }), !colorScale ? (_jsxs("div", { className: "flex items-center justify-between gap-2", children: [_jsx("span", { className: "text-xs font-medium", children: "Color" }), _jsx(ColorSwatchInput, { "aria-label": "Color", value: deckMapRgbaToHex(flatColor), disabled: readOnly, onChange: (hex) => {
165
+ const next = parseColorString(hex, flatColor[3] ?? 255);
166
+ applyConfig(setDeckMapLayerFlatColor(mapConfig, layerIndex, accessor, next));
167
+ } })] })) : null, colorScale ? (_jsxs("div", { className: "flex flex-col gap-2", children: [columns.length > 0 ? (_jsx(ColumnsProvider, { columns: columns, children: _jsx(Field, { label: "Field", required: true, children: _jsx(ColumnSelector.Colorable, { value: colorScale.field, onChange: (field) => updateColorScale({ field }), disabled: readOnly }) }) })) : null, _jsx(Field, { label: "Type", children: _jsxs(Select, { value: colorScaleType, onValueChange: (value) => updateColorScale({ type: value }), children: [_jsx(SelectTrigger, { className: "w-full", children: _jsx(SelectValue, {}) }), _jsx(SelectContent, { children: DECK_MAP_COLOR_SCALE_TYPE_OPTIONS.map((option) => (_jsx(SelectItem, { value: option.value, children: option.label }, option.value))) })] }) }), _jsx(Field, { label: "Scheme", children: _jsxs(Select, { value: colorScale.scheme, onValueChange: (value) => updateColorScale({ scheme: value }), children: [_jsx(SelectTrigger, { className: "w-full [&>span]:line-clamp-none", children: _jsx(SelectValue, {}) }), _jsx(SelectContent, { children: schemeOptions.map((scheme) => (_jsx(SelectItem, { value: scheme, children: _jsx(ColorSchemeOptionLabel, { scheme: scheme, type: colorScaleType }) }, scheme))) })] }) })] })) : null, _jsx(AppearanceOpacitySlider, { valuePercent: opacityPercent, disabled: readOnly, onChange: (percent) => {
168
+ applyConfig(updateDeckMapLayer(mapConfig, layerIndex, (nextLayer) => {
169
+ const detached = detachDeckMapLayerOpacity(nextLayer);
170
+ if (colorScale) {
171
+ const scale = getDeckMapLayerColorScale(detached, accessor) ??
172
+ colorScale;
173
+ return {
174
+ ...detached,
175
+ [accessor]: {
176
+ ...scale,
177
+ opacity: percent / 100,
178
+ },
179
+ };
180
+ }
181
+ const current = getDeckMapLayerFlatColor(detached, accessor) ?? flatColor;
182
+ return {
183
+ ...detached,
184
+ [accessor]: [
185
+ current[0],
186
+ current[1],
187
+ current[2],
188
+ opacityPercentToAlpha(percent),
189
+ ],
190
+ };
191
+ }));
192
+ } })] })) : null] }));
193
+ };
194
+ /** Arc source/target colors; optional shared color scale. */
195
+ const AppearanceArcColorPanel = ({ layer, columns, mapConfig, layerIndex, applyConfig, lastColorScaleFieldsRef, readOnly, }) => {
196
+ const sourceScale = getDeckMapLayerColorScale(layer, 'getSourceColor');
197
+ const targetScale = getDeckMapLayerColorScale(layer, 'getTargetColor');
198
+ const colorScale = sourceScale ?? targetScale;
199
+ const colorScaleType = colorScale?.type ?? 'sequential';
200
+ const schemeOptions = getSchemeOptions(colorScaleType);
201
+ // Don't read lastColorScaleFieldsRef during render (react-hooks/refs).
202
+ const defaultField = getDefaultColorScaleField(columns, colorScaleType);
203
+ const sourceFlat = getDeckMapLayerFlatColor(layer, 'getSourceColor') ?? [
204
+ ...DECK_MAP_DEFAULT_LAYER_COLOR,
205
+ ];
206
+ const targetFlat = getDeckMapLayerFlatColor(layer, 'getTargetColor') ?? [
207
+ ...DECK_MAP_DEFAULT_LAYER_COLOR,
208
+ ];
209
+ const opacityPercent = Math.round((getDeckMapLayerChannelOpacityPercent(layer, 'getSourceColor', sourceFlat[3] ?? 255) +
210
+ getDeckMapLayerChannelOpacityPercent(layer, 'getTargetColor', targetFlat[3] ?? 255)) /
211
+ 2);
212
+ const applySharedColorScale = (patch) => {
213
+ const preferredField = patch.field ??
214
+ colorScale?.field ??
215
+ lastColorScaleFieldsRef.current.getSourceColor ??
216
+ lastColorScaleFieldsRef.current.getTargetColor;
217
+ const resolved = resolveColorScaleFieldAndType(columns, patch.type ?? colorScale?.type ?? 'sequential', preferredField);
218
+ if (!resolved)
219
+ return;
220
+ const { field, type } = resolved;
221
+ lastColorScaleFieldsRef.current.getSourceColor = field;
222
+ lastColorScaleFieldsRef.current.getTargetColor = field;
223
+ const scheme = patch.scheme ??
224
+ (patch.type && patch.type !== colorScale?.type
225
+ ? undefined
226
+ : type !== colorScale?.type
227
+ ? undefined
228
+ : colorScale?.scheme);
229
+ applyConfig(updateDeckMapLayer(mapConfig, layerIndex, (nextLayer) => {
230
+ const detached = detachDeckMapLayerOpacity(nextLayer);
231
+ const existing = getDeckMapLayerColorScale(detached, 'getSourceColor') ??
232
+ getDeckMapLayerColorScale(detached, 'getTargetColor');
233
+ const source = getDeckMapLayerFlatColor(detached, 'getSourceColor') ?? sourceFlat;
234
+ const target = getDeckMapLayerFlatColor(detached, 'getTargetColor') ?? targetFlat;
235
+ const opacity = existing
236
+ ? getDeckMapColorScaleOpacity(existing)
237
+ : Math.round(((source[3] ?? 255) + (target[3] ?? 255)) / 2) / 255;
238
+ const scale = createDeckMapLayerColorScale({
239
+ field,
240
+ type,
241
+ scheme,
242
+ title: field,
243
+ opacity,
244
+ });
245
+ return {
246
+ ...detached,
247
+ getSourceColor: scale,
248
+ getTargetColor: scale,
249
+ };
250
+ }));
251
+ };
252
+ const setFlatColor = (accessor, color) => {
253
+ applyConfig(setDeckMapLayerFlatColor(mapConfig, layerIndex, accessor, color));
254
+ };
255
+ return (_jsxs("div", { className: "flex flex-col gap-2", children: [_jsxs("div", { className: "flex items-center justify-between gap-2", children: [_jsx("span", { className: "text-xs font-medium", children: "Color scale" }), _jsx(Switch, { checked: Boolean(colorScale), disabled: readOnly || (!colorScale && !defaultField), "aria-label": "Color scale", onCheckedChange: (checked) => {
256
+ if (checked) {
257
+ applySharedColorScale({});
258
+ return;
259
+ }
260
+ if (colorScale?.field) {
261
+ lastColorScaleFieldsRef.current.getSourceColor = colorScale.field;
262
+ lastColorScaleFieldsRef.current.getTargetColor = colorScale.field;
263
+ }
264
+ applyConfig(updateDeckMapLayer(mapConfig, layerIndex, (nextLayer) => {
265
+ const replacements = {};
266
+ if (getDeckMapLayerColorScale(nextLayer, 'getSourceColor')) {
267
+ replacements.getSourceColor = DECK_MAP_DEFAULT_LAYER_COLOR;
268
+ }
269
+ if (getDeckMapLayerColorScale(nextLayer, 'getTargetColor')) {
270
+ replacements.getTargetColor = DECK_MAP_DEFAULT_LAYER_COLOR;
271
+ }
272
+ return replaceDeckMapLayerColorScalesWithFlat(nextLayer, replacements);
273
+ }));
274
+ } })] }), !colorScale ? (_jsxs(_Fragment, { children: [_jsxs("div", { className: "flex items-center justify-between gap-2", children: [_jsx("span", { className: "text-xs font-medium", children: "Source color" }), _jsx(ColorSwatchInput, { "aria-label": "Source color", value: deckMapRgbaToHex(sourceFlat), disabled: readOnly, onChange: (hex) => {
275
+ const next = parseColorString(hex, sourceFlat[3] ?? 255);
276
+ setFlatColor('getSourceColor', next);
277
+ } })] }), _jsxs("div", { className: "flex items-center justify-between gap-2", children: [_jsx("span", { className: "text-xs font-medium", children: "Target color" }), _jsx(ColorSwatchInput, { "aria-label": "Target color", value: deckMapRgbaToHex(targetFlat), disabled: readOnly, onChange: (hex) => {
278
+ const next = parseColorString(hex, targetFlat[3] ?? 255);
279
+ setFlatColor('getTargetColor', next);
280
+ } })] })] })) : (_jsxs("div", { className: "flex flex-col gap-2", children: [columns.length > 0 ? (_jsx(ColumnsProvider, { columns: columns, children: _jsx(Field, { label: "Field", required: true, children: _jsx(ColumnSelector.Colorable, { value: colorScale.field, onChange: (field) => applySharedColorScale({ field }), disabled: readOnly }) }) })) : null, _jsx(Field, { label: "Type", children: _jsxs(Select, { value: colorScaleType, onValueChange: (value) => applySharedColorScale({
281
+ type: value,
282
+ }), children: [_jsx(SelectTrigger, { className: "w-full", children: _jsx(SelectValue, {}) }), _jsx(SelectContent, { children: DECK_MAP_COLOR_SCALE_TYPE_OPTIONS.map((option) => (_jsx(SelectItem, { value: option.value, children: option.label }, option.value))) })] }) }), _jsx(Field, { label: "Scheme", children: _jsxs(Select, { value: colorScale.scheme, onValueChange: (value) => applySharedColorScale({ scheme: value }), children: [_jsx(SelectTrigger, { className: "w-full [&>span]:line-clamp-none", children: _jsx(SelectValue, {}) }), _jsx(SelectContent, { children: schemeOptions.map((scheme) => (_jsx(SelectItem, { value: scheme, children: _jsx(ColorSchemeOptionLabel, { scheme: scheme, type: colorScaleType }) }, scheme))) })] }) })] })), _jsx(AppearanceOpacitySlider, { valuePercent: opacityPercent, disabled: readOnly, onChange: (percent) => {
283
+ applyConfig(updateDeckMapLayer(mapConfig, layerIndex, (nextLayer) => {
284
+ const detached = detachDeckMapLayerOpacity(nextLayer);
285
+ if (colorScale) {
286
+ const scale = getDeckMapLayerColorScale(detached, 'getSourceColor') ??
287
+ getDeckMapLayerColorScale(detached, 'getTargetColor') ??
288
+ colorScale;
289
+ const nextScale = { ...scale, opacity: percent / 100 };
290
+ return {
291
+ ...detached,
292
+ getSourceColor: nextScale,
293
+ getTargetColor: nextScale,
294
+ };
295
+ }
296
+ const alpha = opacityPercentToAlpha(percent);
297
+ const source = getDeckMapLayerFlatColor(detached, 'getSourceColor') ??
298
+ sourceFlat;
299
+ const target = getDeckMapLayerFlatColor(detached, 'getTargetColor') ??
300
+ targetFlat;
301
+ return {
302
+ ...detached,
303
+ getSourceColor: [source[0], source[1], source[2], alpha],
304
+ getTargetColor: [target[0], target[1], target[2], alpha],
305
+ };
306
+ }));
307
+ } })] }));
308
+ };
309
+ const AppearanceExtrusionPanel = ({ layer, columns, mapConfig, layerIndex, applyConfig, readOnly }) => {
310
+ const extruded = getDeckMapLayerExtruded(layer);
311
+ return (_jsxs("div", { className: "flex flex-col gap-2", children: [_jsxs("div", { className: "flex items-center justify-between gap-2", children: [_jsx("span", { className: "text-xs font-medium", children: "Extruded" }), _jsx(Switch, { checked: extruded, onCheckedChange: (checked) => applyConfig(updateDeckMapLayer(mapConfig, layerIndex, (nextLayer) => ({
312
+ ...nextLayer,
313
+ extruded: checked,
314
+ getElevation: checked
315
+ ? (nextLayer.getElevation ?? 1)
316
+ : nextLayer.getElevation,
317
+ }))) })] }), extruded && columns.length > 0 ? (_jsx(ColumnsProvider, { columns: columns, children: _jsx(Field, { label: "Elevation column", children: _jsx(ColumnSelector.Numeric, { value: (() => {
318
+ const elev = layer?.getElevation;
319
+ if (elev &&
320
+ typeof elev === 'object' &&
321
+ '@@function' in elev) {
322
+ return elev.field;
323
+ }
324
+ if (typeof elev === 'string' && elev.startsWith('@@=')) {
325
+ return elev.slice(3);
326
+ }
327
+ return undefined;
328
+ })(), onChange: (elevationColumn) => applyConfig(updateDeckMapLayer(mapConfig, layerIndex, (nextLayer) => ({
329
+ ...nextLayer,
330
+ getElevation: elevationColumn
331
+ ? {
332
+ '@@function': 'scale',
333
+ field: elevationColumn,
334
+ type: 'linear',
335
+ domain: 'auto',
336
+ range: [0, 200],
337
+ }
338
+ : undefined,
339
+ elevationScale: nextLayer.elevationScale ?? 1,
340
+ }))), placeholder: "Select elevation column...", disabled: readOnly }) }) })) : null, extruded ? (_jsx(SettingsSliderField, { label: `Elevation scale: ${layer?.elevationScale ?? 1}x`, children: _jsx(Slider, { min: 0.01, max: 1000, step: 0.01, value: [layer?.elevationScale ?? 1], onValueChange: (values) => {
341
+ const value = values[0] ?? 1;
342
+ applyConfig(updateDeckMapLayer(mapConfig, layerIndex, (nextLayer) => ({
343
+ ...nextLayer,
344
+ elevationScale: value,
345
+ })));
346
+ } }) })) : null] }));
347
+ };
348
+ export const DeckMapSettingsPanel = ({ title, selectedTable, config, tables, onClose, onTableChange, onTitleChange, onConfigChange, readOnly, customConfig = false, preferDatasetSource = false, }) => {
56
349
  const [layerIndex, setLayerIndex] = useState(0);
57
- const [colorAccessor, setColorAccessor] = useState('getFillColor');
58
- const [viewMode, setViewMode] = useState('settings');
350
+ const [viewMode, setViewMode] = useState(customConfig ? 'code' : 'settings');
351
+ // Last color-scale field per accessor (restored when re-enabling a scale).
352
+ const lastColorScaleFieldsRef = useRef({});
59
353
  const selectedDataTable = useMemo(() => selectedTable
60
354
  ? resolveTableReference(tables, selectedTable).table
61
355
  : undefined, [selectedTable, tables]);
@@ -66,7 +360,7 @@ export const DeckMapSettingsPanel = ({ title, selectedTable, config, tables, onC
66
360
  }, [onTableChange, readOnly]);
67
361
  const mapConfig = config;
68
362
  const serializedMapConfig = useMemo(() => JSON.stringify(config, null, 2), [config]);
69
- const showCode = viewMode === 'code';
363
+ const showCode = customConfig || viewMode === 'code';
70
364
  const layers = getDeckMapLayerRecords(mapConfig);
71
365
  const activeLayerIndex = Math.min(layerIndex, Math.max(layers.length - 1, 0));
72
366
  const activeLayer = layers[activeLayerIndex];
@@ -74,9 +368,7 @@ export const DeckMapSettingsPanel = ({ title, selectedTable, config, tables, onC
74
368
  const activeLayerDataset = activeLayerDatasetId
75
369
  ? mapConfig.datasets?.[activeLayerDatasetId]
76
370
  : undefined;
77
- // Resolve the structured source table separately from the compiled dataset
78
- // output. Coordinate selectors edit the source transform, while render
79
- // bindings such as geometry/color/elevation target output columns.
371
+ // Source table (coords/transform) vs compiled output columns (bindings).
80
372
  const activeLayerDatasetSource = activeLayerDataset?.source;
81
373
  const fallbackTableName = isDeckMapTableDatasetSource(activeLayerDatasetSource)
82
374
  ? activeLayerDatasetSource.tableName
@@ -84,7 +376,9 @@ export const DeckMapSettingsPanel = ({ title, selectedTable, config, tables, onC
84
376
  const fallbackTable = useMemo(() => fallbackTableName
85
377
  ? resolveTableReference(tables, fallbackTableName).table
86
378
  : undefined, [fallbackTableName, tables]);
87
- const sourceDataTable = selectedDataTable ?? fallbackTable;
379
+ const sourceDataTable = preferDatasetSource
380
+ ? (fallbackTable ?? selectedDataTable)
381
+ : (selectedDataTable ?? fallbackTable);
88
382
  const sourceColumns = sourceDataTable?.columns ?? EMPTY_COLUMNS;
89
383
  const resolvedActiveLayerDatasetSource = useMemo(() => {
90
384
  if (!activeLayerDatasetSource) {
@@ -96,14 +390,14 @@ export const DeckMapSettingsPanel = ({ title, selectedTable, config, tables, onC
96
390
  return activeLayerDatasetSource;
97
391
  }
98
392
  return {
99
- tableName: selectedDataTable
393
+ tableName: !preferDatasetSource && selectedDataTable
100
394
  ? getTableIdentity(selectedDataTable.table)
101
395
  : activeLayerDatasetSource.tableName,
102
396
  ...(activeLayerDatasetSource.transformSql
103
397
  ? { transformSql: activeLayerDatasetSource.transformSql }
104
398
  : {}),
105
399
  };
106
- }, [activeLayerDatasetSource, selectedDataTable]);
400
+ }, [activeLayerDatasetSource, preferDatasetSource, selectedDataTable]);
107
401
  const datasetSchema = useDeckMapDatasetSchema({
108
402
  source: resolvedActiveLayerDatasetSource,
109
403
  sourceColumns,
@@ -118,181 +412,243 @@ export const DeckMapSettingsPanel = ({ title, selectedTable, config, tables, onC
118
412
  const showColumnRadiusSetting = usesColumnRadiusSetting(activeLayer?.['@@type']);
119
413
  const showTripsSettings = usesTripsSettings(activeLayer?.['@@type']);
120
414
  const showExtrusionSettings = usesExtrusionSettings(activeLayer?.['@@type']);
415
+ const showStrokeSetting = usesStrokeSetting(activeLayer?.['@@type']);
416
+ const strokeEnabled = typeof activeLayer?.stroked === 'boolean'
417
+ ? activeLayer.stroked
418
+ : getDeckMapLayerStrokeDefault(activeLayer?.['@@type'], {
419
+ extruded: getDeckMapLayerExtruded(activeLayer),
420
+ });
421
+ const strokeWidthPixels = activeLayer?.lineWidthUnits === 'pixels' &&
422
+ typeof activeLayer?.getLineWidth === 'number'
423
+ ? activeLayer.getLineWidth
424
+ : (activeLayer?.lineWidthMinPixels ?? 1);
121
425
  const colorAccessorOptions = getDeckMapColorAccessorOptions(activeLayer?.['@@type']);
122
- const effectiveColorAccessor = colorAccessorOptions.find((option) => option.value === colorAccessor)
123
- ?.value ?? colorAccessorOptions[0]?.value;
426
+ const hasFillColor = colorAccessorOptions.some((option) => option.value === 'getFillColor');
427
+ const hasLineColor = colorAccessorOptions.some((option) => option.value === 'getLineColor');
428
+ const hasPathColor = colorAccessorOptions.some((option) => option.value === 'getColor');
429
+ const hasSourceColor = colorAccessorOptions.some((option) => option.value === 'getSourceColor');
430
+ const hasTargetColor = colorAccessorOptions.some((option) => option.value === 'getTargetColor');
431
+ const isPathLayer = activeLayer?.['@@type'] === 'GeoArrowPathLayer';
124
432
  const isGeoJsonLayer = activeLayer?.['@@type'] === 'GeoJsonLayer';
433
+ // Keep unknown types visible if the layer already uses them.
434
+ const activeLayerType = typeof activeLayer?.['@@type'] === 'string'
435
+ ? activeLayer['@@type']
436
+ : undefined;
437
+ const layerTypeOptions = activeLayerType &&
438
+ !DECK_MAP_LAYER_TYPE_OPTIONS.some((option) => option.value === activeLayerType)
439
+ ? [
440
+ ...DECK_MAP_LAYER_TYPE_OPTIONS,
441
+ { value: activeLayerType, label: activeLayerType },
442
+ ]
443
+ : DECK_MAP_LAYER_TYPE_OPTIONS;
444
+ // GeoJsonLayer uses pointRadius* / getPointRadius; scatterplot uses radius* / getRadius.
125
445
  const pointRadiusPixels = isGeoJsonLayer
126
- ? typeof activeLayer?.getPointRadius === 'number'
446
+ ? activeLayer?.pointRadiusUnits === 'pixels' &&
447
+ typeof activeLayer?.getPointRadius === 'number'
127
448
  ? activeLayer.getPointRadius
128
449
  : (activeLayer?.pointRadiusMinPixels ?? 2)
129
450
  : activeLayer?.radiusUnits === 'pixels' &&
130
451
  typeof activeLayer?.getRadius === 'number'
131
452
  ? activeLayer.getRadius
132
453
  : (activeLayer?.radiusMinPixels ?? 2);
133
- const colorScale = effectiveColorAccessor
134
- ? getDeckMapLayerColorScale(activeLayer, effectiveColorAccessor)
135
- : undefined;
136
- const colorScaleType = colorScale?.type ?? 'sequential';
137
- const schemeOptions = getSchemeOptions(colorScaleType);
138
454
  const isHeatmapLayer = activeLayer?.['@@type'] === 'GeoArrowHeatmapLayer';
139
- const firstColumnName = dataOutputColumns[0]?.name;
455
+ const showAppearanceCard = isHeatmapLayer || colorAccessorOptions.length > 0;
456
+ const lineWidthPixels = activeLayer?.widthUnits === 'pixels' &&
457
+ typeof activeLayer?.getWidth === 'number'
458
+ ? activeLayer.getWidth
459
+ : (activeLayer?.widthMinPixels ??
460
+ (showTripsSettings ? 3 : 1));
140
461
  const applyConfig = useCallback((nextConfig) => {
141
462
  if (readOnly)
142
463
  return;
143
464
  onConfigChange(nextConfig);
144
465
  }, [onConfigChange, readOnly]);
145
- const updateColorScale = (patch) => {
146
- const field = patch.field ?? colorScale?.field ?? firstColumnName;
147
- if (!field || !effectiveColorAccessor)
466
+ // Drop point/heatmap radius leftovers on Column layers (pixels vs meters).
467
+ useEffect(() => {
468
+ if (!showColumnRadiusSetting || !activeLayer || readOnly)
148
469
  return;
149
- const type = patch.type ?? colorScale?.type ?? 'sequential';
150
- const scheme = patch.scheme ??
151
- (patch.type && patch.type !== colorScale?.type
152
- ? undefined
153
- : colorScale?.scheme);
154
- applyConfig(setDeckMapLayerColorScale(mapConfig, activeLayerIndex, effectiveColorAccessor, createDeckMapLayerColorScale({
155
- field,
156
- type,
157
- scheme,
158
- title: field,
470
+ const needsHeal = activeLayer.radiusUnits === 'pixels' ||
471
+ activeLayer.getRadius !== undefined ||
472
+ activeLayer.radiusMinPixels !== undefined ||
473
+ activeLayer.radiusMaxPixels !== undefined ||
474
+ activeLayer.radiusPixels !== undefined;
475
+ if (!needsHeal)
476
+ return;
477
+ const radius = typeof activeLayer.radius === 'number' &&
478
+ Number.isFinite(activeLayer.radius) &&
479
+ activeLayer.radius > 0
480
+ ? activeLayer.radius
481
+ : 50;
482
+ applyConfig(setDeckMapLayerColumnRadius(mapConfig, activeLayerIndex, radius));
483
+ }, [
484
+ activeLayer,
485
+ activeLayerIndex,
486
+ applyConfig,
487
+ mapConfig,
488
+ readOnly,
489
+ showColumnRadiusSetting,
490
+ ]);
491
+ // Fix path/arc/trips where widthMaxPixels < widthMinPixels (clamps the slider).
492
+ useEffect(() => {
493
+ const isLineWidthLayer = showTripsSettings ||
494
+ activeLayer?.['@@type'] === 'GeoArrowPathLayer' ||
495
+ showArcColumnSetting;
496
+ if (!isLineWidthLayer || !activeLayer || readOnly)
497
+ return;
498
+ const min = typeof activeLayer.widthMinPixels === 'number'
499
+ ? activeLayer.widthMinPixels
500
+ : undefined;
501
+ const max = typeof activeLayer.widthMaxPixels === 'number'
502
+ ? activeLayer.widthMaxPixels
503
+ : undefined;
504
+ if (min === undefined || max === undefined || max >= min)
505
+ return;
506
+ applyConfig(updateDeckMapLayer(mapConfig, activeLayerIndex, (layer) => ({
507
+ ...layer,
508
+ widthMinPixels: Math.min(min, max),
509
+ widthMaxPixels: Math.max(min, max),
159
510
  })));
511
+ }, [
512
+ activeLayer,
513
+ activeLayerIndex,
514
+ applyConfig,
515
+ mapConfig,
516
+ readOnly,
517
+ showArcColumnSetting,
518
+ showTripsSettings,
519
+ ]);
520
+ const setStrokeWidth = (value) => {
521
+ applyConfig(updateDeckMapLayer(mapConfig, activeLayerIndex, (layer) => {
522
+ const nextLayer = {
523
+ ...layer,
524
+ lineWidthUnits: 'pixels',
525
+ lineWidthMinPixels: value,
526
+ lineWidthMaxPixels: value,
527
+ };
528
+ if (typeof layer.getLineWidth !== 'string') {
529
+ nextLayer.getLineWidth = value;
530
+ }
531
+ return nextLayer;
532
+ }));
533
+ };
534
+ const setLineWidth = (value) => {
535
+ applyConfig(updateDeckMapLayer(mapConfig, activeLayerIndex, (layer) => {
536
+ const nextLayer = {
537
+ ...layer,
538
+ widthUnits: 'pixels',
539
+ widthMinPixels: value,
540
+ widthMaxPixels: value,
541
+ };
542
+ if (typeof layer.getWidth !== 'string') {
543
+ nextLayer.getWidth = value;
544
+ }
545
+ return nextLayer;
546
+ }));
160
547
  };
161
- return (_jsxs("div", { className: "flex h-full min-h-0 flex-col", children: [_jsx(SettingsPanelHeader, { title: "Map settings", className: "shrink-0 p-2", actions: _jsx(DeckMapCodeViewToggleButton, { label: showCode ? 'Show settings' : 'View code', selected: showCode, onClick: () => setViewMode((currentViewMode) => currentViewMode === 'code' ? 'settings' : 'code') }), onClose: onClose, closeLabel: "Close map settings" }), showCode ? (_jsx(DeckMapCodeViewerPanel, { value: serializedMapConfig, copyTooltipLabel: "Copy map config" })) : (_jsx(ScrollArea, { className: "min-h-0 flex-1 [&_[data-radix-scroll-area-viewport]>div]:!block", children: _jsxs("div", { className: "flex flex-col gap-2 p-2 pt-0", children: [_jsx(Field, { label: "Title", children: _jsx("input", { value: title, onChange: (e) => onTitleChange(e.target.value), placeholder: "Map title", disabled: readOnly, className: "border-input placeholder:text-muted-foreground focus-visible:ring-ring h-8 w-full rounded-md border bg-transparent px-3 py-2 text-xs font-medium shadow-sm outline-hidden transition-colors focus-visible:ring-1 disabled:cursor-not-allowed disabled:opacity-50" }) }), _jsx(Field, { label: "Dataset", required: true, children: _jsx(DataTableSelector, { onChange: handleTableChange, tables: tables, value: selectedDataTable, disabled: readOnly }) }), layers.length > 0 && (_jsxs("div", { className: "flex flex-col gap-3", children: [layers.length > 1 && (_jsx(Field, { label: "Layer", children: _jsxs(Select, { value: String(activeLayerIndex), onValueChange: (value) => setLayerIndex(Number(value)), children: [_jsx(SelectTrigger, { className: "w-full", children: _jsx(SelectValue, {}) }), _jsx(SelectContent, { children: layers.map((layer, index) => (_jsx(SelectItem, { value: String(index), children: String(layer.id ?? `Layer ${index + 1}`) }, index))) })] }) })), _jsxs("div", { className: "flex items-center justify-between", children: [_jsx("span", { className: "text-xs font-medium", children: "Visible" }), _jsx(Switch, { checked: activeLayer?.visible !== false, onCheckedChange: (checked) => applyConfig(updateDeckMapLayer(mapConfig, activeLayerIndex, (layer) => ({
548
+ const setPointRadius = (value) => {
549
+ applyConfig(updateDeckMapLayer(mapConfig, activeLayerIndex, (layer) => {
550
+ if (layer['@@type'] === 'GeoJsonLayer') {
551
+ const nextLayer = {
552
+ ...layer,
553
+ pointRadiusUnits: 'pixels',
554
+ pointRadiusMinPixels: value,
555
+ pointRadiusMaxPixels: value,
556
+ };
557
+ if (typeof layer.getPointRadius !== 'string') {
558
+ nextLayer.getPointRadius = value;
559
+ }
560
+ return nextLayer;
561
+ }
562
+ const nextLayer = {
563
+ ...layer,
564
+ radiusUnits: 'pixels',
565
+ radiusMinPixels: value,
566
+ radiusMaxPixels: value,
567
+ };
568
+ if (typeof layer.getRadius !== 'string') {
569
+ nextLayer.getRadius = value;
570
+ }
571
+ return nextLayer;
572
+ }));
573
+ };
574
+ const setColumnRadius = (value) => {
575
+ applyConfig(setDeckMapLayerColumnRadius(mapConfig, activeLayerIndex, value));
576
+ };
577
+ const fillRadiusProps = showRadiusSetting
578
+ ? {
579
+ radiusValue: pointRadiusPixels,
580
+ onRadiusChange: setPointRadius,
581
+ radiusLabel: 'Point radius',
582
+ radiusUnit: 'px',
583
+ radiusMin: 0.1,
584
+ radiusMax: 50,
585
+ radiusStep: 0.1,
586
+ }
587
+ : showColumnRadiusSetting
588
+ ? {
589
+ radiusValue: activeLayer?.radius ?? 50,
590
+ onRadiusChange: setColumnRadius,
591
+ radiusLabel: 'Column radius',
592
+ radiusUnit: 'm',
593
+ radiusMin: 1,
594
+ radiusMax: 10000,
595
+ radiusStep: 1,
596
+ }
597
+ : {};
598
+ return (_jsxs("div", { className: "flex h-full min-h-0 flex-col", children: [_jsx(SettingsPanelHeader, { title: "Map settings", className: "shrink-0 p-2", actions: customConfig ? undefined : (_jsx(DeckMapCodeViewToggleButton, { label: showCode ? 'Show settings' : 'View code', selected: showCode, onClick: () => setViewMode((currentViewMode) => currentViewMode === 'code' ? 'settings' : 'code') })), onClose: onClose, closeLabel: "Close map settings" }), _jsxs("div", { className: "flex shrink-0 flex-col gap-2 px-2 pb-2", children: [_jsx(Field, { label: "Title", children: _jsx("input", { value: title, onChange: (e) => onTitleChange(e.target.value), placeholder: "Map title", disabled: readOnly, className: "border-input placeholder:text-muted-foreground focus-visible:ring-ring h-8 w-full rounded-md border bg-transparent px-3 py-2 text-xs font-medium shadow-sm outline-hidden transition-colors focus-visible:ring-1 disabled:cursor-not-allowed disabled:opacity-50" }) }), _jsx(DeckMapBasemapSelect, { config: config, onConfigChange: onConfigChange, readOnly: readOnly })] }), showCode ? (_jsx(DeckMapCodeViewerPanel, { value: serializedMapConfig, copyTooltipLabel: "Copy map config" })) : (_jsx(ScrollArea, { className: "min-h-0 flex-1 [&_[data-radix-scroll-area-viewport]>div]:!block", children: _jsxs("div", { className: "flex flex-col gap-2 p-2 pt-0", children: [_jsx(Field, { label: "Dataset", required: true, children: _jsx(DataTableSelector, { onChange: handleTableChange, tables: tables, value: sourceDataTable, disabled: readOnly }) }), layers.length > 0 && (_jsxs("div", { className: "flex flex-col gap-3", children: [layers.length > 1 && (_jsx(Field, { label: "Layer", children: _jsxs(Select, { value: String(activeLayerIndex), onValueChange: (value) => setLayerIndex(Number(value)), children: [_jsx(SelectTrigger, { className: "w-full", children: _jsx(SelectValue, {}) }), _jsx(SelectContent, { children: layers.map((layer, index) => (_jsx(SelectItem, { value: String(index), children: String(layer.id ?? `Layer ${index + 1}`) }, index))) })] }) })), _jsxs("div", { className: "flex items-center justify-between", children: [_jsx("span", { className: "text-xs font-medium", children: "Visible" }), _jsx(Switch, { checked: activeLayer?.visible !== false, onCheckedChange: (checked) => applyConfig(updateDeckMapLayer(mapConfig, activeLayerIndex, (layer) => ({
162
599
  ...layer,
163
600
  visible: checked,
164
601
  }))) })] }), _jsx(Field, { label: "Layer type", children: _jsxs(Select, { value: typeof activeLayer?.['@@type'] === 'string'
165
602
  ? activeLayer['@@type']
166
- : undefined, onValueChange: (value) => applyConfig(setDeckMapLayerType(mapConfig, activeLayerIndex, value)), children: [_jsx(SelectTrigger, { className: "w-full", children: _jsx(SelectValue, { placeholder: "Select layer type" }) }), _jsx(SelectContent, { children: DECK_MAP_LAYER_TYPE_OPTIONS.map((option) => (_jsx(SelectItem, { value: option.value, children: option.label }, option.value))) })] }) }), (datasetSchema.isLoading || datasetSchemaErrorMessage) && (_jsx("div", { className: "text-muted-foreground rounded-md border px-2 py-1.5 text-xs", children: datasetSchema.isLoading
603
+ : undefined, onValueChange: (value) => applyConfig(setDeckMapLayerType(mapConfig, activeLayerIndex, value)), children: [_jsx(SelectTrigger, { className: "w-full text-xs", children: _jsx(SelectValue, { placeholder: "Select layer type" }) }), _jsx(SelectContent, { children: layerTypeOptions.map((option) => (_jsx(SelectItem, { value: option.value, className: "text-xs", children: option.label }, option.value))) })] }) }), (datasetSchema.isLoading || datasetSchemaErrorMessage) && (_jsx("div", { className: "text-muted-foreground rounded-md border px-2 py-1.5 text-xs", children: datasetSchema.isLoading
167
604
  ? 'Inspecting dataset schema...'
168
- : `Dataset schema unavailable: ${datasetSchemaErrorMessage}` })), showTripsSettings && (_jsxs(_Fragment, { children: [outputColumns.length > 0 && (_jsx(ColumnsProvider, { columns: outputColumns, children: _jsx(Field, { label: "Timestamp column", required: true, children: _jsx(ColumnSelector, { value: activeLayer?._sqlroomsBinding?.timestampColumn, onChange: (timestampColumn) => applyConfig(setDeckMapLayerTimestampColumn(mapConfig, activeLayerIndex, timestampColumn)), placeholder: "Select timestamp column...", disabled: readOnly }) }) })), _jsx(Field, { label: `Line width: ${activeLayer?.widthMinPixels ?? 3}px`, children: _jsx(Slider, { min: 1, max: 20, step: 1, value: [
169
- activeLayer?.widthMinPixels ??
170
- 3,
171
- ], onValueChange: (values) => {
172
- const value = values[0] ?? 3;
173
- applyConfig(updateDeckMapLayer(mapConfig, activeLayerIndex, (layer) => ({
605
+ : `Dataset schema unavailable: ${datasetSchemaErrorMessage}` })), showAppearanceCard && (_jsx(Field, { label: "Appearance", children: _jsx("div", { className: "flex flex-col gap-2 rounded-md border p-2", children: isHeatmapLayer ? (_jsxs(_Fragment, { children: [_jsx(SettingsSliderField, { label: `Radius: ${activeLayer?.radiusPixels ?? 30}px`, children: _jsx(Slider, { min: 1, max: 100, step: 1, value: [
606
+ activeLayer?.radiusPixels ?? 30,
607
+ ], onValueChange: (values) => {
608
+ const value = values[0] ?? 30;
609
+ applyConfig(updateDeckMapLayer(mapConfig, activeLayerIndex, (layer) => ({
610
+ ...layer,
611
+ radiusPixels: value,
612
+ })));
613
+ } }) }), _jsxs(Select, { value: detectHeatmapScheme(activeLayer?.colorRange), onValueChange: (value) => applyConfig(updateDeckMapLayer(mapConfig, activeLayerIndex, (layer) => ({
174
614
  ...layer,
175
- widthMinPixels: value,
176
- })));
177
- } }) }), _jsx(Field, { label: `Trail length: ${Math.round((activeLayer?._trailLengthFactor ?? 0.4) * 100)}%`, children: _jsx(Slider, { min: 5, max: 100, step: 5, value: [
178
- Math.round((activeLayer?._trailLengthFactor ?? 0.4) * 100),
179
- ], onValueChange: (values) => {
180
- const value = (values[0] ?? 40) / 100;
181
- applyConfig(updateDeckMapLayer(mapConfig, activeLayerIndex, (layer) => ({
182
- ...layer,
183
- _trailLengthFactor: value,
184
- })));
185
- } }) })] })), isHeatmapLayer ? (_jsxs("div", { className: "flex flex-col gap-2 rounded-md border p-2", children: [_jsx("span", { className: "text-xs font-medium", children: "Color scheme (density)" }), _jsx(Field, { label: "Scheme", children: _jsxs(Select, { value: detectHeatmapScheme(activeLayer?.colorRange), onValueChange: (value) => applyConfig(updateDeckMapLayer(mapConfig, activeLayerIndex, (layer) => ({
186
- ...layer,
187
- colorRange: schemeToColorRange(value),
188
- }))), children: [_jsx(SelectTrigger, { className: "w-full", children: _jsx(SelectValue, {}) }), _jsx(SelectContent, { children: continuousSequentialSchemes.map((scheme) => (_jsx(SelectItem, { value: scheme, children: scheme }, scheme))) })] }) })] })) : (_jsxs("div", { className: "flex flex-col gap-2 rounded-md border p-2", children: [_jsxs("div", { className: "flex items-center justify-between gap-2", children: [_jsx("span", { className: "text-xs font-medium", children: "Color scale" }), _jsx(Switch, { checked: Boolean(colorScale), onCheckedChange: (checked) => {
189
- if (!effectiveColorAccessor)
190
- return;
191
- if (checked) {
192
- updateColorScale({});
193
- return;
194
- }
195
- applyConfig(clearDeckMapLayerColorScale(mapConfig, activeLayerIndex, effectiveColorAccessor));
196
- }, disabled: !firstColumnName || !effectiveColorAccessor })] }), effectiveColorAccessor && (_jsx(Field, { label: "Color property", children: _jsxs(Select, { value: effectiveColorAccessor, onValueChange: (value) => setColorAccessor(value), children: [_jsx(SelectTrigger, { className: "w-full", children: _jsx(SelectValue, {}) }), _jsx(SelectContent, { children: colorAccessorOptions.map((option) => (_jsx(SelectItem, { value: option.value, children: option.label }, option.value))) })] }) })), colorScale && dataOutputColumns.length > 0 && (_jsx(ColumnsProvider, { columns: dataOutputColumns, children: _jsx(Field, { label: "Color field", required: true, children: colorScaleType === 'categorical' ? (_jsx(ColumnSelector.Categorical, { value: colorScale.field, onChange: (field) => updateColorScale({ field }), disabled: readOnly })) : (_jsx(ColumnSelector.Quantitative, { value: colorScale.field, onChange: (field) => updateColorScale({ field }), disabled: readOnly })) }) })), colorScale && (_jsxs(_Fragment, { children: [_jsx(Field, { label: "Scale type", children: _jsxs(Select, { value: colorScaleType, onValueChange: (value) => updateColorScale({
197
- type: value,
198
- }), children: [_jsx(SelectTrigger, { className: "w-full", children: _jsx(SelectValue, {}) }), _jsx(SelectContent, { children: DECK_MAP_COLOR_SCALE_TYPE_OPTIONS.map((option) => (_jsx(SelectItem, { value: option.value, children: option.label }, option.value))) })] }) }), _jsx(Field, { label: "Scheme", children: _jsxs(Select, { value: colorScale.scheme, onValueChange: (value) => updateColorScale({
199
- scheme: value,
200
- }), children: [_jsx(SelectTrigger, { className: "w-full", children: _jsx(SelectValue, {}) }), _jsx(SelectContent, { children: schemeOptions.map((scheme) => (_jsx(SelectItem, { value: scheme, children: scheme }, scheme))) })] }) })] }))] }))] })), isHeatmapLayer && (_jsx(Field, { label: `Radius: ${activeLayer?.radiusPixels ?? 30}px`, children: _jsx("div", { className: "pt-0.5", children: _jsx(Slider, { min: 1, max: 100, step: 1, value: [
201
- activeLayer?.radiusPixels ?? 30,
202
- ], onValueChange: (values) => {
203
- const value = values[0] ?? 30;
204
- applyConfig(updateDeckMapLayer(mapConfig, activeLayerIndex, (layer) => ({
205
- ...layer,
206
- radiusPixels: value,
207
- })));
208
- } }) }) })), showRadiusSetting && (_jsx(Field, { label: `Point radius: ${pointRadiusPixels}px`, children: _jsx("div", { className: "pt-0.5", children: _jsx(Slider, { min: 1, max: 50, step: 1, value: [pointRadiusPixels], onValueChange: (values) => {
209
- const value = values[0] ?? 2;
210
- applyConfig(updateDeckMapLayer(mapConfig, activeLayerIndex, (layer) => {
211
- const nextLayer = layer['@@type'] === 'GeoJsonLayer'
212
- ? {
213
- ...layer,
214
- pointRadiusUnits: 'pixels',
215
- pointRadiusMinPixels: value,
216
- pointRadiusMaxPixels: Math.max(value, layer.pointRadiusMaxPixels ?? value),
217
- getPointRadius: value,
218
- }
219
- : {
220
- ...layer,
221
- radiusMinPixels: value,
222
- radiusMaxPixels: Math.max(value, layer.radiusMaxPixels ?? value),
223
- };
224
- if (layer['@@type'] !== 'GeoJsonLayer' &&
225
- layer.radiusUnits === 'pixels' &&
226
- typeof layer.getRadius !== 'string') {
227
- nextLayer.getRadius = value;
228
- }
229
- return nextLayer;
230
- }));
231
- } }) }) })), showColumnRadiusSetting && (_jsx(Field, { label: `Column radius: ${activeLayer?.radius ?? 50}m`, children: _jsx("div", { className: "pt-0.5", children: _jsx(Slider, { min: 1, max: 10000, step: 1, value: [activeLayer?.radius ?? 50], onValueChange: (values) => {
232
- const value = values[0] ?? 50;
233
- applyConfig(updateDeckMapLayer(mapConfig, activeLayerIndex, (layer) => ({
234
- ...layer,
235
- radius: value,
236
- })));
237
- } }) }) })), outputColumns.length > 0 && showGeometryColumnSetting && (_jsx(ColumnsProvider, { columns: outputColumns, children: _jsx(Field, { label: "Geometry column", required: true, children: _jsx(ColumnSelector, { value: activeLayerDataset?.geometryColumn, onChange: (geometryColumn) => applyConfig(setDeckMapLayerGeometryColumn(mapConfig, activeLayerIndex, geometryColumn)), placeholder: "Select geometry column...", disabled: readOnly }) }) })), outputColumns.length > 0 && showH3ColumnSetting && (_jsx(ColumnsProvider, { columns: outputColumns, children: _jsx(Field, { label: "H3 column", required: true, children: _jsx(ColumnSelector, { value: activeLayer?._sqlroomsBinding
238
- ?.hexagonColumn, onChange: (hexagonColumn) => applyConfig(setDeckMapLayerHexagonColumn(mapConfig, activeLayerIndex, hexagonColumn)), placeholder: "Select H3 index column...", disabled: readOnly }) }) })), outputColumns.length > 0 && showArcColumnSetting && (_jsxs(ColumnsProvider, { columns: outputColumns, children: [_jsx(Field, { label: "Source geometry", required: true, children: _jsx(ColumnSelector, { value: activeLayer?._sqlroomsBinding
239
- ?.sourceGeometryColumn, onChange: (sourceGeometryColumn) => applyConfig(setDeckMapLayerArcColumns(mapConfig, activeLayerIndex, {
240
- sourceGeometryColumn,
241
- })), placeholder: "Select source geometry...", disabled: readOnly }) }), _jsx(Field, { label: "Target geometry", required: true, children: _jsx(ColumnSelector, { value: activeLayer?._sqlroomsBinding
242
- ?.targetGeometryColumn, onChange: (targetGeometryColumn) => applyConfig(setDeckMapLayerArcColumns(mapConfig, activeLayerIndex, {
243
- targetGeometryColumn,
244
- })), placeholder: "Select target geometry...", disabled: readOnly }) })] })), showArcColumnSetting && (_jsxs("div", { className: "flex items-center justify-between", children: [_jsx("span", { className: "text-xs font-medium", children: "Flat lines" }), _jsx(Switch, { checked: activeLayer?.getHeight === 0, onCheckedChange: (checked) => applyConfig(updateDeckMapLayer(mapConfig, activeLayerIndex, (layer) => ({
245
- ...layer,
246
- getHeight: checked ? 0 : undefined,
247
- }))) })] })), showArcColumnSetting && (_jsx(Field, { label: `Line width: ${activeLayer?.widthMinPixels ?? 1}px`, children: _jsx(Slider, { min: 1, max: 20, step: 1, value: [
248
- activeLayer?.widthMinPixels ?? 1,
249
- ], onValueChange: (values) => {
250
- const value = values[0] ?? 1;
251
- applyConfig(updateDeckMapLayer(mapConfig, activeLayerIndex, (layer) => ({
252
- ...layer,
253
- widthMinPixels: value,
254
- })));
255
- } }) })), showExtrusionSettings && (_jsxs("div", { className: "flex flex-col gap-2 rounded-md border p-2", children: [_jsxs("div", { className: "flex items-center justify-between gap-2", children: [_jsx("span", { className: "text-xs font-medium", children: "Extrusion" }), _jsx(Switch, { checked: Boolean(activeLayer?.extruded), onCheckedChange: (checked) => applyConfig(updateDeckMapLayer(mapConfig, activeLayerIndex, (layer) => ({
256
- ...layer,
257
- extruded: checked,
258
- getElevation: checked
259
- ? (layer.getElevation ?? 1)
260
- : layer.getElevation,
261
- }))) })] }), Boolean(activeLayer?.extruded) &&
262
- dataOutputColumns.length > 0 && (_jsx(ColumnsProvider, { columns: dataOutputColumns, children: _jsx(Field, { label: "Elevation column", children: _jsx(ColumnSelector.Numeric, { value: (() => {
263
- const elev = activeLayer?.getElevation;
264
- if (elev &&
265
- typeof elev === 'object' &&
266
- '@@function' in elev) {
267
- return elev.field;
268
- }
269
- if (typeof elev === 'string' &&
270
- elev.startsWith('@@=')) {
271
- return elev.slice(3);
272
- }
273
- return undefined;
274
- })(), onChange: (elevationColumn) => applyConfig(updateDeckMapLayer(mapConfig, activeLayerIndex, (layer) => ({
275
- ...layer,
276
- getElevation: elevationColumn
277
- ? {
278
- '@@function': 'scale',
279
- field: elevationColumn,
280
- type: 'linear',
281
- domain: 'auto',
282
- range: [0, 200],
283
- }
284
- : undefined,
285
- elevationScale: layer.elevationScale ?? 1,
286
- }))), placeholder: "Select elevation column...", disabled: readOnly }) }) })), Boolean(activeLayer?.extruded) && (_jsx(Field, { label: `Elevation scale: ${activeLayer?.elevationScale ?? 1}x`, children: _jsx("div", { className: "pt-1.5", children: _jsx(Slider, { min: 0.01, max: 100, step: 0.01, value: [
287
- activeLayer?.elevationScale ??
288
- 1,
289
- ], onValueChange: (values) => {
290
- const value = values[0] ?? 1;
291
- applyConfig(updateDeckMapLayer(mapConfig, activeLayerIndex, (layer) => ({
292
- ...layer,
293
- elevationScale: value,
294
- })));
295
- } }) }) }))] })), sourceDataTable &&
615
+ colorRange: heatmapSchemeToColorRange(value),
616
+ }))), children: [_jsx(SelectTrigger, { className: "w-full [&>span]:line-clamp-none", children: _jsx(SelectValue, {}) }), _jsx(SelectContent, { children: continuousSequentialSchemes.map((scheme) => (_jsx(SelectItem, { value: scheme, children: _jsx(ColorSchemeOptionLabel, { scheme: scheme, type: "sequential" }) }, scheme))) })] }), _jsx(AppearanceOpacitySlider, { valuePercent: getLayerOpacityPercent(activeLayer), disabled: readOnly, onChange: (percent) => {
617
+ applyConfig(updateDeckMapLayer(mapConfig, activeLayerIndex, (layer) => ({
618
+ ...layer,
619
+ opacity: percent / 100,
620
+ })));
621
+ } })] })) : (_jsxs(_Fragment, { children: [(showTripsSettings ||
622
+ isPathLayer ||
623
+ showArcColumnSetting) && (_jsx(SettingsSliderField, { label: `${showTripsSettings ? 'Trip width' : 'Line width'}: ${lineWidthPixels}px`, children: _jsx(Slider, { min: 1, max: 20, step: 1, value: [lineWidthPixels], onValueChange: (values) => setLineWidth(values[0] ?? lineWidthPixels) }) })), showArcColumnSetting ? (_jsxs("div", { className: "flex items-center justify-between gap-2", children: [_jsx("span", { className: "text-xs font-medium", children: "Flat lines" }), _jsx(Switch, { checked: activeLayer?.getHeight === 0, onCheckedChange: (checked) => applyConfig(updateDeckMapLayer(mapConfig, activeLayerIndex, (layer) => ({
624
+ ...layer,
625
+ getHeight: checked ? 0 : undefined,
626
+ }))) })] })) : null, showTripsSettings ? (_jsx(SettingsSliderField, { label: `Trail length: ${Math.round((activeLayer?._trailLengthFactor ?? 0.4) * 100)}%`, children: _jsx(Slider, { min: 5, max: 100, step: 5, value: [
627
+ Math.round((activeLayer?._trailLengthFactor ?? 0.4) * 100),
628
+ ], onValueChange: (values) => {
629
+ const value = (values[0] ?? 40) / 100;
630
+ applyConfig(updateDeckMapLayer(mapConfig, activeLayerIndex, (layer) => ({
631
+ ...layer,
632
+ _trailLengthFactor: value,
633
+ })));
634
+ } }) })) : null, hasFillColor && hasLineColor && showStrokeSetting ? (_jsxs(Tabs, { defaultValue: "fill", className: "w-full", children: [_jsxs(TabsList, { className: `grid h-8 w-full ${showExtrusionSettings ? 'grid-cols-3' : 'grid-cols-2'}`, children: [_jsx(TabsTrigger, { value: "fill", className: "text-xs", children: "Fill" }), _jsx(TabsTrigger, { value: "stroke", className: "text-xs", children: "Stroke" }), showExtrusionSettings ? (_jsx(TabsTrigger, { value: "extrusion", className: "text-xs", children: "Extrusion" })) : null] }), _jsx(TabsContent, { value: "fill", className: "mt-2", children: _jsx(AppearanceColorChannel, { accessor: "getFillColor", layer: activeLayer, columns: dataOutputColumns, mapConfig: mapConfig, layerIndex: activeLayerIndex, applyConfig: applyConfig, lastColorScaleFieldsRef: lastColorScaleFieldsRef, readOnly: readOnly, ...fillRadiusProps }) }), _jsx(TabsContent, { value: "stroke", className: "mt-2", children: _jsx(AppearanceColorChannel, { accessor: "getLineColor", layer: activeLayer, columns: dataOutputColumns, mapConfig: mapConfig, layerIndex: activeLayerIndex, applyConfig: applyConfig, lastColorScaleFieldsRef: lastColorScaleFieldsRef, readOnly: readOnly, enabled: strokeEnabled, onEnabledChange: (checked) => applyConfig(updateDeckMapLayer(mapConfig, activeLayerIndex, (layer) => {
635
+ if (checked) {
636
+ return { ...layer, stroked: true };
637
+ }
638
+ // Clear stroke color scale when stroke is off.
639
+ const next = {
640
+ ...layer,
641
+ stroked: false,
642
+ };
643
+ if (getDeckMapLayerColorScale(next, 'getLineColor')) {
644
+ return replaceDeckMapLayerColorScaleWithFlat(next, 'getLineColor', DECK_MAP_DEFAULT_STROKE_COLOR);
645
+ }
646
+ return next;
647
+ })), enableLabel: "Stroke", defaultFlatColor: DECK_MAP_DEFAULT_STROKE_COLOR, widthPixels: strokeWidthPixels, onWidthChange: setStrokeWidth, widthLabel: "Stroke width", extrusionDisablesStroke: usesStrokeExtrusionWarning(activeLayer?.['@@type']) && getDeckMapLayerExtruded(activeLayer) }) }), showExtrusionSettings ? (_jsx(TabsContent, { value: "extrusion", className: "mt-2", children: _jsx(AppearanceExtrusionPanel, { layer: activeLayer, columns: dataOutputColumns, mapConfig: mapConfig, layerIndex: activeLayerIndex, applyConfig: applyConfig, readOnly: readOnly }) })) : null] }, `appearance-fill-stroke${showExtrusionSettings ? '-extrusion' : ''}`)) : hasFillColor && showExtrusionSettings ? (_jsxs(Tabs, { defaultValue: "fill", className: "w-full", children: [_jsxs(TabsList, { className: "grid h-8 w-full grid-cols-2", children: [_jsx(TabsTrigger, { value: "fill", className: "text-xs", children: "Fill" }), _jsx(TabsTrigger, { value: "extrusion", className: "text-xs", children: "Extrusion" })] }), _jsx(TabsContent, { value: "fill", className: "mt-2", children: _jsx(AppearanceColorChannel, { accessor: "getFillColor", layer: activeLayer, columns: dataOutputColumns, mapConfig: mapConfig, layerIndex: activeLayerIndex, applyConfig: applyConfig, lastColorScaleFieldsRef: lastColorScaleFieldsRef, readOnly: readOnly, ...fillRadiusProps }) }), _jsx(TabsContent, { value: "extrusion", className: "mt-2", children: _jsx(AppearanceExtrusionPanel, { layer: activeLayer, columns: dataOutputColumns, mapConfig: mapConfig, layerIndex: activeLayerIndex, applyConfig: applyConfig, readOnly: readOnly }) })] }, "appearance-fill-extrusion")) : hasFillColor ? (_jsx(AppearanceColorChannel, { accessor: "getFillColor", layer: activeLayer, columns: dataOutputColumns, mapConfig: mapConfig, layerIndex: activeLayerIndex, applyConfig: applyConfig, lastColorScaleFieldsRef: lastColorScaleFieldsRef, readOnly: readOnly, ...fillRadiusProps })) : null, hasSourceColor && hasTargetColor ? (_jsx(AppearanceArcColorPanel, { layer: activeLayer, columns: dataOutputColumns, mapConfig: mapConfig, layerIndex: activeLayerIndex, applyConfig: applyConfig, lastColorScaleFieldsRef: lastColorScaleFieldsRef, readOnly: readOnly })) : null, hasPathColor ? (_jsx(AppearanceColorChannel, { accessor: "getColor", layer: activeLayer, columns: dataOutputColumns, mapConfig: mapConfig, layerIndex: activeLayerIndex, applyConfig: applyConfig, lastColorScaleFieldsRef: lastColorScaleFieldsRef, readOnly: readOnly })) : null] })) }) })), outputColumns.length > 0 && showGeometryColumnSetting && (_jsx(ColumnsProvider, { columns: outputColumns, children: _jsx(Field, { label: "Geometry column", required: true, children: _jsx(ColumnSelector, { value: activeLayerDataset?.geometryColumn, onChange: (geometryColumn) => applyConfig(setDeckMapLayerGeometryColumn(mapConfig, activeLayerIndex, geometryColumn)), placeholder: "Select geometry column...", disabled: readOnly }) }) })), outputColumns.length > 0 && showTripsSettings && (_jsx(ColumnsProvider, { columns: outputColumns, children: _jsx(Field, { label: "Timestamp column", required: true, children: _jsx(ColumnSelector, { value: activeLayer?._sqlroomsBinding?.timestampColumn, onChange: (timestampColumn) => applyConfig(setDeckMapLayerTimestampColumn(mapConfig, activeLayerIndex, timestampColumn)), placeholder: "Select timestamp column...", disabled: readOnly }) }) })), outputColumns.length > 0 && showH3ColumnSetting && (_jsx(ColumnsProvider, { columns: outputColumns, children: _jsx(Field, { label: "H3 column", required: true, children: _jsx(ColumnSelector, { value: activeLayer?._sqlroomsBinding?.hexagonColumn, onChange: (hexagonColumn) => applyConfig(setDeckMapLayerHexagonColumn(mapConfig, activeLayerIndex, hexagonColumn)), placeholder: "Select H3 index column...", disabled: readOnly }) }) })), outputColumns.length > 0 && showArcColumnSetting && (_jsxs(ColumnsProvider, { columns: outputColumns, children: [_jsx(Field, { label: "Source geometry", required: true, children: _jsx(ColumnSelector, { value: activeLayer?._sqlroomsBinding?.sourceGeometryColumn, onChange: (sourceGeometryColumn) => applyConfig(setDeckMapLayerArcColumns(mapConfig, activeLayerIndex, {
648
+ sourceGeometryColumn,
649
+ })), placeholder: "Select source geometry...", disabled: readOnly }) }), _jsx(Field, { label: "Target geometry", required: true, children: _jsx(ColumnSelector, { value: activeLayer?._sqlroomsBinding?.targetGeometryColumn, onChange: (targetGeometryColumn) => applyConfig(setDeckMapLayerArcColumns(mapConfig, activeLayerIndex, {
650
+ targetGeometryColumn,
651
+ })), placeholder: "Select target geometry...", disabled: readOnly }) })] }))] })), sourceDataTable &&
296
652
  !showGeometryColumnSetting &&
297
653
  !showH3ColumnSetting &&
298
654
  !showArcColumnSetting && (_jsxs(ColumnsProvider, { columns: sourceColumns, children: [_jsx(Field, { label: "Latitude column", required: true, children: _jsx(ColumnSelector.Numeric, { value: mapConfig.fitToData?.latitudeColumn, onChange: (latitudeColumn) => {