highstock K線圖 深入研究


K線圖,相信每個股民都不陌生,如何用SVG畫好一個K線圖是一個難題。

我選擇用highstock做為畫圖組件,適當的修改了一下源碼,參考了數個財經網站的案例,完成了一個不太成熟的K線圖,歡迎大家批評指正。

 

 


上圖就是整個K線圖的樣子,圖的上半部分是K線圖和5日均線,10日均線,30日均線,下半部分是成交量,用柱狀圖顯示,tooltips顯示了用戶選擇點的股票指標,所有顏色符合紅漲綠跌的原則。

實現的功能主要有:

1.根據用戶選擇的時間區間,顯示最高價和最低價。

2.點擊最高價或最低價的flags會顯示出相應的時間。

3.動態改變X軸時間顯示格式(%Y       %Y-%m        %m-%d),防止樣式重疊在一起。

4. 動態改變Y軸的最大值最小值,防止K線圖畫出去。

5.根據當前點的開盤價和收盤價改變柱狀圖的顏色。

6.本地化一些常量,本地化日期格式。

7.根據鼠標指向的當前點的位置。動態改變tooltip的位置

源碼:

 

//highstock K線圖
var highStockChart = function(divID,result,crrentData){
    var $reporting = $("#report");
    var firstTouch = true;
    //開盤價^最高價^最低價^收盤價^成交量^成交額^漲跌幅^換手率^五日均線^十日均線^20日均線^30日均線^昨日收盤價 ^當前點離左邊的相對距離
    var  open,high,low,close,y,zde,zdf,hsl,MA5,MA10,MA20,MA30,zs,relativeWidth; 
    //定義數組
    var ohlcArray = [],volumeArray = [],MA5Array = [],MA10Array=[],MA20Array=[],MA30Array=[],zdfArray=[],zdeArray=[],hslArray=[],data=[],dailyData = [],data =[];    
    /*
     * 這個方法用來控制K線上的flags的顯示情況,當afterSetExtremes時觸發該方法,通過flags顯示當前時間區間最高價和最低價
     * minTime  當前k線圖上最小的時間點
     * maxTime  當前k線圖上最大的時間點
     * chart  當前的highstock對象
     */
    var showTips =     function (minTime,maxTime,chart){
    //    console.log( Highcharts.dateFormat('%Y-%m-%d %H:%M',minTime));
    //    console.log( Highcharts.dateFormat('%Y-%m-%d %H:%M',maxTime));
        chart.showLoading();
        //定義當前時間區間中最低價的最小值,最高價的最大值 以及對應的時間
        var lowestPrice,highestPrice,array=[],highestArray=[],lowestArray=[],highestTime,lowestTime,flagsMaxData_1=[],flagsMaxData_2=[],flagsMinData_1,flagsMinData_2; 
//        var chartData = chart.series[0].data;
//        for(var i=0;i<chartData.length-1;i++){
//            if(chartData[i].x>minTime && chartData[i].x<=maxTime){
//                array.push([
//                            chartData[i].x,
//                            chartData[i].high, //最高價
//                            chartData[i].low //最低價
//                            ])
//            }
//        }
        for(var i=0;i<ohlcArray.length-1;i++){
            if(ohlcArray[i][0]>=minTime && ohlcArray[i][0]<=maxTime){
                array.push([
                            ohlcArray[i][0],
                            ohlcArray[i][2], //最高價
                            ohlcArray[i][3] //最低價
                            ])
            }
        }
        if(!array.length>0){ return; } highestArray = array.sort(function(x, y){ return y[1] - x[1];})[0];// 根據最高價降序排列 highestTime =highestArray[0]; highestPrice =highestArray[1].toFixed(2); lowestArray = array.sort(function(x, y){ return x[2] - y[2];})[0]; //根據最低價升序排列 lowestTime =lowestArray[0]; lowestPrice =lowestArray[2].toFixed(2); var formatDate1 = Highcharts.dateFormat('%Y-%m-%d',highestTime) var formatDate2 = Highcharts.dateFormat('%Y-%m-%d',lowestTime) flagsMaxData_1 = [ { x : highestTime, title : highestPrice+"("+formatDate1+")" } ]; flagsMaxData_2 = [ { x : highestTime, title : highestPrice } ]; flagsMinData_1 = [ { x : lowestTime, title : lowestPrice+"("+formatDate2+")" } ]; flagsMinData_2 = [ { x : lowestTime, title : lowestPrice } ]; var min = parseFloat(flagsMinData_2[0].title) - parseFloat(flagsMinData_2[0].title)*0.05; var max = parseFloat(flagsMaxData_2[0].title)+parseFloat(flagsMaxData_2[0].title)*0.05; var tickInterval = (( max-min)/5).toFixed(1)*1; var oneMonth = 1000*3600*24*30; var oneYear = 1000*3600*24*365; var tickIntervalTime,dataFormat='%Y-%m'; if(maxTime-minTime>oneYear*2){ tickIntervalTime = oneYear*2 dataFormat = '%Y'; }else if(maxTime-minTime>oneYear){ tickIntervalTime = oneMonth*6 }else if(maxTime-minTime>oneMonth*6){ tickIntervalTime = oneMonth*3 }else{ tickIntervalTime = oneMonth dataFormat = '%m-%d' } //Y軸坐標自適應 chart.yAxis[0].update({ min : min, max : max, tickInterval: tickInterval }); //X軸坐標自適應 chart.xAxis[0].update({ min : minTime, max : maxTime, tickInterval: tickIntervalTime, labels: { y:-78,//調節y偏移 formatter: function(e) { return Highcharts.dateFormat(dataFormat, this.value); } } }); //動態update flags(最高價) chart.series[5].update({ data : flagsMaxData_2, point:{ events:{ click:function(){ chart.series[5].update({ data : flagsMaxData_1, width : 100 }); chart.series[6].update({ data : flagsMinData_1, width : 100 }); } } }, events:{ mouseOut:function(){ chart.series[5].update({ data :flagsMaxData_2, width : 25 }); chart.series[6].update({ data :flagsMinData_2, width : 25 }); } } }); //動態update flags(最低價) chart.series[6].update({ data : flagsMinData_2, point:{ events:{ click:function(){ chart.series[6].update({ data : flagsMinData_1, width : 100 }); chart.series[5].update({ data : flagsMaxData_1, width : 100 }); } } }, events:{ mouseOut:function(){ chart.series[6].update({ data :flagsMinData_2, width : 25 }); chart.series[5].update({ data :flagsMaxData_2, width : 25 }); } } }); chart.hideLoading(); } //修改colum條的顏色(重寫了源碼方法) var originalDrawPoints = Highcharts.seriesTypes.column.prototype.drawPoints; Highcharts.seriesTypes.column.prototype.drawPoints = function () { var merge = Highcharts.merge, series = this, chart = this.chart, points = series.points, i = points.length; while (i--) { var candlePoint = chart.series[0].points[i]; if(candlePoint.open != undefined && candlePoint.close != undefined){ //如果是K線圖 改變矩形條顏色,否則不變 var color = (candlePoint.open < candlePoint.close) ? '#DD2200' : '#33AA11'; var seriesPointAttr = merge(series.pointAttr); seriesPointAttr[''].fill = color; seriesPointAttr.hover.fill = Highcharts.Color(color).brighten(0.3).get(); seriesPointAttr.select.fill = color; }else{ var seriesPointAttr = merge(series.pointAttr); } points[i].pointAttr = seriesPointAttr; } originalDrawPoints.call(this); } //常量本地化 Highcharts.setOptions({ global : { useUTC : false }, lang: { rangeSelectorFrom:"日期:", rangeSelectorTo:"至", rangeSelectorZoom:"范圍", loading:'加載中...', /*decimalPoint:'.', downloadPNG:'下載PNG圖片', downloadJPEG:'下載JPG圖片', downloadPDF:'下載PDF文件', exportButtonTitle:'導出...', printButtonTitle:'打印圖表', resetZoom:'還原圖表', resetZoomTitle:'還原圖表為1:1大小', thousandsSep:',',*/ shortMonths:['1月','2月','3月','4月','5月','6月','7月','8月','9月','10月','11月','12月'], weekdays:['星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六'], }, }); //格式化數據,准備繪圖 dailyData = result.vl.split("~"); for(i=0;i<dailyData.length-1;i++){ data[i] = dailyData[i].split("^"); } //把當前最新K線數據加載進來 var length = data.length-1; var time = parseFloat(data[length][0]); var crrentTime = crrentData[0]; // if(!(isNaN(crrentData[1]) || isNaN(crrentData[2]) || isNaN(crrentData[3]) || isNaN(crrentData[4]))){ // if(crrentData[1]!=0 || crrentData[2]!=0 || crrentData[3]!=0 || crrentData[4]!=0){ // if(time < crrentTime){ // data.push(crrentData); // }else if(time == crrentTime){ // data[length] = crrentData; // } // } // } for (i = 0; i < data.length; i++) { // console.log( Highcharts.dateFormat('%A ,%Y-%m-%d %H:%M',parseInt(data[i][0]))); ohlcArray.push([ parseInt(data[i][0]), // the date parseFloat(data[i][1]), // open parseFloat(data[i][3]), // high parseFloat(data[i][4]), // low parseFloat(data[i][2]) // close ]); MA5Array.push([ parseInt(data[i][0]), // the date parseFloat(data[i][11]) ]); MA10Array.push([ parseInt(data[i][0]), parseFloat(data[i][12]), ]); MA20Array.push([ parseInt(data[i][0]), parseFloat(data[i][13]), ]) MA30Array.push([ parseInt(data[i][0]), parseFloat(data[i][14]) ]); volumeArray.push([ parseInt(data[i][0]), // the date parseInt(data[i][5]) // 成交量 ]); } //開始繪圖 return new Highcharts.StockChart( { chart:{ renderTo : divID, margin: [30, 30,30, 30], plotBorderColor: '#3C94C4', plotBorderWidth: 0.3, events:{ load:function(){ var length = ohlcArray.length-1; showTips(ohlcArray[0][0],ohlcArray[length][0],this); } } }, loading: { labelStyle: { position: 'relative', top: '10em', zindex:1000 } }, credits:{ enabled:false }, rangeSelector: { // selected: 1, // buttons: [{ // type: 'month', // count: 1, // text: '1月' // }, { // type: 'month', // count: 2, // text: '2月' // },{ // type: 'all', // text: 'All' // }], enabled:false, inputDateFormat: '%Y-%m-%d' //設置右上角的日期格式 }, plotOptions: { //修改蠟燭顏色 candlestick: { color: '#33AA11', upColor: '#DD2200', lineColor: '#33AA11', upLineColor: '#DD2200', maker:{ states:{ hover:{ enabled:false, } } } }, //去掉曲線和蠟燭上的hover事件 series: { states: { hover: { enabled: false } }, line: { marker: { enabled: false } } } }, //格式化懸浮框 tooltip: { formatter: function() { if(this.y == undefined){ return; } for(var i =0;i<data.length;i++){ if(this.x == data[i][0]){ zdf = parseFloat(data[i][7]).toFixed(2); zde = parseFloat(data[i][8]).toFixed(2); // hsl = parseFloat(data[i][9]).toFixed(2); zs = parseFloat(data[i][10]).toFixed(2); } } open = this.points[0].point.open.toFixed(2); high = this.points[0].point.high.toFixed(2); low = this.points[0].point.low.toFixed(2); close = this.points[0].point.close.toFixed(2); y = (this.points[1].point.y*0.0001).toFixed(2); MA5 =this.points[2].y.toFixed(2); MA10 =this.points[3].y.toFixed(2); MA30 =this.points[4].y.toFixed(2); relativeWidth = this.points[0].point.shapeArgs.x; var stockName = this.points[0].series.name; var tip= '<b>'+ Highcharts.dateFormat('%Y-%m-%d %A', this.x) +'</b><br/>'; tip +=stockName+"<br/>"; if(open>zs){ tip += '開盤價:<span style="color: #DD2200;">'+open+' </span><br/>'; }else{ tip += '開盤價:<span style="color: #33AA11;">'+open+' </span><br/>'; } if(high>zs){ tip += '最高價:<span style="color: #DD2200;">'+high+' </span><br/>'; }else{ tip += '最高價:<span style="color: #33AA11;">'+high+' </span><br/>'; } if(low>zs){ tip += '最低價:<span style="color: #DD2200;">'+low+' </span><br/>'; }else{ tip += '最低價:<span style="color: #33AA11;">'+low+' </span><br/>'; } if(close>zs){ tip += '收盤價:<span style="color: #DD2200;">'+close+' </span><br/>'; }else{ tip += '收盤價:<span style="color: #33AA11;">'+close+' </span><br/>'; } if(zde>0){ tip += '漲跌額:<span style="color: #DD2200;">'+zde+' </span><br/>'; }else{ tip += '漲跌額:<span style="color: #33AA11;">'+zde+' </span><br/>'; } if(zdf>0){ tip += '漲跌幅:<span style="color: #DD2200;">'+zdf+' </span><br/>'; }else{ tip += '漲跌幅:<span style="color: #33AA11;">'+zdf+' </span><br/>'; } if(y>10000){ tip += "成交量:"+(y*0.0001).toFixed(2)+"(億股)<br/>"; }else{ tip += "成交量:"+y+"(萬股)<br/>"; } /* tip += "換手率:"+hsl+"<br/>";*/ $reporting.html( ' <span style="font-weight:bold">'+stockName+'</span>' + ' <span>開盤:</span>'+ open +' <span>收盤:</span>'+close +' <span>最高:</span>'+ high +' <span>最低:</span>'+ low +' <span style="padding-left:25px;"> </span>'+ Highcharts.dateFormat('%Y-%m-%d',this.x) +' <br/><b style="color:#1aadce;padding-left:25px">MA5</b> '+ MA5 +' <b style="color: #8bbc21;padding-left:150px">MA10 </b> '+ MA10 +' <b style="color:#910000;padding-left:150px">MA30</b> '+ MA30 ); return tip; }, //crosshairs: [true, true]//雙線 crosshairs: { dashStyle: 'dash' }, borderColor: 'white', positioner: function () { //設置tips顯示的相對位置 var halfWidth = this.chart.chartWidth/2;//chart寬度 var width = this.chart.chartWidth-155; var height = this.chart.chartHeight/5-8;//chart高度 if(relativeWidth<halfWidth){ return { x: width, y:height }; }else{ return { x: 30, y: height }; } }, shadow: false }, title: { enabled:false }, exporting: { enabled: false //設置導出按鈕不可用 }, scrollbar: { barBackgroundColor: 'gray', barBorderRadius: 7, barBorderWidth: 0, buttonBackgroundColor: 'gray', buttonBorderWidth: 0, buttonArrowColor: 'yellow', buttonBorderRadius: 7, rifleColor: 'yellow', trackBackgroundColor: 'white', trackBorderWidth: 1, trackBorderColor: 'silver', trackBorderRadius: 7, //enabled: false, liveRedraw: false //設置scrollbar在移動過程中,chart不會重繪 }, navigator: { adaptToUpdatedData: false, xAxis: { labels: { formatter: function(e) { return Highcharts.dateFormat('%m-%d', this.value); } } }, handles: { backgroundColor: '#808080', // borderColor: '#268FC9' }, margin:-10 }, xAxis: { type: 'datetime', tickLength: 0,//X軸下標長度 // minRange: 3600 * 1000*24*30, // one month events: { afterSetExtremes: function(e) { var minTime = Highcharts.dateFormat("%Y-%m-%d", e.min); var maxTime = Highcharts.dateFormat("%Y-%m-%d", e.max); var chart = this.chart; showTips(e.min,e.max,chart); } } }, yAxis: [{ title: { enable:false }, height: '70%', lineWidth:1,//Y軸邊緣線條粗細 gridLineColor: '#346691', gridLineWidth:0.1, // gridLineDashStyle: 'longdash', opposite:true },{ title: { enable:false }, top: '75%', height: '25%', labels:{ x:-15 }, gridLineColor: '#346691', gridLineWidth:0.1, lineWidth: 1, }], series: [ { type: 'candlestick', id:"candlestick", name: result.cname, data: ohlcArray, dataGrouping: { enabled: false } } ,{ type: 'column',//2 name: '成交量', data: volumeArray, yAxis: 1, dataGrouping: { enabled: false } } ,{ type: 'spline', name: 'MA5', color:'#1aadce', data: MA5Array, lineWidth:1, dataGrouping: { enabled: false } },{ type: 'spline', name: 'MA10', data: MA10Array, color:'#8bbc21', threshold: null, lineWidth:1, dataGrouping: { enabled: false } },{ type: 'spline', name: 'MA30', data: MA30Array, color:'#910000', threshold: null, lineWidth:1, dataGrouping: { enabled: false } },{ type : 'flags', cursor:'pointer', style:{ fontSize: '11px', fontWeight: 'normal', textAlign: 'center' }, lineWidth:0.5, onSeries : 'candlestick', width : 25, shape: 'squarepin' },{ type : 'flags', cursor:'pointer', y: 33, style:{ fontSize: '11px', fontWeight: 'normal', textAlign: 'center' }, lineWidth:0.5, onSeries : 'candlestick', width : 25, shape: 'squarepin' } ] }); }

 html頁面調用的話需要這樣寫

  1. <script>  
  2. new highStockChart('container',retTrade,crrentData);  
  3. </script>  
  4. <div id="container" style="height: 400px;width: 545px">  

 

其中container是highstock的renderID , retTrade是需要組裝的數組,crrentData是當天需要實時更新的數組(也就是圖上的最后一個點,需要過一段時間更新一遍,因為當天的K線指標一直在變)
retTrade的數據格式如下:

 

{日期^1開盤價^2收盤價^3最高價^4最低價^5成交量^6成交額^7漲跌幅^8漲跌額^9換手率^10昨日收盤價^11MA5^12MA10^13MA20^14MA30^15MA60}
crrentData的數據格式如下:

{日期^1開盤價^2收盤價^3最高價^4最低價^5成交量^MA5^MA10^MA20^MA30}

當然您也可以自定義,只需要把js中的相應數組下標調整好就可以了。

最后,別忘了引入highstock.js和較高版本的jQuery,good luck!

 

附上demo,點開html就能看到效果


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM