Quellcode durchsuchen

横向堆叠、漏斗图图形组件

liujiejie vor 7 Monaten
Ursprung
Commit
02f63e430b

+ 76 - 0
src/assets/js/constants/echarts-config/funnelPlot2.js

@@ -0,0 +1,76 @@
+/*
+ * @Author: your name
+ * @Date: 2025-08-25 15:54:37
+ * @LastEditTime: 2025-08-25 16:05:42
+ * @LastEditors: bogon
+ * @Description: In User Settings Edit
+ * @FilePath: /performance-test/src/assets/js/constants/echarts-config/funnelPlot2.js
+ */
+import { colorPalette } from "../color";
+
+export const option = {
+  color: colorPalette,
+  //   color: ["#4CAF50", "#2196F3", "#FF9800", "#E91E63", "#9C27B0"],
+  title: {
+    text: "风机运行状态分析",
+    left: "center",
+  },
+  tooltip: {
+    trigger: "item",
+    formatter: "{b} : {c}台 ({d}%)",
+  },
+  toolbox: {
+    feature: {
+      dataView: { readOnly: false },
+      restore: {},
+      saveAsImage: {},
+    },
+  },
+  legend: {
+    top: "5%",
+    data: ["正常运行", "轻微故障", "重度故障", "检修中", "停机"],
+  },
+  series: [
+    {
+      name: "风机状态",
+      type: "funnel",
+      left: "10%",
+      top: 80,
+      bottom: 60,
+      width: "80%",
+      min: 0,
+      max: 120,
+      minSize: "0%",
+      maxSize: "100%",
+      sort: "descending",
+      gap: 2,
+      label: {
+        show: true,
+        position: "inside",
+      },
+      labelLine: {
+        length: 10,
+        lineStyle: {
+          width: 1,
+          type: "solid",
+        },
+      },
+      itemStyle: {
+        borderColor: "#fff",
+        borderWidth: 1,
+      },
+      emphasis: {
+        label: {
+          fontSize: 18,
+        },
+      },
+      data: [
+        { value: 100, name: "正常运行" },
+        { value: 80, name: "轻微故障" },
+        { value: 50, name: "重度故障" },
+        { value: 30, name: "检修中" },
+        { value: 20, name: "停机" },
+      ],
+    },
+  ],
+};

+ 61 - 0
src/assets/js/constants/echarts-config/horizontalStackedBar1.js

@@ -0,0 +1,61 @@
+/*
+ * @Author: your name
+ * @Date: 2025-08-25 14:46:21
+ * @LastEditTime: 2025-08-25 14:46:59
+ * @LastEditors: bogon
+ * @Description: In User Settings Edit
+ * @FilePath: /performance-test/src/assets/js/constants/echarts-config/horizontalStackedBar1.js
+ */
+// 风机状态堆叠柱状图
+// 用途:显示风机在不同状态(运行、停机、故障)下的时间分布。
+// 适用场景:风机运行监控。
+// 实现建议:
+// 使用堆叠柱状图。
+import { colorPalette } from "../color";
+export const option = {
+  color: colorPalette,
+  title: {
+    text: "风机状态",
+    name: "时间 (小时)",
+    type: "value",
+  },
+  xAxis: {},
+  legend: {
+    top: "bottom",
+  },
+  toolbox: {
+    feature: {
+      dataView: { show: true, readOnly: false },
+      magicType: { show: true, type: ["line", "bar"] },
+      restore: { show: true },
+      saveAsImage: { show: true },
+    },
+  },
+  yAxis: {
+    type: "category",
+    data: ["风机1", "风机2", "风机3", "风机4"], // 风机编号
+  },
+  series: [
+    {
+      name: "运行",
+      type: "bar",
+      stack: "总量",
+      data: [10, 15, 20, 25],
+    },
+    {
+      name: "停机",
+      type: "bar",
+      stack: "总量",
+      data: [5, 5, 4, 6],
+    },
+    {
+      name: "故障",
+      type: "bar",
+      stack: "总量",
+      data: [1, 0, 2, 3],
+    },
+  ],
+  tooltip: {
+    trigger: "axis",
+  },
+};

+ 7 - 7
src/views/performance/components/custonAsCom/dragChart/components/chartConfig/config.js

