Removing segments from a d3 animated pie - d3.js

I have prototype code I am working with here:
jsfiddle
The example shows how to add a segment when new data is added but not how to remove it again when the data changes [back]. I am fairly new to d3 and don't quite get the exit() function yet...
if you reverse the initial and second dataset you will see that the grapes segment is never removed. Thanks in advance!
any help would be great!
The update code: (my final chart needs to update on a timer when data changes)
var arcs = arc_grp.selectAll("path")
.data(donut(data));
arcs.enter()
.append("path")
.attr("stroke", "white")
.attr("stroke-width", 0.8)
.attr("fill", function(d, i) { return color(i); })
.attr("d", arc)
.each(function(d) { return this.current = d; });
var sliceLabel = label_group.selectAll("text")
.data(donut(data));
sliceLabel.enter()
.append("text")
.attr("class", "arcLabel")
.attr("transform", function(d) { return "translate(" + (arc.centroid(d)) +
")"; })
.attr("text-anchor", "middle")
.style("fill-opacity", function(d) {
if (d.value === 0) { return 1e-6; }
else { return 1; }
})
.text(function(d) { return d.data.label; });
};

To remove elements without data, you have to use exit(), which...
Returns the exit selection: existing DOM elements in the selection for which no new datum was found.
So, inside your updateChart function, you need an exit selection for both paths and texts:
var newArcs = arcs.data(donut(dataset));
newArcs.exit().remove();
var newSlices = sliceLabel.data(donut(dataset));
newSlices.exit().remove();
Here is your updated code:
// Setup all the constants
var duration = 500;
var width = 500
var height = 300
var radius = Math.floor(Math.min(width / 2, height / 2) * 0.9);
var colors = ["#d62728", "#ff9900", "#004963", "#3497D3"];
// Test Data
var d2 = [{
label: 'apples',
value: 20
}, {
label: 'oranges',
value: 50
}, {
label: 'pears',
value: 100
}];
var d1 = [{
label: 'apples',
value: 100
}, {
label: 'oranges',
value: 20
}, {
label: 'pears',
value: 20
}, {
label: 'grapes',
value: 20
}];
// Set the initial data
var data = d1
var updateChart = function(dataset) {
var newArcs = arcs.data(donut(dataset));
newArcs.exit().remove();
newArcs.enter()
.append("path")
.attr("stroke", "white")
.attr("stroke-width", 0.8)
.attr("fill", function(d, i) {
return color(i);
})
.attr("d", arc);
newArcs.transition()
.duration(duration)
.attrTween("d", arcTween);
var newSlices = sliceLabel.data(donut(dataset));
newSlices.exit().remove();
newSlices.transition()
.duration(duration)
.attr("transform", function(d) {
return "translate(" + (arc.centroid(d)) + ")";
})
.style("fill-opacity", function(d) {
if (d.value === 0) {
return 1e-6;
} else {
return 1;
}
});
sliceLabel.data(donut(dataset)).enter()
.append("text")
.attr("class", "arcLabel")
.attr("transform", function(d) {
return "translate(" + (arc.centroid(d)) + ")";
})
.attr("text-anchor", "middle")
.style("fill-opacity", function(d) {
if (d.value === 0) {
return 1e-6;
} else {
return 1;
}
})
.text(function(d) {
return d.data.label;
});
};
var color = d3.scale.category20();
var donut = d3.layout.pie()
.sort(null)
.value(function(d) {
return d.value;
});
var arc = d3.svg.arc()
.innerRadius(radius * .4)
.outerRadius(radius);
var svg = d3.select("body")
.append("svg")
.attr("width", width)
.attr("height", height);
var arc_grp = svg.append("g")
.attr("class", "arcGrp")
.attr("transform", "translate(" + (width / 2) + "," + (height / 2) + ")");
var label_group = svg.append("g")
.attr("class", "lblGroup")
.attr("transform", "translate(" + (width / 2) + "," + (height / 2) + ")");
var arcs = arc_grp.selectAll("path")
.data(donut(data));
arcs.enter()
.append("path")
.attr("stroke", "white")
.attr("stroke-width", 0.8)
.attr("fill", function(d, i) {
return color(i);
})
.attr("d", arc)
.each(function(d) {
return this.current = d;
});
var sliceLabel = label_group.selectAll("text")
.data(donut(data));
sliceLabel.enter()
.append("text")
.attr("class", "arcLabel")
.attr("transform", function(d) {
return "translate(" + (arc.centroid(d)) + ")";
})
.attr("text-anchor", "middle")
.style("fill-opacity", function(d) {
if (d.value === 0) {
return 1e-6;
} else {
return 1;
}
})
.text(function(d) {
return d.data.label;
});
// Update the data
setInterval(function(model) {
return updateChart(d2);
}, 2000);
// Tween Function
var arcTween = function(a) {
var i = d3.interpolate(this.current, a);
this.current = i(0);
return function(t) {
return arc(i(t));
};
};
.arcLabel {
font: 10px sans-serif;
fill: #fff;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>

Related

D3 Pack Layout symmetry got changed in version greater than v4.5

I was using d3 version 4.5 earlier in my project for d3 pack circles. Now I have used latest version and got difference in pack layout symmetry. Before Image and After Image
Here is my code in both cases. Want to have same symmetry as it was in earlier version. Is there any new way to get this symmetry in latest version of d3.
var diameter = 250;
var color = d3.scaleOrdinal(d3.schemeCategory20);
var bubble = d3.pack(data)
.size([diameter, 185])
.padding(1.4);
var svg = d3.select("#trending-topic")
.append("svg")
.attr("width", diameter)
.attr("height", 185)
.attr("class", "bubble");
var nodes = d3.hierarchy(data)
.sum(function(d) {
return d.Count;
});
var format = d3.format(",d");
d3.selection.prototype.moveToFront = function() {
return this.each(function() {
this.parentNode.appendChild(this);
});
};
var node = svg.selectAll(".node")
.data(bubble(nodes)
.descendants())
.enter()
.filter(function(d) {
return !d.children;
})
.append("g")
.attr("class", "node")
.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
})
.attr("title", function(d) {
return d.Name;
});
/* transparent circle with border */
node.append("circle")
.attr("r", function(d) {
return d.r;
//return d.data.Radius;
})
.style("fill", function(d, i) {
return d.data.fillColor;
})
.on("click", function(d) {
getTopicArticle(d.data.tid);
});
node.append("text")
.each(function(d) {
var arr = d.data.Name.split(" ");
for(i = 0; i < arr.length; i++) {
if(arr[i].length > 10) {
arr[i] = arr[i].substring(0, 7) + '...';
}
d3.select(this)
.append("tspan")
.text(arr[i])
.attr("dy", i ? "1.2em" : 0)
.attr("x", 0)
.attr("text-anchor", "middle")
.attr("class", "tspan" + i)
.attr("fill", "white")
.attr("font-size", function(d) {
return d.r / 3;
})
.on("click", function(d) {
getTopicArticle(d.data.tid);
});
}
d3.select(this)
.append("title")
.text(d.data.Name);
});
d3.select(self.frameElement)
.style("height", 185 + "px");
d3.selectAll(".node")
.on("mouseover", function(d) {
var circle = d3.select(this)
.select("circle");
var text = d3.select(this)
.selectAll("tspan");
text.transition()
.duration(500)
.attr("font-size", function(d) {
return d.r;
});
})
.on("mousemove", function(d) {
})
.on("mouseleave", function(d) {
var circle = d3.select(this)
.select("circle");
circle.transition()
.duration(500)
.attr("r", function(d) {
return d.r;
});
var text = d3.select(this)
.selectAll("tspan");
text.transition()
.duration(500)
.attr("font-size", function(d) {
return d.r / 3;
});
});
You were right, it's this commit in d3-hierarchy between 1.1.1 and 1.1.2, which in turn was introduced between d3 4.5.0 and 4.5.1. It addresses this issue, about packing circles more condensely.
I recommend just accepting the changes, but if you really don't want to change the layout, import d3-hierarchy 1.1.1 *after* d3` to override the hierarchy module. This returns the same layout as the older version of d3:
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.5.1/d3.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3-hierarchy/1.1.1/d3-hierarchy.min.js"></script>
The downside is that by making the package versions out of sync, you might break something now or in the future, so it's not a long term sustainable approach.
To test use the following snippet and comment/uncomment the script imports.
var diameter = 250;
var color = d3.scaleOrdinal(d3.schemeCategory20);
var data = {
children: [{
Name: 'Economy',
fillColor: 'grey',
Count: 12
},
{
Name: 'Politics',
fillColor: 'grey',
Count: 10
},
{
Name: 'ESG',
fillColor: 'lightblue',
Count: 5
},
{
Name: 'Tech',
fillColor: 'lightblue',
Count: 5
},
{
Name: 'Leisure',
fillColor: 'pink',
Count: 4
},
{
Name: 'Coronavirus',
fillColor: 'pink',
Count: 4
},
{
Name: 'Blockchain',
fillColor: 'darkblue',
Count: 2,
},
{
Name: 'Sports',
fillColor: 'darkblue',
Count: 2,
},
{
Name: 'Coding',
fillColor: 'purple',
Count: 1,
},
{
Name: 'India',
fillColor: 'purple',
Count: 1,
}
],
};
var bubble = d3.pack(data).size([diameter, 185]).padding(1.4);
var svg = d3.select("#trending-topic")
.append("svg")
.attr("width", diameter)
.attr("height", 185)
.attr("class", "bubble");
var nodes = d3.hierarchy(data)
.sum(function(d) {
return d.Count;
});
var format = d3.format(",d");
d3.selection.prototype.moveToFront = function() {
return this.each(function() {
this.parentNode.appendChild(this);
});
};
var node = svg.selectAll(".node")
.data(bubble(nodes).descendants())
.enter()
.filter(function(d) {
return !d.children
})
.append("g")
.attr("class", "node")
.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
})
.attr("title", function(d) {
return d.Name;
});
/* transparent circle with border */
node.append("circle").attr("r", function(d) {
return d.r;
//return d.data.Radius;
}).style("fill", function(d, i) {
return d.data.fillColor;
});
node.append("text").each(function(d) {
var arr = d.data.Name.split(" ");
for (i = 0; i < arr.length; i++) {
if (arr[i].length > 10) {
arr[i] = arr[i].substring(0, 7) + '...';
}
d3.select(this).append("tspan")
.text(arr[i])
.attr("dy", i ? "1.2em" : 0)
.attr("x", 0)
.attr("text-anchor", "middle")
.attr("class", "tspan" + i).attr("fill", "white").attr("font-size", function(d) {
return d.r / 3;
});
}
d3.select(this).append("title").text(d.data.Name);
});
d3.select(self.frameElement).style("height", 185 + "px");
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.5.1/d3.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.5.0/d3.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3-hierarchy/1.1.1/d3-hierarchy.min.js"></script>
<div id="trending-topic"></div>

