mirror of https://github.com/grafana/grafana
Geomap: configure legend on map (#37077)
Co-authored-by: Ryan McKinley <ryantxu@gmail.com>pull/37246/head
parent
5f0bc252bc
commit
154c380c8c
@ -0,0 +1,52 @@ |
||||
import React, { Component } from 'react'; |
||||
import { Observable, Unsubscribable } from 'rxjs'; |
||||
|
||||
interface Props<T> { |
||||
watch: Observable<T>; |
||||
child: React.ComponentType<T>; |
||||
initialSubProps: T; |
||||
} |
||||
|
||||
interface State<T> { |
||||
subProps: T; |
||||
} |
||||
|
||||
export class ObservablePropsWrapper<T> extends Component<Props<T>, State<T>> { |
||||
sub?: Unsubscribable; |
||||
|
||||
constructor(props: Props<T>) { |
||||
super(props); |
||||
this.state = { |
||||
subProps: props.initialSubProps, |
||||
}; |
||||
} |
||||
|
||||
componentDidMount() { |
||||
console.log('ObservablePropsWrapper:subscribe'); |
||||
this.sub = this.props.watch.subscribe({ |
||||
next: (subProps: T) => { |
||||
console.log('ObservablePropsWrapper:NEXT', subProps); |
||||
this.setState({ subProps }); |
||||
}, |
||||
complete: () => { |
||||
console.log('ObservablePropsWrapper:complete'); |
||||
}, |
||||
error: (err) => { |
||||
console.log('ObservablePropsWrapper:error', err); |
||||
}, |
||||
}); |
||||
} |
||||
|
||||
componentWillUnmount() { |
||||
if (this.sub) { |
||||
this.sub.unsubscribe(); |
||||
} |
||||
console.log('ObservablePropsWrapper:unsubscribe'); |
||||
} |
||||
|
||||
render() { |
||||
const { subProps } = this.state; |
||||
console.log('RENDER (wrap)', subProps); |
||||
return <this.props.child {...subProps} />; |
||||
} |
||||
} |
@ -1,96 +0,0 @@ |
||||
import React, { PureComponent } from 'react'; |
||||
import { stylesFactory } from '@grafana/ui'; |
||||
import { FieldType, formattedValueToString, GrafanaTheme, PanelData, ThresholdsConfig } from '@grafana/data'; |
||||
import { css } from '@emotion/css'; |
||||
import { config } from 'app/core/config'; |
||||
import tinycolor from 'tinycolor2'; |
||||
|
||||
interface Props { |
||||
txt: string; |
||||
data?: PanelData; |
||||
} |
||||
|
||||
interface State {} |
||||
|
||||
export class SimpleLegend extends PureComponent<Props, State> { |
||||
style = getStyles(config.theme); |
||||
|
||||
constructor(props: Props) { |
||||
super(props); |
||||
this.state = {}; |
||||
} |
||||
|
||||
render() { |
||||
let fmt = (v: any) => `${v}`; |
||||
let thresholds: ThresholdsConfig | undefined; |
||||
const series = this.props.data?.series; |
||||
if (series) { |
||||
for (const frame of series) { |
||||
for (const field of frame.fields) { |
||||
if (field.type === FieldType.number && field.config.thresholds) { |
||||
thresholds = field.config.thresholds; |
||||
fmt = (v: any) => `${formattedValueToString(field.display!(v))}`; |
||||
break; |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
return ( |
||||
<div className={this.style.infoWrap}> |
||||
<div>{this.props.txt}</div> |
||||
{thresholds && ( |
||||
<div className={this.style.legend}> |
||||
{thresholds.steps.map((step, idx) => { |
||||
const next = thresholds!.steps[idx + 1]; |
||||
let info = <span>?</span>; |
||||
if (idx === 0) { |
||||
info = <span>< {fmt(next.value)}</span>; |
||||
} else if (next) { |
||||
info = ( |
||||
<span> |
||||
{fmt(step.value)} - {fmt(next.value)} |
||||
</span> |
||||
); |
||||
} else { |
||||
info = <span>{fmt(step.value)} +</span>; |
||||
} |
||||
return ( |
||||
<div key={`${idx}/${step.value}`} className={this.style.legendItem}> |
||||
<i style={{ background: config.theme2.visualization.getColorByName(step.color) }}></i> |
||||
{info} |
||||
</div> |
||||
); |
||||
})} |
||||
</div> |
||||
)} |
||||
</div> |
||||
); |
||||
} |
||||
} |
||||
|
||||
const getStyles = stylesFactory((theme: GrafanaTheme) => ({ |
||||
infoWrap: css` |
||||
color: ${theme.colors.text}; |
||||
background: ${tinycolor(theme.colors.panelBg).setAlpha(0.7).toString()}; |
||||
border-radius: 2px; |
||||
padding: 8px; |
||||
`,
|
||||
legend: css` |
||||
line-height: 18px; |
||||
color: #555; |
||||
display: flex; |
||||
flex-direction: column; |
||||
|
||||
i { |
||||
width: 18px; |
||||
height: 18px; |
||||
float: left; |
||||
margin-right: 8px; |
||||
opacity: 0.7; |
||||
} |
||||
`,
|
||||
legendItem: css` |
||||
white-space: nowrap; |
||||
`,
|
||||
})); |
@ -0,0 +1,113 @@ |
||||
import React from 'react'; |
||||
import { Label, stylesFactory } from '@grafana/ui'; |
||||
import { formattedValueToString, getFieldColorModeForField, GrafanaTheme } from '@grafana/data'; |
||||
import { css } from '@emotion/css'; |
||||
import { config } from 'app/core/config'; |
||||
import { DimensionSupplier } from '../../dims/types'; |
||||
import { getMinMaxAndDelta } from '../../../../../../../packages/grafana-data/src/field/scale';
|
||||
|
||||
export interface MarkersLegendProps { |
||||
color?: DimensionSupplier<string>; |
||||
size?: DimensionSupplier<number>; |
||||
} |
||||
export function MarkersLegend(props: MarkersLegendProps) { |
||||
const { color } = props; |
||||
if (!color || (!color.field && color.fixed)) { |
||||
return ( |
||||
<></> |
||||
) |
||||
} |
||||
const style = getStyles(config.theme); |
||||
|
||||
const fmt = (v: any) => `${formattedValueToString(color.field!.display!(v))}`; |
||||
const colorMode = getFieldColorModeForField(color!.field!); |
||||
|
||||
if (colorMode.isContinuous && colorMode.getColors) { |
||||
const colors = colorMode.getColors(config.theme2) |
||||
const colorRange = getMinMaxAndDelta(color.field!) |
||||
// TODO: explore showing mean on the gradiant scale
|
||||
// const stats = reduceField({
|
||||
// field: color.field!,
|
||||
// reducers: [
|
||||
// ReducerID.min,
|
||||
// ReducerID.max,
|
||||
// ReducerID.mean,
|
||||
// // std dev?
|
||||
// ]
|
||||
// })
|
||||
|
||||
return <> |
||||
<Label>{color?.field?.name}</Label> |
||||
<div className={style.gradientContainer} style={{backgroundImage: `linear-gradient(to right, ${colors.map((c) => c).join(', ')}`}}> |
||||
<div>{fmt(colorRange.min)}</div> |
||||
<div>{fmt(colorRange.max)}</div> |
||||
</div> |
||||
</> |
||||
} |
||||
|
||||
const thresholds = color.field?.config?.thresholds; |
||||
if (!thresholds) { |
||||
return <div className={style.infoWrap}>no thresholds????</div>; |
||||
} |
||||
|
||||
return ( |
||||
<div className={style.infoWrap}> |
||||
{thresholds && ( |
||||
<div className={style.legend}> |
||||
{thresholds.steps.map((step:any, idx:number) => { |
||||
const next = thresholds!.steps[idx + 1]; |
||||
let info = <span>?</span>; |
||||
if (idx === 0) { |
||||
info = <span>< {fmt(next.value)}</span>; |
||||
} else if (next) { |
||||
info = ( |
||||
<span> |
||||
{fmt(step.value)} - {fmt(next.value)} |
||||
</span> |
||||
); |
||||
} else { |
||||
info = <span>{fmt(step.value)} +</span>; |
||||
} |
||||
return ( |
||||
<div key={`${idx}/${step.value}`} className={style.legendItem}> |
||||
<i style={{ background: config.theme2.visualization.getColorByName(step.color) }}></i> |
||||
{info} |
||||
</div> |
||||
); |
||||
})} |
||||
</div> |
||||
)} |
||||
</div> |
||||
) |
||||
} |
||||
|
||||
const getStyles = stylesFactory((theme: GrafanaTheme) => ({ |
||||
infoWrap: css` |
||||
color: #999; |
||||
background: #CCCC; |
||||
border-radius: 2px; |
||||
padding: 8px; |
||||
`,
|
||||
legend: css` |
||||
line-height: 18px; |
||||
color: #555; |
||||
display: flex; |
||||
flex-direction: column; |
||||
|
||||
i { |
||||
width: 18px; |
||||
height: 18px; |
||||
float: left; |
||||
margin-right: 8px; |
||||
opacity: 0.7; |
||||
} |
||||
`,
|
||||
legendItem: css` |
||||
white-space: nowrap; |
||||
`,
|
||||
gradientContainer: css` |
||||
min-width: 200px; |
||||
display: flex; |
||||
justify-content: space-between; |
||||
` |
||||
})); |
Loading…
Reference in new issue