vue2制作长方形容器,正方形网格散点图,并且等比缩放拖动

news/2025/1/16 2:29:07 标签: javascript, vue, 前端

需求:有个长方形的容器,但是需要正方形的网格线,网格线是等比缩放的并且可以无线拖动的,并且添加自适应缩放和动态切换,工具是plotly.js,已完成功能如下

1.正方形网格

2.散点分组

3.自定义悬浮框的数据

4.根据窗口大小自适应缩放

5.解决数据过大或者过小网格线较少问题

6.解决项目引入plotly.js失败问题

1.效果

录像有点看不清,项目运行的话是正方形的

2.下载插件

2.1下载plotly.js

npm install plotly.js-dist-min

github的插件地址 ,里面有引入步骤

GitHub - plotly/plotly.js: Open-source JavaScript charting library behind Plotly and Dash 

plotly.js官网如下(全英文),里面有案例可以看

Plotly javascript graphing library in JavaScript

2.2下载linq.js

npm install linq

2.3 引入(解决引入报错)

通常都是第一种import的方式引入

javascript">// ES6 module
import Plotly from 'plotly.js-dist-min'

// CommonJS
var Plotly = require('plotly.js-dist-min')

注意!!!如果引入后运行代码提示BREAKING CHANGE: webpack < 5 used to include polyfills for node.js core modules by default. 可以看看这篇文章,完美解决Vue-解决BREAKING CHANGE: webpack < 5 used to include polyfills for node.js core modules by default._breaking change: webpack < 5 used to include polyf-CSDN博客

3.代码详解

3.1 添加容器

   <div id="gd" ref="chart" style="width: 100%; height: 90vh"></div>

 3.2 模拟散点数据

javascript">{
  "points": [
    {
      "id": 1553,
      "project_id": 11,
      "name": "1504",
      "x": -2.456,
      "y": 25440.3437,
      "z": -2.5487
    },
    {
      "id": 1554,
      "project_id": 11,
      "name": "1503",
      "point_type": 2,
      "x": -1.7427000000000001,
      "y": 25440.4148,
      "z": -2.5736
    },
    {
      "id": 1555,
      "project_id": 11,
      "name": "1502",
      "point_type": 1,
      "x": 0.5841,
      "y": 25440.6208,
      "z": -0.7072
    },
  ]
}

3.3 画散点图

需要准备容器的id,data和layout还有配置项,有俩种写法,不做数据切换的时候可以用Plotly.newPlot,做数据切换的时候使用Plotly.react ,其他功能看看这个Function reference in JavaScript

javascript">    Plotly.react(
        "gd",
        this.data,
        this.layout,
        this.options
      );

 3.4 配置data

配置项地址: 

Scatter traces in JavaScript

 通过linq根据数据源的point_type进行分组,一共分为4组散点数据图例分别为点组1234,

  1. x,y:点在图形的位置,格式为[1,2,3,4]
  2. text:点名称
  3. type: "scatter" 散点图
  4. marker:给散点根据组设置不同的样式,这个点目前没看到有自定义样式的选项,但是可供选择的样式有很多,可以根据官网进行配置

  5. name:图例的名称

  6. hovertemplate:鼠标滑到点上显示的悬浮框样式

  7. customdata:自定义数据,作用就是把z的数据添加到悬浮框中

javascript">      const groupedData = Enumerable.from(data)
        .groupBy((point) => point.point_type)
        .select((group) => {
          const xValues = group.select((point) => point.x).toArray();
          const yValues = group.select((point) => point.y).toArray();
          const zValues = group.select((point) => point.z).toArray();
          const textValues = group.select((point) => point.name).toArray();

          return {
            x: xValues,
            y: yValues,
            text: textValues,
            type: "scatter",
            mode: "markers",
            marker: {
              size: 12,
              sizemode: "diameter",
              symbol: (() => {
                switch (group.key()) {
                  case 1:
                    return "232";
                  case 2:
                    return "222";
                  case 3:
                    return "diamond";
                  default:
                    return "triangle-up";
                }
              })(),
            },
            name: (() => {
              switch (group.key()) {
                case 1:
                  return "点组1";
                case 2:
                  return "点组2";
                case 3:
                  return "点组3";
                default:
                  return "点组4";
              }
            })(),
           hovertemplate: `点名:%{text} <br> X: %{x}<br>Y: %{y}<br>Z:%{customdata}<br><extra></extra>`,
            customdata: zValues, //自定义数据
          };
        })
        .toArray();

3.5 配置layout(重点)

  正方形网格代码,必须设置

         scaleanchor: "y", // 将X轴的缩放锚定到Y轴

         scaleratio: 1, // 设置X轴和Y轴的比例为1:1

并且后续需要设置xaxis.dtick和yaxis.dtick的刻度间隔设置为一致,不然不是正方形

javascript">        xaxis: {
          dtick: null, // 设置X轴刻度间隔(不需要设置范围,只需要对齐yaxis的间隔设置一致就可以使图形是正方形并且等比缩放了)
          scaleanchor: "y", // 将X轴的缩放锚定到Y轴
          scaleratio: 1, // 设置X轴和Y轴的比例为1:1
          autorange: true,
          // range: [1, 5], // 初始X轴范围
        },

代码我写了备注,有些细节注意!!如果要做数据切换必须设置   uirevision: "true",  autorange: true

dragmode:“pan”,,配置项还有:"zoom" | "pan" | "select" | "lasso" | "drawclosedpath" | "drawopenpath" | "drawline" | "drawrect" | "drawcircle" | "orbit" | "turntable" | false

showlegend:显示图例

legend:调整图例的位置,不然默认是在右侧

javascript">      layout: {
        margin: { t: 0 }, //canvas对顶部的距离
        xaxis: {
          dtick: null, // 设置X轴刻度间隔(不需要设置范围,只需要对齐yaxis的间隔设置一致就可以使图形是正方形并且等比缩放了)
          scaleanchor: "y", // 将X轴的缩放锚定到Y轴
          scaleratio: 1, // 设置X轴和Y轴的比例为1:1
          autorange: true,
          // range: [1, 5], // 初始X轴范围
        },
        yaxis: {
          // dtick: 1000, // 设置Y轴刻度间隔
          dtick: null, // 设置X轴刻度间隔
          autorange: true,
          // range: [1, 100], // 初始Y轴范围
        },
        // dragmode: "zoom",
        dragmode: "pan", // 启用平移功能
        showlegend: true,
        uirevision: "true",
        legend: {
          x: 0.5, // 图例的x坐标
          y: 1, // 图例的y坐标
          xanchor: "center", // 图例水平居中
          yanchor: "bottom", // 图例底部对齐
          orientation: "h", // 图例横向排列
        },
      },

3.6 设置 

这段代码意思就是为了保证无论数据过大还是过小的情况下都能显示多个网格线,不会导致数据只会出现一条网格线,并且数据更新的时候都得重新设置

  this.layout.xaxis.autorange = true;

      this.layout.yaxis.autorange = true;

      this.layout.uirevision = maxY; uirevision必须和上一条数据不一致不然可能会导致数据更新失败

javascript">   calculateDtick(maxY, maxX, minY, minX) {
      // 计算绝对值
      const absX = Math.abs(maxX);
      const absY = Math.abs(maxY);

      // 取较大的绝对值
      const maxAbs = Math.max(absX, absY);
      const minAbs = Math.min(minY, minY);

      // 最大值减最小值除以 5(也就是默认分为5等分)
      let cz = Math.abs(maxAbs - minAbs);
      let result = cz / 5;

      // 将结果转换为整数,并且确保结果是 10 的倍数
      let roundedResult = Math.round(result / 10) * 10;
      console.log(roundedResult, "rounded result");
      // 确保 dtick 不为 0
      if (roundedResult < 1) {
        roundedResult = 1;
      }
      this.layout.xaxis.dtick = roundedResult;
      this.layout.yaxis.dtick = roundedResult;
      console.log(
        this.data,
        this.layout,
        this.options,
        this.layout.xaxis.dtick,
        "data数据"
      );
      this.layout.xaxis.autorange = true;
      this.layout.yaxis.autorange = true;
      this.layout.uirevision = maxY;
      // newPlot
      Plotly.react(
        "gd",
        this.data,
        this.layout,
        this.options
        // /* JSON object */ {
        //   data: ,
        //   layout: ,
        //   options: ,
        // }
      );
    },

4.完整代码

<template>
  <div style="width: 100%; height: 100%">
    <button @click="updateChart()">修改代码</button>
    <button @click="updateChartOne()">修改代码22</button>
    <div id="gd" ref="chart" style="width: 100%; height: 90vh"></div>
  </div>
</template>

