|
| 1 | +import React, { useEffect, useMemo } from 'react' |
| 2 | +import PropTypes from 'prop-types' |
| 3 | +import ImmutablePropTypes from 'react-immutable-proptypes' |
| 4 | +import { List } from 'immutable' |
| 5 | +import ReactEChartsCore from 'echarts-for-react/lib/core' |
| 6 | +import * as echarts from 'echarts/core' |
| 7 | +import { HeatmapChart } from 'echarts/charts' |
| 8 | +import { |
| 9 | + TooltipComponent, |
| 10 | + CalendarComponent, |
| 11 | + VisualMapComponent |
| 12 | +} from 'echarts/components' |
| 13 | +import { CanvasRenderer } from 'echarts/renderers' |
| 14 | + |
| 15 | +echarts.use([ |
| 16 | + HeatmapChart, |
| 17 | + TooltipComponent, |
| 18 | + CalendarComponent, |
| 19 | + CanvasRenderer, |
| 20 | + VisualMapComponent |
| 21 | +]) |
| 22 | + |
| 23 | +export default function AccountActivityCalendar({ |
| 24 | + account, |
| 25 | + get_account_blocks_per_day |
| 26 | +}) { |
| 27 | + const account_address = account.get('account') |
| 28 | + |
| 29 | + useEffect(() => { |
| 30 | + if (account_address) { |
| 31 | + get_account_blocks_per_day(account_address) |
| 32 | + } |
| 33 | + }, [account_address, get_account_blocks_per_day]) |
| 34 | + |
| 35 | + const blocks_per_day = account.get('blocks_per_day', new List()) |
| 36 | + const is_loading = account.get('account_is_loading_blocks_per_day') |
| 37 | + |
| 38 | + const heatmap_data_by_year = useMemo(() => { |
| 39 | + const data_by_year = {} |
| 40 | + blocks_per_day |
| 41 | + .filter((item) => item.day != null) |
| 42 | + .forEach((item) => { |
| 43 | + const year = item.day.split('-')[0] |
| 44 | + if (!data_by_year[year]) { |
| 45 | + data_by_year[year] = [] |
| 46 | + } |
| 47 | + data_by_year[year].push([item.day, Number(item.block_count)]) |
| 48 | + }) |
| 49 | + return data_by_year |
| 50 | + }, [blocks_per_day]) |
| 51 | + |
| 52 | + const max_block_count = useMemo(() => { |
| 53 | + let max = 0 |
| 54 | + Object.values(heatmap_data_by_year).forEach((yearData) => { |
| 55 | + yearData.forEach((item) => { |
| 56 | + if (item[1] > max) max = item[1] |
| 57 | + }) |
| 58 | + }) |
| 59 | + return max |
| 60 | + }, [heatmap_data_by_year]) |
| 61 | + |
| 62 | + const options_by_year = useMemo(() => { |
| 63 | + const years_descending = Object.keys(heatmap_data_by_year).sort( |
| 64 | + (a, b) => b - a |
| 65 | + ) |
| 66 | + return years_descending.map((year, index) => { |
| 67 | + const data = heatmap_data_by_year[year] |
| 68 | + return { |
| 69 | + tooltip: { |
| 70 | + position: 'top', |
| 71 | + formatter: function (params) { |
| 72 | + const formatted_date = new Date(params.value[0]).toLocaleDateString( |
| 73 | + 'en-US', |
| 74 | + { |
| 75 | + year: 'numeric', |
| 76 | + month: 'long', |
| 77 | + day: 'numeric' |
| 78 | + } |
| 79 | + ) |
| 80 | + const color_span = `<span style="display:inline-block;margin-left:4px;border-radius:10px;width:10px;height:10px;background-color:${params.color};"></span>` |
| 81 | + return `Date: ${formatted_date}<br/>Blocks: ${params.value[1]}${color_span}` |
| 82 | + } |
| 83 | + }, |
| 84 | + visualMap: { |
| 85 | + show: index === 0, |
| 86 | + min: 0, |
| 87 | + max: max_block_count, |
| 88 | + calculable: true, |
| 89 | + orient: 'horizontal', |
| 90 | + left: 'center', |
| 91 | + top: 0 |
| 92 | + }, |
| 93 | + calendar: { |
| 94 | + range: year, |
| 95 | + cellSize: ['auto', 13], |
| 96 | + top: index === 0 ? '80' : '20', // Adjusted top margin for the first chart to make room for the visualMap |
| 97 | + bottom: '20' |
| 98 | + }, |
| 99 | + series: [ |
| 100 | + { |
| 101 | + type: 'heatmap', |
| 102 | + coordinateSystem: 'calendar', |
| 103 | + data |
| 104 | + } |
| 105 | + ] |
| 106 | + } |
| 107 | + }) |
| 108 | + }, [heatmap_data_by_year, max_block_count]) |
| 109 | + |
| 110 | + return ( |
| 111 | + <> |
| 112 | + {options_by_year.map((option, index) => ( |
| 113 | + <ReactEChartsCore |
| 114 | + key={index} |
| 115 | + echarts={echarts} |
| 116 | + option={option} |
| 117 | + style={{ |
| 118 | + height: index === 0 ? '200px' : '140px', |
| 119 | + width: '100%', |
| 120 | + marginTop: '10px', |
| 121 | + marginBottom: '10px' |
| 122 | + }} |
| 123 | + showLoading={is_loading} |
| 124 | + loadingOption={{ |
| 125 | + maskColor: 'rgba(255, 255, 255, 0)', |
| 126 | + text: '', |
| 127 | + spinnerRadius: 24, |
| 128 | + lineWidth: 2 |
| 129 | + }} |
| 130 | + /> |
| 131 | + ))} |
| 132 | + </> |
| 133 | + ) |
| 134 | +} |
| 135 | + |
| 136 | +AccountActivityCalendar.propTypes = { |
| 137 | + account: ImmutablePropTypes.map.isRequired, |
| 138 | + get_account_blocks_per_day: PropTypes.func.isRequired |
| 139 | +} |
0 commit comments