Bootstrap

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: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:调整图例的位置,不然默认是在右侧

      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必须和上一条数据不一致不然可能会导致数据更新失败

   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>

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

;