github.com/thanos-io/thanos@v0.32.5/pkg/ui/react-app/src/pages/graph/GraphHelpers.ts (about)

     1  import $ from 'jquery';
     2  
     3  import { escapeHTML } from '../../utils';
     4  import { GraphProps, GraphSeries } from './Graph';
     5  import moment from 'moment-timezone';
     6  import { colorPool } from './ColorPool';
     7  
     8  export const formatValue = (y: number | null): string => {
     9    if (y === null) {
    10      return 'null';
    11    }
    12    const absY = Math.abs(y);
    13  
    14    if (absY >= 1e24) {
    15      return (y / 1e24).toFixed(2) + 'Y';
    16    } else if (absY >= 1e21) {
    17      return (y / 1e21).toFixed(2) + 'Z';
    18    } else if (absY >= 1e18) {
    19      return (y / 1e18).toFixed(2) + 'E';
    20    } else if (absY >= 1e15) {
    21      return (y / 1e15).toFixed(2) + 'P';
    22    } else if (absY >= 1e12) {
    23      return (y / 1e12).toFixed(2) + 'T';
    24    } else if (absY >= 1e9) {
    25      return (y / 1e9).toFixed(2) + 'G';
    26    } else if (absY >= 1e6) {
    27      return (y / 1e6).toFixed(2) + 'M';
    28    } else if (absY >= 1e3) {
    29      return (y / 1e3).toFixed(2) + 'k';
    30    } else if (absY >= 1) {
    31      return y.toFixed(2);
    32    } else if (absY === 0) {
    33      return y.toFixed(2);
    34    } else if (absY < 1e-23) {
    35      return (y / 1e-24).toFixed(2) + 'y';
    36    } else if (absY < 1e-20) {
    37      return (y / 1e-21).toFixed(2) + 'z';
    38    } else if (absY < 1e-17) {
    39      return (y / 1e-18).toFixed(2) + 'a';
    40    } else if (absY < 1e-14) {
    41      return (y / 1e-15).toFixed(2) + 'f';
    42    } else if (absY < 1e-11) {
    43      return (y / 1e-12).toFixed(2) + 'p';
    44    } else if (absY < 1e-8) {
    45      return (y / 1e-9).toFixed(2) + 'n';
    46    } else if (absY < 1e-5) {
    47      return (y / 1e-6).toFixed(2) + 'ยต';
    48    } else if (absY < 1e-2) {
    49      return (y / 1e-3).toFixed(2) + 'm';
    50    } else if (absY <= 1) {
    51      return y.toFixed(2);
    52    }
    53    throw Error("couldn't format a value, this is a bug");
    54  };
    55  
    56  export const getHoverColor = (color: string, opacity: number, stacked: boolean): string => {
    57    const { r, g, b } = $.color.parse(color);
    58    if (!stacked) {
    59      return `rgba(${r}, ${g}, ${b}, ${opacity})`;
    60    }
    61    /*
    62      Unfortunately flot doesn't take into consideration
    63      the alpha value when adjusting the color on the stacked series.
    64      TODO: find better way to set the opacity.
    65    */
    66    const base = (1 - opacity) * 255;
    67    return `rgb(${Math.round(base + opacity * r)},${Math.round(base + opacity * g)},${Math.round(base + opacity * b)})`;
    68  };
    69  
    70  export const toHoverColor = (index: number, stacked: boolean) => (series: GraphSeries, i: number) => ({
    71    ...series,
    72    color: getHoverColor(series.color, i !== index ? 0.3 : 1, stacked),
    73  });
    74  
    75  export const getOptions = (stacked: boolean, useLocalTime: boolean): jquery.flot.plotOptions => {
    76    return {
    77      grid: {
    78        hoverable: true,
    79        clickable: true,
    80        autoHighlight: true,
    81        mouseActiveRadius: 100,
    82      },
    83      legend: {
    84        show: false,
    85      },
    86      xaxis: {
    87        mode: 'time',
    88        showTicks: true,
    89        showMinorTicks: true,
    90        timeBase: 'milliseconds',
    91        timezone: useLocalTime ? 'browser' : undefined,
    92      },
    93      yaxis: {
    94        tickFormatter: formatValue,
    95      },
    96      crosshair: {
    97        mode: 'xy',
    98        color: '#bbb',
    99      },
   100      tooltip: {
   101        show: true,
   102        cssClass: 'graph-tooltip',
   103        content: (_, xval, yval, { series }): string => {
   104          const { labels, color } = series;
   105          let dateTime = moment(xval);
   106          if (!useLocalTime) {
   107            dateTime = dateTime.utc();
   108          }
   109          return `
   110              <div class="date">${dateTime.format('YYYY-MM-DD HH:mm:ss Z')}</div>
   111              <div>
   112                <span class="detail-swatch" style="background-color: ${color}"></span>
   113                <span>${labels.__name__ || 'value'}: <strong>${yval}</strong></span>
   114              <div>
   115              <div class="labels mt-1">
   116                ${Object.keys(labels)
   117                  .map((k) =>
   118                    k !== '__name__' ? `<div class="mb-1"><strong>${k}</strong>: ${escapeHTML(labels[k])}</div>` : ''
   119                  )
   120                  .join('')}
   121              </div>
   122            `;
   123        },
   124        defaultTheme: false,
   125        lines: true,
   126      },
   127      series: {
   128        stack: stacked,
   129        lines: {
   130          lineWidth: stacked ? 1 : 2,
   131          steps: false,
   132          fill: stacked,
   133        },
   134        shadowSize: 0,
   135      },
   136    };
   137  };
   138  
   139  export const normalizeData = ({ queryParams, data }: GraphProps): GraphSeries[] => {
   140    const { startTime, endTime, resolution } = queryParams!;
   141    return data.result.map(({ values, histograms, metric }, index) => {
   142      // Insert nulls for all missing steps.
   143      const data = [];
   144      let valPos = 0;
   145      let histogramPos = 0;
   146  
   147      for (let t = startTime; t <= endTime; t += resolution) {
   148        // Allow for floating point inaccuracy.
   149        const currentValue = values && values[valPos];
   150        const currentHistogram = histograms && histograms[histogramPos];
   151        if (currentValue && values.length > valPos && currentValue[0] < t + resolution / 100) {
   152          data.push([currentValue[0] * 1000, parseValue(currentValue[1])]);
   153          valPos++;
   154        } else if (currentHistogram && histograms.length > histogramPos && currentHistogram[0] < t + resolution / 100) {
   155          data.push([currentHistogram[0] * 1000, parseValue(currentHistogram[1].sum)]);
   156          histogramPos++;
   157        } else {
   158          data.push([t * 1000, null]);
   159        }
   160      }
   161  
   162      return {
   163        labels: metric !== null ? metric : {},
   164        color: colorPool[index % colorPool.length],
   165        data,
   166        index,
   167      };
   168    });
   169  };
   170  
   171  export const parseValue = (value: string): null | number => {
   172    const val = parseFloat(value);
   173    // "+Inf", "-Inf", "+Inf" will be parsed into NaN by parseFloat(). They
   174    // can't be graphed, so show them as gaps (null).
   175    return isNaN(val) ? null : val;
   176  };