1- import { useMemo , useState , useEffect , memo } from 'react' ;
2- import { ResponsiveContainer , Cell , PieChart , Pie , Tooltip , Legend } from 'recharts' ;
31import './VolumeGraph.scss' ;
2+ import { useMemo , useState , useEffect , memo , useCallback } from 'react' ;
3+ import { ResponsiveContainer , Cell , PieChart , Pie , Tooltip , Legend } from 'recharts' ;
44import { getBarColors , TOTAL_LABELS , Units } from '../../../../utilities/constants' ;
55import { formatCurrency } from '../../../../utilities/data-formatters' ;
66import { transformVolumeGraphData } from '../../../../services/data-transform.service' ;
@@ -9,6 +9,7 @@ import { useLocation } from 'react-router-dom';
99import { useSelector } from 'react-redux' ;
1010import { selectIsDarkMode , selectUIConfigUnit } from '../../../../store/rootSelectors' ;
1111import { selectVolumeForwards } from '../../../../store/bkprSelectors' ;
12+ import MultiSelectDropdown from '../../../shared/MultiSelectDropdown/MultiSelectDropdown' ;
1213
1314const VolumeGraphTooltip = ( { active, payload, unit } : any ) => {
1415 if ( active && payload && payload . length >= 0 ) {
@@ -70,6 +71,23 @@ const VolumeGraph = () => {
7071 const uiConfigUnit = useSelector ( selectUIConfigUnit ) ;
7172 const volumeForwards = useSelector ( selectVolumeForwards ) ;
7273 const [ animate , setAnimate ] = useState ( false ) ;
74+ const [ selectedScids , setSelectedScids ] = useState < string [ ] > ( [ ] ) ;
75+ const [ filterMode , setFilterMode ] = useState < string > ( 'include' ) ;
76+
77+ const uniqueInChannelSCIDOptions = useMemo ( ( ) => (
78+ [ ...new Set ( volumeForwards ?. map ( forward => forward . in_channel_scid ) ?? [ ] ) ]
79+ . map ( scid => ( { value : scid , label : scid } ) )
80+ ) , [ volumeForwards ] ) ;
81+
82+ const filteredVolumeForwards = useMemo ( ( ) => {
83+ if ( ! volumeForwards ) return [ ] ;
84+ if ( selectedScids . length === 0 ) return volumeForwards ;
85+
86+ return volumeForwards . filter ( forward => {
87+ const isMatch = selectedScids . includes ( forward . in_channel_scid ) ;
88+ return filterMode === 'include' ? isMatch : ! isMatch ;
89+ } ) ;
90+ } , [ volumeForwards , selectedScids , filterMode ] ) ;
7391
7492 useEffect ( ( ) => {
7593 setAnimate ( false ) ;
@@ -83,15 +101,18 @@ const VolumeGraph = () => {
83101 const RADIAN = Math . PI / 180 ;
84102
85103 const renderLabel = ( { cx, cy, midAngle, innerRadius, outerRadius, index, data, isInner = false } : any ) => {
86- const entry = data [ index ] ;
104+ const entry = data ?. [ index ] ;
105+
106+ if ( ! entry ) return null ;
107+
87108 const radius = innerRadius + ( outerRadius - innerRadius ) * ( isInner ? 0.5 : 1.4 ) ;
88109 const x = cx + radius * Math . cos ( - midAngle * RADIAN ) ;
89110 const y = cy + radius * Math . sin ( - midAngle * RADIAN ) ;
90111
91112 const textAnchor = isInner
92113 ? ( x > cx ? 'start' : 'end' )
93114 : ( Math . cos ( midAngle * RADIAN ) > 0 ? 'start' : 'end' ) ;
94-
115+
95116 return (
96117 < text
97118 x = { x }
@@ -112,73 +133,95 @@ const VolumeGraph = () => {
112133 } ;
113134
114135 const { inbound, outbound } : any = useMemo ( ( ) => {
115- return transformVolumeGraphData ( volumeForwards ) ;
116- } , [ volumeForwards ] ) ;
136+ return transformVolumeGraphData ( filteredVolumeForwards ) ;
137+ } , [ filteredVolumeForwards ] ) ;
117138
118139 pieColors = getBarColors ( inbound . length ) ;
119140 const colorChannelMap = new Map ( ) ;
120141 inbound . forEach ( ( channel , index ) => {
121142 colorChannelMap . set ( channel . in_channel_scid , pieColors [ inbound . length - index - 1 ] ) ;
122143 } ) ;
123144
145+ const multiSelectChangeHandler = useCallback ( ( selectedOptions : string [ ] , mode : string ) => {
146+ setTimeout ( ( ) => {
147+ setSelectedScids ( selectedOptions ) ;
148+ setFilterMode ( mode ) ;
149+ } , 0 ) ;
150+ } , [ ] ) ;
151+
124152 return (
125153 < div data-testid = 'volume-graph' className = 'volume-graph' >
126154 < ResponsiveContainer width = '100%' key = { location . key } >
127- { animate ? (
128- < PieChart margin = { { top : 20 , right : 20 , bottom : 20 , left : 20 } } >
129- < Tooltip content = { < VolumeGraphTooltip unit = { uiConfigUnit } /> } />
130- < Legend
131- content = { < VolumeGraphLegend colorChannelMap = { colorChannelMap } /> }
132- layout = 'horizontal'
133- verticalAlign = 'bottom'
134- align = 'center'
155+ < div className = "d-flex w-100 h-100 volume-graph-container" >
156+ < div className = "left-column" >
157+ < MultiSelectDropdown
158+ options = { uniqueInChannelSCIDOptions }
159+ placeholder = 'Filter Inbound'
160+ onChange = { multiSelectChangeHandler }
135161 />
136- < Pie
137- data = { inbound }
138- cx = '50%'
139- cy = '50%'
140- labelLine = { false }
141- label = { ( props ) => renderLabel ( { ...props , data : inbound , isInner : true } ) }
142- outerRadius = '76%'
143- dataKey = 'fee_msat'
144- isAnimationActive = { true }
145- animationBegin = { 0 }
146- animationDuration = { 1000 }
147- nameKey = 'in_channel_scid'
148- >
149- { inbound . map ( ( entry , index ) => (
150- < Cell
151- key = { `cell-${ index } ` }
152- fill = { colorChannelMap . get ( entry . in_channel_scid ) }
153- stroke = '#333'
154- strokeWidth = { 1 }
155- />
156- ) ) }
157- </ Pie >
158- < Pie
159- data = { outbound }
160- dataKey = 'fee_msat'
161- cx = '50%'
162- cy = '50%'
163- innerRadius = '80%'
164- outerRadius = '98%'
165- label = { ( props ) => ( outbound [ props . index ] . show_label ) ? renderLabel ( { ...props , data : outbound , isInner : false } ) : null }
166- labelLine = { outbound . length <= TOTAL_LABELS }
167- isAnimationActive = { true }
168- animationBegin = { 200 }
169- animationDuration = { 1000 }
170- >
171- { outbound . map ( ( entry , index ) => (
172- < Cell
173- key = { `outbound-cell-${ index } ` }
174- fill = { colorChannelMap . get ( entry . in_channel_scid ) }
175- stroke = '#333'
176- strokeWidth = { 1 }
162+ </ div >
163+ < div className = "right-column p-2" >
164+ { animate ? (
165+ < PieChart margin = { { top : 20 , right : 20 , bottom : 20 , left : 20 } } >
166+ < Tooltip content = { < VolumeGraphTooltip unit = { uiConfigUnit } /> } />
167+ < Legend
168+ content = { < VolumeGraphLegend colorChannelMap = { colorChannelMap } /> }
169+ layout = 'horizontal'
170+ verticalAlign = 'bottom'
171+ align = 'center'
177172 />
178- ) ) }
179- </ Pie >
180- </ PieChart >
181- ) : ( < > </ > ) }
173+ < Pie
174+ data = { inbound }
175+ cx = '50%'
176+ cy = '50%'
177+ labelLine = { false }
178+ label = { ( props ) => renderLabel ( { ...props , data : inbound , isInner : true } ) }
179+ outerRadius = '76%'
180+ dataKey = 'fee_msat'
181+ isAnimationActive = { true }
182+ animationBegin = { 0 }
183+ animationDuration = { 1000 }
184+ nameKey = 'in_channel_scid'
185+ >
186+ { inbound . map ( ( entry , index ) => (
187+ < Cell
188+ key = { `cell-${ index } ` }
189+ fill = { colorChannelMap . get ( entry . in_channel_scid ) }
190+ stroke = '#333'
191+ strokeWidth = { 1 }
192+ />
193+ ) ) }
194+ </ Pie >
195+ < Pie
196+ data = { outbound }
197+ dataKey = 'fee_msat'
198+ cx = '50%'
199+ cy = '50%'
200+ innerRadius = '80%'
201+ outerRadius = '98%'
202+ label = { ( props ) => {
203+ const entry = outbound ?. [ props . index ] ;
204+ if ( ! entry ) return null ;
205+ return entry . show_label ? renderLabel ( { ...props , data : outbound , isInner : false } ) : null ;
206+ } }
207+ labelLine = { outbound . length <= TOTAL_LABELS }
208+ isAnimationActive = { true }
209+ animationBegin = { 200 }
210+ animationDuration = { 1000 }
211+ >
212+ { outbound . map ( ( entry , index ) => (
213+ < Cell
214+ key = { `outbound-cell-${ index } ` }
215+ fill = { colorChannelMap . get ( entry . in_channel_scid ) }
216+ stroke = '#333'
217+ strokeWidth = { 1 }
218+ />
219+ ) ) }
220+ </ Pie >
221+ </ PieChart >
222+ ) : null }
223+ </ div >
224+ </ div >
182225 </ ResponsiveContainer >
183226 </ div >
184227 ) ;
0 commit comments