/**
|
* eventRiver 布局算法
|
* @module echarts/layout/eventRiver
|
* @author clmtulip (车丽美, clmtulip@gmail.com)
|
*/
|
define(function(require) {
|
|
function eventRiverLayout(series, intervalX, area) {
|
var space = 4;
|
var scale = intervalX;
|
|
function importanceSort(a, b) {
|
var x = a.importance;
|
var y = b.importance;
|
return ((x > y) ? -1 : ((x < y) ? 1 : 0));
|
}
|
|
/**
|
* 查询数组中元素的index
|
*/
|
function indexOf(array, value) {
|
if (array.indexOf) {
|
return array.indexOf(value);
|
}
|
for (var i = 0, len = array.length; i < len; i++) {
|
if (array[i] === value) {
|
return i;
|
}
|
}
|
return -1;
|
}
|
|
// step 0. calculate event importance and sort descending
|
for (var i = 0; i < series.length; i++) {
|
for (var j = 0; j < series[i].data.length; j++) {
|
if (series[i].data[j].weight == null) {
|
series[i].data[j].weight = 1;
|
}
|
var importance = 0;
|
for (var k = 0; k < series[i].data[j].evolution.length; k++) {
|
importance += series[i].data[j].evolution[k].valueScale;
|
}
|
series[i].data[j].importance = importance * series[i].data[j].weight;
|
}
|
series[i].data.sort(importanceSort);
|
}
|
|
// step 1. 计算每个group的重要值importance,并按递减顺序排序
|
for (var i = 0; i < series.length; i++) {
|
if (series[i].weight == null) {
|
series[i].weight = 1;
|
}
|
var importance = 0;
|
for (var j = 0; j < series[i].data.length; j++) {
|
importance += series[i].data[j].weight;
|
}
|
series[i].importance = importance * series[i].weight;
|
}
|
// 根据importance对groups进行递减排序
|
series.sort(importanceSort);
|
|
// step 3. set bubble positions in group order, then in event order
|
// 找到包含所有事件的时间段,即最小和最大时间点
|
var minTime = Number.MAX_VALUE;
|
var maxTime = 0;
|
for (var i = 0; i < series.length; i++) {
|
for (var j = 0; j < series[i].data.length; j++) {
|
for (var k = 0; k < series[i].data[j].evolution.length; k++) {
|
var time = series[i].data[j].evolution[k].timeScale;
|
minTime = Math.min(minTime, time);
|
maxTime = Math.max(maxTime, time);
|
}
|
}
|
}
|
//console.log('minTime: ' + minTime);
|
//console.log('maxTime: ' + maxTime);
|
|
// 时间范围 即 x轴显示的起始范围
|
minTime = ~~minTime;
|
maxTime = ~~maxTime;
|
|
// 气泡之间的间隙
|
var flagForOffset = (function () {
|
|
var length = maxTime - minTime + 1 + (~~intervalX);
|
if (length <= 0){
|
return [0];
|
}
|
var result = [];
|
while (length--){
|
result.push(0);
|
}
|
return result;
|
})();
|
|
var flagForPos = flagForOffset.slice(0);
|
|
var bubbleData = [];
|
var totalMaxy = 0;
|
var totalOffset = 0;
|
|
for (var i = 0; i < series.length; i++) {
|
for (var j = 0; j < series[i].data.length; j++) {
|
var e = series[i].data[j];
|
e.time = [];
|
e.value = [];
|
var tmp;
|
var maxy = 0;
|
for (var k = 0; k < series[i].data[j].evolution.length; k++) {
|
tmp = series[i].data[j].evolution[k];
|
e.time.push(tmp.timeScale);
|
e.value.push(tmp.valueScale);
|
maxy = Math.max(maxy, tmp.valueScale);
|
}
|
|
// 边界计算
|
bubbleBound(e, intervalX, minTime);
|
|
// 得到可以放置的位置
|
e.y = findLocation(flagForPos, e, function (e, index){return e.ypx[index];});
|
// 得到偏移量
|
e._offset = findLocation(flagForOffset, e, function (){ return space;});
|
|
totalMaxy = Math.max(totalMaxy, e.y + maxy);
|
totalOffset = Math.max(totalOffset, e._offset);
|
|
bubbleData.push(e);
|
}
|
}
|
|
// 映射到显示区域内
|
scaleY(bubbleData, area, totalMaxy, totalOffset);
|
}
|
|
/**
|
* 映射到显示区域内
|
*/
|
function scaleY(bubbleData, area, maxY, offset) {
|
var height = area.height;
|
var offsetScale = offset / height > 0.5 ? 0.5 : 1;
|
|
var yBase = area.y;
|
var yScale = (area.height - offset) / maxY;
|
|
for (var i = 0, length = bubbleData.length; i < length; i++){
|
var e = bubbleData[i];
|
e.y = yBase + yScale * e.y + e._offset * offsetScale;
|
|
delete e.time;
|
delete e.value;
|
delete e.xpx;
|
delete e.ypx;
|
delete e._offset;
|
|
// 修改值域范围
|
var evolutionList = e.evolution;
|
for (var k = 0, klen = evolutionList.length; k < klen; k++) {
|
evolutionList[k].valueScale *= yScale;
|
}
|
}
|
}
|
|
|
/**
|
* 得到两点式的方程函数 y = k*x + b
|
* @param {number} x0 起点横坐标
|
* @param {number} y0 起点纵坐标
|
* @param {number} x1 终点横坐标
|
* @param {number} y1 终点纵坐标
|
* @returns {Function} 输入为横坐标 返回纵坐标s
|
*/
|
function line(x0, y0, x1, y1){
|
|
// 横坐标相同,应该抛出错误
|
if (x0 === x1) {
|
throw new Error('x0 is equal with x1!!!');
|
}
|
|
// 纵坐标相同
|
if (y0 === y1) {
|
return function () {
|
return y0;
|
}
|
}
|
|
var k = (y0 - y1) / (x0 - x1);
|
var b = (y1 * x0 - y0 * x1) / (x0 - x1);
|
|
return function (x) {
|
return k * x + b;
|
}
|
}
|
|
/**
|
* 计算当前气泡的值经过的边界
|
* @param {object} e 气泡的值
|
* @param {array} e.time 时间范围
|
* @param {array} e.value 值域范围
|
* @param {number} intervalX 气泡尾巴长度
|
*/
|
function bubbleBound(e, intervalX, minX){
|
var space = ~~intervalX;
|
var length = e.time.length;
|
|
e.xpx = [];
|
e.ypx = [];
|
|
var i = 0;
|
var x0 = 0;
|
var x1 = 0;
|
var y0 = 0;
|
var y1 = 0;
|
var newline;
|
for(; i < length; i++){
|
|
x0 = ~~e.time[i];
|
y0 = e.value[i] / 2;
|
|
if (i === length - 1) {
|
// i = length - 1 ~ += intervalX
|
x1 = x0 + space;
|
y1 = 0;
|
} else {
|
x1 = ~~(e.time[i + 1]);
|
y1 = e.value[i + 1] / 2;
|
}
|
|
// to line
|
newline = line(x0, y0, x1, y1);
|
//
|
for (var x = x0; x < x1; x++){
|
e.xpx.push(x - minX);
|
e.ypx.push(newline(x));
|
}
|
}
|
|
e.xpx.push(x1 - minX);
|
e.ypx.push(y1);
|
}
|
|
function findLocation(flags, e, yvalue){
|
var pos = 0;
|
|
var length = e.xpx.length;
|
var i = 0;
|
var y;
|
for(; i < length; i++){
|
y = yvalue(e, i);
|
pos = Math.max(pos, y + flags[e.xpx[i]]);
|
}
|
|
// reset flags
|
for(i = 0; i < length; i++){
|
y = yvalue(e, i);
|
flags[e.xpx[i]] = pos + y;
|
}
|
|
return pos;
|
}
|
|
return eventRiverLayout;
|
});
|