This commit is contained in:
duanhao 2025-08-07 14:58:50 +08:00
commit f24cce9202
3 changed files with 146 additions and 67 deletions

View File

@ -0,0 +1,116 @@
//布局算法的参数定义
export const layoutParamsDict ={
'hierarchical':[ //层级布局
{'name':'layerSpace','value': 150,'type':'number','label':'层间距'},
{'name':'nodeSpace','value': 80,'type':'number','label':'点间距'},
{'name':'sortType','value': 'selected','type':'array','label':'排序类型',
'types':[
{'label':'指定点','value':'selected'},
{'label':'度大小','value':'hubsize'},
{'label':'连线方向','value':'directed'}
]
},
{'name':'direction','value': 'UD','type':'array','label':'方向',
'types':[
{'label':'上下','value':'UD'},
{'label':'下上','value':'DU'},
{'label':'左右','value':'LR'},
{'label':'右左','value':'RL'}
]
}
],
'grid':[ //网格布局
{'name':'layerSpace','value': 120,'type':'number','label':'层间距'},
{'name':'nodeSpace','value': 140,'type':'number','label':'点间距'}
],
'concentric':[ //同心圆布局
{'name':'maxNodeSize','value': 70,'type':'number','label':'节点大小'}
],
'fastForce':[ //网络布局
{'name':'linkDistance','value': 150,'type':'number','label':'连线长度'},
{'name':'charge','value': -450,'type':'number','label':'吸引力'},
{'name':'friction','value': 0.85,'type':'decimal','label':'排斥力'},
{'name':'linkStrength','value': 0.1,'type':'decimal','label':'连线强度'},
{'name':'gravity','value': 0.05,'type':'decimal','label':'中心引力'}
],
'simulation':[ //力导向布局
{'name':'strength','value': -800,'type':'number','label':'吸引力'},
{'name':'linkDistance','value': 150,'type':'number','label':'连线长度'},
{'name':'ajustCluster','value': false,'type':'array','label':'类型分组',
'types':[
{'label':'是','value':true},
{'label':'否','value':false}
]
}
],
'kawakai':[ //最优路径布局
{'name':'sizeScale','value': 1.5,'type':'number','label':'缩放比例'}
],
'circle':[ //环形布局
{'name':'scale','value': 1.2,'type':'number','label':'缩放比例'},
{'name':'ordering','value': 'degree','type':'array','label':'排序方式',
'types':[
{'label':'度大小排序','value':'degree'},
{'label':'拓扑排序','value':'topology'}
]
}
],
'arf':[
{'name':'neighberForce','value': 5.0,'type':'number','label':'邻边引力'},
{'name':'attraction','value': 0.05,'type':'number','label':'向心力'},
{'name':'forceScale','value': 5.0,'type':'number','label':'缩放比例'}
],
'tree':[ //树形布局
{'name':'layerSpace','value': 150,'type':'number','label':'层间距'},
{'name':'nodeSpace','value': 80,'type':'number','label':'点间距'},
{'name':'direction','value': 'UD','type':'array','label':'方向',
'types':[
{'label':'上下','value':'UD'},
{'label':'下上','value':'DU'},
{'label':'左右','value':'LR'},
{'label':'右左','value':'RL'}
]
}
],
'avoidlap':[ //避免重叠
{'name':'maxPadding','value': 5,'type':'number','label':'节点间距'}
],
'dagre':[ //流程图布局
{'name':'nodesep','value': 10,'type':'number','label':'点间距'},
{'name':'ranksep','value': 100,'type':'number','label':'层间距'},
{'name':'nodeSize','value': 50,'type':'number','label':'节点大小'},
{'name':'rankdir','value': 'TB','type':'array','label':'排列方向',
'types':[
{'label':'上下','value':'TB'},
{'label':'下上','value':'BT'},
{'label':'左右','value':'LR'},
{'label':'右左','value':'RL'}
]
}
]
};
//获取默认的布局参数
export const getLayoutDefaultConfig = function(layoutType){
let layoutConfig = {};
layoutParamsDict[layoutType].forEach(param=>{
layoutConfig[param.name] = param.value;
});
return layoutConfig;
};
//获取布局参数列表
export const getLayoutParams = function(layoutType){
return layoutParamsDict[layoutType];
};
//布局算法列表
export const layoutTypeList = [
{label:'快速弹性布局',value:'fastForce',icon:'el-icon-orange'},
{label:'力导向布局',value:'simulation',icon:'el-icon-orange'},
{label:'最优路径布局',value:'kawakai',icon:'el-icon-cpu'},
//{label:'球面引力布局',value:'arf',icon:'el-icon-orange'},
{label:'层级布局',value:'hierarchical',icon:'el-icon-share'},
{label:'中心圆布局',value:'concentric',icon:'el-icon-bangzhu'},
{label:'去除重叠',value:'avoidlap',icon:'el-icon-copy-document'}
];

