Files
C4IT-F4SD-Client/FasdDesktopUi/Basics/UserControls/DataCanvas/DynamicChart.xaml.cs
2025-11-11 11:03:42 +01:00

833 lines
27 KiB
C#

using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Shapes;
using FasdDesktopUi.Basics.Models;
using static C4IT.Logging.cLogManager;
namespace FasdDesktopUi.Basics.UserControls
{
public partial class DynamicChart : UserControl
{
#region Variables
int DataDurationStart;
int DataDurationEnd;
int DataDurationTotal;
int timeSkip;
private double PaddingTop;
private double PaddingBottom;
private double PaddingLeft;
private double PaddingRight;
private double DataHeight;
private double DataWidth;
private LinearTransformation xCoordinate;
private LinearTransformation yCoordinate;
Size sizeY;
Size sizeX;
#endregion
#region Properties
#region ChartData
public cChartModel ChartData
{
get { return (cChartModel)GetValue(ChartDataProperty); }
set { SetValue(ChartDataProperty, value); }
}
public static readonly DependencyProperty ChartDataProperty =
DependencyProperty.Register("ChartData", typeof(cChartModel), typeof(DynamicChart), new PropertyMetadata(null, new PropertyChangedCallback(HandleDependancyPropertyChanged)));
private static void HandleDependancyPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (!(d is DynamicChart me)) return;
me.BindingValueChange();
}
#endregion
#region IsCloseButtonVisible
public bool IsCloseButtonVisible
{
get { return (bool)GetValue(IsCloseButtonVisibleProperty); }
set { SetValue(IsCloseButtonVisibleProperty, value); }
}
public static readonly DependencyProperty IsCloseButtonVisibleProperty =
DependencyProperty.Register("IsCloseButtonVisible", typeof(bool), typeof(DynamicChart), new PropertyMetadata(false));
#endregion
#region IsBarChart
public bool isBarChart
{
get { return (bool)GetValue(isBarChartProperty); }
set { SetValue(isBarChartProperty, value); }
}
// Using a DependencyProperty as the backing store for isBarChart. This enables animation, styling, binding, etc...
public static readonly DependencyProperty isBarChartProperty =
DependencyProperty.Register("isBarChart", typeof(bool), typeof(DynamicChart), new PropertyMetadata(true, new PropertyChangedCallback(HandleBarChartPropertyChanged)));
private static void HandleBarChartPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (!(d is DynamicChart me)) return;
me.ChartTypeChange();
}
#endregion
#region GraphicDataProperty
public List<cChartValue> GraphicDataProperty { get; set; } = new List<cChartValue>();
#endregion
#endregion
#region Internal Classes
public class cChartValue
{
public DateTime Time { get; set; }
public double Value { get; set; }
public int Duration { get; set; }
}
public class LinearTransformation
{
private double x0;
private double x1;
private double z0;
private double z1;
private double a;
private double c;
public LinearTransformation(double x0, double x1, double z0, double z1)
{
this.x0 = x0;
this.x1 = x1;
this.z0 = z0;
this.z1 = z1;
a = (x1 - x0) / (z1 - z0);
c = x0 - (a * z0);
}
public double GetCoordinate(double x)
{
return a * x + c;
}
}
#endregion
public Action CloseButtonClickedAction { get; set; }
#region Restructure
public void BindingValueChange()
{
try
{
ClearControl();
if (ChartData == null)
{
return;
}
if (this.ActualHeight == 0 || this.ActualWidth == 0)
{
return;
}
SetGraphicDataProperty();
SetTitle();
SetDate();
SetTime();
SetCanvas();
SetScale();
SetTimeStamps();
SetLines();
if (isBarChart == true)
{
SetValuesBarChart();
}
else
{
SetValuesLineGraph();
}
}
catch (Exception E)
{
LogException(E);
}
}
public void ChartTypeChange()
{
try
{
GraphicCanvas.Children.Clear();
if (isBarChart == true)
{
BarChartButton.Visibility = Visibility.Collapsed;
LineGraphButton.Visibility = Visibility.Visible;
SetScale();
SetTimeStamps();
SetLines();
SetValuesBarChart();
}
else
{
BarChartButton.Visibility = Visibility.Visible;
LineGraphButton.Visibility = Visibility.Collapsed;
SetScale();
SetTimeStamps();
SetLines();
SetValuesLineGraph();
}
}
catch (Exception E)
{
LogException(E);
}
}
#endregion
#region Methods
public void SetGraphicDataProperty()
{
try
{
if (ChartData?.Data == null) return;
var reverseData = ChartData.Data.ToList();
reverseData.Reverse();
if ((DateTime)reverseData[0][ChartData.TimeIndex] < (DateTime)reverseData[reverseData.Count() - 1][ChartData.TimeIndex])
{
foreach (var item in reverseData)
{
if (item[ChartData.ValueIndex] == null)
{
continue;
}
cChartValue data = new cChartValue();
data.Value = (double)Convert.ChangeType(item[ChartData.ValueIndex], typeof(double));
if (ChartData.DurationIndex > -1)
data.Duration = int.Parse(item[ChartData.DurationIndex].ToString());
data.Time = (DateTime)item[ChartData.TimeIndex];
data.Time = data.Time.ToLocalTime();
GraphicDataProperty.Add(data);
}
}
else if ((DateTime)ChartData.Data[0][ChartData.TimeIndex] < (DateTime)ChartData.Data[ChartData.Data.Count() - 1][ChartData.TimeIndex] || ChartData.Data.Count == 1)
{
foreach (var item in ChartData.Data)
{
if (item[ChartData.ValueIndex] == null)
{
continue;
}
cChartValue data = new cChartValue();
data.Value = (double)Convert.ChangeType(item[ChartData.ValueIndex], typeof(double));
if (ChartData.DurationIndex > -1)
data.Duration = int.Parse(item[ChartData.DurationIndex].ToString());
data.Time = (DateTime)item[ChartData.TimeIndex];
data.Time = data.Time.ToLocalTime();
GraphicDataProperty.Add(data);
}
}
}
catch (Exception E)
{
LogException(E);
}
}
public void ClearControl()
{
TitleBlock.Text = string.Empty;
DateBlock.Text = string.Empty;
GraphicCanvas.Children.Clear();
GraphicDataProperty.Clear();
}
public void SetTitle()
{
TitleBlock.Text = ChartData.ChartTitle;
}
public void SetDate()
{
try
{
DateTime dataDate = GraphicDataProperty.First<cChartValue>().Time;
dataDate = dataDate.Date;
DateBlock.Text = $"{dataDate.ToString("dddd", new CultureInfo(cFasdCockpitConfig.Instance.SelectedLanguage))}, {dataDate.ToString("d", new CultureInfo(cFasdCockpitConfig.Instance.SelectedLanguage))}";
}
catch (Exception E)
{
LogException(E);
}
}
public void SetTime()
{
try
{
DateTime firstTime = GraphicDataProperty.First<cChartValue>().Time;
DateTime lastTime = GraphicDataProperty.Last<cChartValue>().Time;
int lastDuration = GraphicDataProperty.Last<cChartValue>().Duration;
lastTime = lastTime.AddMilliseconds(lastDuration);
DataDurationStart = firstTime.Hour;
DataDurationEnd = lastTime.Hour + 1;
TimeSpan totalDuration = lastTime - firstTime;
DataDurationTotal = DataDurationEnd - DataDurationStart + (24 * totalDuration.Days);
DataDurationTotal = GetNextLogicalTotalDuration(DataDurationTotal);
// temp fix
DataDurationEnd = DataDurationStart + DataDurationTotal;
timeSkip = (int)Math.Ceiling(DataDurationTotal / 5.0);
}
catch (Exception E)
{
LogException(E);
}
// this is a temporary helper method to keep the structure of the current state of the charts.
// however the logic should be rethought and maybe finally replaced for a even more dynamic approach
int GetNextLogicalTotalDuration(int totalDuration)
{
if (totalDuration < 0)
return 24 + totalDuration;
else if (totalDuration <= 3)
return 3;
else if (totalDuration <= 5)
return 5;
else if (totalDuration <= 8)
return 8;
else if (totalDuration <= 10)
return 10;
else if (totalDuration <= 12)
return 12;
else if (totalDuration <= 15)
return 15;
else if (totalDuration <= 18)
return 18;
else if (totalDuration <= 20)
return 20;
else return 24;
}
}
public void SetCanvas()
{
try
{
string textY;
if (ChartData.UnitFormat.Contains("{0}"))
{
textY = String.Format(ChartData.UnitFormat, ChartData.MaxValue.ToString());
}
else
{
textY = ChartData.MaxValue.ToString() + ChartData.UnitFormat;
}
sizeY = GetStringSize(textY, 11, new System.Windows.Media.FontFamily("Calibri"));
string textX = "09:00";
sizeX = GetStringSize(textX, 11, new System.Windows.Media.FontFamily("Calibri"));
PaddingTop = 5 + (sizeY.Height / 2);
PaddingRight = 10 + sizeY.Width;
PaddingBottom = 10 + sizeX.Height + PaddingTop;
PaddingLeft = 10 + (sizeX.Width / 2);
DataHeight = GraphicCanvas.ActualHeight - PaddingTop - PaddingBottom;
DataWidth = GraphicCanvas.ActualWidth - PaddingLeft - PaddingRight;
yCoordinate = new LinearTransformation(PaddingTop + DataHeight, PaddingTop, ChartData.MinValue, ChartData.MaxValue);
xCoordinate = new LinearTransformation(PaddingLeft, PaddingLeft + DataWidth, DataDurationStart * 3600000, (DataDurationStart + DataDurationTotal) * 3600000);
}
catch (Exception E)
{
LogException(E);
}
}
public void SetScale()
{
try
{
int Minimum = 0;
if ((ChartData.MinValue / ChartData.StepLengthScale) % 1 != 0)
{
int Multiplikator = ChartData.MinValue / ChartData.StepLengthScale;
Multiplikator++;
Minimum = Multiplikator * ChartData.MinValue;
}
else
{
Minimum = ChartData.MinValue;
}
for (int i = Minimum; i <= ChartData.MaxValue; i += ChartData.StepLengthScale)
{
string text;
if (ChartData.UnitFormat.Contains("{0}"))
{
text = String.Format(ChartData.UnitFormat, i.ToString());
}
else
{
text = i.ToString() + ChartData.UnitFormat;
}
TextBlock textBlock = new TextBlock()
{
Text = text,
FontFamily = new FontFamily("Calibri"),
FontSize = 11,
};
textBlock.SetResourceReference(TextBlock.ForegroundProperty, "Color.Menu.Icon");
Canvas.SetTop(textBlock, yCoordinate.GetCoordinate(i) - (sizeY.Height / 2));
Canvas.SetLeft(textBlock, PaddingLeft + DataWidth + 1);
GraphicCanvas.Children.Add(textBlock);
}
}
catch (Exception E)
{
LogException(E);
}
}
public void SetTimeStamps()
{
try
{
for (int i = DataDurationStart; i <= DataDurationEnd; i++)
{
if ((DataDurationStart - i) % timeSkip != 0)
{
continue;
}
string timeStamp;
int timeStampTime = i;
if (timeStampTime > 24)
{
timeStampTime -= 24;
}
if (i < 10)
{
timeStamp = "0" + timeStampTime.ToString() + ":00";
}
else
{
timeStamp = timeStampTime.ToString() + ":00";
}
TextBlock textBlock = new TextBlock()
{
Text = timeStamp,
FontFamily = new FontFamily("Calibri"),
FontSize = 11,
};
textBlock.SetResourceReference(TextBlock.ForegroundProperty, "Color.Menu.Icon");
Canvas.SetTop(textBlock, PaddingTop + DataHeight + (sizeX.Height / 2));
Canvas.SetLeft(textBlock, (xCoordinate.GetCoordinate(i * 3600000)) - (sizeX.Width / 2));
GraphicCanvas.Children.Add(textBlock);
}
}
catch (Exception E)
{
LogException(E);
}
}
public void SetLines()
{
try
{
int Minimum = 0;
if ((ChartData.MinValue / ChartData.StepLengthLine) % 1 != 0)
{
int Multiplikator = ChartData.MinValue / ChartData.StepLengthLine;
Multiplikator++;
Minimum = Multiplikator * ChartData.MinValue;
}
else
{
Minimum = ChartData.MinValue;
}
for (int i = Minimum; i <= ChartData.MaxValue; i += ChartData.StepLengthLine)
{
Point point1 = new Point(PaddingLeft, yCoordinate.GetCoordinate(i));
Point point2 = new Point(PaddingLeft + DataWidth, yCoordinate.GetCoordinate(i));
Line dottedLine = new Line();
dottedLine.SetResourceReference(Line.StrokeProperty, "HorizontalLineColor");
dottedLine.StrokeThickness = 1;
dottedLine.StrokeDashArray = new DoubleCollection { 5, 5 };
dottedLine.X1 = point1.X;
dottedLine.Y1 = point1.Y;
dottedLine.X2 = point2.X;
dottedLine.Y2 = point2.Y;
GraphicCanvas.Children.Add(dottedLine);
}
}
catch (Exception E)
{
LogException(E);
}
}
public void SetValuesLineGraph()
{
try
{
foreach (var data in GraphicDataProperty)
{
DateTime dataTime = data.Time;
double dataValue = data.Value;
int dataDuration = data.Duration;
double time = dataTime.TimeOfDay.TotalMilliseconds;
double xLeft = xCoordinate.GetCoordinate(time);
double xRight = xCoordinate.GetCoordinate(time + dataDuration);
double y = 0;
if (dataValue > ChartData.MaxValue)
{
y = yCoordinate.GetCoordinate(ChartData.MaxValue) - 5;
}
else if (dataValue < ChartData.MinValue)
{
y = yCoordinate.GetCoordinate(ChartData.MinValue) + 5;
}
else
{
y = yCoordinate.GetCoordinate(dataValue);
}
Border border = new Border()
{
Height = 6.0,
Width = xRight - xLeft,
CornerRadius = new CornerRadius(4.0),
ToolTip = dataTime.ToShortTimeString() + " - " + dataTime.AddMilliseconds(dataDuration).ToShortTimeString() + " | " + dataValue,
};
if (border.Width < 6)
{
border.Width = 6;
}
if (ChartData.IsThresholdActive == false)
{
border.Background = new SolidColorBrush(Color.FromRgb(0, 157, 221));
}
else
{
if (ChartData.IsDirectionUp == false)
{
if (dataValue <= ChartData.ErrorThreshold)
{
border.Background = new SolidColorBrush(Color.FromRgb(206, 61, 54));
}
else if (dataValue <= ChartData.WarningThreshold)
{
border.Background = new SolidColorBrush(Color.FromRgb(251, 157, 40));
}
else
{
border.Background = new SolidColorBrush(Color.FromRgb(117, 177, 89));
}
}
else
{
if (dataValue >= ChartData.ErrorThreshold)
{
border.Background = new SolidColorBrush(Color.FromRgb(206, 61, 54));
}
else if (dataValue >= ChartData.WarningThreshold)
{
border.Background = new SolidColorBrush(Color.FromRgb(251, 157, 40));
}
else
{
border.Background = new SolidColorBrush(Color.FromRgb(117, 177, 89));
}
}
}
Canvas.SetTop(border, y - (border.Height / 2));
Canvas.SetLeft(border, xLeft);
GraphicCanvas.Children.Add(border);
}
}
catch (Exception E)
{
LogException(E);
}
}
public void SetValuesBarChart()
{
try
{
foreach (var data in GraphicDataProperty)
{
DateTime dataTime = data.Time;
double dataValue = data.Value;
int dataDuration = data.Duration;
double time = dataTime.TimeOfDay.TotalMilliseconds;
double xLeft = xCoordinate.GetCoordinate(time);
double xRight = xCoordinate.GetCoordinate(time + dataDuration);
double y = 0;
if (dataValue > ChartData.MaxValue)
{
y = yCoordinate.GetCoordinate(ChartData.MaxValue) - 5;
}
else if (dataValue < ChartData.MinValue)
{
y = yCoordinate.GetCoordinate(ChartData.MinValue) + 5;
}
else
{
y = yCoordinate.GetCoordinate(dataValue);
}
Border border = new Border()
{
Height = yCoordinate.GetCoordinate(0) - y,
Width = xRight - xLeft,
CornerRadius = new CornerRadius(2, 2, 0, 0),
ToolTip = dataTime.ToShortTimeString() + " - " + dataTime.AddMilliseconds(dataDuration).ToShortTimeString() + " | " + dataValue,
};
if (border.Width < 6)
{
border.Width = 6;
}
if (ChartData.IsThresholdActive == false)
{
border.Background = new SolidColorBrush(Color.FromRgb(0, 157, 221));
}
else
{
if (ChartData.IsDirectionUp == false)
{
if (dataValue <= ChartData.ErrorThreshold)
{
border.Background = new SolidColorBrush(Color.FromRgb(206, 61, 54));
}
else if (dataValue <= ChartData.WarningThreshold)
{
border.Background = new SolidColorBrush(Color.FromRgb(251, 157, 40));
}
else
{
border.Background = new SolidColorBrush(Color.FromRgb(117, 177, 89));
}
}
else
{
if (dataValue >= ChartData.ErrorThreshold)
{
border.Background = new SolidColorBrush(Color.FromRgb(206, 61, 54));
}
else if (dataValue >= ChartData.WarningThreshold)
{
border.Background = new SolidColorBrush(Color.FromRgb(251, 157, 40));
}
else
{
border.Background = new SolidColorBrush(Color.FromRgb(117, 177, 89));
}
}
}
Canvas.SetTop(border, y);
Canvas.SetLeft(border, xLeft);
GraphicCanvas.Children.Add(border);
}
}
catch (Exception E)
{
LogException(E);
}
}
#endregion
public DynamicChart()
{
InitializeComponent();
}
#region CloseButton_Click
private void CloseButton_Click()
{
if (CloseButtonClickedAction != null)
CloseButtonClickedAction.Invoke();
}
private void CloseButton_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
CloseButton_Click();
}
private void CloseButton_TouchDown(object sender, TouchEventArgs e)
{
CloseButton_Click();
}
#endregion
#region ChangeChartType_Click
private void ChangeChartType_Click(object sender)
{
if (!(sender is FrameworkElement senderElement))
{
return;
}
if (senderElement.Name == "BarChartButton")
{
isBarChart = true;
}
if (senderElement.Name == "LineGraphButton")
{
isBarChart = false;
}
}
private void ChangeChartType_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
ChangeChartType_Click(sender);
}
private void ChangeChartType_TouchDown(object sender, TouchEventArgs e)
{
ChangeChartType_Click(sender);
}
#endregion
private void UserControl_SizeChanged(object sender, SizeChangedEventArgs e)
{
BindingValueChange();
}
public static Size GetStringSize(string text, double fontSize, FontFamily fontFamily)
{
CultureInfo cultureInfo = CultureInfo.GetCultureInfo("de-DE");
FlowDirection flowDirection = FlowDirection.LeftToRight;
#pragma warning disable CS0618 // Type or member is obsolete
FormattedText formattedText = new FormattedText(
text,
cultureInfo,
flowDirection,
new Typeface(fontFamily.Source),
fontSize,
Brushes.Black
);
#pragma warning restore CS0618 // Type or member is obsolete
return new Size(formattedText.Width, formattedText.Height);
}
}
}