@@ -6,13 +6,13 @@ export const configApi = [
     name: "configText", //tab name
     desc: "标题组件,包含主标题和副标题。",
   },
-  // {
-  //   label: "调色工具栏", //提示文本
-  //   value: "toolbox", //组件名
-  //   icon: "styleChart1", //图标
-  //   name: "configStyleChart", //tab name
-  //   desc: "标题组件,包含主标题和副标题。",
-  // },
+  {
+    label: "调色工具栏", //提示文本
+    value: "toolbox", //组件名
+    icon: "styleChart1", //图标
+    name: "configStyleChart", //tab name
+    desc: "标题组件,包含主标题和副标题。",
+  },
   // {
   //   label: "背景色",
   //   value: "backgroundColor",

+ 3 - 1
src/views/performance/components/custonAsCom/dragChart/components/chartConfig/form/chartLogic/index.js

@@ -1,7 +1,7 @@
 /*
  * @Author: your name
  * @Date: 2024-11-20 11:18:08
- * @LastEditTime: 2024-11-28 11:07:41
+ * @LastEditTime: 2025-08-25 14:49:24
  * @LastEditors: bogon
  * @Description: In User Settings Edit
  * @FilePath: /performance-test/src/utils/chartLogic/index.js
@@ -18,6 +18,7 @@ import { handleParetoChartLogic } from "./modules/pareto";
 import { handleBoxPlotChartLogic } from "./modules/boxPlot";
 import { handleSankeyDiagramPlotChartLogic } from "./modules/sankeyDiagram"; //流图 桑基图
 import { handleHeatmapPlotChartLogic } from "./modules/Heatmap";
+import { handlHorizontalStackedBar1ChartLogic } from "./modules/horizontalStackedBar1";
 export {
   handleBarChartLogic,
   handleScatterChartLogic,
@@ -30,4 +31,5 @@ export {
   handleBoxPlotChartLogic,
   handleSankeyDiagramPlotChartLogic,
   handleHeatmapPlotChartLogic,
+  handlHorizontalStackedBar1ChartLogic,
 };

+ 64 - 0
src/views/performance/components/custonAsCom/dragChart/components/chartConfig/form/chartLogic/modules/funnelPlot2.js

@@ -0,0 +1,64 @@
+/*
+ * @Author: your name
+ * @Date: 2025-08-25 15:38:10
+ * @LastEditTime: 2025-08-25 16:18:38
+ * @LastEditors: bogon
+ * @Description: In User Settings Edit
+ * @FilePath: /performance-test/src/views/performance/components/custonAsCom/dragChart/components/chartConfig/form/chartLogic/modules/funnelPlot2.js
+ */
+import { filterData } from "../dargChartFIlter";
+import { getFormattedLabels } from "../../configFn";
+
+export function handleFunnelPlot2Chart(
+  item,
+  formLabelAlign,
+  formFilterAlign,
+  isFilter,
+  type
+) {
+  // 1. 数据筛选逻辑保持不变
+  if (isFilter === "filter") {
+    const filterResult = formLabelAlign.Ydata.map((yItem, index) => ({
+      label: yItem.label,
+      id: yItem.id,
+      data: yItem.data.map((val) => {
+        const filters = formFilterAlign[index]?.filters || [];
+        return val === null ||
+          (filters.length > 0 && !filters.includes(val[yItem.label]))
+          ? null
+          : val;
+      }),
+    }));
+    const filterList = filterResult.map((filteredItem, index) => {
+      const filter = formFilterAlign[index];
+      const { number1, number2 } = filter;
+
+      if (
+        (number1 === null || number1 === "") &&
+        (number2 === null || number2 === "")
+      ) {
+        return {
+          label: filteredItem.label,
+          id: filteredItem.id,
+          data: filteredItem.data,
+        };
+      } else {
+        const filterDatas = filterData(
+          filter,
+          filteredItem.data,
+          filteredItem.label
+        );
+        return {
+          label: filteredItem.label,
+          id: filteredItem.id,
+          data: [...filterDatas],
+        };
+      }
+    });
+    item.Xdata = formLabelAlign.Xdata;
+    item.Ydata = filterList;
+  } else {
+    item.Xdata = formLabelAlign.Xdata;
+    item.Ydata = formLabelAlign.Ydata;
+  }
+}

+ 124 - 0
src/views/performance/components/custonAsCom/dragChart/components/chartConfig/form/chartLogic/modules/horizontalStackedBar1.js

@@ -0,0 +1,124 @@
+// /*
+//  * @Author: your name
+//  * @Date: 2025-08-25 14:43:10
+//  * @LastEditTime: 2025-08-25 14:43:48
+//  * @LastEditors: bogon
+//  * @Description: In User Settings Edit
+//  * @FilePath: /performance-test/src/views/performance/components/custonAsCom/dragChart/components/chartConfig/form/chartLogic/modules/horizontalStackedBar1.js
+//  */
+// //stackedBar 堆叠柱状图
+import { filterData } from "../dargChartFIlter";
+import { getFormattedLabels } from "../../configFn";
+
+export function handlHorizontalStackedBar1ChartLogic(
+  item,
+  formLabelAlign,
+  formFilterAlign,
+  isFilter,
+  type
+) {
+  // 1. 数据筛选逻辑保持不变
+  if (isFilter === "filter") {
+    const filterResult = formLabelAlign.Ydata.map((yItem, index) => ({
+      label: yItem.label,
+      id: yItem.id,
+      data: yItem.data.map((val) => {
+        const filters = formFilterAlign[index]?.filters || [];
+        return val === null ||
+          (filters.length > 0 && !filters.includes(val[yItem.label]))
+          ? null
+          : val;
+      }),
+    }));
+
+    const filterList = filterResult.map((filteredItem, index) => {
+      const filter = formFilterAlign[index];
+      const { number1, number2 } = filter;
+
+      if (
+        (number1 === null || number1 === "") &&
+        (number2 === null || number2 === "")
+      ) {
+        return {
+          label: filteredItem.label,
+          id: filteredItem.id,
+          data: filteredItem.data,
+        };
+      } else {
+        const filterDatas = filterData(
+          filter,
+          filteredItem.data,
+          filteredItem.label
+        );
+        return {
+          label: filteredItem.label,
+          id: filteredItem.id,
+          data: [...filterDatas],
+        };
+      }
+    });
+    item.Xdata = formLabelAlign.Xdata;
+    item.Ydata = filterList;
+  } else {
+    item.Xdata = formLabelAlign.Xdata;
+    item.Ydata = formLabelAlign.Ydata;
+  }
+
+  // 2. 修改坐标轴配置(横向)
+  item.option.xAxis = {
+    ...item.option.xAxis,
+    type: "value", // 横向,x轴是数值轴
+    name: formLabelAlign.Xlable, // x轴名称
+  };
+  item.option.yAxis = {
+    ...item.option.yAxis,
+    type: "category", // 横向,y轴是分类轴
+    name: formLabelAlign.Ylable, // y轴名称
+  };
+
+  // 3. 设置 series(保持不变)
+  if (item.Ydata[0]?.data?.length > 0) {
+    let series = [];
+    item.Ydata.forEach((item) => {
+      series.push({
+        name: item.label,
+        type: "bar",
+        stack: "d",
+        progressiveThreshold: 3000,
+        progressive: true,
+        renderMode: "webgl",
+        data: item.data.map(
+          (data) => parseFloat(data !== null && data[item.label]) || 0
+        ),
+      });
+    });
+    item.option.series = series;
+  }
+
+  // 4. 横向时类目轴数据改到 yAxis
+  if (item.Xdata[0]?.data?.length > 0) {
+    item.option.yAxis = {
+      ...item.option.yAxis,
+      data: item.Xdata[0].data, // 横向柱状图的类目在 yAxis
+      axisTick: { alignWithLabel: true },
+      axisLabel: {
+        formatter: (value, index) =>
+          value !== null ? getFormattedLabels(index, item.Xdata, value) : "",
+      },
+    };
+
+    // 5. tooltip 保持不变
+    item.option.tooltip = {
+      trigger: "axis",
+      axisPointer: { type: "shadow" },
+      formatter: (params) => {
+        const index = params[0]?.dataIndex;
+        const content = params
+          .map((param) => `${param.marker}${param.seriesName}: ${param.value}`)
+          .join("<br/>");
+        const customLabels = getFormattedLabels(index, item.Xdata);
+        return `${customLabels}<br/>${content}`;
+      },
+    };
+  }
+}

+ 11 - 2
src/views/performance/components/custonAsCom/dragChart/components/chartConfig/form/chartTitle.vue

@@ -701,9 +701,9 @@ import {
   handleBoxPlotChartLogic,
   handleSankeyDiagramPlotChartLogic,
   handleHeatmapPlotChartLogic,
+  handlHorizontalStackedBar1ChartLogic,
 } from "./chartLogic/index";
 import Vue from "vue";
-import { constructNow } from "date-fns";
 export default {
   name: "chartTitle",
   props: {
@@ -834,6 +834,7 @@ export default {
           "pie",
           // "doughnut",
           // "lineHighlight",
+          "horizontalStackedBar1",
           "roseChart",
           "stackedBar",
           "boxPlot",
@@ -1278,7 +1279,7 @@ export default {
     changeChart(isFilter) {
       if (!this.curEdit?.option) return; // 确保 curEdit 和 option 存在
       const item = JSON.parse(JSON.stringify(this.curEdit));
-      console.log(item, "item 全部数据渲染");
+
       if (this.curEdit.type === "scatter" || this.curEdit.type === "radar") {
         //判断散点选择的是否为number 或可转为number
         const isAllNumbers = (data) =>
@@ -1370,6 +1371,14 @@ export default {
           isFilter,
           this.curEdit.type
         );
+      } else if (this.curEdit.type === "horizontalStackedBar1") {
+        handlHorizontalStackedBar1ChartLogic(
+          item,
+          this.formLabelAlign,
+          this.formFilterAlign,
+          isFilter,
+          this.curEdit.type
+        );
       } else if (this.curEdit.type === "pareto") {
         handleParetoChartLogic(
           item,

+ 11 - 1
src/views/performance/components/custonAsCom/dragChart/components/chartConfig/form/toolbox.vue

@@ -1,7 +1,7 @@
 <!--
  * @Author: your name
  * @Date: 2025-08-14 11:28:28
- * @LastEditTime: 2025-08-14 13:50:35
+ * @LastEditTime: 2025-08-25 14:53:19
  * @LastEditors: bogon
  * @Description: In User Settings Edit
  * @FilePath: /performance-test/src/views/performance/components/custonAsCom/dragChart/components/chartConfig/form/toolbox.vue
@@ -709,6 +709,7 @@ import {
   handleBoxPlotChartLogic,
   handleSankeyDiagramPlotChartLogic,
   handleHeatmapPlotChartLogic,
+  handlHorizontalStackedBar1ChartLogic,
 } from "./chartLogic/index";
 import Vue from "vue";
 import { constructNow } from "date-fns";
@@ -843,6 +844,7 @@ export default {
           "roseChart",
           "stackedBar",
           "boxPlot",
+          "horizontalStackedBar1",
           "sankeyDiagram",
           "Heatmap",
         ].includes(type)
@@ -1394,6 +1396,14 @@ export default {
           isFilter,
           this.curEdit.type
         );
+      } else if (this.curEdit.type === "horizontalStackedBar1") {
+        handlHorizontalStackedBar1ChartLogic(
+          item,
+          this.formLabelAlign,
+          this.formFilterAlign,
+          isFilter,
+          this.curEdit.type
+        );
       }
       //设置仓库
       this.setFormFilterAlignData({

+ 13 - 13
src/views/performance/components/custonAsCom/dragChart/components/chartsAttributes.vue

@@ -1,7 +1,7 @@
 <!--
  * @Author: your name
  * @Date: 2024-11-01 10:14:11
- * @LastEditTime: 2025-08-15 09:48:30
+ * @LastEditTime: 2025-08-25 14:27:43
  * @LastEditors: bogon
  * @Description: In User Settings Edit
  * @FilePath: /performance-test/src/views/performance/components/custonAsCom/dragChart/components/chartsContent.vue
@@ -9,9 +9,9 @@
 <template>
   <div class="chartContiner">
     <el-card class="card" shadow="always">
-      <h6>图表配置</h6>
+      <!-- <h6>图表配置</h6> -->
       <el-row>
-        <el-col :span="8" v-for="chartItem in svgIcon" :key="chartItem.type">
+        <el-col :span="6" v-for="chartItem in svgIcon" :key="chartItem.type">
           <el-tooltip
             class="item"
             effect="dark"
@@ -125,18 +125,18 @@ export default {
           type: "pareto",
           name: "帕累托图",
         },
-        // {
-        //   type: "horizontalStackedBar1",
-        //   name: "横向堆叠柱状图",
-        // },
+        {
+          type: "horizontalStackedBar1",
+          name: "横向堆叠柱状图",
+        },
         {
           type: "boxPlot",
           name: "箱线图",
         },
-        // {
-        //   type: "funnelPlot2",
-        //   name: "漏斗图",
-        // },
+        {
+          type: "funnelPlot2",
+          name: "漏斗图",
+        },
         {
           type: "sankeyDiagram",
           name: "流图",
@@ -210,8 +210,8 @@ export default {
 <style scoped lang="scss">
 .chartContiner {
   background-color: pink;
-  // width: 270px;
-  width: 200px;
+  width: 268px;
+  // width: 200px;
   height: 100%;
 
   ::v-deep .card {

+ 2 - 2
src/views/performance/components/custonAsCom/dragChart/components/chartsData.vue

@@ -1,7 +1,7 @@
 <template>
   <div class="chartContiner">
-    <el-card shadow="never" class="card">
-      <h6>数据配置</h6>
+    <el-card class="card" shadow="always">
+      <!-- <h6>数据配置</h6> -->
       <el-input placeholder="输入关键字进行过滤" v-model="filterText" />
       <el-tree
         class="filter-tree"

+ 116 - 57
src/views/performance/components/custonAsCom/dragChart/index.vue

@@ -1,57 +1,59 @@
 <template>
   <div class="chartsBox">
-    <ChartsContent
-      :addChartType="addChartType"
-      :config="config"
-    ></ChartsContent>
+    <!-- 主内容区 -->
+    <ChartsContent :addChartType="addChartType" :config="config" />
 
-    <!-- 抽屉 1:属性 -->
-    <div class="drawer drawerText">
-      <svg-icon
-        @click="() => (drawer = !drawer)"
-        v-show="!drawer"
+    <!-- 抽屉1 -->
+    <div class="drawer-box">
+      <!-- 控制按钮 -->
+      <!-- <svg-icon
+        class="drawer-btn"
+        @click="toggleDrawer(1)"
         icon-class="text"
+        v-show="!drawer1"
         style="width: 30px; height: 30px"
-      />
-      <el-drawer
-        :with-header="false"
-        :visible.sync="drawer"
-        direction="rtl"
-        size="270"
-        :append-to-body="false"
-        :modal="false"
-        :wrapper-closable="false"
-        :close-on-click-modal="false"
-        :close-on-press-escape="false"
-        :show-close="false"
-        custom-class="drawer-attr"
-      >
-        <i class="el-icon-s-unfold" @click="() => (drawer = !drawer)"></i>
-        <ChartsAttributes @addChart="addChart" :addChartType="addChartType" />
-      </el-drawer>
+      /> -->
+      <div class="drawer-text" @click="toggleDrawer(1)" v-show="!drawer1">
+        <i class="el-icon-s-unfold"></i>
+        可视化 &emsp;筛选器
+      </div>
+      <!-- 抽屉面板 -->
+      <transition name="drawer-slide">
+        <div class="drawer-panel" v-show="drawer1">
+          <div class="titleBox" @click="toggleDrawer(1)">
+            <button class="drawer-btn" type="text">可视化图表</button>
+            <i class="el-icon-s-fold"></i>
+          </div>
+          <ChartsAttributes @addChart="addChart" :addChartType="addChartType" />
+        </div>
+      </transition>
     </div>
 
-    <!-- 抽屉 2:数据 -->
-    <div class="drawer drawerStyle">
-      <svg-icon
-        @click="() => (drawer1 = !drawer1)"
-        v-show="!drawer1"
+    <!-- 抽屉2 -->
+    <div class="drawer-box">
+      <!-- 控制按钮 -->
+      <!-- <svg-icon
+        class="drawer-btn"
+        @click="toggleDrawer(2)"
+        v-show="!drawer2"
         icon-class="styleChart1"
         style="width: 30px; height: 30px"
-      />
-      <el-drawer
-        :with-header="false"
-        :visible.sync="drawer1"
-        direction="rtl"
-        size="300"
-        :modal="false"
-        :append-to-body="false"
-        :show-close="false"
-        custom-class="drawer-data"
-      >
-        <i class="el-icon-s-unfold" @click="() => (drawer1 = !drawer1)"></i>
-        <ChartsData :addChartType="addChartType" />
-      </el-drawer>
+      /> -->
+      <div class="drawer-text" @click="toggleDrawer(2)" v-show="!drawer2">
+        <i class="el-icon-s-unfold"></i>
+        字段
+      </div>
+      <!-- 抽屉面板 -->
+      <transition name="drawer-slide">
+        <div class="drawer-panel" v-show="drawer2">
+          <div class="titleBox" @click="toggleDrawer(2)">
+            <button class="drawer-btn" type="text">数据字段</button>
+            <i class="el-icon-s-fold"></i>
+          </div>
+
+          <ChartsData :addChartType="addChartType" />
+        </div>
+      </transition>
     </div>
   </div>
 </template>
@@ -70,15 +72,19 @@ export default {
   data() {
     return {
       addChartType: "",
-      options: "",
       config: "",
-      configType: "",
-      drawer: false,
-      drawer1: false,
-      direction: "rtl", // 从右往左推
+      drawer1: true,
+      drawer2: true,
     };
   },
   methods: {
+    toggleDrawer(index) {
+      if (index === 1) {
+        this.drawer1 = !this.drawer1;
+      } else if (index === 2) {
+        this.drawer2 = !this.drawer2;
+      }
+    },
     addChart(type) {
       if (this.addChartType === type) {
         this.addChartType = "";
@@ -96,18 +102,71 @@ export default {
 <style scoped lang="scss">
 .chartsBox {
   width: 100%;
+  height: 100%;
+  display: flex;
+  gap: 5px; /* 控制抽屉之间的间距 */
+  align-items: flex-start;
+  background-color: #fff;
+}
+
+/* 每组按钮 + 抽屉的容器 */
+.drawer-box {
   display: flex;
+  align-items: flex-start;
+  gap: 0px;
   height: 100%;
-  position: relative;
+  overflow: scroll;
+  .drawer-text {
+    background-color: #eef1f3;
+    writing-mode: vertical-rl; /* 从上到下,从右到左 */
+    text-orientation: upright; /* 保证文字不倒置 */
+    height: 100%;
+    // display: inline-block; /* 必须加,否则transform无效 */
+    // transform: rotate(90deg);
+    // transform-origin: left top; /* 设置旋转基准点,可根据需求调整 */
+  }
 }
 
-/* 第一个抽屉(属性设置)靠最右 */
-.drawer-attr {
-  right: 0 !important;
+/* 控制按钮 */
+.drawer-btn {
+  // padding: 6px 12px;
+  // background: #409eff;
+  color: #000;
+  border: none;
+  border-radius: 4px;
+  cursor: pointer;
+  transition: 0.2s;
+}
+.drawer-btn:hover {
+  background: #eef1f3;
 }
 
-/* 第二个抽屉(数据设置)往左推 270px */
-.drawer-data {
-  right: 270px !important;
+/* 抽屉内容区 */
+.drawer-panel {
+  width: 270px;
+  background: #fff;
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
+  border-radius: 6px;
+  border: 1px #eef1f3 solid;
+  // padding: 10px;
+  height: 100%;
+  transition: all 0.3s ease;
+  .titleBox {
+    display: flex;
+    padding: 10px;
+    justify-content: space-between;
+    align-items: center;
+  }
+}
+
+/* 抽屉动画 */
+.drawer-slide-enter-active,
+.drawer-slide-leave-active {
+  transition: all 0.3s ease;
+}
+.drawer-slide-enter,
+.drawer-slide-leave-to {
+  opacity: 0;
+  transform: translateX(10px);
 }
 </style>