mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Geomap: Implement Heatmap overlays (#36769)
This commit is contained in:
parent
cfd06775c0
commit
dd11e232d0
160
public/app/plugins/panel/geomap/layers/data/heatMap.tsx
Normal file
160
public/app/plugins/panel/geomap/layers/data/heatMap.tsx
Normal 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;
|
||||||
|
};
|
@ -1,5 +1,6 @@
|
|||||||
import { markersLayer } from './markersLayer';
|
import { markersLayer } from './markersLayer';
|
||||||
import { geojsonMapper } from './geojsonMapper';
|
import { geojsonMapper } from './geojsonMapper';
|
||||||
|
import { heatmapLayer } from './heatMap';
|
||||||
import { lastPointTracker } from './lastPointTracker';
|
import { lastPointTracker } from './lastPointTracker';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -7,6 +8,7 @@ import { lastPointTracker } from './lastPointTracker';
|
|||||||
*/
|
*/
|
||||||
export const dataLayers = [
|
export const dataLayers = [
|
||||||
markersLayer,
|
markersLayer,
|
||||||
|
heatmapLayer,
|
||||||
lastPointTracker,
|
lastPointTracker,
|
||||||
geojsonMapper, // dummy for now
|
geojsonMapper, // dummy for now
|
||||||
];
|
];
|
||||||
|
Loading…
Reference in New Issue
Block a user