How to update plot using Oxyplot in Windows Form - c#

I am working on a project where I read some serial data from a board, and try to show it on a graph plot.
So far I have managed to implement the Oxyplot inside my application.
But I am confused how to update the plot on each new data coming from the serial port?
Here is my code in a simplified version
using OxyPlot;
namespace Motor
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
ComPort.DataReceived += new
System.IO.Ports.SerialDataReceivedEventHandler(port_DataReceived_1);
plot1.Model = GridLinesHorizontal();
}
public static PlotModel GridLinesHorizontal()
{
var plotModel = new PlotModel();
plotModel.Title = "Horizontal";
var linearAxis1 = new LinearAxis();
linearAxis1.MajorGridlineStyle = LineStyle.Solid;
linearAxis1.MinorGridlineStyle = LineStyle.Dot;
linearAxis1.Maximum = 5;
linearAxis1.Minimum = -5;
plotModel.Axes.Add(linearAxis1);
return plotModel;
}
private void port_DataReceived_1(object sender, SerialDataReceivedEventArgs e)
{
InputData = ComPort.ReadLine();
if (InputData != String.Empty)
{
this.BeginInvoke(new SetTextCallback(SetText), new object[] { InputData });
}
}
private void SetText(string text)
{
dVal = double.Parse(text, CultureInfo.InvariantCulture); // convert to double
///// HERE I WANT TO UPDATE THE PLOT with dval
}
}
}

Not sure if that's the best way of doing it but something like this should work:
using OxyPlot;
using OxyPlot.Axes;
using OxyPlot.Series;
namespace Motor
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
ComPort.DataReceived += new
System.IO.Ports.SerialDataReceivedEventHandler(port_DataReceived_1);
plot1.Model = GridLinesHorizontal();
//create new LineSeries and add it to the PlotView
Line1 = new LineSeries
{
Title = "Test Series",
Color = OxyColors.Red,
TextColor = OxyColors.Red,
BrokenLineColor = OxyColors.Red
};
plot1.Model.Series.Add(Line1);
}
LineSeries Line1; // declare Line1 as global
public static PlotModel GridLinesHorizontal()
{
var plotModel = new PlotModel();
plotModel.Title = "Horizontal";
var linearAxis1 = new LinearAxis();
linearAxis1.MajorGridlineStyle = LineStyle.Solid;
linearAxis1.MinorGridlineStyle = LineStyle.Dot;
linearAxis1.Maximum = 5;
linearAxis1.Minimum = -5;
plotModel.Axes.Add(linearAxis1);
return plotModel;
}
private void port_DataReceived_1(object sender, SerialDataReceivedEventArgs e)
{
InputData = ComPort.ReadLine();
if (InputData != String.Empty)
{
this.BeginInvoke(new SetTextCallback(SetText), new object[] { InputData });
}
}
int plotIndex = 0;
private void SetText(string text)
{
dVal = double.Parse(text, CultureInfo.InvariantCulture); // convert to double
///// plotIndex is the x value of the new point, not sure if OxyPlot offers an auto increment option
Line1.Points.Add(new DataPoint(plotIndex, dVal));
plotIndex++;
plot1.Invalidate();
}
}
}

Related

How to dynamically create a pie chart using any clicked data points in cartesian chart

Can anyone tell me how to extract cartesian chart values for a given X axis value? I created a cartesian chart and figured out how to change it. Now I want to dynamically create a pie chart for any clicked data point. To illustrate:
When I clicked on any data point with datetime 2022.02.27 00:57:04, I want to create and show a new page like this
I have three files, Data.cs for my custom data type, mainForm.cs with cartesianChart and pie.cs with pieChart
Data.cs
using System;
namespace TestUI
{
internal class Data
{
public DateTime dateTime { get; set; }
public double value { get; set; }
}
}
mainForm.cs
using LiveCharts;
using LiveCharts.Configurations;
using LiveCharts.Wpf;
using System;
using System.Windows.Forms;
using Brushes = System.Windows.Media.Brushes;
namespace TestUI
{
public partial class mainForm : Form
{
const int numLineSeries = 5;
const int numValues = 10;
const int ONESEC = 1000;
public mainForm()
{
InitializeComponent();
// I believe this is where the magic will happen
// cartesianChart.DataClick += CartesianChart_DataClick;
}
private void mainForm_Load(object sender, EventArgs e)
{
var dayConfig = Mappers.Xy<Data>()
.X(data => (double)data.dateTime.Ticks / TimeSpan.FromHours(1).Ticks)
.Y(data => data.value);
SeriesCollection seriesCollection = new SeriesCollection(dayConfig);
Timer chartTimer = new Timer() { Interval = 3 * ONESEC };
Random random = new Random();
for (int i = 0; i < numLineSeries; i++)
{
LineSeries lineSeries = new LineSeries
{
Values = new ChartValues<Data>(),
Fill = Brushes.Transparent,
Title = $"LineSeries[{i}]"
};
EventHandler updateLineSeries = (sender_, e_) =>
{
if (lineSeries.Values.Count > numValues) { lineSeries.Values.RemoveAt(0); };
Data data = new Data { dateTime = DateTime.Now, value = random.NextDouble() };
lineSeries.Values.Add(data);
};
seriesCollection.Add(lineSeries);
chartTimer.Tick += updateLineSeries;
}
cartesianChart.Series = seriesCollection;
Axis axis = new Axis() { LabelFormatter = value => new System.DateTime((long)(value * TimeSpan.FromHours(1).Ticks)).ToString("yyyy.MM.dd HH:mm:ss") };
cartesianChart.AxisX.Add(axis);
}
}
}
pie.cs
using LiveCharts;
using LiveCharts.Wpf;
using System;
using System.Windows.Forms;
namespace TestUI
{
public partial class pie : Form
{
public pie()
{
InitializeComponent();
}
private void pie_Load(object sender, EventArgs e)
{
Func<ChartPoint, string> labelPoint = chartPoint => string.Format("{0} ({1:P})", chartPoint.Y, chartPoint.Participation);
SeriesCollection seriesCollection = new SeriesCollection();
// How can I send these values from mainForm.cs to here?
double[] Values = { 0.56, 0.66, 0.01, 0.43, 0.58 };
int num = 0;
foreach (double value in Values)
{
PieSeries pieSeries = new PieSeries
{
Title = $"LineSeries[{num++}]",
Values = new ChartValues<double> { value },
DataLabels = true,
LabelPoint = labelPoint
};
seriesCollection.Add(pieSeries);
}
pieChart.Series = seriesCollection;
}
}
}

How to change the image in picturebox based on timer?

In my user control I'm trying to change the image in a PictureBox when the timer is stopped. But it works only for the first time. If I start the timer again the image is not updating. I used the below code to change the image.
public partial class CANMsgLEDControl : UserControl
{
#region Constants
private const int kLedOn = 2;
private const int kLedOff = 4;
#endregion
private bool _blinkEnabled = false;
private int _blinkRate = 500;
private GridStyleInfo _currentCell = null;
private System.Timers.Timer _timer = new System.Timers.Timer();
public CANMsgLEDControl()
{
}
public CANMsgLEDControl(GridStyleInfo cell)
{
InitializeComponent();
picBxLed.Image = imageList1.Images[kLedOff];
_timer.Elapsed += new ElapsedEventHandler(_timer_Elapsed);
_currentCell = cell;
if (_currentCell != null)
this.BackColor = _currentCell.BackColor;
Create Handle
if (!this.IsHandleCreated)
{
CreateHandle();
}
}
#region Properties
[Browsable(true)]
public bool BlinkEnabled
{
get { return _blinkEnabled; }
set { _blinkEnabled = value; }
}
[Browsable(true)]
public int BlinkRate
{
get { return _blinkRate; }
set
{
_blinkRate = value;
_timer.Interval = _blinkRate;
}
}
#endregion
public void ResetLedColor()
{
this.picBxLed.Image = imageList1.Images[kLedOff];
}
public void BlinkStart()
{
_currentCell.BeginUpdate();
picBxLed.Image = imageList1.Images[kLedOn];
_currentCell.EndUpdate();
_timer.Start();
}
#region Events
private void _timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
if (InvokeRequired)
{
BeginInvoke(new ElapsedEventHandler(_timer_Elapsed), new object[] { sender, e });
}
else
{
if (this.BlinkEnabled)
{
_timer.Stop();
_currentCell.BeginUpdate();
picBxLed.Image = imageList1.Images[kLedOff];
_currentCell.EndUpdate();
}
}
}
#endregion
}
How to change the image when the timer is stopped? Can anyone suggest a way to overcome this issue?
Regards,
Arulpriya

