实现内容主要根据节点children的类型字段来判定是采用文件树模式展示还是思维脑图展示,实现页面如下图展示
addAllNode(){
const cells = []
const addNode = (data,x,y) => {
var newNode = this.graph.createNode({
id: data.id,
shape: (data.type === 'topic'||data.type === 'topic-branch')?'topic':'topic-child',
x: x,
y: y,
width: data.width,
height: data.height,
attrs: {
label:data.type!='topic-branch'&&data.type!='topic'?{
refX: 1,
textAnchor: 'start',
textVerticalAnchor: 'middle',
textWrap: {
text: data.type === 'functional' ? data.label:
data.type === 'property' ? data.label:
data.type === 'mode' ? 'FM:'+data.label:
data.type === 'cause' ? 'FC:'+data.label:
data.type === 'cause_mode' ? 'FC:'+data.label:
data.type === 'result' ? 'FE:'+data.label:
data.type === 'cause_result' ? 'FE:'+data.label:
data.type === 'result_mode' ? 'FE:'+data.label:
data.type === 'measure' ? data.measure_type+":"+data.label:
data.type === 'cause_measure' ? 'DC:'+data.label:data.label
},
fontWeight: (data.type === 'topic'|| data.type === 'cause_mode'||data.type === 'result_mode'||data.type === 'cause_measure'||data.type === 'cause_result')? 'bold':'normal',
fill: data.type==='functional'?'#00b400':
data.type==='property'?'#63B8FF':
data.type==='mode'?'#f50004':
(data.type==='cause'||data.type==='cause_mode')?'#ff8430':
(data.type==='result'||data.type==='result_mode'||data.type==='cause_result')?'#eb00eb':
(data.type==='measure'&&(data.measure_type==='DC'||data.measure_type==='ODC'))?'#4559FE':
(data.type==='measure'&&(data.measure_type==='PC'||data.measure_type==='OPC'))?'rgb(153, 102, 255)':
data.type==='cause_measure'?'#4559FE':
data.type==='d_interface'?'green':
data.type==='interface'?'#009999':'black',
}:{
textWrap: {
text: data.label
},
fontWeight: data.type === 'topic' ? 'bold':'normal',
fill: data.type === 'topic' ? '#ffffff':'black',
},
body: {
fill: data.type==='topic'?'#5091c6':data.type==='topic-branch'?'#EFF4FF':
data.is_customer_need==1?'rgb(228, 255, 253)':'none',
},
},
type: data.type,
name: data.label,
level: data.level,
tip_info: data.tip_info,
is_have_upper_mode: data.is_have_upper_mode,
is_customer_need:data.is_customer_need,
ports: { ...this.ports },
})
if(data.leaf){
newNode.toggleButtonVisibility(data.leaf === false)
}
this.addNodePort(newNode)
var son_fun_x = x+40;//非产品节点x坐标 = 上级x+40
var son_fun_y = y+newNode.size().height+this.fun_hap;//非产品节点y坐标
var son_pro_x = x+(newNode.size().width>150?newNode.size().width:150)+40;//产品节点x坐标
if (data.children) {
if(data.collapse==0){//展开
let son_pro_num = 0;//子产品个数
//先添加非产品节点
data.children.forEach((item) => {
if(item.type!='topic-branch'){
var newSonNodeInfo = addNode(item,son_fun_x,son_fun_y);
var newSonNode = newSonNodeInfo.node;
son_fun_y = newSonNodeInfo.last_son_y;//依次往下累加高度y
//
var sun_fun_width = son_fun_x+newSonNode.size().width+40;
if(sun_fun_width>son_pro_x){
son_pro_x = sun_fun_width
}
if(newSonNodeInfo.sun_fun_width>son_pro_x){//依次取出非产品节点最宽
son_pro_x = newSonNodeInfo.sun_fun_width
}
//添加连接线
cells.push(this.addEdgeMethod(newNode,newSonNode,2))
}else{
son_pro_num += 1;
}
})
//计算下级产品的高度
let son_pro_notlast_total_height = 0;
let add_son_pro_num = 0;
//最后一个产品高度不纳入高度计算
data.children.forEach((item) => {
if(item.type=='topic-branch'){
add_son_pro_num += 1;
api.lowProductTotalHeight(item,this.pro_hap,this.fun_hap)
if(add_son_pro_num<son_pro_num){
son_pro_notlast_total_height += item.total_height
}else{//如果最后一个下面有多个同级,将高度纳入总高度计算
if(data.have_mul_son_product&&data.have_mul_son_product==1&&item.collapse==0&&(item.have_mul_son_product&&item.have_mul_son_product==1)){
son_pro_notlast_total_height += item.total_height
}
}
}
})
//添加产品类节点
var first_product_height = 0;//第一个子产品y坐标
if(son_pro_num==1){//子产品个数只有1个
if(data.type=='topic'){//父级是根节点,y坐标需要处理,因为根节点高度和其他节点高度不一样
first_product_height = y + newNode.size().height/2 - data.children[0].height/2
}else{//父级不是根节点,y坐标=父级y坐标
first_product_height = y
}
}else if(son_pro_num>1){//子产品个数有多个,将子级所有高度对半开
first_product_height = y + newNode.size().height/2 - data.children[0].height/2 - son_pro_notlast_total_height/2;
}
var front_pro_total_height = 0;//上面产品总高度
//
var pro_index = 0;//子产品顺序
data.children.forEach((item) => {
if(item.type=='topic-branch'){
var newSonNodeInfo;
if(pro_index==0){//第一个产品,从顶部第一个子产品y坐标开始
newSonNodeInfo = addNode(item,son_pro_x,first_product_height);
if(item.have_mul_son_product&&item.have_mul_son_product==1){//下面同级有多个产品
front_pro_total_height = first_product_height + item.low_half_height
}else{//下面同级没有多个产品
front_pro_total_height = first_product_height + item.total_height
}
}else{//非第一个产品
if(item.have_mul_son_product&&item.have_mul_son_product==1){//下面同级有多个产品
var son_y = front_pro_total_height + item.up_half_height
newSonNodeInfo = addNode(item,son_pro_x,son_y);
}else{//下面同级没有多个产品
newSonNodeInfo = addNode(item,son_pro_x,front_pro_total_height);
}
front_pro_total_height += item.total_height
}
var newSonNode = newSonNodeInfo.node;
//添加连接线
cells.push(this.addEdgeMethod(newNode,newSonNode,1))
pro_index += 1;
}
})
}else{
newNode.toggleCollapse()
}
}
cells.push(newNode);
var info = {
node: newNode,
last_son_y: son_fun_y,
sun_fun_width: son_pro_x
}
return info;
}
//根节点
addNode(this.data,this.width/3,this.height/3)
this.graph.resetCells(cells)
},
//连接节点type=1 产品-产品;
addEdgeMethod(source,target,type){
const sourcePorts = source.getPorts()
const targetPorts = target.getPorts()
return this.graph.createEdge({
source:{
cell:source,
port:sourcePorts[type==1?0:2].id,
},
target:{
cell:target,
port:targetPorts[1].id,
},
router: {
name: 'manhattan'
},
attrs: {
line: {
targetMarker: '',
stroke: '#A2B1C3',
strokeWidth: 2,
},
},
zIndex:0
})
},
//添加连接点
addNodePort(node){
node.addPort({ group: 'right' })
node.addPort({ group: 'left' })
node.addPort(
{
id:node.getProp("id")+"3",
group: 'bottom',
args: {
x: 10,
y: node.getBBox().height
},
}
)
},
init() {
var that = this
//定义BOM
class TreeNode extends Node {
collapsed = false
postprocess() {
this.toggleCollapse(false)
}
isCollapsed() {
return this.collapsed
}
toggleButtonVisibility(visible) {
this.attr('buttonGroup', {
display: visible ? 'block' : 'none',
})
}
toggleCollapse(collapsed) {
const target = collapsed == null ? !this.collapsed : collapsed
if (target) {
this.attr('buttonSign', {
d: 'M 1 5 9 5 M 5 1 5 9',
strokeWidth: 1.6,
})
} else {
this.attr('buttonSign', {
d: 'M 2 5 8 5',
strokeWidth: 1.8,
})
}
this.collapsed = target
}
}
TreeNode.config({
markup: [
{
tagName: 'rect',
selector: 'body',
},
{
tagName: 'g',
selector: 'buttonGroup',
children: [
{
tagName: 'rect',
selector: 'button',
attrs: {
'pointer-events': 'visiblePainted',
},
},
{
tagName: 'path',
selector: 'buttonSign',
attrs: {
fill: 'none',
'pointer-events': 'none',
},
},
],
},
{
tagName: 'text',
selector: 'label',
},
],
attrs: {
body: {
rx: 6,
ry: 6,
refWidth: '100%',
refHeight: '100%',
strokeWidth: 1,
fill: '#EFF4FF',
stroke: '#5091c6',
},
label: {
textWrap: {
ellipsis: true,
width: -10,
},
textAnchor: 'middle',
textVerticalAnchor: 'middle',
refX: '50%',
refY: '50%',
fontSize: 14,
},
buttonGroup: {
refX: '0%',
refY: '50%',
},
button: {
fill: '#5F95FF',
stroke: 'none',
x: -14,
y: -7.5,
height: 15,
width: 15,
rx: 10,
ry: 10,
cursor: 'pointer',
event: 'node:collapse',
},
buttonSign: {
refX: -11.5,
refY: -5,
stroke: '#FFFFFF',
strokeWidth: 1.6,
},
},
})
Node.registry.register('topic', TreeNode, true)
//定义模式/措施/原因/结果节点
class TreeLineNode extends Node {
collapsed = false
postprocess() {
this.toggleCollapse(false)
}
isCollapsed() {
return this.collapsed
}
toggleButtonVisibility(visible) {
this.attr('buttonGroup', {
display: visible ? 'block' : 'none',
})
}
toggleCollapse(collapsed) {
const target = collapsed == null ? !this.collapsed : collapsed
if (target) {
this.attr('buttonSign', {
d: 'M 1 5 9 5 M 5 1 5 9',
strokeWidth: 1.6,
})
} else {
this.attr('buttonSign', {
d: 'M 2 5 8 5',
strokeWidth: 1.8,
})
}
this.collapsed = target
}
}
TreeLineNode.config({
markup: [
{
tagName: 'rect',
selector: 'body',
},
{
tagName: 'g',
selector: 'buttonGroup',
children: [
{
tagName: 'rect',
selector: 'button',
attrs: {
'pointer-events': 'visiblePainted',
},
},
{
tagName: 'path',
selector: 'buttonSign',
attrs: {
fill: 'none',
'pointer-events': 'none',
},
},
],
},
{
tagName: 'text',
selector: 'label',
},
],
attrs: {
body: {
refWidth: '100%',
refHeight: '100%',
fill: '#ffffff',
strokeWidth: 0,
stroke: '#5F95FF'
},
label: {
textWrap: {
ellipsis: true,
width: -10,
},
textAnchor: 'middle',
textVerticalAnchor: 'middle',
refX: '50%',
refY: '50%',
fontSize: 12,
},
buttonGroup: {
refX: '0%',
refY: '50%',
},
button: {
fill: '#5F95FF',
stroke: 'none',
x: -14,
y: -7.5,
height: 15,
width: 15,
rx: 10,
ry: 10,
cursor: 'pointer',
event: 'node:collapse',
},
buttonSign: {
refX: -11.5,
refY: -5,
stroke: '#FFFFFF',
strokeWidth: 1.6,
},
},
})
Node.registry.register('topic-child', TreeLineNode, true)
// 边
Graph.registerEdge('mindmap-edge',{
inherit: 'edge',
attrs: {
line: {
targetMarker: '',
stroke: '#A2B1C3',
strokeWidth: 1
}
},
zIndex: 0
},true)
//定义画布
const containerRef = this.$refs.containerRef
this.graph = new Graph({
container: containerRef,
width:this.width,
height:this.height,
grid: {
visible: false
},
background: {
color: '#F2F7FA',
},
panning:false,//拖拽--禁用拖拽因为与滚动冲突
mousewheel: {//鼠标滚动
enabled: true,
modifiers: ['ctrl', 'meta'],
},
})
//滚动
this.graph.use(
new Scroller({
enabled: true,
pageVisible:true
}),
)
//渲染
const render = () => {
that.addAllNode()
}
this.graph.on('node:collapse', ({ node }) => {
var node_data = api.findNodeData(that.data,node.getProp('id'))
if(node_data.collapse==0){
node_data.collapse = 1//不展开
node_data.have_mul_son_product = 0
}else{
node_data.collapse = 0
}
that.addAllNode()
})
//节点右键事件
if(this.dfmea_info.statu==0){
this.graph.on('node:contextmenu', ({ e, node}) => {
this.onContextmenu(e,node)
})
}
//节点点击事件
this.graph.on('node:click', ({ e,node }) => {
this.onNodeClick(e,node)
})
// 渲染
render()
},
//产品下产品高度
export function lowProductTotalHeight(node,pro_hap,fun_hap){
let totalHeight = 0;
if(node.children&&node.children.length>0&&node.collapse==0){
//子产品个数
let son_pro_num = 0;
node.children.forEach(c => {
if(c.type=='topic-branch'){
son_pro_num += 1;
}
});
//产品总高度高度
let funHeight = node.height + pro_hap;
let notLastProTotalHeight = 0;//如果最后一个子级产品下面没多同级产品 或者 当前节点下面没多同级产品 或者最后一个节点不展开,则不计算最后一个节点高度
let lastProHeight = 0;//最后一个产品高度
let pro_index = 0;
let have_mul_low_product = 0;//下面有多个同级产品
node.children.forEach(c => {
if(c.type!='topic-branch'){
funHeight += lowFunTotalHeight(c,fun_hap)
}else{
lowProductTotalHeight(c,pro_hap,fun_hap)
if(c.have_mul_son_product&&c.have_mul_son_product==1){//下面有多个同级产品
have_mul_low_product = 1
}
pro_index += 1;
if(son_pro_num>pro_index){
notLastProTotalHeight += c.total_height
}else{
if(node.have_mul_son_product&&node.have_mul_son_product==1&&c.collapse==0&&(c.have_mul_son_product&&c.have_mul_son_product==1)){
notLastProTotalHeight += c.total_height
}else{
lastProHeight = c.total_height
}
}
}
});
if(have_mul_low_product==1||son_pro_num>1){//下面有多个同级产品 高度=子级产品上半高度+max(子级产品下半高度,功能总高度)
node.up_half_height = notLastProTotalHeight/2
node.low_half_height = Math.max(funHeight,notLastProTotalHeight/2+lastProHeight)
totalHeight = Math.max(funHeight,notLastProTotalHeight/2+lastProHeight)+(notLastProTotalHeight/2)
}else if(son_pro_num==1){//下面同级产品只有一个子级产品 高度 = max(自身子级功能类高度,子级产品高度)
totalHeight = Math.max(funHeight,notLastProTotalHeight+lastProHeight)
}else{//下面没有子级产品 高度 = max(自身子级功能类高度,子级产品高度)
totalHeight = funHeight
}
if(son_pro_num>1||have_mul_low_product==1){//有多个同级下级产品
node.have_mul_son_product = 1;
}
}
node.total_height = Math.max(node.height+pro_hap,totalHeight)
return node;
}
//产品下功能/特性等高度
function lowFunTotalHeight(node,fun_hap) {
let totalHeight = 0;
if(node.children&&node.children.length>0&&node.collapse==0){
totalHeight += node.height+fun_hap
node.children.forEach((c,index) => {
totalHeight += lowFunTotalHeight(c,fun_hap);
});
}
return Math.max(node.height+fun_hap, totalHeight);
}
//根据id去查找节点数据
export function findNodeData(data,node_id){
var node_data = null;
if(data.id==node_id){
node_data = data
}else{
for(var i=0;i<data.children.length;i++){
node_data = findNodeData(data.children[i],node_id)
if(node_data!=null){
break
}
}
}
return node_data;
}
//根据id去查找节点上级数据
export function findNodeParentData(data,node_id){
var node_data = null;
for(var i=0;i<data.children.length;i++){
if(data.children[i].id==node_id){
node_data = data;
break
}else{
node_data = findNodeParentData(data.children[i],node_id)
if(node_data!=null){
break
}
}
}
return node_data;
}
//对比数据差异并重新修改原数据
export function CompareDifferences(old_data,new_data){
old_data.label = new_data.label
old_data.width = new_data.width
old_data.leaf = new_data.leaf
//是否有删除数据
for(var i=0;i<old_data.children.length;i++){
let is_del = true ;
for(var j=0;j<new_data.children.length;j++){
if(old_data.children[i].id==new_data.children[j].id){
is_del = false;
CompareDifferences(old_data.children[i],new_data.children[j]);
break;
}
}
if(is_del){
old_data.children.splice(i,1)
i--
}
}
//是否有新增数据
var new_children = []
for(var i=0;i<new_data.children.length;i++){
let is_add = true ;
for(var j=0;j<old_data.children.length;j++){
if(new_data.children[i].id==old_data.children[j].id){
is_add = false;
break;
}
}
if(is_add){
new_children.push(new_data.children[i])
}
}
//新增
for(var i=0;i<new_children.length;i++){
old_data.children.push(new_children[i])
}
}