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

搜索及局部重新渲染

parent bcfd01c7
......@@ -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,20 +89,9 @@ 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.graph.selectAll('*').remove()
// }
// instance = new RelationGraph(
// container.value,
// props.data,
......@@ -111,7 +100,18 @@ export default {
// 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) {
......
<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>
<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="data"
:data="graphData"
@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)
// data.value = res.data.content
// systemOptions.value = data.value.nodes
// graphData.value = res.data.content
// systemOptions.value = graphData.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
data.value = {
graphData.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 = data.value.nodes
systemOptions.value = graphData.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) => {
data.value = res.data.content
systemOptions.value = data.value.nodes
graphData.value = res.data.content
systemOptions.value = graphData.value.nodes
.filter((node) => node.nodeLabel === 'System')
.map((node) => ({ label: node.systemName, value: node.nodeId }))
})
......@@ -266,7 +289,7 @@ export default {
isLoading,
relationOptions,
systemOptions,
data,
graphData,
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 40px 1fr 70px
grid-template-rows 0px 1fr 70px
padding 4px
font-weight bold !important
</style>
<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>
......@@ -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)
......
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