今天展示一下如何在 Asp.Net MVC 上顯示 ECharts 的 K 線圖套件,這個 K 線圖在ECharts 上稱為 Candlestick (蠟燭圖),主要的用途就在展示股價的開盤、最高、最低、收盤價及成交量的訊號。
在官方上可以看到各種的樣式
連結網址: https://echarts.apache.org/examples/zh/index.html#chart-type-candlestick
我今天展示的是基本的樣式,上方圖示為開盤、最高、最低、收盤,下方圖示為成交量的 Bar 圖,後端資料簡單用 JSON 模擬一組真實的 K線價格。
先來看看成果展示
這是由 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 裡面。
付費後可下載此篇文章教學程式碼。
重點整理
相關學習文章
[C#] 取得證交所台股價格的 3 種實用方法(附範例下載)