@sqlrooms/deck 0.29.0-rc.10 → 0.29.0-rc.12

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