How can I refactor a d3 pie to accept more or less data points?

I have a project that almost works the way I want. When a smaller dataset is added, slices are removed. It fails when a larger dataset is added. The space for the arc is added but no label or color is added for it.
This is my enter() code:
newArcs.enter()
.append("path")
.attr("stroke", "white")
.attr("stroke-width", 0.8)
.attr("fill", function(d, i) {
return color(i);
})
.attr("d", arc);
What am I doing wrong?
I've fixed the code such that it works now:
// Tween Function
var arcTween = function(a) {
var i = d3.interpolate(this.current || {}, a);
this.current = i(0);
return function(t) {
return arc(i(t));
};
};
// Setup all the constants
var duration = 500;
var width = 500
var height = 300
var radius = Math.floor(Math.min(width / 2, height / 2) * 0.9);
var colors = ["#d62728", "#ff9900", "#004963", "#3497D3"];
// Test Data
var d2 = [{
label: 'apples',
value: 20
}, {
label: 'oranges',
value: 50
}, {
label: 'pears',
value: 100
}];
var d1 = [{
label: 'apples',
value: 100
}, {
label: 'oranges',
value: 20
}, {
label: 'pears',
value: 20
}, {
label: 'grapes',
value: 20
}];
// Set the initial data
var data = d1
var updateChart = function(dataset) {
arcs = arcs.data(donut(dataset), function(d) { return d.data.label });
arcs.exit().remove();
arcs.enter()
.append("path")
.attr("stroke", "white")
.attr("stroke-width", 0.8)
.attr("fill", function(d, i) {
return color(i);
})
.attr("d", arc);
arcs.transition()
.duration(duration)
.attrTween("d", arcTween);
sliceLabel = sliceLabel.data(donut(dataset), function(d) { return d.data.label });
sliceLabel.exit().remove();
sliceLabel.enter()
.append("text")
.attr("class", "arcLabel")
.attr("transform", function(d) {
return "translate(" + (arc.centroid(d)) + ")";
})
.attr("text-anchor", "middle")
.style("fill-opacity", function(d) {
if (d.value === 0) {
return 1e-6;
} else {
return 1;
}
})
.text(function(d) {
return d.data.label;
});
sliceLabel.transition()
.duration(duration)
.attr("transform", function(d) {
return "translate(" + (arc.centroid(d)) + ")";
})
.style("fill-opacity", function(d) {
if (d.value === 0) {
return 1e-6;
} else {
return 1;
}
});
};
var color = d3.scale.category20();
var donut = d3.layout.pie()
.sort(null)
.value(function(d) {
return d.value;
});
var arc = d3.svg.arc()
.innerRadius(radius * .4)
.outerRadius(radius);
var svg = d3.select("body")
.append("svg")
.attr("width", width)
.attr("height", height);
var arc_grp = svg.append("g")
.attr("class", "arcGrp")
.attr("transform", "translate(" + (width / 2) + "," + (height / 2) + ")");
var label_group = svg.append("g")
.attr("class", "lblGroup")
.attr("transform", "translate(" + (width / 2) + "," + (height / 2) + ")");
var arcs = arc_grp.selectAll("path");
var sliceLabel = label_group.selectAll("text");
updateChart(data);
// returns random integer between min and max number
function getRand() {
var min = 1,
max = 2;
var res = Math.floor(Math.random() * (max - min + 1) + min);
//console.log(res);
return res;
}
// Update the data
setInterval(function(model) {
var r = getRand();
return updateChart(r == 1 ? d1 : d2);
}, 2000);
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>

D3 V4 Rect bind data in stacked bar chart

