eventsChart.tsx 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741
  1. import {Component, isValidElement} from 'react';
  2. import {InjectedRouter} from 'react-router';
  3. import {Theme, withTheme} from '@emotion/react';
  4. import type {
  5. EChartsOption,
  6. LegendComponentOption,
  7. LineSeriesOption,
  8. XAXisComponentOption,
  9. YAXisComponentOption,
  10. } from 'echarts';
  11. import {Query} from 'history';
  12. import isEqual from 'lodash/isEqual';
  13. import {Client} from 'sentry/api';
  14. import {AreaChart, AreaChartProps} from 'sentry/components/charts/areaChart';
  15. import {BarChart, BarChartProps} from 'sentry/components/charts/barChart';
  16. import ChartZoom, {ZoomRenderProps} from 'sentry/components/charts/chartZoom';
  17. import ErrorPanel from 'sentry/components/charts/errorPanel';
  18. import {LineChart, LineChartProps} from 'sentry/components/charts/lineChart';
  19. import ReleaseSeries from 'sentry/components/charts/releaseSeries';
  20. import TransitionChart from 'sentry/components/charts/transitionChart';
  21. import TransparentLoadingMask from 'sentry/components/charts/transparentLoadingMask';
  22. import {
  23. getInterval,
  24. processTableResults,
  25. RELEASE_LINES_THRESHOLD,
  26. } from 'sentry/components/charts/utils';
  27. import {WorldMapChart, WorldMapChartProps} from 'sentry/components/charts/worldMapChart';
  28. import {IconWarning} from 'sentry/icons';
  29. import {t} from 'sentry/locale';
  30. import {DateString, OrganizationSummary} from 'sentry/types';
  31. import {Series} from 'sentry/types/echarts';
  32. import {defined} from 'sentry/utils';
  33. import {
  34. axisLabelFormatter,
  35. axisLabelFormatterUsingAggregateOutputType,
  36. tooltipFormatter,
  37. } from 'sentry/utils/discover/charts';
  38. import {TableDataWithTitle} from 'sentry/utils/discover/discoverQuery';
  39. import {
  40. aggregateMultiPlotType,
  41. aggregateOutputType,
  42. AggregationOutputType,
  43. getEquation,
  44. isEquation,
  45. } from 'sentry/utils/discover/fields';
  46. import {DiscoverDatasets} from 'sentry/utils/discover/types';
  47. import {decodeList} from 'sentry/utils/queryString';
  48. import EventsGeoRequest from './eventsGeoRequest';
  49. import EventsRequest from './eventsRequest';
  50. type ChartComponent =
  51. | React.ComponentType<BarChartProps>
  52. | React.ComponentType<AreaChartProps>
  53. | React.ComponentType<LineChartProps>
  54. | React.ComponentType<WorldMapChartProps>;
  55. type ChartProps = {
  56. currentSeriesNames: string[];
  57. loading: boolean;
  58. previousSeriesNames: string[];
  59. reloading: boolean;
  60. stacked: boolean;
  61. tableData: TableDataWithTitle[];
  62. theme: Theme;
  63. timeseriesData: Series[];
  64. yAxis: string;
  65. zoomRenderProps: ZoomRenderProps;
  66. additionalSeries?: LineSeriesOption[];
  67. chartComponent?: ChartComponent;
  68. chartOptions?: Omit<EChartsOption, 'xAxis' | 'yAxis'> & {
  69. xAxis?: XAXisComponentOption;
  70. yAxis?: YAXisComponentOption;
  71. };
  72. colors?: string[];
  73. /**
  74. * By default, only the release series is disableable. This adds
  75. * a list of series names that are also disableable.
  76. */
  77. disableableSeries?: string[];
  78. fromDiscover?: boolean;
  79. height?: number;
  80. interval?: string;
  81. legendOptions?: LegendComponentOption;
  82. minutesThresholdToDisplaySeconds?: number;
  83. previousSeriesTransformer?: (series?: Series | null) => Series | null | undefined;
  84. previousTimeseriesData?: Series[] | null;
  85. referrer?: string;
  86. releaseSeries?: Series[];
  87. /**
  88. * A callback to allow for post-processing of the series data.
  89. * Can be used to rename series or even insert a new series.
  90. */
  91. seriesTransformer?: (series: Series[]) => Series[];
  92. showDaily?: boolean;
  93. showLegend?: boolean;
  94. timeframe?: {end: number; start: number};
  95. timeseriesResultsTypes?: Record<string, AggregationOutputType>;
  96. topEvents?: number;
  97. };
  98. type State = {
  99. forceUpdate: boolean;
  100. seriesSelection: Record<string, boolean>;
  101. };
  102. class Chart extends Component<ChartProps, State> {
  103. state: State = {
  104. seriesSelection: {},
  105. forceUpdate: false,
  106. };
  107. shouldComponentUpdate(nextProps: ChartProps, nextState: State) {
  108. if (nextState.forceUpdate) {
  109. return true;
  110. }
  111. if (!isEqual(this.state.seriesSelection, nextState.seriesSelection)) {
  112. return true;
  113. }
  114. if (nextProps.reloading || !nextProps.timeseriesData) {
  115. return false;
  116. }
  117. if (
  118. isEqual(this.props.timeseriesData, nextProps.timeseriesData) &&
  119. isEqual(this.props.releaseSeries, nextProps.releaseSeries) &&
  120. isEqual(this.props.previousTimeseriesData, nextProps.previousTimeseriesData) &&
  121. isEqual(this.props.tableData, nextProps.tableData) &&
  122. isEqual(this.props.additionalSeries, nextProps.additionalSeries)
  123. ) {
  124. return false;
  125. }
  126. return true;
  127. }
  128. getChartComponent(): ChartComponent {
  129. const {showDaily, timeseriesData, yAxis, chartComponent} = this.props;
  130. if (defined(chartComponent)) {
  131. return chartComponent;
  132. }
  133. if (showDaily) {
  134. return BarChart;
  135. }
  136. if (timeseriesData.length > 1) {
  137. switch (aggregateMultiPlotType(yAxis)) {
  138. case 'line':
  139. return LineChart;
  140. case 'area':
  141. return AreaChart;
  142. default:
  143. throw new Error(`Unknown multi plot type for ${yAxis}`);
  144. }
  145. }
  146. return AreaChart;
  147. }
  148. handleLegendSelectChanged = legendChange => {
  149. const {disableableSeries = []} = this.props;
  150. const {selected} = legendChange;
  151. const seriesSelection = Object.keys(selected).reduce((state, key) => {
  152. // we only want them to be able to disable the Releases&Other series,
  153. // and not any of the other possible series here
  154. const disableable =
  155. ['Releases', 'Other'].includes(key) || disableableSeries.includes(key);
  156. state[key] = disableable ? selected[key] : true;
  157. return state;
  158. }, {});
  159. // we have to force an update here otherwise ECharts will
  160. // update its internal state and disable the series
  161. this.setState({seriesSelection, forceUpdate: true}, () =>
  162. this.setState({forceUpdate: false})
  163. );
  164. };
  165. render() {
  166. const {
  167. theme,
  168. loading: _loading,
  169. reloading: _reloading,
  170. yAxis,
  171. releaseSeries,
  172. zoomRenderProps,
  173. timeseriesData,
  174. previousTimeseriesData,
  175. showLegend,
  176. legendOptions,
  177. chartOptions: chartOptionsProp,
  178. currentSeriesNames,
  179. previousSeriesNames,
  180. seriesTransformer,
  181. previousSeriesTransformer,
  182. colors,
  183. height,
  184. timeframe,
  185. topEvents,
  186. tableData,
  187. fromDiscover,
  188. timeseriesResultsTypes,
  189. additionalSeries,
  190. ...props
  191. } = this.props;
  192. const {seriesSelection} = this.state;
  193. let ChartComponent = this.getChartComponent();
  194. if (ChartComponent === WorldMapChart) {
  195. const {data, title} = processTableResults(tableData);
  196. const tableSeries = [
  197. {
  198. seriesName: title,
  199. data,
  200. },
  201. ];
  202. return <WorldMapChart series={tableSeries} fromDiscover={fromDiscover} />;
  203. }
  204. ChartComponent = ChartComponent as Exclude<
  205. ChartComponent,
  206. React.ComponentType<WorldMapChartProps>
  207. >;
  208. const data = [
  209. ...(currentSeriesNames.length > 0 ? currentSeriesNames : [t('Current')]),
  210. ...(previousSeriesNames.length > 0 ? previousSeriesNames : [t('Previous')]),
  211. ...(additionalSeries ? additionalSeries.map(series => series.name as string) : []),
  212. ];
  213. const releasesLegend = t('Releases');
  214. const hasOther = topEvents && topEvents + 1 === timeseriesData.length;
  215. if (hasOther) {
  216. data.push('Other');
  217. }
  218. if (Array.isArray(releaseSeries)) {
  219. data.push(releasesLegend);
  220. }
  221. // Temporary fix to improve performance on pages with a high number of releases.
  222. const releases = releaseSeries && releaseSeries[0];
  223. const hideReleasesByDefault =
  224. Array.isArray(releaseSeries) &&
  225. (releases as any)?.markLine?.data &&
  226. (releases as any).markLine.data.length >= RELEASE_LINES_THRESHOLD;
  227. const selected = !Array.isArray(releaseSeries)
  228. ? seriesSelection
  229. : Object.keys(seriesSelection).length === 0 && hideReleasesByDefault
  230. ? {[releasesLegend]: false}
  231. : seriesSelection;
  232. const legend = showLegend
  233. ? {
  234. right: 16,
  235. top: 12,
  236. data,
  237. selected,
  238. ...(legendOptions ?? {}),
  239. }
  240. : undefined;
  241. let series = Array.isArray(releaseSeries)
  242. ? [...timeseriesData, ...releaseSeries]
  243. : timeseriesData;
  244. let previousSeries = previousTimeseriesData;
  245. if (seriesTransformer) {
  246. series = seriesTransformer(series);
  247. }
  248. if (previousSeriesTransformer) {
  249. previousSeries = previousSeries?.map(
  250. prev => previousSeriesTransformer(prev) as Series
  251. );
  252. }
  253. const chartColors = timeseriesData.length
  254. ? colors?.slice(0, series.length) ?? [
  255. ...theme.charts.getColorPalette(timeseriesData.length - 2 - (hasOther ? 1 : 0)),
  256. ]
  257. : undefined;
  258. if (chartColors && chartColors.length && hasOther) {
  259. chartColors.push(theme.chartOther);
  260. }
  261. const chartOptions = {
  262. colors: chartColors,
  263. grid: {
  264. left: '24px',
  265. right: '24px',
  266. top: '32px',
  267. bottom: '12px',
  268. },
  269. seriesOptions: {
  270. showSymbol: false,
  271. },
  272. tooltip: {
  273. trigger: 'axis' as const,
  274. truncate: 80,
  275. valueFormatter: (value: number, label?: string) => {
  276. const aggregateName = label
  277. ?.replace(/^previous /, '')
  278. .split(':')
  279. .pop()
  280. ?.trim();
  281. if (aggregateName) {
  282. return timeseriesResultsTypes
  283. ? tooltipFormatter(value, timeseriesResultsTypes[aggregateName])
  284. : tooltipFormatter(value, aggregateOutputType(aggregateName));
  285. }
  286. return tooltipFormatter(value, 'number');
  287. },
  288. },
  289. xAxis: timeframe
  290. ? {
  291. min: timeframe.start,
  292. max: timeframe.end,
  293. }
  294. : undefined,
  295. yAxis: {
  296. axisLabel: {
  297. color: theme.chartLabel,
  298. formatter: (value: number) => {
  299. if (timeseriesResultsTypes) {
  300. // Check to see if all series output types are the same. If not, then default to number.
  301. const outputType =
  302. new Set(Object.values(timeseriesResultsTypes)).size === 1
  303. ? timeseriesResultsTypes[yAxis]
  304. : 'number';
  305. return axisLabelFormatterUsingAggregateOutputType(value, outputType);
  306. }
  307. return axisLabelFormatter(value, aggregateOutputType(yAxis));
  308. },
  309. },
  310. },
  311. ...(chartOptionsProp ?? {}),
  312. animation: typeof ChartComponent === typeof BarChart ? false : undefined,
  313. };
  314. return (
  315. <ChartComponent
  316. {...props}
  317. {...zoomRenderProps}
  318. {...chartOptions}
  319. legend={legend}
  320. onLegendSelectChanged={this.handleLegendSelectChanged}
  321. series={series}
  322. previousPeriod={previousSeries ? previousSeries : undefined}
  323. height={height}
  324. additionalSeries={additionalSeries}
  325. />
  326. );
  327. }
  328. }
  329. const ThemedChart = withTheme(Chart);
  330. export type EventsChartProps = {
  331. api: Client;
  332. /**
  333. * Absolute end date.
  334. */
  335. end: DateString;
  336. /**
  337. * Environment condition.
  338. */
  339. environments: string[];
  340. organization: OrganizationSummary;
  341. /**
  342. * Project ids
  343. */
  344. projects: number[];
  345. /**
  346. * The discover query string to find events with.
  347. */
  348. query: string;
  349. router: InjectedRouter;
  350. /**
  351. * Absolute start date.
  352. */
  353. start: DateString;
  354. /**
  355. * The aggregate/metric to plot.
  356. */
  357. yAxis: string | string[];
  358. additionalSeries?: LineSeriesOption[];
  359. /**
  360. * Markup for optional chart header
  361. */
  362. chartHeader?: React.ReactNode;
  363. /**
  364. * Override the default color palette.
  365. */
  366. colors?: string[];
  367. confirmedQuery?: boolean;
  368. /**
  369. * Name of the series
  370. */
  371. currentSeriesName?: string;
  372. /**
  373. * Specifies the dataset to query from. Defaults to discover.
  374. */
  375. dataset?: DiscoverDatasets;
  376. /**
  377. * Don't show the previous period's data. Will automatically disable
  378. * when start/end are used.
  379. */
  380. disablePrevious?: boolean;
  381. /**
  382. * Don't show the release marklines.
  383. */
  384. disableReleases?: boolean;
  385. /**
  386. * A list of release names to visually emphasize. Can only be used when `disableReleases` is false.
  387. */
  388. emphasizeReleases?: string[];
  389. /**
  390. * The fields that act as grouping conditions when generating a topEvents chart.
  391. */
  392. field?: string[];
  393. /**
  394. * The interval resolution for a chart e.g. 1m, 5m, 1d
  395. */
  396. interval?: string;
  397. /**
  398. * Whether or not the request for processed baseline data has been resolved/terminated
  399. */
  400. loadingAdditionalSeries?: boolean;
  401. /**
  402. * Order condition when showing topEvents
  403. */
  404. orderby?: string;
  405. /**
  406. * Relative datetime expression. eg. 14d
  407. */
  408. period?: string | null;
  409. preserveReleaseQueryParams?: boolean;
  410. /**
  411. * Name of the previous series
  412. */
  413. previousSeriesName?: string;
  414. /**
  415. * A unique name for what's triggering this request, see organization_events_stats for an allowlist
  416. */
  417. referrer?: string;
  418. releaseQueryExtra?: Query;
  419. reloadingAdditionalSeries?: boolean;
  420. /**
  421. * Override the interval calculation and show daily results.
  422. */
  423. showDaily?: boolean;
  424. /**
  425. * Fetch n top events as dictated by the field and orderby props.
  426. */
  427. topEvents?: number;
  428. /**
  429. * Chart zoom will change 'pageStart' instead of 'start'
  430. */
  431. usePageZoom?: boolean;
  432. /**
  433. * Should datetimes be formatted in UTC?
  434. */
  435. utc?: boolean | null;
  436. /**
  437. * Whether or not to zerofill results
  438. */
  439. withoutZerofill?: boolean;
  440. } & Pick<
  441. ChartProps,
  442. | 'seriesTransformer'
  443. | 'previousSeriesTransformer'
  444. | 'showLegend'
  445. | 'minutesThresholdToDisplaySeconds'
  446. | 'disableableSeries'
  447. | 'legendOptions'
  448. | 'chartOptions'
  449. | 'chartComponent'
  450. | 'height'
  451. | 'fromDiscover'
  452. >;
  453. type ChartDataProps = {
  454. errored: boolean;
  455. loading: boolean;
  456. reloading: boolean;
  457. zoomRenderProps: ZoomRenderProps;
  458. previousTimeseriesData?: Series[] | null;
  459. releaseSeries?: Series[];
  460. results?: Series[];
  461. tableData?: TableDataWithTitle[];
  462. timeframe?: {end: number; start: number};
  463. timeseriesData?: Series[];
  464. timeseriesResultsTypes?: Record<string, AggregationOutputType>;
  465. topEvents?: number;
  466. };
  467. class EventsChart extends Component<EventsChartProps> {
  468. isStacked() {
  469. const {topEvents, yAxis} = this.props;
  470. return (
  471. (typeof topEvents === 'number' && topEvents > 0) ||
  472. (Array.isArray(yAxis) && yAxis.length > 1)
  473. );
  474. }
  475. render() {
  476. const {
  477. api,
  478. organization,
  479. period,
  480. utc,
  481. query,
  482. router,
  483. start,
  484. end,
  485. projects,
  486. environments,
  487. showLegend,
  488. minutesThresholdToDisplaySeconds,
  489. yAxis,
  490. disablePrevious,
  491. disableReleases,
  492. emphasizeReleases,
  493. currentSeriesName: currentName,
  494. previousSeriesName: previousName,
  495. seriesTransformer,
  496. previousSeriesTransformer,
  497. field,
  498. interval,
  499. showDaily,
  500. topEvents,
  501. orderby,
  502. confirmedQuery,
  503. colors,
  504. chartHeader,
  505. legendOptions,
  506. chartOptions,
  507. preserveReleaseQueryParams,
  508. releaseQueryExtra,
  509. disableableSeries,
  510. chartComponent,
  511. usePageZoom,
  512. height,
  513. withoutZerofill,
  514. fromDiscover,
  515. additionalSeries,
  516. loadingAdditionalSeries,
  517. reloadingAdditionalSeries,
  518. dataset,
  519. ...props
  520. } = this.props;
  521. // Include previous only on relative dates (defaults to relative if no start and end)
  522. const includePrevious = !disablePrevious && !start && !end;
  523. const yAxisArray = decodeList(yAxis);
  524. const yAxisSeriesNames = yAxisArray.map(name => {
  525. let yAxisLabel = name && isEquation(name) ? getEquation(name) : name;
  526. if (yAxisLabel && yAxisLabel.length > 60) {
  527. yAxisLabel = yAxisLabel.substr(0, 60) + '...';
  528. }
  529. return yAxisLabel;
  530. });
  531. const previousSeriesNames = previousName
  532. ? [previousName]
  533. : yAxisSeriesNames.map(name => t('previous %s', name));
  534. const currentSeriesNames = currentName ? [currentName] : yAxisSeriesNames;
  535. const intervalVal = showDaily ? '1d' : interval || getInterval(this.props, 'high');
  536. let chartImplementation = ({
  537. zoomRenderProps,
  538. releaseSeries,
  539. errored,
  540. loading,
  541. reloading,
  542. results,
  543. timeseriesData,
  544. previousTimeseriesData,
  545. timeframe,
  546. tableData,
  547. timeseriesResultsTypes,
  548. }: ChartDataProps) => {
  549. if (errored) {
  550. return (
  551. <ErrorPanel>
  552. <IconWarning color="gray300" size="lg" />
  553. </ErrorPanel>
  554. );
  555. }
  556. const seriesData = results ? results : timeseriesData;
  557. return (
  558. <TransitionChart
  559. loading={loading}
  560. reloading={reloading || !!reloadingAdditionalSeries}
  561. height={height ? `${height}px` : undefined}
  562. >
  563. <TransparentLoadingMask visible={reloading || !!reloadingAdditionalSeries} />
  564. {isValidElement(chartHeader) && chartHeader}
  565. <ThemedChart
  566. zoomRenderProps={zoomRenderProps}
  567. loading={loading || !!loadingAdditionalSeries}
  568. reloading={reloading || !!reloadingAdditionalSeries}
  569. showLegend={showLegend}
  570. minutesThresholdToDisplaySeconds={minutesThresholdToDisplaySeconds}
  571. releaseSeries={releaseSeries || []}
  572. timeseriesData={seriesData ?? []}
  573. previousTimeseriesData={previousTimeseriesData}
  574. currentSeriesNames={currentSeriesNames}
  575. previousSeriesNames={previousSeriesNames}
  576. seriesTransformer={seriesTransformer}
  577. additionalSeries={additionalSeries}
  578. previousSeriesTransformer={previousSeriesTransformer}
  579. stacked={this.isStacked()}
  580. yAxis={yAxisArray[0]}
  581. showDaily={showDaily}
  582. colors={colors}
  583. legendOptions={legendOptions}
  584. chartOptions={chartOptions}
  585. disableableSeries={disableableSeries}
  586. chartComponent={chartComponent}
  587. height={height}
  588. timeframe={timeframe}
  589. topEvents={topEvents}
  590. tableData={tableData ?? []}
  591. fromDiscover={fromDiscover}
  592. timeseriesResultsTypes={timeseriesResultsTypes}
  593. />
  594. </TransitionChart>
  595. );
  596. };
  597. if (!disableReleases) {
  598. const previousChart = chartImplementation;
  599. chartImplementation = chartProps => (
  600. <ReleaseSeries
  601. utc={utc}
  602. period={period}
  603. start={start}
  604. end={end}
  605. projects={projects}
  606. environments={environments}
  607. emphasizeReleases={emphasizeReleases}
  608. preserveQueryParams={preserveReleaseQueryParams}
  609. queryExtra={releaseQueryExtra}
  610. >
  611. {({releaseSeries}) => previousChart({...chartProps, releaseSeries})}
  612. </ReleaseSeries>
  613. );
  614. }
  615. return (
  616. <ChartZoom
  617. router={router}
  618. period={period}
  619. start={start}
  620. end={end}
  621. utc={utc}
  622. usePageDate={usePageZoom}
  623. {...props}
  624. >
  625. {zoomRenderProps => {
  626. if (chartComponent === WorldMapChart) {
  627. return (
  628. <EventsGeoRequest
  629. api={api}
  630. organization={organization}
  631. yAxis={yAxis}
  632. query={query}
  633. orderby={orderby}
  634. projects={projects}
  635. period={period}
  636. start={start}
  637. end={end}
  638. environments={environments}
  639. referrer={props.referrer}
  640. dataset={dataset}
  641. >
  642. {({errored, loading, reloading, tableData}) =>
  643. chartImplementation({
  644. errored,
  645. loading,
  646. reloading,
  647. zoomRenderProps,
  648. tableData,
  649. })
  650. }
  651. </EventsGeoRequest>
  652. );
  653. }
  654. return (
  655. <EventsRequest
  656. {...props}
  657. api={api}
  658. organization={organization}
  659. period={period}
  660. project={projects}
  661. environment={environments}
  662. start={start}
  663. end={end}
  664. interval={intervalVal}
  665. query={query}
  666. includePrevious={includePrevious}
  667. currentSeriesNames={currentSeriesNames}
  668. previousSeriesNames={previousSeriesNames}
  669. yAxis={yAxis}
  670. field={field}
  671. orderby={orderby}
  672. topEvents={topEvents}
  673. confirmedQuery={confirmedQuery}
  674. partial
  675. // Cannot do interpolation when stacking series
  676. withoutZerofill={withoutZerofill && !this.isStacked()}
  677. dataset={dataset}
  678. >
  679. {eventData => {
  680. return chartImplementation({
  681. ...eventData,
  682. zoomRenderProps,
  683. });
  684. }}
  685. </EventsRequest>
  686. );
  687. }}
  688. </ChartZoom>
  689. );
  690. }
  691. }
  692. export default EventsChart;