grafana/grafana-polystat-panel

View on GitHub
src/utils.ts

Summary

Maintainability
A
2 hrs
Test Coverage
import { sortBy as lodashSortBy, toLower as lodashToLower, isNumber as lodashIsNumber } from 'lodash';

function GetDecimalsForValue(value: any, panelDecimals: any): { decimals: number; scaledDecimals: any } {
  if (lodashIsNumber(panelDecimals)) {
    return { decimals: panelDecimals, scaledDecimals: null };
  }

  const delta = value / 2;
  let dec = -Math.floor(Math.log(delta) / Math.LN10);

  const magn = Math.pow(10, -dec);
  const norm = delta / magn; // norm is between 1.0 and 10.0
  let size;

  if (norm < 1.5) {
    size = 1;
  } else if (norm < 3) {
    size = 2;
    // special case for 2.5, requires an extra decimal
    if (norm > 2.25) {
      size = 2.5;
      ++dec;
    }
  } else if (norm < 7.5) {
    size = 5;
  } else {
    size = 10;
  }

  size *= magn;

  // reduce starting decimals if not needed
  if (Math.floor(value) === value) {
    dec = 0;
  }

  const result = {
    decimals: 0,
    scaledDecimals: 0,
  };
  result.decimals = Math.max(0, dec);
  result.scaledDecimals = result.decimals - Math.floor(Math.log(size) / Math.LN10) + 2;
  return result;
}

/**
 * Find the largest font size (in pixels) that allows the string to fit in the given width.
 *
 * @param {String} text The text to be rendered.
 * @param {String} font The css font descriptor that text is to be rendered with (e.g. "bold ?px verdana")
 *                      -- note the use of ? in place of the font size.
 * @param {number} width width in pixels the string must fit in
 * @param {number} minFontPx the smallest acceptable font size in pixels
 * @param {number} maxFontPx the largest acceptable font size in pixels
 */
function getTextSizeForWidth(text: string, font: any, width: number, minFontPx: number, maxFontPx: number) {
  let s = font.replace('?', maxFontPx);
  let w = getTextWidth(text, s);
  if (w <= width) {
    return maxFontPx;
  }
  // pad width by 10px
  width = width - 20;
  // start from large to small, return 0 for no-fit
  for (let fontSize = maxFontPx; fontSize >= minFontPx; fontSize--) {
    s = font.replace('?', fontSize);
    w = getTextWidth(text, s);
    if (w < width) {
      return Math.ceil(fontSize);
    }
  }
  // 0 if no fit
  return 0;
}

/**
 * Find the largest font size (in pixels) that allows the string to fit in the given width.
 *
 * @param {String} text The text to be rendered.
 * @param {String} font The css font descriptor that text is to be rendered with (e.g. "bold ?px verdana")
 *                      -- note the use of ? in place of the font size.
 * @param {number} width the width in pixels the string must fit in
 * @param {number} height the height in pixels
 * @param {number} minFontPx the smallest acceptable font size in pixels
 * @param {number} maxFontPx the largest acceptable font size in pixels
 */
function getTextSizeForWidthAndHeight(
  text: string,
  font: any,
  width: number,
  height: number,
  minFontPx: number,
  maxFontPx: number
) {
  let s = font.replace('?', maxFontPx);
  let w = getTextWidth(text, s);
  // need to pad the width: use 80% of the space (leave 10% padding on each side)
  width = width * 0.95;
  if (w <= width && maxFontPx <= height) {
    return maxFontPx;
  }
  // start from large to small, return 0 for no-fit
  for (let fontSize = maxFontPx; fontSize >= minFontPx; fontSize--) {
    s = font.replace('?', fontSize);
    w = getTextWidth(text, s);
    // has to fit within the width of the text area, and not exceed the height
    if (w < width && fontSize <= height) {
      return Math.ceil(fontSize);
    }
  }
  // 0 if no fit
  return 0;
}

/**
 * Uses canvas.measureText to compute and return the width of the given text of given font in pixels.
 *
 * @param {String} text The text to be rendered.
 * @param {String} font The css font descriptor that text is to be rendered with (e.g. "bold 14px verdana").
 *
 * @see https://stackoverflow.com/questions/118241/calculate-text-width-with-javascript/21015393#21015393
 */
function getTextWidth(text: string, font: string) {
  // re-use canvas object for better performance
  // intentional use of deprecated function (no widely implemented alternative)
  // eslint-disable-next-line deprecation/deprecation
  const canvas = document.createElement('canvas');
  const context = canvas.getContext('2d');
  if (context) {
    context.font = font;
    const metrics = context.measureText(text);
    return metrics.width;
  }
  return 40; // unknown due to context failure
}

function RGBToHex(text: string) {
  // check if in rgb notation
  if (!text.startsWith('rgb')) {
    return text;
  }
  let hex = '#FFFFFF';
  try {
    const a = text.split('(')[1].split(')')[0];
    const b = a.split(',');
    const c = b.map((x) => {
      // For each array element
      x = parseInt(x, 10).toString(16); // Convert to a base16 string
      return x.length === 1 ? '0' + x : x; // Add zero if we get only one character
    });
    hex = '#' + c.join('');
  } catch (e) {
    return hex;
  }
  return hex;
}

function getTextOrValue(o: any) {
  if (isNaN(o)) {
    return o;
  } else {
    return Number(o);
  }
}

function SortVariableValuesByField(options: any, sortField: string, sortOrder: number) {
  if (sortOrder === 0) {
    return options;
  }
  const sortType = Math.ceil(sortOrder / 2);
  const reverseSort = sortOrder % 2 === 0;

  switch (sortType) {
    case 1: // Alphabetical Case Sensitive
      options = lodashSortBy(options, sortField);
      break;
    case 2: // Numerical with sub-match
      options = lodashSortBy(options, (item) => {
        // if the content of the field to sort by is textual, check if there is a numerical area to sort by
        if (isNaN(item[sortField])) {
          const matchField = item[sortField];
          const matches = matchField.match(/.*?(\d+).*/);
          if (!matches || matches.length < 2) {
            return -1;
          } else {
            return parseInt(matches[1], 10);
          }
        } else {
          return getTextOrValue(item[sortField]);
        }
      });
      break;
    case 3: // Alphabetical Case Insensitive
      options = lodashSortBy(options, (item) => {
        const itemContent = item[sortField];
        if (isNaN(itemContent)) {
          return lodashToLower(item[sortField]);
        } else {
          return itemContent;
        }
      });
      break;
  }

  if (reverseSort) {
    options = options.reverse();
  }

  return options;
}

const roundValue = (num: number, decimals: number): number | null => {
  if (num === null) {
    return null;
  }
  const n = Math.pow(10, decimals);
  const formatted = (n * num).toFixed(decimals);
  return Math.round(parseFloat(formatted)) / n;
};

export {
  GetDecimalsForValue,
  getTextSizeForWidth,
  getTextSizeForWidthAndHeight,
  getTextWidth,
  RGBToHex,
  SortVariableValuesByField,
  getTextOrValue,
  roundValue
};