<script>
import Plotly from "plotly.js-dist-min";
import dataJson from "@/utils/data.json";
import Enumerable from "linq";
export default {
  data() {
    return {
      data: [],
      layout: {
        margin: { t: 0 }, //canvas对顶部的距离
        xaxis: {
          dtick: null, // 设置X轴刻度间隔(不需要设置范围,只需要对齐yaxis的间隔设置一致就可以使图形是正方形并且等比缩放了)
          scaleanchor: "y", // 将X轴的缩放锚定到Y轴
          scaleratio: 1, // 设置X轴和Y轴的比例为1:1
          autorange: true,
          // range: [1, 5], // 初始X轴范围
        },
        yaxis: {
          // dtick: 1000, // 设置Y轴刻度间隔
          dtick: null, // 设置X轴刻度间隔
          autorange: true,
          // range: [1, 100], // 初始Y轴范围
        },
        // dragmode: "zoom",
        dragmode: "pan", // 启用平移功能
        showlegend: true,
        uirevision: "true",
        legend: {
          x: 0.5, // 图例的x坐标
          y: 1, // 图例的y坐标
          xanchor: "center", // 图例水平居中
          yanchor: "bottom", // 图例底部对齐
          orientation: "h", // 图例横向排列
        },
      },
      options: {
        scrollZoom: true, //启用缩放功能
        displayModeBar: false, //不显示操作栏
        responsive: true, //制作响应式图表
        // tickmode: auto,
        // nticks: 10000,
      },
    };
  },
  mounted() {
    this.getData(dataJson.points, false);
  },
  methods: {
    updateChart() {
      this.getData(
        [
          {
            id: 2711,
            project_id: 11,
            name: "aa222",
            x: 10,
            y: 22,
            z: 332,
          },
        ],
      );
    },
    // 2126.474 1205.8596 1930.9592 850.2585
    updateChartOne() {
      this.getData(
        [
          {
            id: 2711,
            project_id: 11,
            name: "aa222",
            point_type: 2,
            x: 2126.474,
            y: 1205.8596,
            z: 332,
          },
          {
            id: 2712,
            project_id: 11,
            name: "aa333",
            point_type: 1,
            x: 850.2585,
            y: 1930.9592,
            z: 1110,
          },
          {
            id: 2712,
            project_id: 11,
            name: "aa333",
            point_type: 1,
            x: 1000,
            y: 1000,
            z: 1110,
          },
        ],
      );
    },
    // 模拟数据
    getData(data) {
      // this.layout.xaxis.range = [];
      // this.layout.yaxis.range = [];
      const groupedData = Enumerable.from(data)
        .groupBy((point) => point.point_type)
        .select((group) => {
          const xValues = group.select((point) => point.x).toArray();
          const yValues = group.select((point) => point.y).toArray();
          const zValues = group.select((point) => point.z).toArray();
          const textValues = group.select((point) => point.name).toArray();
          // 假设我们有一个函数可以根据 point_type 计算 z 值
          return {
            x: xValues,
            y: yValues,
            text: textValues,
            type: "scatter",
            mode: "markers",
            marker: {
              size: 12,
              sizemode: "diameter",
              symbol: (() => {
                switch (group.key()) {
                  case 1:
                    return "232";
                  case 2:
                    return "222";
                  case 3:
                    return "diamond";
                  default:
                    return "triangle-up";
                }
              })(),
            },
            name: (() => {
              switch (group.key()) {
                case 1:
                  return "点组1";
                case 2:
                  return "点组2";
                case 3:
                  return "点组3";
                default:
                  return "点组4";
              }
            })(),
            hovertemplate: `点名:%{text} <br> X: %{x}<br>Y: %{y}<br>Z:%{customdata}<br><extra></extra>`,
            customdata: zValues, //自定义数据
          };
        })
        .toArray();
      const maxY = Enumerable.from(data).max("$.y");
      const maxX = Enumerable.from(data).max("$.x");
      const minY = Enumerable.from(data).min("$.y");
      const minX = Enumerable.from(data).min("$.x");
      var len_str = (Math.ceil(maxY - minX) + "").length;
      let numStr = Math.pow(10, len_str - 1); // 初始化公共间最大间隔,
      console.log("最大 y 值:", maxY, maxX, minY, minX, numStr);
      // this.layout.xaxis.range = [1, maxX];
      // this.layout.yaxis.range = [1, maxY];

      this.data = groupedData;
      console.log(groupedData);

      this.calculateDtick(maxY, maxX, minY, minX);
    },
    calculateDtick(maxY, maxX, minY, minX) {
      // 计算绝对值
      const absX = Math.abs(maxX);
      const absY = Math.abs(maxY);

      // 取较大的绝对值
      const maxAbs = Math.max(absX, absY);
      const minAbs = Math.min(minY, minY);

      // 最大值减最小值除以 5(也就是默认分为5等分)
      let cz = Math.abs(maxAbs - minAbs);
      let result = cz / 5;

      // 将结果转换为整数,并且确保结果是 10 的倍数
      let roundedResult = Math.round(result / 10) * 10;
      console.log(roundedResult, "rounded result");
      // 确保 dtick 不为 0
      if (roundedResult < 1) {
        roundedResult = 1;
      }
      this.layout.xaxis.dtick = roundedResult;
      this.layout.yaxis.dtick = roundedResult;
      console.log(
        this.data,
        this.layout,
        this.options,
        this.layout.xaxis.dtick,
        "data数据"
      );
      this.layout.xaxis.autorange = true;
      this.layout.yaxis.autorange = true;
      this.layout.uirevision = maxY;
      // newPlot
      Plotly.react(
        "gd",
        this.data,
        this.layout,
        this.options
        // /* JSON object */ {
        //   data: ,
        //   layout: ,
        //   options: ,
        // }
      );
    },
  },
};
</script>

<style lang="scss" scoped></style>

文章到此结束,希望对你有所帮助~


http://www.niftyadmin.cn/n/5824560.html

相关文章

FPGA工程师成长四阶段

朋友&#xff0c;你有入行三年、五年、十年的职业规划吗&#xff1f;你知道你所做的岗位未来该如何成长吗&#xff1f; FPGA行业的发展近几年是蓬勃发展&#xff0c;有越来越多的人才想要或已经踏进了FPGA行业的大门。很多同学在入行FPGA之前&#xff0c;都会抱着满腹对职业发…

【优选算法篇】:模拟算法的力量--解决复杂问题的新视角

✨感谢您阅读本篇文章&#xff0c;文章内容是个人学习笔记的整理&#xff0c;如果哪里有误的话还请您指正噢✨ ✨ 个人主页&#xff1a;余辉zmh–CSDN博客 ✨ 文章所属专栏&#xff1a;优选算法篇–CSDN博客 文章目录 一.模拟算法二.例题1.替换所有的问号2.提莫攻击3.外观数列4…

《零基础Go语言算法实战》【题目 4-2】使用 Go 语言实现一个模拟栈数据结构操作的类 FrequencyStack

《零基础Go语言算法实战》 【题目 4-2】使用 Go 语言实现一个模拟栈数据结构操作的类 FrequencyStack FrequencyStack 有两个功能&#xff1a;push(int x) 方法将整数 x 压入栈&#xff0c;pop() 方法将栈中出现频次 最高的元素删除并返回&#xff1b;如果出现频次最高的元素…

在 Webpack 中使用 预加载(Preloading) 技术可以通过动态导入(import())以及指定预加载的方式来进行优化

1. Webpack 中的预加载和预获取 Webpack 提供了两种注释&#xff1a; /* webpackPreload: true */&#xff1a;用于预加载当前页面需要的关键资源。/* webpackPrefetch: true */&#xff1a;用于预获取未来可能用到的资源&#xff08;如下一个页面的资源&#xff09;。 2. 如…

备战蓝桥杯 队列和queue详解

目录 队列的概念 队列的静态实现 总代码 stl的queue 队列算法题 1.队列模板题 2.机器翻译 3.海港 双端队列 队列的概念 和栈一样&#xff0c;队列也是一种访问受限的线性表&#xff0c;它只能在表头位置删除&#xff0c;在表尾位置插入&#xff0c;队列是先进先出&…

【练习】力扣热题100 有效的括号

题目 给定一个只包括 ‘(’&#xff0c;‘)’&#xff0c;‘{’&#xff0c;‘}’&#xff0c;‘[’&#xff0c;‘]’ 的字符串 s &#xff0c;判断字符串是否有效。 有效字符串需满足&#xff1a; 左括号必须用相同类型的右括号闭合。 左括号必须以正确的顺序闭合。 每个右括…

Nginx是什么?怎么用?

Nginx介绍 Nginx (读作 “engine-x”) 是一款高性能的HTTP和反向代理服务器&#xff0c;同时也可用作IMAP/POP3/SMTP代理服务器。由俄罗斯程序员Igor Sysoev开发&#xff0c;首次公开发布于2004年。Nginx以其稳定性、高性能和低内存消耗闻名&#xff0c;尤其擅长处理静态文件、…

SpringBoot之OriginTrackedPropertiesLoader类源码学习

源码解析 /*** 作用是从给定的资源&#xff08;如文件或输入流&#xff09;中加载 .properties 文件&#xff0c;* 并将属性键值对转换为带有来源信息&#xff08;origin&#xff09;的 OriginTrackedValue 对象。*/ public class OriginTrackedPropertiesLoader {private fin…