• 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; 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就能看到效果

  • 相关阅读:
    npx vs npm
    RubyGem镜像/ruby国内镜像
    IOS开发依赖管理工具CocoaPods
    alpine linux
    阿里妈妈图标库
    java应用系统运行速度慢的解决方法
    jvm程序执行慢诊断手册
    js强制不使用“兼容性视图”
    java.lang.NumberFormatException: Infinite or NaN
    ALTER添加列后,立即UPDATE该列会报错
  • 原文地址:https://www.cnblogs.com/onetwo/p/6050948.html
Copyright © 2020-2023  润新知