所以我讨厌我问这个问题,但是我已经到达了“不能见树木换树木”的阶段,这是最后的手段。 我试图举这个例子:http://plnkr.co/edit/P2Jcqh12cxVyToLdXun6?p=preview并放入Angular应用程序。一开始就有一些小问题。我的JS和Angular知识不是世界上最好的,并且该示例是在D3的早期版本中编写的,但对功能的一些主要部分进行了更改,即对角线投影以及查找节点和链接。 初始节点和按钮正确显示,但是我不得不将rightCliCk函数更改为:
onRightClick = (d) => {
event.stopPropagation();
var childName = "temp";
if(typeof d.children === 'undefined'){
var newChild = [{
"name": childName,
"parent": "Son of A",
}];
var newnode = d3.hierarchy(newChild);
d.children = newnode;
d.children = [];
d.children.push(newnode);
//console.log(d.children);
this.update(d);
} else {
var newChild = [{
"name": childName,
"parent": "Son of A",
}];
console.log(d.children);
d.children.push(newChild);
console.log(d.children);
this.update(d);
};
};
这是基于对该主题进行的一些研究。但是,当我返回链接的更新功能时,出现错误“无法读取未定义的属性'id'”。
//Update the links
var link = this.svg.selectAll('path.link')
.data(links, (d) => { return d.target.id; })
我认为香港专业教育学院在这里错过了一些东西,或者我没有得到D3过去用来对抗现在工作方式的方式。
完整的TS文件:
import { Component, OnInit, HostListener } from '@angular/core';
import * as d3 from 'd3';
import { AppService } from './app.service';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss']
})
export class AppComponent implements OnInit {
@HostListener('window:resize', ['$event'])
onResize(event?){
this.screenHeight = window.innerHeight;
this.screenWidth = window.innerWidth;
}
title = 'angular-d3-tree-example';
margin: any;
width: number;
height: number;
scale: number;
svg: any;
duration: number;
root: any;
tree: any;
rightTree: any;
leftTree: any;
treeData: any;
nodes: any;
links: any;
screenHeight: number;
screenWidth: number;
oldrx;
oldry;
constructor(private appservice: AppService) {
this.onResize();
}
ngOnInit(): void {
//this.treeData = this.appservice.fetchData();
this.treeData = [{
"name": "Device",
"parent": "null"
}];
this.setData();
}
setData(){
//Set the dimensions and margins of the diagram
this.margin = { top: 20, right: 120, bottom: 20, left: 120 };
this.width = this.screenWidth - this.margin.left - this.margin.right;
this.height = this.screenHeight - this.margin.top - this.margin.bottom - 20;
this.scale = 1;
this.duration = 750;
// declares a tree layout and assigns the size
this.tree = d3.cluster().size([this.height, this.width]);
//append the svg object to the body of the page
//appends a 'group' element to 'svg'
//moves the 'group' element to the top left margin
this.svg = d3.select('body')
.append('svg')
.attr('width', this.width + this.margin.right + this.margin.left)
.attr('height', this.height + this.margin.top + this.margin.bottom)
//.append("g")
//.attr('transform', 'translate(' + this.width/2 + this.height / 2 + ')');
this.makeRightTree();
}
makeRightTree = () => {
this.rightTree = d3.cluster().size([this.height, this.width]);
this.svg = d3.select('svg')
.append('g').attr('transform', 'translate(600,0)');
this.root = d3.hierarchy(this.treeData, (d) => { return d.children;});
this.oldrx = this.root.x0 = this.height/2;
this.oldry = this.root.y0 = 0;
this.update(this.root);
}
update = (source) => {
var i = 0;
//Assigns the x and y position for the nodes
this.treeData = this.tree(this.root);
//Compute the new tree layout
var nodes = this.treeData.descendants();
var links = this.treeData.descendants().slice(1);
//Normalise for fixed-depth
nodes.forEach((d) => { d.y = d.depth * 180; });
//Update Nodes
var node = this.svg.selectAll('g.node')
.data(nodes, (d) => { return d.id || (d.id = ++i); });
//Enter any new nodes at the parents previous position
var nodeEnter = node.enter().append('g')
.attr('class', (d) => {
if(d.parent == "null"){
return "node rightparent";
} else {
return "node rightchild";
}
})
.attr('transform', (d) => {
return 'translate(' + source.y0 + ',' + source.x0 + ")";
})
//.on('click', this.click);
//Node Shapes
nodeEnter.append('rect')
.attr('x', '-10')
.attr('y', '-15')
.attr('height', 30)
.attr('width', 100)
.attr('rx', 15)
.attr('ry', 15)
.style('fill', '#f1f1f1');
//Node Text
nodeEnter.append('text')
.attr('x', (d) => { return d.children || d._children ? -13 : 13; })
.attr('dy', '.35em')
.attr('text-anchor', (d) => { return d.children || d._children ? 'end' : 'start'; })
.text((d) => { return d.name; })
.style('fill-opacity', 1e-6);
//Buttons - circle then the plus +
var addRightChild = nodeEnter.append('g');
addRightChild.append('rect')
.attr('x', '90')
.attr('y', '-10')
.attr('height', 20)
.attr('width', 20)
.attr('rx', 10)
.attr('ry', 10)
.style('stroke', '#444')
.style('stroke-width', '2')
.style('fill', '#ccc');
addRightChild.append('line')
.attr('x1', 95)
.attr('y1', 1)
.attr('x2', 105)
.attr('y2', 1)
.attr('stroke', '#444')
.style('stroke-width', '2');
addRightChild.append('line')
.attr('x1', 100)
.attr('y1', -4)
.attr('x2', 100)
.attr('y2', 6)
.attr('stroke', '#444')
.style('stroke-width', '2');
//Button click
addRightChild.on('click', this.onRightClick);
//Transition nodes to their new position
var nodeUpdate = node.transition()
.duration(this.duration)
.attr('transform', (d) => {
if(d.parent == "null") {
d.y = this.oldry;
d.x = this.oldrx;
}
});
nodeUpdate.select('circle')
.attr('r', 10)
.style('fill', (d) => { return d._children ? 'lightsteelblue' : '#fff'; });
nodeUpdate.select('text')
.style('fill-opacity', 1);
//Transition exiting nodes to the parents new position
var nodeExit = node.exit().transition()
.duration(this.duration)
.attr('transform', (d) => { return 'translate(' + source.y + ',' + source.x + ')'; })
.remove();
nodeExit.select('circle')
.attr('r', 1e-6);
nodeExit.select('text')
.style('fill-opacity', 1e-6);
//Update the links
var link = this.svg.selectAll('path.link')
.data(links, (d) => { return d.target.id; })
//Enter any new links at the parents previous position
link.enter().insert('path', 'g')
.attr('class', 'link')
.style('stroke', 'black')
.attr('d', (d) => {
var o = { x: source.x0, y: source.y0 };
return this.diagonal(o,o);
})
.on('click', this.removeLink);
//Transition links to their new position
link.transition()
.duration(this.duration)
.attr('d', this.diagonal);
//transition exiting nodes to parents new position
link.exit().transition()
.duration(this.duration)
.attr('d', (d) => {
var o = { x: source.x, y: source.y };
return this.diagonal(o,o);
})
.remove();
//Stash the old positions for transition
nodes.forEach((d) => {
d.x0 = d.x;
d.y0 = d.y;
});
};
onRightClick = (d) => {
event.stopPropagation();
var childName = "temp";
if(typeof d.children === 'undefined'){
var newChild = [{
"name": childName,
"parent": "Son of A",
}];
var newnode = d3.hierarchy(newChild);
d.children = newnode;
d.children = [];
d.children.push(newnode);
//console.log(d.children);
this.update(d);
} else {
var newChild = [{
"name": childName,
"parent": "Son of A",
}];
console.log(d.children);
d.children.push(newChild);
console.log(d.children);
this.update(d);
};
};
removeLink = (d) => {
//This is the links target node which you want to remove
var target = d.target;
//make new set of children
var children = [];
//iterate through the children
target.parent.children.forEach((child) => {
if(child.id != target.id){
//add to the child list if target id is not same
//so that the node target is removed
children.push(child);
}
});
//set the target parent with new set of children sans the one which is removed
target.parent.children = children;
//redraw the parent since one of its children is removed
this.update(d.target.parent);
}
//Creates a curved (diagonal) path from parent to the child nodes
diagonal = (s, d) => {
let path = `M ${s.y} ${s.x}
C ${(s.y + d.y) / 2} ${s.x},
${(s.y + d.y) / 2} ${d.x},
${d.y} ${d.x}`;
return path;
}
}