miniBarChart.tsx 4.9 KB

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