diff --git a/public/app/plugins/panel/heatmap-new/ColorScale.tsx b/public/app/core/components/ColorScale/ColorScale.tsx similarity index 89% rename from public/app/plugins/panel/heatmap-new/ColorScale.tsx rename to public/app/core/components/ColorScale/ColorScale.tsx index e21f231e94f..31080de26bd 100644 --- a/public/app/plugins/panel/heatmap-new/ColorScale.tsx +++ b/public/app/core/components/ColorScale/ColorScale.tsx @@ -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([]); const [scaleHover, setScaleHover] = useState({ isShown: false, value: 0 }); const [percent, setPercent] = useState(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) => { 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; `, diff --git a/public/app/plugins/panel/geomap/GeomapPanel.tsx b/public/app/plugins/panel/geomap/GeomapPanel.tsx index c49c6a37b73..420a3125a9c 100644 --- a/public/app/plugins/panel/geomap/GeomapPanel.tsx +++ b/public/app/plugins/panel/geomap/GeomapPanel.tsx @@ -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 { 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 { 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 { 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 { 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 { options, layer, handler, + mouseEvents: new Subject(), getName: () => UID, diff --git a/public/app/plugins/panel/geomap/layers/data/MarkersLegend.tsx b/public/app/plugins/panel/geomap/layers/data/MarkersLegend.tsx index b403b1fb805..b1e982b68f4 100644 --- a/public/app/plugins/panel/geomap/layers/data/MarkersLegend.tsx +++ b/public/app/plugins/panel/geomap/layers/data/MarkersLegend.tsx @@ -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; 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 (
@@ -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 ( <> - -
c).join(', ')}` }} - > -
{fmt(colorRange.min)}
-
{fmt(colorRange.max)}
+
+ + +
+
+
); @@ -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; + ` })); diff --git a/public/app/plugins/panel/geomap/layers/data/markersLayer.tsx b/public/app/plugins/panel/geomap/layers/data/markersLayer.tsx index 64dcb32ab0d..8641c09ce0b 100644 --- a/public/app/plugins/panel/geomap/layers/data/markersLayer.tsx +++ b/public/app/plugins/panel/geomap/layers/data/markersLayer.tsx @@ -67,12 +67,6 @@ export const markersLayer: MapLayerRegistryItem = { ...options?.config, }; - const legendProps = new ReplaySubject(1); - let legend: ReactNode = null; - if (config.showLegend) { - legend = ; - } - 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 = { source, }); + const legendProps = new ReplaySubject(1); + let legend: ReactNode = null; + if (config.showLegend) { + legend = ; + } + if (!style.fields) { // Set a global style vectorLayer.setStyle(style.maker(style.base)); @@ -142,6 +142,7 @@ export const markersLayer: MapLayerRegistryItem = { styleConfig: style, size: style.dims?.size, layerName: options.name, + layer: vectorLayer, }); } diff --git a/public/app/plugins/panel/geomap/types.ts b/public/app/plugins/panel/geomap/types.ts index 9944c5b5ac9..575ba4f333d 100644 --- a/public/app/plugins/panel/geomap/types.ts +++ b/public/app/plugins/panel/geomap/types.ts @@ -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 extends LayerElement { layer: BaseLayer; // the openlayers instance onChange: (cfg: MapLayerOptions) => void; isBasemap?: boolean; + mouseEvents: Subject; } diff --git a/public/app/plugins/panel/heatmap-new/HeatmapPanel.tsx b/public/app/plugins/panel/heatmap-new/HeatmapPanel.tsx index a76ece81a90..490f66842ff 100644 --- a/public/app/plugins/panel/heatmap-new/HeatmapPanel.tsx +++ b/public/app/plugins/panel/heatmap-new/HeatmapPanel.tsx @@ -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 {} @@ -113,7 +113,9 @@ export const HeatmapPanel: React.FC = ({ return ( - +
+ +
); }; @@ -164,4 +166,8 @@ const getStyles = (theme: GrafanaTheme2) => ({ closeButtonSpacer: css` margin-bottom: 15px; `, + colorScaleWrapper: css` + margin-left: 25px; + padding: 10px 0; + `, }); diff --git a/public/app/plugins/panel/heatmap-new/module.tsx b/public/app/plugins/panel/heatmap-new/module.tsx index 3e9afc9e063..d425a25217d 100644 --- a/public/app/plugins/panel/heatmap-new/module.tsx +++ b/public/app/plugins/panel/heatmap-new/module.tsx @@ -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(HeatmapPanel) .useFieldConfig()