C# Chart グラフをマウスで操作（パン、ズーム）する

# はじめに

[C#のChartコントロールで描いた散布図](https://qiita.com/nekotadon/items/5c0a8ff0260e2d27811e)をマウスで操作する方法です。例えばドラッグで移動（パン）したり、ホイール操作で拡大縮小（ズームイン/アウト）したりします。  

![chart_mouse_all.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3193449/fef9b3d2-be0a-d66a-b0f8-c9875cc1d76d.gif)  

# 移動

## ScaleViewのスクロールバーで移動する

![chart_mouse_scale.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3193449/d639662a-ec5f-8f05-e65b-8a62f119a5bc.gif)  

[`ScaleView`](https://learn.microsoft.com/ja-jp/dotnet/api/system.windows.forms.datavisualization.charting.axisscaleview?view=netframework-4.8.1)で表示範囲を拡大するとスクロールバーで表示範囲を変更できます。  

```cs
using System;
using System.Windows.Forms;
using System.Windows.Forms.DataVisualization.Charting;//グラフ用

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

            //初期化
            chart1.ChartAreas.Clear();
            chart1.Series.Clear();
            chart1.Legends.Clear();
            chart1.Dock = DockStyle.Fill;

            //グラフエリアを作成
            chart1.ChartAreas.Add(new ChartArea());//グラフエリア

            //データ系列を作成
            Series series = new Series { ChartType = SeriesChartType.Line, BorderWidth = 2 };

            //データ系列にプロットデータを追加
            for (int i = 0; i <= 360; i++)
            {
                series.Points.AddXY(i, Math.Sin((double)i / 360 * 2 * Math.PI));
            }

            //データ系列をChartコントロールに追加
            chart1.Series.Add(series);

            //Zoomしたとき引数の値をそのまま表示範囲にしたいのでマージンをなしにする。
            //マージンありで、Zoom(-50.0, 50.0)とした場合-51～51のように左右にマージンが作られてしまう。
            chart1.ChartAreas[0].AxisX.IsMarginVisible = false;

            //ScaleViewでグラフを拡大
            chart1.ChartAreas[0].AxisX.ScaleView.Zoom(50.0, 100.0);
        }
    }
}
```

なお、[`Zoom`](https://learn.microsoft.com/ja-jp/dotnet/api/system.windows.forms.datavisualization.charting.axisscaleview.zoom?view=netframework-4.8.1#system-windows-forms-datavisualization-charting-axisscaleview-zoom(system-double-system-double))では軸の最小最大値の範囲内でしか拡大できないようです。そのような場合は[`Position`](https://learn.microsoft.com/ja-jp/dotnet/api/system.windows.forms.datavisualization.charting.axisscaleview.position?view=netframework-4.8.1)と[`Size`](https://learn.microsoft.com/ja-jp/dotnet/api/system.windows.forms.datavisualization.charting.axisscaleview.size?view=netframework-4.8.1)を利用すると拡大できるようです。  

```cs
//軸の最小最大値のがdouble.NaNの場合

//表示範囲が-100～100になる。
ScaleView.Zoom(-100.0, 100.0);

//表示範囲が-100～100になる。
ScaleView.Position = -100;//開始位置
ScaleView.Size = 200;//幅
```

```cs
//軸の最小最大値のが-50～50の場合

//表示範囲は-50～50のまま。
ScaleView.Zoom(-100.0, 100.0);

//表示範囲が-100～100になる。
ScaleView.Position = -100;//開始位置
ScaleView.Size = 200;//幅
```

## ドラッグで移動する

![chart_mouse_move.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3193449/501cc924-2a4e-2bf3-dbd3-1a32c6d27329.gif)  

マウスカーソルが移動したとき、軸の値としてはどれだけ変化するかを[`ValueToPixelPosition`](https://learn.microsoft.com/ja-jp/dotnet/api/system.windows.forms.datavisualization.charting.axis.valuetopixelposition?view=netframework-4.8.1)を使って確認し、その分、目盛の表示範囲を変化させています。  

```cs
using System;
using System.Windows.Forms;
using System.Drawing;
using System.Windows.Forms.DataVisualization.Charting;//グラフ用

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

            //初期化
            chart1.ChartAreas.Clear();
            chart1.Series.Clear();
            chart1.Legends.Clear();
            chart1.Dock = DockStyle.Fill;

            //ChartAreaの追加
            chart1.ChartAreas.Add(new ChartArea());

            //ChartAreaの設定
            ChartArea chartArea = chart1.ChartAreas[0];

            //軸目盛のフォーマットを指定
            chartArea.AxisX.LabelStyle.Format = "0.0";
            chartArea.AxisY.LabelStyle.Format = "0.0";

            //移動時にグラフエリアのサイズが
            //自動調整（変更）されないようにグラフエリアを固定
            chartArea.InnerPlotPosition.Auto = false;
            chartArea.InnerPlotPosition.X = 8.0f;
            chartArea.InnerPlotPosition.Y = 4.0f;
            chartArea.InnerPlotPosition.Width = 84.0f;
            chartArea.InnerPlotPosition.Height = 84.0f;

            //イベント登録

            //マウスで移動
            chart1.MouseDown += new MouseEventHandler(this.chart1_MouseDown);
            chart1.MouseMove += new MouseEventHandler(this.chart1_MouseMove);
            chart1.MouseUp += new MouseEventHandler(this.chart1_MouseUp);

            //最初にグラフを描くときの初期化用
            chart1.Paint += new PaintEventHandler(this.chart1_Paint);

            //系列の作成
            Series series = new Series
            {
                Color = Color.Blue,
                MarkerColor = Color.Blue,
                MarkerSize = 10,
                MarkerStyle = MarkerStyle.Circle,
                ChartType = SeriesChartType.Line
            };

            //系列にプロットデータ追加
            for (double i = 0; i < 720; i++)
            {
                series.Points.AddXY(i, Math.Sin(i / 360 * 3.14 * 2));
            }

            //系列を追加
            chart1.Series.Add(series);
        }

        //起動時に比率計算
        bool atBoot = true;//起動時かどうか
        private void chart1_Paint(object sender, PaintEventArgs e)
        {
            if (atBoot)
            {
                atBoot = false;

                //マウスカーソルの移動量と軸の値の変化量の比率を計算
                mouseMove.rateX = calcRate(chart1.ChartAreas[0].AxisX);
                mouseMove.rateY = calcRate(chart1.ChartAreas[0].AxisY);
            }
        }

        private double calcRate(Axis axis)
        {
            //値
            double value_min = axis.ScaleView.ViewMinimum;
            double value_max = axis.ScaleView.ViewMaximum;

            //座標
            double pixel_min = axis.ValueToPixelPosition(value_min);
            double pixel_max = axis.ValueToPixelPosition(value_max);

            return (value_max - value_min) / (pixel_max - pixel_min);
        }

        //マウスカーソルがグラフ表示エリア内にあるかどうか
        private bool IsMouseInChartArea()
        {
            try
            {
                Axis AxisX = chart1.ChartAreas[0].AxisX;
                Axis AxisY = chart1.ChartAreas[0].AxisY;

                //Chartのクライアント座標でのマウス位置 
                Point mouse = chart1.PointToClient(System.Windows.Forms.Cursor.Position);

                //グラフ表示エリアの座標
                double pos_xmin = AxisX.ValueToPixelPosition(AxisX.ScaleView.ViewMinimum);//X軸左端
                double pos_xmax = AxisX.ValueToPixelPosition(AxisX.ScaleView.ViewMaximum);//X軸右端
                double pos_ymin = AxisY.ValueToPixelPosition(AxisY.ScaleView.ViewMaximum);//Y軸上端
                double pos_ymax = AxisY.ValueToPixelPosition(AxisY.ScaleView.ViewMinimum);//Y軸下端

                return (pos_ymin <= mouse.Y && mouse.Y <= pos_ymax && pos_xmin <= mouse.X && mouse.X <= pos_xmax);
            }
            catch (Exception)
            {
                return false;
            }
        }

        //マウスで移動
        private class MouseMoveClass
        {
            public bool isMouseDown = false;//マウスを押しているか
            public Point mouse0 = new Point();//マウスを押したときのマウスカーソル位置
            public double xmax0, xmin0;//マウスを押したときのX軸の最大値、最小値
            public double ymax0, ymin0;//マウスを押したときのX軸の最大値、最小値
            public double rateX, rateY;//比率（1ピクセルあたり値がいくつ変化するか）計算
        }

        MouseMoveClass mouseMove = new MouseMoveClass();

        private void chart1_MouseDown(object sender, MouseEventArgs e)
        {
            mouseMove.isMouseDown = false;

            Axis AxisX = chart1.ChartAreas[0].AxisX;
            Axis AxisY = chart1.ChartAreas[0].AxisY;

            //chartエリア内の場合でマウスの左ボタンの場合のみ有効
            if (!IsMouseInChartArea() || e.Button != MouseButtons.Left) return;

            //マウスボタンが押された時のマウスカーソル位置を覚えておく
            mouseMove.mouse0 = System.Windows.Forms.Cursor.Position;

            //マウスボタンが押された時の軸の最小値、最大値を覚えておく
            mouseMove.xmin0 = AxisX.ScaleView.ViewMinimum;
            mouseMove.xmax0 = AxisX.ScaleView.ViewMaximum;
            mouseMove.ymin0 = AxisY.ScaleView.ViewMinimum;
            mouseMove.ymax0 = AxisY.ScaleView.ViewMaximum;

            //作動中
            mouseMove.isMouseDown = true;
        }

        private void chart1_MouseUp(object sender, MouseEventArgs e)
        {
            //初期化
            mouseMove.isMouseDown = false;
        }

        private void chart1_MouseMove(object sender, MouseEventArgs e)
        {
            if (mouseMove.isMouseDown)
            {
                Axis AxisX = chart1.ChartAreas[0].AxisX;
                Axis AxisY = chart1.ChartAreas[0].AxisY;

                //現在のマウスカーソル位置
                Point mouse = System.Windows.Forms.Cursor.Position;

                if (Math.Abs(mouse.X - mouseMove.mouse0.X) >= 2)
                {
                    //マウスカーソルの移動量に比率をかけて値の変化量を算出
                    double delta = -mouseMove.rateX * (mouse.X - mouseMove.mouse0.X);

                    //グラフビューの変更
                    AxisX.ScaleView.Position = mouseMove.xmin0 + delta;
                    AxisX.ScaleView.Size = mouseMove.xmax0 - mouseMove.xmin0;
                }

                if (Math.Abs(mouse.Y - mouseMove.mouse0.Y) >= 2)
                {
                    //マウスカーソルの移動量に比率をかけて値の変化量を算出
                    double delta = -mouseMove.rateY * (mouse.Y - mouseMove.mouse0.Y);

                    //グラフビューの変更
                    AxisY.ScaleView.Position = mouseMove.ymin0 + delta;
                    AxisY.ScaleView.Size = mouseMove.ymax0 - mouseMove.ymin0;
                }
            }
        }
    }
}
```

## マウスホイールで移動する
![chart_mouse_wheelmove.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3193449/cb8887b9-20e4-675f-28f6-fe69ea96e5fc.gif)  

マウスホイールに以下の操作を割り当てます。  
* マウスホイール：上下スクロール（Y軸移動）  
* マウスの右ボタンが押された状態でマウスホイール：左右スクロール（X軸移動）  

```cs
using System;
using System.Windows.Forms;
using System.Windows.Forms.DataVisualization.Charting;//グラフ用

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

            //初期化
            chart1.ChartAreas.Clear();
            chart1.Series.Clear();
            chart1.Legends.Clear();
            chart1.Dock = DockStyle.Fill;

            //グラフエリアを作成
            chart1.ChartAreas.Add(new ChartArea());//グラフエリア

            //データ系列を作成
            Series series = new Series { ChartType = SeriesChartType.Line, BorderWidth = 2 };

            //データ系列にプロットデータを追加
            for (int i = 0; i <= 360; i++)
            {
                series.Points.AddXY(i, Math.Sin((double)i / 36 * 2 * Math.PI));
            }

            //データ系列をChartコントロールに追加
            chart1.Series.Add(series);

            //Zoomしたとき引数の値をそのまま表示範囲にしたいのでマージンをなしにする。
            //マージンありで、Zoom(-50.0, 50.0)とした場合-51～51のように左右にマージンが作られてしまう。
            chart1.ChartAreas[0].AxisX.IsMarginVisible = false;

            //ScaleViewでグラフを拡大
            chart1.ChartAreas[0].AxisX.ScaleView.Zoom(50.0, 100.0);
            chart1.ChartAreas[0].AxisY.ScaleView.Zoom(-0.5, 0.5);

            //ホイールで移動
            chart1.MouseWheel += new MouseEventHandler(this.chart1_MouseWheel);
            chart1.ChartAreas[0].AxisX.ScaleView.SmallScrollSize = 10.0;//スクロールでのX軸移動量
            chart1.ChartAreas[0].AxisY.ScaleView.SmallScrollSize = 0.2;//スクロールでのY軸移動量
        }

        //マウスホイールによるスクロール、マウス右ボタンが押されている場合はX軸スクロール、それ以外はY軸スクロール
        private void chart1_MouseWheel(object sender, System.Windows.Forms.MouseEventArgs e)
        {
            //マウス右ボタンが押されているかどうか
            bool right = (MouseButtons & MouseButtons.Right) == MouseButtons.Right;

            //マウス右ボタンが押されている場合はX軸、それ以外はY軸をスクロール
            Axis axis = right ? this.chart1.ChartAreas[0].AxisX : this.chart1.ChartAreas[0].AxisY;

            //マウスホイール手前でX軸なら右、Y軸なら下へ
            bool forward = right ? e.Delta > 0 : e.Delta < 0;

            //スクロールバーが表示されている場合のみスクロール
            if (axis.ScrollBar.IsVisible)
            {
                axis.ScaleView.Scroll(forward ? ScrollType.SmallDecrement : ScrollType.SmallIncrement);
            }
        }
    }
}
```

# 拡大・縮小

## ホイール操作で拡大・縮小する

![chart_mouse_wheelzoom.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3193449/f41200d9-ce3f-d5f7-f82d-fbd1ca6e447e.gif)  

ホイール操作がされたときに、[`MouseWheel`](https://learn.microsoft.com/ja-jp/dotnet/api/system.windows.forms.control.mousewheel?view=netframework-4.8.1)イベントが発生することを利用して、表示範囲を10%増減させています。また、マウスカーソルの位置を拡大縮小するようにしています。  

```cs
using System;
using System.Windows.Forms;
using System.Drawing;
using System.Windows.Forms.DataVisualization.Charting;//グラフ用

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

            //初期化
            chart1.ChartAreas.Clear();
            chart1.Series.Clear();
            chart1.Legends.Clear();
            chart1.Dock = DockStyle.Fill;

            //ChartAreaの追加
            chart1.ChartAreas.Add(new ChartArea());

            //ChartAreaの設定
            ChartArea chartArea = chart1.ChartAreas[0];

            //軸目盛のマージンなし
            chartArea.AxisX.IsMarginVisible = false;
            chartArea.AxisY.IsMarginVisible = false;

            //軸目盛のフォーマットを指定
            chartArea.AxisX.LabelStyle.Format = "0.0";
            chartArea.AxisY.LabelStyle.Format = "0.00";

            //移動、拡大時にグラフエリアのサイズが
            //自動調整（変更）されないようにグラフエリアを固定
            chartArea.InnerPlotPosition.Auto = false;
            chartArea.InnerPlotPosition.X = 8.0f;
            chartArea.InnerPlotPosition.Y = 4.0f;
            chartArea.InnerPlotPosition.Width = 84.0f;
            chartArea.InnerPlotPosition.Height = 84.0f;

            //イベント登録

            //マウスホールで拡大
            chart1.MouseWheel += new MouseEventHandler(this.chart1_MouseWheel);

            //系列の作成
            Series series = new Series
            {
                Color = Color.Blue,
                MarkerColor = Color.Blue,
                MarkerSize = 10,
                MarkerStyle = MarkerStyle.Circle,
                ChartType = SeriesChartType.Line
            };

            //系列にプロットデータ追加
            for (double i = 0; i < 720; i++)
            {
                series.Points.AddXY(i, Math.Sin(i / 360 * 3.14 * 2));
            }

            //系列を追加
            chart1.Series.Add(series);
        }

        //マウスカーソルがグラフ表示エリア内にあるかどうか
        private bool IsMouseInChartArea()
        {
            try
            {
                Axis AxisX = chart1.ChartAreas[0].AxisX;
                Axis AxisY = chart1.ChartAreas[0].AxisY;

                //Chartのクライアント座標でのマウス位置 
                Point mouse = chart1.PointToClient(System.Windows.Forms.Cursor.Position);

                //グラフ表示エリアの座標
                double pos_xmin = AxisX.ValueToPixelPosition(AxisX.ScaleView.ViewMinimum);//X軸左端
                double pos_xmax = AxisX.ValueToPixelPosition(AxisX.ScaleView.ViewMaximum);//X軸右端
                double pos_ymin = AxisY.ValueToPixelPosition(AxisY.ScaleView.ViewMaximum);//Y軸上端
                double pos_ymax = AxisY.ValueToPixelPosition(AxisY.ScaleView.ViewMinimum);//Y軸下端

                return (pos_ymin <= mouse.Y && mouse.Y <= pos_ymax && pos_xmin <= mouse.X && mouse.X <= pos_xmax);
            }
            catch (Exception)
            {
                return false;
            }
        }

        //マウスホイールによるズーム
        private void chart1_MouseWheel(object sender, System.Windows.Forms.MouseEventArgs e)
        {
            if (IsMouseInChartArea())
            {
                Point mouse = chart1.PointToClient(System.Windows.Forms.Cursor.Position);

                mouseZoom(chart1.ChartAreas[0].AxisX, mouse.X, e.Delta > 0);//X軸ズーム
                mouseZoom(chart1.ChartAreas[0].AxisY, mouse.Y, e.Delta > 0);//Y軸ズーム
            }
        }

        private void mouseZoom(Axis axis, int mousePosition, bool zoomIn)//前に回転で拡大
        {
            try
            {
                //10%ずつ拡大縮小
                double zoomRate = 0.9;

                //変化量率
                double ratio = 1.0 - (zoomIn ? zoomRate : 1.0 / zoomRate);

                //マウスカーソルの位置の値
                double value = axis.PixelPositionToValue(mousePosition);

                //現在のグラフビューの最小値、最大値
                double vmin0 = axis.ScaleView.ViewMinimum;//最小値
                double vmax0 = axis.ScaleView.ViewMaximum;//最大値

                //変更後のグラフビューの最小値、最大値
                double vmax1 = vmax0 - (vmax0 - value) * ratio;
                double vmin1 = vmin0 + (value - vmin0) * ratio;

                //グラフビューの変更
                axis.ScaleView.Position = vmin1;
                axis.ScaleView.Size = vmax1 - vmin1;
            }
            catch (Exception)
            {
                ;
            }
        }
    }
}
```

## ドラッグで拡大する

### 選択範囲を拡大する

![chart_mouse_mousezoom.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3193449/9006b39b-9611-e04e-b503-2a14320fca4c.gif)  

[`ChartArea`](https://learn.microsoft.com/ja-jp/dotnet/api/system.windows.forms.datavisualization.charting.chartarea?view=netframework-4.8.1)の`CursorX`,`CursorY`プロパティの[`IsUserSelectionEnabled`](https://learn.microsoft.com/ja-jp/dotnet/api/system.windows.forms.datavisualization.charting.cursor.isuserselectionenabled?view=netframework-4.8.1)をtrueにすることで、マウスで選択した範囲を拡大することができます。  

```cs
using System;
using System.Windows.Forms;
using System.Drawing;
using System.Windows.Forms.DataVisualization.Charting;//グラフ用

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

            //初期化
            chart1.ChartAreas.Clear();
            chart1.Series.Clear();
            chart1.Legends.Clear();
            chart1.Dock = DockStyle.Fill;

            //ChartAreaの追加
            chart1.ChartAreas.Add(new ChartArea());

            //ChartAreaの設定
            ChartArea chartArea = chart1.ChartAreas[0];

            //軸目盛のマージンなし
            chartArea.AxisX.IsMarginVisible = false;
            chartArea.AxisY.IsMarginVisible = false;

            //軸目盛のフォーマットを指定
            chartArea.AxisX.LabelStyle.Format = "0.0";
            chartArea.AxisY.LabelStyle.Format = "0.00";

            //任意範囲を拡大可能にする
            chartArea.CursorX.IsUserSelectionEnabled = true;
            chartArea.CursorY.IsUserSelectionEnabled = true;
            chartArea.CursorX.Interval = 0;
            chartArea.CursorY.Interval = 0;

            //移動、拡大時にグラフエリアのサイズが
            //自動調整（変更）されないようにグラフエリアを固定
            chartArea.InnerPlotPosition.Auto = false;
            chartArea.InnerPlotPosition.X = 8.0f;
            chartArea.InnerPlotPosition.Y = 4.0f;
            chartArea.InnerPlotPosition.Width = 84.0f;
            chartArea.InnerPlotPosition.Height = 84.0f;

            //系列の作成
            Series series = new Series
            {
                Color = Color.Blue,
                MarkerColor = Color.Blue,
                MarkerSize = 10,
                MarkerStyle = MarkerStyle.Circle,
                ChartType = SeriesChartType.Line
            };

            //系列にプロットデータ追加
            for (double i = 0; i < 720; i++)
            {
                series.Points.AddXY(i, Math.Sin(i / 360 * 3.14 * 2));
            }

            //系列を追加
            chart1.Series.Add(series);
        }
    }
}
```

### X,Y軸で個別に拡大する

![chart_mouse_mousezoom_xy.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3193449/e29135e3-25d2-88af-f186-38c9a6685397.gif)  

上記の例では、単純に選択範囲がそのまま拡大されます。例えば`CursorY`の`IsUserSelectionEnabled`をfalseにすれば、X軸のみ拡大できますが、操作性が悪くなります。そのため、マウスの横方向への移動量と縦方向への移動量を比較して、移動量が大きいほうの軸だけを拡大するようにしたのが下記です。また、誤って拡大しないよう、一定以上の移動量がないときは拡大しないようにしています。  

```cs
using System;
using System.Windows.Forms;
using System.Drawing;
using System.Windows.Forms.DataVisualization.Charting;//グラフ用

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

            //初期化
            chart1.ChartAreas.Clear();
            chart1.Series.Clear();
            chart1.Legends.Clear();
            chart1.Dock = DockStyle.Fill;

            //ChartAreaの追加
            chart1.ChartAreas.Add(new ChartArea());

            //ChartAreaの設定
            ChartArea chartArea = chart1.ChartAreas[0];

            //軸目盛のマージンなし
            chartArea.AxisX.IsMarginVisible = false;
            chartArea.AxisY.IsMarginVisible = false;

            //軸目盛のフォーマットを指定
            chartArea.AxisX.LabelStyle.Format = "0.0";
            chartArea.AxisY.LabelStyle.Format = "0.00";

            //任意範囲を拡大可能にする
            chartArea.CursorX.IsUserSelectionEnabled = true;
            chartArea.CursorY.IsUserSelectionEnabled = true;
            chartArea.CursorX.Interval = 0;
            chartArea.CursorY.Interval = 0;

            //移動、拡大時にグラフエリアのサイズが
            //自動調整（変更）されないようにグラフエリアを固定
            chartArea.InnerPlotPosition.Auto = false;
            chartArea.InnerPlotPosition.X = 8.0f;
            chartArea.InnerPlotPosition.Y = 4.0f;
            chartArea.InnerPlotPosition.Width = 84.0f;
            chartArea.InnerPlotPosition.Height = 84.0f;

            //イベント登録

            //マウスで拡大
            chart1.MouseDown += new MouseEventHandler(this.chart1_MouseDown);
            chart1.MouseMove += new MouseEventHandler(this.chart1_MouseMove);
            chart1.MouseUp += new MouseEventHandler(this.chart1_MouseUp);

            //系列の作成
            Series series = new Series
            {
                Color = Color.Blue,
                MarkerColor = Color.Blue,
                MarkerSize = 10,
                MarkerStyle = MarkerStyle.Circle,
                ChartType = SeriesChartType.Line
            };

            //系列にプロットデータ追加
            for (double i = 0; i < 720; i++)
            {
                series.Points.AddXY(i, Math.Sin(i / 360 * 3.14 * 2));
            }

            //系列を追加
            chart1.Series.Add(series);
        }

        //マウスカーソルがグラフ表示エリア内にあるかどうか
        private bool IsMouseInChartArea()
        {
            try
            {
                Axis AxisX = chart1.ChartAreas[0].AxisX;
                Axis AxisY = chart1.ChartAreas[0].AxisY;

                //Chartのクライアント座標でのマウス位置 
                Point mouse = chart1.PointToClient(System.Windows.Forms.Cursor.Position);

                //グラフ表示エリアの座標
                double pos_xmin = AxisX.ValueToPixelPosition(AxisX.ScaleView.ViewMinimum);//X軸左端
                double pos_xmax = AxisX.ValueToPixelPosition(AxisX.ScaleView.ViewMaximum);//X軸右端
                double pos_ymin = AxisY.ValueToPixelPosition(AxisY.ScaleView.ViewMaximum);//Y軸上端
                double pos_ymax = AxisY.ValueToPixelPosition(AxisY.ScaleView.ViewMinimum);//Y軸下端

                return (pos_ymin <= mouse.Y && mouse.Y <= pos_ymax && pos_xmin <= mouse.X && mouse.X <= pos_xmax);
            }
            catch (Exception)
            {
                return false;
            }
        }

        //マウスで拡大
        private class MouseMoveClass
        {
            public bool isMouseDown = false;//マウスを押しているか
            public Point mouse0 = new Point();//マウスを押したときのマウスカーソル位置
            public double xmax0, xmin0;//マウスを押したときのX軸の最大値、最小値
            public double ymax0, ymin0;//マウスを押したときのX軸の最大値、最小値
            public double x0, y0;//マウスを押したときのX,Y軸の値

            //モード
            public int Mode;//モード
            public int MODE_NONE = 0;//拡大なし
            public int MODE_CURSOR_X = 1;//X軸拡大
            public int MODE_CURSOR_Y = 2;//Y軸拡大
        }

        MouseMoveClass mouseMove = new MouseMoveClass();

        private void chart1_MouseDown(object sender, MouseEventArgs e)
        {
            mouseMove.isMouseDown = false;

            Axis AxisX = chart1.ChartAreas[0].AxisX;
            Axis AxisY = chart1.ChartAreas[0].AxisY;
            System.Windows.Forms.DataVisualization.Charting.Cursor CursorX = chart1.ChartAreas[0].CursorX;
            System.Windows.Forms.DataVisualization.Charting.Cursor CursorY = chart1.ChartAreas[0].CursorY;

            CursorX.IsUserSelectionEnabled = false;
            CursorY.IsUserSelectionEnabled = false;

            //chartエリア内の場合でマウスの左ボタンの場合のみ有効
            if (!IsMouseInChartArea() || e.Button != MouseButtons.Left) return;

            //マウスボタンが押された時のマウスカーソル位置を覚えておく
            mouseMove.mouse0 = System.Windows.Forms.Cursor.Position;

            //マウスボタンが押された時の軸の最小値、最大値を覚えておく
            mouseMove.xmin0 = AxisX.ScaleView.ViewMinimum;
            mouseMove.xmax0 = AxisX.ScaleView.ViewMaximum;
            mouseMove.ymin0 = AxisY.ScaleView.ViewMinimum;
            mouseMove.ymax0 = AxisY.ScaleView.ViewMaximum;

            try
            {
                //マウスボタンが押された時の軸のマウスカーソルの位置の値、最小値、最大値を覚えておく
                mouseMove.x0 = AxisX.PixelPositionToValue(chart1.PointToClient(mouseMove.mouse0).X);
                mouseMove.y0 = AxisY.PixelPositionToValue(chart1.PointToClient(mouseMove.mouse0).Y);

                //モード設定
                mouseMove.Mode = mouseMove.MODE_NONE;
                CursorX.IsUserSelectionEnabled = true;
                CursorY.IsUserSelectionEnabled = true;

                //作動中
                mouseMove.isMouseDown = true;
            }
            catch (Exception)
            {
                ;
            }
        }

        private void chart1_MouseUp(object sender, MouseEventArgs e)
        {
            Axis AxisX = chart1.ChartAreas[0].AxisX;
            Axis AxisY = chart1.ChartAreas[0].AxisY;
            System.Windows.Forms.DataVisualization.Charting.Cursor CursorX = chart1.ChartAreas[0].CursorX;
            System.Windows.Forms.DataVisualization.Charting.Cursor CursorY = chart1.ChartAreas[0].CursorY;

            //初期化
            mouseMove.isMouseDown = false;
            mouseMove.Mode = mouseMove.MODE_NONE;

            CursorX.IsUserSelectionEnabled = true;
            CursorY.IsUserSelectionEnabled = true;
            AxisX.ScaleView.Zoomable = true;
            AxisY.ScaleView.Zoomable = true;
            CursorX.SelectionStart = CursorX.SelectionEnd;
            CursorY.SelectionStart = CursorY.SelectionEnd;
        }

        private void chart1_MouseMove(object sender, MouseEventArgs e)
        {
            if (mouseMove.isMouseDown)
            {
                Axis AxisX = chart1.ChartAreas[0].AxisX;
                Axis AxisY = chart1.ChartAreas[0].AxisY;
                System.Windows.Forms.DataVisualization.Charting.Cursor CursorX = chart1.ChartAreas[0].CursorX;
                System.Windows.Forms.DataVisualization.Charting.Cursor CursorY = chart1.ChartAreas[0].CursorY;

                //現在のマウスカーソル位置
                Point mouse = System.Windows.Forms.Cursor.Position;

                if (Math.Abs(mouse.X - mouseMove.mouse0.X) >= 10 || Math.Abs(mouse.Y - mouseMove.mouse0.Y) >= 10)
                {
                    AxisX.ScaleView.Zoomable = AxisY.ScaleView.Zoomable = true;

                    if (Math.Abs(mouse.X - mouseMove.mouse0.X) > Math.Abs(mouse.Y - mouseMove.mouse0.Y))
                    {
                        if (mouseMove.Mode != mouseMove.MODE_CURSOR_X)
                        {
                            mouseMove.Mode = mouseMove.MODE_CURSOR_X;

                            //X軸の範囲選択のみ可とする
                            CursorX.IsUserSelectionEnabled = true;
                            CursorY.IsUserSelectionEnabled = false;
                            CursorY.SelectionStart = CursorY.SelectionEnd;

                            //選択範囲開始位置
                            CursorX.SelectionStart = mouseMove.x0;

                            //選択範囲終了位置
                            try
                            {
                                CursorX.SelectionEnd = AxisX.PixelPositionToValue(chart1.PointToClient(mouse).X);
                                if (CursorX.SelectionEnd < mouseMove.xmin0)
                                {
                                    CursorX.SelectionEnd = mouseMove.xmin0;
                                }
                                if (CursorX.SelectionEnd > mouseMove.xmax0)
                                {
                                    CursorX.SelectionEnd = mouseMove.xmax0;
                                }
                            }
                            catch (Exception)
                            {
                                ;
                            }
                        }
                    }
                    else
                    {
                        if (mouseMove.Mode != mouseMove.MODE_CURSOR_Y)
                        {
                            mouseMove.Mode = mouseMove.MODE_CURSOR_Y;

                            //Y軸の範囲選択のみ可とする
                            CursorY.IsUserSelectionEnabled = true;
                            CursorX.IsUserSelectionEnabled = false;
                            CursorX.SelectionStart = CursorX.SelectionEnd;

                            //選択範囲開始位置
                            CursorY.SelectionStart = mouseMove.y0;

                            //選択範囲終了位置
                            try
                            {
                                CursorY.SelectionEnd = AxisY.PixelPositionToValue(chart1.PointToClient(mouse).Y);
                                if (CursorY.SelectionEnd < mouseMove.ymin0)
                                {
                                    CursorY.SelectionEnd = mouseMove.ymin0;
                                }
                                if (CursorY.SelectionEnd > mouseMove.ymax0)
                                {
                                    CursorY.SelectionEnd = mouseMove.ymax0;
                                }
                            }
                            catch (Exception)
                            {
                                ;
                            }
                        }
                    }
                }
                else
                {
                    AxisX.ScaleView.Zoomable = AxisY.ScaleView.Zoomable = false;
                }
            }
        }
    }
}
```

# 表示範囲のリセット

## ダブルクリックで表示範囲をリセットする

![chart_mouse_dclick.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3193449/95b96ca9-1002-70e3-6e4b-6dcfb4543f41.gif)  

拡大や縮小により変わってしまった表示範囲をダブルクリックで元に戻します。  

```cs
using System;
using System.Windows.Forms;
using System.Drawing;
using System.Windows.Forms.DataVisualization.Charting;//グラフ用

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

            //初期化
            chart1.ChartAreas.Clear();
            chart1.Series.Clear();
            chart1.Legends.Clear();
            chart1.Dock = DockStyle.Fill;

            //ChartAreaの追加
            chart1.ChartAreas.Add(new ChartArea());

            //ChartAreaの設定
            ChartArea chartArea = chart1.ChartAreas[0];

            //軸目盛のマージンなし
            chartArea.AxisX.IsMarginVisible = false;
            chartArea.AxisY.IsMarginVisible = false;

            //軸目盛のフォーマットを指定
            chartArea.AxisX.LabelStyle.Format = "0.0";
            chartArea.AxisY.LabelStyle.Format = "0.00";

            //任意範囲を拡大可能にする
            chartArea.CursorX.IsUserSelectionEnabled = true;
            chartArea.CursorY.IsUserSelectionEnabled = true;
            chartArea.CursorX.Interval = 0;
            chartArea.CursorY.Interval = 0;

            //移動、拡大時にグラフエリアのサイズが
            //自動調整（変更）されないようにグラフエリアを固定
            chartArea.InnerPlotPosition.Auto = false;
            chartArea.InnerPlotPosition.X = 8.0f;
            chartArea.InnerPlotPosition.Y = 4.0f;
            chartArea.InnerPlotPosition.Width = 84.0f;
            chartArea.InnerPlotPosition.Height = 84.0f;

            //イベント登録

            //マウスダブルクリックでズームリセット
            chart1.MouseDoubleClick += new MouseEventHandler(this.chart1_MouseDoubleClick);

            //系列の作成
            Series series = new Series
            {
                Color = Color.Blue,
                MarkerColor = Color.Blue,
                MarkerSize = 10,
                MarkerStyle = MarkerStyle.Circle,
                ChartType = SeriesChartType.Line
            };

            //系列にプロットデータ追加
            for (double i = 0; i < 720; i++)
            {
                series.Points.AddXY(i, Math.Sin(i / 360 * 3.14 * 2));
            }

            //系列を追加
            chart1.Series.Add(series);
        }

        //マウスカーソルがグラフ表示エリア内にあるかどうか
        private bool IsMouseInChartArea()
        {
            try
            {
                Axis AxisX = chart1.ChartAreas[0].AxisX;
                Axis AxisY = chart1.ChartAreas[0].AxisY;

                //Chartのクライアント座標でのマウス位置 
                Point mouse = chart1.PointToClient(System.Windows.Forms.Cursor.Position);

                //グラフ表示エリアの座標
                double pos_xmin = AxisX.ValueToPixelPosition(AxisX.ScaleView.ViewMinimum);//X軸左端
                double pos_xmax = AxisX.ValueToPixelPosition(AxisX.ScaleView.ViewMaximum);//X軸右端
                double pos_ymin = AxisY.ValueToPixelPosition(AxisY.ScaleView.ViewMaximum);//Y軸上端
                double pos_ymax = AxisY.ValueToPixelPosition(AxisY.ScaleView.ViewMinimum);//Y軸下端

                return (pos_ymin <= mouse.Y && mouse.Y <= pos_ymax && pos_xmin <= mouse.X && mouse.X <= pos_xmax);
            }
            catch (Exception)
            {
                return false;
            }
        }

        //ダブルクリックでズームリセット
        private void chart1_MouseDoubleClick(object sender, MouseEventArgs e)
        {
            if (IsMouseInChartArea())
            {
                Axis AxisX = chart1.ChartAreas[0].AxisX;
                Axis AxisY = chart1.ChartAreas[0].AxisY;

                //ズームのリセット
                AxisX.Minimum = AxisY.Minimum = AxisX.Maximum = AxisY.Maximum = double.NaN;
                AxisX.ScaleView.Zoom(AxisX.Minimum, AxisX.Maximum);
                AxisY.ScaleView.Zoom(AxisY.Minimum, AxisY.Maximum);
                chart1.ChartAreas[0].RecalculateAxesScale();
            }
        }
    }
}
```

# 実装サンプル

上記のコードを利用した実装サンプルです。記事トップの画像の元コードになります。Chartコントロールの[`Paint`](https://learn.microsoft.com/ja-jp/dotnet/api/system.windows.forms.control.paint?view=netframework-4.8.1) [`AxisViewChanged`](https://learn.microsoft.com/ja-jp/dotnet/api/system.windows.forms.datavisualization.charting.chart.axisviewchanged?view=netframework-4.8.1) [`SizeChanged`](https://learn.microsoft.com/ja-jp/dotnet/api/system.windows.forms.control.sizechanged?view=netframework-4.8.1)イベントにより、起動時やグラフサイズ変更時などに[目盛間隔を自動調整する](https://qiita.com/nekotadon/items/f86b3a2708d49e85c076)ようにしています。  

|マウス操作|対応機能|
|:----|:----|
|マウスホイール操作|X軸の拡大縮小|
|Shift+マウスホイール操作|Y軸の拡大縮小|
|マウスドラッグ操作|移動|
|Shift+マウスドラッグ操作|範囲拡大|
|ダブルクリック|表示範囲のリセット|


```cs
using System;
using System.Windows.Forms;
using System.Drawing;
using System.Windows.Forms.DataVisualization.Charting;//グラフ用

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

            //初期化
            chart1.ChartAreas.Clear();
            chart1.Series.Clear();
            chart1.Legends.Clear();
            chart1.Dock = DockStyle.Fill;

            //ChartAreaの追加
            chart1.ChartAreas.Add(new ChartArea());

            //ChartAreaの設定
            ChartArea chartArea = chart1.ChartAreas[0];

            //軸目盛のマージンなし
            chartArea.AxisX.IsMarginVisible = false;
            chartArea.AxisY.IsMarginVisible = false;

            //任意範囲を拡大可能にする
            chartArea.CursorX.IsUserSelectionEnabled = true;
            chartArea.CursorY.IsUserSelectionEnabled = true;
            chartArea.CursorX.Interval = 0;
            chartArea.CursorY.Interval = 0;

            //移動、拡大時にグラフエリアのサイズが
            //自動調整（変更）されないようにグラフエリアを固定
            chartArea.InnerPlotPosition.Auto = false;
            chartArea.InnerPlotPosition.X = 8.0f;
            chartArea.InnerPlotPosition.Y = 4.0f;
            chartArea.InnerPlotPosition.Width = 84.0f;
            chartArea.InnerPlotPosition.Height = 84.0f;

            //イベント登録

            //マウスホールで拡大
            chart1.MouseWheel += new MouseEventHandler(this.chart1_MouseWheel);

            //マウスで移動
            chart1.MouseDown += new MouseEventHandler(this.chart1_MouseDown);
            chart1.MouseMove += new MouseEventHandler(this.chart1_MouseMove);
            chart1.MouseUp += new MouseEventHandler(this.chart1_MouseUp);

            //マウスダブルクリックでズームリセット
            chart1.MouseDoubleClick += new MouseEventHandler(this.chart1_MouseDoubleClick);

            //最初にグラフを描くときの初期化用
            chart1.Paint += new PaintEventHandler(this.chart1_Paint);

            //サイズ、スケール変更時
            chart1.AxisViewChanged += new EventHandler<ViewEventArgs>(this.chart1_AxisViewChanged);
            chart1.SizeChanged += new EventHandler(this.chart1_SizeChanged);

            //系列の作成
            Series series = new Series
            {
                Color = Color.Blue,
                MarkerColor = Color.Blue,
                MarkerSize = 10,
                MarkerStyle = MarkerStyle.Circle,
                ChartType = SeriesChartType.Line
            };

            //系列にプロットデータ追加
            for (double i = 0; i < 720; i++)
            {
                series.Points.AddXY(i, Math.Sin(i / 360 * 3.14 * 2));
            }

            //系列を追加
            chart1.Series.Add(series);
        }

        //目盛間隔の自動調整
        private void autoIntervalAdjust(Axis axis)
        {
            //軸の最大最小値がdouble.NaN(自動設定)の場合は、
            //先にグラフ エリアのプロパティを再計算 ChartArea.RecalculateAxesScale(); しておくこと

            //大体何ピクセルごとに目盛をふるか
            double pixelWidthInterval = 75.0;

            //軸の分割数の配列（この中の値のいずれかで分割される）
            int[] splitCounts = new int[] { 1, 2, 4, 5, 10, 20, 40, 50, 100 };

            //目盛数値の区切り配列（この中の値のいずれか×10^nが目盛数値になる）
            int[] IntervalSteps = new int[] { 1, 2, 5, 10 };

            try
            {
                //グラフビューの最小値、最大値
                double vmin = axis.ScaleView.ViewMinimum;
                double vmax = axis.ScaleView.ViewMaximum;

                //グラフビューの幅（ピクセル）
                double pixelWidth = Math.Abs(axis.ValueToPixelPosition(vmax) - axis.ValueToPixelPosition(vmin));

                //軸の分割数を仮設定
                int splitCount = (int)Math.Floor(pixelWidth / pixelWidthInterval);//仮分割数（実数）

                //軸の分割数を設定（軸の分割数の配列の中から採用）
                int idx;
                idx = splitCounts.Length - 1;
                for (int i = 0; i < splitCounts.Length; i++)
                {
                    if (splitCount <= splitCounts[i])
                    {
                        idx = i;
                        break;
                    }
                }
                splitCount = splitCounts[idx];//分割数（整数）

                //目盛間隔を仮設定
                double autoInterval = (vmax - vmin) / splitCount;         //仮目盛間隔（実数）
                double exponent = Math.Floor(Math.Log10(autoInterval));   //仮目盛間隔の指数表記の指数部
                double mantissa = autoInterval / Math.Pow(10.0, exponent);//仮目盛間隔の指数表記の仮数部

                //目盛間隔を設定（目盛数値の区切り配列の中から採用）
                idx = IntervalSteps.Length - 1;
                for (int i = 0; i < IntervalSteps.Length; i++)
                {
                    if (mantissa <= IntervalSteps[i])
                    {
                        idx = i;
                        break;
                    }
                }
                axis.Interval = IntervalSteps[idx] * Math.Pow(10.0, exponent);//目盛間隔（整数）

                //目盛のオフセット、例えば最小値が-10.28で目盛間隔が2の場合は-12から目盛を振る
                double left = Math.Floor(vmin / axis.Interval) * axis.Interval;//目盛最左端-12
                axis.IntervalOffset = left - vmin;//-12-(-10.28)
            }
            catch (Exception)
            {
                ;
            }
        }

        //表示変更時再計算する
        bool atBoot = true;//起動時かどうか
        private void chart1_Paint(object sender, PaintEventArgs e)
        {
            //起動時
            if (atBoot)
            {
                atBoot = false;

                //目盛間隔の自動調整と比率計算
                calcIntervalAndRate();
            }
        }
        private void chart1_SizeChanged(object sender, EventArgs e)
        {
            //目盛間隔の自動調整と比率計算
            calcIntervalAndRate();
        }

        private void chart1_AxisViewChanged(object sender, ViewEventArgs e)
        {
            //目盛間隔の自動調整と比率計算
            calcIntervalAndRate();
        }
        private void calcIntervalAndRate()
        {
            //目盛間隔の自動調整
            autoIntervalAdjust(chart1.ChartAreas[0].AxisX);
            autoIntervalAdjust(chart1.ChartAreas[0].AxisY);

            //比率再計算
            calcRateXY();
        }

        //マウスカーソルの移動量と軸の値の変化量の比率を計算
        private void calcRateXY()
        {
            mouseMove.rateX = calcRate(chart1.ChartAreas[0].AxisX);
            mouseMove.rateY = calcRate(chart1.ChartAreas[0].AxisY);
        }

        private double calcRate(Axis axis)
        {
            //値
            double value_min = axis.ScaleView.ViewMinimum;
            double value_max = axis.ScaleView.ViewMaximum;

            //座標
            double pixel_min = axis.ValueToPixelPosition(value_min);
            double pixel_max = axis.ValueToPixelPosition(value_max);

            return (value_max - value_min) / (pixel_max - pixel_min);
        }

        //マウスカーソルがグラフ表示エリア内にあるかどうか
        private bool IsMouseInChartArea()
        {
            try
            {
                Axis AxisX = chart1.ChartAreas[0].AxisX;
                Axis AxisY = chart1.ChartAreas[0].AxisY;

                //Chartのクライアント座標でのマウス位置 
                Point mouse = chart1.PointToClient(System.Windows.Forms.Cursor.Position);

                //グラフ表示エリアの座標
                double pos_xmin = AxisX.ValueToPixelPosition(AxisX.ScaleView.ViewMinimum);//X軸左端
                double pos_xmax = AxisX.ValueToPixelPosition(AxisX.ScaleView.ViewMaximum);//X軸右端
                double pos_ymin = AxisY.ValueToPixelPosition(AxisY.ScaleView.ViewMaximum);//Y軸上端
                double pos_ymax = AxisY.ValueToPixelPosition(AxisY.ScaleView.ViewMinimum);//Y軸下端

                return (pos_ymin <= mouse.Y && mouse.Y <= pos_ymax && pos_xmin <= mouse.X && mouse.X <= pos_xmax);
            }
            catch (Exception)
            {
                return false;
            }
        }

        //マウスホイールによるズーム
        private void chart1_MouseWheel(object sender, System.Windows.Forms.MouseEventArgs e)
        {
            if (IsMouseInChartArea())
            {
                Point mouse = chart1.PointToClient(System.Windows.Forms.Cursor.Position);

                if ((Control.ModifierKeys & Keys.Shift) == Keys.Shift)//Shiftが押されている場合はY軸ズーム
                {
                    mouseZoom(chart1.ChartAreas[0].AxisY, mouse.Y, e.Delta > 0);//前に回転で拡大
                    mouseMove.rateY = calcRate(chart1.ChartAreas[0].AxisY);
                }
                else//通常はX軸ズーム
                {
                    mouseZoom(chart1.ChartAreas[0].AxisX, mouse.X, e.Delta > 0);//前に回転で拡大
                    mouseMove.rateX = calcRate(chart1.ChartAreas[0].AxisX);
                }
            }
        }

        private void mouseZoom(Axis axis, int mousePosition, bool zoomIn)
        {
            try
            {
                //10%ずつ拡大縮小
                double zoomRate = 0.9;

                //変化量率
                double ratio = 1.0 - (zoomIn ? zoomRate : 1.0 / zoomRate);

                //マウスカーソルの位置の値
                double value = axis.PixelPositionToValue(mousePosition);

                //現在のグラフビューの最小値、最大値
                double vmin0 = axis.ScaleView.ViewMinimum;//最小値
                double vmax0 = axis.ScaleView.ViewMaximum;//最大値

                //変更後のグラフビューの最小値、最大値
                double vmax1 = vmax0 - (vmax0 - value) * ratio;
                double vmin1 = vmin0 + (value - vmin0) * ratio;

                //グラフビューの変更
                axis.ScaleView.Position = vmin1;
                axis.ScaleView.Size = vmax1 - vmin1;

                //目盛間隔の自動調整
                autoIntervalAdjust(axis);
            }
            catch (Exception)
            {
                ;
            }
        }

        //マウスで移動、拡大
        private class MouseMoveClass
        {
            //共通
            public bool isMouseDown = false;//マウスを押しているか
            public Point mouse0 = new Point();//マウスを押したときのマウスカーソル位置
            public double xmax0, xmin0;//マウスを押したときのX軸の最大値、最小値
            public double ymax0, ymin0;//マウスを押したときのX軸の最大値、最小値

            //移動用
            public double rateX, rateY;//比率（1ピクセルあたり値がいくつ変化するか）計算

            //拡大用
            public double x0, y0;//マウスを押したときのX,Y軸の値

            //モード
            public int Mode;//モード
            public int MODE_NONE = 0;//拡大なし
            public int MODE_CURSOR_X = 1;//X軸拡大
            public int MODE_CURSOR_Y = 2;//Y軸拡大
            public int MODE_MOVE = 3;//移動
        }

        MouseMoveClass mouseMove = new MouseMoveClass();

        private void chart1_MouseDown(object sender, MouseEventArgs e)
        {
            mouseMove.isMouseDown = false;

            Axis AxisX = chart1.ChartAreas[0].AxisX;
            Axis AxisY = chart1.ChartAreas[0].AxisY;
            System.Windows.Forms.DataVisualization.Charting.Cursor CursorX = chart1.ChartAreas[0].CursorX;
            System.Windows.Forms.DataVisualization.Charting.Cursor CursorY = chart1.ChartAreas[0].CursorY;

            CursorX.IsUserSelectionEnabled = false;
            CursorY.IsUserSelectionEnabled = false;

            //chartエリア内の場合でマウスの左ボタンの場合のみ有効
            if (!IsMouseInChartArea() || e.Button != MouseButtons.Left) return;

            //マウスボタンが押された時のマウスカーソル位置を覚えておく
            mouseMove.mouse0 = System.Windows.Forms.Cursor.Position;

            //マウスボタンが押された時の軸の最小値、最大値を覚えておく
            mouseMove.xmin0 = AxisX.ScaleView.ViewMinimum;
            mouseMove.xmax0 = AxisX.ScaleView.ViewMaximum;
            mouseMove.ymin0 = AxisY.ScaleView.ViewMinimum;
            mouseMove.ymax0 = AxisY.ScaleView.ViewMaximum;

            if ((ModifierKeys & Keys.Shift) == Keys.Shift)//Shiftが押されている場合は拡大
            {
                try
                {
                    //マウスボタンが押された時の軸のマウスカーソルの位置の値、最小値、最大値を覚えておく
                    mouseMove.x0 = AxisX.PixelPositionToValue(chart1.PointToClient(mouseMove.mouse0).X);
                    mouseMove.y0 = AxisY.PixelPositionToValue(chart1.PointToClient(mouseMove.mouse0).Y);

                    //モード設定
                    mouseMove.Mode = mouseMove.MODE_NONE;
                    CursorX.IsUserSelectionEnabled = true;
                    CursorY.IsUserSelectionEnabled = true;

                    //作動中
                    mouseMove.isMouseDown = true;
                }
                catch (Exception)
                {
                    ;
                }
            }
            else//通常は移動
            {
                try
                {
                    //モード設定
                    mouseMove.Mode = mouseMove.MODE_MOVE;

                    //作動中
                    mouseMove.isMouseDown = true;
                }
                catch (Exception)
                {
                    ;
                }
            }
        }

        private void chart1_MouseUp(object sender, MouseEventArgs e)
        {
            Axis AxisX = chart1.ChartAreas[0].AxisX;
            Axis AxisY = chart1.ChartAreas[0].AxisY;
            System.Windows.Forms.DataVisualization.Charting.Cursor CursorX = chart1.ChartAreas[0].CursorX;
            System.Windows.Forms.DataVisualization.Charting.Cursor CursorY = chart1.ChartAreas[0].CursorY;

            //初期化
            mouseMove.isMouseDown = false;
            mouseMove.Mode = mouseMove.MODE_NONE;

            CursorX.IsUserSelectionEnabled = true;
            CursorY.IsUserSelectionEnabled = true;
            AxisX.ScaleView.Zoomable = true;
            AxisY.ScaleView.Zoomable = true;
            CursorX.SelectionStart = CursorX.SelectionEnd;
            CursorY.SelectionStart = CursorY.SelectionEnd;
        }

        private void chart1_MouseMove(object sender, MouseEventArgs e)
        {
            if (mouseMove.isMouseDown)
            {
                Axis AxisX = chart1.ChartAreas[0].AxisX;
                Axis AxisY = chart1.ChartAreas[0].AxisY;
                System.Windows.Forms.DataVisualization.Charting.Cursor CursorX = chart1.ChartAreas[0].CursorX;
                System.Windows.Forms.DataVisualization.Charting.Cursor CursorY = chart1.ChartAreas[0].CursorY;

                //現在のマウスカーソル位置
                Point mouse = System.Windows.Forms.Cursor.Position;

                if (mouseMove.Mode == mouseMove.MODE_MOVE)//移動
                {
                    if (Math.Abs(mouse.X - mouseMove.mouse0.X) >= 2)
                    {
                        //マウスカーソルの移動量に比率をかけて値の変化量を算出
                        double delta = -mouseMove.rateX * (mouse.X - mouseMove.mouse0.X);

                        //グラフビューの変更
                        AxisX.ScaleView.Position = mouseMove.xmin0 + delta;
                        AxisX.ScaleView.Size = mouseMove.xmax0 - mouseMove.xmin0;

                        //目盛間隔の自動調整
                        autoIntervalAdjust(AxisX);
                    }

                    if (Math.Abs(mouse.Y - mouseMove.mouse0.Y) >= 2)
                    {
                        //マウスカーソルの移動量に比率をかけて値の変化量を算出
                        double delta = -mouseMove.rateY * (mouse.Y - mouseMove.mouse0.Y);

                        //グラフビューの変更
                        AxisY.ScaleView.Position = mouseMove.ymin0 + delta;
                        AxisY.ScaleView.Size = mouseMove.ymax0 - mouseMove.ymin0;

                        //目盛間隔の自動調整
                        autoIntervalAdjust(AxisY);
                    }
                }
                else//拡大
                {
                    if (Math.Abs(mouse.X - mouseMove.mouse0.X) >= 10 || Math.Abs(mouse.Y - mouseMove.mouse0.Y) >= 10)
                    {
                        AxisX.ScaleView.Zoomable = AxisY.ScaleView.Zoomable = true;

                        if (Math.Abs(mouse.X - mouseMove.mouse0.X) > Math.Abs(mouse.Y - mouseMove.mouse0.Y))
                        {
                            if (mouseMove.Mode != mouseMove.MODE_CURSOR_X)
                            {
                                mouseMove.Mode = mouseMove.MODE_CURSOR_X;

                                //X軸の範囲選択のみ可とする
                                CursorX.IsUserSelectionEnabled = true;
                                CursorY.IsUserSelectionEnabled = false;
                                CursorY.SelectionStart = CursorY.SelectionEnd;

                                //選択範囲開始位置
                                CursorX.SelectionStart = mouseMove.x0;

                                //選択範囲終了位置
                                try
                                {
                                    CursorX.SelectionEnd = AxisX.PixelPositionToValue(chart1.PointToClient(mouse).X);
                                    if (CursorX.SelectionEnd < mouseMove.xmin0)
                                    {
                                        CursorX.SelectionEnd = mouseMove.xmin0;
                                    }
                                    if (CursorX.SelectionEnd > mouseMove.xmax0)
                                    {
                                        CursorX.SelectionEnd = mouseMove.xmax0;
                                    }
                                }
                                catch (Exception)
                                {
                                    ;
                                }
                            }
                        }
                        else
                        {
                            if (mouseMove.Mode != mouseMove.MODE_CURSOR_Y)
                            {
                                mouseMove.Mode = mouseMove.MODE_CURSOR_Y;

                                //Y軸の範囲選択のみ可とする
                                CursorY.IsUserSelectionEnabled = true;
                                CursorX.IsUserSelectionEnabled = false;
                                CursorX.SelectionStart = CursorX.SelectionEnd;

                                //選択範囲開始位置
                                CursorY.SelectionStart = mouseMove.y0;

                                //選択範囲終了位置
                                try
                                {
                                    CursorY.SelectionEnd = AxisY.PixelPositionToValue(chart1.PointToClient(mouse).Y);
                                    if (CursorY.SelectionEnd < mouseMove.ymin0)
                                    {
                                        CursorY.SelectionEnd = mouseMove.ymin0;
                                    }
                                    if (CursorY.SelectionEnd > mouseMove.ymax0)
                                    {
                                        CursorY.SelectionEnd = mouseMove.ymax0;
                                    }
                                }
                                catch (Exception)
                                {
                                    ;
                                }
                            }
                        }
                    }
                    else
                    {
                        AxisX.ScaleView.Zoomable = AxisY.ScaleView.Zoomable = false;
                    }
                }
            }
        }

        //ダブルクリックでズームリセット
        private void chart1_MouseDoubleClick(object sender, MouseEventArgs e)
        {
            if (IsMouseInChartArea())
            {
                Axis AxisX = chart1.ChartAreas[0].AxisX;
                Axis AxisY = chart1.ChartAreas[0].AxisY;

                //ズームのリセット
                AxisX.Minimum = AxisY.Minimum = AxisX.Maximum = AxisY.Maximum = double.NaN;
                AxisX.ScaleView.Zoom(AxisX.Minimum, AxisX.Maximum);
                AxisY.ScaleView.Zoom(AxisY.Minimum, AxisY.Maximum);
                chart1.ChartAreas[0].RecalculateAxesScale();

                //目盛間隔の再調整
                autoIntervalAdjust(AxisX);
                autoIntervalAdjust(AxisY);

                //比率再計算
                calcRateXY();
            }
        }
    }
}
```
# 番外編

## マウスカーソル位置の値を表示

![chart_mouse_value.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3193449/44003c93-0b66-54ba-6f57-4a191e789f9e.gif)  

マウスカーソルの位置に最も近いデータ点の値をフォームのタイトルに表示します。  

```cs
using System;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Windows.Forms.DataVisualization.Charting;//グラフ用

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

            //初期化
            chart1.Series.Clear();
            chart1.Dock = DockStyle.Fill;

            //データ系列を作成
            Series series = new Series
            {
                ChartType = SeriesChartType.Line,
                MarkerStyle = MarkerStyle.Circle,
                MarkerSize = 8
            };

            //データ系列にプロットデータを追加
            for (int i = 1; i <= 100; i++)
            {
                series.Points.AddXY(i, Math.Sin((double)i / 36 * 2 * Math.PI));
            }

            //系列をグラフに追加
            this.chart1.Series.Add(series);

            //マウスカーソル移動時、フォームのタイトルに値を表示
            this.chart1.MouseMove += (sender, args) => chart1_MouseMove(sender, args);
            this.chart1.Paint += (sender, e) => chart1_Paint(sender, e);
        }

        //起動時の処理
        bool atBoot = true;
        private void chart1_Paint(object sender, PaintEventArgs e)
        {
            atBoot = false;
        }

        //マウスカーソル移動時の処理
        bool searching = false;
        private void chart1_MouseMove(object sender, MouseEventArgs e)
        {
            if (atBoot) return;

            if (!searching)
            {
                //処理開始
                searching = true;

                Task.Run(() =>
                {
                    this.Invoke(new Action(() =>
                    {
                        //マウスクリックした箇所のX値を取得
                        var x = this.chart1.ChartAreas[0].AxisX.PixelPositionToValue(e.X);

                        //カーソル位置設定
                        chart1.ChartAreas[0].CursorX.Position = x;

                        //インデックス取得
                        Series series = this.chart1.Series[0];
                        int i = indexFromXValue(x, series);

                        //タイトル変更
                        this.Text = "index=" + i.ToString()
                            + ", x=" + series.Points[i].XValue.ToString()
                            + ", y=" + series.Points[i].YValues[0].ToString();
                    }));

                    //処理終了
                    searching = false;
                });
            }
        }

        //X値からインデックスを取得
        //mode
        // -1:x以下の最も近いIndex
        //  0:xに最も近いIndex
        //  1:x以上の最も近いIndex
        private int indexFromXValue(double x, Series series, int mode = 0)
        {
            if (series != null && series.Points.Count != 0)
            {
                int minIndex = 0;
                int maxIndex = series.Points.Count - 1;

                if (x <= series.Points[minIndex].XValue)
                {
                    if (x == series.Points[minIndex].XValue || mode != -1)
                    {
                        return minIndex;
                    }
                    else
                    {
                        return minIndex - 1;
                    }
                }
                else if (series.Points[maxIndex].XValue <= x)
                {
                    if (x == series.Points[maxIndex].XValue || mode != 1)
                    {
                        return maxIndex;
                    }
                    else
                    {
                        return maxIndex + 1;
                    }
                }
                else
                {
                    int i_min = minIndex;
                    int i_max = maxIndex;

                    for (; i_max - i_min >= 2;)
                    {
                        int i_middle = (i_min + i_max) / 2;

                        if (x < series.Points[i_middle].XValue)
                        {
                            i_max = i_middle;
                        }
                        else
                        {
                            i_min = i_middle;
                        }
                    }

                    if (x == series.Points[i_min].XValue)
                    {
                        return i_min;
                    }
                    else if (x == series.Points[i_max].XValue)
                    {
                        return i_max;
                    }
                    else
                    {
                        if ((mode == 0 && Math.Abs(series.Points[i_min].XValue - x) <= Math.Abs(series.Points[i_max].XValue - x)) || mode == -1)
                        {
                            return i_min;
                        }
                        else
                        {
                            return i_max;
                        }
                    }
                }
            }

            return -2;
        }
    }
}
```

## 表示されているインデックス範囲を表示

![chart_mouse_indexrange.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3193449/17592ef9-8bf3-481d-018d-a11fdd673abb.gif)  

データ系列のデータ点のうち現在表示されているインデックス範囲をフォームのタイトルに表示します。`indexFromXValue`関数は上記と同じため記載を省略しています。  
```cs
using System;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Windows.Forms.DataVisualization.Charting;//グラフ用

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

            //初期化
            chart1.Series.Clear();
            chart1.Dock = DockStyle.Fill;

            //データ系列を作成
            Series series = new Series
            {
                ChartType = SeriesChartType.Line,
                MarkerStyle = MarkerStyle.Circle,
                MarkerSize = 8
            };

            //データ系列にプロットデータを追加
            for (int i = 1; i <= 100; i++)
            {
                series.Points.AddXY(i, Math.Sin((double)i / 36 * 2 * Math.PI));
            }

            //系列をグラフに追加
            this.chart1.Series.Add(series);

            //マウスカーソル移動時、フォームのタイトルに値を表示
            this.chart1.MouseMove += (sender, args) => chart1_MouseMove(sender, args);
            this.chart1.Paint += (sender, e) => chart1_Paint(sender, e);

            //X軸範囲設定、拡大表示
            Axis axis = this.chart1.ChartAreas[0].AxisX;
            axis.Minimum = -300;
            axis.Maximum = 300;
            axis.ScaleView.Zoom(-10, 40);
        }

        //起動時の処理
        bool atBoot = true;
        private void chart1_Paint(object sender, PaintEventArgs e)
        {
            atBoot = false;
        }

        //マウスカーソル移動時の処理
        bool searching = false;
        private void chart1_MouseMove(object sender, MouseEventArgs e)
        {
            if (atBoot) return;

            if (!searching)
            {
                //処理開始
                searching = true;

                Task.Run(() =>
                {
                    this.Invoke(new Action(() =>
                    {
                        //現在の表示範囲を取得
                        Axis axis = this.chart1.ChartAreas[0].AxisX;
                        var xmin = axis.ScaleView.ViewMinimum;
                        var xmax = axis.ScaleView.ViewMaximum;

                        //インデックス取得
                        Series series = this.chart1.Series[0];
                        int i_min = indexFromXValue(xmin, series, 1);
                        int i_max = indexFromXValue(xmax, series, -1);

                        //タイトル変更
                        if (series.Points.Count - 1 < i_min || i_max < 0 || i_min <= -1)
                        {
                            this.Text = "範囲外";
                        }
                        else
                        {
                            this.Text = "index : " + i_min.ToString() + " to " + i_max.ToString();
                        }
                    }));

                    //処理終了
                    searching = false;
                });
            }
        }

        //X値からインデックスを取得
        //mode
        // -1:x以下の最も近いIndex
        //  0:xに最も近いIndex
        //  1:x以上の最も近いIndex
        private int indexFromXValue(double x, Series series, int mode = 0)
        {
            //上記に同じ
        }
    }
}
```

## 複数の系列の値とデータ範囲を表示

![chart_mouse_value_multi.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3193449/d5e3157e-4e93-bb7f-3f64-7e9effc8f70b.gif)  

複数の系列がある場合に、上記同様、マウスカーソル位置の値と表示されているインデックス範囲を表示します。複数あるためフォームのタイトルではなくラベルで表示しています。`indexFromXValue`関数は上記と同じため記載を省略しています。  

```cs
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Windows.Forms.DataVisualization.Charting;//グラフ用

namespace WindowsFormsApp1
{
    public partial class Form1 : Form
    {
        //表示用のラベル
        List<Label> labels = new List<Label>();

        public Form1()
        {
            InitializeComponent();

            //初期化
            chart1.Series.Clear();
            chart1.Legends.Clear();
            chart1.Dock = DockStyle.Left;

            //データ系列を作成
            Series series1 = new Series
            {
                ChartType = SeriesChartType.Line,
                MarkerStyle = MarkerStyle.Circle,
                MarkerSize = 8,
                Color = Color.Red,
            };
            Series series2 = new Series
            {
                ChartType = SeriesChartType.Line,
                MarkerStyle = MarkerStyle.Circle,
                MarkerSize = 8,
                Color = Color.Blue,
            };

            //データ系列にプロットデータを追加
            var rand = new Random();
            for (int i = 1; i <= 100; i += rand.Next(1, 6))
            {
                series1.Points.AddXY(i, rand.Next(-10, 10));
            }
            for (int i = 1; i <= 100; i += rand.Next(1, 6))
            {
                series2.Points.AddXY(i, rand.Next(-10, 10));
            }

            //系列をグラフに追加
            this.chart1.Series.Add(series1);
            this.chart1.Series.Add(series2);

            //マウスカーソル移動時、フォームのタイトルに値を表示
            this.chart1.MouseMove += (sender, args) => chart1_MouseMove(sender, args);
            this.chart1.Paint += (sender, e) => chart1_Paint(sender, e);

            //X軸範囲設定、拡大表示
            Axis axis = this.chart1.ChartAreas[0].AxisX;
            axis.Minimum = -300;
            axis.Maximum = 300;
            axis.ScaleView.Zoom(-50, 50);

            //ラベル追加
            setLabels();
        }

        //グラフの右側にラベルを表示
        private void setLabels()
        {
            //ラベル削除
            foreach (var label in labels)
            {
                this.Controls.Remove(label);
            }
            labels = new List<Label>();

            //ラベル追加
            int maxHeight = 0;
            int maxWidth = 0;
            foreach (Series series in this.chart1.Series)
            {
                //ラベル作成
                Label label = new Label
                {
                    AutoSize = true,
                    Visible = false,
                    Text = series.Name + ": index=000, x=000, y=000(000 to 000)",
                    ForeColor = series.Color,
                };

                //ラベル追加
                labels.Add(label);
                this.Controls.Add(label);

                //サイズ確認
                if (maxHeight < label.Height)
                {
                    maxHeight = label.Height;
                }
                if (maxWidth < label.Width)
                {
                    maxWidth = label.Width;
                }

                //ラベル表示
                label.Text = series.Name;
                label.Visible = true;
            }

            //ラベル位置調整
            int index = 0;
            chart1.Width = this.ClientSize.Width - maxWidth;
            foreach (var label in labels)
            {
                label.AutoSize = false;
                label.Left = chart1.Width;
                label.Top = index * (maxHeight + 1);
                label.Width = maxWidth;
                label.Height = maxHeight;
                index++;
            }
        }

        //起動時の処理
        bool atBoot = true;
        private void chart1_Paint(object sender, PaintEventArgs e)
        {
            atBoot = false;
        }

        //マウスカーソル移動時の処理
        bool searching = false;
        private void chart1_MouseMove(object sender, MouseEventArgs e)
        {
            if (atBoot) return;

            if (!searching)
            {
                //処理開始
                searching = true;

                Task.Run(() =>
                {
                    this.Invoke(new Action(() =>
                    {
                        //マウスクリックした箇所のX値を取得
                        Axis axis = this.chart1.ChartAreas[0].AxisX;

                        var x = axis.PixelPositionToValue(e.X);
                        var xmin = axis.ScaleView.ViewMinimum;
                        var xmax = axis.ScaleView.ViewMaximum;

                        //カーソル位置設定
                        chart1.ChartAreas[0].CursorX.Position = x;

                        for (int idx = 0; idx < this.chart1.Series.Count; idx++)
                        {
                            Series series = this.chart1.Series[idx];

                            //インデックス取得
                            int i = indexFromXValue(x, series);
                            int i_min = indexFromXValue(xmin, series, 1);
                            int i_max = indexFromXValue(xmax, series, -1);

                            //ラベル作成
                            StringBuilder sb = new StringBuilder();
                            sb.Append(series.Name);
                            sb.Append(": index=");
                            sb.Append(i.ToString());

                            if (series.Points.Count - 1 < i_min || i_max < 0 || i_min <= -1)
                            {
                                sb.Append("(範囲外)");
                            }
                            else
                            {
                                sb.Append("(");
                                sb.Append(i_min.ToString());
                                sb.Append(" to ");
                                sb.Append(i_max.ToString());
                                sb.Append(")");
                            }
                            sb.Append(", x=");
                            sb.Append(series.Points[i].XValue.ToString());
                            sb.Append(", y=");
                            sb.Append(series.Points[i].YValues[0].ToString());

                            labels[idx].Text = sb.ToString();
                        }
                    }));

                    //処理終了
                    searching = false;
                });
            }
        }

        //mode
        //-1:x以下の最も近いIndex
        //0 :xに最も近いIndex
        //1 :x以上の最も近いIndex
        private int indexFromXValue(double x, Series series, int mode = 0)
        {
            //上記に同じ
        }
    }
}
```