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>
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
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 =).