C# WPF XAMLで、CSV読み込み、グラフ（LiveCharts.Wpf）

# ■画面

![スクリーンショット (14).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/478380/cf303de9-26fa-4d5e-ac98-c2dccc97911e.png)

![スクリーンショット (15).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/478380/f5dcb29c-2c3f-4b6c-bf47-486c6583c468.png)

# ■ファイル構成

・MainWindow.xaml
    ・MainWindow.cs
・MainViewModel.cs
・Person.cs
・RelayCommand.cs

# ■ソースコード

``` MainWindow.xam 
<Window x:Class="test_01.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:test_01"
        xmlns:lvc="clr-namespace:LiveCharts.Wpf;assembly=LiveCharts.Wpf"
        mc:Ignorable="d"
        Title="MainWindow" Height="800" Width="1200">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="250"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>

        <!-- CSV読み込みボタン -->
        <Button Content="過去の国勢調査を見る"
                Command="{Binding LoadCsvCommand}"
                Margin="40"
                Padding="20"
                FontSize="20"
                Grid.Row="0"/>

        <!-- ListView -->
        <ScrollViewer VerticalScrollBarVisibility="Auto" Height="250" Grid.Row="1">
            <ListView ItemsSource="{Binding People}">
                <ListView.View>
                    <GridView>
                        <GridViewColumn Header="都道府県コード" DisplayMemberBinding="{Binding PrefectureCode}" Width="100"/>
                        <GridViewColumn Header="都道府県名" DisplayMemberBinding="{Binding PrefectureName}" Width="150"/>
                        <GridViewColumn Header="元号" DisplayMemberBinding="{Binding Era}" Width="100"/>
                        <GridViewColumn Header="和暦（年）" DisplayMemberBinding="{Binding JapaneseYear}" Width="100"/>
                        <GridViewColumn Header="西暦（年）" DisplayMemberBinding="{Binding GregorianYear}" Width="100"/>
                        <GridViewColumn Header="注" DisplayMemberBinding="{Binding Note}" Width="150"/>
                        <GridViewColumn Header="人口（総数）" DisplayMemberBinding="{Binding PopulationTotal}" Width="100"/>
                        <GridViewColumn Header="人口（男）" DisplayMemberBinding="{Binding PopulationMale}" Width="100"/>
                        <GridViewColumn Header="人口（女）" DisplayMemberBinding="{Binding PopulationFemale}" Width="100"/>
                    </GridView>
                </ListView.View>
            </ListView>
        </ScrollViewer>

        <!-- グラフ（スクロール可能） -->
        <Grid Grid.Row="2">
            <ScrollViewer HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Disabled">
                <Grid>
                    <lvc:CartesianChart Margin="10" x:Name="lvChart"
                                        Series="{Binding SeriesCollection}"
                                        MinHeight="300" MinWidth="1200">
                        <lvc:CartesianChart.AxisX>
                            <lvc:Axis Title="年" Labels="{Binding YearLabels}" MinValue="0" MaxValue="10">
                                <lvc:Axis.Separator>
                                    <lvc:Separator Step="1"/>
                                </lvc:Axis.Separator>
                            </lvc:Axis>
                        </lvc:CartesianChart.AxisX>
                        <lvc:CartesianChart.AxisY>
                            <lvc:Axis Title="人口" MinValue="0" MaxValue="100000000" />
                        </lvc:CartesianChart.AxisY>
                    </lvc:CartesianChart>
                </Grid>
            </ScrollViewer>
        </Grid>
    </Grid>
</Window>

```

``` MainWindow.cs

using LiveCharts;
using System;
using System.Collections.ObjectModel;
using System.Windows;

namespace test_01
{

    public partial class MainWindow : Window
    {

        public MainWindow()
        {
            // コンポーネント初期化
            InitializeComponent();
            // データバインド適用
            DataContext = new MainViewModel();
        }
    }
}

```

