vislite
Version:
灵活、快速、简单的数据可视化交互式跨端前端库
273 lines (254 loc) • 10 kB
JavaScript
function initOption(setOption, defaultOption) {
for (var key in setOption) {
defaultOption[key] = setOption[key];
}
return defaultOption;
}
function rotate (cx, cy, deg, x, y) {
var cos = Math.cos(deg), sin = Math.sin(deg);
return [
(x - cx) * cos - (y - cy) * sin + cx,
(x - cx) * sin + (y - cy) * cos + cy
];
}
//当前正在运动的动画的tick函数堆栈
let $timers = [];
//唯一定时器的定时间隔
let $interval = 13;
//定时器ID
let $timerId;
/**
* 动画轮播
* @param {function} doback 轮询函数,有一个形参deep,0-1,表示执行进度
* @param {number} duration 动画时长,可选
* @param {function} callback 动画结束回调,可选,有一个形参deep,0-1,表示执行进度
*
* @returns {object} 返回一个对象,包含一个stop方法,用于在动画结束前结束动画
*/
function animation(doback, duration, callback) {
if (arguments.length < 2) duration = 400;
if (arguments.length < 3) callback = function () { };
let clock = {
//把tick函数推入堆栈
"timer": function (tick, duration, callback) {
if (!tick) {
throw new Error('Tick is required!');
}
let id = new Date().valueOf() + "_" + (Math.random() * 1000).toFixed(0);
$timers.push({
"id": id,
"createTime": new Date(), // 开始时间
"pauseTime": -1, // 暂停的时间,继续运行的时候,借助此计算暂停的时间差
"pauseKeepTime": 0, // 暂停用去的时间
"status": "running", // running(运行中), paused(暂停中)
"tick": tick,
"duration": duration,
"callback": callback
});
clock.start();
return id;
},
//开启唯一的定时器timerId
"start": function () {
if (!$timerId) {
try {
if (globalThis && globalThis.requestAnimationFrame) {
$timerId = globalThis.requestAnimationFrame(function step() {
clock.tick();
if ($timerId) $timerId = globalThis.requestAnimationFrame(step);
});
} else {
$timerId = setInterval(clock.tick, $interval);
}
} catch (e) {
$timerId = setInterval(clock.tick, $interval);
}
}
},
//被定时器调用,遍历timers堆栈
"tick": function () {
let createTime, flag, tick, callback, timer, duration, passTime, timers = $timers;
$timers = [];
$timers.length = 0;
for (flag = 0; flag < timers.length; flag++) {
//初始化数据
timer = timers[flag];
createTime = timer.createTime;
tick = timer.tick;
duration = timer.duration;
callback = timer.callback;
//执行
passTime = (+new Date().valueOf() - createTime.valueOf() - timer.pauseKeepTime) / duration;
passTime = passTime > 1 ? 1 : passTime;
if (timer.status === "running") {
tick(passTime);
}
// 只有当动画没有结束或者动画处于暂停状态时,才继续添加到timers堆栈
if ((passTime < 1 || timer.status === "paused") && timer.id) {
//动画没有结束再添加
$timers.push(timer);
} else {
callback(passTime);
}
}
if ($timers.length <= 0) {
clock.stop();
}
},
//停止定时器,重置timerId=null
"stop": function () {
if ($timerId) {
try {
if (globalThis && globalThis.requestAnimationFrame) globalThis.cancelAnimationFrame($timerId);
else clearInterval($timerId);
} catch (e) {
clearInterval($timerId);
}
$timerId = null;
}
}
};
let id = clock.timer(function (deep) {
//其中deep为0-1,表示改变的程度
doback(deep);
}, duration, callback);
return {
// 结束动画
stop: function () {
for (let i in $timers) {
if ($timers[i].id == id) {
$timers[i].id = void 0;
}
}
},
// 暂停动画
pause: function () {
for (let i in $timers) {
if ($timers[i].id == id) {
if ($timers[i].pauseTime === -1) {
$timers[i].pauseTime = new Date();
$timers[i].status = "paused";
}
}
}
},
// 继续动画
resume: function () {
for (let i in $timers) {
if ($timers[i].id == id) {
if ($timers[i].pauseTime !== -1) {
$timers[i].pauseKeepTime += (new Date().valueOf() - $timers[i].pauseTime.valueOf());
$timers[i].pauseTime = -1;
$timers[i].status = "running";
}
}
}
}
};
}
var PieLayout = (function () {
function PieLayout(config) {
if (config === void 0) { config = {}; }
this.name = 'PieLayout';
this.__option = {
cx: 200,
cy: 200,
radius: [50, 100],
duration: 200,
};
this.__hoverIndex = -1;
this.__config = initOption(config, {
name: function (pieData, initPie) { return pieData.name; },
value: function (pieData, initPie) { return pieData.value; }
});
}
PieLayout.prototype.setOption = function (option) {
initOption(option, this.__option);
return this;
};
PieLayout.prototype.use = function (initPie, hoverIndex) {
if (hoverIndex === void 0) { hoverIndex = -1; }
var pie = {
count: initPie.length,
cx: this.__option.cx,
cy: this.__option.cy,
radius: this.__option.radius,
hoverIndex: hoverIndex,
node: []
};
var totalValue = 0;
var names = [], values = [];
for (var i = 0; i < initPie.length; i++) {
names[i] = this.__config.name(initPie[i], initPie);
values[i] = this.__config.value(initPie[i], initPie);
totalValue += values[i];
}
var beginDeg = Math.PI * -0.5, currenDeg;
for (var i = 0; i < initPie.length; i++) {
currenDeg = values[i] / totalValue * Math.PI * 2;
var radius = [this.__option.radius[0] * (1 + (this.__option.radius[0] > 0 && hoverIndex === i ? -0.1 : 0)), this.__option.radius[1] * (1 + (hoverIndex === i ? 0.05 : 0))];
var pdeg = beginDeg + currenDeg * 0.5, pradius = Math.max(this.__option.radius[0], this.__option.radius[1]);
var p0 = rotate(pie.cx, pie.cy, pdeg, pie.cx + pradius, pie.cy);
var p1 = rotate(pie.cx, pie.cy, pdeg, pie.cx + pradius + 15, pie.cy);
var pflag = p0[0] > pie.cx ? 1 : -1;
var p2 = [p1[0] + pflag * 20, p1[1]];
var p3 = [p1[0] + pflag * 25, p1[1]];
pie.node[i] = {
value: values[i],
name: names[i],
beginDeg: beginDeg,
deg: currenDeg,
isHover: hoverIndex === i,
radius: radius,
label: {
line: [p0, p1, p2],
position: p3,
align: pflag === -1 ? "right" : "left"
}
};
beginDeg += currenDeg;
}
return pie;
};
PieLayout.prototype.bind = function (initPie, renderBack) {
this.__rback = renderBack;
this.__oralPie = initPie;
this.__prePie = this.use(this.__oralPie, this.__hoverIndex);
this.__rback(this.__prePie);
return this;
};
PieLayout.prototype.unbind = function () {
this.__rback = function () { return null; };
this.__oralPie = null;
this.__prePie = null;
this.__hoverIndex = -1;
return this;
};
PieLayout.prototype.setHover = function (index) {
if (!this.__prePie || this.__hoverIndex === index)
return this;
this.__hoverIndex = index;
this.doUpdate();
return this;
};
PieLayout.prototype.doUpdate = function () {
var _this = this;
var newPie = this.use(this.__oralPie, this.__hoverIndex);
var cachePie = JSON.parse(JSON.stringify(newPie));
animation(function (deep) {
if (_this.__prePie) {
for (var i = 0; i < cachePie.count; i++) {
cachePie.node[i].radius[0] = _this.__prePie.node[i].radius[0] + (newPie.node[i].radius[0] - _this.__prePie.node[i].radius[0]) * deep;
cachePie.node[i].radius[1] = _this.__prePie.node[i].radius[1] + (newPie.node[i].radius[1] - _this.__prePie.node[i].radius[1]) * deep;
}
}
_this.__rback(cachePie);
}, this.__option.duration, function () {
_this.__prePie = newPie;
_this.__rback(_this.__prePie);
});
return this;
};
return PieLayout;
}());
export { PieLayout as default };