Microsoft Chart Controls を使って東京電力の電力使用状況をグラフ化する

.NET Framework 4から導入されたSystem.Windows.Forms.DataVisualization.Charting名前空間にあるMicrosoft Chart Controlsを使うと、簡単にグラフを作成できます。

.NET Framework 3.5でも、このページから必要なものを落としてきてインストールすれば使えます。http://archive.msdn.microsoft.com/mschart

けっこう高機能で、初めての時やしばらくぶりに触るときはなかなか思うように扱えません。今回は、東京電力のWebページから公開されている電力使用状況のCSVデータをグラフ化することで勘を取り戻します。

1. 準備

Visual Studioで、「Windows Formアプリケーション」としてプロジェクトを新規作成します。参照設定から、System.Windows.Forms.DataVisualizationを追加します。


これで使えるようになります。Button等と同様にデザイナからフォームに貼り付けることができます。Form1.csのデザイナを開き、Chartコントロールを適当に貼り付けます。


これで準備完了です。これ以降はデザイナは使わず、Form1.csのコードを書いていきます。コンストラクタまたはForm1_Loadといったところに書いていけばいいと思います。

2. 電力使用状況データ

以下のURLにて公開されています。5行目から下に各時間ごとの使用量が記されており、今回はここをグラフ化することを目指します。

http://www.tepco.co.jp/forecast/html/images/juyo-j.csv

まずはこれをさくっと文字列として取ってきましょう。

private string GetCsv(string url)
{
    using (System.Net.WebClient wc = new System.Net.WebClient())
    {
        return wc.DownloadString(url);
    }
}


行ごとに処理するので、改行コードで文字列を分割しておきます。ついでに供給量のピークをパース。

string csv = GetCsv("http://www.tepco.co.jp/forecast/html/images/juyo-j.csv");

string[] lines = csv.Replace("\r\n", "\n").TrimEnd('\n').Split('\n');
int peak = int.Parse(lines[2].Split(',')[0]);

3. CSVをパース

あらかじめ、各時間ごとのデータ、CSVでいうところの1行分のデータを表すクラスを作っておきます。

class ElectricityDemand
{
    /// <summary>
    /// 日時
    /// </summary>
    public DateTime Time { get; set; }
    /// <summary>
    /// 当日実績(万kW)
    /// </summary>
    public int Today { get; set; }
    /// <summary>
    /// 前日実績(万kW)
    /// </summary>
    public int Yesterday { get; set; }

    public ElectricityDemand()
    {
    }
    public ElectricityDemand(DateTime time, int today, int yesterday)
    {
        Time = time;
        Today = today;
        Yesterday = yesterday;
    }
}


あとは1行分の文字列をコンマ区切りででSplitして、このクラスに流し込んでやればいいことになります。

for (int i = 5; i < lines.Length; i++)
{
    string[] fields = lines[i].Split(',');
    // ...
}


CSVでは別個のフィールドとなっているDATEとTIMEは、せっかく.NETにDateTimeという構造体があるので一緒にしてしまいましょう。文字列からDateTime構造体をつくるには、DateTime.Parseが便利です。"2011/03/30 12:34:56" という形式の文字列からDateTimeオブジェクトを生成できます。秒以外はCSVデータ形式をそのまま使えます。

private ElectricityDemand[] ParseCsv(string[] lines)
{
    List<ElectricityDemand> result = new List<ElectricityDemand>(lines.Length - 5);

    for (int i = 5; i < lines.Length; i++)
    {
        string[] fields = lines[i].Split(',');
        DateTime time = DateTime.Parse(string.Format("{0} {1}:00", fields[0], fields[1]));
        result.Add(new ElectricityDemand(time, int.Parse(fields[2]), int.Parse(fields[3])));
    }

    return result.ToArray();
}
string[] lines = csv.Replace("\r\n", "\n").TrimEnd('\n').Split('\n');
int peak = int.Parse(lines[2].Split(',')[0]);

ElectricityDemand[] elems = ParseCsv(lines);

Chartコントロールの設定

まずはグラフの系列を用意します。当日実績と前日実績の2種類のグラフを表示したいので2つ用意しておきます。SeriesChartType.Column で棒グラフになります。

chart1.Series.Clear();
chart1.Series.Add(new Series("当日実績") { ChartType = SeriesChartType.Column });
chart1.Series.Add(new Series("前日実績") { ChartType = SeriesChartType.Column });


次にグラフの中身の設定。表示域の最大・最小値や、目盛りをどれぐらいの間隔にするか、といった設定をしています。

ChartArea area = new ChartArea();
area.AxisX.Title = "時間";
area.AxisX.Minimum = 0;
area.AxisX.Maximum = 24;
area.AxisX.Interval = 3;
area.AxisY.Title = "電力需要 (万kW)";
area.AxisY.Minimum = 0;
area.AxisY.Maximum = peak;
area.AxisY.Interval = 500;
chart1.ChartAreas.Clear();
chart1.ChartAreas.Add(area);

