Geomap: simplify styles (#41842)

This commit is contained in:
Ryan McKinley 2021-11-17 16:05:10 -08:00 committed by GitHub
parent 40d3072df2
commit bf85ae44a2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 175 additions and 61 deletions

View File

@ -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<MarkersConfig> = {
legend = <ObservablePropsWrapper watch={legendProps} initialSubProps={{}} child={MarkersLegend} />;
}
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<MarkersConfig> = {
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<MarkersConfig> = {
// 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!

View File

@ -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<string>;
size?: DimensionSupplier<number>;
text?: DimensionSupplier<string>;
}
export interface StyleConfigState {
config: StyleConfig;
hasText?: boolean;
base: StyleConfigValues;
fields?: StyleConfigFields;
dims?: StyleDimensions;
maker: StyleMaker;
}
/**
* Given values create a style
*/

View File

@ -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],
}
`);
});
});

View File

@ -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<string> | undefined {
const fields = new Set<string>();
export async function getStyleConfigState(cfg?: StyleConfig): Promise<StyleConfigState> {
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 (config.text?.field) {
fields.add(config.text.field);
if (cfg.size?.field?.length) {
fields.size = cfg.size.field;
}
return fields;
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;
}
}
// Clear the fields if possible
if (!Object.keys(fields).length) {
state.fields = undefined;
}
return state;
}

View File

@ -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<string>;
sizeDim: DimensionSupplier<number>;
opacity: number;
styleMaker: StyleMaker;
textDim?: DimensionSupplier<string>;
textConfig?: TextStyleConfig;
}
export const getFeatures = (
frame: DataFrame,
info: LocationInfo,
config: FeaturesStylesBuilderConfig
style: StyleConfigState
): Array<Feature<Point>> | undefined => {
const features: Array<Feature<Point>> = [];
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);
}