Commit b12ffcdd authored by 郭铭瑶's avatar 郭铭瑶 🤘

调整&优化

parent 067bede7
......@@ -4,8 +4,7 @@
<script>
import RelationGraph from '@/util/useD3.js'
import { ref } from '@vue/reactivity'
import { nextTick, onMounted, watch } from '@vue/runtime-core'
import { nextTick, onMounted, watch, ref } from 'vue'
export default {
name: 'D3',
props: {
......@@ -44,13 +43,21 @@ export default {
</script>
<style lang="stylus">
.relation-tooltip {
position: fixed;
padding: 0.5rem 1rem;
background-color: rgba(0,0,0,0.5);
border-radius: 0.5rem;
color: #fff;
backdrop-filter: blur(0.5rem);
-webkit-backdrop-filter: blur(0.5rem);
}
.relation-tooltip
position fixed
padding 0.5rem 1rem
background-color rgba(0,0,0,0.5)
border-radius 0.3rem
color #fff
backdrop-filter blur(0.5rem)
-webkit-backdrop-filter blur(0.5rem)
.menu-segment
cursor pointer
fill #2c3e50
&:hover
fill #58b2dc
.menu-icon
pointer-events none
</style>
<template>
<D3
:data="data"
:data="mockData"
:config="config"
style="background: #fff; width: 100%; height: 100vh"
/>
......@@ -10,37 +10,20 @@
import D3 from './d3.vue'
import mockData from '@/util/mock.js'
import { ref } from 'vue'
import RadialMenu from '@/util/menu.js'
export default {
name: 'Main',
components: { D3 },
setup() {
const data = ref(mockData)
const config = ref({
config: {
strokeColor: 'skyblue',
},
})
return {
data,
mockData,
config,
}
},
}
</script>
<style lang="stylus">
.menu-segment {
cursor: pointer;
fill: #2c3e50;
}
.menu-segment:hover {
fill: #58b2dc;
}
.menu-icon {
pointer-events: none;
}
</style>
......@@ -9,15 +9,12 @@ export default function RadialMenu() {
//#region Local Variables
// The following variables have getter/setter functions exposed so are configurable
let data = [{}]
const data = [{}]
let padding = 1
let radius = 50
let thickness = 20
let iconSize = 16
let animationDuration = 250 // The duration to run animations for
let onClick = function (a) {
alert(a)
}
// Private Variables
let offsetAngleDeg = -180 / data.length // Initial rotation angle designed to put centre the first segment at the top
......@@ -28,19 +25,6 @@ export default function RadialMenu() {
//#endregion
//#region Getter/Setter Accessors
/**
* The function to execute on a menu click
* @param {object} onClick - The function to execute on a menu click
* @returns {number} The function to execute on a menu click or the control
*/
control.onClick = function (_) {
if (!arguments.length) return onClick
onClick = _
return control
}
/**
* Time in ms to animate transitions
* @param {object} animationDuration - The time in ms to animate transitions
......@@ -124,9 +108,7 @@ export default function RadialMenu() {
// Create pie layout
pie = d3
.pie()
.value(function (d) {
return data.length
})
.value(data.length)
.padAngle((padding * Math.PI) / 180)
// Create the arc function
......@@ -142,12 +124,9 @@ export default function RadialMenu() {
* @returns {object} The control
*/
control.appendTo = function (target) {
// Convert the target into a valid D3 selection
// that we can append our menu into
target = d3.select(target)
// Create the visualiziation
segmentLayer = target
segmentLayer = d3
.select(target)
.append('g')
.attr('transform', 'rotate(' + offsetAngleDeg + ')')
......@@ -158,10 +137,9 @@ export default function RadialMenu() {
* Display the menu
* @returns {object} The control
*/
control.show = function (_) {
control.show = function (data) {
// Calculate the new offset angle based on the number of data items and
// then rotate the menu to re-centre the first segment
data = _
offsetAngleDeg = -180 / data.length
segmentLayer.attr('transform', 'rotate(' + offsetAngleDeg + ')')
......@@ -219,7 +197,8 @@ export default function RadialMenu() {
this._current = d
}) // store the initial data value for later
.on('click', function (e, d) {
console.log(d.data.action)
const { action } = d.data
action && action(e, d)
})
.transition()
.duration(animationDuration)
......@@ -299,11 +278,14 @@ export default function RadialMenu() {
return arc.innerRadius(innerTween(t)).outerRadius(outerTween(t))(a)
}
})
// .each('end', function (d) {
// dataJoin.remove() // Remove all of the segment groups once the transition has completed
// })
dataJoin.remove()
.remove()
let timer = setTimeout(() => {
segmentLayer.remove()
clearTimeout(timer)
timer = null
}, animationDuration + 100)
return control
}
......
......@@ -36,16 +36,16 @@ function getTransform(source, target, _dis) {
// 使文字折行
function textWrap(text, width) {
text.each(function () {
let text = d3.select(this),
const text = d3.select(this),
words = text.text().split('').reverse(),
word,
line = [],
lineNumber = 0,
lineHeight = 1.1, // ems
x = text.attr('x'),
y = text.attr('y'),
// dy = parseFloat(text.attr("dy")),
dy = 0,
dy = 0
let word,
line = [],
lineNumber = 0,
tspan = text
.text(null)
.append('tspan')
......@@ -91,10 +91,10 @@ const defaultConfig = {
}
const menuData = [
{ icon: branch, action: 'branch' },
{ icon: more, action: 'more' },
{ icon: add, action: 'add' },
{ icon: del, action: 'del' },
{ icon: branch, action: () => console.log('branch') },
{ icon: more, action: () => console.log('more') },
{ icon: add, action: () => console.log('add') },
{ icon: del, action: () => console.log('del') },
]
let menu = null
......@@ -102,10 +102,6 @@ export default class RelationGraph {
constructor(selector, data, configs = {}) {
const mapW = selector.offsetWidth
const mapH = selector.offsetHeight
const defaultWH = {
width: mapW,
height: mapH,
}
// 画布
this.graph = d3.select(selector)
......@@ -113,21 +109,25 @@ export default class RelationGraph {
// 转换links的source和target
const nodes = [...data.nodes]
const links = [...data.links]
links.forEach((item) => {
nodes.forEach((e, i) => {
if (e.id === item.source) {
item.source = i
} else if (e.id === item.target) {
item.target = i
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 = Object.assign(
{},
defaultConfig,
{ links, nodes },
defaultWH,
{
width: mapW,
height: mapH,
},
configs
)
......@@ -142,7 +142,6 @@ export default class RelationGraph {
// 创建力学模拟器
initSimulation() {
const self = this
// 1. 创建一个力学模拟器
this.simulation = d3
.forceSimulation(this.config.nodes)
......@@ -155,7 +154,8 @@ export default class RelationGraph {
'center',
d3.forceCenter(this.config.width / 2, this.config.height / 2)
)
// 碰撞作用力,为节点指定一个radius区域来防止节点重叠,设置碰撞力的强度,范围[0,1], 默认为0.7。设置迭代次数,默认为1,迭代次数越多最终的布局效果越好,但是计算复杂度更高
// 碰撞作用力,为节点指定一个radius区域来防止节点重叠,设置碰撞力的强度,范围[0,1], 默认为0.7。
// 设置迭代次数,默认为1,迭代次数越多最终的布局效果越好,但是计算复杂度更高
.force(
'collide',
d3.forceCollide(this.config.collide).strength(0.2).iterations(5)
......@@ -169,7 +169,6 @@ export default class RelationGraph {
// 2.创建svg标签
this.SVG = this.graph
.append('svg')
.attr('id', 'graph-svg-container')
.attr('width', this.config.width)
.attr('height', this.config.height)
.call(
......@@ -183,10 +182,11 @@ export default class RelationGraph {
})
)
.on('click', () => menu && menu.hide())
.on('dblclick.zoom', null)
.on('dblclick.zoom', null) // 取消双击放大
// 3.defs <defs>标签的内容不会显示,只有调用的时候才显示
this.defs = this.SVG.append('defs')
// 3.1 添加箭头
const border =
this.config.strokeWidth == 0 ? 7.5 : 1.8 * this.config.strokeWidth
......@@ -205,14 +205,12 @@ export default class RelationGraph {
// 3.2 添加多个圈圈图片的 <pattern>
this.patterns = this.defs
.selectAll('pattern.patternclass')
.selectAll('pattern.circle-bg')
.data(this.config.nodes)
.enter()
.append('pattern')
.attr('class', 'patternclass')
.attr('id', function (d) {
return d.id
})
.attr('class', 'circle-bg')
.attr('id', (d) => d.id)
.attr('width', '1')
.attr('height', '1')
......@@ -220,33 +218,18 @@ export default class RelationGraph {
.append('rect')
.attr('width', 2 * this.config.r)
.attr('height', 2 * this.config.r)
.attr('fill', function (d) {
return d.color || self.config.nodeColor
})
.attr('fill', (d) => d.color || this.config.nodeColor)
this.patterns
.append('text')
.attr('class', 'nodetext')
.attr('class', 'node-text')
.attr('x', this.config.r)
.attr('y', this.config.r * 0.9) // edit
.attr('text-anchor', 'middle')
.attr('fill', self.config.fontColor)
.attr('fill', this.config.fontColor)
.style('font-size', this.config.r / 3.8)
.text(function (d) {
// if (d.name.length > 4) {
// return [].slice.call(d.name, 0, 4).join('') + '...'
// }
return d.name
})
// .text(d => {
// const {name} = d
// const arr = name.split('')
// let result = ''
// while (arr.length > 4){
// result += (arr.splice(0, 4).join('') + '<br/>')
// }
// return result
// })
.text((d) => d.name)
// 4.放关系图的容器
this.relMap_g = this.SVG.append('g')
.attr('class', 'relMap_g')
......@@ -278,29 +261,18 @@ export default class RelationGraph {
.on('click', function (e, d) {
console.log('线click')
})
.attr('fill', function (d) {
return d.color || self.config.linkColor
})
.attr('fill', (d) => d.color || this.config.linkColor)
// 5.2 添加线
this.links = this.edges
.append('path')
.attr('class', 'links')
.attr('d', (d) => {
return (
'M' +
this.config.linkSrc +
',' +
0 +
' L' +
getDis(d.source, d.target) +
',0'
.attr(
'd',
(d) => `M${this.config.linkSrc},0 L${getDis(d.source, d.target)},0`
)
})
.style('marker-end', 'url(#marker)')
.attr('stroke', (d) => {
return d.color || this.config.linkColor
})
.attr('stroke', (d) => d.color || this.config.linkColor)
// 5.3 添加关系文字的容器
this.rect_g = this.edges.append('g').attr('class', 'rect_g')
......@@ -313,12 +285,7 @@ export default class RelationGraph {
.attr('width', 40)
.attr('height', 20)
.attr('fill', 'white')
.attr('stroke', (d) => {
// var str = d.color ? '#' + d.color : this.config.linkColor
// return str
// return 'gray'
return 'transparent'
})
.attr('stroke', 'transparent')
// 5.5 文本标签 坐标(x,y)代表 文本的左下角的点
this.texts = this.rect_g
......@@ -327,9 +294,7 @@ export default class RelationGraph {
.attr('y', 5)
.attr('text-anchor', 'middle') // <text>文本中轴对齐方式居中 start | middle | end
.style('font-size', 12)
.text((d) => {
return d.relation
})
.text((d) => d.relation)
const tooltip = d3
.select('body')
......@@ -337,35 +302,21 @@ export default class RelationGraph {
.attr('class', 'relation-tooltip')
.style('opacity', 0)
// 给圈圈节点添加g便于后面添加menu
this.circlesWrapper = this.relMap_g
.selectAll('g.circle')
.selectAll('g.circle-wrapper')
.data(this.config.nodes)
.enter()
.append('g')
.attr('class', 'circle')
.attr('class', 'circle-wrapper')
// 6.关系图添加用于显示圈圈的节点
// this.circles = this.relMap_g
// .selectAll('circle.circleclass')
// .data(this.config.nodes)
// .enter()
// // .append('rect')
// // .attr('width', this.config.r * 2)
// // .attr('height', this.config.r * 2)
this.circles = this.circlesWrapper
.append('circle')
.attr('r', this.config.r)
.attr('class', 'circleclass')
.attr('class', 'node')
.style('cursor', 'pointer')
// .attr("cx", function (d) {
// return d.x;
// })
// .attr("cy", function (d) {
// return d.y;
// })
.attr('fill', function (d) {
return 'url(#' + d.id + ')'
})
.attr('fill', (d) => `url(#${d.id})`)
.attr('stroke', this.config.strokeColor)
.attr('stroke-width', this.config.strokeWidth)
.on('mouseover', function (e, d) {
......@@ -398,25 +349,12 @@ export default class RelationGraph {
.thickness(40)
.appendTo(this.parentNode)
.show(menuData)
// console.log('圈圈节点click', d)
// // 展示方式2 :浮窗展示
// const pageX = e.pageX
// ? e.pageX
// : e.clientX +
// (document.body.scrollLeft || document.documentElement.scrollLeft)
// const pageY = e.pageY
// ? e.pageY
// : e.clientY +
// (document.body.scrollTop || document.documentElement.scrollTop)
// // console.log('pagex', pageX);
// // console.log('pageY', pageY);
//阻止事件冒泡 阻止事件默认行为
e.stopPropagation ? e.stopPropagation() : (e.cancelBubble = true)
e.preventDefault ? e.preventDefault() : (e.returnValue = false)
})
.on('contextmenu', function (e) {
//鼠标右键菜单
// 取消默认行为
// 取消鼠标右键菜单默认行为
e.cancelBubble = true
e.returnValue = false
})
......@@ -450,36 +388,27 @@ export default class RelationGraph {
)
// 文字折行
this.SVG.selectAll('text.nodetext').call(textWrap, this.config.r * 1.8)
this.SVG.selectAll('text.node-text').call(textWrap, this.config.r * 1.8)
}
ticked() {
// 7.1 修改每条容器edge的位置
this.edges.attr('transform', function (d) {
return getTransform(d.source, d.target, getDis(d.source, d.target))
})
this.edges.attr('transform', (d) =>
getTransform(d.source, d.target, getDis(d.source, d.target))
)
// 7.2 修改每条线link位置
this.links.attr('d', (d) => {
return (
'M' +
this.config.linkSrc +
',' +
0 +
' L' +
getDis(d.source, d.target) +
',0'
this.links.attr(
'd',
(d) => `M${this.config.linkSrc},0 L${getDis(d.source, d.target)},0`
)
})
// 7.3 修改线中关系文字text的位置 及 文字的反正
this.texts
.attr('x', function (d) {
// 7.3.1 根据字的长度来更新兄弟元素 rect 的宽度
const bbox = d3.select(this).node().getBBox()
const { width } = bbox
// ########################
// $(this).prev('rect').attr('width', width + 10);
// d3.select(this).prev('rect').attr('width', width + 10);
// // 7.3.1 根据字的长度来更新兄弟元素 rect 的宽度
// const { width } = d3.select(this).node().getBBox()
// 7.3.2 更新 text 的位置
return getDis(d.source, d.target) / 2
})
......@@ -495,19 +424,11 @@ export default class RelationGraph {
// 7.4 修改线中装文本矩形rect的位置
this.rects.attr('x', function (d) {
// ######################
// return getDis(d.source, d.target) / 2 - $(this).attr('width') / 2
return getDis(d.source, d.target) / 2 - d3.select(this).attr('width') / 2
}) // x 坐标为两点中心距离减去自身长度一半
// 5.修改节点的位置
this.circles
.attr('cx', function (d) {
return d.x
})
.attr('cy', function (d) {
return d.y
})
this.circles.attr('cx', (d) => d.x).attr('cy', (d) => d.y)
// 让menu随圆圈移动
this.circles.each(function () {
......@@ -519,13 +440,6 @@ export default class RelationGraph {
menuEle.attr('transform', `translate(${cx},${cy})`)
}
})
// .attr('x', function (d) {
// return d.x - d3.select(this).attr('width') / 2
// })
// .attr('y', function (d) {
// return d.y - d3.select(this).attr('height') / 2
// })
}
// 高亮元素及其相关的元素
......@@ -547,6 +461,7 @@ export default class RelationGraph {
.filter((d) => this.dependsNode.indexOf(d.index) == -1)
.transition()
.style('opacity', 0.1)
// 隐藏线
this.SVG.selectAll('.edge')
.filter((d) => {
......@@ -560,20 +475,11 @@ export default class RelationGraph {
} else {
// 取消高亮
// 恢复隐藏的线
this.SVG.selectAll('circle')
.filter(() => true)
.transition()
.style('opacity', 1)
this.SVG.selectAll('circle').transition().style('opacity', 1)
// 恢复隐藏的线
this.SVG.selectAll('.edge')
.filter((d) => {
return (
this.dependsLinkAndText.indexOf(d.source.index) == -1 &&
this.dependsLinkAndText.indexOf(d.target.index) == -1
)
})
.transition()
.style('opacity', 1)
this.SVG.selectAll('.edge').transition().style('opacity', 1)
this.dependsNode = []
this.dependsLinkAndText = []
}
......@@ -594,20 +500,16 @@ export default class RelationGraph {
})
.transition()
.style('opacity', 0.1)
// 隐藏线
this.SVG.selectAll('.edge')
.filter((d) => d.id != obj.id)
.transition()
.style('opacity', 0.1)
} else {
this.SVG.selectAll('circle')
.filter(() => true)
.transition()
.style('opacity', 1)
this.SVG.selectAll('.edge')
.filter(() => true)
.transition()
.style('opacity', 1)
this.SVG.selectAll('circle').transition().style('opacity', 1)
this.SVG.selectAll('.edge').transition().style('opacity', 1)
}
}
}
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