In Normalized stacked bar I am trying to bind data in all rect in a bar but wrong value is passed. I adopted my code from this example and made it horizontal. Below is my code and I have created a plunker as well. In .text function entire object is passed. Can someone help me where I am going wrong
var svg = d3.select("svg"),
margin = {
top: 20,
right: 60,
bottom: 30,
left: 40
},
/*width = +svg.attr("width") - margin.left - margin.right,
height = +svg.attr("height") - margin.top - margin.bottom,*/
width = 120,
height = 120,
g = svg.append("g").attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var y = d3.scaleBand()
.rangeRound([0, width])
.padding(0.1)
.align(0.1);
var x = d3.scaleLinear()
.rangeRound([height, 0]);
var z = d3.scaleOrdinal()
.range(['#02CA22', '#FB5652', '#FFB005']);
var stack = d3.stack()
.offset(d3.stackOffsetExpand);
d3.csv("data.csv", type, function (error, data) {
if (error) throw error;
/*data.sort(function(a, b) {
return b[data.columns[1]] / b.total - a[data.columns[1]] / a.total;
});*/
y.domain(data.map(function (d) {
return d.State;
}));
z.domain(data.columns.slice(1));
var serie = g.selectAll(".serie")
.data(stack.keys(data.columns.slice(1))(data))
.enter().append("g")
.attr("class", "serie")
.attr("fill", function (d) {
return z(d.key);
});
var rect = serie.selectAll("rect")
.data(function (d) {
return d;
}).enter();
rect.append("rect")
.attr("y", function (d) {
return y(d.data.State);
})
.attr("x", function (d) {
return x(d[1]);
})
.attr("width", function (d) {
return x(d[0]) - x(d[1]);
})
.attr("height", y.bandwidth());
rect.append("text")
.text(function (d) {
console.log('d');
console.log(d);
console.log(d.data.key);
return 'val';
})
.attr("y", function (d) { return y(d.data.State) + y.bandwidth() / 2; })
.attr("x", function (d) {
return x(d[1]);
});
/* g.append("g")
.attr("class", "axis axis--x")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x).ticks(2, "%"));*/
g.append("g")
.attr("class", "axis axis--y")
.call(d3.axisLeft(y));
var legend = serie.append("g")
.attr("class", "legend")
.attr("transform", function (d) {
var d = d[0];
return "translate(" + ((x(d[0]) + x(d[1])) / 2) + ", " + (y(d.data.State) - y.bandwidth()) + ")";
});
/*legend.append("line")
.attr("y1", 5)
.attr("x1", 15)
.attr("x2", 15)
.attr("y2", 12)
.attr("stroke", "#000");
legend.append("text")
.attr("x", 9)
.attr("dy", "0.35em")
.attr("fill", "#000")
.style("font", "10px sans-serif")
.text(function (d) {
return d.key;
}); */
});
function type(d, i, columns) {
var t;
for (i = 1, t = 0; i < columns.length; ++i) t += d[columns[i]] = +d[columns[i]];
d.total = t;
return d;
}
I think the best way to do this is to modify your subselection data-binding to include that information:
var rect = serie.selectAll("rect")
.data(function (d) {
// return all the data you need as flat as possible
var rv = d.map(function(da){
return {p: da, key: d.key, state: da.data.State}
});
return rv;
}).enter();
The text is then available as:
rect.append("text")
.text(function (d) {
return d.key;
})
.attr("y", function (d) { return y(d.state) + y.bandwidth() / 2; })
.attr("x", function (d) {
return x(d.p[1]);
});
Updated Plunker.

Placing label in the middle of a link in d3 js tree layout

