mirror of https://github.com/grafana/grafana.git
				
				
				
			Geomap: Add Property and values to GeoJSON style rule (#41845)
Co-authored-by: Ryan McKinley <ryantxu@gmail.com>
This commit is contained in:
		
							parent
							
								
									0c280319af
								
							
						
					
					
						commit
						7a3b52783c
					
				| 
						 | 
				
			
			@ -1,15 +1,14 @@
 | 
			
		|||
import React, { FC, useCallback } from 'react';
 | 
			
		||||
import { StandardEditorProps, StandardEditorsRegistryItem } from '@grafana/data';
 | 
			
		||||
import { ComparisonOperation, FeatureStyleConfig } from '../types';
 | 
			
		||||
import { FeatureStyleConfig } from '../types';
 | 
			
		||||
import { Button } from '@grafana/ui';
 | 
			
		||||
import { DEFAULT_STYLE_RULE } from '../layers/data/geojsonLayer';
 | 
			
		||||
import { StyleRuleEditor, StyleRuleEditorSettings } from './StyleRuleEditor';
 | 
			
		||||
 | 
			
		||||
export const GeomapStyleRulesEditor: FC<StandardEditorProps<FeatureStyleConfig[], any, any>> = (props) => {
 | 
			
		||||
  const { value, onChange, context } = props;
 | 
			
		||||
 | 
			
		||||
  const OPTIONS = getComparisonOperatorOptions();
 | 
			
		||||
  const { value, onChange, context, item } = props;
 | 
			
		||||
 | 
			
		||||
  const settings = item.settings;
 | 
			
		||||
  const onAddRule = useCallback(() => {
 | 
			
		||||
    onChange([...value, DEFAULT_STYLE_RULE]);
 | 
			
		||||
  }, [onChange, value]);
 | 
			
		||||
| 
						 | 
				
			
			@ -32,7 +31,7 @@ export const GeomapStyleRulesEditor: FC<StandardEditorProps<FeatureStyleConfig[]
 | 
			
		|||
    value &&
 | 
			
		||||
    value.map((style, idx: number) => {
 | 
			
		||||
      const itemSettings: StandardEditorsRegistryItem<any, StyleRuleEditorSettings> = {
 | 
			
		||||
        settings: { options: OPTIONS },
 | 
			
		||||
        settings,
 | 
			
		||||
      } as any;
 | 
			
		||||
 | 
			
		||||
      return (
 | 
			
		||||
| 
						 | 
				
			
			@ -55,11 +54,3 @@ export const GeomapStyleRulesEditor: FC<StandardEditorProps<FeatureStyleConfig[]
 | 
			
		|||
    </>
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const getComparisonOperatorOptions = () => {
 | 
			
		||||
  const options = [];
 | 
			
		||||
  for (const value of Object.values(ComparisonOperation)) {
 | 
			
		||||
    options.push({ value: value, label: value });
 | 
			
		||||
  }
 | 
			
		||||
  return options;
 | 
			
		||||
};
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,35 +1,61 @@
 | 
			
		|||
import React, { ChangeEvent, FC, useCallback } from 'react';
 | 
			
		||||
import React, { FC, useCallback, useMemo } from 'react';
 | 
			
		||||
import { GrafanaTheme2, SelectableValue, StandardEditorProps } from '@grafana/data';
 | 
			
		||||
import { ComparisonOperation, FeatureStyleConfig } from '../types';
 | 
			
		||||
import { Button, InlineField, InlineFieldRow, Input, Select, useStyles2 } from '@grafana/ui';
 | 
			
		||||
import { Button, InlineField, InlineFieldRow, Select, useStyles2 } from '@grafana/ui';
 | 
			
		||||
import { css } from '@emotion/css';
 | 
			
		||||
import { StyleEditor } from '../layers/data/StyleEditor';
 | 
			
		||||
import { defaultStyleConfig, StyleConfig } from '../style/types';
 | 
			
		||||
import { DEFAULT_STYLE_RULE } from '../layers/data/geojsonLayer';
 | 
			
		||||
import { Observable } from 'rxjs';
 | 
			
		||||
import { useObservable } from 'react-use';
 | 
			
		||||
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 {
 | 
			
		||||
  options: SelectableValue[];
 | 
			
		||||
  features: Observable<FeatureLike[]>;
 | 
			
		||||
  layerInfo: Observable<LayerContentInfo>;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const comparators = [
 | 
			
		||||
  { label: '==', value: ComparisonOperation.EQ },
 | 
			
		||||
  { label: '>', value: ComparisonOperation.GT },
 | 
			
		||||
  { label: '>=', value: ComparisonOperation.GTE },
 | 
			
		||||
  { label: '<', value: ComparisonOperation.LT },
 | 
			
		||||
  { label: '<=', value: ComparisonOperation.LTE },
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
export const StyleRuleEditor: FC<StandardEditorProps<FeatureStyleConfig, any, any, StyleRuleEditorSettings>> = (
 | 
			
		||||
  props
 | 
			
		||||
) => {
 | 
			
		||||
  const { value, onChange, item, context } = props;
 | 
			
		||||
  const settings: StyleRuleEditorSettings = item.settings;
 | 
			
		||||
  const { features, layerInfo } = settings;
 | 
			
		||||
 | 
			
		||||
  const propertyOptions = useObservable(layerInfo);
 | 
			
		||||
  const feats = useObservable(features);
 | 
			
		||||
 | 
			
		||||
  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 [];
 | 
			
		||||
  }, [feats, value]);
 | 
			
		||||
 | 
			
		||||
  const styles = useStyles2(getStyles);
 | 
			
		||||
 | 
			
		||||
  const LABEL_WIDTH = 10;
 | 
			
		||||
 | 
			
		||||
  const onChangeComparisonProperty = useCallback(
 | 
			
		||||
    (e: ChangeEvent<HTMLInputElement>) => {
 | 
			
		||||
  const onChangeProperty = useCallback(
 | 
			
		||||
    (selection?: SelectableValue) => {
 | 
			
		||||
      onChange({
 | 
			
		||||
        ...value,
 | 
			
		||||
        check: {
 | 
			
		||||
          ...value.check,
 | 
			
		||||
          property: e.currentTarget.value,
 | 
			
		||||
          operation: value.check?.operation ?? ComparisonOperation.EQ,
 | 
			
		||||
          value: value.check?.value ?? '',
 | 
			
		||||
          ...value.check!,
 | 
			
		||||
          property: selection?.value,
 | 
			
		||||
        },
 | 
			
		||||
      });
 | 
			
		||||
    },
 | 
			
		||||
| 
						 | 
				
			
			@ -41,25 +67,34 @@ export const StyleRuleEditor: FC<StandardEditorProps<FeatureStyleConfig, any, an
 | 
			
		|||
      onChange({
 | 
			
		||||
        ...value,
 | 
			
		||||
        check: {
 | 
			
		||||
          ...value.check,
 | 
			
		||||
          ...value.check!,
 | 
			
		||||
          operation: selection.value ?? ComparisonOperation.EQ,
 | 
			
		||||
          property: value.check?.property ?? '',
 | 
			
		||||
          value: value.check?.value ?? '',
 | 
			
		||||
        },
 | 
			
		||||
      });
 | 
			
		||||
    },
 | 
			
		||||
    [onChange, value]
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
  const onChangeComparisonValue = useCallback(
 | 
			
		||||
    (e: ChangeEvent<HTMLInputElement>) => {
 | 
			
		||||
  const onChangeValue = useCallback(
 | 
			
		||||
    (selection?: SelectableValue) => {
 | 
			
		||||
      onChange({
 | 
			
		||||
        ...value,
 | 
			
		||||
        check: {
 | 
			
		||||
          ...value.check,
 | 
			
		||||
          value: e.currentTarget.value,
 | 
			
		||||
          operation: value.check?.operation ?? ComparisonOperation.EQ,
 | 
			
		||||
          property: value.check?.property ?? '',
 | 
			
		||||
          ...value.check!,
 | 
			
		||||
          value: selection?.value,
 | 
			
		||||
        },
 | 
			
		||||
      });
 | 
			
		||||
    },
 | 
			
		||||
    [onChange, value]
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
  const onChangeNumericValue = useCallback(
 | 
			
		||||
    (v?: number) => {
 | 
			
		||||
      onChange({
 | 
			
		||||
        ...value,
 | 
			
		||||
        check: {
 | 
			
		||||
          ...value.check!,
 | 
			
		||||
          value: v!,
 | 
			
		||||
        },
 | 
			
		||||
      });
 | 
			
		||||
    },
 | 
			
		||||
| 
						 | 
				
			
			@ -78,36 +113,57 @@ export const StyleRuleEditor: FC<StandardEditorProps<FeatureStyleConfig, any, an
 | 
			
		|||
  }, [onChange]);
 | 
			
		||||
 | 
			
		||||
  const check = value.check ?? DEFAULT_STYLE_RULE.check!;
 | 
			
		||||
  const propv = getSelectionInfo(check.property, propertyOptions?.propertes);
 | 
			
		||||
  const valuev = getSelectionInfo(check.value, uniqueSelectables);
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <div className={styles.rule}>
 | 
			
		||||
      <InlineFieldRow className={styles.row}>
 | 
			
		||||
        <InlineField label="Rule" labelWidth={LABEL_WIDTH} grow={true}>
 | 
			
		||||
          <Input
 | 
			
		||||
            type="text"
 | 
			
		||||
            placeholder={'Feature property'}
 | 
			
		||||
            value={check.property ?? ''}
 | 
			
		||||
            onChange={onChangeComparisonProperty}
 | 
			
		||||
            aria-label={'Feature property'}
 | 
			
		||||
          />
 | 
			
		||||
        </InlineField>
 | 
			
		||||
        <InlineField className={styles.inline} grow={true}>
 | 
			
		||||
          <Select
 | 
			
		||||
            menuShouldPortal
 | 
			
		||||
            value={check.operation ?? ComparisonOperation.EQ}
 | 
			
		||||
            options={settings.options}
 | 
			
		||||
            placeholder={'Feature property'}
 | 
			
		||||
            value={propv.current}
 | 
			
		||||
            options={propv.options}
 | 
			
		||||
            onChange={onChangeProperty}
 | 
			
		||||
            aria-label={'Feature property'}
 | 
			
		||||
            isClearable
 | 
			
		||||
            allowCustomValue
 | 
			
		||||
          />
 | 
			
		||||
        </InlineField>
 | 
			
		||||
        <InlineField className={styles.inline}>
 | 
			
		||||
          <Select
 | 
			
		||||
            menuShouldPortal
 | 
			
		||||
            value={comparators.find((v) => v.value === check.operation)}
 | 
			
		||||
            options={comparators}
 | 
			
		||||
            onChange={onChangeComparison}
 | 
			
		||||
            aria-label={'Comparison operator'}
 | 
			
		||||
            width={6}
 | 
			
		||||
          />
 | 
			
		||||
        </InlineField>
 | 
			
		||||
        <InlineField className={styles.inline} grow={true}>
 | 
			
		||||
          <Input
 | 
			
		||||
            type="text"
 | 
			
		||||
            placeholder={'value'}
 | 
			
		||||
            value={`${check.value}` ?? ''}
 | 
			
		||||
            onChange={onChangeComparisonValue}
 | 
			
		||||
            aria-label={'Comparison value'}
 | 
			
		||||
          />
 | 
			
		||||
          <>
 | 
			
		||||
            {check.operation === ComparisonOperation.EQ && (
 | 
			
		||||
              <Select
 | 
			
		||||
                menuShouldPortal
 | 
			
		||||
                placeholder={'value'}
 | 
			
		||||
                value={valuev.current}
 | 
			
		||||
                options={valuev.options}
 | 
			
		||||
                onChange={onChangeValue}
 | 
			
		||||
                aria-label={'Comparison value'}
 | 
			
		||||
                isClearable
 | 
			
		||||
                allowCustomValue
 | 
			
		||||
              />
 | 
			
		||||
            )}
 | 
			
		||||
            {check.operation !== ComparisonOperation.EQ && (
 | 
			
		||||
              <NumberInput
 | 
			
		||||
                key={`${check.property}/${check.operation}`}
 | 
			
		||||
                value={isNumber(check.value) ? check.value : 0}
 | 
			
		||||
                placeholder="numeric value"
 | 
			
		||||
                onChange={onChangeNumericValue}
 | 
			
		||||
              />
 | 
			
		||||
            )}
 | 
			
		||||
          </>
 | 
			
		||||
        </InlineField>
 | 
			
		||||
        <Button
 | 
			
		||||
          size="md"
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,4 +1,10 @@
 | 
			
		|||
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';
 | 
			
		||||
| 
						 | 
				
			
			@ -13,6 +19,10 @@ import { defaultStyleConfig, StyleConfig } from '../../style/types';
 | 
			
		|||
import { getStyleConfigState } from '../../style/utils';
 | 
			
		||||
import { polyStyle } from '../../style/markers';
 | 
			
		||||
import { StyleEditor } from './StyleEditor';
 | 
			
		||||
import { ReplaySubject } from 'rxjs';
 | 
			
		||||
import { map as rxjsmap, first } from 'rxjs/operators';
 | 
			
		||||
import { getLayerPropertyInfo } from '../../utils/getFeatures';
 | 
			
		||||
 | 
			
		||||
export interface GeoJSONMapperConfig {
 | 
			
		||||
  // URL for a geojson file
 | 
			
		||||
  src?: string;
 | 
			
		||||
| 
						 | 
				
			
			@ -64,16 +74,13 @@ export const geojsonLayer: MapLayerRegistryItem<GeoJSONMapperConfig> = {
 | 
			
		|||
      format: new GeoJSON(),
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    const features = new ReplaySubject<FeatureLike[]>();
 | 
			
		||||
 | 
			
		||||
    const key = source.on('change', () => {
 | 
			
		||||
      //one geojson loads
 | 
			
		||||
      if (source.getState() == 'ready') {
 | 
			
		||||
        unByKey(key);
 | 
			
		||||
        // var olFeatures = source.getFeatures(); // olFeatures.length === 1
 | 
			
		||||
        // window.setTimeout(function () {
 | 
			
		||||
        //     var olFeatures = source.getFeatures(); // olFeatures.length > 1
 | 
			
		||||
        //     // Only after using setTimeout can I search the feature list... :(
 | 
			
		||||
        // }, 100)
 | 
			
		||||
 | 
			
		||||
        console.log('SOURCE READY!!!', source.getFeatures().length);
 | 
			
		||||
        features.next(source.getFeatures());
 | 
			
		||||
      }
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -117,17 +124,13 @@ export const geojsonLayer: MapLayerRegistryItem<GeoJSONMapperConfig> = {
 | 
			
		|||
      init: () => vectorLayer,
 | 
			
		||||
      update: (data: PanelData) => {
 | 
			
		||||
        console.log('todo... find values matching the ID and update');
 | 
			
		||||
 | 
			
		||||
        // // Update each feature
 | 
			
		||||
        // source.getFeatures().forEach((f) => {
 | 
			
		||||
        //   console.log('Find: ', f.getId(), f.getProperties());
 | 
			
		||||
        // });
 | 
			
		||||
      },
 | 
			
		||||
 | 
			
		||||
      // Geojson source url
 | 
			
		||||
      registerOptionsUI: (builder) => {
 | 
			
		||||
        const features = source.getFeatures();
 | 
			
		||||
        console.log('FEATURES', source.getState(), features.length, options);
 | 
			
		||||
        // get properties for first feature to use as ui options
 | 
			
		||||
        const layerInfo = features.pipe(
 | 
			
		||||
          first(),
 | 
			
		||||
          rxjsmap((v) => getLayerPropertyInfo(v)),
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        builder
 | 
			
		||||
          .addSelect({
 | 
			
		||||
| 
						 | 
				
			
			@ -149,7 +152,10 @@ export const geojsonLayer: MapLayerRegistryItem<GeoJSONMapperConfig> = {
 | 
			
		|||
            name: 'Style Rules',
 | 
			
		||||
            description: 'Apply styles based on feature properties',
 | 
			
		||||
            editor: GeomapStyleRulesEditor,
 | 
			
		||||
            settings: {},
 | 
			
		||||
            settings: {
 | 
			
		||||
              features: features,
 | 
			
		||||
              layerInfo: layerInfo,
 | 
			
		||||
            },
 | 
			
		||||
            defaultValue: [],
 | 
			
		||||
          })
 | 
			
		||||
          .addCustomEditor({
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -8,17 +8,18 @@ import { FeatureRuleConfig, ComparisonOperation } from '../types';
 | 
			
		|||
 * @returns boolean
 | 
			
		||||
 */
 | 
			
		||||
export const checkFeatureMatchesStyleRule = (rule: FeatureRuleConfig, feature: FeatureLike) => {
 | 
			
		||||
  const val = feature.get(rule.property);
 | 
			
		||||
  switch (rule.operation) {
 | 
			
		||||
    case ComparisonOperation.EQ:
 | 
			
		||||
      return feature.get(rule.property) === rule.value;
 | 
			
		||||
      return val === rule.value;
 | 
			
		||||
    case ComparisonOperation.GT:
 | 
			
		||||
      return feature.get(rule.property) > rule.value;
 | 
			
		||||
      return val > rule.value;
 | 
			
		||||
    case ComparisonOperation.GTE:
 | 
			
		||||
      return feature.get(rule.property) >= rule.value;
 | 
			
		||||
      return val >= rule.value;
 | 
			
		||||
    case ComparisonOperation.LT:
 | 
			
		||||
      return feature.get(rule.property) < rule.value;
 | 
			
		||||
      return val < rule.value;
 | 
			
		||||
    case ComparisonOperation.LTE:
 | 
			
		||||
      return feature.get(rule.property) <= rule.value;
 | 
			
		||||
      return val <= rule.value;
 | 
			
		||||
    default:
 | 
			
		||||
      return false;
 | 
			
		||||
  }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,43 @@
 | 
			
		|||
import { Feature } from 'ol';
 | 
			
		||||
import { Point } from 'ol/geom';
 | 
			
		||||
import { GeometryTypeId } from '../style/types';
 | 
			
		||||
import { getLayerPropertyInfo, getUniqueFeatureValues } from './getFeatures';
 | 
			
		||||
 | 
			
		||||
describe('get features utils', () => {
 | 
			
		||||
  const features = [
 | 
			
		||||
    new Feature({ a: 1, b: 30, hello: 'world', geometry: new Point([0, 0]) }),
 | 
			
		||||
    new Feature({ a: 2, b: 20, hello: 'world', geometry: new Point([0, 0]) }),
 | 
			
		||||
    new Feature({ a: 2, b: 10, c: 30, geometry: new Point([0, 0]) }),
 | 
			
		||||
  ];
 | 
			
		||||
 | 
			
		||||
  it('reads the distinct field names', () => {
 | 
			
		||||
    const info = getLayerPropertyInfo(features);
 | 
			
		||||
    expect(info.geometryType).toBe(GeometryTypeId.Point);
 | 
			
		||||
    expect(info.propertes.map((v) => v.value)).toMatchInlineSnapshot(`
 | 
			
		||||
      Array [
 | 
			
		||||
        "a",
 | 
			
		||||
        "b",
 | 
			
		||||
        "hello",
 | 
			
		||||
        "c",
 | 
			
		||||
      ]
 | 
			
		||||
    `);
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  it('can collect distinct values', () => {
 | 
			
		||||
    const uniqueA = getUniqueFeatureValues(features, 'a');
 | 
			
		||||
    const uniqueB = getUniqueFeatureValues(features, 'b');
 | 
			
		||||
    expect(uniqueA).toMatchInlineSnapshot(`
 | 
			
		||||
      Array [
 | 
			
		||||
        "1",
 | 
			
		||||
        "2",
 | 
			
		||||
      ]
 | 
			
		||||
    `);
 | 
			
		||||
    expect(uniqueB).toMatchInlineSnapshot(`
 | 
			
		||||
      Array [
 | 
			
		||||
        "10",
 | 
			
		||||
        "20",
 | 
			
		||||
        "30",
 | 
			
		||||
      ]
 | 
			
		||||
    `);
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
| 
						 | 
				
			
			@ -1,7 +1,8 @@
 | 
			
		|||
import { DataFrame } from '@grafana/data';
 | 
			
		||||
import { DataFrame, SelectableValue } from '@grafana/data';
 | 
			
		||||
import { Feature } from 'ol';
 | 
			
		||||
import { FeatureLike } from 'ol/Feature';
 | 
			
		||||
import { Point } from 'ol/geom';
 | 
			
		||||
import { StyleConfigState } from '../style/types';
 | 
			
		||||
import { GeometryTypeId, StyleConfigState } from '../style/types';
 | 
			
		||||
import { LocationInfo } from './location';
 | 
			
		||||
 | 
			
		||||
export const getFeatures = (
 | 
			
		||||
| 
						 | 
				
			
			@ -41,3 +42,60 @@ export const getFeatures = (
 | 
			
		|||
 | 
			
		||||
  return features;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export interface LayerContentInfo {
 | 
			
		||||
  geometryType: GeometryTypeId;
 | 
			
		||||
  propertes: Array<SelectableValue<string>>;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function getLayerPropertyInfo(features: FeatureLike[]): LayerContentInfo {
 | 
			
		||||
  const types = new Set<string>();
 | 
			
		||||
  const props = new Set<string>();
 | 
			
		||||
  features.some((feature, idx) => {
 | 
			
		||||
    for (const key of Object.keys(feature.getProperties())) {
 | 
			
		||||
      if (key === 'geometry') {
 | 
			
		||||
        continue;
 | 
			
		||||
      }
 | 
			
		||||
      props.add(key);
 | 
			
		||||
      const g = feature.getGeometry();
 | 
			
		||||
      if (g) {
 | 
			
		||||
        types.add(g.getType());
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    return idx > 10; // first 10 items
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  let geometryType = GeometryTypeId.Any;
 | 
			
		||||
  if (types.size === 1) {
 | 
			
		||||
    switch (types.values().next().value) {
 | 
			
		||||
      case 'Point':
 | 
			
		||||
      case 'MultiPoint':
 | 
			
		||||
        geometryType = GeometryTypeId.Point;
 | 
			
		||||
        break;
 | 
			
		||||
      case 'Line':
 | 
			
		||||
      case 'MultiLine':
 | 
			
		||||
        geometryType = GeometryTypeId.Line;
 | 
			
		||||
        break;
 | 
			
		||||
      case 'Polygon':
 | 
			
		||||
        geometryType = GeometryTypeId.Polygon;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return {
 | 
			
		||||
    geometryType,
 | 
			
		||||
    propertes: Array.from(props.keys()).map((v) => ({ label: v, value: v })),
 | 
			
		||||
  };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function getUniqueFeatureValues(features: FeatureLike[], key: string): string[] {
 | 
			
		||||
  const unique = new Set<string>();
 | 
			
		||||
  for (const feature of features) {
 | 
			
		||||
    const v = feature.get(key);
 | 
			
		||||
    if (v != null) {
 | 
			
		||||
      unique.add(`${v}`); // always string
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  const buffer = Array.from(unique);
 | 
			
		||||
  buffer.sort();
 | 
			
		||||
  return buffer;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,33 @@
 | 
			
		|||
import { SelectableValue } from '@grafana/data';
 | 
			
		||||
 | 
			
		||||
export interface SelectionInfo<T = any> {
 | 
			
		||||
  options: Array<SelectableValue<T>>;
 | 
			
		||||
  current?: SelectableValue<T>;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * The select component is really annoying -- if the current value is not in the list of options
 | 
			
		||||
 * it won't show up.  This is a wrapper to make that happen.
 | 
			
		||||
 */
 | 
			
		||||
export function getSelectionInfo<T>(v?: T, options?: Array<SelectableValue<T>>): SelectionInfo<T> {
 | 
			
		||||
  if (v && !options) {
 | 
			
		||||
    const current = { label: `${v}`, value: v };
 | 
			
		||||
    return { options: [current], current };
 | 
			
		||||
  }
 | 
			
		||||
  if (!options) {
 | 
			
		||||
    options = [];
 | 
			
		||||
  }
 | 
			
		||||
  let current = options.find((item) => item.value === v);
 | 
			
		||||
 | 
			
		||||
  if (v && !current) {
 | 
			
		||||
    current = {
 | 
			
		||||
      label: `${v} (not found)`,
 | 
			
		||||
      value: v,
 | 
			
		||||
    };
 | 
			
		||||
    options.push(current);
 | 
			
		||||
  }
 | 
			
		||||
  return {
 | 
			
		||||
    options,
 | 
			
		||||
    current,
 | 
			
		||||
  };
 | 
			
		||||
}
 | 
			
		||||
		Loading…
	
		Reference in New Issue