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.Controls; 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 : ContextWindow { public readonly int Year; public readonly int AvNr; private readonly PaymentVar PaymentVar; private ScatterPlot DataPlot; private ScatterPlot? GebundenPlot; private MarkerPlot HighlightedPointPlot; private MarkerPlot PrimaryMarkedPointPlot; private MarkerPlot SecondaryMarkedPointPlot; private Tooltip TooltipPlot; private (Graph? graph, int index) LastHighlighted = (null, -1); private (Graph? graph, int index) Highlighted = (null, -1); private Graph? ActiveGraph = null; private int PrimaryMarkedPoint = -1; private int SecondaryMarkedPoint = -1; private bool HoverChanged = false; private bool HoverActive = false; private const int MinOechsle = 50; private const int MaxOechsle = 140; private List GraphEntries = []; private GraphEntry? SelectedGraphEntry; public ChartWindow(int year, int avnr) { InitializeComponent(); Year = year; AvNr = avnr; PaymentVar = Context.PaymentVariants.Find(year, avnr) ?? throw new ArgumentException("PaymentVar not found"); Title = $"{PaymentVar?.Name} - Lese {year} - Elwig"; } private void Window_Loaded(object sender, RoutedEventArgs evt) { OechslePricePlot.IsEnabled = false; } private async Task RefreshGraphList() { await Context.PaymentVariants.LoadAsync(); await RefreshGraphListQuery(); } private async Task RefreshGraphListQuery() { var attrVariants = Context.DeliveryParts .Where(d => d.Year == Year) .Select(d => $"{d.SortId}{d.AttrId}") .Distinct() .ToList() .Union(Context.WineVarieties.Select(v => v.SortId)) .Order() .ToList(); var data = EditBillingData.FromJson(PaymentVar.Data, attrVariants); GraphEntries.AddRange(data.GetPaymentGraphEntries()); GraphEntries.AddRange(data.GetQualityGraphEntries()); //var contracts = ContractSelection.GetContractsForYear(Context, Year).DistinctBy(c => c.Listing).Order().ToList(); //ControlUtils.RenewItemsSource(ContractInput, contracts, g => (g as GraphEntry)?.Id); ControlUtils.RenewItemsSource(ContractInput, attrVariants, g => g); ControlUtils.RenewItemsSource(GraphList, GraphEntries, g => (g as GraphEntry)?.Id, null, ControlUtils.RenewSourceDefault.IfOnly); RefreshInputs(); } private string ParseContracts(JsonObject auszahlungsSorten, int num) { return ""; } private void RefreshInputs(bool validate = false) { ResetPlot(); if (!PaymentVar.TestVariant) { AddButton.IsEnabled = false; CopyButton.IsEnabled = false; DeleteButton.IsEnabled = false; DisableUnitTextBox(OechsleInput); DisableUnitTextBox(PriceInput); GebundenTypeFixed.IsEnabled = false; GebundenTypeGraph.IsEnabled = false; GebundenTypeNone.IsEnabled = false; ContractInput.IsEnabled = false; EnableOptionButtons(); FillInputs(); } else if (SelectedGraphEntry != null) { CopyButton.IsEnabled = true; DeleteButton.IsEnabled = true; //EnableUnitTextBox(OechsleInput); GebundenTypeFixed.IsEnabled = true; GebundenTypeGraph.IsEnabled = true; GebundenTypeNone.IsEnabled = true; ContractInput.IsEnabled = true; EnableOptionButtons(); FillInputs(); } else { CopyButton.IsEnabled = false; DeleteButton.IsEnabled = false; DisableUnitTextBox(OechsleInput); DisableOptionButtons(); } GC.Collect(); } private void FillInputs() { GraphNum.Text = SelectedGraphEntry.Id.ToString(); if (SelectedGraphEntry.GebundenFlatBonus != null) { GebundenTypeFixed.IsChecked = true; } else if (SelectedGraphEntry.GebundenGraph != null) { GebundenTypeGraph.IsChecked = true; } else { GebundenTypeNone.IsChecked = true; ; } ControlUtils.SelectCheckComboBoxItems(ContractInput, SelectedGraphEntry.Contracts, i => (i as ContractSelection)?.Listing); InitPlot(); OechslePricePlot.IsEnabled = true; } protected override async Task OnRenewContext() { await RefreshGraphList(); } private void InitPlot() { if (SelectedGraphEntry?.GebundenGraph != null) { GebundenPlot = OechslePricePlot.Plot.AddScatter(SelectedGraphEntry.GebundenGraph.DataX, SelectedGraphEntry.GebundenGraph.DataY); GebundenPlot.LineColor = Color.Green; GebundenPlot.MarkerColor = Color.Green; GebundenPlot.MarkerSize = 9; } DataPlot = OechslePricePlot.Plot.AddScatter(SelectedGraphEntry.DataGraph.DataX, SelectedGraphEntry.DataGraph.DataY); DataPlot.LineColor = Color.Blue; DataPlot.MarkerColor = Color.Blue; DataPlot.MarkerSize = 9; if (SelectedGraphEntry.GebundenGraph == null) { ChangeActiveGraph(SelectedGraphEntry.DataGraph); } OechslePricePlot.RightClicked -= OechslePricePlot.DefaultRightClickEvent; OechslePricePlot.Configuration.DoubleClickBenchmark = false; //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); HighlightedPointPlot = OechslePricePlot.Plot.AddPoint(0, 0); HighlightedPointPlot.Color = Color.Red; HighlightedPointPlot.MarkerSize = 10; HighlightedPointPlot.MarkerShape = MarkerShape.openCircle; HighlightedPointPlot.IsVisible = false; PrimaryMarkedPointPlot = OechslePricePlot.Plot.AddPoint(0, 0); PrimaryMarkedPointPlot.Color = Color.Red; PrimaryMarkedPointPlot.MarkerSize = 6; PrimaryMarkedPointPlot.MarkerShape = MarkerShape.filledCircle; PrimaryMarkedPointPlot.IsVisible = false; SecondaryMarkedPointPlot = OechslePricePlot.Plot.AddPoint(0, 0); SecondaryMarkedPointPlot.Color = Color.Red; SecondaryMarkedPointPlot.MarkerSize = 6; SecondaryMarkedPointPlot.MarkerShape = MarkerShape.filledCircle; SecondaryMarkedPointPlot.IsVisible = false; OechslePricePlot.Refresh(); RefreshFreeZoom(); RefreshGradationLines(); } private void ResetPlot() { PrimaryMarkedPoint = -1; SecondaryMarkedPoint = -1; ChangeActiveGraph(null); HideGradationLines(); OechslePricePlot.Plot.Remove(DataPlot); OechslePricePlot.Plot.Remove(GebundenPlot); 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 LinearIncreaseGraph(int begin, int end, double inc) { } private void EnableActionButtons() { if (PaymentVar.TestVariant) { 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 && SelectedGraphEntry != null && !OechslePricePlot.Plot.GetPlottables().OfType().Any()) { ShowGradationLines(); ShowLegend(); } else if (GradationLinesInput.IsChecked == false) { 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) { if (ActiveGraph == null) { return; } bool success = int.TryParse(OechsleInput.Text, out int oechsle); SecondaryMarkedPoint = -1; ChangeMarker(SecondaryMarkedPointPlot, false); if (success) { if (oechsle >= MinOechsle && oechsle <= MaxOechsle) { PrimaryMarkedPoint = oechsle - MinOechsle; ChangeMarker(PrimaryMarkedPointPlot, true, ActiveGraph.GetOechsleAt(PrimaryMarkedPoint), ActiveGraph.GetPriceAt(PrimaryMarkedPoint)); PriceInput.Text = ActiveGraph.GetPriceAt(PrimaryMarkedPoint).ToString(); EnableActionButtons(); OechslePricePlot.Render(); EnableUnitTextBox(PriceInput); return; } } PrimaryMarkedPoint = -1; //ChangeActiveGraph(null); ChangeMarker(PrimaryMarkedPointPlot, false); DisableActionButtons(); PriceInput.Text = ""; DisableUnitTextBox(PriceInput); OechslePricePlot.Render(); DisableUnitTextBox(PriceInput); } private void PriceInput_TextChanged(object sender, TextChangedEventArgs evt) { if (PrimaryMarkedPoint != -1 && ActiveGraph != null) { bool success = Double.TryParse(PriceInput.Text, out double price); if (success) { ActiveGraph.SetPriceAt(PrimaryMarkedPoint, price); PrimaryMarkedPointPlot.Y = price; OechslePricePlot.Refresh(); } } } private void LeftFlatButton_Click(object sender, RoutedEventArgs evt) { if (PrimaryMarkedPoint == -1 || ActiveGraph == null) { return; } ActiveGraph.FlattenGraphLeft(PrimaryMarkedPoint); OechslePricePlot.Render(); } private void RightFlatButton_Click(object sender, RoutedEventArgs evt) { if (PrimaryMarkedPoint == -1 || ActiveGraph == null) { return; } ActiveGraph.FlattenGraphRight(PrimaryMarkedPoint); OechslePricePlot.Render(); } private void InterpolateButton_Click(object sender, RoutedEventArgs evt) { if (PrimaryMarkedPoint == SecondaryMarkedPoint || PrimaryMarkedPoint == -1 || SecondaryMarkedPoint == -1 || ActiveGraph == null) { return; } ActiveGraph.InterpolateGraph(PrimaryMarkedPoint, SecondaryMarkedPoint); OechslePricePlot.Render(); } private void LinearIncreaseButton_Click(object sender, RoutedEventArgs e) { if (PrimaryMarkedPoint == -1 || ActiveGraph == null) { return; } double? priceIncrease = Utils.ShowLinearPriceIncreaseDialog(); if (priceIncrease == null) { return; } ActiveGraph.LinearIncreaseGraphToEnd(PrimaryMarkedPoint, priceIncrease.Value); OechslePricePlot.Render(); } private void OechslePricePlot_MouseDown(object sender, MouseEventArgs e) { if (GraphList.SelectedItem == null) { return; } if (HoverActive) { if (PaymentVar.TestVariant && Keyboard.IsKeyDown(Key.LeftCtrl)) { if (PrimaryMarkedPoint == -1 || ActiveGraph == null || ActiveGraph != Highlighted.graph) { return; } SecondaryMarkedPoint = Highlighted.index; ChangeMarker(SecondaryMarkedPointPlot, true, ActiveGraph.GetOechsleAt(SecondaryMarkedPoint), ActiveGraph.GetPriceAt(SecondaryMarkedPoint)); InterpolateButton.IsEnabled = true; return; } PrimaryMarkedPoint = Highlighted.index; ChangeActiveGraph(Highlighted.graph); ChangeMarker(PrimaryMarkedPointPlot, true, ActiveGraph.GetOechsleAt(PrimaryMarkedPoint), ActiveGraph.GetPriceAt(PrimaryMarkedPoint)); OechsleInput.Text = Highlighted.graph.GetOechsleAt(Highlighted.index).ToString(); PriceInput.Text = Highlighted.graph.GetPriceAt(Highlighted.index).ToString(); EnableActionButtons(); } else { PrimaryMarkedPoint = -1; SecondaryMarkedPoint = -1; if (SelectedGraphEntry!.GebundenGraph != null) { ChangeActiveGraph(null); } ChangeMarker(PrimaryMarkedPointPlot, false); ChangeMarker(SecondaryMarkedPointPlot, false); OechsleInput.Text = ""; PriceInput.Text = ""; DisableUnitTextBox(PriceInput); DisableActionButtons(); } } private (double, double, int)? MouseOnPlot(ScatterPlot? plot) { if (plot == null) { return null; } (double mouseCoordX, double mouseCoordY) = OechslePricePlot.GetMouseCoordinates(); (double mousePixelX, double mousePixelY) = OechslePricePlot.GetMousePixel(); double xyRatio = OechslePricePlot.Plot.XAxis.Dims.PxPerUnit / OechslePricePlot.Plot.YAxis.Dims.PxPerUnit; (double pointX, double pointY, int pointIndex) = plot.GetPointNearest(mouseCoordX, mouseCoordY, xyRatio); (double pointPixelX, double pointPixelY) = OechslePricePlot.Plot.GetPixel(pointX, pointY); if (Math.Abs(mousePixelX - pointPixelX) < 3 && Math.Abs(mousePixelY - pointPixelY) < 3) { return (pointX, pointY, pointIndex); } else { return null; } } private void OechslePricePlot_MouseMove(object sender, MouseEventArgs e) { if (GraphList.SelectedItem == null) { return; } (double x, double y, int index)? mouseOnData = MouseOnPlot(DataPlot); (double x, double y , int index)? mouseOnGebunden = MouseOnPlot(GebundenPlot); Highlighted = LastHighlighted; if (mouseOnData != null) { ChangeMarker(HighlightedPointPlot, true, mouseOnData.Value.x, mouseOnData.Value.y); HighlightedPointPlot.IsVisible = true; HoverChanged = true ^ HoverActive; HoverActive = true; HandleTooltip(mouseOnData.Value.x, mouseOnData.Value.y, mouseOnData.Value.index, SelectedGraphEntry!.DataGraph); } else if (mouseOnGebunden != null) { ChangeMarker(HighlightedPointPlot, true, mouseOnGebunden.Value.x, mouseOnGebunden.Value.y); HighlightedPointPlot.IsVisible = true; HoverChanged = true ^ HoverActive; HoverActive = true; HandleTooltip(mouseOnGebunden.Value.x, mouseOnGebunden.Value.y, mouseOnGebunden.Value.index, SelectedGraphEntry!.GebundenGraph); } else { ChangeMarker(HighlightedPointPlot, false); HoverChanged = false ^ HoverActive; HoverActive = false; OechslePricePlot.Plot.Remove(TooltipPlot); OechslePricePlot.Render(); } } private void HandleTooltip(double pointX, double pointY, int pointIndex, Graph g) { if (LastHighlighted != Highlighted || HoverChanged) { OechslePricePlot.Plot.Remove(TooltipPlot); if (TooltipInput.IsChecked == true) { TooltipPlot = OechslePricePlot.Plot.AddTooltip($"Oechsle: {pointX:N2}, Preis: {Math.Round(pointY, 4)}€/kg)", pointX, pointY); } LastHighlighted = (g, pointIndex); HoverChanged = false; OechslePricePlot.Render(); } } private int GetMaxGraphId() { return GraphEntries.Count == 0 ? 0 : GraphEntries.Select(g => g.Id).Max(); } private void AddButton_Click(object sender, RoutedEventArgs e) { GraphEntry newGraphEntry = new(GetMaxGraphId() + 1, BillingData.CurveMode.Oe, MinOechsle, MaxOechsle); GraphEntries.Add(newGraphEntry); GraphList.Items.Refresh(); GraphList.SelectedItem = newGraphEntry; } private void CopyButton_Click(object sender, RoutedEventArgs e) { if (SelectedGraphEntry == null) return; GraphEntry newGraphEntry = SelectedGraphEntry.Copy(GetMaxGraphId() + 1); GraphEntries.Add(newGraphEntry); GraphList.Items.Refresh(); GraphList.SelectedItem = newGraphEntry; } private void DeleteButton_Click(object sender, RoutedEventArgs e) { if (SelectedGraphEntry == null) return; var r = MessageBox.Show( $"Soll der Graph {SelectedGraphEntry.Id} (verwendet in folgenden Verträgen: {SelectedGraphEntry.Contracts}) wirklich gelöscht werden?", "Graph löschen", MessageBoxButton.YesNo, MessageBoxImage.Warning, MessageBoxResult.No); if (r == MessageBoxResult.Yes) { GraphEntries.Remove(SelectedGraphEntry); GraphList.Items.Refresh(); } } private async void SaveButton_Click(object sender, RoutedEventArgs e) { await SaveGraphs(); } private async Task SaveGraphs() { var payment = new JsonObject(); var curves = new JsonArray(); foreach (var entry in GraphEntries) { curves.Add(entry.ToJson()); foreach (var contract in entry.Contracts) { payment[$"{contract.Variety?.SortId}/{contract.Attribute?.AttrId}"] = $"curve:{entry.Id}"; } } var data = new JsonObject { ["mode"] = "elwig", ["version"] = 1, ["payment"] = payment, ["curves"] = curves }; MessageBox.Show(data.ToJsonString()); EntityEntry? tr = null; try { PaymentVar.Data = data.ToJsonString(); tr = Context.Update(PaymentVar); await Context.SaveChangesAsync(); await App.HintContextChange(); } catch (Exception exc) { if (tr != null) await tr.ReloadAsync(); var str = "Der Eintrag konnte nicht in der Datenbank gespeichert werden!\n\n" + exc.Message; if (exc.InnerException != null) str += "\n\n" + exc.InnerException.Message; MessageBox.Show(str, "Graph speichern", MessageBoxButton.OK, MessageBoxImage.Error); } } private void EnableUnitTextBox(UnitTextBox u) { if (PaymentVar.TestVariant) { u.IsEnabled = true; u.TextBox.IsReadOnly = false; } } private void DisableUnitTextBox(UnitTextBox u) { u.IsEnabled = false; u.TextBox.IsReadOnly = true; } private void ChangeActiveGraph(Graph? g) { if (g != null && g == SelectedGraphEntry?.DataGraph) { EnableUnitTextBox(OechsleInput); ChangeLineWidth(DataPlot, 4); ChangeLineWidth(GebundenPlot, 1); } else if (g != null && g == SelectedGraphEntry?.GebundenGraph) { EnableUnitTextBox(OechsleInput); ChangeLineWidth(GebundenPlot, 4); ChangeLineWidth(DataPlot, 1); } else { DisableUnitTextBox(OechsleInput); DisableUnitTextBox(PriceInput); OechsleInput.Text = ""; PriceInput.Text = ""; ChangeLineWidth(DataPlot, 1); ChangeLineWidth(GebundenPlot, 1); } ActiveGraph = g; } private void ChangeLineWidth(ScatterPlot? p, double lineWidth) { if (p != null) { p.LineWidth = lineWidth; } } private void GraphList_SelectionChanged(object sender, SelectionChangedEventArgs e) { SelectedGraphEntry = (GraphEntry)GraphList.SelectedItem; 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 GebundenFlatBonus_TextChanged(object sender, TextChangedEventArgs e) { var r = Validator.CheckDecimal(GebundenFlatBonus.TextBox, true, 2, 8); if (r.IsValid) { SelectedGraphEntry?.SetGebundenFlatBonus(decimal.Parse(GebundenFlatBonus.Text)); } } private void ContractInput_Changed(object sender, RoutedEventArgs e) { var r = ContractInput.SelectedItems.Cast(); SelectedGraphEntry!.Contracts = r.ToList(); GraphList.Items.Refresh(); } private void GebundenType_Checked(object sender, RoutedEventArgs e) { if (SelectedGraphEntry == null) { DisableUnitTextBox(GebundenFlatBonus); return; } else if (GebundenTypeNone.IsChecked == true) { SelectedGraphEntry.SetGebundenFlatBonus(null); SelectedGraphEntry.RemoveGebundenGraph(); DisableUnitTextBox(GebundenFlatBonus); RefreshInputs(); } else if (GebundenTypeFixed.IsChecked == true) { SelectedGraphEntry.SetGebundenFlatBonus(0); SelectedGraphEntry.RemoveGebundenGraph(); EnableUnitTextBox(GebundenFlatBonus); RefreshInputs(); } else if (GebundenTypeGraph.IsChecked == true) { GebundenFlatBonus.Text = ""; SelectedGraphEntry.SetGebundenFlatBonus(null); SelectedGraphEntry.AddGebundenGraph(); DisableUnitTextBox(GebundenFlatBonus); RefreshInputs(); } } } }