iT邦幫忙

0

[C#+ECharts] 使用 ECharts 建立股票 K 線圖 (附範例)

Mars 2021-08-13 14:54:062426 瀏覽
  • 分享至 

  • xImage
  •  

今天展示一下如何在 Asp.Net MVC 上顯示 ECharts 的 K 線圖套件,這個 K 線圖在ECharts 上稱為 Candlestick (蠟燭圖),主要的用途就在展示股價的開盤、最高、最低、收盤價及成交量的訊號。

在官方上可以看到各種的樣式
https://ithelp.ithome.com.tw/upload/images/20210813/20140626sgyiwvibXj.jpg

連結網址: https://echarts.apache.org/examples/zh/index.html#chart-type-candlestick

我今天展示的是基本的樣式,上方圖示為開盤、最高、最低、收盤,下方圖示為成交量的 Bar 圖,後端資料簡單用 JSON 模擬一組真實的 K線價格。

先來看看成果展示

https://blog.hungwin.com.tw/wp-content/uploads/2021/04/echarts_Candlestick2.jpg

這是由 ASP.NET MVC 預設的頁面改寫而來,最後會附上完整的程式碼可供下載參考。

引用 Js 來源

首先來看看前端程式碼,先引用需要的 js 檔案

<script src="https://cdn.jsdelivr.net/npm/vue@2.6.12/dist/vue.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery.blockUI/2.70/jquery.blockUI.js"></script>
@Scripts.Render("~/Scripts/Teach/vueMixin.js")
@Scripts.Render("~/Scripts/Teach/baseAjax.js")
@Scripts.Render("~/Scripts/Teach/Page.js")
@Scripts.Render("~/Scripts/ECharts/echarts.js")

前端 Javascript 語法

再來看前端的 javascript 及頁面

<main id="Page">
    <br />
    <div id="ChartKlineDiv" style="height:450px;"></div>
</main>
<script>
    var Page = new Vue({
        el: '#Page'
        , mixins: [vueMixin]
        , data: function () {
            var data = {
                actions: {}, form: {}
            };
            data.Chart = {
                ChartKlineObj: null
                , ChartKlineOption: {}
            };
            return data;
        }
        , created: function () {
            var self = this;
        }
        , mounted: function () {
            var self = this;
            self.drawKlineChart();
            self.GetKLineData();
        }
        , methods: {
            GetToken: function () {
                var token = '@Html.AntiForgeryToken()';
                token = $(token).val();
                return token;
            }
            // 繪製k線圖表
            , drawKlineChart: function () {
                var self = this;

                var upColor = '#ec0000';
                var upBorderColor = '#8A0000';
                var downColor = '#00da3c';
                var downBorderColor = '#008F28';

                var data0 = self.splitData([]);

                self.Chart.ChartKlineOption = {
                    title: {
                        text: '',
                        subtext: '',
                        left: 0
                    },
                    animation: false,
                    axisPointer: {
                        link: { xAxisIndex: 'all' },
                        label: {
                            backgroundColor: '#777'
                        }
                    },
                    toolbox: {
                        feature: {
                            restore: {},
                            saveAsImage: {}
                        }
                    },
                    tooltip: {
                        trigger: 'axis',
                        confine: true,
                        axisPointer: {
                            type: 'cross'
                        },
                        backgroundColor: 'rgba(245, 245, 245, 0.8)',
                        borderWidth: 1,
                        borderColor: '#ccc',
                        padding: 10,
                        textStyle: {
                            color: '#000'
                        },
                        position: function (pos, params, el, elRect, size) {
                            var obj = { top: 10 };
                            obj[['left', 'right'][+(pos[0] < size.viewSize[0] / 2)]] = 30;
                            return obj;
                        },
                        formatter: function (param) {
                            if (param[0].seriesType == "candlestick") {
                                return [
                                    '日期' + ': ' + param[0].name + '<hr size=1 style="margin: 3px 0">',
                                    '開盤價' + ': ' + self.CommaFormat(param[0].data[1]) + ' <br/> ',
                                    '最高價' + ': ' + self.CommaFormat(param[0].data[4]) + '<br/>',
                                    '最低價' + ': ' + self.CommaFormat(param[0].data[3]) + '<br/>',
                                    '收盤價' + ': ' + self.CommaFormat(param[0].data[2]) + '<br/>',
                                    '成交量' + ': ' + self.CommaFormat(param[1].data) + '<br/>'
                                ].join('');
                            } else if (param[0].seriesType == "bar") {
                                return [
                                    '日期' + ': ' + param[1].name + '<hr size=1 style="margin: 3px 0">',
                                    '開盤價' + ': ' + self.CommaFormat(param[1].data[1]) + '<br/>',
                                    '最高價' + ': ' + self.CommaFormat(param[1].data[4]) + '<br/>',
                                    '最低價' + ': ' + self.CommaFormat(param[1].data[3]) + '<br/>',
                                    '收盤價' + ': ' + self.CommaFormat(param[1].data[2]) + '<br/>',
                                    '成交量' + ': ' + self.CommaFormat(param[0].data) + '<br/>'
                                ].join('');
                            }
                        }
                    },
                    grid: [
                        {
                            left: '10%',
                            right: '8%',
                            height: '50%'
                        },
                        {
                            left: '10%',
                            right: '8%',
                            bottom: '20%',
                            height: '15%'
                        }
                    ],
                    xAxis: [
                        {
                            type: 'category',
                            data: data0.categoryData,
                            scale: true,
                            boundaryGap: false,
                            axisLine: { onZero: false },
                            splitLine: { show: false },
                            splitNumber: 20,
                            min: 'dataMin',
                            max: 'dataMax',
                            axisPointer: {
                                z: 100
                            }
                        },
                        {
                            type: 'category',
                            gridIndex: 1,
                            data: data0.categoryData,
                            scale: true,
                            boundaryGap: false,
                            axisLine: { onZero: false },
                            axisTick: { show: false },
                            splitLine: { show: false },
                            axisLabel: { show: false },
                            splitNumber: 20,
                            min: 'dataMin',
                            max: 'dataMax'
                        }
                    ],
                    yAxis: [
                        {
                            scale: true,
                            splitArea: {
                                show: true
                            }
                        },
                        {
                            scale: true,
                            gridIndex: 1,
                            splitNumber: 2,
                            axisLabel: { show: false },
                            axisLine: { show: false },
                            axisTick: { show: false },
                            splitLine: { show: false }
                        }
                    ],
                    dataZoom: [
                        {
                            type: 'inside',
                            xAxisIndex: [0, 1],
                            start: 0,
                            end: 100
                        },
                        {
                            show: true,
                            xAxisIndex: [0, 1],
                            type: 'slider',
                            y: '90%',
                            start: 0,
                            end: 100
                        }
                    ],
                    series: [
                        {
                            name: '日K',
                            type: 'candlestick',
                            data: data0.values,
                            itemStyle: {
                                normal: {
                                    color: upColor,
                                    color0: downColor,
                                    borderColor: upBorderColor,
                                    borderColor0: downBorderColor
                                }
                            },
                            markPoint: {
                                label: {
                                    normal: {
                                        formatter: function (param) {
                                            return param != null ? param.name + '\n' + param.value : '';
                                        }
                                    }
                                }
                                , data: []
                                , symbolOffset: [0, -20]//位置偏移
                            }
                        }
                        , {
                            name: 'Volumn',
                            type: 'bar',
                            xAxisIndex: 1,
                            yAxisIndex: 1,
                            data: data0.volume,
                            itemStyle: {
                                normal: {
                                    color: '#7fbe9e'
                                },
                                emphasis: {
                                    color: '#140'
                                }
                            }
                        }
                    ]
                };
                if (self.Chart.ChartKlineObj == null) {
                    self.Chart.ChartKlineObj = echarts.init(document.getElementById('ChartKlineDiv'));
                    // 使用刚指定的配置项和数据显示图表。
                    self.Chart.ChartKlineObj.setOption(self.Chart.ChartKlineOption);
                }
            }
            // 分割k線資料
            , splitData: function (rawData) {
                var categoryData = [];
                var values = [];
                var volume = [];
                for (var i = 0; i < rawData.length; i++) {
                    categoryData.push(rawData[i][0]);
                    values.push(rawData[i].slice(1, 5));
                    volume.push(rawData[i][5]);
                }
                return { categoryData: categoryData, values: values, volume: volume };
            }
            // 取得K線資料
            , GetKLineData: function () {
                var self = this;
                var postData = self._GetPostData(self.form, "");
                $.DoAjax({
                    url: '@Url.Content("~/Home/GetKLineData")',
                    data: { inModel: postData, __RequestVerificationToken: self.GetToken() },
                    success: function (datas) {
                        // 將後端資料綁定到圖表上
                        var value = datas.KlineData;
                        var data0 = self.splitData(value);
                        self.Chart.ChartKlineOption.title.text = datas.KlineName;
                        self.Chart.ChartKlineOption.xAxis[0].data = data0.categoryData;
                        self.Chart.ChartKlineOption.xAxis[1].data = data0.categoryData;
                        self.Chart.ChartKlineOption.series[0].data = data0.values;
                        self.Chart.ChartKlineOption.series[1].data = data0.volume;
                        self.Chart.ChartKlineObj.setOption(self.Chart.ChartKlineOption);
                        self.Chart.ChartKlineObj.resize();
                    }
                });
            }
             // 千分位
            , CommaFormat: function (value) {
                if (value === null) {
                    return '';
                } else if (isset(value) === false) {
                    return '';
                } else {
                    return value.toString().replace(/^(-?\d+?)((?:\d{3})+)(?=\.\d+$|$)/, function (all, pre, groupOf3Digital) {
                        return pre + groupOf3Digital.replace(/\d{3}/g, ',$&');
                    });
                }
            }
        }
    })
</script>

我使用 Vue.Js 為前端框架,先呼叫 drawKlineChart 建立起 ECharts 的圖層後,就可以再呼叫 GetKLineData 向後端取得K線資料,取得資料後再綁定在圖表上。

後端 C# 語法

再來看一下後端的程式碼

public ActionResult GetKLineData(Hashtable inModel)
{
	Hashtable outModel = new Hashtable();

	// 模擬資料
	string klineDataJson = "[[\"2021/02/03\",78.5,78.2,77.7,78.7,13776350.0],[\"2021/02/04\",78.1,78.9,77.8,79.5,14189736.0],[\"2021/02/05\",79.2,78.4,78.3,79.2,12993221.0],[\"2021/02/17\",77.0,75.2,75.0,77.1,45710651.0],[\"2021/02/18\",75.1,75.4,74.5,76.3,23783853.0],[\"2021/02/19\",75.1,76.1,74.8,76.3,17266074.0],[\"2021/02/22\",76.1,75.8,75.6,76.5,17977579.0],[\"2021/02/23\",75.5,75.1,74.7,75.5,16072377.0],[\"2021/02/24\",75.3,76.0,75.1,76.9,19395382.0],[\"2021/02/25\",77.0,75.9,75.6,77.2,20094518.0],[\"2021/02/26\",75.4,74.6,74.5,75.4,30799534.0],[\"2021/03/02\",75.5,73.9,73.9,75.5,18482582.0],[\"2021/03/03\",74.5,75.5,73.9,75.5,14282155.0],[\"2021/03/04\",75.0,74.3,74.1,75.2,13034873.0],[\"2021/03/05\",73.8,73.5,73.3,74.2,16264538.0],[\"2021/03/08\",74.0,73.2,73.2,74.7,14686433.0],[\"2021/03/09\",73.1,72.8,72.6,73.6,12652479.0],[\"2021/03/10\",73.7,73.9,73.5,74.8,9461920.0],[\"2021/03/11\",71.8,72.0,70.5,72.0,39798139.0],[\"2021/03/12\",72.0,71.4,70.7,72.0,23957562.0],[\"2021/03/15\",71.2,71.5,70.8,71.9,13421933.0],[\"2021/03/16\",71.7,71.5,71.5,72.2,14844029.0],[\"2021/03/17\",72.0,72.3,71.8,73.0,15558930.0],[\"2021/03/18\",73.0,74.3,72.9,74.8,28006359.0],[\"2021/03/19\",74.0,75.0,73.6,75.0,22729106.0],[\"2021/03/22\",75.0,74.0,73.6,75.1,16066908.0],[\"2021/03/23\",74.1,74.0,73.2,74.5,16528621.0],[\"2021/03/24\",73.9,74.2,73.6,74.9,12939688.0],[\"2021/03/25\",74.3,75.7,74.1,75.8,21462205.0],[\"2021/03/26\",74.9,73.8,73.3,74.9,47540850.0],[\"2021/03/29\",73.9,73.6,73.1,74.2,20731674.0],[\"2021/03/30\",73.8,73.7,73.3,73.9,14493679.0],[\"2021/03/31\",73.9,74.1,73.7,74.3,12607518.0],[\"2021/04/01\",74.5,73.5,73.4,74.5,13527595.0],[\"2021/04/06\",73.8,73.0,73.0,74.0,19438539.0],[\"2021/04/07\",73.2,72.8,72.5,73.4,15430708.0],[\"2021/04/08\",73.2,73.6,73.0,74.0,16362556.0],[\"2021/04/09\",73.8,73.3,73.2,74.0,12827226.0],[\"2021/04/12\",73.3,72.7,72.6,73.4,16056617.0],[\"2021/04/13\",72.5,72.5,72.5,73.4,12679527.0]]";

	// K線圖資料
	List<List<object>> data = JsonConvert.DeserializeObject<List<List<object>>>(klineDataJson);

	outModel["KlineData"] = data;
	outModel["KlineName"] = "股票名稱";

	ContentResult result = new ContentResult();
	result.ContentType = "application/json";
	string json = JsonConvert.SerializeObject(outModel);
	result.Content = json;

	return result;
}

後端部份 K 線資料就由 JSON 來模擬實際取得的來源,再把 JSON 轉換資料陣列回傳至前端。

前端的 GetKLineData 方法可以取得後端的資料,再經由 splitData() 方法拆解資料內容後放至 ECharts 裡面。

範例下載

付費後可下載此篇文章教學程式碼

重點整理

  1. ECharts 提供非常多免費的圖表套件
  2. 使用 Candlestick 繪製股票 K 線圖
  3. 需要先準備好 K 線資料
  4. 套用官方範例語法

相關學習文章

[C#] 取得證交所台股價格的 3 種實用方法(附範例下載)


圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言