View File

@ -211,7 +211,6 @@ export const useGroupDiscoveryStore = defineStore("groupDiscovery", {
this.timeList = res.data this.timeList = res.data
}, },
//根据时间参数获取节点数据
async initialGraphByUtcTime(utcTime = "") { async initialGraphByUtcTime(utcTime = "") {
const setColor = (groupId) => { const setColor = (groupId) => {
const colorMap = { const colorMap = {
@ -237,16 +236,23 @@ export const useGroupDiscoveryStore = defineStore("groupDiscovery", {
} }
return false return false
}) })
.map((node) => ({ .map((node) => {
id: node.name, return {
label: node.name, id: node.name,
color: setColor(node.groupId) label: node.name,
})) color: setColor(node.groupId),
cluster: parseInt(node.groupId)
}
})
}, },
// 通过时间来获取帖文列表 // 通过时间来获取帖文列表
//根据时间参数获取贴文数据 //根据时间参数获取贴文数据
async initialPostByUtcTime(utcTime) {} async initialPostByUtcTime(utcTime) {
const res = await getPostByUtcTime(utcTime)
if (res.code != 200) return
this.posts = res.data
}
}, },
persist: true // 开启持久化 persist: true // 开启持久化
}) })

View File

@ -17,36 +17,23 @@
</template> </template>
<script setup> <script setup>
import { import { defineProps, defineEmits, onUnmounted, ref, toRaw, watch } from "vue"
defineProps,
defineEmits,
onMounted,
onUnmounted,
nextTick,
ref,
toRaw,
watchEffect,
watch
} from "vue"
import { storeToRefs } from "pinia" import { storeToRefs } from "pinia"
import { convertToUtcIsoString } from "@/utils/transform" import { convertToUtcIsoString } from "@/utils/transform"
import { paintNodeFunction, paintLineFunction } from "@/utils/customePaint" import { paintNodeFunction, paintLineFunction } from "@/utils/customePaint"
import { demoData } from "@/assets/customeGraph/data" import { demoData } from "@/assets/customeGraph/data"
import TimeAxis from "@/components/timeAxis.vue" import TimeAxis from "@/components/timeAxis.vue"
import GraphVis from "@/assets/package/graphvis.esm.min.js" import GraphVis from "@/assets/package/graphvis.esm.min.js"
const props = defineProps({ const props = defineProps({
store: { store: {
required: true required: true
} }
}) })
const emit = defineEmits(["click:pointerDownAndSlide"]) const emit = defineEmits(["click:pointerDownAndSlide"])
// store state
const { timeList, graph } = storeToRefs(props.store) const { timeList, graph } = storeToRefs(props.store)
let graphVis = null let graphVis = null
let forceSimulator = null let forceSimulator = null
let currentSelectNode = ref(null) let currentSelectNode = ref(null)
let isGraphVisReady = false //
const defaultConfig = { const defaultConfig = {
node: { node: {
label: { label: {
@ -65,7 +52,7 @@ const defaultConfig = {
borderWidth: 2, borderWidth: 2,
borderColor: "100,250,100", borderColor: "100,250,100",
showShadow: true, // showShadow: true, //
shadowBlur: 30, // shadowBlur: 10, //
shadowColor: "50,250,30" // shadowColor: "50,250,30" //
} }
}, },
@ -77,7 +64,7 @@ const defaultConfig = {
font: "normal 11px KaiTi", // font: "normal 11px KaiTi", //
background: "255,255,255" //, background: "255,255,255" //,
}, },
lineType: "curver", //straight lineType: "straight", // curver
showArrow: false, showArrow: false,
lineWidth: 2, lineWidth: 2,
colorType: "both", colorType: "both",
@ -177,46 +164,20 @@ const runForceLayout = () => {
// //
const updateChart = (newGraphData) => { const updateChart = (newGraphData) => {
if (!graphVis) { if (!graphVis) {
initChart(); initChart()
return; return
} }
// //
graphVis.clearAll(); graphVis.clearAll()
// //
graphVis.addGraph({ ...toRaw(newGraphData) }); graphVis.addGraph({ ...toRaw(newGraphData) })
graphVis.autoGroupLayout(toRaw(newGraphData).nodes)
// //
runForceLayout(); runForceLayout()
} }
const initChart = () => { const initChart = () => {
const getGroupCenters = (groupCount, width, height, radius = 200) => {
//
const angleStep = (2 * Math.PI) / groupCount
const centerX = width / 2
const centerY = height / 2
return Array.from({ length: groupCount }).map((_, i) => ({
x: centerX + Math.cos(i * angleStep) * radius,
y: centerY + Math.sin(i * angleStep) * radius
}))
}
const assignNodePositions = (nodes, groupCenters) => {
const groupMap = {}
nodes.forEach((node) => {
const groupIdx = node.groupId || 0
if (!groupMap[groupIdx]) groupMap[groupIdx] = []
groupMap[groupIdx].push(node)
})
Object.entries(groupMap).forEach(([groupIdx, groupNodes]) => {
const center = groupCenters[groupIdx]
groupNodes.forEach((node, i) => {
//
node.x = center.x + Math.random() * 80 - 40
node.y = center.y + Math.random() * 80 - 40
})
})
}
const createGraph = () => { const createGraph = () => {
if (!graphVis) { if (!graphVis) {
graphVis = new GraphVis({ graphVis = new GraphVis({
@ -227,31 +188,27 @@ const initChart = () => {
} }
graphVis.setDragHideLine(false) //线 graphVis.setDragHideLine(false) //线
graphVis.setShowDetailScale(0.1) // graphVis.setShowDetailScale(0.1) //
graphVis.setZoomRange(0.1, 5) // graphVis.setZoomRange(0.05, 5) //
registCustomePaintFunc() // registCustomePaintFunc() //
registEvents() registEvents()
} }
// const groupCount = 3 //
// const width = 900,
// height = 500 //
// const groupCenters = getGroupCenters(groupCount, width, height)
// assignNodePositions(graph.value.nodes, groupCenters)
createGraph() createGraph()
console.log(graph.value)
graphVis.addGraph({ ...toRaw(graph.value) }) graphVis.addGraph({ ...toRaw(graph.value) })
runForceLayout() runForceLayout()
} }
let lastLength = 0 //
watch( watch(
graph, graph,
(newValue) => { (newValue) => {
if (newValue && Object.keys(newValue).length > 0) { if (newValue && newValue.nodes.length > lastLength) {
updateChart(newValue); updateChart(newValue)
} }
lastLength = newValue.nodes.length
}, },
{ deep: true, immediate: true } { deep: true }
); )
onUnmounted(() => { onUnmounted(() => {
if (graphVis) { if (graphVis) {