Skip to content

Commit 7ceafea

Browse files
Merge pull request #122 from subash-sf/main
Implement the Fast Scatter series support in the Cartesian Chart in MAUI Toolkit
2 parents 097cc5d + 29e487e commit 7ceafea

File tree

11 files changed

+1182
-0
lines changed

11 files changed

+1182
-0
lines changed

maui/samples/Gallery/SampleList/CartesianChartSamplesList.xml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,8 @@
5757
</CardLayout>
5858
</SubCategory>
5959

60+
<Sample Title="Fast Scatter" StatusTag="New" SampleName="FastScatterChart" SearchTags="scatter, scatter chart, scatter plot, fast scatter, fast scatter chart, fast scatter plot, high performance chart"/>
61+
6062
<SubCategory Title="Spline">
6163
<CardLayout>
6264
<Sample Title="Default spline chart" SampleName="SplineChart" Description="This sample demonstrates the default spline chart. The marker, tooltip, and legend are enabled in the model. Tap the data point to view information about that data point in a tooltip." SearchTags="spline"/>
@@ -151,6 +153,7 @@
151153
<Sample Title="OHLC chart" SampleName="OHLC" SearchTags="high,low,open,close"/>
152154
</CardLayout>
153155
</SubCategory>
156+
154157
</Category>
155158

