Geomap: Display legend (#46886)

* Display legend for fixed colors and field; Hide tooltip on base layer;
This commit is contained in:
Adela Almasan 2022-03-30 09:41:13 -05:00 committed by GitHub
parent b52794601d
commit 118b87ee8f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 88 additions and 25 deletions

View File

@ -46,6 +46,7 @@ type Props = PanelProps<GeomapPanelOptions>;
interface State extends OverlayProps {
ttip?: GeomapHoverPayload;
ttipOpen: boolean;
legends: ReactNode[];
}
export interface GeomapLayerActions {
@ -82,7 +83,7 @@ export class GeomapPanel extends Component<Props, State> {
constructor(props: Props) {
super(props);
this.state = { ttipOpen: false };
this.state = { ttipOpen: false, legends: [] };
this.subs.add(
this.props.eventBus.subscribe(PanelEditExitedEvent, (evt) => {
if (this.mapDiv && this.props.id === evt.payload) {
@ -114,7 +115,7 @@ export class GeomapPanel extends Component<Props, State> {
return true; // always?
}
/** This funciton will actually update the JSON model */
/** This function will actually update the JSON model */
private doOptionsUpdate(selected: number) {
const { options, onOptionsChange } = this.props;
const layers = this.layers;
@ -124,7 +125,7 @@ export class GeomapPanel extends Component<Props, State> {
layers: layers.slice(1).map((v) => v.options),
});
// Notify the the panel editor
// Notify the panel editor
if (this.panelContext.onInstanceStateChange) {
this.panelContext.onInstanceStateChange({
map: this.map,
@ -133,6 +134,8 @@ export class GeomapPanel extends Component<Props, State> {
actions: this.actions,
});
}
this.setState({ legends: this.getLegends() });
}
getNextLayerName = () => {
@ -308,6 +311,8 @@ export class GeomapPanel extends Component<Props, State> {
actions: this.actions,
});
}
this.setState({ legends: this.getLegends() });
};
clearTooltip = () => {
@ -447,6 +452,9 @@ export class GeomapPanel extends Component<Props, State> {
return false;
}
// Just to trigger a state update
this.setState({ legends: [] });
this.layers = layers;
this.doOptionsUpdate(layerIndex);
return true;
@ -481,6 +489,7 @@ export class GeomapPanel extends Component<Props, State> {
if (!options.name) {
options.name = this.getNextLayerName();
}
const UID = options.name;
const state: MapLayerState<any> = {
// UID, // unique name when added to the map (it may change and will need special handling)
@ -496,6 +505,7 @@ export class GeomapPanel extends Component<Props, State> {
this.updateLayer(UID, cfg);
},
};
this.byName.set(UID, state);
(state.layer as any).__state = state;
return state;
@ -597,15 +607,26 @@ export class GeomapPanel extends Component<Props, State> {
this.setState({ topRight });
}
getLegends() {
const legends: ReactNode[] = [];
for (const state of this.layers) {
if (state.handler.legend) {
legends.push(<div key={state.options.name}>{state.handler.legend}</div>);
}
}
return legends;
}
render() {
const { ttip, ttipOpen, topRight, bottomLeft } = this.state;
const { ttip, ttipOpen, topRight, legends } = this.state;
return (
<>
<Global styles={this.globalCSS} />
<div className={this.style.wrap} onMouseLeave={this.clearTooltip}>
<div className={this.style.map} ref={this.initMapRef}></div>
<GeomapOverlay bottomLeft={bottomLeft} topRight={topRight} />
<GeomapOverlay bottomLeft={legends} topRight={topRight} />
</div>
<GeomapTooltip ttip={ttip} isOpen={ttipOpen} onClose={this.tooltipPopupClosed} />
</>

View File

@ -4,7 +4,7 @@ import { NestedPanelOptions, NestedValueAccess } from '@grafana/data/src/utils/O
import { defaultMarkersConfig } from '../layers/data/markersLayer';
import { hasAlphaPanels } from 'app/core/config';
import { MapLayerState } from '../types';
import { get as lodashGet } from 'lodash';
import { get as lodashGet, isEqual } from 'lodash';
import { setOptionImmutably } from 'app/features/dashboard/components/PanelEditor/utils';
import { addLocationFields } from 'app/features/geo/editor/locationEditor';
@ -93,12 +93,14 @@ export function getLayerEditor(opts: LayerEditorOptions): NestedPanelOptions<Map
// TODO -- add opacity check
}
builder.addBooleanSwitch({
path: 'tooltip',
name: 'Display tooltip',
description: 'Show the tooltip for layer',
defaultValue: true,
});
if (!isEqual(opts.category, ['Base layer'])) {
builder.addBooleanSwitch({
path: 'tooltip',
name: 'Display tooltip',
description: 'Show the tooltip for layer',
defaultValue: true,
});
}
},
};
}

View File

@ -5,28 +5,55 @@ import { css } from '@emotion/css';
import { config } from 'app/core/config';
import { DimensionSupplier } from 'app/features/dimensions';
import { getThresholdItems } from 'app/plugins/panel/state-timeline/utils';
import { getMinMaxAndDelta } from '../../../../../../../packages/grafana-data/src/field/scale';
import { getMinMaxAndDelta } from '@grafana/data/src/field/scale';
import SVG from 'react-inlinesvg';
import { StyleConfigState } from '../../style/types';
export interface MarkersLegendProps {
color?: DimensionSupplier<string>;
size?: DimensionSupplier<number>;
layerName?: string;
styleConfig?: StyleConfigState;
}
export function MarkersLegend(props: MarkersLegendProps) {
const { color } = props;
const { layerName, styleConfig } = props;
const theme = useTheme2();
const style = getStyles(theme);
if (!color || (!color.field && color.fixed)) {
if (!styleConfig) {
return <></>;
}
const { color, opacity} = styleConfig?.base ?? {};
const symbol = styleConfig?.config.symbol?.fixed;
const colorField = styleConfig.dims?.color?.field;
if (color && symbol && !colorField) {
return (
<div className={style.infoWrap}>
<div className={style.fixedColorContainer}>
<SVG
src={`public/${symbol}`}
className={style.legendSymbol}
title={'Symbol'}
style={{ fill: color, opacity: opacity }}
/>
<span>{layerName}</span>
</div>
</div>
)
}
if (!colorField) {
return <></>;
}
const style = getStyles(theme);
const fmt = (v: any) => `${formattedValueToString(color.field!.display!(v))}`;
const colorMode = getFieldColorModeForField(color!.field!);
const fmt = (v: any) => `${formattedValueToString(colorField.display!(v))}`;
const colorMode = getFieldColorModeForField(colorField);
if (colorMode.isContinuous && colorMode.getColors) {
const colors = colorMode.getColors(config.theme2);
const colorRange = getMinMaxAndDelta(color.field!);
const colorRange = getMinMaxAndDelta(colorField);
// TODO: explore showing mean on the gradiant scale
// const stats = reduceField({
// field: color.field!,
@ -40,7 +67,7 @@ export function MarkersLegend(props: MarkersLegendProps) {
return (
<>
<Label>{color?.field?.name}</Label>
<Label>{colorField?.name}</Label>
<div
className={style.gradientContainer}
style={{ backgroundImage: `linear-gradient(to right, ${colors.map((c) => c).join(', ')}` }}
@ -52,12 +79,12 @@ export function MarkersLegend(props: MarkersLegendProps) {
);
}
const thresholds = color.field?.config?.thresholds;
const thresholds = colorField?.config?.thresholds;
if (!thresholds || thresholds.steps.length < 2) {
return <div></div>; // don't show anything in the legend
}
const items = getThresholdItems(color.field!.config, config.theme2);
const items = getThresholdItems(colorField!.config, config.theme2);
return (
<div className={style.infoWrap}>
<div className={style.legend}>
@ -95,6 +122,16 @@ const getStyles = stylesFactory((theme: GrafanaTheme2) => ({
legendItem: css`
white-space: nowrap;
`,
fixedColorContainer: css`
min-width: 80px;
font-size: ${theme.typography.bodySmall.fontSize};
`,
legendSymbol: css`
height: 10px;
width: 10px;
margin: auto;
margin-right: 4px;
`,
gradientContainer: css`
min-width: 200px;
display: flex;

View File

@ -56,7 +56,9 @@ export const markersLayer: MapLayerRegistryItem<MarkersConfig> = {
/**
* Function that configures transformation and returns a transformer
* @param map
* @param options
* @param theme
*/
create: async (map: Map, options: MapLayerOptions<MarkersConfig>, theme: GrafanaTheme2) => {
// Assert default values
@ -137,8 +139,9 @@ export const markersLayer: MapLayerRegistryItem<MarkersConfig> = {
// Post updates to the legend component
if (legend) {
legendProps.next({
color: style.dims?.color,
styleConfig: style,
size: style.dims?.size,
layerName: options.name,
});
}

View File

@ -22,7 +22,7 @@ export const defaultBaseLayer: MapLayerRegistryItem = {
if (serverLayerType) {
const layer = geomapLayerRegistry.getIfExists(serverLayerType);
if (!layer) {
throw new Error('Invalid basemap configuraiton on server');
throw new Error('Invalid basemap configuration on server');
}
return layer.create(map, config.geomapDefaultBaseLayerConfig!, theme);
}