很久沒寫博客了,此次來個質量點的。html
K線圖,相信每一個股民都不陌生,如何用SVG畫好一個K線圖是一個難題。數組
我選擇用highstock作爲畫圖組件,適當的修改了一下源碼,參考了數個財經網站的案例,完成了一個不太成熟的K線圖,歡迎你們批評指正。ssh
上圖就是整個K線圖的樣子,圖的上半部分是K線圖和5日均線,10日均線,30日均線,下半部分是成交量,用柱狀圖顯示,tooltips顯示了用戶選擇點的股票指標,全部顏色符合紅漲綠跌的原則。ide
實現的功能主要有:網站
1.根據用戶選擇的時間區間,顯示最高價和最低價。this
2.點擊最高價或最低價的flags會顯示出相應的時間。
spa
3.動態改變X軸時間顯示格式(%Y %Y-%m %m-%d),防止樣式重疊在一塊兒。.net
4. 動態改變Y軸的最大值最小值,防止K線圖畫出去。
prototype
5.根據當前點的開盤價和收盤價改變柱狀圖的顏色。code
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頁面調用的話須要這樣寫
<script> new highStockChart('container',retTrade,crrentData); </script> <div id="container" style="height: 400px;width: 545px">其中container是highstock的renderID , retTrade是須要組裝的數組,crrentData是當天須要實時更新的數組(也就是圖上的最後一個點,須要過一段時間更新一遍,由於當天的K線指標一直在變)
{日期^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!