WebSurfer's Home

トップ > Blog 1   |   ログイン
APMLフィルター

Chart の注釈のサイズ・位置設定

by WebSurfer 2022年7月10日 18:16

Windows Forms アプリ用の Chart の注釈 (Annotation) のサイズと位置の設定方法を備忘録として以下に書いておきます。

サイズは Annotation.Width プロパティAnnotation.Height プロパティで、位置は Annotation.X プロパティAnnotation.Y プロパティ で設定します。

分かりにくかったのが (1) 相対 Chart 座標 (relative chart coordinates) と (2) 軸座標 (axes coordinates) という 2 つの方法があって、それぞれ設定するものが全く違うということでした。

(1) 相対 Chart 座標 (relative chart coordinates)

これがデフォルトだそうです。

サイズは、Annotation.Width, Annotation.Height にそれぞれ Chart.Width, Chart.Height のパーセント値を設定します。例えば、注釈の幅を Chart 本体の 50% に設定したい場合は Annotation.Width に 50 を設定します。

位置もやはり Chart.Width, Chart.Height のパーセント値で設定します。例えば、注釈の左端を Chart 本体の左端から 10% 右の位置に設定したい場合は Annotation.X に 10 を設定します。

下の画像がその例で、長方形の注釈 (RectangleAnnotation) のサイズを Chart 本体のサイズの 50% に、注釈の左上端の位置を Char 本体の左上端から 10% 右下に設定しています。

相対 Chart 座標 (relative chart coordinates)

上の画像を表示するのに使ったサンプルコードをこの記事の下の方に載せておきます。

(2) 軸座標 (axes coordinates)

Microsoft のドキュメントによると「軸座標 (axes coordinates)」にするには AxisX プロパティAxisY プロパティを設定し、IsSizeAlwaysRelative プロパティを false に設定するのだそうです。そうすると、注釈のサイズ・位置は X, Y 軸目盛が基準になります。

サイズは、例えば幅を X 軸 2 目盛分にしたい場合は Annotation.Width に 2 を設定します。高さも同様で、Y 軸 2 目盛分にしたい場合は Annotation.Height に 2 を設定します。

位置は、例えば注釈の左端を X 軸目盛の 1 の位置にしたい場合は annotation.X に 1 を設定します。上下位置も同様で、注釈の下端を Y 軸目盛の 1 の位置をにしたい場合は annotation.Y に 1 を設定します。

下の画像がその例で、長方形の注釈 (RectangleAnnotation) のサイズを X, Y 軸 2 目盛分に、注釈の左下端の位置を X, Y 軸目盛の 1 に設定しています。

軸座標 (axes coordinates)

X, Y 軸の目盛がベースになるので、上の画像の ComboBox の選択を変えて X, Y 軸を変えるとそれに応じて注釈のサイズ・位置も変わって、設定が意味をなさなくなりますので注意してください。


以下にサンプルコードを載せておきます。CreateAnnotation メソッドの中でコメントアウトしてるのが「(1) 相対 Chart 座標 (relative chart coordinates)」のものです。

using System;
using System.Data;
using System.Windows.Forms.DataVisualization.Charting;
using System.Windows.Forms;

namespace WindowsFormsApp1
{
    public partial class Form6 : Form
    {
        private Chart chart1;
        private DataTable table;

        public Form6()
        {
            InitializeComponent();

            chart1 = new Chart
            {
                Location = new System.Drawing.Point(50, 50),
                Width = 600,
                Height = 300
            };
            Controls.Add(chart1);
        }

        private void Form6_Load(object sender, EventArgs e)
        {
            // ここでは Chart は作らない。下の「コンボボックスの初期値を
            // 設定」のところで ComboBox.SelectedIndexChangedイベントが
            // 発生するので、ここで Chart を作るとイベントハンドラでも
            // Chart が作られて意味不明な動きになる。

            // グラフのデータ作成。X軸 3 通りと Y軸 3 通りを ComboBox の
            // 選択で切り替えて表示する
            table = new DataTable();
            table.Columns.Add("A", typeof(double));
            table.Columns.Add("B", typeof(double));
            table.Columns.Add("C", typeof(double));
            table.Rows.Add(new Object[] { 1, 5000000, 55 });
            table.Rows.Add(new Object[] { 2, 4000000, 44 });
            table.Rows.Add(new Object[] { 3, 3000000, 33 });

            // コンボボックスに DataTable の列名を格納
            comboBoxX.Items.AddRange(new string[] { "A", "B", "C" });
            comboBoxY.Items.AddRange(new string[] { "A", "B", "C" });

            // コンボボックスの初期値を設定
            comboBoxX.SelectedIndex = 0;
            comboBoxY.SelectedIndex = 0;
        }

