I am still on my chart, and I need to default close the level 2 & 3 nodes, and keep the expand/collapse function on click.
Depending on the node clicked and its level, run a specific action (change color for example). My link must be a value of my data object (var pubs in my codepen) as you can see bellow (level 0 has no link, "TOOLS" in my example) :
{
"name": "TOOLS",
"children":
[
{
"name": "Localization",
"url": "http://#",
"children":
[
{"name": "FRANCE", "url": "http://france.fr"}
...
Finaly another event listener on "mouseover" to do some styling on the node (closed or open) etc...
My current code : https://codepen.io/anon/pen/BqjJJv
To add an event listener to all elements with class: Use the document. querySelectorAll() method to select the elements by class. Use the forEach() method to iterate over the collection of elements.
The method addEventListener() works by adding a function, or an object that implements EventListener , to the list of event listeners for the specified event type on the EventTarget on which it's called.
If you want to collapse all nodes except the root and the first set of children, you can run the following code:
root.children.forEach(collapse);
This recursively applies the collapse
function to all the children of the root.
There is already a click event listener on your nodes, which you can alter according to your plans. Adding listeners for mouseover and mouseout events is very simple:
var nodeEnter = node.enter().append("g")
.attr("class", "node")
.on("click", click)
.on('mouseover', mouseover) // mouseover!
.on('mouseout', mouseout) // mouseout!
You then need to add functions that will define the actions that occur. Here are a couple of sample functions:
// `d` is the data item attached to the DOM node;
// `this` is the node that triggered the event (`g.node`)
function mouseover(d) {
// select the `rect` node that is the child of the DOM node that triggered the event
d3.select(this).select('rect').style('fill', function(d){
// depending on the level of the node, give it one of these tacky colours
if (d.depth === 0) {
return 'deepskyblue'
} else if (d.depth === 1) {
return 'deeppink'
} else if (d.depth === 2) {
return 'goldenrod'
}
return 'papayawhip' // default
})
}
function mouseout(d) {
// select the rect element
d3.select(this).select('rect')
// if the node has collapsed children, turn it light blue; otherwise turn it red
.style('fill', d => d._children ? "lightsteelblue" : "red")
}
You can also add extra functionality to the click
function in a similar manner.
Here are the event listeners in action:
var pubs = {
"name": "TOOLS",
"children": [{
"name": "Localization",
"children": [{
"name": "FRANCE"
},
{
"name": "SUISSE"
},
{
"name": "USA"
},
{
"name": "UK"
}
]
},
{
"name": "Test",
"children": [{
"name": "FRANCE"
},
{
"name": "SUISSE"
},
{
"name": "USA"
},
{
"name": "UK"
}
]
},
{
"name": "Oh My God",
"children": [{
"name": "FRANCE"
},
{
"name": "SUISSE"
},
{
"name": "USA"
},
{
"name": "UK"
}
]
},
{
"name": "Another Tool",
"children": [{
"name": "FRANCE"
},
{
"name": "SUISSE"
},
{
"name": "USA"
},
{
"name": "UK"
}
]
},
{
"name": "And Again",
"children": [{
"name": "FRANCE"
},
{
"name": "SUISSE"
},
{
"name": "USA"
},
{
"name": "UK"
}
]
},
{
"name": "And Again",
"children": [{
"name": "FRANCE"
},
{
"name": "SUISSE"
},
{
"name": "USA"
},
{
"name": "UK"
}
]
},
{
"name": "Production",
"children": [{
"name": "FRANCE"
},
{
"name": "SUISSE"
},
{
"name": "USA"
},
{
"name": "UK"
}
]
},
{
"name": "Audio",
"children": [{
"name": "AUT-11"
},
{
"name": "AUT-12"
}
]
},
{
"name": "Animation",
"children": [{
"name": "AUT-11"
},
{
"name": "AUT-12"
}
]
},
{
"name": "Tags",
"children": [{
"name": "AUT-11"
},
{
"name": "AUT-12"
}
]
}
]
};
var diameter = 800;
var margin = {
top: 20,
right: 120,
bottom: 20,
left: 120
},
width = diameter,
height = diameter;
var i = 0,
duration = 350,
root;
var tree = d3.layout.tree()
.size([360, diameter / 2 - 80])
.separation(function(a, b) {
return (a.parent == b.parent ? 1 : 1) / a.depth;
});
var diagonal_extras = {
path: {
// diagonal line
direct: function(p) {
return [p.source, p.target];
}
// this is also the default path in radial trees
,
l_shape: function(p) {
return [p.source, {
x: p.target.x,
y: p.source.y
}, p.target];
}
,
l_shape_2: function(p) {
return [p.source, {
x: p.source.x,
y: p.target.y
}, p.target];
}
,
dogleg: function(p) {
return [p.source,
{
x: p.source.x,
y: (p.source.y + p.target.y) / 2
},
{
x: (p.source.x + p.target.x) / 2,
y: (p.source.y + p.target.y) / 2
},
{
x: p.target.x,
y: (p.source.y + p.target.y) / 2
},
p.target
];
}
,
dogleg_2: function(p) {
return [p.source,
{
x: (p.source.x + p.target.x) / 2,
y: p.source.y
},
{
x: (p.source.x + p.target.x) / 2,
y: (p.source.y + p.target.y) / 2
},
{
x: (p.source.x + p.target.x) / 2,
y: p.target.y
},
p.target
];
}
}
,
polar_obj_to_cart: function(pt) {
var angle = pt.x / 180 * Math.PI;
return [pt.y * Math.cos(angle), pt.y * Math.sin(angle)];
}
,
polar_coords_to_cart: function(xy) {
var angle = xy[0] / 180 * Math.PI;
return [xy[1] * Math.cos(angle), xy[1] * Math.sin(angle)];
}
}
diagonal_extras.right_angle = function() {
var projection = d3.svg.diagonal().projection(),
path_type = 'dogleg';
function diagonal(d) {
return diagonal.path_maker(diagonal_extras.path[diagonal.path_type()](d));
}
diagonal.path_maker = function(pathData) {
return "M" + pathData.map(projection).join(' ');
};
diagonal.valid_path_types = function() {
return Object.keys(diagonal_extras.path);
};
diagonal.path_type = function(x) {
if (!arguments.length) {
return path_type;
}
if (diagonal_extras.path[x]) {
path_type = x;
return diagonal;
}
throw new Error(x + ' is not a valid path type');
};
diagonal.projection = function(x) {
if (!arguments.length) {
return projection;
}
projection = x;
return diagonal;
};
diagonal.path = function(x) {
if (!arguments.length) {
return path;
}
path = x;
return diagonal;
};
diagonal.draw = function(d) {
return diagonal(d);
};
return diagonal;
}
diagonal_extras.radial = function() {
var diagonal = diagonal_extras.right_angle(),
projection = function(pt) {
return [pt.x, pt.y];
};
diagonal.path_type('direct');
diagonal.projection = function(x) {
if (!arguments.length) {
return projection;
}
projection = x;
return diagonal;
};
diagonal.path_maker = function(pathData) {
var projected = pathData.map(function(x) {
return projection(x);
}),
pl = projected.length,
points, prev_angle;
// direct link:
if (2 === pl) {
return 'M' + projected.map(function(x) {
return diagonal_extras.polar_coords_to_cart(x);
}).join(' ');
}
points = projected.map(function(obj) {
return {
angle: obj[0] / 180 * Math.PI,
radius: obj[1]
};
});
return "M" + points.map(function(pt) {
var str = '';
if (prev_angle) {
if (prev_angle === pt.angle) {
// draw a straight line
str = 'L';
} else {
// draw an arc to the new radius and angle
str = 'A' + pt.radius + ',' + pt.radius
// x axis rotation
+
" 0 "
// large arc flag
+
" 0,"
// sweep
+
(pt.angle > prev_angle ? 1 : 0) + " ";
}
}
prev_angle = pt.angle;
return str + pt.radius * Math.cos(pt.angle) + "," + pt.radius * Math.sin(pt.angle);
}).join(' ');
};
return diagonal;
}
var diagonal = diagonal_extras.radial()
.path_type('dogleg')
.projection(function(d) {
return [d.x - 90, d.y];
});
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + diameter / 2 + "," + diameter / 2 + ")");
var rect = {
l: 95,
w: 20
}
root = pubs;
root.x0 = height / 2;
root.y0 = 0;
root.children.forEach(collapse); // start with all children collapsed
update(root);
d3.select(self.frameElement).style("height", "800px");
function update(source) {
// Compute the new tree layout.
var nodes = tree.nodes(root),
links = tree.links(nodes),
offset = nodes[0].x;
// Normalize for fixed-depth.
nodes.forEach(function(d) {
d.y = d.depth * 150;
});
// Normalise angles so that the root is horizontal
if (nodes[0].x > 180) {
nodes[0].x = nodes[0].x - 90
} else {
nodes[0].x = nodes[0].x + 90
}
// Update the nodes…
var node = svg.selectAll("g.node")
.data(nodes, function(d) {
return d.id || (d.id = ++i);
});
// Enter any new nodes at the parent's previous position.
var nodeEnter = node.enter().append("g")
.attr("class", "node")
//.attr("transform", function(d) { return "rotate(" + (d.x - 90) + ")translate(" + d.y + ")"; })
.on("click", click)
.on('mouseover', mouseover)
.on('mouseout', mouseout)
nodeEnter.append("rect")
.attr("width", rect.l)
.attr("height", rect.w)
.attr("x", -rect.l / 2)
.attr("y", -rect.w / 2)
.style("fill", function(d) {
return d._children ? "lightsteelblue" : "red";
});
nodeEnter.append("text")
.attr("x", 0)
.attr("dy", ".35em")
.attr("text-anchor", "middle")
//.attr("transform", function(d) { return d.x < 180 ? "translate(0)" : "rotate(180)translate(-" + (d.name.length * 8.5) + ")"; })
.text(function(d) {
return d.name;
})
.style("fill-opacity", 1e-6);
// Transition nodes to their new position.
var nodeUpdate = node.transition()
.duration(duration)
.attr("transform", function(d) {
return "rotate(" + (d.x - 90) + ")translate(" + d.y + ")";
})
// nodeUpdate.select("circle")
// .attr("r", 4.5)
// .style("fill", function(d) { return d._children ? "lightsteelblue" : "#fff"; });
nodeUpdate.select("text")
.style("fill-opacity", 1)
.attr("transform", function(d) {
return d.x < 180 ? "translate(0)" : "rotate(180)";
});
// TODO: appropriate transform
var nodeExit = node.exit().transition()
.duration(duration)
//.attr("transform", function(d) { return "diagonal(" + 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 = svg.selectAll("path.link")
.data(links, function(d) {
return d.target.id;
});
// Enter any new links at the parent's previous position.
link.enter().insert("path", "g")
.attr("class", "link")
.attr("d", function(d) {
var o = {
x: source.x0,
y: source.y0
};
return diagonal({
source: o,
target: o
});
});
// Transition links to their new position.
link.transition()
.duration(duration)
.attr("d", function(d) {
return diagonal({
source: {
x: d.source.x,
y: d.source.y + rect.l / 2
},
target: {
x: d.target.x,
y: d.target.y - rect.l / 2
}
})
});
// Transition exiting nodes to the parent's new position.
link.exit().transition()
.duration(duration)
.attr("d", function(d) {
var o = {
x: source.x,
y: source.y
};
return diagonal({
source: o,
target: o
});
})
.remove();
// Stash the old positions for transition.
nodes.forEach(function(d) {
d.x0 = d.x;
d.y0 = d.y;
});
}
function mouseover(d) {
d3.select(this).select('rect').style('fill', function(d){
if (d.depth === 0) {
return 'deepskyblue'
} else if (d.depth === 1) {
return 'deeppink'
} else if (d.depth === 2) {
return 'goldenrod'
}
return 'papayawhip'
})
}
function mouseout(d) {
d3.select(this).select('rect').style('fill', d => d._children ? "lightsteelblue" : "red")
}
// Toggle children on click.
function click(d) {
if (d.children) {
d._children = d.children;
d.children = null;
} else {
d.children = d._children;
d._children = null;
}
update(d);
}
// Collapse nodes
function collapse(d) {
if (d.children) {
d._children = d.children;
d._children.forEach(collapse);
d.children = null;
}
}
.node {
cursor: pointer;
}
.node text {
font: 10px sans-serif;
}
.link {
fill: none;
stroke: #ccc;
stroke-width: 1.5px;
}
<script type="text/javascript" src="//d3js.org/d3.v3.min.js"></script>
If what you want is for the levels 2 and 3 to be closed by default you could just rename every children
property to _children
in the pubs
object. Like you already do in your click handler, only start out with underscored children and that's it. For your other questions, I have no answer yet.
Like this:
{
"name": "TOOLS",
"_children":
[
{
"name": "Localization",
"url": "http://#",
"_children":
[
{"name": "FRANCE", "url": "http://france.fr"}
...
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With