Geomap: Implement Heatmap overlays (#36769)

This commit is contained in:
Eunice Kim 2021-07-16 14:23:48 -07:00 committed by GitHub
parent cfd06775c0
commit dd11e232d0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 162 additions and 0 deletions

View File

@ -0,0 +1,160 @@
import {
FieldCalcs,
FieldType,
getFieldColorModeForField,
GrafanaTheme2,
MapLayerHandler,
MapLayerOptions,
MapLayerRegistryItem,
PanelData,
reduceField,
ReducerID,
} from '@grafana/data';
import Map from 'ol/Map';
import Feature from 'ol/Feature';
import * as layer from 'ol/layer';
import * as source from 'ol/source';
import { dataFrameToPoints, getLocationMatchers } from '../../utils/location';
// Configuration options for Heatmap overlays
export interface HeatmapConfig {
blur: number;
radius: number;
}
const defaultOptions: HeatmapConfig = {
blur: 15,
radius: 5,
};
/**
* Map layer configuration for heatmap overlay
*/
export const heatmapLayer: MapLayerRegistryItem<HeatmapConfig> = {
id: 'heatmap',
name: 'Heatmap',
description: 'visualizes a heatmap of the data',
isBaseMap: false,
showLocation: true,
/**
* Function that configures transformation and returns a transformer
* @param options
*/
create: (map: Map, options: MapLayerOptions<HeatmapConfig>, theme: GrafanaTheme2): MapLayerHandler => {
const config = { ...defaultOptions, ...options.config };
const matchers = getLocationMatchers(options.location);
const vectorSource = new source.Vector();
// Create a new Heatmap layer
// Weight function takes a feature as attribute and returns a normalized weight value
const vectorLayer = new layer.Heatmap({
source: vectorSource,
blur: config.blur,
radius: config.radius,
weight: function (feature) {
var weight = feature.get('value');
return weight;
},
});
return {
init: () => vectorLayer,
update: (data: PanelData) => {
const frame = data.series[0];
// Remove previous data before updating
const features = vectorLayer.getSource().getFeatures();
features.forEach((feature) => {
vectorLayer.getSource().removeFeature(feature);
});
// Get data points (latitude and longitude coordinates)
const info = dataFrameToPoints(frame, matchers);
if(info.warning) {
console.log( 'WARN', info.warning);
return; // ???
}
// Get the field of data values
const field = frame.fields.find(field => field.type === FieldType.number); // TODO!!!!
// Return early if metric field is not matched
if (field === undefined) {
return;
};
// Retrieve the min, max and range of data values
const calcs = reduceField({
field: field,
reducers: [
ReducerID.min,
ReducerID.range,
]
});
// Map each data value into new points
for (let i = 0; i < frame.length; i++) {
const cluster = new Feature({
geometry: info.points[i],
value: normalize(calcs, field.values.get(i)),
});
vectorSource.addFeature(cluster);
};
vectorLayer.setSource(vectorSource);
// Set gradient of heatmap
const colorMode = getFieldColorModeForField(field);
if (colorMode.isContinuous && colorMode.getColors) {
// getColors return an array of color string from the color scheme chosen
const colors = colorMode.getColors(theme);
vectorLayer.setGradient(colors);
} else {
// Set the gradient back to default if threshold or single color is chosen
vectorLayer.setGradient(['#00f', '#0ff', '#0f0', '#ff0', '#f00']);
}
},
};
},
// Heatmap overlay options
registerOptionsUI: (builder) => {
builder
.addSliderInput({
path: 'config.radius',
description: 'configures the size of clusters',
name: 'Radius',
defaultValue: defaultOptions.radius,
settings: {
min: 1,
max: 50,
step: 1,
},
})
.addSliderInput({
path: 'config.blur',
description: 'configures the amount of blur of clusters',
name: 'Blur',
defaultValue: defaultOptions.blur,
settings: {
min: 1,
max: 50,
step: 1,
},
});
},
// fill in the default values
defaultOptions,
};
/**
* Function that normalize the data values to a range between 0.1 and 1
* Returns the weights for each value input
*/
function normalize(calcs: FieldCalcs, value: number) {
// If all data values are the same, it should return the largest weight
if (calcs.range == 0) {
return 1;
};
// Normalize value in range of [0.1,1]
const norm = 0.1 + ((value - calcs.min) / calcs.range) * 0.9
return norm;
};

View File

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