        private void comboBoxX_SelectedIndexChanged(object sender, EventArgs e)
        {
            //コンボボックスで選択した DataTable の列名を読み込む
            object Select_X = comboBoxX.SelectedItem;
            object Select_Y = comboBoxY.SelectedItem;

            // Form_Load の comboBox1.SelectedIndex = 0; でイベントが発生
            // した時は comboBox2.SelectedItem は null なので何もしない
            if (Select_X == null || Select_Y == null) return;

            var X_Data = Select_X.ToString();
            var Y_Data = Select_Y.ToString();

            chart1.Series.Clear();
            chart1.ChartAreas.Clear();

            ChartArea chartArea = new ChartArea();
            chart1.ChartAreas.Add(chartArea);

            Series series1 = new Series();

            // ComboBox 選択された列名を軸ラベルに設定する
            chart1.ChartAreas[0].AxisX.Title = X_Data;
            chart1.ChartAreas[0].AxisY.Title = Y_Data;

            // データを Chart 上にグラフとして表示
            for (int i = 0; i < table.Rows.Count; i++)
            {
                series1.Points.AddXY(table.Rows[i][X_Data], table.Rows[i][Y_Data]);
            }
            chart1.Series.Add(series1);

            // RectangleAnnotation を追加
            chart1.Annotations.Clear();
            chart1.Annotations.Add(CreateAnnotation());
        }

        private void comboBoxY_SelectedIndexChanged(object sender, EventArgs e)
        {
            //コンボボックスで選択した DataTable の列名を読み込む
            object Select_X = comboBoxX.SelectedItem;
            object Select_Y = comboBoxY.SelectedItem;

            // Form_Load の「コンボボックスの初期値を設定」順であれば null
            // になることはなさそうだが念のため
            if (Select_X == null || Select_Y == null) return;

            var X_Data = Select_X.ToString();
            var Y_Data = Select_Y.ToString();

            chart1.Series.Clear();
            chart1.ChartAreas.Clear();

            ChartArea chartArea = new ChartArea();
            chart1.ChartAreas.Add(chartArea);

            Series series1 = new Series();

            // ComboBox 選択された列名を軸ラベルに設定する
            chart1.ChartAreas[0].AxisX.Title = X_Data;
            chart1.ChartAreas[0].AxisY.Title = Y_Data;

            // データを Chart 上にグラフとして表示
            for (int i = 0; i < table.Rows.Count; i++)
            {
                series1.Points.AddXY(table.Rows[i][X_Data], table.Rows[i][Y_Data]);
            }
            chart1.Series.Add(series1);

            // RectangleAnnotation を追加
            chart1.Annotations.Clear();
            chart1.Annotations.Add(CreateAnnotation());
        }

        private Annotation CreateAnnotation()
        {
            var annotation = new RectangleAnnotation();

            // 相対 Chart 座標 (relative chart coordinates)
            // AnchorDataPoint, AxisX, AxisY は設定しない
            //annotation.X = 10d;         // Chart の左端から 10% 右の位置
            //annotation.Y = 10d;         // Chart の上端から 10% 下の位置
            //annotation.Width = 50d;     // Chart.Width の 50% の幅
            //annotation.Height = 50d;    // Chart.Height の 50% の高さ            

            // 軸座標 (axes coordinates)
            // AxisX, AxisY を設定。IsSizeAlwaysRelative を false に設定
            annotation.AxisX = chart1.ChartAreas[0].AxisX;
            annotation.AxisY = chart1.ChartAreas[0].AxisY;
            annotation.IsSizeAlwaysRelative = false;
            annotation.X = 1d;          // X 軸目盛の 1 の位置が注釈の左端
            annotation.Y = 1d;          // Y 軸目盛の 1 の位置が注釈の下端
            annotation.Width = 2d;      // X 軸の 2 目盛分の幅
            annotation.Height = 2d;     // Y 軸の 2 目盛分の幅

            annotation.Text = "I am a\nRectangleAnnotation";
            annotation.ForeColor = System.Drawing.Color.Black;
            annotation.Font = new System.Drawing.Font("Arial", 12); ;
            annotation.LineWidth = 2;
            annotation.BackColor = System.Drawing.Color.PaleGreen;
            annotation.LineDashStyle = ChartDashStyle.Dash;

            return annotation;
        }
    }
}

