Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 35 additions & 2 deletions maui/src/Charts/Series/BoxAndWhiskerSeries.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1034,15 +1034,21 @@ internal override void GenerateSegments(SeriesView seriesView)

for (int i = 0; i < PointsCount; i++)
{
var yList = YDataCollection[i].Where(x => !double.IsNaN(x)).ToArray();
var yList = FilterNaNValues(YDataCollection[i]);

List<double> outliers = [];

if (yList.Length > 0)
{
// The quartile, whisker, and min/max calculations below all rely on ascending values.
Array.Sort(yList);
average = yList.Average();
double sum = 0;
for (int j = 0; j < yList.Length; j++)
{
sum += yList[j];
}

average = sum / yList.Length;
}

int yCount = yList.Length;
Expand Down Expand Up @@ -1489,6 +1495,33 @@ static void GetMinMaxandOutlier(double lowerQuartile, double upperQuartile, doub
}
}

/// <summary>
/// Filters out NaN values from the source list without LINQ allocations.
/// </summary>
static double[] FilterNaNValues(IList<double> source)
{
int count = 0;
for (int i = 0; i < source.Count; i++)
{
if (!double.IsNaN(source[i]))
{
count++;
}
}

var result = new double[count];
int index = 0;
for (int i = 0; i < source.Count; i++)
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why does FilterNaNValues(IList source) use two for loops (one to count valid values, then one to copy them) so that double.IsNaN is checked twice for each element, instead of using a single-pass approach?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot Check this feedback

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The two-pass version is intentional because this helper returns a double[] with exact length while avoiding LINQ/list allocations. In a true single-pass approach, we’d either use List<double> (extra object + ToArray copy) or allocate source.Count and then resize/copy. For this hot path, avoiding those extra allocations/copies was prioritized over the extra IsNaN check.

{
if (!double.IsNaN(source[i]))
{
result[index++] = source[i];
}
}

return result;
}

void GetQuartileValues(double[] yList, int len, out double lowerQuartile, out double upperQuartile)
{
double[] lowerQuartileArray;
Expand Down