我正在尝试做与此问题类似的事情d3.js how to dynamically add nodes to a tree。但是,我发现很难在d3.js的v4中使用任何类型的解决方案。
我正在从一些预加载的json构建树,然后我希望能够在用户单击节点时将子节点添加到树中。添加子节点的过程将涉及对REST服务的调用,该服务将返回一些json。我的大多数代码都基于http://bl.ocks.org/mbostock/4339083,它是一个可扩展/可折叠的整洁树,使用v3和https://bl.ocks.org/mbostock/9d0899acb5d3b8d839d9d613a9e1fe04,这是一个使用v4的不可扩展/可折叠的整洁树。
我的代码在https://jsfiddle.net/rkian/3e73Lz6d/。我可以毫无问题地绘制树,但我确实在弄清楚如何向树中添加节点时遇到了麻烦。我怀疑这个:
import React from 'react'
import {
StyleSheet,
Text,
TextInput,
View,
TouchableHighlight,
} from 'react-native'
export default class TabBar extends React.Component {
state = {
selectedTab: 'list'
}
render() {
return (
<TabBarIOS selectedTab={this.state.selectedTab}
unselectedTintColor="#ffffff"
tintColor="#ffe429"
barTintColor="#294163">
<TabBarIOS.Item
title="My List"
systemIcon="bookmarks"
selected={this.state.selectedTab==='list'}
onPress={() => {
this.setState({
selectedTab: 'list',
});
}}
>
<CreateUser />
</TabBarIOS.Item>
</TabBarIOS>
);
}
}
var styles = StyleSheet.create({
tabContent: {
flex: 1,
alignItems: 'center',
},
tabText: {
color: 'darkslategrey',
margin: 50,
},
});
export default class CreateUser extends React.Component{
render(){
return (
<View style={styles.container}>
<TouchableHighlight style={styles.button}>
<Text style={styles.buttonText}>LOG IN</Text>
</TouchableHighlight>
</View>
)
}
}
var styles = StyleSheet.create({
container: {
flex: 1,
flexDirection: "column",
justifyContent: "flex-end",
alignItems: 'center',
},
button: {
backgroundColor: "#ffe429",
borderRadius: 3,
height: 60,
width: 200,
margin: 7,
//flex: 1,
alignItems: "center",
justifyContent: "center",
},
buttonText: {
color: "#294163",
}
})
是关键,但我只是无法弄清楚如何实现它。
答案 0 :(得分:0)
有一些奇怪的谱系变量(平面数组要变成层次结构),我试图从中构建我的树,当我把它变成树时,它改变了它内的对象。我通过制作血统的深层副本来解决这个问题。我的所有代码都在这里,我希望它可以帮到某人:
var lineage = $.parseJSON('[{"id":739,"name":"Life","parent":null,"rank":9,"child_count":1},{"id":1,"name":"Eukaryota","parent":739,"rank":1,"child_count":3},{"id":43,"name":"Animalia","parent":1,"rank":2,"child_count":9},{"id":2,"name":"Archaeplastida","parent":1,"rank":2,"child_count":1},{"id":740,"name":"Plantae","parent":1,"rank":2,"child_count":0},{"id":417,"name":"Annelida","parent":43,"rank":3,"child_count":1},{"id":336,"name":"Arthropoda","parent":43,"rank":3,"child_count":6},{"id":228,"name":"Chordata","parent":43,"rank":3,"child_count":3},{"id":222,"name":"Cnidaria","parent":43,"rank":3,"child_count":4},{"id":328,"name":"Mollusca","parent":43,"rank":3,"child_count":1},{"id":548,"name":"Myzozoa","parent":43,"rank":3,"child_count":1},{"id":604,"name":"Nematoda","parent":43,"rank":3,"child_count":1},{"id":467,"name":"Platyhelminthes","parent":43,"rank":3,"child_count":2},{"id":275,"name":"Porifera","parent":43,"rank":3,"child_count":2},{"id":418,"name":"Polychaeta","parent":417,"rank":4,"child_count":3},{"id":419,"name":"Spionida","parent":418,"rank":5,"child_count":1},{"id":452,"name":"Sabellida","parent":418,"rank":5,"child_count":1},{"id":448,"name":"Terebellida","parent":418,"rank":5,"child_count":1},{"id":420,"name":"Spionidae","parent":419,"rank":6,"child_count":2},{"id":699,"name":"Boccardia","parent":420,"rank":7,"child_count":1},{"id":421,"name":"Polydora","parent":420,"rank":7,"child_count":1},{"id":700,"name":"Boccardia proboscidea","parent":699,"rank":8,"child_count":0}]');
$(document).ready(function() {
// WTF js.
var lineage_flat;
// Color manipulation functions and settings for the tree
function shadeColor2(color, percent) {
var f=parseInt(color.slice(1),16),t=percent<0?0:255,p=percent<0?percent*-1:percent,R=f>>16,G=f>>8&0x00FF,B=f&0x0000FF;
return "#"+(0x1000000+(Math.round((t-R)*p)+R)*0x10000+(Math.round((t-G)*p)+G)*0x100+(Math.round((t-B)*p)+B)).toString(16).slice(1);
}
function blendColors(c0, c1, p) {
var f=parseInt(c0.slice(1),16),t=parseInt(c1.slice(1),16),R1=f>>16,G1=f>>8&0x00FF,B1=f&0x0000FF,R2=t>>16,G2=t>>8&0x00FF,B2=t&0x0000FF;
return "#"+(0x1000000+(Math.round((R2-R1)*p)+R1)*0x10000+(Math.round((G2-G1)*p)+G1)*0x100+(Math.round((B2-B1)*p)+B1)).toString(16).slice(1);
}
// Color variables
var main_color = '#FFFFFF';
var secondary_color = '#FFFFFF';
var line_color = '#FFF793';
var max_rank = lineage[lineage.length - 1]['rank'];
// Used to assign colors to ranks
function get_rank_colour(d) {
rank = d.data.data.rank;
if(rank == 9) {
return main_color;
}
else {
return blendColors(main_color, secondary_color, (d.data.rank / max_rank))
}
}
// Draws a curve between two points
function connector(d) {
return 'M' + d.x + ',' + (d.y - 18) +
"C" + (d.x + d.parent.x) / 2 + "," + (d.y - 25) +
" " + (d.x + d.parent.x) / 2 + "," + (d.parent.y + 25) +
" " + d.parent.x + "," + (d.parent.y + 17);
};
// Transition vars
var duration = 500;
// Get the width of the container element for the tree
width = $('#svgcontainer').width();
// Calculate the height required based on the number of node levels on the ancestry tree
height = lineage[lineage.length - 1]['rank']* 150;
// Set the svg element's height
$('#lifetree').attr('height', height + 'px');
// Select the svg element in the DOM
var svg = d3.select("svg")
// Insert a group container and move it 40 px to the right (to pad the tree contents in the svg container)
var g = svg.append("g").attr("transform", "translate(40,40)");
// Create and return a d3 tree object of the correct width and height, run the root hierarchy element through it
var tree = d3.tree().size([width-200, height - 160]);
var root = getTreeData(lineage);
updateTree(root);
// Get the data for the tree
function getTreeData(json) {
// Save the flat lineage, we have to do this weird parse thing to make a deep copy
lineage_flat = JSON.parse(JSON.stringify(json));
// This seems to unflatten arrays of objects with parentIds and parents. Wish I'd known about it sooner.
var dataTree = d3.stratify()
.id(function(d){ return d.id; })
.parentId(function(d){ return d.parent; })
(JSON.parse(JSON.stringify(json)));
// D3 requires a hierarchy object which then gets made into a tree
var root = d3.hierarchy(dataTree);
tree(root);
// Normalize for fixed-depth, also we do some fancy transitions so save a copy of original xys
root.each(function(d) { d.y = d.depth * 100; d.x0 = d.x; d.y0 = d.y; });
return root;
}
function drawElements(node) {
// Add circles above each node
node.append("circle")
.attr("r", 2)
.attr("transform", function(d) { return "translate(0,-18)"; })
.attr("class", "upper-circle")
.style("stroke", get_rank_colour)
.style("fill", get_rank_colour);
// Add the circles below each node
node.append("circle")
.attr("r", 4)
.attr("transform", function(d) { return "translate(0,16)"; })
.attr("class", "lower-circle")
.style("stroke", "#000000")
.style("fill", function(d) {
return d.data.data.child_count > 0 ? "#FFFFFF" : "#000000";
});
// Add text
node.append("text")
.attr("dy", 3)
.style("fill", '#FFFFFF')
.style("text-anchor", "middle")
.text(function(d) {
return d.data.data.name;
if(d.data.rank == max_rank || d.data.name == "Life") {
return d.data.name;
}
else if(d.children) {
return d.data.name + ' (' + d.children.length + ")";
}
else {
return d.data.name + ' (' + d.data.count + ")";
}
})
.each(function(d) {
d.textwidth = this.getBBox().width;
d.textheight = this.getBBox().height;
});
// Add clickable background rectangle so it looks nicer
node.insert("rect",":first-child")
.style("fill", '#000000')
.style("fill-opacity", function(d) {
if(d.children || d.data.data.rank == max_rank) { return 0.5; }
else { return 0.2; }
}
)
.attr('height', function(d) { return d.textheight + 10; })
.attr('width', function(d) { return d.textwidth + 10; })
.attr("transform", function(d) {
if(d.data.data.rank == 9) {
return "translate(-" + ((d.textwidth + 10) / 2) + ",-" + ((d.textheight + 30) / 2) + ")";
}
return "translate(-" + ((d.textwidth + 10) / 2) + ",-" + ((d.textheight + 15) / 2) + ")";
})
.attr('rx', 10)
.attr('ry', 10);
}
function updateTree(source, shallowestDepth = 0) {
/*
* Nodes
*/
// Data join with source data, keeping ids so it knows about the same nodes
var node = g.selectAll(".node")
.data(source.descendants() , function(d) { return d.data.id; });
// Data enter, this starts doing things to all the new nodes
var nodeEnter = node.enter()
.append("g")
.attr("class", function(d) { return "rank-" + d.data.data.rank + " node" + (d.children ? " node--internal" : " node--leaf"); })
.attr("transform", function(d) {
if(d.parent != null) {
return "translate(" + d.parent.x + "," + d.parent.y + ")";
}
return "translate(" + d.x + "," + d.y + ")";
})
.on("click", click);
// Add text + bg + circles to the nodes
drawElements(nodeEnter);
// Add pretty hover class for each taxon node
$('g').hover(function() {
$(this).children('rect').addClass('recthover');
}, function() {
$(this).children('rect').removeClass('recthover');
});
// Transition nodes to their new position.
var nodeMerge = node.merge(nodeEnter).transition()
.duration(duration)
.attr('transform', function (d) {
return 'translate(' + d.x + ',' + d.y + ')';
});
nodeMerge.select('rect', ':first-child').style("fill-opacity", function(d) {
if(d.children || d.data.data.rank == max_rank) { return 0.5; }
else { return 0.2; }
});
// Get the old elements for removal
var oldNode = node.exit();
// Find the shallowest depth in the old element, that's the parent
oldNode.each(function(d) {
var shallowestParent = d;
do { shallowestParent = shallowestParent.parent; }
while(shallowestParent.depth > shallowestDepth);
d.shallowestParentX = shallowestParent.x;
d.shallowestParentY = shallowestParent.y;
});
// Transition the old nodes out
var transitionedNodes = oldNode.transition()
.duration(duration)
.attr("transform", function(d) {
return "translate(" + d.shallowestParentX + "," + d.shallowestParentY + ")";
});
oldNode.selectAll('rect').transition()
.style("fill-opacity", 0)
.duration(duration/2)
oldNode.selectAll('text').transition()
.style("fill-opacity", 0)
.duration(duration/2)
oldNode.selectAll('circle').transition()
.style("fill-opacity", 0)
.duration(duration/3)
transitionedNodes.remove();
/*
* Links
*/
var link = g.selectAll(".link")
.data(source.descendants().slice(1).reverse(), function(d) { return d.data.id; })
// Draw the links between nodes
var linkEnter = link.enter()
.insert("path",":first-child")
.attr("class", "link")
.style("stroke", function(d) {
if(d.children) {
return line_color;
}
return blendColors(main_color, secondary_color, (d.data.data.rank / max_rank))
})
.attr("d", function (d) {
var o = {x: d.parent.x0, y: d.parent.y0, parent: {x: d.parent.x0, y: d.parent.y0}};
return connector(o);
});
// Transition links to their new position.
var linkMerge = link.merge(linkEnter).transition()
.duration(0)
.attr('d', connector);
// Style the links
linkMerge.style("stroke", function(d) {
if(d.children) {
return line_color;
}
return blendColors(main_color, secondary_color, (d.data.data.rank / max_rank))
})
// Transition the old links out
var oldLink = link.exit();
oldLink.transition()
.duration(duration/2)
.attr("d", function(d) {
var o = {x: d.x, y: d.y, parent: {x: d.x, y: d.y}};
return connector(o);
})
.remove();
}
// Toggle children on click.
function click(d) {
// If the node does not have any pre-loaded children
if (!d.children && !d._children) {
var jsonPath = '/taxa/api/children/' + d.data.id;
// Get the JSON lineage for it
d3.json(jsonPath, function(error, json) {
// Get the children
children = json['children'];
// Make a new lineage array, can't use the one previously stored because
// of javascript variables mutability being weird
new_lineage = [];
lineage_flat.forEach(function(node, i) {
recalcIndex = lineage.indexOf(node);
// The node.rank 9 is in there because Life for some reason has rank 9
// Basically we want to exclude all nodes of a lower rank than the one clicked
if(node.rank > d.data.data.rank && node.rank != 9) {
// console.log(node); - we don't want these nodes
}
else { new_lineage.push(node); }
});
// Append the children to the new lineage
children.forEach(function(child) {
new_lineage.push(child);
});
// Javascript is weird. We need a deep copy of new_lineage
temp = JSON.parse(JSON.stringify(new_lineage));
new_lineage = JSON.parse(JSON.stringify(temp));
// Turn it into a tree and update our svg
root = getTreeData(new_lineage);
updateTree(root, d.depth);
});
}
}
});
.link {
fill: none;
stroke: orange;
stroke-width: 1.5px;
/* Transition. */
-o-transition:.5s;
-ms-transition:.5s;
-moz-transition:.5s;
-webkit-transition:.5s;
/* ...and now for the proper property */
transition:.5s;
}
.link:hover {
fill: none;
stroke: orange;
stroke-width: 3px;
}
.link.warning{
stroke: orange;
}
.lower-circle, .upper-circle {
z-index: 1;
stroke-width: 2px;
}
path.link {
stroke-width: 3px;
}
.rank-9 .upper-circle {
display: none;
}
.rank-9 text {
font-size: 3em;
font-weight: bold;
}
.rank-1 {
font-size: 1.5em;
font-weight: bold;
}
.rank-2 {
font-size: 1em;
font-weight: bold;
margin-right: 20px;
margin-left: 20px;
}
svg text {
}
.rank-3 {
font-size: 1em;
}
#triangles {
/*position: relative;
top: -20px;
margin-top: -50px;
padding-top: 80px;
-webkit-transform: rotate(180deg);
-moz-transform: rotate(180deg);
-ms-transform: rotate(180deg);
-o-transform: rotate(180deg);
transform: rotate(180deg);*/
}
#svgcontainer {
position: relative;
padding-top: 80px;
/*-webkit-transform: rotate(180deg);
-moz-transform: rotate(180deg);
-ms-transform: rotate(180deg);
-o-transform: rotate(180deg);
transform: rotate(180deg);*/
}
#svgparent-disablethis {
background: transparent url('static/img/carousel/1.jpg' 0 0 no-repeat );
background: -moz-linear-gradient(top, rgba(255,255,255,0) 100%, rgba(130,91,0,1) 0%); /* FF3.6+ */
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,rgba(255,255,255,0)), color-stop(100%,rgba(130,91,0,1))); /* Chrome,Safari4+ */
background: -webkit-linear-gradient(top, rgba(255,255,255,0) 0%,rgba(130,91,0,1) 100%); /* Chrome10+,Safari5.1+ */
background: -o-linear-gradient(top, rgba(255,255,255,0) 0%,rgba(130,91,0,1) 100%); /* Opera 11.10+ */
background: -ms-linear-gradient(top, rgba(255,255,255,0) 0%,rgba(130,91,0,1) 100%); /* IE10+ */
background: linear-gradient(to bottom, rgba(255,255,255,0) 0%,rgba(130,91,0,1) 100%); /* W3C */
filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#00ffffff', endColorstr='#825b00',GradientType=0 ); /* IE6-9 */
}
svg { width: 100%; height: auto; }
rect {
/* Transition. */
-o-transition:.2s;
-ms-transition:.2s;
-moz-transition:.2s;
-webkit-transition:.2s;
/* ...and now for the proper property */
transition:.2s;
}
text {
cursor: pointer;
}
.lower-circle {
cursor: pointer;
}
.lower-circle:hover {
fill: #FFF !important;
}
.recthover {
fill: #FFEC1C !important;
}
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="svgparent">
<div id="svgcontainer">
<svg id='lifetree'></svg>
</div>
</div>