欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 新闻 > 焦点 > vue2制作长方形容器,正方形网格散点图,并且等比缩放拖动

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

2025/1/16 13:15:40 来源:https://blog.csdn.net/qq_44278289/article/details/145137912  浏览:    关键词:vue2制作长方形容器,正方形网格散点图,并且等比缩放拖动

需求:有个长方形的容器,但是需要正方形的网格线,网格线是等比缩放的并且可以无线拖动的,并且添加自适应缩放和动态切换,工具是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的方式引入

// 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 模拟散点数据

{"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

    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的数据添加到悬浮框中

      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的刻度间隔设置为一致,不然不是正方形

        xaxis: {dtick: null, // 设置X轴刻度间隔(不需要设置范围,只需要对齐yaxis的间隔设置一致就可以使图形是正方形并且等比缩放了)scaleanchor: "y", // 将X轴的缩放锚定到Y轴scaleratio: 1, // 设置X轴和Y轴的比例为1:1autorange: 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:调整图例的位置,不然默认是在右侧

      layout: {margin: { t: 0 }, //canvas对顶部的距离xaxis: {dtick: null, // 设置X轴刻度间隔(不需要设置范围,只需要对齐yaxis的间隔设置一致就可以使图形是正方形并且等比缩放了)scaleanchor: "y", // 将X轴的缩放锚定到Y轴scaleratio: 1, // 设置X轴和Y轴的比例为1:1autorange: 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必须和上一条数据不一致不然可能会导致数据更新失败

   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 不为 0if (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;// newPlotPlotly.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:1autorange: 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.2585updateChartOne() {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 不为 0if (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;// newPlotPlotly.react("gd",this.data,this.layout,this.options// /* JSON object */ {//   data: ,//   layout: ,//   options: ,// });},},
};
</script><style lang="scss" scoped></style>

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

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com