Geomap: use same style config for makers and geojson (#41846)

pull/41454/head
Ryan McKinley 4 years ago committed by GitHub
parent 18cc552edb
commit 837e268395
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 4
      public/app/plugins/panel/geomap/editor/GeomapStyleRulesEditor.tsx
  2. 87
      public/app/plugins/panel/geomap/editor/StyleRuleEditor.tsx
  3. 67
      public/app/plugins/panel/geomap/layers/data/StyleEditor.tsx
  4. 102
      public/app/plugins/panel/geomap/layers/data/geojsonLayer.ts
  5. 4
      public/app/plugins/panel/geomap/layers/data/index.ts
  6. 8
      public/app/plugins/panel/geomap/style/markers.ts
  7. 7
      public/app/plugins/panel/geomap/types.ts
  8. 20
      public/app/plugins/panel/geomap/utils/getGeoMapStyle.ts

@ -2,7 +2,7 @@ import React, { FC, useCallback } from 'react';
import { StandardEditorProps, StandardEditorsRegistryItem } from '@grafana/data';
import { ComparisonOperation, FeatureStyleConfig } from '../types';
import { Button } from '@grafana/ui';
import { DEFAULT_STYLE_RULE } from '../layers/data/geojsonMapper';
import { DEFAULT_STYLE_RULE } from '../layers/data/geojsonLayer';
import { StyleRuleEditor, StyleRuleEditorSettings } from './StyleRuleEditor';
export const GeomapStyleRulesEditor: FC<StandardEditorProps<FeatureStyleConfig[], any, any>> = (props) => {
@ -41,7 +41,7 @@ export const GeomapStyleRulesEditor: FC<StandardEditorProps<FeatureStyleConfig[]
onChange={onRuleChange(idx)}
context={context}
item={itemSettings}
key={`${idx}-${style.rule}`}
key={`${idx}-${style.check?.property}`}
/>
);
});

