diff --git a/public/app/plugins/panel/geomap/editor/StyleEditor.tsx b/public/app/plugins/panel/geomap/editor/StyleEditor.tsx index 56aba8718aa..2828d3a9bc8 100644 --- a/public/app/plugins/panel/geomap/editor/StyleEditor.tsx +++ b/public/app/plugins/panel/geomap/editor/StyleEditor.tsx @@ -31,7 +31,15 @@ import { } from 'app/features/dimensions/editors'; import { ResourceFolderName, defaultTextConfig, MediaType } from 'app/features/dimensions/types'; -import { defaultStyleConfig, GeometryTypeId, StyleConfig, TextAlignment, TextBaseline } from '../style/types'; +import { + HorizontalAlign, + VerticalAlign, + defaultStyleConfig, + GeometryTypeId, + StyleConfig, + TextAlignment, + TextBaseline, +} from '../style/types'; import { styleUsesText } from '../style/utils'; import { LayerContentInfo } from '../utils/getFeatures'; @@ -101,6 +109,14 @@ export const StyleEditor = (props: Props) => { onChange({ ...value, textConfig: { ...value.textConfig, textBaseline: textBaseline } }); }; + const onAlignHorizontalChange = (alignHorizontal: HorizontalAlign) => { + onChange({ ...value, symbolAlign: { ...value.symbolAlign, horizontal: alignHorizontal } }); + }; + + const onAlignVerticalChange = (alignVertical: VerticalAlign) => { + onChange({ ...value, symbolAlign: { ...value.symbolAlign, vertical: alignVertical } }); + }; + const propertyOptions = useObservable(settings?.layerInfo ?? of()); const featuresHavePoints = propertyOptions?.geometryType === GeometryTypeId.Point; const hasTextLabel = styleUsesText(value); @@ -200,24 +216,48 @@ export const StyleEditor = (props: Props) => { /> {!settings?.hideSymbol && ( - - - + <> + + + + + + + + + + )} { "fixed": "img/icons/marker/circle.svg", "mode": "fixed", }, + "symbolAlign": { + "horizontal": "center", + "vertical": "center", + }, "textConfig": { "fontSize": 12, "offsetX": 0, @@ -222,6 +226,10 @@ describe('geomap migrations', () => { "fixed": "img/icons/marker/triangle.svg", "mode": "fixed", }, + "symbolAlign": { + "horizontal": "center", + "vertical": "center", + }, "textConfig": { "fontSize": 12, "offsetX": 0, diff --git a/public/app/plugins/panel/geomap/style/markers.ts b/public/app/plugins/panel/geomap/style/markers.ts index 78da73af7af..293ed5d741b 100644 --- a/public/app/plugins/panel/geomap/style/markers.ts +++ b/public/app/plugins/panel/geomap/style/markers.ts @@ -6,6 +6,7 @@ import { config } from '@grafana/runtime'; import { getPublicOrAbsoluteUrl } from 'app/features/dimensions'; import { defaultStyleConfig, DEFAULT_SIZE, StyleConfigValues, StyleMaker } from './types'; +import { getDisplacement } from './utils'; interface SymbolMaker extends RegistryItem { aliasIds: string[]; @@ -80,11 +81,13 @@ export const textMarker = (cfg: StyleConfigValues) => { export const circleMarker = (cfg: StyleConfigValues) => { const stroke = new Stroke({ color: cfg.color, width: cfg.lineWidth ?? 1 }); + const radius = cfg.size ?? DEFAULT_SIZE; return new Style({ image: new Circle({ stroke, fill: getFillColor(cfg), - radius: cfg.size ?? DEFAULT_SIZE, + radius, + displacement: getDisplacement(cfg.symbolAlign ?? defaultStyleConfig.symbolAlign, radius), }), text: textLabel(cfg), stroke, // in case lines are sent to the markers layer @@ -153,7 +156,9 @@ const makers: SymbolMaker[] = [ fill: getFillColor(cfg), points: 4, radius, - rotation: (rotation * Math.PI) / 180 + Math.PI / 4, + angle: Math.PI / 4, + rotation: (rotation * Math.PI) / 180, + displacement: getDisplacement(cfg.symbolAlign ?? defaultStyleConfig.symbolAlign, radius), }), text: textLabel(cfg), }); @@ -174,6 +179,7 @@ const makers: SymbolMaker[] = [ radius, rotation: (rotation * Math.PI) / 180, angle: 0, + displacement: getDisplacement(cfg.symbolAlign ?? defaultStyleConfig.symbolAlign, radius), }), text: textLabel(cfg), }); @@ -195,6 +201,7 @@ const makers: SymbolMaker[] = [ radius2: radius * 0.4, angle: 0, rotation: (rotation * Math.PI) / 180, + displacement: getDisplacement(cfg.symbolAlign ?? defaultStyleConfig.symbolAlign, radius), }), text: textLabel(cfg), }); @@ -215,6 +222,7 @@ const makers: SymbolMaker[] = [ radius2: 0, angle: 0, rotation: (rotation * Math.PI) / 180, + displacement: getDisplacement(cfg.symbolAlign ?? defaultStyleConfig.symbolAlign, radius), }), text: textLabel(cfg), }); @@ -233,7 +241,9 @@ const makers: SymbolMaker[] = [ points: 4, radius, radius2: 0, - rotation: (rotation * Math.PI) / 180 + Math.PI / 4, + angle: Math.PI / 4, + rotation: (rotation * Math.PI) / 180, + displacement: getDisplacement(cfg.symbolAlign ?? defaultStyleConfig.symbolAlign, radius), }), text: textLabel(cfg), }); @@ -315,6 +325,7 @@ export async function getMarkerMaker(symbol?: string, hasTextLabel?: boolean): P opacity: cfg.opacity ?? 1, scale: (DEFAULT_SIZE + radius) / 100, rotation: (rotation * Math.PI) / 180, + displacement: getDisplacement(cfg.symbolAlign ?? defaultStyleConfig.symbolAlign, radius / 2), }), text: !cfg?.text ? undefined : textLabel(cfg), }), diff --git a/public/app/plugins/panel/geomap/style/types.ts b/public/app/plugins/panel/geomap/style/types.ts index 6f2a22ba732..cf8bc2c96ce 100644 --- a/public/app/plugins/panel/geomap/style/types.ts +++ b/public/app/plugins/panel/geomap/style/types.ts @@ -29,6 +29,7 @@ export interface StyleConfig { // Used for points and dynamic text size?: ScaleDimensionConfig; symbol?: ResourceDimensionConfig; + symbolAlign?: SymbolAlign; // Can show markers and text together! text?: TextDimensionConfig; @@ -50,6 +51,16 @@ export enum TextBaseline { Middle = 'middle', Bottom = 'bottom', } +export enum HorizontalAlign { + Left = 'left', + Center = 'center', + Right = 'right', +} +export enum VerticalAlign { + Top = 'top', + Center = 'center', + Bottom = 'bottom', +} export const defaultStyleConfig = Object.freeze({ size: { @@ -65,6 +76,10 @@ export const defaultStyleConfig = Object.freeze({ mode: ResourceDimensionMode.Fixed, fixed: 'img/icons/marker/circle.svg', }, + symbolAlign: { + horizontal: HorizontalAlign.Center, + vertical: VerticalAlign.Center, + }, textConfig: { fontSize: 12, textAlign: TextAlignment.Center, @@ -80,6 +95,11 @@ export const defaultStyleConfig = Object.freeze({ }, }); +export interface SymbolAlign { + horizontal?: HorizontalAlign; + vertical?: VerticalAlign; +} + /** * Static options for text display. See: * https://openlayers.org/en/latest/apidoc/module-ol_style_Text.html @@ -99,6 +119,7 @@ export interface StyleConfigValues { lineWidth?: number; size?: number; symbol?: string; // the point symbol + symbolAlign?: SymbolAlign; rotation?: number; text?: string; diff --git a/public/app/plugins/panel/geomap/style/utils.test.ts b/public/app/plugins/panel/geomap/style/utils.test.ts index 15546670b82..2b1e1f78e35 100644 --- a/public/app/plugins/panel/geomap/style/utils.test.ts +++ b/public/app/plugins/panel/geomap/style/utils.test.ts @@ -1,7 +1,7 @@ import { ResourceDimensionMode } from '@grafana/schema'; -import { StyleConfig } from './types'; -import { getStyleConfigState } from './utils'; +import { HorizontalAlign, VerticalAlign, StyleConfig, SymbolAlign } from './types'; +import { getDisplacement, getStyleConfigState } from './utils'; describe('style utils', () => { it('should fill in default values', async () => { @@ -41,6 +41,10 @@ describe('style utils', () => { "opacity": 0.4, "rotation": 0, "size": 5, + "symbolAlign": { + "horizontal": "center", + "vertical": "center", + }, }, "config": null, "fields": { @@ -52,4 +56,22 @@ describe('style utils', () => { } `); }); + it('should return correct displacement array for top left', async () => { + const symbolAlign: SymbolAlign = { horizontal: HorizontalAlign.Left, vertical: VerticalAlign.Top }; + const radius = 10; + const displacement = getDisplacement(symbolAlign, radius); + expect(displacement).toEqual([-10, 10]); + }); + it('should return correct displacement array for bottom right', async () => { + const symbolAlign: SymbolAlign = { horizontal: HorizontalAlign.Right, vertical: VerticalAlign.Bottom }; + const radius = 10; + const displacement = getDisplacement(symbolAlign, radius); + expect(displacement).toEqual([10, -10]); + }); + it('should return correct displacement array for center center', async () => { + const symbolAlign: SymbolAlign = { horizontal: HorizontalAlign.Center, vertical: VerticalAlign.Center }; + const radius = 10; + const displacement = getDisplacement(symbolAlign, radius); + expect(displacement).toEqual([0, 0]); + }); }); diff --git a/public/app/plugins/panel/geomap/style/utils.ts b/public/app/plugins/panel/geomap/style/utils.ts index 9075b271339..8116fdead31 100644 --- a/public/app/plugins/panel/geomap/style/utils.ts +++ b/public/app/plugins/panel/geomap/style/utils.ts @@ -2,7 +2,15 @@ import { config } from '@grafana/runtime'; import { TextDimensionMode } from '@grafana/schema'; import { getMarkerMaker } from './markers'; -import { defaultStyleConfig, StyleConfig, StyleConfigFields, StyleConfigState } from './types'; +import { + HorizontalAlign, + VerticalAlign, + defaultStyleConfig, + StyleConfig, + StyleConfigFields, + StyleConfigState, + SymbolAlign, +} from './types'; /** Indicate if the style wants to show text values */ export function styleUsesText(config: StyleConfig): boolean { @@ -37,6 +45,7 @@ export async function getStyleConfigState(cfg?: StyleConfig): Promise