mirror of
https://github.com/grafana/grafana.git
synced 2025-02-16 10:24:54 -06:00
Geomap: Style based on feature property (#39498)
* hardcode styles * add custom styles and rules * refactor and test to check feature matches rule * update types and default style * format geojsonmapper * adjust types and reformat tests * remove unused property Co-authored-by: Ryan McKinley <ryantxu@gmail.com>
This commit is contained in:
parent
6eceb69d43
commit
8d92e7ab90
@ -3,20 +3,24 @@ import Map from 'ol/Map';
|
||||
import VectorLayer from 'ol/layer/Vector';
|
||||
import VectorSource from 'ol/source/Vector';
|
||||
import GeoJSON from 'ol/format/GeoJSON';
|
||||
|
||||
import { Feature } from 'ol';
|
||||
import { Geometry } from 'ol/geom';
|
||||
import { getGeoMapStyle } from '../../utils/getGeoMapStyle';
|
||||
import { checkFeatureMatchesStyleRule } from '../../utils/checkFeatureMatchesStyleRule';
|
||||
import { FeatureStyleConfig } from '../../types';
|
||||
import { Stroke, Style } from 'ol/style';
|
||||
import { FeatureLike } from 'ol/Feature';
|
||||
export interface GeoJSONMapperConfig {
|
||||
// URL for a geojson file
|
||||
src?: string;
|
||||
|
||||
// Field name that will map to each featureId
|
||||
idField?: string;
|
||||
|
||||
// Field to use that will set color
|
||||
valueField?: string;
|
||||
// Styles that can be applied
|
||||
styles?: FeatureStyleConfig[];
|
||||
}
|
||||
|
||||
const defaultOptions: GeoJSONMapperConfig = {
|
||||
src: 'public/maps/countries.geojson',
|
||||
styles: [],
|
||||
};
|
||||
|
||||
export const geojsonMapper: MapLayerRegistryItem<GeoJSONMapperConfig> = {
|
||||
@ -38,8 +42,26 @@ export const geojsonMapper: MapLayerRegistryItem<GeoJSONMapperConfig> = {
|
||||
format: new GeoJSON(),
|
||||
});
|
||||
|
||||
const defaultStyle = new Style({
|
||||
stroke: new Stroke({
|
||||
color: '#1F60C4',
|
||||
width: 1,
|
||||
}),
|
||||
});
|
||||
|
||||
const vectorLayer = new VectorLayer({
|
||||
source,
|
||||
style: (feature: FeatureLike) => {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
return defaultStyle;
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
|
@ -43,3 +43,21 @@ export interface GeomapPanelOptions {
|
||||
basemap: MapLayerOptions;
|
||||
layers: MapLayerOptions[];
|
||||
}
|
||||
export interface FeatureStyleConfig {
|
||||
fillColor: string; //eventually be ColorDimensionConfig
|
||||
strokeWidth?: number;
|
||||
rule?: FeatureRuleConfig;
|
||||
}
|
||||
export interface FeatureRuleConfig {
|
||||
property: string;
|
||||
operation: ComparisonOperation;
|
||||
value: string | boolean | number;
|
||||
}
|
||||
|
||||
export enum ComparisonOperation {
|
||||
EQ = 'eq',
|
||||
LT = 'lt',
|
||||
LTE = 'lte',
|
||||
GT = 'gt',
|
||||
GTE = 'gte',
|
||||
}
|
||||
|
@ -0,0 +1,176 @@
|
||||
import { Feature } from 'ol';
|
||||
import { ComparisonOperation } from '../types';
|
||||
import { checkFeatureMatchesStyleRule } from './checkFeatureMatchesStyleRule';
|
||||
|
||||
describe('check if feature matches style rule', () => {
|
||||
it('can compare with numbers', () => {
|
||||
const feature = new Feature({
|
||||
number: 3,
|
||||
});
|
||||
|
||||
expect(
|
||||
checkFeatureMatchesStyleRule(
|
||||
{
|
||||
operation: ComparisonOperation.EQ,
|
||||
property: 'number',
|
||||
value: 3,
|
||||
},
|
||||
feature
|
||||
)
|
||||
).toEqual(true);
|
||||
expect(
|
||||
checkFeatureMatchesStyleRule(
|
||||
{
|
||||
operation: ComparisonOperation.LT,
|
||||
property: 'number',
|
||||
value: 2,
|
||||
},
|
||||
feature
|
||||
)
|
||||
).toEqual(false);
|
||||
expect(
|
||||
checkFeatureMatchesStyleRule(
|
||||
{
|
||||
operation: ComparisonOperation.LTE,
|
||||
property: 'number',
|
||||
value: 3,
|
||||
},
|
||||
feature
|
||||
)
|
||||
).toEqual(true);
|
||||
expect(
|
||||
checkFeatureMatchesStyleRule(
|
||||
{
|
||||
operation: ComparisonOperation.GT,
|
||||
property: 'number',
|
||||
value: 3,
|
||||
},
|
||||
feature
|
||||
)
|
||||
).toEqual(false);
|
||||
expect(
|
||||
checkFeatureMatchesStyleRule(
|
||||
{
|
||||
operation: ComparisonOperation.GTE,
|
||||
property: 'number',
|
||||
value: 3,
|
||||
},
|
||||
feature
|
||||
)
|
||||
).toEqual(true);
|
||||
});
|
||||
it('can compare with strings', () => {
|
||||
const feature = new Feature({
|
||||
string: 'b',
|
||||
});
|
||||
|
||||
expect(
|
||||
checkFeatureMatchesStyleRule(
|
||||
{
|
||||
operation: ComparisonOperation.EQ,
|
||||
property: 'string',
|
||||
value: 'B',
|
||||
},
|
||||
feature
|
||||
)
|
||||
).toEqual(false);
|
||||
expect(
|
||||
checkFeatureMatchesStyleRule(
|
||||
{
|
||||
operation: ComparisonOperation.LT,
|
||||
property: 'string',
|
||||
value: 'c',
|
||||
},
|
||||
feature
|
||||
)
|
||||
).toEqual(true);
|
||||
expect(
|
||||
checkFeatureMatchesStyleRule(
|
||||
{
|
||||
operation: ComparisonOperation.LTE,
|
||||
property: 'string',
|
||||
value: 'bc',
|
||||
},
|
||||
feature
|
||||
)
|
||||
).toEqual(true);
|
||||
expect(
|
||||
checkFeatureMatchesStyleRule(
|
||||
{
|
||||
operation: ComparisonOperation.GT,
|
||||
property: 'string',
|
||||
value: 'ab',
|
||||
},
|
||||
feature
|
||||
)
|
||||
).toEqual(true);
|
||||
expect(
|
||||
checkFeatureMatchesStyleRule(
|
||||
{
|
||||
operation: ComparisonOperation.GTE,
|
||||
property: 'string',
|
||||
value: 'abc',
|
||||
},
|
||||
feature
|
||||
)
|
||||
).toEqual(true);
|
||||
});
|
||||
it('can compare with booleans', () => {
|
||||
const feature = new Feature({
|
||||
name: 'test polygon',
|
||||
});
|
||||
|
||||
feature.setProperties({ boolean: false });
|
||||
|
||||
expect(
|
||||
checkFeatureMatchesStyleRule(
|
||||
{
|
||||
operation: ComparisonOperation.EQ,
|
||||
property: 'boolean',
|
||||
value: false,
|
||||
},
|
||||
feature
|
||||
)
|
||||
).toEqual(true);
|
||||
expect(
|
||||
checkFeatureMatchesStyleRule(
|
||||
{
|
||||
operation: ComparisonOperation.LT,
|
||||
property: 'boolean',
|
||||
value: true,
|
||||
},
|
||||
feature
|
||||
)
|
||||
).toEqual(true);
|
||||
expect(
|
||||
checkFeatureMatchesStyleRule(
|
||||
{
|
||||
operation: ComparisonOperation.LTE,
|
||||
property: 'boolean',
|
||||
value: true,
|
||||
},
|
||||
feature
|
||||
)
|
||||
).toEqual(true);
|
||||
expect(
|
||||
checkFeatureMatchesStyleRule(
|
||||
{
|
||||
operation: ComparisonOperation.GT,
|
||||
property: 'boolean',
|
||||
value: false,
|
||||
},
|
||||
feature
|
||||
)
|
||||
).toEqual(false);
|
||||
expect(
|
||||
checkFeatureMatchesStyleRule(
|
||||
{
|
||||
operation: ComparisonOperation.GTE,
|
||||
property: 'boolean',
|
||||
value: false,
|
||||
},
|
||||
feature
|
||||
)
|
||||
).toEqual(true);
|
||||
});
|
||||
});
|
@ -0,0 +1,25 @@
|
||||
import { FeatureLike } from 'ol/Feature';
|
||||
import { FeatureRuleConfig, ComparisonOperation } from '../types';
|
||||
|
||||
/**
|
||||
* Check whether feature has property value that matches rule
|
||||
* @param rule - style rule with an operation, property, and value
|
||||
* @param feature - feature with properties and values
|
||||
* @returns boolean
|
||||
*/
|
||||
export const checkFeatureMatchesStyleRule = (rule: FeatureRuleConfig, feature: FeatureLike) => {
|
||||
switch (rule.operation) {
|
||||
case ComparisonOperation.EQ:
|
||||
return feature.get(rule.property) === rule.value;
|
||||
case ComparisonOperation.GT:
|
||||
return feature.get(rule.property) > rule.value;
|
||||
case ComparisonOperation.GTE:
|
||||
return feature.get(rule.property) >= rule.value;
|
||||
case ComparisonOperation.LT:
|
||||
return feature.get(rule.property) < rule.value;
|
||||
case ComparisonOperation.LTE:
|
||||
return feature.get(rule.property) <= rule.value;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
};
|
21
public/app/plugins/panel/geomap/utils/getGeoMapStyle.ts
Normal file
21
public/app/plugins/panel/geomap/utils/getGeoMapStyle.ts
Normal file
@ -0,0 +1,21 @@
|
||||
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,
|
||||
//handle a shape/marker too?
|
||||
});
|
||||
};
|
Loading…
Reference in New Issue
Block a user