I am trying to make my tree graph respond based on the value that is selected in the input. I get it to work for the first selection but nothing happens when I put the selection back to "All" again.
I get the following error in the console:
I guess it is because when going to the selection some of the children are removed and thus not found anymore when putting it on the full selection but I am not sure how I could fix this.
See codepen
The code for the graph:
//Load data
const data = [
{
"name": "Top Level",
"parent": "null",
"value": 10,
"type": "black",
"level": "red",
"children": [
{
"name": "Level 2A",
"parent": "Top Level",
"value": 15,
"type": "grey",
"level": "red",
"children": [
{
"name": "Son of A",
"parent": "Level 2: A",
"value": 5,
"type": "steelblue",
"level": "orange"
},
{
"name": "Daughter of A",
"parent": "Level 2: A",
"value": 8,
"type": "steelblue",
"level": "red"
}
]
},
{
"name": "Level 2B",
"parent": "Top Level",
"value": 10,
"type": "grey",
"level": "green"
}
]
}
];
var margin = {top: 10, right: 10, bottom: 10, left: 50},
width = 500 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var tree = d3.cluster()
.size([height, width])
.size([height-margin.top-margin.bottom,width-margin.left-margin.right]);
//Create root
var root = d3.hierarchy(data[0], function(d) { return d.children;});
//Attach canvas element
var svg = d3.select("body")
.append("svg")
.attr("width", 1000)
.attr("height", 1000);
var g = svg
.append("g")
.attr('transform','translate('+ margin.left +','+ margin.right +')');
var link = g.selectAll(".link")
.data(tree(root).links())
.enter()
.append("path")
.attr("class", "link")
.attr("d", d3.linkHorizontal()
.x(function(d) {return d.y;})
.y(function(d) {return d.x;}));
var node = g.selectAll(".node")
.data(root.descendants())
.enter()
.append("g")
.attr("class", "link")
.attr("class", d =>
{ return "node" + (d.children ? " node--internal" : " node--leaf")})
.attr("transform", d =>
{ return "translate(" + d.y + ","+ d.x + ")" ; })
var text = g.selectAll("text")
.data(root.descendants())
.enter().append("text")
.text(d => d.data.name)
.attr("class", "label glow")
.attr('text-anchor', "start")
.attr("x", d => d.y)
.attr("y", d => d.x);
circle = node.append("circle")
.attr("r", 2.5)
//Select
var selections = ["All", "Level 2A", "Level 2B"]
d3.select("#selectButton")
.selectAll('myOptions')
.data(selections)
.enter()
.append('option')
.text(function (d) { return d; }) //
.attr("value", function (d) { return d; })
// SelectInput
d3.select("#selectButton").on("change", function(d){
selectedGroup = this.value
updateChart(selectedGroup, data)
})
//Update chart function
function updateChart(selection, data){
//Filter data
if(selection != "All"){
var selData = data[0].children.filter(d => d.name == selection);
} else {
var selData = data
}
console.log(selData)
var root = d3.hierarchy(selData[0], function(d) { return d.children;});
console.log(root)
//update chart
link
.data(tree(root).links())
.join()
.transition()
.duration(1000)
.attr("class", "link")
.attr("d", d3.linkHorizontal()
.x(function(d) {return d.y;})
.y(function(d) {return d.x;})
);
text
.data(root.descendants())
.join()
.transition()
.duration(1000)
.attr('text-anchor', "start")
.attr("x", d => d.y)
.attr("y", d => d.x);
node
.data(root.descendants())
.join()
.transition()
.duration(1000)
.attr("class", "link")
.attr("class", d =>
{ return "node" + (d.children ? " node--internal" : " node--leaf")})
.attr("transform", d =>
{ return "translate(" + d.y + ","+ d.x + ")" ; })
circle
.data(root.descendants())
.join()
.transition()
.duration(1000)
.attr("r", 2.5)
};
To answer my own question. I had to set what exactly needs to happen upon enter(), update() and exit().
I changed from just joining:
link
.data(tree(root).links())
.join()
.transition()
.duration(1000)
.attr("class", "link")
.attr("d", d3.linkHorizontal()
.x(function(d) {return d.y;})
.y(function(d) {return d.x;})
);
To:
var link = g
.selectAll(".link")
.data(tree(root).links())
.join(
(enter) =>
enter
.append("path")
.attr("class", "link")
.attr(
"d",
d3
.linkHorizontal()
.x(function (d) {
return d.y;
})
.y(function (d) {
return d.x;
})
),
(update) =>
update
.transition()
.duration(1000)
.attr("class", "link")
.attr(
"d",
d3
.linkHorizontal()
.x(function (d) {
return d.y;
})
.y(function (d) {
return d.x;
})
),
(exit) => exit.remove()
);
For the full working tree see:
https://codepen.io/nvelden/pen/jOxgoVj
Related
I am working on d3.js. My code is as follows:
https://jsfiddle.net/g543kxoh/
code in js fiddle is as follows
const treeData = {
"id": 1,
"name": "Root",
"checked": false,
"color": "white",
"children": [
{
"id": 2,
"name": "Leaf A",
"checked": false,
"color": "red",
"children": [
{
"id": 3,
"name": "A - 1",
"checked": false,
"color": "brown",
},
{
"id": 4,
"name": "A - 2",
"checked": false,
"color": "orange",
},
{
"id": 5,
"name": "A - 3",
"checked": false,
"color": "yellow",
},
]
},
{
"id": 6,
"name": "Leaf B",
"checked": false,
"color": "green",
"children": [
{
"id": 7,
"name": "B - 1",
"checked": false,
"color": "#00ff40",
},
{
"id": 8,
"name": "B - 2",
"checked": false,
"color": "#00ff80",
}
]
}
]
};
const margin = {
top: 20,
right: 120,
bottom: 20,
left: 120
};
const width = 600 - margin.right - margin.left;
const height = 400 - margin.top - margin.bottom;
var i = 0,duration = 750;
const tree = d3.layout.tree()
.size([height, width]);
const diagonal = d3.svg.diagonal()
.projection(function(d) {
return [d.y, d.x];
});
const svg = d3.select("body").append("svg")
.attr("width", width + margin.right + margin.left)
.attr("height", height + margin.top + margin.bottom);
const container = svg.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
const root = treeData;
root.x0 = height / 2;
root.y0 = 0;
// Collapse after the second level
root.children.forEach(collapse);
update(root);
// Collapse the node and all it's children
function collapse(d) {
if (d.children) {
d._children = d.children
d._children.forEach(collapse)
d.children = null
}
}
function update(source) {
// Compute the new tree layout.
const nodes = tree.nodes(root).reverse();
const links = tree.links(nodes);
// Normalize for fixed-depth.
nodes.forEach(function(d) {
d.y = d.depth * 200;
});
// Update the nodes…
const node = container.selectAll("g.node")
.data(nodes, d => d.id);
// Enter any new nodes at the parent's previous position.
var nodeEnter = node.enter()
.append("g")
.attr("class", "node")
.attr("transform", d => `translate(${source.y0},${source.x0})`)
.on("click", onClickNode);
nodeEnter.append("circle")
.attr("r", 10)
.style("fill", d => d.color);
nodeEnter.append("text")
.attr("x", 20)
.attr("dy", 4)
.attr("text-anchor", "start")
.text(d => d.name);
nodeEnter.append('foreignObject')
.attr('width', '20')
.attr('height', '20')
.attr("x", -30)
.attr("y", -8)
.append('xhtml:input')
.attr('type', 'checkbox')
.attr("id", d => `checkbox-${d.id}`)
.on("click", onClickCheckbox)
// Transition nodes to their new position.
var nodeUpdate = node.transition()
.duration(duration)
.attr("transform", function(d) {
return "translate(" + d.y + "," + d.x + ")";
});
// ???
nodeUpdate.select("circle")
.style("stroke", 'black');
nodeUpdate.each(function(d) {
const cb = d3.select(this).select('[type="checkbox"]').node();
cb.checked = d.checked;
cb.disabled = isParentChecked(d);
});
nodeUpdate.select("text")
.style("fill-opacity", 1);
// Transition exiting nodes to the parent's new position.
const nodeExit = node.exit().transition()
.duration(duration)
.attr("transform", function(d) {
return "translate(" + source.y + "," + source.x + ")";
})
.remove();
nodeExit.select("circle")
.attr("r", 0);
nodeExit.select("text")
.style("fill-opacity", 0);
// Update the links…
var link = container.selectAll("path.link")
.data(links, d => d.target.id);
// Enter any new links at the parent's previous position.
link.enter().insert("path", "g")
.attr("class", "link")
.attr("stroke-width", 1)
.attr("d", function(d) {
var o = {
x: source.x0,
y: source.y0
};
return diagonal({
source: o,
target: o
});
})
.attr("opacity", "0.3")
.style("stroke", 'black');
// Transition links to their new position.
link.transition()
.duration(duration)
.attr("d", diagonal);
// 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 findParent(datum) {
if (datum.depth < 2) {
return datum.name
} else {
return findParent(datum.parent)
}
}
function findParentLinks(datum) {
if (datum.target.depth < 2) {
return datum.target.name
} else {
return findParent(datum.target.parent)
}
}
const checkNode = (d, checked, byParent) => {
if (d.id === 2)
console.log('CHECK TO: ', checked);
d.checked = checked;
const children = d.children || d._children;
if (children)
children.forEach(child => checkNode(child, checked, true));
if (!byParent && checked && d.parent) {
console.log('UNCHECK SIBLINGS');
const siblings = d.parent.children || d.parent._children;
siblings.forEach(sibling => {
if (sibling.id !== d.id) {
console.log('UNCHECK: ', sibling)
checkNode(sibling, false, true);
}
});
}
}
function isParentChecked (d) {
if (!d.parent) {
return false;
}
if (d.parent.checked) {
return true;
}
return isParentChecked(d.parent);
}
function onClickCheckbox(d) {
d3.event.stopPropagation();
checkNode(d, d3.event.target.checked, false);
console.log('ROOT: ', root);
update(root);
}
// Toggle children on click.
function onClickNode(d) {
if (d.children) {
d._children = d.children;
d.children = null;
}
else {
d.children = d._children;
d._children = null;
}
update(d);
}
.node {
cursor: pointer;
}
.node circle {
fill: #fff;
stroke: #C0C0C0;
stroke-width: 1.5px;
}
.node text {
font: 10px sans-serif;
}
.link {
fill: none;
stroke: #C0C0C0;
stroke-width: 1.5px;
}
I want to check arrow and border color on the basis of node checked or not.
If node is checked then I want to have a line like as shown in following attached image. Not sure what we call it in d3.js(either diagonal or link).
.
Currently I have grey color normal line as shown in fiddle. But once node is checked I want to have all the checked nodes connect with lines as shown in attached image. Also border color of node should
If node is unchecked then I want to have diagonal/link connecting nodes as shown in attached image
.
In this case also border color of node should be same as color of unchecked diagonal/link
How can I do that?
Depending on criterion to mark the link, do:
// Transition links to their new position.
link
.style('stroke', d => d.target.checked ? 'blue' : 'gray')
.transition()
.duration(duration)
.attr("d", diagonal);
if you want to color the links of checked nodes, or:
link
.style('stroke', d => d.source.checked ? 'blue' : 'gray')
.transition...
if you want color the links of checked parents.
See it in a fiddle.
UPDATE: Add colored points:
nodeEnter.append('circle')
.classed('child-link-point', true)
.attr('cx', 10)
.attr('r', 4);
nodeEnter.append('circle')
.classed('parent-link-point', true)
.attr('cx', -10)
.attr('r', 4);
...
nodeUpdate.select('.child-link-point')
.style('visibility', d => d.children ? 'visible' : 'hidden')
.style('fill', d => d.checked ? 'blue' : 'gray')
.style('stroke', d => d.checked ? 'blue' : 'gray');
nodeUpdate.select('.parent-link-point')
.style('visibility', d => d.parent ? 'visible' : 'hidden')
.style('fill', d => (d.parent || {}).checked ? 'blue' : 'gray')
.style('stroke', d => (d.parent || {}).checked ? 'blue' : 'gray');
I am trying to add new nodes in force directed, but when I call the function QuickSearch(value) then it duplicates the graph.
Can anybody advise me how to add a new node only.
// Graph variables
var w = window.innerWidth;
var h = window.innerHeight;
var svg = d3.select("#svgData"),
scheme = ['#e41a1c','#377eb8','#4daf4a','#984ea3','#ff7f00','#ffff33','#a65628','#f781bf','#999999'],
width = +svg.attr(w),
height = +svg.attr(h),
color = d3.scaleOrdinal(d3.schemeCategory20);
//color = d3.scaleOrdinal(scheme);
var info = {
"nodes": [
{"id": "1", "name": "1", "group": 1},
{"id": "2", "name": "2", "group": 1},
{"id": "3", "name": "3", "group": 1},
{"id": "4", "name": "4", "group": 1},
{"id": "5", "name": "5", "group": 1}
],
"links": [
{"source": "1", "target": "2", "value": 1},
{"source": "1", "target": "3", "value": 1},
{"source": "1", "target": "4", "value": 1},
{"source": "1", "target": "5", "value": 1}
]
}
var marker = d3.select("#svgData").append('defs')
.append('marker')
.attr("id", "Triangle")
.attr('viewBox', '-0 -5 10 10')
.attr("refX", 25)
.attr("refY", 0)
.attr("markerUnits", 'userSpaceOnUse')
.attr("orient", 'auto')
.attr("markerWidth", 13)
.attr("markerHeight", 13)
.attr('xoverflow', 'visible')
.append('path')
.attr("d", 'M 0,-5 L 10 ,0 L 0,5');
function QuickSearch(value) {
var new_node = {};
//console.log(info.nodes);
new_node = {"id": value, "name": value, "group": 1};
info.nodes.findIndex(x => x.id == new_node.id) == -1 ? info.nodes.push(new_node) : console.log("object already exists")
createGraph(info);
};
function createGraph(graph) {
// Zoom
var zoom = d3.zoom()
.scaleExtent([0, 10])
.on("zoom", zoomed);
d3.select("#svgData").call(zoom);
function zoomed() {
const currentTransform = d3.event.transform;
container.attr("transform", currentTransform);
}
// Simulation
var simulation = d3.forceSimulation()
.force("link", d3.forceLink().id(function(d) { return d.id; }).distance(200))
.force("charge", d3.forceManyBody().strength(10).distanceMax(1000))
.force("center", d3.forceCenter(w / 2, h / 2))
.force('collision', d3.forceCollide().radius(30))
var container = svg.append("g");
var link = container.append("g")
.attr("class", "links")
.selectAll("path")
.data(graph.links)
.enter().append("path")
.attr("marker-end", "url(#Triangle)");
var value = d3.select("g.links")
.selectAll("text")
.data(graph.links)
.enter().append("text")
.attr("dy", ".35em")
.attr("text-anchor", "middle")
.text(function(d) {
return d.value;
});
var node = container.append("g")
.attr("class", "nodes")
.selectAll("circle")
.data(graph.nodes)
.enter().append("circle")
.attr('stroke-width', 3)
.attr('stroke', function(d) { return color(d.group); })
.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended))
.on("click", listInfo);
var lable = d3.select(".nodes")
.selectAll("text")
.data(graph.nodes)
.enter().append("text")
.text(function(d) { return d.name; });
simulation
.nodes(graph.nodes)
.on("tick", ticked);
simulation.force("link")
.links(graph.links)
function ticked() {
link
.attr("d", function(d) {
return "M" + d.source.x + "," + d.source.y
+ "C" + d.source.x + "," + (d.source.y + d.target.y) / 2
+ " " + d.target.x + "," + d.target.y
+ " " + d.target.x + "," + d.target.y;
})
.attr("stroke-dasharray", function() {
return this.getTotalLength() - 25;
});
value
.attr("x", function(d) { return (d.source.x + d.target.x)/2; })
.attr("y", function(d) { return (d.source.y + d.target.y)/2; });
node
.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; });
lable
.attr("x", function(d) { return d.x; })
.attr("y", function(d) { return d.y - 30; });
}
function slided(d) {
zoom.scaleTo(svg, d3.select(this).property("value"));
}
function dragstarted(d) {
if (!d3.event.active) simulation.alphaTarget(0.1).restart();
d.fx = d.x;
d.fy = d.y;
d3.select(this).classed("dragging", true);
d.fixed = true;
}
function dragged(d) {
d.fx = d3.event.x;
d.fy = d3.event.y;
//fix_nodes(d);
}
// Preventing other nodes from moving while dragging one node
function fix_nodes(this_node) {
node.each(function(d){
if (this_node != d){
d.fx = d.x;
d.fy = d.y;
}
});
}
function dragended(d) {
if (!d3.event.active) simulation.alphaTarget(0);
d3.select(this).classed("dragging", false);
//d.fixed = true;
}
var nodeText;
function listInfo(d) {
d3.select(this)
.select("text")
.text(function(d) { return d.name;})
var nodeText = d.name;
document.getElementById("nodeId").innerHTML = nodeText;
}
};
I tried several ways to use .exit().remove() option, but it doesn't work for me.
Problem: highlighted text(in the image below) is been over-written(therefore its looking bold) when the node is expanded.
Expected Output:
Collapsible d3 Treechart
var linktext = svg.selectAll("g.link")
.data(links, function (d) {
console.log("Text Data Id..."+d.id);
return d.id;
});
linktext.enter()
.insert("g")
.merge(linktext)
.attr("class", "link")
.attr("transform", function (d) {
return "translate(" + ((d.parent.y + d.y) / 2) + "," + ((d.parent.x +d.x) / 2) + ")"; })
.append("text")
.attr("dy", ".35em")
.attr("text-anchor", "middle")
.text(function (d) {
debugger;
console.log("Text Data Rule..."+d.data.rule);
return d.data.rule;
});
linktext.exit().transition()
.remove();
Your problem is caused due to .merge function, this code will fix the multiple-element issue
var treeData =
{ "name": "E", "children": [{ "name": "A", "rule": "yes", "children": [{ "name": "B", "rule": "<=3.843750000000001", "children": [{ "name": "C", "rule": "yes" }, { "name": "D", "rule": "no", "children": [{ "name": "Sex", "rule": "yes", "children": [{ "name": "E", "rule": "Male", "children": [{ "name": "F", "rule": "<=62.5125" }, { "name": "G", "rule": ">62.5125", "children": [{ "name": "H", "rule": "yes" }, { "name": "K", "rule": "no" }] }] }, { "name": "I", "rule": "Female" }] }, { "name": "J", "rule": "no" }] }] }, { "name": "K", "rule": ">3.843750000000001", "children": [{ "name": "Q", "rule": "yes" }, { "name": "H", "rule": "no", "children": [{ "name": "S", "rule": "yes" }, { "name": "N", "rule": "no" }] }] }] }, { "name": "L", "rule": "no", "children": [{ "name": "S", "rule": "yes", "children": [{ "name": "T", "rule": "<=82.025", "children": [{ "name": "Y", "rule": "<=102.9125" }, { "name": "Malaise", "rule": ">102.9125", "children": [{ "name": "Age", "rule": "no", "children": [{ "name": "J", "rule": ">40.6625", "children": [{ "name": "M", "rule": "yes", "children": [{ "name": "N", "rule": "no", "children": [{ "name": "G", "rule": "yes" }, { "name": "E", "rule": "no" }] }] }] }] }] }] }] }, { "name": "survive", "rule": "no" }] }] };
// Set the dimensions and margins of the diagram
var margin = { top: 20, right: 90, bottom: 30, left: 90 },
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
// 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
var svg = d3.select("body").append("svg")
.attr("width", width + margin.right + margin.left)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate("
+ margin.left + "," + margin.top + ")");
var i = 0,
duration = 750,
root;
// declares a tree layout and assigns the size
var treemap = d3.tree().size([height, width]);
// Assigns parent, children, height, depth
root = d3.hierarchy(treeData, function (d) { return d.children; });
root.x0 = height / 2;
root.y0 = 0;
// Collapse after the second level
root.children.forEach(collapse);
update(root);
// Collapse the node and all it's children
function collapse(d) {
if (d.children) {
d._children = d.children
d._children.forEach(collapse)
d.children = null
}
}
function update(source) {
// Assigns the x and y position for the nodes
var treeData = treemap(root);
// Compute the new tree layout.
var nodes = treeData.descendants(),
links = treeData.descendants().slice(1);
// Normalize for fixed-depth.
nodes.forEach(function (d) { d.y = d.depth * 180 });
// ****************** Nodes section ***************************
// Update the nodes...
var node = svg.selectAll('g.node')
.data(nodes, function (d) { return d.id || (d.id = ++i); });
// Enter any new modes at the parent's previous position.
var nodeEnter = node.enter().append('g')
.attr('class', 'node')
.attr("transform", function (d) {
return "translate(" + source.y0 + "," + source.x0 + ")";
})
.on('click', click);
// Add Circle for the nodes
nodeEnter.append('circle')
.attr('class', 'node')
.attr('r', 1e-6)
.style("fill", function (d) {
return d._children ? "lightsteelblue" : "#fff";
});
// Add labels for the nodes
nodeEnter.append('text')
.attr("dy", ".35em")
.attr("x", function (d) {
return d.children || d._children ? -13 : 13;
})
.attr("text-anchor", function (d) {
return d.children || d._children ? "end" : "start";
})
.text(function (d) { return d.data.name; });
// UPDATE
var nodeUpdate = nodeEnter.merge(node);
// Transition to the proper position for the node
nodeUpdate.transition()
.duration(duration)
.attr("transform", function (d) {
return "translate(" + d.y + "," + d.x + ")";
});
// Update the node attributes and style
nodeUpdate.select('circle.node')
.attr('r', 10)
.style("fill", function (d) {
return d._children ? "lightsteelblue" : "#fff";
})
.attr('cursor', 'pointer');
// Remove any exiting nodes
var nodeExit = node.exit().transition()
.duration(duration)
.attr("transform", function (d) {
return "translate(" + source.y + "," + source.x + ")";
})
.remove();
// On exit reduce the node circles size to 0
nodeExit.select('circle')
.attr('r', 1e-6);
// On exit reduce the opacity of text labels
nodeExit.select('text')
.style('fill-opacity', 1e-6);
// ****************** links section ***************************
// Update the links...
var link = svg.selectAll('path.link')
.data(links, function (d) { return d.id; });
// Enter any new links at the parent's previous position.
var linkEnter = link.enter().insert('path', "g")
.attr("class", "link")
.attr('d', function (d) {
var o = { x: source.x0, y: source.y0 }
return diagonal(o, o)
});
// UPDATE
var linkUpdate = linkEnter.merge(link);
// Transition back to the parent element position
linkUpdate.transition()
.duration(duration)
.attr('d', function (d) { return diagonal(d, d.parent) });
// Remove any exiting links
link.exit().transition()
.duration(duration)
.attr('d', function (d) {
var o = { x: source.x, y: source.y }
return diagonal(o, o)
})
.remove();
var linktext = svg.selectAll("g.link")
.data(links, function (d) {
return d.id;
});
linktext.enter()
.insert("g")
//.merge(linktext)
.attr("class", "link")
.attr("transform", function (d) {
return "translate(" + ((d.parent.y + d.y) / 2) + "," + ((d.parent.x + d.x) / 2) + ")";
})
.append("text")
.attr("dy", ".35em")
.attr("text-anchor", "middle")
.text(function (d) {
return d.data.rule;
});
linktext.exit().remove();
// Store the old positions for transition.
nodes.forEach(function (d) {
d.x0 = d.x;
d.y0 = d.y;
});
// Creates a curved (diagonal) path from parent to the child nodes
function diagonal(s, d) {
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
}
}
// 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);
}
Also replace your css with the below to fix the link-text font:
.node circle {
fill: #fff;
stroke: steelblue;
stroke-width: 3px;
}
.node text {
font: 12px sans-serif;
}
path.link {
fill: none;
stroke: #000;
stroke-width: 1px;
}
I am working on a d3js horizontal chart - the designers are specific in having the labels this way.
I've built the following - but would like to model it more on older code that had animation properties.
//current chart
https://codepen.io/anon/pen/ZmJzXZ
//static vertical chart http://jsfiddle.net/pg886/201/
//animated vertical chart http://jsfiddle.net/Qh9X5/12073/
-- d3js code
var data = [{
"name": "Apples",
"value": 20,
},
{
"name": "Bananas",
"value": 12,
},
{
"name": "Grapes",
"value": 19,
},
{
"name": "Lemons",
"value": 5,
},
{
"name": "Limes",
"value": 16,
},
{
"name": "Oranges",
"value": 26,
},
{
"name": "Pears",
"value": 30,
}];
//sort bars based on value
data = data.sort(function (a, b) {
return d3.ascending(a.value, b.value);
})
//set up svg using margin conventions - we'll need plenty of room on the left for labels
var margin = {
top: 15,
right: 25,
bottom: 15,
left: 60
};
var width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var svg = d3.select("#graphic").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var x = d3.scale.linear()
.range([0, width])
.domain([0, d3.max(data, function (d) {
return d.value;
})]);
var y = d3.scale.ordinal()
.rangeRoundBands([height, 0], .3)
.domain(data.map(function (d) {
return d.name;
}));
//make y axis to show bar names
var yAxis = d3.svg.axis()
.scale(y)
//no tick marks
.tickSize(0)
.orient("right");
var gy = svg.append("g")
.attr("class", "y axis")
.call(yAxis)
var bars = svg.selectAll(".bar")
.data(data)
.enter()
.append("g")
.attr("class", "bars")
//append rects
bars.append("rect")
.attr("class", "bar")
.attr("y", function (d) {
return y(d.name);
})
.attr("height", y.rangeBand())
.attr("x", 0)
.attr("width", function (d) {
return x(d.value);
});
//add a value label to the right of each bar
bars.append("text")
.attr("class", "label")
//y position of the label is halfway down the bar
.attr("y", function (d) {
return y(d.name) + y.rangeBand() / 2 + 4;
})
//x position is 3 pixels to the right of the bar
.attr("x", function (d) {
return x(d.value) + 3;
})
.text(function (d) {
return d.value;
});
var labels =
bars.append("text")
.attr("class", "labels")
.attr("y", function (d) {
return y(d.name) + y.rangeBand() / 2 - 30;
})
.attr("x", 0)
.text(function (d) {
return d.name;
});
I had a similar chart that I've made a few modifications to that might fill your requierments, so I'll be basing my answer of my own code.
I'll just go through the most relevant part of the question and you can just have a look at the code and hopefully figure out how it works yourself.
The inital animation works exactly the same way as in the third link you posted:
.transition().duration(speed)
.delay((_, i) => delay * i)
we set a delay so that each bar appear one at a time.
I've also set it up so that you can change the data using the d3js update pattern.
var bar = svg.selectAll(".bar")
.data(data, d => d.name)
bar.exit().remove();
bar.enter().insert("g", ".y-axis").append("rect")
.attr("class", "bar")
.attr("fill", "#ccc")
.attr("x", x(0))
.attr("y", d => y(d.name))
.attr("height", y.bandwidth())
.merge(bar)
.transition().duration(speed)
.delay((_, i) => delay * i)
.attr("y", d => y(d.name))
.attr("width", d => x(d.value) - x(0));
Since you didn't specify how you want to update the new data it's just a year filter for now.
Here's all the code:
var init = [
{"year": "2017", "name": "Apples", "value": 20},
{"year": "2017", "name": "Bananas","value": 12},
{"year": "2017", "name": "Grapes", "value": 19},
{"year": "2017", "name": "Lemons", "value": 5},
{"year": "2017", "name": "Limes", "value": 16},
{"year": "2017", "name": "Oranges", "value": 26},
{"year": "2017", "name": "Pears","value": 30},
{"year": "2018", "name": "Apples", "value": 10},
{"year": "2018", "name": "Bananas","value": 42},
{"year": "2018", "name": "Grapes", "value": 69},
{"year": "2018", "name": "Lemons", "value": 15},
{"year": "2018", "name": "Limes", "value": 26},
{"year": "2018", "name": "Oranges", "value": 36},
{"year": "2018", "name": "Pears","value": 20}
];
chart(init)
function chart(result) {
var format = d3.format(",.0f")
var years = [...new Set(result.map(d => d.year))]
var fruit = [...new Set(result.map(d => d.name))]
var options = d3.select("#year").selectAll("option")
.data(years)
.enter().append("option")
.text(d => d)
var svg = d3.select("#graphic"),
margin = {top: 25, bottom: 10, left: 50, right: 45},
width = +svg.attr("width") - margin.left - margin.right,
height = +svg.attr("height") - margin.top - margin.bottom;
var x = d3.scaleLinear()
.range([margin.left, width - margin.right])
var y = d3.scaleBand()
.range([margin.top, height - margin.bottom])
.padding(0.1)
.paddingOuter(0.5)
.paddingInner(0.5)
var xAxis = svg.append("g")
.attr("class", "x-axis")
.attr("transform", `translate(0,${margin.top})`)
var yAxis = svg.append("g")
.attr("class", "y-axis")
.attr("transform", `translate(${margin.left},0)`)
update(d3.select("#year").property("value"), 750, 250)
function update(input, speed, delay) {
var data = result.filter(f => f.year == input)
var sum = d3.sum(data, d => d.value)
x.domain([0, d3.max(data, d => d.value)]).nice()
svg.selectAll(".x-axis").transition().duration(speed)
.call(d3.axisTop(x).tickSizeOuter(0));
data.sort((a, b) => b.value - a.value)
y.domain(data.map(d => d.name))
svg.selectAll(".y-axis").transition().duration(speed)
.call(d3.axisLeft(y));
yAxis.selectAll("text").remove()
yAxis.selectAll("line").remove()
var bar = svg.selectAll(".bar")
.data(data, d => d.name)
bar.exit().remove();
bar.enter().insert("g", ".y-axis").append("rect")
.attr("class", "bar")
.attr("fill", "#ccc")
.attr("x", x(0))
.attr("y", d => y(d.name))
.attr("height", y.bandwidth())
.merge(bar)
.transition().duration(speed)
.delay((_, i) => delay * i)
.attr("y", d => y(d.name))
.attr("width", d => x(d.value) - x(0));
var value = svg.selectAll(".value")
.data(data, d => d.name)
value.exit().remove();
value.enter().append("text")
.attr("class", "value")
.attr("opacity", 0)
.attr("dy", 4)
.attr("y", d => y(d.name) + y.bandwidth() / 2)
.merge(value)
.transition().duration(speed)
.delay((_, i) => delay * i)
.attr("opacity", 1)
.attr("y", d => y(d.name) + y.bandwidth() / 2)
.attr("x", d => x(d.value) + 5)
.text(d => format((d.value / sum) * 100) + " %")
var name = svg.selectAll(".name")
.data(data, d => d.name)
name.exit().remove();
name.enter().append("text")
.attr("class", "name")
.attr("opacity", 0)
.attr("dy", -5)
.attr("y", d => y(d.name))
.merge(name)
.transition().duration(speed)
.delay((_, i) => delay * i)
.attr("opacity", 1)
.attr("y", d => y(d.name))
.attr("x", d => x(0) + 5)
.text(d => d.name)
}
var select = d3.select("#year")
.style("border-radius", "5px")
.on("change", function() {
update(this.value, 750, 0)
})
}
body {
margin: auto;
width: 650px;
font: 12px arial;
}
<script src="https://d3js.org/d3.v5.min.js"></script>
<svg id="graphic" width="600" height="380"></svg><br>
Choose year:
<select id="year"></select>
I am having force-directed graph. It shows nodes without problems and on console it writes links - source and target. But doesn't connect it to nodes. I can see there is no field for coordinators see picture
Whole code is in Kibana and more complicated but here is the core:
const link = svg.selectAll('link')
.data(links)
.enter()
.append('svg:line')
.attr('class', 'link')
.style("stroke-width", function (d) {return Math.sqrt(d.value);})
.attr('x1', function(d) { return d.source.x;})
.attr('y1', function(d) { return d.source.y;})
.attr('x2', function(d) { return d.target.x;})
.attr('y2', function(d) { return d.target.y;});
force.on("tick", tick);
function tick() {
link.attr("x1", function(d) { return d.source.x; })
.attr("y1", function(d) { return d.source.y; })
.attr("x2", function(d) { return d.target.x; })
.attr("y2", function(d) { return d.target.y; });
node.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")"; });
};
var node = svg.selectAll('node')
.data(nodes)
.enter()
.append('circle')
.attr('class', 'node')
.style("opacity", .9)
.attr("r", function(d) { return 10; })
.attr("id", function(d) { return d.id; })
.attr("cy", function(d){return d.y;})
.attr("cx", function(d){return d.x;})
.style("fill", function(d) { return c20(d.value);})
.style("stroke-width", 20);
const svg = div.append('svg')
.attr('width', width)
.attr('height', height)
.append('g')
.attr('transform', 'translate('+ width / 2 + ',' + height / 3 + ')');
var force = d3.layout.force()
.nodes(nodes)
.links(links)
.charge(-150)
.linkDistance(90)
.start();
MY data structure:
links =
[{"source": 0, "target": 1, "value": 30},
{"source": 0, "target": 2, "value": 5},
{"source": 1, "target": 3, "value": 1},
{"source": 2, "target": 0, "value": 20}]
nodes =
[{"ip": "92.15.122.1", "value": 5, id: 0},
{"ip": "12.154.154.22", "value": 20, id: 1},
{"ip": "255.12.11.1", "value": 30, id: 2},
{"ip": "54.55.6.55", "value": 1, id: 3}]
I think the problem is connecting "id" from "nodes" to "source" and "target" in links. Any idea how?
You need to set the stroke colour on the link variable
var link = svg.selectAll('link')
.data(links)
.enter()
.append('line')
.attr('class', 'link')
.style("stroke", 'black')
See below the full snippet. I have cleaned it up to get it to run in a Stack snippet.
var width = 320,
height = 240;
links = [{
"source": 0,
"target": 1,
"value": 30
},
{
"source": 0,
"target": 2,
"value": 5
},
{
"source": 1,
"target": 3,
"value": 1
},
{
"source": 2,
"target": 0,
"value": 20
}
]
nodes = [{
"ip": "92.15.122.1",
"value": 5,
id: 0
},
{
"ip": "12.154.154.22",
"value": 20,
id: 1
},
{
"ip": "255.12.11.1",
"value": 30,
id: 2
},
{
"ip": "54.55.6.55",
"value": 1,
id: 3
}
]
var svg = d3.select('body').append('svg')
.attr('width', width)
.attr('height', height)
.attr('transform', 'translate(' + width / 2 + ',' + height / 3 + ')');
var force = d3.layout.force()
.size([width, height])
.nodes(nodes)
.links(links);
function tick() {
node.attr('r', width / 25)
.attr('cx', function(d) {
return d.x;
})
.attr('cy', function(d) {
return d.y;
});
link.attr('x1', function(d) {
return d.source.x;
})
.attr('y1', function(d) {
return d.source.y;
})
.attr('x2', function(d) {
return d.target.x;
})
.attr('y2', function(d) {
return d.target.y;
});
}
force.on("tick", tick);
var link = svg.selectAll('link')
.data(links)
.enter()
.append('line')
.attr('class', 'link')
.style("stroke", 'black')
.style("stroke-width", function(d) {
return Math.sqrt(d.value);
}).attr('x1', function(d) { return d.source.x;})
.attr('y1', function(d) { return d.source.y;})
.attr('x2', function(d) { return d.target.x;})
.attr('y2', function(d) { return d.target.y;});
var node = svg.selectAll('node')
.data(nodes)
.enter()
.append('circle')
.attr('class', 'node')
.style("opacity", .9)
.attr("r", function(d) {
return 10;
})
.attr("id", function(d) {
return d.id;
})
.attr("cy", function(d) {
return d.y;
})
.attr("cx", function(d) {
return d.x;
})
.style("stroke-width", 20);
force
.nodes(nodes)
.links(links)
.charge(-150)
.linkDistance(90)
.start();
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>