I'm trying to create a labelled double tree using d3js tree layout. Functionality is working fine so far , but the problem I face is , i'm unable to place the label in the middle of the link.
Right now it appears like this ( once you have added enough nodes )
This is the part of the code where I add the edge and label inside a g element. I'm trying add the label in the midpoint of the link , but it's not working. ( the code shown is for the right side of the double tree )
var edge = d3.select("#right-edges")
.append("g")
.attr("class", "edge-container")
.data(links);
var link = edge.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", "edge-container")
.attr("class", "link")
.attr("id", function(d) {
return ("rightlink" + d.source.id + "-" + d.target.id)
})
.attr("d", function(d) {
var o = {
x: source.x0,
y: source.y0
};
return diagonal({
source: o,
target: o
});
}).on("click", removelink)
.on("mouseover", showRemoveButton)
.on("mouseout", hideRemoveButton);
function showRemoveButton() {
console.log("hover");
}
function hideRemoveButton() {
console.log("hover-out");
}
var text = edge.selectAll("text.text-link")
.data(links, function(d) {
return d.target.id + d.source.id;
});
text.enter().insert("text", "edge-container")
.attr("class", "text-link")
.attr("x", function(d) {
var x = (d.source.y + d.target.y) / 2
return parseInt(x + 25);
})
.attr("y", function(d) {
var y = (d.source.x + d.target.x) / 2
return y;
})
.text("test-label")
.attr("text-anchor", "start")
.style("font-size", "12px");
Here is the working code so far -
var treeData = [{
"name": "Device",
"parent": "null"
}
];
var treeData2 = [{
"name": "Device",
"parent": "null"
}
];
$(document).ready(function($) {
var margin = {
top: 20,
right: 120,
bottom: 20,
left: 120
},
width = 2260 - margin.right - margin.left,
height = 500 - margin.top - margin.bottom;
var svg = d3.select('.doubletree-container').append("svg")
.attr("width", width + margin.right + margin.left)
.attr("height", height + margin.top + margin.bottom);
$.fn.makeDoubleTree = function() {
this.makeRightTree();
this.makeLeftTree();
};
}(jQuery));
$.fn.makeRightTree = function() {
var margin = {
top: 20,
right: 120,
bottom: 20,
left: 120
},
width = 1260 - margin.right - margin.left,
height = 500 - margin.top - margin.bottom;
var i = 0,
duration = 750,
root;
var tree = d3.layout.tree()
.size([height, width]);
var diagonal = d3.svg.diagonal()
.projection(function(d) {
return [d.y, d.x];
});
var svg = d3.select("svg").append("g")
.attr("class", "right-tree-container")
.attr("transform", "translate(600,0)");
svg.append("g").attr("id", "right-edges")
root = treeData[0];
oldrx = root.x0 = height / 2;
oldry = root.y0 = 0;
update(root);
function update(source) {
var nodes = tree.nodes(root).reverse(),
links = tree.links(nodes);
nodes.forEach(function(d) {
d.y = d.depth * 180;
});
// 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", function(d) {
console.log(d);
if (d.parent == "null") {
if (d.children)
{
return "node rightparent collapsed" //since its root its parent is null
} else {
return "node rightparent"
}
} else {
if (d.children) {
return "node rightchild collapsed" //all nodes with parent will have this class
} else {
return "node rightchild" //all nodes with parent will have this class
}
}
})
.attr("transform", function(d) {
return "translate(" + source.y0 + "," + source.x0 + ")";
})
.on("click", click);
nodeEnter.append("rect")
.attr("id", function(d) {
return "rightnode" + d.id;
})
.attr("x", "-10")
.attr("y", "-15")
.attr("height", 30)
.attr("width", 100)
.attr("rx", 15)
.attr("ry", 15)
.style("fill", "#f1f1f1");
nodeEnter.append("image")
.attr("xlink:href", "img.png")
.attr("x", "0")
.attr("y", "-10")
.attr("width", 16)
.attr("height", 16);
nodeEnter.append("text")
.attr("x", function(d) {
return d.children || d._children ? -13 : 13;
})
.attr("dy", ".35em")
.attr("text-anchor", function(d) {
return d.children || d._children ? "end" : "start";
})
.text(function(d) {
return d.name;
})
.style("fill-opacity", 1e-6);
var addRightChild = nodeEnter.append("g").attr("class", "addRightChild");
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");
// adding the right chevron
var rightChevron = nodeEnter.append("g").attr("class", "right-chevron");
rightChevron.append("line")
.attr("x1", 75)
.attr("y1", -5)
.attr("x2", 80)
.attr("y2", 0)
.attr("stroke", "#444")
.style("stroke-width", "2");
rightChevron.append("line")
.attr("x1", 80)
.attr("y1", 0)
.attr("x2", 75)
.attr("y2", 5)
.attr("stroke", "#444")
.style("stroke-width", "2");
rightChevron.on("click", function(d) {
});
// Transition nodes to their new position.
var nodeUpdate = node.transition()
.duration(duration)
.attr("transform", function(d) {
if (d.parent == "null") {
d.y = oldry;
d.x = oldrx;
}
return "translate(" + d.y + "," + d.x + ")";
});
nodeUpdate.select("text")
.style("fill-opacity", 1);
// Transition exiting nodes to the parent's new position.
var nodeExit = node.exit().transition()
.duration(duration)
.attr("transform", function(d) {
return "translate(" + source.y + "," + source.x + ")";
})
.remove();
nodeExit.select("text")
.style("fill-opacity", 1e-6);
// Update the links…
var edge = d3.select("#right-edges")
.append("g")
.attr("class", "edge-container")
.data(links);
var link = edge.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", "edge-container")
.attr("class", "link")
.attr("id", function(d) {
return ("rightlink" + d.source.id + "-" + d.target.id)
})
.attr("d", function(d) {
var o = {
x: source.x0,
y: source.y0
};
return diagonal({
source: o,
target: o
});
}).on("click", removelink)
.on("mouseover", showRemoveButton)
.on("mouseout", hideRemoveButton);
function showRemoveButton() {
console.log("hover");
}
function hideRemoveButton() {
console.log("hover-out");
}
var text = edge.selectAll("text.text-link")
.data(links, function(d) {
return d.target.id + d.source.id;
});
text.enter().insert("text", "edge-container")
.attr("class", "text-link")
.attr("x", function(d) {
var x = (d.source.y + d.target.y) / 2
return parseInt(x + 25);
})
.attr("y", function(d) {
var y = (d.source.x + d.target.x) / 2
return y;
})
.text("test-label")
.attr("text-anchor", "start")
.style("font-size", "12px");
/* transition labels to new positions */
var label = svg.selectAll("text.text-link")
.data(links, function(d) {
return d.target.id;
});
// Transition links to their new position.
label.transition()
.duration(0);
// Transition exiting nodes to the parent's new position.
label.exit().transition()
.duration(duration)
.attr("d", function(d) {
var o = {
x: source.x,
y: source.y
};
return diagonal({
source: o,
target: o
});
})
.remove();
// d3.selectAll(".right-tree-container .remove-child").remove();
// var removeButton = edge.selectAll(".remove-child")
// .data(links, function(d) {
// return d.target.id + d.source.id;
// });
// removeButton.enter().insert("rect", "edge-container")
// .attr("class", "remove-child")
// .attr("x", function(d) {
// var x = (d.source.y + d.target.y) / 2
// return parseInt(x + 45);
// })
// .attr("y", function(d) {
// var y = (d.source.x + d.target.x) / 2
// return y;
// })
// .attr("width","20")
// .attr("height","20")
// .attr("rx","10")
// .attr("ry","10")
// .style("fill","white")
// .style("stroke","#444")
// .style("stroke-width","2");
function removelink(d) {
var confirmDelete = confirm("Are you sure you want to delete?");
if (confirmDelete) {
//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(function(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
update(d.target.parent)
}
}
var link = svg.selectAll("path.link")
.data(links, function(d) {
return d.target.id;
});
// 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;
});
addRightChild.on("click", function(d) {
event.stopPropagation();
$("#child-info").show();
$("#child-text").val("");
$("#btn-add-child").off('click');
$("#btn-add-child").click(function() {
var childname = $("#child-text").val();
if (typeof d._children === 'undefined' || d._children === null) {
if (typeof d.children === 'undefined') {
var newChild = [{
"name": childname,
"parent": "Son Of A",
}];
var newnodes = tree.nodes(newChild);
d.children = newnodes[0];
// console.log(d.children);
update(d);
} else {
var newChild = {
"name": childname,
"parent": "Son Of A",
};
// console.log(d.children);
d.children.push(newChild);
// console.log(d.children);
update(d);
}
} else {
var newChild = {
"name": childname,
"parent": "Son Of A",
};
d.children = d._children;
d.children.push(newChild);
// console.log(d.children);
update(d);
}
$("#child-info").hide();
});
});;
}
// 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);
$(".addLeftChild, .addRightChild").hide();
if (d.id === 1) {
$(".rightparent").children(".addRightChild").show();
$(this).children(".addRightChild").show();
} else {
$(this).children(".addRightChild").show();
}
d3.selectAll("rect").style("fill", "#f1f1f1"); //reset all node colors
d3.selectAll("path").style("stroke", "#85e0e0"); //reset the color for all links
while (d.parent) {
d3.select("#leftnode1").style("fill", "#F7CA18");
d3.selectAll("#rightnode" + d.id).style("fill", "#F7CA18"); //color the node
if (d.parent != "null")
d3.selectAll("#rightlink" + d.parent.id + "-" + d.id).style("stroke", "#F7CA18"); //color the path
d = d.parent;
}
}
}
$.fn.makeLeftTree = function() {
// ************** Generate the tree diagram *****************
var margin = {
top: 20,
right: 120,
bottom: 20,
left: 120
},
width = 1260 - margin.right - margin.left,
height = 500 - margin.top - margin.bottom;
var i = 0,
duration = 750,
root;
var tree = d3.layout.tree()
.size([height, width]);
var diagonal = d3.svg.diagonal()
.projection(function(d) {
return [d.y, d.x];
});
var svg = d3.select("svg").append("g")
.attr("class", "left-tree-container")
.attr("transform", "translate(-421,0)");
svg.append("g").attr("id", "left-edges")
root = treeData2[0];
oldlx = root.x0 = height / 2;
oldly = root.y0 = width;
update(root);
function update(source) {
// Compute the new tree layout.
var nodes = tree.nodes(root).reverse(),
links = tree.links(nodes);
// Normalize for fixed-depth.
nodes.forEach(function(d) {
d.y = width - (d.depth * 180);
});
// 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", function(d) {
if (d.parent == "null") {
return "node leftparent" //since its root its parent is null
} else
return "node leftchild" //all nodes with parent will have this class
})
.attr("transform", function(d) {
return "translate(" + source.y0 + "," + source.x0 + ")";
})
.on("click", click);
nodeEnter.append("rect")
.attr("x", "-10")
.attr("id", function(d) {
return "leftnode" + d.id;
})
.attr("y", "-15")
.attr("height", 30)
.attr("width", 100)
.attr("rx", 15)
.attr("ry", 15)
.style("fill", "#f1f1f1");
nodeEnter.append("image")
.attr("xlink:href", "img.png")
.attr("x", "60")
.attr("y", "-10")
.attr("width", 16)
.attr("height", 16);
nodeEnter.append("text")
.attr("x", function(d) {
return d.children || d._children ? -13 : 13;
})
.attr("dy", ".35em")
.attr("text-anchor", function(d) {
return d.children || d._children ? "end" : "start";
})
.text(function(d) {
return d.name;
})
.style("fill-opacity", 1e-6);
var addLeftChild = nodeEnter.append("g").attr("class", "addLeftChild");
addLeftChild.append("rect")
.attr("x", "-30")
.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");
addLeftChild.append("line")
.attr("x1", -25)
.attr("y1", 1)
.attr("x2", -15)
.attr("y2", 1)
.attr("stroke", "#444")
.style("stroke-width", "2");
addLeftChild.append("line")
.attr("x1", -20)
.attr("y1", -4)
.attr("x2", -20)
.attr("y2", 6)
.attr("stroke", "#444")
.style("stroke-width", "2");
var leftChevron = nodeEnter.append("g").attr("class", "left-chevron");
leftChevron.append("line")
.attr("x1", 5)
.attr("y1", -5)
.attr("x2", 0)
.attr("y2", 0)
.attr("stroke", "#444")
.style("stroke-width", "2");
leftChevron.append("line")
.attr("x1", 0)
.attr("y1", 0)
.attr("x2", 5)
.attr("y2", 5)
.attr("stroke", "#444")
.style("stroke-width", "2");
// Transition nodes to their new position.
var nodeUpdate = node.transition()
.duration(duration)
.attr("transform", function(d) {
if (d.parent == "null") {
d.y = oldly;
d.x = oldlx;
}
return "translate(" + d.y + "," + d.x + ")";
});
nodeUpdate.select("text")
.style("fill-opacity", 1);
// Transition exiting nodes to the parent's new position.
var nodeExit = node.exit().transition()
.duration(duration)
.attr("transform", function(d) {
return "translate(" + source.y + "," + source.x + ")";
})
.remove();
// nodeExit.select("circle")
// .attr("r", 1e-6);
nodeExit.select("text")
.style("fill-opacity", 1e-6);
var edge = d3.select("#left-edges")
.append("g")
.attr("class", "edge-container")
.data(links);
var text = edge.selectAll("text.text-link")
.data(links, function(d) {
return d.target.id + d.source.id;
});
text.enter().append("text")
.attr("class", "text-link")
.attr("x", function(d) {
var x = (d.source.y + d.target.y) / 2
return parseInt(x + 45);
})
.attr("y", function(d) {
var y = (d.source.x + d.target.x) / 2
return y;
})
.text("test-label")
.attr("text-anchor", "start")
.style("font-size", "12px");
/* transition labels to new positions */
var label = svg.selectAll("text.text-link")
.data(links, function(d) {
return d.target.id;
});
// Transition links to their new position.
label.transition()
.duration(0);
// Transition exiting nodes to the parent's new position.
label.exit().transition()
.duration(duration)
.attr("d", function(d) {
var o = {
x: source.x,
y: source.y
};
return diagonal({
source: o,
target: o
});
})
.remove();
var link = edge.selectAll("path.link")
.data(links, function(d) {
return d.target.id;
});
// Update the links…
// Enter any new links at the parent's previous position.
link.enter().insert("path", "edge-container")
.attr("class", "link")
.attr("id", function(d) {
return ("leftlink" + d.source.id + "-" + d.target.id)
})
.attr("d", function(d) {
var o = {
x: source.x0,
y: source.y0
};
return diagonal({
source: o,
target: o
});
}).on("click", removelink);
function removelink(d) {
var confirmDelete = confirm("Are you sure you want to delete?");
if (confirmDelete) {
//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(function(child) {
if (child.id != target.id) {
//add to teh 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
update(d.target.parent)
}
}
var link = svg.selectAll("path.link")
.data(links, function(d) {
return d.target.id;
});
// 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;
});
addLeftChild.on("click", function(d) {
event.stopPropagation();
$("#child-info").show();
$("#child-text").val("");
$("#btn-add-child").off('click');
$("#btn-add-child").click(function() {
var childname = $("#child-text").val();
if (typeof d._children === 'undefined' || d._children === null) {
if (typeof d.children === 'undefined') {
var newChild = [{
"name": childname,
"parent": "Son Of A",
}];
// console.log(tree.nodes(newChild[0]));
var newnodes = tree.nodes(newChild);
d.children = newnodes[0];
// console.log(d.children);
update(d);
} else {
var newChild = {
"name": childname,
"parent": "Son Of A",
};
// console.log(d.children);
d.children.push(newChild);
// console.log(d.children);
update(d);
}
} else {
var newChild = {
"name": childname,
"parent": "Son Of A",
};
console.log("collapsed case");
d.children = d._children;
d.children.push(newChild);
// console.log(d.children);
update(d);
}
$("#child-info").hide();
});
});
}
// Toggle children on click.
function click(d) {
if (d.id !== 1) {
if (d.children) {
d._children = d.children;
d.children = null;
} else {
d.children = d._children;
d._children = null;
}
update(d);
}
$(".addLeftChild, .addRightChild").hide();
if (d.id === 1) {
$(".rightparent").children(".addRightChild").show();
$(this).children(".addLeftChild").show();
} else {
$(this).children(".addLeftChild").show();
}
d3.selectAll("rect").style("fill", "#f1f1f1"); //reset all node colors
d3.selectAll("path").style("stroke", "#85e0e0"); //reset the color for all links
while (d.parent) {
d3.selectAll("#leftnode" + d.id).style("fill", "#F7CA18"); //color the node
if (d.parent != "null")
d3.selectAll("#leftlink" + d.parent.id + "-" + d.id).style("stroke", "F7CA18"); //color the path
d = d.parent;
}
}
}
body {
margin: 0;
padding: 0;
}
#child-info {
width: 100px;
height: 50px;
height: auto;
position: fixed;
padding: 10px;
display: none;
left: 40%;
z-index: 100;
}
.asset-title {
padding: 15px;
float: left;
}
.node {
cursor: pointer;
}
.node text {
font: 12px sans-serif;
}
.link {
fill: none;
stroke: #85e0e0;
stroke-width: 2px;
}
.rightparent>rect {
display: none;
}
.leftparent>rect {
fill: #f1f1f1;
stroke: #ccc;
stroke-width: 2;
}
.leftparent .left-chevron {
display: none;
}
.leftparent image {
display: none;
}
.addLeftChild,
.addRightChild {
display: none;
}
.left-chevron,
.right-chevron {
display: none;
}
.collapsed .left-chevron,
.collapsed .right-chevron {
display: block;
}
<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>
<div id="child-info" style="display:none">
<input type="text" id="child-text" placeholder="child name">
<button id="btn-add-child">add</button>
</div>
<div class="doubletree-container">
</div>
<script type="text/javascript">
$(document).ready(function() {
$(".doubletree-container").makeDoubleTree();
});
</script>
I've added to your code. Below is the result. The main part was this :
.attr("x", function(d) {
var x = (d.source.y + d.target.y) / 2;
d.thisText = "test-label";
return x - d.thisText.length/2 + 5; //added 5 to make up for node width. This needs to be worked out properly
})
It's not exactly center as I haven't implemented the different between the center of the node and the label. But changing the above piece of code will get the result you want :) Hope this helps.
var treeData = [{
"name": "Device",
"parent": "null"
}
];
var treeData2 = [{
"name": "Device",
"parent": "null"
}
];
$(document).ready(function($) {
var margin = {
top: 20,
right: 120,
bottom: 20,
left: 120
},
width = 2260 - margin.right - margin.left,
height = 500 - margin.top - margin.bottom;
var svg = d3.select('.doubletree-container').append("svg")
.attr("width", width + margin.right + margin.left)
.attr("height", height + margin.top + margin.bottom);
$.fn.makeDoubleTree = function() {
this.makeRightTree();
this.makeLeftTree();
};
}(jQuery));
$.fn.makeRightTree = function() {
var margin = {
top: 20,
right: 120,
bottom: 20,
left: 120
},
width = 1260 - margin.right - margin.left,
height = 500 - margin.top - margin.bottom;
var i = 0,
duration = 750,
root;
var tree = d3.layout.tree()
.size([height, width]);
var diagonal = d3.svg.diagonal()
.projection(function(d) {
return [d.y, d.x];
});
var svg = d3.select("svg").append("g")
.attr("class", "right-tree-container")
.attr("transform", "translate(600,0)");
svg.append("g").attr("id", "right-edges")
root = treeData[0];
oldrx = root.x0 = height / 2;
oldry = root.y0 = 0;
update(root);
function update(source) {
var nodes = tree.nodes(root).reverse(),
links = tree.links(nodes);
nodes.forEach(function(d) {
d.y = d.depth * 180;
});
// 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", function(d) {
console.log(d);
if (d.parent == "null") {
if (d.children)
{
return "node rightparent collapsed" //since its root its parent is null
} else {
return "node rightparent"
}
} else {
if (d.children) {
return "node rightchild collapsed" //all nodes with parent will have this class
} else {
return "node rightchild" //all nodes with parent will have this class
}
}
})
.attr("transform", function(d) {
return "translate(" + source.y0 + "," + source.x0 + ")";
})
.on("click", click);
nodeEnter.append("rect")
.attr("id", function(d) {
return "rightnode" + d.id;
})
.attr("x", "-10")
.attr("y", "-15")
.attr("height", 30)
.attr("width", 100)
.attr("rx", 15)
.attr("ry", 15)
.style("fill", "#f1f1f1");
nodeEnter.append("image")
.attr("xlink:href", "img.png")
.attr("x", "0")
.attr("y", "-10")
.attr("width", 16)
.attr("height", 16);
nodeEnter.append("text")
.attr("x", function(d) {
return d.children || d._children ? 0 : 0;
})
.attr("dy", ".35em")
.attr("text-anchor", function(d) {
return d.children || d._children ? "end" : "start";
})
.text(function(d) {
return d.name;
})
.style("fill-opacity", 1e-6);
var addRightChild = nodeEnter.append("g").attr("class", "addRightChild");
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");
// adding the right chevron
var rightChevron = nodeEnter.append("g").attr("class", "right-chevron");
rightChevron.append("line")
.attr("x1", 75)
.attr("y1", -5)
.attr("x2", 80)
.attr("y2", 0)
.attr("stroke", "#444")
.style("stroke-width", "2");
rightChevron.append("line")
.attr("x1", 80)
.attr("y1", 0)
.attr("x2", 75)
.attr("y2", 5)
.attr("stroke", "#444")
.style("stroke-width", "2");
rightChevron.on("click", function(d) {
});
// Transition nodes to their new position.
var nodeUpdate = node.transition()
.duration(duration)
.attr("transform", function(d) {
if (d.parent == "null") {
d.y = oldry;
d.x = oldrx;
}
return "translate(" + d.y + "," + d.x + ")";
});
nodeUpdate.select("text")
.style("fill-opacity", 1);
// Transition exiting nodes to the parent's new position.
var nodeExit = node.exit().transition()
.duration(duration)
.attr("transform", function(d) {
return "translate(" + source.y + "," + source.x + ")";
})
.remove();
nodeExit.select("text")
.style("fill-opacity", 1e-6);
// Update the links…
var edge = d3.select("#right-edges")
.append("g")
.attr("class", "edge-container")
.data(links);
var link = edge.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", "edge-container")
.attr("class", "link")
.attr("id", function(d) {
return ("rightlink" + d.source.id + "-" + d.target.id)
})
.attr("d", function(d) {
var o = {
x: source.x0,
y: source.y0
};
return diagonal({
source: o,
target: o
});
}).on("click", removelink)
.on("mouseover", showRemoveButton)
.on("mouseout", hideRemoveButton);
function showRemoveButton() {
console.log("hover");
}
function hideRemoveButton() {
console.log("hover-out");
}
var text = edge.selectAll("text.text-link")
.data(links, function(d) {
return d.target.id + d.source.id;
});
text.enter().insert("text", "edge-container")
.attr("class", "text-link")
.attr("x", function(d) {
var x = (d.target.y + d.source.y) / 2
return x - 50;
})
.attr("y", function(d) {
var y = (d.source.x + d.target.x) / 2
return y;
})
.text("test-label")
.attr("text-anchor", "start")
.style("font-size", "12px");
/* transition labels to new positions */
var label = svg.selectAll("text.text-link")
.data(links, function(d) {
return d.target.id;
});
// Transition links to their new position.
label.transition()
.duration(0);
// Transition exiting nodes to the parent's new position.
label.exit().transition()
.duration(duration)
.attr("d", function(d) {
var o = {
x: source.x,
y: source.y
};
return diagonal({
source: o,
target: o
});
})
.remove();
// d3.selectAll(".right-tree-container .remove-child").remove();
// var removeButton = edge.selectAll(".remove-child")
// .data(links, function(d) {
// return d.target.id + d.source.id;
// });
// removeButton.enter().insert("rect", "edge-container")
// .attr("class", "remove-child")
// .attr("x", function(d) {
// var x = (d.source.y + d.target.y) / 2
// return parseInt(x + 45);
// })
// .attr("y", function(d) {
// var y = (d.source.x + d.target.x) / 2
// return y;
// })
// .attr("width","20")
// .attr("height","20")
// .attr("rx","10")
// .attr("ry","10")
// .style("fill","white")
// .style("stroke","#444")
// .style("stroke-width","2");
function removelink(d) {
var confirmDelete = confirm("Are you sure you want to delete?");
if (confirmDelete) {
//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(function(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
update(d.target.parent)
}
}
var link = svg.selectAll("path.link")
.data(links, function(d) {
return d.target.id;
});
// 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;
});
addRightChild.on("click", function(d) {
event.stopPropagation();
$("#child-info").show();
$("#child-text").val("");
$("#btn-add-child").off('click');
$("#btn-add-child").click(function() {
var childname = $("#child-text").val();
if (typeof d._children === 'undefined' || d._children === null) {
if (typeof d.children === 'undefined') {
var newChild = [{
"name": childname,
"parent": "Son Of A",
}];
var newnodes = tree.nodes(newChild);
d.children = newnodes[0];
// console.log(d.children);
update(d);
} else {
var newChild = {
"name": childname,
"parent": "Son Of A",
};
// console.log(d.children);
d.children.push(newChild);
// console.log(d.children);
update(d);
}
} else {
var newChild = {
"name": childname,
"parent": "Son Of A",
};
d.children = d._children;
d.children.push(newChild);
// console.log(d.children);
update(d);
}
$("#child-info").hide();
});
});;
}
// 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);
$(".addLeftChild, .addRightChild").hide();
if (d.id === 1) {
$(".rightparent").children(".addRightChild").show();
$(this).children(".addRightChild").show();
} else {
$(this).children(".addRightChild").show();
}
d3.selectAll("rect").style("fill", "#f1f1f1"); //reset all node colors
d3.selectAll("path").style("stroke", "#85e0e0"); //reset the color for all links
while (d.parent) {
d3.select("#leftnode1").style("fill", "#F7CA18");
d3.selectAll("#rightnode" + d.id).style("fill", "#F7CA18"); //color the node
if (d.parent != "null")
d3.selectAll("#rightlink" + d.parent.id + "-" + d.id).style("stroke", "#F7CA18"); //color the path
d = d.parent;
}
}
}
$.fn.makeLeftTree = function() {
// ************** Generate the tree diagram *****************
var margin = {
top: 20,
right: 120,
bottom: 20,
left: 120
},
width = 1260 - margin.right - margin.left,
height = 500 - margin.top - margin.bottom;
var i = 0,
duration = 750,
root;
var tree = d3.layout.tree()
.size([height, width]);
var diagonal = d3.svg.diagonal()
.projection(function(d) {
return [d.y, d.x];
});
var svg = d3.select("svg").append("g")
.attr("class", "left-tree-container")
.attr("transform", "translate(-421,0)");
svg.append("g").attr("id", "left-edges")
root = treeData2[0];
oldlx = root.x0 = height / 2;
oldly = root.y0 = width;
update(root);
function update(source) {
// Compute the new tree layout.
var nodes = tree.nodes(root).reverse(),
links = tree.links(nodes);
// Normalize for fixed-depth.
nodes.forEach(function(d) {
d.y = width - (d.depth * 180);
});
// 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", function(d) {
if (d.parent == "null") {
return "node leftparent" //since its root its parent is null
} else
return "node leftchild" //all nodes with parent will have this class
})
.attr("transform", function(d) {
return "translate(" + source.y0 + "," + source.x0 + ")";
})
.on("click", click);
nodeEnter.append("rect")
.attr("x", "-10")
.attr("id", function(d) {
return "leftnode" + d.id;
})
.attr("y", "-15")
.attr("height", 30)
.attr("width", 100)
.attr("rx", 15)
.attr("ry", 15)
.style("fill", "#f1f1f1");
nodeEnter.append("image")
.attr("xlink:href", "img.png")
.attr("x", "60")
.attr("y", "-10")
.attr("width", 16)
.attr("height", 16);
nodeEnter.append("text")
.attr("x", function(d) {
return d.children || d._children ? -13 : 13;
})
.attr("dy", ".35em")
.attr("text-anchor", function(d) {
return d.children || d._children ? "end" : "start";
})
.text(function(d) {
return d.name;
})
.style("fill-opacity", 1e-6);
var addLeftChild = nodeEnter.append("g").attr("class", "addLeftChild");
addLeftChild.append("rect")
.attr("x", "-30")
.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");
addLeftChild.append("line")
.attr("x1", -25)
.attr("y1", 1)
.attr("x2", -15)
.attr("y2", 1)
.attr("stroke", "#444")
.style("stroke-width", "2");
addLeftChild.append("line")
.attr("x1", -20)
.attr("y1", -4)
.attr("x2", -20)
.attr("y2", 6)
.attr("stroke", "#444")
.style("stroke-width", "2");
var leftChevron = nodeEnter.append("g").attr("class", "left-chevron");
leftChevron.append("line")
.attr("x1", 5)
.attr("y1", -5)
.attr("x2", 0)
.attr("y2", 0)
.attr("stroke", "#444")
.style("stroke-width", "2");
leftChevron.append("line")
.attr("x1", 0)
.attr("y1", 0)
.attr("x2", 5)
.attr("y2", 5)
.attr("stroke", "#444")
.style("stroke-width", "2");
// Transition nodes to their new position.
var nodeUpdate = node.transition()
.duration(duration)
.attr("transform", function(d) {
if (d.parent == "null") {
d.y = oldly;
d.x = oldlx;
}
return "translate(" + d.y + "," + d.x + ")";
});
nodeUpdate.select("text")
.style("fill-opacity", 1);
// Transition exiting nodes to the parent's new position.
var nodeExit = node.exit().transition()
.duration(duration)
.attr("transform", function(d) {
return "translate(" + source.y + "," + source.x + ")";
})
.remove();
// nodeExit.select("circle")
// .attr("r", 1e-6);
nodeExit.select("text")
.style("fill-opacity", 1e-6);
var edge = d3.select("#left-edges")
.append("g")
.attr("class", "edge-container")
.data(links);
var text = edge.selectAll("text.text-link")
.data(links, function(d) {
return d.target.id + d.source.id;
});
text.enter().append("text")
.attr("class", "text-link")
.attr("x", function(d) {
var x = (d.source.y + d.target.y) / 2;
d.thisText = "test-label";
return x - d.thisText.length/2 + 5;
})
.attr("y", function(d) {
var y = (d.source.x + d.target.x) / 2
return y;
})
.text(function(d){return d.thisText})
.attr("text-anchor", "start")
.style("font-size", "12px");
/* transition labels to new positions */
var label = svg.selectAll("text.text-link")
.data(links, function(d) {
return d.target.id;
});
// Transition links to their new position.
label.transition()
.duration(0);
// Transition exiting nodes to the parent's new position.
label.exit().transition()
.duration(duration)
.attr("d", function(d) {
var o = {
x: source.x,
y: source.y
};
return diagonal({
source: o,
target: o
});
})
.remove();
var link = edge.selectAll("path.link")
.data(links, function(d) {
return d.target.id;
});
// Update the links…
// Enter any new links at the parent's previous position.
link.enter().insert("path", "edge-container")
.attr("class", "link")
.attr("id", function(d) {
return ("leftlink" + d.source.id + "-" + d.target.id)
})
.attr("d", function(d) {
var o = {
x: source.x0,
y: source.y0
};
return diagonal({
source: o,
target: o
});
}).on("click", removelink);
function removelink(d) {
var confirmDelete = confirm("Are you sure you want to delete?");
if (confirmDelete) {
//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(function(child) {
if (child.id != target.id) {
//add to teh 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
update(d.target.parent)
}
}
var link = svg.selectAll("path.link")
.data(links, function(d) {
return d.target.id;
});
// 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;
});
addLeftChild.on("click", function(d) {
event.stopPropagation();
$("#child-info").show();
$("#child-text").val("");
$("#btn-add-child").off('click');
$("#btn-add-child").click(function() {
var childname = $("#child-text").val();
if (typeof d._children === 'undefined' || d._children === null) {
if (typeof d.children === 'undefined') {
var newChild = [{
"name": childname,
"parent": "Son Of A",
}];
// console.log(tree.nodes(newChild[0]));
var newnodes = tree.nodes(newChild);
d.children = newnodes[0];
// console.log(d.children);
update(d);
} else {
var newChild = {
"name": childname,
"parent": "Son Of A",
};
// console.log(d.children);
d.children.push(newChild);
// console.log(d.children);
update(d);
}
} else {
var newChild = {
"name": childname,
"parent": "Son Of A",
};
console.log("collapsed case");
d.children = d._children;
d.children.push(newChild);
// console.log(d.children);
update(d);
}
$("#child-info").hide();
});
});
}
// Toggle children on click.
function click(d) {
if (d.id !== 1) {
if (d.children) {
d._children = d.children;
d.children = null;
} else {
d.children = d._children;
d._children = null;
}
update(d);
}
$(".addLeftChild, .addRightChild").hide();
if (d.id === 1) {
$(".rightparent").children(".addRightChild").show();
$(this).children(".addLeftChild").show();
} else {
$(this).children(".addLeftChild").show();
}
d3.selectAll("rect").style("fill", "#f1f1f1"); //reset all node colors
d3.selectAll("path").style("stroke", "#85e0e0"); //reset the color for all links
while (d.parent) {
d3.selectAll("#leftnode" + d.id).style("fill", "#F7CA18"); //color the node
if (d.parent != "null")
d3.selectAll("#leftlink" + d.parent.id + "-" + d.id).style("stroke", "F7CA18"); //color the path
d = d.parent;
}
}
}
body {
margin: 0;
padding: 0;
}
#child-info {
width: 100px;
height: 50px;
height: auto;
position: fixed;
padding: 10px;
display: none;
left: 40%;
z-index: 100;
}
.asset-title {
padding: 15px;
float: left;
}
.node {
cursor: pointer;
}
.node text {
font: 12px sans-serif;
}
.link {
fill: none;
stroke: #85e0e0;
stroke-width: 2px;
}
.rightparent>rect {
display: none;
}
.leftparent>rect {
fill: #f1f1f1;
stroke: #ccc;
stroke-width: 2;
}
.leftparent .left-chevron {
display: none;
}
.leftparent image {
display: none;
}
.addLeftChild,
.addRightChild {
display: none;
}
.left-chevron,
.right-chevron {
display: none;
}
.collapsed .left-chevron,
.collapsed .right-chevron {
display: block;
}
<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>
<div id="child-info" style="display:none">
<input type="text" id="child-text" placeholder="child name">
<button id="btn-add-child">add</button>
</div>
<div class="doubletree-container">
</div>
<script type="text/javascript">
$(document).ready(function() {
$(".doubletree-container").makeDoubleTree();
});
</script>
You would need to do the same with the Y. But instead of taking away the textLength/2, just take away the textHeight/2 if you want it exactly central

