diff --git a/app/(studio)/types.ts b/app/(studio)/types.ts index 7715845..6456ec6 100644 --- a/app/(studio)/types.ts +++ b/app/(studio)/types.ts @@ -107,6 +107,8 @@ export interface IndividualLabelOverride { id: string; // Unique identifier: "symbol-{index}" or "choropleth-{featureId}" x?: number; // Override x position (SVG coordinates) y?: number; // Override y position (SVG coordinates) + offsetX?: number; // Per-label x offset in pixels + offsetY?: number; // Per-label y offset in pixels fontFamily?: string; fontStyle?: 'normal' | 'italic'; fontWeight?: 'normal' | 'bold'; diff --git a/components/label-editor-toolbar.tsx b/components/label-editor-toolbar.tsx index 1927f48..a084964 100644 --- a/components/label-editor-toolbar.tsx +++ b/components/label-editor-toolbar.tsx @@ -154,6 +154,8 @@ export const LabelEditorToolbar: React.FC = ({ }; const alignment = getAlignmentFromOverride(); + const offsetX = currentOverride.offsetX ?? 0; + const offsetY = currentOverride.offsetY ?? 0; const updateOverride = (updates: Partial) => { const currentOverrides = stylingSettings.individualLabelOverrides || {}; @@ -437,6 +439,39 @@ export const LabelEditorToolbar: React.FC = ({ )} + +
+
+ + + updateOverride({ offsetX: value[0], x: undefined, y: undefined }) + } + min={-50} + max={50} + step={1} + /> +
+
+ + + updateOverride({ offsetY: value[0], x: undefined, y: undefined }) + } + min={-50} + max={50} + step={1} + /> +
+
); diff --git a/modules/map-preview/labels.ts b/modules/map-preview/labels.ts index 3ea4f77..860dc52 100644 --- a/modules/map-preview/labels.ts +++ b/modules/map-preview/labels.ts @@ -277,10 +277,16 @@ export const renderSymbolLabels = ({ }); // Apply position override if exists, otherwise use calculated position - 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; + const globalOffsetX = stylingSettings.symbol.labelOffsetX ?? 0; + const globalOffsetY = stylingSettings.symbol.labelOffsetY ?? 0; + const individualOffsetX = override?.offsetX ?? 0; + const individualOffsetY = override?.offsetY ?? 0; + const finalX = hasPositionOverride + ? override!.x! + : projected[0] + position.dx + globalOffsetX + individualOffsetX; + const finalY = hasPositionOverride + ? override!.y! + : projected[1] + position.dy + globalOffsetY + individualOffsetY; // Use override textAnchor/dominantBaseline if provided, otherwise use calculated position const finalTextAnchor = textAnchor ?? position.anchor; @@ -513,10 +519,16 @@ export const renderChoroplethLabels = ({ }; // Apply position override if exists, otherwise use centroid - 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; + const globalOffsetX = stylingSettings.choropleth.labelOffsetX ?? 0; + const globalOffsetY = stylingSettings.choropleth.labelOffsetY ?? 0; + const individualOffsetX = override?.offsetX ?? 0; + const individualOffsetY = override?.offsetY ?? 0; + const finalX = hasPositionOverride + ? override!.x! + : centroid[0] + globalOffsetX + individualOffsetX; + const finalY = hasPositionOverride + ? override!.y! + : centroid[1] + globalOffsetY + individualOffsetY; textElement .attr('x', finalX)