``` MainViewModel.cs

using LiveCharts;
using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Input;
using LiveCharts.Wpf;

namespace test_01
{
    public class MainViewModel : INotifyPropertyChanged
    {
        public ObservableCollection<Person> People { get; set; }
        public ICommand LoadCsvCommand { get; set; }


        // グラフ用のプロパティ
        
        public ObservableCollection<int> PopulationTotalValues { get; set; }
        public ObservableCollection<int> PopulationMaleValues { get; set; }
        public ObservableCollection<int> PopulationFemaleValues { get; set; }
        public ObservableCollection<string> YearLabels { get; set; }

        // LiveCharts用のプロパティ
        public SeriesCollection SeriesCollection { get; set; }
        
        // 1920年 - 2015年のデータ作成
        private static readonly int[] Years = Enumerable.Range(1920, (2015 - 1920) / 5 + 1).Select(x => x * 5).ToArray();


        public MainViewModel()
        {
            try
            {
                // 初期化
                People = new ObservableCollection<Person>();

                PopulationTotalValues = new ObservableCollection<int>();
                PopulationMaleValues = new ObservableCollection<int>();
                PopulationFemaleValues = new ObservableCollection<int>();

                YearLabels = new ObservableCollection<string>();
                SeriesCollection = new SeriesCollection
                {
                    new ColumnSeries
                    {
                        Title = "総人口",
                        Values = new ChartValues<int>()  // 初期は空の値
                    },
                    new ColumnSeries
                    {
                        Title = "男性人口",
                        Values = new ChartValues<int>()
                    },
                    new ColumnSeries
                    {
                        Title = "女性人口",
                        Values = new ChartValues<int>()
                    }
                };

                // UI スレッドで初期データを追加
                /*
                Application.Current.Dispatcher.Invoke(() =>
                {
                    YearLabels.Add("2020");
                    YearLabels.Add("2021");
                    YearLabels.Add("2022");
                    YearLabels.Add("2023");
                });
                */

                LoadCsvCommand = new RelayCommand(LoadCsv);

            }
            catch (Exception ex)
            {
                MessageBox.Show($"ViewModel 初期化エラー: {ex.Message}", "エラー", MessageBoxButton.OK, MessageBoxImage.Error);
            }
        }

        /**
         *  CSV読み込みボタンを押した、処理
         **/
        private void LoadCsv()
        {
            // string FilePath = "test_01.c01.csv";
            string FilePath = System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "c01.csv");

            People.Clear();
            PopulationTotalValues.Clear();
            PopulationMaleValues.Clear();
            PopulationFemaleValues.Clear();
            YearLabels.Clear();

            try
            {
                if (File.Exists(FilePath))
                {
                    var lines = File.ReadAllLines(FilePath, Encoding.GetEncoding("Shift_JIS"));

                    // 最初の行（ヘッダー行）をスキップ
                    var dataLines = lines.Skip(1).ToList();

                    // 前の年を保持する変数
                    string previousYear = string.Empty; 

                    for (int i = 0; i < dataLines.Count; i++)
                    {
                        var values = dataLines[i].Split(',');
                        if (values.Length >= 9)
                        {
                            var totalPopulation = int.TryParse(values[6], out int total) ? total : 0;
                            var malePopulation = int.TryParse(values[7], out int male) ? male : 0;
                            var femalePopulation = int.TryParse(values[8], out int female) ? female : 0;

                            People.Add(new Person
                            {
                                PrefectureCode = values[0],
                                PrefectureName = values[1],
                                Era = values[2],
                                JapaneseYear = values[3],
                                GregorianYear = values[4],
                                Note = values[5],
                                PopulationTotal = totalPopulation,
                                PopulationMale = malePopulation,
                                PopulationFemale = femalePopulation
                            });

                  
                            // **2 行目（i == 0）のデータだけをグラフに反映**
                            if (previousYear != values[4])  // 年が変更された場合
                            {
                                previousYear = values[4];

                                // 年が変更された場合のみ、グラフにデータを追加
                                Application.Current.Dispatcher.Invoke(() =>
                                {
                                    YearLabels.Add(values[4]);  // 西暦（年）をラベルに追加

                                    PopulationTotalValues.Add(totalPopulation / 10000);
                                    PopulationMaleValues.Add(malePopulation / 10000);
                                    PopulationFemaleValues.Add(femalePopulation / 10000);

                                    SeriesCollection[0].Values.Add(totalPopulation); // 総人口
                                    SeriesCollection[1].Values.Add(malePopulation);  // 男性人口
                                    SeriesCollection[2].Values.Add(femalePopulation); // 女性人口
                                });
                            }
                        }
                    }

                    // 変更をビューに通知
                    OnPropertyChanged(nameof(PopulationTotalValues));
                    OnPropertyChanged(nameof(PopulationMaleValues));
                    OnPropertyChanged(nameof(PopulationFemaleValues));
                    OnPropertyChanged(nameof(YearLabels));

                    OnPropertyChanged(nameof(SeriesCollection)); // グラフの更新を通知
                }
                else
                {
                    MessageBox.Show("---ERR--- CSVファイルが見つかりません。", "エラー", MessageBoxButton.OK, MessageBoxImage.Error);
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show($"LoadCsv エラー: {ex.Message}", "エラー", MessageBoxButton.OK, MessageBoxImage.Error);
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;
        protected void OnPropertyChanged(string propertyName)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

```

