diff --git a/ULogViewer/Controls/SessionView.axaml.LogChart.cs b/ULogViewer/Controls/SessionView.axaml.LogChart.cs index 2c43b5d4..b1344eb9 100644 --- a/ULogViewer/Controls/SessionView.axaml.LogChart.cs +++ b/ULogViewer/Controls/SessionView.axaml.LogChart.cs @@ -146,7 +146,7 @@ public LvcSize Measure(Chart chart) // Tool tip of log chart. - class LogChartToolTip(SessionView view) : IChartTooltip + class LogChartToolTip(SessionView view, bool isXAxisInverted) : IChartTooltip { // Fields. StackPanel? container; @@ -166,6 +166,9 @@ public void Hide(Chart chart) } } + // Whether X-axis is inverted or not. + public bool IsXAxisInverted { get; } = isXAxisInverted; + // Show public void Show(IEnumerable foundPoints, Chart chart) { @@ -211,7 +214,7 @@ public void Show(IEnumerable foundPoints, Chart(); + ChartPoint[] logChartPointerDownData = []; Point? logChartPointerDownPosition; readonly Stopwatch logChartPointerDownWatch = new(); readonly ObservableList logChartSeries = new(); @@ -345,6 +348,7 @@ public override void InitializeTask(SkiaSharpDrawingContext drawingContext) { CrosshairSnapEnabled = true, }; + (double?, double?)? logChartXAxisLimitToKeep; double logChartXCoordinateScaling = 1.0; readonly Axis logChartYAxis = new() { @@ -378,9 +382,6 @@ void AttachToLogChart(LogChartViewModel viewModel) this.areLogChartAxesReady = false; this.UpdateLogChartAxes(); this.UpdateLogChartPanelVisibility(); - - // start animation later - this.startLogChartAnimationsAction.Reschedule(InitLogChartAnimationDelay); } @@ -439,6 +440,7 @@ ISeries CreateLogChartSeries(LogChartViewModel viewModel, DisplayableLogChartSer // create series var chartType = viewModel.ChartType; + var isInverted = viewModel.IsXAxisInverted; switch (chartType) { case LogChartType.ValueStatisticBars: @@ -454,7 +456,7 @@ ISeries CreateLogChartSeries(LogChartViewModel viewModel, DisplayableLogChartSer Rx = 0, Ry = 0, Tag = series, - Values = series.Values, + Values = isInverted ? series.Values.Reverse() : series.Values, XToolTipLabelFormatter = chartType == LogChartType.ValueStatisticBars ? null : this.GetLogChartXToolTipLabel, YToolTipLabelFormatter = p => this.GetLogChartYToolTipLabel(series, p, false), }; @@ -487,7 +489,7 @@ ISeries CreateLogChartSeries(LogChartViewModel viewModel, DisplayableLogChartSer IsAntialias = true, }, Tag = series, - Values = series.Values, + Values = isInverted ? series.Values.Reverse() : series.Values, XToolTipLabelFormatter = this.GetLogChartXToolTipLabel, YToolTipLabelFormatter = p => this.GetLogChartYToolTipLabel(series, p, false), }; @@ -503,7 +505,7 @@ ISeries CreateLogChartSeries(LogChartViewModel viewModel, DisplayableLogChartSer Rx = 0, Ry = 0, Tag = series, - Values = series.Values, + Values = isInverted ? series.Values.Reverse() : series.Values, XToolTipLabelFormatter = this.GetLogChartXToolTipLabel, YToolTipLabelFormatter = p => this.GetLogChartYToolTipLabel(series, p, false), }; @@ -560,7 +562,7 @@ or LogChartType.ValueCurvesWithDataPoints }, }, Tag = series, - Values = series.Values, + Values = isInverted ? series.Values.Reverse() : series.Values, XToolTipLabelFormatter = this.GetLogChartXToolTipLabel, YToolTipLabelFormatter = p => this.GetLogChartYToolTipLabel(series, p, false), }; @@ -677,7 +679,7 @@ string GetLogChartSeriesDisplayName(DisplayableLogChartSeriesSource source) // Get X label of tool tip of log chart. string GetLogChartXToolTipLabel(ChartPoint point) => this.GetLogChartXToolTipLabel(point.Model); - string GetLogChartXToolTipLabel(ChartPoint point) + string GetLogChartXToolTipLabel(ChartPoint point, bool isInverted) { if (point.Context.Series is not ICartesianSeries cartesianSeries || cartesianSeries.Tag is not DisplayableLogChartSeries series) @@ -685,6 +687,8 @@ string GetLogChartXToolTipLabel(ChartPoint point) return ""; } var index = (int)(point.Coordinate.SecondaryValue / this.logChartXCoordinateScaling + 0.5); + if (isInverted) + index = series.Values.Count - index - 1; if (index < 0 || index >= series.Values.Count) return ""; return this.GetLogChartXToolTipLabel(series.Values[index]); @@ -908,7 +912,7 @@ void OnLogChartDataClick(ChartPoint[] points) // Called when pointer moved on log chart. void OnLogChartPointerMoved(object? sender, PointerEventArgs e) { - if (this.logChartPointerDownPosition.HasValue && this.logChartPointerDownData.Length > 0) + if (this.logChartPointerDownPosition.HasValue && this.logChartPointerDownData.IsNotEmpty()) { var downPosition = this.logChartPointerDownPosition.Value; var position = e.GetPosition(this.logChart); @@ -917,7 +921,7 @@ void OnLogChartPointerMoved(object? sender, PointerEventArgs e) var distance = Math.Sqrt(diffX * diffX + diffY * diffY); if (distance >= PointerDistanceToDropClickEvent) { - this.logChartPointerDownData = Array.Empty(); + this.logChartPointerDownData = []; this.logChartPointerDownPosition = null; this.logChartPointerDownWatch.Reset(); } @@ -938,6 +942,13 @@ void OnLogChartPointerPressed(object? sender, PointerPressedEventArgs e) } + // Called when pointer wheel changed on log chart. + void OnLogChartPointerWheelChanged(object? sender, PointerWheelEventArgs e) + { + // + } + + // Called when pointer released on log chart. void OnLogChartPointerReleased(object? sender, PointerReleasedEventArgs e) { @@ -950,18 +961,18 @@ void OnLogChartPointerReleased(object? sender, PointerReleasedEventArgs e) if (this.isLogChartDoubleTapped) { this.isLogChartDoubleTapped = false; - this.logChartPointerDownData = Array.Empty(); this.logChartPointerDownPosition = null; this.logChartPointerDownWatch.Reset(); - this.SynchronizationContext.PostDelayed(this.ResetLogChartZoom, 100); + if (this.logChartXAxis.MinLimit.HasValue || this.logChartXAxis.MaxLimit.HasValue) + this.SynchronizationContext.Post(this.ResetLogChartZoom); return; } // single click - if (this.logChartPointerDownData.Length > 0) + if (this.logChartPointerDownData.IsNotEmpty()) { var data = this.logChartPointerDownData; - this.logChartPointerDownData = Array.Empty(); + this.logChartPointerDownData = []; if (this.logChartPointerDownPosition.HasValue) { var downPosition = this.logChartPointerDownPosition.Value; @@ -972,6 +983,9 @@ void OnLogChartPointerReleased(object? sender, PointerReleasedEventArgs e) this.logChartPointerDownPosition = null; if (distance < PointerDistanceToDropClickEvent && this.logChartPointerDownWatch.ElapsedMilliseconds < DurationToDropClickEvent) this.OnLogChartDataClick(data); + var minLimit = this.logChartXAxis.MinLimit; + var maxLimit = this.logChartXAxis.MaxLimit; + this.logChartXAxisLimitToKeep = (minLimit, maxLimit); // [Workaround] Prevent changing limit by click } this.logChartPointerDownWatch.Reset(); } @@ -1327,7 +1341,9 @@ TimeSpan SelectLogChartSeriesAnimationSpeed() return this.SelectLogChartSeriesAnimationSpeed(session.LogChart); } TimeSpan SelectLogChartSeriesAnimationSpeed(LogChartViewModel viewModel) => - default; + this.startLogChartAnimationsAction.IsScheduled + ? TimeSpan.Zero + : this.Application.FindResourceOrDefault("TimeSpan/Animation.Fast", TimeSpan.FromMilliseconds(200)); // Select color for series. @@ -1550,10 +1566,10 @@ public void ShowLogChartTypeMenu() this.logChartTypeMenu.DataContext = (this.DataContext as Session)?.LogChart.ChartType; this.logChartTypeMenu.Open(this.logChartTypeButton); } - - - // Start all animations og log chart. - void StartLogChartAnimations() + + + // Update all animations of log chart. + void UpdateLogChartAnimations() { var animationSpeed = this.SelectLogChartSeriesAnimationSpeed(); foreach (var series in this.logChartSeries) @@ -1588,7 +1604,7 @@ void UpdateLogChartAxes() var viewModel = (this.DataContext as Session)?.LogChart; var chartType = viewModel?.ChartType ?? LogChartType.None; var axisType = viewModel?.XAxisType ?? LogChartXAxisType.None; - axis.IsInverted = (this.DataContext as Session)?.LogChart.IsXAxisInverted ?? false; + axis.IsInverted = viewModel?.IsXAxisInverted ?? false; if (axisType != LogChartXAxisType.None && chartType.IsDirectNumberValueSeriesType()) { var textPaint = textBrush.Let(brush => @@ -1603,6 +1619,12 @@ void UpdateLogChartAxes() var doubleIndex = (value / this.logChartXCoordinateScaling); var intIndex = (int)(doubleIndex + 0.5); var logs = session.Logs; + if (axis.IsInverted) + { + var diff = doubleIndex - intIndex; + intIndex = (logs.Count - intIndex - 1); + doubleIndex = intIndex - diff; + } if (Math.Abs(intIndex - doubleIndex) < 0.001 && intIndex >= 0 && intIndex < logs.Count) return this.GetLogChartXToolTipLabel(logs[intIndex], axisType == LogChartXAxisType.SimpleTimestamp, true); return ""; @@ -1656,6 +1678,8 @@ void UpdateLogChartAxes() axis.TextSize = (float)axisFontSize; axis.ZeroPaint = new SolidColorPaint(textPaint.Color, (float)axisWidth); }); + if ((this.logChart.Tooltip as LogChartToolTip)?.IsXAxisInverted != this.logChartXAxis.IsInverted) + this.logChart.Tooltip = new LogChartToolTip(this, this.logChartXAxis.IsInverted); this.areLogChartAxesReady = true; } @@ -1741,6 +1765,27 @@ void UpdateLogChartXAxisLimit() var axis = this.logChartXAxis; var minLimit = axis.MinLimit ?? double.NaN; var maxLimit = axis.MaxLimit ?? double.NaN; + if (this.logChartXAxisLimitToKeep.HasValue) + { + var limit = this.logChartXAxisLimitToKeep.Value; + this.logChartXAxisLimitToKeep = null; + if (limit.Item1.HasValue) + { + if (double.IsNaN(minLimit) || Math.Abs(minLimit - limit.Item1.Value) >= 0.001) + axis.MinLimit = limit.Item1.Value; + } + else if (double.IsFinite(minLimit)) + axis.MinLimit = null; + if (limit.Item2.HasValue) + { + if (double.IsNaN(maxLimit) || Math.Abs(maxLimit - limit.Item2.Value) >= 0.001) + axis.MaxLimit = limit.Item2.Value; + } + else if (double.IsFinite(maxLimit)) + axis.MaxLimit = null; + minLimit = limit.Item1 ?? double.NaN; + maxLimit = limit.Item2 ?? double.NaN; + } if (!double.IsFinite(minLimit) && !double.IsFinite(maxLimit)) { this.SetValue(IsLogChartHorizontallyZoomedProperty, false); diff --git a/ULogViewer/Controls/SessionView.axaml.cs b/ULogViewer/Controls/SessionView.axaml.cs index 40ee47d8..c7b23d72 100644 --- a/ULogViewer/Controls/SessionView.axaml.cs +++ b/ULogViewer/Controls/SessionView.axaml.cs @@ -551,9 +551,9 @@ public SessionView() it.AddHandler(PointerMovedEvent, this.OnLogChartPointerMoved, RoutingStrategies.Tunnel); it.AddHandler(PointerPressedEvent, this.OnLogChartPointerPressed, RoutingStrategies.Tunnel); it.AddHandler(PointerReleasedEvent, this.OnLogChartPointerReleased, RoutingStrategies.Tunnel); + it.AddHandler(PointerWheelChangedEvent, this.OnLogChartPointerWheelChanged, RoutingStrategies.Tunnel); it.SizeChanged += (_, e) => this.OnLogChartSizeChanged(e); it.Legend = new LogChartLegend(this); - it.Tooltip = new LogChartToolTip(this); this.logChartXAxis.PropertyChanged += this.OnLogChartAxisPropertyChanged; this.logChartYAxis.PropertyChanged += this.OnLogChartAxisPropertyChanged; }); @@ -1049,7 +1049,7 @@ public SessionView() this.scrollToLatestLogAnalysisResultAction.Cancel(); this.ScrollToLatestLogAnalysisResult(true); }); - this.startLogChartAnimationsAction = new(this.StartLogChartAnimations); + this.startLogChartAnimationsAction = new(this.UpdateLogChartAnimations); this.updateCanEditCurrentScriptLogDataSourceProviderAction = new(() => { var session = this.DataContext as Session; @@ -3007,6 +3007,7 @@ protected override void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e }); } }); + window.SizeChanged += this.OnWindowSizeChanged; }); // attach to control fonts @@ -3160,7 +3161,11 @@ protected override void OnDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs this.areInitDialogsClosedObserverToken = this.areInitDialogsClosedObserverToken.DisposeAndReturnNull(); this.hasDialogsObserverToken = this.hasDialogsObserverToken.DisposeAndReturnNull(); this.isActiveObserverToken = this.isActiveObserverToken.DisposeAndReturnNull(); - this.attachedWindow = null; + if (this.attachedWindow is not null) + { + this.attachedWindow.SizeChanged -= this.OnWindowSizeChanged; + this.attachedWindow = null; + } // call base base.OnDetachedFromLogicalTree(e); @@ -4619,6 +4624,15 @@ void OnToolBarPointerReleased(object? sender, PointerReleasedEventArgs e) } + // Called when size of attached window changed. + void OnWindowSizeChanged(object? sender, SizeChangedEventArgs e) + { + // pause animation of log chart temporarily + this.startLogChartAnimationsAction.Reschedule(ResumeLogChartAnimationDelay); + this.UpdateLogChartAnimations(); + } + + /// /// Perform full GC. /// diff --git a/ULogViewer/ViewModels/LogChartViewModel.cs b/ULogViewer/ViewModels/LogChartViewModel.cs index 2b933b9e..dbe7112e 100644 --- a/ULogViewer/ViewModels/LogChartViewModel.cs +++ b/ULogViewer/ViewModels/LogChartViewModel.cs @@ -271,6 +271,7 @@ void ApplyLogChartType(LogChartType chartType) if (this.filteredLogsSeriesGenerator is not null) this.filteredLogsSeriesGenerator.LogChartType = chartType; this.markedLogsSeriesGenerator.LogChartType = chartType; + this.UpdateAxisInversionState(chartType); } @@ -644,6 +645,7 @@ protected override void OnLogProfilePropertyChanged(PropertyChangedEventArgs e) if (this.filteredLogsSeriesGenerator is not null) this.filteredLogsSeriesGenerator.SourceLogComparer = logComparer; this.markedLogsSeriesGenerator.SourceLogComparer = logComparer; + this.UpdateAxisInversionState(this.GetValue(ChartTypeProperty)); }); break; } @@ -1151,6 +1153,23 @@ void ToggleVisibleSeriesSource(DisplayableLogChartSeriesSource? source) public ICommand ToggleVisibleSeriesSourceCommand { get; } + // Update inversion state of axes. + void UpdateAxisInversionState(LogChartType chartType) + { + if (this.LogProfile is not { } logProfile || logProfile.SortDirection == SortDirection.Ascending) + { + this.ResetValue(IsXAxisInvertedProperty); + return; + } + this.SetValue(IsXAxisInvertedProperty, chartType switch + { + LogChartType.None + or LogChartType.ValueStatisticBars => false, + _ => true, + }); + } + + // Update whether setting type of log chart is available or not. void UpdateCanSetChartType() {