@xiaomengqiang/charts
Version:
hcharts library for web visualization
473 lines (451 loc) • 19.6 kB
JavaScript
function _createForOfIteratorHelperLoose(r, e) { var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (t) return (t = t.call(r)).next.bind(t); if (Array.isArray(r) || (t = _unsupportedIterableToArray(r)) || e && r && "number" == typeof r.length) { t && (r = t); var o = 0; return function () { return o >= r.length ? { done: !0 } : { done: !1, value: r[o++] }; }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } }
function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; }
import { formatDate } from '../../util/convert.js';
import defendXSS from '../../util/defendXSS.js';
import Theme from '../../feature/token/index.js';
import chartToken from './chartToken.js';
/**
* Copyright (c) 2024 - present OpenTiny HUICharts Authors.
* Copyright (c) 2024 - present Huawei Cloud Computing Technologies Co., Ltd.
*
* Use of this source code is governed by an MIT-style license.
*
* THE OPEN SOURCE SOFTWARE IN THIS PRODUCT IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL,
* BUT WITHOUT ANY WARRANTY, WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR
* A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS.
*
*/
var TimeLine = /*#__PURE__*/function () {
function TimeLine(canvasId, currentTime, option) {
// 显示元素
this.showElement = function (element) {
element.style.display = 'block';
};
this.hideElement = function (element) {
element.style.display = 'none';
};
this.canvas = document.getElementById(canvasId);
this.ctx = this.canvas.getContext('2d');
// 获取配置项
this.option = option;
// 当前时间
this.currentTime = currentTime;
// 自定义时间轴的背景色
this.canvas.style.background = this.option.background || 'transparent';
// 自定义时间轴刻度线的颜色
this.axisLineColor = this.option.axisLineStyle && this.option.axisLineStyle.color || Theme.config.xAxisTickLineColor;
// 自定义时间轴文本颜色
this.labelColor = this.option.labelStyle && this.option.labelStyle.color || Theme.config.xAxisLabelColor;
// 整个时间轴的时间
this.totalHours = null;
// 刻度间隔(分钟)
this.minuteStep = [1, 2, 5, 10, 15, 20, 30, 60, 120, 180, 240, 360, 720, 1440];
// 最小刻度间距20px
this.minScaleSpacing = 20;
// 允许的最小大格长度px值 如果调小 大格会变密集
this.minLargeScaleSpacing = 80;
// 缩放层级
this.zoom = 24;
// 鼠标选中位置
this.selectedPosition = [];
// 弹窗定时器
this.setTipTimeOut = null;
// 初始化时间轴
this.init();
var _this = this;
this.eventListener = {
mouseup: function mouseup(event) {
_this.clickEvent(event);
_this.hoverMove(event);
},
mousemove: function mousemove(event) {
_this.hoverMove(event);
},
mouseleave: function mouseleave(event) {
_this.init();
document.getElementsByClassName('timeline_hoverPointer')[0].style.display = 'none';
document.getElementsByClassName('timeline_hoverTooltip')[0].style.display = 'none';
}
};
this.canvas.addEventListener('mousemove', this.eventListener.mousemove);
this.canvas.addEventListener('mouseup', this.eventListener.mouseup);
this.canvas.addEventListener('mouseleave', this.eventListener.mouseleave);
}
var _proto = TimeLine.prototype;
_proto.init = function init() {
// 更新初始时间戳
this.refreshStartTimestamp();
// 清空画布
this.clearCanvas();
// 画刻度
this.drawScale();
// 画当前时间游标
this.option.currentTime && this.drawCursor(this.option.currentTime);
// 画warning事件
this.option.alarmData && this.drawAlarm(this.option);
// 画状态区间背景
this.drawRoundRect(20, this.canvas.height - 40, this.canvas.width - 40, 8, [4, 4, 4, 4]);
// 画状态区间
this.option.statusRangeData && this.drawStatusRange(this.option);
}
// 更新初始时间戳
;
_proto.refreshStartTimestamp = function refreshStartTimestamp() {
if (this.option.timeRange !== undefined) {
this.totalHours = (this.option.timeRange.endTime - this.option.timeRange.startTime) / (60 * 60 * 1000);
}
// 当currentTime改变或者整个时间轴的totalHours改变的时候 就刷新左边开始时间
this.startTimestamp = this.option.timeRange && this.option.timeRange.startTime || this.currentTime - this.totalHours * 60 * 60 * 1000;
}
// 清空画布
;
_proto.clearCanvas = function clearCanvas() {
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
}
// 画刻度
;
_proto.drawScale = function drawScale() {
// px/分钟
var oneMinutePx = (this.canvas.width - 40) / (this.totalHours * 60);
// px/毫秒
var oneMSPx = oneMinutePx / (60 * 1000);
// 刻度间隔 默认20px
var scaleSpacing = this.minScaleSpacing;
// 每格代表多少分钟
var scaleUnit = scaleSpacing / oneMinutePx;
var len = this.minuteStep.length;
// 配置项刻度间隔
if (this.option.interval !== undefined) {
scaleUnit = this.option.interval;
scaleSpacing = oneMinutePx * scaleUnit;
}
// 自动根据画布长度选择合适的刻度间隔
else {
for (var i = 0; i < len; i++) {
if (scaleUnit < this.minuteStep[i]) {
scaleUnit = this.minuteStep[i];
scaleSpacing = oneMinutePx * scaleUnit;
break;
}
}
}
// 大刻度相当于多少分钟
var mediumStep = 30;
if (this.option.mediumStep !== undefined) {
mediumStep = this.option.mediumStep;
} else {
for (var _i = 0; _i < len; _i++) {
if (this.minLargeScaleSpacing / oneMinutePx <= this.minuteStep[_i]) {
mediumStep = this.minuteStep[_i];
break;
}
}
}
var totalScales = (this.canvas.width - 40) / scaleSpacing;
// 某个刻度距离最左端的距离
var scaleLeft = 0;
// 某个刻度的时间
var scaleTime = this.startTimestamp;
var lineHeight;
var leftOffsetMs = scaleUnit * 60 * 1000 - this.startTimestamp % (scaleUnit * 60 * 1000);
// 开始时间偏移距离(px)
var leftOffsetPx = leftOffsetMs * oneMSPx;
// 一刻度多少毫秒
var oneScalesMS = scaleSpacing / oneMSPx;
var scaleLineHeight = this.canvas.height - 30;
// 画刻度线和标签
for (var _i2 = 0; _i2 < totalScales + 1; _i2++) {
var date = new Date(scaleTime);
if (scaleTime / (60 * 1000) % mediumStep === 0) {
// 大格刻度标签长度
lineHeight = 6;
var scaleText = this.createScaleText(date);
this.ctx.font = 'normal 12px HarmonyOS_Sans';
// 文字颜色
this.ctx.fillStyle = this.labelColor;
this.ctx.fillText(scaleText, scaleLeft + 20 - this.ctx.measureText(scaleText).width / 2, this.canvas.height - 4);
} else {
// 小格刻度标签长度
lineHeight = 3;
}
this.ctx.beginPath();
this.ctx.moveTo(scaleLeft + 20, scaleLineHeight);
this.ctx.lineTo(scaleLeft + 20, scaleLineHeight + lineHeight);
this.ctx.closePath();
this.ctx.lineWidth = 1;
// 刻度线颜色
this.ctx.strokeStyle = this.axisLineColor;
this.ctx.stroke();
// 距离 = 开始偏移距离 + 格数 * 每格得px;
scaleLeft = leftOffsetPx + _i2 * scaleSpacing;
// 时间 = 左侧开始时间 + 偏移时间 + 格数 * 一格多少毫秒
scaleTime = this.startTimestamp + leftOffsetMs + _i2 * oneScalesMS;
}
}
// 画当前时间游标
;
_proto.drawCursor = function drawCursor(currentTime) {
this.selectedPosition.push(this.getTimePos(currentTime), formatDate(currentTime, 'hh:mm'));
this.drawSelectedPointer(this.selectedPosition);
}
// 叠加warning事件圆点
;
_proto.drawAlarm = function drawAlarm(option) {
var alarmData = option.alarmData;
var background = option.customBackground;
for (var i = 0; i < alarmData.length; i++) {
var x = this.getTimePos(alarmData[i].time);
var type = alarmData[i].type;
this.ctx.beginPath();
this.ctx.arc(x, this.canvas.height - 60, 7, 0, 2 * Math.PI);
this.ctx.closePath();
this.ctx.fillStyle = background[type] || Theme.config.colorState.colorNone;
this.ctx.fill();
}
}
// 叠加状态区间
;
_proto.drawStatusRange = function drawStatusRange(option) {
var statusRangeData = option.statusRangeData;
var timeRange = option.timeRange;
var background = option.customBackground;
for (var i = 0; i < statusRangeData.length; i++) {
var startPosition = this.getTimePos(statusRangeData[i].time[0]);
var endPosition = this.getTimePos(statusRangeData[i].time[1]);
var type = statusRangeData[i].type;
this.ctx.beginPath();
if (statusRangeData[i].time[0] === timeRange.startTime && statusRangeData[i].time[1] === timeRange.endTime) {
this.roundRect(startPosition, this.canvas.height - 40, endPosition - startPosition, 8, [4, 4, 4, 4]);
} else if (statusRangeData[i].time[0] === timeRange.startTime) {
this.roundRect(startPosition, this.canvas.height - 40, endPosition - startPosition, 8, [4, 0, 0, 4]);
} else if (statusRangeData[i].time[1] === timeRange.endTime) {
this.roundRect(startPosition, this.canvas.height - 40, endPosition - startPosition, 8, [0, 4, 4, 0]);
} else {
this.ctx.rect(startPosition, this.canvas.height - 40, endPosition - startPosition, 8);
}
this.ctx.closePath();
this.ctx.fillStyle = background[type] || Theme.config.colorState.colorNone;
this.ctx.fill();
}
}
// 绘制状态区间背景的圆角矩形
;
_proto.drawRoundRect = function drawRoundRect(x, y, width, height, radius) {
this.ctx.beginPath();
this.roundRect(x, y, width, height, radius);
this.ctx.closePath();
this.ctx.fillStyle = chartToken.roundRectBg;
this.ctx.fill();
};
_proto.roundRect = function roundRect(x, y, width, height, radiusArray) {
// 左上角
this.ctx.moveTo(x + radiusArray[0], y);
this.ctx.lineTo(x + width - radiusArray[1], y);
this.ctx.arcTo(x + width, y, x + width, y + radiusArray[1], radiusArray[1]);
// 右上角
this.ctx.lineTo(x + width, y + height - radiusArray[2]);
this.ctx.arcTo(x + width, y + height, x + width - radiusArray[2], y + height, radiusArray[2]);
// 右下角
this.ctx.lineTo(x + radiusArray[3], y + height);
this.ctx.arcTo(x, y + height, x, y + height - radiusArray[3], radiusArray[3]);
// 左下角
this.ctx.lineTo(x, y + radiusArray[0]);
this.ctx.arcTo(x, y, x + radiusArray[0], y, radiusArray[0]);
}
// 鼠标悬浮事件
;
_proto.hoverMove = function hoverMove(event) {
var mouseX = this.getMouseXRelativePos(event);
var hoverPointer = document.getElementsByClassName('timeline_hoverPointer')[0];
var hoverTooltip = document.getElementsByClassName('timeline_hoverTooltip')[0];
var hoverTime = this.getMousePosTime(event);
if (mouseX > 0 && mouseX < this.canvas.width - 40) {
this.showElement(hoverPointer);
// 开启悬浮指针的时间悬浮框
if (this.option.timeTooltip === true) {
this.showElement(hoverTooltip);
hoverTooltip.innerHTML = "<p>" + formatDate(hoverTime, 'yy-MM-dd hh:mm') + "</p>";
if (mouseX + 142 > this.canvas.width - 40) {
hoverTooltip.style.left = mouseX - 1 + 20 - 142 + 'px';
} else {
hoverTooltip.style.left = mouseX - 1 + 30 + 'px';
}
hoverTooltip.style.top = this.canvas.height - 30 + 'px';
}
hoverPointer.style.left = mouseX - 1 + 20 + 'px';
hoverPointer.style.top = this.canvas.height - 30 + 'px';
} else {
this.hideElement(hoverPointer);
this.hideElement(hoverTooltip);
}
this.setAlarmTip(event, mouseX);
}
// 设置Alarm点的tip
;
_proto.setAlarmTip = function setAlarmTip(event, mouseX) {
var _this2 = this;
var mouseY = this.getMouseYRelativePos(event);
var alarmTooltip = document.getElementsByClassName('timeline_alarmTooltip')[0];
// 清除定时
alarmTooltip.addEventListener('mouseenter', function (e) {
if (_this2.setTipTimeOut) {
clearTimeout(_this2.setTipTimeOut);
}
});
// 移出悬浮框关闭
alarmTooltip.addEventListener('mouseleave', function (e) {
_this2.closeAlarmTip(alarmTooltip);
});
var alarmData = this.option.alarmData;
if (alarmData) {
this.processAlarmData(alarmData, mouseX, mouseY, alarmTooltip);
}
};
_proto.closeAlarmTip = function closeAlarmTip(alarmTooltip) {
var _this3 = this;
// 增加延时
this.setTipTimeOut = setTimeout(function () {
_this3.hideElement(alarmTooltip);
}, 200);
};
_proto.processAlarmData = function processAlarmData(alarmData, mouseX, mouseY, alarmTooltip) {
for (var _iterator = _createForOfIteratorHelperLoose(alarmData), _step; !(_step = _iterator()).done;) {
var data = _step.value;
var x = this.getTimePos(data.time);
if (this.isMouseInCircle(mouseX + 20, mouseY, x, this.canvas.height - 60)) {
if (this.setTipTimeOut) {
clearTimeout(this.setTipTimeOut);
}
this.showElement(alarmTooltip);
this.option.onCircleHover(alarmTooltip, data);
if (x - alarmTooltip.offsetWidth / 2 < 0) {
alarmTooltip.style.left = '0px';
} else if (x - alarmTooltip.offsetWidth / 2 > this.canvas.width - alarmTooltip.offsetWidth - 40) {
alarmTooltip.style.left = this.canvas.width - alarmTooltip.offsetWidth + 'px';
} else {
alarmTooltip.style.left = x - alarmTooltip.offsetWidth / 2 + 'px';
}
alarmTooltip.style.top = this.canvas.height - 80 - alarmTooltip.clientHeight + 'px';
return;
}
// 移出圆点清除悬浮框
if (this.setTipTimeOut) {
clearTimeout(this.setTipTimeOut);
}
this.closeAlarmTip(alarmTooltip);
}
}
// 判断鼠标是否在圆点内
;
_proto.isMouseInCircle = function isMouseInCircle(mouseX, mouseY, x, y) {
var distance = Math.sqrt(Math.pow(mouseX - x, 2) + Math.pow(mouseY - y, 2));
return distance <= 8;
}
// 鼠标点击事件
;
_proto.clickEvent = function clickEvent(event) {
// 进入选中状态
this.isSelected = true;
var mouseX = this.getMouseXRelativePos(event);
var time = this.getMousePosTime(event);
// 选中时间
var seletedTime = time.getTime();
// 当前北京时间
var currentTime = new Date().getTime();
if (seletedTime <= currentTime) {
this.option.onClick && this.option.onClick(time.getTime());
// 如果有点击时间,将时间赋值给配置项的当前时间
// this.option.currentTime = time;
this.selectedPosition.splice(0, this.selectedPosition.length);
this.selectedPosition.push(mouseX + 20, formatDate(time, 'hh:mm'));
var selectedPointer = document.getElementsByClassName('timeline_selectedPointer')[0];
var selectedTooltip = document.getElementsByClassName('timeline_selectedTooltip')[0];
var selectedCircle = document.getElementsByClassName('timeline_selectedCircle')[0];
if (mouseX > 0 && mouseX < this.canvas.width - 40) {
this.drawSelectedPointer(this.selectedPosition);
} else {
this.hideElement(selectedPointer);
this.hideElement(selectedTooltip);
this.hideElement(selectedCircle);
}
}
}
// 画选中状态的游标
;
_proto.drawSelectedPointer = function drawSelectedPointer(selectedPosition) {
var selectedPointer = document.getElementsByClassName('timeline_selectedPointer')[0];
var selectedTooltip = document.getElementsByClassName('timeline_selectedTooltip')[0];
var selectedCircle = document.getElementsByClassName('timeline_selectedCircle')[0];
selectedPointer.style.left = selectedPosition[0] - 1 + 'px';
selectedPointer.style.top = this.canvas.height - 42 + 'px';
this.showElement(selectedPointer);
selectedTooltip.innerHTML = "<p>" + defendXSS(selectedPosition[1]) + "</p>";
selectedTooltip.style.left = selectedPosition[0] - 20 + 'px';
selectedTooltip.style.top = this.canvas.height - 16 + 'px';
this.showElement(selectedTooltip);
selectedCircle.style.left = selectedPosition[0] - 4 + 'px';
selectedCircle.style.top = this.canvas.height - 50 + 'px';
this.showElement(selectedCircle);
}
// 设置长短刻度标签文字
;
_proto.createScaleText = function createScaleText(time) {
if (time.getTime() === this.option.timeRange.startTime) {
return formatDate(time, '00:00');
} else if (time.getTime() === this.option.timeRange.endTime && (this.option.timeRange.endTime - this.option.timeRange.startTime) % (24 * 60 * 60 * 1000) === 0) {
return formatDate(time, '24:00');
}
return formatDate(time, 'hh:mm');
}
// 获取鼠标相对于canvas元素左侧20px的相对位置
;
_proto.getMouseXRelativePos = function getMouseXRelativePos(event) {
var scrollX = document.documentElement.scrollLeft || document.body.scrollLeft;
var x = event.pageX || event.clientX + scrollX;
// canvas元素距离窗口左侧距离
var baseLeft = this.canvas.getBoundingClientRect().x;
return x - baseLeft - 20;
}
// 获取鼠标相对于canvas元素上侧的相对位置
;
_proto.getMouseYRelativePos = function getMouseYRelativePos(event) {
var scrollY = document.documentElement.scrollTop || document.body.scrollTop;
var y = event.pageY || event.clientY + scrollY;
// canvas元素距离窗口左侧距离
var baseTop = this.canvas.getBoundingClientRect().y;
return y - baseTop;
}
// 根据画布的宽度和时间计算出鼠标所在的时间点
;
_proto.getMousePosTime = function getMousePosTime(event) {
var posX = this.getMouseXRelativePos(event);
// 每毫秒多少像素
var onePxsMS = (this.canvas.width - 40) / (this.totalHours * 60 * 60 * 1000);
var time = new Date(this.startTimestamp + posX / onePxsMS);
return time;
}
// 根据时间点计算所在点距离canvas左侧20px的相对位置
;
_proto.getTimePos = function getTimePos(time) {
// 每毫秒多少像素
var onePxsMS = (this.canvas.width - 40) / (this.totalHours * 60 * 60 * 1000);
var posX = (time - this.startTimestamp) * onePxsMS + 20;
return posX;
};
// 设置当前时间
_proto.setCurrentTime = function setCurrentTime(time) {
var newTime;
if (typeof time === 'string') {
newTime = new Date(time).getTime();
} else if (typeof time === 'object') {
newTime = time.getTime && time.getTime();
} else if (typeof time === 'number') {
newTime = time;
}
this.currentTime = newTime;
this.init();
};
return TimeLine;
}();
export { TimeLine as default };