Skip to content

Commit a3dc7b9

Browse files
committed
feat: add immer for immutable state management and enhance geometry handling
- Added immer as a dependency for easier immutable state updates. - Enhanced Polygon class to automatically close polygons by adding the first point to the end if they are not the same. - Introduced a new method in MultiPolygon class to convert geometries to GeoJSON FeatureCollection. - Created new Vue components: AppDrawer, AppSidebar, DataRelation, Dragger, EntityList, SidebarToggleButton for improved UI/UX. - Implemented a data management system using DataEntity and DataGroup classes to handle entities and their relationships. - Added tooltip configuration for displaying properties of GeoJSON objects. - Refactored layout files to integrate new data management and visualization features.
1 parent 660ffe3 commit a3dc7b9

File tree

18 files changed

+1496
-281
lines changed

18 files changed

+1496
-281
lines changed

docs/components.d.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,17 +8,33 @@ export {}
88
/* prettier-ignore */
99
declare module 'vue' {
1010
export interface GlobalComponents {
11+
AppDrawer: typeof import('./src/components/AppDrawer.vue')['default']
12+
AppFooter: typeof import('./src/components/AppFooter.vue')['default']
13+
AppSidebar: typeof import('./src/components/AppSidebar.vue')['default']
14+
ContextMenu: typeof import('./src/components/ContextMenu.vue')['default']
15+
copy: typeof import('./src/components/DataRelation copy.vue')['default']
16+
DataEntityList: typeof import('./src/components/DataEntityList.vue')['default']
17+
DataRelation: typeof import('./src/components/DataRelation.vue')['default']
18+
DataTree: typeof import('./src/components/dataTree/DataTree.vue')['default']
19+
Dragger: typeof import('./src/components/Dragger.vue')['default']
1120
ElAside: typeof import('element-plus/es')['ElAside']
21+
ElButton: typeof import('element-plus/es')['ElButton']
1222
ElCheckbox: typeof import('element-plus/es')['ElCheckbox']
1323
ElContainer: typeof import('element-plus/es')['ElContainer']
1424
ElDrawer: typeof import('element-plus/es')['ElDrawer']
25+
ElFooter: typeof import('element-plus/es')['ElFooter']
1526
ElIcon: typeof import('element-plus/es')['ElIcon']
1627
ElMain: typeof import('element-plus/es')['ElMain']
28+
ElProgress: typeof import('element-plus/es')['ElProgress']
1729
ElScrollbar: typeof import('element-plus/es')['ElScrollbar']
1830
ElSlider: typeof import('element-plus/es')['ElSlider']
31+
ElTag: typeof import('element-plus/es')['ElTag']
1932
ElTooltip: typeof import('element-plus/es')['ElTooltip']
33+
EntityList: typeof import('./src/components/EntityList.vue')['default']
2034
Layers: typeof import('./src/components/layers.vue')['default']
2135
Map: typeof import('./src/components/map.vue')['default']
2236
SidebarControls: typeof import('./src/components/SidebarControls.vue')['default']
37+
SidebarToggleButton: typeof import('./src/components/SidebarToggleButton.vue')['default']
38+
TreeNode: typeof import('./src/components/dataTree/TreeNode.vue')['default']
2339
}
2440
}

docs/src/components/AppDrawer.vue

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<template>
2+
<el-drawer
3+
:model-value="modelValue"
4+
@update:model-value="$emit('update:modelValue', $event)"
5+
:show-close="true"
6+
:with-header="false"
7+
direction="btt"
8+
:class="'myDrawer'"
9+
:size="'50%'"
10+
>
11+
<slot></slot>
12+
</el-drawer>
13+
</template>
14+
15+
<script setup>
16+
defineProps({
17+
modelValue: {
18+
type: Boolean,
19+
required: true
20+
}
21+
})
22+
23+
defineEmits(['update:modelValue'])
24+
</script>
25+
26+
<style scoped>
27+
.myDrawer {
28+
background-color: var(--vp-c-bg);
29+
border: 1px solid var(--vp-c-border);
30+
}
31+
</style>