@ -1,9 +1,11 @@
import React, { ChangeEvent, FC, useCallback } from 'react';
import { GrafanaTheme2, SelectableValue, StandardEditorProps } from '@grafana/data';
import { ComparisonOperation, FeatureStyleConfig } from '../types';
import { Button, ColorPicker, InlineField, InlineFieldRow, Input, Select, useStyles2 } from '@grafana/ui';
import { Button, InlineField, InlineFieldRow, Input, Select, useStyles2 } from '@grafana/ui';
import { css } from '@emotion/css';
import { NumberInput } from 'app/features/dimensions/editors/NumberInput';
import { StyleEditor } from '../layers/data/StyleEditor';
import { defaultStyleConfig, StyleConfig } from '../style/types';
import { DEFAULT_STYLE_RULE } from '../layers/data/geojsonLayer';
export interface StyleRuleEditorSettings {
options: SelectableValue[];
@ -12,7 +14,7 @@ export interface StyleRuleEditorSettings {
export const StyleRuleEditor: FC<StandardEditorProps<FeatureStyleConfig, any, any, StyleRuleEditorSettings>> = (
props
) => {
const { value, onChange, item } = props;
const { value, onChange, item, context } = props;
const settings: StyleRuleEditorSettings = item.settings;
const styles = useStyles2(getStyles);
@ -23,11 +25,11 @@ export const StyleRuleEditor: FC<StandardEditorProps<FeatureStyleConfig, any, an
(e: ChangeEvent<HTMLInputElement>) => {
onChange({
...value,
rule: {
...value.rule,
check: {
...value.check,
property: e.currentTarget.value,
operation: value.rule?.operation ?? ComparisonOperation.EQ,
value: value.rule?.value ?? '',
operation: value.check?.operation ?? ComparisonOperation.EQ,
value: value.check?.value ?? '',
},
});
},
@ -38,11 +40,11 @@ export const StyleRuleEditor: FC<StandardEditorProps<FeatureStyleConfig, any, an
(selection: SelectableValue) => {
onChange({
...value,
rule: {
...value.rule,
check: {
...value.check,
operation: selection.value ?? ComparisonOperation.EQ,
property: value.rule?.property ?? '',
value: value.rule?.value ?? '',
property: value.check?.property ?? '',
value: value.check?.value ?? '',
},
});
},
@ -53,27 +55,20 @@ export const StyleRuleEditor: FC<StandardEditorProps<FeatureStyleConfig, any, an
(e: ChangeEvent<HTMLInputElement>) => {
onChange({
...value,
rule: {
...value.rule,
check: {
...value.check,
value: e.currentTarget.value,
operation: value.rule?.operation ?? ComparisonOperation.EQ,
property: value.rule?.property ?? '',
operation: value.check?.operation ?? ComparisonOperation.EQ,
property: value.check?.property ?? '',
},
});
},
[onChange, value]
);
const onChangeColor = useCallback(
(c: string) => {
onChange({ ...value, fillColor: c });
},
[onChange, value]
);
const onChangeStrokeWidth = useCallback(
(num: number | undefined) => {
onChange({ ...value, strokeWidth: num ?? value.strokeWidth ?? 1 });
const onChangeStyle = useCallback(
(style?: StyleConfig) => {
onChange({ ...value, style });
},
[onChange, value]
);
@ -82,6 +77,8 @@ export const StyleRuleEditor: FC<StandardEditorProps<FeatureStyleConfig, any, an
onChange(undefined);
}, [onChange]);
const check = value.check ?? DEFAULT_STYLE_RULE.check!;
return (
<div className={styles.rule}>
<InlineFieldRow className={styles.row}>
@ -89,7 +86,7 @@ export const StyleRuleEditor: FC<StandardEditorProps<FeatureStyleConfig, any, an
<Input
type="text"
placeholder={'Feature property'}
value={`${value?.rule?.property}`}
value={check.property ?? ''}
onChange={onChangeComparisonProperty}
aria-label={'Feature property'}
/>
@ -97,7 +94,7 @@ export const StyleRuleEditor: FC<StandardEditorProps<FeatureStyleConfig, any, an
<InlineField className={styles.inline} grow={true}>
<Select
menuShouldPortal
value={`${value?.rule?.operation}` ?? ComparisonOperation.EQ}
value={check.operation ?? ComparisonOperation.EQ}
options={settings.options}
onChange={onChangeComparison}
aria-label={'Comparison operator'}
@ -107,26 +104,11 @@ export const StyleRuleEditor: FC<StandardEditorProps<FeatureStyleConfig, any, an
<Input
type="text"
placeholder={'value'}
value={`${value?.rule?.value}`}
value={`${check.value}` ?? ''}
onChange={onChangeComparisonValue}
aria-label={'Comparison value'}
/>
</InlineField>
</InlineFieldRow>
<InlineFieldRow className={styles.row}>
<InlineField label="Style" labelWidth={LABEL_WIDTH} className={styles.color}>
<ColorPicker color={value?.fillColor} onChange={onChangeColor} />
</InlineField>
<InlineField label="Stroke" className={styles.inline} grow={true}>
<NumberInput
value={value?.strokeWidth ?? 1}
min={1}
max={20}
step={0.5}
aria-label={'Stroke width'}
onChange={onChangeStrokeWidth}
/>
</InlineField>
<Button
size="md"
icon="trash-alt"
@ -136,6 +118,20 @@ export const StyleRuleEditor: FC<StandardEditorProps<FeatureStyleConfig, any, an
className={styles.button}
></Button>
</InlineFieldRow>
<div>
<StyleEditor
value={value.style ?? defaultStyleConfig}
context={context}
onChange={onChangeStyle}
item={
{
settings: {
simpleFixedValues: true,
},
} as any
}
/>
</div>
</div>
);
};
@ -152,11 +148,6 @@ const getStyles = (theme: GrafanaTheme2) => ({
margin-bottom: 0;
margin-left: 4px;
`,
color: css`
align-items: center;
margin-bottom: 0;
margin-right: 4px;
`,
button: css`
margin-left: 4px;
`,

@ -1,6 +1,16 @@
import React, { FC } from 'react';
import { StandardEditorProps } from '@grafana/data';
import { Field, HorizontalGroup, NumberValueEditor, RadioButtonGroup, SliderValueEditor } from '@grafana/ui';
import {
ColorPicker,
Field,
HorizontalGroup,
InlineField,
InlineFieldRow,
InlineLabel,
NumberValueEditor,
RadioButtonGroup,
SliderValueEditor,
} from '@grafana/ui';
import {
ColorDimensionEditor,
@ -17,8 +27,18 @@ import {
defaultTextConfig,
} from 'app/features/dimensions/types';
import { defaultStyleConfig, StyleConfig, TextAlignment, TextBaseline } from '../../style/types';
import { styleUsesText } from '../../style/utils';
export interface StyleEditorOptions {
simpleFixedValues?: boolean;
}
export const StyleEditor: FC<StandardEditorProps<StyleConfig, any, any>> = ({ value, context, onChange }) => {
export const StyleEditor: FC<StandardEditorProps<StyleConfig, StyleEditorOptions, any>> = ({
value,
context,
onChange,
item,
}) => {
const onSizeChange = (sizeValue: ScaleDimensionConfig | undefined) => {
onChange({ ...value, size: sizeValue });
};
@ -59,7 +79,45 @@ export const StyleEditor: FC<StandardEditorProps<StyleConfig, any, any>> = ({ va
onChange({ ...value, textConfig: { ...value.textConfig, textBaseline: textBaseline as TextBaseline } });
};
const hasTextLabel = Boolean(value.text?.fixed || value.text?.field);
// Simple fixed value display
if (item.settings?.simpleFixedValues) {
return (
<>
<InlineFieldRow>
<InlineField label="Color" labelWidth={10}>
<InlineLabel width={4}>
<ColorPicker
color={value.color?.fixed ?? defaultStyleConfig.color.fixed}
onChange={(v) => {
onColorChange({ fixed: v });
}}
/>
</InlineLabel>
</InlineField>
</InlineFieldRow>
<InlineFieldRow>
<InlineField label="Opacity" labelWidth={10} grow={true}>
<SliderValueEditor
value={value.opacity ?? defaultStyleConfig.opacity}
context={context}
onChange={onOpacityChange}
item={
{
settings: {
min: 0,
max: 1,
step: 0.1,
},
} as any
}
/>
</InlineField>
</InlineFieldRow>
</>
);
}
const hasTextLabel = styleUsesText(value);
return (
<>
@ -128,7 +186,8 @@ export const StyleEditor: FC<StandardEditorProps<StyleConfig, any, any>> = ({ va
item={{} as any}
/>
</Field>
{(value.text?.fixed || value.text?.field) && (
{hasTextLabel && (
<>
<HorizontalGroup>
<Field label={'Font size'}>

@ -4,44 +4,53 @@ import VectorLayer from 'ol/layer/Vector';
import VectorSource from 'ol/source/Vector';
import GeoJSON from 'ol/format/GeoJSON';
import { unByKey } from 'ol/Observable';
import { Feature } from 'ol';
import { Geometry } from 'ol/geom';
import { getGeoMapStyle } from '../../utils/getGeoMapStyle';
import { checkFeatureMatchesStyleRule } from '../../utils/checkFeatureMatchesStyleRule';
import { ComparisonOperation, FeatureStyleConfig } from '../../types';
import { Stroke, Style } from 'ol/style';
import { ComparisonOperation, FeatureRuleConfig, FeatureStyleConfig } from '../../types';
import { Style } from 'ol/style';
import { FeatureLike } from 'ol/Feature';
import { GeomapStyleRulesEditor } from '../../editor/GeomapStyleRulesEditor';
import { circleMarker } from '../../style/markers';
import { defaultStyleConfig, StyleConfig } from '../../style/types';
import { getStyleConfigState } from '../../style/utils';
import { polyStyle } from '../../style/markers';
import { StyleEditor } from './StyleEditor';
export interface GeoJSONMapperConfig {
// URL for a geojson file
src?: string;
// Styles that can be applied
styles: FeatureStyleConfig[];
// Pick style based on a rule
rules: FeatureStyleConfig[];
// The default style (applied if no rules match)
style: StyleConfig;
}
const defaultOptions: GeoJSONMapperConfig = {
src: 'public/maps/countries.geojson',
styles: [],
rules: [],
style: defaultStyleConfig,
};
interface StyleCheckerState {
poly: Style | Style[];
point: Style | Style[];
rule?: FeatureRuleConfig;
}
export const DEFAULT_STYLE_RULE: FeatureStyleConfig = {
fillColor: '#1F60C4',
strokeWidth: 1,
rule: {
style: defaultStyleConfig,
check: {
property: '',
operation: ComparisonOperation.EQ,
value: '',
},
};
export const geojsonMapper: MapLayerRegistryItem<GeoJSONMapperConfig> = {
id: 'geojson-value-mapper',
name: 'Map values to GeoJSON file',
description: 'color features based on query results',
export const geojsonLayer: MapLayerRegistryItem<GeoJSONMapperConfig> = {
id: 'geojson',
name: 'GeoJSON',
description: 'Load static data from a geojson file',
isBaseMap: false,
state: PluginState.alpha,
state: PluginState.beta,
/**
* Function that configures transformation and returns a transformer
@ -68,30 +77,39 @@ export const geojsonMapper: MapLayerRegistryItem<GeoJSONMapperConfig> = {
}
});
const defaultStyle = new Style({
stroke: new Stroke({
color: DEFAULT_STYLE_RULE.fillColor,
width: DEFAULT_STYLE_RULE.strokeWidth,
}),
});
const styles: StyleCheckerState[] = [];
if (config.rules) {
for (const r of config.rules) {
if (r.style) {
const s = await getStyleConfigState(r.style);
styles.push({
point: s.maker(s.base),
poly: polyStyle(s.base),
rule: r.check,
});
}
}
}
if (true) {
const s = await getStyleConfigState(config.style);
styles.push({
point: s.maker(s.base),
poly: polyStyle(s.base),
});
}
const vectorLayer = new VectorLayer({
source,
style: (feature: FeatureLike) => {
const type = feature.getGeometry()?.getType();
if (type === 'Point') {
return circleMarker({color:DEFAULT_STYLE_RULE.fillColor});
}
const isPoint = feature.getGeometry()?.getType() === 'Point';
if (feature && config?.styles?.length) {
for (const style of config.styles) {
//check if there is no style rule or if the rule matches feature property
if (!style.rule || checkFeatureMatchesStyleRule(style.rule, feature as Feature<Geometry>)) {
return getGeoMapStyle(style, feature);
}
for (const check of styles) {
if (check.rule && !checkFeatureMatchesStyleRule(check.rule, feature)) {
continue;
}
return isPoint ? check.point : check.poly;
}
return defaultStyle;
return undefined; // unreachable
},
});
@ -126,12 +144,24 @@ export const geojsonMapper: MapLayerRegistryItem<GeoJSONMapperConfig> = {
defaultValue: defaultOptions.src,
})
.addCustomEditor({
id: 'config.styles',
path: 'config.styles',
id: 'config.rules',
path: 'config.rules',
name: 'Style Rules',
description: 'Apply styles based on feature properties',
editor: GeomapStyleRulesEditor,
settings: {},
defaultValue: [],
})
.addCustomEditor({
id: 'config.style',
path: 'config.style',
name: 'Default Style',
description: 'The style to apply when no rules above match',
editor: StyleEditor,
settings: {
simpleFixedValues: true,
},
defaultValue: defaultOptions.style,
});
},
};

@ -1,5 +1,5 @@
import { markersLayer } from './markersLayer';
import { geojsonMapper } from './geojsonMapper';
import { geojsonLayer } from './geojsonLayer';
import { heatmapLayer } from './heatMap';
import { lastPointTracker } from './lastPointTracker';
@ -10,5 +10,5 @@ export const dataLayers = [
markersLayer,
heatmapLayer,
lastPointTracker,
geojsonMapper, // dummy for now
geojsonLayer,
];

@ -75,6 +75,14 @@ export const circleMarker = (cfg: StyleConfigValues) => {
});
};
export const polyStyle = (cfg: StyleConfigValues) => {
return new Style({
fill: getFillColor(cfg),
stroke: new Stroke({ color: cfg.color, width: cfg.lineWidth ?? 1 }),
text: textLabel(cfg),
});
};
// Square and cross
const errorMarker = (cfg: StyleConfigValues) => {
const radius = cfg.size ?? DEFAULT_SIZE;

@ -1,6 +1,7 @@
import { MapLayerHandler, MapLayerOptions, SelectableValue } from '@grafana/data';
import BaseLayer from 'ol/layer/Base';
import { Units } from 'ol/proj/Units';
import { StyleConfig } from './style/types';
import { MapCenterID } from './view';
export interface ControlsOptions {
@ -45,10 +46,8 @@ export interface GeomapPanelOptions {
layers: MapLayerOptions[];
}
export interface FeatureStyleConfig {
fillColor: string; //eventually be ColorDimensionConfig
opacity?: number;
strokeWidth?: number;
rule?: FeatureRuleConfig;
style?: StyleConfig;
check?: FeatureRuleConfig;
}
export interface FeatureRuleConfig {
property: string;

@ -1,20 +0,0 @@
import { Style, Stroke, Fill } from 'ol/style';
import { FeatureStyleConfig } from '../types';
/**
* Gets a geomap style based on fill, stroke, and stroke width
* @returns ol style
*/
export const getGeoMapStyle = (config: FeatureStyleConfig, property: any) => {
return new Style({
fill: new Fill({
color: `${config.fillColor ?? '#1F60C4'}`,
}),
stroke: config?.strokeWidth
? new Stroke({
color: `${config.fillColor ?? '#1F60C4'}`,
width: config.strokeWidth,
})
: undefined,
});
};
Loading…
Cancel
Save