miniBarChart.tsx 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205
  1. // Import to ensure echarts components are loaded.
  2. import './components/markPoint';
  3. import {useTheme} from '@emotion/react';
  4. import type {GridComponentOption} from 'echarts';
  5. import set from 'lodash/set';
  6. import {formatAbbreviatedNumber} from 'sentry/utils/formatters';
  7. import {BarChart, BarChartProps, BarChartSeries} from './barChart';
  8. import type BaseChart from './baseChart';
  9. type ChartProps = React.ComponentProps<typeof BaseChart>;
  10. interface Props extends Omit<ChartProps, 'css' | 'colors' | 'series' | 'height'> {
  11. /**
  12. * Chart height
  13. */
  14. height: number;
  15. /**
  16. * Colors to use on the chart.
  17. */
  18. colors?: string[];
  19. /**
  20. * A list of colors to use on hover.
  21. * By default hover state will shift opacity from 0.6 to 1.0.
  22. * You can use this prop to also shift colors on hover.
  23. */
  24. emphasisColors?: string[];
  25. /**
  26. * Override the default grid padding
  27. */
  28. grid?: GridComponentOption;
  29. /**
  30. * Delay time for hiding tooltip, in ms.
  31. */
  32. hideDelay?: number;
  33. /**
  34. * Show max/min values on yAxis
  35. */
  36. labelYAxisExtents?: boolean;
  37. series?: BarChartProps['series'];
  38. /**
  39. * Whether not we show a MarkLine label
  40. */
  41. showMarkLineLabel?: boolean;
  42. /**
  43. * Whether not the series should be stacked.
  44. *
  45. * Some of our stats endpoints return data where the 'total' series includes
  46. * breakdown data (issues). For these results `stacked` should be false.
  47. * Other endpoints return decomposed results that need to be stacked (outcomes).
  48. */
  49. stacked?: boolean;
  50. /**
  51. * Function to format tooltip values
  52. */
  53. tooltipFormatter?: (value: number) => string;
  54. /**
  55. * Whether timestamps are should be shown in UTC or local timezone.
  56. */
  57. utc?: boolean;
  58. }
  59. function MiniBarChart({
  60. emphasisColors,
  61. series,
  62. hideDelay,
  63. tooltipFormatter,
  64. colors,
  65. stacked = false,
  66. labelYAxisExtents = false,
  67. showMarkLineLabel = false,
  68. height,
  69. grid,
  70. ...props
  71. }: Props) {
  72. const {ref: _ref, ...barChartProps} = props;
  73. const theme = useTheme();
  74. const colorList = Array.isArray(colors)
  75. ? colors
  76. : [theme.gray200, theme.purple300, theme.purple300];
  77. let chartSeries: BarChartSeries[] = [];
  78. // Ensure bars overlap and that empty values display as we're disabling the axis lines.
  79. if (!!series?.length) {
  80. chartSeries = series.map((original, i: number) => {
  81. const updated: BarChartSeries = {
  82. ...original,
  83. cursor: 'normal',
  84. type: 'bar',
  85. };
  86. if (i === 0) {
  87. updated.barMinHeight = 1;
  88. if (stacked === false) {
  89. updated.barGap = '-100%';
  90. }
  91. }
  92. if (stacked) {
  93. updated.stack = 'stack1';
  94. }
  95. set(updated, 'itemStyle.color', colorList[i]);
  96. set(updated, 'itemStyle.opacity', 0.6);
  97. set(updated, 'emphasis.itemStyle.opacity', 1.0);
  98. set(updated, 'emphasis.itemStyle.color', emphasisColors?.[i] ?? colorList[i]);
  99. return updated;
  100. });
  101. }
  102. const yAxisOptions = labelYAxisExtents
  103. ? {
  104. showMinLabel: true,
  105. showMaxLabel: true,
  106. interval: Infinity,
  107. axisLabel: {
  108. formatter(value: number) {
  109. if (tooltipFormatter) {
  110. return tooltipFormatter(value);
  111. }
  112. return formatAbbreviatedNumber(value);
  113. },
  114. },
  115. }
  116. : {
  117. axisLabel: {
  118. show: false,
  119. },
  120. };
  121. const chartOptions: Omit<BarChartProps, 'series'> = {
  122. tooltip: {
  123. trigger: 'axis',
  124. hideDelay,
  125. valueFormatter: tooltipFormatter
  126. ? (value: number) => tooltipFormatter(value)
  127. : undefined,
  128. },
  129. yAxis: {
  130. max(value: {max: number; min: number}) {
  131. // This keeps small datasets from looking 'scary'
  132. // by having full bars for < 10 values.
  133. if (value.max < 10) {
  134. return 10;
  135. }
  136. // Adds extra spacing at the top of the chart canvas, ensuring the series doesn't hit the ceiling, leaving more empty space.
  137. // When the user hovers over an empty space, a tooltip with all series information is displayed.
  138. return (value.max * (height + 10)) / height;
  139. },
  140. splitLine: {
  141. show: false,
  142. },
  143. ...yAxisOptions,
  144. },
  145. grid: grid ?? {
  146. // Offset to ensure there is room for the marker symbols at the
  147. // default size.
  148. top: labelYAxisExtents || showMarkLineLabel ? 6 : 0,
  149. bottom: labelYAxisExtents || showMarkLineLabel ? 4 : 0,
  150. left: showMarkLineLabel ? 35 : 4,
  151. right: 0,
  152. },
  153. xAxis: {
  154. axisLine: {
  155. show: false,
  156. },
  157. axisTick: {
  158. show: false,
  159. alignWithLabel: true,
  160. },
  161. axisLabel: {
  162. show: false,
  163. },
  164. axisPointer: {
  165. type: 'line' as const,
  166. label: {
  167. show: false,
  168. },
  169. lineStyle: {
  170. width: 0,
  171. },
  172. },
  173. },
  174. options: {
  175. animation: false,
  176. },
  177. };
  178. return (
  179. <BarChart series={chartSeries} height={height} {...chartOptions} {...barChartProps} />
  180. );
  181. }
  182. export default MiniBarChart;