This commit is contained in:
qumeng039@126.com 2025-07-24 17:02:34 +08:00
commit 906d69402a
5 changed files with 250 additions and 53 deletions

View File

@ -27,6 +27,7 @@ export const useKeyNodeStore2 = defineStore('keyNode2', () => {
const allLeaderData = ref([ const allLeaderData = ref([
{ {
id: "President Biden Archived", id: "President Biden Archived",
nodeId: "1349149096909668363",
postId: 1, postId: 1,
name: "President Biden Archived", name: "President Biden Archived",
chineseName: null, chineseName: null,
@ -39,6 +40,7 @@ export const useKeyNodeStore2 = defineStore('keyNode2', () => {
}, },
{ {
id: "Joe Truzman", id: "Joe Truzman",
nodeId: "3006348240",
postId: 2, postId: 2,
name: "Joe Truzman", name: "Joe Truzman",
chineseName: null, chineseName: null,
@ -51,6 +53,7 @@ export const useKeyNodeStore2 = defineStore('keyNode2', () => {
}, },
{ {
id: "OSINTdefender", id: "OSINTdefender",
nodeId: "1457867047334031360",
postId: 3, postId: 3,
name: "OSINTdefender", name: "OSINTdefender",
chineseName: null, chineseName: null,
@ -63,6 +66,7 @@ export const useKeyNodeStore2 = defineStore('keyNode2', () => {
}, },
{ {
id: "Paul Golding", id: "Paul Golding",
nodeId: "455264233",
postId: 4, postId: 4,
name: "Paul Golding", name: "Paul Golding",
chineseName: null, chineseName: null,
@ -75,6 +79,7 @@ export const useKeyNodeStore2 = defineStore('keyNode2', () => {
}, },
{ {
id: "Rep. Matt Gaetz", id: "Rep. Matt Gaetz",
nodeId: "818948638890217473",
postId: 5, postId: 5,
name: "Rep. Matt Gaetz", name: "Rep. Matt Gaetz",
chineseName: null, chineseName: null,
@ -87,6 +92,7 @@ export const useKeyNodeStore2 = defineStore('keyNode2', () => {
}, },
{ {
id: "Israel Defense Forces", id: "Israel Defense Forces",
nodeId: "18576537",
postId: 6, postId: 6,
name: "Israel Defense Forces", name: "Israel Defense Forces",
chineseName: null, chineseName: null,
@ -99,6 +105,7 @@ export const useKeyNodeStore2 = defineStore('keyNode2', () => {
}, },
{ {
id: "Andy Ngo", id: "Andy Ngo",
nodeId: "2835451658",
postId: 7, postId: 7,
name: "Andy Ngo", name: "Andy Ngo",
chineseName: null, chineseName: null,
@ -111,6 +118,7 @@ export const useKeyNodeStore2 = defineStore('keyNode2', () => {
}, },
{ {
id: "Secretary Antony Blinken", id: "Secretary Antony Blinken",
nodeId: "1350150750966603777",
postId: 8, postId: 8,
name: "Secretary Antony Blinken", name: "Secretary Antony Blinken",
chineseName: null, chineseName: null,
@ -123,6 +131,8 @@ export const useKeyNodeStore2 = defineStore('keyNode2', () => {
}, },
{ {
id: "Emmanuel Macron", id: "Emmanuel Macron",
nodeId: "1976143068",
postId: 9,
name: "Emmanuel Macron", name: "Emmanuel Macron",
chineseName: null, chineseName: null,
followers: "1018.6万", followers: "1018.6万",
@ -134,6 +144,8 @@ export const useKeyNodeStore2 = defineStore('keyNode2', () => {
}, },
{ {
id: "Jackson Hinkle 🇺🇸", id: "Jackson Hinkle 🇺🇸",
nodeId: "1151913018936053760",
postId: 10,
name: "Jackson Hinkle 🇺🇸", name: "Jackson Hinkle 🇺🇸",
chineseName: null, chineseName: null,
followers: "304.9万", followers: "304.9万",

View File

@ -51,7 +51,7 @@
:allLeaderData="store.allLeaderData" :allLeaderData="store.allLeaderData"
@handle:openDialog="handleGraphNodeClick" @handle:openDialog="handleGraphNodeClick"
/> />
<BridgeCommunityGraph v-if="true" /> <BridgeCommunityGraph v-if="true" :timestamp="store.activeTimePoint" ref="bridgeGraphRef"/>
</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>
@ -102,6 +102,8 @@ import BridgeCommunityGraph from './graph/bridgeCommunityGraph.vue';
const store = useKeyNodeStore2(); const store = useKeyNodeStore2();
const leaderGraphRef = ref(null); const leaderGraphRef = ref(null);
// BridgeCommunityGraph
const bridgeGraphRef = ref(null);
const handleGraphNodeClick = (leaderData) => { const handleGraphNodeClick = (leaderData) => {
store.openLeaderDetail(leaderData); store.openLeaderDetail(leaderData);
@ -111,6 +113,10 @@ const highlightNode = (leaderId) => {
if (leaderGraphRef.value) { if (leaderGraphRef.value) {
leaderGraphRef.value.highlightNode(leaderId); leaderGraphRef.value.highlightNode(leaderId);
} }
// BridgeCommunityGraphhighlightNode
if (bridgeGraphRef.value) {
bridgeGraphRef.value.highlightNode(leaderId);
}
}; };
// //

View File

@ -56,7 +56,7 @@
</div> </div>
<div class="heat-item"> <div class="heat-item">
<p class="diamond"></p> <p class="diamond"></p>
贴文被转总数:   1329 贴文被转总数: {{ store.activeLeader.leaderOriginInfo.transmit }}
</div> </div>
<div class="heat-item"> <div class="heat-item">
<p class="diamond"></p> <p class="diamond"></p>

View File

@ -3,51 +3,91 @@
</template> </template>
<script setup> <script setup>
import { onMounted, onUnmounted } from 'vue'; import { onMounted, onUnmounted, defineProps, watch, ref } from 'vue';
import * as echarts from 'echarts'; import * as echarts from 'echarts';
import bridgeData from '@/assets/json/bridge_neighbors_communities.json'; import bridgeData from '@/assets/json/bridge_neighbors_communities.json';
import bridgeNodeHoverBgImg from '@/assets/images/bridge_node_hoverbgimg.png' import bridgeNodeHoverBgImg from '@/assets/images/bridge_node_hoverbgimg.png'
import { useKeyNodeStore2 } from '@/store/keyNodeStore2'; // store import { useKeyNodeStore2 } from '@/store/keyNodeStore2'; // store
import { cropToCircleAsync } from "@/utils/transform"; //
const props = defineProps({
timestamp: {
type: Number,
default: 1
}
})
let chartInstance = null; let chartInstance = null;
// ID
const activeNodeId = ref(null);
//
const nodesData = ref([]);
const linksData = ref([]);
// ECharts // ECharts
const processData = () => { const processData = async() => {
const nodes = []; const nodes = [];
const links = []; const links = [];
const addedCommunities = new Set(); const addedCommunities = new Set();
const keyNodeStore2 = useKeyNodeStore2(); // store // store
const keyNodeStore2 = useKeyNodeStore2();
// base64
const bridgeNodes = keyNodeStore2.bridgeNodes;
// timestamp
const filteredBridgeNodes = bridgeNodes.filter(node => node.postsId <= props.timestamp);
for (const node of filteredBridgeNodes) {
if (node.defImg) {
node.defImg = await cropToCircleAsync(node.defImg);
}
if (node.activeImg) {
node.activeImg = await cropToCircleAsync(node.activeImg);
}
}
// usersNum // usersNum
let minUsersNum = Infinity; let minUsersNum = Infinity;
let maxUsersNum = -Infinity; let maxUsersNum = -Infinity;
//
bridgeData.forEach(item => { bridgeData.forEach(item => {
//
const isIncluded = filteredBridgeNodes.some(node => node.Node === item.bridgeId);
if (isIncluded) {
item.bridgeCommunities.forEach(community => { item.bridgeCommunities.forEach(community => {
minUsersNum = Math.min(minUsersNum, community.usersNum); minUsersNum = Math.min(minUsersNum, community.usersNum);
maxUsersNum = Math.max(maxUsersNum, community.usersNum); maxUsersNum = Math.max(maxUsersNum, community.usersNum);
}); });
}
}); });
// //
const minNodeSize = 30; const minNodeSize = 30;
const maxNodeSize = 45; const maxNodeSize = 50;
//
bridgeData.forEach(item => { bridgeData.forEach(item => {
//
const isIncluded = filteredBridgeNodes.some(node => node.Node === item.bridgeId);
if (!isIncluded) {
return;
}
const bridgeId = item.bridgeId; const bridgeId = item.bridgeId;
// //
const bridgeNodeInfo = keyNodeStore2.bridgeNodes.find(node => node.Node === bridgeId); const bridgeNodeInfo = filteredBridgeNodes.find(node => node.Node === bridgeId);
const bridgeNodeImg = bridgeNodeInfo ? bridgeNodeInfo.defImg : ''; const isActiveNode = activeNodeId.value === bridgeId;
const bridgeNodeImg = bridgeNodeInfo ?
(isActiveNode ? bridgeNodeInfo.activeImg : bridgeNodeInfo.defImg) : '';
// - //
nodes.push({ nodes.push({
id: `bridge_${bridgeId}`, id: `bridge_${bridgeId}`,
// name: ` ${bridgeId.substring(0, 5)}...`,
category: 0, category: 0,
value: 20, // symbol: bridgeNodeImg ? `image://${bridgeNodeImg}` : 'circle',
symbol: bridgeNodeImg ? `image://${bridgeNodeImg}` : 'circle', // 使 symbolSize: 100,
symbolSize: 40 // originalId: bridgeId // ID
}); });
// //
@ -56,53 +96,98 @@ const processData = () => {
const userNumofCommunity = community.usersNum const userNumofCommunity = community.usersNum
const communityKey = `community_${communityId}_${userNumofCommunity}`; const communityKey = `community_${communityId}_${userNumofCommunity}`;
const usersNum = community.usersNum; const usersNum = community.usersNum;
// usersNum // usersNum
const size = minNodeSize + const size = minNodeSize +
((usersNum - minUsersNum) / (maxUsersNum - minUsersNum)) * ((usersNum - minUsersNum) / (maxUsersNum - minUsersNum)) *
(maxNodeSize - minNodeSize); (maxNodeSize - minNodeSize);
//
if (!addedCommunities.has(communityKey)) { if (!addedCommunities.has(communityKey)) {
nodes.push({ nodes.push({
id: communityKey, id: communityKey,
// name: ` ${communityId} (${usersNum})`,
category: 1, category: 1,
value: size, // usersNum
symbolSize: size symbolSize: size
}); });
addedCommunities.add(communityKey); addedCommunities.add(communityKey);
} }
//
links.push({ links.push({
source: `bridge_${bridgeId}`, source: `bridge_${bridgeId}`,
target: communityKey, target: communityKey,
value: 1 value: 1
}); });
});
});
return { })
nodes,
links })
};
const result = { nodes, links };
nodesData.value = nodes;
linksData.value = links;
return result;
}; };
// //
const initChart = () => { const initChart = async() => {
if (chartInstance) { if (chartInstance) {
chartInstance.dispose(); chartInstance.dispose();
} }
const chartDom = document.getElementById('bridgeCommunityChart'); const chartDom = document.getElementById('bridgeCommunityChart');
if (!chartDom) return; if (!chartDom) return;
chartInstance = echarts.init(chartDom); chartInstance = echarts.init(chartDom);
const { nodes, links } = processData(); //
chartInstance.on('mouseover', function(params) {
if (params.data && params.data.category === 0) {
//
const nodeId = params.data.originalId;
//
activeNodeId.value = nodeId;
updateNodeImage(nodeId, true);
}
});
//
chartInstance.on('mouseout', function(params) {
if (params.data && params.data.category === 0) {
//
const nodeId = params.data.originalId;
if (activeNodeId.value === nodeId) {
activeNodeId.value = null;
updateNodeImage(nodeId, false);
}
}
});
//
chartInstance.on('click', function(params) {
if (params.data && params.data.category === 0) {
//
const nodeId = params.data.originalId;
// store
const keyNodeStore2 = useKeyNodeStore2();
// leader
const leader = keyNodeStore2.allLeaderData.find(l => l.nodeId === nodeId);
if (leader) {
// leader
keyNodeStore2.openLeaderDetail({
id: leader.id
});
}
}
});
const { nodes, links } = await processData();
const categories = [ const categories = [
{name: "桥梁节点", category: 0}, {name: "桥梁节点", category: 0},
{name: "社团节点", category: 1} {name: "普通社团", category: 1}
] ]
const option = { const option = {
@ -119,10 +204,13 @@ const initChart = () => {
fontSize: 14, fontSize: 14,
}, },
formatter: function(params) { formatter: function(params) {
//
if (params.dataType === 'edge') {
return '';
}
// //
if(params.data.category === 1) { if(params.data.category === 1) {
// //
// const usersNum = params.data.name.match(/\((\d+)\)/)?.[1] || 0;
const parts = params.data.id.split('_'); const parts = params.data.id.split('_');
const extractedUserNum = parseInt(parts[parts.length - 1], 10); const extractedUserNum = parseInt(parts[parts.length - 1], 10);
return `<div return `<div
@ -144,8 +232,9 @@ const initChart = () => {
</div> </div>
</div>`; </div>`;
}else { }else {
// : bridgeCommunities //
const bridgeItem = bridgeData.find(item => item.bridgeId === params.data.id.replace('bridge_', '')); // bridgeCommunities
const bridgeItem = bridgeData.find(item => item.bridgeId === (params.data.id || '').replace('bridge_', ''));
const communityCount = bridgeItem ? bridgeItem.bridgeCommunities.length : 0; const communityCount = bridgeItem ? bridgeItem.bridgeCommunities.length : 0;
return `<div return `<div
style = " style = "
@ -200,8 +289,18 @@ const initChart = () => {
series: [ series: [
{ {
type: 'graph', type: 'graph',
zoom: 0.8, zoom: 0.1,
layout: 'force', layout: 'force',
force: {
//
repulsion: 50000,
// 线
edgeLength: [800,6000],
//
gravity: 0.8,
//
friction: 0.2,
},
animation: false, animation: false,
draggable: true, draggable: true,
data: nodes, data: nodes,
@ -212,17 +311,18 @@ const initChart = () => {
itemStyle: { itemStyle: {
// //
color: '#ff7300', color: '#ff7300',
} },
}, },
{ {
name: '社团节点', name: '普通社团',
itemStyle: { itemStyle: {
// //
color: new echarts.graphic.RadialGradient(0.97, 0.38, 0.8, [ color: new echarts.graphic.RadialGradient(0.98, 0.38, 0.9, [
{ offset: 0, color: "#49c3ed" }, // { offset: 1, color: "#1a3860" }, //
{ offset: 0.5, color: "#5fa3e0" }, // { offset: 0.5, color: "#38546b" }, //
{ offset: 1, color: "#7286d4" } // { offset: 0, color: "#5fb3b3" } //
]), ]),
opacity: 0.8,
// //
borderColor: '#2AB9FE', borderColor: '#2AB9FE',
borderWidth: 1, borderWidth: 1,
@ -230,19 +330,28 @@ const initChart = () => {
borderImageSource: 'linear-gradient(90deg, #2AB9FE 12.25%, #52FFF3 100.6%)', borderImageSource: 'linear-gradient(90deg, #2AB9FE 12.25%, #52FFF3 100.6%)',
borderImageSlice: 1, borderImageSlice: 1,
}, },
symbolSize: 18
} }
], ],
roam: true, roam: true,
lineStyle: { lineStyle: {
color: 'source', // color: '#50AAD6',
curveness: 0.3 color: new echarts.graphic.LinearGradient(1, 0, 0, 0, [
}, { offset: 0, color: '#233144' },
force: { { offset: 0.9, color: '#0578b0' }
repulsion: 1000, ]),
edgeLength: 100 curveness: 0,
with: 2,
type: 'solid',
opacity: 0.5,
}, },
animationDurationUpdate: 3500, // animationDurationUpdate: 3500, //
animationEasingUpdate: 'quinticInOut',
emphasis: { //
focus: 'adjacency', //
lineStyle: { // 线
width: 10 // 线(10)
}
},
} }
] ]
}; };
@ -250,9 +359,68 @@ const initChart = () => {
chartInstance.setOption(option); chartInstance.setOption(option);
}; };
//
const updateNodeImage = (bridgeId, isActive) => {
//
const keyNodeStore2 = useKeyNodeStore2();
const filteredBridgeNodes = keyNodeStore2.bridgeNodes.filter(node => node.postsId <= props.timestamp);
//
const newNodes = [...nodesData.value];
//
if (isActive) {
newNodes.forEach((node, index) => {
if (node.category === 0) { //
const nodeInfo = filteredBridgeNodes.find(n => n.Node === node.originalId);
if (nodeInfo && node.originalId !== bridgeId) {
newNodes[index].symbol = nodeInfo.defImg ? `image://${nodeInfo.defImg}` : 'circle';
}
}
});
}
//
const bridgeNodeInfo = filteredBridgeNodes.find(node => node.Node === bridgeId);
if (bridgeNodeInfo) {
//
const nodeIndex = nodesData.value.findIndex(node => node.originalId === bridgeId);
if (nodeIndex !== -1) {
//
const newNodes = [...nodesData.value];
//
const imgUrl = isActive ? bridgeNodeInfo.activeImg : bridgeNodeInfo.defImg;
newNodes[nodeIndex].symbol = imgUrl ? `image://${imgUrl}` : 'circle';
// 使setOption
chartInstance.setOption({
series: [{
data: newNodes
}]
});
// nodesData
nodesData.value = newNodes;
}
}
};
// highlightNode
const highlightNode = (leaderId) => {
// ID
const keyNodeStore2 = useKeyNodeStore2();
const leader = keyNodeStore2.allLeaderData.find(l => l.id === leaderId);
if (leader) {
//
if (activeNodeId.value) {
updateNodeImage(activeNodeId.value, false);
}
//
activeNodeId.value = leader.nodeId;
updateNodeImage(leader.nodeId, true);
}
};
onMounted(() => { onMounted(() => {
setTimeout(() => { setTimeout(async () => {
initChart(); await initChart();
}, 100); }, 100);
window.addEventListener('resize', () => { window.addEventListener('resize', () => {
@ -262,6 +430,13 @@ onMounted(() => {
}); });
}); });
watch(
() => props.timestamp,
async (newValue) => {
await initChart()
}
)
onUnmounted(() => { onUnmounted(() => {
if (chartInstance) { if (chartInstance) {
chartInstance.dispose(); chartInstance.dispose();
@ -269,6 +444,9 @@ onUnmounted(() => {
} }
window.removeEventListener('resize', () => {}); window.removeEventListener('resize', () => {});
}); });
// highlightNode
defineExpose({ highlightNode });
</script> </script>
<style scoped> <style scoped>

View File

@ -58,6 +58,7 @@ const graphPanelRef = ref(null);
// activeChartTab --- // activeChartTab ---
const activeChartTab = ref('trend'); // 'trend' const activeChartTab = ref('trend'); // 'trend'
const handleLeaderSelect = (leader) => { const handleLeaderSelect = (leader) => {
console.log("选中的领袖ID:", leader);
if (graphPanelRef.value && leader.id) { if (graphPanelRef.value && leader.id) {
graphPanelRef.value.highlightNode(leader.id); graphPanelRef.value.highlightNode(leader.id);
} }