From ba27fb499a45fe8082234331f589cb462adea207 Mon Sep 17 00:00:00 2001 From: svickars Date: Mon, 10 Aug 2026 12:16:08 -0700 Subject: [PATCH] Add X and Y offset sliders to the labels styling panel. Expose global label offset controls for symbol and choropleth maps so users can nudge label positions without moving each label individually. Co-authored-by: Cursor --- app/(studio)/types.ts | 4 +++ components/map-styling.tsx | 62 +++++++++++++++++++++++++++++++++++ modules/map-preview/labels.ts | 12 ++++--- state/studio-store.ts | 4 +++ 4 files changed, 78 insertions(+), 4 deletions(-) diff --git a/app/(studio)/types.ts b/app/(studio)/types.ts index 0eb88ec..7715845 100644 --- a/app/(studio)/types.ts +++ b/app/(studio)/types.ts @@ -191,6 +191,8 @@ export interface StylingSettings { labelOutlineColor: string; labelFontSize: number; labelOutlineThickness: number; + labelOffsetX: number; + labelOffsetY: number; labelAlignment: | 'auto' | 'top-left' @@ -214,6 +216,8 @@ export interface StylingSettings { labelOutlineColor: string; labelFontSize: number; labelOutlineThickness: number; + labelOffsetX: number; + labelOffsetY: number; }; individualLabelOverrides?: Record; drawnPaths?: DrawnPath[]; // Array of user-drawn paths diff --git a/components/map-styling.tsx b/components/map-styling.tsx index c8c4888..8f21ea7 100644 --- a/components/map-styling.tsx +++ b/components/map-styling.tsx @@ -110,6 +110,8 @@ interface StylingSettings { labelOutlineColor: string; labelFontSize: number; labelOutlineThickness: number; + labelOffsetX: number; + labelOffsetY: number; labelAlignment: | 'auto' | 'top-left' @@ -133,6 +135,8 @@ interface StylingSettings { labelOutlineColor: string; labelFontSize: number; labelOutlineThickness: number; + labelOffsetX: number; + labelOffsetY: number; }; } @@ -1025,6 +1029,35 @@ export function MapStyling({ /> + +
+
+ + updateSetting('symbol', 'labelOffsetX', value[0])} + min={-50} + max={50} + step={1} + /> +
+
+ + updateSetting('symbol', 'labelOffsetY', value[0])} + min={-50} + max={50} + step={1} + /> +
+
@@ -1207,6 +1240,35 @@ export function MapStyling({ />
+ +
+
+ + updateSetting('choropleth', 'labelOffsetX', value[0])} + min={-50} + max={50} + step={1} + /> +
+
+ + updateSetting('choropleth', 'labelOffsetY', value[0])} + min={-50} + max={50} + step={1} + /> +
+
)} diff --git a/modules/map-preview/labels.ts b/modules/map-preview/labels.ts index b1d4ee9..3ea4f77 100644 --- a/modules/map-preview/labels.ts +++ b/modules/map-preview/labels.ts @@ -277,8 +277,10 @@ export const renderSymbolLabels = ({ }); // Apply position override if exists, otherwise use calculated position - const finalX = hasPositionOverride ? override!.x! : projected[0] + position.dx; - const finalY = hasPositionOverride ? override!.y! : projected[1] + position.dy; + const offsetX = stylingSettings.symbol.labelOffsetX ?? 0; + const offsetY = stylingSettings.symbol.labelOffsetY ?? 0; + const finalX = hasPositionOverride ? override!.x! : projected[0] + position.dx + offsetX; + const finalY = hasPositionOverride ? override!.y! : projected[1] + position.dy + offsetY; // Use override textAnchor/dominantBaseline if provided, otherwise use calculated position const finalTextAnchor = textAnchor ?? position.anchor; @@ -511,8 +513,10 @@ export const renderChoroplethLabels = ({ }; // Apply position override if exists, otherwise use centroid - const finalX = hasPositionOverride ? override!.x! : centroid[0]; - const finalY = hasPositionOverride ? override!.y! : centroid[1]; + const offsetX = stylingSettings.choropleth.labelOffsetX ?? 0; + const offsetY = stylingSettings.choropleth.labelOffsetY ?? 0; + const finalX = hasPositionOverride ? override!.x! : centroid[0] + offsetX; + const finalY = hasPositionOverride ? override!.y! : centroid[1] + offsetY; textElement .attr('x', finalX) diff --git a/state/studio-store.ts b/state/studio-store.ts index bcdd88e..35b5bc2 100644 --- a/state/studio-store.ts +++ b/state/studio-store.ts @@ -194,6 +194,8 @@ const createDefaultStylingSettings = (): StylingSettings => ({ labelOutlineColor: '#ffffff', labelFontSize: 10, labelOutlineThickness: 0, + labelOffsetX: 0, + labelOffsetY: 0, labelAlignment: 'auto', customSvgPath: '', }, @@ -207,6 +209,8 @@ const createDefaultStylingSettings = (): StylingSettings => ({ labelOutlineColor: '#ffffff', labelFontSize: 10, labelOutlineThickness: 0, + labelOffsetX: 0, + labelOffsetY: 0, }, individualLabelOverrides: {}, drawnPaths: [],