import {useTheme} from '@emotion/react';
import max from 'lodash/max';
import min from 'lodash/min';
import type {AreaChartProps} from 'sentry/components/charts/areaChart';
import {AreaChart} from 'sentry/components/charts/areaChart';
import ChartZoom from 'sentry/components/charts/chartZoom';
import {LineChart} from 'sentry/components/charts/lineChart';
import type {DateString} from 'sentry/types/core';
import type {Series} from 'sentry/types/echarts';
import {
axisLabelFormatter,
getDurationUnit,
tooltipFormatter,
} from 'sentry/utils/discover/charts';
import {aggregateOutputType} from 'sentry/utils/discover/fields';
type Props = {
data: Series[];
end: DateString;
loading: boolean;
start: DateString;
statsPeriod: string | null | undefined;
utc: boolean;
chartColors?: string[];
definedAxisTicks?: number;
disableMultiAxis?: boolean;
disableXAxis?: boolean;
grid?: AreaChartProps['grid'];
height?: number;
isLineChart?: boolean;
previousData?: Series[];
};
// adapted from https://stackoverflow.com/questions/11397239/rounding-up-for-a-graph-maximum
export function computeAxisMax(data: Series[]) {
// assumes min is 0
const valuesDict = data.map(value => value.data.map(point => point.value));
const maxValue = max(valuesDict.map(max)) as number;
if (maxValue <= 1) {
return 1;
}
const power = Math.log10(maxValue);
const magnitude = min([max([10 ** (power - Math.floor(power)), 0]), 10]) as number;
let scale: number;
if (magnitude <= 2.5) {
scale = 0.2;
} else if (magnitude <= 5) {
scale = 0.5;
} else if (magnitude <= 7.5) {
scale = 1.0;
} else {
scale = 2.0;
}
const step = 10 ** Math.floor(power) * scale;
return Math.round(Math.ceil(maxValue / step) * step);
}
function Chart({
data,
previousData,
statsPeriod,
start,
end,
utc,
loading,
height,
grid,
disableMultiAxis,
disableXAxis,
definedAxisTicks,
chartColors,
isLineChart,
}: Props) {
const theme = useTheme();
if (!data || data.length <= 0) {
return null;
}
const colors = chartColors ?? theme.charts.getColorPalette(4);
const durationOnly = data.every(
value => aggregateOutputType(value.seriesName) === 'duration'
);
const dataMax = durationOnly ? computeAxisMax(data) : undefined;
const xAxes = disableMultiAxis
? undefined
: [
{
gridIndex: 0,
type: 'time' as const,
},
{
gridIndex: 1,
type: 'time' as const,
},
];
const durationUnit = getDurationUnit(data);
const yAxes = disableMultiAxis
? [
{
minInterval: durationUnit,
splitNumber: definedAxisTicks,
axisLabel: {
color: theme.chartLabel,
formatter(value: number) {
return axisLabelFormatter(
value,
aggregateOutputType(data[0].seriesName),
undefined,
durationUnit
);
},
},
},
]
: [
{
gridIndex: 0,
scale: true,
minInterval: durationUnit,
max: dataMax,
axisLabel: {
color: theme.chartLabel,
formatter(value: number) {
return axisLabelFormatter(
value,
aggregateOutputType(data[0].seriesName),
undefined,
durationUnit
);
},
},
},
{
gridIndex: 1,
scale: true,
max: dataMax,
minInterval: durationUnit,
axisLabel: {
color: theme.chartLabel,
formatter(value: number) {
return axisLabelFormatter(
value,
aggregateOutputType(data[1].seriesName),
undefined,
durationUnit
);
},
},
},
];
const axisPointer = disableMultiAxis
? undefined
: {
// Link the two series x-axis together.
link: [{xAxisIndex: [0, 1]}],
};
const areaChartProps = {
seriesOptions: {
showSymbol: false,
},
grid: disableMultiAxis
? grid
: [
{
top: '8px',
left: '24px',
right: '52%',
bottom: '16px',
},
{
top: '8px',
left: '52%',
right: '24px',
bottom: '16px',
},
],
axisPointer,
xAxes,
yAxes,
utc,
isGroupedByDate: true,
showTimeInTooltip: true,
colors: [colors[0], colors[1]] as string[],
tooltip: {
valueFormatter: (value, seriesName) => {
return tooltipFormatter(
value,
aggregateOutputType(data?.length ? data[0].seriesName : seriesName)
);
},
nameFormatter(value: string) {
return value === 'epm()' ? 'tpm()' : value;
},
},
};
if (loading) {
if (isLineChart) {
return ;
}
return ;
}
const series = data.map((values, i: number) => ({
...values,
yAxisIndex: i,
xAxisIndex: i,
}));
const xAxis = disableXAxis
? {
show: false,
axisLabel: {show: true, margin: 0},
axisLine: {show: false},
}
: undefined;
return (
{zoomRenderProps => {
if (isLineChart) {
return (
);
}
return (
);
}}
);
}
export default Chart;