Geomap: Minor style fixes (#38532)

* Fixed hover font-weight, option casing, and added simple test dashboard with 3 panels

* Update theme colors

* Style tweaks to legend

* Updated

Co-authored-by: Ryan McKinley <ryantxu@gmail.com>
pull/38577/head
Torkel Ödegaard 4 years ago committed by GitHub
parent 12320dda3c
commit d5ed4e9c8c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      .prettierignore
  2. 404
      devenv/dev-dashboards/panel-geomap/panel-geomap.json
  3. 4
      public/app/plugins/panel/geomap/components/DataHoverView.tsx
  4. 8
      public/app/plugins/panel/geomap/editor/LayerEditor.tsx
  5. 22
      public/app/plugins/panel/geomap/globalStyles.ts
  6. 57
      public/app/plugins/panel/geomap/layers/data/MarkersLegend.tsx
  7. 6
      public/app/plugins/panel/geomap/layers/data/geojsonMapper.ts
  8. 23
      public/app/plugins/panel/geomap/layers/data/heatMap.tsx
  9. 8
      public/app/plugins/panel/geomap/layers/data/index.ts
  10. 8
      public/app/plugins/panel/geomap/layers/data/lastPointTracker.ts
  11. 58
      public/app/plugins/panel/geomap/layers/data/markersLayer.tsx
  12. 12
      public/app/plugins/panel/geomap/module.tsx

@ -5,7 +5,7 @@ pkg/
node_modules node_modules
public/vendor/ public/vendor/
vendor/ vendor/
data/ /data/
e2e/tmp e2e/tmp
public/build/ public/build/
public/sass/*.generated.scss public/sass/*.generated.scss

@ -0,0 +1,404 @@
{
"annotations": {
"list": [
{
"builtIn": 1,
"datasource": "-- Grafana --",
"enable": true,
"hide": true,
"iconColor": "rgba(0, 211, 255, 1)",
"name": "Annotations & Alerts",
"target": {
"limit": 100,
"matchAny": false,
"tags": [],
"type": "dashboard"
},
"type": "dashboard"
}
]
},
"editable": true,
"gnetId": null,
"graphTooltip": 0,
"links": [],
"liveNow": false,
"panels": [
{
"datasource": null,
"fieldConfig": {
"defaults": {
"color": {
"mode": "continuous-GrYlRd"
},
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "red",
"value": 80
}
]
}
},
"overrides": []
},
"gridPos": {
"h": 11,
"w": 9,
"x": 0,
"y": 0
},
"id": 62,
"options": {
"basemap": {
"config": {},
"type": "default"
},
"controls": {
"mouseWheelZoom": true,
"showAttribution": true,
"showDebug": false,
"showScale": false,
"showZoom": true
},
"layers": [
{
"config": {
"color": {
"field": "Price",
"fixed": "dark-green"
},
"fillOpacity": 0.4,
"shape": "circle",
"showLegend": true,
"size": {
"field": "Count",
"fixed": 5,
"max": 15,
"min": 2
}
},
"location": {
"gazetteer": "public/gazetteer/usa-states.json",
"lookup": "State",
"mode": "auto"
},
"type": "markers"
}
],
"view": {
"id": "coords",
"lat": 38.297683,
"lon": -99.228359,
"shared": true,
"zoom": 3.98
}
},
"targets": [
{
"csvFileName": "flight_info_by_state.csv",
"refId": "A",
"scenarioId": "csv_file"
}
],
"title": "Size, color mapped to different fields + share view",
"type": "geomap"
},
{
"datasource": null,
"fieldConfig": {
"defaults": {
"color": {
"mode": "thresholds"
},
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "red",
"value": 80
},
{
"color": "#EAB839",
"value": 90
}
]
}
},
"overrides": []
},
"gridPos": {
"h": 11,
"w": 9,
"x": 9,
"y": 0
},
"id": 66,
"options": {
"basemap": {
"config": {},
"type": "default"
},
"controls": {
"mouseWheelZoom": true,
"showAttribution": true,
"showDebug": false,
"showScale": false,
"showZoom": true
},
"layers": [
{
"config": {
"color": {
"field": "Price",
"fixed": "dark-green"
},
"fillOpacity": 0.4,
"shape": "circle",
"showLegend": true,
"size": {
"field": "Count",
"fixed": 5,
"max": 15,
"min": 2
}
},
"location": {
"gazetteer": "public/gazetteer/usa-states.json",
"lookup": "State",
"mode": "auto"
},
"type": "markers"
}
],
"view": {
"id": "coords",
"lat": 38.297683,
"lon": -99.228359,
"shared": true,
"zoom": 3.98
}
},
"targets": [
{
"csvFileName": "flight_info_by_state.csv",
"refId": "A",
"scenarioId": "csv_file"
}
],
"title": "Thresholds legend",
"type": "geomap"
},
{
"datasource": null,
"fieldConfig": {
"defaults": {
"color": {
"mode": "continuous-BlYlRd"
},
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "red",
"value": 80
}
]
}
},
"overrides": []
},
"gridPos": {
"h": 11,
"w": 9,
"x": 0,
"y": 11
},
"id": 63,
"options": {
"basemap": {
"config": {},
"type": "default"
},
"controls": {
"mouseWheelZoom": true,
"showAttribution": true,
"showDebug": false,
"showScale": false,
"showZoom": true
},
"layers": [
{
"config": {
"blur": 27,
"radius": 25,
"weight": {
"field": "Count",
"fixed": 1,
"max": 1,
"min": 0
}
},
"location": {
"gazetteer": "public/gazetteer/usa-states.json",
"lookup": "State",
"mode": "auto"
},
"type": "heatmap"
}
],
"view": {
"id": "coords",
"lat": 38.251497,
"lon": -100.932144,
"shared": false,
"zoom": 4.15
}
},
"targets": [
{
"csvFileName": "flight_info_by_state.csv",
"refId": "A",
"scenarioId": "csv_file"
}
],
"title": "Heatmap data layer",
"transformations": [],
"type": "geomap"
},
{
"datasource": null,
"fieldConfig": {
"defaults": {
"color": {
"mode": "continuous-GrYlRd"
},
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "red",
"value": 80
}
]
}
},
"overrides": []
},
"gridPos": {
"h": 11,
"w": 9,
"x": 9,
"y": 11
},
"id": 65,
"options": {
"basemap": {
"config": {
"server": "world-imagery"
},
"type": "esri-xyz"
},
"controls": {
"mouseWheelZoom": true,
"showAttribution": true,
"showDebug": false,
"showScale": false,
"showZoom": true
},
"layers": [
{
"config": {
"color": {
"fixed": "#ff001e"
},
"fillOpacity": 0.4,
"shape": "star",
"showLegend": true,
"size": {
"field": "Count",
"fixed": 5,
"max": 15,
"min": 2
}
},
"location": {
"gazetteer": "public/gazetteer/usa-states.json",
"lookup": "State",
"mode": "auto"
},
"type": "markers"
}
],
"view": {
"id": "coords",
"lat": 40.159084,
"lon": -96.508021,
"shared": true,
"zoom": 3.83
}
},
"targets": [
{
"csvFileName": "flight_info_by_state.csv",
"refId": "A",
"scenarioId": "csv_file"
}
],
"title": "Base layer ArcGIS wold imagery + star shape + share view",
"type": "geomap"
}
],
"refresh": "",
"schemaVersion": 30,
"style": "dark",
"tags": [
"gdev",
"panel-tests"
],
"templating": {
"list": []
},
"time": {
"from": "now-6h",
"to": "now"
},
"timepicker": {
"refresh_intervals": [
"10s",
"30s",
"1m",
"5m",
"15m",
"30m",
"1h",
"2h",
"1d"
]
},
"timezone": "",
"title": "Panel Tests - Geomap",
"uid": "2xuwrgV7z",
"version": 5
}

@ -46,8 +46,8 @@ const getStyles = stylesFactory((theme: GrafanaTheme2) => ({
infoWrap: css` infoWrap: css`
padding: 8px; padding: 8px;
th { th {
font-weight: bold; font-weight: ${theme.typography.fontWeightMedium};
padding: 2px 10px 2px 0px; padding: ${theme.spacing(0.25, 2)};
} }
`, `,
highlight: css` highlight: css`

@ -60,7 +60,7 @@ export const LayerEditor: FC<LayerEditorProps> = ({ options, onChange, data, fil
}) })
.addFieldNamePicker({ .addFieldNamePicker({
path: 'location.latitude', path: 'location.latitude',
name: 'Latitude Field', name: 'Latitude field',
settings: { settings: {
filter: (f: Field) => f.type === FieldType.number, filter: (f: Field) => f.type === FieldType.number,
noFieldsMessage: 'No numeric fields found', noFieldsMessage: 'No numeric fields found',
@ -69,7 +69,7 @@ export const LayerEditor: FC<LayerEditorProps> = ({ options, onChange, data, fil
}) })
.addFieldNamePicker({ .addFieldNamePicker({
path: 'location.longitude', path: 'location.longitude',
name: 'Longitude Field', name: 'Longitude field',
settings: { settings: {
filter: (f: Field) => f.type === FieldType.number, filter: (f: Field) => f.type === FieldType.number,
noFieldsMessage: 'No numeric fields found', noFieldsMessage: 'No numeric fields found',
@ -78,7 +78,7 @@ export const LayerEditor: FC<LayerEditorProps> = ({ options, onChange, data, fil
}) })
.addFieldNamePicker({ .addFieldNamePicker({
path: 'location.geohash', path: 'location.geohash',
name: 'Geohash Field', name: 'Geohash field',
settings: { settings: {
filter: (f: Field) => f.type === FieldType.string, filter: (f: Field) => f.type === FieldType.string,
noFieldsMessage: 'No strings fields found', noFieldsMessage: 'No strings fields found',
@ -89,7 +89,7 @@ export const LayerEditor: FC<LayerEditorProps> = ({ options, onChange, data, fil
}) })
.addFieldNamePicker({ .addFieldNamePicker({
path: 'location.lookup', path: 'location.lookup',
name: 'Lookup Field', name: 'Lookup field',
settings: { settings: {
filter: (f: Field) => f.type === FieldType.string, filter: (f: Field) => f.type === FieldType.string,
noFieldsMessage: 'No strings fields found', noFieldsMessage: 'No strings fields found',

@ -2,7 +2,6 @@ import { css } from '@emotion/react';
import { GrafanaTheme2 } from '@grafana/data'; import { GrafanaTheme2 } from '@grafana/data';
import 'ol/ol.css'; import 'ol/ol.css';
import tinycolor from 'tinycolor2';
/** /**
* Will be loaded *after* the css above * Will be loaded *after* the css above
@ -45,11 +44,9 @@ export function getGlobalStyles(theme: GrafanaTheme2) {
// border: 2px dotted rgba(0,60,136,0.7); // border: 2px dotted rgba(0,60,136,0.7);
// } // }
const bg = tinycolor(theme.v1.colors.panelBg);
const button = tinycolor(theme.colors.secondary.main);
return css` return css`
.ol-scale-line { .ol-scale-line {
background: ${bg.setAlpha(0.7).toRgbString()}; // rgba(0,60,136,0.3); background: ${theme.colors.border.weak}; // rgba(0,60,136,0.3);
} }
.ol-scale-line-inner { .ol-scale-line-inner {
border: 1px solid ${theme.colors.text.primary}; // #eee; border: 1px solid ${theme.colors.text.primary}; // #eee;
@ -57,28 +54,27 @@ export function getGlobalStyles(theme: GrafanaTheme2) {
color: ${theme.colors.text.primary}; // #eee; color: ${theme.colors.text.primary}; // #eee;
} }
.ol-control { .ol-control {
background-color: ${bg.setAlpha(0.4).toRgbString()}; //rgba(255,255,255,0.4); background-color: ${theme.colors.background.secondary}; //rgba(255,255,255,0.4);
} }
.ol-control:hover { .ol-control:hover {
background-color: ${bg.setAlpha(0.6).toRgbString()}; // rgba(255,255,255,0.6); background-color: ${theme.colors.action.hover}; // rgba(255,255,255,0.6);
} }
.ol-control button { .ol-control button {
color: ${bg.setAlpha(0.8).toRgbString()}; // white; color: ${theme.colors.secondary.text}; // white;
background-color: ${button.setAlpha(0.5).toRgbString()}; // rgba(0,60,136,0.5); background-color: ${theme.colors.secondary.main}; // rgba(0,60,136,0.5);
} }
.ol-control button:hover { .ol-control button:hover {
background-color: ${button.setAlpha(0.7).toRgbString()}; // rgba(0,60,136,0.7); background-color: ${theme.colors.secondary.shade}; // rgba(0,60,136,0.5);
} }
.ol-control button:focus { .ol-control button:focus {
// same as button background-color: ${theme.colors.secondary.main}; // rgba(0,60,136,0.5);
background-color: ${button.setAlpha(0.5).toRgbString()}; // rgba(0,60,136,0.5);
} }
.ol-attribution ul { .ol-attribution ul {
color: ${theme.colors.text.primary}; // #000; color: ${theme.colors.text.primary}; // #000;
text-shadow: 0 0 0px #fff; // removes internal styling! text-shadow: none;
} }
.ol-attribution:not(.ol-collapsed) { .ol-attribution:not(.ol-collapsed) {
background-color: ${bg.setAlpha(0.8).toRgbString()}; // rgba(255,255,255,0.8); background-color: ${theme.colors.background.secondary}; // rgba(255,255,255,0.8);
} }
`; `;
} }

@ -1,6 +1,6 @@
import React from 'react'; import React from 'react';
import { Label, stylesFactory } from '@grafana/ui'; import { Label, stylesFactory, useTheme2 } from '@grafana/ui';
import { formattedValueToString, getFieldColorModeForField, GrafanaTheme } from '@grafana/data'; import { formattedValueToString, getFieldColorModeForField, GrafanaTheme2 } from '@grafana/data';
import { css } from '@emotion/css'; import { css } from '@emotion/css';
import { config } from 'app/core/config'; import { config } from 'app/core/config';
import { DimensionSupplier } from 'app/features/dimensions'; import { DimensionSupplier } from 'app/features/dimensions';
@ -10,21 +10,22 @@ export interface MarkersLegendProps {
color?: DimensionSupplier<string>; color?: DimensionSupplier<string>;
size?: DimensionSupplier<number>; size?: DimensionSupplier<number>;
} }
export function MarkersLegend(props: MarkersLegendProps) { export function MarkersLegend(props: MarkersLegendProps) {
const { color } = props; const { color } = props;
const theme = useTheme2();
if (!color || (!color.field && color.fixed)) { if (!color || (!color.field && color.fixed)) {
return ( return <></>;
<></>
)
} }
const style = getStyles(config.theme);
const style = getStyles(theme);
const fmt = (v: any) => `${formattedValueToString(color.field!.display!(v))}`; const fmt = (v: any) => `${formattedValueToString(color.field!.display!(v))}`;
const colorMode = getFieldColorModeForField(color!.field!); const colorMode = getFieldColorModeForField(color!.field!);
if (colorMode.isContinuous && colorMode.getColors) { if (colorMode.isContinuous && colorMode.getColors) {
const colors = colorMode.getColors(config.theme2) const colors = colorMode.getColors(config.theme2);
const colorRange = getMinMaxAndDelta(color.field!) const colorRange = getMinMaxAndDelta(color.field!);
// TODO: explore showing mean on the gradiant scale // TODO: explore showing mean on the gradiant scale
// const stats = reduceField({ // const stats = reduceField({
// field: color.field!, // field: color.field!,
@ -36,13 +37,18 @@ export function MarkersLegend(props: MarkersLegendProps) {
// ] // ]
// }) // })
return <> return (
<Label>{color?.field?.name}</Label> <>
<div className={style.gradientContainer} style={{backgroundImage: `linear-gradient(to right, ${colors.map((c) => c).join(', ')}`}}> <Label>{color?.field?.name}</Label>
<div>{fmt(colorRange.min)}</div> <div
<div>{fmt(colorRange.max)}</div> className={style.gradientContainer}
</div> style={{ backgroundImage: `linear-gradient(to right, ${colors.map((c) => c).join(', ')}` }}
</> >
<div style={{ color: theme.colors.getContrastText(colors[0]) }}>{fmt(colorRange.min)}</div>
<div style={{ color: theme.colors.getContrastText(colors[colors.length - 1]) }}>{fmt(colorRange.max)}</div>
</div>
</>
);
} }
const thresholds = color.field?.config?.thresholds; const thresholds = color.field?.config?.thresholds;
@ -54,7 +60,7 @@ export function MarkersLegend(props: MarkersLegendProps) {
<div className={style.infoWrap}> <div className={style.infoWrap}>
{thresholds && ( {thresholds && (
<div className={style.legend}> <div className={style.legend}>
{thresholds.steps.map((step:any, idx:number) => { {thresholds.steps.map((step: any, idx: number) => {
const next = thresholds!.steps[idx + 1]; const next = thresholds!.steps[idx + 1];
let info = <span>?</span>; let info = <span>?</span>;
if (idx === 0) { if (idx === 0) {
@ -78,21 +84,20 @@ export function MarkersLegend(props: MarkersLegendProps) {
</div> </div>
)} )}
</div> </div>
) );
} }
const getStyles = stylesFactory((theme: GrafanaTheme) => ({ const getStyles = stylesFactory((theme: GrafanaTheme2) => ({
infoWrap: css` infoWrap: css`
color: #999; background: ${theme.colors.background.secondary};
background: #CCCC;
border-radius: 2px; border-radius: 2px;
padding: 8px; padding: ${theme.spacing(1)};
`, `,
legend: css` legend: css`
line-height: 18px; line-height: 18px;
color: #555;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
font-size: ${theme.typography.bodySmall.fontSize};
i { i {
width: 18px; width: 18px;
@ -109,5 +114,7 @@ const getStyles = stylesFactory((theme: GrafanaTheme) => ({
min-width: 200px; min-width: 200px;
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
` font-size: ${theme.typography.bodySmall.fontSize};
padding: ${theme.spacing(0, 0.5)};
`,
})); }));

@ -45,11 +45,11 @@ export const geojsonMapper: MapLayerRegistryItem<GeoJSONMapperConfig> = {
return { return {
init: () => vectorLayer, init: () => vectorLayer,
update: (data: PanelData) => { update: (data: PanelData) => {
console.log( "todo... find values matching the ID and update"); console.log('todo... find values matching the ID and update');
// Update each feature // Update each feature
source.getFeatures().forEach( f => { source.getFeatures().forEach((f) => {
console.log( "Find: ", f.getId(), f.getProperties() ); console.log('Find: ', f.getId(), f.getProperties());
}); });
}, },
}; };

@ -24,7 +24,7 @@ export interface HeatmapConfig {
const defaultOptions: HeatmapConfig = { const defaultOptions: HeatmapConfig = {
weight: { weight: {
fixed: 1, fixed: 1,
min: 0, min: 0,
max: 1, max: 1,
}, },
blur: 15, blur: 15,
@ -76,8 +76,8 @@ export const heatmapLayer: MapLayerRegistryItem<HeatmapConfig> = {
// Get data points (latitude and longitude coordinates) // Get data points (latitude and longitude coordinates)
const info = dataFrameToPoints(frame, matchers); const info = dataFrameToPoints(frame, matchers);
if(info.warning) { if (info.warning) {
console.log( 'WARN', info.warning); console.log('WARN', info.warning);
return; // ??? return; // ???
} }
@ -86,18 +86,18 @@ export const heatmapLayer: MapLayerRegistryItem<HeatmapConfig> = {
// Map each data value into new points // Map each data value into new points
for (let i = 0; i < frame.length; i++) { for (let i = 0; i < frame.length; i++) {
const cluster = new Feature({ const cluster = new Feature({
geometry: info.points[i], geometry: info.points[i],
value: weightDim.get(i), value: weightDim.get(i),
}); });
vectorSource.addFeature(cluster); vectorSource.addFeature(cluster);
}; }
vectorLayer.setSource(vectorSource); vectorLayer.setSource(vectorSource);
// Set heatmap gradient colors // Set heatmap gradient colors
let colors = ['#00f', '#0ff', '#0f0', '#ff0', '#f00']; let colors = ['#00f', '#0ff', '#0f0', '#ff0', '#f00'];
// Either the configured field or the first numeric field value // Either the configured field or the first numeric field value
const field = weightDim.field ?? frame.fields.find(field => field.type === FieldType.number); const field = weightDim.field ?? frame.fields.find((field) => field.type === FieldType.number);
if (field) { if (field) {
const colorMode = getFieldColorModeForField(field); const colorMode = getFieldColorModeForField(field);
if (colorMode.isContinuous && colorMode.getColors) { if (colorMode.isContinuous && colorMode.getColors) {
@ -123,7 +123,8 @@ export const heatmapLayer: MapLayerRegistryItem<HeatmapConfig> = {
max: 1, max: 1,
hideRange: true, // Don't show the scale factor hideRange: true, // Don't show the scale factor
}, },
defaultValue: { // Configured values defaultValue: {
// Configured values
fixed: 1, fixed: 1,
min: 0, min: 0,
max: 1, max: 1,
@ -135,9 +136,9 @@ export const heatmapLayer: MapLayerRegistryItem<HeatmapConfig> = {
name: 'Radius', name: 'Radius',
defaultValue: defaultOptions.radius, defaultValue: defaultOptions.radius,
settings: { settings: {
min: 1, min: 1,
max: 50, max: 50,
step: 1, step: 1,
}, },
}) })
.addSliderInput({ .addSliderInput({

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

@ -53,13 +53,13 @@ export const lastPointTracker: MapLayerRegistryItem<LastPointConfig> = {
const frame = data.series[0]; const frame = data.series[0];
if (frame && frame.length) { if (frame && frame.length) {
const info = dataFrameToPoints(frame, matchers); const info = dataFrameToPoints(frame, matchers);
if(info.warning) { if (info.warning) {
console.log( 'WARN', info.warning); console.log('WARN', info.warning);
return; // ??? return; // ???
} }
if(info.points?.length) { if (info.points?.length) {
const last = info.points[info.points.length-1]; const last = info.points[info.points.length - 1];
point.setGeometry(last); point.setGeometry(last);
} }
} }

@ -1,6 +1,11 @@
import React, { ReactNode } from 'react'; import React, { ReactNode } from 'react';
import { MapLayerRegistryItem, MapLayerOptions, PanelData, GrafanaTheme2, FrameGeometrySourceMode } from '@grafana/data'; import {
MapLayerRegistryItem,
MapLayerOptions,
PanelData,
GrafanaTheme2,
FrameGeometrySourceMode,
} from '@grafana/data';
import Map from 'ol/Map'; import Map from 'ol/Map';
import Feature from 'ol/Feature'; import Feature from 'ol/Feature';
import * as layer from 'ol/layer'; import * as layer from 'ol/layer';
@ -8,7 +13,12 @@ import * as source from 'ol/source';
import tinycolor from 'tinycolor2'; import tinycolor from 'tinycolor2';
import { dataFrameToPoints, getLocationMatchers } from '../../utils/location'; import { dataFrameToPoints, getLocationMatchers } from '../../utils/location';
import { ColorDimensionConfig, ScaleDimensionConfig, getScaledDimension, getColorDimension } from 'app/features/dimensions'; import {
ColorDimensionConfig,
ScaleDimensionConfig,
getScaledDimension,
getColorDimension,
} from 'app/features/dimensions';
import { ScaleDimensionEditor, ColorDimensionEditor } from 'app/features/dimensions/editors'; import { ScaleDimensionEditor, ColorDimensionEditor } from 'app/features/dimensions/editors';
import { ObservablePropsWrapper } from '../../components/ObservablePropsWrapper'; import { ObservablePropsWrapper } from '../../components/ObservablePropsWrapper';
import { MarkersLegend, MarkersLegendProps } from './MarkersLegend'; import { MarkersLegend, MarkersLegendProps } from './MarkersLegend';
@ -31,23 +41,23 @@ const defaultOptions: MarkersConfig = {
max: 15, max: 15,
}, },
color: { color: {
fixed: 'dark-green', // picked from theme fixed: 'dark-green', // picked from theme
}, },
fillOpacity: 0.4, fillOpacity: 0.4,
shape: 'circle', shape: 'circle',
showLegend: true, showLegend: true,
}; };
export const MARKERS_LAYER_ID = "markers"; export const MARKERS_LAYER_ID = 'markers';
// Used by default when nothing is configured // Used by default when nothing is configured
export const defaultMarkersConfig:MapLayerOptions<MarkersConfig> = { export const defaultMarkersConfig: MapLayerOptions<MarkersConfig> = {
type: MARKERS_LAYER_ID, type: MARKERS_LAYER_ID,
config: defaultOptions, config: defaultOptions,
location: { location: {
mode: FrameGeometrySourceMode.Auto, mode: FrameGeometrySourceMode.Auto,
} },
} };
/** /**
* Map layer configuration for circle overlay * Map layer configuration for circle overlay
@ -72,31 +82,27 @@ export const markersLayer: MapLayerRegistryItem<MarkersConfig> = {
...options?.config, ...options?.config,
}; };
const legendProps= new ReplaySubject<MarkersLegendProps>(1); const legendProps = new ReplaySubject<MarkersLegendProps>(1);
let legend:ReactNode = null; let legend: ReactNode = null;
if (config.showLegend) { if (config.showLegend) {
legend = <ObservablePropsWrapper legend = <ObservablePropsWrapper watch={legendProps} initialSubProps={{}} child={MarkersLegend} />;
watch={legendProps}
initialSubProps={{}}
child={MarkersLegend}
/>
} }
const shape = markerMakers.getIfExists(config.shape) ?? circleMarker; const shape = markerMakers.getIfExists(config.shape) ?? circleMarker;
return { return {
init: () => vectorLayer, init: () => vectorLayer,
legend: legend, legend: legend,
update: (data: PanelData) => { update: (data: PanelData) => {
if(!data.series?.length) { if (!data.series?.length) {
return; // ignore empty return; // ignore empty
} }
const features: Feature[] = []; const features: Feature[] = [];
for(const frame of data.series) { for (const frame of data.series) {
const info = dataFrameToPoints(frame, matchers); const info = dataFrameToPoints(frame, matchers);
if(info.warning) { if (info.warning) {
console.log( 'Could not find locations', info.warning); console.log('Could not find locations', info.warning);
continue; // ??? continue; // ???
} }
@ -114,7 +120,7 @@ export const markersLayer: MapLayerRegistryItem<MarkersConfig> = {
const radius = sizeDim.get(i); const radius = sizeDim.get(i);
// Create a new Feature for each point returned from dataFrameToPoints // Create a new Feature for each point returned from dataFrameToPoints
const dot = new Feature( info.points[i] ); const dot = new Feature(info.points[i]);
dot.setProperties({ dot.setProperties({
frame, frame,
rowIndex: i, rowIndex: i,
@ -122,7 +128,7 @@ export const markersLayer: MapLayerRegistryItem<MarkersConfig> = {
dot.setStyle(shape!.make(color, fillColor, radius)); dot.setStyle(shape!.make(color, fillColor, radius));
features.push(dot); features.push(dot);
}; }
// Post updates to the legend component // Post updates to the legend component
if (legend) { if (legend) {
@ -149,7 +155,8 @@ export const markersLayer: MapLayerRegistryItem<MarkersConfig> = {
name: 'Marker Color', name: 'Marker Color',
editor: ColorDimensionEditor, editor: ColorDimensionEditor,
settings: {}, settings: {},
defaultValue: { // Configured values defaultValue: {
// Configured values
fixed: 'grey', fixed: 'grey',
}, },
}) })
@ -162,7 +169,8 @@ export const markersLayer: MapLayerRegistryItem<MarkersConfig> = {
min: 1, min: 1,
max: 100, // possible in the UI max: 100, // possible in the UI
}, },
defaultValue: { // Configured values defaultValue: {
// Configured values
fixed: 5, fixed: 5,
min: 1, min: 1,
max: 20, max: 20,
@ -185,7 +193,7 @@ export const markersLayer: MapLayerRegistryItem<MarkersConfig> = {
max: 1, max: 1,
step: 0.1, step: 0.1,
}, },
showIf: (cfg) => (markerMakers.getIfExists((cfg as any).config?.shape)?.hasFill), showIf: (cfg) => markerMakers.getIfExists((cfg as any).config?.shape)?.hasFill,
}) })
.addBooleanSwitch({ .addBooleanSwitch({
path: 'config.showLegend', path: 'config.showLegend',

@ -13,7 +13,7 @@ export const plugin = new PanelPlugin<GeomapPanelOptions>(GeomapPanel)
.setPanelChangeHandler(mapPanelChangedHandler) .setPanelChangeHandler(mapPanelChangedHandler)
.useFieldConfig() .useFieldConfig()
.setPanelOptions((builder) => { .setPanelOptions((builder) => {
let category = ['Map View']; let category = ['Map view'];
builder.addCustomEditor({ builder.addCustomEditor({
category, category,
id: 'view', id: 'view',
@ -33,25 +33,25 @@ export const plugin = new PanelPlugin<GeomapPanelOptions>(GeomapPanel)
}); });
builder.addCustomEditor({ builder.addCustomEditor({
category: ['Base Layer'], category: ['Base layer'],
id: 'basemap', id: 'basemap',
path: 'basemap', path: 'basemap',
name: 'Base Layer', name: 'Base layer',
editor: BaseLayerEditor, editor: BaseLayerEditor,
defaultValue: DEFAULT_BASEMAP_CONFIG, defaultValue: DEFAULT_BASEMAP_CONFIG,
}); });
builder.addCustomEditor({ builder.addCustomEditor({
category: ['Data Layer'], category: ['Data layer'],
id: 'layers', id: 'layers',
path: 'layers', path: 'layers',
name: 'Data Layer', name: 'Data layer',
editor: DataLayersEditor, editor: DataLayersEditor,
defaultValue: [defaultMarkersConfig], defaultValue: [defaultMarkersConfig],
}); });
// The controls section // The controls section
category = ['Map Controls']; category = ['Map controls'];
builder builder
.addBooleanSwitch({ .addBooleanSwitch({
category, category,

Loading…
Cancel
Save