ComboBox control shows no items

I'm trying to learn the basics of C# and decided to make a simple windows form to demonstrate the Dictionary class, but when I start my program, the Combo-/ListBox controls stay blank, although I loaded some data to them. Hope you could help me out with this.
using System;
using System.Collections.Generic;
using System.Windows.Forms;
namespace ClassDictionaryExample
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
Dictionary<string, string[]> CountryList = new Dictionary<string, string[]>();
private void Form1_Load(object sender, EventArgs e)
{
CountryList["Bulgaria"] = new string[] { "Sofia University St Kliment Ohridski", "Technical University of Sofia",
"Plovdiv University Paisii Hilendarski" };
CountryList["Romania"] = new string[] { "Alexandru Ioan Cuza University", "Babes-Bolyai University",
"University of Bucharest" };
CountryList["Serbia"] = new string[] { "University of Belgrade", "University of Novi Sad", "University of Niš" };
foreach (var CountryKey in CountryList.Keys)
{
comboBoxCountry.Items.Add(CountryKey);
}
comboBoxCountry.SelectedIndex = 0;
}
private void comboBoxCountry_SelectedIndexChanged(object sender, EventArgs e)
{
string selectedCountry = comboBoxCountry.SelectedItem.ToString();
if (comboBoxCountry.SelectedIndex == 0)
listBoxUniversities.Items.Clear();
else
{
listBoxUniversities.Items.Clear();
listBoxUniversities.Items.AddRange(CountryList[selectedCountry]);
}
}
}
}
I tried to reproduce your problem but could not. You need to keep in mind that you have to assign 2 events to your code. One on FormLoaded and other on ComboboxSelectionChanged.
I have reduced your code to show universities when selected Index is zero.
public Form1()
{
InitializeComponent();
this.Load += Form1_Load;
comboBoxCountry.SelectedIndexChanged += comboBoxCountry_SelectedIndexChanged;
}
private Dictionary<string, string[]> _countryList;
public Dictionary<string, string[]> CountryList
{
get
{
if (_countryList == null)
{
_countryList = new Dictionary<string, string[]>();
_countryList["Bulgaria"] = new string[] { "Sofia University St Kliment Ohridski", "Technical University of Sofia", "Plovdiv University Paisii Hilendarski" };
_countryList["Romania"] = new string[] { "Alexandru Ioan Cuza University", "Babes-Bolyai University", "University of Bucharest" };
_countryList["Serbia"] = new string[] { "University of Belgrade", "University of Novi Sad", "University of Niš" };
}
return _countryList;
}
}
private void Form1_Load(object sender, EventArgs e)
{
foreach (var CountryKey in CountryList.Keys)
comboBoxCountry.Items.Add(CountryKey);
comboBoxCountry.SelectedIndex = 0;
}
private void comboBoxCountry_SelectedIndexChanged(object sender, EventArgs e)
{
string selectedCountry = comboBoxCountry.SelectedItem.ToString();
listBoxUniversities.Items.Clear();
listBoxUniversities.Items.AddRange(CountryList[selectedCountry]);
}

How to improve performance of this control?

