German format for grafana dashboard

Hi,

I changed the code from the script valueFormats.ts for the decimal delimiter in locale formate and it is working!

But now i have some problems if the decimals undefined in the panel for the case when the scale by the y-axes is 2.5-steps. The scale show me ‘NaN’.

I tried many steps but nothing changed my problem, maybe someone have an idea?

Thank you

Picture, what shows the Problem

And the code:

import { clamp } from 'lodash';

import { TimeZone } from '../types';
import { DecimalCount } from '../types/displayValue';

import { getCategories } from './categories';
import { toDateTimeValueFormatter } from './dateTimeFormatters';
import { getOffsetFromSIPrefix, SIPrefix, currency } from './symbolFormatters';


export interface FormattedValue {
  text: string;
  prefix?: string;
  suffix?: string;
}

export function formattedValueToString(val: FormattedValue): string {
  return `${val.prefix ?? ''}${val.text}${val.suffix ?? ''}`;
}

export type ValueFormatter = (
  value: number,
  decimals?: DecimalCount,
  scaledDecimals?: DecimalCount,
  timeZone?: TimeZone,
  showMs?: boolean
) => FormattedValue;

export interface ValueFormat {
  name: string;
  id: string;
  fn: ValueFormatter;
}

export interface ValueFormatCategory {
  name: string;
  formats: ValueFormat[];
}

export interface ValueFormatterIndex {
  [id: string]: ValueFormatter;
}

// Globals & formats cache
let categories: ValueFormatCategory[] = [];
const index: ValueFormatterIndex = {};
let hasBuiltIndex = false;

export function toFixed(value: number, decimals?: DecimalCount): string {
  if (value === null) {
    return '';
  }

  if (value === Number.NEGATIVE_INFINITY || value === Number.POSITIVE_INFINITY) {
    return value.toLocaleString('de-DE');
  }

  if (decimals === null) {
    decimals = getDecimalsForValue(value);
  }

  if (decimals === undefined) {
    return value.toLocaleString('de-DE'); //decimals = getDecimalsForValue(value)
  }

  if (value === 0) {
    return value.toFixed(decimals).replace('.',',');
  }

  const factor = decimals ? Math.pow(10, Math.max(0, decimals)) : 1;
  const formatted = String(Math.round(value * factor) / factor).replace('.',',');

  // if exponent return directly
  if (formatted.indexOf('e') !== -1 || value === 0) {
    return value.toLocaleString('de-DE');
  }

  const decimalPos = formatted.indexOf(',');
  const precision = decimalPos === -1 ? 0 : formatted.length - decimalPos - 1;
  if (precision < decimals) {
    return (precision ? formatted : formatted + ',') + String(factor).slice(1, decimals - precision + 1).replace('.',',');
  }

  const language =  'de-DE'; // use the current language if available, otherwise use "de-DE"
  const options = { minimumFractionDigits: decimals, maximumFractionDigits: decimals };

  return value.toLocaleString(language, options);
}

function getDecimalsForValue(value: number): number {
  const absValue = Math.abs(value);
  const log10 = Math.floor(Math.log(absValue) / Math.LN10);
  let dec = -log10 + 1;
  const magn = Math.pow(10, -dec);
  const norm = absValue / magn; // norm is between 1.0 and 10.0
  


  // special case for 2.5, requires an extra decimal
  if (norm > 2.25) {
    ++dec;
  }

  if (value % 1 === 0) {
    dec = 0;
  }

  const decimals = Math.max(0, dec);

  return decimals;
}

export function toFixedScaled(value: number, decimals: DecimalCount, ext?: string): FormattedValue {

  if (decimals === undefined) {
    decimals = 1;
  }

  return {
    text: toFixed(value, decimals),
    suffix: ext,
  };
}

export function toFixedUnit(unit: string, asPrefix?: boolean): ValueFormatter {
  return (size: number, decimals?: DecimalCount) => {
    if (size === null) {
      return { text: '' };
    }
    const text = toFixed(size, decimals);
    if (unit) {
      if (asPrefix) {
        return { text, prefix: unit };
      }
      return { text, suffix: ' ' + unit };
    }
    return { text };
  };
}

export function isBooleanUnit(unit?: string) {
  return unit && unit.startsWith('bool');
}

export function booleanValueFormatter(t: string, f: string): ValueFormatter {
  return (value) => {
    return { text: value ? t : f };
  };
}

const logb = (b: number, x: number) => Math.log10(x) / Math.log10(b);

export function scaledUnits(factor: number, extArray: string[], offset = 0): ValueFormatter {
  return (size: number, decimals?: DecimalCount) => {
    if (size === null || size === undefined) {
      return { text: '' };
    }

    if (size === Number.NEGATIVE_INFINITY || size === Number.POSITIVE_INFINITY || isNaN(size)) {
      return { text: size.toLocaleString('de-DE') };
    }

    const siIndex = size === 0 ? 0 : Math.floor(logb(factor, Math.abs(size)));
    const suffix = extArray[clamp(offset + siIndex, 0, extArray.length - 1)];

    if (decimals === undefined) {
      decimals = 1;
    }

    return {
      text: toFixed(size / factor ** clamp(siIndex, -offset, extArray.length - offset - 1), decimals),
      suffix,
    };
  };
}

