displayValue.ts 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145
  1. import { ValueMapping, Threshold } from '../types';
  2. import _ from 'lodash';
  3. import { getValueFormat, DecimalCount } from './valueFormats/valueFormats';
  4. import { getMappedValue } from './valueMappings';
  5. import { GrafanaTheme, GrafanaThemeType } from '../types';
  6. import { getColorFromHexRgbOrName } from './namedColorsPalette';
  7. import moment from 'moment';
  8. export interface DisplayValue {
  9. text: string; // Show in the UI
  10. numeric: number; // Use isNaN to check if it is a real number
  11. color?: string; // color based on configs or Threshold
  12. }
  13. export interface DisplayValueOptions {
  14. unit?: string;
  15. decimals?: DecimalCount;
  16. scaledDecimals?: DecimalCount;
  17. dateFormat?: string; // If set try to convert numbers to date
  18. color?: string;
  19. mappings?: ValueMapping[];
  20. thresholds?: Threshold[];
  21. prefix?: string;
  22. suffix?: string;
  23. // Alternative to empty string
  24. noValue?: string;
  25. // Context
  26. isUtc?: boolean;
  27. theme?: GrafanaTheme; // Will pick 'dark' if not defined
  28. }
  29. export type DisplayProcessor = (value: any) => DisplayValue;
  30. export function getDisplayProcessor(options?: DisplayValueOptions): DisplayProcessor {
  31. if (options && !_.isEmpty(options)) {
  32. const formatFunc = getValueFormat(options.unit || 'none');
  33. return (value: any) => {
  34. const { prefix, suffix, mappings, thresholds, theme } = options;
  35. let color = options.color;
  36. let text = _.toString(value);
  37. let numeric = toNumber(value);
  38. let shouldFormat = true;
  39. if (mappings && mappings.length > 0) {
  40. const mappedValue = getMappedValue(mappings, value);
  41. if (mappedValue) {
  42. text = mappedValue.text;
  43. const v = toNumber(text);
  44. if (!isNaN(v)) {
  45. numeric = v;
  46. }
  47. shouldFormat = false;
  48. }
  49. }
  50. if (options.dateFormat) {
  51. const date = toMoment(value, numeric, options.dateFormat);
  52. if (date.isValid()) {
  53. text = date.format(options.dateFormat);
  54. shouldFormat = false;
  55. }
  56. }
  57. if (!isNaN(numeric)) {
  58. if (shouldFormat && !_.isBoolean(value)) {
  59. text = formatFunc(numeric, options.decimals, options.scaledDecimals, options.isUtc);
  60. }
  61. if (thresholds && thresholds.length > 0) {
  62. color = getColorFromThreshold(numeric, thresholds, theme);
  63. }
  64. }
  65. if (!text) {
  66. text = options.noValue ? options.noValue : '';
  67. }
  68. if (prefix) {
  69. text = prefix + text;
  70. }
  71. if (suffix) {
  72. text = text + suffix;
  73. }
  74. return { text, numeric, color };
  75. };
  76. }
  77. return toStringProcessor;
  78. }
  79. function toMoment(value: any, numeric: number, format: string): moment.Moment {
  80. if (!isNaN(numeric)) {
  81. const v = moment(numeric);
  82. if (v.isValid()) {
  83. return v;
  84. }
  85. }
  86. const v = moment(value, format);
  87. if (v.isValid) {
  88. return v;
  89. }
  90. return moment(value); // moment will try to parse the format
  91. }
  92. /** Will return any value as a number or NaN */
  93. function toNumber(value: any): number {
  94. if (typeof value === 'number') {
  95. return value;
  96. }
  97. if (value === null || value === undefined || Array.isArray(value)) {
  98. return NaN; // lodash calls them 0
  99. }
  100. if (typeof value === 'boolean') {
  101. return value ? 1 : 0;
  102. }
  103. return _.toNumber(value);
  104. }
  105. function toStringProcessor(value: any): DisplayValue {
  106. return { text: _.toString(value), numeric: toNumber(value) };
  107. }
  108. export function getColorFromThreshold(value: number, thresholds: Threshold[], theme?: GrafanaTheme): string {
  109. const themeType = theme ? theme.type : GrafanaThemeType.Dark;
  110. if (thresholds.length === 1) {
  111. return getColorFromHexRgbOrName(thresholds[0].color, themeType);
  112. }
  113. const atThreshold = thresholds.filter(threshold => value === threshold.value)[0];
  114. if (atThreshold) {
  115. return getColorFromHexRgbOrName(atThreshold.color, themeType);
  116. }
  117. const belowThreshold = thresholds.filter(threshold => value > threshold.value);
  118. if (belowThreshold.length > 0) {
  119. const nearestThreshold = belowThreshold.sort((t1, t2) => t2.value - t1.value)[0];
  120. return getColorFromHexRgbOrName(nearestThreshold.color, themeType);
  121. }
  122. // Use the first threshold as the default color
  123. return getColorFromHexRgbOrName(thresholds[0].color, themeType);
  124. }