@@ -30,6 +30,28 @@ private void PaintBraille(CharacterBuffer buffer, int graphX, int graphY,
3030 if ( pixelWidth <= 0 || pixelHeight <= 0 )
3131 return ;
3232
33+ double range = globalMax - globalMin ;
34+
35+ // For single series, use the fast path (original logic)
36+ if ( seriesSnapshots . Count <= 1 )
37+ {
38+ PaintBrailleSingleSeries ( buffer , graphX , graphY , graphWidth , graphHeight ,
39+ pixelWidth , pixelHeight , clipRect , bgColor , seriesSnapshots , globalMin , range ) ;
40+ return ;
41+ }
42+
43+ // Multi-series: render each series into its own pixel grid,
44+ // then composite into buffer with per-series colors.
45+ PaintBrailleMultiSeries ( buffer , graphX , graphY , graphWidth , graphHeight ,
46+ pixelWidth , pixelHeight , clipRect , bgColor , seriesSnapshots , globalMin , range ) ;
47+ }
48+
49+ private void PaintBrailleSingleSeries ( CharacterBuffer buffer , int graphX , int graphY ,
50+ int graphWidth , int graphHeight , int pixelWidth , int pixelHeight ,
51+ LayoutRect clipRect , Color bgColor ,
52+ List < ( LineGraphSeries series , List < double > data ) > seriesSnapshots ,
53+ double globalMin , double range )
54+ {
3355 // Allocate or reuse pixel grid
3456 if ( _pixelGridCache == null ||
3557 _pixelGridCache . GetLength ( 0 ) != pixelHeight ||
@@ -42,60 +64,61 @@ private void PaintBraille(CharacterBuffer buffer, int graphX, int graphY,
4264 Array . Clear ( _pixelGridCache ) ;
4365 }
4466
45- double range = globalMax - globalMin ;
67+ Color seriesColor = Color . Cyan1 ;
68+ ColorGradient ? gradient = null ;
4669
47- // Track which series contributes to each cell column for coloring
48- // Last series rendered wins per cell
49- var seriesColorMap = new int [ graphWidth ] ;
50- Array . Fill ( seriesColorMap , - 1 ) ;
70+ if ( seriesSnapshots . Count == 1 )
71+ {
72+ var ( series , data ) = seriesSnapshots [ 0 ] ;
73+ seriesColor = series . LineColor ;
74+ gradient = series . Gradient ;
75+ if ( data . Count > 0 )
76+ DrawSeriesPixels ( _pixelGridCache , data , pixelWidth , pixelHeight , globalMin , range ) ;
77+ }
5178
52- // Draw all series into pixel grid
53- for ( int si = 0 ; si < seriesSnapshots . Count ; si ++ )
79+ for ( int row = 0 ; row < graphHeight ; row ++ )
5480 {
55- var ( series , data ) = seriesSnapshots [ si ] ;
56- if ( data . Count == 0 )
81+ int paintY = graphY + row ;
82+ if ( paintY < clipRect . Y || paintY >= clipRect . Bottom )
5783 continue ;
5884
59- if ( data . Count == 1 )
85+ for ( int col = 0 ; col < graphWidth ; col ++ )
6086 {
61- // Single point: draw a dot at the mapped position
62- int py = pixelHeight - 1 - ( int ) ( ( data [ 0 ] - globalMin ) / range * ( pixelHeight - 1 ) ) ;
63- py = Math . Clamp ( py , 0 , pixelHeight - 1 ) ;
64- int px = pixelWidth / 2 ;
65- if ( px >= 0 && px < pixelWidth )
87+ int paintX = graphX + col ;
88+ if ( paintX < clipRect . X || paintX >= clipRect . Right )
89+ continue ;
90+
91+ char brailleChar = BrailleHelpers . GetBrailleChar ( _pixelGridCache , col , row ) ;
92+
93+ if ( brailleChar == BrailleHelpers . BrailleEmpty )
6694 {
67- _pixelGridCache [ py , px ] = true ;
68- seriesColorMap [ px / BrailleHelpers . DotsPerCellWidth ] = si ;
95+ buffer . SetNarrowCell ( paintX , paintY , brailleChar , EmptyCellColor , bgColor ) ;
6996 }
70- continue ;
71- }
72-
73- // Draw line segments between consecutive points
74- for ( int i = 0 ; i < data . Count - 1 ; i ++ )
75- {
76- int x1 = ( int ) ( i * ( pixelWidth - 1.0 ) / Math . Max ( data . Count - 1 , 1 ) ) ;
77- int y1 = pixelHeight - 1 - ( int ) ( ( data [ i ] - globalMin ) / range * ( pixelHeight - 1 ) ) ;
78- int x2 = ( int ) ( ( i + 1 ) * ( pixelWidth - 1.0 ) / Math . Max ( data . Count - 1 , 1 ) ) ;
79- int y2 = pixelHeight - 1 - ( int ) ( ( data [ i + 1 ] - globalMin ) / range * ( pixelHeight - 1 ) ) ;
80-
81- x1 = Math . Clamp ( x1 , 0 , pixelWidth - 1 ) ;
82- y1 = Math . Clamp ( y1 , 0 , pixelHeight - 1 ) ;
83- x2 = Math . Clamp ( x2 , 0 , pixelWidth - 1 ) ;
84- y2 = Math . Clamp ( y2 , 0 , pixelHeight - 1 ) ;
85-
86- BrailleHelpers . DrawLinePixels ( _pixelGridCache , x1 , y1 , x2 , y2 ) ;
87-
88- // Mark cell columns touched by this segment for coloring
89- int cellStart = Math . Min ( x1 , x2 ) / BrailleHelpers . DotsPerCellWidth ;
90- int cellEnd = Math . Max ( x1 , x2 ) / BrailleHelpers . DotsPerCellWidth ;
91- for ( int c = cellStart ; c <= cellEnd && c < graphWidth ; c ++ )
97+ else
9298 {
93- seriesColorMap [ c ] = si ;
99+ Color cellColor ;
100+ if ( gradient != null )
101+ {
102+ double position = graphWidth > 1 ? ( double ) col / ( graphWidth - 1 ) : 0.5 ;
103+ cellColor = gradient . Interpolate ( position ) ;
104+ }
105+ else
106+ {
107+ cellColor = seriesColor ;
108+ }
109+ buffer . SetNarrowCell ( paintX , paintY , brailleChar , cellColor , bgColor ) ;
94110 }
95111 }
96112 }
113+ }
97114
98- // Convert pixel grid to braille characters and write to buffer
115+ private void PaintBrailleMultiSeries ( CharacterBuffer buffer , int graphX , int graphY ,
116+ int graphWidth , int graphHeight , int pixelWidth , int pixelHeight ,
117+ LayoutRect clipRect , Color bgColor ,
118+ List < ( LineGraphSeries series , List < double > data ) > seriesSnapshots ,
119+ double globalMin , double range )
120+ {
121+ // First pass: paint empty braille background for all cells
99122 for ( int row = 0 ; row < graphHeight ; row ++ )
100123 {
101124 int paintY = graphY + row ;
@@ -108,38 +131,102 @@ private void PaintBraille(CharacterBuffer buffer, int graphX, int graphY,
108131 if ( paintX < clipRect . X || paintX >= clipRect . Right )
109132 continue ;
110133
111- char brailleChar = BrailleHelpers . GetBrailleChar ( _pixelGridCache , col , row ) ;
134+ buffer . SetNarrowCell ( paintX , paintY , BrailleHelpers . BrailleEmpty , EmptyCellColor , bgColor ) ;
135+ }
136+ }
112137
113- if ( brailleChar == BrailleHelpers . BrailleEmpty )
114- {
115- buffer . SetNarrowCell ( paintX , paintY , brailleChar , EmptyCellColor , bgColor ) ;
116- }
117- else
138+ // Render each series independently with its own pixel grid and color
139+ var pixelGrid = new bool [ pixelHeight , pixelWidth ] ;
140+
141+ for ( int si = 0 ; si < seriesSnapshots . Count ; si ++ )
142+ {
143+ var ( series , data ) = seriesSnapshots [ si ] ;
144+ if ( data . Count == 0 )
145+ continue ;
146+
147+ Array . Clear ( pixelGrid ) ;
148+ DrawSeriesPixels ( pixelGrid , data , pixelWidth , pixelHeight , globalMin , range ) ;
149+
150+ // Write this series' braille chars to buffer with its own color
151+ for ( int row = 0 ; row < graphHeight ; row ++ )
152+ {
153+ int paintY = graphY + row ;
154+ if ( paintY < clipRect . Y || paintY >= clipRect . Bottom )
155+ continue ;
156+
157+ for ( int col = 0 ; col < graphWidth ; col ++ )
118158 {
159+ int paintX = graphX + col ;
160+ if ( paintX < clipRect . X || paintX >= clipRect . Right )
161+ continue ;
162+
163+ char brailleChar = BrailleHelpers . GetBrailleChar ( pixelGrid , col , row ) ;
164+ if ( brailleChar == BrailleHelpers . BrailleEmpty )
165+ continue ;
166+
119167 Color cellColor ;
120- int seriesIndex = seriesColorMap [ col ] ;
121- if ( seriesIndex >= 0 && seriesIndex < seriesSnapshots . Count )
168+ if ( series . Gradient != null )
122169 {
123- var ( series , _) = seriesSnapshots [ seriesIndex ] ;
124- if ( series . Gradient != null )
125- {
126- double position = graphWidth > 1 ? ( double ) col / ( graphWidth - 1 ) : 0.5 ;
127- cellColor = series . Gradient . Interpolate ( position ) ;
128- }
129- else
130- {
131- cellColor = series . LineColor ;
132- }
170+ double position = graphWidth > 1 ? ( double ) col / ( graphWidth - 1 ) : 0.5 ;
171+ cellColor = series . Gradient . Interpolate ( position ) ;
133172 }
134173 else
135174 {
136- cellColor = Color . Cyan1 ;
175+ cellColor = series . LineColor ;
137176 }
138177
139- buffer . SetNarrowCell ( paintX , paintY , brailleChar , cellColor , bgColor ) ;
178+ // Merge with existing braille content in this cell
179+ var existing = buffer . GetCell ( paintX , paintY ) ;
180+ int existingBraille = existing . Character . Value ;
181+ int newBraille = brailleChar ;
182+
183+ // If the existing cell is a braille character, OR the patterns together
184+ if ( existingBraille >= 0x2800 && existingBraille <= 0x28FF )
185+ {
186+ int merged = existingBraille | newBraille ;
187+ buffer . SetNarrowCell ( paintX , paintY , ( char ) merged , cellColor , bgColor ) ;
188+ }
189+ else
190+ {
191+ buffer . SetNarrowCell ( paintX , paintY , brailleChar , cellColor , bgColor ) ;
192+ }
140193 }
141194 }
142195 }
143196 }
197+
198+ /// <summary>
199+ /// Draw a series' data points into a pixel grid using Bresenham line segments.
200+ /// </summary>
201+ private static void DrawSeriesPixels ( bool [ , ] grid , List < double > data ,
202+ int pixelWidth , int pixelHeight , double globalMin , double range )
203+ {
204+ if ( data . Count == 1 )
205+ {
206+ int py = range > 0
207+ ? pixelHeight - 1 - ( int ) ( ( data [ 0 ] - globalMin ) / range * ( pixelHeight - 1 ) )
208+ : pixelHeight / 2 ;
209+ py = Math . Clamp ( py , 0 , pixelHeight - 1 ) ;
210+ int px = pixelWidth / 2 ;
211+ if ( px >= 0 && px < pixelWidth )
212+ grid [ py , px ] = true ;
213+ return ;
214+ }
215+
216+ for ( int i = 0 ; i < data . Count - 1 ; i ++ )
217+ {
218+ int x1 = ( int ) ( i * ( pixelWidth - 1.0 ) / Math . Max ( data . Count - 1 , 1 ) ) ;
219+ int y1 = pixelHeight - 1 - ( int ) ( ( data [ i ] - globalMin ) / range * ( pixelHeight - 1 ) ) ;
220+ int x2 = ( int ) ( ( i + 1 ) * ( pixelWidth - 1.0 ) / Math . Max ( data . Count - 1 , 1 ) ) ;
221+ int y2 = pixelHeight - 1 - ( int ) ( ( data [ i + 1 ] - globalMin ) / range * ( pixelHeight - 1 ) ) ;
222+
223+ x1 = Math . Clamp ( x1 , 0 , pixelWidth - 1 ) ;
224+ y1 = Math . Clamp ( y1 , 0 , pixelHeight - 1 ) ;
225+ x2 = Math . Clamp ( x2 , 0 , pixelWidth - 1 ) ;
226+ y2 = Math . Clamp ( y2 , 0 , pixelHeight - 1 ) ;
227+
228+ BrailleHelpers . DrawLinePixels ( grid , x1 , y1 , x2 , y2 ) ;
229+ }
230+ }
144231 }
145232}
0 commit comments