156159
<Category Title="Real-time Charts">
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
<localCore:SampleView xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
2+
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
3+
x:Class="Syncfusion.Maui.ControlsGallery.CartesianChart.SfCartesianChart.FastScatterChart"
4+
xmlns:local="clr-namespace:Syncfusion.Maui.ControlsGallery.CartesianChart.SfCartesianChart"
5+
xmlns:localCore="clr-namespace:Syncfusion.Maui.ControlsGallery;assembly=Syncfusion.Maui.ControlsGallery"
6+
xmlns:chart="clr-namespace:Syncfusion.Maui.Toolkit.Charts;assembly=Syncfusion.Maui.Toolkit">
7+
8+
<localCore:SampleView.Resources>
9+
<ResourceDictionary>
10+
<ResourceDictionary.MergedDictionaries>
11+
<local:CartesianChartColorResources/>
12+
</ResourceDictionary.MergedDictionaries>
13+
</ResourceDictionary>
14+
</localCore:SampleView.Resources>
15+
16+
<localCore:SampleView.Content>
17+
<chart:SfCartesianChart x:Name="fastScatterChart" PaletteBrushes="{AppThemeBinding Light={StaticResource PaletteBrushesDark}, Dark={StaticResource PaletteBrushesDark}}" HorizontalOptions="Fill" VerticalOptions="Fill">
18+
<chart:SfCartesianChart.BindingContext>
19+
<local:FastScatterSeriesViewModel/>
20+
</chart:SfCartesianChart.BindingContext>
21+
<chart:SfCartesianChart.Legend>
22+
<chart:ChartLegend ToggleSeriesVisibility="True"/>
23+
</chart:SfCartesianChart.Legend>
24+
<chart:SfCartesianChart.Title>
25+
<Label Text="Transportation Network Travel Speed Across City Clusters" LineBreakMode="WordWrap" Margin="0" HorizontalOptions="Fill" HorizontalTextAlignment="Center" VerticalOptions="Center" FontSize="16" />
26+
</chart:SfCartesianChart.Title>
27+
<chart:SfCartesianChart.XAxes>
28+
<chart:NumericalAxis Maximum="1500" Interval="100" EdgeLabelsDrawingMode="Shift" ShowMajorGridLines="False" >
29+
<chart:NumericalAxis.Title>
30+
<chart:ChartAxisTitle Text="Euclidean Distance (km)"></chart:ChartAxisTitle>
31+
</chart:NumericalAxis.Title>
32+
</chart:NumericalAxis>
33+
</chart:SfCartesianChart.XAxes>
34+
<chart:SfCartesianChart.YAxes>
35+
<chart:NumericalAxis Interval="30" ShowMajorGridLines="False" >
36+
<chart:NumericalAxis.Title>
37+
<chart:ChartAxisTitle Text="Transportation Network Travel Speed (km/h)" ></chart:ChartAxisTitle>
38+
</chart:NumericalAxis.Title>
39+
</chart:NumericalAxis>
40+
</chart:SfCartesianChart.YAxes>
41+
<chart:FastScatterSeries ItemsSource="{Binding AutomobileTravelData}"
42+
XBindingPath="Value"
43+
YBindingPath="Size"
44+
EnableTooltip="True"
45+
Label="Train travel"/>
46+
47+
<chart:FastScatterSeries ItemsSource="{Binding TrainTravelData}"
48+
XBindingPath="Value"
49+
YBindingPath="Size"
50+
EnableTooltip="True"
51+
Label="Automobile travel"/>
52+
</chart:SfCartesianChart>
53+
</localCore:SampleView.Content>
54+
</localCore:SampleView>
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
namespace Syncfusion.Maui.ControlsGallery.CartesianChart.SfCartesianChart
2+
{
3+
public partial class FastScatterChart : SampleView
4+
{
5+
public FastScatterChart()
6+
{
7+
InitializeComponent();
8+
}
9+
10+
public override void OnDisappearing()
11+
{
12+
base.OnDisappearing();
13+
fastScatterChart.Handler?.DisconnectHandler();
14+
}
15+
}
16+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
using System.Collections.ObjectModel;
2+
3+
namespace Syncfusion.Maui.ControlsGallery.CartesianChart.SfCartesianChart
4+
{
5+
public partial class FastScatterSeriesViewModel : BaseViewModel
6+
{
7+
public ObservableCollection<ChartDataModel> TrainTravelData { get; set; }
8+
public ObservableCollection<ChartDataModel> AutomobileTravelData { get; set; }
9+
10+
public FastScatterSeriesViewModel()
11+
{
12+
TrainTravelData = new ObservableCollection<ChartDataModel>();
13+
AutomobileTravelData = new ObservableCollection<ChartDataModel>();
14+
15+
for (int i = 1; i <= 2000; i++)
16+
{
17+
double distance = i * 0.7 + ((i % 15 - 7) * (i / 400.0));
18+
19+
double variationFactor = ((i / 5) % 5) * 3.0;
20+
double increaseFactor = (i / 150.0);
21+
22+
double trainScatter = ((i % 9 - 4) * 3.2 + (i % 13 - 6) * 2.1 + variationFactor) * (1 + i / 800.0);
23+
double trainSpeed = 12 + (Math.Log(i + 1) * 9) + (Math.Sin(i * 0.1) * 12) + trainScatter + increaseFactor * 5;
24+
25+
double autoScatter = ((i % 6 - 3) * 3.8 + (i % 10 - 5) * 2.5 + variationFactor) * (1 + i / 900.0);
26+
double autoSpeed = 35 + (Math.Log(i + 1) * 11) + (Math.Cos(i * 0.15) * 10) + autoScatter + increaseFactor * 6;
27+
28+
TrainTravelData.Add(new ChartDataModel(distance, Math.Max(10, trainSpeed)));
29+
AutomobileTravelData.Add(new ChartDataModel(distance, Math.Max(20, autoSpeed)));
30+
}
31+
}
32+
}
33+
}
Lines changed: 254 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,254 @@
1+
namespace Syncfusion.Maui.Toolkit.Charts
2+
{
3+
/// <summary>
4+
/// Represents a segment of the <see cref="FastScatterSeries"/> chart, responsible for drawing the scatter and managing its visual appearance.
5+
/// </summary>
6+
public partial class FastScatterSegment : CartesianSegment
7+
{
8+
#region Fields
9+
10+
float _preXPos;
11+
float _preYPos;
12+
float _preXValue;
13+
float _preYValue;
14+
readonly List<PointF> _fastScatterPlottingPoints = [];
15+
RectF _actualRectF;
16+
17+
#endregion
18+
19+
#region Internal Properties
20+
21+
internal List<double>? XValues { get; set; }
22+
internal IList<double>? YValues { get; set; }
23+
24+
#endregion
25+
26+
#region Methods
27+
28+
#region Internal Methods
29+
30+
internal void SetData(List<double> xValues, IList<double> yValues)
31+
{
32+
if (Series is XYDataSeries series)
33+
{
34+
// Initialize min/max values
35+
double xMin = double.MaxValue, xMax = double.MinValue, yMin = double.MaxValue, yMax = double.MinValue;
36+
XValues = xValues;
37+
YValues = yValues;
38+
int dataCount = series.PointsCount;
39+
40+
if (dataCount == 0)
41+
{
42+
return;
43+
}
44+
45+
// Adjust for indexed series by setting xMin/xMax
46+
if (series.IsIndexed && dataCount > 0)
47+
{
48+
xMin = XValues[0];
49+
xMax = XValues[dataCount - 1];
50+
}
51+
52+
for (int i = 0; i < dataCount; i++)
53+
{
54+
// Ensure we are in range for both lists
55+
if (i >= XValues.Count)
56+
{
57+
break;
58+
}
59+
60+
double xValue = XValues[i];
61+
double yValue = YValues[i];
62+
63+
// Mark as empty if any NaN values are encountered
64+
if (double.IsNaN(xValue) || double.IsNaN(yValue))
65+
{
66+
continue;
67+
}
68+
69+
if (!series.IsIndexed)
70+
{
71+
// Use Math.Min/Max for cleaner comparisons
72+
xMin = Math.Min(xMin, xValue);
73+
xMax = Math.Max(xMax, xValue);
74+
}
75+
76+
yMin = Math.Min(yMin, yValue);
77+
yMax = Math.Max(yMax, yValue);
78+
}
79+
80+
// Handle no valid entries scenario by setting NaNs
81+
if (xMin == double.MaxValue)
82+
{
83+
xMin = double.NaN;
84+
}
85+
86+
if (xMax == double.MinValue)
87+
{
88+
xMax = double.NaN;
89+
}
90+
91+
if (yMin == double.MaxValue)
92+
{
93+
yMin = double.NaN;
94+
}
95+
96+
if (yMax == double.MinValue)
97+
{
98+
yMax = double.NaN;
99+
}
100+
101+
// Update series range
102+
Series.XRange += new DoubleRange(xMin, xMax);
103+
Series.YRange += new DoubleRange(yMin, yMax);
104+
}
105+
}
106+
107+
#endregion
108+
109+
#region Override Methods
110+
/// <inheritdoc/>
111+
protected internal override void OnLayout()
112+
{
113+
if (Series is FastScatterSeries series && series.ActualXAxis is ChartAxis xAxis && series.ActualYAxis is ChartAxis yAxis && XValues is not null && YValues is not null)
114+
{
115+
int dataCount = series.PointsCount;
116+
117+
var _xRange = xAxis.VisibleRange;
118+
var _yRange = yAxis.VisibleRange;
119+
120+
double xStart = _xRange.Start;
121+
double xEnd = _xRange.End;
122+
123+
double yStart = _yRange.Start;
124+
double yEnd = _yRange.End;
125+
126+
_preXValue = (float)XValues[0];
127+
_preYValue = (float)YValues[0];
128+
129+
_preXPos = series.TransformToVisibleX(_preXValue, _preYValue);
130+
_preYPos = series.TransformToVisibleY(_preXValue, _preYValue);
131+
132+
_fastScatterPlottingPoints.Clear();
133+
134+
if (!series.IsIndexed)
135+
{
136+
for (int i = 1; i < dataCount; i++)
137+
{
138+
double xValue = XValues[i];
139+
double yValue = YValues[i];
140+
141+
if (xEnd <= xValue && xStart >= XValues[i - 1])
142+
{
143+
float x = series.TransformToVisibleX(xValue, yValue);
144+
float y = series.TransformToVisibleY(xValue, yValue);
145+
_preXPos = series.TransformToVisibleX(XValues[i - 1], YValues[i - 1]);
146+
_preYPos = series.TransformToVisibleY(XValues[i - 1], YValues[i - 1]);
147+
148+
_fastScatterPlottingPoints.Add(new PointF(_preXPos, _preYPos));
149+
150+
_preXPos = x;
151+
_preYPos = y;
152+
_preXValue = (float)xValue;
153+
_preYValue = (float)yValue;
154+
}
155+
else if ((xValue <= xEnd && xValue >= xStart) || (yValue >= yStart && yValue <= yEnd))
156+
{
157+
float x = series.TransformToVisibleX(xValue, yValue);
158+
float y = series.TransformToVisibleY(xValue, yValue);
159+
160+
_fastScatterPlottingPoints.Add(new PointF(_preXPos, _preYPos));
161+
162+
_preXPos = x;
163+
_preYPos = y;
164+
_preXValue = (float)xValue;
165+
_preYValue = (float)yValue;
166+
}
167+
}
168+
}
169+
else
170+
{
171+
for (int i = 1; i < dataCount; i++)
172+
{
173+
double yValue = YValues[i];
174+
175+
if ((i <= xEnd + 1) && (i >= xStart - 1))
176+
{
177+
float x = series.TransformToVisibleX(i, yValue);
178+
float y = series.TransformToVisibleY(i, yValue);
179+
180+
_fastScatterPlottingPoints.Add(new PointF(_preXPos, _preYPos));
181+
182+
_preXPos = x;
183+
_preYPos = y;
184+
_preXValue = (float)i;
185+
_preYValue = (float)yValue;
186+
}
187+
}
188+
}
189+
190+
if (_fastScatterPlottingPoints.Count != dataCount)
191+
{
192+
float lastX = series.TransformToVisibleX(XValues[dataCount - 1], YValues[dataCount - 1]);
193+
float lastY = series.TransformToVisibleY(XValues[dataCount - 1], YValues[dataCount - 1]);
194+
_fastScatterPlottingPoints.Add(new PointF(lastX, lastY));
195+
}
196+
}
197+
}
198+
199+
/// <inheritdoc/>
200+
protected internal override void Draw(ICanvas canvas)
201+
{
202+
if (Series is FastScatterSeries series)
203+
{
204+
float scatterHeight = (float)series.PointHeight;
205+
float scatterWidth = (float)series.PointWidth;
206+
207+
float halfHeight = scatterHeight / 2;
208+
float halfWidth = scatterWidth / 2;
209+
210+
if (HasStroke)
211+
{
212+
canvas.StrokeColor = Stroke.ToColor();
213+
canvas.StrokeSize = (float)StrokeWidth;
214+
}
215+
216+
if (series.Type == ShapeType.Circle)
217+
{
218+
foreach (var point in _fastScatterPlottingPoints)
219+
{
220+
if (double.IsNaN(point.Y) || double.IsNaN(point.X))
221+
{
222+
continue;
223+
}
224+
225+
_actualRectF = new RectF((float)point.X - halfWidth, point.Y - halfHeight, scatterWidth, scatterHeight);
226+
227+
canvas.SetFillPaint(Fill, _actualRectF);
228+
canvas.FillEllipse(_actualRectF);
229+
230+
if (HasStroke)
231+
{
232+
canvas.DrawEllipse(_actualRectF);
233+
}
234+
}
235+
}
236+
else
237+
{
238+
foreach (var point in _fastScatterPlottingPoints)
239+
{
240+
_actualRectF = new RectF((float)point.X - halfWidth, point.Y - halfHeight, scatterWidth, scatterHeight);
241+
242+
canvas.SetFillPaint(Fill, _actualRectF);
243+
244+
canvas.DrawShape(_actualRectF, series.Type, HasStroke, false);
245+
}
246+
247+
}
248+
}
249+
}
250+
251+
#endregion
252+
#endregion
253+
}
254+
}

maui/src/Charts/Series/CartesianSeries.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -729,6 +729,8 @@ internal set
729729
}
730730
}
731731

732+
internal virtual bool IsFillEmptyPoint { get { return true; } }
733+
732734
#endregion
733735

734736
#region Constructor

0 commit comments

Comments
 (0)