RangeChart.js 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149
  1. import React from 'react'
  2. import { reverse } from 'lodash'
  3. import { makeStyles } from '@material-ui/styles'
  4. import { BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer } from 'recharts'
  5. import { primary, greyscale, browns } from '../../constants/colors';
  6. import { toCurrency } from '../../util';
  7. const useStyles = makeStyles({
  8. tooltip: {
  9. borderRadius: 2,
  10. background: 'rgba(255, 255, 255, 0.95)',
  11. padding: 12,
  12. },
  13. tooltipLineItem: {
  14. fontSize: '1rem',
  15. margin: 0,
  16. marginBottom: 4,
  17. padding: 0,
  18. },
  19. })
  20. function toBarLabels(allocationRange) {
  21. let keyToFill = {}
  22. let p = 0
  23. let g = 0
  24. let b = 0
  25. for (const { idle } of allocationRange) {
  26. for (const allocation of idle) {
  27. const key = allocation.name
  28. if (keyToFill[key] === undefined) {
  29. // idle allocations are assigned grey
  30. keyToFill[key] = greyscale[g]
  31. g = (g+1) % greyscale.length
  32. }
  33. }
  34. }
  35. for (const { top } of allocationRange) {
  36. for (const allocation of top) {
  37. const key = allocation.name
  38. if (keyToFill[key] === undefined) {
  39. if (key === "__unallocated__") {
  40. // unallocated gets black (clean up)
  41. keyToFill[key] = "#212121"
  42. } else {
  43. // non-idle allocations get the next available color
  44. keyToFill[key] = primary[p]
  45. p = (p+1) % primary.length
  46. }
  47. }
  48. }
  49. }
  50. for (const { other } of allocationRange) {
  51. for (const allocation of other) {
  52. const key = allocation.name
  53. if (keyToFill[key] === undefined) {
  54. // idle allocations are assigned grey
  55. keyToFill[key] = browns[b]
  56. b = (b+1) % browns.length
  57. }
  58. }
  59. }
  60. let labels = []
  61. for (const key in keyToFill) {
  62. labels.push({
  63. dataKey: key,
  64. fill: keyToFill[key],
  65. })
  66. }
  67. return reverse(labels)
  68. }
  69. function toBar(datum) {
  70. const { top, other, idle } = datum
  71. const bar = {}
  72. for (const key in top) {
  73. const allocation = top[key]
  74. const start = new Date(allocation.start)
  75. bar.start = `${start.getUTCFullYear()}-${start.getUTCMonth()+1}-${start.getUTCDate()}`
  76. bar[allocation.name] = allocation.totalCost
  77. }
  78. for (const key in other) {
  79. const allocation = other[key]
  80. const start = new Date(allocation.start)
  81. bar.start = `${start.getUTCFullYear()}-${start.getUTCMonth()+1}-${start.getUTCDate()}`
  82. bar[allocation.name] = allocation.totalCost
  83. }
  84. for (const key in idle) {
  85. const allocation = idle[key]
  86. const start = new Date(allocation.start)
  87. bar.start = `${start.getUTCFullYear()}-${start.getUTCMonth()+1}-${start.getUTCDate()}`
  88. bar[allocation.name] = allocation.totalCost
  89. }
  90. return bar
  91. }
  92. const RangeChart = ({ data, currency, height }) => {
  93. const classes = useStyles()
  94. const barData = data.map(toBar)
  95. const barLabels = toBarLabels(data)
  96. const CustomTooltip = (params) => {
  97. const { active, payload } = params
  98. if (!payload || payload.length == 0) {
  99. return null
  100. }
  101. const total = payload.reduce((sum, item) => sum + item.value, 0.0)
  102. if (active) {
  103. return (
  104. <div className={classes.tooltip}>
  105. <p className={classes.tooltipLineItem} style={{ color: '#000000' }}>{`Total: ${toCurrency(total, currency)}`}</p>
  106. {reverse(payload).map((item, i) => (
  107. <p key={i} className={classes.tooltipLineItem} style={{ color: item.fill }}>{`${item.name}: ${toCurrency(item.value, currency)}`}</p>
  108. ))}
  109. </div>
  110. )
  111. }
  112. return null
  113. }
  114. return (
  115. <ResponsiveContainer width="100%" height={height}>
  116. <BarChart
  117. data={barData}
  118. margin={{ top: 30, right: 30, left: 30, bottom: 12 }}
  119. >
  120. <CartesianGrid strokeDasharray="3 3" />
  121. <XAxis dataKey="start" />
  122. <YAxis />
  123. <Tooltip content={<CustomTooltip />} />
  124. {barLabels.map((barLabel, i) => <Bar key={i} dataKey={barLabel.dataKey} stackId="a" fill={barLabel.fill} />)}
  125. </BarChart>
  126. </ResponsiveContainer>
  127. )
  128. }
  129. export default RangeChart