BarGauge.tsx 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239
  1. // Library
  2. import React, { PureComponent, CSSProperties } from 'react';
  3. import tinycolor from 'tinycolor2';
  4. // Utils
  5. import { getColorFromHexRgbOrName, getValueFormat, getThresholdForValue } from '../../utils';
  6. // Types
  7. import { Themeable, TimeSeriesValue, Threshold, ValueMapping, VizOrientation } from '../../types';
  8. const BAR_SIZE_RATIO = 0.8;
  9. export interface Props extends Themeable {
  10. height: number;
  11. unit: string;
  12. width: number;
  13. thresholds: Threshold[];
  14. valueMappings: ValueMapping[];
  15. value: TimeSeriesValue;
  16. maxValue: number;
  17. minValue: number;
  18. orientation: VizOrientation;
  19. prefix?: string;
  20. suffix?: string;
  21. decimals?: number;
  22. }
  23. /*
  24. * This visualization is still in POC state, needed more tests & better structure
  25. */
  26. export class BarGauge extends PureComponent<Props> {
  27. static defaultProps: Partial<Props> = {
  28. maxValue: 100,
  29. minValue: 0,
  30. value: 100,
  31. unit: 'none',
  32. orientation: VizOrientation.Horizontal,
  33. thresholds: [],
  34. valueMappings: [],
  35. };
  36. getNumericValue(): number {
  37. if (Number.isFinite(this.props.value as number)) {
  38. return this.props.value as number;
  39. }
  40. return 0;
  41. }
  42. getValueColors(): BarColors {
  43. const { thresholds, theme, value } = this.props;
  44. const activeThreshold = getThresholdForValue(thresholds, value);
  45. if (activeThreshold !== null) {
  46. const color = getColorFromHexRgbOrName(activeThreshold.color, theme.type);
  47. return {
  48. value: color,
  49. border: color,
  50. bar: tinycolor(color)
  51. .setAlpha(0.3)
  52. .toRgbString(),
  53. };
  54. }
  55. return {
  56. value: getColorFromHexRgbOrName('gray', theme.type),
  57. bar: getColorFromHexRgbOrName('gray', theme.type),
  58. border: getColorFromHexRgbOrName('gray', theme.type),
  59. };
  60. }
  61. getCellColor(positionValue: TimeSeriesValue): string {
  62. const { thresholds, theme, value } = this.props;
  63. const activeThreshold = getThresholdForValue(thresholds, positionValue);
  64. if (activeThreshold !== null) {
  65. const color = getColorFromHexRgbOrName(activeThreshold.color, theme.type);
  66. // if we are past real value the cell is not "on"
  67. if (value === null || (positionValue !== null && positionValue > value)) {
  68. return tinycolor(color)
  69. .setAlpha(0.15)
  70. .toRgbString();
  71. } else {
  72. return tinycolor(color)
  73. .setAlpha(0.7)
  74. .toRgbString();
  75. }
  76. }
  77. return 'gray';
  78. }
  79. getValueStyles(value: string, color: string, width: number): CSSProperties {
  80. const guess = width / (value.length * 1.1);
  81. const fontSize = Math.min(Math.max(guess, 14), 40);
  82. return {
  83. color: color,
  84. fontSize: fontSize + 'px',
  85. };
  86. }
  87. renderVerticalBar(valueFormatted: string, valuePercent: number) {
  88. const { height, width } = this.props;
  89. const maxHeight = height * BAR_SIZE_RATIO;
  90. const barHeight = Math.max(valuePercent * maxHeight, 0);
  91. const colors = this.getValueColors();
  92. const valueStyles = this.getValueStyles(valueFormatted, colors.value, width);
  93. const containerStyles: CSSProperties = {
  94. width: `${width}px`,
  95. height: `${height}px`,
  96. display: 'flex',
  97. flexDirection: 'column',
  98. justifyContent: 'flex-end',
  99. };
  100. const barStyles: CSSProperties = {
  101. height: `${barHeight}px`,
  102. width: `${width}px`,
  103. backgroundColor: colors.bar,
  104. borderTop: `1px solid ${colors.border}`,
  105. };
  106. return (
  107. <div style={containerStyles}>
  108. <div className="bar-gauge__value" style={valueStyles}>
  109. {valueFormatted}
  110. </div>
  111. <div style={barStyles} />
  112. </div>
  113. );
  114. }
  115. renderHorizontalBar(valueFormatted: string, valuePercent: number) {
  116. const { height, width } = this.props;
  117. const maxWidth = width * BAR_SIZE_RATIO;
  118. const barWidth = Math.max(valuePercent * maxWidth, 0);
  119. const colors = this.getValueColors();
  120. const valueStyles = this.getValueStyles(valueFormatted, colors.value, width * (1 - BAR_SIZE_RATIO));
  121. valueStyles.marginLeft = '8px';
  122. const containerStyles: CSSProperties = {
  123. width: `${width}px`,
  124. height: `${height}px`,
  125. display: 'flex',
  126. flexDirection: 'row',
  127. alignItems: 'center',
  128. };
  129. const barStyles = {
  130. height: `${height}px`,
  131. width: `${barWidth}px`,
  132. backgroundColor: colors.bar,
  133. borderRight: `1px solid ${colors.border}`,
  134. };
  135. return (
  136. <div style={containerStyles}>
  137. <div style={barStyles} />
  138. <div className="bar-gauge__value" style={valueStyles}>
  139. {valueFormatted}
  140. </div>
  141. </div>
  142. );
  143. }
  144. renderHorizontalLCD(valueFormatted: string, valuePercent: number) {
  145. const { height, width, maxValue, minValue } = this.props;
  146. const valueRange = maxValue - minValue;
  147. const maxWidth = width * BAR_SIZE_RATIO;
  148. const cellSpacing = 4;
  149. const cellCount = 30;
  150. const cellWidth = (maxWidth - cellSpacing * cellCount) / cellCount;
  151. const colors = this.getValueColors();
  152. const valueStyles = this.getValueStyles(valueFormatted, colors.value, width * (1 - BAR_SIZE_RATIO));
  153. valueStyles.marginLeft = '8px';
  154. const containerStyles: CSSProperties = {
  155. width: `${width}px`,
  156. height: `${height}px`,
  157. display: 'flex',
  158. flexDirection: 'row',
  159. alignItems: 'center',
  160. };
  161. const cells: JSX.Element[] = [];
  162. for (let i = 0; i < cellCount; i++) {
  163. const currentValue = (valueRange / cellCount) * i;
  164. const cellColor = this.getCellColor(currentValue);
  165. const cellStyles: CSSProperties = {
  166. width: `${cellWidth}px`,
  167. backgroundColor: cellColor,
  168. marginRight: '4px',
  169. height: `${height}px`,
  170. borderRadius: '2px',
  171. };
  172. cells.push(<div style={cellStyles} />);
  173. }
  174. return (
  175. <div style={containerStyles}>
  176. {cells}
  177. <div className="bar-gauge__value" style={valueStyles}>
  178. {valueFormatted}
  179. </div>
  180. </div>
  181. );
  182. }
  183. render() {
  184. const { maxValue, minValue, orientation, unit, decimals } = this.props;
  185. const numericValue = this.getNumericValue();
  186. const valuePercent = Math.min(numericValue / (maxValue - minValue), 1);
  187. const formatFunc = getValueFormat(unit);
  188. const valueFormatted = formatFunc(numericValue, decimals);
  189. const vertical = orientation === 'vertical';
  190. return vertical
  191. ? this.renderVerticalBar(valueFormatted, valuePercent)
  192. : this.renderHorizontalLCD(valueFormatted, valuePercent);
  193. }
  194. }
  195. interface BarColors {
  196. value: string;
  197. bar: string;
  198. border: string;
  199. }