docs/src/components/AppSidebar.vue

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
<template>
2+
<el-aside :width="isCollapsed ? '0px' : '300px'" class="sidebar" :class="{ 'sidebar-collapsed': isCollapsed }">
3+
<el-scrollbar class="sidebar-scrollbar" always>
4+
<div class="sidebar-content">
5+
<slot></slot>
6+
</div>
7+
</el-scrollbar>
8+
</el-aside>
9+
</template>
10+
11+
<script setup>
12+
defineProps({
13+
isCollapsed: {
14+
type: Boolean,
15+
required: true
16+
}
17+
})
18+
</script>
19+
20+
<style scoped>
21+
.sidebar {
22+
transition: width 0.3s ease;
23+
position: relative;
24+
height: 88vh;
25+
display: flex;
26+
border: 1px solid var(--vp-c-border);
27+
}
28+
29+
.sidebar-collapsed {
30+
width: 0 !important;
31+
overflow: hidden;
32+
}
33+
34+
.sidebar-scrollbar {
35+
height: 100%;
36+
width: 100%;
37+
}
38+
39+
.sidebar-content {
40+
width: 100%;
41+
padding: 10px;
42+
box-sizing: border-box;
43+
display: flex;
44+
flex-direction: column;
45+
gap: 10px;
46+
}
47+
</style>
Lines changed: 213 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,213 @@
1+
<template>
2+
<div ref="container" class="relation-graph-container">
3+
<svg ref="svg" :width="computedWidth" :height="computedHeight"></svg>
4+
</div>
5+
</template>
6+
7+
<script setup>
8+
import * as d3 from 'd3'
9+
import { ref, onMounted, watch, toRefs, computed, onBeforeUnmount } from 'vue'
10+
import { useData } from 'vitepress'
11+
12+
const { isDark } = useData()
13+
14+
const props = defineProps({
15+
D3DAGData: {
16+
type: Object,
17+
required: true,
18+
validator: (value) => {
19+
return value.nodes && value.links
20+
}
21+
},
22+
width: {
23+
type: [Number, String],
24+
default: '100%'
25+
},
26+
height: {
27+
type: [Number, String],
28+
default: '100%'
29+
},
30+
nodeRadius: {
31+
type: Number,
32+
default: 10,
33+
validator: (value) => value > 0
34+
},
35+
zoomable: {
36+
type: Boolean,
37+
default: true
38+
}
39+
})
40+
41+
const { D3DAGData, width, height, nodeRadius, zoomable } = toRefs(props)
42+
43+
const container = ref(null)
44+
const svg = ref(null)
45+
const simulation = ref(null)
46+
const resizeObserver = ref(null)
47+
48+
// 计算实际宽度和高度
49+
const computedWidth = computed(() => {
50+
return width.value === '100%' && container.value
51+
? container.value.clientWidth
52+
: parseInt(width.value)
53+
})
54+
55+
const computedHeight = computed(() => {
56+
return height.value === '100%' && container.value
57+
? container.value.clientHeight
58+
: parseInt(height.value)
59+
})
60+
61+
// 颜色比例尺
62+
const colorScale = d3.scaleOrdinal()
63+
.domain(['derived', 'composed', 'referenced'])
64+
.range(['#66c2a5', '#fc8d62', '#8da0cb'])
65+
66+
// 初始化图表
67+
const initChart = () => {
68+
if (!svg.value || !D3DAGData.value) return
69+
70+
// 清除现有模拟和内容
71+
if (simulation.value) {
72+
simulation.value.stop()
73+
simulation.value = null
74+
}
75+
d3.select(svg.value).selectAll('*').remove()
76+
77+
const { nodes, links } = D3DAGData.value
78+
79+
// 设置 SVG 尺寸
80+
svg.value.setAttribute('width', computedWidth.value)
81+
svg.value.setAttribute('height', computedHeight.value)
82+
83+
// 创建主分组
84+
const g = d3.select(svg.value).append('g')
85+
86+
// 添加缩放行为
87+
if (zoomable.value) {
88+
const zoom = d3.zoom()
89+
.scaleExtent([0.1, 8])
90+
.on('zoom', (event) => {
91+
g.attr('transform', event.transform)
92+
})
93+
94+
d3.select(svg.value).call(zoom)
95+
}
96+
97+
// 创建力导向图模拟
98+
simulation.value = d3.forceSimulation(nodes)
99+
.force('link', d3.forceLink(links).id(d => d.id).distance(100))
100+
.force('charge', d3.forceManyBody().strength(-300))
101+
.force('center', d3.forceCenter(computedWidth.value / 2, computedHeight.value / 2))
102+
.force('collision', d3.forceCollide().radius(nodeRadius.value * 1.5))
103+
104+
// 创建箭头标记
105+
g.append('defs').selectAll('marker')
106+
.data(links)
107+
.enter().append('marker')
108+
.attr('id', d => `arrow-${d.source.id}-${d.target.id}`)
109+
.attr('viewBox', '0 -5 10 10')
110+
.attr('refX', nodeRadius.value + 5)
111+
.attr('refY', 0)
112+
.attr('markerWidth', 6)
113+
.attr('markerHeight', 6)
114+
.attr('orient', 'auto')
115+
.append('path')
116+
.attr('d', 'M0,-5L10,0L0,5')
117+
.attr('fill', d => colorScale(d.type))
118+
119+
// 创建连线
120+
const link = g.append('g')
121+
.selectAll('line')
122+
.data(links)
123+
.enter().append('line')
124+
.attr('stroke', d => colorScale(d.type))
125+
.attr('stroke-width', 2)
126+
.attr('marker-end', d => `url(#arrow-${d.source.id}-${d.target.id})`)
127+
128+
// 创建节点组
129+
const node = g.append('g')
130+
.selectAll('g')
131+
.data(nodes)
132+
.enter().append('g')
133+
.call(d3.drag()
134+
.on('start', dragstarted)
135+
.on('drag', dragged)
136+
.on('end', dragended))
137+
138+
// 添加节点圆形
139+
node.append('circle')
140+
.attr('r', nodeRadius.value)
141+
.attr('fill', d => d3.schemeCategory10[d.type ? d.type.charCodeAt(0) % 10 : 0])
142+
.attr('stroke', '#fff')
143+
.attr('stroke-width', 2)
144+
145+
// 添加节点文本 - 根据暗夜模式调整颜色
146+
node.append('text')
147+
.attr('dy', nodeRadius.value + 15)
148+
.attr('text-anchor', 'middle')
149+
.text(d => d.id)
150+
.attr('fill', isDark.value ? '#fff' : '#333')
151+
.attr('font-size', '12px')
152+
153+
// 添加模拟更新
154+
simulation.value.on('tick', () => {
155+
link
156+
.attr('x1', d => d.source.x)
157+
.attr('y1', d => d.source.y)
158+
.attr('x2', d => d.target.x)
159+
.attr('y2', d => d.target.y)
160+
161+
node.attr('transform', d => `translate(${d.x},${d.y})`)
162+
})
163+
164+
// 拖拽函数
165+
function dragstarted(event, d) {
166+
if (!event.active) simulation.value.alphaTarget(0.3).restart()
167+
d.fx = d.x
168+
d.fy = d.y
169+
}
170+
171+
function dragged(event, d) {
172+
d.fx = event.x
173+
d.fy = event.y
174+
}
175+
176+
function dragended(event, d) {
177+
if (!event.active) simulation.value.alphaTarget(0)
178+
d.fx = null
179+
d.fy = null
180+
}
181+
}
182+
183+
// 响应式更新
184+
watch([D3DAGData, nodeRadius, isDark], () => {
185+
initChart()
186+
}, { deep: true })
187+
188+
onMounted(() => {
189+
initChart()
190+
})
191+
192+
onBeforeUnmount(() => {
193+
// 清理模拟
194+
if (simulation.value) {
195+
simulation.value.stop()
196+
}
197+
})
198+
</script>
199+
200+
<style scoped>
201+
.relation-graph-container {
202+
width: 100%;
203+
height: 100%;
204+
overflow: hidden;
205+
border: 1px solid var(--vp-c-divider);
206+
border-radius: 4px;
207+
background-color: var(--vp-c-bg);
208+
}
209+
210+
.relation-graph-container svg {
211+
display: block;
212+
}
213+
</style>

0 commit comments

Comments
 (0)