1import React, { ReactNode } from 'react'; 2import { 3 MapLayerRegistryItem, 4 MapLayerOptions, 5 PanelData, 6 GrafanaTheme2, 7 FrameGeometrySourceMode, 8} from '@grafana/data'; 9import Map from 'ol/Map'; 10import Feature from 'ol/Feature'; 11import { Point } from 'ol/geom'; 12import * as layer from 'ol/layer'; 13import * as source from 'ol/source'; 14import { dataFrameToPoints, getLocationMatchers } from '../../utils/location'; 15import { getScaledDimension, getColorDimension, getTextDimension, getScalarDimension } from 'app/features/dimensions'; 16import { ObservablePropsWrapper } from '../../components/ObservablePropsWrapper'; 17import { MarkersLegend, MarkersLegendProps } from './MarkersLegend'; 18import { ReplaySubject } from 'rxjs'; 19import { getFeatures } from '../../utils/getFeatures'; 20import { defaultStyleConfig, StyleConfig, StyleDimensions } from '../../style/types'; 21import { StyleEditor } from './StyleEditor'; 22import { getStyleConfigState } from '../../style/utils'; 23 24// Configuration options for Circle overlays 25export interface MarkersConfig { 26 style: StyleConfig; 27 showLegend?: boolean; 28} 29 30const defaultOptions: MarkersConfig = { 31 style: defaultStyleConfig, 32 showLegend: true, 33}; 34 35export const MARKERS_LAYER_ID = 'markers'; 36 37// Used by default when nothing is configured 38export const defaultMarkersConfig: MapLayerOptions<MarkersConfig> = { 39 type: MARKERS_LAYER_ID, 40 name: '', // will get replaced 41 config: defaultOptions, 42 location: { 43 mode: FrameGeometrySourceMode.Auto, 44 }, 45}; 46 47/** 48 * Map layer configuration for circle overlay 49 */ 50export const markersLayer: MapLayerRegistryItem<MarkersConfig> = { 51 id: MARKERS_LAYER_ID, 52 name: 'Markers', 53 description: 'use markers to render each data point', 54 isBaseMap: false, 55 showLocation: true, 56 57 /** 58 * Function that configures transformation and returns a transformer 59 * @param options 60 */ 61 create: async (map: Map, options: MapLayerOptions<MarkersConfig>, theme: GrafanaTheme2) => { 62 const matchers = await getLocationMatchers(options.location); 63 const vectorLayer = new layer.Vector({}); 64 // Assert default values 65 const config = { 66 ...defaultOptions, 67 ...options?.config, 68 }; 69 70 const legendProps = new ReplaySubject<MarkersLegendProps>(1); 71 let legend: ReactNode = null; 72 if (config.showLegend) { 73 legend = <ObservablePropsWrapper watch={legendProps} initialSubProps={{}} child={MarkersLegend} />; 74 } 75 76 // Set the default style 77 const style = await getStyleConfigState(config.style); 78 if (!style.fields) { 79 vectorLayer.setStyle(style.maker(style.base)); 80 } 81 82 return { 83 init: () => vectorLayer, 84 legend: legend, 85 update: (data: PanelData) => { 86 if (!data.series?.length) { 87 return; // ignore empty 88 } 89 90 const features: Feature<Point>[] = []; 91 92 for (const frame of data.series) { 93 const info = dataFrameToPoints(frame, matchers); 94 if (info.warning) { 95 console.log('Could not find locations', info.warning); 96 continue; // ??? 97 } 98 99 if (style.fields) { 100 const dims: StyleDimensions = {}; 101 if (style.fields.color) { 102 dims.color = getColorDimension(frame, style.config.color ?? defaultStyleConfig.color, theme); 103 } 104 if (style.fields.size) { 105 dims.size = getScaledDimension(frame, style.config.size ?? defaultStyleConfig.size); 106 } 107 if (style.fields.text) { 108 dims.text = getTextDimension(frame, style.config.text!); 109 } 110 if (style.fields.rotation) { 111 dims.rotation = getScalarDimension(frame, style.config.rotation ?? defaultStyleConfig.rotation); 112 } 113 style.dims = dims; 114 } 115 116 const frameFeatures = getFeatures(frame, info, style); 117 118 if (frameFeatures) { 119 features.push(...frameFeatures); 120 } 121 122 // Post updates to the legend component 123 if (legend) { 124 legendProps.next({ 125 color: style.dims?.color, 126 size: style.dims?.size, 127 }); 128 } 129 break; // Only the first frame for now! 130 } 131 132 // Source reads the data and provides a set of features to visualize 133 const vectorSource = new source.Vector({ features }); 134 vectorLayer.setSource(vectorSource); 135 }, 136 137 // Marker overlay options 138 registerOptionsUI: (builder) => { 139 builder 140 .addCustomEditor({ 141 id: 'config.style', 142 path: 'config.style', 143 name: 'Styles', 144 editor: StyleEditor, 145 settings: { 146 displayRotation: true, 147 }, 148 defaultValue: defaultOptions.style, 149 }) 150 .addBooleanSwitch({ 151 path: 'config.showLegend', 152 name: 'Show legend', 153 description: 'Show legend', 154 defaultValue: defaultOptions.showLegend, 155 }); 156 }, 157 }; 158 }, 159 160 // fill in the default values 161 defaultOptions, 162}; 163