Commit 674f4769 authored by 郭铭瑶's avatar 郭铭瑶 🤘

搜索及局部重新渲染

parent bcfd01c7
...@@ -65,12 +65,12 @@ export default { ...@@ -65,12 +65,12 @@ export default {
action: (d) => ctx.emit('branch', d), action: (d) => ctx.emit('branch', d),
title: '分支', title: '分支',
}, },
{ // {
key: 'more', // key: 'more',
icon: more, // icon: more,
action: (d) => ctx.emit('more', d), // action: (d) => ctx.emit('more', d),
title: '更多', // title: '更多',
}, // },
{ {
key: 'add', key: 'add',
icon: add, icon: add,
...@@ -89,29 +89,29 @@ export default { ...@@ -89,29 +89,29 @@ export default {
} }
function init() { function init() {
if (!container.value) return if (!container.value) return
if (instance) {
instance.graph.selectAll('*').remove()
}
instance = new RelationGraph(
container.value,
props.data,
props.config,
menuData,
colorList,
setCurNode
)
// if (instance) { // if (instance) {
// instance.update(props.data) // instance.graph.selectAll('*').remove()
// } else {
// instance = new RelationGraph(
// container.value,
// props.data,
// props.config,
// menuData,
// colorList,
// setCurNode
// )
// } // }
// 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() setLegend()
} }
function setKey(key) { function setKey(key) {
......
<template> <template>
<div class="footer"> <div class="footer">
<n-button type="primary" ghost @click="addSubject">Add Subject</n-button> <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-space>
<n-tag v-show="nodeVal" size="small" round :color="color"> <n-tag v-show="nodeVal" size="small" round :color="color">
{{ nodeVal && nodeVal.nodeLabel }} {{ nodeVal && nodeVal.nodeLabel }}
...@@ -32,7 +41,7 @@ export default defineComponent({ ...@@ -32,7 +41,7 @@ export default defineComponent({
}), }),
}, },
}, },
emits: ['set', 'add'], emits: ['set', 'add', 'search'],
setup(props, ctx) { setup(props, ctx) {
const colorList = { const colorList = {
default: 'skyblue', default: 'skyblue',
...@@ -69,6 +78,13 @@ export default defineComponent({ ...@@ -69,6 +78,13 @@ export default defineComponent({
const addSubject = () => { const addSubject = () => {
ctx.emit('add') ctx.emit('add')
} }
const searchKey = ref(null)
const search = () => ctx.emit('search', searchKey.value)
const clear = () => {
searchKey.value = null
search()
}
return { return {
curKey, curKey,
setKey, setKey,
...@@ -76,6 +92,9 @@ export default defineComponent({ ...@@ -76,6 +92,9 @@ export default defineComponent({
color, color,
nodeVal, nodeVal,
addSubject, addSubject,
searchKey,
search,
clear,
} }
}, },
}) })
...@@ -90,4 +109,6 @@ export default defineComponent({ ...@@ -90,4 +109,6 @@ export default defineComponent({
background #f8f9fb background #f8f9fb
box-shadow 10px 6px 10px 0 #000 box-shadow 10px 6px 10px 0 #000
margin-top 4px margin-top 4px
.search-bar
width 50%
</style> </style>
<template> <template>
<div class="main"> <div class="main">
<Nav style="grid-area: nav" @search="handleSearch" /> <Nav style="grid-area: nav" />
<D3 <D3
ref="d3Ref" ref="d3Ref"
style="grid-area: content" style="grid-area: content"
class="graph" class="graph"
:data="data" :data="graphData"
@branch="fetchSubordinates" @branch="fetchSubordinates"
@add="handleAdd" @add="handleAdd"
@curNode="curNode = $event" @curNode="curNode = $event"
...@@ -17,6 +17,7 @@ ...@@ -17,6 +17,7 @@
:node="curNode" :node="curNode"
@add="showSubjectDrawer = true" @add="showSubjectDrawer = true"
@set="setKey" @set="setKey"
@search="handleSearch"
/> />
</div> </div>
<n-drawer v-model:show="showDrawer" :width="400" placement="left"> <n-drawer v-model:show="showDrawer" :width="400" placement="left">
...@@ -99,7 +100,7 @@ export default { ...@@ -99,7 +100,7 @@ export default {
const d3Ref = ref(null) const d3Ref = ref(null)
const relationOptions = ref([]) const relationOptions = ref([])
const systemOptions = ref([]) const systemOptions = ref([])
const data = ref([]) const graphData = ref({ nodes: [], links: [] })
const subjectNodes = ref([]) const subjectNodes = ref([])
function fetchSubjects() { function fetchSubjects() {
// ajax // ajax
...@@ -109,8 +110,8 @@ export default { ...@@ -109,8 +110,8 @@ export default {
// }) // })
// .then((res) => { // .then((res) => {
// console.log('nodes:', res) // console.log('nodes:', res)
// data.value = res.data.content // graphData.value = res.data.content
// systemOptions.value = data.value.nodes // systemOptions.value = graphData.value.nodes
// .filter((node) => node.nodeLabel === 'System') // .filter((node) => node.nodeLabel === 'System')
// .map((node) => ({ label: node.systemName, value: node.nodeId })) // .map((node) => ({ label: node.systemName, value: node.nodeId }))
// }) // })
...@@ -122,7 +123,10 @@ export default { ...@@ -122,7 +123,10 @@ export default {
.then((res) => { .then((res) => {
console.log('subject', res.data.content) console.log('subject', res.data.content)
subjectNodes.value = res.data.content subjectNodes.value = res.data.content
data.value = { nodes: subjectNodes.value, links: [] } graphData.value = {
nodes: [...subjectNodes.value],
links: [],
}
}) })
} }
fetchSubjects() fetchSubjects()
...@@ -145,14 +149,24 @@ export default { ...@@ -145,14 +149,24 @@ export default {
}) })
.then((res) => { .then((res) => {
const { links, nodes } = res.data.content const { links, nodes } = res.data.content
data.value = { graphData.value = {
nodes: [ nodes: [
...data.value.nodes, ...graphData.value.nodes,
...nodes.filter((node) => node.nodeLabel !== 'Subject'), ...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 = data.value.nodes systemOptions.value = graphData.value.nodes
.filter((node) => node.nodeLabel === 'System') .filter((node) => node.nodeLabel === 'System')
.map((node) => ({ label: node.systemName, value: node.nodeId })) .map((node) => ({ label: node.systemName, value: node.nodeId }))
}) })
...@@ -183,7 +197,6 @@ export default { ...@@ -183,7 +197,6 @@ export default {
}, },
}) })
.then(() => { .then(() => {
// fetchSubjects()
fetchSubordinates({ nodeId: subjectId.value }) fetchSubordinates({ nodeId: subjectId.value })
isLoading.value = false isLoading.value = false
showDrawer.value = false showDrawer.value = false
...@@ -211,7 +224,16 @@ export default { ...@@ -211,7 +224,16 @@ export default {
if (data.nodeLabel === 'Subject') { if (data.nodeLabel === 'Subject') {
fetchSubjects() fetchSubjects()
} else { } 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('删除成功!') message.success('删除成功!')
}) })
...@@ -242,6 +264,7 @@ export default { ...@@ -242,6 +264,7 @@ export default {
}) })
} }
const handleSearch = (key) => { const handleSearch = (key) => {
console.log(key)
if (!key) { if (!key) {
fetchSubjects() fetchSubjects()
return return
...@@ -252,8 +275,8 @@ export default { ...@@ -252,8 +275,8 @@ export default {
params: { nodeName: key }, params: { nodeName: key },
}) })
.then((res) => { .then((res) => {
data.value = res.data.content graphData.value = res.data.content
systemOptions.value = data.value.nodes systemOptions.value = graphData.value.nodes
.filter((node) => node.nodeLabel === 'System') .filter((node) => node.nodeLabel === 'System')
.map((node) => ({ label: node.systemName, value: node.nodeId })) .map((node) => ({ label: node.systemName, value: node.nodeId }))
}) })
...@@ -266,7 +289,7 @@ export default { ...@@ -266,7 +289,7 @@ export default {
isLoading, isLoading,
relationOptions, relationOptions,
systemOptions, systemOptions,
data, graphData,
mockData, mockData,
fetchSubordinates, fetchSubordinates,
handleAdd, handleAdd,
...@@ -322,7 +345,7 @@ export default { ...@@ -322,7 +345,7 @@ export default {
overflow hidden overflow hidden
grid-template-areas 'nav nav' 'side content' 'side footer' grid-template-areas 'nav nav' 'side content' 'side footer'
grid-template-columns 0px 1fr grid-template-columns 0px 1fr
grid-template-rows 40px 1fr 70px grid-template-rows 0px 1fr 70px
padding 4px padding 4px
font-weight bold !important font-weight bold !important
</style> </style>
<template> <template>
<div class="nav"> <div class="nav">
<n-input <!-- <n-input
v-model:value="searchKey" v-model:value="searchKey"
type="text" type="text"
placeholder="搜索Subject" placeholder="搜索Subject"
clearable clearable
@keypress.enter="search" @keypressenter="search"
/> /> -->
</div> </div>
</template> </template>
...@@ -27,8 +27,8 @@ export default defineComponent({ ...@@ -27,8 +27,8 @@ export default defineComponent({
</script> </script>
<style lang="stylus" scoped> <style lang="stylus" scoped>
.nav // .nav
background #f9fbfd // background #f9fbfd
border-bottom 4px solid #fff // border-bottom 4px solid #fff
padding 0 10px // padding 0 10px
</style> </style>
...@@ -7,7 +7,7 @@ import RadialMenu from './menu' ...@@ -7,7 +7,7 @@ import RadialMenu from './menu'
// 求两点间的距离 // 求两点间的距离
function getDis(s, t) { 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 属性值 // 求元素移动到目标位置所需要的 transform 属性值
...@@ -158,15 +158,27 @@ export default class RelationGraph { ...@@ -158,15 +158,27 @@ export default class RelationGraph {
} }
update(data) { update(data) {
const self = this const self = this
console.log('====', data) const t = d3.transition().duration(750)
// this.config = Object.assign({}, this.config, data) // 转换links的source和target
this.config.nodes = [...data.nodes] const nodes = [...data.nodes]
this.config.links = [...data.links] 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) console.log('update', this.config)
// d3.selectAll('pattern.circle-bg').data(this.config.nodes) // d3.selectAll('pattern.circle-bg').data(this.config.nodes)
// d3.selectAll('g.circle-wrapper').data(this.config.nodes) // d3.selectAll('g.circle-wrapper').data(this.config.nodes)
// d3.selectAll('g.edge').data(this.config.links) // d3.selectAll('g.edge').data(this.config.links)
this.patterns.remove()
this.patterns = this.defs this.patterns = this.defs
.selectAll('pattern.circle-bg') .selectAll('pattern.circle-bg')
.data(this.config.nodes) .data(this.config.nodes)
...@@ -199,13 +211,78 @@ export default class RelationGraph { ...@@ -199,13 +211,78 @@ export default class RelationGraph {
.style('font-size', this.config.r / 3.8) .style('font-size', this.config.r / 3.8)
.text((d) => d[d.nodeLabel.toLowerCase() + 'Name']) .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 // 给圈圈节点添加g便于后面添加menu
this.circlesWrapper.remove()
this.circlesWrapper = this.relMap_g this.circlesWrapper = this.relMap_g
.selectAll('g.circle-wrapper') .selectAll('g.circle-wrapper')
.data(this.config.nodes) .data(this.config.nodes)
.enter() .enter()
.append('g') .append('g')
.attr('class', 'circle-wrapper') .attr('class', 'circle-wrapper')
.merge(this.circlesWrapper)
this.circlesWrapper.exit().remove()
// 6.关系图添加用于显示圈圈的节点 // 6.关系图添加用于显示圈圈的节点
this.circles = this.circlesWrapper this.circles = this.circlesWrapper
...@@ -292,67 +369,34 @@ export default class RelationGraph { ...@@ -292,67 +369,34 @@ export default class RelationGraph {
// 文字折行 // 文字折行
this.SVG.selectAll('text.node-text').call(textWrap, this.config.r * 1.8) this.SVG.selectAll('text.node-text').call(textWrap, this.config.r * 1.8)
// 5.关系图添加线 this.simulation
// 5.1 每条线是个容器,有线 和一个装文字的容器 .nodes(this.config.nodes)
this.edges = this.relMap_g // simulation.force(name,[force])函数,添加某种力
.selectAll('g.edge') .force('link', d3.forceLink(this.config.links))
.data(this.config.links) // 万有引力
.enter() .force('charge', d3.forceManyBody().strength(this.config.chargeStrength))
.append('g') // d3.forceCenter()用指定的x坐标和y坐标创建一个新的居中力。
.attr('class', 'edge') .force(
.on('mouseover', function (e, d) { 'center',
if (self.config.isHighLight) { d3.forceCenter(this.config.width / 2, this.config.height / 2)
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)') // 碰撞作用力,为节点指定一个radius区域来防止节点重叠,设置碰撞力的强度,范围[0,1], 默认为0.7。
.attr('stroke', (d) => d.color || this.config.linkColor) // 设置迭代次数,默认为1,迭代次数越多最终的布局效果越好,但是计算复杂度更高
.force(
// 5.3 添加关系文字的容器 'collide',
this.rect_g = this.edges.append('g').attr('class', 'rect_g') d3.forceCollide(this.config.collide).strength(0.2).iterations(5)
)
// 5.4 添加rect // 在计时器的每一帧中,仿真的alpha系数会不断削减,当alpha到达一个系数时,仿真将会停止,也就是alpha的目标系数alphaTarget,该值区间为[0,1]. 默认为0,
this.rects = this.rect_g // 控制力学模拟衰减率,[0-1] ,设为0则不停止 , 默认0.0228,直到0.001
.append('rect') .alphaDecay(this.config.alphaDecay)
.attr('x', 40) // 监听事件 ,tick|end ,例如监听 tick 滴答事件
.attr('y', -10) .on('tick', () => this.ticked())
.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)
this.simulation.restart() this.simulation.alphaTarget(0.3).restart()
const timer = setTimeout(() => {
this.simulation.alphaTarget(0)
clearTimeout(timer)
}, 500)
} }
openMenu(self, d, type) { openMenu(self, d, type) {
this.setCurNode(d) this.setCurNode(d)
...@@ -375,7 +419,6 @@ export default class RelationGraph { ...@@ -375,7 +419,6 @@ export default class RelationGraph {
// 创建力学模拟器 // 创建力学模拟器
createSimulation() { createSimulation() {
console.log('createSimulation', this.config.nodes, this.config.links)
// 1. 创建一个力学模拟器 // 1. 创建一个力学模拟器
this.simulation = d3 this.simulation = d3
.forceSimulation(this.config.nodes) .forceSimulation(this.config.nodes)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment