Merge branch 'master' of http://172.16.20.1:3000/duanhao/SocialNetworks_duan
This commit is contained in:
commit
4bf765aa75
BIN
src/assets/images/bridge_node_hoverbgimg.png
Normal file
BIN
src/assets/images/bridge_node_hoverbgimg.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.3 KiB |
BIN
src/assets/images/icon/bridge_node_legend.png
Normal file
BIN
src/assets/images/icon/bridge_node_legend.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.4 KiB |
BIN
src/assets/images/icon/community_node_legend.png
Normal file
BIN
src/assets/images/icon/community_node_legend.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.5 KiB |
1236
src/assets/json/bridge_neighbors_communities.json
Normal file
1236
src/assets/json/bridge_neighbors_communities.json
Normal file
File diff suppressed because it is too large
Load Diff
12
src/assets/json/bridge_nodes.json
Normal file
12
src/assets/json/bridge_nodes.json
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
[
|
||||
{"postsId": 1, "postsName": "President Biden Archive", "Node": "1349149096909668363","Number_of_across_communities": 226,"Community_of_affiliation": 0, "Community_size": 5965 },
|
||||
{"postsId": 2, "postsName": "Joe Truzman", "Node": "3006348240","Number_of_across_communities": 121,"Community_of_affiliation": 13, "Community_size": 827 },
|
||||
{"postsId": 3, "postsName": "OSINTdefender", "Node": "1457867047334031360","Number_of_across_communities": 81,"Community_of_affiliation": 4, "Community_size": 2323 },
|
||||
{"postsId": 4, "postsName": "Paul Golding", "Node": "455264233","Number_of_across_communities": 80, "Community_of_affiliation": 17, "Community_size": 812 },
|
||||
{"postsId": 5, "postsName": "Rep. Matt Gaetz", "Node": "818948638890217473","Number_of_across_communities": 97,"Community_of_affiliation": 3, "Community_size": 3075 },
|
||||
{"postsId": 6, "postsName": "Israel Defense Forces", "Node": "18576537","Number_of_across_communities": 349,"Community_of_affiliation": 6, "Community_size": 1178 },
|
||||
{"postsId": 7, "postsName": "Andy Ngo", "Node": "2835451658","Number_of_across_communities": 243,"Community_of_affiliation": 7, "Community_size": 1170 },
|
||||
{"postsId": 8, "postsName": "Secretary Antony Blinken", "Node": "1350150750966603777","Number_of_across_communities": 113,"Community_of_affiliation": 0, "Community_size": 5965 },
|
||||
{"postsId": 9, "postsName": "Emmanuel Macron", "Node": "1976143068","Number_of_across_communities": 240,"Community_of_affiliation": 5, "Community_size": 1195 },
|
||||
{"postsId": 10, "postsName": "Jackson Hinkle 🇺🇸", "Node": "1151913018936053760","Number_of_across_communities": 98,"Community_of_affiliation": 19, "Community_size": 784 }
|
||||
]
|
||||
|
|
@ -182,7 +182,7 @@ onUnmounted(() => {
|
|||
/* 容器样式,无边框 */
|
||||
.panel-container {
|
||||
width: 350px;
|
||||
height: 276px;
|
||||
height: 257px;
|
||||
background: rgba(4, 20, 33, 0.4);
|
||||
border-radius: 2px;
|
||||
box-shadow: 0 0 18px 0 #0a2e55 inset;
|
||||
|
|
@ -245,6 +245,7 @@ onUnmounted(() => {
|
|||
.chart-container {
|
||||
flex-grow: 1;
|
||||
width: 100%;
|
||||
height: 180px;
|
||||
margin-top: -20px;
|
||||
margin-left: -10px;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -117,7 +117,7 @@ onBeforeUnmount(() => {
|
|||
<style scoped>
|
||||
.post-dynamics-container {
|
||||
width: 800px;
|
||||
height: 275px; /* Set a fixed height to enable scrolling for the list */
|
||||
height: 257px; /* Set a fixed height to enable scrolling for the list */
|
||||
padding: 16px;
|
||||
background-color: #03102a; /* A dark blue background to mimic the screenshot context */
|
||||
font-family: "PingFang SC", sans-serif;
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ const props = defineProps({
|
|||
const scanAngle = ref(0);
|
||||
const scanTimer = ref(null);
|
||||
const containerWidth = 370;
|
||||
const containerHeight = 276;
|
||||
const containerHeight = 257;
|
||||
|
||||
const words = ref(props.wordsCloudList);
|
||||
|
||||
|
|
@ -124,7 +124,7 @@ onBeforeUnmount(() => {
|
|||
.word-cloud-container {
|
||||
position: relative;
|
||||
width: 370px;
|
||||
height: 276px;
|
||||
height: 257px;
|
||||
background-color: rgba(4, 20, 33, 0.4);
|
||||
background-image: linear-gradient(to right, rgba(6, 61, 113, 0.2), rgba(8, 30, 56, 0.8));
|
||||
border: none;
|
||||
|
|
|
|||
|
|
@ -250,7 +250,9 @@ export const useKeyNodeStore2 = defineStore('keyNode2', () => {
|
|||
{postsId: 10, postsName: "Jackson Hinkle 🇺🇸", 'Node': '1151913018936053760','Number_of_across_communities': 98,'Community_of_affiliation': 19, 'Community_size': 784 },
|
||||
])
|
||||
|
||||
// 当前激活的时间点
|
||||
const activeTimePoint = ref(1);
|
||||
// 所有时间点数据
|
||||
const timePoints = ref([]);
|
||||
const activeLeader = ref(null);
|
||||
const currentPost = ref(null);
|
||||
|
|
@ -287,6 +289,7 @@ export const useKeyNodeStore2 = defineStore('keyNode2', () => {
|
|||
};
|
||||
};
|
||||
|
||||
// 初始化时间点数据
|
||||
function initializeTimePoints() {
|
||||
if (timePoints.value.length > 0) return;
|
||||
timePoints.value = allLeaderData.value.map((leader, index) => ({
|
||||
|
|
@ -296,6 +299,7 @@ export const useKeyNodeStore2 = defineStore('keyNode2', () => {
|
|||
}));
|
||||
}
|
||||
|
||||
// 设置激活的时间点
|
||||
function setActiveTimePoint(id) {
|
||||
activeTimePoint.value = id;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -46,11 +46,12 @@
|
|||
<div class="chart-container">
|
||||
<DynamicGraph
|
||||
ref="leaderGraphRef"
|
||||
v-if="false"
|
||||
:timestamp="store.activeTimePoint"
|
||||
:allLeaderData="store.allLeaderData"
|
||||
@handle:openDialog="handleGraphNodeClick"
|
||||
/>
|
||||
<bridgeGroup v-if="false"></bridgeGroup>
|
||||
<BridgeCommunityGraph v-if="true" />
|
||||
</div>
|
||||
<div class="timeline-container">
|
||||
<span class="time-label">2023.10.07 00:00:00</span>
|
||||
|
|
@ -97,7 +98,7 @@
|
|||
import { ref, defineExpose, watch, computed } from 'vue';
|
||||
import { useKeyNodeStore2 } from '@/store/keyNodeStore2';
|
||||
import DynamicGraph from "./graph/dynamicGraph.vue";
|
||||
import bridgeGroup from './graph/bridgeGroup.vue';
|
||||
import BridgeCommunityGraph from './graph/bridgeCommunityGraph.vue';
|
||||
|
||||
const store = useKeyNodeStore2();
|
||||
const leaderGraphRef = ref(null);
|
||||
|
|
|
|||
|
|
@ -130,9 +130,7 @@ const isVisible = computed({
|
|||
}
|
||||
.leader-post-detail-content {
|
||||
width: 100%;
|
||||
background:
|
||||
linear-gradient(0deg, #0d2743, #0d2743),
|
||||
linear-gradient(270deg, rgba(147, 210, 255, 0.06) 0%, rgba(147, 210, 255, 0.16) 100%);
|
||||
background: linear-gradient(90deg, #3e607c71 0%, #2f455f62 100%);
|
||||
margin-top: 30px;
|
||||
height: 262px;
|
||||
overflow: auto;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,260 @@
|
|||
<template>
|
||||
<div id="bridgeCommunityChart" style="width: 100%; height: 100%;"></div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { onMounted, onUnmounted } from 'vue';
|
||||
import * as echarts from 'echarts';
|
||||
import bridgeData from '@/assets/json/bridge_neighbors_communities.json';
|
||||
import bridgeNodeHoverBgImg from '@/assets/images/bridge_node_hoverbgimg.png'
|
||||
|
||||
let chartInstance = null;
|
||||
|
||||
// 处理数据生成ECharts所需格式
|
||||
const processData = () => {
|
||||
const nodes = [];
|
||||
const links = [];
|
||||
const addedCommunities = new Set();
|
||||
|
||||
// 找出usersNum的最小值和最大值,用于归一化
|
||||
let minUsersNum = Infinity;
|
||||
let maxUsersNum = -Infinity;
|
||||
|
||||
bridgeData.forEach(item => {
|
||||
item.bridgeCommunities.forEach(community => {
|
||||
minUsersNum = Math.min(minUsersNum, community.usersNum);
|
||||
maxUsersNum = Math.max(maxUsersNum, community.usersNum);
|
||||
});
|
||||
});
|
||||
|
||||
// 节点大小的范围
|
||||
const minNodeSize = 30;
|
||||
const maxNodeSize = 45;
|
||||
|
||||
// 添加桥梁节点和社团节点
|
||||
bridgeData.forEach(item => {
|
||||
const bridgeId = item.bridgeId;
|
||||
|
||||
// 添加桥梁节点 - 可以调整这个值来改变桥梁节点大小
|
||||
nodes.push({
|
||||
id: `bridge_${bridgeId}`,
|
||||
name: `桥梁节点 ${bridgeId.substring(0, 5)}...`,
|
||||
category: 0,
|
||||
value: 20 // 桥梁节点大小(可修改)
|
||||
});
|
||||
|
||||
// 添加社团节点和边
|
||||
item.bridgeCommunities.forEach(community => {
|
||||
const communityId = community.communityId;
|
||||
const communityKey = `community_${communityId}`;
|
||||
const usersNum = community.usersNum;
|
||||
|
||||
// 归一化usersNum到节点大小范围
|
||||
const size = minNodeSize +
|
||||
((usersNum - minUsersNum) / (maxUsersNum - minUsersNum)) *
|
||||
(maxNodeSize - minNodeSize);
|
||||
|
||||
if (!addedCommunities.has(communityKey)) {
|
||||
nodes.push({
|
||||
id: communityKey,
|
||||
name: `社团 ${communityId} (${usersNum})`,
|
||||
category: 1,
|
||||
value: size // 根据usersNum动态设置大小
|
||||
});
|
||||
addedCommunities.add(communityKey);
|
||||
}
|
||||
|
||||
links.push({
|
||||
source: `bridge_${bridgeId}`,
|
||||
target: communityKey,
|
||||
value: 1
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
return {
|
||||
nodes,
|
||||
links
|
||||
};
|
||||
};
|
||||
|
||||
// 初始化图表
|
||||
const initChart = () => {
|
||||
if (chartInstance) {
|
||||
chartInstance.dispose();
|
||||
}
|
||||
|
||||
const chartDom = document.getElementById('bridgeCommunityChart');
|
||||
if (!chartDom) return;
|
||||
|
||||
chartInstance = echarts.init(chartDom);
|
||||
|
||||
const { nodes, links } = processData();
|
||||
|
||||
const categories = [
|
||||
{name: "桥梁节点", category: 0},
|
||||
{name: "社团节点", category: 1}
|
||||
]
|
||||
|
||||
const option = {
|
||||
//hover上去的窗口
|
||||
tooltip: {
|
||||
trigger: "item",
|
||||
trigger: "item",
|
||||
backgroundColor: "rgba(0,0,0,0)", // 透明背景
|
||||
borderColor: "rgba(0,0,0,0)", // 透明边框
|
||||
borderWidth: 0,
|
||||
extraCssText: "box-shadow:none;padding:0;",
|
||||
textStyle: {
|
||||
color: '#fff', // 设置字体颜色为白色
|
||||
fontSize: 14,
|
||||
},
|
||||
formatter: function(params) {
|
||||
// 判断节点类型
|
||||
if(params.data.category === 1) {
|
||||
// 社团节点
|
||||
const usersNum = params.data.name.match(/\((\d+)\)/)?.[1] || 0;
|
||||
return `<div
|
||||
style = "
|
||||
width: 126px;
|
||||
height: 44px;
|
||||
border-radius: 4px;
|
||||
background: url('${bridgeNodeHoverBgImg}');
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
">
|
||||
<div style="color:#fff;letter-spacing: 0.14px;">
|
||||
社团用户数:${usersNum}
|
||||
</div>
|
||||
</div>`;
|
||||
}else {
|
||||
// 桥梁节点: 查找对应的bridgeCommunities长度
|
||||
const bridgeItem = bridgeData.find(item => item.bridgeId === params.data.id.replace('bridge_', ''));
|
||||
const communityCount = bridgeItem ? bridgeItem.bridgeCommunities.length : 0;
|
||||
return `<div
|
||||
style = "
|
||||
width: 126px;
|
||||
height: 44px;
|
||||
border-radius: 4px;
|
||||
background: url('${bridgeNodeHoverBgImg}');
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
padding:20px 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
">
|
||||
链接社团数:${communityCount}
|
||||
</div>`;
|
||||
}
|
||||
},
|
||||
},
|
||||
//图例配置
|
||||
legend: [
|
||||
{
|
||||
data: categories.map((c) => ({
|
||||
name: c.name,
|
||||
// 锚点账号用圆形头像,普通账号保持默认
|
||||
// 添加自定义图标
|
||||
icon: c.category === 1
|
||||
? `image://${new URL('@/assets/images/icon/community_node_legend.png', import.meta.url)}`
|
||||
: `image://${new URL('@/assets/images/icon/bridge_node_legend.png', import.meta.url)}`
|
||||
})),
|
||||
right: 15,
|
||||
bottom: 13,
|
||||
icon: "circle",
|
||||
orient: "vertical",
|
||||
itemWidth: 16,
|
||||
itemHeight: 16,
|
||||
itemGap: 12,
|
||||
backgroundColor: "rgba(0,67,125,0.56)", // 半透明深蓝
|
||||
borderRadius: 8, // 圆角
|
||||
borderColor: "#c2f2ff", // 淡蓝色边框
|
||||
borderWidth: 0.3,
|
||||
padding: [12, 20, 12, 20], // 上右下左
|
||||
textStyle: {
|
||||
color: "#fff",
|
||||
fontSize: 16,
|
||||
fontWeight: "normal"
|
||||
}
|
||||
}
|
||||
],
|
||||
series: [
|
||||
{
|
||||
type: 'graph',
|
||||
zoom: 0.8,
|
||||
layout: 'force',
|
||||
animation: false,
|
||||
draggable: true,
|
||||
data: nodes,
|
||||
links: links,
|
||||
categories: [
|
||||
{
|
||||
name: '桥梁节点',
|
||||
itemStyle: {
|
||||
color: '#ff7300'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: '社团节点',
|
||||
itemStyle: {
|
||||
color: '#1890ff'
|
||||
}
|
||||
}
|
||||
],
|
||||
roam: true,
|
||||
label: {
|
||||
show: true,
|
||||
position: 'right',
|
||||
formatter: '{b}'
|
||||
},
|
||||
lineStyle: {
|
||||
color: 'source',
|
||||
curveness: 0.3
|
||||
},
|
||||
force: {
|
||||
repulsion: 1000,
|
||||
edgeLength: 100
|
||||
},
|
||||
animationDurationUpdate: 3500, // 节点移动更平滑
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
chartInstance.setOption(option);
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
setTimeout(() => {
|
||||
initChart();
|
||||
}, 100);
|
||||
|
||||
window.addEventListener('resize', () => {
|
||||
if (chartInstance) {
|
||||
chartInstance.resize();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
if (chartInstance) {
|
||||
chartInstance.dispose();
|
||||
chartInstance = null;
|
||||
}
|
||||
window.removeEventListener('resize', () => {});
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
#bridgeCommunityChart {
|
||||
width: 100%;
|
||||
height: 600px;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
<template>
|
||||
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { useKeyNodeStore2 } from '@/store/keyNodeStore2';
|
||||
import { computed } from 'vue';
|
||||
|
||||
const store = useKeyNodeStore2();
|
||||
// 获取到桥梁节点数据
|
||||
const bridgeNodes = computed(() => store.bridgeNodes);
|
||||
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
||||
</style>
|
||||
|
|
@ -89,6 +89,7 @@ const automaticPlay = () => {
|
|||
}, 1500);
|
||||
};
|
||||
|
||||
// 调用 store.initializeTimePoints() 初始化时间点
|
||||
onMounted(() => {
|
||||
store.initializeTimePoints();
|
||||
automaticPlay();
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user