``` Person.cs
using System.ComponentModel;

namespace test_01
{
    public class Person : INotifyPropertyChanged
    {
        /**
         * プロパティ
         * */
        private string prefectureCode;
        private string prefectureName;
        private string era;
        private string japaneseYear;
        private string gregorianYear;
        private string note;
        private int populationTotal; // グラフにも使用
        private int populationMale;  // グラフにも使用
        private int populationFemale;// グラフにも使用


        /**
         * ゲッター、セッター
         * */
        public string PrefectureCode
        {
            get => prefectureCode;
            set { prefectureCode = value; OnPropertyChanged(nameof(PrefectureCode)); }
        }

        public string PrefectureName
        {
            get => prefectureName;
            set { prefectureName = value; OnPropertyChanged(nameof(PrefectureName)); }
        }

        public string Era
        {
            get => era;
            set { era = value; OnPropertyChanged(nameof(Era)); }
        }

        public string JapaneseYear
        {
            get => japaneseYear;
            set { japaneseYear = value; OnPropertyChanged(nameof(JapaneseYear)); }
        }

        public string GregorianYear
        {
            get => gregorianYear;
            set { gregorianYear = value; OnPropertyChanged(nameof(GregorianYear)); }
        }

        public string Note
        {
            get => note;
            set { note = value; OnPropertyChanged(nameof(Note)); }
        }

        public int PopulationTotal
        {
            get => populationTotal;
            set { populationTotal = value; OnPropertyChanged(nameof(PopulationTotal)); }
        }

        public int PopulationMale
        {
            get => populationMale;
            set { populationMale = value; OnPropertyChanged(nameof(PopulationMale)); }
        }

        public int PopulationFemale
        {
            get => populationFemale;
            set { populationFemale = value; OnPropertyChanged(nameof(PopulationFemale)); }
        }

        public event PropertyChangedEventHandler PropertyChanged;
        protected void OnPropertyChanged(string propertyName)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

``` 
``` RelayCommand.cs

using System;
using System.ComponentModel;
using System.Windows.Input;

namespace test_01
{
    public class RelayCommand : ICommand
    {
        private readonly Action execute;
        private readonly Func<bool> canExecute;

        public RelayCommand(Action execute, Func<bool> canExecute = null)
        {
            this.execute = execute;
            this.canExecute = canExecute;
        }

        public event EventHandler CanExecuteChanged
        {
            add { CommandManager.RequerySuggested += value; }
            remove { CommandManager.RequerySuggested -= value; }
        }

        public bool CanExecute(object parameter) => canExecute == null || canExecute();
        public void Execute(object parameter) => execute();
    }
}

```