miniBarChart.tsx 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209
  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. export function getYAxisMaxFn(height: number) {
  60. return (value: {max: number; min: number}) => {
  61. // This keeps small datasets from looking 'scary'
  62. // by having full bars for < 10 values.
  63. if (value.max < 10) {
  64. return 10;
  65. }
  66. // Adds extra spacing at the top of the chart canvas, ensuring the series doesn't hit the ceiling, leaving more empty space.
  67. // When the user hovers over an empty space, a tooltip with all series information is displayed.
  68. return (value.max * (height + 10)) / height;
  69. };
  70. }
  71. function MiniBarChart({
  72. emphasisColors,
  73. series,
  74. hideDelay,
  75. tooltipFormatter,
  76. colors,
  77. stacked = false,
  78. labelYAxisExtents = false,
  79. showMarkLineLabel = false,
  80. height,
  81. grid,
  82. ...props
  83. }: Props) {
  84. const {ref: _ref, ...barChartProps} = props;
  85. const theme = useTheme();
  86. const colorList = Array.isArray(colors)
  87. ? colors
  88. : [theme.gray200, theme.purple300, theme.purple300];
  89. let chartSeries: BarChartSeries[] = [];
  90. // Ensure bars overlap and that empty values display as we're disabling the axis lines.
  91. if (series?.length) {
  92. chartSeries = series.map((original, i: number) => {
  93. const updated: BarChartSeries = {
  94. ...original,
  95. cursor: 'normal',
  96. type: 'bar',
  97. };
  98. if (i === 0) {
  99. updated.barMinHeight = 1;
  100. if (stacked === false) {
  101. updated.barGap = '-100%';
  102. }
  103. }
  104. if (stacked) {
  105. updated.stack = 'stack1';
  106. }
  107. set(updated, 'itemStyle.color', colorList[i]);
  108. set(updated, 'itemStyle.opacity', 0.6);
  109. set(updated, 'emphasis.itemStyle.opacity', 1.0);
  110. set(updated, 'emphasis.itemStyle.color', emphasisColors?.[i] ?? colorList[i]);
  111. return updated;
  112. });
  113. }
  114. const yAxisOptions = labelYAxisExtents
  115. ? {
  116. showMinLabel: true,
  117. showMaxLabel: true,
  118. interval: Infinity,
  119. axisLabel: {
  120. formatter(value: number) {
  121. if (tooltipFormatter) {
  122. return tooltipFormatter(value);
  123. }
  124. return formatAbbreviatedNumber(value);
  125. },
  126. },
  127. }
  128. : {
  129. axisLabel: {
  130. show: false,
  131. },
  132. };
  133. const chartOptions: Omit<BarChartProps, 'series'> = {
  134. tooltip: {
  135. trigger: 'axis',
  136. hideDelay,
  137. valueFormatter: tooltipFormatter
  138. ? (value: number) => tooltipFormatter(value)
  139. : undefined,
  140. },
  141. yAxis: {
  142. max: getYAxisMaxFn(height),
  143. splitLine: {
  144. show: false,
  145. },
  146. ...yAxisOptions,
  147. },
  148. grid: grid ?? {
  149. // Offset to ensure there is room for the marker symbols at the
  150. // default size.
  151. top: labelYAxisExtents || showMarkLineLabel ? 6 : 0,
  152. bottom: labelYAxisExtents || showMarkLineLabel ? 4 : 0,
  153. left: showMarkLineLabel ? 35 : 4,
  154. right: 0,
  155. },
  156. xAxis: {
  157. axisLine: {
  158. show: false,
  159. },
  160. axisTick: {
  161. show: false,
  162. alignWithLabel: true,
  163. },
  164. axisLabel: {
  165. show: false,
  166. },
  167. axisPointer: {
  168. type: 'line' as const,
  169. label: {
  170. show: false,
  171. },
  172. lineStyle: {
  173. width: 0,
  174. },
  175. },
  176. },
  177. options: {
  178. animation: false,
  179. },
  180. };
  181. return (
  182. <BarChart series={chartSeries} height={height} {...chartOptions} {...barChartProps} />
  183. );
  184. }
  185. export default MiniBarChart;