基于Vue3的数据大屏标尺设计与实现

引言

在数据驱动的时代,数据大屏以其直观、实时的数据展示效果,成为了各行各业数据监控和分析的重要工具。本文将详细介绍如何在Vue3框架下设计与实现一个功能强大的数据大屏标尺,包括双指缩放、标尺跟随缩放、高亮显示选中图表的x轴y轴位置,以及鼠标滚轮缩放和平移功能。
image.png

现状

现版本爱分析数据大屏只能手动通过Slider滑块进行调整,精度不高,效率不高,本期优化,5.28上线,敬请期待!
image.png

标尺设计与实现

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>

结论

通过添加缩放标尺功能,可以显著提高用户配置大屏的效率。在现有的爱分析版本中,缩放画布只能通过配置区最上方的画布比例进行手动控制,这不仅准确度不高,而且操作费力。新增的双指缩放功能能够有效解决这些问题,使用户能够更直观、便捷地调整画布。同时,标尺刻度与选中图表的对应功能也能精准地帮助用户定位到当前想查看或配置的图表位置,避免了肉眼对比图表位置关系的繁琐过程。这些改进大大提升了数据大屏的用户体验和操作效率。

感谢观看。

  • 4
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
基于Vue的可视化大屏展示的设计实现需要从以下几个方面入手: 1. 数据获取和处理:首先需要确定要展示的数据源,并通过Vue的异步请求或WebSocket等技术获取数据。在数据获取后,还需要对数据进行处理和格式化,以满足展示需求。 2. 组件设计和分层:根据可视化大屏的布局和需求,在Vue设计各个展示组件。可以根据功能和复用性将组件进行分层,例如底层的图表组件、中间层的数据展示组件和顶层的控制和交互组件。 3. 数据和视图绑定:Vue采用了单向数据流的概念,数据的变化会自动更新视图。在大屏展示中,可以通过Vue数据绑定机制实现数据和视图的同步更新,即当数据更新时,视图也会同步更新,反之亦然。 4. 动画和过渡效果:为了增加页面的交互性和视觉效果,在可视化大屏展示中可以使用Vue的过渡和动画效果。可以利用Vue提供的Transition组件和动画钩子函数,实现元素的渐变、平移、缩放等效果,增强页面的动感和美观性。 5. 状态管理和事件处理:大屏展示通常需要处理大量的数据和用户事件,为了简化数据的管理和事件的处理,可以使用Vue的状态管理工具,例如Vuex。通过Vuex可以统一管理数据,方便组件之间的数据通信和状态同步,同时还可以利用Vuex的事件机制处理用户的交互和操作。 综上所述,基于Vue的可视化大屏展示的设计实现需要考虑数据获取和处理、组件设计和分层、数据和视图的绑定、动画和过渡效果以及状态管理和事件处理等方面。通过合理的设计实现,可以开发出功能强大、易用且美观的可视化大屏展示应用。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

疾风亦有归途ぃ

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值