Skip to content

Commit 8581a73

Browse files
perf: Optimize Chart control performance
- Replace O(n²) IndexOf lookups with Dictionary in CategoryAxis.GroupData() - Use HashSet for O(1) Contains checks in category value grouping - Replace LINQ ToList() for single item with simple loop in GetAxisByName - Cache GetType().Name result to avoid repeated reflection in stacking calculations - Replace four LINQ Where/Min/Max queries with single loop in ErrorBarSegment - Replace LINQ Where().Sum() with simple loop in stacking value calculation - Replace LINQ-based index generation with pre-allocated List in GetXValues - Replace LINQ Where/Select/Min/Max with single loop for YRange calculation - Eliminate unnecessary List allocations in GetSdErrorValue - Cache Values.ToList() result outside loops in SBS calculations Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent dada3bb commit 8581a73

5 files changed

Lines changed: 190 additions & 81 deletions

File tree

maui/src/Charts/Area/Partial/CartesianChartArea.cs

Lines changed: 66 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -183,9 +183,10 @@ internal void UpdateSBS()
183183
double totalWidth = GetTotalWidth() / SideBySideSeriesPosition.Count;
184184
double startPosition = 0, end = 0;
185185

186-
for (int i = 0; i < SideBySideSeriesPosition.Count; i++)
186+
var seriesGroups = SideBySideSeriesPosition.Values.ToList();
187+
for (int i = 0; i < seriesGroups.Count; i++)
187188
{
188-
var seriesGroup = SideBySideSeriesPosition.Values.ToList()[i];
189+
var seriesGroup = seriesGroups[i];
189190
double sbsMaxWidth = GetSBSMaxWidth(seriesGroup);
190191

191192
foreach (ChartSeries chartSeries in seriesGroup)
@@ -370,10 +371,11 @@ double GetTotalWidth()
370371

371372
if (SideBySideSeriesPosition != null)
372373
{
373-
for (int i = 0; i < SideBySideSeriesPosition.Count; i++)
374+
var valuesList = SideBySideSeriesPosition.Values.ToList();
375+
for (int i = 0; i < valuesList.Count; i++)
374376
{
375377
double maxWidth = 0;
376-
foreach (ChartSeries sideBySideSeries in SideBySideSeriesPosition.Values.ToList()[i])
378+
foreach (ChartSeries sideBySideSeries in valuesList[i])
377379
{
378380
CartesianSeries cartesianSeries = (CartesianSeries)sideBySideSeries;
379381
double width = cartesianSeries.GetActualWidth();
@@ -483,73 +485,88 @@ static void CalculateStackingValues(Dictionary<string, List<StackingSeriesBase>>
483485
}
484486

485487
foreach (var series in seriesList)
488+
{
489+
var xValues = series.GetXValues();
490+
var yValues = series.YValues;
491+
var bottomValues = new List<double>();
492+
var topValues = new List<double>();
493+
var seriesTypeName = series.GetType().Name;
494+
bool is100Series = seriesTypeName.Contains("Stacking", StringComparison.Ordinal) && seriesTypeName.Contains("100Series", StringComparison.Ordinal);
495+
496+
if (xValues != null)
486497
{
487-
var xValues = series.GetXValues();
488-
var yValues = series.YValues;
489-
var bottomValues = new List<double>();
490-
var topValues = new List<double>();
491-
492-
if (xValues != null)
498+
for (int i = 0; i < xValues.Count; i++)
493499
{
494-
for (int i = 0; i < xValues.Count; i++)
495-
{
496-
var xValue = xValues[i];
497-
var yValue = i < yValues.Count ? yValues[i] : 0;
498-
yValue = double.IsNaN(yValue) ? 0 : yValue;
500+
var xValue = xValues[i];
501+
var yValue = i < yValues.Count ? yValues[i] : 0;
502+
yValue = double.IsNaN(yValue) ? 0 : yValue;
499503

500-
if (yValue >= 0)
504+
if (yValue >= 0)
505+
{
506+
if (positiveYValues.TryGetValue(xValue, out double currentValue))
501507
{
502-
if (positiveYValues.TryGetValue(xValue, out double currentValue))
503-
{
504-
bottomValues.Add((axisCross > currentValue) ? axisCross : currentValue);
505-
if (series.GetType().Name.Contains("Stacking", StringComparison.Ordinal) && series.GetType().Name.Contains("100Series", StringComparison.Ordinal))
506-
{
507-
yValue = GetYValue(seriesList, yValue, i);
508-
}
509-
positiveYValues[xValue] = currentValue + yValue;
510-
}
511-
else
508+
bottomValues.Add((axisCross > currentValue) ? axisCross : currentValue);
509+
if (is100Series)
512510
{
513-
bottomValues.Add(axisCross);
514-
if (series.GetType().Name.Contains("Stacking", StringComparison.Ordinal) && series.GetType().Name.Contains("100Series", StringComparison.Ordinal))
515-
{
516-
yValue = GetYValue(seriesList, yValue, i);
517-
}
518-
positiveYValues.Add(xValue, yValue);
511+
yValue = GetYValue(seriesList, yValue, i);
519512
}
520-
521-
topValues.Add(positiveYValues[xValue]);
513+
positiveYValues[xValue] = currentValue + yValue;
522514
}
523515
else
524516
{
525-
if (!negativeYValues.TryAdd(xValue, yValue))
517+
bottomValues.Add(axisCross);
518+
if (is100Series)
526519
{
527-
bottomValues.Add((axisCross < negativeYValues[xValue]) ? axisCross : negativeYValues[xValue]);
528-
if (series.GetType().Name.Contains("Stacking", StringComparison.Ordinal) && series.GetType().Name.Contains("100Series", StringComparison.Ordinal))
529-
{
530-
yValue = GetYValue(seriesList, yValue, i);
531-
}
532-
negativeYValues[xValue] += yValue;
520+
yValue = GetYValue(seriesList, yValue, i);
533521
}
534-
else
522+
positiveYValues.Add(xValue, yValue);
523+
}
524+
525+
topValues.Add(positiveYValues[xValue]);
526+
}
527+
else
528+
{
529+
if (!negativeYValues.TryAdd(xValue, yValue))
530+
{
531+
bottomValues.Add((axisCross < negativeYValues[xValue]) ? axisCross : negativeYValues[xValue]);
532+
if (is100Series)
535533
{
536-
bottomValues.Add(axisCross);
534+
yValue = GetYValue(seriesList, yValue, i);
537535
}
538-
539-
topValues.Add(negativeYValues[xValue]);
536+
negativeYValues[xValue] += yValue;
537+
}
538+
else
539+
{
540+
bottomValues.Add(axisCross);
540541
}
541-
}
542542

543-
series.BottomValues = bottomValues;
544-
series.TopValues = topValues;
543+
topValues.Add(negativeYValues[xValue]);
544+
}
545545
}
546+
547+
series.BottomValues = bottomValues;
548+
series.TopValues = topValues;
546549
}
547550
}
551+
}
548552
}
549553

550554
static double GetYValue(List<StackingSeriesBase> SeriesList, double yValue, int index)
551555
{
552-
double total = SeriesList.Where(series => series != null && series.YValues.Count > index).Sum(series => double.IsNaN(series.YValues[index]) ? 0 : Math.Abs(series.YValues[index]));
556+
double total = 0;
557+
for (int i = 0; i < SeriesList.Count; i++)
558+
{
559+
var series = SeriesList[i];
560+
if (series != null && series.YValues.Count > index)
561+
{
562+
double val = series.YValues[index];
563+
if (!double.IsNaN(val))
564+
{
565+
total += Math.Abs(val);
566+
}
567+
}
568+
}
569+
553570
if (yValue != 0)
554571
{
555572
yValue = (yValue / total) * 100;

maui/src/Charts/Axis/CategoryAxis.cs

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,10 @@ internal void GroupData()
4040
{
4141
if (groupedDatas.Count != 0)
4242
{
43-
for (int j = 0; j <= xValues.Count - 1; j++)
43+
var seen = new HashSet<string>(groupingValues);
44+
for (int j = 0; j < xValues.Count; j++)
4445
{
45-
if (!groupingValues.Contains(xValues[j]))
46+
if (seen.Add(xValues[j]))
4647
{
4748
groupingValues.Add(xValues[j]);
4849
}
@@ -65,16 +66,31 @@ internal void GroupData()
6566
}
6667

6768
var distinctXValues = groupingValues.Distinct().ToList();
69+
var indexLookup = new Dictionary<string, int>(distinctXValues.Count);
70+
for (int i = 0; i < distinctXValues.Count; i++)
71+
{
72+
indexLookup[distinctXValues[i]] = i;
73+
}
6874

6975
foreach (CartesianSeries series in RegisteredSeries.Cast<CartesianSeries>())
7076
{
7177
if (series.ActualXValues is List<string> list)
7278
{
73-
series.GroupedXValuesIndexes = (from val in list select (double)distinctXValues.IndexOf(val)).ToList();
79+
var indexes = new List<double>(list.Count);
80+
for (int i = 0; i < list.Count; i++)
81+
{
82+
indexes.Add(indexLookup.TryGetValue(list[i], out int idx) ? idx : -1);
83+
}
84+
series.GroupedXValuesIndexes = indexes;
7485
}
75-
else if (series.ActualXValues != null)
86+
else if (series.ActualXValues is List<double> doubleValues)
7687
{
77-
series.GroupedXValuesIndexes = (from val in series.ActualXValues as List<double> select (double)distinctXValues.IndexOf(val.ToString())).ToList();
88+
var indexes = new List<double>(doubleValues.Count);
89+
for (int i = 0; i < doubleValues.Count; i++)
90+
{
91+
indexes.Add(indexLookup.TryGetValue(doubleValues[i].ToString(), out int idx) ? idx : -1);
92+
}
93+
series.GroupedXValuesIndexes = indexes;
7894
}
7995

8096
series.GroupedXValues = distinctXValues;

maui/src/Charts/Axis/Layouts/CartesianAxisLayout.cs

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -499,10 +499,17 @@ void InternalCreateSegments(ChartSeries series)
499499

500500
static ChartAxis? GetAxisByName(string name, ObservableCollection<ChartAxis>? axes)
501501
{
502-
var item = (from x in axes where x.Name == name select x).ToList();
503-
if (item != null && item.Count > 0)
502+
if (axes == null)
503+
{
504+
return null;
505+
}
506+
507+
for (int i = 0; i < axes.Count; i++)
504508
{
505-
return item[0];
509+
if (axes[i].Name == name)
510+
{
511+
return axes[i];
512+
}
506513
}
507514

508515
return null;

maui/src/Charts/Segment/ErrorBarSegment.cs

Lines changed: 72 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -83,10 +83,30 @@ internal override void SetData(IList xList, IList yList)
8383
break;
8484
}
8585

86-
double xMin = xValues.Where(v => !double.IsNaN(v)).DefaultIfEmpty(0).Min();
87-
double xMax = xValues.Where(v => !double.IsNaN(v)).DefaultIfEmpty(0).Max();
88-
double yMin = yValues.Where(v => !double.IsNaN(v)).DefaultIfEmpty(0).Min();
89-
double yMax = yValues.Where(v => !double.IsNaN(v)).DefaultIfEmpty(0).Max();
86+
double xMin = double.MaxValue, xMax = double.MinValue;
87+
double yMin = double.MaxValue, yMax = double.MinValue;
88+
for (int i = 0; i < xValues.Count; i++)
89+
{
90+
double xVal = xValues[i];
91+
if (!double.IsNaN(xVal))
92+
{
93+
if (xVal < xMin) xMin = xVal;
94+
if (xVal > xMax) xMax = xVal;
95+
}
96+
}
97+
for (int i = 0; i < yValues.Count; i++)
98+
{
99+
double yVal = yValues[i];
100+
if (!double.IsNaN(yVal))
101+
{
102+
if (yVal < yMin) yMin = yVal;
103+
if (yVal > yMax) yMax = yVal;
104+
}
105+
}
106+
if (xMin == double.MaxValue) xMin = 0;
107+
if (xMax == double.MinValue) xMax = 0;
108+
if (yMin == double.MaxValue) yMin = 0;
109+
if (yMax == double.MinValue) yMax = 0;
90110

91111
double leftPointMin = xMin - _horizontalErrorValue;
92112
double leftPointMax = xMax - _horizontalErrorValue;
@@ -192,12 +212,35 @@ internal override void SetData(IList xList, IList yList)
192212

193213
if (errorBarSeries.Type == ErrorBarType.Percentage)
194214
{
195-
var validTopPoints = _topPointCollection.Where(p => !double.IsNaN(p.Y)).Select(p => p.Y);
196-
var validBottomPoints = _bottomPointCollection.Where(p => !double.IsNaN(p.Y)).Select(p => p.Y);
215+
double rangeMin = double.MaxValue;
216+
double rangeMax = double.MinValue;
217+
bool hasValid = false;
197218

198-
if (validTopPoints.Any() && validBottomPoints.Any())
219+
for (int i = 0; i < _topPointCollection.Count; i++)
199220
{
200-
YRange = new DoubleRange(Math.Min(validBottomPoints.Min(), validTopPoints.Min()), Math.Max(validBottomPoints.Max(), validTopPoints.Max()));
221+
double y = _topPointCollection[i].Y;
222+
if (!double.IsNaN(y))
223+
{
224+
if (y < rangeMin) rangeMin = y;
225+
if (y > rangeMax) rangeMax = y;
226+
hasValid = true;
227+
}
228+
}
229+
230+
for (int i = 0; i < _bottomPointCollection.Count; i++)
231+
{
232+
double y = _bottomPointCollection[i].Y;
233+
if (!double.IsNaN(y))
234+
{
235+
if (y < rangeMin) rangeMin = y;
236+
if (y > rangeMax) rangeMax = y;
237+
hasValid = true;
238+
}
239+
}
240+
241+
if (hasValid)
242+
{
243+
YRange = new DoubleRange(rangeMin, rangeMax);
201244
}
202245
}
203246
else
@@ -383,28 +426,36 @@ double[] GetSdErrorValue(IList<double> values)
383426
return valueDoubles;
384427
}
385428

386-
var validValues = values.Where(v => !double.IsNaN(v)).ToList();
429+
double sum = 0;
430+
int validCount = 0;
431+
for (int i = 0; i < values.Count; i++)
432+
{
433+
if (!double.IsNaN(values[i]))
434+
{
435+
sum += values[i];
436+
validCount++;
437+
}
438+
}
387439

388-
if (validValues.Count <= 1)
440+
if (validCount <= 1)
389441
{
390442
return valueDoubles;
391443
}
392444

393-
var sum = validValues.Sum();
394-
var mean = sum / validValues.Count;
395-
var dev = new List<double>();
396-
var sQDev = new List<double>();
445+
double mean = sum / validCount;
446+
double sumSqDev = 0;
397447

398-
for (var i = 0; i < validValues.Count; i++)
448+
for (int i = 0; i < values.Count; i++)
399449
{
400-
dev.Add(validValues[i] - mean);
401-
sQDev.Add(dev[i] * dev[i]);
450+
if (!double.IsNaN(values[i]))
451+
{
452+
double dev = values[i] - mean;
453+
sumSqDev += dev * dev;
454+
}
402455
}
403456

404-
var sumSqDev = sQDev.Sum(x => x);
405-
406-
var sDValue = Math.Sqrt(sumSqDev / (validValues.Count - 1));
407-
var sDErrorValue = sDValue / Math.Sqrt(validValues.Count);
457+
var sDValue = Math.Sqrt(sumSqDev / (validCount - 1));
458+
var sDErrorValue = sDValue / Math.Sqrt(validCount);
408459

409460
valueDoubles[0] = mean;
410461
valueDoubles[1] = errorBarSeries.Type == ErrorBarType.StandardDeviation ? sDValue : sDErrorValue;

maui/src/Charts/Series/CartesianSeries.cs

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1027,18 +1027,36 @@ internal override void UpdateRange()
10271027
return null;
10281028
}
10291029

1030-
double xIndexValues = 0d;
10311030
var xValues = ActualXValues as List<double>;
10321031

10331032
if (IsIndexed || xValues == null)
10341033
{
10351034
if (ActualXAxis is CategoryAxis categoryAxis && !categoryAxis.ArrangeByIndex || ActualXAxis == null)
10361035
{
1037-
xValues = GroupedXValuesIndexes.Count > 0 ? GroupedXValuesIndexes : (from val in (ActualXValues as List<string>) select (xIndexValues++)).ToList();
1036+
if (GroupedXValuesIndexes.Count > 0)
1037+
{
1038+
xValues = GroupedXValuesIndexes;
1039+
}
1040+
else
1041+
{
1042+
var stringValues = ActualXValues as List<string>;
1043+
int count = stringValues?.Count ?? 0;
1044+
xValues = new List<double>(count);
1045+
for (int i = 0; i < count; i++)
1046+
{
1047+
xValues.Add(i);
1048+
}
1049+
}
10381050
}
10391051
else
10401052
{
1041-
xValues = xValues != null ? (from val in xValues select (xIndexValues++)).ToList() : (from val in (ActualXValues as List<string>) select (xIndexValues++)).ToList();
1053+
int count = xValues?.Count ?? (ActualXValues as List<string>)?.Count ?? 0;
1054+
var indexValues = new List<double>(count);
1055+
for (int i = 0; i < count; i++)
1056+
{
1057+
indexValues.Add(i);
1058+
}
1059+
xValues = indexValues;
10421060
}
10431061
}
10441062

0 commit comments

Comments
 (0)