Tags: , ,

.NET Framework

JSON 文字列の日付時刻のデシリアライズ

by WebSurfer 2022年2月18日 15:49

日付時刻を表す JSON 文字列を .NET の DateTime 型のオブジェクトにデシリアライズするにはどのようにするかという話を書きます。

{"name":"value"} という JSON 文字列の value に直接設定できるのは string, number, object, array, true, false, null だけですので、.NET の DateTime オブジェクトは string 型に変換されて JSON 文字列に設定されます。

変換の結果はシリアライザによって違います。詳しくは先の記事「日付時刻と JSON 文字列」に書きましたが、それをまとめた表を以下に再掲しておきます。表の一番最後の項目 (6) ASP.NET Web API は Newtonsoft.Json および System.Text.Json 名前空間の JsonSerializer クラスを使ったシリアライズ結果です。

No. 方法 結果
(1) JSON.stringify() 2017-02-01T03:15:45.000Z
(2) DataContractJsonSerializer \/Date(1503727168573+0900)\/
(3) WCF 上記 (2) の結果と同じ
(4) JavaScriptSerializer \/Date(1030806000000)\/
(5) ASP.NET Web サービス 上記 (4) の結果と同じ
(6) ASP.NET Web API 2017-08-26T15:39:32.6330349+09:00

上の表のように string 型にシリアライズされた JSON 文字列を元の DateTime 型のオブジェクトに変換するには、自力でコードを書いて文字列をパースする必要があると思っていました。

しかしそれは思い違いで、上の表の形式にシリアライズした文字列は Newtonsoft.Json であれば全て、System.Text.Json 名前空間の JsonSerializer クラスの場合も (1) と (6) は DateTime 型にデシリアライズしてくれました。

知ってましたか? 実は自分は最近まで知らなかったです。(汗) 以下にその話を書きます。

まず、JSON 文字列を C# のオブジェクトにデシリアライズする際、C# のオブジェクトのクラス定義が必要ですが、Visual Studio のツールを利用してそれが可能です。詳しくは先の記事「JSON 文字列から C# のクラス定義生成」を見てください。

Visual Studio のツールを使って C# のクラス定義を生成すると、上の表の日付日時の JSON 文字列に該当するプロパティの型は、上の表の例 (1) ~ (6) 全てが DateTime 型になります。

例えば、以下のような JSON 文字列から Visual Studio のツールを使って C# のクラス定義を生成すると、

{
  "name":"WebSurfer",
  "date":"2017-02-01T03:15:45.000Z"
}

日付時間の JSON 文字列に該当するプロパティの型は以下のよう DataTime 型になります。

public class Rootobject
{
    public string name { get; set; }
    public DateTime date { get; set; }
}

シリアライザによって JSON 文字列の形式が上の表の (1) ~ (6) のように異なりますが、いずれも同じ結果すなわち C# のクラスの当該プロパティの型は DateTime になります。

次にデシリアライザが上の表の (1) ~ (6) の文字列に対応しているかを調べました。結果は上にも述べましたが Newtonsoft.Json は (1) ~ (6) 全てに対応していました。検証に使ったコードを以下に載せておきます。

using Newtonsoft.Json;
using System;

