|
|
|
@ -1,5 +1,5 @@ |
|
|
|
|
// Library
|
|
|
|
|
import React, { PureComponent, CSSProperties } from 'react'; |
|
|
|
|
import React, { PureComponent, CSSProperties, ReactNode } from 'react'; |
|
|
|
|
import tinycolor from 'tinycolor2'; |
|
|
|
|
|
|
|
|
|
// Utils
|
|
|
|
@ -23,22 +23,37 @@ export interface Props extends Themeable { |
|
|
|
|
prefix?: string; |
|
|
|
|
suffix?: string; |
|
|
|
|
decimals?: number; |
|
|
|
|
displayMode: 'simple' | 'lcd'; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/* |
|
|
|
|
* This visualization is still in POC state, needed more tests & better structure |
|
|
|
|
*/ |
|
|
|
|
export class BarGauge extends PureComponent<Props> { |
|
|
|
|
static defaultProps: Partial<Props> = { |
|
|
|
|
maxValue: 100, |
|
|
|
|
minValue: 0, |
|
|
|
|
value: 100, |
|
|
|
|
unit: 'none', |
|
|
|
|
displayMode: 'simple', |
|
|
|
|
orientation: VizOrientation.Horizontal, |
|
|
|
|
thresholds: [], |
|
|
|
|
valueMappings: [], |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
render() { |
|
|
|
|
const { maxValue, minValue, unit, decimals, displayMode } = this.props; |
|
|
|
|
|
|
|
|
|
const numericValue = this.getNumericValue(); |
|
|
|
|
const valuePercent = Math.min(numericValue / (maxValue - minValue), 1); |
|
|
|
|
|
|
|
|
|
const formatFunc = getValueFormat(unit); |
|
|
|
|
const valueFormatted = formatFunc(numericValue, decimals); |
|
|
|
|
|
|
|
|
|
if (displayMode === 'lcd') { |
|
|
|
|
return this.renderLcdMode(valueFormatted, valuePercent); |
|
|
|
|
} else { |
|
|
|
|
return this.renderSimpleMode(valueFormatted, valuePercent); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
getNumericValue(): number { |
|
|
|
|
if (Number.isFinite(this.props.value as number)) { |
|
|
|
|
return this.props.value as number; |
|
|
|
@ -70,28 +85,6 @@ export class BarGauge extends PureComponent<Props> { |
|
|
|
|
}; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
getCellColor(positionValue: TimeSeriesValue): string { |
|
|
|
|
const { thresholds, theme, value } = this.props; |
|
|
|
|
const activeThreshold = getThresholdForValue(thresholds, positionValue); |
|
|
|
|
|
|
|
|
|
if (activeThreshold !== null) { |
|
|
|
|
const color = getColorFromHexRgbOrName(activeThreshold.color, theme.type); |
|
|
|
|
|
|
|
|
|
// if we are past real value the cell is not "on"
|
|
|
|
|
if (value === null || (positionValue !== null && positionValue > value)) { |
|
|
|
|
return tinycolor(color) |
|
|
|
|
.setAlpha(0.15) |
|
|
|
|
.toRgbString(); |
|
|
|
|
} else { |
|
|
|
|
return tinycolor(color) |
|
|
|
|
.setAlpha(0.7) |
|
|
|
|
.toRgbString(); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return 'gray'; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
getValueStyles(value: string, color: string, width: number): CSSProperties { |
|
|
|
|
const guess = width / (value.length * 1.1); |
|
|
|
|
const fontSize = Math.min(Math.max(guess, 14), 40); |
|
|
|
@ -102,29 +95,50 @@ export class BarGauge extends PureComponent<Props> { |
|
|
|
|
}; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
renderVerticalBar(valueFormatted: string, valuePercent: number) { |
|
|
|
|
const { height, width } = this.props; |
|
|
|
|
/* |
|
|
|
|
* Return width or height depending on viz orientation |
|
|
|
|
* */ |
|
|
|
|
get size() { |
|
|
|
|
const { height, width, orientation } = this.props; |
|
|
|
|
return orientation === VizOrientation.Horizontal ? width : height; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
renderSimpleMode(valueFormatted: string, valuePercent: number): ReactNode { |
|
|
|
|
const { height, width, orientation } = this.props; |
|
|
|
|
|
|
|
|
|
const maxHeight = height * BAR_SIZE_RATIO; |
|
|
|
|
const barHeight = Math.max(valuePercent * maxHeight, 0); |
|
|
|
|
const maxSize = this.size * BAR_SIZE_RATIO; |
|
|
|
|
const barSize = Math.max(valuePercent * maxSize, 0); |
|
|
|
|
const colors = this.getValueColors(); |
|
|
|
|
const valueStyles = this.getValueStyles(valueFormatted, colors.value, width); |
|
|
|
|
const valueStyles = this.getValueStyles(valueFormatted, colors.value, this.size - maxSize); |
|
|
|
|
|
|
|
|
|
const containerStyles: CSSProperties = { |
|
|
|
|
width: `${width}px`, |
|
|
|
|
height: `${height}px`, |
|
|
|
|
display: 'flex', |
|
|
|
|
flexDirection: 'column', |
|
|
|
|
justifyContent: 'flex-end', |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
const barStyles: CSSProperties = { |
|
|
|
|
height: `${barHeight}px`, |
|
|
|
|
width: `${width}px`, |
|
|
|
|
backgroundColor: colors.bar, |
|
|
|
|
borderTop: `1px solid ${colors.border}`, |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
// Custom styles for vertical orientation
|
|
|
|
|
if (orientation === VizOrientation.Vertical) { |
|
|
|
|
containerStyles.flexDirection = 'column'; |
|
|
|
|
containerStyles.justifyContent = 'flex-end'; |
|
|
|
|
barStyles.height = `${barSize}px`; |
|
|
|
|
barStyles.width = `${width}px`; |
|
|
|
|
barStyles.borderTop = `1px solid ${colors.border}`; |
|
|
|
|
} else { |
|
|
|
|
// Custom styles for horizontal orientation
|
|
|
|
|
containerStyles.flexDirection = 'row-reverse'; |
|
|
|
|
containerStyles.justifyContent = 'flex-end'; |
|
|
|
|
containerStyles.alignItems = 'center'; |
|
|
|
|
barStyles.height = `${height}px`; |
|
|
|
|
barStyles.width = `${barSize}px`; |
|
|
|
|
barStyles.marginRight = '10px'; |
|
|
|
|
barStyles.borderRight = `1px solid ${colors.border}`; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return ( |
|
|
|
|
<div style={containerStyles}> |
|
|
|
|
<div className="bar-gauge__value" style={valueStyles}> |
|
|
|
@ -135,74 +149,73 @@ export class BarGauge extends PureComponent<Props> { |
|
|
|
|
); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
renderHorizontalBar(valueFormatted: string, valuePercent: number) { |
|
|
|
|
const { height, width } = this.props; |
|
|
|
|
|
|
|
|
|
const maxWidth = width * BAR_SIZE_RATIO; |
|
|
|
|
const barWidth = Math.max(valuePercent * maxWidth, 0); |
|
|
|
|
const colors = this.getValueColors(); |
|
|
|
|
const valueStyles = this.getValueStyles(valueFormatted, colors.value, width * (1 - BAR_SIZE_RATIO)); |
|
|
|
|
|
|
|
|
|
valueStyles.marginLeft = '8px'; |
|
|
|
|
getCellColor(positionValue: TimeSeriesValue): string { |
|
|
|
|
const { thresholds, theme, value } = this.props; |
|
|
|
|
const activeThreshold = getThresholdForValue(thresholds, positionValue); |
|
|
|
|
|
|
|
|
|
const containerStyles: CSSProperties = { |
|
|
|
|
width: `${width}px`, |
|
|
|
|
height: `${height}px`, |
|
|
|
|
display: 'flex', |
|
|
|
|
flexDirection: 'row', |
|
|
|
|
alignItems: 'center', |
|
|
|
|
}; |
|
|
|
|
if (activeThreshold !== null) { |
|
|
|
|
const color = getColorFromHexRgbOrName(activeThreshold.color, theme.type); |
|
|
|
|
|
|
|
|
|
const barStyles = { |
|
|
|
|
height: `${height}px`, |
|
|
|
|
width: `${barWidth}px`, |
|
|
|
|
backgroundColor: colors.bar, |
|
|
|
|
borderRight: `1px solid ${colors.border}`, |
|
|
|
|
}; |
|
|
|
|
// if we are past real value the cell is not "on"
|
|
|
|
|
if (value === null || (positionValue !== null && positionValue > value)) { |
|
|
|
|
return tinycolor(color) |
|
|
|
|
.setAlpha(0.15) |
|
|
|
|
.toRgbString(); |
|
|
|
|
} else { |
|
|
|
|
return tinycolor(color) |
|
|
|
|
.setAlpha(0.7) |
|
|
|
|
.toRgbString(); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return ( |
|
|
|
|
<div style={containerStyles}> |
|
|
|
|
<div style={barStyles} /> |
|
|
|
|
<div className="bar-gauge__value" style={valueStyles}> |
|
|
|
|
{valueFormatted} |
|
|
|
|
</div> |
|
|
|
|
</div> |
|
|
|
|
); |
|
|
|
|
return 'gray'; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
renderHorizontalLCD(valueFormatted: string, valuePercent: number) { |
|
|
|
|
const { height, width, maxValue, minValue } = this.props; |
|
|
|
|
renderLcdMode(valueFormatted: string, valuePercent: number): ReactNode { |
|
|
|
|
const { height, width, maxValue, minValue, orientation } = this.props; |
|
|
|
|
|
|
|
|
|
const valueRange = maxValue - minValue; |
|
|
|
|
const maxWidth = width * BAR_SIZE_RATIO; |
|
|
|
|
const maxSize = this.size * BAR_SIZE_RATIO; |
|
|
|
|
const cellSpacing = 4; |
|
|
|
|
const cellCount = 30; |
|
|
|
|
const cellWidth = (maxWidth - cellSpacing * cellCount) / cellCount; |
|
|
|
|
const cellSize = (maxSize - cellSpacing * cellCount) / cellCount; |
|
|
|
|
const colors = this.getValueColors(); |
|
|
|
|
const valueStyles = this.getValueStyles(valueFormatted, colors.value, width * (1 - BAR_SIZE_RATIO)); |
|
|
|
|
valueStyles.marginLeft = '8px'; |
|
|
|
|
const valueStyles = this.getValueStyles(valueFormatted, colors.value, this.size - maxSize); |
|
|
|
|
|
|
|
|
|
const containerStyles: CSSProperties = { |
|
|
|
|
width: `${width}px`, |
|
|
|
|
height: `${height}px`, |
|
|
|
|
display: 'flex', |
|
|
|
|
flexDirection: 'row', |
|
|
|
|
alignItems: 'center', |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
if (orientation === VizOrientation.Horizontal) { |
|
|
|
|
containerStyles.flexDirection = 'row'; |
|
|
|
|
containerStyles.alignItems = 'center'; |
|
|
|
|
} else { |
|
|
|
|
containerStyles.flexDirection = 'column-reverse'; |
|
|
|
|
containerStyles.alignItems = 'center'; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
const cells: JSX.Element[] = []; |
|
|
|
|
|
|
|
|
|
for (let i = 0; i < cellCount; i++) { |
|
|
|
|
const currentValue = (valueRange / cellCount) * i; |
|
|
|
|
const cellColor = this.getCellColor(currentValue); |
|
|
|
|
const cellStyles: CSSProperties = { |
|
|
|
|
width: `${cellWidth}px`, |
|
|
|
|
backgroundColor: cellColor, |
|
|
|
|
marginRight: '4px', |
|
|
|
|
height: `${height}px`, |
|
|
|
|
borderRadius: '2px', |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
if (orientation === VizOrientation.Horizontal) { |
|
|
|
|
cellStyles.width = `${cellSize}px`; |
|
|
|
|
cellStyles.height = `${height}px`; |
|
|
|
|
cellStyles.marginRight = '4px'; |
|
|
|
|
} else { |
|
|
|
|
cellStyles.height = `${cellSize}px`; |
|
|
|
|
cellStyles.width = `${width}px`; |
|
|
|
|
cellStyles.marginTop = '4px'; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
cells.push(<div style={cellStyles} />); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
@ -215,21 +228,6 @@ export class BarGauge extends PureComponent<Props> { |
|
|
|
|
</div> |
|
|
|
|
); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
render() { |
|
|
|
|
const { maxValue, minValue, orientation, unit, decimals } = this.props; |
|
|
|
|
|
|
|
|
|
const numericValue = this.getNumericValue(); |
|
|
|
|
const valuePercent = Math.min(numericValue / (maxValue - minValue), 1); |
|
|
|
|
|
|
|
|
|
const formatFunc = getValueFormat(unit); |
|
|
|
|
const valueFormatted = formatFunc(numericValue, decimals); |
|
|
|
|
const vertical = orientation === 'vertical'; |
|
|
|
|
|
|
|
|
|
return vertical |
|
|
|
|
? this.renderVerticalBar(valueFormatted, valuePercent) |
|
|
|
|
: this.renderHorizontalLCD(valueFormatted, valuePercent); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
interface BarColors { |
|
|
|
|