I new in js and can't solve problem i faced by myself, so I will be glad for any advices or helps.
In my project I created force layout graph.
What I should do is to add zoom in and zoom out functionality for my graph.
I created little example to show what problem I faced.
function myGraph(el){
this.addNode = function (id, category, opened, parentT, name, nodeType, countLinks, dob, country, childsCount) {
var parent = [];
parent.push(parentT);
nodes.push({ "id": id, "category": category, "opened": opened, "parent": parent, "name": name, "nodeType": nodeType, "countLinks": countLinks, "dob": dob, "country": country, "childCount": childsCount });
update();
}
this.addLink = function (sourceId, targetId, type) {
var sourceNode = findNode(sourceId);
var targetNode = findNode(targetId);
if ((sourceNode !== undefined) && (targetNode !== undefined)) {
links.push({ "source": sourceNode, "target": targetNode, "type": type });
update();
}
}
var findNode = function (id) {
for (var i = 0; i < nodes.length; i++) {
if (nodes[i].id === id)
return nodes[i]
};
}
var w = 360,
h = 200;
var vis = this.vis = d3.select(el).append("svg:svg")
.attr("id", "mySvg")
.attr("width", w)
.attr("height", h)
.attr("viewBox", "0 0 " + w + " " + h)
.call(d3.behavior.zoom().scaleExtent([-0.5, 1]).on("zoom", redraw))
var g = vis.append('svg:g')
function redraw(e) {
if (!e) e = window.event;
if (e.type == "wheel" || e.shiftKey == true) {
g.attr("transform", "translate(" + d3.event.translate + ")scale(" + d3.event.scale + ")");
}
}
var force = d3.layout.force()
.gravity(0.05)
.distance(100)
.charge(-200)
.size([w, h]);
var nodes = force.nodes(),
links = force.links();
var hiddenType = [];
g.append("g").attr("class", "links");
g.append("g").attr("class", "nodes");
var update = function () {
for (var i=0; i<links.length; i++) {
if (i != 0 &&
links[i].source == links[i-1].source &&
links[i].target == links[i-1].target) {
links[i].linknum = links[i-1].linknum + 1;
}
else {links[i].linknum = 1;};
};
var myLink = g.select(".links").selectAll(".link");
myLink.remove();
var link = g.select(".links").selectAll(".link")
.data(links);
link.enter().append("polyline")
.attr("class", "link")
.attr("type", function (d) { return "link_" + d.type; })
link.exit().remove();
var node = g.select(".nodes").selectAll(".node")
.data(nodes, function (d) { return d.id; })
var nodeEnter = node.enter().append("g")
.attr("class", "node")
.attr("links", function (d) { return d.countLinks })
.attr("type", function (d) { return d.nodeType })
.on("mousedown", mousedown)
.call(force.drag);
nodeEnter.append("circle")
.attr("r", "10px")
.attr("x", "-8px")
.attr("y", "-8px")
.attr("width", "16px")
.attr("height", "16px")
.style("fill", "white")
.style("stroke", "#ccc")
nodeEnter.append("rect")
.attr("pointer-events", "none")
.attr("class", "shape")
.attr("width", "250px")
.attr("height", "150px")
.style("fill", "transparent")
nodeEnter.append("text")
.attr("pointer-events", "none")
.attr("class", function (d) { return "nodetext_" + d.id })
.attr("y", "-10")
.text(function (d) { return d.id })
nodeEnter.append("title")
.text(function (d) { return d.id });
node.exit().remove();
function mousedown(d) {
d.fixed = true;
}
force.on("tick", function () {
link.attr("points", function(d){
if (d.source.y > d.target.y) {
return d.source.x + "," + d.source.y + " " + (d.source.x + ((d.target.x - d.source.x) / 2)) + "," + (d.target.y + ((d.source.y - d.target.y) / 2) + (15 * (d.linknum - 1))) + " " + d.target.x + "," + d.target.y;
}
if (d.source.y < d.target.y) {
return d.source.x + "," + d.source.y + " " + (d.source.x + ((d.target.x - d.source.x) / 2)) + "," + (d.source.y + ((d.target.y - d.source.y) / 2) + (15 * (d.linknum - 1))) + " " + d.target.x + "," + d.target.y;
}
})
node.attr("transform", function (d) {
if (d.fixed !== true) {
return "translate(" + d.x + "," + d.y + ")";
}
else {
return "translate(" + d.px + "," + d.py + ")";
}
});
});
force.start();
}
update();
}
graph = new myGraph("#graph");
graph.addNode("A")
graph.addNode("B")
graph.addNode("C")
graph.addLink("A", "B")
graph.addLink("A", "C")
https://jsfiddle.net/IMykytiv/nsxL8c52/
So problem is:
In my graph users can drag nodes in any direction, after that node's position becomes fixed. When I added zooming I can't drag nodes because zooming works first, so I added condition that zoom works only when shift key is pressed. So now I can both drag nodes and zoom.
But now I faced problem that if I drag node and try to zoom with mouse wheel it changed transform not from mouse position but from another point and I can't understand why. If I remove shift key condition zooming works as expected.
I finaly found solution and it was preaty easy.
Instead of adding condition with shift key pressed all I need to do is to prevent trigger zoom function on start draggin node event:
var drag = force.drag()
.on("dragstart", function (d) {
d3.event.sourceEvent.stopPropagation();
});
Updated jsfiddle here:
https://jsfiddle.net/IMykytiv/nsxL8c52/2/
Hope that it will be useful for anyone :)
Related
I was creating a radial tree and trying to download it to SVG but it has some issues
It downloads with those black thick strokes which are not displayed on the webpage.
Any idea where the black lines are coming from?
var svg = d3.select("svg"),
width = +svg.attr("width"),
height = +svg.attr("height"),
g = svg.append("g").attr("transform", "translate(" + (width / 2 - 15) + ","
+ (height / 2 + 25) + ")");
var stratify = d3.stratify()
.parentId(function(d) {
console.log(d.id.substring(0, d.id.lastIndexOf(".")));
return d.id.substring(0, d.id.lastIndexOf(".")); });
var tree = d3.cluster()
tree.size([360, 360])
tree.separation(function(a, b) { return (a.parent == b.parent ? 1 : 2) /
a.depth; });
d3.csv("flare.csv", function(error, data) {
if (error) throw error;
var root = tree(stratify(data)
.sort(function(a, b) { return (a.height - b.height + 100) ||
a.id.localeCompare(b.id); }));
var link = g.selectAll(".link")
.data(root.descendants().slice(1))
.enter().append("path")
.attr("class", "link")
.attr("d", function(d) {
return "M" + project(d.x, d.y)
+ "C" + project(d.x, (d.y + d.parent.y) / 2)
+ " " + project(d.parent.x, (d.y + d.parent.y) / 2)
+ " " + project(d.parent.x, d.parent.y);
});
link.attr('stroke', function(d) {
if (d.id.startsWith("Mother.Biological")){
return "#386eff";
}
if (d.id.startsWith("Mother.Env")){
return "#45cbf2";
}
else return '#70f2ad';
});
var node = g.selectAll(".node")
.data(root.descendants())
.enter().append("g")
.attr("class", function(d) { return "node" + (d.children ? " node--
internal" : " node--leaf"); })
.attr("transform", function(d) { return "translate(" + project(d.x,
d.y)
+ ")"; });
node.append("circle")
.attr("r", function(d) {
console.log(d.value);
if (d.id == "Mother") return 4.4;
else return 2.4;
} )
.style('fill', function(d) {
if (d.id.startsWith("Mother.Biological")){
return "#386eff";
}
if (d.id.startsWith("Mother.Env")){
return "#45cbf2";
}
if (d.id.startsWith("Mother.Biological")){
return "#386eff";
}
if (d.id.startsWith("Mother.Form")){
return '#70f2ad';
}
d.color = 'red';
return d.color});
node.append("text")
.attr("dy", ".31em")
.attr("x", function(d) { return d.x < 180 === !d.children ? 6 : -6; })
.style("text-anchor", function(d) { return d.x < 180 === !d.children ?
"start" : "end"; })
.attr("transform", function(d) { return "rotate(" + (d.x < 180 ? d.x - 90
: d.x + 90) + ")"; })
.text(function(d) { return d.id.substring(d.id.lastIndexOf(".") + 1); });
});
function project(x, y) {
var angle = (x - 90) / 180 * Math.PI, radius = y;
return [radius * Math.cos(angle), radius * Math.sin(angle)];
}
d3.select("#download")
.on("mouseover", writeDownloadLink);
function writeDownloadLink(){
var html = d3.select("svg")
.attr("title", "svg_title")
.attr("version", 1.1)
.attr("xmlns", "http://www.w3.org/2000/svg")
.node().parentNode.innerHTML;
d3.select(this)
.attr("href-lang", "image/svg+xml")
.attr("href", "data:image/svg+xml;base64,\n" + btoa(html))
.on("mousedown", function(){
if(event.button != 2){
d3.select(this)
.attr("href", null)
.html("Use right click");
}
})
.on("mouseout", function(){
d3.select(this)
.html("Download");
});
};
It looks ok in any browser but when I download it and convert it to SVG or EFM it comes back with those lines.
Page that you provided contains style tag. I'm not sure how exactly do you export SVG, but some css rules seems to be lost along the way.
In similar case I managed to get current css properties and put them into style property:
d3.selectAll('...').each(function() {
var that = d3.select(this);
['stroke', 'stroke-width', 'opacity', 'fill', ...].forEach(function(property) {
that.style(property, that.style(property))
});
});
Hope this will help you.
I have a sunburst which has following layers of data
world=>continents=>countries=>fuels
NOw I want to include names of elements only until countries and not names of fuels. With my code I can add names of all elements in the dropdown but not sure how to remove names of fuels from the dropdown.
Fiddle: http://jsfiddle.net/8wd2xt9n/14/
Full code:
var width = 960,
height = 700,
radius = Math.min(width, height) / 2;
var b = {
w: 130,
h: 30,
s: 3,
t: 10
};
var x = d3.scale.linear()
.range([0, 2 * Math.PI]);
var y = d3.scale.sqrt()
.range([0, radius]);
var changeArray = [100, 80, 60, 0, -60, -80, -100];
var colorArray = ["#67000d", "#b73e43", "#d5464a", "#f26256", "#fb876e", "#fca78e", "#fcbba1", "#fee0d2", "#fff5f0"];
var svg = d3.select("#diagram").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + (height / 2 + 10) + ")");
var partition = d3.layout.partition()
.value(function (d) {
return d.Total;
});
var arc = d3.svg.arc()
.startAngle(function (d) {
return Math.max(0, Math.min(2 * Math.PI, x(d.x)));
})
.endAngle(function (d) {
return Math.max(0, Math.min(2 * Math.PI, x(d.x + d.dx)));
})
.innerRadius(function (d) {
return Math.max(0, y(d.y));
})
.outerRadius(function (d) {
return Math.max(0, y(d.y + d.dy));
});
console.log(arc)
function checkIt(error, root) {
initializeBreadcrumbTrail();
//intilaize dropdown
if (error) throw error;
var g = svg.selectAll("g")
.data(partition.nodes(root))
.enter().append("g");
var path = g.append("path")
.attr("d", arc)
.style("fill", function (d) {
var color;
if (d.Total_Change > 100) {
color = colorArray[0]
} else if (d.Total_Change > 0 && d.Total_Change < 100) {
color = colorArray[1]
} else {
color = colorArray[2]
}
d.color = color;
return color
})
.on("click", click)
.on("mouseover", mouseover);
var tooltip = d3.select("body")
.append("div")
.attr("id", "tooltips")
.style("position", "absolute")
.style("background-color", "#fff")
.style("z-index", "10")
.style("visibility", "hidden");
g.on("mouseover", function (d) {
return tooltip.style("visibility", "visible")
.html("<div class=" + "tooltip_container>" + "<h4 class=" + "tooltip_heading>" + d.name.replace(/[_-]/g, " ") + "</h4>" + "<br>" + "<p> Emissions 2013:" + " " + "<br>" + d.Total + " " + "<span>in Million Tons</span></p>" + "<br>" + "<p> Change in Emissions: <span>" + (d.Total_Change / d.Total * 100).toPrecision(3) + "%" + "</span></p>" + "</div>");
})
.on("mousemove", function () {
return tooltip.style("top", (d3.event.pageY - 10) + "px").style("left", (d3.event.pageX + 10) + "px");
})
.on("mouseout", function () {
return tooltip.style("visibility", "hidden");
});
//creating a dropdown
var dropDown = d3.select("#dropdown_container")
.append("select")
.attr("class", "selection")
.attr("name", "country-list");
var nodeArr = partition.nodes(root);
var options = dropDown.selectAll("option")
.data(nodeArr)
.enter()
.append("option");
options.text(function (d) {
var prefix = new Array(d.depth + 1);
var dropdownValues = d.name.replace(/[_-]/g, " ");
return dropdownValues;
}).attr("value", function (d) {
var dropdownValues = d.name;
return dropdownValues;
});
// transition on click
function click(d) {
// fade out all text elements
console.log(d)
path.transition()
.duration(750)
.attrTween("d", arcTween(d))
.each("end", function (e, i) {
// check if the animated element's data e lies within the visible angle span given in d
if (e.x >= d.x && e.x < (d.x + d.dx)) {
// get a selection of the associated text element
var arcText = d3.select(this.parentNode).select("text");
// fade in the text element and recalculate positions
arcText.transition().duration(750)
.attr("opacity", 1)
.attr("transform", function () {
return "rotate(" + computeTextRotation(e) + ")"
})
.attr("x", function (d) {
return y(d.y);
});
}
});
};
d3.select(".selection").on("change", function changePie() {
var value = this.value;
var index = this.selectedIndex;
var dataObj = nodeArr[index];
path[0].forEach(function (p) {
var data = d3.select(p).data(); //get the data from the path
if (data[0].name === value) {
console.log(data)
click(data[0]);//call the click function
}
});
console.log(this.value + " :c " + dataObj["Iron and steel"] + " in " + (dataObj.parent && dataObj.parent.name));
});
};
d3.json("https://gist.githubusercontent.com/heenaI/cbbc5c5f49994f174376/raw/743b3964d1dcc0b005ec2b024414877a36b0bd33/data.json", checkIt);
d3.select(self.frameElement).style("height", height + "px");
// Interpolate the scales!
function arcTween(d) {
var xd = d3.interpolate(x.domain(), [d.x, d.x + d.dx]),
yd = d3.interpolate(y.domain(), [d.y, 1]),
yr = d3.interpolate(y.range(), [d.y ? 20 : 0, radius]);
return function (d, i) {
return i ? function (t) {
return arc(d);
} : function (t) {
x.domain(xd(t));
y.domain(yd(t)).range(yr(t));
return arc(d);
};
};
}
function initializeBreadcrumbTrail() {
// Add the svg area.
var trail = d3.select("#sequence").append("svg:svg")
.attr("width", width)
.attr("height", 50)
.attr("id", "trail");
// Add the label at the end, for the percentage.
trail.append("svg:text")
.attr("id", "endlabel")
.style("fill", "#000");
}
function breadcrumbPoints(d, i) {
var points = [];
points.push("0,0");
points.push(b.w + ",0");
points.push(b.w + b.t + "," + (b.h / 2));
points.push(b.w + "," + b.h);
points.push("0," + b.h);
if (i > 0) { // Leftmost breadcrumb; don't include 6th vertex.
points.push(b.t + "," + (b.h / 2));
}
return points.join(" ");
}
// Update the breadcrumb trail to show the current sequence and percentage.
function updateBreadcrumbs(nodeArray) {
// Data join; key function combines name and depth (= position in sequence).
var g = d3.select("#trail")
.selectAll("g")
.data(nodeArray, function (d) {
return d.name.replace(/[_-]/g, " ") + d.Total;
});
// Add breadcrumb and label for entering nodes.
var entering = g.enter().append("svg:g");
entering.append("svg:polygon")
.attr("points", breadcrumbPoints)
.style("fill", "#d3d3d3");
entering.append("svg:text")
.attr("x", (b.w + b.t) / 2)
.attr("y", b.h / 2)
.attr("dy", "0.35em")
.attr("text-anchor", "middle")
.text(function (d) {
return d.name.replace(/[_-]/g, " ");
});
// Set position for entering and updating nodes.
g.attr("transform", function (d, i) {
return "translate(" + i * (b.w + b.s) + ", 0)";
});
// Remove exiting nodes.
g.exit().remove();
// Now move and update the percentage at the end.
d3.select("#trail").select("#endlabel")
.attr("x", (nodeArray.length + 0.5) * (b.w + b.s))
.attr("y", b.h / 2)
.attr("dy", "0.35em")
.attr("text-anchor", "middle");
// Make the breadcrumb trail visible, if it's hidden.
d3.select("#trail")
.style("visibility", "");
}
function getAncestors(node) {
var path = [];
var current = node;
while (current.parent) {
path.unshift(current);
current = current.parent;
}
return path;
}
function mouseover(d) {
var sequenceArray = getAncestors(d);
updateBreadcrumbs(sequenceArray);
}
Code that creates dropdown
/creating a dropdown
var dropDown = d3.select("#dropdown_container")
.append("select")
.attr("class", "selection")
.attr("name", "country-list");
var nodeArr = partition.nodes(root);
var options = dropDown.selectAll("option")
.data(nodeArr)
.enter()
.append("option");
options.text(function (d) {
var prefix = new Array(d.depth + 1);
var dropdownValues = d.name.replace(/[_-]/g, " ");
return dropdownValues;
}).attr("value", function (d) {
var dropdownValues = d.name;
return dropdownValues;
});
Data structure can be viewed here
As you are grabbing the values of the dropdown menu from the nodes of the partition, you have the depth of the nodes at hand when setting up the dropdown, thus you can filter:
var nodeArr = partition.nodes(root);
var options = dropDown.selectAll("option")
.data(nodeArr.filter(function(d){return d.depth < 3;}))
.enter()
.append("option");
I hope that helps!
(see fiddle)
I would just pre-process the data. In the original root structure you know the countries are 2 levels down so:
var countries = [];
root.children.forEach(function (c){
c.children.forEach(function (d){
countries.push(d.name.replace(/[_-]/g, " "));
});
});
And the dropdown becomes:
var options = dropDown.selectAll("option")
.data(countries)
.enter()
.append("option");
options.text(function (d) {
return d;
}).attr("value", function (d) {
return d;
});
Updated fiddle.
I have a sunburst for which I would like to initiate zoom ability via dropdown. That is when a country name is selected from the drop down menu its part in the sunburst zooms exactly like when its clicked.
js fiddle: http://jsfiddle.net/8wd2xt9n/7/
var width = 960,
height = 700,
radius = Math.min(width, height) / 2;
var b = {
w: 130, h: 30, s: 3, t: 10
};
var x = d3.scale.linear()
.range([0, 2 * Math.PI]);
var y = d3.scale.sqrt()
.range([0, radius]);
var changeArray = [100,80,60,0, -60, -80, -100];
var colorArray = ["#67000d", "#b73e43", "#d5464a", "#f26256", "#fb876e", "#fca78e", "#fcbba1", "#fee0d2", "#fff5f0"];
var svg = d3.select("#diagram").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + (height / 2 + 10) + ")");
var partition = d3.layout.partition()
.value(function(d) { return d.Total; });
var arc = d3.svg.arc()
.startAngle(function(d) { return Math.max(0, Math.min(2 * Math.PI, x(d.x))); })
.endAngle(function(d) { return Math.max(0, Math.min(2 * Math.PI, x(d.x + d.dx))); })
.innerRadius(function(d) { return Math.max(0, y(d.y)); })
.outerRadius(function(d) { return Math.max(0, y(d.y + d.dy)); });
console.log(arc)
function checkIt(error, root){
initializeBreadcrumbTrail();
//intilaize dropdown
if (error) throw error;
var g = svg.selectAll("g")
.data(partition.nodes(root))
.enter().append("g");
var path = g.append("path")
.attr("d", arc)
.style("fill", function(d) { var color;
if(d.Total_Change > 100)
{color = colorArray[0]
}
else if(d.Total_Change > 0 && d.Total_Change < 100)
{color = colorArray[1]
}
else
{
color = colorArray[2]
}
d.color = color;
return color})
.on("click", click)
.on("mouseover", mouseover);
var tooltip = d3.select("body")
.append("div")
.attr("id", "tooltips")
.style("position", "absolute")
.style("background-color", "#fff")
.style("z-index", "10")
.style("visibility", "hidden");
g.on("mouseover", function(d){return tooltip.style("visibility", "visible")
.html("<div class=" + "tooltip_container>"+"<h4 class=" + "tooltip_heading>" +d.name.replace(/[_-]/g, " ") +"</h4>" +"<br>" + "<p> Emissions 2013:" + " " + "<br>" + d.Total + " " +"<span>in Million Tons</span></p>"+ "<br>"+ "<p> Change in Emissions: <span>" + (d.Total_Change/d.Total*100).toPrecision(3) + "%" + "</span></p>"+"</div>" );})
.on("mousemove", function(){return tooltip.style("top",(d3.event.pageY-10)+"px").style("left",(d3.event.pageX+10)+"px");})
.on("mouseout", function(){return tooltip.style("visibility", "hidden");});
//creating a dropdown
var dropDown = d3.select("#dropdown_container")
.append("select")
.attr("class", "selection")
.attr("name", "country-list");
var nodeArr = partition.nodes(root);
var options = dropDown.selectAll("option")
.data(nodeArr)
.enter()
.append("option");
options.text(function (d) {
var prefix = new Array(d.depth + 1);
var dropdownValues = d.name.replace(/[_-]/g, " ");
return dropdownValues;
}).attr("value", function (d) {
var dropdownValues = d.name;
return dropdownValues;
});
// transition on click
function click(d) {
// fade out all text elements
path.transition()
.duration(750)
.attrTween("d", arcTween(d))
.each("end", function(e, i) {
// check if the animated element's data e lies within the visible angle span given in d
if (e.x >= d.x && e.x < (d.x + d.dx)) {
// get a selection of the associated text element
var arcText = d3.select(this.parentNode).select("text");
// fade in the text element and recalculate positions
arcText.transition().duration(750)
.attr("opacity", 1)
.attr("transform", function() { return "rotate(" + computeTextRotation(e) + ")" })
.attr("x", function(d) { return y(d.y); });
}
});
};
d3.select(".selection").on("change", function changePie() {
var value = this.value;
var index = this.selectedIndex;
var dataObj = nodeArr[index];
path[0].forEach(function(p){
var data = d3.select(p).data();//get the data from the path
if (data[0].name === value){
path.transition()
.duration(750)
.attrTween("d", arcTween(d))
.each("end", function(e, i) {
// check if the animated element's data e lies within the visible angle span given in d
if (e.x >= d.x && e.x < (d.x + d.dx)) {
// get a selection of the associated text element
var arcText = d3.select(this.parentNode).select("text");
// fade in the text element and recalculate positions
arcText.transition().duration(750)
.attr("opacity", 1)
.attr("transform", function() { return "rotate(" + computeTextRotation(e) + ")" })
.attr("x", function(d) { return y(d.y); });
}
});
}
});
console.log(this.value + " :c " + dataObj["Iron and steel"] + " in " + (dataObj.parent && dataObj.parent.name));
});
};
d3.json("https://gist.githubusercontent.com/heenaI/cbbc5c5f49994f174376/raw/55c672bbca7991442f1209cfbbb6ded45d5e8c8e/data.json", checkIt);
d3.select(self.frameElement).style("height", height + "px");
// Interpolate the scales!
function arcTween(d) {
var xd = d3.interpolate(x.domain(), [d.x, d.x + d.dx]),
yd = d3.interpolate(y.domain(), [d.y, 1]),
yr = d3.interpolate(y.range(), [d.y ? 20 : 0, radius]);
return function(d, i) {
return i
? function(t) { return arc(d); }
: function(t) { x.domain(xd(t)); y.domain(yd(t)).range(yr(t)); return arc(d); };
};
}
function initializeBreadcrumbTrail() {
// Add the svg area.
var trail = d3.select("#sequence").append("svg:svg")
.attr("width", width)
.attr("height", 50)
.attr("id", "trail");
// Add the label at the end, for the percentage.
trail.append("svg:text")
.attr("id", "endlabel")
.style("fill", "#000");
}
function breadcrumbPoints(d, i) {
var points = [];
points.push("0,0");
points.push(b.w + ",0");
points.push(b.w + b.t + "," + (b.h / 2));
points.push(b.w + "," + b.h);
points.push("0," + b.h);
if (i > 0) { // Leftmost breadcrumb; don't include 6th vertex.
points.push(b.t + "," + (b.h / 2));
}
return points.join(" ");
}
// Update the breadcrumb trail to show the current sequence and percentage.
function updateBreadcrumbs(nodeArray) {
// Data join; key function combines name and depth (= position in sequence).
var g = d3.select("#trail")
.selectAll("g")
.data(nodeArray, function(d) { return d.name.replace(/[_-]/g, " ") + d.Total; });
// Add breadcrumb and label for entering nodes.
var entering = g.enter().append("svg:g");
entering.append("svg:polygon")
.attr("points", breadcrumbPoints)
.style("fill", "#d3d3d3");
entering.append("svg:text")
.attr("x", (b.w + b.t) / 2)
.attr("y", b.h / 2)
.attr("dy", "0.35em")
.attr("text-anchor", "middle")
.text(function(d) { return d.name.replace(/[_-]/g, " "); });
// Set position for entering and updating nodes.
g.attr("transform", function(d, i) {
return "translate(" + i * (b.w + b.s) + ", 0)";
});
// Remove exiting nodes.
g.exit().remove();
// Now move and update the percentage at the end.
d3.select("#trail").select("#endlabel")
.attr("x", (nodeArray.length + 0.5) * (b.w + b.s))
.attr("y", b.h / 2)
.attr("dy", "0.35em")
.attr("text-anchor", "middle");
// Make the breadcrumb trail visible, if it's hidden.
d3.select("#trail")
.style("visibility", "");
}
function getAncestors(node) {
var path = [];
var current = node;
while (current.parent) {
path.unshift(current);
current = current.parent;
}
return path;
}
function mouseover(d) {
var sequenceArray = getAncestors(d);
updateBreadcrumbs(sequenceArray);}
Add this one line and it will work :)
Inside your selection change add this var d = data[0]; as shown below:
d3.select(".selection").on("change", function changePie() {
var value = this.value;
var index = this.selectedIndex;
var dataObj = nodeArr[index];
path[0].forEach(function(p){
var data = d3.select(p).data();//get the data from the path
if (data[0].name === value){
var d = data[0];//this line is missing
path.transition()
Working code here
Other option is to call the click function on selection change like below
d3.select(".selection").on("change", function changePie() {
var value = this.value;
var index = this.selectedIndex;
var dataObj = nodeArr[index];
path[0].forEach(function (p) {
var data = d3.select(p).data(); //get the data from the path
if (data[0].name === value) {
console.log(data)
click(data[0]);//call the click function
}
Working code here
Hope this helps!
I'm trying to create a sankey chart using the d3 sankey plugin with dynamic shipping data. It works great most of the time except when I get a data set like this:
[{"DeparturePort":"CHARLESTON","ArrivalPort":"BREMERHAVEN","volume":5625.74},{"DeparturePort":"CHARLESTON","ArrivalPort":"ITAPOA","volume":2340},{"DeparturePort":"PT EVERGLADES","ArrivalPort":"PT AU PRINCE","volume":41.02},{"DeparturePort":"BREMERHAVEN","ArrivalPort":"CHARLESTON","volume":28}]
The key to my issue is the first and last entry in the data set. It seems that having opposite directions in the same sankey chart sends the javascript into an infinite loop and kills the browser. Any ideas on how to prevent this from happening?
Here's my chart code where raw would be the object above:
var data = raw;
var units = "Volume";
var margin = { top: 100, right: 0, bottom: 30, left: 0 },
width = $("#"+divID).width() - margin.left - margin.right,
height = divID == "enlargeChart" ? 800 - margin.top - margin.bottom : 600 - margin.top - margin.bottom;
var formatNumber = d3.format(",.0f"), // zero decimal places
format = function (d) { return ""; },
color = d3.scale.ordinal()
.range(["#0077c0", "#FF6600"]);
// append the svg canvas to the page
var svg = d3.select("#"+divID).append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.style("font-size", "12px")
.append("g")
.attr("transform",
"translate(" + margin.left + "," + margin.top + ")");
// Set the sankey diagram properties
var sankey = d3.sankey(width)
.nodeWidth(10)
.nodePadding(10)
.size([width, height]);
var path = sankey.link();
// load the data (using the timelyportfolio csv method)
//d3.csv("sankey.csv", function (error, data) {
//set up graph in same style as original example but empty
graph = { "nodes": [], "links": [] };
var checklist = [];
data.forEach(function (d) {
if ($.inArray(d.DeparturePort, checklist) == -1) {
checklist.push(d.DeparturePort)
graph.nodes.push({ "name": d.DeparturePort });
}
if ($.inArray(d.ArrivalPort, checklist) == -1) {
checklist.push(d.ArrivalPort)
graph.nodes.push({ "name": d.ArrivalPort });
}
graph.links.push({
"source": d.DeparturePort,
"target": d.ArrivalPort,
"value": +d.volume
});
});
// return only the distinct / unique nodes
graph.nodes = d3.keys(d3.nest()
.key(function (d) { return d.name; })
.map(graph.nodes));
// loop through each link replacing the text with its index from node
graph.links.forEach(function (d, i) {
graph.links[i].source = graph.nodes.indexOf(graph.links[i].source);
graph.links[i].target = graph.nodes.indexOf(graph.links[i].target);
});
//now loop through each nodes to make nodes an array of objects
// rather than an array of strings
graph.nodes.forEach(function (d, i) {
graph.nodes[i] = { "name": d };
});
sankey
.nodes(graph.nodes)
.links(graph.links)
.layout(32);
// add in the links
var link = svg.append("g").selectAll(".link")
.data(graph.links)
.enter().append("path")
.attr("class", "link")
.attr("d", path)
.style("stroke-width", function (d) { return Math.max(1, d.dy); })
.sort(function (a, b) { setTimeout(function () { return b.dy - a.dy; }, 10);});
// add the link titles
link.append("title")
.text(function (d) {
return d.source.name + " → " +
d.target.name + "\n" + d.value.toFixed(0) + " TEU";
});
$("#" + divID + " .loading").addClass("hide");
// add in the nodes
var node = svg.append("g").selectAll(".node")
.data(graph.nodes)
.enter().append("g")
.attr("class", "node")
.attr("transform", function (d) {
//setTimeout(function () {
return "translate(" + d.x + "," + d.y + ")";
//}, 10);
})
.call(d3.behavior.drag()
.origin(function (d) { return d; })
.on("dragstart", function () {
this.parentNode.appendChild(this);
})
.on("drag", dragmove));
// add the rectangles for the nodes
node.append("rect")
.attr("height", function (d) { if (d.dy < 0) { d.dy = (d.dy * -1); } return d.dy; })
.attr("width", sankey.nodeWidth())
.style("fill", function (d) {
return d.color = color(d.name);
})
.style("stroke", function (d) {
return d3.rgb(d.color);
})
.append("title")
.text(function (d) {
return d.name + "\n" + format(d.value);
});
// add in the title for the nodes
node.append("text")
.attr("x", -6)
.attr("y", function (d) { return d.dy / 2; })
.attr("dy", ".35em")
.attr("text-anchor", "end")
.style("stroke", function (d) { return "#000000" })
.attr("transform", null)
.text(function (d) { return d.name; })
.filter(function (d) { return d.x < width / 2; })
.attr("x", 6 + sankey.nodeWidth())
.attr("text-anchor", "start");
// the function for moving the nodes
function dragmove(d) {
d3.select(this).attr("transform",
"translate(" + d.x + "," + (
d.y = Math.max(0, Math.min(height - d.dy, d3.event.y))
) + ")");
sankey.relayout();
link.attr("d", path);
}
}, 0)
It's an issue with the sankey.js script.
See this commit (on a fork of sankey.js) which fixed it:
https://github.com/soxofaan/d3-plugin-captain-sankey/commit/0edba18918aac3e9afadffd4a169c47f88a98f81
while (remainingNodes.length) {
becomes:
while (remainingNodes.length && x < nodes.length) {
That should prevent the endless loop.
I am trying to render an tree diagram using d3.js (which I am very new at). So I am not sure how to make the bottom-to top orientation. I used as reference the example here.
I also want that for every node click node attributes will be displayed (click function call).
Can somebody enlighten me with this? Thanks
Here's my code so far.
var orientation = {
"": {
size: [width, height],
x: function(node) { return node.x; },
y: function(node) { return height - node.y; }
},
};
var data = (function () {
var jason = null;
$.ajax({
'async': false,
'global': false,
'url': "/../GMGR_restructure/json/tree6.json",
'dataType': "json",
'success': function (data) {
jason = data;
}
});
return jason;
})();
var realWidth = window.innerWidth;
var realHeight = window.innerHeight;
var margin = {top: 140, right: 10, bottom: 140, left: 10},
m = [0, 1000, 0, 100],
width = 1500 - margin.left - margin.right,
height = 1000 - margin.top - margin.bottom
h = realHeight -m[0] -m[2],
rectW = 200;
rectH = 30;
w = realWidth -m[0] -m[0];
var customNodes = new Array(),
tmpNodes,
label_w = 70,
branch_w = 70,
layer_wider_label = new Array(),
depencencyChart;
function graph2() {
var ms = document.getElementById('maxStep').value;
tmpNodes = d3.layout.tree().size([600, 1000]).nodes(data);//.on("click", click);
//Create a svg canvas
depencencyChart = d3.select("#graphDiv").append("svg:svg")
.data(d3.entries(orientation))
.attr("width", 2000)
.attr("height", 1000)
.append("svg:g")
.attr("class","drawarea")
.attr("transform", "translate(80, 80)") // shift everything to the right
//.on("click", click);
var fakeTxtBox = depencencyChart.append("svg:text")
.attr("id", "fakeTXT")
.attr("text-anchor", "right")
.text(data.name + "(" + data.gid + ")");
//.on("click", click);
layer_wider_label[0] = fakeTxtBox.node().getComputedTextLength();
depencencyChart.select("#fakeTXT").remove();
data.y = getNodeY(data.id);
data.x = 0;
data.depth = parseInt(data.layer);
customNodes.push(data);//.on("click", click);
prepareNodes(data.children);//.on("click", click(data));
//align nodes.
updateNodesXOffset()
if(ms==""||ms==" "||ms=="All")
drawChart2();
else
drawChart(ms);
d3.select("g")
.call(d3.behavior.zoom()
.scaleExtent([0.1,5])
.on("zoom", zoom));
}
function updateNodesXOffset(){
var x_offsets = new Array();
x_offsets[0] = 0;
customNodes.forEach(function(node) {
node.x = 0;
if (node.layer > 0) {
node.x = x_offsets[node.layer - 1] + layer_wider_label[node.layer - 1] + branch_w;
x_offsets[node.layer] = node.x;
}
});
}
function getNodeY(id) {
var ret = 0;
tmpNodes.some(function(node) {
if (node.id === id) {
//return x:d3.tree has a vertical layout by default.
ret = node.x;
return;
}
})
return ret;
}
function prepareNodes(nodes) {
nodes.forEach(function(node) {
prepareNode(node);
if (node.children) {
prepareNodes(node.children);
}
});
}
function prepareNode(node) {
node.y = getNodeY(node.id);
//.on("click", click);
//fake element to calculate labels area width.
var fakeTxtBox = depencencyChart.append("svg:text")
.attr("id", "fakeTXT")
.attr("text-anchor", "right")
.text(node.name + " : " + node.gid)
//.on("click", click(node));
var this_label_w = fakeTxtBox.node().getComputedTextLength();
depencencyChart.select("#fakeTXT").remove();
if (layer_wider_label[node.layer] == null) {
layer_wider_label[node.layer] = this_label_w;
} else {
if (this_label_w > layer_wider_label[node.layer]) {
layer_wider_label[node.layer] = this_label_w;
}
}
//x will be set
node.depth = parseInt(node.layer);
customNodes.push(node);
//node.on("click", click(node));
}
function customSpline(d) {
var p = new Array();
p[0] = d.source.x + "," + d.source.y;
p[3] = d.target.x + "," + d.target.y;
var m = (d.source.x + d.target.x) / 2
p[1] = m + "," + d.source.y;
p[2] = m + "," + d.target.y;
//This is to change the points where the spline is anchored
//from [source.right,target.left] to [source.top,target.bottom]
// var m = (d.source.y + d.target.y)/2
// p[1] = d.source.x + "," + m;
// p[2] = d.target.x + "," + m;
return "M" + p[0] + "C" + p[1] + " " + p[2] + " " + p[3];
}
function drawChart(ms) {
var cnt=0;
customNodes.forEach(function(node) { //alert(node.layer);
if(node.depth <= ms){
cnt++;
var nodeSVG = depencencyChart.append("svg:g")
.attr("transform", "translate(" + node.x + "," + node.y + ")")
//alert(node.depth);
if (node.depth > 0) {
nodeSVG.append("svg:circle")
.attr("stroke", node.children ? "#3191c1" : "#269926")
.attr("fill", "#fff")
.attr("r", 3)
}
var txtBox = nodeSVG.append("svg:text")
.on("click", click)
.attr("class", "name")
.attr("dx", 8)
.attr("dy", 4)
//.attr("fill", node.current ? "#ffffff" : node.children ? "#226586" : "#269926")
.attr("fill", node.current ? "#ffffff" : node.children ? "#ffffff" : "#ffffff")
.text(node.name)
//.text(node.name + " \n (" + node.gid + ") ")
//.attr("dx", 8)
//.attr("dy", 8)
//.text(node.gid)
var txtW = txtBox.node().getComputedTextLength();
nodeSVG.insert("rect", "text")
.attr("fill", node.children ? "#3191c1" : "#3191c1")
.attr("stroke", "black")
.attr("width", txtW + 8)
.attr("height", "20")
.attr("y", "-12")
.attr("x", "5")
.attr("rx", 4)
.attr("ry", 4)
//.on("click", click(node))
if (node.current) {
nodeSVG.insert("rect", "text")
.attr("fill", node.children ? "#FF4D4D" : "#269926")
.attr("width", txtW + 8)
.attr("height", "20")
.attr("y", "-12")
.attr("x", "5")
.attr("rx", 4)
.attr("ry", 4)
}
//if(cnt <= ms){
if (node.children) {
node.x = node.x + txtW + 20;
//prepare links;
var links = new Array();
node.children.forEach(function(child) {
var st = new Object();
st.source = node;
// st.parent = node;
st.target = child;
st.warning = child.warning;
links.push(st);
})
//.on("click", click(node));
//draw links (under nodes)
depencencyChart.selectAll("pathlink")
.data(links)
.enter().insert("svg:path", "g")
.attr("class", function(d) {
return d.warning === "true" ? "link warning" : "link"
})
.attr("d", customSpline)
//draw a node at the end of the link
nodeSVG.append("svg:circle")
.attr("stroke", "#3191c1")
.attr("fill", "#fff")
.attr("r", 5.5)
//.on("click", click(node))
.attr("transform", "translate(" + (txtW + 20) + ",0)");
//}
}
//node.on("click", click(node));
}
});
d3.select("svg")
.call(d3.behavior.zoom()
.scaleExtent([0.5, 10])
.on("zoom", zoom));
//update(data);
}
function drawChart2() {
var cnt=0;
customNodes.forEach(function(node) {
//if(node.depth <= ms){
cnt++;
var nodeSVG = depencencyChart.append("svg:g")
.attr("transform", "translate(" + node.x + "," + node.y + ")")
//alert(node.depth);
if (node.depth > 0) {
nodeSVG.append("svg:circle")
.attr("stroke", node.children ? "#3191c1" : "#269926")
.attr("fill", "#fff")
.attr("r", 3)
//.attr("x",(node.x-5))
//.attr("y",(node.y-8))
}
var txtBox = nodeSVG.append("svg:text")
//.on("click", click(node))
.attr("class", "name")
.attr("dx", 8)
.attr("dy", 4)
//.attr("fill", node.current ? "#ffffff" : node.children ? "#226586" : "#269926")
.attr("fill", node.current ? "#ffffff" : node.children ? "#ffffff" : "#ffffff")
.text(node.name)
/*.html(function(node){
var str=node.name;
if(str.length>10){
var txt1=str.slice(0,10);
txt1+="<br/>";
txt2=str.slice(10,str.length);
return txt1+txt2;
}
return node.name;
});*/
//.text(node.name + " \n (" + node.gid + ") ")
//.attr("dx", 8)
//.attr("dy", 8)
//.text(node.gid)
var txtW = txtBox.node().getComputedTextLength();
nodeSVG.insert("rect", "text")
.attr("fill", node.children ? "#3191c1" : "#3191c1")
.attr("stroke", "black")
.attr("width", txtW + 8)
.attr("height", "20")
.attr("y", "-12")
.attr("x", "5")
.attr("rx", 4)
.attr("ry", 4)
//.on("click", click(node))
if (node.current) {
nodeSVG.insert("rect", "text")
.attr("fill", node.children ? "#FF4D4D" : "#269926")
.attr("width", txtW + 8)
.attr("height", "20")
.attr("y", "-12")
.attr("x", "5")
.attr("rx", 4)
.attr("ry", 4)
}
//if(cnt <= ms){
if (node.children) {
node.x = node.x + txtW + 20;
//prepare links;
var links = new Array();
node.children.forEach(function(child) {
var st = new Object();
st.source = node;
// st.parent = node;
st.target = child;
st.warning = child.warning;
links.push(st);
})
//.on("click", click(node));
//draw links (under nodes)
depencencyChart.selectAll("pathlink")
.data(links)
.enter().insert("svg:path", "g")
.attr("class", function(d) {
return d.warning === "true" ? "link warning" : "link"
})
.attr("d", customSpline)
//draw a node at the end of the link
nodeSVG.append("svg:circle")
.attr("stroke", "#3191c1")
.attr("fill", "#fff")
.attr("r", 5.5)
//.on("click", click(node))
.attr("transform", "translate(" + (txtW + 20) + ",0)");
//}
}
//node.on("click", click(node));
//}
});
d3.select("svg")
.call(d3.behavior.zoom()
.scaleExtent([0.5, 10])
.on("zoom", zoom));
//update(data);
}
function zoom() {
var scale = d3.event.scale,
translation = d3.event.translate,
tbound = -h * scale,
bbound = h * scale,
lbound = (-w - m[1]) * scale,
rbound = (w - m[3]) * scale;
// limit translation to thresholds
translation = [
Math.max(Math.min(translation[0], rbound), lbound),
Math.max(Math.min(translation[1], bbound), tbound)
];
d3.select(".drawarea")
.attr("transform", "translate(" + translation + ")" +
" scale(" + scale + ")");
}
// 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);
}
/*function click(node)
{
alert(node.name);
//document.getElementById('gid').innerHTML = "sucess";
document.getElementById('gid').innerHTML = node.gid;
document.getElementById('gname').innerHTML = node.name;
document.getElementById('gmethod').innerHTML = node.method;
document.getElementById('gmtype').innerHTML = node.methodtype;
document.getElementById('gdate').innerHTML = node.date;
document.getElementById('gcountry').innerHTML = node.country;
document.getElementById('gloc').innerHTML = node.location;
document.getElementById('gcname').innerHTML = node.cname;
document.getElementById('gref').innerHTML = node.ref;
document.getElementById('gpid1').innerHTML = node.gpid1;
document.getElementById('gpid2').innerHTML = node.gpid2;
//document.getElementById('try').value = d.name;
}*/