vue中基于Echars的拖拽数据可视化
背景
我司产品提出了一个需求,做一个数据基于Echars的可拖拽缩放的数据可视化,上网百度了一番,结果出现了两种结局,一种花钱买成熟产品(公司不出钱),一种没有成熟代码,只能自己写了,故事即将开始,敬请期待......。 不,还是先上一张效果图吧,请看......
前期知识点
1. offset(偏移量)
定义:当前元素在屏幕上占用的空间,如下图:
其中:
offsetHeight: 该元素在垂直方向上的占用的空间,单位为px,不包括margin。
offsetWidth:该元素在水平方向上的占用空间,单位为px,不包括margin。
offsetLeft: 该元素距离左侧并且是定位过(relative || absolute)的父元素内边框的距离。注意:如果父元素中没有一个是定位的,则是距离body元素的距离。
offsetTop:该元素距离顶部并且是定位过(relative || absolute)的父元素内边框的距离。注意:如果父元素中没有一个是定位的,则是距离body元素的距离。
2. 鼠标事件
2.1 当前鼠标的坐标点
clientX:返回鼠标触点相对于浏览器可视区域的X坐标,单位为px,这个属性值可以根据用户对可视区的缩放行为发生变化。
clientY:返回鼠标触点相对于浏览器可视区域的Y坐标,单位为px,这个属性值可以根据用户对可视区的缩放行为发生变化。
2.2 相关的鼠标事件
ondragstart: 规定当元素被拖动时,发生什么,该属性调用一个函数,drag(event),它规定了被拖动的数据。可通过dataTransfer.setData() 方法设置被拖数据的数据类型和值:
function drag(ev)
{
ev.dataTransfer.setData("Text",ev.target.id);
}
ondragover: 规定在何处放置被拖动的数据,默认地,无法将数据/元素放置到其他元素中。如果需要设置允许放置,我们必须阻止对元素的默认处理方式。这要通过调用 ondragover 事件的 event.preventDefault() 方法:
event.preventDefault()
ondrop:当放置被拖数据时,会发生 drop 事件。ondrop 属性调用了一个函数,drop(event):
function drop(ev)
{
// 避免浏览器对数据的默认处理(drop 事件的默认行为是以链接形式打开)
ev.preventDefault();
// 获得被拖的数据。该方法将返回在 setData() 方法中设置为相同类型的任何数据。
var data=ev.dataTransfer.getData("Text");
// 把被拖元素追加到放置元素(目标元素)中
ev.target.appendChild(document.getElementById(data));
}
onMouseDown: 鼠标上的按钮被按下时触发的事件 onMouseMove:鼠标移动时触发的事件 onmouseup:鼠标按下后,松开时激发的事件
拖拽功能
本功能以Echarts图表中柱状图为例,进行讲解:
- 先定义可拖拽元素
<div>
<el-button class="drag-button" type="success" draggable="true"
@dragstart.native="dragStart($event,'histogram')">
柱状图
</el-button>
</div>
注意:元素默认是不能进行拖拽的,需要将draggable属性设置为"true",即draggable="true"
dragStart(event,type){
event.dataTransfer.setData("Text",type);
},
- 定义放置区域
<div class="grid-content bg-purple-light drag-resize-area"
@drop.prevent="drop($event)" @dragover.prevent="">
<div style="height:300px;width:400px" id="'histogram'></div>
</div>
其中drop事件如下:
drop(event){
const data=event.dataTransfer.getData("Text");
if(data === 'histogram'){
var myChart = echarts.init(document.getElementById('histogram')); // 指定图表的配置项和数据
var option = {
title: { text: 'ECharts 入门示例' },
tooltip: {}, legend: { data:['销量'] },
xAxis: { data: ["衬衫","羊毛衫","雪纺衫","裤子","高跟鞋","袜子"] },
yAxis: {}, series: [{ name: '销量', type: 'bar', data: [5, 20, 36, 10, 10, 20] }]
};
// 使用刚指定的配置项和数据显示图表。
myChart.setOption(option);
}
}
基于自己封装的组件的源码请看github:github地址 效果图如下:
拖动功能
此功能用到了上面提到的offsetLeft、offsetTop、clientX,clientY。实现思路如下: (1)当鼠标刚按下去时,记录当前元素距离带定位的父元素的offsetLeft、offsetTop距离;以及当前鼠标在浏览器可视区的坐标clientX、clientY的距离。 (2)分别计算两者的差值作为偏移常量,如下:
let disX = e.clientX - el.offsetLeft;
let disY = e.clientY - el.offsetTop;
(3)监听鼠标的移动事件,获取当前鼠标距离浏览器可是区域的坐标clientX,clientY;然后减去偏移常量,即为当前元素的坐标
let tX = e.clientX - disX;
let tY = e.clientY - disY;
el.style.left = tX + 'px';
el.style.top = tY + 'px';
说明:el为当前图表对象
由于本人封装了通用vue组件,详细拖拽方法代码如下:
// 初始化可拖拽方法
initDrag(){
let el = this.$el;
el.onmousedown = (e)=>{
e.preventDefault();
e.target.style.cursor = 'move';
//鼠标按下,计算鼠标触点距离元素左侧和顶部的距离
let disX = e.clientX - el.offsetLeft;
let disY = e.clientY - el.offsetTop;
// console.log('22222',document);
document.onmousemove = function (e) {
//计算需要移动的距离
let tX = e.clientX - disX;
let tY = e.clientY - disY;
//移动当前元素
if (tX >= 0 && tX <= window.innerWidth - el.offsetWidth) {
el.style.left = tX + 'px';
}
if (tY >= 0 && tY <= window.innerHeight - el.offsetHeight) {
el.style.top = tY + 'px';
}
};
//鼠标松开时,注销鼠标事件,停止元素拖拽。
document.onmouseup = function (e) {
document.onmousemove = null;
document.onmouseup = null;
e.target.style.cursor = 'default';
};
}
},
如果想看封装的组件,请查看github地址:github地址 拖动效果如下图:
缩放功能
此功能用到了上面提到的 offsetWidth、offsetHeight、offsetLeft、offsetTop、clientX,clientY。实现思路如下: (1)先设置可缩放的四个拖拽方框
<div v-show="isResize && resizeFlag" ref="resizeDivTag" id="resizeDivTag">
<span class="br"></span>
<span class="bl"></span>
<span class="tr"></span>
<span class="tl"></span>
</div>
其中isResize 与 resizeFlag表示是否可缩放以及是否可拖拽 (2)为每个可可缩放方框设置缩放函数,请看注释
// 初始化可缩放
initResize(){
let el = this.$el; //获取当前元素
let spanNodes = this.$refs.resizeDivTag.childNodes;
for(let i=0;i<spanNodes.length;i++){
this.resizeElementFun(spanNodes[i],el); // 分别为四个缩放方框设置监听事件
}
},
resizeElementFun(element,el){
element.onmousedown = function(ev){
console.log('我是按下的元素')
let oEv = ev || event;
oEv.stopPropagation();
let oldWidth = el.offsetWidth; // 当前元素的宽度
let oldHeight = el.offsetHeight; // 当前元素的高度
console.log('-----'+ oldWidth+'----'+oldHeight);
let oldX = oEv.clientX; // 当前鼠标对于浏览器可视区域的X坐标
let oldY = oEv.clientY; // 当前鼠标对于浏览器可视区域的Y坐标
let oldLeft = el.offsetLeft; // 当前元素对于父级定位元素的宽度
let oldTop = el.offsetTop; // 当前元素对于父级定位元素的高度
console.log('--zuo---'+ oldLeft+'--gao--'+oldTop);
document.onmousemove = function(ev){
// oEv.stopPropagation();
let oEv = ev || event;
let disY = (oldTop + (oEv.clientY - oldY)); // 当前缩放的元素距离定位父元素的高度
// let disX = (oldLeft + (oEv.clientX - oldLeft));
let disX = (oldLeft + (oEv.clientX - oldX)); // 当前缩放的元素距离定位父元素的宽度
if(disX>oldLeft+oldWidth){
disX=oldLeft+oldWidth
}
if(disY>oldTop+oldHeight){
disY=oldTop+oldHeight
}
if(element.className == 'tl'){ // 左上缩放时
el.style.width = oldWidth - (oEv.clientX - oldX) + 'px';
// 元素宽度= 缩放前宽度-鼠标当前坐标与原始坐标差值
el.style.height = oldHeight - (oEv.clientY - oldY) + 'px';
// 元素宽度= 缩放前高度-鼠标当前坐标与原始坐标差值
el.style.left = disX + 'px';
// 元素距离父元素的距离 =
el.style.top = disY + 'px';
} else if (element.className == 'bl'){ // 左下
el.style.width = oldWidth - (oEv.clientX - oldX) + 'px';
el.style.height = oldHeight + (oEv.clientY - oldY) + 'px';
el.style.left = disX + 'px';
// el.style.bottom = oldTop + (oEv.clientY + oldY) + 'px';
} else if (element.className == 'tr'){ //右上
el.style.width = oldWidth + (oEv.clientX - oldX) + 'px';
el.style.height = oldHeight - (oEv.clientY - oldY) + 'px';
el.style.right = oldLeft - (oEv.clientX - oldX) + 'px';
el.style.top = disY + 'px';
} else if (element.className == 'br'){ //右下
el.style.width = oldWidth + (oEv.clientX - oldX) + 'px';
el.style.height = oldHeight + (oEv.clientY - oldY) + 'px';
el.style.right = oldLeft - (oEv.clientX - oldX) + 'px';
// el.style.bottom = oldTop + (oEv.clientY + oldY) + 'px';
}
}
document.onmouseup = function(){
document.onmousemove = null;
};
return false;
}
},
如果想看封装的组件,请查看github地址:github地址 缩放效果如下图:
Echarts缩放中存在的问题
vue中使用echarts图表自适应的几种基本解决方案,此处不再进行赘述,详情请参考如下链接:echarts图表自适应
结束语
本文只是粗略的记录了数据可视化简单demo的实现思路,如果您觉得对您有帮助,请下载源码 github地址 喜欢的小伙伴给个star,谢谢