chart.tsx 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247
  1. import {InjectedRouter} from 'react-router';
  2. import {useTheme} from '@emotion/react';
  3. import max from 'lodash/max';
  4. import min from 'lodash/min';
  5. import {AreaChart, AreaChartProps} from 'sentry/components/charts/areaChart';
  6. import ChartZoom from 'sentry/components/charts/chartZoom';
  7. import {LineChart} from 'sentry/components/charts/lineChart';
  8. import {DateString} from 'sentry/types';
  9. import {Series} from 'sentry/types/echarts';
  10. import {axisLabelFormatter, tooltipFormatter} from 'sentry/utils/discover/charts';
  11. import {aggregateOutputType} from 'sentry/utils/discover/fields';
  12. type Props = {
  13. data: Series[];
  14. end: DateString;
  15. loading: boolean;
  16. router: InjectedRouter;
  17. start: DateString;
  18. statsPeriod: string | null | undefined;
  19. utc: boolean;
  20. chartColors?: string[];
  21. definedAxisTicks?: number;
  22. disableMultiAxis?: boolean;
  23. disableXAxis?: boolean;
  24. grid?: AreaChartProps['grid'];
  25. height?: number;
  26. isLineChart?: boolean;
  27. previousData?: Series[];
  28. };
  29. // adapted from https://stackoverflow.com/questions/11397239/rounding-up-for-a-graph-maximum
  30. function computeAxisMax(data) {
  31. // assumes min is 0
  32. const valuesDict = data.map(value => value.data.map(point => point.value));
  33. const maxValue = max(valuesDict.map(max)) as number;
  34. if (maxValue <= 1) {
  35. return 1;
  36. }
  37. const power = Math.log10(maxValue);
  38. const magnitude = min([max([10 ** (power - Math.floor(power)), 0]), 10]) as number;
  39. let scale: number;
  40. if (magnitude <= 2.5) {
  41. scale = 0.2;
  42. } else if (magnitude <= 5) {
  43. scale = 0.5;
  44. } else if (magnitude <= 7.5) {
  45. scale = 1.0;
  46. } else {
  47. scale = 2.0;
  48. }
  49. const step = 10 ** Math.floor(power) * scale;
  50. return Math.round(Math.ceil(maxValue / step) * step);
  51. }
  52. function Chart({
  53. data,
  54. previousData,
  55. router,
  56. statsPeriod,
  57. start,
  58. end,
  59. utc,
  60. loading,
  61. height,
  62. grid,
  63. disableMultiAxis,
  64. disableXAxis,
  65. definedAxisTicks,
  66. chartColors,
  67. isLineChart,
  68. }: Props) {
  69. const theme = useTheme();
  70. if (!data || data.length <= 0) {
  71. return null;
  72. }
  73. const colors = chartColors ?? theme.charts.getColorPalette(4);
  74. const durationOnly = data.every(
  75. value => aggregateOutputType(value.seriesName) === 'duration'
  76. );
  77. const dataMax = durationOnly ? computeAxisMax(data) : undefined;
  78. const xAxes = disableMultiAxis
  79. ? undefined
  80. : [
  81. {
  82. gridIndex: 0,
  83. type: 'time' as const,
  84. },
  85. {
  86. gridIndex: 1,
  87. type: 'time' as const,
  88. },
  89. ];
  90. const yAxes = disableMultiAxis
  91. ? [
  92. {
  93. splitNumber: definedAxisTicks,
  94. axisLabel: {
  95. color: theme.chartLabel,
  96. formatter(value: number) {
  97. return axisLabelFormatter(value, data[0].seriesName);
  98. },
  99. },
  100. },
  101. ]
  102. : [
  103. {
  104. gridIndex: 0,
  105. scale: true,
  106. max: dataMax,
  107. axisLabel: {
  108. color: theme.chartLabel,
  109. formatter(value: number) {
  110. return axisLabelFormatter(value, data[0].seriesName);
  111. },
  112. },
  113. },
  114. {
  115. gridIndex: 1,
  116. scale: true,
  117. max: dataMax,
  118. axisLabel: {
  119. color: theme.chartLabel,
  120. formatter(value: number) {
  121. return axisLabelFormatter(value, data[1].seriesName);
  122. },
  123. },
  124. },
  125. ];
  126. const axisPointer = disableMultiAxis
  127. ? undefined
  128. : {
  129. // Link the two series x-axis together.
  130. link: [{xAxisIndex: [0, 1]}],
  131. };
  132. const areaChartProps = {
  133. seriesOptions: {
  134. showSymbol: false,
  135. },
  136. grid: disableMultiAxis
  137. ? grid
  138. : [
  139. {
  140. top: '8px',
  141. left: '24px',
  142. right: '52%',
  143. bottom: '16px',
  144. },
  145. {
  146. top: '8px',
  147. left: '52%',
  148. right: '24px',
  149. bottom: '16px',
  150. },
  151. ],
  152. axisPointer,
  153. xAxes,
  154. yAxes,
  155. utc,
  156. isGroupedByDate: true,
  157. showTimeInTooltip: true,
  158. colors: [colors[0], colors[1]] as string[],
  159. tooltip: {
  160. valueFormatter: (value, seriesName) => {
  161. return tooltipFormatter(
  162. value,
  163. data && data.length ? data[0].seriesName : seriesName
  164. );
  165. },
  166. nameFormatter(value: string) {
  167. return value === 'epm()' ? 'tpm()' : value;
  168. },
  169. },
  170. };
  171. if (loading) {
  172. if (isLineChart) {
  173. return <LineChart height={height} series={[]} {...areaChartProps} />;
  174. }
  175. return <AreaChart height={height} series={[]} {...areaChartProps} />;
  176. }
  177. const series = data.map((values, i: number) => ({
  178. ...values,
  179. yAxisIndex: i,
  180. xAxisIndex: i,
  181. }));
  182. const xAxis = disableXAxis
  183. ? {
  184. show: false,
  185. axisLabel: {show: true, margin: 0},
  186. axisLine: {show: false},
  187. }
  188. : undefined;
  189. return (
  190. <ChartZoom
  191. router={router}
  192. period={statsPeriod}
  193. start={start}
  194. end={end}
  195. utc={utc}
  196. xAxisIndex={disableMultiAxis ? undefined : [0, 1]}
  197. >
  198. {zoomRenderProps => {
  199. if (isLineChart) {
  200. return (
  201. <LineChart
  202. height={height}
  203. {...zoomRenderProps}
  204. series={series}
  205. previousPeriod={previousData}
  206. xAxis={xAxis}
  207. yAxis={areaChartProps.yAxes[0]}
  208. tooltip={areaChartProps.tooltip}
  209. />
  210. );
  211. }
  212. return (
  213. <AreaChart
  214. height={height}
  215. {...zoomRenderProps}
  216. series={series}
  217. previousPeriod={previousData}
  218. xAxis={xAxis}
  219. {...areaChartProps}
  220. />
  221. );
  222. }}
  223. </ChartZoom>
  224. );
  225. }
  226. export default Chart;