Geomap: Improve geojson style editor (#41926)

This commit is contained in:
Nathan Marrs 2021-11-18 17:39:13 -08:00 committed by GitHub
parent e904f423e4
commit 541d1543db
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 115 additions and 30 deletions

View File

@ -1,17 +1,27 @@
import React, { FC, useCallback } from 'react';
import { Button, useTheme2 } from '@grafana/ui';
import { StandardEditorProps, StandardEditorsRegistryItem } from '@grafana/data';
import { FeatureStyleConfig } from '../types';
import { Button } from '@grafana/ui';
import { DEFAULT_STYLE_RULE } from '../layers/data/geojsonLayer';
import { StyleRuleEditor, StyleRuleEditorSettings } from './StyleRuleEditor';
import { defaultStyleConfig } from '../style/types';
export const GeomapStyleRulesEditor: FC<StandardEditorProps<FeatureStyleConfig[], any, any>> = (props) => {
const { value, onChange, context, item } = props;
const theme = useTheme2();
const settings = item.settings;
const onAddRule = useCallback(() => {
onChange([...value, DEFAULT_STYLE_RULE]);
}, [onChange, value]);
const { palette } = theme.visualization;
const color = {
fixed: palette[Math.floor(Math.random() * palette.length)],
};
const newRule = [...value, { ...DEFAULT_STYLE_RULE, style: { ...defaultStyleConfig, color } }];
onChange(newRule);
}, [onChange, value, theme.visualization]);
const onRuleChange = useCallback(
(idx) => (style: FeatureStyleConfig | undefined) => {

View File

@ -12,7 +12,6 @@ import { getUniqueFeatureValues, LayerContentInfo } from '../utils/getFeatures';
import { FeatureLike } from 'ol/Feature';
import { getSelectionInfo } from '../utils/selection';
import { NumberInput } from 'app/features/dimensions/editors/NumberInput';
import { isNumber } from 'lodash';
export interface StyleRuleEditorSettings {
features: Observable<FeatureLike[]>;
@ -21,6 +20,7 @@ export interface StyleRuleEditorSettings {
const comparators = [
{ label: '==', value: ComparisonOperation.EQ },
{ label: '!=', value: ComparisonOperation.NEQ },
{ label: '>', value: ComparisonOperation.GT },
{ label: '>=', value: ComparisonOperation.GTE },
{ label: '<', value: ComparisonOperation.LT },
@ -40,7 +40,21 @@ export const StyleRuleEditor: FC<StandardEditorProps<FeatureStyleConfig, any, an
const uniqueSelectables = useMemo(() => {
const key = value?.check?.property;
if (key && feats && value.check?.operation === ComparisonOperation.EQ) {
return getUniqueFeatureValues(feats, key).map((v) => ({ value: v, label: v }));
return getUniqueFeatureValues(feats, key).map((v) => {
let newValue;
let isNewValueNumber = !isNaN(Number(v));
if (isNewValueNumber) {
newValue = {
value: Number(v),
label: v,
};
} else {
newValue = { value: v, label: v };
}
return newValue;
});
}
return [];
}, [feats, value]);
@ -143,7 +157,7 @@ export const StyleRuleEditor: FC<StandardEditorProps<FeatureStyleConfig, any, an
</InlineField>
<InlineField className={styles.inline} grow={true}>
<>
{check.operation === ComparisonOperation.EQ && (
{(check.operation === ComparisonOperation.EQ || check.operation === ComparisonOperation.NEQ) && (
<Select
menuShouldPortal
placeholder={'value'}
@ -158,7 +172,7 @@ export const StyleRuleEditor: FC<StandardEditorProps<FeatureStyleConfig, any, an
{check.operation !== ComparisonOperation.EQ && (
<NumberInput
key={`${check.property}/${check.operation}`}
value={isNumber(check.value) ? check.value : 0}
value={!isNaN(Number(check.value)) ? Number(check.value) : 0}
placeholder="numeric value"
onChange={onChangeNumericValue}
/>
@ -183,6 +197,7 @@ export const StyleRuleEditor: FC<StandardEditorProps<FeatureStyleConfig, any, an
{
settings: {
simpleFixedValues: true,
layerInfo,
},
} as any
}

View File

@ -11,6 +11,8 @@ import {
RadioButtonGroup,
SliderValueEditor,
} from '@grafana/ui';
import { Observable } from 'rxjs';
import { useObservable } from 'react-use';
import {
ColorDimensionEditor,
@ -28,8 +30,10 @@ import {
} from 'app/features/dimensions/types';
import { defaultStyleConfig, StyleConfig, TextAlignment, TextBaseline } from '../../style/types';
import { styleUsesText } from '../../style/utils';
import { LayerContentInfo } from '../../utils/getFeatures';
export interface StyleEditorOptions {
layerInfo?: Observable<LayerContentInfo>;
simpleFixedValues?: boolean;
}
@ -79,10 +83,40 @@ export const StyleEditor: FC<StandardEditorProps<StyleConfig, StyleEditorOptions
onChange({ ...value, textConfig: { ...value.textConfig, textBaseline: textBaseline as TextBaseline } });
};
let featuresHavePoints = false;
if (item.settings?.layerInfo) {
const propertyOptions = useObservable(item.settings?.layerInfo);
featuresHavePoints = propertyOptions?.geometryType === 'point';
}
const hasTextLabel = styleUsesText(value);
// Simple fixed value display
if (item.settings?.simpleFixedValues) {
return (
<>
{featuresHavePoints && (
<InlineFieldRow>
<InlineField label={'Symbol'}>
<ResourceDimensionEditor
value={value.symbol ?? defaultStyleConfig.symbol}
context={context}
onChange={onSymbolChange}
item={
{
settings: {
resourceType: 'icon',
folderName: ResourceFolderName.Marker,
placeholderText: hasTextLabel ? 'Select a symbol' : 'Select a symbol or add a text label',
placeholderValue: defaultStyleConfig.symbol.fixed,
showSourceRadio: false,
},
} as any
}
/>
</InlineField>
</InlineFieldRow>
)}
<InlineFieldRow>
<InlineField label="Color" labelWidth={10}>
<InlineLabel width={4}>
@ -96,7 +130,7 @@ export const StyleEditor: FC<StandardEditorProps<StyleConfig, StyleEditorOptions
</InlineField>
</InlineFieldRow>
<InlineFieldRow>
<InlineField label="Opacity" labelWidth={10} grow={true}>
<InlineField label="Opacity" labelWidth={10} grow>
<SliderValueEditor
value={value.opacity ?? defaultStyleConfig.opacity}
context={context}
@ -117,8 +151,6 @@ export const StyleEditor: FC<StandardEditorProps<StyleConfig, StyleEditorOptions
);
}
const hasTextLabel = styleUsesText(value);
return (
<>
<Field label={'Size'}>

View File

@ -1,10 +1,4 @@
import {
MapLayerRegistryItem,
MapLayerOptions,
PanelData,
GrafanaTheme2,
PluginState,
} from '@grafana/data';
import { MapLayerRegistryItem, MapLayerOptions, PanelData, GrafanaTheme2, PluginState } from '@grafana/data';
import Map from 'ol/Map';
import VectorLayer from 'ol/layer/Vector';
import VectorSource from 'ol/source/Vector';
@ -129,7 +123,7 @@ export const geojsonLayer: MapLayerRegistryItem<GeoJSONMapperConfig> = {
// get properties for first feature to use as ui options
const layerInfo = features.pipe(
first(),
rxjsmap((v) => getLayerPropertyInfo(v)),
rxjsmap((v) => getLayerPropertyInfo(v))
);
builder
@ -146,18 +140,6 @@ export const geojsonLayer: MapLayerRegistryItem<GeoJSONMapperConfig> = {
},
defaultValue: defaultOptions.src,
})
.addCustomEditor({
id: 'config.rules',
path: 'config.rules',
name: 'Style Rules',
description: 'Apply styles based on feature properties',
editor: GeomapStyleRulesEditor,
settings: {
features: features,
layerInfo: layerInfo,
},
defaultValue: [],
})
.addCustomEditor({
id: 'config.style',
path: 'config.style',
@ -166,8 +148,21 @@ export const geojsonLayer: MapLayerRegistryItem<GeoJSONMapperConfig> = {
editor: StyleEditor,
settings: {
simpleFixedValues: true,
layerInfo,
},
defaultValue: defaultOptions.style,
})
.addCustomEditor({
id: 'config.rules',
path: 'config.rules',
name: 'Style Rules',
description: 'Apply styles based on feature properties',
editor: GeomapStyleRulesEditor,
settings: {
features,
layerInfo,
},
defaultValue: [],
});
},
};

View File

@ -57,6 +57,7 @@ export interface FeatureRuleConfig {
export enum ComparisonOperation {
EQ = 'eq',
NEQ = 'neq',
LT = 'lt',
LTE = 'lte',
GT = 'gt',

View File

@ -58,6 +58,16 @@ describe('check if feature matches style rule', () => {
feature
)
).toEqual(true);
expect(
checkFeatureMatchesStyleRule(
{
operation: ComparisonOperation.NEQ,
property: 'number',
value: 3,
},
feature
)
).toEqual(false);
});
it('can compare with strings', () => {
const feature = new Feature({
@ -114,6 +124,16 @@ describe('check if feature matches style rule', () => {
feature
)
).toEqual(true);
expect(
checkFeatureMatchesStyleRule(
{
operation: ComparisonOperation.NEQ,
property: 'string',
value: 'b',
},
feature
)
).toEqual(false);
});
it('can compare with booleans', () => {
const feature = new Feature({
@ -172,5 +192,15 @@ describe('check if feature matches style rule', () => {
feature
)
).toEqual(true);
expect(
checkFeatureMatchesStyleRule(
{
operation: ComparisonOperation.NEQ,
property: 'boolean',
value: false,
},
feature
)
).toEqual(false);
});
});

View File

@ -12,6 +12,8 @@ export const checkFeatureMatchesStyleRule = (rule: FeatureRuleConfig, feature: F
switch (rule.operation) {
case ComparisonOperation.EQ:
return val === rule.value;
case ComparisonOperation.NEQ:
return val !== rule.value;
case ComparisonOperation.GT:
return val > rule.value;
case ComparisonOperation.GTE: