при нажатии кнопки удаления карты карта не исчезает

<Window x:Class="ContourMapVisualizer.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:ContourMapVisualizer"
        Title="Contour Map Visualizer" Height="600" Width="1000" Icon="/Screenshot 2024-11-06 152658.png" ResizeMode="CanResizeWithGrip" WindowStyle="ThreeDBorderWindow">
    <Window.Resources>
        <DataTemplate x:Key="ContourMapTemplate">
            <Border BorderBrush="Black" BorderThickness="1" Margin="5" Padding="5">
                <Grid>
                    <StackPanel>
                        <TextBlock Text="{Binding FileName}" FontWeight="Bold" HorizontalAlignment="Center" Margin="0,0,0,5"/>
                        <Image Source="{Binding ContourBitmap}" Stretch="Uniform" Width="300" Height="300"/>
                        <StackPanel Orientation="Horizontal" Margin="0,5,0,0">
                            <TextBlock Text="Min:" VerticalAlignment="Center" />
                            <TextBox Text="{Binding MinValue, UpdateSourceTrigger=PropertyChanged}" Width="60" Margin="5,0"/>
                            <TextBlock Text="Max:" VerticalAlignment="Center" Margin="10,0,0,0"/>
                            <TextBox Text="{Binding MaxValue, UpdateSourceTrigger=PropertyChanged}" Width="60" Margin="5,0"/>
                            <Button Content="Auto Range" Click="AutoRangeButton_Click" Tag="{Binding}" Margin="10,0,0,0"/>
                        </StackPanel>
                    </StackPanel>
                    <!-- Кнопка-крестик для удаления карты -->
                    <Button Content="×" 
                            Width="20" 
                            Height="20" 
                            HorizontalAlignment="Right" 
                            VerticalAlignment="Top" 
                            Background="Transparent" 
                            BorderBrush="Transparent" 
                            Foreground="Red" 
                            FontWeight="Bold"
                            ToolTip="Удалить карту"
                            Click="RemoveMapButton_Click" 
                            Margin="0,0,5,0"/>
                </Grid>
            </Border>
        </DataTemplate>
    </Window.Resources>
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
            <RowDefinition Height="30"/>
        </Grid.RowDefinitions>

        <StackPanel Grid.Row="0" Orientation="Horizontal" Margin="10">
            <Button Content="Open DAT Files" Click="OpenFile_Click" Width="120" Margin="0,0,10,0"/>
            <ComboBox x:Name="PaletteSelector" Width="120" Margin="0,0,10,0" SelectionChanged="PaletteSelector_SelectionChanged">
                <ComboBoxItem Content="Rainbow"/>
                <ComboBoxItem Content="Hot"/>
                <ComboBoxItem Content="Cool"/>
                <ComboBoxItem Content="Gray Scale"/>
            </ComboBox>
            <CheckBox x:Name="ReverseScale" Content="Reverse Scale" Checked="ReverseScale_CheckedChanged" 
                     Unchecked="ReverseScale_CheckedChanged" VerticalAlignment="Center"/>
            <Button Content="Auto Range" Click="AutoRange_Click" Width="100" Margin="10,0,0,0"/>
            <!-- Новая кнопка Auto Range -->
        </StackPanel>

        <ScrollViewer Grid.Row="1" Margin="10">
            <ItemsControl x:Name="ContourImagesControl" 
                          ItemTemplate="{StaticResource ContourMapTemplate}"
                          ItemsSource="{Binding LoadedFiles}">
                <ItemsControl.ItemsPanel>
                    <ItemsPanelTemplate>
                        <WrapPanel/>
                    </ItemsPanelTemplate>
                </ItemsControl.ItemsPanel>
            </ItemsControl>
        </ScrollViewer>

        <StackPanel Grid.Row="2" Orientation="Horizontal" HorizontalAlignment="Center" 
                    Visibility="Collapsed" x:Name="ColorScalePanel">
            <TextBlock x:Name="MinScaleLabel" Text="Min" Margin="5,0" VerticalAlignment="Center"/>
            <Image x:Name="ColorScale" Height="20" Width="256"/>
            <TextBlock x:Name="MaxScaleLabel" Text="Max" Margin="5,0" VerticalAlignment="Center"/>
        </StackPanel>
    </Grid>
</Window>

using Microsoft.Win32;
using System;
using System.Collections.ObjectModel; // Добавлено
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Imaging;

namespace ContourMapVisualizer
{
    public partial class MainWindow : Window, INotifyPropertyChanged // Добавлено INotifyPropertyChanged
    {
        private ObservableCollection<LoadedFile> loadedFiles = new ObservableCollection<LoadedFile>(); // Изменено
        public ObservableCollection<LoadedFile> LoadedFiles
        {
            get => loadedFiles;
            set
            {
                if (loadedFiles != value)
                {
                    loadedFiles = value;
                    OnPropertyChanged(nameof(LoadedFiles));
                }
            }
        }

        private string currentPalette = "Rainbow";
        private bool isScaleReversed = false;

        public MainWindow()
        {
            InitializeComponent();
            PaletteSelector.SelectedIndex = 0;
            this.DataContext = this; // Установка DataContext
        }

        public event PropertyChangedEventHandler PropertyChanged;

        protected void OnPropertyChanged(string propertyName)
            => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));

        private void OpenFile_Click(object sender, RoutedEventArgs e)
        {
            var openFileDialog = new OpenFileDialog
            {
                Filter = "DAT files (*.dat)|*.dat|All files (*.*)|*.*",
                Multiselect = true
            };

            if (openFileDialog.ShowDialog() == true)
            {
                var previewWindow = new FilePreviewWindow(openFileDialog.FileNames);
                previewWindow.Owner = this;

                if (previewWindow.ShowDialog() == true)
                {
                    try
                    {
                        foreach (var file in previewWindow.SelectedFiles)
                        {
                            var loadedFile = LoadDataFromFile(file.FilePath, file.SkipLines);
                            LoadedFiles.Add(loadedFile);

                            // Подписываемся на изменения свойств LoadedFile
                            loadedFile.PropertyChanged += LoadedFile_PropertyChanged;
                        }

                        // Проверка успешной загрузки данных
                        MessageBox.Show("Данные успешно загружены.", "Успех", MessageBoxButton.OK, MessageBoxImage.Information);

                        UpdateColorScales();
                        ColorScalePanel.Visibility = Visibility.Visible;
                    }
                    catch (Exception ex)
                    {
                        MessageBox.Show($"Ошибка при обработке файла: {ex.Message}", "Ошибка", MessageBoxButton.OK, MessageBoxImage.Error);
                    }
                }
            }
        }

        private void RemoveMapButton_Click(object sender, RoutedEventArgs e)
        {
            if (sender is Button button && button.Tag is LoadedFile file)
            {
                // Подтверждение удаления
                var result = MessageBox.Show($"Вы уверены, что хотите удалить карту \"{file.FileName}\"?", "Подтверждение удаления", MessageBoxButton.YesNo, MessageBoxImage.Question);
                if (result == MessageBoxResult.Yes)
                {
                    // Удаление файла из коллекции
                    LoadedFiles.Remove(file);

                    // Обновление цветовой шкалы
                    UpdateColorScales();

                    // Если все карты удалены, скрыть панель цветовой шкалы
                    if (LoadedFiles.Count == 0)
                    {
                        ColorScalePanel.Visibility = Visibility.Collapsed;
                    }
                }
            }
        }

        private void LoadedFile_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            if (sender is LoadedFile file)
            {
                if (e.PropertyName == nameof(LoadedFile.MinValue) || e.PropertyName == nameof(LoadedFile.MaxValue))
                {
                    // Перегенерируем ContourBitmap для этого файла
                    var bitmap = GenerateContourBitmap(file);
                    file.ContourBitmap = bitmap;
                }
                else if (e.PropertyName == nameof(LoadedFile.Palette))
                {
                    // Палитра изменена: перегенерируем все карты
                    foreach (var f in LoadedFiles)
                    {
                        f.ContourBitmap = GenerateContourBitmap(f);
                    }
                    UpdateColorScales();
                }
                // Можно добавить дополнительные условия при необходимости
            }
        }

        private LoadedFile LoadDataFromFile(string filename, int skipLines)
        {
            var allLines = File.ReadAllLines(filename);
            var lines = allLines.Skip(skipLines).ToArray();

            if (lines.Length == 0)
            {
                throw new Exception($"Нет данных для отображения в файле {filename} после пропуска {skipLines} строк.");
            }

            int rows = lines.Length;
            int cols = lines[0].Split(new[] { ' ', '\t' }, StringSplitOptions.RemoveEmptyEntries).Length;

            double[,] data = new double[rows, cols];
            double minValue = double.MaxValue;
            double maxValue = double.MinValue;

            for (int i = 0; i < rows; i++)
            {
                var values = lines[i].Split(new[] { ' ', '\t' }, StringSplitOptions.RemoveEmptyEntries);
                if (values.Length != cols)
                {
                    throw new Exception($"Неправильный формат данных в файле {filename} на строке {i + skipLines + 1}.");
                }

                for (int j = 0; j < cols; j++)
                {
                    if (!double.TryParse(values[j], out double value))
                    {
                        throw new Exception($"Неверное число в файле {filename} на строке {i + skipLines + 1}, позиции {j + 1}.");
                    }
                    data[i, j] = value;
                    minValue = Math.Min(minValue, data[i, j]);
                    maxValue = Math.Max(maxValue, data[i, j]);
                }
            }

            var loadedFileObj = new LoadedFile
            {
                FilePath = filename,
                FileName = System.IO.Path.GetFileName(filename),
                Data = data,
                MinValue = minValue,
                MaxValue = maxValue,
                Palette = currentPalette,
                IsScaleReversed = isScaleReversed
            };

            // Отладочное сообщение (можно заменить на логирование)
            Console.WriteLine($"Файл {loadedFileObj.FileName} загружен: {rows} строк, {cols} столбцов, Min={minValue}, Max={maxValue}");

            // Инициализируем ContourBitmap
            loadedFileObj.ContourBitmap = GenerateContourBitmap(loadedFileObj);

            return loadedFileObj;
        }

        private WriteableBitmap GenerateContourBitmap(LoadedFile file)
        {
            if (file.Data == null) return null;

            // Используем индивидуальные Min и Max для каждой карты
            double minVal = file.MinValue;
            double maxVal = file.MaxValue;

            if (minVal == maxVal)
            {
                // Избегаем деления на ноль
                minVal -= 1;
                maxVal += 1;
            }

            int width = file.Data.GetLength(1);
            int height = file.Data.GetLength(0);

            var bitmap = new WriteableBitmap(width, height, 96, 96, PixelFormats.Bgr32, null);
            int stride = bitmap.BackBufferStride;
            int[] pixels = new int[width * height];

            for (int i = 0; i < height; i++)
            {
                for (int j = 0; j < width; j++)
                {
                    double normalizedValue = (file.Data[i, j] - minVal) / (maxVal - minVal);
                    normalizedValue = Math.Max(0, Math.Min(1, normalizedValue));

                    double effectiveValue = file.IsScaleReversed ? 1 - normalizedValue : normalizedValue;

                    Color color = GetColorFromPalette(effectiveValue, file.Palette);
                    pixels[i * width + j] = (color.A << 24) | (color.R << 16) | (color.G << 8) | color.B;
                }
            }

            bitmap.WritePixels(new Int32Rect(0, 0, width, height), pixels, stride, 0);

            // Опционально: масштабируем битмап до желаемого размера отображения
            return bitmap;
        }

        private void UpdateColorScales()
        {
            if (LoadedFiles.Count == 0)
            {
                ColorScalePanel.Visibility = Visibility.Collapsed;
                return;
            }

            // Вычисляем глобальный min и max для цветовой шкалы
            double globalMin = LoadedFiles.Min(f => f.MinValue);
            double globalMax = LoadedFiles.Max(f => f.MaxValue);

            if (globalMin == globalMax)
            {
                globalMin -= 1;
                globalMax += 1;
            }

            MinScaleLabel.Text = globalMin.ToString("F2");
            MaxScaleLabel.Text = globalMax.ToString("F2");

            int scaleWidth = 256;
            int scaleHeight = 20;

            var bitmap = new WriteableBitmap(scaleWidth, scaleHeight, 96, 96, PixelFormats.Bgr32, null);
            int stride = bitmap.BackBufferStride;
            int[] pixels = new int[scaleWidth * scaleHeight];

            for (int x = 0; x < scaleWidth; x++)
            {
                double value = (double)x / (scaleWidth - 1);
                value = isScaleReversed ? 1 - value : value;

                // Используем глобальную палитру для шкалы
                Color color = GetColorFromPalette(value, currentPalette);

                for (int y = 0; y < scaleHeight; y++)
                {
                    pixels[y * scaleWidth + x] = (color.A << 24) | (color.R << 16) | (color.G << 8) | color.B;
                }
            }

            bitmap.WritePixels(new Int32Rect(0, 0, scaleWidth, scaleHeight), pixels, stride, 0);
            ColorScale.Source = bitmap;
        }

        private void ReverseScale_CheckedChanged(object sender, RoutedEventArgs e)
        {
            isScaleReversed = ReverseScale.IsChecked ?? false;

            // Обновляем свойство для всех загруженных файлов
            foreach (var file in LoadedFiles)
            {
                file.IsScaleReversed = isScaleReversed;
                file.ContourBitmap = GenerateContourBitmap(file);
            }

            UpdateColorScales();
        }

        private Color GetColorFromPalette(double value, string palette)
        {
            return palette switch
            {
                "Rainbow" => GetRainbowColor(value),
                "Hot" => GetHotColor(value),
                "Cool" => GetCoolColor(value),
                "Gray Scale" => GetGrayScaleColor(value),
                _ => GetRainbowColor(value),
            };
        }

        private Color GetGrayScaleColor(double value)
        {
            byte intensity = (byte)(value * 255);
            return Color.FromRgb(intensity, intensity, intensity);
        }

        private Color GetRainbowColor(double value)
        {
            double hue = 240 - (240 * value); // 240 (синий) до 0 (красный)
            return ColorFromHSV(hue, 1, 1);
        }

        private Color GetHotColor(double value)
        {
            if (value < 0.33)
                return Color.FromRgb((byte)(value * 3 * 255), 0, 0);
            else if (value < 0.66)
                return Color.FromRgb(255, (byte)((value - 0.33) * 3 * 255), 0);
            else
                return Color.FromRgb(255, 255, (byte)((value - 0.66) * 3 * 255));
        }

        private Color GetCoolColor(double value)
        {
            return Color.FromRgb((byte)(value * 255), (byte)((1 - value) * 255), 255);
        }

        private Color ColorFromHSV(double hue, double saturation, double value)
        {
            int hi = Convert.ToInt32(Math.Floor(hue / 60)) % 6;
            double f = hue / 60 - Math.Floor(hue / 60);

            value = value * 255;
            byte v = (byte)value;
            byte p = (byte)(value * (1 - saturation));
            byte q = (byte)(value * (1 - f * saturation));
            byte t = (byte)(value * (1 - (1 - f) * saturation));

            return hi switch
            {
                0 => Color.FromRgb(v, t, p),
                1 => Color.FromRgb(q, v, p),
                2 => Color.FromRgb(p, v, t),
                3 => Color.FromRgb(p, q, v),
                4 => Color.FromRgb(t, p, v),
                _ => Color.FromRgb(v, p, q),
            };
        }

        private void PaletteSelector_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            if (PaletteSelector.SelectedItem != null)
            {
                ComboBoxItem selectedItem = (ComboBoxItem)PaletteSelector.SelectedItem;
                currentPalette = selectedItem.Content.ToString();

                // Обновляем палитру для всех файлов и пересоздаём контурные карты
                foreach (var file in LoadedFiles)
                {
                    file.Palette = currentPalette;
                    file.ContourBitmap = GenerateContourBitmap(file);
                }

                UpdateColorScales();
            }
        }

        private void AutoRangeButton_Click(object sender, RoutedEventArgs e)
        {
            if (sender is Button button && button.Tag is LoadedFile file)
            {
                // Рассчитываем min и max для конкретного файла
                double min = double.MaxValue;
                double max = double.MinValue;
                int rows = file.Data.GetLength(0);
                int cols = file.Data.GetLength(1);

                for (int i = 0; i < rows; i++)
                    for (int j = 0; j < cols; j++)
                    {
                        if (file.Data[i, j] < min) min = file.Data[i, j];
                        if (file.Data[i, j] > max) max = file.Data[i, j];
                    }

                // Обновляем свойства LoadedFile
                file.MinValue = min;
                file.MaxValue = max;

                // Обновление отображения происходит автоматически через событие PropertyChanged
            }
        }

        private void AutoRange_Click(object sender, RoutedEventArgs e)
        {
            if (LoadedFiles.Count == 0) return;

            // Вычисляем глобальный min и max для всех загруженных файлов
            double globalMin = LoadedFiles.Min(f => f.MinValue);
            double globalMax = LoadedFiles.Max(f => f.MaxValue);

            // Обновляем MinValue и MaxValue каждого файла до глобальных значений
            foreach (var file in LoadedFiles)
            {
                file.MinValue = globalMin;
                file.MaxValue = globalMax;
            }

            UpdateColorScales();
        }
    }
}

<Window x:Class="ContourMapVisualizer.FilePreviewWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Предпросмотр файлов" Height="600" Width="800" WindowStartupLocation="CenterOwner">
    <Grid Margin="10">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <!-- Row 0: TextBlock -->
            <RowDefinition Height="2*"/>
            <!-- Row 1: DataGrid -->
            <RowDefinition Height="*"/>
            <!-- Row 2: ScrollViewer -->
            <RowDefinition Height="Auto"/>
            <!-- Row 3: Buttons -->
        </Grid.RowDefinitions>

        <TextBlock Grid.Row="0" Text="Выберите файл и настройте пропуск строк:" FontWeight="Bold" />

        <DataGrid x:Name="FilesDataGrid" Grid.Row="1" AutoGenerateColumns="False" CanUserAddRows="False" SelectionMode="Single" SelectionChanged="FilesDataGrid_SelectionChanged" Margin="0,10,0,10">
            <DataGrid.Columns>
                <DataGridTextColumn Header="Файл" Binding="{Binding FileName}" IsReadOnly="True" Width="*"/>
                <DataGridTextColumn Header="Пропустить строк" Binding="{Binding SkipLines, UpdateSourceTrigger=PropertyChanged}" Width="150"/>
            </DataGrid.Columns>
        </DataGrid>

        <!-- Удалена кнопка "Загрузить предварительный просмотр" -->

        <ScrollViewer Grid.Row="2" VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Auto">
            <TextBox x:Name="PreviewTextBox" IsReadOnly="True" FontFamily="Consolas" FontSize="12" TextWrapping="NoWrap"/>
        </ScrollViewer>

        <StackPanel Grid.Row="3" Orientation="Horizontal" HorizontalAlignment="Right" Margin="0,10,0,0">
            <Button Content="OK" Click="ConfirmButton_Click" Width="75" Margin="0,0,10,0"/>
            <Button Content="Отмена" Click="CancelButton_Click" Width="75"/>
        </StackPanel>
    </Grid>
</Window>

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;

namespace ContourMapVisualizer
{
    public partial class FilePreviewWindow : Window
    {
        public List<LoadedFilePreview> SelectedFiles { get; private set; } = new List<LoadedFilePreview>();

        public FilePreviewWindow(string[] filePaths)
        {
            InitializeComponent();
            InitializeDataGrid(filePaths);
        }

        private void InitializeDataGrid(string[] filePaths)
        {
            foreach (var path in filePaths)
            {
                try
                {
                    var preview = new LoadedFilePreview
                    {
                        FilePath = path,
                        FileName = System.IO.Path.GetFileName(path),
                        SkipLines = 0 // Строки по умолчанию для пропуска
                    };
                    SelectedFiles.Add(preview);
                }
                catch (Exception ex)
                {
                    MessageBox.Show($"Ошибка при загрузке файла {path}: {ex.Message}", "Ошибка", MessageBoxButton.OK, MessageBoxImage.Error);
                }
            }
            FilesDataGrid.ItemsSource = SelectedFiles;
            if (SelectedFiles.Count > 0)
                FilesDataGrid.SelectedIndex = 0;
        }

        private void FilesDataGrid_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            if (FilesDataGrid.SelectedItem is LoadedFilePreview selectedFile)
            {
                DisplayPreview(selectedFile);
            }
            else
            {
                PreviewTextBox.Text = string.Empty;
            }
        }

        private void DisplayPreview(LoadedFilePreview selectedFile)
        {
            try
            {
                var allLines = File.ReadAllLines(selectedFile.FilePath);
                var previewLines = allLines.Skip(selectedFile.SkipLines).Take(100).ToArray(); // Отображаем 100 строк

                StringBuilder sb = new StringBuilder();
                for (int i = 0; i < previewLines.Length; i++)
                {
                    int lineNumber = selectedFile.SkipLines + i + 1;
                    sb.AppendLine($"{lineNumber}: {previewLines[i]}");
                }

                if (allLines.Length > selectedFile.SkipLines + 100)
                {
                    sb.AppendLine("...");
                }

                PreviewTextBox.Text = sb.ToString();
            }
            catch (Exception ex)
            {
                MessageBox.Show($"Ошибка при загрузке файла: {ex.Message}", "Ошибка", MessageBoxButton.OK, MessageBoxImage.Error);
                PreviewTextBox.Text = string.Empty;
            }
        }

        private void ConfirmButton_Click(object sender, RoutedEventArgs e)
        {
            // Проверка корректности пропусков строк для каждого файла
            foreach (var file in SelectedFiles)
            {
                if (file.SkipLines < 0)
                {
                    MessageBox.Show($"Неверное количество пропущенных строк для файла {file.FileName}.", "Ошибка", MessageBoxButton.OK, MessageBoxImage.Warning);
                    return;
                }

                try
                {
                    int totalLines = File.ReadAllLines(file.FilePath).Length;
                    if (file.SkipLines >= totalLines)
                    {
                        MessageBox.Show($"Количество пропущенных строк для файла {file.FileName} превышает общее количество строк в файле.", "Ошибка", MessageBoxButton.OK, MessageBoxImage.Warning);
                        return;
                    }
                }
                catch (Exception ex)
                {
                    MessageBox.Show($"Ошибка при проверке файла {file.FileName}: {ex.Message}", "Ошибка", MessageBoxButton.OK, MessageBoxImage.Error);
                    return;
                }
            }

            this.DialogResult = true;
            Close();
        }

        private void CancelButton_Click(object sender, RoutedEventArgs e)
        {
            this.DialogResult = false;
            Close();
        }
    }

    public class LoadedFilePreview : INotifyPropertyChanged
    {
        private int skipLines;
        public string FilePath { get; set; }
        public string FileName { get; set; }
        public int SkipLines
        {
            get => skipLines;
            set
            {
                if (skipLines != value)
                {
                    skipLines = value;
                    OnPropertyChanged(nameof(SkipLines));
                }
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;

        protected void OnPropertyChanged(string propertyName)
            => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

using System.ComponentModel;
using System.Windows.Media;
using System.Windows.Media.Imaging;

namespace ContourMapVisualizer
{
    public class LoadedFile : INotifyPropertyChanged
    {
        public string FilePath { get; set; }
        public string FileName { get; set; }
        public double[,] Data { get; set; }

        private double minValue;
        public double MinValue
        {
            get => minValue;
            set
            {
                if (minValue != value)
                {
                    minValue = value;
                    OnPropertyChanged(nameof(MinValue));
                }
            }
        }

        private double maxValue;
        public double MaxValue
        {
            get => maxValue;
            set
            {
                if (maxValue != value)
                {
                    maxValue = value;
                    OnPropertyChanged(nameof(MaxValue));
                }
            }
        }

        private string palette = "Rainbow";
        public string Palette
        {
            get => palette;
            set
            {
                if (palette != value)
                {
                    palette = value;
                    OnPropertyChanged(nameof(Palette));
                }
            }
        }

        private bool isScaleReversed = false;
        public bool IsScaleReversed
        {
            get => isScaleReversed;
            set
            {
                if (isScaleReversed != value)
                {
                    isScaleReversed = value;
                    OnPropertyChanged(nameof(IsScaleReversed));
                }
            }
        }

        private WriteableBitmap contourBitmap;
        public WriteableBitmap ContourBitmap
        {
            get => contourBitmap;
            set
            {
                if (contourBitmap != value)
                {
                    contourBitmap = value;
                    OnPropertyChanged(nameof(ContourBitmap));
                }
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;

        protected void OnPropertyChanged(string propertyName)
            => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}