From bf85ae44a2f09cb008bae1764bcbbdd4192ba360 Mon Sep 17 00:00:00 2001 From: Ryan McKinley Date: Wed, 17 Nov 2021 16:05:10 -0800 Subject: [PATCH] Geomap: simplify styles (#41842) --- .../panel/geomap/layers/data/markersLayer.tsx | 47 +++++++------ .../app/plugins/panel/geomap/style/types.ts | 23 +++++++ .../plugins/panel/geomap/style/utils.test.ts | 54 +++++++++++++++ .../app/plugins/panel/geomap/style/utils.ts | 66 ++++++++++++++++--- .../plugins/panel/geomap/utils/getFeatures.ts | 44 +++++-------- 5 files changed, 174 insertions(+), 60 deletions(-) create mode 100644 public/app/plugins/panel/geomap/style/utils.test.ts diff --git a/public/app/plugins/panel/geomap/layers/data/markersLayer.tsx b/public/app/plugins/panel/geomap/layers/data/markersLayer.tsx index f46ce0437f6..bbb3438fd33 100644 --- a/public/app/plugins/panel/geomap/layers/data/markersLayer.tsx +++ b/public/app/plugins/panel/geomap/layers/data/markersLayer.tsx @@ -16,10 +16,10 @@ import { getScaledDimension, getColorDimension, getTextDimension } from 'app/fea import { ObservablePropsWrapper } from '../../components/ObservablePropsWrapper'; import { MarkersLegend, MarkersLegendProps } from './MarkersLegend'; import { ReplaySubject } from 'rxjs'; -import { FeaturesStylesBuilderConfig, getFeatures } from '../../utils/getFeatures'; -import { getMarkerMaker } from '../../style/markers'; -import { defaultStyleConfig, StyleConfig } from '../../style/types'; +import { getFeatures } from '../../utils/getFeatures'; +import { defaultStyleConfig, StyleConfig, StyleDimensions } from '../../style/types'; import { StyleEditor } from './StyleEditor'; +import { getStyleConfigState } from '../../style/utils'; // Configuration options for Circle overlays export interface MarkersConfig { @@ -73,9 +73,11 @@ export const markersLayer: MapLayerRegistryItem = { legend = ; } - const style = config.style ?? defaultStyleConfig; - const hasTextLabel = Boolean(style.text?.fixed || style.text?.field); - const markerMaker = await getMarkerMaker(style.symbol?.fixed, hasTextLabel); + // Set the default style + const style = await getStyleConfigState(config.style); + if (!style.fields) { + vectorLayer.setStyle(style.maker(style.base)); + } return { init: () => vectorLayer, @@ -94,24 +96,21 @@ export const markersLayer: MapLayerRegistryItem = { continue; // ??? } - const colorDim = getColorDimension(frame, style.color ?? defaultStyleConfig.color, theme); - const sizeDim = getScaledDimension(frame, style.size ?? defaultStyleConfig.size); - let textDim = undefined; - if (style?.text && (style.text.field || style.text.fixed)) { - textDim = getTextDimension(frame, style.text); + if (style.fields) { + const dims: StyleDimensions = {}; + if (style.fields.color) { + dims.color = getColorDimension(frame, style.config.color ?? defaultStyleConfig.color, theme); + } + if (style.fields.size) { + dims.size = getScaledDimension(frame, style.config.size ?? defaultStyleConfig.size); + } + if (style.fields.text) { + dims.text = getTextDimension(frame, style.config.text!); + } + style.dims = dims; } - const opacity = style?.opacity ?? defaultStyleConfig.opacity; - - const featureDimensionConfig: FeaturesStylesBuilderConfig = { - colorDim: colorDim, - sizeDim: sizeDim, - textDim: textDim, - textConfig: style?.textConfig, - opacity: opacity, - styleMaker: markerMaker, - }; - const frameFeatures = getFeatures(frame, info, featureDimensionConfig); + const frameFeatures = getFeatures(frame, info, style); if (frameFeatures) { features.push(...frameFeatures); @@ -120,8 +119,8 @@ export const markersLayer: MapLayerRegistryItem = { // Post updates to the legend component if (legend) { legendProps.next({ - color: colorDim, - size: sizeDim, + color: style.dims?.color, + size: style.dims?.size, }); } break; // Only the first frame for now! diff --git a/public/app/plugins/panel/geomap/style/types.ts b/public/app/plugins/panel/geomap/style/types.ts index d40cbb1ffaa..29dc302947f 100644 --- a/public/app/plugins/panel/geomap/style/types.ts +++ b/public/app/plugins/panel/geomap/style/types.ts @@ -1,5 +1,6 @@ import { ColorDimensionConfig, + DimensionSupplier, ResourceDimensionConfig, ResourceDimensionMode, ScaleDimensionConfig, @@ -93,6 +94,28 @@ export interface StyleConfigValues { textConfig?: TextStyleConfig; } +/** When the style depends on a field */ +export interface StyleConfigFields { + color?: string; + size?: string; + text?: string; +} + +export interface StyleDimensions { + color?: DimensionSupplier; + size?: DimensionSupplier; + text?: DimensionSupplier; +} + +export interface StyleConfigState { + config: StyleConfig; + hasText?: boolean; + base: StyleConfigValues; + fields?: StyleConfigFields; + dims?: StyleDimensions; + maker: StyleMaker; +} + /** * Given values create a style */ diff --git a/public/app/plugins/panel/geomap/style/utils.test.ts b/public/app/plugins/panel/geomap/style/utils.test.ts new file mode 100644 index 00000000000..b046c705797 --- /dev/null +++ b/public/app/plugins/panel/geomap/style/utils.test.ts @@ -0,0 +1,54 @@ +import { ResourceDimensionMode } from 'app/features/dimensions'; +import { StyleConfig } from './types'; +import { getStyleConfigState } from './utils'; + +describe('style utils', () => { + it('should fill in default values', async () => { + const cfg: StyleConfig = { + color: { + field: 'Price', + fixed: 'dark-green', + }, + opacity: 0.4, + size: { + field: 'Count', + fixed: 5, + max: 15, + min: 2, + }, + symbol: { + fixed: 'img/icons/marker/star.svg', + mode: ResourceDimensionMode.Fixed, // 'fixed', + }, + textConfig: { + fontSize: 12, + offsetX: 0, + offsetY: 0, + // textAlign: 'center', + // textBaseline: 'middle', + }, + }; + + const state = await getStyleConfigState(cfg); + state.config = null as any; // not interesting in the snapshot + expect(state.hasText).toBe(false); + expect(state).toMatchInlineSnapshot(` + Object { + "base": Object { + "color": "#37872D", + "lineWidth": 1, + "opacity": 0.4, + "rotation": 0, + "size": 5, + }, + "config": null, + "fields": Object { + "color": "Price", + "size": "Count", + }, + "hasText": false, + "maker": [Function], + } + `); + }); +}); diff --git a/public/app/plugins/panel/geomap/style/utils.ts b/public/app/plugins/panel/geomap/style/utils.ts index 8bd9169ffcf..f21fa5c2a43 100644 --- a/public/app/plugins/panel/geomap/style/utils.ts +++ b/public/app/plugins/panel/geomap/style/utils.ts @@ -1,18 +1,64 @@ -import { StyleConfig } from './types'; +import { config } from '@grafana/runtime'; +import { TextDimensionMode } from 'app/features/dimensions'; +import { getMarkerMaker } from './markers'; +import { defaultStyleConfig, StyleConfig, StyleConfigFields, StyleConfigState } from './types'; + +/** Indicate if the style wants to show text values */ +export function styleUsesText(config: StyleConfig): boolean { + const { text } = config; + if (!text) { + return false; + } + if (text.mode === TextDimensionMode.Fixed && text.fixed?.length) { + return true; + } + if (text.mode === TextDimensionMode.Field && text.field?.length) { + return true; + } + return false; +} /** Return a distinct list of fields used to dynamically change the style */ -export function getDependantFields(config: StyleConfig): Set | undefined { - const fields = new Set(); +export async function getStyleConfigState(cfg?: StyleConfig): Promise { + if (!cfg) { + cfg = defaultStyleConfig; + } + const hasText = styleUsesText(cfg); + const fields: StyleConfigFields = {}; + const maker = await getMarkerMaker(cfg.symbol?.fixed, hasText); + const state: StyleConfigState = { + config: cfg, // raw values + hasText, + fields, + base: { + color: config.theme2.visualization.getColorByName(cfg.color?.fixed ?? defaultStyleConfig.color.fixed), + opacity: cfg.opacity ?? defaultStyleConfig.opacity, + lineWidth: cfg.lineWidth ?? 1, + size: cfg.size?.fixed ?? defaultStyleConfig.size.fixed, + rotation: 0, // dynamic will follow path + }, + maker, + }; - if (config.color?.field) { - fields.add(config.color.field); + if (cfg.color?.field?.length) { + fields.color = cfg.color.field; } - if (config.size?.field) { - fields.add(config.size.field); + if (cfg.size?.field?.length) { + fields.size = cfg.size.field; } - if (config.text?.field) { - fields.add(config.text.field); + + if (hasText) { + state.base.text = cfg.text?.fixed; + state.base.textConfig = cfg.textConfig ?? defaultStyleConfig.textConfig; + + if (cfg.text?.field?.length) { + fields.text = cfg.text.field; + } } - return fields; + // Clear the fields if possible + if (!Object.keys(fields).length) { + state.fields = undefined; + } + return state; } diff --git a/public/app/plugins/panel/geomap/utils/getFeatures.ts b/public/app/plugins/panel/geomap/utils/getFeatures.ts index 09d87ac5039..89fb3adee01 100644 --- a/public/app/plugins/panel/geomap/utils/getFeatures.ts +++ b/public/app/plugins/panel/geomap/utils/getFeatures.ts @@ -1,41 +1,20 @@ import { DataFrame } from '@grafana/data'; -import { DimensionSupplier } from 'app/features/dimensions'; import { Feature } from 'ol'; import { Point } from 'ol/geom'; -import { StyleMaker, TextStyleConfig } from '../style/types'; +import { StyleConfigState } from '../style/types'; import { LocationInfo } from './location'; -export interface FeaturesStylesBuilderConfig { - colorDim: DimensionSupplier; - sizeDim: DimensionSupplier; - opacity: number; - styleMaker: StyleMaker; - textDim?: DimensionSupplier; - textConfig?: TextStyleConfig; -} - export const getFeatures = ( frame: DataFrame, info: LocationInfo, - config: FeaturesStylesBuilderConfig + style: StyleConfigState ): Array> | undefined => { const features: Array> = []; - const opacity = config.opacity; + const { dims } = style; + const values = { ...style.base }; // Map each data value into new points for (let i = 0; i < frame.length; i++) { - // Get the color for the feature based on color scheme - const color = config.colorDim.get(i); - - // Get the size for the feature based on size dimension - const size = config.sizeDim.get(i); - - // Get the text for the feature based on text dimension - const text = config?.textDim ? config?.textDim.get(i) : undefined; - - // Get the textConfig - const textConfig = config?.textConfig; - // Create a new Feature for each point returned from dataFrameToPoints const dot = new Feature(info.points[i]); dot.setProperties({ @@ -43,7 +22,20 @@ export const getFeatures = ( rowIndex: i, }); - dot.setStyle(config.styleMaker({ color, size, text, opacity, textConfig })); + // Update values used in dynamic styles + if (dims) { + if (dims.color) { + values.color = dims.color.get(i); + } + if (dims.size) { + values.size = dims.size.get(i); + } + if (dims.text) { + values.text = dims.text.get(i); + } + + dot.setStyle(style.maker(values)); + } features.push(dot); }