以上の設定はデザイナからも行えます。説明の簡単のためコードで書きましたが、今回のように固定値で設定するのであればデザイナでやる方が望ましいかもしれません。

グラフの描画

お膳立てはようやく終わり、いよいよデータのプロットです。DataPointクラスにx, y座標を設定してSeriesに追加していきます。

ElectricityDemand[] elems = ParseCsv(lines);
foreach (ElectricityDemand ed in elems)
{
    DataPoint dpToday = new DataPoint();
    dpToday.SetValueXY(ed.Time.Hour, ed.Today);
    chart1.Series[0].Points.Add(dpToday);

    DataPoint dpYesterday = new DataPoint();
    dpYesterday.SetValueXY(ed.Time.Hour, ed.Yesterday);
    chart1.Series[1].Points.Add(dpYesterday);
}


おわりに

グラフを作るだけならExcelでもできますし、東京電力謹製のグラフより情報量も劣っているので、これだけではあまり面白くありません。グラフを凝ったり、他の情報と組み合わせたり、取得した情報によってPCを制御したり・・・といったことをやっていくと面白くなるかもしれません。残念ながらそこまで余力がなさそうです。

当日以外に過去の電力使用状況データまでJSON形式で配布しているところ (http://denki.cuppat.net/) があります。こちらをデータ取得元とすればとりあえず面白くできそうです。

コード

Form1.cs

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Windows.Forms.DataVisualization.Charting;

namespace TepcoGraph
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        /// <summary>
        /// 表示されるときの処理
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Form1_Load(object sender, EventArgs e)
        {
            string csv = GetCsv("http://www.tepco.co.jp/forecast/html/images/juyo-j.csv");

            // 行ごとに切り分け
            string[] lines = csv.Replace("\r\n", "\n").TrimEnd('\n').Split('\n');
            int peak = int.Parse(lines[2].Split(',')[0]);

            // グラフの設定
            chart1.Series.Clear();
            chart1.Series.Add(new Series("当日実績") { ChartType = SeriesChartType.Column });
            chart1.Series.Add(new Series("前日実績") { ChartType = SeriesChartType.Column });

            ChartArea area = new ChartArea();
            area.AxisX.Title = "時間";
            area.AxisX.Minimum = 0;
            area.AxisX.Maximum = 24;
            area.AxisX.Interval = 3;
            area.AxisY.Title = "電力需要 (万kW)";
            area.AxisY.Minimum = 0;
            area.AxisY.Maximum = peak;
            area.AxisY.Interval = 500;
            chart1.ChartAreas.Clear();
            chart1.ChartAreas.Add(area);

            // グラフの描画
            ElectricityDemand[] elems = ParseCsv(lines);
            foreach (ElectricityDemand ed in elems)
            {
                DataPoint dpToday = new DataPoint();
                dpToday.SetValueXY(ed.Time.Hour, ed.Today);
                chart1.Series[0].Points.Add(dpToday);

                DataPoint dpYesterday = new DataPoint();
                dpYesterday.SetValueXY(ed.Time.Hour, ed.Yesterday);
                chart1.Series[1].Points.Add(dpYesterday);
            }
        }

        /// <summary>
        /// csvをWeb上からとってくる
        /// </summary>
        /// <param name="url"></param>
        /// <returns></returns>
        private string GetCsv(string url)
        {
            using (System.Net.WebClient wc = new System.Net.WebClient())
            {
                return wc.DownloadString(url);
            }
        }

        /// <summary>
        /// csvをパースして各時間ごとの電力需要を取得
        /// </summary>
        /// <param name="lines"></param>
        /// <returns></returns>
        private ElectricityDemand[] ParseCsv(string[] lines)
        {
            List<ElectricityDemand> result = new List<ElectricityDemand>(lines.Length - 5);

            for (int i = 5; i < lines.Length; i++)
            {
                string[] fields = lines[i].Split(',');
                result.Add(new ElectricityDemand(fields[0], fields[1], fields[2], fields[3]));
            }

            return result.ToArray();
        }
    }
}

ElectricityDemand.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace TepcoGraph
{
    /// <summary>
    /// 電力需要
    /// </summary>
    class ElectricityDemand
    {
        /// <summary>
        /// 日時
        /// </summary>
        public DateTime Time { get; set; }
        /// <summary>
        /// 当日実績(万kW)
        /// </summary>
        public int Today { get; set; }
        /// <summary>
        /// 前日実績(万kW)
        /// </summary>
        public int Yesterday { get; set; }

        public ElectricityDemand()
        {
        }
        public ElectricityDemand(string date, string time, string today, string yesterday)
            : this(DateTime.Parse(string.Format("{0} {1}:00", date, time)), int.Parse(today), int.Parse(yesterday))
        {
        }
        public ElectricityDemand(DateTime time, int today, int yesterday)
        {
            Time = time;
            Today = today;
            Yesterday = yesterday;
        }
    }
}