diff --git a/RealTimeGraphX.UWP.Demo/RealTimeGraphX.UWP.Demo.csproj b/RealTimeGraphX.UWP.Demo/RealTimeGraphX.UWP.Demo.csproj
index 35a4a01..a51fdb6 100644
--- a/RealTimeGraphX.UWP.Demo/RealTimeGraphX.UWP.Demo.csproj
+++ b/RealTimeGraphX.UWP.Demo/RealTimeGraphX.UWP.Demo.csproj
@@ -11,7 +11,7 @@
RealTimeGraphX.UWP.Demo
en-US
UAP
- 10.0.17134.0
+ 10.0.17763.0
10.0.17134.0
14
512
diff --git a/RealTimeGraphX.UWP/RealTimeGraphX.UWP.csproj b/RealTimeGraphX.UWP/RealTimeGraphX.UWP.csproj
index 65d5daa..52802ad 100644
--- a/RealTimeGraphX.UWP/RealTimeGraphX.UWP.csproj
+++ b/RealTimeGraphX.UWP/RealTimeGraphX.UWP.csproj
@@ -11,7 +11,7 @@
RealTimeGraphX.UWP
en-US
UAP
- 10.0.17134.0
+ 10.0.17763.0
10.0.17134.0
14
512
diff --git a/RealTimeGraphX.WPF.Demo/MainWindow.xaml b/RealTimeGraphX.WPF.Demo/MainWindow.xaml
index b194b28..eb5849a 100644
--- a/RealTimeGraphX.WPF.Demo/MainWindow.xaml
+++ b/RealTimeGraphX.WPF.Demo/MainWindow.xaml
@@ -10,194 +10,209 @@
mc:Ignorable="d"
Title="RealtimeGraphX WPF Demo" Height="587" Width="1168.5" d:DataContext="{d:DesignData Type=local:MainWindowVM, IsDesignTimeCreatable=False}">
-
-
-
-
-
- DodgerBlue
- Red
- Green
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Duration
-
-
- Refresh Rate
-
-
- Minimum Y
-
-
- Maximum Y
-
-
- Auto Range (Y)
-
- Stroke
-
-
- Thickness
-
-
- Fill
-
-
- Display ToolTip
-
- Paused
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Duration
-
-
- Refresh Rate
-
-
- Minimum Y
-
-
- Maximum Y
-
-
- Auto Range (Y)
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+ DodgerBlue
+ Red
+ Green
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Duration
+
+
+ Refresh Rate
+
+
+ Minimum Y
+
+
+ Maximum Y
+
+
+ Auto Range (Y)
+
+ Stroke
+
+
+ Thickness
+
+
+ Fill
+
+
+ Display ToolTip
+
+ Paused
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Duration
+
+
+ Refresh Rate
+
+
+ Minimum Y
+
+
+ Maximum Y
+
+
+ Auto Range (Y)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Format:
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Format:
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/RealTimeGraphX.WPF.Demo/MainWindowVM.cs b/RealTimeGraphX.WPF.Demo/MainWindowVM.cs
index 5a8a373..7c73570 100644
--- a/RealTimeGraphX.WPF.Demo/MainWindowVM.cs
+++ b/RealTimeGraphX.WPF.Demo/MainWindowVM.cs
@@ -32,11 +32,12 @@ public MainWindowVM()
Controller.Range.MaximumX = TimeSpan.FromSeconds(10);
Controller.Range.AutoY = true;
Controller.Range.AutoYFallbackMode = GraphRangeAutoYFallBackMode.MinMax;
+ Controller.Range.XStartBehavior = XStartBehavior.slideInFromRight ;
Controller.DataSeriesCollection.Add(new WpfGraphDataSeries()
{
Name = "Series",
- Stroke = Colors.DodgerBlue,
+ Stroke = Colors.Red,
});
MultiController = new WpfGraphController();
@@ -44,6 +45,7 @@ public MainWindowVM()
MultiController.Range.MaximumY = 1080;
MultiController.Range.MaximumX = TimeSpan.FromSeconds(10);
MultiController.Range.AutoY = true;
+ MultiController.Range.XStartBehavior = XStartBehavior.fillFromLeft ;
MultiController.DataSeriesCollection.Add(new WpfGraphDataSeries()
{
diff --git a/RealTimeGraphX.WPF.Demo/Properties/AssemblyInfo.cs b/RealTimeGraphX.WPF.Demo/Properties/AssemblyInfo.cs
index 7e50401..9910ab1 100644
--- a/RealTimeGraphX.WPF.Demo/Properties/AssemblyInfo.cs
+++ b/RealTimeGraphX.WPF.Demo/Properties/AssemblyInfo.cs
@@ -51,5 +51,5 @@
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
-[assembly: AssemblyVersion("1.0.0.0")]
-[assembly: AssemblyFileVersion("1.0.0.0")]
+[assembly: AssemblyVersion("1.0.0.3")]
+[assembly: AssemblyFileVersion("1.0.0.3")]
diff --git a/RealTimeGraphX.WPF.Demo/RealTimeGraphX.WPF.Demo.csproj b/RealTimeGraphX.WPF.Demo/RealTimeGraphX.WPF.Demo.csproj
index 352ea02..684db45 100644
--- a/RealTimeGraphX.WPF.Demo/RealTimeGraphX.WPF.Demo.csproj
+++ b/RealTimeGraphX.WPF.Demo/RealTimeGraphX.WPF.Demo.csproj
@@ -58,7 +58,6 @@
-
MSBuild:Compile
Designer
@@ -71,10 +70,6 @@
MainWindow.xaml
Code
-
- MSBuild:Compile
- Designer
-
diff --git a/RealTimeGraphX.WPF.Demo/Themes/Generic.xaml b/RealTimeGraphX.WPF.Demo/Themes/Generic.xaml
deleted file mode 100644
index 1174e5b..0000000
--- a/RealTimeGraphX.WPF.Demo/Themes/Generic.xaml
+++ /dev/null
@@ -1,55 +0,0 @@
-
-
-
-
-
diff --git a/RealTimeGraphX.WPF.Demo/WpfGraphControl.cs b/RealTimeGraphX.WPF.Demo/WpfGraphControl.cs
deleted file mode 100644
index cbace56..0000000
--- a/RealTimeGraphX.WPF.Demo/WpfGraphControl.cs
+++ /dev/null
@@ -1,73 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-using System.Windows;
-using System.Windows.Controls;
-using System.Windows.Data;
-using System.Windows.Documents;
-using System.Windows.Input;
-using System.Windows.Media;
-using System.Windows.Media.Imaging;
-using System.Windows.Navigation;
-using System.Windows.Shapes;
-
-namespace RealTimeGraphX.WPF.Demo
-{
- public class WpfGraphControl : Control
- {
- ///
- /// Gets or sets the graph controller.
- ///
- public IGraphController Controller
- {
- get { return (IGraphController)GetValue(ControllerProperty); }
- set { SetValue(ControllerProperty, value); }
- }
- public static readonly DependencyProperty ControllerProperty =
- DependencyProperty.Register("Controller", typeof(IGraphController), typeof(WpfGraphControl), new PropertyMetadata(null));
-
- ///
- /// Gets or sets a value indicating whether to display a tool tip with the current cursor value.
- ///
- public bool DisplayToolTip
- {
- get { return (bool)GetValue(DisplayToolTipProperty); }
- set { SetValue(DisplayToolTipProperty, value); }
- }
- public static readonly DependencyProperty DisplayToolTipProperty =
- DependencyProperty.Register("DisplayToolTip", typeof(bool), typeof(WpfGraphControl), new PropertyMetadata(false));
-
- ///
- /// Gets or sets the string format for the X Axis.
- ///
- public String StringFormatX
- {
- get { return (String)GetValue(StringFormatXProperty); }
- set { SetValue(StringFormatXProperty, value); }
- }
- public static readonly DependencyProperty StringFormatXProperty =
- DependencyProperty.Register("StringFormatX", typeof(String), typeof(WpfGraphControl), new PropertyMetadata("0.0"));
-
- ///
- /// Gets or sets the string format for the Y Axis.
- ///
- public String StringFormatY
- {
- get { return (String)GetValue(StringFormatYProperty); }
- set { SetValue(StringFormatYProperty, value); }
- }
- public static readonly DependencyProperty StringFormatYProperty =
- DependencyProperty.Register("StringFormatY", typeof(String), typeof(WpfGraphControl), new PropertyMetadata("hh\\:mm\\:ss"));
-
-
- ///
- /// Initializes the class.
- ///
- static WpfGraphControl()
- {
- DefaultStyleKeyProperty.OverrideMetadata(typeof(WpfGraphControl), new FrameworkPropertyMetadata(typeof(WpfGraphControl)));
- }
- }
-}
diff --git a/RealTimeGraphX.WPF/Properties/AssemblyInfo.cs b/RealTimeGraphX.WPF/Properties/AssemblyInfo.cs
index a08459d..9b0cc8e 100644
--- a/RealTimeGraphX.WPF/Properties/AssemblyInfo.cs
+++ b/RealTimeGraphX.WPF/Properties/AssemblyInfo.cs
@@ -16,8 +16,8 @@
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
-// Setting ComVisible to false makes the types in this assembly not visible
-// to COM components. If you need to access a type in this assembly from
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
@@ -51,5 +51,5 @@
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
-[assembly: AssemblyVersion("1.0.0.0")]
-[assembly: AssemblyFileVersion("1.0.0.0")]
+[assembly: AssemblyVersion("1.0.0.3")]
+[assembly: AssemblyFileVersion("1.0.0.3")]
diff --git a/RealTimeGraphX.WPF/RealTimeGraphX.WPF.csproj b/RealTimeGraphX.WPF/RealTimeGraphX.WPF.csproj
index 6287cae..6127f07 100644
--- a/RealTimeGraphX.WPF/RealTimeGraphX.WPF.csproj
+++ b/RealTimeGraphX.WPF/RealTimeGraphX.WPF.csproj
@@ -57,6 +57,7 @@
+
diff --git a/RealTimeGraphX.WPF/Themes/Generic.xaml b/RealTimeGraphX.WPF/Themes/Generic.xaml
index a309fc0..3b8e94c 100644
--- a/RealTimeGraphX.WPF/Themes/Generic.xaml
+++ b/RealTimeGraphX.WPF/Themes/Generic.xaml
@@ -1,15 +1,35 @@
-
+
+
+
+
+
+
+
+
diff --git a/RealTimeGraphX.WPF/WpfGraphAxisControl.cs b/RealTimeGraphX.WPF/WpfGraphAxisControl.cs
index aa738b2..fe3f7a2 100644
--- a/RealTimeGraphX.WPF/WpfGraphAxisControl.cs
+++ b/RealTimeGraphX.WPF/WpfGraphAxisControl.cs
@@ -68,16 +68,18 @@ internal ObservableCollection Items
public static readonly DependencyProperty ItemsProperty =
DependencyProperty.Register("Items", typeof(ObservableCollection), typeof(WpfGraphAxisControl), new PropertyMetadata(null));
+ internal int Ticks => Divisions + 1 ;
+
///
- /// Gets or sets the number of ticks to display on the control.
+ /// Gets or sets the number of divisions to display on the control.
///
- public int Ticks
+ public int Divisions
{
- get { return (int)GetValue(TicksProperty); }
- set { SetValue(TicksProperty, value); }
+ get { return (int)GetValue(DivisionsProperty); }
+ set { SetValue(DivisionsProperty, value); }
}
- public static readonly DependencyProperty TicksProperty =
- DependencyProperty.Register("Ticks", typeof(int), typeof(WpfGraphAxisControl), new PropertyMetadata(9, (d, e) => (d as WpfGraphAxisControl).OnTicksChanged()));
+ public static readonly DependencyProperty DivisionsProperty =
+ DependencyProperty.Register("Divisions", typeof(int), typeof(WpfGraphAxisControl), new PropertyMetadata(8, (d, e) => (d as WpfGraphAxisControl).OnDivisionsChanged()));
///
/// Gets or sets the string format which is used to format the ticks value.
@@ -99,13 +101,13 @@ public override void OnApplyTemplate()
_items_control = GetTemplateChild("PART_ItemsControl") as ItemsControl;
- _items_control.Loaded += (x, e) =>
+ _items_control.Loaded += (x, e) =>
{
ItemsPresenter itemsPresenter = GetVisualChild(_items_control);
_axisPanel = VisualTreeHelper.GetChild(itemsPresenter, 0) as WpfGraphAxisPanel;
};
- OnTicksChanged();
+ OnDivisionsChanged();
}
private static T GetVisualChild(DependencyObject parent) where T : Visual
@@ -129,6 +131,7 @@ private static T GetVisualChild(DependencyObject parent) where T : Visual
return child;
}
+#if false
///
/// Called when the property has changed.
///
@@ -140,6 +143,19 @@ protected virtual void OnTicksChanged()
_axisPanel?.UpdatePanel();
}
+#endif
+
+ ///
+ /// Called when the property has changed.
+ ///
+ protected virtual void OnDivisionsChanged()
+ {
+ Items = new ObservableCollection(Enumerable.Range(0, Ticks).Select(x => new WpfGraphAxisTickData()));
+
+ Controller?.RequestVirtualRangeChange();
+
+ _axisPanel?.UpdatePanel();
+ }
protected override void OnControllerChanged(IGraphController oldController, IGraphController newController)
{
diff --git a/RealTimeGraphX.WPF/WpfGraphControl.cs b/RealTimeGraphX.WPF/WpfGraphControl.cs
new file mode 100644
index 0000000..83713df
--- /dev/null
+++ b/RealTimeGraphX.WPF/WpfGraphControl.cs
@@ -0,0 +1,172 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Data;
+using System.Windows.Documents;
+using System.Windows.Input;
+using System.Windows.Media;
+using System.Windows.Media.Imaging;
+using System.Windows.Navigation;
+using System.Windows.Shapes;
+
+namespace RealTimeGraphX.WPF
+{
+ public class WpfGraphControl : Control
+ {
+ ///
+ /// Gets or sets the graph controller.
+ ///
+ public IGraphController Controller
+ {
+ get { return (IGraphController)GetValue(ControllerProperty); }
+ set { SetValue(ControllerProperty, value); }
+ }
+ public static readonly DependencyProperty ControllerProperty =
+ DependencyProperty.Register("Controller", typeof(IGraphController), typeof(WpfGraphControl), new PropertyMetadata(null));
+
+ ///
+ /// Gets or sets a value indicating whether to display a tool tip with the current cursor value.
+ ///
+ public bool DisplayToolTip
+ {
+ get { return (bool)GetValue(DisplayToolTipProperty); }
+ set { SetValue(DisplayToolTipProperty, value); }
+ }
+ public static readonly DependencyProperty DisplayToolTipProperty =
+ DependencyProperty.Register("DisplayToolTip", typeof(bool), typeof(WpfGraphControl), new PropertyMetadata(false));
+
+ ///
+ /// Gets or sets the string format for the X Axis.
+ ///
+ public String StringFormatX
+ {
+ get { return (String)GetValue(StringFormatXProperty); }
+ set { SetValue(StringFormatXProperty, value); }
+ }
+ public static readonly DependencyProperty StringFormatXProperty =
+ DependencyProperty.Register("StringFormatX", typeof(String), typeof(WpfGraphControl), new PropertyMetadata("hh\\:mm\\:ss"));
+
+ ///
+ /// Gets or sets the string format for the Y Axis.
+ ///
+ public String StringFormatY
+ {
+ get { return (String)GetValue(StringFormatYProperty); }
+ set { SetValue(StringFormatYProperty, value); }
+ }
+ public static readonly DependencyProperty StringFormatYProperty =
+ DependencyProperty.Register("StringFormatY", typeof(String), typeof(WpfGraphControl), new PropertyMetadata("0.0"));
+
+
+ ///
+ /// Gets or sets the caption of the Y axis
+ ///
+ public String AxisCaptionY
+ {
+ get { return (String)GetValue(AxisCaptionYProperty); }
+ set { SetValue(AxisCaptionYProperty, value); }
+ }
+ public static readonly DependencyProperty AxisCaptionYProperty =
+ DependencyProperty.Register("AxisCaptionY", typeof(String), typeof(WpfGraphControl), new PropertyMetadata(null));
+
+ ///
+ /// Gets or sets the caption of the X axis
+ ///
+ public String AxisCaptionX
+ {
+ get { return (String)GetValue(AxisCaptionXProperty); }
+ set { SetValue(AxisCaptionXProperty, value); }
+ }
+ public static readonly DependencyProperty AxisCaptionXProperty =
+ DependencyProperty.Register("AxisCaptionX", typeof(String), typeof(WpfGraphControl), new PropertyMetadata(null));
+
+ ///
+ /// Gets or sets the brush used for the grid lines
+ ///
+ public Brush GridLinesBrush
+ {
+ get { return (Brush)GetValue(GridLinesBrushProperty); }
+ set { SetValue(GridLinesBrushProperty, value); }
+ }
+ public static readonly DependencyProperty GridLinesBrushProperty =
+ DependencyProperty.Register( "GridLinesBrush", typeof(Brush), typeof(WpfGraphControl), new PropertyMetadata((SolidColorBrush)new BrushConverter().ConvertFrom("#FF2E2E2E")) ) ;
+
+ ///
+ /// Gets or sets the corner radius for the border
+ ///
+ public CornerRadius CornerRadius
+ {
+ get { return (CornerRadius)GetValue(CornerRadiusProperty); }
+ set { SetValue(CornerRadiusProperty, value); }
+ }
+ public static readonly DependencyProperty CornerRadiusProperty =
+ DependencyProperty.Register( "CornerRadius", typeof(CornerRadius), typeof(WpfGraphControl), new PropertyMetadata(new CornerRadius(5)) ) ;
+
+ ///
+ /// Boolean property so show or hide the X-Axis
+ ///
+ public bool ShowAxisX
+ {
+ get { return (bool)GetValue(ShowAxisXProperty); }
+ set { SetValue(ShowAxisXProperty, value); }
+ }
+ public static readonly DependencyProperty ShowAxisXProperty =
+ DependencyProperty.Register("ShowAxisX", typeof(bool), typeof(WpfGraphControl), new PropertyMetadata(true));
+
+ ///
+ /// Boolean property so show or hide the Y-Axis
+ ///
+ public bool ShowAxisY
+ {
+ get { return (bool)GetValue(ShowAxisYProperty); }
+ set { SetValue(ShowAxisYProperty, value); }
+ }
+ public static readonly DependencyProperty ShowAxisYProperty =
+ DependencyProperty.Register("ShowAxisY", typeof(bool), typeof(WpfGraphControl), new PropertyMetadata(true));
+
+ ///
+ /// Width of the Y axis, default 70.
+ ///
+ public int WidthAxisY
+ {
+ get { return (int)GetValue(WidthAxisYProperty); }
+ set { SetValue(WidthAxisYProperty, value); }
+ }
+ public static readonly DependencyProperty WidthAxisYProperty =
+ DependencyProperty.Register("WidthAxisY", typeof(int), typeof(WpfGraphControl), new PropertyMetadata(70));
+
+ ///
+ /// Number of divisions in the Y axis
+ ///
+ public int DivisionsY
+ {
+ get { return (int)GetValue(DivisionsYProperty); }
+ set { SetValue(DivisionsYProperty, value); }
+ }
+ public static readonly DependencyProperty DivisionsYProperty =
+ DependencyProperty.Register("DivisionsY", typeof(int), typeof(WpfGraphControl), new PropertyMetadata(8));
+
+ ///
+ /// Number of divisions in the X axis
+ ///
+ public int DivisionsX
+ {
+ get { return (int)GetValue(DivisionsXProperty); }
+ set { SetValue(DivisionsXProperty, value); }
+ }
+ public static readonly DependencyProperty DivisionsXProperty =
+ DependencyProperty.Register("DivisionsX", typeof(int), typeof(WpfGraphControl), new PropertyMetadata(8));
+
+ ///
+ /// Initializes the class.
+ ///
+ static WpfGraphControl()
+ {
+ DefaultStyleKeyProperty.OverrideMetadata(typeof(WpfGraphControl), new FrameworkPropertyMetadata(typeof(WpfGraphControl)));
+ }
+ }
+}
diff --git a/RealTimeGraphX/DataPoints/DateTimeDataPoint.cs b/RealTimeGraphX/DataPoints/DateTimeDataPoint.cs
index 86f8099..eb39470 100644
--- a/RealTimeGraphX/DataPoints/DateTimeDataPoint.cs
+++ b/RealTimeGraphX/DataPoints/DateTimeDataPoint.cs
@@ -184,5 +184,21 @@ protected override DateTimeDataPoint OnGetDefaultMargins()
{
return new DateTimeDataPoint(new DateTime(1, 1, 1, 1, 1, 1));
}
+
+ ///
+ /// Expands the range by adding factor * ( max - min ) to the max and subtracting the same value from the min.
+ ///
+ ///
+ ///
+ ///
+ public override void ExpandRange ( ref GraphDataPoint min, ref GraphDataPoint max, double factor )
+ {
+ DateTime dMin = min as DateTimeDataPoint;
+ DateTime dMax = max as DateTimeDataPoint;
+ long delta = dMax.Ticks - dMin.Ticks ;
+ long margin = (long)(factor * delta) ;
+ max = new DateTimeDataPoint ( new DateTime ( dMax.Ticks + margin ) ) ;
+ min = new DateTimeDataPoint ( new DateTime ( dMin.Ticks - margin ) ) ;
+ }
}
}
diff --git a/RealTimeGraphX/DataPoints/DoubleDataPoint.cs b/RealTimeGraphX/DataPoints/DoubleDataPoint.cs
index 14f4a9b..0455a38 100644
--- a/RealTimeGraphX/DataPoints/DoubleDataPoint.cs
+++ b/RealTimeGraphX/DataPoints/DoubleDataPoint.cs
@@ -184,5 +184,21 @@ protected override DoubleDataPoint OnGetDefaultMargins()
{
return new DoubleDataPoint(0.5);
}
+
+ ///
+ /// Expands the range by adding factor * ( max - min ) to the max and subtracting the same value from the min.
+ ///
+ ///
+ ///
+ ///
+ public override void ExpandRange ( ref GraphDataPoint min, ref GraphDataPoint max, double factor )
+ {
+ double minimum = (double)min.GetValue();
+ double maximum = (double)max.GetValue();
+ double delta = maximum - minimum ;
+ double margin = factor * delta ;
+ max = new DoubleDataPoint ( (max as DoubleDataPoint).Value + margin ) ;
+ min = new DoubleDataPoint ( (min as DoubleDataPoint).Value - margin ) ;
+ }
}
}
diff --git a/RealTimeGraphX/DataPoints/FloatDataPoint.cs b/RealTimeGraphX/DataPoints/FloatDataPoint.cs
index 146a700..09923fe 100644
--- a/RealTimeGraphX/DataPoints/FloatDataPoint.cs
+++ b/RealTimeGraphX/DataPoints/FloatDataPoint.cs
@@ -184,5 +184,21 @@ protected override FloatDataPoint OnGetDefaultMargins()
{
return new FloatDataPoint(0.5f);
}
+
+ ///
+ /// Expands the range by adding factor * ( max - min ) to the max and subtracting the same value from the min.
+ ///
+ ///
+ ///
+ ///
+ public override void ExpandRange ( ref GraphDataPoint min, ref GraphDataPoint max, double factor )
+ {
+ float minimum = (float)min.GetValue();
+ float maximum = (float)max.GetValue();
+ float delta = maximum - minimum ;
+ float margin = (float)(factor * delta) ;
+ max = new FloatDataPoint ( (max as FloatDataPoint).Value + margin ) ;
+ min = new FloatDataPoint ( (min as FloatDataPoint).Value - margin ) ;
+ }
}
}
diff --git a/RealTimeGraphX/DataPoints/Int32DataPoint.cs b/RealTimeGraphX/DataPoints/Int32DataPoint.cs
index 9e9ad26..90f89b7 100644
--- a/RealTimeGraphX/DataPoints/Int32DataPoint.cs
+++ b/RealTimeGraphX/DataPoints/Int32DataPoint.cs
@@ -189,5 +189,21 @@ protected override Int32DataPoint OnGetDefaultMargins()
{
return new Int32DataPoint(1);
}
+
+ ///
+ /// Expands the range by adding factor * ( max - min ) to the max and subtracting the same value from the min.
+ ///
+ ///
+ ///
+ ///
+ public override void ExpandRange ( ref GraphDataPoint min, ref GraphDataPoint max, double factor )
+ {
+ int minimum = (int)min.GetValue();
+ int maximum = (int)max.GetValue();
+ int delta = maximum - minimum ;
+ int margin = (int)(factor * delta) ;
+ max = new Int32DataPoint ( (max as Int32DataPoint).Value + margin ) ;
+ min = new Int32DataPoint ( (min as Int32DataPoint).Value - margin ) ;
+ }
}
}
diff --git a/RealTimeGraphX/DataPoints/TimeSpanDataPoint.cs b/RealTimeGraphX/DataPoints/TimeSpanDataPoint.cs
index e026c7b..5a62547 100644
--- a/RealTimeGraphX/DataPoints/TimeSpanDataPoint.cs
+++ b/RealTimeGraphX/DataPoints/TimeSpanDataPoint.cs
@@ -186,5 +186,21 @@ protected override TimeSpanDataPoint OnGetDefaultMargins()
{
return new TimeSpanDataPoint(TimeSpan.FromSeconds(1));
}
+
+ ///
+ /// Expands the range by adding factor * ( max - min ) to the max and subtracting the same value from the min.
+ ///
+ ///
+ ///
+ ///
+ public override void ExpandRange ( ref GraphDataPoint min, ref GraphDataPoint max, double factor )
+ {
+ double minimum = ((TimeSpan)min.GetValue()).TotalMilliseconds;
+ double maximum = ((TimeSpan)max.GetValue()).TotalMilliseconds;
+ double delta = maximum - minimum ;
+ double margin = factor * delta ;
+ max = new TimeSpanDataPoint ( TimeSpan.FromMilliseconds ( maximum + margin ) ) ;
+ min = new TimeSpanDataPoint ( TimeSpan.FromMilliseconds ( minimum - margin ) ) ;
+ }
}
}
diff --git a/RealTimeGraphX/GraphController.cs b/RealTimeGraphX/GraphController.cs
index 19dfd4c..4a3e94e 100644
--- a/RealTimeGraphX/GraphController.cs
+++ b/RealTimeGraphX/GraphController.cs
@@ -1,237 +1,237 @@
-using System;
-using System.Collections.Generic;
-using System.Collections.ObjectModel;
-using System.Text;
-using System.Linq;
-using System.Threading;
-using System.Threading.Tasks;
-using System.Drawing;
-using RealTimeGraphX.EventArguments;
-using RealTimeGraphX.Renderers;
-using System.Diagnostics;
-
-namespace RealTimeGraphX
-{
- ///
- /// Represents an base class.
- ///
- /// The type of the data series.
- /// The type of the x data point.
- /// The type of the y data point.
- ///
- ///
- public abstract class GraphController : GraphObject, IGraphController
- where TXDataPoint : GraphDataPoint
- where TYDataPoint : GraphDataPoint
- where TDataSeries : IGraphDataSeries
- {
- private GraphDataQueue> _pending_series_collection;
- private Dictionary _to_render;
- private DateTime _last_render_time;
- private object _render_lock = new object();
- private Thread _render_thread;
- private bool _clear;
-
- #region Pending Series Class
-
- protected class PendingSeries
- {
- public TDataSeries Series { get; set; }
- public List XX { get; set; }
- public List YY { get; set; }
-
- public int NewItemsCount
- {
- get { return XX.Count - RenderedItems; }
- }
-
- public int RenderedItems { get; set; }
-
- public bool IsClearSeries { get; set; }
-
- public bool IsUpdateSeries { get; set; }
- }
-
- #endregion
-
- #region Events
-
- ///
- /// Occurs when the current effective minimum/maximum has changed.
- ///
- public event EventHandler EffectiveRangeChanged;
-
- ///
- /// Occurs when the current virtual (effective minimum/maximum after transformation) minimum/maximum has changed.
- ///
- public event EventHandler VirtualRangeChanged;
-
- #endregion
-
- #region Properties
-
- ///
- /// Gets or sets the controller refresh rate.
- /// Higher rate requires more CPU time.
- ///
- public TimeSpan RefreshRate { get; set; }
-
- ///
- /// Gets or sets a value indicating whether to pause rendering.
- ///
- public bool IsPaused { get; set; }
-
- ///
- /// Gets or sets a value indicating whether to disable the rendering of data.
- ///
- public bool DisableRendering { get; set; }
-
- ///
- /// Gets the data series collection.
- ///
- public ObservableCollection DataSeriesCollection { get; }
-
- private IGraphRenderer _renderer;
- ///
- /// Gets or sets the graph renderer.
- ///
- public IGraphRenderer Renderer
- {
- get
- {
- return _renderer;
- }
- set
- {
- _renderer = value; RaisePropertyChangedAuto();
- }
- }
-
- private IGraphSurface _surface;
- ///
- /// Gets or sets the rendering surface.
- ///
- public IGraphSurface Surface
- {
- get { return _surface; }
- set
- {
- var previous = _surface;
- _surface = value;
- RequestVirtualRangeChange();
- OnSurfaceChanged(previous, _surface);
- }
- }
-
- private GraphRange _range;
- ///
- /// Gets or sets the graph range (data point boundaries).
- ///
- public GraphRange Range
- {
- get
- {
- return _range;
- }
- set
- {
- _range = value; RaisePropertyChangedAuto();
- }
- }
-
- ///
- /// Gets the current effective x-axis minimum.
- ///
- public GraphDataPoint EffectiveMinimumX { get; private set; }
-
- ///
- /// Gets the current effective x-axis maximum.
- ///
- public GraphDataPoint EffectiveMaximumX { get; private set; }
-
- ///
- /// Gets the current effective y-axis minimum.
- ///
- public GraphDataPoint EffectiveMinimumY { get; private set; }
-
- ///
- /// Gets the current effective y-axis maximum.
- ///
- public GraphDataPoint EffectiveMaximumY { get; private set; }
-
- ///
- /// Gets the current virtual (effective minimum/maximum after transformation) x-axis minimum.
- ///
- public GraphDataPoint VirtualMinimumX { get; private set; }
-
- ///
- /// Gets the current virtual (effective minimum/maximum after transformation) x-axis maximum.
- ///
- public GraphDataPoint VirtualMaximumX { get; private set; }
-
- ///
- /// Gets the current virtual (effective minimum/maximum after transformation) y-axis minimum.
- ///
- public GraphDataPoint VirtualMinimumY { get; private set; }
-
- ///
- /// Gets the current virtual (effective minimum/maximum after transformation) y-axis maximum.
- ///
- public GraphDataPoint VirtualMaximumY { get; private set; }
-
- #endregion
-
- #region Commands
-
- ///
- /// Gets the clear command.
- ///
- public GraphCommand ClearCommand { get; private set; }
-
- #endregion
-
- #region Constructors
-
- ///
- /// Initializes a new instance of the class.
- ///
- public GraphController()
- {
- Renderer = new ScrollingLineRenderer();
-
- DataSeriesCollection = new ObservableCollection();
- Range = new GraphRange();
-
- _last_render_time = DateTime.Now;
- _to_render = new Dictionary();
- _pending_series_collection = new GraphDataQueue>();
- RefreshRate = TimeSpan.FromMilliseconds(50);
-
- ClearCommand = new GraphCommand(Clear);
-
- _render_thread = new Thread(RenderThreadMethod);
- _render_thread.IsBackground = true;
- _render_thread.Priority = ThreadPriority.Highest;
- _render_thread.Start();
- }
-
- #endregion
-
- #region Render Thread
-
- ///
- /// The rendering thread method.
- ///
- private void RenderThreadMethod()
- {
- while (true)
- {
- if ((!IsPaused && !DisableRendering) || _clear)
- {
- try
- {
- List> pending_lists = new List>();
-
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Text;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using System.Drawing;
+using RealTimeGraphX.EventArguments;
+using RealTimeGraphX.Renderers;
+using System.Diagnostics;
+
+namespace RealTimeGraphX
+{
+ ///
+ /// Represents an base class.
+ ///
+ /// The type of the data series.
+ /// The type of the x data point.
+ /// The type of the y data point.
+ ///
+ ///
+ public abstract class GraphController : GraphObject, IGraphController
+ where TXDataPoint : GraphDataPoint
+ where TYDataPoint : GraphDataPoint
+ where TDataSeries : IGraphDataSeries
+ {
+ private GraphDataQueue> _pending_series_collection;
+ private Dictionary _to_render;
+ private DateTime _last_render_time;
+ private object _render_lock = new object();
+ private Thread _render_thread;
+ private bool _clear;
+
+ #region Pending Series Class
+
+ protected class PendingSeries
+ {
+ public TDataSeries Series { get; set; }
+ public List XX { get; set; }
+ public List YY { get; set; }
+
+ public int NewItemsCount
+ {
+ get { return XX.Count - RenderedItems; }
+ }
+
+ public int RenderedItems { get; set; }
+
+ public bool IsClearSeries { get; set; }
+
+ public bool IsUpdateSeries { get; set; }
+ }
+
+ #endregion
+
+ #region Events
+
+ ///
+ /// Occurs when the current effective minimum/maximum has changed.
+ ///
+ public event EventHandler EffectiveRangeChanged;
+
+ ///
+ /// Occurs when the current virtual (effective minimum/maximum after transformation) minimum/maximum has changed.
+ ///
+ public event EventHandler VirtualRangeChanged;
+
+ #endregion
+
+ #region Properties
+
+ ///
+ /// Gets or sets the controller refresh rate.
+ /// Higher rate requires more CPU time.
+ ///
+ public TimeSpan RefreshRate { get; set; }
+
+ ///
+ /// Gets or sets a value indicating whether to pause rendering.
+ ///
+ public bool IsPaused { get; set; }
+
+ ///
+ /// Gets or sets a value indicating whether to disable the rendering of data.
+ ///
+ public bool DisableRendering { get; set; }
+
+ ///
+ /// Gets the data series collection.
+ ///
+ public ObservableCollection DataSeriesCollection { get; }
+
+ private IGraphRenderer _renderer;
+ ///
+ /// Gets or sets the graph renderer.
+ ///
+ public IGraphRenderer Renderer
+ {
+ get
+ {
+ return _renderer;
+ }
+ set
+ {
+ _renderer = value; RaisePropertyChangedAuto();
+ }
+ }
+
+ private IGraphSurface _surface;
+ ///
+ /// Gets or sets the rendering surface.
+ ///
+ public IGraphSurface Surface
+ {
+ get { return _surface; }
+ set
+ {
+ var previous = _surface;
+ _surface = value;
+ RequestVirtualRangeChange();
+ OnSurfaceChanged(previous, _surface);
+ }
+ }
+
+ private GraphRange _range;
+ ///
+ /// Gets or sets the graph range (data point boundaries).
+ ///
+ public GraphRange Range
+ {
+ get
+ {
+ return _range;
+ }
+ set
+ {
+ _range = value; RaisePropertyChangedAuto();
+ }
+ }
+
+ ///
+ /// Gets the current effective x-axis minimum.
+ ///
+ public GraphDataPoint EffectiveMinimumX { get; private set; }
+
+ ///
+ /// Gets the current effective x-axis maximum.
+ ///
+ public GraphDataPoint EffectiveMaximumX { get; private set; }
+
+ ///
+ /// Gets the current effective y-axis minimum.
+ ///
+ public GraphDataPoint EffectiveMinimumY { get; private set; }
+
+ ///
+ /// Gets the current effective y-axis maximum.
+ ///
+ public GraphDataPoint EffectiveMaximumY { get; private set; }
+
+ ///
+ /// Gets the current virtual (effective minimum/maximum after transformation) x-axis minimum.
+ ///
+ public GraphDataPoint VirtualMinimumX { get; private set; }
+
+ ///
+ /// Gets the current virtual (effective minimum/maximum after transformation) x-axis maximum.
+ ///
+ public GraphDataPoint VirtualMaximumX { get; private set; }
+
+ ///
+ /// Gets the current virtual (effective minimum/maximum after transformation) y-axis minimum.
+ ///
+ public GraphDataPoint VirtualMinimumY { get; private set; }
+
+ ///
+ /// Gets the current virtual (effective minimum/maximum after transformation) y-axis maximum.
+ ///
+ public GraphDataPoint VirtualMaximumY { get; private set; }
+
+ #endregion
+
+ #region Commands
+
+ ///
+ /// Gets the clear command.
+ ///
+ public GraphCommand ClearCommand { get; private set; }
+
+ #endregion
+
+ #region Constructors
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public GraphController()
+ {
+ Renderer = new ScrollingLineRenderer();
+
+ DataSeriesCollection = new ObservableCollection();
+ Range = new GraphRange();
+
+ _last_render_time = DateTime.Now;
+ _to_render = new Dictionary();
+ _pending_series_collection = new GraphDataQueue>();
+ RefreshRate = TimeSpan.FromMilliseconds(50);
+
+ ClearCommand = new GraphCommand(Clear);
+
+ _render_thread = new Thread(RenderThreadMethod);
+ _render_thread.IsBackground = true;
+ _render_thread.Priority = ThreadPriority.Highest;
+ _render_thread.Start();
+ }
+
+ #endregion
+
+ #region Render Thread
+
+ ///
+ /// The rendering thread method.
+ ///
+ private void RenderThreadMethod()
+ {
+ while (true)
+ {
+ if ((!IsPaused && !DisableRendering) || _clear)
+ {
+ try
+ {
+ List> pending_lists = new List>();
+
var pending_list_first = _pending_series_collection.BlockDequeue();
if (_clear)
@@ -246,455 +246,476 @@ private void RenderThreadMethod()
_pending_series_collection.BlockDequeue();
}
continue;
- }
-
- pending_lists.Add(pending_list_first);
-
- while (_pending_series_collection.Count > 0)
- {
- var pending_list = _pending_series_collection.BlockDequeue();
- pending_lists.Add(pending_list);
- }
-
- foreach (var pending_list in pending_lists)
- {
- foreach (var pending_series in pending_list)
- {
- if (!pending_series.IsUpdateSeries)
- {
- if (_to_render.ContainsKey(pending_series.Series))
- {
- var s = _to_render[pending_series.Series];
- s.XX.AddRange(pending_series.XX);
- s.YY.AddRange(pending_series.YY);
- }
- else
- {
- _to_render[pending_series.Series] = pending_series;
- }
- }
- }
- }
-
+ }
+
+ pending_lists.Add(pending_list_first);
+
+ while (_pending_series_collection.Count > 0)
+ {
+ var pending_list = _pending_series_collection.BlockDequeue();
+ pending_lists.Add(pending_list);
+ }
+
+ foreach (var pending_list in pending_lists)
+ {
+ foreach (var pending_series in pending_list)
+ {
+ if (!pending_series.IsUpdateSeries)
+ {
+ if (_to_render.ContainsKey(pending_series.Series))
+ {
+ var s = _to_render[pending_series.Series];
+ s.XX.AddRange(pending_series.XX);
+ s.YY.AddRange(pending_series.YY);
+ }
+ else
+ {
+ _to_render[pending_series.Series] = pending_series;
+ }
+ }
+ }
+ }
+
if (Surface != null)
{
Render();
- }
- }
- catch (Exception ex)
- {
- Debug.WriteLine($"Error in RealTimeGraphX:\n{ex.ToString()}");
- }
- }
- else if (IsPaused && !DisableRendering)
+ }
+ }
+ catch (Exception ex)
+ {
+ Debug.WriteLine($"Error in RealTimeGraphX:\n{ex.ToString()}");
+ }
+ }
+ else if (IsPaused && !DisableRendering)
{
if (Surface != null)
{
Render();
- }
- Thread.Sleep(RefreshRate);
- }
- else
- {
- Thread.Sleep(RefreshRate);
- }
- }
- }
-
- private void Render()
- {
- if (_to_render.Count > 0)
- {
- GraphDataPoint min_x = _range.MaximumX - _range.MaximumX;
- GraphDataPoint max_x = _range.MaximumX;
- GraphDataPoint min_y = _range.MinimumY;
- GraphDataPoint max_y = _range.MaximumY;
-
- if (_to_render.Count > 0 && _to_render.First().Value.XX.Count > 0)
- {
- min_x = _to_render.First().Value.XX.First();
- max_x = _to_render.First().Value.XX.Last();
- }
- else
- {
- return;
- }
-
- if (_range.AutoY)
- {
- min_y = _to_render.Select(x => x.Value).SelectMany(x => x.YY).Min();
- max_y = _to_render.Select(x => x.Value).SelectMany(x => x.YY).Max();
- }
-
- if (min_y == max_y)
- {
- if (_range.AutoYFallbackMode == GraphRangeAutoYFallBackMode.MinMax)
- {
- min_y = _range.MinimumY;
- max_y = _range.MaximumY;
- }
- else if (_range.AutoYFallbackMode == GraphRangeAutoYFallBackMode.Margins)
- {
- min_y -= _range.AutoYFallbackMargins;
- max_y += _range.AutoYFallbackMargins;
- }
- }
-
- EffectiveMinimumX = min_x;
- EffectiveMaximumX = max_x;
- EffectiveMinimumY = min_y;
- EffectiveMaximumY = max_y;
-
- VirtualMinimumX = EffectiveMinimumX;
- VirtualMaximumX = EffectiveMaximumX;
- VirtualMinimumY = EffectiveMinimumY;
- VirtualMaximumY = EffectiveMaximumY;
-
- _last_render_time = DateTime.Now;
-
- if (Surface != null)
- {
- var surface_size = Surface.GetSize();
- var zoom_rect = Surface.GetZoomRect();
-
- if (surface_size.Width > 0 && surface_size.Height > 0)
- {
- Surface.BeginDraw();
-
- if (zoom_rect.Width > 0 && zoom_rect.Height > 0)
- {
- var zoom_rect_top_percentage = zoom_rect.Top / surface_size.Height;
- var zoom_rect_bottom_percentage = zoom_rect.Bottom / surface_size.Height;
- var zoom_rect_left_percentage = zoom_rect.Left / surface_size.Width;
- var zoom_rect_right_percentage = zoom_rect.Right / surface_size.Width;
-
- VirtualMinimumY = EffectiveMaximumY - GraphDataPointHelper.ComputeAbsolutePosition(EffectiveMinimumY, EffectiveMaximumY, zoom_rect_bottom_percentage);
- VirtualMaximumY = EffectiveMaximumY - GraphDataPointHelper.ComputeAbsolutePosition(EffectiveMinimumY, EffectiveMaximumY, zoom_rect_top_percentage);
-
- VirtualMinimumX = GraphDataPointHelper.ComputeAbsolutePosition(EffectiveMinimumX, EffectiveMaximumX, zoom_rect_left_percentage);
- VirtualMaximumX = GraphDataPointHelper.ComputeAbsolutePosition(EffectiveMinimumX, EffectiveMaximumX, zoom_rect_right_percentage);
-
- GraphTransform transform = new GraphTransform();
- var scale_x = (float)(surface_size.Width / zoom_rect.Width);
- var scale_y = (float)(surface_size.Height / zoom_rect.Height);
- var translate_x = (float)-zoom_rect.Left * scale_x;
- var translate_y = (float)-zoom_rect.Top * scale_y;
-
- transform = new GraphTransform();
- transform.TranslateX = translate_x;
- transform.TranslateY = translate_y;
- transform.ScaleX = scale_x;
- transform.ScaleY = scale_y;
-
- Surface.SetTransform(transform);
- }
-
- List>> to_draw = new List>>();
-
- var to_render = _to_render.Select(x => x.Value).ToList();
-
- foreach (var item in to_render)
- {
- if (item.YY.Count > 0)
- {
- item.Series.CurrentValue = item.YY.Last().GetValue();
- }
-
- var points = Renderer.Render(Surface, item.Series, _range, item.XX, item.YY, min_x, max_x, min_y, max_y);
- to_draw.Add(new Tuple>(item.Series, points));
- }
-
- for (int i = 0; i < to_draw.Count; i++)
- {
- if (to_draw[i].Item2.Count() > 2)
- {
- if (to_draw[i].Item1.IsVisible)
- {
- Renderer.Draw(Surface, to_draw[i].Item1, to_draw[i].Item2, i, to_draw.Count);
- }
- }
- }
-
- Surface?.EndDraw();
- }
- }
-
- OnEffectiveRangeChanged(EffectiveMinimumX, EffectiveMaximumX, EffectiveMinimumY, EffectiveMaximumY);
- OnVirtualRangeChanged(VirtualMinimumX, VirtualMaximumX, VirtualMinimumY, VirtualMaximumY);
- }
- }
-
- #endregion
-
- #region Protected Methods
-
- ///
- /// Called when the surface has changed.
- ///
- /// The previous.
- /// The surface.
- protected virtual void OnSurfaceChanged(IGraphSurface previous, IGraphSurface surface)
- {
- if (previous != null)
- {
- previous.SurfaceSizeChanged -= Surface_SurfaceSizeChanged;
- previous.ZoomRectChanged -= Surface_ZoomRectChanged;
- }
-
- if (surface != null)
- {
- surface.SurfaceSizeChanged += Surface_SurfaceSizeChanged;
- surface.ZoomRectChanged += Surface_ZoomRectChanged;
- }
- }
-
- ///
- /// Raises the event.
- ///
- /// The minimum x.
- /// The maximum x.
- /// The minimum y.
- /// The maximum y.
- protected virtual void OnEffectiveRangeChanged(GraphDataPoint minimumX, GraphDataPoint maximumX, GraphDataPoint minimumY, GraphDataPoint maximumY)
- {
- EffectiveRangeChanged?.Invoke(this, new RangeChangedEventArgs(minimumX, maximumX, minimumY, maximumY));
- }
-
- ///
- /// Raises the event.
- ///
- /// The minimum x.
- /// The maximum x.
- /// The minimum y.
- /// The maximum y.
- protected virtual void OnVirtualRangeChanged(GraphDataPoint minimumX, GraphDataPoint maximumX, GraphDataPoint minimumY, GraphDataPoint maximumY)
- {
- var range = new RangeChangedEventArgs(minimumX, maximumX, minimumY, maximumY);
- VirtualRangeChanged?.Invoke(this, range);
- }
-
- ///
- /// Converts the specified relative x position to graph absolute position.
- ///
- /// The relative x position.
- ///
- protected virtual float ConvertXValueToRendererValue(double x)
- {
- return (float)(x * Surface.GetSize().Width / 100);
- }
-
- ///
- /// Converts the specified relative y position to graph absolute position.
- ///
- /// The relative y position.
- ///
- protected virtual float ConvertYValueToRendererValue(double y)
- {
- return (float)(Surface.GetSize().Height - (y * Surface.GetSize().Height / 100));
- }
-
- #endregion
-
- #region Surface Event Handlers
-
- ///
- /// Handles the ZoomRectChanged event of the Surface control.
- ///
- /// The source of the event.
- /// The instance containing the event data.
- private void Surface_ZoomRectChanged(object sender, EventArgs e)
- {
- if (!_pending_series_collection.ToList().SelectMany(x => x).ToList().Exists(x => x.IsUpdateSeries))
- {
- List updateSeries = new List();
-
- foreach (var pending_Series in _to_render)
- {
- updateSeries.Add(new PendingSeries()
- {
- IsUpdateSeries = true,
- Series = pending_Series.Value.Series,
- XX = new List(),
- YY = new List(),
- });
- }
-
- _pending_series_collection.BlockEnqueue(updateSeries);
- }
- }
-
- ///
- /// Handles the SurfaceSizeChanged event of the Surface control.
- ///
- /// The source of the event.
- /// The instance containing the event data.
- private void Surface_SurfaceSizeChanged(object sender, EventArgs e)
- {
- if (!_pending_series_collection.ToList().SelectMany(x => x).ToList().Exists(x => x.IsUpdateSeries))
- {
- List updateSeries = new List();
-
- foreach (var pending_Series in _to_render)
- {
- updateSeries.Add(new PendingSeries()
- {
- IsUpdateSeries = true,
- Series = pending_Series.Value.Series,
- XX = new List(),
- YY = new List(),
- });
- }
-
- _pending_series_collection.BlockEnqueue(updateSeries);
- }
- }
-
- #endregion
-
- #region Public Methods
-
- ///
- /// Submits the specified x and y data points.
- /// If the controller has more than one data series the data points will be duplicated.
- ///
- /// X data point.
- /// Y data point.
- public void PushData(TXDataPoint x, TYDataPoint y)
- {
- if (DataSeriesCollection.Count == 0) return;
-
- List> xxxx = new List>();
- List> yyyy = new List>();
-
- foreach (var series in DataSeriesCollection.ToList())
- {
- xxxx.Add(new List() { x });
- yyyy.Add(new List() { y });
- }
-
- PushData(xxxx, yyyy);
- }
-
- ///
- /// Submits the specified collections of x and y data points.
- /// If the controller has more than one data series the data points will be distributed evenly.
- ///
- /// X data point collection.
- /// Y data point collection.
- public void PushData(IEnumerable xx, IEnumerable yy)
- {
- if (DataSeriesCollection.Count == 0) return;
-
- var xList = xx.ToList();
- var yList = yy.ToList();
-
- List> xxxx = new List>();
- List> yyyy = new List>();
-
- foreach (var series in DataSeriesCollection.ToList())
- {
- xxxx.Add(new List());
- yyyy.Add(new List());
- }
-
- int counter = 0;
-
- for (int i = 0; i < xList.Count; i++)
- {
- xxxx[counter].Add(xList[i]);
- yyyy[counter].Add(yList[i]);
-
- counter++;
-
- if (counter >= xxxx.Count)
- {
- counter = 0;
- }
- }
-
- PushData(xxxx, yyyy);
- }
-
- ///
- /// Submits a matrix of x and y data points. Meaning each data series should process a single collection of x/y data points.
- ///
- /// X matrix.
- /// Y matrix.
- public void PushData(IEnumerable> xxxx, IEnumerable> yyyy)
- {
- if (DataSeriesCollection.Count == 0) return;
-
- IEnumerable> xxxxI = xxxx.Select(x => x.ToList()).ToList();
- IEnumerable> yyyyI = yyyy.Select(x => x.ToList()).ToList();
-
- List> xxxxList = xxxxI.Select(x => x.ToList()).ToList();
- List> yyyyList = yyyyI.Select(x => x.ToList()).ToList();
-
- int first_count_x = xxxxList[0].Count;
- int first_count_y = yyyyList[0].Count;
-
-
- bool is_data_valid = true;
-
- for (int i = 0; i < xxxxList.Count; i++)
- {
- if (xxxxList[0].Count != first_count_x)
- {
- is_data_valid = false;
- break;
- }
-
- if (xxxxList[0].Count != yyyyList[0].Count)
- {
- is_data_valid = false;
- break;
- }
- }
-
- if (!is_data_valid)
- {
- throw new ArgumentOutOfRangeException("When pushing data to a multi series renderer, each series must contain the same amount of data.");
- }
-
- var list = DataSeriesCollection.ToList();
-
- var pending_list = new List();
-
- for (int i = 0; i < list.Count; i++)
- {
- pending_list.Add(new PendingSeries()
- {
- Series = list[i],
- XX = xxxxList[i].ToList(),
- YY = yyyyList[i].ToList(),
- });
- }
-
- _pending_series_collection.BlockEnqueue(pending_list);
- }
-
- ///
- /// Clears all data points from this controller.
- ///
- public void Clear()
- {
+ }
+ Thread.Sleep(RefreshRate);
+ }
+ else
+ {
+ Thread.Sleep(RefreshRate);
+ }
+ }
+ }
+
+ private void Render()
+ {
+ if (_to_render.Count > 0)
+ {
+ GraphDataPoint min_x = _range.MaximumX - _range.MaximumX;
+ GraphDataPoint max_x = _range.MaximumX;
+ GraphDataPoint min_y = _range.MinimumY;
+ GraphDataPoint max_y = _range.MaximumY;
+
+ if (_to_render.Count > 0 && _to_render.First().Value.XX.Count > 0)
+ {
+ min_x = _to_render.First().Value.XX.First();
+ max_x = _to_render.First().Value.XX.Last();
+
+ if ( ( max_x - min_x ) < _range.MaximumX )
+ {
+ switch ( _range.XStartBehavior )
+ {
+ case XStartBehavior.stretchData:
+ // Original behavior. No action necessary.
+ break ;
+ case XStartBehavior.slideInFromRight:
+ min_x = max_x - _range.MaximumX ;
+ break ;
+ case XStartBehavior.fillFromLeft:
+ max_x = min_x + _range.MaximumX ;
+ break ;
+ }
+ }
+ }
+ else
+ {
+ return;
+ }
+
+ if (_range.AutoY)
+ {
+ min_y = _to_render.Select(x => x.Value).SelectMany(x => x.YY).Min();
+ max_y = _to_render.Select(x => x.Value).SelectMany(x => x.YY).Max();
+
+ // Note: The "this" object to which this method is applied is not used,
+ // making it like a static method (but abstract). I'm not really happy
+ // with this structure.
+ min_y.ExpandRange ( ref min_y, ref max_y, 0.05 ) ;
+ }
+
+ if (min_y == max_y)
+ {
+ if (_range.AutoYFallbackMode == GraphRangeAutoYFallBackMode.MinMax)
+ {
+ min_y = _range.MinimumY;
+ max_y = _range.MaximumY;
+ }
+ else if (_range.AutoYFallbackMode == GraphRangeAutoYFallBackMode.Margins)
+ {
+ min_y -= _range.AutoYFallbackMargins;
+ max_y += _range.AutoYFallbackMargins;
+ }
+ }
+
+ EffectiveMinimumX = min_x;
+ EffectiveMaximumX = max_x;
+ EffectiveMinimumY = min_y;
+ EffectiveMaximumY = max_y;
+
+ VirtualMinimumX = EffectiveMinimumX;
+ VirtualMaximumX = EffectiveMaximumX;
+ VirtualMinimumY = EffectiveMinimumY;
+ VirtualMaximumY = EffectiveMaximumY;
+
+ _last_render_time = DateTime.Now;
+
+ if (Surface != null)
+ {
+ var surface_size = Surface.GetSize();
+ var zoom_rect = Surface.GetZoomRect();
+
+ if (surface_size.Width > 0 && surface_size.Height > 0)
+ {
+ Surface.BeginDraw();
+
+ if (zoom_rect.Width > 0 && zoom_rect.Height > 0)
+ {
+ var zoom_rect_top_percentage = zoom_rect.Top / surface_size.Height;
+ var zoom_rect_bottom_percentage = zoom_rect.Bottom / surface_size.Height;
+ var zoom_rect_left_percentage = zoom_rect.Left / surface_size.Width;
+ var zoom_rect_right_percentage = zoom_rect.Right / surface_size.Width;
+
+ VirtualMinimumY = EffectiveMaximumY - GraphDataPointHelper.ComputeAbsolutePosition(EffectiveMinimumY, EffectiveMaximumY, zoom_rect_bottom_percentage);
+ VirtualMaximumY = EffectiveMaximumY - GraphDataPointHelper.ComputeAbsolutePosition(EffectiveMinimumY, EffectiveMaximumY, zoom_rect_top_percentage);
+
+ VirtualMinimumX = GraphDataPointHelper.ComputeAbsolutePosition(EffectiveMinimumX, EffectiveMaximumX, zoom_rect_left_percentage);
+ VirtualMaximumX = GraphDataPointHelper.ComputeAbsolutePosition(EffectiveMinimumX, EffectiveMaximumX, zoom_rect_right_percentage);
+
+ GraphTransform transform = new GraphTransform();
+ var scale_x = (float)(surface_size.Width / zoom_rect.Width);
+ var scale_y = (float)(surface_size.Height / zoom_rect.Height);
+ var translate_x = (float)-zoom_rect.Left * scale_x;
+ var translate_y = (float)-zoom_rect.Top * scale_y;
+
+ transform = new GraphTransform();
+ transform.TranslateX = translate_x;
+ transform.TranslateY = translate_y;
+ transform.ScaleX = scale_x;
+ transform.ScaleY = scale_y;
+
+ Surface.SetTransform(transform);
+ }
+
+ List>> to_draw = new List>>();
+
+ var to_render = _to_render.Select(x => x.Value).ToList();
+
+ foreach (var item in to_render)
+ {
+ if (item.YY.Count > 0)
+ {
+ item.Series.CurrentValue = item.YY.Last().GetValue();
+ }
+
+ var points = Renderer.Render(Surface, item.Series, _range, item.XX, item.YY, min_x, max_x, min_y, max_y);
+ to_draw.Add(new Tuple>(item.Series, points));
+ }
+
+ for (int i = 0; i < to_draw.Count; i++)
+ {
+ if (to_draw[i].Item2.Count() > 2)
+ {
+ if (to_draw[i].Item1.IsVisible)
+ {
+ Renderer.Draw(Surface, to_draw[i].Item1, to_draw[i].Item2, i, to_draw.Count);
+ }
+ }
+ }
+
+ Surface?.EndDraw();
+ }
+ }
+
+ OnEffectiveRangeChanged(EffectiveMinimumX, EffectiveMaximumX, EffectiveMinimumY, EffectiveMaximumY);
+ OnVirtualRangeChanged(VirtualMinimumX, VirtualMaximumX, VirtualMinimumY, VirtualMaximumY);
+ }
+ }
+
+ #endregion
+
+ #region Protected Methods
+
+ ///
+ /// Called when the surface has changed.
+ ///
+ /// The previous.
+ /// The surface.
+ protected virtual void OnSurfaceChanged(IGraphSurface previous, IGraphSurface surface)
+ {
+ if (previous != null)
+ {
+ previous.SurfaceSizeChanged -= Surface_SurfaceSizeChanged;
+ previous.ZoomRectChanged -= Surface_ZoomRectChanged;
+ }
+
+ if (surface != null)
+ {
+ surface.SurfaceSizeChanged += Surface_SurfaceSizeChanged;
+ surface.ZoomRectChanged += Surface_ZoomRectChanged;
+ }
+ }
+
+ ///
+ /// Raises the event.
+ ///
+ /// The minimum x.
+ /// The maximum x.
+ /// The minimum y.
+ /// The maximum y.
+ protected virtual void OnEffectiveRangeChanged(GraphDataPoint minimumX, GraphDataPoint maximumX, GraphDataPoint minimumY, GraphDataPoint maximumY)
+ {
+ EffectiveRangeChanged?.Invoke(this, new RangeChangedEventArgs(minimumX, maximumX, minimumY, maximumY));
+ }
+
+ ///
+ /// Raises the event.
+ ///
+ /// The minimum x.
+ /// The maximum x.
+ /// The minimum y.
+ /// The maximum y.
+ protected virtual void OnVirtualRangeChanged(GraphDataPoint minimumX, GraphDataPoint maximumX, GraphDataPoint minimumY, GraphDataPoint maximumY)
+ {
+ var range = new RangeChangedEventArgs(minimumX, maximumX, minimumY, maximumY);
+ VirtualRangeChanged?.Invoke(this, range);
+ }
+
+ ///
+ /// Converts the specified relative x position to graph absolute position.
+ ///
+ /// The relative x position.
+ ///
+ protected virtual float ConvertXValueToRendererValue(double x)
+ {
+ return (float)(x * Surface.GetSize().Width / 100);
+ }
+
+ ///
+ /// Converts the specified relative y position to graph absolute position.
+ ///
+ /// The relative y position.
+ ///
+ protected virtual float ConvertYValueToRendererValue(double y)
+ {
+ return (float)(Surface.GetSize().Height - (y * Surface.GetSize().Height / 100));
+ }
+
+ #endregion
+
+ #region Surface Event Handlers
+
+ ///
+ /// Handles the ZoomRectChanged event of the Surface control.
+ ///
+ /// The source of the event.
+ /// The instance containing the event data.
+ private void Surface_ZoomRectChanged(object sender, EventArgs e)
+ {
+ if (!_pending_series_collection.ToList().SelectMany(x => x).ToList().Exists(x => x.IsUpdateSeries))
+ {
+ List updateSeries = new List();
+
+ foreach (var pending_Series in _to_render)
+ {
+ updateSeries.Add(new PendingSeries()
+ {
+ IsUpdateSeries = true,
+ Series = pending_Series.Value.Series,
+ XX = new List(),
+ YY = new List(),
+ });
+ }
+
+ _pending_series_collection.BlockEnqueue(updateSeries);
+ }
+ }
+
+ ///
+ /// Handles the SurfaceSizeChanged event of the Surface control.
+ ///
+ /// The source of the event.
+ /// The instance containing the event data.
+ private void Surface_SurfaceSizeChanged(object sender, EventArgs e)
+ {
+ if (!_pending_series_collection.ToList().SelectMany(x => x).ToList().Exists(x => x.IsUpdateSeries))
+ {
+ List updateSeries = new List();
+
+ foreach (var pending_Series in _to_render)
+ {
+ updateSeries.Add(new PendingSeries()
+ {
+ IsUpdateSeries = true,
+ Series = pending_Series.Value.Series,
+ XX = new List(),
+ YY = new List(),
+ });
+ }
+
+ _pending_series_collection.BlockEnqueue(updateSeries);
+ }
+ }
+
+ #endregion
+
+ #region Public Methods
+
+ ///
+ /// Submits the specified x and y data points.
+ /// If the controller has more than one data series the data points will be duplicated.
+ ///
+ /// X data point.
+ /// Y data point.
+ public void PushData(TXDataPoint x, TYDataPoint y)
+ {
+ if (DataSeriesCollection.Count == 0) return;
+
+ List> xxxx = new List>();
+ List> yyyy = new List>();
+
+ foreach (var series in DataSeriesCollection.ToList())
+ {
+ xxxx.Add(new List() { x });
+ yyyy.Add(new List() { y });
+ }
+
+ PushData(xxxx, yyyy);
+ }
+
+ ///
+ /// Submits the specified collections of x and y data points.
+ /// If the controller has more than one data series the data points will be distributed evenly.
+ ///
+ /// X data point collection.
+ /// Y data point collection.
+ public void PushData(IEnumerable xx, IEnumerable yy)
+ {
+ if (DataSeriesCollection.Count == 0) return;
+
+ var xList = xx.ToList();
+ var yList = yy.ToList();
+
+ List> xxxx = new List>();
+ List> yyyy = new List>();
+
+ foreach (var series in DataSeriesCollection.ToList())
+ {
+ xxxx.Add(new List());
+ yyyy.Add(new List());
+ }
+
+ int counter = 0;
+
+ for (int i = 0; i < xList.Count; i++)
+ {
+ xxxx[counter].Add(xList[i]);
+ yyyy[counter].Add(yList[i]);
+
+ counter++;
+
+ if (counter >= xxxx.Count)
+ {
+ counter = 0;
+ }
+ }
+
+ PushData(xxxx, yyyy);
+ }
+
+ ///
+ /// Submits a matrix of x and y data points. Meaning each data series should process a single collection of x/y data points.
+ ///
+ /// X matrix.
+ /// Y matrix.
+ public void PushData(IEnumerable> xxxx, IEnumerable> yyyy)
+ {
+ if (DataSeriesCollection.Count == 0) return;
+
+ IEnumerable> xxxxI = xxxx.Select(x => x.ToList()).ToList();
+ IEnumerable> yyyyI = yyyy.Select(x => x.ToList()).ToList();
+
+ List> xxxxList = xxxxI.Select(x => x.ToList()).ToList();
+ List> yyyyList = yyyyI.Select(x => x.ToList()).ToList();
+
+ int first_count_x = xxxxList[0].Count;
+ int first_count_y = yyyyList[0].Count;
+
+
+ bool is_data_valid = true;
+
+ for (int i = 0; i < xxxxList.Count; i++)
+ {
+ if (xxxxList[0].Count != first_count_x)
+ {
+ is_data_valid = false;
+ break;
+ }
+
+ if (xxxxList[0].Count != yyyyList[0].Count)
+ {
+ is_data_valid = false;
+ break;
+ }
+ }
+
+ if (!is_data_valid)
+ {
+ throw new ArgumentOutOfRangeException("When pushing data to a multi series renderer, each series must contain the same amount of data.");
+ }
+
+ var list = DataSeriesCollection.ToList();
+
+ var pending_list = new List();
+
+ for (int i = 0; i < list.Count; i++)
+ {
+ pending_list.Add(new PendingSeries()
+ {
+ Series = list[i],
+ XX = xxxxList[i].ToList(),
+ YY = yyyyList[i].ToList(),
+ });
+ }
+
+ _pending_series_collection.BlockEnqueue(pending_list);
+ }
+
+ ///
+ /// Clears all data points from this controller.
+ ///
+ public void Clear()
+ {
_clear = true;
- _pending_series_collection.BlockEnqueue(new List()
- {
- new PendingSeries()
- {
- IsClearSeries = true
- },
- });
- }
-
- ///
- /// Requests the controller to invoke a virtual range change event.
- ///
- public void RequestVirtualRangeChange()
- {
- OnVirtualRangeChanged(Range.MaximumX, Range.MaximumX, Range.MinimumY, Range.MaximumY);
+ _pending_series_collection.BlockEnqueue(new List()
+ {
+ new PendingSeries()
+ {
+ IsClearSeries = true
+ },
+ });
+ }
+
+ ///
+ /// Requests the controller to invoke a virtual range change event.
+ ///
+ public void RequestVirtualRangeChange()
+ {
+ OnVirtualRangeChanged(Range.MaximumX, Range.MaximumX, Range.MinimumY, Range.MaximumY);
}
///
@@ -731,8 +752,8 @@ public IGraphDataPoint TranslateSurfaceY(double surfaceYPosition)
{
return default(IGraphDataPoint);
}
- }
-
- #endregion
- }
-}
+ }
+
+ #endregion
+ }
+}
diff --git a/RealTimeGraphX/GraphDataPoint.cs b/RealTimeGraphX/GraphDataPoint.cs
index f1bbcfc..99f63fb 100644
--- a/RealTimeGraphX/GraphDataPoint.cs
+++ b/RealTimeGraphX/GraphDataPoint.cs
@@ -256,6 +256,14 @@ public Type Type
///
public abstract IGraphDataPoint GetDefaultMargins();
+ ///
+ /// Expands the range by adding factor * ( max - min ) to the max and subtracting the same value from the min.
+ ///
+ ///
+ ///
+ ///
+ public abstract void ExpandRange ( ref GraphDataPoint min, ref GraphDataPoint max, double factor ) ;
+
#endregion
}
diff --git a/RealTimeGraphX/GraphRange.cs b/RealTimeGraphX/GraphRange.cs
index fa2b850..3490186 100644
--- a/RealTimeGraphX/GraphRange.cs
+++ b/RealTimeGraphX/GraphRange.cs
@@ -6,6 +6,31 @@
namespace RealTimeGraphX
{
+ ///
+ /// This enum defines the initial behaviour, when there is not enough new data to fill the X-Axis.
+ ///
+ public enum XStartBehavior
+ {
+ ///
+ /// The option strechData means that the initial data will be stretched to fill the
+ /// X-Axis. As new data arrives, the data will be squeezed until the specified range
+ /// for the X-Axis has been reached. After that the data will scroll to the left.
+ ///
+ stretchData,
+ ///
+ /// The option slideInFromRight means that the range of the X-Axis is fixed. New
+ /// data will appear at the right of the diagram and scroll to the left. The data
+ /// scrolls from very beginning.
+ ///
+ slideInFromRight,
+ ///
+ /// The option fillFromLeft means that the range of the X-Axis is fixed. New Data
+ /// is drawn relative to the left hand side and does not scroll at all until the
+ /// specified range of the X-Axis is full. After that the data will scroll to the left.
+ ///
+ fillFromLeft
+ }
+
///
/// Represents a graph x/y data points boundaries.
///
@@ -40,6 +65,12 @@ public interface IGraphRange : IGraphComponent
/// Gets or sets the AutoY fallback margins when is set to Margins.
///
GraphDataPoint AutoYFallBackMargins { get; set; }
+
+ ///
+ /// This property defines how data is displayed in the initial period, before the MaximumX has been reached.
+ /// The options are streching the data, sliding the data in from the right, or filling the data from the left.
+ ///
+ XStartBehavior XStartBehavior { get; set; }
}
///
@@ -181,5 +212,16 @@ GraphDataPoint IGraphRange.AutoYFallBackMargins
AutoYFallbackMargins = (YDataPoint)value;
}
}
+
+ private XStartBehavior _xStartBehavior = XStartBehavior.stretchData ;
+ ///
+ /// This property defines how data is displayed in the initial period, before the MaximumX has been reached.
+ /// The options are streching the data, sliding the data in from the right, or filling the data from the left.
+ ///
+ public XStartBehavior XStartBehavior
+ {
+ get { return _xStartBehavior; }
+ set { _xStartBehavior = value; RaisePropertyChangedAuto(); }
+ }
}
}
diff --git a/RealTimeGraphX/RealTimeGraphX.csproj b/RealTimeGraphX/RealTimeGraphX.csproj
index 3dc4555..c8e847f 100644
--- a/RealTimeGraphX/RealTimeGraphX.csproj
+++ b/RealTimeGraphX/RealTimeGraphX.csproj
@@ -3,7 +3,7 @@
netstandard2.0
false
- 1.0.0
+ 1.0.0.3
RealTimeGraphX is a data type agnostic, high performance plotting library for WPF and UWP.
LICENSE
https://github.com/royben/RealTimeGraphX
@@ -12,7 +12,7 @@
Chart;Graph;Plotting;RealTime;WPF;UWP
Roy Ben Shabat
Roy Ben Shabat
- Release
+ Release;Debug