1- import React , { useEffect , useRef } from 'react' ;
2- import {
3- Chart as ChartJS ,
4- CategoryScale ,
5- LinearScale ,
6- PointElement ,
7- LineElement ,
8- Title ,
9- Tooltip ,
10- Legend ,
11- Filler
12- } from 'chart.js' ;
13- import { Line } from 'react-chartjs-2' ;
14-
15- // Register Chart.js components
16- ChartJS . register (
17- CategoryScale ,
18- LinearScale ,
19- PointElement ,
20- LineElement ,
21- Title ,
22- Tooltip ,
23- Legend ,
24- Filler
25- ) ;
1+ import React , { useState } from 'react' ;
262
273export default function VolumePerDayChart ( { data, network, timeframe } ) {
28- const chartRef = useRef ( ) ;
4+ const [ hoveredPoint , setHoveredPoint ] = useState ( null ) ;
295
306 const formatDate = ( dateString ) => {
317 const date = new Date ( dateString ) ;
@@ -40,137 +16,91 @@ export default function VolumePerDayChart({ data, network, timeframe }) {
4016 }
4117 } ;
4218
43- const chartData = {
44- labels : data . map ( point => formatDate ( point . time ) ) ,
45- datasets : [
46- {
47- label : 'Protocol Volume (SOL)' ,
48- data : data . map ( point => point . volume ) ,
49- borderColor : network . color || '#9945FF' ,
50- backgroundColor : `${ network . color || '#9945FF' } 20` ,
51- borderWidth : 2 ,
52- fill : true ,
53- tension : 0.4 ,
54- pointRadius : 3 ,
55- pointHoverRadius : 6 ,
56- pointBackgroundColor : network . color || '#9945FF' ,
57- pointBorderColor : '#ffffff' ,
58- pointBorderWidth : 2
59- }
60- ]
61- } ;
62-
63- const chartOptions = {
64- responsive : true ,
65- maintainAspectRatio : false ,
66- plugins : {
67- legend : {
68- display : true ,
69- position : 'top' ,
70- labels : {
71- color : '#1f2937' ,
72- font : {
73- size : 12 ,
74- family : 'Inter, system-ui, sans-serif'
75- }
76- }
77- } ,
78- title : {
79- display : false
80- } ,
81- tooltip : {
82- mode : 'index' ,
83- intersect : false ,
84- backgroundColor : 'rgba(0, 0, 0, 0.8)' ,
85- titleColor : '#ffffff' ,
86- bodyColor : '#ffffff' ,
87- borderColor : network . color || '#9945FF' ,
88- borderWidth : 1 ,
89- cornerRadius : 8 ,
90- displayColors : false ,
91- callbacks : {
92- label : function ( context ) {
93- const volume = context . parsed . y ;
94- if ( volume >= 1000000 ) {
95- return `Volume: ${ ( volume / 1000000 ) . toFixed ( 2 ) } M SOL` ;
96- } else if ( volume >= 1000 ) {
97- return `Volume: ${ ( volume / 1000 ) . toFixed ( 2 ) } K SOL` ;
19+ // ASCII Chart Generation Functions
20+ const generateAsciiChart = ( data , width = 60 , height = 12 ) => {
21+ if ( ! data || data . length === 0 ) return [ ] ;
22+
23+ const volumes = data . map ( point => point . volume ) ;
24+ const maxVolume = Math . max ( ...volumes ) ;
25+ const minVolume = Math . min ( ...volumes ) ;
26+ const range = maxVolume - minVolume || 1 ;
27+
28+ // Create chart grid
29+ const chart = Array ( height ) . fill ( ) . map ( ( ) => Array ( width ) . fill ( ' ' ) ) ;
30+
31+ // Plot data points
32+ for ( let i = 0 ; i < data . length && i < width ; i ++ ) {
33+ const volume = volumes [ i ] ;
34+ const normalizedHeight = Math . round ( ( ( volume - minVolume ) / range ) * ( height - 1 ) ) ;
35+ const y = height - 1 - normalizedHeight ;
36+
37+ if ( y >= 0 && y < height ) {
38+ chart [ y ] [ i ] = '*' ;
39+
40+ // Connect points with lines if not first point
41+ if ( i > 0 ) {
42+ const prevVolume = volumes [ i - 1 ] ;
43+ const prevNormalizedHeight = Math . round ( ( ( prevVolume - minVolume ) / range ) * ( height - 1 ) ) ;
44+ const prevY = height - 1 - prevNormalizedHeight ;
45+
46+ // Draw connecting line
47+ const startY = Math . min ( y , prevY ) ;
48+ const endY = Math . max ( y , prevY ) ;
49+
50+ for ( let lineY = startY ; lineY <= endY ; lineY ++ ) {
51+ if ( lineY >= 0 && lineY < height && chart [ lineY ] [ i ] === ' ' ) {
52+ if ( y > prevY ) {
53+ chart [ lineY ] [ i ] = '/' ;
54+ } else if ( y < prevY ) {
55+ chart [ lineY ] [ i ] = '\\' ;
56+ } else {
57+ chart [ lineY ] [ i ] = '-' ;
58+ }
9859 }
99- return `Volume: ${ volume . toFixed ( 2 ) } SOL` ;
100- } ,
101- afterLabel : function ( context ) {
102- const usdValue = ( context . parsed . y * 150 ) . toFixed ( 0 ) ; // Mock SOL price
103- return `≈ $${ Number ( usdValue ) . toLocaleString ( ) } USD` ;
10460 }
10561 }
10662 }
107- } ,
108- interaction : {
109- mode : 'nearest' ,
110- axis : 'x' ,
111- intersect : false
112- } ,
113- scales : {
114- x : {
115- display : true ,
116- title : {
117- display : true ,
118- text : 'Time' ,
119- color : '#6b7280' ,
120- font : {
121- size : 12 ,
122- family : 'Inter, system-ui, sans-serif'
123- }
124- } ,
125- grid : {
126- color : 'rgba(107, 114, 128, 0.1)' ,
127- borderColor : 'rgba(107, 114, 128, 0.2)'
128- } ,
129- ticks : {
130- color : '#6b7280' ,
131- font : {
132- size : 11
133- } ,
134- maxTicksLimit : 8
135- }
136- } ,
137- y : {
138- display : true ,
139- title : {
140- display : true ,
141- text : 'Volume (SOL)' ,
142- color : '#6b7280' ,
143- font : {
144- size : 12 ,
145- family : 'Inter, system-ui, sans-serif'
146- }
147- } ,
148- grid : {
149- color : 'rgba(107, 114, 128, 0.1)' ,
150- borderColor : 'rgba(107, 114, 128, 0.2)'
151- } ,
152- ticks : {
153- color : '#6b7280' ,
154- font : {
155- size : 11
156- } ,
157- callback : function ( value ) {
158- if ( value >= 1000000 ) {
159- return `${ ( value / 1000000 ) . toFixed ( 1 ) } M` ;
160- } else if ( value >= 1000 ) {
161- return `${ ( value / 1000 ) . toFixed ( 1 ) } K` ;
162- }
163- return value . toFixed ( 0 ) ;
164- }
165- } ,
166- beginAtZero : true
63+ }
64+
65+ return chart ;
66+ } ;
67+
68+ const generateYAxisLabels = ( data , height = 12 ) => {
69+ if ( ! data || data . length === 0 ) return [ ] ;
70+
71+ const volumes = data . map ( point => point . volume ) ;
72+ const maxVolume = Math . max ( ...volumes ) ;
73+ const minVolume = Math . min ( ...volumes ) ;
74+
75+ const labels = [ ] ;
76+ for ( let i = 0 ; i < height ; i ++ ) {
77+ const value = minVolume + ( ( maxVolume - minVolume ) / ( height - 1 ) ) * ( height - 1 - i ) ;
78+ let formattedValue ;
79+ if ( value >= 1000000 ) {
80+ formattedValue = `${ ( value / 1000000 ) . toFixed ( 1 ) } M` ;
81+ } else if ( value >= 1000 ) {
82+ formattedValue = `${ ( value / 1000 ) . toFixed ( 1 ) } K` ;
83+ } else {
84+ formattedValue = value . toFixed ( 0 ) ;
16785 }
168- } ,
169- elements : {
170- point : {
171- hoverBackgroundColor : network . color || '#9945FF'
86+ labels . push ( formattedValue . padStart ( 6 ) ) ;
87+ }
88+ return labels ;
89+ } ;
90+
91+ const generateXAxisLabels = ( data , width = 60 ) => {
92+ if ( ! data || data . length === 0 ) return [ ] ;
93+
94+ const labels = [ ] ;
95+ const step = Math . max ( 1 , Math . floor ( data . length / 8 ) ) ; // Show ~8 labels max
96+
97+ for ( let i = 0 ; i < width ; i ++ ) {
98+ if ( i < data . length && ( i % step === 0 || i === data . length - 1 ) ) {
99+ const formatted = formatDate ( data [ i ] . time ) ;
100+ labels . push ( { position : i , label : formatted } ) ;
172101 }
173102 }
103+ return labels ;
174104 } ;
175105
176106 const formatVolume = ( volume ) => {
@@ -187,6 +117,13 @@ export default function VolumePerDayChart({ data, network, timeframe }) {
187117 const minVolume = data . length > 0 ? Math . min ( ...data . map ( point => point . volume ) ) : 0 ;
188118 const maxVolume = data . length > 0 ? Math . max ( ...data . map ( point => point . volume ) ) : 0 ;
189119
120+ // Generate ASCII chart data
121+ const chartWidth = 60 ;
122+ const chartHeight = 12 ;
123+ const asciiChart = generateAsciiChart ( data , chartWidth , chartHeight ) ;
124+ const yAxisLabels = generateYAxisLabels ( data , chartHeight ) ;
125+ const xAxisLabels = generateXAxisLabels ( data , chartWidth ) ;
126+
190127 return (
191128 < div className = "volume-chart" >
192129 < div className = "chart-header" >
@@ -217,13 +154,83 @@ export default function VolumePerDayChart({ data, network, timeframe }) {
217154 </ div >
218155 </ div >
219156
220- < div className = "chart-container" >
157+ < div className = "ascii- chart-container" >
221158 { data . length > 0 ? (
222- < Line
223- ref = { chartRef }
224- data = { chartData }
225- options = { chartOptions }
226- />
159+ < div className = "ascii-chart" >
160+ < div className = "chart-legend" >
161+ < span className = "legend-item" > [*] Protocol Volume (SOL)</ span >
162+ </ div >
163+
164+ < div className = "ascii-chart-grid" >
165+ { asciiChart . map ( ( row , rowIndex ) => (
166+ < div key = { rowIndex } className = "chart-row" >
167+ < span className = "y-axis-label" >
168+ { yAxisLabels [ rowIndex ] }
169+ </ span >
170+ < span className = "y-axis-separator" > |</ span >
171+ < span className = "chart-line" >
172+ { row . map ( ( char , colIndex ) => (
173+ < span
174+ key = { colIndex }
175+ className = { `chart-char ${ char !== ' ' ? 'chart-point' : '' } ` }
176+ onMouseEnter = { ( ) => {
177+ if ( char !== ' ' && colIndex < data . length ) {
178+ setHoveredPoint ( {
179+ index : colIndex ,
180+ volume : data [ colIndex ] . volume ,
181+ time : data [ colIndex ] . time
182+ } ) ;
183+ }
184+ } }
185+ onMouseLeave = { ( ) => setHoveredPoint ( null ) }
186+ >
187+ { char }
188+ </ span >
189+ ) ) }
190+ </ span >
191+ </ div >
192+ ) ) }
193+
194+ { /* X-axis */ }
195+ < div className = "chart-row x-axis-row" >
196+ < span className = "y-axis-label" > </ span >
197+ < span className = "y-axis-separator" > |</ span >
198+ < span className = "chart-line" >
199+ { '_' . repeat ( chartWidth ) }
200+ </ span >
201+ </ div >
202+
203+ { /* X-axis labels */ }
204+ < div className = "x-axis-labels" >
205+ < span className = "y-axis-label" > </ span >
206+ < span className = "y-axis-separator" > </ span >
207+ < div className = "x-labels-container" >
208+ { xAxisLabels . map ( ( labelData , index ) => (
209+ < span
210+ key = { index }
211+ className = "x-axis-label"
212+ style = { { left : `${ ( labelData . position / chartWidth ) * 100 } %` } }
213+ >
214+ { labelData . label }
215+ </ span >
216+ ) ) }
217+ </ div >
218+ </ div >
219+ </ div >
220+
221+ { hoveredPoint && (
222+ < div className = "ascii-tooltip" >
223+ < div className = "tooltip-header" >
224+ [VOLUME DATA]
225+ </ div >
226+ < div className = "tooltip-content" >
227+ < div > Time: { formatDate ( hoveredPoint . time ) } </ div >
228+ < div > Volume: { formatVolume ( hoveredPoint . volume ) } </ div >
229+ < div > ≈ ${ ( hoveredPoint . volume * 150 ) . toLocaleString ( ) } USD</ div >
230+ </ div >
231+ </ div >
232+ ) }
233+ </ div >
227234 ) : (
228235 < div className = "chart-placeholder" >
229236 < div className = "placeholder-icon" > [CHART]</ div >
0 commit comments