d3.js parallel coordinate with one different scale

I'm trying to generate a parallel coordinate using d3.js
My problem is that the first scale should display different strings.
with the original code it looks like this:
and with my test it looks like this (no lines):
the error code is:
Error: Invalid value for attribute d="M33,NaNL99,161.37817638266068L165,6.543121881682145L231,16.962488563586458L297,180"
here is my code:
function parallelChart (id, size) {
if(size == 'small') {
var margin = {top: 20, right: 80, bottom: 30, left: 50},
width = 460 - margin.left - margin.right,
height = 230 - margin.top - margin.bottom;
} else {
var margin = {top: 20, right: 80, bottom: 30, left: 50},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
}
var x = d3.scale.ordinal().rangePoints([0, width], 1),
y = {},
dragging = {};
var line = d3.svg.line(),
axis = d3.svg.axis().orient("left"),
background,
foreground;
var svg = d3.select(id).append("svg")
.attr("class", 'center-block')
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
// Original
d3.csv("dataNew.csv", function(error, healthdata) {
x.domain(dimensions = d3.keys(healthdata[0]).filter(function(d) {
return d != "Datum" && (y[d] = d3.scale.linear()
.domain(d3.extent(healthdata, function(p) { return +p[d]; }))
.range([height, 0]));
}));
// this did not work
// d3.csv("dataNew.csv", function(error, healthdata) {
// x.domain(dimensions = d3.keys(healthdata[0]).filter(function(d) {
// if(d == "Datum") {
// return d == "Datum" && ( (y[d] = d3.time.scale()
// .domain(d3.extent(healthdata, function(p) { return +p[d]; }))
// .range([height, 0])));
// }
// return d != "Datum" && ( (y[d] = d3.scale.linear()
// .domain(d3.extent(healthdata, function(p) { return +p[d]; }))
// .range([height, 0])));
// }));
// Add grey background lines for context.
background = svg.append("g")
.attr("class", "background")
.selectAll("path")
.data(healthdata)
.enter().append("path")
.attr("d", path);
// Add blue foreground lines for focus.
foreground = svg.append("g")
.attr("class", "foreground")
.selectAll("path")
.data(healthdata)
.enter().append("path")
.attr("d", path);
// Add a group element for each dimension.
var g = svg.selectAll(".dimension")
.data(dimensions)
.enter().append("g")
.attr("class", "dimension")
.attr("transform", function(d) { return "translate(" + x(d) + ")"; })
.call(d3.behavior.drag()
.origin(function(d) { return {x: x(d)}; })
.on("dragstart", function(d) {
dragging[d] = x(d);
background.attr("visibility", "hidden");
})
.on("drag", function(d) {
dragging[d] = Math.min(width, Math.max(0, d3.event.x));
foreground.attr("d", path);
dimensions.sort(function(a, b) { return position(a) - position(b); });
x.domain(dimensions);
g.attr("transform", function(d) { return "translate(" + position(d) + ")"; })
})
.on("dragend", function(d) {
delete dragging[d];
transition(d3.select(this)).attr("transform", "translate(" + x(d) + ")");
transition(foreground).attr("d", path);
background
.attr("d", path)
.transition()
.delay(500)
.duration(0)
.attr("visibility", null);
}));
// Add an axis and title.
g.append("g")
.attr("class", "axis")
.each(function(d) { d3.select(this).call(axis.scale(y[d])); })
.append("text")
.style("text-anchor", "middle")
.attr("y", -9)
.text(function(d) { return d; });
// Add and store a brush for each axis.
g.append("g")
.attr("class", "brush")
.each(function(d) {
d3.select(this).call(y[d].brush = d3.svg.brush().y(y[d]).on("brushstart", brushstart).on("brush", brush));
})
.selectAll("rect")
.attr("x", -8)
.attr("width", 16);
});
function position(d) {
var v = dragging[d];
return v == null ? x(d) : v;
}
function transition(g) {
return g.transition().duration(500);
}
// Returns the path for a given data point.
function path(d) {
return line(dimensions.map(function(p) { return [position(p), y[p](d[p])]; }));
}
function brushstart() {
d3.event.sourceEvent.stopPropagation();
}
// Handles a brush event, toggling the display of foreground lines.
function brush() {
var actives = dimensions.filter(function(p) { return !y[p].brush.empty(); }),
extents = actives.map(function(p) { return y[p].brush.extent(); });
foreground.style("display", function(d) {
return actives.every(function(p, i) {
return extents[i][0] <= d[p] && d[p] <= extents[i][1];
}) ? null : "none";
});
}
}
Here goes one example where the author manage string and numbers in the same parallel coord:
http://bl.ocks.org/syntagmatic/4020926
Create an array of dimensions that will be further used...
var dimensions = [
{
name: "name",
scale: d3.scale.ordinal().rangePoints([0, height]),
type: "string"
},
{
name: "economy (mpg)",
scale: d3.scale.linear().range([0, height]),
type: "number"
},
...
]
...before load the data, define the domains by mapping your previous dimensions definition...
var x = d3.scale.ordinal()
.domain(dimensions.map(function(d) { return d.name; }))
.rangePoints([0, width]);
...define a variable dimension (pay attention, dimensions != dimension) with the locations of each axis...
var dimension = svg.selectAll(".dimension")
.data(dimensions)
.enter().append("g")
.attr("class", "dimension")
.attr("transform", function(d) { return "translate(" + x(d.name) + ")"; });
...once the data is loaded, execute a for each to define the domain of each dimension...
d3.csv("cars.small.csv", function(data) {
dimensions.forEach(function(dimension) {
dimension.scale.domain(dimension.type === "number"
? d3.extent(data, function(d) { return +d[dimension.name]; })
: data.map(function(d) { return d[dimension.name]; }).sort());
});
...
}
... axis lines and foreground are still loaded in the same way...
svg.append("g")
.attr("class", "background")
.selectAll("path")
.data(data)
.enter().append("path")
.attr("d", draw);
svg.append("g")
.attr("class", "foreground")
.selectAll("path")
.data(data)
.enter().append("path")
.attr("d", draw);
... this code will load the text of each axis, observe that it is now using properties from the dimensions that we defined in the beggining.
dimension.append("g")
.attr("class", "axis")
.each(function(d) { d3.select(this).call(yAxis.scale(d.scale)); })
.append("text")
.attr("class", "title")
.attr("text-anchor", "middle")
.attr("y", -9)
.text(function(d) { return d.name; });
that`s all =).

Resources