export function locale(value: number, decimals: DecimalCount): FormattedValue {
  if (value == null) {
    return { text: '' };
  }
  return {
    text: value.toLocaleString(undefined, { maximumFractionDigits: decimals ?? undefined }),
  };
}

export function simpleCountUnit(symbol: string): ValueFormatter {
  const units = ['', 'K', 'M', 'B', 'T'];
  const scaler = scaledUnits(1000, units);
  return (size: number, decimals?: DecimalCount, scaledDecimals?: DecimalCount) => {
    if (size === null) {
      return { text: '' };
    }
    const v = scaler(size, decimals, scaledDecimals);
    v.suffix += ' ' + symbol;
    return v;
  };
}

export function stringFormater(value: number): FormattedValue {
  return { text: `${value}` };
}

function buildFormats() {
  categories = getCategories();

  for (const cat of categories) {
    for (const format of cat.formats) {
      index[format.id] = format.fn;
    }
  }

  // Resolve units pointing to old IDs
  [{ from: 'farenheit', to: 'fahrenheit' }].forEach((alias) => {
    const f = index[alias.to];
    if (f) {
      index[alias.from] = f;
    }
  });

  hasBuiltIndex = true;
}

export function getValueFormat(id?: string | null): ValueFormatter {
  if (!id) {
    return toFixedUnit('');
  }

  if (!hasBuiltIndex) {
    buildFormats();
  }

  const fmt = index[id];

  if (!fmt && id) {
    let idx = id.indexOf(':');

    if (idx > 0) {
      const key = id.substring(0, idx);
      const sub = id.substring(idx + 1);

      if (key === 'prefix') {
        return toFixedUnit(sub, true);
      }

      if (key === 'suffix') {
        return toFixedUnit(sub, false);
      }

      if (key === 'time') {
        return toDateTimeValueFormatter(sub);
      }

      if (key === 'si') {
        const offset = getOffsetFromSIPrefix(sub.charAt(0));
        const unit = offset === 0 ? sub : sub.substring(1);
        return SIPrefix(unit, offset);
      }

      if (key === 'count') {
        return simpleCountUnit(sub);
      }

      if (key === 'currency') {
        return currency(sub);
      }

      if (key === 'bool') {
        idx = sub.indexOf('/');
        if (idx >= 0) {
          const t = sub.substring(0, idx);
          const f = sub.substring(idx + 1);
          return booleanValueFormatter(t, f);
        }
        return booleanValueFormatter(sub, '-');
      }
    }

    return toFixedUnit(id);
  }

  return fmt;
}

export function getValueFormatterIndex(): ValueFormatterIndex {
  if (!hasBuiltIndex) {
    buildFormats();
  }

  return index;
}

export function getValueFormats() {
  if (!hasBuiltIndex) {
    buildFormats();
  }

  return categories.map((cat) => {
    return {
      text: cat.name,
      submenu: cat.formats.map((format) => {
        return {
          text: format.name,
          value: format.id,
        };
      }),
    };
  });
}

Hi @dpace,

Thanks for opening this post.

Please review the submission template and include more details:

  • What Grafana version and what operating system are you using?
  • What is your datasource?
  • What visualization panel you are using e.g. time-series, bar chart, histogram etc?
  • What are you trying to achieve?
  • How are you trying to achieve it?
  • What happened?
  • What did you expect to happen?
  • Can you copy/paste the configuration(s) that you are having problems with?
  • Did you receive any errors in the Grafana UI or in related logs? If so, please tell us exactly what they were.
  • Did you follow any online instructions? If so, what is the URL?

Hello,

I am not sure, if you read my problem. But it is nothing to do with a datasource or anything. I working directly on the open source code of garfana (Version 9) and try to change this code for a permitly locale formate for the values. And it is working except the case what i wrote. The dashboard shows me all values in european formates (without using the setting for unti = “locale format”) but if you have the case that graph panel (with decimals setting “auto”) has to make a 2,5 Value-step between the y-axis value range, it will show me ‘NaN’ for the values (See picture from my last message). For example i show a picture from the graph panel with the decimals settings by 1 or more (it is the left panel from the picture, the right one is with the decimals settings ‘auto’). You see with this settings it has not any problems to show the values in a 2,5 value tep by the y-axes. I think it is a problem with the round-function by the toFixed()-function.
But i don´t find a solution. I hope someone have an idea!

Hi @dpace

Thanks for the detailed explanation.

As this is a bit beyond my scope and therefore would appreciate it if you can open a bug report about this in our official GitHub repository using this link.

Provide all the information (you can simply copy/paste your data + screenshot from here to there).

Lastly, paste the link to your GitHub issue here in this post so that all other community users can also track it. I will also add more additional information to it :+1: