diff --git a/public/app/features/canvas/elements/ellipse.tsx b/public/app/features/canvas/elements/ellipse.tsx new file mode 100644 index 00000000000..00bf1b8c936 --- /dev/null +++ b/public/app/features/canvas/elements/ellipse.tsx @@ -0,0 +1,185 @@ +import { css } from '@emotion/css'; +import React, { PureComponent } from 'react'; + +import { GrafanaTheme2 } from '@grafana/data'; +import { stylesFactory } from '@grafana/ui'; +import { config } from 'app/core/config'; +import { DimensionContext } from 'app/features/dimensions/context'; +import { ColorDimensionEditor } from 'app/features/dimensions/editors/ColorDimensionEditor'; +import { TextDimensionEditor } from 'app/features/dimensions/editors/TextDimensionEditor'; + +import { CanvasElementItem, CanvasElementProps, defaultBgColor, defaultTextColor } from '../element'; +import { Align, VAlign, EllipseConfig, EllipseData } from '../types'; + +class EllipseDisplay extends PureComponent> { + render() { + const { data } = this.props; + const styles = getStyles(config.theme2, data); + return ( +
+ {data?.text} +
+ ); + } +} + +const getStyles = stylesFactory((theme: GrafanaTheme2, data) => ({ + container: css` + display: table; + position: absolute; + top: 50%; + transform: translateY(-50%); + width: 100%; + height: 100%; + background-color: ${data?.backgroundColor}; + border: ${data?.width}px solid ${data?.borderColor}; + border-radius: 50%; + `, + span: css` + display: table-cell; + vertical-align: ${data?.valign}; + text-align: ${data?.align}; + font-size: ${data?.size}px; + color: ${data?.color}; + `, +})); + +export const ellipseItem: CanvasElementItem = { + id: 'ellipse', + name: 'Ellipse', + description: 'Ellipse', + + display: EllipseDisplay, + + defaultSize: { + width: 160, + height: 160, + }, + + getNewOptions: (options) => ({ + ...options, + config: { + backgroundColor: { + fixed: defaultBgColor, + }, + borderColor: { + fixed: 'transparent', + }, + width: 1, + align: Align.Center, + valign: VAlign.Middle, + color: { + fixed: defaultTextColor, + }, + }, + background: { + color: { + fixed: 'transparent', + }, + }, + }), + + prepareData: (ctx: DimensionContext, cfg: EllipseConfig) => { + const data: EllipseData = { + width: cfg.width, + text: cfg.text ? ctx.getText(cfg.text).value() : '', + align: cfg.align ?? Align.Center, + valign: cfg.valign ?? VAlign.Middle, + size: cfg.size, + }; + + if (cfg.backgroundColor) { + data.backgroundColor = ctx.getColor(cfg.backgroundColor).value(); + } + if (cfg.borderColor) { + data.borderColor = ctx.getColor(cfg.borderColor).value(); + } + if (cfg.color) { + data.color = ctx.getColor(cfg.color).value(); + } + + return data; + }, + + // Heatmap overlay options + registerOptionsUI: (builder) => { + const category = ['Ellipse']; + builder + .addCustomEditor({ + category, + id: 'textSelector', + path: 'config.text', + name: 'Text', + editor: TextDimensionEditor, + }) + .addCustomEditor({ + category, + id: 'config.color', + path: 'config.color', + name: 'Text color', + editor: ColorDimensionEditor, + settings: {}, + defaultValue: {}, + }) + .addRadio({ + category, + path: 'config.align', + name: 'Align text', + settings: { + options: [ + { value: Align.Left, label: 'Left' }, + { value: Align.Center, label: 'Center' }, + { value: Align.Right, label: 'Right' }, + ], + }, + defaultValue: Align.Left, + }) + .addCustomEditor({ + category, + id: 'config.borderColor', + path: 'config.borderColor', + name: 'Ellipse border color', + editor: ColorDimensionEditor, + settings: {}, + defaultValue: {}, + }) + .addNumberInput({ + category, + path: 'config.width', + name: 'Ellipse border width', + settings: { + placeholder: 'Auto', + }, + }) + .addCustomEditor({ + category, + id: 'config.backgroundColor', + path: 'config.backgroundColor', + name: 'Ellipse background color', + editor: ColorDimensionEditor, + settings: {}, + defaultValue: {}, + }) + .addRadio({ + category, + path: 'config.valign', + name: 'Vertical align', + settings: { + options: [ + { value: VAlign.Top, label: 'Top' }, + { value: VAlign.Middle, label: 'Middle' }, + { value: VAlign.Bottom, label: 'Bottom' }, + ], + }, + defaultValue: VAlign.Middle, + }) + .addNumberInput({ + category, + path: 'config.size', + name: 'Text size', + settings: { + placeholder: 'Auto', + }, + }); + }, +}; diff --git a/public/app/features/canvas/registry.ts b/public/app/features/canvas/registry.ts index fad1ebaa292..a99d142d617 100644 --- a/public/app/features/canvas/registry.ts +++ b/public/app/features/canvas/registry.ts @@ -5,6 +5,7 @@ import { buttonItem } from './elements/button'; import { droneFrontItem } from './elements/droneFront'; import { droneSideItem } from './elements/droneSide'; import { droneTopItem } from './elements/droneTop'; +import { ellipseItem } from './elements/ellipse'; import { iconItem } from './elements/icon'; import { metricValueItem } from './elements/metricValue'; import { rectangleItem } from './elements/rectangle'; @@ -22,6 +23,7 @@ export const DEFAULT_CANVAS_ELEMENT_CONFIG: CanvasElementOptions = { export const defaultElementItems = [ metricValueItem, // default for now textItem, + ellipseItem, rectangleItem, iconItem, serverItem, diff --git a/public/app/features/canvas/types.ts b/public/app/features/canvas/types.ts index 32bbe8b35db..b0e667d1024 100644 --- a/public/app/features/canvas/types.ts +++ b/public/app/features/canvas/types.ts @@ -48,6 +48,18 @@ export interface TextConfig { valign: VAlign; } +export interface EllipseConfig extends TextConfig { + backgroundColor?: ColorDimensionConfig; + borderColor?: ColorDimensionConfig; + width?: number; +} + +export interface EllipseData extends TextData { + backgroundColor?: string; + borderColor?: string; + width?: number; +} + export { Placement, Constraint,