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 {
|
.panel-container {
|
||||||
width: 350px;
|
width: 350px;
|
||||||
height: 276px;
|
height: 257px;
|
||||||
background: rgba(4, 20, 33, 0.4);
|
background: rgba(4, 20, 33, 0.4);
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
box-shadow: 0 0 18px 0 #0a2e55 inset;
|
box-shadow: 0 0 18px 0 #0a2e55 inset;
|
||||||
|
|
@ -245,6 +245,7 @@ onUnmounted(() => {
|
||||||
.chart-container {
|
.chart-container {
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
height: 180px;
|
||||||
margin-top: -20px;
|
margin-top: -20px;
|
||||||
margin-left: -10px;
|
margin-left: -10px;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -117,7 +117,7 @@ onBeforeUnmount(() => {
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.post-dynamics-container {
|
.post-dynamics-container {
|
||||||
width: 800px;
|
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;
|
padding: 16px;
|
||||||
background-color: #03102a; /* A dark blue background to mimic the screenshot context */
|
background-color: #03102a; /* A dark blue background to mimic the screenshot context */
|
||||||
font-family: "PingFang SC", sans-serif;
|
font-family: "PingFang SC", sans-serif;
|
||||||
|
|
|
||||||
|
|
@ -43,7 +43,7 @@ const props = defineProps({
|
||||||
const scanAngle = ref(0);
|
const scanAngle = ref(0);
|
||||||
const scanTimer = ref(null);
|
const scanTimer = ref(null);
|
||||||
const containerWidth = 370;
|
const containerWidth = 370;
|
||||||
const containerHeight = 276;
|
const containerHeight = 257;
|
||||||
|
|
||||||
const words = ref(props.wordsCloudList);
|
const words = ref(props.wordsCloudList);
|
||||||
|
|
||||||
|
|
@ -124,7 +124,7 @@ onBeforeUnmount(() => {
|
||||||
.word-cloud-container {
|
.word-cloud-container {
|
||||||
position: relative;
|
position: relative;
|
||||||
width: 370px;
|
width: 370px;
|
||||||
height: 276px;
|
height: 257px;
|
||||||
background-color: rgba(4, 20, 33, 0.4);
|
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));
|
background-image: linear-gradient(to right, rgba(6, 61, 113, 0.2), rgba(8, 30, 56, 0.8));
|
||||||
border: none;
|
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 },
|
{postsId: 10, postsName: "Jackson Hinkle 🇺🇸", 'Node': '1151913018936053760','Number_of_across_communities': 98,'Community_of_affiliation': 19, 'Community_size': 784 },
|
||||||
])
|
])
|
||||||
|
|
||||||
|
// 当前激活的时间点
|
||||||
const activeTimePoint = ref(1);
|
const activeTimePoint = ref(1);
|
||||||
|
// 所有时间点数据
|
||||||
const timePoints = ref([]);
|
const timePoints = ref([]);
|
||||||
const activeLeader = ref(null);
|
const activeLeader = ref(null);
|
||||||
const currentPost = ref(null);
|
const currentPost = ref(null);
|
||||||
|
|
@ -287,6 +289,7 @@ export const useKeyNodeStore2 = defineStore('keyNode2', () => {
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 初始化时间点数据
|
||||||
function initializeTimePoints() {
|
function initializeTimePoints() {
|
||||||
if (timePoints.value.length > 0) return;
|
if (timePoints.value.length > 0) return;
|
||||||
timePoints.value = allLeaderData.value.map((leader, index) => ({
|
timePoints.value = allLeaderData.value.map((leader, index) => ({
|
||||||
|
|
@ -296,6 +299,7 @@ export const useKeyNodeStore2 = defineStore('keyNode2', () => {
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 设置激活的时间点
|
||||||
function setActiveTimePoint(id) {
|
function setActiveTimePoint(id) {
|
||||||
activeTimePoint.value = id;
|
activeTimePoint.value = id;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -46,11 +46,12 @@
|
||||||
<div class="chart-container">
|
<div class="chart-container">
|
||||||
<DynamicGraph
|
<DynamicGraph
|
||||||
ref="leaderGraphRef"
|
ref="leaderGraphRef"
|
||||||
|
v-if="false"
|
||||||
:timestamp="store.activeTimePoint"
|
:timestamp="store.activeTimePoint"
|
||||||
:allLeaderData="store.allLeaderData"
|
:allLeaderData="store.allLeaderData"
|
||||||
@handle:openDialog="handleGraphNodeClick"
|
@handle:openDialog="handleGraphNodeClick"
|
||||||
/>
|
/>
|
||||||
<bridgeGroup v-if="false"></bridgeGroup>
|
<BridgeCommunityGraph v-if="true" />
|
||||||
</div>
|
</div>
|
||||||
<div class="timeline-container">
|
<div class="timeline-container">
|
||||||
<span class="time-label">2023.10.07 00:00:00</span>
|
<span class="time-label">2023.10.07 00:00:00</span>
|
||||||
|
|
@ -97,7 +98,7 @@
|
||||||
import { ref, defineExpose, watch, computed } from 'vue';
|
import { ref, defineExpose, watch, computed } from 'vue';
|
||||||
import { useKeyNodeStore2 } from '@/store/keyNodeStore2';
|
import { useKeyNodeStore2 } from '@/store/keyNodeStore2';
|
||||||
import DynamicGraph from "./graph/dynamicGraph.vue";
|
import DynamicGraph from "./graph/dynamicGraph.vue";
|
||||||
import bridgeGroup from './graph/bridgeGroup.vue';
|
import BridgeCommunityGraph from './graph/bridgeCommunityGraph.vue';
|
||||||
|
|
||||||
const store = useKeyNodeStore2();
|
const store = useKeyNodeStore2();
|
||||||
const leaderGraphRef = ref(null);
|
const leaderGraphRef = ref(null);
|
||||||
|
|
|
||||||
|
|
@ -130,9 +130,7 @@ const isVisible = computed({
|
||||||
}
|
}
|
||||||
.leader-post-detail-content {
|
.leader-post-detail-content {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
background:
|
background: linear-gradient(90deg, #3e607c71 0%, #2f455f62 100%);
|
||||||
linear-gradient(0deg, #0d2743, #0d2743),
|
|
||||||
linear-gradient(270deg, rgba(147, 210, 255, 0.06) 0%, rgba(147, 210, 255, 0.16) 100%);
|
|
||||||
margin-top: 30px;
|
margin-top: 30px;
|
||||||
height: 262px;
|
height: 262px;
|
||||||
overflow: auto;
|
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);
|
}, 1500);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 调用 store.initializeTimePoints() 初始化时间点
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
store.initializeTimePoints();
|
store.initializeTimePoints();
|
||||||
automaticPlay();
|
automaticPlay();
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user