人物互动隐关系预测二级界面跳转,一级界面互动隐关系已标识
This commit is contained in:
parent
49b4f2edd0
commit
d79cb47cea
|
|
@ -1 +1 @@
|
||||||
VITE_APP_BASE_API = "http://127.0.0.1:3000/api"
|
VITE_APP_BASE_API = "http://10.7.1.183:8080/api"
|
||||||
|
|
@ -5,12 +5,11 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { defineProps, onMounted, ref, defineEmits } from "vue"
|
import { onMounted, defineEmits, inject } from "vue"
|
||||||
import * as echarts from "echarts"
|
import * as echarts from "echarts"
|
||||||
import nodeHoverImg from "@/assets/images/nodeHover.png"
|
import nodeHoverImg from "@/assets/images/nodeHover.png"
|
||||||
import { inject } from "vue"
|
|
||||||
let chart = null
|
let chart = null
|
||||||
const emit = defineEmits(["click:node"])
|
const emit = defineEmits(["click:node", "click:edge"])
|
||||||
|
|
||||||
const initChart = async () => {
|
const initChart = async () => {
|
||||||
chart = echarts.init(document.getElementById("container"))
|
chart = echarts.init(document.getElementById("container"))
|
||||||
|
|
@ -20,10 +19,32 @@ const initChart = async () => {
|
||||||
id: parseInt(item.id),
|
id: parseInt(item.id),
|
||||||
name: parseInt(item.id),
|
name: parseInt(item.id),
|
||||||
isIncludePredictNodes: item.isIncludePredictNodes,
|
isIncludePredictNodes: item.isIncludePredictNodes,
|
||||||
nodesNum: item.nodesNum
|
nodesNum: item.nodesNum,
|
||||||
|
neighbors: item.neighbors.map((item) => ({ ...item, name: parseInt(item.id) })),
|
||||||
|
category: item.isIncludePredictNodes ? 1 : 0
|
||||||
}))
|
}))
|
||||||
|
|
||||||
const links = [] //待处理
|
//处理连边
|
||||||
|
const links = []
|
||||||
|
const edgeSet = new Set() //去除重复边
|
||||||
|
nodes.forEach((communityNode) => {
|
||||||
|
communityNode.neighbors.forEach((communityNei) => {
|
||||||
|
const key = [communityNode.name, communityNei.name].sort().join("-")
|
||||||
|
if (edgeSet.has(key)) return
|
||||||
|
links.push({
|
||||||
|
source: communityNode.name,
|
||||||
|
target: communityNei.name,
|
||||||
|
edge: communityNei.isHidden ? 1 : 0, //该边存在互动隐关系则权值为1,否则为0
|
||||||
|
lineStyle: {
|
||||||
|
width: communityNei.isHidden ? 4 : 1, // 无互动=细线,有互动=加粗
|
||||||
|
color: communityNei.isHidden ? "#f8bf38" : "#37ACD7", // 无互动=灰色,有互动=黄色
|
||||||
|
type: communityNei.isHidden ? "dashed" : "solid", // 无互动=实线,有互动=虚线
|
||||||
|
dashArray: [2, 1] // 2像素实线,1像素空白
|
||||||
|
}
|
||||||
|
})
|
||||||
|
edgeSet.add(key)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
const data = { nodes, links }
|
const data = { nodes, links }
|
||||||
|
|
||||||
|
|
@ -84,7 +105,6 @@ const initChart = async () => {
|
||||||
backgroundColor: "rgba(0,0,0,0)", // 透明背景
|
backgroundColor: "rgba(0,0,0,0)", // 透明背景
|
||||||
borderColor: "rgba(0,0,0,0)", // 透明边框
|
borderColor: "rgba(0,0,0,0)", // 透明边框
|
||||||
borderWidth: 0,
|
borderWidth: 0,
|
||||||
|
|
||||||
extraCssText: "box-shadow:none;padding:0;",
|
extraCssText: "box-shadow:none;padding:0;",
|
||||||
formatter: function (params) {
|
formatter: function (params) {
|
||||||
if (params.dataType === "node") {
|
if (params.dataType === "node") {
|
||||||
|
|
@ -110,15 +130,14 @@ const initChart = async () => {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
//权值设置样式
|
edgeLabel: {
|
||||||
// edgeLabel: {
|
show: false,
|
||||||
// show: false,
|
position: "middle",
|
||||||
// position: "middle",
|
formatter: function (params) {
|
||||||
// formatter: function (params) {
|
return params.data.edge
|
||||||
// return params.data.edge
|
},
|
||||||
// },
|
fontSize: 14
|
||||||
// fontSize: 14
|
},
|
||||||
// },
|
|
||||||
emphasis: {
|
emphasis: {
|
||||||
edgeLabel: {
|
edgeLabel: {
|
||||||
show: true,
|
show: true,
|
||||||
|
|
@ -137,10 +156,11 @@ const initChart = async () => {
|
||||||
animation: false,
|
animation: false,
|
||||||
draggable: true,
|
draggable: true,
|
||||||
roam: true,
|
roam: true,
|
||||||
zoom: 0.4,
|
zoom: 0.3,
|
||||||
categories: categories,
|
categories: categories,
|
||||||
|
|
||||||
force: {
|
force: {
|
||||||
edgeLength: 2000,
|
edgeLength: 2500,
|
||||||
repulsion: 4000,
|
repulsion: 4000,
|
||||||
gravity: 0.4,
|
gravity: 0.4,
|
||||||
friction: 0.02,
|
friction: 0.02,
|
||||||
|
|
@ -201,14 +221,20 @@ const initChart = async () => {
|
||||||
}
|
}
|
||||||
chart.setOption(option)
|
chart.setOption(option)
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleClickNode = () => {
|
const handleClickNode = () => {
|
||||||
chart.on("click", function (params) {
|
chart.on("click", function (params) {
|
||||||
//params.data.name才是社团id
|
|
||||||
if (params.dataType === "node") {
|
if (params.dataType === "node") {
|
||||||
emit("click:node", params.data)
|
emit("click:node", params.data)
|
||||||
|
} else if (params.dataType == "edge") {
|
||||||
|
const { data } = params
|
||||||
|
if (data.edge) {
|
||||||
|
emit("click:edge", data)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
await initChart()
|
await initChart()
|
||||||
handleClickNode()
|
handleClickNode()
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,12 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="detailNode-component">
|
<div class="detailNode-component">
|
||||||
|
<img src="@/assets/images/icon/goback.png" alt="" class="goback" @click="handleGoback" />
|
||||||
<div class="graph-container" id="container"></div>
|
<div class="graph-container" id="container"></div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { defineProps } from "vue"
|
import { defineProps, defineEmits } from "vue"
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
detailNode: {
|
detailNode: {
|
||||||
|
|
@ -13,6 +14,10 @@ const props = defineProps({
|
||||||
default: () => []
|
default: () => []
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
const emit = defineEmits(["click:goback"])
|
||||||
|
const handleGoback = () => {
|
||||||
|
emit("click:goback", "CommunityNode")
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="less">
|
<style scoped lang="less">
|
||||||
|
|
@ -20,6 +25,12 @@ const props = defineProps({
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
.goback {
|
||||||
|
position: absolute;
|
||||||
|
top: 10px;
|
||||||
|
left: 20px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
.graph-container {
|
.graph-container {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 93%;
|
height: 93%;
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,12 @@
|
||||||
<div class="graph-component">
|
<div class="graph-component">
|
||||||
<img :src="title" alt="" class="title" />
|
<img :src="title" alt="" class="title" />
|
||||||
|
|
||||||
<CommunityNode v-if="curComponent == '社团节点'" @click:node="handleClickNode"></CommunityNode>
|
<CommunityNode
|
||||||
<DetailNode v-else></DetailNode>
|
v-if="curComponent == 'CommunityNode'"
|
||||||
|
@click:node="handleClickNode"
|
||||||
|
@click:edge="handleClickEdge"
|
||||||
|
></CommunityNode>
|
||||||
|
<DetailNode v-else @click:goback="handleClickGoBack"></DetailNode>
|
||||||
|
|
||||||
<div class="time-axis"></div>
|
<div class="time-axis"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -15,7 +19,7 @@ import CommunityNode from "./communityNode.vue"
|
||||||
import DetailNode from "./detailNode.vue"
|
import DetailNode from "./detailNode.vue"
|
||||||
import { useCharacterInteractionStore } from "@/store/llinkPrediction/index"
|
import { useCharacterInteractionStore } from "@/store/llinkPrediction/index"
|
||||||
const interactionStore = useCharacterInteractionStore()
|
const interactionStore = useCharacterInteractionStore()
|
||||||
const curComponent = ref("社团节点")
|
const curComponent = ref("CommunityNode")
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
title: {
|
title: {
|
||||||
type: String,
|
type: String,
|
||||||
|
|
@ -27,6 +31,16 @@ const handleClickNode = (nodeInfo) => {
|
||||||
interactionStore.initGraphCommunityDetailNode([nodeInfo.id])
|
interactionStore.initGraphCommunityDetailNode([nodeInfo.id])
|
||||||
console.log(nodeInfo)
|
console.log(nodeInfo)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleClickEdge = (edgeInfo) => {
|
||||||
|
curComponent.value = "detailNode"
|
||||||
|
const ids = [edgeInfo.source, edgeInfo.target]
|
||||||
|
console.log(ids)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleClickGoBack = (currentComponentName) => {
|
||||||
|
curComponent.value = currentComponentName
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="less">
|
<style scoped lang="less">
|
||||||
|
|
|
||||||
|
|
@ -47,8 +47,6 @@ watch(riskEventIndex, (newIndex) => {
|
||||||
const tooltipList = keyNodeStore.tooltipList
|
const tooltipList = keyNodeStore.tooltipList
|
||||||
|
|
||||||
const highRiskCount = tooltipList[newIndex].filter((item) => item.riskType == "高风险").length
|
const highRiskCount = tooltipList[newIndex].filter((item) => item.riskType == "高风险").length
|
||||||
console.log(tooltipList[newIndex])
|
|
||||||
|
|
||||||
keyNodeStore.statisticsList[5].number = highRiskCount
|
keyNodeStore.statisticsList[5].number = highRiskCount
|
||||||
keyNodeStore.statisticsList[4].number = tooltipList[newIndex].length
|
keyNodeStore.statisticsList[4].number = tooltipList[newIndex].length
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -6,48 +6,48 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { defineProps, onMounted, ref } from "vue";
|
import { defineProps, onMounted, ref } from "vue"
|
||||||
import * as echarts from "echarts";
|
import * as echarts from "echarts"
|
||||||
import { cropToCircleAsync } from "@/utils/transform";
|
import { cropToCircleAsync } from "@/utils/transform"
|
||||||
import anchorNeighbors from "@/assets/json/anchor_neighbors.json";
|
import anchorNeighbors from "@/assets/json/anchor_neighbors.json"
|
||||||
import nodeHoverImg from "@/assets/images/nodeHover.png";
|
import nodeHoverImg from "@/assets/images/nodeHover.png"
|
||||||
import { useKeyNodeRecognitionStore } from "@/store/keyNodeRecognition/index";
|
import { useKeyNodeRecognitionStore } from "@/store/keyNodeRecognition/index"
|
||||||
const keyNodeStore = useKeyNodeRecognitionStore();
|
const keyNodeStore = useKeyNodeRecognitionStore()
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
communityNode: {
|
communityNode: {
|
||||||
type: Object,
|
type: Object,
|
||||||
default: 0
|
default: 0
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
const emit = defineEmits(["click:openDialog", "click:goback"]);
|
const emit = defineEmits(["click:openDialog", "click:goback"])
|
||||||
const detailContainer = ref(null);
|
const detailContainer = ref(null)
|
||||||
const currentSelectedCommunity = ref({});
|
const currentSelectedCommunity = ref({})
|
||||||
let chart = null;
|
let chart = null
|
||||||
|
|
||||||
// 根据节点数量计算缩放比例
|
// 根据节点数量计算缩放比例
|
||||||
const calculateInitialZoom = (nodes) => {
|
const calculateInitialZoom = (nodes) => {
|
||||||
const NODE_COUNT = nodes.length;
|
const NODE_COUNT = nodes.length
|
||||||
if (NODE_COUNT > 1000) return 0.1;
|
if (NODE_COUNT > 1000) return 0.1
|
||||||
if (NODE_COUNT > 200) return 0.2;
|
if (NODE_COUNT > 200) return 0.2
|
||||||
if (NODE_COUNT > 100) return 0.3;
|
if (NODE_COUNT > 100) return 0.3
|
||||||
if (NODE_COUNT > 50) return 0.4;
|
if (NODE_COUNT > 50) return 0.4
|
||||||
return 0.8; // 默认值
|
return 0.8 // 默认值
|
||||||
};
|
}
|
||||||
|
|
||||||
const initChart = async () => {
|
const initChart = async () => {
|
||||||
currentSelectedCommunity.value = props.communityNode;
|
currentSelectedCommunity.value = props.communityNode
|
||||||
|
|
||||||
chart = echarts.init(document.getElementById("container"));
|
chart = echarts.init(document.getElementById("container"))
|
||||||
//处理节点
|
//处理节点
|
||||||
//从锚点邻居数据集中查找出该锚点所有的邻居节点
|
//从锚点邻居数据集中查找出该锚点所有的邻居节点
|
||||||
let nodes = [];
|
let nodes = []
|
||||||
|
|
||||||
//批量将头像转换成base64
|
//批量将头像转换成base64
|
||||||
const extraInfo = keyNodeStore.anchorExtraInfos;
|
const extraInfo = keyNodeStore.anchorExtraInfos
|
||||||
const keys = Object.keys(extraInfo);
|
const keys = Object.keys(extraInfo)
|
||||||
for (const key of keys) {
|
for (const key of keys) {
|
||||||
const userInfo = extraInfo[key];
|
const userInfo = extraInfo[key]
|
||||||
userInfo.avatar = await cropToCircleAsync(userInfo.avatar);
|
userInfo.avatar = await cropToCircleAsync(userInfo.avatar)
|
||||||
}
|
}
|
||||||
|
|
||||||
//筛选出本社团中所有锚点的邻居节点并重新将需要补充的数据映射到该列表中
|
//筛选出本社团中所有锚点的邻居节点并重新将需要补充的数据映射到该列表中
|
||||||
|
|
@ -57,40 +57,40 @@ const initChart = async () => {
|
||||||
...extraInfo[filteredList[0]],
|
...extraInfo[filteredList[0]],
|
||||||
anchor: filteredList[0],
|
anchor: filteredList[0],
|
||||||
neighbors: filteredList[1].map((neighNode) => ({ name: neighNode, avatar: "" }))
|
neighbors: filteredList[1].map((neighNode) => ({ name: neighNode, avatar: "" }))
|
||||||
}));
|
}))
|
||||||
|
|
||||||
//处理连线
|
//处理连线
|
||||||
let links = [];
|
let links = []
|
||||||
filterResult.forEach(({ anchor, neighbors }) => {
|
filterResult.forEach(({ anchor, neighbors }) => {
|
||||||
(neighbors ?? []).forEach((neigh) => {
|
;(neighbors ?? []).forEach((neigh) => {
|
||||||
links.push({ source: anchor, target: neigh.name });
|
links.push({ source: anchor, target: neigh.name })
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
|
|
||||||
// 合并所有锚点和邻居节点到一个数组并去重
|
// 合并所有锚点和邻居节点到一个数组并去重
|
||||||
let nodeSet = new Set();
|
let nodeSet = new Set()
|
||||||
filterResult.forEach((item) => {
|
filterResult.forEach((item) => {
|
||||||
//添加锚点自己
|
//添加锚点自己
|
||||||
if (!nodeSet.has(item.anchor)) {
|
if (!nodeSet.has(item.anchor)) {
|
||||||
nodes.push({ name: item?.anchor, value: item?.anchor, category: 1, ...item });
|
nodes.push({ name: item?.anchor, value: item?.anchor, category: 1, ...item })
|
||||||
nodeSet.add(item?.anchor);
|
nodeSet.add(item?.anchor)
|
||||||
}
|
}
|
||||||
//添加该锚点的邻居
|
//添加该锚点的邻居
|
||||||
(item.neighbors || []).forEach((n) => {
|
;(item.neighbors || []).forEach((n) => {
|
||||||
if (!nodeSet.has(n?.name)) {
|
if (!nodeSet.has(n?.name)) {
|
||||||
nodes.push({ name: n.name, value: n.name, category: 0, avatar: n.avatar ?? "" });
|
nodes.push({ name: n.name, value: n.name, category: 0, avatar: n.avatar ?? "" })
|
||||||
nodeSet.add(n.name);
|
nodeSet.add(n.name)
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
|
|
||||||
const data = { nodes, links };
|
const data = { nodes, links }
|
||||||
console.log(data);
|
console.log(data)
|
||||||
|
|
||||||
const categories = [
|
const categories = [
|
||||||
{ name: "邻居账号", category: 0 },
|
{ name: "邻居账号", category: 0 },
|
||||||
{ name: "锚点账号", category: 1 }
|
{ name: "锚点账号", category: 1 }
|
||||||
];
|
]
|
||||||
|
|
||||||
const option = {
|
const option = {
|
||||||
//图例配置
|
//图例配置
|
||||||
|
|
@ -178,7 +178,7 @@ const initChart = async () => {
|
||||||
<div style="margin-left: 20px">粉丝数: ${params.data.fancy}</div>
|
<div style="margin-left: 20px">粉丝数: ${params.data.fancy}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>`;
|
</div>`
|
||||||
} else if (params.dataType === "node" && !params.data.category) {
|
} else if (params.dataType === "node" && !params.data.category) {
|
||||||
return `<div
|
return `<div
|
||||||
style="
|
style="
|
||||||
|
|
@ -211,7 +211,7 @@ const initChart = async () => {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>`;
|
</div>`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -273,29 +273,37 @@ const initChart = async () => {
|
||||||
lineStyle: {
|
lineStyle: {
|
||||||
color: "#37ACD7",
|
color: "#37ACD7",
|
||||||
width: 1
|
width: 1
|
||||||
|
},
|
||||||
|
emphasis: {
|
||||||
|
// 高亮配置
|
||||||
|
focus: "adjacency", // 高亮相邻节点
|
||||||
|
lineStyle: {
|
||||||
|
// 高亮时线条样式
|
||||||
|
width: 10 // 线条宽度(10)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
};
|
}
|
||||||
|
|
||||||
chart.setOption(option);
|
chart.setOption(option)
|
||||||
};
|
}
|
||||||
|
|
||||||
const handleClickNode = () => {
|
const handleClickNode = () => {
|
||||||
chart.on("click", function (params) {
|
chart.on("click", function (params) {
|
||||||
if (params.dataType == "node" && params.data.category == 1) {
|
if (params.dataType == "node" && params.data.category == 1) {
|
||||||
emit("click:openDialog", params.data);
|
emit("click:openDialog", params.data)
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
};
|
}
|
||||||
|
|
||||||
const handleGoback = () => {
|
const handleGoback = () => {
|
||||||
emit("click:goback", "CommunityNode");
|
emit("click:goback", "CommunityNode")
|
||||||
};
|
}
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
await initChart();
|
await initChart()
|
||||||
handleClickNode();
|
handleClickNode()
|
||||||
});
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="less">
|
<style scoped lang="less">
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user