I needed a marquee for a project and after much Googling and trial and error, I created one. However, the animation itself is a little jittery. I need some pointers on how to improve the performance of this. Thanks.
p.s. Some of the code may be redundant...
public class Marquee : Canvas
{
static Marquee()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(Marquee), new FrameworkPropertyMetadata(typeof(Marquee)));
}
private IList<string> _lines = new List<string>();
private IList<string> Lines
{
get
{
return _lines;
}
set
{
_lines = value;
}
}
public double FontSize
{
get { return (double)GetValue(FontSizeProperty); }
set { SetValue(FontSizeProperty, value); }
}
// Using a DependencyProperty as the backing store for FontSize. This enables animation, styling, binding, etc...
public static readonly DependencyProperty FontSizeProperty =
DependencyProperty.Register("FontSize", typeof(double), typeof(Marquee));
public Brush FontBrush
{
get { return (Brush)GetValue(FontBrushProperty); }
set { SetValue(FontBrushProperty, value); }
}
// Using a DependencyProperty as the backing store for FontBrush. This enables animation, styling, binding, etc...
public static readonly DependencyProperty FontBrushProperty =
DependencyProperty.Register("FontBrush", typeof(Brush), typeof(Marquee));
public string SourceFile { get; set; }
List<RenderTargetBitmap> _images = new List<RenderTargetBitmap>();
private void CreateBitmaps()
{
foreach (var line in Lines)
{
FormattedText ft = new FormattedText(line,
System.Globalization.CultureInfo.CurrentUICulture,
System.Windows.FlowDirection.LeftToRight,
new Typeface(FontFamily.Source),
FontSize,
FontBrush);
if (ft.Height == 0 || ft.Width == 0)
continue;
DrawingVisual drawingVisual = new DrawingVisual();
DrawingContext drawingContext = drawingVisual.RenderOpen();
drawingContext.DrawText(ft, new Point(0, 0));
drawingContext.Close();
RenderTargetBitmap bmp = new RenderTargetBitmap((int)ft.Width, (int)ft.Height, 72, 72, PixelFormats.Pbgra32);
bmp.Render(drawingVisual);
bmp.Freeze();
_images.Add(bmp);
}
}
private int nextImgIndex = 0;
private Image _Image;
private void GetNextImage()
{
if (_images.Count == 0)
return;
if (nextImgIndex >= _images.Count)
nextImgIndex = 0;
_Image.Source = _images.ElementAt(nextImgIndex++);
}
private string _curStr = null;
private string CurrentString
{
get
{
return _curStr;
}
set
{
_curStr = value;
}
}
TextBlock _textBlock = new TextBlock();
DispatcherTimer timer;
public Marquee()
{
Loaded += Marquee_Loaded;
FontSize = 12;
FontBrush = Brushes.Black;
if (Rate == 0d)
{
Rate = 150d;
}
this.CacheMode = new BitmapCache(2);
FontBrush.Freeze();
_Image = new Image();
_Image.CacheMode = new BitmapCache();
_Image.ClipToBounds = false;
FontFamily = new FontFamily("Calibri");
this.Children.Add(_Image);
}
void Marquee_Loaded(object sender, RoutedEventArgs e)
{
ReadFile();
CreateBitmaps();
CreateAnimation();
//throw new NotImplementedException();
}
//[ValueConversion(typeof(string), typeof(TimeSpan))]
//public class StringFormatConverter : IValueConverter
//{
// public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
// {
// string[] vals = ((string)value).Split(new[] { ':' });
// if (vals.Count() != 3)
// throw new FormatException(string.Format("Invalid timespan format : {0}", value));
// return new TimeSpan(int.Parse(vals[0]), int.Parse(vals[1]), int.Parse(vals[2]));
// }
// public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
// {
// throw new NotImplementedException();
// }
//}
DoubleAnimation anim;
TranslateTransform transform;
Duration duration;
public double Rate { get; set; }
void CreateAnimation()
{
if (CurrentString == null)
return;
GetNextImage();
transform = new TranslateTransform(Application.Current.MainWindow.ActualWidth, 0);
_Image.RenderTransform = transform;
var width = _Image.Source.Width;
double secs = (Application.Current.MainWindow.ActualWidth + width) / Rate;
duration = new Duration(TimeSpan.FromSeconds(secs));
anim = new DoubleAnimation(-width, duration);
anim.Completed += anim_Completed;
transform.BeginAnimation(TranslateTransform.XProperty, anim);
}
void anim_Completed(object sender, EventArgs e)
{
CreateAnimation();
}
double MeasureStringLength(string text)
{
if (text == null)
return 0;
FormattedText ft = new FormattedText(text,
System.Globalization.CultureInfo.CurrentUICulture,
System.Windows.FlowDirection.LeftToRight,
new Typeface(_textBlock.FontFamily.ToString()),
FontSize,
FontBrush);
return ft.Width;
}
public FontFamily FontFamily
{
get { return (FontFamily)GetValue(FontFamilyProperty); }
set { SetValue(FontFamilyProperty, value); }
}
// Using a DependencyProperty as the backing store for FontFamily. This enables animation, styling, binding, etc...
public static readonly DependencyProperty FontFamilyProperty =
DependencyProperty.Register("FontFamily", typeof(FontFamily), typeof(Marquee));
FormattedText GetFormattedText(string text)
{
if (text == null)
return null;
FormattedText ft = new FormattedText(text,
System.Globalization.CultureInfo.CurrentUICulture,
System.Windows.FlowDirection.LeftToRight,
new Typeface(_textBlock.FontFamily.ToString()),
FontSize,
FontBrush);
return ft;
}
void ReadFile()
{
if (SourceFile == null)
return;
StreamReader fR = new StreamReader(SourceFile);
string line;
while ((line = fR.ReadLine()) != null)
{
Lines.Add(line);
}
if (Lines.Count > 0)
CurrentString = Lines[0];
}
}
I'm modified your control in the following manner (removing the transform animation, using direct rendering instead of rendering to image cache):
public static readonly DependencyProperty OffsetProperty = DependencyProperty.Register("Offset", typeof(double),
typeof(Marquee), new FrameworkPropertyMetadata(0.0, FrameworkPropertyMetadataOptions.AffectsRender));
//
public double Offset {
get { return (double)GetValue(OffsetProperty); }
}
protected override void OnRender(DrawingContext dc) {
dc.DrawText(currentText, new Point(Offset, 0)); // direct render
}
int nextTextIndex = 0;
FormattedText currentText;
void GetNextText() {
if(formattedTexts.Count == 0) return;
currentText = formattedTexts[(nextTextIndex++) % formattedTexts.Count];
}
void CreateAnimation() {
if(CurrentString == null)
return;
GetNextText();
double width = currentText.Width;
double secs = (Application.Current.MainWindow.ActualWidth + width) / Rate;
duration = new Duration(TimeSpan.FromSeconds(secs));
anim = new DoubleAnimation(0, -width, duration);
anim.Completed += anim_Completed;
BeginAnimation(OffsetProperty, anim);
}
//
void anim_Completed(object sender, EventArgs e) {
anim.Completed -= anim_Completed;
CreateAnimation();
}
List<FormattedText> formattedTexts = new List<FormattedText>();
void CreateTexts() {
foreach(var line in Lines) {
FormattedText ft = new FormattedText(line,
System.Globalization.CultureInfo.CurrentUICulture,
System.Windows.FlowDirection.LeftToRight,
new Typeface(FontFamily.Source),
FontSize,
FontBrush);
if(ft.Height == 0 || ft.Width == 0)
continue;
formattedTexts.Add(ft);
}
}
Now it is more smoother to me.

