Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Contribute to GitLab
Sign in / Register
Toggle navigation
R
relation-graph
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
郭铭瑶
relation-graph
Commits
674f4769
Commit
674f4769
authored
Aug 15, 2021
by
郭铭瑶
🤘
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
搜索及局部重新渲染
parent
bcfd01c7
Hide whitespace changes
Inline
Side-by-side
Showing
5 changed files
with
204 additions
and
117 deletions
+204
-117
d3.vue
src/components/d3.vue
+27
-27
footer.vue
src/components/footer.vue
+22
-1
main.vue
src/components/main.vue
+40
-17
nav.vue
src/components/nav.vue
+7
-7
useD3.js
src/util/useD3.js
+108
-65
No files found.
src/components/d3.vue
View file @
674f4769
...
...
@@ -65,12 +65,12 @@ export default {
action
:
(
d
)
=>
ctx
.
emit
(
'branch'
,
d
),
title
:
'分支'
,
}
,
{
key
:
'more'
,
icon
:
more
,
action
:
(
d
)
=>
ctx
.
emit
(
'more'
,
d
),
title
:
'更多'
,
}
,
//
{
//
key: 'more',
//
icon: more,
//
action: (d) => ctx.emit('more', d),
//
title: '更多',
//
}
,
{
key
:
'add'
,
icon
:
add
,
...
...
@@ -89,29 +89,29 @@ export default {
}
function
init
()
{
if
(
!
container
.
value
)
return
if
(
instance
)
{
instance
.
graph
.
selectAll
(
'*'
).
remove
()
}
instance
=
new
RelationGraph
(
container
.
value
,
props
.
data
,
props
.
config
,
menuData
,
colorList
,
setCurNode
)
// if (instance)
{
// instance.update(props.data)
//
}
else
{
// instance = new RelationGraph(
// container.value,
// props.data,
// props.config,
// menuData,
// colorList,
// setCurNode
// )
// instance.graph.selectAll('*').remove()
//
}
// instance = new RelationGraph(
// container.value,
// props.data,
// props.config,
// menuData,
// colorList,
// setCurNode
// )
if
(
instance
)
{
instance
.
update
(
props
.
data
)
}
else
{
instance
=
new
RelationGraph
(
container
.
value
,
props
.
data
,
props
.
config
,
menuData
,
colorList
,
setCurNode
)
}
setLegend
()
}
function
setKey
(
key
)
{
...
...
src/components/footer.vue
View file @
674f4769
<
template
>
<div
class=
"footer"
>
<n-button
type=
"primary"
ghost
@
click=
"addSubject"
>
Add Subject
</n-button>
<n-input
v-model:value=
"searchKey"
class=
"search-bar"
type=
"text"
placeholder=
"Search Subject ..."
clearable
:on-clear=
"clear"
@
keypress
.
enter=
"search"
/>
<n-space>
<n-tag
v-show=
"nodeVal"
size=
"small"
round
:color=
"color"
>
{{
nodeVal
&&
nodeVal
.
nodeLabel
}}
...
...
@@ -32,7 +41,7 @@ export default defineComponent({
}),
},
},
emits
:
[
'set'
,
'add'
],
emits
:
[
'set'
,
'add'
,
'search'
],
setup
(
props
,
ctx
)
{
const
colorList
=
{
default
:
'skyblue'
,
...
...
@@ -69,6 +78,13 @@ export default defineComponent({
const
addSubject
=
()
=>
{
ctx
.
emit
(
'add'
)
}
const
searchKey
=
ref
(
null
)
const
search
=
()
=>
ctx
.
emit
(
'search'
,
searchKey
.
value
)
const
clear
=
()
=>
{
searchKey
.
value
=
null
search
()
}
return
{
curKey
,
setKey
,
...
...
@@ -76,6 +92,9 @@ export default defineComponent({
color
,
nodeVal
,
addSubject
,
searchKey
,
search
,
clear
,
}
},
})
...
...
@@ -90,4 +109,6 @@ export default defineComponent({
background #f8f9fb
box-shadow 10px 6px 10px 0 #000
margin-top 4px
.search-bar
width 50%
</
style
>
src/components/main.vue
View file @
674f4769
<
template
>
<div
class=
"main"
>
<Nav
style=
"grid-area: nav"
@
search=
"handleSearch"
/>
<Nav
style=
"grid-area: nav"
/>
<D3
ref=
"d3Ref"
style=
"grid-area: content"
class=
"graph"
:data=
"
d
ata"
:data=
"
graphD
ata"
@
branch=
"fetchSubordinates"
@
add=
"handleAdd"
@
curNode=
"curNode = $event"
...
...
@@ -17,6 +17,7 @@
:node=
"curNode"
@
add=
"showSubjectDrawer = true"
@
set=
"setKey"
@
search=
"handleSearch"
/>
</div>
<n-drawer
v-model:show=
"showDrawer"
:width=
"400"
placement=
"left"
>
...
...
@@ -99,7 +100,7 @@ export default {
const
d3Ref
=
ref
(
null
)
const
relationOptions
=
ref
([])
const
systemOptions
=
ref
([])
const
data
=
ref
([]
)
const
graphData
=
ref
({
nodes
:
[],
links
:
[]
}
)
const
subjectNodes
=
ref
([])
function
fetchSubjects
()
{
// ajax
...
...
@@ -109,8 +110,8 @@ export default {
// })
// .then((res) => {
// console.log('nodes:', res)
//
d
ata.value = res.data.content
// systemOptions.value =
d
ata.value.nodes
//
graphD
ata.value = res.data.content
// systemOptions.value =
graphD
ata.value.nodes
// .filter((node) => node.nodeLabel === 'System')
// .map((node) => ({ label: node.systemName, value: node.nodeId }))
// })
...
...
@@ -122,7 +123,10 @@ export default {
.
then
((
res
)
=>
{
console
.
log
(
'subject'
,
res
.
data
.
content
)
subjectNodes
.
value
=
res
.
data
.
content
data
.
value
=
{
nodes
:
subjectNodes
.
value
,
links
:
[]
}
graphData
.
value
=
{
nodes
:
[...
subjectNodes
.
value
],
links
:
[],
}
})
}
fetchSubjects
()
...
...
@@ -145,14 +149,24 @@ export default {
})
.
then
((
res
)
=>
{
const
{
links
,
nodes
}
=
res
.
data
.
content
d
ata
.
value
=
{
graphD
ata
.
value
=
{
nodes
:
[
...
data
.
value
.
nodes
,
...
nodes
.
filter
((
node
)
=>
node
.
nodeLabel
!==
'Subject'
),
...
graphData
.
value
.
nodes
,
...
nodes
.
filter
(
(
node
)
=>
node
.
nodeLabel
!==
'Subject'
&&
graphData
.
value
.
nodes
.
findIndex
((
e
)
=>
e
.
id
==
node
.
id
)
<
0
),
],
links
:
[
...
graphData
.
value
.
links
,
...
links
.
filter
(
(
link
)
=>
graphData
.
value
.
links
.
findIndex
((
e
)
=>
e
.
id
==
link
.
id
)
<
0
),
],
links
:
[...
data
.
value
.
links
,
...
links
],
}
systemOptions
.
value
=
d
ata
.
value
.
nodes
systemOptions
.
value
=
graphD
ata
.
value
.
nodes
.
filter
((
node
)
=>
node
.
nodeLabel
===
'System'
)
.
map
((
node
)
=>
({
label
:
node
.
systemName
,
value
:
node
.
nodeId
}))
})
...
...
@@ -183,7 +197,6 @@ export default {
},
})
.
then
(()
=>
{
// fetchSubjects()
fetchSubordinates
({
nodeId
:
subjectId
.
value
})
isLoading
.
value
=
false
showDrawer
.
value
=
false
...
...
@@ -211,7 +224,16 @@ export default {
if
(
data
.
nodeLabel
===
'Subject'
)
{
fetchSubjects
()
}
else
{
fetchSubordinates
({
nodeId
:
null
})
// fetchSubordinates({ nodeId: data.subjectId })
graphData
.
value
=
{
nodes
:
graphData
.
value
.
nodes
.
filter
(
(
node
)
=>
node
.
id
!=
data
.
id
),
links
:
graphData
.
value
.
links
.
filter
(
(
link
)
=>
link
.
source
.
id
!=
data
.
id
&&
link
.
target
.
id
!=
data
.
id
),
}
}
message
.
success
(
'删除成功!'
)
})
...
...
@@ -242,6 +264,7 @@ export default {
})
}
const
handleSearch
=
(
key
)
=>
{
console
.
log
(
key
)
if
(
!
key
)
{
fetchSubjects
()
return
...
...
@@ -252,8 +275,8 @@ export default {
params
:
{
nodeName
:
key
},
})
.
then
((
res
)
=>
{
d
ata
.
value
=
res
.
data
.
content
systemOptions
.
value
=
d
ata
.
value
.
nodes
graphD
ata
.
value
=
res
.
data
.
content
systemOptions
.
value
=
graphD
ata
.
value
.
nodes
.
filter
((
node
)
=>
node
.
nodeLabel
===
'System'
)
.
map
((
node
)
=>
({
label
:
node
.
systemName
,
value
:
node
.
nodeId
}))
})
...
...
@@ -266,7 +289,7 @@ export default {
isLoading
,
relationOptions
,
systemOptions
,
d
ata
,
graphD
ata
,
mockData
,
fetchSubordinates
,
handleAdd
,
...
...
@@ -322,7 +345,7 @@ export default {
overflow hidden
grid-template-areas 'nav nav' 'side content' 'side footer'
grid-template-columns 0px 1fr
grid-template-rows
4
0px 1fr 70px
grid-template-rows 0px 1fr 70px
padding 4px
font-weight bold !important
</
style
>
src/components/nav.vue
View file @
674f4769
<
template
>
<div
class=
"nav"
>
<n-input
<
!--
<
n-input
v-model:value=
"searchKey"
type=
"text"
placeholder=
"搜索Subject"
clearable
@
keypress
.
enter=
"search"
/>
@
keypressenter=
"search"
/>
-->
</div>
</
template
>
...
...
@@ -27,8 +27,8 @@ export default defineComponent({
</
script
>
<
style
lang=
"stylus"
scoped
>
.nav
background #f9fbfd
border-bottom 4px solid #fff
padding 0 10px
//
.nav
//
background #f9fbfd
//
border-bottom 4px solid #fff
//
padding 0 10px
</
style
>
src/util/useD3.js
View file @
674f4769
...
...
@@ -7,7 +7,7 @@ import RadialMenu from './menu'
// 求两点间的距离
function
getDis
(
s
,
t
)
{
return
Math
.
sqrt
((
s
.
x
-
t
.
x
)
*
(
s
.
x
-
t
.
x
)
+
(
s
.
y
-
t
.
y
)
*
(
s
.
y
-
t
.
y
))
return
Math
.
sqrt
((
s
.
x
-
t
.
x
)
*
(
s
.
x
-
t
.
x
)
+
(
s
.
y
-
t
.
y
)
*
(
s
.
y
-
t
.
y
))
||
100
}
// 求元素移动到目标位置所需要的 transform 属性值
...
...
@@ -158,15 +158,27 @@ export default class RelationGraph {
}
update
(
data
)
{
const
self
=
this
console
.
log
(
'===='
,
data
)
// this.config = Object.assign({}, this.config, data)
this
.
config
.
nodes
=
[...
data
.
nodes
]
this
.
config
.
links
=
[...
data
.
links
]
const
t
=
d3
.
transition
().
duration
(
750
)
// 转换links的source和target
const
nodes
=
[...
data
.
nodes
]
const
links
=
[...
data
.
links
]
links
.
forEach
((
link
)
=>
{
nodes
.
forEach
((
node
)
=>
{
if
(
node
.
id
===
link
.
source
)
{
link
.
source
=
node
}
else
if
(
node
.
id
===
link
.
target
)
{
link
.
target
=
node
}
})
})
this
.
config
.
nodes
=
[...
nodes
]
this
.
config
.
links
=
[...
links
]
console
.
log
(
'update'
,
this
.
config
)
// d3.selectAll('pattern.circle-bg').data(this.config.nodes)
// d3.selectAll('g.circle-wrapper').data(this.config.nodes)
// d3.selectAll('g.edge').data(this.config.links)
this
.
patterns
.
remove
()
this
.
patterns
=
this
.
defs
.
selectAll
(
'pattern.circle-bg'
)
.
data
(
this
.
config
.
nodes
)
...
...
@@ -199,13 +211,78 @@ export default class RelationGraph {
.
style
(
'font-size'
,
this
.
config
.
r
/
3.8
)
.
text
((
d
)
=>
d
[
d
.
nodeLabel
.
toLowerCase
()
+
'Name'
])
// 5.关系图添加线
// 5.1 每条线是个容器,有线 和一个装文字的容器
this
.
edges
.
remove
()
this
.
edges
=
this
.
relMap_g
.
selectAll
(
'g.edge'
)
.
data
(
this
.
config
.
links
)
.
enter
()
.
append
(
'g'
)
.
attr
(
'class'
,
'edge'
)
.
merge
(
this
.
edges
)
.
on
(
'mouseover'
,
function
(
e
,
d
)
{
if
(
self
.
config
.
isHighLight
)
{
self
.
highlightLinks
(
d
)
}
d3
.
select
(
this
).
selectAll
(
'path.links'
).
attr
(
'stroke-width'
,
2
)
d3
.
select
(
this
).
selectAll
(
'.rect_g text'
).
style
(
'font-weight'
,
'bold'
)
})
.
on
(
'mouseout'
,
function
()
{
if
(
self
.
config
.
isHighLight
)
{
self
.
highlightLinks
(
null
)
}
d3
.
select
(
this
).
selectAll
(
'path.links'
).
attr
(
'stroke-width'
,
1
)
d3
.
select
(
this
).
selectAll
(
'.rect_g text'
).
style
(
'font-weight'
,
'normal'
)
})
.
on
(
'click'
,
function
(
e
,
d
)
{
console
.
log
(
'线click'
)
})
.
attr
(
'fill'
,
(
d
)
=>
d
.
color
||
this
.
config
.
linkColor
)
// 5.2 添加线
this
.
links
=
this
.
edges
.
append
(
'path'
)
.
attr
(
'class'
,
'links'
)
.
attr
(
'd'
,
(
d
)
=>
`M
${
this
.
config
.
linkSrc
}
,0 L
${
getDis
(
d
.
source
,
d
.
target
)}
,0`
)
.
style
(
'marker-end'
,
'url(#marker)'
)
.
attr
(
'stroke'
,
(
d
)
=>
d
.
color
||
this
.
config
.
linkColor
)
// 5.3 添加关系文字的容器
this
.
rect_g
=
this
.
edges
.
append
(
'g'
).
attr
(
'class'
,
'rect_g'
)
// 5.4 添加rect
this
.
rects
=
this
.
rect_g
.
append
(
'rect'
)
.
attr
(
'x'
,
40
)
.
attr
(
'y'
,
-
10
)
.
attr
(
'width'
,
50
)
.
attr
(
'height'
,
20
)
.
attr
(
'fill'
,
'#f9fbfd'
)
.
attr
(
'stroke'
,
'transparent'
)
// 5.5 文本标签 坐标(x,y)代表 文本的左下角的点
this
.
texts
=
this
.
rect_g
.
append
(
'text'
)
.
attr
(
'x'
,
40
)
.
attr
(
'y'
,
5
)
.
attr
(
'text-anchor'
,
'middle'
)
// <text>文本中轴对齐方式居中 start | middle | end
.
style
(
'font-size'
,
12
)
.
text
((
d
)
=>
d
.
name
)
// 给圈圈节点添加g便于后面添加menu
this
.
circlesWrapper
.
remove
()
this
.
circlesWrapper
=
this
.
relMap_g
.
selectAll
(
'g.circle-wrapper'
)
.
data
(
this
.
config
.
nodes
)
.
enter
()
.
append
(
'g'
)
.
attr
(
'class'
,
'circle-wrapper'
)
.
merge
(
this
.
circlesWrapper
)
this
.
circlesWrapper
.
exit
().
remove
()
// 6.关系图添加用于显示圈圈的节点
this
.
circles
=
this
.
circlesWrapper
...
...
@@ -292,67 +369,34 @@ export default class RelationGraph {
// 文字折行
this
.
SVG
.
selectAll
(
'text.node-text'
).
call
(
textWrap
,
this
.
config
.
r
*
1.8
)
// 5.关系图添加线
// 5.1 每条线是个容器,有线 和一个装文字的容器
this
.
edges
=
this
.
relMap_g
.
selectAll
(
'g.edge'
)
.
data
(
this
.
config
.
links
)
.
enter
()
.
append
(
'g'
)
.
attr
(
'class'
,
'edge'
)
.
on
(
'mouseover'
,
function
(
e
,
d
)
{
if
(
self
.
config
.
isHighLight
)
{
self
.
highlightLinks
(
d
)
}
d3
.
select
(
this
).
selectAll
(
'path.links'
).
attr
(
'stroke-width'
,
2
)
d3
.
select
(
this
).
selectAll
(
'.rect_g text'
).
style
(
'font-weight'
,
'bold'
)
})
.
on
(
'mouseout'
,
function
()
{
if
(
self
.
config
.
isHighLight
)
{
self
.
highlightLinks
(
null
)
}
d3
.
select
(
this
).
selectAll
(
'path.links'
).
attr
(
'stroke-width'
,
1
)
d3
.
select
(
this
).
selectAll
(
'.rect_g text'
).
style
(
'font-weight'
,
'normal'
)
})
.
on
(
'click'
,
function
(
e
,
d
)
{
console
.
log
(
'线click'
)
})
.
attr
(
'fill'
,
(
d
)
=>
d
.
color
||
this
.
config
.
linkColor
)
// 5.2 添加线
this
.
links
=
this
.
edges
.
append
(
'path'
)
.
attr
(
'class'
,
'links'
)
.
attr
(
'd'
,
(
d
)
=>
`M
${
this
.
config
.
linkSrc
}
,0 L
${
getDis
(
d
.
source
,
d
.
target
)}
,0`
this
.
simulation
.
nodes
(
this
.
config
.
nodes
)
// simulation.force(name,[force])函数,添加某种力
.
force
(
'link'
,
d3
.
forceLink
(
this
.
config
.
links
))
// 万有引力
.
force
(
'charge'
,
d3
.
forceManyBody
().
strength
(
this
.
config
.
chargeStrength
))
// d3.forceCenter()用指定的x坐标和y坐标创建一个新的居中力。
.
force
(
'center'
,
d3
.
forceCenter
(
this
.
config
.
width
/
2
,
this
.
config
.
height
/
2
)
)
.
style
(
'marker-end'
,
'url(#marker)'
)
.
attr
(
'stroke'
,
(
d
)
=>
d
.
color
||
this
.
config
.
linkColor
)
// 5.3 添加关系文字的容器
this
.
rect_g
=
this
.
edges
.
append
(
'g'
).
attr
(
'class'
,
'rect_g'
)
// 5.4 添加rect
this
.
rects
=
this
.
rect_g
.
append
(
'rect'
)
.
attr
(
'x'
,
40
)
.
attr
(
'y'
,
-
10
)
.
attr
(
'width'
,
50
)
.
attr
(
'height'
,
20
)
.
attr
(
'fill'
,
'#f9fbfd'
)
.
attr
(
'stroke'
,
'transparent'
)
// 5.5 文本标签 坐标(x,y)代表 文本的左下角的点
this
.
texts
=
this
.
rect_g
.
append
(
'text'
)
.
attr
(
'x'
,
40
)
.
attr
(
'y'
,
5
)
.
attr
(
'text-anchor'
,
'middle'
)
// <text>文本中轴对齐方式居中 start | middle | end
.
style
(
'font-size'
,
12
)
.
text
((
d
)
=>
d
.
name
)
// 碰撞作用力,为节点指定一个radius区域来防止节点重叠,设置碰撞力的强度,范围[0,1], 默认为0.7。
// 设置迭代次数,默认为1,迭代次数越多最终的布局效果越好,但是计算复杂度更高
.
force
(
'collide'
,
d3
.
forceCollide
(
this
.
config
.
collide
).
strength
(
0.2
).
iterations
(
5
)
)
// 在计时器的每一帧中,仿真的alpha系数会不断削减,当alpha到达一个系数时,仿真将会停止,也就是alpha的目标系数alphaTarget,该值区间为[0,1]. 默认为0,
// 控制力学模拟衰减率,[0-1] ,设为0则不停止 , 默认0.0228,直到0.001
.
alphaDecay
(
this
.
config
.
alphaDecay
)
// 监听事件 ,tick|end ,例如监听 tick 滴答事件
.
on
(
'tick'
,
()
=>
this
.
ticked
())
this
.
simulation
.
restart
()
this
.
simulation
.
alphaTarget
(
0.3
).
restart
()
const
timer
=
setTimeout
(()
=>
{
this
.
simulation
.
alphaTarget
(
0
)
clearTimeout
(
timer
)
},
500
)
}
openMenu
(
self
,
d
,
type
)
{
this
.
setCurNode
(
d
)
...
...
@@ -375,7 +419,6 @@ export default class RelationGraph {
// 创建力学模拟器
createSimulation
()
{
console
.
log
(
'createSimulation'
,
this
.
config
.
nodes
,
this
.
config
.
links
)
// 1. 创建一个力学模拟器
this
.
simulation
=
d3
.
forceSimulation
(
this
.
config
.
nodes
)
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment