UsageBarChart.tsx 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114
  1. import dayjs from 'dayjs'
  2. import {
  3. Bar,
  4. CartesianGrid,
  5. Cell,
  6. ComposedChart,
  7. ResponsiveContainer,
  8. Tooltip,
  9. XAxis,
  10. YAxis,
  11. } from 'recharts'
  12. import { cn } from 'ui'
  13. import { Attribute, COLOR_MAP } from './Usage.constants'
  14. import { MultiAttributeTooltipContent, SingleAttributeTooltipContent } from './UsageChartTooltips'
  15. import { DataPoint } from '@/data/analytics/constants'
  16. // [Joshen] This BarChart is specifically for usage, hence not a reusable component, and not
  17. // replacing the existing BarChart in ui/Charts
  18. export interface UsageBarChartProps {
  19. data: DataPoint[]
  20. name: string // Used within the tooltip
  21. attributes: Attribute[]
  22. unit: 'bytes' | 'absolute' | 'percentage' | 'hours' | 'gigabytes'
  23. yLimit?: number
  24. yMin?: number
  25. yLeftMargin?: number
  26. yFormatter?: (value: number | string) => string
  27. tooltipFormatter?: (value: number | string) => string
  28. }
  29. const UsageBarChart = ({
  30. data,
  31. name,
  32. attributes,
  33. unit,
  34. yLimit,
  35. yLeftMargin = 10,
  36. yFormatter,
  37. yMin,
  38. tooltipFormatter,
  39. }: UsageBarChartProps) => {
  40. const yDomain = [yMin ?? 0, Math.max(yMin ?? 0, yLimit ?? 0)]
  41. return (
  42. <div className="w-full h-[200px]">
  43. <ResponsiveContainer width="100%" height={200}>
  44. <ComposedChart data={data} margin={{ top: 0, right: 0, left: yLeftMargin, bottom: 0 }}>
  45. <CartesianGrid
  46. vertical={false}
  47. strokeDasharray="3 3"
  48. className="stroke-border-stronger"
  49. />
  50. <XAxis dataKey="periodStartFormatted" />
  51. <YAxis
  52. width={40}
  53. axisLine={false}
  54. tickLine={{ stroke: 'none' }}
  55. domain={yDomain}
  56. tickFormatter={yFormatter}
  57. />
  58. <Tooltip
  59. content={(props) => {
  60. const { active, payload } = props
  61. if (active && payload && payload.length) {
  62. const dataPeriod = dayjs(payload[0].payload.period_start)
  63. const isAfterToday = dataPeriod.startOf('day').isAfter(dayjs().startOf('day'))
  64. return (
  65. <div
  66. className={cn(
  67. 'border bg-surface-100 rounded-md px-2 py-2',
  68. attributes.length > 1 && !isAfterToday ? 'w-[250px]' : 'w-[170px]'
  69. )}
  70. >
  71. {attributes.length > 1 ? (
  72. <MultiAttributeTooltipContent
  73. attributes={attributes}
  74. values={payload}
  75. isAfterToday={isAfterToday}
  76. tooltipFormatter={tooltipFormatter}
  77. unit={unit}
  78. />
  79. ) : (
  80. <SingleAttributeTooltipContent
  81. name={name}
  82. unit={unit}
  83. value={payload[0].value}
  84. tooltipFormatter={tooltipFormatter}
  85. isAfterToday={isAfterToday}
  86. />
  87. )}
  88. <p className="text-xs text-foreground-light mt-1">
  89. {dataPeriod.format('DD MMM YYYY')}
  90. </p>
  91. </div>
  92. )
  93. } else return null
  94. }}
  95. />
  96. {attributes?.map((attr) => (
  97. <Bar key={attr.key} dataKey={attr.key} stackId="a">
  98. {data.map((entry) => {
  99. return <Cell key={`${entry.period_start}`} className={COLOR_MAP[attr.color].bar} />
  100. })}
  101. </Bar>
  102. ))}
  103. </ComposedChart>
  104. </ResponsiveContainer>
  105. </div>
  106. )
  107. }
  108. export default UsageBarChart