namespace ConsoleNewtonsoftJson
{
    internal class Program
    {
        static void Main(string[] args)
        {
            string jsonString1 = "{\"name\":\"WebSurfer\",\"date\":\"2017-02-01T03:15:45.000Z\"}";
            string jsonString2 = "{\"name\":\"WebSurfer\",\"date\":\"\\/Date(1503727168573+0900)\\/\"}";
            string jsonString3 = "{\"name\":\"WebSurfer\",\"date\":\"\\/Date(1030806000000)\\/\"}";
            string jsonString4 = "{\"name\":\"WebSurfer\",\"date\":\"2017-08-26T15:39:32.6330349+09:00\"}";

            Rootobject rootobject = JsonConvert.DeserializeObject<Rootobject>(jsonString1);
            Console.WriteLine($"name: {rootobject.name}, date: {rootobject.date}");

            rootobject = JsonConvert.DeserializeObject<Rootobject>(jsonString2);
            Console.WriteLine($"name: {rootobject.name}, date: {rootobject.date}");

            rootobject = JsonConvert.DeserializeObject<Rootobject>(jsonString3);
            Console.WriteLine($"name: {rootobject.name}, date: {rootobject.date}");

            rootobject = JsonConvert.DeserializeObject<Rootobject>(jsonString4);
            Console.WriteLine($"name: {rootobject.name}, date: {rootobject.date}");
        }
    }

    public class Rootobject
    {
        public string name { get; set; }
        public DateTime date { get; set; }
    }
}

/*
実行結果は:
name: WebSurfer, date: 2017/02/01 3:15:45
name: WebSurfer, date: 2017/08/26 14:59:28
name: WebSurfer, date: 2002/08/31 15:00:00
name: WebSurfer, date: 2017/08/26 15:39:32
*/

JsonSerializer クラスのデシリアライザも同様に検証してみましたが、jsonString2 と jsonString3 場合は JsonException がスローされ "The JSON value could not be converted to System.DateTime." というエラーメッセージが出ます。jsonString1 と jsonString4 は問題なく DateTime 型のオブジェクトにデシリアライズされました。

例外のスタックトレースを見ると、Utf8JsonReader.GetDateTime() メソッドで例外がスローされており、そのメソッドの Microsoft のドキュメントを見ると Remarks に "This method only creates a DateTime representation of JSON strings that conform to the ISO 8601-1 extended format" と書いてあります。

ということで、上の表の (1) と (6) の文字列は ISO 8601-1 extended format に準拠しているので DateTime にデシリアライズできた、他の形式はダメだったということのようです。

Tags: , ,

.NET Framework

WPF で Chart を表示

by WebSurfer 2022年2月8日 12:33

ASP.NET Web アプリで Chart を使う方法を先の記事「Chart (ASP.NET Web Forms 用)」や「MVC で Chart を利用する方法」に書きましたが、それらに表示した棒グラフを同様に Chart を使って WPF アプリで表示する方法を書きます。

WPF アプリに Chart 表示

この記事でプロジェクトの作成に使ったのは Visual Studio Community 2022 のテンプレート「WPF アプリ (.NET Framework)」で、フレームワークは .NET Framework 4.8 です。

プロジェクトを作成したら、ソリューションエクスプローラーの「参照」を左クリック ⇒[参照の追加(R)...]をクリックして表示される「参照マネージャー」」ダイアログで以下の項目を参照に追加します。

  • WindowsFormIntegration
  • System.Windows.Forms
  • System.Windows.Forms.DataVisualization

上の 2 つが WPF アプリで Windows Forms 用のコントロールを使うために必要なもの、最後のものが Chart コントロールを使うためのものです。

次に、自動生成された MainWindow.xaml の XAML のコードに以下の画像の赤枠のコードを追加します。赤枠部分に Windows Froms 用のコントロール(この記事の例では Chart)を配置することができます。

XAML

Microsoft のドキュメント「チュートリアル: WPF での XAML を使用した Windows フォーム コントロールのホスト」のように XAML 上で Windows Forms 用コントロールを配置することができますが、この記事では MainWindow.xaml.cs で動的に Chart コントロールを作って配置することにします。

MainWindow.xaml.cs のコードは以下の通りです。これを実行した結果がこの記事の一番上の画像です。

using System.Windows;
using System.Data;
using System.Windows.Forms.DataVisualization.Charting;
using System.Data.SqlClient;

namespace WpfChart
{
    /// <summary>
    /// MainWindow.xaml の相互作用ロジック
    /// </summary>
    public partial class MainWindow : Window
    {
        private readonly Chart chart;

        public MainWindow()
        {
            InitializeComponent();

            this.chart = new Chart
            {
                Name = "chart1",
                Width = 600,
                DataSource = CreateDataTable().DefaultView
            };

            this.windowsFormsHost.Child = this.chart;

            Legend legend = new Legend()
            {
                DockedToChartArea = "ChartArea1",
                IsDockedInsideChartArea = false,
                Name = "Legend1"
            };

            chart.Legends.Add(legend);

            Series series = new Series()
            {
                Name = "三吉",
                ChartType = SeriesChartType.StackedColumn,
                CustomProperties = "DrawingStyle=Cylinder",
                IsValueShownAsLabel = true,
                Label = "#PERCENT{P1}",
                Legend = "Legend1",
                XValueMember = "Month",
                YValueMembers = "三吉"
            };
            chart.Series.Add(series);

            series = new Series()
            {
                Name = "春日",
                ChartType = SeriesChartType.StackedColumn,
                CustomProperties = "DrawingStyle=Cylinder",
                IsValueShownAsLabel = true,
                Label = "#PERCENT{P1}",
                Legend = "Legend1",
                XValueMember = "Month",
                YValueMembers = "春日"
            };
            chart.Series.Add(series);

            series = new Series()
            {
                Name = "東雲",
                ChartType = SeriesChartType.StackedColumn,
                CustomProperties = "DrawingStyle=Cylinder",
                IsValueShownAsLabel = true,
                Label = "#PERCENT{P1}",
                Legend = "Legend1",
                XValueMember = "Month",
                YValueMembers = "東雲"
            };
            chart.Series.Add(series);

            series = new Series()
            {
                Name = "府中",
                ChartType = SeriesChartType.StackedColumn,
                CustomProperties = "DrawingStyle=Cylinder",
                IsValueShownAsLabel = true,
                Label = "#PERCENT{P1}",
                Legend = "Legend1",
                XValueMember = "Month",
                YValueMembers = "府中"
            };
            chart.Series.Add(series);

            series = new Series()
            {
                Name = "広島",
                ChartType = SeriesChartType.StackedColumn,
                CustomProperties = "DrawingStyle=Cylinder",
                IsValueShownAsLabel = true,
                Label = "#PERCENT{P1}",
                Legend = "Legend1",
                XValueMember = "Month",
                YValueMembers = "広島"
            };
            chart.Series.Add(series);

            ChartArea chartArea = new ChartArea()
            {
                Name = "ChartArea1",
                AxisY = new Axis() { Title = "売上高" },
                AxisX = new Axis() { Title = "売上月" }
            };
            chart.ChartAreas.Add(chartArea);
        }

        private DataTable CreateDataTable()
        {
            string connString = @"・・・接続文字列・・・";
            string selectQuery = @"SELECT Month, 
                SUM(CASE WHEN Name = N'三吉' THEN Sales ELSE 0 END) AS 三吉,
                SUM(CASE WHEN Name = N'春日' THEN Sales ELSE 0 END) AS 春日,
                SUM(CASE WHEN Name = N'東雲' THEN Sales ELSE 0 END) AS 東雲,
                SUM(CASE WHEN Name = N'府中' THEN Sales ELSE 0 END) AS 府中,
                SUM(CASE WHEN Name = N'広島' THEN Sales ELSE 0 END) AS 広島
                FROM Shop GROUP BY Month";

            using (var connection = new SqlConnection(connString))
            {
                using (var command = new SqlCommand(selectQuery, connection))
                {
                    var adapter = new SqlDataAdapter(command);
                    var table = new DataTable();
                    adapter.Fill(table);
                    return table;
                }
            }
        }
    }
}

Tags: ,

.NET Framework

About this blog

2010年5月にこのブログを立ち上げました。主に ASP.NET Web アプリ関係の記事です。

Calendar

<<  2024年4月  >>
31123456
78910111213
14151617181920
21222324252627
2829301234
567891011

View posts in large calendar