MS Chart 介紹與應用(三) - 樣板封裝

本文將分享微軟提供的圖表解決方案【Microsoft Chart Controls】 開發時的經驗。

前言

圖表工具的設定,由於靈活度非常高的關係,可供設定的東西非常的多,若每一個功能都是從頭設定一次,往往不知覺的程式碼就會變成得很長。不僅開發中後期需要檢查程式碼時會很痛苦,往後維護的工程師需要進行功能調整擴充或是優化時,也會很痛苦。因此對於圖表的設定,從實務面來說,會多包一層物件,當作是簡單樣板使用。樣板的建立不僅系統的各個圖表風格可以容易統一,主要流程的程式碼也會相對乾淨,往後須調整樣板時,也不需要各頁面檢查。至於各個樣板的複雜度,就取決你的開發風格了。

 

第一個樣板

首先先開一個空的類別,繼承自Chart,以及開一個建構值。

using System.Windows.Forms.DataVisualization.Charting;
namespace WinForm.Core
{
    //簡單樣板
    public partial class SampleChart : Chart
    {
        public SampleChart() : base()
        {
        }
        
    }
}

編輯專案,成功後應該可以在工具箱看到它,此時就可以使用了。

接著,因為覺得設定資料那塊有點複雜且麻煩,所以就開個設定功能,簡化設定。

程式碼稍微調整一下

namespace WinForm.Core
{
    //簡單樣板
    public partial class SampleChart : Chart
    {
        public SampleChart() : base()
        {
        }
        public SampleChart Set(Dictionary<string, double> InputData)
        {
            var lisValueX = InputData.Keys.ToList();
            var lisValueY = InputData.Values.ToList();
            var series1 = Series.First();
            series1.Points.DataBindXY(lisValueX, lisValueY);
            return this;
        }
    }
}

使用的部分也跟著調整


namespace WinForm
{
    public partial class DemoChart : Form
    {
        public DemoChart()
        {
            InitializeComponent();
            CreateChart();
        }
        void CreateChart()
        {
            var dicDataSource = new Dictionary<string, double>() {
                { "一月" , 25},
                { "二月" , 18},
                { "三月" , 30},
                { "四月" , 24},
                { "五月" , 35},
                { "六月" , 50},
                { "七月" , 40},
            };
            sampleChart1.Set(dicDataSource);
        }
    }
}

實際跑起來就會長得像是這樣

此時突然想到,圖表不是通常會有多組的資料進行比對嗎?

因此就稍微調整一下設定的樣板與使用的程式


namespace WinForm.Core
{
    //簡單樣板
    public partial class SampleChart : Chart
    {
        public SampleChart() : base()
        {
        }
        public SampleChart Set(List<Dictionary<string, double>> InputData)
        {
            Series.Clear();
            foreach (var DataSource in InputData)
            {
                var series = Series.Add(InputData.IndexOf(DataSource).ToString());
                var lisValueX = DataSource.Keys.ToList();
                var lisValueY = DataSource.Values.ToList();
                series.Points.DataBindXY(lisValueX, lisValueY);
                series.ChartArea = ChartAreas.First().Name;
            }
            return this;
        }
    }
}

namespace WinForm
{
    public partial class DemoChart : Form
    {
        public DemoChart()
        {
            InitializeComponent();
            CreateChart();
        }
        void CreateChart()
        {
            var dicDataSource = new Dictionary<string, double>() {
                { "一月" , 25},
                { "二月" , 18},
                { "三月" , 30},
                { "四月" , 24},
                { "五月" , 35},
                { "六月" , 50},
                { "七月" , 40},
            };
            var dicDataSource2 = new Dictionary<string, double>() {
                { "一月" , 50},
                { "二月" , 20},
                { "三月" , 40},
                { "四月" , 80},
                { "五月" , 40},
                { "六月" , 20},
                { "七月" , 40},
            };
            sampleChart1.Set(new List<Dictionary<string, double>> { dicDataSource, dicDataSource2 });
        }
    }
}

實際跑起來的結果長得像這樣,開始感覺有點樣子了?

接著感覺右邊的標籤應該也要給予比較容易識別的資訊,因此再稍微調整一下程式碼,加上Legend的設定。


namespace WinForm.Core
{
    //簡單樣板
    public partial class SampleChart : Chart
    {
        public SampleChart() : base()
        {
        }
        public SampleChart Set(List<SerieSimple> InputData)
        {
            Series.Clear();
            foreach (var seriesData in InputData)
            {
                var series = Series.Add(InputData.IndexOf(seriesData).ToString());
                var lisValueX = seriesData.DataSource.Keys.ToList();
                var lisValueY = seriesData.DataSource.Values.ToList();
                series.Points.DataBindXY(lisValueX, lisValueY);
                series.ChartArea = ChartAreas.First().Name;
                series.Name = seriesData.LegendName;
            }
            return this;
        }
        public class SerieSimple
        {
            //因為東西開始複雜起來了,因此就包成一個傳輸物件,方便開發操作
            public Dictionary<string, double> DataSource { get; set; }
            public string LegendName { get; set; }
        }
    }
}

