using System; using System.Collections.Generic; using System.Drawing; using System.Linq; using System.Text.Json.Nodes; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; using System.Windows.Input; using Elwig.Helpers; using Elwig.Helpers.Billing; using Elwig.Models.Entities; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.ChangeTracking; using ScottPlot; using ScottPlot.Plottable; namespace Elwig.Windows { public partial class ChartWindow : AdministrationWindow { public readonly int Year; public readonly int AvNr; private ScatterPlot OechslePricePlotScatter; private MarkerPlot HighlightedPoint; private MarkerPlot PrimaryMarkedPoint; private MarkerPlot SecondaryMarkedPoint; private Tooltip Tooltip; private int LastHighlightedIndex = -1; private int HighlightedIndex = -1; private int PrimaryMarkedPointIndex = -1; private int SecondaryMarkedPointIndex = -1; private bool HoverChanged = false; private bool HoverActive = false; private const int MinOechsle = 50; private const int MaxOechsle = 140; private Graph? Graph; public ChartWindow(int year, int avnr) { InitializeComponent(); Year = year; AvNr = avnr; var v = Context.PaymentVariants.Find(year, avnr); Title = $"{v?.Name} - Lese {year} - Elwig"; ExemptInputs = new Control[] { GraphList, OechsleInput, PriceInput, FreeZoomInput, GradationLinesInput, TooltipInput }; } private void Window_Loaded(object sender, RoutedEventArgs evt) { LockInputs(); OechslePricePlot.IsEnabled = false; } private async Task RefreshGraphList() { await Context.PaymentVariants.LoadAsync(); await RefreshGraphListQuery(); } private async Task RefreshGraphListQuery(bool updateSort = false) { List paymentVars = await Context.PaymentVariants.Where(p => p.Year == Year && p.AvNr == AvNr).ToListAsync(); if (paymentVars.Count != 1) { return; } PaymentVar paymentVar = paymentVars[0]; var data = JsonNode.Parse(paymentVar.Data).AsObject(); var auszahlungsSorten = data["AuszahlungSorten"]?.AsObject(); if (auszahlungsSorten == null) { return; } var Graphs = auszahlungsSorten["Kurven"]?.AsArray(); if (Graphs == null) { return; } List GraphsList = new(); int i = 1; foreach (var graph in Graphs) { GraphsList.Add(new Graph("Oe", i, graph?.AsObject(), ParseContracts(auszahlungsSorten, i - 1), 50, 140)); i++; } ControlUtils.RenewItemsSource(GraphList, GraphsList, g => (g as Graph)?.Num); if (GraphsList.Count == 1) { GraphList.SelectedIndex = 0; } RefreshInputs(); } private String ParseContracts(JsonObject auszahlungsSorten, int num) { List contracts = new(); foreach (var sorte in auszahlungsSorten) { if (sorte.Key == "Kurven") continue; foreach (var attribut in sorte.Value.AsObject()) { foreach (var bindung in attribut.Value.AsObject()) { if ((int)bindung.Value.AsValue() == num) { contracts.Add($"{sorte.Key}/{attribut.Key}/{bindung.Key}"); } } } } return string.Join("\n", contracts.ToArray()); } private async Task RemoveGraph(int num) { List paymentVars = await Context.PaymentVariants.Where(p => p.Year == Year && p.AvNr == AvNr).ToListAsync(); if (paymentVars.Count != 1) { return false; } PaymentVar paymentVar = paymentVars[0]; var data = JsonNode.Parse(paymentVar.Data).AsObject(); var auszahlungsSorten = data["AuszahlungSorten"]?.AsObject(); if (auszahlungsSorten == null) { return false; } var Graphs = auszahlungsSorten["Kurven"]?.AsObject(); if (Graphs == null) { return false; } int i = 1; foreach (var graph in Graphs) { if (i == num) { Graphs.Remove(graph.Key); break; } i++; } foreach (var sorte in auszahlungsSorten) { if (sorte.Key == "Kurven") continue; foreach (var attribut in sorte.Value.AsObject()) { var bindungen = attribut.Value.AsObject(); foreach (var bindung in bindungen) { int v = (int)bindung.Value; if (v == num - 1) { bindungen.Remove(bindung.Key); } else if (v > num - 1) { bindungen[bindung.Key] = v - 1; } } } } EntityEntry? tr = null; try { paymentVar.Data = data.ToString(); tr = Context.Update(paymentVar); await Context.SaveChangesAsync(); } catch (Exception exc) { if (tr != null) await tr.ReloadAsync(); var str = "Der Eintrag konnte nicht in der Datenbank gelöscht werden!\n\n" + exc.Message; if (exc.InnerException != null) str += "\n\n" + exc.InnerException.Message; MessageBox.Show(str, "Graph löschen", MessageBoxButton.OK, MessageBoxImage.Error); } return true; } private void RefreshInputs(bool validate = false) { ResetPlot(); ClearInputStates(); if (GraphList.SelectedItem is Graph g) { EditButton.IsEnabled = true; DeleteButton.IsEnabled = true; EnableOptionButtons(); FillInputs(g); } else { EditButton.IsEnabled = false; DeleteButton.IsEnabled = false; DisableOptionButtons(); ClearOriginalValues(); ClearInputs(validate); ClearInputStates(); } GC.Collect(); } private void FillInputs(Graph g) { ClearOriginalValues(); Graph = (Graph)g.Clone(); GraphNumberInput.Text = Graph.Num.ToString(); if (Graph.Type == "oe") { OechsleGraphType_Input.IsChecked = true; } else if (Graph.Type == "kmw") { KmwGraphType_Input.IsChecked = true; } InitPlot(); OechslePricePlot.IsEnabled = true; FinishInputFilling(); } private void InitInputs() { GraphNumberInput.Text = (GraphList.Items.Count + 1).ToString(); OechsleGraphType_Input.IsChecked = true; FinishInputFilling(); } protected override async Task OnRenewContext() { await base.OnRenewContext(); await RefreshGraphList(); } private void InitPlot() { OechslePricePlotScatter = OechslePricePlot.Plot.AddScatter(Graph.DataX, Graph.DataY); OechslePricePlot.Configuration.DoubleClickBenchmark = false; OechslePricePlotScatter.LineColor = Color.Blue; OechslePricePlotScatter.MarkerColor = Color.Blue; OechslePricePlotScatter.MarkerSize = 9; //OechslePricePlot.Plot.XAxis.ManualTickSpacing(1); OechslePricePlot.Plot.YAxis.ManualTickSpacing(0.1); OechslePricePlot.Plot.SetAxisLimits(MinOechsle - 1, MaxOechsle + 1, -0.1, 2); OechslePricePlot.Plot.Layout(padding: 0); OechslePricePlot.Plot.XAxis2.Layout(padding: 0); OechslePricePlot.Plot.YAxis.Layout(padding: 0); OechslePricePlot.Plot.YAxis2.Layout(padding: 0); HighlightedPoint = OechslePricePlot.Plot.AddPoint(0, 0); HighlightedPoint.Color = Color.Red; HighlightedPoint.MarkerSize = 10; HighlightedPoint.MarkerShape = MarkerShape.openCircle; HighlightedPoint.IsVisible = false; PrimaryMarkedPoint = OechslePricePlot.Plot.AddPoint(0, 0); PrimaryMarkedPoint.Color = Color.Red; PrimaryMarkedPoint.MarkerSize = 6; PrimaryMarkedPoint.MarkerShape = MarkerShape.filledCircle; PrimaryMarkedPoint.IsVisible = false; SecondaryMarkedPoint = OechslePricePlot.Plot.AddPoint(0, 0); SecondaryMarkedPoint.Color = Color.Red; SecondaryMarkedPoint.MarkerSize = 6; SecondaryMarkedPoint.MarkerShape = MarkerShape.filledCircle; SecondaryMarkedPoint.IsVisible = false; OechslePricePlot.Refresh(); RefreshFreeZoom(); RefreshGradationLines(); } private void ResetPlot() { Graph = null; PrimaryMarkedPointIndex = -1; OechslePricePlot.Plot.Remove(OechslePricePlotScatter); OechslePricePlot.Plot.Clear(); OechslePricePlot.Reset(); OechslePricePlot.Refresh(); } private void ChangeMarker(MarkerPlot point, bool visible, double x = 0, double y = 0) { point.X = x; point.Y = y; point.IsVisible = visible; } private void FlattenGraph(int begin, int end, double value) { for (int i = begin; i <= end; i++) { Graph.DataY[i] = value; } OechslePricePlot.Render(); } private void LinearIncreaseGraph(int begin, int end, double inc) { for (int i = begin; i < end; i++) { Graph.DataY[i + 1] = Graph.DataY[i] + inc; } OechslePricePlot.Render(); } private void EnableActionButtons() { LeftFlatButton.IsEnabled = true; RightFlatButton.IsEnabled = true; LinearIncreaseButton.IsEnabled = true; } private void DisableActionButtons() { LeftFlatButton.IsEnabled = false; RightFlatButton.IsEnabled = false; InterpolateButton.IsEnabled = false; LinearIncreaseButton.IsEnabled = false; } private void FreeZoomInput_Changed(object sender, RoutedEventArgs evt) { RefreshFreeZoom(); } private void RefreshFreeZoom() { if (FreeZoomInput.IsChecked == true) { UnlockZoom(); } else { LockZoom(); } OechslePricePlot.Refresh(); } private void LockZoom() { OechslePricePlot.Plot.XAxis.SetBoundary(MinOechsle - 1, MaxOechsle + 1); OechslePricePlot.Plot.YAxis.SetBoundary(-0.1, 2); OechslePricePlot.Plot.XAxis.SetZoomOutLimit(MaxOechsle - MinOechsle + 2); OechslePricePlot.Plot.YAxis.SetZoomOutLimit(2.1); OechslePricePlot.Plot.SetAxisLimits(MinOechsle - 1, MaxOechsle + 1, -0.1, 2); } private void UnlockZoom() { OechslePricePlot.Plot.XAxis.SetBoundary(); OechslePricePlot.Plot.YAxis.SetBoundary(); OechslePricePlot.Plot.XAxis.SetZoomOutLimit((MaxOechsle - MinOechsle) * 1.5); OechslePricePlot.Plot.YAxis.SetZoomOutLimit(3.5); } private void EnableOptionButtons() { FreeZoomInput.IsEnabled = true; GradationLinesInput.IsEnabled = true; TooltipInput.IsEnabled = true; } private void DisableOptionButtons() { FreeZoomInput.IsEnabled = false; GradationLinesInput.IsEnabled = false; TooltipInput.IsEnabled = false; } private void GradationLinesInput_Changed(object sender, RoutedEventArgs evt) { RefreshGradationLines(); } private void RefreshGradationLines() { if (GradationLinesInput.IsChecked == true) { ShowGradationLines(); ShowLegend(); } else { HideGradationLines(); HideLegend(); } OechslePricePlot.Refresh(); } private void ShowGradationLines() { OechslePricePlot.Plot.AddVerticalLine(68, Color.Red, 2, label: "68 Oechsle (LDW)"); OechslePricePlot.Plot.AddVerticalLine(73, Color.Orange, 2, label: "73 Oechsle (QUW)"); OechslePricePlot.Plot.AddVerticalLine(84, Color.Green, 2, label: "84 Oechsle (KAB)"); } private void HideGradationLines() { OechslePricePlot.Plot.Clear(typeof(VLine)); } private void ShowLegend() { OechslePricePlot.Plot.Legend(true, Alignment.UpperRight); } private void HideLegend() { OechslePricePlot.Plot.Legend(false, Alignment.UpperRight); } private void OechsleInput_TextChanged(object sender, TextChangedEventArgs evt) { IntegerInput_TextChanged(sender, evt); bool success = int.TryParse(OechsleInput.Text, out int oechsle); SecondaryMarkedPointIndex = -1; ChangeMarker(SecondaryMarkedPoint, false); if (success) { if (oechsle >= MinOechsle && oechsle <= MaxOechsle) { PrimaryMarkedPointIndex = oechsle - MinOechsle; ChangeMarker(PrimaryMarkedPoint, true, Graph.DataX[PrimaryMarkedPointIndex], Graph.DataY[PrimaryMarkedPointIndex]); PriceInput.Text = Graph.DataY[PrimaryMarkedPointIndex].ToString(); if (IsEditing || IsCreating) EnableActionButtons(); OechslePricePlot.Render(); return; } } PrimaryMarkedPointIndex = -1; ChangeMarker(PrimaryMarkedPoint, false); DisableActionButtons(); PriceInput.Text = ""; OechslePricePlot.Render(); } private void PriceInput_TextChanged(object sender, RoutedEventArgs evt) { if (PrimaryMarkedPointIndex != -1) { bool success = Double.TryParse(PriceInput.Text, out double price); if (success) { Graph.DataY[PrimaryMarkedPointIndex] = price; PrimaryMarkedPoint.Y = price; SaveButton.IsEnabled = true; ResetButton.IsEnabled = true; OechslePricePlot.Refresh(); } } } private void LeftFlatButton_Click(object sender, RoutedEventArgs evt) { if (PrimaryMarkedPointIndex == -1) { return; } FlattenGraph(0, PrimaryMarkedPointIndex, Graph.DataY[PrimaryMarkedPointIndex]); SaveButton.IsEnabled = true; ResetButton.IsEnabled = true; } private void RightFlatButton_Click(object sender, RoutedEventArgs evt) { if (PrimaryMarkedPointIndex == -1) { return; } FlattenGraph(PrimaryMarkedPointIndex, Graph.DataY.Length - 1, Graph.DataY[PrimaryMarkedPointIndex]); SaveButton.IsEnabled = true; ResetButton.IsEnabled = true; } private void InterpolateButton_Click(object sender, RoutedEventArgs evt) { int steps = Math.Abs(PrimaryMarkedPointIndex - SecondaryMarkedPointIndex); if (PrimaryMarkedPointIndex == -1 || SecondaryMarkedPointIndex == -1 || steps < 2) { return; } var (lowIndex, highIndex) = PrimaryMarkedPointIndex < SecondaryMarkedPointIndex ? (PrimaryMarkedPointIndex, SecondaryMarkedPointIndex): (SecondaryMarkedPointIndex, PrimaryMarkedPointIndex); double step = (Graph.DataY[highIndex] - Graph.DataY[lowIndex]) / steps; for (int i = lowIndex; i < highIndex - 1; i++) { Graph.DataY[i + 1] = Math.Round(Graph.DataY[i] + step, 4); // TODO richtig runden } SaveButton.IsEnabled = true; ResetButton.IsEnabled = true; } private void LinearIncreaseButton_Click(object sender, RoutedEventArgs e) { if (PrimaryMarkedPointIndex == -1) { return; } double? priceIncrease = Utils.ShowLinearPriceIncreaseDialog(); if (priceIncrease == null) { return; } LinearIncreaseGraph(PrimaryMarkedPointIndex, Graph.DataY.Length - 1, priceIncrease.Value); SaveButton.IsEnabled = true; ResetButton.IsEnabled = true; } private void OechslePricePlot_MouseDown(object sender, MouseEventArgs e) { if (!IsCreating && GraphList.SelectedItem == null) { return; } if (HoverActive) { if ((IsEditing || IsCreating) && Keyboard.IsKeyDown(Key.LeftCtrl)) { if (PrimaryMarkedPointIndex == -1) { return; } SecondaryMarkedPointIndex = HighlightedIndex; ChangeMarker(SecondaryMarkedPoint, true, Graph.DataX[SecondaryMarkedPointIndex], Graph.DataY[SecondaryMarkedPointIndex]); InterpolateButton.IsEnabled = true; return; } PrimaryMarkedPointIndex = HighlightedIndex; ChangeMarker(PrimaryMarkedPoint, true, Graph.DataX[PrimaryMarkedPointIndex], Graph.DataY[PrimaryMarkedPointIndex]); OechsleInput.Text = Graph.DataX[HighlightedIndex].ToString(); PriceInput.Text = Graph.DataY[HighlightedIndex].ToString(); if (IsEditing || IsCreating) { EnableActionButtons(); } } else { PrimaryMarkedPointIndex = -1; SecondaryMarkedPointIndex = -1; ChangeMarker(PrimaryMarkedPoint, false); ChangeMarker(SecondaryMarkedPoint, false); OechsleInput.Text = ""; PriceInput.Text = ""; DisableActionButtons(); } } private void OechslePricePlot_MouseMove(object sender, MouseEventArgs e) { if (!IsCreating && GraphList.SelectedItem == null) { return; } (double mouseCoordX, double mouseCoordY) = OechslePricePlot.GetMouseCoordinates(); double xyRatio = OechslePricePlot.Plot.XAxis.Dims.PxPerUnit / OechslePricePlot.Plot.YAxis.Dims.PxPerUnit; (double pointX, double pointY, int pointIndex) = OechslePricePlotScatter.GetPointNearest(mouseCoordX, mouseCoordY, xyRatio); (double mousePixelX, double mousePixelY) = OechslePricePlot.GetMousePixel(); (double pointPixelX, double pointPixelY) = OechslePricePlot.Plot.GetPixel(pointX, pointY); HighlightedIndex = LastHighlightedIndex; if (Math.Abs(mousePixelX - pointPixelX) < 3 && Math.Abs(mousePixelY - pointPixelY) < 3) { ChangeMarker(HighlightedPoint, true, pointX, pointY); HighlightedPoint.IsVisible = true; HoverChanged = true ^ HoverActive; HoverActive = true; } else { ChangeMarker(HighlightedPoint, false); HoverChanged= false ^ HoverActive; HoverActive= false; OechslePricePlot.Plot.Remove(Tooltip); OechslePricePlot.Render(); } if (LastHighlightedIndex != HighlightedIndex || HoverChanged) { OechslePricePlot.Plot.Remove(Tooltip); if (TooltipInput.IsChecked == true) { Tooltip = OechslePricePlot.Plot.AddTooltip($"Oechsle: {pointX:N2}, Preis: {Math.Round(pointY, 4)})", pointX, pointY); } LastHighlightedIndex = pointIndex; HoverChanged = false; OechslePricePlot.Render(); } } override protected void UpdateButtons() { if (!IsEditing && !IsCreating) return; bool ch = HasChanged, v = IsValid; } private void DisableNewEditDeleteButtons() { NewButton.IsEnabled = false; EditButton.IsEnabled = false; DeleteButton.IsEnabled = false; } private void EnableNewEditDeleteButtons() { NewButton.IsEnabled = true; EditButton.IsEnabled = GraphList.SelectedItem != null; DeleteButton.IsEnabled = GraphList.SelectedItem != null; } private void ShowSaveResetCancelButtons() { SaveButton.IsEnabled = false; ResetButton.IsEnabled = false; CancelButton.IsEnabled = true; SaveButton.Visibility = Visibility.Visible; ResetButton.Visibility = Visibility.Visible; CancelButton.Visibility = Visibility.Visible; } private void HideSaveResetCancelButtons() { SaveButton.IsEnabled = false; ResetButton.IsEnabled = false; CancelButton.IsEnabled = false; SaveButton.Visibility = Visibility.Hidden; ResetButton.Visibility = Visibility.Hidden; CancelButton.Visibility = Visibility.Hidden; } private void ShowNewEditDeleteButtons() { EnableNewEditDeleteButtons(); NewButton.Visibility = Visibility.Visible; EditButton.Visibility = Visibility.Visible; DeleteButton.Visibility = Visibility.Visible; } private void HideNewEditDeleteButtons() { DisableNewEditDeleteButtons(); NewButton.Visibility = Visibility.Hidden; EditButton.Visibility = Visibility.Hidden; DeleteButton.Visibility = Visibility.Hidden; } private void NewButton_Click(object sender, RoutedEventArgs e) { IsCreating = true; GraphList.IsEnabled = false; GraphList.SelectedItem = null; HideNewEditDeleteButtons(); ShowSaveResetCancelButtons(); UnlockInputs(); PriceInput.IsReadOnly = false; OechsleInput.IsReadOnly = false; InitInputs(); FillInputs(new Graph(GraphList.Items.Count + 1, MinOechsle, MaxOechsle)); EnableOptionButtons(); } private void EditButton_Click(object sender, RoutedEventArgs e) { if (GraphList.SelectedItem == null) { return; } IsEditing = true; GraphList.IsEnabled = false; HideNewEditDeleteButtons(); ShowSaveResetCancelButtons(); UnlockInputs(); PriceInput.IsReadOnly = false; OechsleInput.IsReadOnly = false; if (PrimaryMarkedPointIndex != -1) EnableActionButtons(); } private async void DeleteButton_Click(object sender, RoutedEventArgs e) { Graph g = (Graph)GraphList.SelectedItem; if (g == null) return; var r = MessageBox.Show( $"Soll der Graph {g.Num} (verwendet in folgenden Verträgen: {g.Contracts}) wirklich unwiderruflich gelöscht werden?", "Graph löschen", MessageBoxButton.YesNo, MessageBoxImage.Warning, MessageBoxResult.No); if (r == MessageBoxResult.Yes) { bool success = await RemoveGraph(g.Num); if (!success) { MessageBox.Show("Der Graph konnte nicht gelöscht werden", "Graph löschen", MessageBoxButton.OK, MessageBoxImage.Error); } await RefreshGraphList(); } } private async void SaveButton_Click(object sender, RoutedEventArgs e) { int? index = await UpdateGraph(Graph); if (index == null) { MessageBox.Show("Der Graph konnte nicht gespeichert werden", "Graph speichern", MessageBoxButton.OK, MessageBoxImage.Error); } IsEditing = false; IsCreating = false; GraphList.IsEnabled = true; HideSaveResetCancelButtons(); ShowNewEditDeleteButtons(); LockInputs(); PriceInput.IsReadOnly = true; OechsleInput.IsReadOnly = true; await RefreshGraphList(); GraphList.SelectedIndex = index.Value; } private async Task UpdateGraph(Graph g) { List paymentVars = await Context.PaymentVariants.Where(p => p.Year == Year && p.AvNr == AvNr).ToListAsync(); if (paymentVars.Count != 1) { return null; } PaymentVar paymentVar = paymentVars[0]; var data = JsonNode.Parse(paymentVar.Data).AsObject(); var auszahlungsSorten = data["AuszahlungSorten"]; if (auszahlungsSorten == null) { return null; } var Graphs = auszahlungsSorten["Kurven"].AsArray(); if (Graphs == null) { return null; } if (IsEditing) { Graphs[g.Num - 1] = g.ToJson(); } else if(IsCreating) { Graphs.Add(g.ToJson()); } else { return null; } EntityEntry? tr = null; try { paymentVar.Data = data.ToString(); tr = Context.Update(paymentVar); await Context.SaveChangesAsync(); } catch (Exception exc) { if (tr != null) await tr.ReloadAsync(); var str = "Der Eintrag konnte nicht in der Datenbank gelöscht werden!\n\n" + exc.Message; if (exc.InnerException != null) str += "\n\n" + exc.InnerException.Message; MessageBox.Show(str, "Graph löschen", MessageBoxButton.OK, MessageBoxImage.Error); } return g.Num - 1; } private void ResetButton_Click(object sender, RoutedEventArgs e) { if (IsEditing) { RefreshInputs(); } else if (IsCreating) { InitInputs(); } UpdateButtons(); } private void CancelButton_Click(object sender, RoutedEventArgs e) { IsEditing = false; IsCreating = false; GraphList.IsEnabled = true; HideSaveResetCancelButtons(); ShowNewEditDeleteButtons(); DisableActionButtons(); RefreshInputs(); PriceInput.Text = ""; OechsleInput.Text = ""; ClearInputStates(); LockInputs(); PriceInput.IsReadOnly = true; OechsleInput.IsReadOnly = true; } private void GraphList_SelectionChanged(object sender, System.Windows.Controls.SelectionChangedEventArgs e) { RefreshInputs(); //var x = OechslePricePlot.Plot.GetPlottables().OfType(); //MessageBox.Show($"SelectionChanged\nLength: {x.ToList().Count}, Ys: {string.Join(", ", ((ScatterPlot)x.First()).Ys)}"); } private void PriceInput_LostFocus(object sender, RoutedEventArgs e) { } private void OechsleInput_LostFocus(object sender, RoutedEventArgs e) { } private void GraphNumberInput_TextChanged(object sender, TextChangedEventArgs e) { } private void GraphNumberInput_LostFocus(object sender, RoutedEventArgs e) { } } }