好久没写博客了,这次来个质量点的。
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;iminTime && chartData[i].x<=maxTime){
// array.push([
// chartData[i].x,
// chartData[i].high, //最高价
// chartData[i].low //最低价
// ])
// }
// }
for(var i=0;i=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
';
tip +=stockName+"
";
if(open>zs){
tip += '开盘价:'+open+'
';
}else{
tip += '开盘价:'+open+'
';
}
if(high>zs){
tip += '最高价:'+high+'
';
}else{
tip += '最高价:'+high+'
';
}
if(low>zs){
tip += '最低价:'+low+'
';
}else{
tip += '最低价:'+low+'
';
}
if(close>zs){
tip += '收盘价:'+close+'
';
}else{
tip += '收盘价:'+close+'
';
}
if(zde>0){
tip += '涨跌额:'+zde+'
';
}else{
tip += '涨跌额:'+zde+'
';
}
if(zdf>0){
tip += '涨跌幅:'+zdf+'
';
}else{
tip += '涨跌幅:'+zdf+'
';
}
if(y>10000){
tip += "成交量:"+(y*0.0001).toFixed(2)+"(亿股)
";
}else{
tip += "成交量:"+y+"(万股)
";
}
/* tip += "换手率:"+hsl+"
";*/
$reporting.html(
' '+stockName+''
+ ' 开盘:'+ open
+' 收盘:'+close
+' 最高:'+ high
+' 最低:'+ low
+' '+ Highcharts.dateFormat('%Y-%m-%d',this.x)
+'
MA5 '+ MA5
+' MA10 '+ MA10
+' MA30 '+ 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
html页面调用的话需要这样写
其中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就能看到效果