namespace WinForm
{
    public partial class DemoChart : Form
    {
        public DemoChart()
        {
            InitializeComponent();
            CreateChart();
        }
        void CreateChart()
        {
            var dicDataSource = new Dictionary<string, double>() {
                { "一月" , 25},
                { "二月" , 18},
                { "三月" , 30},
                { "四月" , 24},
                { "五月" , 35},
                { "六月" , 50},
                { "七月" , 40},
            };
            var dicDataSource2 = new Dictionary<string, double>() {
                { "一月" , 50},
                { "二月" , 20},
                { "三月" , 40},
                { "四月" , 80},
                { "五月" , 40},
                { "六月" , 20},
                { "七月" , 40},
            };
            sampleChart1.Set(new List<SampleChart.SerieSimple> {
                new SampleChart.SerieSimple{ LegendName = "dicDataSource" , DataSource = dicDataSource },
                new SampleChart.SerieSimple{ LegendName = "dicDataSource2" , DataSource = dicDataSource2 }
            });
        }
    }
}

實際跑起來結果如下,越來越有感覺了是吧?

剩下的就是針對顯示區塊的寬度等等BLABLABLA的設定,因此程式碼便再度調整一下。


namespace WinForm.Core
{
    //簡單樣板
    public partial class SampleChart : Chart
    {
        public SampleChart() : base()
        {
        }
        public SampleChart Set(SetDataInput InputData)
        {
            Series.Clear();
            var ChartArea = ChartAreas.First();
            foreach (var seriesData in InputData.DataSource)
            {
                var series = Series.Add(seriesData.LegendName);
                var lisValueX = seriesData.DataSource.Keys.ToList();
                var lisValueY = seriesData.DataSource.Values.ToList();
                series.Points.DataBindXY(lisValueX, lisValueY);
                series.ChartArea = ChartArea.Name;
                series.Name = seriesData.LegendName;
                series.ChartType = seriesData.ChartType;
                if (seriesData.Color.HasValue)
                {
                    series.Color = seriesData.Color.Value;
                }
            }
            ChartArea.BorderColor = InputData.BorderColor;
            ChartArea.BorderWidth = InputData.BorderWidth;
            Dock = InputData.DefaultDock;
            return this;
        }
        public class SerieSimple
        {
            //序列資料設定
            public Dictionary<string, double> DataSource { get; set; }
            public string LegendName { get; set; }
            public SeriesChartType ChartType { get; set; } = SeriesChartType.Column;
            public Color? Color { get; set; } = null;
        }

        public class SetDataInput
        {
            //主要圖表設定
            public List<SerieSimple> DataSource { get; set; }
            public Color BackColor { get; set; } = Color.White;
            public int BorderWidth { get; set; } = 0;
            public Color BorderColor { get; set; } = Color.Black;
            public DockStyle DefaultDock { get; set; } = DockStyle.Fill;
        }
    }
}

namespace WinForm
{
    public partial class DemoChart : Form
    {
        public DemoChart()
        {
            InitializeComponent();
            CreateChart();
        }
        void CreateChart()
        {
            var dicDataSource = new Dictionary<string, double>() {
                { "一月" , 25},
                { "二月" , 18},
                { "三月" , 30},
                { "四月" , 24},
                { "五月" , 35},
                { "六月" , 50},
                { "七月" , 40},
            };
            var dicDataSource2 = new Dictionary<string, double>() {
                { "一月" , 50},
                { "二月" , 20},
                { "三月" , 40},
                { "四月" , 80},
                { "五月" , 40},
                { "六月" , 20},
                { "七月" , 40},
            };
            sampleChart1.Set(new SampleChart.SetDataInput
            {
                DataSource = new List<SampleChart.SerieSimple>{
                    new SampleChart.SerieSimple{ LegendName = "dicDataSource" , DataSource = dicDataSource ,Color = Color.Pink},
                    new SampleChart.SerieSimple{ LegendName = "dicDataSource2" , DataSource = dicDataSource2 ,ChartType = SeriesChartType.Line} }
                ,
                BorderWidth = 1
            });
        }
    }
}

執行結果如下

基本上到設定ChartArea的層級就差不多了,大多數圖表基本上都只有一個顯示區域,若真的有需求再弄出一個樣板來設定即可。而使用自訂樣板設計開發的話,往後在資料繫結與設定時,便不用撰寫設定冗長的元件設定,只要套用規劃好的設定處理便可輕易完成圖表。

(而過後幾年的維護人員需要進行統一的預設樣式調整時也不用到處翻程式碼了,可喜可賀)

 

後語

1.其實實務開發樣板的設定檔時,是需要寫好寫滿Summary的....這樣後續人員才知道如何設定。

2.由於前端的圖表樣板網路上可以抓到,因此Web Form的部分原則上就不建議再使用了。(除非你需要開發一個Web Form的專案,恩.....)

3.同一專案中的圖表的樣式原則上是不會有太多變化,這是考量到呈現的風格與維護難易度所致,因此真正開發圖表前可以先跟使用者確認這些細節,幫助樣板的訂立。

4.由於示範所故,因使設定的類別與自訂樣板放在一起。但實務上是要分開的,請保持一個類別一個檔案的簡單原則。

5.這邊文章只是大概說明一下樣板的開發流程,因此省略了一些邏輯細節,實際上在使用的樣板開發時需要的細節會更多,請注意請小心。

6.開發時發生元件找不到的狀況,可以善用Visual Studio的功能【快速重建與重購】幫助引用必要元件。