chart.tsx 4.9 KB

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