I have two D3 edge bundle charts, one on top of the other (CSS grid). For the top chart (id: edgeB) I have a div input style slider that changes the tension (amount of curve) of the node lines. It should affect the top chart only. Currently, the slider associated with the top chart is affecting the bottom most chart (id: edgeBTwo). I've tried including the correct div id but it seems to have no effect. I'm thinking that it is affecting the most recently created D3 chart but I'm not sure how to change this behavior.
Here is all of my code.
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<link rel="icon" type="globe_icon.png">
<script type='text/javascript' src='https://ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js'></script>
<style type="text/css">
body {
padding: 90px;
margin: 0px;
height: 100%;
}
html,
.grid-container {
display: grid;
grid-template-columns: 1fr;
grid-template-rows: auto auto;
background-color: #ffffff;
padding: 10px;
grid-row-gap: 10px;
}
.grid-item1 {
background-color: rgb(255, 255, 255);
border: 5px solid rgba(255, 0, 0, 0.8);
padding: 15px;
font-size: 12px;
text-align: left;
grid-row-gap: 10px;
font: 8pt "Helvetica Neue", Arial, Helvetica, sans-serif;
}
.grid-item2 {
background-color: rgb(255, 255, 255);
border: 5px solid rgba(234, 0, 255, 0.8);
padding: 15px;
font-size: 12px;
text-align: left;
grid-row-gap: 10px;
font: 8pt "Helvetica Neue", Arial, Helvetica, sans-serif;
}
.wrapword {
white-space: -moz-pre-wrap !important; /* Mozilla, since 1999 */
white-space: -webkit-pre-wrap; /* Chrome & Safari */
white-space: -pre-wrap; /* Opera 4-6 */
white-space: -o-pre-wrap; /* Opera 7 */
white-space: pre-wrap; /* CSS3 */
word-wrap: break-word; /* Internet Explorer 5.5+ */
word-break: break-all;
white-space: normal;
}
path.arc {
cursor: move;
fill: #000000;
}
.node {
font: 300 9 "Helvetica Neue", Helvetica, Arial, sans-serif;
fill: rgb(0, 0, 0);
}
.node:hover {
fill: rgb(132, 236, 255);
}
.node:hover,
.node--source,
.node--target {
font-weight: 700;
}
.link {
fill: none;
stroke: #1f77b4;
stroke-opacity: .5;
pointer-events: none;
}
.link.source, .link.target {
stroke-opacity: .8;
stroke-width: 2px;
}
.node.target {
fill: #d62728 !important;
}
.link.source {
stroke: #d62728;
}
.node.source {
fill: #00ff40;
}
.link.target {
stroke: #00ff40;
}
</style>
</head>
<body>
<div class="grid-container"></div>
<div class="grid-item1" id="edgeB">
<div id="edgeB" style="position:absolute;bottom: 2%;;font-size:16px;">Tension: <input style="position:relative;top:3px;" type="range" min="0" max="100" value="85"></div>
<script type="text/javascript" src="//d3js.org/d3.v3.min.js"></script>
<script type="text/javascript" src="packages.js"></script>
<script>
(function chart1(){
color = d3.scale.category10();
var w = 700,
h = 700,
rx = w / 2.1,
ry = h / 2.1,
m0,
rotate = 0
pi = Math.PI;
var splines = [];
var cluster = d3.layout.cluster()
.size([360, ry - 180])
.sort(function(a, b) {
return d3.ascending(a.key, b.key);
});
var bundle = d3.layout.bundle();
var line = d3.svg.line.radial()
.interpolate("bundle")
.tension(.8)
.radius(function(d) {
return d.y;
})
.angle(function(d) {
return d.x / 180 * Math.PI;
});
// Chrome 15 bug: <http://code.google.com/p/chromium/issues/detail?id=98951>
var div = d3.select("#edgeB")
.style("width", w + "px")
.style("height", w + "px")
.style("position", "relative");
var svg = div.append("svg:svg")
.attr("width", 700)
.attr("height", 700)
.append("svg:g")
.attr("transform", "translate(" + rx + "," + ry + ")");
svg.append("svg:path")
.attr("class", "arc")
.attr("d", d3.svg.arc().outerRadius(ry - 180).innerRadius(0).startAngle(0).endAngle(2 * Math.PI))
.on("mousedown", mousedown);
d3.json("flare_year.json", function(classes) {
var nodes = cluster.nodes(packages.root(classes)),
links = packages.imports(nodes),
splines = bundle(links);
var path = svg.selectAll("path.link")
.data(links)
.enter().append("svg:path")
.attr("class", function(d) {
return "link source-" + d.source.key + " target-" + d.target.key;
})
.attr("d", function(d, i) {
return line(splines[i]);
});
var groupData = svg.selectAll("g.group")
.data(nodes.filter(function(d) {
return (d.key == '1970-1979' || d.key == '1980-1989' || d.key == '1990-1999' || d.key == '2000-2009' || d.key == '2010-2019') && d.children;
}))
.enter().append("group")
.attr("class", "group");
var groupArc = d3.svg.arc()
.innerRadius(ry - 177)
.outerRadius(ry - 157)
.startAngle(function(d) {
return (findStartAngle(d.__data__.children) - 2.3) * pi / 180;
})
.endAngle(function(d) {
return (findEndAngle(d.__data__.children) + 2.3) * pi / 180;
});
svg.selectAll("g.arc")
.data(groupData[0])
.enter().append("svg:path")
.attr("d", groupArc)
.attr("class", "groupArc")
.attr("id", function(d, i) {console.log(d.__data__.key); return d.__data__.key;})
.style("fill", function(d, i) {return color(i);})
.style("fill-opacity", 0.5)
.each(function(d,i) {
var firstArcSection = /(^.+?)L/;
var newArc = firstArcSection.exec( d3.select(this).attr("d") )[1];
newArc = newArc.replace(/,/g , " ");
svg.append("path")
.attr("class", "hiddenArcs")
.attr("id", "hidden"+d.__data__.key)
.attr("d", newArc)
.style("fill", "none");
});
svg.selectAll(".arcText")
.data(groupData[0])
.enter().append("text")
.attr("class", "arcText")
.attr("dy", 12)
.append("textPath")
.attr("startOffset","50%")
.style("text-anchor","middle")
.attr("xlink:href",function(d,i){return "#hidden" + d.__data__.key;})
.text(function(d){return d.__data__.key;});
svg.selectAll("g.node")
.data(nodes.filter(function(n) {
return !n.children;
}))
.enter().append("svg:g")
.attr("class", "node")
.attr("id", function(d) {
return "node-" + d.key;
})
.attr("transform", function(d) {
return "rotate(" + (d.x - 90) + ")translate(" + d.y + ")";
})
.append("svg:text")
.attr("dx", function(d) {
return d.x < 180 ? 25 : -25;
})
.attr("dy", ".31em")
.attr("text-anchor", function(d) {
return d.x < 180 ? "start" : "end";
})
.attr("transform", function(d) {
return d.x < 180 ? null : "rotate(180)";
})
.text(function(d) {
return d.key.replace(/_/g, ' ');
})
.on("mouseover", mouseover)
.on("mouseout", mouseout);
d3.select("input[type=range]").on("change", function() {
line.tension(this.value / 100);
path.attr("d", function(d, i) {
return line(splines[i]);
});
});
});
d3.select(window)
.on("mousemove", mousemove)
.on("mouseup", mouseup);
function mouse(e) {
return [e.pageX - rx, e.pageY - ry];
}
function mousedown() {
m0 = mouse(d3.event);
d3.event.preventDefault();
}
function mousemove() {
if (m0) {
var m1 = mouse(d3.event),
dm = Math.atan2(cross(m0, m1), dot(m0, m1)) * 180 / Math.PI;
div.style("-webkit-transform", "translate3d(0," + (ry - rx) + "px,0)rotate3d(0,0,0," + dm + "deg)translate3d(0," + (rx - ry) + "px,0)");
}
}
function mouseup() {
if (m0) {
var m1 = mouse(d3.event),
dm = Math.atan2(cross(m0, m1), dot(m0, m1)) * 180 / Math.PI;
rotate += dm;
if (rotate > 360) rotate -= 360;
else if (rotate < 0) rotate += 360;
m0 = null;
div.style("-webkit-transform", "rotate3d(0,0,0,0deg)");
svg.attr("transform", "translate(" + rx + "," + ry + ")rotate(" + rotate + ")")
.selectAll("g.node text")
.attr("dx", function(d) { return (d.x + rotate) % 360 < 180 ? 25 : -25; })
.attr("text-anchor", function(d) { return (d.x + rotate) % 360 < 180 ? "start" : "end"; })
.attr("transform", function(d) { return (d.x + rotate) % 360 < 180 ? null : "rotate(180)"; });
}
}
function mouseover(d) {
svg.selectAll("path.link.target-" + d.key)
.classed("target", true)
.each(updateNodes("source", true));
svg.selectAll("path.link.source-" + d.key)
.classed("source", true)
.each(updateNodes("target", true));
}
function mouseout(d) {
svg.selectAll("path.link.source-" + d.key)
.classed("source", false)
.each(updateNodes("target", false));
svg.selectAll("path.link.target-" + d.key)
.classed("target", false)
.each(updateNodes("source", false));
}
function updateNodes(name, value) {
return function(d) {
if (value) this.parentNode.appendChild(this);
svg.select("#node-" + d[name].key).classed(name, value);
};
}
function cross(a, b) {
return a[0] * b[1] - a[1] * b[0];
}
function dot(a, b) {
return a[0] * b[0] + a[1] * b[1];
}
function findStartAngle(children) {
var min = children[0].x;
children.forEach(function(d) {
if (d.x < min)
min = d.x;
});
return min;
}
function findEndAngle(children) {
var max = children[0].x;
children.forEach(function(d) {
if (d.x > max)
max = d.x;
});
return max;
}}())
</script>
</div>
<div class="grid-item2" id="edgeBTwo">
<script type="text/javascript" src="//d3js.org/d3.v3.min.js"></script>
<script type="text/javascript" src="packages.js"></script>
<script>
(function chart2(){
color = d3.scale.category10();
var w = 700,
h = 700,
rx = w / 2.1,
ry = h / 2.1,
m0,
rotate = 0
pi = Math.PI;
var splines = [];
var cluster = d3.layout.cluster()
.size([360, ry - 180])
.sort(function(a, b) {
return d3.ascending(a.key, b.key);
});
var bundle = d3.layout.bundle();
var line = d3.svg.line.radial()
.interpolate("bundle")
.tension(.8)
.radius(function(d) {
return d.y;
})
.angle(function(d) {
return d.x / 180 * Math.PI;
});
// Chrome 15 bug: <http://code.google.com/p/chromium/issues/detail?id=98951>
var div = d3.select("#edgeBTwo")
.style("width", w + "px")
.style("height", w + "px")
.style("position", "relative");
var svg = div.append("svg:svg")
.attr("width", 700)
.attr("height", 700)
.append("svg:g")
.attr("transform", "translate(" + rx + "," + ry + ")");
svg.append("svg:path")
.attr("class", "arc")
.attr("d", d3.svg.arc().outerRadius(ry - 180).innerRadius(0).startAngle(0).endAngle(2 * Math.PI))
.on("mousedown", mousedown);
d3.json("flare_test.json", function(classes) {
var nodes = cluster.nodes(packages.root(classes)),
links = packages.imports(nodes),
splines = bundle(links);
var path = svg.selectAll("path.link")
.data(links)
.enter().append("svg:path")
.attr("class", function(d) {
return "link source-" + d.source.key + " target-" + d.target.key;
})
.attr("d", function(d, i) {
return line(splines[i]);
});
var groupData = svg.selectAll("g.group")
.data(nodes.filter(function(d) {
return (d.key == '1970-1979' || d.key == 'Low' || d.key == 'Medium' || d.key == 'High') && d.children;
}))
.enter().append("group")
.attr("class", "group");
var groupArc = d3.svg.arc()
.innerRadius(ry - 177)
.outerRadius(ry - 157)
.startAngle(function(d) {
return (findStartAngle(d.__data__.children) - 2.3) * pi / 180;
})
.endAngle(function(d) {
return (findEndAngle(d.__data__.children) + 2.3) * pi / 180;
});
svg.selectAll("g.arc")
.data(groupData[0])
.enter().append("svg:path")
.attr("d", groupArc)
.attr("class", "groupArc")
.attr("id", function(d, i) {console.log(d.__data__.key); return d.__data__.key;})
.style("fill", function(d, i) {return color(i);})
.style("fill-opacity", 0.5)
.each(function(d,i) {
var firstArcSection = /(^.+?)L/;
var newArc = firstArcSection.exec( d3.select(this).attr("d") )[1];
newArc = newArc.replace(/,/g , " ");
svg.append("path")
.attr("class", "hiddenArcs")
.attr("id", "hidden"+d.__data__.key)
.attr("d", newArc)
.style("fill", "none");
});
svg.selectAll(".arcText")
.data(groupData[0])
.enter().append("text")
.attr("class", "arcText")
.attr("dy", 12)
.append("textPath")
.attr("startOffset","50%")
.style("text-anchor","middle")
.attr("xlink:href",function(d,i){return "#hidden" + d.__data__.key;})
.text(function(d){return d.__data__.key;});
svg.selectAll("g.node")
.data(nodes.filter(function(n) {
return !n.children;
}))
.enter().append("svg:g")
.attr("class", "node")
.attr("id", function(d) {
return "node-" + d.key;
})
.attr("transform", function(d) {
return "rotate(" + (d.x - 90) + ")translate(" + d.y + ")";
})
.append("svg:text")
.attr("dx", function(d) {
return d.x < 180 ? 25 : -25;
})
.attr("dy", ".31em")
.attr("text-anchor", function(d) {
return d.x < 180 ? "start" : "end";
})
.attr("transform", function(d) {
return d.x < 180 ? null : "rotate(180)";
})
.text(function(d) {
return d.key.replace(/_/g, ' ');
})
.on("mouseover", mouseover)
.on("mouseout", mouseout);
d3.select("input[type=range]").on("change", function() {
line.tension(this.value / 100);
path.attr("d", function(d, i) {
return line(splines[i]);
});
});
});
d3.select(window)
.on("mousemove", mousemove)
.on("mouseup", mouseup);
function mouse(e) {
return [e.pageX - rx, e.pageY - ry];
}
function mousedown() {
m0 = mouse(d3.event);
d3.event.preventDefault();
}
function mousemove() {
if (m0) {
var m1 = mouse(d3.event),
dm = Math.atan2(cross(m0, m1), dot(m0, m1)) * 180 / Math.PI;
div.style("-webkit-transform", "translate3d(0," + (ry - rx) + "px,0)rotate3d(0,0,0," + dm + "deg)translate3d(0," + (rx - ry) + "px,0)");
}
}
function mouseup() {
if (m0) {
var m1 = mouse(d3.event),
dm = Math.atan2(cross(m0, m1), dot(m0, m1)) * 180 / Math.PI;
rotate += dm;
if (rotate > 360) rotate -= 360;
else if (rotate < 0) rotate += 360;
m0 = null;
div.style("-webkit-transform", "rotate3d(0,0,0,0deg)");
svg.attr("transform", "translate(" + rx + "," + ry + ")rotate(" + rotate + ")")
.selectAll("g.node text")
.attr("dx", function(d) { return (d.x + rotate) % 360 < 180 ? 25 : -25; })
.attr("text-anchor", function(d) { return (d.x + rotate) % 360 < 180 ? "start" : "end"; })
.attr("transform", function(d) { return (d.x + rotate) % 360 < 180 ? null : "rotate(180)"; });
}
}
function mouseover(d) {
svg.selectAll("path.link.target-" + d.key)
.classed("target", true)
.each(updateNodes("source", true));
svg.selectAll("path.link.source-" + d.key)
.classed("source", true)
.each(updateNodes("target", true));
}
function mouseout(d) {
svg.selectAll("path.link.source-" + d.key)
.classed("source", false)
.each(updateNodes("target", false));
svg.selectAll("path.link.target-" + d.key)
.classed("target", false)
.each(updateNodes("source", false));
}
function updateNodes(name, value) {
return function(d) {
if (value) this.parentNode.appendChild(this);
svg.select("#node-" + d[name].key).classed(name, value);
};
}
function cross(a, b) {
return a[0] * b[1] - a[1] * b[0];
}
function dot(a, b) {
return a[0] * b[0] + a[1] * b[1];
}
function findStartAngle(children) {
var min = children[0].x;
children.forEach(function(d) {
if (d.x < min)
min = d.x;
});
return min;
}
function findEndAngle(children) {
var max = children[0].x;
children.forEach(function(d) {
if (d.x > max)
max = d.x;
});
return max;
}}());
</script>
</div>
</body>
</html>
Here is my packages.js file and both data files needed to run the above code:
packages.js
(function() {
packages = {
// Lazily construct the package hierarchy from class names.
root: function(classes) {
var map = {};
function find(name, data) {
var node = map[name], i;
if (!node) {
node = map[name] = data || {name: name, children: []};
if (name.length) {
node.parent = find(name.substring(0, i = name.lastIndexOf(".")));
node.parent.children.push(node);
node.key = name.substring(i + 1);
}
}
return node;
}
classes.forEach(function(d) {
find(d.name, d);
});
return map[""];
},
// Return a list of imports for the given array of nodes.
imports: function(nodes) {
var map = {},
imports = [];
// Compute a map from name to node.
nodes.forEach(function(d) {
map[d.name] = d;
});
// For each import, construct a link from the source to target node.
nodes.forEach(function(d) {
if (d.imports) d.imports.forEach(function(i) {
imports.push({source: map[d.name], target: map[i]});
});
});
return imports;
}
};
})();
flare_year.json
[
{
"name": "root.2010-2019.A",
"imports": [
"root.1990-1999.B",
]
},
{
"name": "root.1990-1999.B",
"imports": [
"root.2010-2019.A",
]
}
]
flare_test.json
[
{
"name": "root.High.A",
"imports": [
"root.Medium.F",
]
},
{
"name": "root.Medium.F",
"imports": [
"root.High.A",
]
}
]
It was a simple issue but took me some time to figure out.
What you are doing wrong is that you are assigning the onChange Event Handler on (input[type="range"]) in chart1 and again assigning it in chart2 on the same node. Event Listener assigned later will always be called.
You need to have seperate inputs for range and corresponding handlers or if you want to assign a same handler, add a dropdown to select which chart to change and get value of that dropdown in the event handler.
Your fixed code below:
(function() {
packages = {
// Lazily construct the package hierarchy from class names.
root: function(classes) {
var map = {};
function find(name, data) {
var node = map[name],
i;
if (!node) {
node = map[name] = data || {
name: name,
children: []
};
if (name.length) {
node.parent = find(name.substring(0, i = name.lastIndexOf(".")));
node.parent.children.push(node);
node.key = name.substring(i + 1);
}
}
return node;
}
classes.forEach(function(d) {
find(d.name, d);
});
return map[""];
},
// Return a list of imports for the given array of nodes.
imports: function(nodes) {
var map = {},
imports = [];
// Compute a map from name to node.
nodes.forEach(function(d) {
map[d.name] = d;
});
// For each import, construct a link from the source to target node.
nodes.forEach(function(d) {
if (d.imports) d.imports.forEach(function(i) {
imports.push({
source: map[d.name],
target: map[i]
});
});
});
return imports;
}
};
})();
(function chart1() {
color = d3.scale.category10();
let w = 700,
h = 700,
rx = w / 2.1,
ry = h / 2.1,
m0,
rotate = 0
pi = Math.PI;
let splines = [];
let cluster = d3.layout.cluster()
.size([360, ry - 180])
.sort(function(a, b) {
return d3.ascending(a.key, b.key);
});
let bundle = d3.layout.bundle();
let line = d3.svg.line.radial()
.interpolate("bundle")
.tension(.8)
.radius(function(d) {
return d.y;
})
.angle(function(d) {
return d.x / 180 * Math.PI;
});
// Chrome 15 bug: <http://code.google.com/p/chromium/issues/detail?id=98951>
let div = d3.select("#edgeB")
.style("width", w + "px")
.style("height", w + "px")
.style("position", "relative");
let svg = div.append("svg:svg")
.attr("width", 700)
.attr("height", 700)
.append("svg:g")
.attr("transform", "translate(" + rx + "," + ry + ")");
svg.append("svg:path")
.attr("class", "arc")
.attr("d", d3.svg.arc().outerRadius(ry - 180).innerRadius(0).startAngle(0).endAngle(2 * Math.PI))
.on("mousedown", mousedown);
{
let classes = [{
"name": "root.2010-2019.A",
"imports": [
"root.1990-1999.B",
]
},
{
"name": "root.1990-1999.B",
"imports": [
"root.2010-2019.A",
]
}
];
let nodes = cluster.nodes(packages.root(classes)),
links = packages.imports(nodes),
splines = bundle(links);
let path = svg.selectAll("path.link")
.data(links)
.enter().append("svg:path")
.attr("class", function(d) {
return "link source-" + d.source.key + " target-" + d.target.key;
})
.attr("d", function(d, i) {
return line(splines[i]);
});
let groupData = svg.selectAll("g.group")
.data(nodes.filter(function(d) {
return (d.key == '1970-1979' || d.key == '1980-1989' || d.key == '1990-1999' || d.key == '2000-2009' || d.key == '2010-2019') && d.children;
}))
.enter().append("group")
.attr("class", "group");
let groupArc = d3.svg.arc()
.innerRadius(ry - 177)
.outerRadius(ry - 157)
.startAngle(function(d) {
return (findStartAngle(d.__data__.children) - 2.3) * pi / 180;
})
.endAngle(function(d) {
return (findEndAngle(d.__data__.children) + 2.3) * pi / 180;
});
svg.selectAll("g.arc")
.data(groupData[0])
.enter().append("svg:path")
.attr("d", groupArc)
.attr("class", "groupArc")
.attr("id", function(d, i) {
console.log(d.__data__.key);
return d.__data__.key;
})
.style("fill", function(d, i) {
return color(i);
})
.style("fill-opacity", 0.5)
.each(function(d, i) {
let firstArcSection = /(^.+?)L/;
let newArc = firstArcSection.exec(d3.select(this).attr("d"))[1];
newArc = newArc.replace(/,/g, " ");
svg.append("path")
.attr("class", "hiddenArcs")
.attr("id", "hidden" + d.__data__.key)
.attr("d", newArc)
.style("fill", "none");
});
svg.selectAll(".arcText")
.data(groupData[0])
.enter().append("text")
.attr("class", "arcText")
.attr("dy", 12)
.append("textPath")
.attr("startOffset", "50%")
.style("text-anchor", "middle")
.attr("xlink:href", function(d, i) {
return "#hidden" + d.__data__.key;
})
.text(function(d) {
return d.__data__.key;
});
svg.selectAll("g.node")
.data(nodes.filter(function(n) {
return !n.children;
}))
.enter().append("svg:g")
.attr("class", "node")
.attr("id", function(d) {
return "node-" + d.key;
})
.attr("transform", function(d) {
return "rotate(" + (d.x - 90) + ")translate(" + d.y + ")";
})
.append("svg:text")
.attr("dx", function(d) {
return d.x < 180 ? 25 : -25;
})
.attr("dy", ".31em")
.attr("text-anchor", function(d) {
return d.x < 180 ? "start" : "end";
})
.attr("transform", function(d) {
return d.x < 180 ? null : "rotate(180)";
})
.text(function(d) {
return d.key.replace(/_/g, ' ');
})
.on("mouseover", mouseover)
.on("mouseout", mouseout);
d3.select("input[type=range]").on("change", function() {
console.log(line);
line.tension(this.value / 100);
path.attr("d", function(d, i) {
return line(splines[i]);
});
});
};
d3.select(window)
.on("mousemove", mousemove)
.on("mouseup", mouseup);
function mouse(e) {
return [e.pageX - rx, e.pageY - ry];
}
function mousedown() {
m0 = mouse(d3.event);
d3.event.preventDefault();
}
function mousemove() {
if (m0) {
var m1 = mouse(d3.event),
dm = Math.atan2(cross(m0, m1), dot(m0, m1)) * 180 / Math.PI;
div.style("-webkit-transform", "translate3d(0," + (ry - rx) + "px,0)rotate3d(0,0,0," + dm + "deg)translate3d(0," + (rx - ry) + "px,0)");
}
}
function mouseup() {
if (m0) {
var m1 = mouse(d3.event),
dm = Math.atan2(cross(m0, m1), dot(m0, m1)) * 180 / Math.PI;
rotate += dm;
if (rotate > 360) rotate -= 360;
else if (rotate < 0) rotate += 360;
m0 = null;
div.style("-webkit-transform", "rotate3d(0,0,0,0deg)");
svg.attr("transform", "translate(" + rx + "," + ry + ")rotate(" + rotate + ")")
.selectAll("g.node text")
.attr("dx", function(d) {
return (d.x + rotate) % 360 < 180 ? 25 : -25;
})
.attr("text-anchor", function(d) {
return (d.x + rotate) % 360 < 180 ? "start" : "end";
})
.attr("transform", function(d) {
return (d.x + rotate) % 360 < 180 ? null : "rotate(180)";
});
}
}
function mouseover(d) {
svg.selectAll("path.link.target-" + d.key)
.classed("target", true)
.each(updateNodes("source", true));
svg.selectAll("path.link.source-" + d.key)
.classed("source", true)
.each(updateNodes("target", true));
}
function mouseout(d) {
svg.selectAll("path.link.source-" + d.key)
.classed("source", false)
.each(updateNodes("target", false));
svg.selectAll("path.link.target-" + d.key)
.classed("target", false)
.each(updateNodes("source", false));
}
function updateNodes(name, value) {
return function(d) {
if (value) this.parentNode.appendChild(this);
svg.select("#node-" + d[name].key).classed(name, value);
};
}
function cross(a, b) {
return a[0] * b[1] - a[1] * b[0];
}
function dot(a, b) {
return a[0] * b[0] + a[1] * b[1];
}
function findStartAngle(children) {
var min = children[0].x;
children.forEach(function(d) {
if (d.x < min)
min = d.x;
});
return min;
}
function findEndAngle(children) {
var max = children[0].x;
children.forEach(function(d) {
if (d.x > max)
max = d.x;
});
return max;
}
}());
(function chart2() {
color = d3.scale.category10();
var w = 700,
h = 700,
rx = w / 2.1,
ry = h / 2.1,
m0,
rotate = 0
pi = Math.PI;
var splines = [];
var cluster = d3.layout.cluster()
.size([360, ry - 180])
.sort(function(a, b) {
return d3.ascending(a.key, b.key);
});
var bundle = d3.layout.bundle();
var line = d3.svg.line.radial()
.interpolate("bundle")
.tension(.8)
.radius(function(d) {
return d.y;
})
.angle(function(d) {
return d.x / 180 * Math.PI;
});
// Chrome 15 bug: <http://code.google.com/p/chromium/issues/detail?id=98951>
var div = d3.select("#edgeBTwo")
.style("width", w + "px")
.style("height", w + "px")
.style("position", "relative");
var svg = div.append("svg:svg")
.attr("width", 700)
.attr("height", 700)
.append("svg:g")
.attr("transform", "translate(" + rx + "," + ry + ")");
svg.append("svg:path")
.attr("class", "arc")
.attr("d", d3.svg.arc().outerRadius(ry - 180).innerRadius(0).startAngle(0).endAngle(2 * Math.PI))
.on("mousedown", mousedown);
{
let classes = [
{
"name": "root.High.A",
"imports": [
"root.Medium.F",
]
},
{
"name": "root.Medium.F",
"imports": [
"root.High.A",
]
}
];
var nodes = cluster.nodes(packages.root(classes)),
links = packages.imports(nodes),
splines = bundle(links);
var path = svg.selectAll("path.link")
.data(links)
.enter().append("svg:path")
.attr("class", function(d) {
return "link source-" + d.source.key + " target-" + d.target.key;
})
.attr("d", function(d, i) {
return line(splines[i]);
});
var groupData = svg.selectAll("g.group")
.data(nodes.filter(function(d) {
return (d.key == '1970-1979' || d.key == 'Low' || d.key == 'Medium' || d.key == 'High') && d.children;
}))
.enter().append("group")
.attr("class", "group");
var groupArc = d3.svg.arc()
.innerRadius(ry - 177)
.outerRadius(ry - 157)
.startAngle(function(d) {
return (findStartAngle(d.__data__.children) - 2.3) * pi / 180;
})
.endAngle(function(d) {
return (findEndAngle(d.__data__.children) + 2.3) * pi / 180;
});
svg.selectAll("g.arc")
.data(groupData[0])
.enter().append("svg:path")
.attr("d", groupArc)
.attr("class", "groupArc")
.attr("id", function(d, i) {
console.log(d.__data__.key);
return d.__data__.key;
})
.style("fill", function(d, i) {
return color(i);
})
.style("fill-opacity", 0.5)
.each(function(d, i) {
var firstArcSection = /(^.+?)L/;
var newArc = firstArcSection.exec(d3.select(this).attr("d"))[1];
newArc = newArc.replace(/,/g, " ");
svg.append("path")
.attr("class", "hiddenArcs")
.attr("id", "hidden" + d.__data__.key)
.attr("d", newArc)
.style("fill", "none");
});
svg.selectAll(".arcText")
.data(groupData[0])
.enter().append("text")
.attr("class", "arcText")
.attr("dy", 12)
.append("textPath")
.attr("startOffset", "50%")
.style("text-anchor", "middle")
.attr("xlink:href", function(d, i) {
return "#hidden" + d.__data__.key;
})
.text(function(d) {
return d.__data__.key;
});
svg.selectAll("g.node")
.data(nodes.filter(function(n) {
return !n.children;
}))
.enter().append("svg:g")
.attr("class", "node")
.attr("id", function(d) {
return "node-" + d.key;
})
.attr("transform", function(d) {
return "rotate(" + (d.x - 90) + ")translate(" + d.y + ")";
})
.append("svg:text")
.attr("dx", function(d) {
return d.x < 180 ? 25 : -25;
})
.attr("dy", ".31em")
.attr("text-anchor", function(d) {
return d.x < 180 ? "start" : "end";
})
.attr("transform", function(d) {
return d.x < 180 ? null : "rotate(180)";
})
.text(function(d) {
return d.key.replace(/_/g, ' ');
})
.on("mouseover", mouseover)
.on("mouseout", mouseout);
d3.select("input#inputEdgesec").on("change", function() {
line.tension(this.value / 100);
path.attr("d", function(d, i) {
return line(splines[i]);
});
});
};
d3.select(window)
.on("mousemove", mousemove)
.on("mouseup", mouseup);
function mouse(e) {
return [e.pageX - rx, e.pageY - ry];
}
function mousedown() {
m0 = mouse(d3.event);
d3.event.preventDefault();
}
function mousemove() {
if (m0) {
var m1 = mouse(d3.event),
dm = Math.atan2(cross(m0, m1), dot(m0, m1)) * 180 / Math.PI;
div.style("-webkit-transform", "translate3d(0," + (ry - rx) + "px,0)rotate3d(0,0,0," + dm + "deg)translate3d(0," + (rx - ry) + "px,0)");
}
}
function mouseup() {
if (m0) {
var m1 = mouse(d3.event),
dm = Math.atan2(cross(m0, m1), dot(m0, m1)) * 180 / Math.PI;
rotate += dm;
if (rotate > 360) rotate -= 360;
else if (rotate < 0) rotate += 360;
m0 = null;
div.style("-webkit-transform", "rotate3d(0,0,0,0deg)");
svg.attr("transform", "translate(" + rx + "," + ry + ")rotate(" + rotate + ")")
.selectAll("g.node text")
.attr("dx", function(d) {
return (d.x + rotate) % 360 < 180 ? 25 : -25;
})
.attr("text-anchor", function(d) {
return (d.x + rotate) % 360 < 180 ? "start" : "end";
})
.attr("transform", function(d) {
return (d.x + rotate) % 360 < 180 ? null : "rotate(180)";
});
}
}
function mouseover(d) {
svg.selectAll("path.link.target-" + d.key)
.classed("target", true)
.each(updateNodes("source", true));
svg.selectAll("path.link.source-" + d.key)
.classed("source", true)
.each(updateNodes("target", true));
}
function mouseout(d) {
svg.selectAll("path.link.source-" + d.key)
.classed("source", false)
.each(updateNodes("target", false));
svg.selectAll("path.link.target-" + d.key)
.classed("target", false)
.each(updateNodes("source", false));
}
function updateNodes(name, value) {
return function(d) {
if (value) this.parentNode.appendChild(this);
svg.select("#node-" + d[name].key).classed(name, value);
};
}
function cross(a, b) {
return a[0] * b[1] - a[1] * b[0];
}
function dot(a, b) {
return a[0] * b[0] + a[1] * b[1];
}
function findStartAngle(children) {
var min = children[0].x;
children.forEach(function(d) {
if (d.x < min)
min = d.x;
});
return min;
}
function findEndAngle(children) {
var max = children[0].x;
children.forEach(function(d) {
if (d.x > max)
max = d.x;
});
return max;
}
}());
body {
padding: 90px;
margin: 0px;
height: 100%;
}
html,
.grid-container {
display: grid;
grid-template-columns: 1fr;
grid-template-rows: auto auto;
background-color: #ffffff;
padding: 10px;
grid-row-gap: 10px;
}
.grid-item1 {
background-color: rgb(255, 255, 255);
border: 5px solid rgba(255, 0, 0, 0.8);
padding: 15px;
font-size: 12px;
text-align: left;
grid-row-gap: 10px;
font: 8pt "Helvetica Neue", Arial, Helvetica, sans-serif;
}
.grid-item2 {
background-color: rgb(255, 255, 255);
border: 5px solid rgba(234, 0, 255, 0.8);
padding: 15px;
font-size: 12px;
text-align: left;
grid-row-gap: 10px;
font: 8pt "Helvetica Neue", Arial, Helvetica, sans-serif;
}
.wrapword {
white-space: -moz-pre-wrap !important; /* Mozilla, since 1999 */
white-space: -webkit-pre-wrap; /* Chrome & Safari */
white-space: -pre-wrap; /* Opera 4-6 */
white-space: -o-pre-wrap; /* Opera 7 */
white-space: pre-wrap; /* CSS3 */
word-wrap: break-word; /* Internet Explorer 5.5+ */
word-break: break-all;
white-space: normal;
}
path.arc {
cursor: move;
fill: #000000;
}
.node {
font: 300 9 "Helvetica Neue", Helvetica, Arial, sans-serif;
fill: rgb(0, 0, 0);
}
.node:hover {
fill: rgb(132, 236, 255);
}
.node:hover,
.node--source,
.node--target {
font-weight: 700;
}
.link {
fill: none;
stroke: #1f77b4;
stroke-opacity: .5;
pointer-events: none;
}
.link.source, .link.target {
stroke-opacity: .8;
stroke-width: 2px;
}
.node.target {
fill: #d62728 !important;
}
.link.source {
stroke: #d62728;
}
.node.source {
fill: #00ff40;
}
.link.target {
stroke: #00ff40;
}
.sliderContainer
{
display:flex;
flex-direction:column;
}
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<link rel="icon" type="globe_icon.png">
<script type='text/javascript' src='https://ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js'></script>
</head>
<body>
<div class="grid-container"></div>
<div class="grid-item1" id="edgeB">
<div id="edgeB" style="position:absolute;bottom: 2%;;font-size:16px;"><div class="sliderContainer">Tension: <input style="position:relative;top:3px;" type="range" min="0" max="100" value="85"> Tension for second <input id="inputEdgesec" style="position:relative;top:3px;" type="range" min="0" max="100" value="85">
</div>
</div>
<script type="text/javascript" src="//d3js.org/d3.v3.min.js"></script>
</script>
</div>
<div class="grid-item2" id="edgeBTwo">
</div>
<script type="text/javascript" src="//d3js.org/d3.v3.min.js"></script>
<script type="text/javascript" src="packages.js"></script>
</body>
</html>
I'm using D3 and javascript to create a BiLevel Partition following this example. The labels in the left side of the diagram are upside down, and I was trying to rotate them, but I've not been successful yet.
I found numerous cases of people with the same problem, but using Sunburst. Also tried to implement those solutions, but I'm still unable to solve this problem.
<body>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script>
/*var margin = {top: 350, right: 480, bottom: 350, left: 480},
radius = Math.min(margin.top, margin.right, margin.bottom, margin.left) - 10;*/
var width = 1200,
height = 1200,
radius = Math.min(width, height) / 2;
function filter_min_arc_size_text(d, i) {return (d.dx*d.depth*radius/3)>14};
var hue = d3.scale.category10();
var luminance = d3.scale.sqrt()
.domain([0, 1e6])
.clamp(true)
.range([90, 20]);
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + (height / 2 + 10) + ")");
var partition = d3.layout.partition()
.sort(function(a, b) { return d3.ascending(a.name, b.name); })
.size([2 * Math.PI, radius]);
var arc = d3.svg.arc()
.startAngle(function(d) { return d.x; })
.endAngle(function(d) { return d.x + d.dx - .01 / (d.depth + .5); })
.innerRadius(function(d) { return radius / 3 * d.depth; })
.outerRadius(function(d) { return radius / 3 * (d.depth + 1) - 1; });
//Tooltip description
var tooltip = d3.select("body")
.append("div")
.attr("id", "tooltip")
.style("position", "absolute")
.style("z-index", "10")
.style("opacity", 0);
function format_number(x) {
return x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
}
function format_description(d) {
var description = d.description;
return /* '<b>' + d.name + '</b></br>'+*/ d.description + '<br> (' + format_number(d.value) + ')';
}
function computeTextRotation(d) {
var ang = ((d.x + d.dx / 2) - Math.PI / 2) / Math.PI * 180;
return (ang > 90) ? 180 + ang : ang;
}
function mouseOverArc(d) {
d3.select(this).attr("stroke","black")
tooltip.html(format_description(d));
return tooltip.transition()
.duration(50)
.style("opacity", 0.9);
}
function mouseOutArc(){
d3.select(this).attr("stroke","")
return tooltip.style("opacity", 0);
}
function mouseMoveArc (d) {
return tooltip
.style("top", (d3.event.pageY-10)+"px")
.style("left", (d3.event.pageX+10)+"px");
}
var root_ = null;
d3.json("flare.json", function(error, root) {
if (error) return console.warn(error);
// Compute the initial layout on the entire tree to sum sizes.
// Also compute the full name and fill color for each node,
// and stash the children so they can be restored as we descend.
partition
.value(function(d) { return d.size; })
.nodes(root)
.forEach(function(d) {
d._children = d.children;
d.sum = d.value;
d.key = key(d);
d.fill = fill(d);
});
// Now redefine the value function to use the previously-computed sum.
partition
.children(function(d, depth) { return depth < 2 ? d._children : null; })
.value(function(d) { return d.sum; });
var center = svg.append("circle")
.attr("r", radius / 3)
.on("click", zoomOut);
center.append("title")
.text("zoom out");
var partitioned_data=partition.nodes(root).slice(1)
var path = svg.selectAll("path")
.data(partitioned_data)
.enter().append("path")
.attr("d", arc)
.style("fill", function(d) { return d.fill; })
.each(function(d) { this._current = updateArc(d); })
.on("click", zoomIn)
.on("mouseover", mouseOverArc)
.on("mousemove", mouseMoveArc)
.on("mouseout", mouseOutArc);
var texts = svg.selectAll("text")
.data(partitioned_data)
.enter().append("text")
.filter(filter_min_arc_size_text)
.attr("transform", function(d) { return "rotate(" + computeTextRotation(d) + ")"; })
.attr("x", function(d) { return radius / 3 * d.depth; })
.attr("dx", "6") // margin
.attr("dy", ".35em") // vertical-align
.text(function(d,i) {return d.name})
function zoomIn(p) {
if (p.depth > 1) p = p.parent;
if (!p.children) return;
zoom(p, p);
}
function zoomOut(p) {
if (!p.parent) return;
zoom(p.parent, p);
}
// Zoom to the specified new root.
function zoom(root, p) {
if (document.documentElement.__transition__) return;
// Rescale outside angles to match the new layout.
var enterArc,
exitArc,
outsideAngle = d3.scale.linear().domain([0, 2 * Math.PI]);
function insideArc(d) {
return p.key > d.key
? {depth: d.depth - 1, x: 0, dx: 0} : p.key < d.key
? {depth: d.depth - 1, x: 2 * Math.PI, dx: 0}
: {depth: 0, x: 0, dx: 2 * Math.PI};
}
function outsideArc(d) {
return {depth: d.depth + 1, x: outsideAngle(d.x), dx: outsideAngle(d.x + d.dx) - outsideAngle(d.x)};
}
center.datum(root);
// When zooming in, arcs enter from the outside and exit to the inside.
// Entering outside arcs start from the old layout.
if (root === p) enterArc = outsideArc, exitArc = insideArc, outsideAngle.range([p.x, p.x + p.dx]);
var new_data=partition.nodes(root).slice(1)
path = path.data(new_data, function(d) { return d.key; });
// When zooming out, arcs enter from the inside and exit to the outside.
// Exiting outside arcs transition to the new layout.
if (root !== p) enterArc = insideArc, exitArc = outsideArc, outsideAngle.range([p.x, p.x + p.dx]);
d3.transition().duration(d3.event.altKey ? 7500 : 750).each(function() {
path.exit().transition()
.style("fill-opacity", function(d) { return d.depth === 1 + (root === p) ? 1 : 0; })
.attrTween("d", function(d) { return arcTween.call(this, exitArc(d)); })
.remove();
path.enter().append("path")
.style("fill-opacity", function(d) { return d.depth === 2 - (root === p) ? 1 : 0; })
.style("fill", function(d) { return d.fill; })
.on("click", zoomIn)
.on("mouseover", mouseOverArc)
.on("mousemove", mouseMoveArc)
.on("mouseout", mouseOutArc)
.each(function(d) { this._current = enterArc(d); });
path.transition()
.style("fill-opacity", 1)
.attrTween("d", function(d) { return arcTween.call(this, updateArc(d)); });
});
texts = texts.data(new_data, function(d) { return d.key; })
texts.exit()
.remove()
texts.enter()
.append("text")
texts.style("opacity", 0)
.attr("transform", function(d) { return "rotate(" + computeTextRotation(d) + ")"; })
.attr("x", function(d) { return radius / 3 * d.depth; })
.attr("dx", "6") // margin
.attr("dy", ".35em") // vertical-align
.filter(filter_min_arc_size_text)
.text(function(d,i) {return d.name})
.transition().delay(750).style("opacity", 1)
}
});
function key(d) {
var k = [], p = d;
while (p.depth) k.push(p.name), p = p.parent;
return k.reverse().join(".");
}
function fill(d) {
var p = d;
while (p.depth > 1) p = p.parent;
var c = d3.lab(hue(p.name));
c.l = luminance(d.sum);
return c;
}
function arcTween(b) {
var i = d3.interpolate(this._current, b);
this._current = i(0);
return function(t) {
return arc(i(t));
};
}
function updateArc(d) {
return {depth: d.depth, x: d.x, dx: d.dx};
}
d3.select(self.frameElement).style("height", margin.top + margin.bottom + "px");
</script>
The problem I have is that it is only showing the right half of the Partition.
I'm trying to use this example http://bl.ocks.org/eesur/be2abfb3155a38be4de4 in my own project. I'm trying to generalize the code block to use. I changed the code a little but now it's not generating similar graph in example. You can see my attempt in jsfiddle https://jsfiddle.net/wykbbvzf/1/
var SuperHeroes = function(selector, w, h) {
this.w = w;
this.h = h;
d3.select(selector).selectAll("svg").remove();
this.svg = d3.select(selector).append("svg:svg")
.attr('width', w)
.attr('height', h);
this.svg.append("svg:rect")
.style("stroke", "#999")
.style("fill", "#fff")
.attr('width', w)
.attr('height', h);
this.force = d3.layout.force()
.charge(function(d) { return d._children ? -d.size / 100 : -40; })
.linkDistance(function(d) { return d.target._children ? 80 : 25; })
.size([h, w]);
};
// some colour variables
var tcBlack = "#130C0E";
// rest of vars
var maxNodeSize = 50,
x_browser = 20,
y_browser = 25;
/*
d3.json("marvel.json", function(json) {
// Build the path
var defs = this.svg.insert("svg:defs")
.data(["end"]);
defs.enter().append("svg:path")
.attr("d", "M0,-5L10,0L0,5");
this.update();
});
*/
/**
*
*/
SuperHeroes.prototype.update = function(json) {
this.root = json;
this.root.fixed = true;
this.root.x = w / 2;
this.root.y = h / 4;
var nodes = this.flatten(this.root),
links = d3.layout.tree().links(nodes);
// Restart the force layout.
this.force.nodes(nodes)
.links(links)
.gravity(0.05)
.charge(-1500)
.linkDistance(100)
.friction(0.5)
.linkStrength(function(l, i) {return 1; })
.size([w, h])
.on("tick", tick)
.start();
var path = this.svg.selectAll("path.link")
.data(links, function(d) { return d.target.id; });
path.enter().insert("svg:path")
.attr("class", "link")
// .attr("marker-end", "url(#end)")
.style("stroke", "#eee");
// Exit any old paths.
path.exit().remove();
// Update the nodes…
var node = this.svg.selectAll("g.node")
.data(nodes, function(d) { return d.id; });
// Enter any new nodes.
var nodeEnter = node.enter().append("svg:g")
.attr("class", "node")
.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; })
.on("click", this.click)
.call(this.force.drag);
// Append a circle
nodeEnter.append("svg:circle")
.attr("r", function(d) { return Math.sqrt(d.size) / 10 || 4.5; })
.style("fill", "#eee");
// Append images
var images = nodeEnter.append("svg:image")
.attr("xlink:href", function(d) { return d.img;})
.attr("x", function(d) { return -25;})
.attr("y", function(d) { return -25;})
.attr("height", 50)
.attr("width", 50);
// make the image grow a little on mouse over and add the text details on click
var setEvents = images
// Append hero text
.on( 'click', function (d) {
d3.select("h1").html(d.hero);
d3.select("h2").html(d.name);
d3.select("h3").html ("Take me to " + "<a href='" + d.link + "' >" + d.hero + " web page ⇢"+ "</a>" );
})
.on( 'mouseenter', function() {
// select element in current context
d3.select( this )
.transition()
.attr("x", function(d) { return -60;})
.attr("y", function(d) { return -60;})
.attr("height", 100)
.attr("width", 100);
})
// set back
.on( 'mouseleave', function() {
d3.select( this )
.transition()
.attr("x", function(d) { return -25;})
.attr("y", function(d) { return -25;})
.attr("height", 50)
.attr("width", 50);
});
// Append hero name on roll over next to the node as well
nodeEnter.append("text")
.attr("class", "nodetext")
.attr("x", x_browser)
.attr("y", y_browser +15)
.attr("fill", tcBlack)
.text(function(d) { return d.hero; });
// Exit any old nodes.
node.exit().remove();
// Re-select for update.
path = this.svg.selectAll("path.link");
node = this.svg.selectAll("g.node");
function tick() {
path.attr("d", function(d) {
var dx = d.target.x - d.source.x,
dy = d.target.y - d.source.y,
dr = Math.sqrt(dx * dx + dy * dy);
return "M" + d.source.x + ","
+ d.source.y
+ "A" + dr + ","
+ dr + " 0 0,1 "
+ d.target.x + ","
+ d.target.y;
});
node.attr("transform", this.nodeTransform);
}
}
/**
* Gives the coordinates of the border for keeping the nodes inside a frame
* http://bl.ocks.org/mbostock/1129492
*/
SuperHeroes.prototype.nodeTransform = function(d) {
d.x = Math.max(maxNodeSize, Math.min(w - (d.imgwidth/2 || 16), d.x));
d.y = Math.max(maxNodeSize, Math.min(h - (d.imgheight/2 || 16), d.y));
return "translate(" + d.x + "," + d.y + ")";
}
/**
* Toggle children on click.
*/
SuperHeroes.prototype.click = function(d) {
if (d.children) {
d._children = d.children;
d.children = null;
} else {
d.children = d._children;
d._children = null;
}
this.update();
}
/**
* Returns a list of all nodes under the root.
*/
SuperHeroes.prototype.flatten = function(root) {
var nodes = [];
var i = 0;
function recurse(node) {
if (node.children)
node.children.forEach(recurse);
if (!node.id)
node.id = ++i;
nodes.push(node);
}
recurse(root);
return nodes;
}
SuperHeroes.prototype.cleanup = function() {
this.update([]);
this.force.stop();
};
var currentSuperHereos;
var createSuperHeroes = function(json) {
// remove previous flower to save memory
if (currentSuperHereos) currentSuperHereos.cleanup();
// adapt layout size to the total number of elements
var total = 5;
w = parseInt(Math.sqrt(total) * 30, 10);
h = parseInt(Math.sqrt(total) * 30, 10);
if (h < 300) h = 300;
if (w < 300) w = 300;
// create a new SuperHeroes
currentSuperHereos = new SuperHeroes("#visualization", w, h).update(json);
var defs = this.svg.insert("svg:defs")
.data(["end"]);
defs.enter().append("svg:path")
.attr("d", "M0,-5L10,0L0,5");
};
createSuperHeroes(JSON.parse('{"name":"MAlkara","img":"https://upload.wikimedia.org/wikipedia/commons/6/60/Google-Fit-Icon.png","children":[{"hero":"Kesan","name":"Keşan","img":"https://upload.wikimedia.org/wikipedia/commons/6/60/Google-Fit-Icon.png","additionalProperties":{}}],"additionalProperties":{}}'));
Do you have any idea about my mistakes?
You will just need to update node transform attribute inside tick function. this.nodeTransform is not seems to be defined in your code. So the tick function should be as shown below.
function tick() {
path.attr("d", function(d) {
var dx = d.target.x - d.source.x,
dy = d.target.y - d.source.y,
dr = Math.sqrt(dx * dx + dy * dy);
return "M" + d.source.x + ","
+ d.source.y
+ "A" + dr + ","
+ dr + " 0 0,1 "
+ d.target.x + ","
+ d.target.y;
});
node.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; });
}
EDIT: Adding link labels
path.enter().insert("svg:path")
.attr("class", "link")
.style("stroke", "#eee")
.attr("id",function(d,i){ return "linkId_"+i; });
path.enter().append("g").attr("class", "linklabelholder")
.append("text")
.attr("class", "linklabel")
.style("font-size", "13px")
.attr("text-anchor", "middle")
.style("fill","#000")
.append("textPath")
.style('text-anchor', 'middle')
.attr('startOffset', '50%')
.attr("xlink:href",function(d,i) { return "#linkId_" + i;})
.text(function(d) {
return "my text"; //Can be dynamic via d object
});
var SuperHeroes = function(selector, w, h) {
this.w = w;
this.h = h;
d3.select(selector).selectAll("svg").remove();
this.svg = d3.select(selector).append("svg:svg")
.attr('width', w)
.attr('height', h);
var defs = this.svg.insert("svg:defs")
.data(["end"]);
defs.enter().append("svg:path")
.attr("d", "M0,-5L10,0L0,5");
this.svg.append("svg:rect")
.style("stroke", "#999")
.style("fill", "#fff")
.attr('width', w)
.attr('height', h);
this.force = d3.layout.force()
.charge(function(d) {
return d._children ? -d.size / 100 : -40;
})
.linkDistance(function(d) {
return d.target._children ? 80 : 25;
})
.size([h, w]);
};
// some colour variables
var tcBlack = "#130C0E";
// rest of vars
var maxNodeSize = 50,
x_browser = 20,
y_browser = 25;
/*
d3.json("marvel.json", function(json) {
// Build the path
var defs = this.svg.insert("svg:defs")
.data(["end"]);
defs.enter().append("svg:path")
.attr("d", "M0,-5L10,0L0,5");
this.update();
});
*/
/**
*
*/
SuperHeroes.prototype.update = function(json) {
this.root = json;
this.root.fixed = true;
this.root.x = w / 2;
this.root.y = h / 4;
var nodes = this.flatten(this.root),
links = d3.layout.tree().links(nodes);
// Restart the force layout.
this.force.nodes(nodes)
.links(links)
.gravity(0.05)
.charge(-1500)
.linkDistance(100)
.friction(0.5)
.linkStrength(function(l, i) {
return 1;
})
.size([w, h])
.on("tick", tick)
.start();
var path = this.svg.selectAll("path.link")
.data(links, function(d) {
return d.target.id;
});
path.enter().insert("svg:path")
.attr("class", "link")
.style("stroke", "#eee")
.attr("id",function(d,i){ return "linkId_"+i; });
path.enter().append("g").attr("class", "linklabelholder")
.append("text")
.attr("class", "linklabel")
.style("font-size", "13px")
.attr("text-anchor", "middle")
.style("fill","#000")
.append("textPath")
.style('text-anchor', 'middle')
.attr('startOffset', '50%')
.attr("xlink:href",function(d,i) { return "#linkId_" + i;})
.text(function(d) {
return "my text"; //Can be dynamic via d object
});
// Exit any old paths.
path.exit().remove();
// Update the nodes…
var node = this.svg.selectAll("g.node")
.data(nodes, function(d) {
return d.id;
});
// Enter any new nodes.
var nodeEnter = node.enter().append("svg:g")
.attr("class", "node")
.on("click", this.click)
.call(this.force.drag);
// Append a circle
nodeEnter.append("svg:circle")
.attr("r", function(d) {
return Math.sqrt(d.size) / 10 || 4.5;
})
.style("fill", "#eee");
// Append images
var images = nodeEnter.append("svg:image")
.attr("xlink:href", function(d) {
return d.img;
})
.attr("x", function(d) {
return -25;
})
.attr("y", function(d) {
return -25;
})
.attr("height", 50)
.attr("width", 50);
// make the image grow a little on mouse over and add the text details on click
var setEvents = images
// Append hero text
.on('click', function(d) {
d3.select("h1").html(d.hero);
d3.select("h2").html(d.name);
d3.select("h3").html("Take me to " + "<a href='" + d.link + "' >" + d.hero + " web page ⇢" + "</a>");
})
.on('mouseenter', function() {
// select element in current context
d3.select(this)
.transition()
.attr("x", function(d) {
return -60;
})
.attr("y", function(d) {
return -60;
})
.attr("height", 100)
.attr("width", 100);
})
// set back
.on('mouseleave', function() {
d3.select(this)
.transition()
.attr("x", function(d) {
return -25;
})
.attr("y", function(d) {
return -25;
})
.attr("height", 50)
.attr("width", 50);
});
// Append hero name on roll over next to the node as well
nodeEnter.append("text")
.attr("class", "nodetext")
.attr("x", x_browser)
.attr("y", y_browser + 15)
.attr("fill", tcBlack)
.text(function(d) {
return d.hero;
});
// Exit any old nodes.
node.exit().remove();
// Re-select for update.
path = this.svg.selectAll("path.link");
node = this.svg.selectAll("g.node");
function tick() {
path.attr("d", function(d) {
var dx = d.target.x - d.source.x,
dy = d.target.y - d.source.y,
dr = Math.sqrt(dx * dx + dy * dy);
return "M" + d.source.x + "," + d.source.y + "A" + dr + "," + dr + " 0 0,1 " + d.target.x + "," + d.target.y;
});
node.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
});
}
}
/**
* Gives the coordinates of the border for keeping the nodes inside a frame
* http://bl.ocks.org/mbostock/1129492
*/
SuperHeroes.prototype.nodeTransform = function(d) {
d.x = Math.max(maxNodeSize, Math.min(w - (d.imgwidth / 2 || 16), d.x));
d.y = Math.max(maxNodeSize, Math.min(h - (d.imgheight / 2 || 16), d.y));
return "translate(" + d.x + "," + d.y + ")";
}
/**
* Toggle children on click.
*/
SuperHeroes.prototype.click = function(d) {
if (d.children) {
d._children = d.children;
d.children = null;
} else {
d.children = d._children;
d._children = null;
}
}
/**
* Returns a list of all nodes under the root.
*/
SuperHeroes.prototype.flatten = function(root) {
var nodes = [];
var i = 0;
function recurse(node) {
if (node.children)
node.children.forEach(recurse);
if (!node.id)
node.id = ++i;
nodes.push(node);
}
recurse(root);
return nodes;
}
SuperHeroes.prototype.cleanup = function() {
this.update([]);
this.force.stop();
};
var currentSuperHereos;
var createSuperHeroes = function(json) {
// remove previous flower to save memory
if (currentSuperHereos) currentSuperHereos.cleanup();
// adapt layout size to the total number of elements
var total = 5;
w = parseInt(Math.sqrt(total) * 30, 10);
h = parseInt(Math.sqrt(total) * 30, 10);
if (h < 300) h = 300;
if (w < 300) w = 300;
// create a new SuperHeroes
currentSuperHereos = new SuperHeroes("#visualization", w, h).update(json);
};
createSuperHeroes(JSON.parse('{"name":"MAlkara","img":"https://upload.wikimedia.org/wikipedia/commons/6/60/Google-Fit-Icon.png","children":[{"hero":"Kesan","name":"Keşan","img":"https://upload.wikimedia.org/wikipedia/commons/6/60/Google-Fit-Icon.png","additionalProperties":{}}],"additionalProperties":{}}'));
body {
font-family: "Source Code Pro", Consolas, monaco, monospace;
line-height: 160%;
font-size: 16px;
margin: 0;
}
path.link {
fill: none;
stroke-width: 2px;
}
.node:not(:hover) .nodetext {
display: none;
}
h1 {
font-size: 36px;
margin: 10px 0;
text-transform: uppercase;
font-weight: normal;
}
h2,
h3 {
font-size: 18px;
margin: 5px 0;
font-weight: normal;
}
header {
padding: 20px;
position: absolute;
top: 0;
left: 0;
}
a:link {
color: #EE3124;
text-decoration: none;
}
a:visited {
color: #EE3124;
}
a:hover {
color: #A4CD39;
text-decoration: underline;
}
a:active {
color: #EE3124;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<div id="visualization"></div>
I have a responsive force directed graph, and it's working great, except I can't get it to stay within the browser screen.
The D3 code suggested to create the bounding box is :
node.attr("cx", function(d) { return d.x = Math.max(r, Math.min(width - r, d.x)); }) .attr("cy", function(d) { return d.y = Math.max(r, Math.min(height - r, d.y)); });
However, this does NOT work, because I'm not defining a width (it's responsive) and I'm not defining r because it's a function, so the nodes are different sizes.
I tried to set:
var r= function(d) {return d.instances;};
Because that's where I'm putting the node size from, but it doesn't work... All of the force directed examples I see use the same size nodes.. I'm just not familiar enough with javascript to figure out a workaround... help?
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<!DOCTYPE html>
<meta charset="utf-8">
<style type="text/css">
*{
margin:0;
padding:0;
}
/*
svg {
display: block;
width: 100%;
margin: 0;
}
*/
.node {
stroke: #fff;
stroke-width: 1.5px;
}
#graph{
max-width:100%;
height:100vh ;
}
.link {
stroke: #999;
stroke-opacity: .6;
}
.node-active{
stroke: #555;
stroke-width: 1.5px;
}
.node:hover{
stroke: #555;
stroke-width: 1.5px;
}
marker {
display:none;
}
.d3-tip {
line-height: 1;
font-weight: bold;
padding: 12px;
background: rgba(0, 0, 0, 0.8);
color: #fff;
border-radius: 2px;
}
.d3-tip.n:after {
margin: -1px 0 0 0;
top: 200%;
left: 0;
}
script {
display:none;
}
</style>
<body>
<div id="graph"></div>
<script src="d3/d3.js"></script>
<script src="d3/d3tip.js"></script>
<script type='text/javascript' src='http://code.jquery.com/jquery-1.11.0.js'></script>
<script>
var color = d3.scale.category20();
var force = d3.layout.force()
.charge(-2010)
.linkDistance(function(d) { return d.distance; })
// .size([width, height])
.gravity(0.7);
//var width = 1000,
// height = 1000;
var svg = d3.select("#graph")
.append("svg")
.attr({
"width": "100%",
"height": "100%"
})
// .attr("viewBox", "0 0 " + width + " " + height )
.attr("preserveAspectRatio", "xMidYMid meet") //.attr("pointer-events", "all")
.call(d3.behavior.zoom().on("zoom", redraw));
var vis = svg
.append('svg:g');
function redraw() {
vis.attr("transform",
"translate(" + d3.event.translate + ")"
+ " scale(" + d3.event.scale + ")");
}
var tip = d3.tip()
.attr('class', 'd3-tip')
.offset([-5, 0])
.html(function (d) {
return d.name + " (" + d.instances + ")";
})
svg.call(tip);
d3.json("datawords.json", function(error, graph) {
var link = vis.selectAll(".link")
.data(graph.links)
.enter().append("line")
.attr("class", "link")
.attr("width", function(d) { return d.totalLength; })
.style("stroke-width", function(d) { return Math.sqrt(d.value); });
var node = vis.selectAll(".node")
.data(graph.nodes)
.enter().append("circle")
.attr("class", "node")
.attr("r", function(d) {return d.instances;})
.style("fill", function(d) { return color(d.instances); })
.call(force.drag)
.on('mouseover', tip.show)
.on('mouseout', tip.hide)
.on('click', connectedNodes)
force
.nodes(graph.nodes)
.links(graph.links)
.start();
force.on("tick", function() {
node[0].x = svg / 2;
node[0].y = svg / 2;
link.attr("x1", function(d) { return d.source.x; })
.attr("y1", function(d) { return d.source.y; })
.attr("x2", function(d) { return d.target.x; })
.attr("y2", function(d) { return d.target.y; });
//
// node.attr("cx", function(d) { return d.x = Math.max(r, Math.min(width - r, d.x)); }) .attr("cy", function(d) { return d.y = Math.max(r, Math.min(height - r, d.y)); });
node.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; });
node.each(collide(0.5));
});
///////////////////
//Toggle stores whether the highlighting is on
var toggle = 0;
//Create an array logging what is connected to what
var linkedByIndex = {};
for (i = 0; i < graph.nodes.length; i++) {
linkedByIndex[i + "," + i] = 1;
};
graph.links.forEach(function (d) {
linkedByIndex[d.source.index + "," + d.target.index] = 1;
});
//This function looks up whether a pair are neighbours
function neighboring(a, b) {
return linkedByIndex[a.index + "," + b.index];
}
function connectedNodes() {
if (toggle == 0) {
//Reduce the opacity of all but the neighbouring nodes
d = d3.select(this).node().__data__;
node.style("opacity", function (o) {
return neighboring(d, o) | neighboring(o, d) ? 1 : 0.1;
});
link.style("opacity", function (o) {
return d.index==o.source.index | d.index==o.target.index ? 1 : 0.1;
});
//Reduce the op
toggle = 1;
} else {
//Put them back to opacity=1
node.style("opacity", 1);
link.style("opacity", 1);
toggle = 0;
};
};
var padding = 10, // separation between circles
radius=15;
function collide(alpha) {
var quadtree = d3.geom.quadtree(graph.nodes);
return function(d) {
var rb = 4*radius + padding,
nx1 = d.x - rb,
nx2 = d.x + rb,
ny1 = d.y - rb,
ny2 = d.y + rb;
quadtree.visit(function(quad, x1, y1, x2, y2) {
if (quad.point && (quad.point !== d)) {
var x = d.x - quad.point.x,
y = d.y - quad.point.y,
l = Math.sqrt(x * x + y * y);
if (l < rb) {
l = (l - rb) / l * alpha;
d.x -= x *= l;
d.y -= y *= l;
quad.point.x += x;
quad.point.y += y;
}
}
return x1 > nx2 || x2 < nx1 || y1 > ny2 || y2 < ny1;
});
};
};
window.addEventListener('resize', resize);
function resize() {
width = window.innerWidth, height = window.innerHeight;
svg.attr("width", width).attr("height", height);
force.size([width, height]).resume();
}
});
</script>
</body>
I cannot comment due to level restrictions however, I believe the issue is caused from your width/height variables. Uncomment your var width/height and change it to var width = innerWidth, height = innerHeight;. Then in your svg set the attr to .attr('width', width).attr('height', height);.
node.attr("cx", (function(w) {
return function(d) {
var r = d.instances;
return d.x = Math.max(r, Math.min(w - r, d.x));
}
})(width()))
.attr("cy", (function(h) {
return function(d) {
var r = d.instances;
return d.y = Math.max(r, Math.min(h - r, d.y));
}
})(height()));
And define width() and height() as functions returning live values.