нужно в окне с графиками  реализовать
1. справа графиков выводить объединенный файл по которому строились графики при нажатии на точку на графике и выделять строку которая строила эту точку при выборе точки на другом графике это окно с файлом должно обновится
2. сделать графики txt файлов в виде точек без линии
3. нужно добавить для графиков по magnete настройку масштаба, там значения x и y в метрах, следовательно нужно сделать выбор масштаба например 1:1 1:10 1:100 и так далее
НАПИШИ ПОЛНЫЕ КОДЫ ДЛЯ ВСЕХ ИЗЕНЕННЫХ ЧАСТЕЙ ЕЗ КОММЕНТАРИЕВ, ТВОЙ ОТВЕТ ПРОСТО ПОЛНЫЕ КОДЫ БЕЗ ЛИШНЕГО

<!-- MainWindow.xaml -->
<Window x:Class="TxtProcessorApp.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:TxtProcessorApp"
        Title="Txt Processor" Height="800" Width="1200">
    <Grid Margin="10">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <!-- Панель кнопок -->
            <RowDefinition Height="*"/>
            <!-- Основная область с данными -->
            <RowDefinition Height="Auto"/>
            <!-- Строка состояния -->
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*"/>
            <!-- Столбец для .txt -->
            <ColumnDefinition Width="20"/>
            <!-- Разделитель -->
            <ColumnDefinition Width="*"/>
            <!-- Столбец для .magnete -->
        </Grid.ColumnDefinitions>

        <!-- Верхняя панель кнопок загрузки и управления файлами -->
        <StackPanel Orientation="Horizontal" Grid.Row="0" Grid.ColumnSpan="3" HorizontalAlignment="Left" Margin="0,0,0,10">
            <Button Content="Загрузить .txt файлы" Width="150" Height="30" Click="LoadTxtFiles_Click"/>
            <Button Content="Загрузить .magnete файлы" Width="180" Height="30" Click="LoadMagneteFiles_Click" Margin="10,0,0,0"/>
            <!-- Кнопка "Проект" с выпадающим списком -->
            <Button Content="Проект" Width="120" Height="30" Margin="10,0,0,0" Click="ProjectButton_Click">
                <Button.ContextMenu>
                    <ContextMenu>
                        <MenuItem Header="Сохранить проект" Click="SaveProject_Click"/>
                        <MenuItem Header="Загрузить проект" Click="LoadProject_Click"/>
                    </ContextMenu>
                </Button.ContextMenu>
            </Button>
            <Button Content="Сформировать файл" Width="150" Height="30" Click="CombineFiles_Click" Margin="10,0,0,0"/>
            <Button Content="Редактировать и Экспортировать" Width="220" Height="30" Click="EditAndExport_Click" IsEnabled="False" x:Name="EditExportButton" Margin="10,0,0,0"/>
            <Button Content="Графики" Width="100" Height="30" Click="ShowGraphs_Click" Margin="10,0,0,0"/>
        </StackPanel>

        <!-- Разделитель между столбцами -->
        <Separator Grid.Row="1" Grid.Column="1" VerticalAlignment="Stretch" Margin="5,0"/>

        <!-- Левая область для .txt файлов -->
        <GroupBox Header=".txt Файлы" Grid.Row="1" Grid.Column="0" Margin="0,10,10,10">
            <TabControl x:Name="TxtFilesTabControl" Margin="5"/>
        </GroupBox>

        <!-- Правая область для .magnete файлов -->
        <GroupBox Header=".magnete Файлы" Grid.Row="1" Grid.Column="2" Margin="10,10,0,10">
            <TabControl x:Name="MagneteFilesTabControl" Margin="5"/>
        </GroupBox>

        <!-- Строка состояния -->
        <StatusBar Grid.Row="2" Grid.ColumnSpan="3">
            <StatusBarItem>
                <TextBlock x:Name="StatusTextBlock" Text="Готово"/>
            </StatusBarItem>
        </StatusBar>
    </Grid>
</Window>

// MainWindow.xaml.cs
using Microsoft.Win32;
using ProjNet.CoordinateSystems.Transformations;
using ProjNet.CoordinateSystems;
using System;
using System.Collections.Generic;
using System.Data;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Data;
using Newtonsoft.Json;

namespace TxtProcessorApp
{
    public partial class MainWindow : Window
    {
        // Словари для хранения данных из файлов
        private Dictionary<string, List<string[]>> txtFileData = new Dictionary<string, List<string[]>>();
        private Dictionary<string, List<string[]>> magneteFileData = new Dictionary<string, List<string[]>>();

        // Хранение объединённых данных для дальнейшего редактирования
        private DataTable combinedTxtDataTable = null;
        private DataTable combinedMagneteDataTable = null;

        public MainWindow()
        {
            InitializeComponent();
        }

        #region Обработчики загрузки файлов .txt

        // Обработчик кнопки загрузки .txt файлов
        private void LoadTxtFiles_Click(object sender, RoutedEventArgs e)
        {
            OpenFileDialog ofd = new OpenFileDialog
            {
                Multiselect = true,
                Filter = "Текстовые файлы (*.txt)|*.txt"
            };

            if (ofd.ShowDialog() == true)
            {
                foreach (var file in ofd.FileNames)
                {
                    ProcessTxtFile(file);
                }

                // Обновляем TabControl с вкладками для каждого .txt файла
                PopulateTxtTabControl();

                // Деактивируем кнопку редактирования до объединения
                EditExportButton.IsEnabled = false;
                combinedTxtDataTable = null;

                StatusTextBlock.Text = $"Загружено {txtFileData.Count} .txt файлов.";
            }
        }

        // Метод для обработки отдельного .txt файла
        private void ProcessTxtFile(string filePath)
        {
            List<string[]> processedLines = new List<string[]>();

            try
            {
                var lines = File.ReadAllLines(filePath).Skip(11); // Пропуск первых 11 строк

                foreach (var line in lines)
                {
                    if (string.IsNullOrWhiteSpace(line))
                        continue;

                    var columns = line.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);

                    if (columns.Length == 0)
                        continue;

                    // Проверка первого столбца
                    if (columns[0].ToLower() == "nan" || string.IsNullOrWhiteSpace(columns[0]))
                        continue;

                    // Создаем список для новых столбцов
                    List<string> newColumns = new List<string>();

                    // Добавляем столбцы, кроме первого
                    for (int i = 1; i < columns.Length; i++)
                    {
                        newColumns.Add(columns[i]);
                    }

                    processedLines.Add(newColumns.ToArray());
                }

                // Добавляем обработанные данные в словарь
                string fileName = System.IO.Path.GetFileName(filePath);

                // Проверяем, если файл уже загружен, добавляем суффикс
                string originalFileName = fileName;
                int duplicateCount = 1;
                while (txtFileData.ContainsKey(fileName))
                {
                    fileName = $"{System.IO.Path.GetFileNameWithoutExtension(originalFileName)}_{duplicateCount}{System.IO.Path.GetExtension(originalFileName)}";
                    duplicateCount++;
                }

                txtFileData[fileName] = processedLines;
            }
            catch (Exception ex)
            {
                MessageBox.Show($"Ошибка при обработке файла {filePath}:\n{ex.Message}", "Ошибка", MessageBoxButton.OK, MessageBoxImage.Error);
            }
        }

        // Метод для заполнения TxtFilesTabControl вкладками для каждого .txt файла
        private void PopulateTxtTabControl()
        {
            TxtFilesTabControl.Items.Clear();

            foreach (var kvp in txtFileData)
            {
                string fileName = kvp.Key;
                List<string[]> data = kvp.Value;

                // Создаем DataGrid для текущего файла
                DataGrid dataGrid = CreateTxtDataGrid(data);

                // Создаем вкладку
                TabItem tabItem = new TabItem
                {
                    Header = fileName,
                    Content = dataGrid
                };

                TxtFilesTabControl.Items.Add(tabItem);
            }
        }

        private DataGrid CreateTxtDataGrid(List<string[]> data)
        {
            DataGrid dataGrid = new DataGrid
            {
                AutoGenerateColumns = false,
                IsReadOnly = true,
                FontFamily = new System.Windows.Media.FontFamily("Consolas"),
                FontSize = 12
            };

            // Определение столбцов вручную
            dataGrid.Columns.Clear();

            dataGrid.Columns.Add(new DataGridTextColumn
            {
                Header = "№",
                Binding = new Binding("Number"),
                Width = new DataGridLength(50, DataGridLengthUnitType.Auto)
            });

            dataGrid.Columns.Add(new DataGridTextColumn
            {
                Header = "BASE-1",
                Binding = new Binding("Base1"),
                Width = new DataGridLength(150, DataGridLengthUnitType.Auto)
            });

            dataGrid.Columns.Add(new DataGridTextColumn
            {
                Header = "Noise",
                Binding = new Binding("Noise"),
                Width = new DataGridLength(100, DataGridLengthUnitType.Auto)
            });

            dataGrid.Columns.Add(new DataGridTextColumn
            {
                Header = "Time",
                Binding = new Binding("Time"),
                Width = new DataGridLength(100, DataGridLengthUnitType.Auto)
            });

            dataGrid.Columns.Add(new DataGridTextColumn
            {
                Header = "Time UTC",
                Binding = new Binding("TimeUTC"),
                Width = new DataGridLength(120, DataGridLengthUnitType.Auto)
            });

            // Добавление стилей для ячеек и заголовков столбцов
            Style cellStyle = new Style(typeof(DataGridCell));
            cellStyle.Setters.Add(new Setter(DataGridCell.PaddingProperty, new Thickness(5)));
            cellStyle.Setters.Add(new Setter(DataGridCell.HorizontalContentAlignmentProperty, HorizontalAlignment.Center));

            Style headerStyle = new Style(typeof(DataGridColumnHeader));
            headerStyle.Setters.Add(new Setter(DataGridColumnHeader.HorizontalContentAlignmentProperty, HorizontalAlignment.Center));
            headerStyle.Setters.Add(new Setter(DataGridColumnHeader.FontWeightProperty, FontWeights.Bold));

            dataGrid.CellStyle = cellStyle;
            dataGrid.ColumnHeaderStyle = headerStyle;

            // Преобразуем данные в DataTable
            DataTable dataTable = ConvertToDataTable(data);
            dataGrid.ItemsSource = dataTable.DefaultView;

            return dataGrid;
        }

        // Метод для преобразования списка строк .txt в DataTable
        private DataTable ConvertToDataTable(List<string[]> data)
        {
            DataTable dt = new DataTable();

            if (data == null || data.Count == 0)
                return dt;

            // Создание столбцов с заданными названиями
            dt.Columns.Add("Number", typeof(int));
            dt.Columns.Add("Base1", typeof(string));
            dt.Columns.Add("Noise", typeof(string));
            dt.Columns.Add("Time", typeof(string));
            dt.Columns.Add("TimeUTC", typeof(string));

            int number = 1;

            // Добавление строк
            foreach (var row in data)
            {
                DataRow dr = dt.NewRow();
                dr["Number"] = number++;
                dr["Base1"] = row.Length > 0 ? row[0] : string.Empty;
                dr["Noise"] = row.Length > 1 ? row[1] : string.Empty;
                dr["Time"] = row.Length > 2 ? row[2] : string.Empty;
                dr["TimeUTC"] = row.Length > 3 ? row[3] : string.Empty;
                dt.Rows.Add(dr);
            }

            return dt;
        }

        #endregion

        #region Обработчики загрузки файлов .magnete

        // Обработчик кнопки загрузки .magnete файлов
        private void LoadMagneteFiles_Click(object sender, RoutedEventArgs e)
        {
            OpenFileDialog ofd = new OpenFileDialog
            {
                Multiselect = true,
                Filter = "Magnete файлы (*.magnete)|*.magnete"
            };

            if (ofd.ShowDialog() == true)
            {
                foreach (var file in ofd.FileNames)
                {
                    ProcessMagneteFile(file);
                }

                // Обновляем TabControl с вкладками для каждого .magnete файла
                PopulateMagneteTabControl();

                // Деактивируем кнопку редактирования до объединения
                EditExportButton.IsEnabled = false;
                combinedMagneteDataTable = null;

                StatusTextBlock.Text = $"Загружено {magneteFileData.Count} .magnete файлов.";
            }
        }

        // Метод для обработки отдельного .magnete файла
        private void ProcessMagneteFile(string filePath)
        {
            List<string[]> processedLines = new List<string[]>();

            try
            {
                var lines = File.ReadAllLines(filePath); // Удалено .Skip(11) для обработки всех строк

                foreach (var line in lines)
                {
                    if (string.IsNullOrWhiteSpace(line))
                        continue;

                    var columns = line.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);

                    if (columns.Length == 0)
                        continue;

                    // Проверка первого столбца
                    if (columns[0].ToLower() == "nan" || string.IsNullOrWhiteSpace(columns[0]))
                        continue;

                    // Добавляем все столбцы
                    processedLines.Add(columns);
                }

                // Добавляем обработанные данные в словарь
                string fileName = System.IO.Path.GetFileName(filePath);

                // Проверяем, если файл уже загружен, добавляем суффикс
                string originalFileName = fileName;
                int duplicateCount = 1;
                while (magneteFileData.ContainsKey(fileName))
                {
                    fileName = $"{System.IO.Path.GetFileNameWithoutExtension(originalFileName)}_{duplicateCount}{System.IO.Path.GetExtension(originalFileName)}";
                    duplicateCount++;
                }

                magneteFileData[fileName] = processedLines;
            }
            catch (Exception ex)
            {
                MessageBox.Show($"Ошибка при обработке файла {filePath}:\n{ex.Message}", "Ошибка", MessageBoxButton.OK, MessageBoxImage.Error);
            }
        }

        // Метод для заполнения MagneteFilesTabControl вкладками для каждого .magnete файла
        private void PopulateMagneteTabControl()
        {
            MagneteFilesTabControl.Items.Clear();

            foreach (var kvp in magneteFileData)
            {
                string fileName = kvp.Key;
                List<string[]> data = kvp.Value;

                // Создаем DataGrid для текущего файла
                DataGrid dataGrid = CreateMagneteDataGrid(data);

                // Создаем вкладку
                TabItem tabItem = new TabItem
                {
                    Header = fileName,
                    Content = dataGrid
                };

                MagneteFilesTabControl.Items.Add(tabItem);
            }
        }

        private DataGrid CreateMagneteDataGrid(List<string[]> data)
        {
            DataGrid dataGrid = new DataGrid
            {
                AutoGenerateColumns = false,
                IsReadOnly = true,
                FontFamily = new System.Windows.Media.FontFamily("Consolas"),
                FontSize = 12
            };

            // Определение столбцов вручную
            dataGrid.Columns.Clear();

            dataGrid.Columns.Add(new DataGridTextColumn
            {
                Header = "Stan",
                Binding = new Binding("Stan"),
                Width = new DataGridLength(100, DataGridLengthUnitType.Auto)
            });

            dataGrid.Columns.Add(new DataGridTextColumn
            {
                Header = "Zeit",
                Binding = new Binding("Zeit"),
                Width = new DataGridLength(100, DataGridLengthUnitType.Auto)
            });

            dataGrid.Columns.Add(new DataGridTextColumn
            {
                Header = "Noise",
                Binding = new Binding("Noise"),
                Width = new DataGridLength(100, DataGridLengthUnitType.Auto)
            });

            dataGrid.Columns.Add(new DataGridTextColumn
            {
                Header = "Time",
                Binding = new Binding("Time"),
                Width = new DataGridLength(100, DataGridLengthUnitType.Auto)
            });

            dataGrid.Columns.Add(new DataGridTextColumn
            {
                Header = "TimeUTC",
                Binding = new Binding("TimeUTC"),
                Width = new DataGridLength(100, DataGridLengthUnitType.Auto)
            });

            // Добавление стилей для ячеек и заголовков столбцов
            Style cellStyle = new Style(typeof(DataGridCell));
            cellStyle.Setters.Add(new Setter(DataGridCell.PaddingProperty, new Thickness(5)));
            cellStyle.Setters.Add(new Setter(DataGridCell.HorizontalContentAlignmentProperty, HorizontalAlignment.Center));

            Style headerStyle = new Style(typeof(DataGridColumnHeader));
            headerStyle.Setters.Add(new Setter(DataGridColumnHeader.HorizontalContentAlignmentProperty, HorizontalAlignment.Center));
            headerStyle.Setters.Add(new Setter(DataGridColumnHeader.FontWeightProperty, FontWeights.Bold));

            dataGrid.CellStyle = cellStyle;
            dataGrid.ColumnHeaderStyle = headerStyle;

            // Преобразуем данные в DataTable
            DataTable dataTable = ConvertMagneteToDataTable(data);
            dataGrid.ItemsSource = dataTable.DefaultView;

            return dataGrid;
        }

        private DataTable ConvertMagneteToDataTable(List<string[]> data)
        {
            DataTable dt = new DataTable();

            // Определение столбцов (поменяйте согласно вашим требованиям)
            dt.Columns.Add("Stan", typeof(string));
            dt.Columns.Add("Zeit", typeof(string));
            dt.Columns.Add("Noise", typeof(string));
            dt.Columns.Add("Time", typeof(string));
            dt.Columns.Add("TimeUTC", typeof(string));

            foreach (var row in data)
            {
                DataRow dr = dt.NewRow();
                dr["Stan"] = row.Length > 0 ? row[0].Trim() : string.Empty;
                dr["Zeit"] = row.Length > 1 ? row[1].Trim() : string.Empty;
                dr["Noise"] = row.Length > 2 ? row[2].Trim() : string.Empty;
                dr["Time"] = row.Length > 3 ? row[3].Trim() : string.Empty;
                dr["TimeUTC"] = row.Length > 4 ? row[4].Trim() : string.Empty;
                dt.Rows.Add(dr);
            }

            return dt;
        }

        #endregion

        #region Обработчик объединения файлов

        // Обработчик кнопки Объединить файлы
        private void CombineFiles_Click(object sender, RoutedEventArgs e)
        {
            if (!txtFileData.Any() && !magneteFileData.Any())
            {
                MessageBox.Show("Нет загруженных файлов для объединения.", "Информация", MessageBoxButton.OK, MessageBoxImage.Information);
                return;
            }

            // Диалоговое окно для выбора типа файлов для объединения
            SelectEditTypeWindow selectWindow = new SelectEditTypeWindow("Выберите тип файлов для объединения:");
            selectWindow.Owner = this;

            if (selectWindow.ShowDialog() == true)
            {
                string selectedOption = selectWindow.SelectedOption;

                if (selectedOption == ".txt файлы" && txtFileData.Any())
                {
                    CombineTxtFiles();
                }
                else if (selectedOption == ".magnete файлы" && magneteFileData.Any())
                {
                    CombineMagneteFiles();
                }
                else
                {
                    MessageBox.Show("Выбранная опция недоступна или нет загруженных файлов данного типа.", "Ошибка", MessageBoxButton.OK, MessageBoxImage.Error);
                }
            }
        }

        // Метод объединения .txt файлов
        private void CombineTxtFiles()
        {
            List<string[]> combinedData = new List<string[]>();
            int lineNumber = 1;

            foreach (var file in txtFileData.Values)
            {
                foreach (var row in file)
                {
                    if (row.Length < 3)
                        continue; // Убедитесь, что есть минимум 3 столбца

                    // Извлечение необходимых столбцов
                    string secondColumn = row[1];
                    string thirdColumn = row.Length > 1 ? row[2] : string.Empty;
                    string fourthColumn = row.Length > 2 ? row[3] : "0"; // Если нет 4-го столбца, используем "0"

                    // Обработка пятого столбца
                    if (double.TryParse(fourthColumn, NumberStyles.Any, CultureInfo.InvariantCulture, out double number))
                    {
                        double divided = number / 24;
                        TimeSpan time = TimeSpan.FromDays(divided);
                        string timeString = time.ToString(@"hh\:mm\:ss");
                        combinedData.Add(new string[]
                        {
                            lineNumber.ToString(),
                            secondColumn,
                            thirdColumn,
                            fourthColumn,
                            timeString
                        });
                    }
                    else
                    {
                        combinedData.Add(new string[]
                        {
                            lineNumber.ToString(),
                            secondColumn,
                            thirdColumn,
                            fourthColumn,
                            "InvalidNumber"
                        });
                    }

                    lineNumber++;
                }
            }

            // Преобразуем объединённые данные в DataTable с нужными названиями столбцов
            combinedTxtDataTable = ConvertToCombinedDataTable(combinedData, isTxt: true);

            // Добавляем вкладку с объединёнными данными
            AddCombinedDataTab(combinedTxtDataTable, ".txt данные объединённые", TxtFilesTabControl);

            // Активируем кнопку редактирования
            EditExportButton.IsEnabled = true;

            StatusTextBlock.Text = $"Объединены .txt файлы. Строк: {combinedTxtDataTable.Rows.Count}.";
        }

        // Метод объединения .magnete файлов с сохранением оригинальной логики
        private void CombineMagneteFiles()
        {
            if (!magneteFileData.Any())
            {
                MessageBox.Show("Нет загруженных .magnete файлов для объединения.", "Информация", MessageBoxButton.OK, MessageBoxImage.Information);
                return;
            }

            // Фиксированная дата (например, 06.01.1980) в UTC
            DateTime fixedDate = new DateTime(1980, 1, 6, 0, 0, 0, DateTimeKind.Utc);

            // Объединяем все данные из .magnete файлов
            combinedMagneteDataTable = new DataTable();

            // Определение столбцов с уникальными внутренними именами (согласно оригиналу)
            combinedMagneteDataTable.Columns.Add("DATA", typeof(string));
            combinedMagneteDataTable.Columns.Add("DATA UTC", typeof(string));
            combinedMagneteDataTable.Columns.Add("TIME", typeof(string));
            combinedMagneteDataTable.Columns.Add("TIME UTC", typeof(string));
            combinedMagneteDataTable.Columns.Add("Долгота WGS", typeof(string)); // раньше "Noise"
            combinedMagneteDataTable.Columns.Add("Широта WGS", typeof(string));  // раньше "Zeit"
            combinedMagneteDataTable.Columns.Add("Долгота UTM", typeof(double)); // раньше "Zero1"
            combinedMagneteDataTable.Columns.Add("Широта UTM", typeof(double));  // раньше "Zero2"
            combinedMagneteDataTable.Columns.Add("№1", typeof(int));             // раньше "SequentialNumber1"
            combinedMagneteDataTable.Columns.Add("Альтитуда", typeof(string));    // раньше "Time"
            combinedMagneteDataTable.Columns.Add("№2", typeof(int));             // раньше "SequentialNumber2"
            combinedMagneteDataTable.Columns.Add("Shark", typeof(string));        // раньше "TimeUTC"

            int sequentialNumber = 1;
            List<string> errorMessages = new List<string>();

            foreach (var file in magneteFileData.Values)
            {
                foreach (var row in file)
                {
                    // Создание новой строки для объединённой таблицы
                    DataRow newRow = combinedMagneteDataTable.NewRow();

                    // Столбцы "DATA UTC"
                    newRow["DATA UTC"] = fixedDate.ToString("dd.MM.yyyy");

                    // Столбцы "TIME" и "TIME UTC"
                    string stanValue = row.Length > 0 ? row[0].Trim() : string.Empty;
                    newRow["TIME"] = stanValue;

                    // Столбцы "Долгота WGS" и "Широта WGS"
                    string zeitValue = row.Length > 1 ? row[1].Trim() : string.Empty;
                    newRow["Широта WGS"] = zeitValue;

                    string noiseValue = row.Length > 2 ? row[2].Trim() : string.Empty;
                    newRow["Долгота WGS"] = noiseValue;

                    // Столбцы "№1" и "№2"
                    newRow["№1"] = sequentialNumber; // SequentialNumber1
                    newRow["№2"] = sequentialNumber; // SequentialNumber2

                    // Столбцы "Альтитуда" и "Shark"
                    string timeValueStr = row.Length > 3 ? row[3].Trim() : string.Empty;
                    newRow["Альтитуда"] = timeValueStr;

                    string timeUtcValue = row.Length > 4 ? row[4].Trim() : string.Empty;
                    newRow["Shark"] = timeUtcValue;

                    // Столбцы "Долгота UTM" и "Широта UTM" будут заполнены позже
                    newRow["Долгота UTM"] = 0.0; // Изначально 0.0, потом заменим
                    newRow["Широта UTM"] = 0.0;

                    // Для логирования
                    string computedDateStr = "N/A";
                    string computedTimeStr = "N/A"; // Объявлено

                    // Преобразование координат из "Долгота WGS" и "Широта WGS" в UTM
                    double longitude, latitude;
                    bool validCoordinates = true;

                    if (!double.TryParse(noiseValue, NumberStyles.Any, CultureInfo.InvariantCulture, out longitude))
                    {
                        string error = $"Не удалось распарсить Долгота WGS='{noiseValue}' как долготу в строке: {string.Join("\t", row)}.";
                        errorMessages.Add(error);
                        LogCalculation(error);
                        validCoordinates = false;
                    }

                    if (!double.TryParse(zeitValue, NumberStyles.Any, CultureInfo.InvariantCulture, out latitude))
                    {
                        string error = $"Не удалось распарсить Широта WGS='{zeitValue}' как широту в строке: {string.Join("\t", row)}.";
                        errorMessages.Add(error);
                        LogCalculation(error);
                        validCoordinates = false;
                    }

                    if (validCoordinates)
                    {
                        try
                        {
                            // Определяем UTM-зону
                            int utmZone = GetUTMZone(longitude);

                            // Преобразуем координаты в UTM
                            var utmCoordinates = ConvertWGS84ToUTM(longitude, latitude, utmZone);

                            // Записываем UTM координаты в "Долгота UTM" и "Широта UTM"
                            newRow["Долгота UTM"] = utmCoordinates.Easting;
                            newRow["Широта UTM"] = utmCoordinates.Northing;
                        }
                        catch (Exception ex)
                        {
                            string error = $"Ошибка при преобразовании координат (Долгота WGS='{noiseValue}', Широта WGS='{zeitValue}') в строке: {string.Join("\t", row)}. Текст ошибки: {ex.Message}.";
                            errorMessages.Add(error);
                            LogCalculation(error);
                        }
                    }

                    // Обработка столбца "Stan" как секунд с фиксированной даты
                    if (double.TryParse(stanValue, NumberStyles.Any, CultureInfo.InvariantCulture, out double stanSeconds))
                    {
                        if (stanSeconds < 0)
                        {
                            string warning = $"Отрицательное значение TIME='{stanSeconds}' в строке: {string.Join("\t", row)}. Установка даты как фиксированной.";
                            errorMessages.Add(warning);
                            LogCalculation(warning);

                            newRow["DATA"] = fixedDate.ToString("dd.MM.yyyy");
                            // ComputedTime - фиксированное время минус 18 секунд (оставляет статус 00:00:00.0)
                            computedTimeStr = fixedDate.ToString("HH:mm:ss.f");
                            newRow["TIME UTC"] = computedTimeStr;
                        }
                        else
                        {
                            try
                            {
                                // Добавляем секунды к фиксированной дате
                                DateTime computedDate = fixedDate.AddSeconds(stanSeconds);

                                // Проверка, что дата находится в допустимом диапазоне
                                if (computedDate > DateTime.MaxValue || computedDate < DateTime.MinValue)
                                {
                                    string warning = $"TIME слишком велико='{stanSeconds}' в строке: {string.Join("\t", row)}. Установка даты как фиксированной.";
                                    errorMessages.Add(warning);
                                    LogCalculation(warning);

                                    newRow["DATA"] = fixedDate.ToString("dd.MM.yyyy");
                                    computedTimeStr = fixedDate.ToString("HH:mm:ss.f");
                                    newRow["TIME UTC"] = computedTimeStr;
                                }
                                else
                                {
                                    newRow["DATA"] = computedDate.ToString("dd.MM.yyyy");

                                    // Формат даты
                                    computedDateStr = computedDate.ToString("dd.MM.yyyy");

                                    // Вычисляем ComputedTime как остаток времени после вычитания 18 секунд
                                    TimeSpan timeOfDay = computedDate.TimeOfDay;

                                    // Вычитаем 18 секунд
                                    TimeSpan offsetTime = TimeSpan.FromSeconds(18);
                                    TimeSpan computedTime;

                                    if (timeOfDay >= offsetTime)
                                    {
                                        computedTime = timeOfDay - offsetTime;
                                    }
                                    else
                                    {
                                        // Если время дня меньше 18 секунд, устанавливаем время как 00:00:00.0
                                        computedTime = TimeSpan.Zero;
                                        string info = $"Время дня меньше 18 секунд в строке: {string.Join("\t", row)}. ComputedTime установлено как 00:00:00.0.";
                                        errorMessages.Add(info);
                                        LogCalculation(info);
                                    }

                                    // Округляем каждый TimeSpan.TotalSeconds до одного знака после запятой
                                    double totalSeconds = computedTime.TotalSeconds;
                                    double roundedSeconds = Math.Round(totalSeconds, 1, MidpointRounding.AwayFromZero);

                                    // Создаём TimeSpan из округлённого количества секунд
                                    TimeSpan roundedTime = TimeSpan.FromSeconds(roundedSeconds);

                                    // Форматируем ComputedTime с учетом одной десятичной части секунд
                                    computedTimeStr = roundedTime.ToString(@"hh\:mm\:ss\.f");
                                    newRow["TIME UTC"] = computedTimeStr;
                                }
                            }
                            catch (Exception ex)
                            {
                                string error = $"Ошибка при расчёте даты для TIME='{stanSeconds}' в строке: {string.Join("\t", row)}. Текст ошибки: {ex.Message}. Установка даты как фиксированной.";
                                errorMessages.Add(error);
                                LogCalculation(error);

                                newRow["DATA"] = fixedDate.ToString("dd.MM.yyyy");
                                computedDateStr = fixedDate.ToString("dd.MM.yyyy");
                                computedTimeStr = fixedDate.ToString("HH:mm:ss.f");
                                newRow["TIME UTC"] = computedTimeStr;
                            }
                        }
                    }
                    else
                    {
                        // Если парсинг не удался, устанавливаем фиксированную дату и время, и логируем
                        string error = $"Не удалось распарсить TIME='{stanValue}' в строке: {string.Join("\t", row)}. Установка даты как фиксированной.";
                        errorMessages.Add(error);
                        LogCalculation(error);

                        newRow["DATA"] = fixedDate.ToString("dd.MM.yyyy");
                        computedDateStr = fixedDate.ToString("dd.MM.yyyy");
                        computedTimeStr = fixedDate.ToString("HH:mm:ss.f");
                        newRow["TIME UTC"] = computedTimeStr;
                    }

                    combinedMagneteDataTable.Rows.Add(newRow);
                    sequentialNumber++;

                    // Логируем расчёты для каждой строки
                    string logMessage = $"TIME: '{stanValue}', ComputedDate: {computedDateStr}, ComputedTime: {computedTimeStr}";
                    LogCalculation(logMessage);
                }
            }

            // Создаём DataGrid с явно определёнными столбцами
            DataGrid dataGridCombined = new DataGrid
            {
                AutoGenerateColumns = false,
                IsReadOnly = true,
                CanUserAddRows = false,
                CanUserDeleteRows = false,
                Margin = new Thickness(0, 0, 0, 0),
                FontFamily = new System.Windows.Media.FontFamily("Consolas"),
                FontSize = 12
            };

            // Определяем столбцы вручную с необходимым форматированием
            // 1. DATA
            dataGridCombined.Columns.Add(new DataGridTextColumn
            {
                Header = "DATA",
                Binding = new Binding("DATA"),
                Width = new DataGridLength(150, DataGridLengthUnitType.Auto)
            });

            // 2. DATA UTC
            dataGridCombined.Columns.Add(new DataGridTextColumn
            {
                Header = "DATA UTC",
                Binding = new Binding("DATA UTC"),
                Width = new DataGridLength(150, DataGridLengthUnitType.Auto)
            });

            // 3. TIME
            dataGridCombined.Columns.Add(new DataGridTextColumn
            {
                Header = "TIME",
                Binding = new Binding("TIME"),
                Width = new DataGridLength(100, DataGridLengthUnitType.Auto)
            });

            // 4. TIME UTC
            dataGridCombined.Columns.Add(new DataGridTextColumn
            {
                Header = "TIME UTC",
                Binding = new Binding("TIME UTC"),
                Width = new DataGridLength(150, DataGridLengthUnitType.Auto)
            });

            // 5. Долгота WGS
            dataGridCombined.Columns.Add(new DataGridTextColumn
            {
                Header = "Долгота WGS",
                Binding = new Binding("Долгота WGS"),
                Width = new DataGridLength(120, DataGridLengthUnitType.Auto)
            });

            // 6. Широта WGS
            dataGridCombined.Columns.Add(new DataGridTextColumn
            {
                Header = "Широта WGS",
                Binding = new Binding("Широта WGS"),
                Width = new DataGridLength(120, DataGridLengthUnitType.Auto)
            });

            // 7. Долгота UTM
            dataGridCombined.Columns.Add(new DataGridTextColumn
            {
                Header = "Долгота UTM",
                Binding = new Binding("Долгота UTM") { StringFormat = "F2" },
                Width = new DataGridLength(140, DataGridLengthUnitType.Auto)
            });

            // 8. Широта UTM
            dataGridCombined.Columns.Add(new DataGridTextColumn
            {
                Header = "Широта UTM",
                Binding = new Binding("Широта UTM") { StringFormat = "F2" },
                Width = new DataGridLength(140, DataGridLengthUnitType.Auto)
            });

            // 9. №1
            dataGridCombined.Columns.Add(new DataGridTextColumn
            {
                Header = "№1",
                Binding = new Binding("№1"),
                Width = new DataGridLength(60, DataGridLengthUnitType.Auto)
            });

            // 10. Альтитуда
            dataGridCombined.Columns.Add(new DataGridTextColumn
            {
                Header = "Альтитуда",
                Binding = new Binding("Альтитуда"),
                Width = new DataGridLength(100, DataGridLengthUnitType.Auto)
            });

            // 11. №2
            dataGridCombined.Columns.Add(new DataGridTextColumn
            {
                Header = "№2",
                Binding = new Binding("№2"),
                Width = new DataGridLength(60, DataGridLengthUnitType.Auto)
            });

            // 12. Shark
            dataGridCombined.Columns.Add(new DataGridTextColumn
            {
                Header = "Shark",
                Binding = new Binding("Shark"),
                Width = new DataGridLength(100, DataGridLengthUnitType.Auto)
            });

            // Присваиваем DataTable как источник данных
            dataGridCombined.ItemsSource = combinedMagneteDataTable.DefaultView;

            // Добавляем вкладку с объединёнными данными
            TabItem tabItemCombined = new TabItem
            {
                Header = ".magnete данные объединённые",
                Content = dataGridCombined
            };

            MagneteFilesTabControl.Items.Add(tabItemCombined);

            // Активируем кнопку редактирования и экспорта
            EditExportButton.IsEnabled = true;

            // Логируем основные события
            LogCalculation($"Файлы .magnete успешно объединены. Объединено строк: {combinedMagneteDataTable.Rows.Count}.");

            if (errorMessages.Any())
            {
                string allErrors = string.Join("\n", errorMessages);
                LogCalculation($"Обнаружены следующие проблемы:\n{allErrors}");
                MessageBox.Show($"Файлы .magnete объединены с ошибками. Подробности в файле логов.", "Внимание", MessageBoxButton.OK, MessageBoxImage.Warning);
            }
            else
            {
                MessageBox.Show($"Файлы .magnete успешно объединены. Объединено строк: {combinedMagneteDataTable.Rows.Count}", "Успех", MessageBoxButton.OK, MessageBoxImage.Information);
            }
        }

        // Метод для добавления объединённой вкладки
        private void AddCombinedDataTab(DataTable combinedData, string header, TabControl parentTabControl)
        {
            // Проверяем, существует ли уже вкладка для объединённых данных
            foreach (TabItem tab in parentTabControl.Items)
            {
                if (tab.Header.ToString() == header)
                    return; // Вкладка уже существует
            }

            DataGrid dataGrid = new DataGrid
            {
                AutoGenerateColumns = false,
                IsReadOnly = true,
                FontFamily = new System.Windows.Media.FontFamily("Consolas"),
                FontSize = 12
            };

            // Определение столбцов
            dataGrid.Columns.Clear();
            foreach (DataColumn column in combinedData.Columns)
            {
                dataGrid.Columns.Add(new DataGridTextColumn
                {
                    Header = column.ColumnName,
                    Binding = new Binding(column.ColumnName),
                    Width = new DataGridLength(1, DataGridLengthUnitType.Star)
                });
            }

            dataGrid.ItemsSource = combinedData.DefaultView;

            TabItem combinedTab = new TabItem
            {
                Header = header,
                Content = dataGrid
            };

            parentTabControl.Items.Add(combinedTab);
        }

        // Метод для преобразования объединённых данных
        private DataTable ConvertToCombinedDataTable(List<string[]> data, bool isTxt)
        {
            DataTable dt = new DataTable();

            if (isTxt)
            {
                dt.Columns.Add("№", typeof(int));
                dt.Columns.Add("BASE-1", typeof(string));
                dt.Columns.Add("Noise", typeof(string));
                dt.Columns.Add("Time", typeof(string));
                dt.Columns.Add("Time UTC", typeof(string));
            }
            else
            {
                // Для объединения .magnete файлов
                // Столбцы уже добавлены при инициализации
            }

            foreach (var row in data)
            {
                DataRow dr = dt.NewRow();
                if (isTxt)
                {
                    dr["№"] = int.Parse(row[0]);
                    dr["BASE-1"] = row[1];
                    dr["Noise"] = row[2];
                    dr["Time"] = row[3];
                    dr["Time UTC"] = row[4];
                }
                else
                {
                    // Для .magnete файлов ничего не требуется здесь
                }
                dt.Rows.Add(dr);
            }

            return dt;
        }

        #endregion

        #region Обработчик редактирования и экспорта

        // Обработчик кнопки Редактировать и Экспортировать
        private void EditAndExport_Click(object sender, RoutedEventArgs e)
        {
            if (combinedTxtDataTable == null && combinedMagneteDataTable == null)
            {
                MessageBox.Show("Нет объединённых данных для редактирования.", "Информация", MessageBoxButton.OK, MessageBoxImage.Information);
                return;
            }

            // Создаём и открываем окно выбора типа данных для редактирования
            SelectEditTypeWindow selectWindow = new SelectEditTypeWindow("Выберите данные для редактирования:");
            selectWindow.Owner = this;

            if (selectWindow.ShowDialog() == true)
            {
                string selectedOption = selectWindow.SelectedOption;

                if (selectedOption == ".txt файлы" && combinedTxtDataTable != null)
                {
                    OpenEditWindow(combinedTxtDataTable, "Редактирование .txt данных");
                }
                else if (selectedOption == ".magnete файлы" && combinedMagneteDataTable != null)
                {
                    OpenEditWindow(combinedMagneteDataTable, "Редактирование .magnete данных");
                }
                else
                {
                    MessageBox.Show("Выбранная опция недоступна или данные отсутствуют.", "Ошибка", MessageBoxButton.OK, MessageBoxImage.Error);
                }
            }
        }

        // Метод для открытия окна редактирования
        private void OpenEditWindow(DataTable dataTable, string windowTitle)
        {
            EditWindow editWindow = new EditWindow(dataTable)
            {
                Title = windowTitle,
                Owner = this
            };
            editWindow.ShowDialog();

            // После редактирования данные могут быть обновлены. При необходимости обновите отображение.
            // Например, обновляем вкладки с объединёнными данными
            // Здесь предполагается, что EditWindow модифицирует переданный DataTable напрямую
        }

        #endregion

        #region Обработчик кнопки "Проект"

        // Обработчик кнопки "Проект"
        private void ProjectButton_Click(object sender, RoutedEventArgs e)
        {
            Button button = sender as Button;
            if (button != null && button.ContextMenu != null)
            {
                button.ContextMenu.PlacementTarget = button;
                button.ContextMenu.Placement = PlacementMode.Bottom;
                button.ContextMenu.IsOpen = true;
            }
        }

        #endregion

        #region Сохранение и загрузка проекта

        // Обработчик кнопки "Сохранить проект"
        private void SaveProject_Click(object sender, RoutedEventArgs e)
        {
            SaveFileDialog saveFileDialog = new SaveFileDialog
            {
                Filter = "Magnte Project Files (*.magn)|*.magn",
                DefaultExt = "magn",
                FileName = "project.magn"
            };

            if (saveFileDialog.ShowDialog() == true)
            {
                ProjectData project = new ProjectData
                {
                    TxtFileData = this.txtFileData,
                    MagneteFileData = this.magneteFileData,
                    CombinedTxtDataTable = this.combinedTxtDataTable,
                    CombinedMagneteDataTable = this.combinedMagneteDataTable
                };

                try
                {
                    // Сериализуем объект в JSON
                    string json = JsonConvert.SerializeObject(project, Formatting.Indented, new JsonSerializerSettings
                    {
                        PreserveReferencesHandling = PreserveReferencesHandling.Objects,
                        ReferenceLoopHandling = ReferenceLoopHandling.Ignore
                    });
                    File.WriteAllText(saveFileDialog.FileName, json, System.Text.Encoding.UTF8);
                    StatusTextBlock.Text = $"Проект сохранён: {saveFileDialog.FileName}";
                    MessageBox.Show("Проект успешно сохранён.", "Успех", MessageBoxButton.OK, MessageBoxImage.Information);
                }
                catch (Exception ex)
                {
                    MessageBox.Show($"Ошибка при сохранении проекта:\n{ex.Message}", "Ошибка", MessageBoxButton.OK, MessageBoxImage.Error);
                }
            }
        }

        // Обработчик кнопки "Загрузить проект"
        private void LoadProject_Click(object sender, RoutedEventArgs e)
        {
            OpenFileDialog openFileDialog = new OpenFileDialog
            {
                Filter = "Magnte Project Files (*.magn)|*.magn",
                DefaultExt = "magn"
            };

            if (openFileDialog.ShowDialog() == true)
            {
                try
                {
                    string json = File.ReadAllText(openFileDialog.FileName, System.Text.Encoding.UTF8);
                    ProjectData project = JsonConvert.DeserializeObject<ProjectData>(json);

                    if (project != null)
                    {
                        // Загрузка данных .txt файлов
                        this.txtFileData = project.TxtFileData ?? new Dictionary<string, List<string[]>>();
                        PopulateTxtTabControl();

                        // Загрузка данных .magnete файлов
                        this.magneteFileData = project.MagneteFileData ?? new Dictionary<string, List<string[]>>();
                        PopulateMagneteTabControl();

                        // Загрузка объединённых DataTables
                        this.combinedTxtDataTable = project.CombinedTxtDataTable;
                        this.combinedMagneteDataTable = project.CombinedMagneteDataTable;

                        // Деактивируем кнопку редактирования до объединения
                        EditExportButton.IsEnabled = false;

                        // Обновляем вкладки с объединёнными данными, если они существуют
                        if (combinedTxtDataTable != null)
                        {
                            AddCombinedDataTab(combinedTxtDataTable, ".txt данные объединённые", TxtFilesTabControl);
                        }

                        if (combinedMagneteDataTable != null)
                        {
                            AddCombinedDataTab(combinedMagneteDataTable, ".magnete данные объединённые", MagneteFilesTabControl);
                        }

                        StatusTextBlock.Text = $"Проект загружен: {openFileDialog.FileName}";
                        MessageBox.Show("Проект успешно загружен.", "Успех", MessageBoxButton.OK, MessageBoxImage.Information);
                    }
                    else
                    {
                        MessageBox.Show("Не удалось загрузить данные проекта.", "Ошибка", MessageBoxButton.OK, MessageBoxImage.Error);
                    }
                }
                catch (Exception ex)
                {
                    MessageBox.Show($"Ошибка при загрузке проекта:\n{ex.Message}", "Ошибка", MessageBoxButton.OK, MessageBoxImage.Error);
                }
            }
        }

        #endregion

        #region Методы для объединения .magnete файлов (оригинальная логика)

        // Метод логирования расчётов и ошибок
        private void LogCalculation(string message, string logFileName = "calculation_log.txt")
        {
            string logFilePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, logFileName);
            try
            {
                File.AppendAllText(logFilePath, $"{DateTime.Now}: {message}{Environment.NewLine}");
            }
            catch (Exception ex)
            {
                MessageBox.Show($"Не удалось записать лог: {ex.Message}", "Ошибка логирования", MessageBoxButton.OK, MessageBoxImage.Error);
            }
        }

        // Метод для определения UTM-зоны по долготе
        private int GetUTMZone(double longitude)
        {
            // Убедитесь, что долгота находится в диапазоне от -180 до 180 градусов
            if (longitude < -180 || longitude > 180)
                throw new ArgumentOutOfRangeException("Долгота должна быть в диапазоне от -180 до 180 градусов.");

            // Формула для расчёта зоны
            int zone = (int)Math.Floor((longitude + 180) / 6) + 1;
            return zone;
        }

        // Метод для преобразования WGS84 координат в UTM
        private (double Easting, double Northing) ConvertWGS84ToUTM(double longitude, double latitude, int utmZone)
        {
            // Определяем систему координат WGS84
            var wgs84 = GeographicCoordinateSystem.WGS84;

            // Определяем систему координат UTM для данного пояса и полушария
            var utmCS = ProjectedCoordinateSystem.WGS84_UTM(utmZone, latitude >= 0);

            // Создаём трансформацию
            var transformFactory = new CoordinateTransformationFactory();
            var transformation = transformFactory.CreateFromCoordinateSystems(wgs84, utmCS);

            // Преобразование координат
            double[] fromPoint = new double[] { longitude, latitude };
            double[] toPoint = transformation.MathTransform.Transform(fromPoint);

            return (toPoint[0], toPoint[1]); // Easting, Northing
        }

        #endregion

        #region Обработчик кнопки "Графики"

        // Обработчик кнопки "Графики"
        private void ShowGraphs_Click(object sender, RoutedEventArgs e)
        {
            if (combinedTxtDataTable == null && combinedMagneteDataTable == null)
            {
                MessageBox.Show("Нет объединённых данных для построения графиков.", "Информация", MessageBoxButton.OK, MessageBoxImage.Information);
                return;
            }

            GraphWindow graphWindow = new GraphWindow(combinedTxtDataTable, combinedMagneteDataTable);
            graphWindow.Owner = this;
            graphWindow.Show();
        }

        #endregion
    }
}

<!-- GraphWindow.xaml -->
<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="Графики" Height="800" Width="1200">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="*"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*"/>
            <ColumnDefinition Width="*"/>
        </Grid.ColumnDefinitions>

        <!-- График 1: .txt Time UTC vs BASE-1 -->
        <GroupBox Header=".txt Time UTC vs BASE-1" Grid.Row="0" Grid.Column="0" Margin="10">
            <oxy:PlotView x:Name="TxtBase1Plot" Model="{Binding TxtBase1Model}" />
        </GroupBox>

        <!-- График 2: .txt Time UTC vs Noise -->
        <GroupBox Header=".txt Time UTC vs Noise" Grid.Row="0" Grid.Column="1" Margin="10">
            <oxy:PlotView x:Name="TxtNoisePlot" Model="{Binding TxtNoiseModel}" />
        </GroupBox>

        <!-- График 3: .magnete Долгота UTM vs Широта UTM (Альтитуда) -->
        <GroupBox Header=".magnete Долгота UTM vs Широта UTM (Альтитуда)" Grid.Row="1" Grid.Column="0" Margin="10">
            <oxy:PlotView x:Name="MagneteAltitudePlot" Model="{Binding MagneteAltitudeModel}" />
        </GroupBox>

        <!-- График 4: .magnete Долгота UTM vs Широта UTM (Shark) -->
        <GroupBox Header=".magnete Долгота UTM vs Широта UTM (Shark)" Grid.Row="1" Grid.Column="1" Margin="10">
            <oxy:PlotView x:Name="MagneteSharkPlot" Model="{Binding MagneteSharkModel}" />
        </GroupBox>
    </Grid>
</Window>

// GraphWindow.xaml.cs
using OxyPlot;
using OxyPlot.Axes;
using OxyPlot.Series;
using System;
using System.Data;
using System.Linq;
using System.Windows;

namespace TxtProcessorApp
{
    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; }

        public GraphWindow(DataTable txtData, DataTable magneteData)
        {
            InitializeComponent();
            InitializePlotModels(txtData, magneteData);
            this.DataContext = this;
        }

        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 LineSeries { Title = "BASE-1" };
            foreach (DataRow row in txtData.Rows)
            {
                if (DateTime.TryParse(row["Time UTC"].ToString(), out DateTime timeUtc) &&
                    double.TryParse(row["BASE-1"].ToString(), out double base1))
                {
                    txtBase1Series.Points.Add(DateTimeAxis.CreateDataPoint(timeUtc, base1));
                }
            }
            TxtBase1Model.Series.Add(txtBase1Series);

            // График 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 LineSeries { Title = "Noise" };
            foreach (DataRow row in txtData.Rows)
            {
                if (DateTime.TryParse(row["Time UTC"].ToString(), out DateTime timeUtc) &&
                    double.TryParse(row["Noise"].ToString(), out double noise))
                {
                    txtNoiseSeries.Points.Add(DateTimeAxis.CreateDataPoint(timeUtc, noise));
                }
            }
            TxtNoiseModel.Series.Add(txtNoiseSeries);

            // График 3: .magnete Долгота UTM vs Широта UTM (Альтитуда)
            MagneteAltitudeModel = new PlotModel { Title = ".magnete Долгота UTM vs Широта UTM (Альтитуда)" };
            MagneteAltitudeModel.Axes.Add(new LinearAxis
            {
                Position = AxisPosition.Bottom,
                Title = "Долгота UTM"
            });
            MagneteAltitudeModel.Axes.Add(new LinearAxis
            {
                Position = AxisPosition.Left,
                Title = "Широта UTM"
            });
            var magneteAltitudeSeries = new ScatterSeries { Title = "Альтитуда", MarkerType = MarkerType.Circle };
            foreach (DataRow row in magneteData.Rows)
            {
                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));
                }
            }
            MagneteAltitudeModel.Axes.Add(new LinearColorAxis
            {
                Position = AxisPosition.Right,
                Palette = OxyPalettes.Jet(500),
                Title = "Альтитуда"
            });
            MagneteAltitudeModel.Series.Add(magneteAltitudeSeries);

            // График 4: .magnete Долгота UTM vs Широта UTM (Shark)
            MagneteSharkModel = new PlotModel { Title = ".magnete Долгота UTM vs Широта UTM (Shark)" };
            MagneteSharkModel.Axes.Add(new LinearAxis
            {
                Position = AxisPosition.Bottom,
                Title = "Долгота UTM"
            });
            MagneteSharkModel.Axes.Add(new LinearAxis
            {
                Position = AxisPosition.Left,
                Title = "Широта UTM"
            });
            var magneteSharkSeries = new ScatterSeries { Title = "Shark", MarkerType = MarkerType.Circle };
            foreach (DataRow row in magneteData.Rows)
            {
                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));
                }
            }
            MagneteSharkModel.Axes.Add(new LinearColorAxis
            {
                Position = AxisPosition.Right,
                Palette = OxyPalettes.Jet(500),
                Title = "Shark"
            });
            MagneteSharkModel.Series.Add(magneteSharkSeries);
        }
    }
}

<!-- SelectEditTypeWindow.xaml -->
<Window x:Class="TxtProcessorApp.SelectEditTypeWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Выбор данных" Height="200" Width="400" WindowStartupLocation="CenterOwner" ResizeMode="NoResize">
    <Grid Margin="10">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>

        <TextBlock Text="Выберите данные:" Grid.Row="0" Margin="0,0,0,10" FontWeight="Bold" />

        <ListBox x:Name="OptionsListBox" Grid.Row="1">
            <ListBoxItem Content=".txt файлы" />
            <ListBoxItem Content=".magnete файлы" />
        </ListBox>

        <StackPanel Grid.Row="2" Orientation="Horizontal" HorizontalAlignment="Right" Margin="0,10,0,0">
            <Button Content="OK" Width="75" Margin="0,0,10,0" IsDefault="True" Click="OK_Click"/>
            <Button Content="Отмена" Width="75" IsCancel="True"/>
        </StackPanel>
    </Grid>
</Window>

// SelectEditTypeWindow.xaml.cs
using System.Windows;
using System.Windows.Controls;

namespace TxtProcessorApp
{
    public partial class SelectEditTypeWindow : Window
    {
        public string SelectedOption { get; private set; }

        public SelectEditTypeWindow(string prompt)
        {
            InitializeComponent();
            // Можно добавить дополнительную настройку окна при необходимости
        }

        private void OK_Click(object sender, RoutedEventArgs e)
        {
            if (OptionsListBox.SelectedItem is ListBoxItem selectedItem)
            {
                SelectedOption = selectedItem.Content.ToString();
                DialogResult = true;
            }
            else
            {
                MessageBox.Show("Пожалуйста, выберите опцию.", "Внимание", MessageBoxButton.OK, MessageBoxImage.Warning);
            }
        }
    }
}

<!-- EditWindow.xaml -->
<Window x:Class="TxtProcessorApp.EditWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:TxtProcessorApp"
        Title="Редактирование данных" Height="600" Width="900">
    <Grid Margin="10">
        <Grid.RowDefinitions>
            <RowDefinition Height="*"/>
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>

        <!-- DataGrid для отображения и редактирования данных -->
        <DataGrid x:Name="EditDataGrid" Grid.Row="0" AutoGenerateColumns="True" 
                  CanUserAddRows="True" CanUserDeleteRows="True" 
                  IsReadOnly="False" 
                  SelectionUnit="CellOrRowHeader" 
                  SelectedCellsChanged="EditDataGrid_SelectedCellsChanged"
                  FontFamily="Consolas" FontSize="12">
            <!-- Контекстное меню для вставки строк и столбцов -->
            <DataGrid.ContextMenu>
                <ContextMenu>
                    <MenuItem Header="Вставить строку выше" Click="InsertRowAbove_Click"/>
                    <MenuItem Header="Вставить строку ниже" Click="InsertRowBelow_Click"/>
                    <Separator/>
                    <MenuItem Header="Вставить столбец слева" Click="InsertColumnLeft_Click"/>
                    <MenuItem Header="Вставить столбец справа" Click="InsertColumnRight_Click"/>
                </ContextMenu>
            </DataGrid.ContextMenu>
        </DataGrid>

        <!-- Панель кнопок ниже DataGrid -->
        <StackPanel Grid.Row="1" Orientation="Horizontal" HorizontalAlignment="Left" Margin="0,10,0,0">
            <!-- Элемент управления для выбора разделителя -->
            <ComboBox x:Name="DelimiterComboBox" Width="100" Height="30" SelectedIndex="0">
                <ComboBoxItem Content="Пробел"/>
                <ComboBoxItem Content="Табуляция"/>
                <ComboBoxItem Content="Запятая"/>
            </ComboBox>
            <Button Content="Экспортировать файл" Width="150" Height="30" Margin="10,0,0,0" Click="ExportButton_Click"/>
        </StackPanel>
    </Grid>
</Window>

// EditWindow.xaml.cs
using Microsoft.Win32;
using System;
using System.Data;
using System.Linq;
using System.IO;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using ProjNet.CoordinateSystems.Transformations;
using ProjNet.CoordinateSystems;

namespace TxtProcessorApp
{
    /// <summary>
    /// Логика взаимодействия для EditWindow.xaml
    /// </summary>
    public partial class EditWindow : Window
    {
        private DataTable _dataTable;

        // Словарь для сопоставления исходных заголовков с заголовками для экспорта
        private readonly Dictionary<string, string> exportHeaderMapping = new Dictionary<string, string>
        {
            { "No1", "№1" },
            { "No2", "№2" }
            // Добавьте другие сопоставления, если необходимо
        };

        public EditWindow(DataTable dataTable)
        {
            InitializeComponent();
            _dataTable = dataTable;
            EditDataGrid.ItemsSource = _dataTable.DefaultView;

            // Разрешаем добавление и удаление строк через интерфейс DataGrid
            EditDataGrid.CanUserAddRows = true;
            EditDataGrid.CanUserDeleteRows = true;
        }

        #region Вставка строк и столбцов

        // Вставка строки выше выбранной
        private void InsertRowAbove_Click(object sender, RoutedEventArgs e)
        {
            int rowIndex = GetSelectedRowIndex();
            if (rowIndex != -1)
            {
                _dataTable.Rows.InsertAt(_dataTable.NewRow(), rowIndex);
            }
            else
            {
                // Если ничего не выбрано, вставляем в конец
                _dataTable.Rows.Add(_dataTable.NewRow());
            }

            RefreshDataGrid();
        }

        // Вставка строки ниже выбранной
        private void InsertRowBelow_Click(object sender, RoutedEventArgs e)
        {
            int rowIndex = GetSelectedRowIndex();
            if (rowIndex != -1)
            {
                _dataTable.Rows.InsertAt(_dataTable.NewRow(), rowIndex + 1);
            }
            else
            {
                // Если ничего не выбрано, вставляем в конец
                _dataTable.Rows.Add(_dataTable.NewRow());
            }

            RefreshDataGrid();
        }

        // Вставка столбца слева от выбранного
        private void InsertColumnLeft_Click(object sender, RoutedEventArgs e)
        {
            int columnIndex = GetSelectedColumnIndex();
            InsertNewColumn(columnIndex, "NewColumn");
            RefreshDataGrid();
        }

        // Вставка столбца справа от выбранного
        private void InsertColumnRight_Click(object sender, RoutedEventArgs e)
        {
            int columnIndex = GetSelectedColumnIndex();
            InsertNewColumn(columnIndex + 1, "NewColumn");
            RefreshDataGrid();
        }

        // Добавление нового столбца в указанное место
        private void InsertNewColumn(int index, string defaultName)
        {
            // Предлагаем пользователю ввести имя нового столбца
            InputDialog inputDialog = new InputDialog("Введите имя нового столбца:", defaultName);
            if (inputDialog.ShowDialog() == true)
            {
                string columnName = inputDialog.ResponseText.Trim();
                if (string.IsNullOrEmpty(columnName))
                {
                    MessageBox.Show("Имя столбца не может быть пустым.", "Ошибка", MessageBoxButton.OK, MessageBoxImage.Error);
                    return;
                }

                // Проверяем, нет ли уже столбца с таким именем
                if (_dataTable.Columns.Contains(columnName))
                {
                    MessageBox.Show("Столбец с таким именем уже существует.", "Ошибка", MessageBoxButton.OK, MessageBoxImage.Error);
                    return;
                }

                // Создаём новый столбец
                DataColumn newColumn = new DataColumn(columnName, typeof(string));

                // Вставляем столбец в нужное место
                _dataTable.Columns.Add(newColumn);
                newColumn.SetOrdinal(Math.Min(index, _dataTable.Columns.Count - 1));

                // Устанавливаем значение по умолчанию для существующих строк
                foreach (DataRow row in _dataTable.Rows)
                {
                    row[newColumn] = string.Empty;
                }
            }
            else
            {
                // Если пользователь отменил ввод, автоматически генерируем уникальное имя
                string autoColumnName = GenerateUniqueColumnName("NewColumn");
                DataColumn newColumn = new DataColumn(autoColumnName, typeof(string));
                _dataTable.Columns.Add(newColumn);
                newColumn.SetOrdinal(Math.Min(index, _dataTable.Columns.Count - 1));

                foreach (DataRow row in _dataTable.Rows)
                {
                    row[newColumn] = string.Empty;
                }
            }
        }

        // Генерация уникального имени столбца
        private string GenerateUniqueColumnName(string baseName)
        {
            int suffix = 1;
            string newName = baseName;
            while (_dataTable.Columns.Contains(newName))
            {
                newName = $"{baseName}_{suffix}";
                suffix++;
            }
            return newName;
        }

        // Получение индекса выбранной строки
        private int GetSelectedRowIndex()
        {
            if (EditDataGrid.SelectedItem is DataRowView selectedRow)
            {
                return _dataTable.Rows.IndexOf(selectedRow.Row);
            }
            return -1;
        }

        // Получение индекса выбранного столбца
        private int GetSelectedColumnIndex()
        {
            if (EditDataGrid.SelectedCells.Count > 0)
            {
                var selectedCell = EditDataGrid.SelectedCells[0];
                var column = selectedCell.Column;
                if (column != null)
                {
                    return column.DisplayIndex;
                }
            }
            return -1;
        }

        #endregion

        // Метод для обновления привязки данных к DataGrid
        private void RefreshDataGrid()
        {
            // Получаем текущий view
            var view = CollectionViewSource.GetDefaultView(EditDataGrid.ItemsSource);

            if (view != null)
            {
                view.Refresh();
            }
        }

        // Обработчик изменения выбранных ячеек (опционально)
        private void EditDataGrid_SelectedCellsChanged(object sender, SelectedCellsChangedEventArgs e)
        {
            // Дополнительная логика при изменении выбора (если требуется)
        }

        // Обработчик кнопки Экспортировать файл
        private void ExportButton_Click(object sender, RoutedEventArgs e)
        {
            // Выбор разделителя
            string delimiter = GetSelectedDelimiter();

            SaveFileDialog sfd = new SaveFileDialog
            {
                Filter = delimiter == "," ? "CSV файлы (*.csv)|*.csv" : "Текстовые файлы (*.txt)|*.txt",
                FileName = delimiter == "," ? "EditedCombinedFile.csv" : "EditedCombinedFile.txt"
            };

            if (sfd.ShowDialog() == true)
            {
                try
                {
                    using (StreamWriter sw = new StreamWriter(sfd.FileName, false, Encoding.UTF8))
                    {
                        if (delimiter == " ")
                        {
                            // Фиксированная ширина с использованием PadRight
                            ExportWithFixedWidth(sw, delimiter);
                        }
                        else
                        {
                            // Экспорт в CSV или табуляцию
                            ExportWithDelimiter(sw, delimiter);
                        }
                    }

                    MessageBox.Show($"Файл успешно экспортирован по адресу:\n{sfd.FileName}", "Успех", MessageBoxButton.OK, MessageBoxImage.Information);
                }
                catch (Exception ex)
                {
                    MessageBox.Show($"Ошибка при сохранении файла:\n{ex.Message}", "Ошибка", MessageBoxButton.OK, MessageBoxImage.Error);
                }
            }
        }

        /// <summary>
        /// Экспорт данных с фиксированной шириной столбцов, используя PadRight.
        /// Подходит для текстовых файлов с пробелом в качестве разделителя.
        /// </summary>
        /// <param name="sw">Поток для записи.</param>
        /// <param name="delimiter">Разделитель.</param>
        private void ExportWithFixedWidth(StreamWriter sw, string delimiter)
        {
            var columns = _dataTable.Columns.Cast<DataColumn>().ToList();

            // Шаг 1: Вычисляем максимальную ширину для каждого столбца
            int[] maxColWidths = new int[columns.Count];

            for (int i = 0; i < columns.Count; i++)
            {
                int maxWidth = columns[i].ColumnName.Length;

                foreach (DataRow row in _dataTable.Rows)
                {
                    string field = row[i]?.ToString() ?? string.Empty;
                    if (field.Length > maxWidth)
                    {
                        maxWidth = field.Length;
                    }
                }

                maxColWidths[i] = maxWidth;
            }

            // Шаг 2: Запись заголовков с padding
            string headerLine = string.Empty;
            for (int i = 0; i < columns.Count; i++)
            {
                string header = columns[i].ColumnName;
                headerLine += header.PadRight(maxColWidths[i] + 2); // Добавляем 2 пробела между колонками
            }

            sw.WriteLine(headerLine);

            // Шаг 3: Запись данных строк с padding
            foreach (DataRow row in _dataTable.Rows)
            {
                string dataLine = string.Empty;
                for (int i = 0; i < columns.Count; i++)
                {
                    string field = row[i]?.ToString() ?? string.Empty;
                    dataLine += field.PadRight(maxColWidths[i] + 2);
                }
                sw.WriteLine(dataLine);
            }
        }

        /// <summary>
        /// Экспорт данных с использованием указанного разделителя (CSV или табуляция).
        /// </summary>
        /// <param name="sw">Поток для записи.</param>
        /// <param name="delimiter">Разделитель.</param>
        private void ExportWithDelimiter(StreamWriter sw, string delimiter)
        {
            // Запись заголовков
            string[] headers = _dataTable.Columns
                .Cast<DataColumn>()
                .Select(column =>
                {
                    if (exportHeaderMapping.ContainsKey(column.ColumnName))
                        return $"\"{exportHeaderMapping[column.ColumnName]}\"";
                    else
                        return $"\"{column.ColumnName}\"";
                })
                .ToArray();
            sw.WriteLine(string.Join(delimiter, headers));

            // Запись данных строк
            foreach (DataRow row in _dataTable.Rows)
            {
                string[] fields = row.ItemArray.Select(field =>
                {
                    string fieldStr = field?.ToString() ?? string.Empty;
                    // Если поле содержит разделитель или кавычки, обрамляем его в кавычки и экранируем двойные кавычки
                    if (fieldStr.Contains(delimiter) || fieldStr.Contains("\""))
                    {
                        fieldStr = $"\"{fieldStr.Replace("\"", "\"\"")}\"";
                    }
                    return fieldStr;
                }).ToArray();
                sw.WriteLine(string.Join(delimiter, fields));
            }
        }

        /// <summary>
        /// Метод для получения выбранного разделителя.
        /// </summary>
        /// <returns>Разделитель в виде строки.</returns>
        private string GetSelectedDelimiter()
        {
            switch (DelimiterComboBox.SelectedIndex)
            {
                case 0:
                    return " "; // Пробел
                case 1:
                    return "\t"; // Табуляция
                case 2:
                    return ","; // Запятая
                default:
                    return "\t"; // По умолчанию табуляция
            }
        }



        #region Методы для объединения .magnete файлов (оригинальная логика)

        // Метод логирования расчётов и ошибок
        private void LogCalculation(string message, string logFileName = "calculation_log.txt")
        {
            string logFilePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, logFileName);
            try
            {
                File.AppendAllText(logFilePath, $"{DateTime.Now}: {message}{Environment.NewLine}");
            }
            catch (Exception ex)
            {
                MessageBox.Show($"Не удалось записать лог: {ex.Message}", "Ошибка логирования", MessageBoxButton.OK, MessageBoxImage.Error);
            }
        }

        // Метод для определения UTM-зоны по долготе
        private int GetUTMZone(double longitude)
        {
            // Убедитесь, что долгота находится в диапазоне от -180 до 180 градусов
            if (longitude < -180 || longitude > 180)
                throw new ArgumentOutOfRangeException("Долгота должна быть в диапазоне от -180 до 180 градусов.");

            // Формула для расчёта зоны
            int zone = (int)Math.Floor((longitude + 180) / 6) + 1;
            return zone;
        }

        // Метод для преобразования WGS84 координат в UTM
        private (double Easting, double Northing) ConvertWGS84ToUTM(double longitude, double latitude, int utmZone)
        {
            // Определяем систему координат WGS84
            var wgs84 = GeographicCoordinateSystem.WGS84;

            // Определяем систему координат UTM для данного пояса и полушария
            var utmCS = ProjectedCoordinateSystem.WGS84_UTM(utmZone, latitude >= 0);

            // Создаём трансформацию
            var transformFactory = new CoordinateTransformationFactory();
            var transformation = transformFactory.CreateFromCoordinateSystems(wgs84, utmCS);

            // Преобразование координат
            double[] fromPoint = new double[] { longitude, latitude };
            double[] toPoint = transformation.MathTransform.Transform(fromPoint);

            return (toPoint[0], toPoint[1]); // Easting, Northing
        }

        #endregion
    }
}

<Window x:Class="TxtProcessorApp.InputDialog"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Ввод" Height="150" Width="400" WindowStartupLocation="CenterOwner" ResizeMode="NoResize">
    <Grid Margin="10">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>

        <TextBlock x:Name="PromptTextBlock" Grid.Row="0" Text="Prompt:" Margin="0,0,0,10" TextWrapping="Wrap"/>

        <TextBox x:Name="ResponseTextBox" Grid.Row="1" Height="25"/>

        <StackPanel Grid.Row="2" Orientation="Horizontal" HorizontalAlignment="Right" Margin="0,10,0,0">
            <Button Content="OK" Width="75" Margin="0,0,10,0" IsDefault="True" Click="OK_Click"/>
            <Button Content="Отмена" Width="75" IsCancel="True"/>
        </StackPanel>
    </Grid>
</Window>

using System.Windows;

namespace TxtProcessorApp
{
    /// <summary>
    /// Логика взаимодействия для InputDialog.xaml
    /// </summary>
    public partial class InputDialog : Window
    {
        public string ResponseText { get; private set; }

        public InputDialog(string question, string defaultAnswer = "")
        {
            InitializeComponent();
            PromptTextBlock.Text = question;
            ResponseTextBox.Text = defaultAnswer;
            ResponseTextBox.SelectAll();
            ResponseTextBox.Focus();
        }

        private void OK_Click(object sender, RoutedEventArgs e)
        {
            ResponseText = ResponseTextBox.Text;
            DialogResult = true;
        }
    }
}

// ProjectData.cs
using System.Collections.Generic;
using System.Data;

namespace TxtProcessorApp
{
    public class ProjectData
    {
        public Dictionary<string, List<string[]>> TxtFileData { get; set; }
        public Dictionary<string, List<string[]>> MagneteFileData { get; set; }
        public DataTable CombinedTxtDataTable { get; set; }
        public DataTable CombinedMagneteDataTable { get; set; }
    }
}