|
|
<template>
|
|
|
<!-- <el-scrollbar ref="scrollbar" height="800px"> -->
|
|
|
<div class="main-top">
|
|
|
<span style="font-weight: bold; font-size: 18px; color: #3596eb">任务描述</span>
|
|
|
<p style="font-weight: 400; font-size: 12px; color: #e6e6e6">使用“客户细分数据”,对客户进行聚类分析,并提交实训任务。</p>
|
|
|
</div>
|
|
|
<div class="main-but">
|
|
|
<el-button @click="algorithmicKnowledge()">
|
|
|
<img src="../../../assets/images/导入.png" alt="" />
|
|
|
算法知识导入
|
|
|
</el-button>
|
|
|
<el-button @click="taskSubmit">
|
|
|
<img src="../../../assets/images/提交.png" alt="" />
|
|
|
实训任务提交
|
|
|
</el-button>
|
|
|
</div>
|
|
|
<div class="app-main">
|
|
|
<div class="main-left">
|
|
|
<el-scrollbar height="640px" style="padding: 0 10px" min-size="5">
|
|
|
<div>
|
|
|
<div class="left-top">
|
|
|
<span>一、选择指标</span>
|
|
|
</div>
|
|
|
<div class="metrics">
|
|
|
<el-select v-model="input" placeholder="请选择" style="width: 180px" @change="optionData">
|
|
|
<el-option v-for="item in algorithmStore.userDataLabel" :key="item" :label="item" :value="item" />
|
|
|
</el-select>
|
|
|
</div>
|
|
|
<div class="metrics-table" style="margin-top: 10px">
|
|
|
<el-table v-loading="loading1" border :data="tableData" style="width: 100%" :header-cell-style="headerCellStyle" @selection-change="handleSelectionChange">
|
|
|
<el-table-column type="selection" label="Date" align="center" :selectable="flag" />
|
|
|
<el-table-column prop="text" label="指标" align="center" />
|
|
|
</el-table>
|
|
|
<el-button @click="taskZB">确定</el-button>
|
|
|
</div>
|
|
|
<div class="left-top" style="margin-top: 50px">
|
|
|
<span>二、数据预处理</span>
|
|
|
</div>
|
|
|
<div class="metrics">
|
|
|
<el-select v-model="input2" @change="optionData2" placeholder="请选择" style="width: 180px">
|
|
|
<el-option v-for="item in options2" :key="item.value" :label="item.label" :value="item.value" />
|
|
|
</el-select>
|
|
|
</div>
|
|
|
<div class="metrics" style="margin-top: 15px">
|
|
|
<el-select v-model="input3" @change="optionData2" placeholder="请选择" style="width: 180px">
|
|
|
<el-option v-for="item in options3" :key="item.value" :label="item.label" :value="item.value" />
|
|
|
</el-select>
|
|
|
</div>
|
|
|
<div class="left-top" style="margin-top: 50px">
|
|
|
<span>三、模型参数设置</span>
|
|
|
</div>
|
|
|
<div class="metrics-table" style="margin-top: 10px">
|
|
|
<span style="font-weight: 400; font-size: 12px; color: #ffffff">聚类方法:</span>
|
|
|
<div class="metrics" style="margin-top: 10px">
|
|
|
<el-select v-model="input4" placeholder="请选择" style="width: 180px">
|
|
|
<el-option v-for="item in options4" :key="item.value" :label="item.label" :value="item.value" />
|
|
|
</el-select>
|
|
|
</div>
|
|
|
<div style="margin-top: 10px">
|
|
|
<span style="font-weight: 400; font-size: 12px; color: #ffffff">设置簇数K值:</span>
|
|
|
<el-input v-model="input5" style="width: 180px; margin-top: 5px" />
|
|
|
</div>
|
|
|
<div style="margin-top: 10px">
|
|
|
<span style="font-weight: 400; font-size: 12px; color: #ffffff">最大迭代次数:</span>
|
|
|
<el-input v-model="input6" style="width: 180px; margin-top: 5px" />
|
|
|
</div>
|
|
|
<el-button @click="clusterAnalysisCalculation">计算</el-button>
|
|
|
</div>
|
|
|
</div>
|
|
|
<div style="margin-top: auto">
|
|
|
<div class="startOver" @click="startOver()">
|
|
|
<img src="../../../assets/images/重新开始.png" alt="" />
|
|
|
<span>重新开始</span>
|
|
|
</div>
|
|
|
</div>
|
|
|
</el-scrollbar>
|
|
|
</div>
|
|
|
<div class="main-right">
|
|
|
<el-scrollbar height="500px" ref="scrollbar" style="padding: 20px">
|
|
|
<div>
|
|
|
<span style="font-weight: bold; font-size: 18px; color: #3596eb">分析数据:</span>
|
|
|
<el-table ref="exportTableRef" v-loading="loading2" :data="tableData2" style="width: 100%; margin-top: 10px" :header-cell-style="headerCellStyle" height="350">
|
|
|
<el-table-column v-for="column in tableLabel" :key="column.prop" :prop="column.prop" :label="column.label" align="center" />
|
|
|
</el-table>
|
|
|
<!-- <el-pagination style="margin-top:10px" background layout="prev, pager, next" :total="1000" /> -->
|
|
|
</div>
|
|
|
<div class="analysisResults" style="margin-top: 20px">
|
|
|
<span style="font-weight: bold; font-size: 18px; color: #3596eb; display: block">分析结果:</span>
|
|
|
<el-button @click="upLoad" v-if="showChart">下载</el-button>
|
|
|
<el-table :data="tableData3" v-loading="loading3" row-key="TSuid" style="width: 100%; margin-top: 10px" :header-cell-style="headerCellStyle" height="350">
|
|
|
<el-table-column v-for="column in tableLabel" :key="column.prop" :prop="column.prop" :label="column.label" align="center" />
|
|
|
</el-table>
|
|
|
<!-- <el-pagination style="margin-top:10px" background layout="prev, pager, next" :total="1000" /> -->
|
|
|
</div>
|
|
|
<div style="color:#FFFFFF;" v-show="showChart">最大迭代次数:{{iterations}}</div>
|
|
|
<div class="pdfRef" id="pdfRef" style="margin-top: 20px; gap: 25px; display: flex" v-show="showChart">
|
|
|
<!-- <img src="../../../assets/images/图1.png" alt=""> -->
|
|
|
<div style="background: #ffffff">
|
|
|
<div id="main" style="width: 500px; height: 500px"></div>
|
|
|
</div>
|
|
|
|
|
|
<div style="background: #ffffff">
|
|
|
<div id="main2" style="width: 500px; height: 500px"></div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</el-scrollbar>
|
|
|
</div>
|
|
|
</div>
|
|
|
<!-- </el-scrollbar> -->
|
|
|
|
|
|
<el-dialog v-model="dialogVisible" title="实训任务提交" width="600" custom-class="dialogClass" :show-close="false">
|
|
|
<template #header="{ close, titleId, titleClass }">
|
|
|
<div class="my-header">
|
|
|
<span :id="titleId" :class="titleClass">实训任务提交</span>
|
|
|
<img class="gbImg" src="../../../assets/images/关闭.png" @click="close" alt="" />
|
|
|
</div>
|
|
|
<img class="xtImg" src="../../../assets/images/线条.png" alt="" />
|
|
|
</template>
|
|
|
<div class="from-item">
|
|
|
<el-form :inline="true" label-position="right" label-width="115px" :model="form" class="demo-form-inline">
|
|
|
<el-form-item label="聚类个数:">
|
|
|
<el-input style="width: 120px" v-model="formInline2.value1" clearable />
|
|
|
</el-form-item>
|
|
|
<el-form-item label="最大迭代次数:">
|
|
|
<el-input style="width: 120px" v-model="formInline2.value2" clearable />
|
|
|
</el-form-item>
|
|
|
</el-form>
|
|
|
<span style="color: #ffffff; font-weight: 700">各簇类特征描述:</span>
|
|
|
<el-form :inline="true" label-position="right" style="margin: 15px 0" label-width="115px" :model="form" class="demo-form-inline">
|
|
|
<el-form-item label="簇类1:">
|
|
|
<el-input style="width: 240px" v-model="formInline2.value3" clearable />
|
|
|
</el-form-item>
|
|
|
<el-form-item label="簇类2:">
|
|
|
<el-input style="width: 240px" v-model="formInline2.value4" clearable />
|
|
|
</el-form-item>
|
|
|
<el-form-item label="簇类3:">
|
|
|
<el-input style="width: 240px" v-model="formInline2.value5" clearable />
|
|
|
</el-form-item>
|
|
|
</el-form>
|
|
|
</div>
|
|
|
<template #footer>
|
|
|
<div class="dialog-footer">
|
|
|
<el-button @click="task()">确定</el-button>
|
|
|
<el-button type="primary" @click="dialogVisible = false">返回</el-button>
|
|
|
</div>
|
|
|
</template>
|
|
|
</el-dialog>
|
|
|
|
|
|
<pop-model :showModel="runResultShow" title="聚类分析" @closePop="runResultShow = false">
|
|
|
<template v-slot:content>
|
|
|
<div>
|
|
|
<p style="font-weight: 700; font-size: 22px">聚类分析的定义及原理</p>
|
|
|
<p style="font-size: 19px">1.定义</p>
|
|
|
<p>
|
|
|
所谓物以类聚、人以群分。聚类分析,即是基于研究对象的特征,将他们分门别类,以让同类别的个体之间差异相对小、相似度相对大,不同类别之间的个体差异大、相似度小。
|
|
|
<br />
|
|
|
聚类分析是一种探索性分析方法,与判别分析不同,聚类分析事先并不知道分类的标准,甚至不知道应该分成几类,而是会根据样本数据的特征,自动进行分类。
|
|
|
</p>
|
|
|
<img src="@/assets/images/f1001.png" style="width: 600px; height: 400px" />
|
|
|
<p>聚类与分类的不同在于,聚类所要求划分的类是未知的</p>
|
|
|
<img src="@/assets/images/f1002.png" style="width: 600px; height: 300px" />
|
|
|
<br />
|
|
|
<img src="@/assets/images/f1003.png" style="width: 600px; height: 300px" />
|
|
|
<!-- <img src="@/assets/images/f1005.png" style="width: 1100px;height: 400px;"/> -->
|
|
|
<li style="font-size: 18px">从统计学的观点看,聚类分析是通过数据建模简化数据的一种方法。</li>
|
|
|
<li style="font-size: 18px">从机器学习的角度看,簇相当于隐藏模式。聚类是搜索簇的无监督学习过程。</li>
|
|
|
<li style="font-size: 18px">从实际应用的角度看,聚类分析是数据预处理(降维)和信息挖掘的主要任务之一。</li>
|
|
|
<p style="font-size: 19px">2.原理</p>
|
|
|
<p>
|
|
|
假定研究对象均用所谓的“点”来表示。
|
|
|
<br />
|
|
|
在聚类分析中,一般的规则是将“距离”较小的点归为同一类,将“距离”较大的点归为不同的类。常见的是对个案分类,也可以对变量分类,但对于变量分类此时一般使用相似系数作为“距离”测量指标。
|
|
|
<br />
|
|
|
一般的规则:
|
|
|
</p>
|
|
|
<img src="@/assets/images/f10010.png" style="width: 600px; height: 300px" />
|
|
|
<br />
|
|
|
<img src="@/assets/images/f10011.png" />
|
|
|
<br />
|
|
|
<img src="@/assets/images/f10012.png" style="width: 600px; height: 300px" />
|
|
|
<p style="font-weight: 700; font-size: 22px">聚类方法及其在SPSS中的实现</p>
|
|
|
<p style="font-size: 19px">1.主要的聚类方法:</p>
|
|
|
<img src="@/assets/images/f10013.png" style="width: 600px; height: 300px" />
|
|
|
<p style="font-size: 19px">2.方法详解:</p>
|
|
|
<p style="font-size: 17px">(1):K-means聚类</p>
|
|
|
<p>
|
|
|
又称为快速聚类(K-Means Cluster),是在聚类的类别数已确定的情况下,快速将其他个案归类到相应的类别,适合大样本数据的聚类。
|
|
|
<br />
|
|
|
具体步骤如下:
|
|
|
</p>
|
|
|
<img src="@/assets/images/f10014.png" style="width: 600px; height: 300px" />
|
|
|
<br />
|
|
|
<img src="@/assets/images/f10015.png" style="width: 600px; height: 300px" />
|
|
|
<p>距离计算规则(欧几里得距离公式):</p>
|
|
|
<img src="@/assets/images/f10016.png" />
|
|
|
<p>图解:</p>
|
|
|
<img src="@/assets/images/f10017.png" style="width: 600px; height: 300px" />
|
|
|
<br />
|
|
|
<img src="@/assets/images/f10018.png" style="width: 600px; height: 300px" />
|
|
|
<p>
|
|
|
K-means的优缺点:
|
|
|
<br />
|
|
|
优势:
|
|
|
<br />
|
|
|
(1)原理比较简单,实现也很容易,收敛速度快。
|
|
|
<br />
|
|
|
(2)在对大规模数据集进行聚类分析时,算法聚类较高效且聚类效果较好。
|
|
|
<br />
|
|
|
(3)簇与簇之间区别明显时,它的聚类效果很好。
|
|
|
<br />
|
|
|
不足:
|
|
|
<br />
|
|
|
(1)分类数从初始分类开始就确定不变了,所以要求事先要对样本有足够的了解。
|
|
|
<br />
|
|
|
(2)仅限于个案间的聚类(Q型聚类),不能对变量进行聚类。
|
|
|
<br />
|
|
|
(3)个案间的距离的测量方法使用的是欧式距离的平方,因此只能对连续变量进行聚类。
|
|
|
<br />
|
|
|
案例分析(SPSS):
|
|
|
<br />
|
|
|
通过查询整理出了2018年我国各省份的20项基本情况,根据这些指标把这31个省市或地区分成3类。
|
|
|
</p>
|
|
|
<img src="@/assets/images/f10019.png" style="width: 600px; height: 300px" />
|
|
|
<p>分析步骤:分析>>分类>>K-均值聚类>>迭代>>次数>>选项>>勾选统计>>确认</p>
|
|
|
<img src="@/assets/images/f10020.png" style="width: 600px; height: 400px" />
|
|
|
<br />
|
|
|
<img src="@/assets/images/f10021.png" style="width: 600px" />
|
|
|
<p>结果分析:</p>
|
|
|
<img src="@/assets/images/f10022.png" />
|
|
|
<br />
|
|
|
<img src="@/assets/images/f10023.png" />
|
|
|
<p>若不收敛则调大迭代次数</p>
|
|
|
<img src="@/assets/images/f10024.png" />
|
|
|
<p>方差分析表:</p>
|
|
|
<img src="@/assets/images/f10025.png" style="width: 600px; height: 400px" />
|
|
|
<p>其中聚类均方对应组间均方差,误差均方对应组内均方差,显著性p<0.05时说明此变量分类效果好。由表可知,大部分变量的p<0.05,且组间均方差大于组内均方差,说明各变量在三个类别中的差异大,分类结果可信度高。</p>
|
|
|
<img src="@/assets/images/f10026.png" />
|
|
|
<br />
|
|
|
<img src="@/assets/images/10027.png" />
|
|
|
<p>聚类结果解读:</p>
|
|
|
<div class="table-box">
|
|
|
<table cellspacing="0" style="width: 355pt">
|
|
|
<tbody>
|
|
|
<tr>
|
|
|
<td style="text-align: center; vertical-align: middle; width: 355pt">
|
|
|
<p style="margin-left: 0in; text-align: center">
|
|
|
<strong>第一类:</strong>
|
|
|
<strong>1</strong>
|
|
|
<strong>个省份(广东省,经济发达)</strong>
|
|
|
</p>
|
|
|
</td>
|
|
|
</tr>
|
|
|
<tr>
|
|
|
<td style="background-color: #e7e7e7; text-align: center; vertical-align: middle; width: 355pt">
|
|
|
<p style="margin-left: 0in; text-align: center">
|
|
|
<strong>第二类:</strong>
|
|
|
<strong>19</strong>
|
|
|
<strong>个省份(云南、西藏等地区,经济一般)</strong>
|
|
|
</p>
|
|
|
</td>
|
|
|
</tr>
|
|
|
<tr>
|
|
|
<td style="text-align: center; vertical-align: middle; width: 355pt">
|
|
|
<p style="margin-left: 0in; text-align: center">
|
|
|
<strong>第三类:</strong>
|
|
|
<strong>11</strong>
|
|
|
<strong>个省份(上海、北京等地区,经济较发达)</strong>
|
|
|
</p>
|
|
|
</td>
|
|
|
</tr>
|
|
|
</tbody>
|
|
|
</table>
|
|
|
</div>
|
|
|
</div>
|
|
|
</template>
|
|
|
</pop-model>
|
|
|
</template>
|
|
|
|
|
|
<script setup>
|
|
|
import * as portraitModel from "@/api/portraitModel";
|
|
|
import useAlgorithmStore from "@/store/modules/algorithm.js";
|
|
|
const algorithmStore = useAlgorithmStore();
|
|
|
import popModel from "@/views/components/popModal.vue";
|
|
|
import { htmlPdf } from "@/utils/pdf.js";
|
|
|
import * as echarts from "echarts";
|
|
|
import * as XLSX from "XLSX";
|
|
|
import JSZip from "jszip";
|
|
|
import * as API from "@/api/AI.js";
|
|
|
import { getUserInfo } from "@/utils/auth";
|
|
|
import { onMounted, reactive } from "vue";
|
|
|
const loading1 = ref(false);
|
|
|
const loading2 = ref(false);
|
|
|
const loading3 = ref(false);
|
|
|
const runResultShow = ref(false);
|
|
|
const { proxy } = getCurrentInstance();
|
|
|
const dialogVisible = ref(false);
|
|
|
const tableLabel = reactive([{ prop: "date", label: "" }]);
|
|
|
const isFlag = ref(false);
|
|
|
const formInline2 = reactive({
|
|
|
value1: "",
|
|
|
value2: "",
|
|
|
value3: "",
|
|
|
value4: "",
|
|
|
value5: "",
|
|
|
});
|
|
|
const task = () => {
|
|
|
let errorNumber = 0;
|
|
|
for (let key in formInline2) {
|
|
|
if (formInline2[key] == "") {
|
|
|
proxy.$modal.msgError("请填写完整!");
|
|
|
return;
|
|
|
}
|
|
|
}
|
|
|
if(parseFloat(formInline2.value1)!==parseFloat(input5.value)){
|
|
|
errorNumber=1
|
|
|
}
|
|
|
if(parseFloat(formInline2.value2)!==parseFloat(iterations.value)){
|
|
|
errorNumber=1
|
|
|
}
|
|
|
if(input.value!=="客户细分数据表"){
|
|
|
errorNumber=1
|
|
|
}
|
|
|
portraitModel
|
|
|
.submit({
|
|
|
userId: JSON.parse(getUserInfo()).userId,
|
|
|
taskName: "聚类分析",
|
|
|
numberOfErrors: errorNumber,
|
|
|
})
|
|
|
.then((res) => {
|
|
|
dialogVisible.value = false;
|
|
|
proxy.$modal.msgSuccess("提交成功");
|
|
|
});
|
|
|
};
|
|
|
const algorithmicKnowledge = () => {
|
|
|
runResultShow.value = true;
|
|
|
};
|
|
|
const getList = () => {
|
|
|
API.selectionMetrics({ userId: JSON.parse(getUserInfo()).userId }).then((res) => {
|
|
|
res.data.forEach((element) => {
|
|
|
options.value.push({
|
|
|
value: element,
|
|
|
label: element,
|
|
|
});
|
|
|
});
|
|
|
});
|
|
|
};
|
|
|
const scrollbar = ref(null);
|
|
|
const fileList = ref([]);
|
|
|
const handleChange = (info) => {
|
|
|
fileList.value.push(info);
|
|
|
};
|
|
|
const flag = ref(false);
|
|
|
let myChart;
|
|
|
let myChart2;
|
|
|
const getRandomColorHex = () => {
|
|
|
const randomValue = (min, max) => {
|
|
|
// 生成 min 到 max 之间的随机整数
|
|
|
const value = Math.floor(Math.random() * (max - min + 1)) + min;
|
|
|
// 将整数转换为两位十六进制形式
|
|
|
return value.toString(16).padStart(2, "0");
|
|
|
};
|
|
|
|
|
|
// 限制红色通道较低,绿色和蓝色通道较高
|
|
|
const r = randomValue(0, 128); // 红色通道较低 (0-128)
|
|
|
const g = randomValue(128, 255); // 绿色通道较高 (128-255)
|
|
|
const b = randomValue(128, 255); // 蓝色通道较高 (128-255)
|
|
|
|
|
|
return `#${r}${g}${b}`;
|
|
|
};
|
|
|
//图标添加数据
|
|
|
const addData = (myChart, newData, color) => {
|
|
|
// 获取当前的数据
|
|
|
let currentData = myChart.getOption().series[0].data || [];
|
|
|
// 添加新数据集,并为每个数据点单独设置颜色
|
|
|
newData.forEach((dataPoint) => {
|
|
|
currentData.push({
|
|
|
value: [dataPoint.x, dataPoint.y],
|
|
|
itemStyle: { color: color },
|
|
|
});
|
|
|
});
|
|
|
|
|
|
// 更新图表
|
|
|
myChart.setOption({
|
|
|
series: [
|
|
|
{
|
|
|
data: currentData,
|
|
|
},
|
|
|
],
|
|
|
});
|
|
|
};
|
|
|
|
|
|
onMounted(() => {
|
|
|
getList();
|
|
|
optionData()
|
|
|
var chartDom = document.getElementById("main");
|
|
|
var chartDom2 = document.getElementById("main2");
|
|
|
myChart = echarts.init(chartDom);
|
|
|
472;
|
|
|
myChart2 = echarts.init(chartDom2);
|
|
|
// 初始化图表配置
|
|
|
var option = {
|
|
|
dataset: [
|
|
|
{
|
|
|
source: [], // 初始时数据为空
|
|
|
},
|
|
|
],
|
|
|
tooltip: {
|
|
|
position: "top",
|
|
|
},
|
|
|
xAxis: {},
|
|
|
yAxis: {},
|
|
|
series: [
|
|
|
{
|
|
|
type: "scatter",
|
|
|
encode: { tooltip: [0, 1] },
|
|
|
symbolSize: 8,
|
|
|
itemStyle: {
|
|
|
borderColor: "#555",
|
|
|
},
|
|
|
},
|
|
|
],
|
|
|
};
|
|
|
// 设置初始的配置
|
|
|
myChart.setOption(option);
|
|
|
myChart2.setOption(option);
|
|
|
});
|
|
|
|
|
|
const optionData = () => {
|
|
|
loading1.value = true;
|
|
|
API.viewingMetrics({ userId: JSON.parse(getUserInfo()).userId, tableName: input.value, algorithmName: "聚类分析" })
|
|
|
.then((res) => {
|
|
|
tableData.value = [];
|
|
|
input3.value = "";
|
|
|
res.data.forEach((element) => {
|
|
|
tableData.value.push({
|
|
|
text: element,
|
|
|
});
|
|
|
});
|
|
|
loading1.value = false;
|
|
|
})
|
|
|
.catch(() => {
|
|
|
loading1.value = false;
|
|
|
});
|
|
|
};
|
|
|
const selectedRows = ref([]);
|
|
|
const handleSelectionChange = (selection) => {
|
|
|
selectedRows.value = selection;
|
|
|
};
|
|
|
const tableData2 = ref([]);
|
|
|
const tableData3 = ref([]);
|
|
|
const resTable2 = ref();
|
|
|
const taskZB = () => {
|
|
|
flag.value = true;
|
|
|
if (selectedRows.value.length === 0) {
|
|
|
return;
|
|
|
}
|
|
|
const sendData = {
|
|
|
tableName: input.value,
|
|
|
userId: JSON.parse(getUserInfo()).userId,
|
|
|
fieldList: selectedRows.value.map((item) => item.text),
|
|
|
};
|
|
|
API.analysisDataDisplay(sendData).then((res) => {
|
|
|
tableLabel.length = 0;
|
|
|
for (const key in res.data[0]) {
|
|
|
tableLabel.push({
|
|
|
label: key,
|
|
|
prop: key,
|
|
|
});
|
|
|
}
|
|
|
tableData2.value = res.data;
|
|
|
flag.value = true;
|
|
|
// proxy.$modal.msgSuccess("计算完成!");
|
|
|
tableData2.value = res.data;
|
|
|
flag.value = true;
|
|
|
});
|
|
|
};
|
|
|
const formInline = reactive({
|
|
|
user: "",
|
|
|
region: "",
|
|
|
});
|
|
|
const input = ref(algorithmStore.userDataLabel[0]);
|
|
|
const options = ref([]);
|
|
|
const input2 = ref();
|
|
|
const options2 = ref([
|
|
|
{
|
|
|
value: "数据区重",
|
|
|
label: "数据去重",
|
|
|
},
|
|
|
]);
|
|
|
const input3 = ref();
|
|
|
const options3 = ref([
|
|
|
{
|
|
|
value: "缺失值处理--剔除数据",
|
|
|
label: "缺失值处理--剔除数据",
|
|
|
},
|
|
|
{
|
|
|
value: "缺失值处理--均值替换",
|
|
|
label: "缺失值处理--均值替换",
|
|
|
},
|
|
|
]);
|
|
|
const input4 = ref();
|
|
|
const options4 = ref([
|
|
|
{
|
|
|
value: "K-means",
|
|
|
label: "K-means",
|
|
|
},
|
|
|
]);
|
|
|
const input5 = ref();
|
|
|
const input6 = ref();
|
|
|
const taskSubmit = () => {
|
|
|
dialogVisible.value = true;
|
|
|
};
|
|
|
const tableData = ref([]);
|
|
|
const headerCellStyle = () => {
|
|
|
return {
|
|
|
backgroundColor: "#1882DE !important", // 设置表头背景颜色
|
|
|
color: "#ffffff !important",
|
|
|
};
|
|
|
};
|
|
|
const optionData2 = () => {
|
|
|
if (tableData2.value.length === 0) {
|
|
|
input2.value = "";
|
|
|
input3.value = "";
|
|
|
proxy.$modal.msgWarning("请先选择指标!");
|
|
|
return;
|
|
|
}
|
|
|
if (input2.value && input3.value) {
|
|
|
const sendData = ref({
|
|
|
mapList: tableData2.value,
|
|
|
method: input3.value,
|
|
|
userId: JSON.parse(getUserInfo()).userId,
|
|
|
});
|
|
|
loading2.value = true;
|
|
|
API.dataPreprocessing(sendData.value)
|
|
|
.then((res) => {
|
|
|
resTable2.value = res.data;
|
|
|
tableData2.value = [];
|
|
|
tableData2.value = res.data;
|
|
|
proxy.$modal.msgSuccess("预处理成功!");
|
|
|
setTimeout(() => {
|
|
|
loading2.value = false;
|
|
|
}, 500);
|
|
|
})
|
|
|
.catch(() => {
|
|
|
loading2.value = false;
|
|
|
});
|
|
|
}
|
|
|
};
|
|
|
const showChart = ref(false);
|
|
|
//生产表格
|
|
|
const iterations=ref(0)
|
|
|
const nowData = ref([]);
|
|
|
const clusterAnalysisCalculation = () => {
|
|
|
if (tableData2.value.length === 0 || input2.value == "" || input3.value == "") {
|
|
|
proxy.$modal.msgWarning("请先计算/对数据进行预处理!");
|
|
|
return;
|
|
|
}
|
|
|
if (input5.value && input6.value && input4.value) {
|
|
|
loading3.value = true;
|
|
|
const sendData = {
|
|
|
k: input5.value,
|
|
|
t: input6.value,
|
|
|
userId: JSON.parse(getUserInfo()).userId,
|
|
|
deduplicatedDataList: resTable2.value,
|
|
|
};
|
|
|
API.clusterAnalysisPlot(sendData)
|
|
|
.then((res) => {
|
|
|
showChart.value = true;
|
|
|
let sex = 1;
|
|
|
tableData3.value = [];
|
|
|
nowData.value = res.data.clusters;
|
|
|
res.data.clusters.forEach((item, index) => {
|
|
|
const newItem = {};
|
|
|
tableLabel.forEach((label, index) => {
|
|
|
if (index === 0) {
|
|
|
newItem[label.prop] = "簇" + sex; // 动态设置字段名
|
|
|
} else {
|
|
|
newItem[label.prop] = "";
|
|
|
}
|
|
|
});
|
|
|
newItem["TSuid"] = sex;
|
|
|
sex++;
|
|
|
tableData3.value.push(newItem);
|
|
|
tableData3.value[index].children = [];
|
|
|
iterations.value=res.data.iterations
|
|
|
const children = ref([]);
|
|
|
item.forEach((item2, sexx) => {
|
|
|
const newItem2 = {};
|
|
|
tableLabel.forEach((label, index2) => {
|
|
|
newItem2[label.prop] = item2[index2]; // 动态设置字段名
|
|
|
});
|
|
|
newItem2["TSuid"] = "" + sex + sexx;
|
|
|
children.value.push(newItem2);
|
|
|
});
|
|
|
tableData3.value[index].children = children.value;
|
|
|
console.log(tableData3.value);
|
|
|
});
|
|
|
})
|
|
|
.then(() => {
|
|
|
API.clusterAnalysis(sendData).then((res) => {
|
|
|
myChart.setOption({
|
|
|
series: [
|
|
|
{
|
|
|
data: [], // 清空数据
|
|
|
},
|
|
|
],
|
|
|
});
|
|
|
myChart2.setOption({
|
|
|
series: [
|
|
|
{
|
|
|
data: [], // 清空数据
|
|
|
},
|
|
|
],
|
|
|
});
|
|
|
Object.keys(res.data.dataSet).forEach((key) => {
|
|
|
const bgColor = getRandomColorHex();
|
|
|
addData(myChart, res.data.dataSet[key], bgColor);
|
|
|
addData(myChart2, res.data.dataSet[key], bgColor);
|
|
|
});
|
|
|
Object.keys(res.data.centroid).forEach((key) => {
|
|
|
addData(myChart2, res.data.centroid[key], "red");
|
|
|
});
|
|
|
setTimeout(() => {
|
|
|
loading3.value = false;
|
|
|
}, 500);
|
|
|
});
|
|
|
})
|
|
|
.catch(() => {
|
|
|
loading3.value = false;
|
|
|
});
|
|
|
nextTick(() => {
|
|
|
setTimeout(() => {
|
|
|
const scrollContainer = scrollbar.value?.$el.querySelector(".el-scrollbar__wrap");
|
|
|
if (scrollContainer) {
|
|
|
scrollContainer.scrollTo({
|
|
|
top: scrollContainer.scrollHeight,
|
|
|
behavior: "smooth", // 平滑滚动
|
|
|
});
|
|
|
}
|
|
|
}, 100);
|
|
|
});
|
|
|
} else {
|
|
|
proxy.$modal.msgWarning("请模型参数进行设置!");
|
|
|
}
|
|
|
};
|
|
|
const exportTableRef = ref(null);
|
|
|
const upLoad = () => {
|
|
|
const tableDom = exportTableRef.value?.$el;
|
|
|
if (!tableDom) {
|
|
|
return;
|
|
|
}
|
|
|
const wb = XLSX.utils.table_to_book(tableDom);
|
|
|
// XLSX.writeFile(wb, '聚类分析.xlsx');
|
|
|
const zip = new JSZip();
|
|
|
const excelBlob = XLSX.write(wb, { bookType: "xlsx", type: "array" });
|
|
|
zip.file("聚类分析.xlsx", new Blob([excelBlob], { type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" }));
|
|
|
zip.file("聚类分析图表.pdf", handleExport());
|
|
|
zip.generateAsync({ type: "blob" }).then((content) => {
|
|
|
saveAs(content, "聚类分析.zip");
|
|
|
});
|
|
|
};
|
|
|
|
|
|
const startOver = () => {
|
|
|
myChart.setOption({
|
|
|
series: [
|
|
|
{
|
|
|
data: [], // 清空数据
|
|
|
},
|
|
|
],
|
|
|
});
|
|
|
myChart2.setOption({
|
|
|
series: [
|
|
|
{
|
|
|
data: [], // 清空数据
|
|
|
},
|
|
|
],
|
|
|
});
|
|
|
input.value = "";
|
|
|
tableData.value = [];
|
|
|
tableData2.value = [];
|
|
|
tableData3.value = [];
|
|
|
input2.value = "";
|
|
|
input3.value = "";
|
|
|
input4.value = "";
|
|
|
input5.value = "";
|
|
|
input6.value = "";
|
|
|
showChart.value = false;
|
|
|
tableLabel.length = 0;
|
|
|
nowData.value = [];
|
|
|
};
|
|
|
//图表导出pdf
|
|
|
const handleExport = async (name) => {
|
|
|
var fileName = "聚类分析图表";
|
|
|
const fileList = document.getElementsByClassName("pdfRef"); // 很重要
|
|
|
const pdfBlob = await htmlPdf(fileName, document.querySelector("#pdfRef"), fileList, true);
|
|
|
return pdfBlob;
|
|
|
};
|
|
|
</script>
|
|
|
|
|
|
<style lang="scss" scoped>
|
|
|
.main-top {
|
|
|
margin: 20px 20px 0;
|
|
|
margin-top: 10px;
|
|
|
height: 85px;
|
|
|
background: #001d40;
|
|
|
border: 1px solid #0452c6;
|
|
|
padding: 5px 20px;
|
|
|
}
|
|
|
.app-main {
|
|
|
// background-color: #f5f5f5;
|
|
|
// min-height: 965px;
|
|
|
margin: 0px 20px 0;
|
|
|
height: 100%;
|
|
|
display: flex;
|
|
|
// overflow: auto;
|
|
|
.main-left {
|
|
|
//水平居中
|
|
|
flex-direction: column;
|
|
|
// min-height: 965px;
|
|
|
display: flex;
|
|
|
width: 205px;
|
|
|
height: 100%;
|
|
|
background: #041c49;
|
|
|
// padding: 0px 10px;
|
|
|
.left-top {
|
|
|
padding: 10px;
|
|
|
span {
|
|
|
font-weight: bold;
|
|
|
font-size: 18px;
|
|
|
color: #3596eb;
|
|
|
}
|
|
|
}
|
|
|
.metrics-table {
|
|
|
.el-button {
|
|
|
width: 149px;
|
|
|
height: 48px;
|
|
|
background: url("../../../assets/images/确定.png");
|
|
|
color: #ffffff;
|
|
|
border-color: #ffffff00;
|
|
|
background-size: 100% 100%;
|
|
|
margin: 15px;
|
|
|
}
|
|
|
.el-input {
|
|
|
--el-input-text-color: #ffffff;
|
|
|
--el-input-bg-color: #002651;
|
|
|
--el-input-border-color: #67b3d6;
|
|
|
}
|
|
|
}
|
|
|
.startOver {
|
|
|
width: 150px;
|
|
|
height: 40px;
|
|
|
background: #00f4ff;
|
|
|
margin: auto;
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
justify-content: center;
|
|
|
cursor: pointer;
|
|
|
}
|
|
|
:deep(.el-table) {
|
|
|
--el-table-row-hover-bg-color: #002651 !important;
|
|
|
--el-table-border-color: #7ac1ff;
|
|
|
.el-table__row {
|
|
|
background-color: #002652 !important;
|
|
|
color: #e6e6e6;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
.main-right {
|
|
|
width: 1639px;
|
|
|
// height: 100%;
|
|
|
// padding: 20px;
|
|
|
border: 1px solid #0452c6;
|
|
|
:deep(.el-table__header) {
|
|
|
.el-table__cell {
|
|
|
background: linear-gradient(0deg, #5304be 0%, #9126fe 100%) !important;
|
|
|
}
|
|
|
}
|
|
|
:deep(.el-table) {
|
|
|
--el-table-row-hover-bg-color: #002651 !important;
|
|
|
--el-table-border-color: #002651;
|
|
|
--el-table-bg-color: #ffffff00;
|
|
|
.el-table__row {
|
|
|
background-color: #002652 !important;
|
|
|
color: #e6e6e6;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
.metrics {
|
|
|
:deep(.el-input) {
|
|
|
.el-input__wrapper {
|
|
|
background-color: #ffffff00;
|
|
|
background-image: url("../../../assets/images/下拉框.png");
|
|
|
background-size: 100% 100%;
|
|
|
}
|
|
|
--el-input-text-color: #ffffff;
|
|
|
--el-input-border-color: #ffffff00;
|
|
|
--el-input-hover-border-color: #ffffff00;
|
|
|
--el-input-hover-border: #ffffff00;
|
|
|
--el-select-border-color-hover: #ffffff00;
|
|
|
--el-select-input-focus-border-color: #ffffff00;
|
|
|
--el-input-placeholder-color: #ffffff;
|
|
|
.el-input__inner {
|
|
|
color: #ffffff;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
.analysisResults {
|
|
|
.el-button {
|
|
|
width: 89px;
|
|
|
height: 31px;
|
|
|
background: url("../../../assets/images/下载.png");
|
|
|
color: black;
|
|
|
border-color: #ffffff00;
|
|
|
// background-size: 100% 100%;
|
|
|
margin: 15px 0px 5px;
|
|
|
}
|
|
|
}
|
|
|
:deep(.el-form-item__label) {
|
|
|
color: #ffffff;
|
|
|
}
|
|
|
.from-item {
|
|
|
padding: 20px;
|
|
|
.el-input {
|
|
|
--el-input-text-color: #ffffff;
|
|
|
--el-input-bg-color: #002651;
|
|
|
--el-input-border-color: #67b3d6;
|
|
|
}
|
|
|
}
|
|
|
.dialog-footer {
|
|
|
margin-bottom: 20px;
|
|
|
}
|
|
|
:deep(.el-table__expand-icon) {
|
|
|
color: #9126fe;
|
|
|
.el-icon {
|
|
|
font-size: 16px;
|
|
|
}
|
|
|
}
|
|
|
</style>
|