Filtering a ListBox

I have a ListBox named lsbEntities. I want to filter it's items based on some selected radio button.
The code below is kind of pseudo, is their a better approach?
private List<string> _listBoxItemsToFilter;
private Thread tFilterEntityList;
enum EntityType
{
Vehicle,
Facility
}
private void FilterEntityList(EntityType entityType)
{
_listBoxItemsToFilter = new List<string>();
Dictionary<string,string> entitiesAndClassTypes;
List<string> listBoxItems = new List<string>();
for(int i = 0; i < lsbEntities.Count; i++)
{
//object listItem = lsbEntities.Items[i];
listBoxItems.Add(lsbEntities[i].ToString());
}
// get associated types
entityClassTypes = _controlFacade.GetClassTypes(listBoxItems);
foreach (KeyValuePair<string,string>
entityAndClass in entitiesAndClassTypes)
{
classType = entityAndClass.Value;
if(classType != entityType)
{
_listBoxItemsToFilter.Add(entityAndClass.Key);
}
}
RemoveFilterFromEntityListBox();
AddFilterToEntityListBox();
}
private void AddFilterToEntityListBox()
{
// DELEGATE NEEDED TO MODIFY LISTBOX FROM THREAD
foreach(string listBoxItem in _listBoxItemsToFilter)
{
if(lsbEntities.Contains(listBoxItem)
{
// REMOVE WITH DELEGATE
}
}
}
private void RemoveFilterFromEntityListBox()
{
// DELEGATE NEEDED TO MODIFY LISTBOX FROM THREAD
if(_listBoxItemsToFilter != null)
{
foreach(string listBoxItem in _listBoxItemsToFilter)
{
if(!lsbEntities.Contains(listBoxItem)
{
// REMOVE WITH DELEGATE
}
}
}
}
// EXAMPLE CALL WHEN CLICKING RADIO-BUTTON
private void rbVehicles_CheckedChanged(object sender, EventArgs e)
{
switch (rbVehicles.Checked)
{
case (true):
{
object entityType = (object)EntityType.Vehicle;
tFilterEntityList = new Thread(FilterEntityList(entityType));
tFilterEntityList.IsBackground = true;
tFilterEntityList.Start();
//FilterEntityList(EntityType.Vehicle);
break;
}
}
}
I have only included an example of selecting to filter everything but VehicleS. The same approach would be used for the Facility class, where the thread would be re-instantiated.
Here is a simple example showing one way to filter items in a ListBox. This could be improved by using a ListView or DataGridView in VirtualMode. It is very unclear to me what you are trying to do, so if this is not helpful I will remove it.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Linq;
using System.Windows.Forms;
public class Form1 : Form
{
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new Form1());
}
List<Entity> items = new List<Entity>()
{
new Entity(EntityType.Vehicle, "Car"),
new Entity(EntityType.Vehicle, "Aeroplane"),
new Entity(EntityType.Vehicle, "Truck"),
new Entity(EntityType.Vehicle, "Bus"),
new Entity(EntityType.Facility, "Garage"),
new Entity(EntityType.Facility, "House"),
new Entity(EntityType.Facility, "Shack"),
};
ListBox listBox;
ComboBox comboBox;
public Form1()
{
Text = "Filtering Demo";
ClientSize = new Size(500, 320);
Controls.Add(listBox = new ListBox
{
Location = new Point(10, 10),
Size = new Size(200, 300),
});
Controls.Add(comboBox = new ComboBox
{
Location = new Point(230, 10),
DropDownStyle = ComboBoxStyle.DropDownList,
Items = { "All", EntityType.Vehicle, EntityType.Facility },
SelectedIndex = 0,
});
comboBox.SelectedIndexChanged += UpdateFilter;
UpdateFilter(comboBox, EventArgs.Empty);
}
void UpdateFilter(object sender, EventArgs e)
{
var filtered = items.Where((i) => comboBox.SelectedItem is string || (EntityType)comboBox.SelectedItem == i.EntityType);
listBox.DataSource = new BindingSource(filtered, "");
}
}
enum EntityType { Vehicle, Facility, }
class Entity : INotifyPropertyChanged
{
public string Name { get; private set; }
public EntityType EntityType { get; private set; }
public Entity(EntityType entityType, string name) { EntityType = entityType; Name = name; }
public override string ToString() { return Name ?? String.Empty; }
// Implementing INotifyPropertyChanged to eliminate (caught) BindingSource exceptions
public event PropertyChangedEventHandler PropertyChanged;
}

Categories