引言
在数据驱动的时代,数据大屏以其直观、实时的数据展示效果,成为了各行各业数据监控和分析的重要工具。本文将详细介绍如何在Vue3框架下设计与实现一个功能强大的数据大屏标尺,包括双指缩放、标尺跟随缩放、高亮显示选中图表的x轴y轴位置,以及鼠标滚轮缩放和平移功能。
现状
现版本爱分析数据大屏只能手动通过Slider滑块进行调整,精度不高,效率不高,本期优化,5.28上线,敬请期待!
标尺设计与实现
1. 初始化Canvas
在主文件中创建一个Canvas元素用于绘制图表和标尺。
<template>
<div id="app">
<canvas ref="canvas" width="2000" height="1000"></canvas>
</div>
</template>
<script>
export default {
name: 'App',
mounted() {
this.initCanvas();
},
methods: {
initCanvas() {
const canvas = this.$refs.canvas;
const ctx = canvas.getContext('2d');
this.drawChart(ctx);
this.drawRuler(ctx);
},
drawChart(ctx) {
// 绘制示例图表代码
},
drawRuler(ctx) {
// 绘制标尺代码
}
}
};
</script>
<style>
#app {
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
background-color: #2c3e50;
}
canvas {
background-color: #1f1f1f;
}
</style>
2. 双指缩放与鼠标滚轮缩放
添加触摸事件监听器和鼠标滚轮事件监听器,实现双指缩放和鼠标滚轮缩放功能。
<script>
export default {
name: 'App',
data() {
return {
scale: 1,
initialDistance: 0,
canvasPosition: { x: 0, y: 0 }
};
},
mounted() {
this.initCanvas();
this.addTouchEvents();
this.addWheelEvent();
},
methods: {
initCanvas() {
const canvas = this.$refs.canvas;
const ctx = canvas.getContext('2d');
this.drawChart(ctx);
this.drawRuler(ctx);
},
drawChart(ctx) {
// 绘制示例图表代码
},
drawRuler(ctx) {
const step = 50 * this.scale; // 动态调整步长
const canvas = this.$refs.canvas;
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.beginPath();
ctx.strokeStyle = '#888';
for (let x = 0; x < canvas.width; x += step) {
ctx.moveTo(x, 0);
ctx.lineTo(x, canvas.height);
}
for (let y = 0; y < canvas.height; y += step) {
ctx.moveTo(0, y);
ctx.lineTo(canvas.width, y);
}
ctx.stroke();
},
addTouchEvents() {
const canvas = this.$refs.canvas;
canvas.addEventListener('touchstart', this.handleTouchStart, false);
canvas.addEventListener('touchmove', this.handleTouchMove, false);
canvas.addEventListener('touchend', this.handleTouchEnd, false);
},
handleTouchStart(event) {
if (event.touches.length === 2) {
this.initialDistance = this.getDistance(event.touches[0], event.touches[1]);
}
},
handleTouchMove(event) {
if (event.touches.length === 2) {
const currentDistance = this.getDistance(event.touches[0], event.touches[1]);
this.scale = currentDistance / this.initialDistance;
this.redrawCanvas();
}
},
handleTouchEnd(event) {
this.initialDistance = 0;
},
getDistance(touch1, touch2) {
return Math.sqrt((touch1.pageX - touch2.pageX) ** 2 + (touch1.pageY - touch2.pageY) ** 2);
},
addWheelEvent() {
const canvas = this.$refs.canvas;
canvas.addEventListener('wheel', this.handleWheel, false);
},
handleWheel(event) {
event.preventDefault();
const zoomFactor = 1.1;
if (event.deltaY < 0) {
this.scale *= zoomFactor;
} else {
this.scale /= zoomFactor;
}
this.redrawCanvas();
},
redrawCanvas() {
const canvas = this.$refs.canvas;
const ctx = canvas.getContext('2d');
ctx.setTransform(this.scale, 0, 0, this.scale, this.canvasPosition.x, this.canvasPosition.y);
this.drawChart(ctx);
this.drawRuler(ctx);
}
}
};
</script>
3. 标尺跟随缩放与平移
在缩放的基础上,添加Canvas的拖拽平移功能,使得标尺和图表能够跟随平移。
<script>
export default {
name: 'App',
data() {
return {
scale: 1,
initialDistance: 0,
canvasPosition: { x: 0, y: 0 },
isDragging: false,
lastPosition: { x: 0, y: 0 }
};
},
mounted() {
this.initCanvas();
this.addTouchEvents();
this.addWheelEvent();
this.addDragEvent();
},
methods: {
initCanvas() {
const canvas = this.$refs.canvas;
const ctx = canvas.getContext('2d');
this.drawChart(ctx);
this.drawRuler(ctx);
},
drawChart(ctx) {
// 绘制示例图表代码
ctx.fillStyle = 'gold';
ctx.fillRect(100, 100, 200, 50); // 示例矩形
},
drawRuler(ctx) {
const step = 50 * this.scale;
const canvas = this.$refs.canvas;
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.beginPath();
ctx.strokeStyle = '#888';
for (let x = 0; x < canvas.width; x += step) {
ctx.moveTo(x, 0);
ctx.lineTo(x, canvas.height);
}
for (let y = 0; y < canvas.height; y += step) {
ctx.moveTo(0, y);
ctx.lineTo(canvas.width, y);
}
ctx.stroke();
if (this.selectedPoint) {
ctx.strokeStyle = 'red';
ctx.moveTo(this.selectedPoint.x, 0);
ctx.lineTo(this.selectedPoint.x, canvas.height);
ctx.moveTo(0, this.selectedPoint.y);
ctx.lineTo(canvas.width, this.selectedPoint.y);
ctx.stroke();
}
},
addTouchEvents() {
const canvas = this.$refs.canvas;
canvas.addEventListener('touchstart', this.handleTouchStart, false);
canvas.addEventListener('touchmove', this.handleTouchMove, false);
canvas.addEventListener('touchend', this.handleTouchEnd, false);
},
handleTouchStart(event) {
if (event.touches.length === 2) {
this.initialDistance = this.getDistance(event.touches[0], event.touches[1]);
} else if (event.touches.length === 1) {
this.isDragging = true;
this.lastPosition = {
x: event.touches[0].pageX,
y: event.touches[0].pageY
};
}
},
handleTouchMove(event) {
if (event.touches.length === 2) {
const currentDistance = this.getDistance(event.touches[0], event.touches[1]);
this.scale = currentDistance / this.initialDistance;
this.redrawCanvas();
} else if (event.touches.length === 1 && this.isDragging) {
const currentPosition = {
x: event.touches[0].pageX,
y: event.touches[0].pageY
};
this.canvasPosition.x += currentPosition.x - this.lastPosition.x;
this.canvasPosition.y += currentPosition.y - this.lastPosition.y;
this.lastPosition = currentPosition;
this.redrawCanvas();
}
},
handleTouchEnd(event) {
this.initialDistance = 0;
this.isDragging = false;
},
getDistance(touch1, touch2) {
return Math.sqrt((touch1.pageX - touch2.pageX) ** 2 + (touch1.pageY - touch2.pageY) ** 2);
},
addWheelEvent() {
const canvas = this.$refs.canvas;
canvas.addEventListener('wheel', this.handleWheel, false);
},
handleWheel(event) {
event.preventDefault();
const zoomFactor = 1.1;
if (event.deltaY < 0) {
this.scale *= zoomFactor;
} else {
this.scale /= zoomFactor;
}
this.redrawCanvas();
},
addDragEvent() {
const canvas = this.$refs.canvas;
canvas.addEventListener('mousedown', this.handleMouseDown, false);
canvas.addEventListener('mousemove', this.handleMouseMove, false);
canvas.addEventListener('mouseup', this.handleMouseUp, false);
canvas.addEventListener('mouseleave', this.handleMouseUp, false);
},
handleMouseDown(event) {
this.isDragging = true;
this.lastPosition = { x: event.clientX, y: event.clientY };
},
handleMouseMove(event) {
if (this.isDragging) {
const currentPosition = { x: event.clientX, y: event.clientY };
this.canvasPosition.x += currentPosition.x - this.lastPosition.x;
this.canvasPosition.y += currentPosition.y - this.lastPosition.y;
this.lastPosition = currentPosition;
this.redrawCanvas();
}
},
handleMouseUp(event) {
this.isDragging = false;
},
redrawCanvas() {
const canvas = this.$refs.canvas;
const ctx = canvas.getContext('2d');
ctx.setTransform(this.scale, 0, 0, this.scale, this.canvasPosition.x, this.canvasPosition.y);
this.drawChart(ctx);
this.drawRuler(ctx);
}
}
};
</script>
4. 高亮显示选中的数据点
在Canvas上监听点击事件,当用户点击数据点时,高亮显示该点的x轴和y轴。
<script>
export default {
name: 'App',
data() {
return {
scale: 1,
initialDistance: 0,
canvasPosition: { x: 0, y: 0 },
isDragging: false,
lastPosition: { x: 0, y: 0 },
selectedPoint: null
};
},
mounted() {
this.initCanvas();
this.addTouchEvents();
this.addWheelEvent();
this.addDragEvent();
this.addClickEvent();
},
methods: {
initCanvas() {
const canvas = this.$refs.canvas;
const ctx = canvas.getContext('2d');
this.drawChart(ctx);
this.drawRuler(ctx);
},
drawChart(ctx) {
// 绘制示例图表代码
ctx.fillStyle = 'gold';
ctx.fillRect(100, 100, 200, 50); // 示例矩形
},
drawRuler(ctx) {
const step = 50 * this.scale;
const canvas = this.$refs.canvas;
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.beginPath();
ctx.strokeStyle = '#888';
for (let x = 0; x < canvas.width; x += step) {
ctx.moveTo(x, 0);
ctx.lineTo(x, canvas.height);
}
for (let y = 0; y < canvas.height; y += step) {
ctx.moveTo(0, y);
ctx.lineTo(canvas.width, y);
}
ctx.stroke();
if (this.selectedPoint) {
ctx.strokeStyle = 'red';
ctx.moveTo(this.selectedPoint.x, 0);
ctx.lineTo(this.selectedPoint.x, canvas.height);
ctx.moveTo(0, this.selectedPoint.y);
ctx.lineTo(canvas.width, this.selectedPoint.y);
ctx.stroke();
}
},
addTouchEvents() {
const canvas = this.$refs.canvas;
canvas.addEventListener('touchstart', this.handleTouchStart, false);
canvas.addEventListener('touchmove', this.handleTouchMove, false);
canvas.addEventListener('touchend', this.handleTouchEnd, false);
},
handleTouchStart(event) {
if (event.touches.length === 2) {
this.initialDistance = this.getDistance(event.touches[0], event.touches[1]);
} else if (event.touches.length === 1) {
this.isDragging = true;
this.lastPosition = {
x: event.touches[0].pageX,
y: event.touches[0].pageY
};
}
},
handleTouchMove(event) {
if (event.touches.length === 2) {
const currentDistance = this.getDistance(event.touches[0], event.touches[1]);
this.scale = currentDistance / this.initialDistance;
this.redrawCanvas();
} else if (event.touches.length === 1 && this.isDragging) {
const currentPosition = {
x: event.touches[0].pageX,
y: event.touches[0].pageY
};
this.canvasPosition.x += currentPosition.x - this.lastPosition.x;
this.canvasPosition.y += currentPosition.y - this.lastPosition.y;
this.lastPosition = currentPosition;
this.redrawCanvas();
}
},
handleTouchEnd(event) {
this.initialDistance = 0;
this.isDragging = false;
},
getDistance(touch1, touch2) {
return Math.sqrt((touch1.pageX - touch2.pageX) ** 2 + (touch1.pageY - touch2.pageY) ** 2);
},
addWheelEvent() {
const canvas = this.$refs.canvas;
canvas.addEventListener('wheel', this.handleWheel, false);
},
handleWheel(event) {
event.preventDefault();
const zoomFactor = 1.1;
if (event.deltaY < 0) {
this.scale *= zoomFactor;
} else {
this.scale /= zoomFactor;
}
this.redrawCanvas();
},
addDragEvent() {
const canvas = this.$refs.canvas;
canvas.addEventListener('mousedown', this.handleMouseDown, false);
canvas.addEventListener('mousemove', this.handleMouseMove, false);
canvas.addEventListener('mouseup', this.handleMouseUp, false);
canvas.addEventListener('mouseleave', this.handleMouseUp, false);
},
handleMouseDown(event) {
this.isDragging = true;
this.lastPosition = { x: event.clientX, y: event.clientY };
},
handleMouseMove(event) {
if (this.isDragging) {
const currentPosition = { x: event.clientX, y: event.clientY };
this.canvasPosition.x += currentPosition.x - this.lastPosition.x;
this.canvasPosition.y += currentPosition.y - this.lastPosition.y;
this.lastPosition = currentPosition;
this.redrawCanvas();
}
},
handleMouseUp(event) {
this.isDragging = false;
},
redrawCanvas() {
const canvas = this.$refs.canvas;
const ctx = canvas.getContext('2d');
ctx.setTransform(this.scale, 0, 0, this.scale, this.canvasPosition.x, this.canvasPosition.y);
this.drawChart(ctx);
this.drawRuler(ctx);
},
addClickEvent() {
const canvas = this.$refs.canvas;
canvas.addEventListener('click', this.handleCanvasClick, false);
},
handleCanvasClick(event) {
const rect = canvas.getBoundingClientRect();
const x = (event.clientX - rect.left - this.canvasPosition.x) / this.scale;
const y = (event.clientY - rect.top - this.canvasPosition.y) / this.scale;
this.selectedPoint = { x, y };
this.redrawCanvas();
}
}
};
</script>
结论
通过添加缩放标尺功能,可以显著提高用户配置大屏的效率。在现有的爱分析版本中,缩放画布只能通过配置区最上方的画布比例进行手动控制,这不仅准确度不高,而且操作费力。新增的双指缩放功能能够有效解决这些问题,使用户能够更直观、便捷地调整画布。同时,标尺刻度与选中图表的对应功能也能精准地帮助用户定位到当前想查看或配置的图表位置,避免了肉眼对比图表位置关系的繁琐过程。这些改进大大提升了数据大屏的用户体验和操作效率。