This commit is contained in:
qumeng039@126.com 2025-07-23 15:51:18 +08:00
commit 4bf765aa75
14 changed files with 1522 additions and 26 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

File diff suppressed because it is too large Load Diff

View 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 }
]

View File

@ -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;
}

View File

@ -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;

View File

@ -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;

View File

@ -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;
}

View File

@ -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);

View File

@ -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;

View File

@ -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>

View File

@ -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>

View File

@ -89,6 +89,7 @@ const automaticPlay = () => {
}, 1500);
};
// store.initializeTimePoints()
onMounted(() => {
store.initializeTimePoints();
automaticPlay();