The open and composable observability and data visualization platform. Visualize metrics, logs, and traces from multiple sources like Prometheus, Loki, Elasticsearch, InfluxDB, Postgres and many more.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 
grafana/public/app/plugins/panel/geomap/layers/data/heatMap.tsx

145 lines
4.1 KiB

import {
FieldType,
getFieldColorModeForField,
GrafanaTheme2,
MapLayerOptions,
MapLayerRegistryItem,
PanelData,
} from '@grafana/data';
import Map from 'ol/Map';
import * as layer from 'ol/layer';
import { getLocationMatchers } from 'app/features/geo/utils/location';
import { ScaleDimensionConfig, getScaledDimension } from 'app/features/dimensions';
import { ScaleDimensionEditor } from 'app/features/dimensions/editors';
import { FrameVectorSource } from 'app/features/geo/utils/frameVectorSource';
// Configuration options for Heatmap overlays
export interface HeatmapConfig {
weight: ScaleDimensionConfig;
blur: number;
radius: number;
}
const defaultOptions: HeatmapConfig = {
weight: {
fixed: 1,
min: 0,
max: 1,
},
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: async (map: Map, options: MapLayerOptions<HeatmapConfig>, theme: GrafanaTheme2) => {
const config = { ...defaultOptions, ...options.config };
const location = await getLocationMatchers(options.location);
const source = new FrameVectorSource(location);
const WEIGHT_KEY = "_weight";
// Create a new Heatmap layer
// Weight function takes a feature as attribute and returns a normalized weight value
const vectorLayer = new layer.Heatmap({
source,
blur: config.blur,
radius: config.radius,
weight: (feature) => {
return feature.get(WEIGHT_KEY);
},
});
return {
init: () => vectorLayer,
update: (data: PanelData) => {
const frame = data.series[0];
if (!frame) {
return;
}
source.update(frame);
const weightDim = getScaledDimension(frame, config.weight);
source.forEachFeature( (f) => {
const idx = f.get('rowIndex') as number;
if(idx != null) {
f.set(WEIGHT_KEY, weightDim.get(idx));
}
});
// Set heatmap gradient colors
let colors = ['#00f', '#0ff', '#0f0', '#ff0', '#f00'];
// Either the configured field or the first numeric field value
const field = weightDim.field ?? frame.fields.find((field) => field.type === FieldType.number);
if (field) {
const colorMode = getFieldColorModeForField(field);
if (colorMode.isContinuous && colorMode.getColors) {
// getColors return an array of color string from the color scheme chosen
colors = colorMode.getColors(theme);
}
}
vectorLayer.setGradient(colors);
},
// Heatmap overlay options
registerOptionsUI: (builder) => {
builder
.addCustomEditor({
id: 'config.weight',
path: 'config.weight',
name: 'Weight values',
description: 'Scale the distribution for each row',
editor: ScaleDimensionEditor,
settings: {
min: 0, // no contribution
max: 1,
hideRange: true, // Don't show the scale factor
},
defaultValue: {
// Configured values
fixed: 1,
min: 0,
max: 1,
},
})
.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,
};