сделай кнопку восстановить исходный масштаб, сделай кнопки undo redo в виде стрелочек в меню
<Window x:Class="TxtProcessorApp.GraphWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:oxy="http://oxyplot.org/wpf"
        Title="GraphWindow" Height="600" Width="800">
    <DockPanel>
        <!-- Панель для выбора графика -->
        <StackPanel Orientation="Horizontal" DockPanel.Dock="Top" Margin="10">
            <Label Content="Выберите график:" VerticalAlignment="Center" Margin="0,0,5,0"/>
            <ComboBox x:Name="ChartSelector" Width="250" SelectionChanged="ChartSelector_SelectionChanged">
                <ComboBoxItem Content=".txt Time UTC vs BASE-1"/>
                <ComboBoxItem Content=".txt Time UTC vs Noise"/>
                <ComboBoxItem Content=".magnete Долгота UTM vs Широта UTM (Альтитуда)"/>
                <ComboBoxItem Content=".magnete Долгота UTM vs Широта UTM (Shark)"/>
            </ComboBox>
        </StackPanel>

        <!-- Область для отображения графика -->
        <oxy:PlotView x:Name="MainPlotView" DockPanel.Dock="Top" Margin="10" Height="476"/>

        <!-- Панель кнопок Save, Cancel, Undo и Redo -->
        <StackPanel Orientation="Horizontal" HorizontalAlignment="Right" DockPanel.Dock="Bottom" Margin="10">
            <Button x:Name="UndoButton" Content="Отмена" Width="100" Margin="0,0,10,0" Click="UndoButton_Click" IsEnabled="False"/>
            <Button x:Name="RedoButton" Content="Повторить" Width="100" Margin="0,0,10,0" Click="RedoButton_Click" IsEnabled="False"/>
            <Button Content="Сохранить" Width="100" Margin="0,0,10,0" Click="SaveButton_Click"/>
            <Button Content="Отмена" Width="100" Click="CancelButton_Click"/>
        </StackPanel>
    </DockPanel>
</Window>

using OxyPlot;
using OxyPlot.Axes;
using OxyPlot.Series;
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;

namespace TxtProcessorApp
{
    /// <summary>
    /// Класс, представляющий GraphWindow с поддержкой Undo и Redo для удаления точек.
    /// Изменения вносятся только в копии данных и сохраняются по подтверждению пользователя.
    /// </summary>
    public partial class GraphWindow : Window
    {
        public PlotModel TxtBase1Model { get; private set; }
        public PlotModel TxtNoiseModel { get; private set; }
        public PlotModel MagneteAltitudeModel { get; private set; }
        public PlotModel MagneteSharkModel { get; private set; }

        // Сопоставление ScatterSeries с соответствующими DataRow
        private Dictionary<ScatterSeries, List<DataRow>> seriesDataRows = new Dictionary<ScatterSeries, List<DataRow>>();

        // Стэки для Undo и Redo
        private readonly Stack<GraphAction> undoStack = new Stack<GraphAction>();
        private readonly Stack<GraphAction> redoStack = new Stack<GraphAction>();

        // Ссылки на выбранную серию и точку
        private ScatterSeries selectedSeries = null;
        private ScatterPoint selectedPoint = null;

        // Копии DataTable для работы в GraphWindow
        private DataTable txtDataCopy;
        private DataTable magneteDataCopy;

        /// <summary>
        /// Публичные свойства для доступа к копиям DataTable
        /// </summary>
        public DataTable TxtDataCopy => txtDataCopy;
        public DataTable MagneteDataCopy => magneteDataCopy;

        /// <summary>
        /// Конструктор класса.
        /// </summary>
        /// <param name="txtData">DataTable с данными .txt файлов.</param>
        /// <param name="magneteData">DataTable с данными .magnete файлов.</param>
        public GraphWindow(DataTable txtData, DataTable magneteData)
        {
            InitializeComponent();

            // Создаем копии переданных DataTable
            txtDataCopy = txtData.Copy();
            magneteDataCopy = magneteData.Copy();

            InitializePlotModels(txtDataCopy, magneteDataCopy);
            this.DataContext = this;

            // Подписка на событие нажатия мыши на PlotView
            MainPlotView.MouseDown += PlotView_MouseDown;

            UpdateUndoRedoButtons();
        }

        /// <summary>
        /// Метод для обновления состояния кнопок Undo и Redo.
        /// </summary>
        private void UpdateUndoRedoButtons()
        {
            UndoButton.IsEnabled = undoStack.Any();
            RedoButton.IsEnabled = redoStack.Any();
        }

        /// <summary>
        /// Устанавливает равный масштаб для осей X и Y.
        /// </summary>
        private void SetEqualAxisScaling(LinearAxis xAxis, LinearAxis yAxis, IEnumerable<DataRow> dataRows, string xColumn, string yColumn)
        {
            try
            {
                // Вычисление минимальных и максимальных значений
                double minX = dataRows.Min(row => Convert.ToDouble(row[xColumn]));
                double maxX = dataRows.Max(row => Convert.ToDouble(row[xColumn]));
                double minY = dataRows.Min(row => Convert.ToDouble(row[yColumn]));
                double maxY = dataRows.Max(row => Convert.ToDouble(row[yColumn]));

                double rangeX = maxX - minX;
                double rangeY = maxY - minY;

                // Определение максимального диапазона
                double maxRange = Math.Max(rangeX, rangeY);

                // Вычисление центра диапазона
                double centerX = (minX + maxX) / 2;
                double centerY = (minY + maxY) / 2;

                // Установка диапазонов осей для равного масштаба
                xAxis.Minimum = centerX - maxRange / 2;
                xAxis.Maximum = centerX + maxRange / 2;
                yAxis.Minimum = centerY - maxRange / 2;
                yAxis.Maximum = centerY + maxRange / 2;

                // Установка основных шагов
                xAxis.MajorStep = maxRange / 10;
                yAxis.MajorStep = maxRange / 10;
            }
            catch (Exception ex)
            {
                MessageBox.Show($"Ошибка при установке масштаба осей: {ex.Message}", "Ошибка", MessageBoxButton.OK, MessageBoxImage.Error);
            }
        }

        /// <summary>
        /// Инициализирует модели графиков.
        /// </summary>
        private void InitializePlotModels(DataTable txtData, DataTable magneteData)
        {
            // График 1: .txt Time UTC vs BASE-1
            TxtBase1Model = new PlotModel { Title = ".txt Time UTC vs BASE-1" };
            TxtBase1Model.Axes.Add(new DateTimeAxis
            {
                Position = AxisPosition.Bottom,
                StringFormat = "HH:mm:ss",
                Title = "Time UTC",
                IntervalType = DateTimeIntervalType.Hours,
                MinorIntervalType = DateTimeIntervalType.Minutes
            });
            TxtBase1Model.Axes.Add(new LinearAxis
            {
                Position = AxisPosition.Left,
                Title = "BASE-1"
            });
            var txtBase1Series = new ScatterSeries
            {
                Title = "BASE-1",
                MarkerType = MarkerType.Circle,
                MarkerSize = 5,
                TrackerFormatString = "Time UTC: {2:HH:mm:ss}\nBASE-1: {4}"
            };
            List<DataRow> txtBase1DataRows = new List<DataRow>();

            foreach (DataRow row in txtData.Rows)
            {
                if (row.RowState == DataRowState.Deleted)
                    continue;

                if (DateTime.TryParse(row["Time UTC"].ToString(), out DateTime timeUtc) &&
                    double.TryParse(row["BASE-1"].ToString(), out double base1))
                {
                    double timeInDouble = DateTimeAxis.ToDouble(timeUtc);
                    txtBase1Series.Points.Add(new ScatterPoint(timeInDouble, base1));
                    txtBase1DataRows.Add(row);
                }
            }
            TxtBase1Model.Series.Add(txtBase1Series);
            seriesDataRows[txtBase1Series] = txtBase1DataRows;

            // График 2: .txt Time UTC vs Noise
            TxtNoiseModel = new PlotModel { Title = ".txt Time UTC vs Noise" };
            TxtNoiseModel.Axes.Add(new DateTimeAxis
            {
                Position = AxisPosition.Bottom,
                StringFormat = "HH:mm:ss",
                Title = "Time UTC",
                IntervalType = DateTimeIntervalType.Hours,
                MinorIntervalType = DateTimeIntervalType.Minutes
            });
            TxtNoiseModel.Axes.Add(new LinearAxis
            {
                Position = AxisPosition.Left,
                Title = "Noise"
            });
            var txtNoiseSeries = new ScatterSeries
            {
                Title = "Noise",
                MarkerType = MarkerType.Circle,
                MarkerSize = 5,
                TrackerFormatString = "Time UTC: {2:HH:mm:ss}\nNoise: {4}"
            };
            List<DataRow> txtNoiseDataRows = new List<DataRow>();

            foreach (DataRow row in txtData.Rows)
            {
                if (row.RowState == DataRowState.Deleted)
                    continue;

                if (DateTime.TryParse(row["Time UTC"].ToString(), out DateTime timeUtc) &&
                    double.TryParse(row["Noise"].ToString(), out double noise))
                {
                    double timeInDouble = DateTimeAxis.ToDouble(timeUtc);
                    txtNoiseSeries.Points.Add(new ScatterPoint(timeInDouble, noise));
                    txtNoiseDataRows.Add(row);
                }
            }
            TxtNoiseModel.Series.Add(txtNoiseSeries);
            seriesDataRows[txtNoiseSeries] = txtNoiseDataRows;

            // График 3: .magnete Долгота UTM vs Широта UTM (Альтитуда)
            MagneteAltitudeModel = new PlotModel { Title = ".magnete Долгота UTM vs Широта UTM (Альтитуда)" };
            var xAxisAltitude = new LinearAxis
            {
                Position = AxisPosition.Bottom,
                Title = "Долгота UTM"
            };
            var yAxisAltitude = new LinearAxis
            {
                Position = AxisPosition.Left,
                Title = "Широта UTM"
            };
            MagneteAltitudeModel.Axes.Add(xAxisAltitude);
            MagneteAltitudeModel.Axes.Add(yAxisAltitude);

            var colorAxisAltitude = new LinearColorAxis
            {
                Position = AxisPosition.Right,
                Palette = OxyPalettes.Jet(500),
                Title = "Альтитуда"
            };
            MagneteAltitudeModel.Axes.Add(colorAxisAltitude);

            var magneteAltitudeSeries = new ScatterSeries
            {
                Title = "Альтитуда",
                MarkerType = MarkerType.Circle,
                MarkerSize = 5,
                TrackerFormatString = "Долгота UTM: {2}\nШирота UTM: {3}\nАльтитуда: {4}"
            };
            List<DataRow> magneteAltitudeDataRows = new List<DataRow>();

            foreach (DataRow row in magneteData.Rows)
            {
                if (row.RowState == DataRowState.Deleted)
                    continue;

                if (double.TryParse(row["Долгота UTM"].ToString(), out double longitudeUTM) &&
                    double.TryParse(row["Широта UTM"].ToString(), out double latitudeUTM) &&
                    double.TryParse(row["Альтитуда"].ToString(), out double altitude))
                {
                    magneteAltitudeSeries.Points.Add(new ScatterPoint(longitudeUTM, latitudeUTM, 5, altitude));
                    magneteAltitudeDataRows.Add(row);
                }
            }
            MagneteAltitudeModel.Series.Add(magneteAltitudeSeries);
            seriesDataRows[magneteAltitudeSeries] = magneteAltitudeDataRows;

            // Установка равного масштаба для осей
            SetEqualAxisScaling(xAxisAltitude, yAxisAltitude, magneteData.Rows.Cast<DataRow>(), "Долгота UTM", "Широта UTM");

            // График 4: .magnete Долгота UTM vs Широта UTM (Shark)
            MagneteSharkModel = new PlotModel { Title = ".magnete Долгота UTM vs Широта UTM (Shark)" };
            var xAxisShark = new LinearAxis
            {
                Position = AxisPosition.Bottom,
                Title = "Долгота UTM"
            };
            var yAxisShark = new LinearAxis
            {
                Position = AxisPosition.Left,
                Title = "Широта UTM"
            };
            MagneteSharkModel.Axes.Add(xAxisShark);
            MagneteSharkModel.Axes.Add(yAxisShark);

            var colorAxisShark = new LinearColorAxis
            {
                Position = AxisPosition.Right,
                Palette = OxyPalettes.Jet(500),
                Title = "Shark"
            };
            MagneteSharkModel.Axes.Add(colorAxisShark);

            var magneteSharkSeries = new ScatterSeries
            {
                Title = "Shark",
                MarkerType = MarkerType.Circle,
                MarkerSize = 5,
                TrackerFormatString = "Долгота UTM: {2}\nШирота UTM: {3}\nShark: {4}"
            };
            List<DataRow> magneteSharkDataRows = new List<DataRow>();

            foreach (DataRow row in magneteData.Rows)
            {
                if (row.RowState == DataRowState.Deleted)
                    continue;

                if (double.TryParse(row["Долгота UTM"].ToString(), out double longitudeUTM) &&
                    double.TryParse(row["Широта UTM"].ToString(), out double latitudeUTM) &&
                    double.TryParse(row["Shark"].ToString(), out double shark))
                {
                    magneteSharkSeries.Points.Add(new ScatterPoint(longitudeUTM, latitudeUTM, 5, shark));
                    magneteSharkDataRows.Add(row);
                }
            }
            MagneteSharkModel.Series.Add(magneteSharkSeries);
            seriesDataRows[magneteSharkSeries] = magneteSharkDataRows;

            // Установка равного масштаба для осей
            SetEqualAxisScaling(xAxisShark, yAxisShark, magneteData.Rows.Cast<DataRow>(), "Долгота UTM", "Широта UTM");

            // Настройка PlotView по умолчанию
            MainPlotView.Model = TxtBase1Model;
        }

        /// <summary>
        /// Обработчик выбора графика из ComboBox.
        /// </summary>
        private void ChartSelector_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            var selectedItem = (sender as ComboBox)?.SelectedItem as ComboBoxItem;
            if (selectedItem != null)
            {
                switch (selectedItem.Content.ToString())
                {
                    case ".txt Time UTC vs BASE-1":
                        MainPlotView.Model = TxtBase1Model;
                        break;
                    case ".txt Time UTC vs Noise":
                        MainPlotView.Model = TxtNoiseModel;
                        break;
                    case ".magnete Долгота UTM vs Широта UTM (Альтитуда)":
                        MainPlotView.Model = MagneteAltitudeModel;
                        break;
                    case ".magnete Долгота UTM vs Широта UTM (Shark)":
                        MainPlotView.Model = MagneteSharkModel;
                        break;
                    default:
                        break;
                }
            }
        }

        /// <summary>
        /// Обработчик события MouseDown для PlotView.
        /// </summary>
        private void PlotView_MouseDown(object sender, MouseButtonEventArgs e)
        {
            var plotModel = MainPlotView.Model;
            if (plotModel == null)
                return;

            // Получить позицию клика относительно PlotView
            var mousePos = e.GetPosition(MainPlotView);

            // Преобразование координат клика в данные координаты
            double xData, yData;
            try
            {
                xData = plotModel.DefaultXAxis.InverseTransform(mousePos.X);
                yData = plotModel.DefaultYAxis.InverseTransform(mousePos.Y);
            }
            catch
            {
                // Если преобразование не удалось, выйти
                return;
            }

            // Определить ближайшую точку и расстояние до неё (в пикселях)
            double thresholdInPixels = 10; // Пороговое расстояние в пикселях
            double minDistance = double.MaxValue;
            ScatterSeries nearestSeries = null;
            ScatterPoint nearestPoint = null;

            foreach (var series in plotModel.Series)
            {
                if (series is ScatterSeries scatterSeries)
                {
                    foreach (var point in scatterSeries.Points)
                    {
                        // Перевести данные координаты в экранные
                        double screenX = plotModel.DefaultXAxis.Transform(point.X);
                        double screenY = plotModel.DefaultYAxis.Transform(point.Y);

                        // Вычисление расстояния между кликом и точкой
                        double distance = Math.Sqrt(Math.Pow(mousePos.X - screenX, 2) + Math.Pow(mousePos.Y - screenY, 2));

                        if (distance < minDistance && distance <= thresholdInPixels)
                        {
                            minDistance = distance;
                            nearestSeries = scatterSeries;
                            nearestPoint = point;
                        }
                    }
                }
            }

            if (nearestSeries != null && nearestPoint != null)
            {
                // Сохранить выбранную серию и точку
                selectedSeries = nearestSeries;
                selectedPoint = nearestPoint;

                // Показать контекстное меню в позиции клика
                // Convert mouse position relative to window
                var windowPos = this.PointToScreen(mousePos);

                ShowContextMenu(new System.Windows.Point(windowPos.X, windowPos.Y));

                // Обработка события завершена
                e.Handled = true;
            }
        }

        /// <summary>
        /// Отображает контекстное меню с опцией удаления точки.
        /// </summary>
        /// <param name="position">Позиция клика для отображения меню (в координатах экрана).</param>
        private void ShowContextMenu(System.Windows.Point position)
        {
            if (selectedSeries == null || selectedPoint == null)
                return;

            // Создание контекстного меню
            ContextMenu contextMenu = new ContextMenu();

            MenuItem deleteItem = new MenuItem
            {
                Header = "Удалить точку"
            };
            deleteItem.Click += DeleteItem_Click;

            contextMenu.Items.Add(deleteItem);

            // Установка позиции контекстного меню
            contextMenu.Placement = System.Windows.Controls.Primitives.PlacementMode.AbsolutePoint;
            contextMenu.HorizontalOffset = position.X;
            contextMenu.VerticalOffset = position.Y;

            // Открытие контекстного меню
            contextMenu.IsOpen = true;
        }

