123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216 |
- import {LegendComponentOption} from 'echarts';
- import {t} from 'sentry/locale';
- import {Series} from 'sentry/types/echarts';
- import {defined, formatBytesBase2} from 'sentry/utils';
- import {AggregationOutputType} from 'sentry/utils/discover/fields';
- import {
- DAY,
- formatAbbreviatedNumber,
- formatPercentage,
- getDuration,
- HOUR,
- MINUTE,
- SECOND,
- WEEK,
- } from 'sentry/utils/formatters';
- /**
- * Formatter for chart tooltips that handle a variety of discover and metrics result values.
- * If the result is metric values, the value can be of type number or null
- */
- export function tooltipFormatter(
- value: number | null,
- outputType: AggregationOutputType = 'number'
- ): string {
- if (!defined(value)) {
- return '\u2014';
- }
- return tooltipFormatterUsingAggregateOutputType(value, outputType);
- }
- /**
- * Formatter for chart tooltips that takes the aggregate output type directly
- */
- export function tooltipFormatterUsingAggregateOutputType(
- value: number | null,
- type: string
- ): string {
- if (!defined(value)) {
- return '\u2014';
- }
- switch (type) {
- case 'integer':
- case 'number':
- return value.toLocaleString();
- case 'percentage':
- return formatPercentage(value, 2);
- case 'duration':
- return getDuration(value / 1000, 2, true);
- case 'size':
- return formatBytesBase2(value);
- default:
- return value.toString();
- }
- }
- /**
- * Formatter for chart axis labels that handle a variety of discover result values
- * This function is *very similar* to tooltipFormatter but outputs data with less precision.
- */
- export function axisLabelFormatter(
- value: number,
- outputType: AggregationOutputType,
- abbreviation: boolean = false,
- durationUnit?: number
- ): string {
- return axisLabelFormatterUsingAggregateOutputType(
- value,
- outputType,
- abbreviation,
- durationUnit
- );
- }
- /**
- * Formatter for chart axis labels that takes the aggregate output type directly
- */
- export function axisLabelFormatterUsingAggregateOutputType(
- value: number,
- type: string,
- abbreviation: boolean = false,
- durationUnit?: number
- ): string {
- switch (type) {
- case 'integer':
- case 'number':
- return abbreviation ? formatAbbreviatedNumber(value) : value.toLocaleString();
- case 'percentage':
- return formatPercentage(value, 0);
- case 'duration':
- return axisDuration(value, durationUnit);
- case 'size':
- return formatBytesBase2(value, 0);
- default:
- return value.toString();
- }
- }
- /**
- * Specialized duration formatting for axis labels.
- * In that context we are ok sacrificing accuracy for more
- * consistent sizing.
- *
- * @param value Number of milliseconds to format.
- */
- export function axisDuration(value: number, durationUnit?: number): string {
- durationUnit ??= categorizeDuration(value);
- if (value === 0) {
- return '0';
- }
- switch (durationUnit) {
- case WEEK: {
- const label = (value / WEEK).toFixed(0);
- return t('%swk', label);
- }
- case DAY: {
- const label = (value / DAY).toFixed(0);
- return t('%sd', label);
- }
- case HOUR: {
- const label = (value / HOUR).toFixed(0);
- return t('%shr', label);
- }
- case MINUTE: {
- const label = (value / MINUTE).toFixed(0);
- return t('%smin', label);
- }
- case SECOND: {
- const label = (value / SECOND).toFixed(0);
- return t('%ss', label);
- }
- default:
- const label = value.toFixed(0);
- return t('%sms', label);
- }
- }
- /**
- * Given an array of series and an eCharts legend object,
- * finds the range of y values (min and max) based on which series is selected in the legend
- * Assumes series[0] > series[1] > ...
- * @param series Array of eCharts series
- * @param legend eCharts legend object
- * @returns
- */
- export function findRangeOfMultiSeries(series: Series[], legend?: LegendComponentOption) {
- let range: {max: number; min: number} | undefined;
- if (series[0]?.data) {
- let minSeries = series[0];
- let maxSeries;
- series.forEach(({seriesName, data}, idx) => {
- if (legend?.selected?.[seriesName] !== false && data.length) {
- minSeries = series[idx];
- maxSeries ??= series[idx];
- }
- });
- if (maxSeries?.data) {
- const max = Math.max(
- ...maxSeries.data.map(({value}) => value).filter(value => !!value)
- );
- const min = Math.min(
- ...minSeries.data.map(({value}) => value).filter(value => !!value)
- );
- range = {max, min};
- }
- }
- return range;
- }
- /**
- * Given a eCharts series and legend, returns the unit to be used on the yAxis for a duration chart
- * @param series eCharts series array
- * @param legend eCharts legend object
- * @returns
- */
- export function getDurationUnit(
- series: Series[],
- legend?: LegendComponentOption
- ): number {
- let durationUnit = 0;
- const range = findRangeOfMultiSeries(series, legend);
- if (range) {
- const avg = (range.max + range.min) / 2;
- durationUnit = categorizeDuration((range.max - range.min) / 5); // avg of 5 yAxis ticks per chart
- const numOfDigits = (avg / durationUnit).toFixed(0).length;
- if (numOfDigits > 6) {
- durationUnit = categorizeDuration(avg);
- }
- }
- return durationUnit;
- }
- /**
- * Categorizes the duration by Second, Minute, Hour, etc
- * Ex) categorizeDuration(1200) = MINUTE
- * @param value Duration in ms
- */
- export function categorizeDuration(value): number {
- if (value >= WEEK) {
- return WEEK;
- }
- if (value >= DAY) {
- return DAY;
- }
- if (value >= HOUR) {
- return HOUR;
- }
- if (value >= MINUTE) {
- return MINUTE;
- }
- if (value >= SECOND) {
- return SECOND;
- }
- return 1;
- }
|