Homology Event(同源事件)

刚刚接触HighCharts不久,公司需要对HighCharts上的功能进行很多的扩展。最近做了一个同源事件的案例,给大家分享出来。
所谓的同源事件,也是我编造的一个词汇,做这样的拓展是为了能让某一图表的事件相应能传递到其他同种类型不同数据的图表中。当然,我们可以把他做成一个图表,但是客户有时候就有这么变态的需求,我们也只好造做了。
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>highstock 多曲线同源事件(Homology Event)</title>
<style type="text/css">
div.title{width:450px;margin:20px auto;}
div.container{width:900px;height:400px;margin:20px auto;}
div.container div.child_container{float:left;width:300px;}
</style>
<script src="jquery.min.js"></script>
<script src="highstock.src.js"></script>
<script type="text/javascript">
$(document).ready(function(){
var names = ['MSFT', 'AAPL', 'GOOG'],
createChart = function (series) {
var options = {
tooltip : {
crosshairs:true,
},
series: [series]
};
options.title = {text : 'chart案例'};
$('#chart_' + series.name).highcharts('Chart', options);
options.title = {text : 'stock案例'};
$('#stock_' + series.name).highcharts('StockChart', options);
};
$.each(names, function (i, name) {
$.getJSON('http://www.hcharts.cn/datas/jsonp.php?filename=' + name.toLowerCase() + '-c.json&callback=?', function (data) {
createChart({name : name.toLowerCase(), data : data});
});
});
/*
* 同源事件的关键性代码
* window.event是单例,因此需要进行属性拷贝,模拟一个新的鼠标事件
*/
$('.container .child_container').on('mousemove', function(e){
var sourceChartContainer = $(this);
var sourceChart = sourceChartContainer.highcharts();
var sourceXAxis = sourceChart.xAxis[0];
var extremes = sourceXAxis.getExtremes();
var targetChartContainerList = sourceChartContainer.siblings();
targetChartContainerList.each(function(index, targetChartContainerElement){
var targetChartContainer = $(targetChartContainerElement);
var targetChart = targetChartContainer.highcharts();
var sourceOffset = sourceChartContainer.offset();
var targetOffset = targetChartContainer.offset();
var targetE = {};
for(var i in e){
targetE[i] = e[i];
}
targetE.pageX = e.pageX + targetOffset.left - sourceOffset.left;
targetE.pageY = e.pageY + targetOffset.top - sourceOffset.top;
var targetEl = targetChartContainer.find('rect.highcharts-background')[0] || targetChartContainer.find('path.highcharts-tracker')[0];
targetE.target = targetE.srcElement = targetE.fromElement = targetE.toElement = targetEl;
targetE.delegateTarget = targetE.currentTarget = targetChartContainer[0];
targetE.originalEvent = targetE;
if(targetChart && targetChart.pointer){
targetChart.pointer.onContainerMouseMove(targetE);
}
if(targetChart && targetChart.scroller){
targetChart.scroller.mouseMoveHandler(targetE);
}
if(sourceChart.mouseIsDown == 'mouseup' || sourceChart.mouseIsDown == 'mousedown'){
if(targetChart && targetChart.xAxis[0]){
var targetXAxis = targetChart.xAxis[0];
targetXAxis.setExtremes(extremes.min, extremes.max);
}
}
});
return false;
});
$('.container .child_container').bind('mouseleave', function(e){
var sourceChartContainer = $(this);
var sourceChart = sourceChartContainer.highcharts();
if(sourceChart && sourceChart.pointer){
sourceChart.pointer.reset();
sourceChart.pointer.chartPosition = null;
}
var targetChartContainerList = sourceChartContainer.siblings();
targetChartContainerList.each(function(index, targetChartContainerElement){
var targetChartContainer = $(targetChartContainerElement);
var targetChart = targetChartContainer.highcharts();
if(targetChart && targetChart.pointer){
targetChart.pointer.reset();
targetChart.pointer.chartPosition = null;
}
});
return false;
});
$('.container .child_container').bind('mouseup', function(e){
var sourceChartContainer = $(this);
var sourceChart = sourceChartContainer.highcharts();
var targetChartContainerList = sourceChartContainer.siblings();
e.type = 'mouseup';
if(sourceChart && sourceChart.pointer){
sourceChart.pointer.drop(e);
sourceChart.mouseIsDown = 'mouseup';
}
});
});
</script>
</head>
<body>
<div class="title"><h3>多曲线同源事件案例</h3></div>
<div class="container" id="chart">
<div class="child_container" id="chart_msft"></div>
<div class="child_container" id="chart_aapl"></div>
<div class="child_container" id="chart_goog"></div>
</div>
<div class="container" id="stock">
<div class="child_container" id="stock_msft"></div>
<div class="child_container" id="stock_aapl"></div>
<div class="child_container" id="stock_goog"></div>
</div>
</body>
</html>
[/i][/i]

QQ截图20150928094835.png


QQ截图20150928094848.png

思路分享
    什么叫同源事件,如果我有几个一模一样的图表,例如A、B、C、D。四个图表长的一模一样,数据模型也一模一样,除了数据不一样。有时我们会有这样的一种业务,当我看到A表中的某个数据时,也希望看到其他表上的同样的数据点。好的,有的人会把这四个图标合成为一个图表,使用单X轴多曲线即可完成。但如果我希望就用四张图表来表示,如果让其中一张图标的行为能触发到其他图表上呢。这就要使用同源事件,顾名思义,就是让所有的图表的事件发生在同一个源头。
    一开始想到做这个时,以为是一个比较简单的事情,但接着就发现难度了。整个HighChart中几乎没有底层绘图的API,而所有的行为都是基于Window.Event来的。我们来看一段简单的源码:
/**
* Start a drag operation
*/
dragStart: function (e) {
var chart = this.chart;
// Record the start position
chart.mouseIsDown = e.type;
chart.cancelClick = false;
chart.mouseDownX = this.mouseDownX = e.chartX;
chart.mouseDownY = this.mouseDownY = e.chartY;
},
    以上代码,我们很清楚他要做什么,他要进行图像的绘制了。然而绘制的图像并不是基于XY轴,而是基于Window.Event事件,从中获取XY属性。除此以外,还会获取其他很多内容,有些是非Window.Event对象的属性。至于我是怎么知道的,这对部分源码进行了解读才有所总结的。
    那么基于数学信息(平面坐标)的API绘制被打消了,那么就只能从Window.Event入手,想办法把这个事件传递到其他图标中去。例如,鼠标移动时,他会根据Window.Event去标记标记点和绘制数据线:
// The mousemove, touchmove and touchstart event handler
onContainerMouseMove: function (e) {
var chart = this.chart;
hoverChartIndex = chart.index;
e = this.normalize(e);
e.returnValue = false; // #2251, #3224

if (chart.mouseIsDown === 'mousedown') {
this.drag(e);
}

// Show the tooltip and run mouse over events (#977)
if ((this.inClass(e.target, 'highcharts-tracker') || chart.isInsidePlot(e.chartX - chart.plotLeft, e.chartY - chart.plotTop)) && !chart.openMenu) {
this.runPointActions(e);
}
}
    我的思路就是,在A图表进行鼠标移动时,将其事件传递到其他图表中,以下为伪代码:
A.bind('mousemove', function(e){
B.onContainerMouseMove(e);
C.onContainerMouseMove(e);
D.onContainerMouseMove(e);
});
    经过测试,发现根本行不通。打了断点测试发现,B、C、D图表传入的Event所作用对象target全是A。根据这个问题,我动态的改变作用对象:
    
A.bind('mousemove', function(e){
e.currentTarget = B;
e.target = A.find('.highcharts-background');
B.onContainerMouseMove(e);
// C、D同理
});
    经过测试,仍然行不通。一方面我们要有触发对象,另一方面Window.Event是一个单例对象,你所创建的新的Event也只是更新了Window.Event的引用。所以,最后我查看源码发现,他会对Event对象进行自定义的规范化处理:
e = this.normalize(e);
    此方法的源码是获取到传进来的Event对象进行计算,获得与Chart直接相关的数据,例如ChartX、ChartY这些非Event原生属性。而对Event对象,normalize方法中并没有做校验处理。而仅仅只是一句:
// common IE normalizing
e = e || window.event;
// Framework specific normalizing (#1165)
e = washMouseEvent(e);
    既然这样,我想到了通过属性复制的方法,重新构建几个非Event实例的JSON对象,因此就有了以上的实现代码。
 
    
已邀请:

john - 个人微博:http://weibo.com/u/2175683160

赞同来自:

感谢分享,牛逼。
 
我已经将代码托管到 HCode 上了,效果见:http://code.hcharts.cn/hcharts.cn/hhhG8q/result
 
 

john - 个人微博:http://weibo.com/u/2175683160

赞同来自:

建议写成插件的形式并提交给官方,插件相关信息见这里:http://www.highcharts.com/plugin-registry 或这里 http://plugins.hcharts.cn/。 
 
另外官方也有一个类似的例子:http://code.hcharts.cn/hcharts.cn/hhhG87
 
 

jsbome - 个人博客:http://highcharts.daxueba.net

赞同来自:

感觉就是大家说的联动。

要回复问题请先登录注册