mirror of
https://github.com/grafana/grafana.git
synced 2024-11-29 20:24:18 -06:00
Geomap: Add color scale in legend (#47803)
Added color scale in geomap legend
This commit is contained in:
parent
516c8b60ee
commit
858a1bd24e
@ -11,6 +11,7 @@ type Props = {
|
||||
// Show a value as string -- when not defined, the raw values will not be shown
|
||||
display?: (v: number) => string;
|
||||
hoverValue?: number;
|
||||
useStopsPercentage?: boolean;
|
||||
};
|
||||
|
||||
type HoverState = {
|
||||
@ -19,8 +20,9 @@ type HoverState = {
|
||||
};
|
||||
|
||||
const LEFT_OFFSET = 2;
|
||||
const GRADIENT_STOPS = 10;
|
||||
|
||||
export const ColorScale = ({ colorPalette, min, max, display, hoverValue }: Props) => {
|
||||
export const ColorScale = ({ colorPalette, min, max, display, hoverValue, useStopsPercentage }: Props) => {
|
||||
const [colors, setColors] = useState<string[]>([]);
|
||||
const [scaleHover, setScaleHover] = useState<HoverState>({ isShown: false, value: 0 });
|
||||
const [percent, setPercent] = useState<number | null>(null);
|
||||
@ -29,8 +31,8 @@ export const ColorScale = ({ colorPalette, min, max, display, hoverValue }: Prop
|
||||
const styles = getStyles(theme, colors);
|
||||
|
||||
useEffect(() => {
|
||||
setColors(getGradientStops({ colorArray: colorPalette }));
|
||||
}, [colorPalette]);
|
||||
setColors(getGradientStops({ colorArray: colorPalette, stops: GRADIENT_STOPS, useStopsPercentage }));
|
||||
}, [colorPalette, useStopsPercentage]);
|
||||
|
||||
const onScaleMouseMove = (event: React.MouseEvent<HTMLDivElement>) => {
|
||||
const divOffset = event.nativeEvent.offsetX;
|
||||
@ -79,9 +81,17 @@ export const ColorScale = ({ colorPalette, min, max, display, hoverValue }: Prop
|
||||
);
|
||||
};
|
||||
|
||||
const getGradientStops = ({ colorArray, stops = 10 }: { colorArray: string[]; stops?: number }): string[] => {
|
||||
const getGradientStops = ({
|
||||
colorArray,
|
||||
stops,
|
||||
useStopsPercentage = true,
|
||||
}: {
|
||||
colorArray: string[];
|
||||
stops: number;
|
||||
useStopsPercentage?: boolean;
|
||||
}): string[] => {
|
||||
const colorCount = colorArray.length;
|
||||
if (colorCount <= 20) {
|
||||
if (useStopsPercentage && colorCount <= 20) {
|
||||
const incr = (1 / colorCount) * 100;
|
||||
let per = 0;
|
||||
const stops: string[] = [];
|
||||
@ -114,8 +124,6 @@ const getStyles = (theme: GrafanaTheme2, colors: string[]) => ({
|
||||
scaleWrapper: css`
|
||||
width: 100%;
|
||||
max-width: 300px;
|
||||
margin-left: 25px;
|
||||
padding: 10px 0;
|
||||
font-size: 11px;
|
||||
opacity: 1;
|
||||
`,
|
@ -33,11 +33,12 @@ import { DebugOverlay } from './components/DebugOverlay';
|
||||
import { getGlobalStyles } from './globalStyles';
|
||||
import { Global } from '@emotion/react';
|
||||
import { GeomapHoverPayload, GeomapLayerHover } from './event';
|
||||
import { Subscription } from 'rxjs';
|
||||
import { Subject, Subscription } from 'rxjs';
|
||||
import { PanelEditExitedEvent } from 'app/types/events';
|
||||
import { defaultMarkersConfig, MARKERS_LAYER_ID } from './layers/data/markersLayer';
|
||||
import { cloneDeep } from 'lodash';
|
||||
import { GeomapTooltip } from './GeomapTooltip';
|
||||
import { FeatureLike } from 'ol/Feature';
|
||||
|
||||
// Allows multiple panels to share the same view instance
|
||||
let sharedView: View | undefined = undefined;
|
||||
@ -313,7 +314,7 @@ export class GeomapPanel extends Component<Props, State> {
|
||||
this.props.eventBus.publish(new DataHoverClearEvent());
|
||||
});
|
||||
|
||||
// Notify the the panel editor
|
||||
// Notify the panel editor
|
||||
if (this.panelContext.onInstanceStateChange) {
|
||||
this.panelContext.onInstanceStateChange({
|
||||
map: this.map,
|
||||
@ -371,6 +372,7 @@ export class GeomapPanel extends Component<Props, State> {
|
||||
this.map.forEachFeatureAtPixel(
|
||||
pixel,
|
||||
(feature, layer, geo) => {
|
||||
const s: MapLayerState = (layer as any).__state;
|
||||
//match hover layer to layer in layers
|
||||
//check if the layer show tooltip is enabled
|
||||
//then also pass the list of tooltip fields if exists
|
||||
@ -382,9 +384,12 @@ export class GeomapPanel extends Component<Props, State> {
|
||||
hoverPayload.data = ttip.data = frame as DataFrame;
|
||||
hoverPayload.rowIndex = ttip.rowIndex = props['rowIndex'];
|
||||
}
|
||||
|
||||
if (s?.mouseEvents) {
|
||||
s.mouseEvents.next(feature);
|
||||
}
|
||||
}
|
||||
|
||||
const s: MapLayerState = (layer as any).__state;
|
||||
if (s) {
|
||||
let h = layerLookup.get(s);
|
||||
if (!h) {
|
||||
@ -406,6 +411,14 @@ export class GeomapPanel extends Component<Props, State> {
|
||||
this.props.eventBus.publish(this.hoverEvent);
|
||||
|
||||
this.setState({ ttip: { ...hoverPayload } });
|
||||
|
||||
if (!layers.length) {
|
||||
// clear mouse events
|
||||
this.layers.forEach((layer) => {
|
||||
layer.mouseEvents.next(undefined);
|
||||
});
|
||||
}
|
||||
|
||||
return layers.length ? true : false;
|
||||
};
|
||||
|
||||
@ -508,6 +521,7 @@ export class GeomapPanel extends Component<Props, State> {
|
||||
options,
|
||||
layer,
|
||||
handler,
|
||||
mouseEvents: new Subject<FeatureLike | undefined>(),
|
||||
|
||||
getName: () => UID,
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
import React, { useMemo } from 'react';
|
||||
import { Label, stylesFactory, useTheme2, VizLegendItem } from '@grafana/ui';
|
||||
import { formattedValueToString, getFieldColorModeForField, GrafanaTheme2 } from '@grafana/data';
|
||||
import { DataFrame, formattedValueToString, getFieldColorModeForField, GrafanaTheme2 } from '@grafana/data';
|
||||
import { css } from '@emotion/css';
|
||||
import { config } from 'app/core/config';
|
||||
import { DimensionSupplier } from 'app/features/dimensions';
|
||||
@ -8,26 +8,50 @@ import { getThresholdItems } from 'app/plugins/panel/state-timeline/utils';
|
||||
import { getMinMaxAndDelta } from '@grafana/data/src/field/scale';
|
||||
import SVG from 'react-inlinesvg';
|
||||
import { StyleConfigState } from '../../style/types';
|
||||
import { ColorScale } from 'app/core/components/ColorScale/ColorScale';
|
||||
import { useObservable } from 'react-use';
|
||||
import { of } from 'rxjs';
|
||||
import BaseLayer from 'ol/layer/Base';
|
||||
import { MapLayerState } from '../../types';
|
||||
|
||||
export interface MarkersLegendProps {
|
||||
size?: DimensionSupplier<number>;
|
||||
layerName?: string;
|
||||
styleConfig?: StyleConfigState;
|
||||
layer?: BaseLayer;
|
||||
}
|
||||
|
||||
export function MarkersLegend(props: MarkersLegendProps) {
|
||||
const { layerName, styleConfig } = props;
|
||||
const { layerName, styleConfig, layer } = props;
|
||||
const theme = useTheme2();
|
||||
const style = getStyles(theme);
|
||||
|
||||
const hoverEvent = useObservable(((layer as any)?.__state as MapLayerState)?.mouseEvents ?? of(undefined));
|
||||
|
||||
const colorField = styleConfig?.dims?.color?.field;
|
||||
const hoverValue = useMemo(() => {
|
||||
if (!colorField || !hoverEvent) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const props = hoverEvent.getProperties();
|
||||
const frame = props.frame as DataFrame;
|
||||
|
||||
if (!frame) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const rowIndex = props.rowIndex as number;
|
||||
return colorField.values.get(rowIndex);
|
||||
}, [hoverEvent, colorField]);
|
||||
|
||||
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}>
|
||||
@ -48,7 +72,6 @@ export function MarkersLegend(props: MarkersLegendProps) {
|
||||
return <></>;
|
||||
}
|
||||
|
||||
const fmt = (v: any) => `${formattedValueToString(colorField.display!(v))}`;
|
||||
const colorMode = getFieldColorModeForField(colorField);
|
||||
|
||||
if (colorMode.isContinuous && colorMode.getColors) {
|
||||
@ -65,15 +88,15 @@ export function MarkersLegend(props: MarkersLegendProps) {
|
||||
// ]
|
||||
// })
|
||||
|
||||
const display = colorField.display ? (v: number) => formattedValueToString(colorField.display!(v)) : (v: number) => `${v}`;
|
||||
return (
|
||||
<>
|
||||
<Label>{colorField?.name}</Label>
|
||||
<div
|
||||
className={style.gradientContainer}
|
||||
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 className={style.labelsWrapper}>
|
||||
<Label>{layerName}</Label>
|
||||
<Label>{colorField?.name}</Label>
|
||||
</div>
|
||||
<div className={style.colorScaleWrapper}>
|
||||
<ColorScale hoverValue={hoverValue} colorPalette={colors} min={colorRange.min as number} max={colorRange.max as number} display={display} useStopsPercentage={false}/>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
@ -132,11 +155,13 @@ const getStyles = stylesFactory((theme: GrafanaTheme2) => ({
|
||||
margin: auto;
|
||||
margin-right: 4px;
|
||||
`,
|
||||
gradientContainer: css`
|
||||
colorScaleWrapper: css`
|
||||
min-width: 200px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
font-size: ${theme.typography.bodySmall.fontSize};
|
||||
padding: ${theme.spacing(0, 0.5)};
|
||||
`,
|
||||
labelsWrapper: css`
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
`
|
||||
}));
|
||||
|
@ -67,12 +67,6 @@ export const markersLayer: MapLayerRegistryItem<MarkersConfig> = {
|
||||
...options?.config,
|
||||
};
|
||||
|
||||
const legendProps = new ReplaySubject<MarkersLegendProps>(1);
|
||||
let legend: ReactNode = null;
|
||||
if (config.showLegend) {
|
||||
legend = <ObservablePropsWrapper watch={legendProps} initialSubProps={{}} child={MarkersLegend} />;
|
||||
}
|
||||
|
||||
const style = await getStyleConfigState(config.style);
|
||||
const location = await getLocationMatchers(options.location);
|
||||
const source = new FrameVectorSource(location);
|
||||
@ -80,6 +74,12 @@ export const markersLayer: MapLayerRegistryItem<MarkersConfig> = {
|
||||
source,
|
||||
});
|
||||
|
||||
const legendProps = new ReplaySubject<MarkersLegendProps>(1);
|
||||
let legend: ReactNode = null;
|
||||
if (config.showLegend) {
|
||||
legend = <ObservablePropsWrapper watch={legendProps} initialSubProps={{}} child={MarkersLegend} />;
|
||||
}
|
||||
|
||||
if (!style.fields) {
|
||||
// Set a global style
|
||||
vectorLayer.setStyle(style.maker(style.base));
|
||||
@ -142,6 +142,7 @@ export const markersLayer: MapLayerRegistryItem<MarkersConfig> = {
|
||||
styleConfig: style,
|
||||
size: style.dims?.size,
|
||||
layerName: options.name,
|
||||
layer: vectorLayer,
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -5,6 +5,8 @@ import BaseLayer from 'ol/layer/Base';
|
||||
import Units from 'ol/proj/Units';
|
||||
import { StyleConfig } from './style/types';
|
||||
import { MapCenterID } from './view';
|
||||
import { Subject } from 'rxjs';
|
||||
import { FeatureLike } from 'ol/Feature';
|
||||
|
||||
export interface ControlsOptions {
|
||||
// Zoom (upper left)
|
||||
@ -80,4 +82,5 @@ export interface MapLayerState<TConfig = any> extends LayerElement {
|
||||
layer: BaseLayer; // the openlayers instance
|
||||
onChange: (cfg: MapLayerOptions<TConfig>) => void;
|
||||
isBasemap?: boolean;
|
||||
mouseEvents: Subject<FeatureLike | undefined>;
|
||||
}
|
||||
|
@ -18,7 +18,7 @@ import { quantizeScheme } from './palettes';
|
||||
import { HeatmapHoverEvent, prepConfig } from './utils';
|
||||
import { HeatmapHoverView } from './HeatmapHoverView';
|
||||
import { CloseButton } from 'app/core/components/CloseButton/CloseButton';
|
||||
import { ColorScale } from './ColorScale';
|
||||
import { ColorScale } from 'app/core/components/ColorScale/ColorScale';
|
||||
|
||||
interface HeatmapPanelProps extends PanelProps<PanelOptions> {}
|
||||
|
||||
@ -113,7 +113,9 @@ export const HeatmapPanel: React.FC<HeatmapPanelProps> = ({
|
||||
|
||||
return (
|
||||
<VizLayout.Legend placement="bottom" maxHeight="20%">
|
||||
<ColorScale hoverValue={hoverValue} colorPalette={palette} min={min} max={max} display={info.display} />
|
||||
<div className={styles.colorScaleWrapper}>
|
||||
<ColorScale hoverValue={hoverValue} colorPalette={palette} min={min} max={max} display={info.display} />
|
||||
</div>
|
||||
</VizLayout.Legend>
|
||||
);
|
||||
};
|
||||
@ -164,4 +166,8 @@ const getStyles = (theme: GrafanaTheme2) => ({
|
||||
closeButtonSpacer: css`
|
||||
margin-bottom: 15px;
|
||||
`,
|
||||
colorScaleWrapper: css`
|
||||
margin-left: 25px;
|
||||
padding: 10px 0;
|
||||
`,
|
||||
});
|
||||
|
@ -15,7 +15,7 @@ import { heatmapChangedHandler } from './migrations';
|
||||
import { addHeatmapCalculationOptions } from 'app/features/transformers/calculateHeatmap/editor/helper';
|
||||
import { colorSchemes, quantizeScheme } from './palettes';
|
||||
import { config } from '@grafana/runtime';
|
||||
import { ColorScale } from './ColorScale';
|
||||
import { ColorScale } from 'app/core/components/ColorScale/ColorScale';
|
||||
|
||||
export const plugin = new PanelPlugin<PanelOptions, GraphFieldConfig>(HeatmapPanel)
|
||||
.useFieldConfig()
|
||||
|
Loading…
Reference in New Issue
Block a user