        /// <summary>
        /// Обработчик события удаления точки из контекстного меню.
        /// </summary>
        private void DeleteItem_Click(object sender, RoutedEventArgs e)
        {
            if (selectedSeries != null && selectedPoint != null)
            {
                var result = MessageBox.Show("Вы уверены, что хотите удалить эту точку?", "Подтверждение удаления", MessageBoxButton.YesNo, MessageBoxImage.Question);
                if (result == MessageBoxResult.Yes)
                {
                    // Найти соответствующую DataRow
                    var dataRows = seriesDataRows[selectedSeries];
                    int pointIndex = selectedSeries.Points.IndexOf(selectedPoint);
                    if (pointIndex >= 0 && pointIndex < dataRows.Count)
                    {
                        DataRow rowToDelete = dataRows[pointIndex];
                        // Помечаем строку как удаленную
                        rowToDelete.Delete();

                        // Удаление точки из ScatterSeries и списка DataRows
                        selectedSeries.Points.RemoveAt(pointIndex);
                        dataRows.RemoveAt(pointIndex);

                        // Запись действия в Undo Stack
                        var action = new GraphAction
                        {
                            Series = selectedSeries,
                            Point = selectedPoint,
                            PointIndex = pointIndex,
                            DataRow = rowToDelete
                        };
                        undoStack.Push(action);

                        // Очистка Redo Stack, так как новое действие делает историю Redo неактуальной
                        redoStack.Clear();

                        // Обновление графика
                        MainPlotView.Model.InvalidatePlot(true);
                    }
                }

                // Сброс выбранной серии и точки
                selectedSeries = null;
                selectedPoint = null;

                // Обновление состояния кнопок
                UpdateUndoRedoButtons();
            }
        }

        /// <summary>
        /// Обработчик кнопки Undo.
        /// </summary>
        private void UndoButton_Click(object sender, RoutedEventArgs e)
        {
            if (!undoStack.Any())
                return;

            var action = undoStack.Pop();
            redoStack.Push(action); // Переносим действие в Redo Stack

            // Восстановление точки
            action.Series.Points.Insert(action.PointIndex, action.Point);
            seriesDataRows[action.Series].Insert(action.PointIndex, action.DataRow);

            // Восстановление строки в DataTable
            action.DataRow.RejectChanges(); // Восстанавливаем удалённую строку

            // Обновление графика
            MainPlotView.Model.InvalidatePlot(true);

            // Обновление состояния кнопок
            UpdateUndoRedoButtons();
        }

        /// <summary>
        /// Обработчик кнопки Redo.
        /// </summary>
        private void RedoButton_Click(object sender, RoutedEventArgs e)
        {
            if (!redoStack.Any())
                return;

            var action = redoStack.Pop();
            undoStack.Push(action); // Переносим действие обратно в Undo Stack

            // Удаление точки снова
            action.Series.Points.RemoveAt(action.PointIndex);
            seriesDataRows[action.Series].RemoveAt(action.PointIndex);

            // Удаление строки из DataTable
            action.DataRow.Delete();

            // Обновление графика
            MainPlotView.Model.InvalidatePlot(true);

            // Обновление состояния кнопок
            UpdateUndoRedoButtons();
        }

        /// <summary>
        /// Обработчик кнопки "Сохранить".
        /// Передает измененные данные обратно в MainWindow.
        /// </summary>
        private void SaveButton_Click(object sender, RoutedEventArgs e)
        {
            var result = MessageBox.Show("Вы уверены, что хотите сохранить изменения?", "Подтверждение сохранения", MessageBoxButton.YesNo, MessageBoxImage.Question);
            if (result == MessageBoxResult.Yes)
            {
                try
                {
                    // Применяем все изменения в копиях DataTable
                    txtDataCopy.AcceptChanges();
                    magneteDataCopy.AcceptChanges();

                    // Закрываем окно с DialogResult = true
                    this.DialogResult = true;
                }
                catch (Exception ex)
                {
                    MessageBox.Show($"Ошибка при сохранении изменений:\n{ex.Message}", "Ошибка", MessageBoxButton.OK, MessageBoxImage.Error);
                }
            }
        }

        /// <summary>
        /// Обработчик кнопки "Отмена".
        /// Закрывает окно без сохранения изменений.
        /// </summary>
        private void CancelButton_Click(object sender, RoutedEventArgs e)
        {
            var result = MessageBox.Show("Вы уверены, что хотите отменить все изменения?", "Подтверждение отмены", MessageBoxButton.YesNo, MessageBoxImage.Question);
            if (result == MessageBoxResult.Yes)
            {
                // Откат всех изменений в копиях DataTable
                txtDataCopy.RejectChanges();
                magneteDataCopy.RejectChanges();

                // Закрываем окно с DialogResult = false
                this.DialogResult = false;
            }
        }
    }
}