How to stop text transitioning off the screen - javascript

I have a visual of a two ringed pie chart.
What I'm building is the following:
- if a sector's radians are too small for the specified text then the text is hidden. This is done and seems to work ok
- if text is hidden then a label should appear outside the pie. This is also done when the visual is initially rendered.
When the radio button is pressed the labels outside the pie should transition accordingly. I've attempted the transition and slowed it to 3500 and these labels are just slowly transitioning off the screen.
How do I fix this transition?
The snippet I've added to try to create the transition starts at line 241:
var arcs2 = svg.data([json]).selectAll(".arcG");
arcs2.data(partition.nodes)
.transition()
.duration(3500)
.attr("transform", function(d) {
var c = arc.centroid(d),
x = c[0],
y = c[1],
// pythagorean theorem for hypotenuse
h = Math.sqrt(x * x + y * y);
return "translate(" + (x / h * labelr) + ',' +
(y / h * labelr) + ")";
})
.attr("text-anchor", "middle");
svg.selectAll(".theTxtsOuter")
.text(function(d, i) {
if (d.name === 'root') {
return;
} else if ((d.depth === 1) && (d.dx < (d.name.length * 0.15))) {
return d.name;
} else if ((d.depth === 2) && (d.dx < (d.name.length * 0.1))) {
return d.name;
} else {
return;
}
});
This is a plunk of the working(!) visual:
https://plnkr.co/edit/jYVPCL?p=preview
Here is the complete javascript used by the pie:
function pieChart(dataFile) {
var plot;
var vis;
var width = 400,
height = 400,
radius = Math.min(width, height) / 2.1,
color = d3.scale.ordinal()
.range(["#338ABA", "#016da9", "#4c96d5"])
.domain([0, 2]);
var labelr = radius + 5 // radius for label anchor
var div = d3.select("body")
.append("div")
.attr("class", "toolTip");
var arc = d3.svg.arc()
.startAngle(function(d) {
return d.x;
})
.endAngle(function(d) {
return d.x + d.dx;
})
.outerRadius(function(d) {
return (d.y + d.dy) / (radius);
})
.innerRadius(function(d) {
return d.y / (radius);
});
//check if the svg already exists
plot = d3.select("#svgPIEChart");
if (plot.empty()) {
vis = d3.select("#pieChart")
.append("svg")
.attr({
id: "svgPIEChart"
});
} else {
vis = d3.select("#svgPIEChart");
vis.selectAll("*").remove();
}
//group of the svg element
var svg = vis
.append("g")
.attr({
'transform': "translate(" + width / 2 + "," + height * .52 + ")"
});
//svg element
vis.attr({
//set the width and height of our visualization (these will be attributes of the <svg> tag
width: width,
height: height
});
d3.text(dataFile, function(text) {
var csv = d3.csv.parseRows(text);
var json = buildHierarchy(csv);
// it seems d3.layout.partition() can be either squares or arcs
var partition = d3.layout.partition()
.sort(null)
.size([2 * Math.PI, radius * radius])
.value(function(d) {
return d.SalesRev;
});
var path = svg.data([json]).selectAll(".theArc")
.data(partition.nodes)
.enter()
.append("path")
.attr("class", "theArc")
.attr("id", function(d, i) {
return "theArc_" + i;
}) //Give each slice a unique ID
.attr("display", function(d) {
return d.depth ? null : "none";
})
.attr("d", arc)
.style("stroke", "#fff")
.style("fill", function(d) {
return color((d.children ? d : d.parent).name);
})
.attr("fill-rule", "evenodd")
.style("opacity", 0.01)
.style("stroke-opacity", 0.01)
.each(stash);
path.transition()
.duration(PIEOBJ.transTime)
.style("opacity", 1)
.style("stroke-opacity", 1)
path
.on("mouseout", mouseout)
.on("mousemove", function(d) {
div.style("left", d3.event.pageX + 10 + "px");
div.style("top", d3.event.pageY - 25 + "px");
div.style("display", "inline-block");
div.html(d.name + "<br>" + PIEOBJ.formatShrtInt(d.SalesRev));
})
var txts = svg.data([json]).selectAll(".theTxts")
.data(partition.nodes)
.enter()
.append("text");
txts
.attr("class", "theTxts")
.attr("dx", 10) //Move the text from the start angle of the arc
.attr("dy", 15) //Move the text down
.style("opacity", 0)
txts
.transition()
.duration(PIEOBJ.transTime)
.style("opacity", 1);
var txtPths = txts.append("textPath")
// .attr("xlink:href", function(d, i) {
.attr("href", function(d, i) {
return "#theArc_" + i;
})
.text(function(d) {
if (d.name === 'root') {
return;
} else if ((d.depth === 1) && (d.dx < (d.name.length * 0.15))) {
return;
} else if ((d.depth === 2) && (d.dx < (d.name.length * 0.1))) {
return;
} else {
return d.name;
}
});
/* ------- TEXT LABELS OUTSIDE THE PIE-------*/
//var arcs = svg.selectAll(".theArc");
var arcs = svg.data([json]).selectAll(".arcG")
.data(partition.nodes)
.enter()
.append("g")
.attr("class", "arcG");
arcs.append("text")
.attr("transform", function(d) {
var c = arc.centroid(d),
x = c[0],
y = c[1],
// pythagorean theorem for hypotenuse
h = Math.sqrt(x * x + y * y);
console.log(c, h);
return "translate(" + (x / h * labelr) + ',' +
(y / h * labelr) + ")";
})
.attr("text-anchor", "middle")
.text(function(d, i) {
if (d.name === 'root') {
return;
} else if ((d.depth === 1) && (d.dx < (d.name.length * 0.15))) {
return d.name;
} else if ((d.depth === 2) && (d.dx < (d.name.length * 0.1))) {
return d.name;
} else {
return;
}
})
.attr("class", "theTxtsOuter");
/* ----------------------------------------*/
d3.selectAll("input").on("change", function change() {
function createValueFunc(val) {
// currentMeasure = val;
return function(d) {
return d[val];
};
}
value = createValueFunc(this.value);
PIEOBJ.currentMeasure = this.value;
var path2 = svg.data([json]).selectAll(".theArc");
path2
.data(partition.value(value).nodes)
.transition()
.duration(1500)
.attrTween("d", arcTween)
.each("start", function() {
d3.select(this)
.on("mouseout", null) //CLEARING the listeners
.on("mousemove", null);
})
.each("end", function() {
d3.select(this)
.on("mouseout", mouseout) //attaching the listeners
.on("mousemove", function(d) {
div.style("left", d3.event.pageX + 10 + "px");
div.style("top", d3.event.pageY - 25 + "px");
div.style("display", "inline-block");
div.html(d.name + "<br>" + PIEOBJ.formatShrtInt(value(d)));
});
});
svg.selectAll("textPath")
.text(function(d) {
if (d.name === 'root') {
return;
} else if ((d.depth === 1) && (d.dx < (d.name.length * 0.15))) {
return;
} else if ((d.depth === 2) && (d.dx < (d.name.length * 0.1))) {
return;
} else {
return d.name;
}
});
var arcs2 = svg.data([json]).selectAll(".arcG");
arcs2.data(partition.nodes)
.transition()
.duration(3500)
.attr("transform", function(d) {
var c = arc.centroid(d),
x = c[0],
y = c[1],
// pythagorean theorem for hypotenuse
h = Math.sqrt(x * x + y * y);
return "translate(" + (x / h * labelr) + ',' +
(y / h * labelr) + ")";
})
.attr("text-anchor", "middle");
svg.selectAll(".theTxtsOuter")
.text(function(d, i) {
if (d.name === 'root') {
return;
} else if ((d.depth === 1) && (d.dx < (d.name.length * 0.15))) {
return d.name;
} else if ((d.depth === 2) && (d.dx < (d.name.length * 0.1))) {
return d.name;
} else {
return;
}
});
// the following deletes what was originally created and then recreates the text
// svg.selectAll("#titleX").remove();
});
function mouseout() {
div.style("display", "none"); //<< gets rid of the tooltip <<
}
// Stash the old values for transition.
function stash(d) {
d.x0 = d.x;
d.dx0 = d.dx;
}
// Interpolate the arcs in data space.
function arcTween(a) {
var i = d3.interpolate({
x: a.x0,
dx: a.dx0
}, a);
return function(t) {
var b = i(t);
a.x0 = b.x;
a.dx0 = b.dx;
return arc(b);
};
}
});
}
// // Take a 2-column CSV and transform it into a hierarchical structure suitable
// // for a partition layout.
function buildHierarchy(csv) {
var root = {
"name": "root",
"children": []
};
for (var i = 0; i < csv.length; i++) {
var sequence = csv[i][0];
// var APD = +csv[i][1];
var SalesRev = +csv[i][1];
var Amount = +csv[i][2];
if (isNaN(SalesRev)) { // e.g. if this is a header row
continue;
}
var parts = sequence.split("-");
var currentNode = root;
for (var j = 0; j < parts.length; j++) {
var children = currentNode.children;
var nodeName = parts[j];
var childNode;
if (j + 1 < parts.length) {
// Not yet at the end of the sequence; move down the tree.
var foundChild = false;
for (var k = 0; k < children.length; k++) {
if (children[k].name == nodeName) {
childNode = children[k];
foundChild = true;
break;
}
}
// If we don't already have a child node for this branch, create it.
if (!foundChild) {
childNode = {
"name": nodeName,
"children": []
};
children.push(childNode);
}
currentNode = childNode;
} else {
// Reached the end of the sequence; create a leaf node.
childNode = {
"name": nodeName,
// "APD": APD,
"SalesRev": SalesRev,
"Amount": Amount
};
children.push(childNode);
}
}
}
root.children.forEach(function(v) {
v.SalesRev = 0;
v.Amount = 0;
v.children.forEach(function(a) {
v.SalesRev += a.SalesRev;
v.Amount += a.Amount;
});
});
return root;
}

When you initially position them you are transforming the text elements. When you transition them you are positioning the outer g elements. These causes conflicting transforms. Use:
arcs2.data(partition.nodes)
.select('text') //<-- apply on child text
.transition()
.duration(3500)
.attr("transform", function(d) {
...
});
Updated plunker.

Related

How to create a dashed line with arrows on each dash in d3?

How to make a line from arrows like a dashed line but somewhat like this "> > > >" for direction of the flow. I am trying to connect nodes via lines but my nodes have to resizable rectangles and it hides the arrowheads , which is why I am looking for such arrow(ed) line. Specifying arrow in the middle could also solve the problem but attr("marker-middle", "url(#arrowhead)") does not work in my code.
Here's the relevant code
svg
.append("defs")
.append("marker")
.attrs({
id: "arrowhead",
viewBox: "-0 -5 10 10",
refX: 13,
refY: 0,
orient: "auto",
markerWidth: 3,
markerHeight: 3
})
.append("svg:path")
.attr("d", "M 0,-5 L 10 ,0 L 0,5")
.attr("fill", "black")
.call(
d3.zoom().on("zoom", function() {
console.log(d3.event.transform);
svg.attr("transform", d3.event.transform);
})
);
var link = svg
.append("g")
.selectAll("line_class.line")
.data(graph.links)
.enter()
.append("line")
.attr("stroke-width", function(d) {
return Math.max(
((d.thickness - min_thickness) /
(max_thickness - min_thickness + 0.01)) *
5,
3
);
})
.style("stroke", "pink")
.text("text", function(d) {
return d.linkname;
})
.on("mouseover", function(d) {
div
.transition()
.duration(200)
.style("opacity", 0.9);
div
.html("Name: " + d.linkname)
.style("left", d3.event.pageX + "px")
.style("top", d3.event.pageY - 28 + "px")
.style("width", "auto")
.style("height", "auto");
})
.on("mouseout", function(d) {
div
.transition()
.duration(500)
.style("opacity", 0);
});
link.call(updateState1);
function updateState1() {
link
.each(function(d) {
//d3.select(this).attr("marker-mid", "url(#arrowhead)");
d3.select(this).attr("marker-end", "url(#arrowhead)");
});
}
var config = [];
var graph = {
nodes: [
{ name: "A" },
{ name: "B" },
{ name: "C" },
{ name: "D" },
{ name: "Dummy1" },
{ name: "Dummy2" },
{ name: "Dummy3" },
{ name: "Dummy4" }
],
links: [
{ source: "A", target: "B", linkname: "A0" },
{ source: "A", target: "C", linkname: "A0" },
{ source: "A", target: "D", linkname: "A1" },
{ source: "Dummy1", target: "A", linkname: "input" },
{ source: "B", target: "Dummy2", linkname: "B" },
{ source: "C", target: "Dummy3", linkname: "C" },
{ source: "D", target: "Dummy4", linkname: "D" }
]
};
var optArray = [];
labelAnchors = [];
labelAnchorLinks = [];
highlightNode_button = d3.select("#highlightNode");
highlightNode_button.on("click", highlightNode);
shrinkNode_button = d3.select("#shrinkNode");
shrinkNode_button.on("click", minimizeNode);
for (var i = 0; i < graph.nodes.length - 1; i++) {
optArray.push(graph.nodes[i].name);
}
optArray = optArray.sort();
$(function() {
$("#targetNode").autocomplete({
source: optArray
});
});
//used to find max thickness of all the nodes, which is used for normalizing later on.
var max_thickness = d3.max(graph.links, function(d) {
return d.thickness;
});
//used to find min thickness of all the nodes, which is used for normalizing later on.
var min_thickness = d3.min(graph.links, function(d) {
return d.thickness;
});
var svg1 = d3.select("svg");
var width = +screen.width;
var height = +screen.height - 500;
svg1.attr("width", width).attr("height", height);
var zoom = d3.zoom().on("zoom", zoomed);
function zoomed() {
svg.attr("transform", d3.event.transform);
}
var svg = svg1
// .call(
// zoom.on("zoom", function() {
// svg.attr("transform", d3.event.transform);
// })
// )
.call(zoom)
.on("dblclick.zoom", null)
.append("g");
// Defining the gradient
//used for coloring the nodes
var gradient = svg
.append("svg:defs")
.append("svg:linearGradient")
.attr("id", "gradient")
.attr("x1", "0%")
.attr("y1", "0%")
.attr("x2", "100%")
.attr("y2", "100%")
.attr("spreadMethod", "pad");
// Define the gradient colors
gradient
.append("svg:stop")
.attr("offset", "0%")
.attr("stop-color", "#b721ff")
.attr("stop-opacity", 1);
gradient
.append("svg:stop")
.attr("offset", "100%")
.attr("stop-color", "#21d4fd")
.attr("stop-opacity", 1);
var linkText = svg
.selectAll(".gLink")
.data(graph.links)
.append("text")
.attr("font-family", "Arial, Helvetica, sans-serif")
.attr("x", function(d) {
if (d.target.x > d.source.x) {
return d.source.x + (d.target.x - d.source.x) / 2;
} else {
return d.target.x + (d.source.x - d.target.x) / 2;
}
})
.attr("y", function(d) {
if (d.target.y > d.source.y) {
return d.source.y + (d.target.y - d.source.y) / 2;
} else {
return d.target.y + (d.source.y - d.target.y) / 2;
}
})
.attr("fill", "Black")
.style("font", "normal 12px Arial")
.attr("dy", ".35em")
.text(function(d) {
return d.linkname;
});
var dragDrop = d3
.drag()
.on("start", node => {
node.fx = node.x;
node.fy = node.y;
})
.on("drag", node => {
simulation.alphaTarget(1).restart();
node.fx = d3.event.x;
node.fy = d3.event.y;
})
.on("end", node => {
if (!d3.event.active) {
simulation.alphaTarget(0);
}
node.fx = null;
node.fy = null;
});
var linkForce = d3
.forceLink(graph.links)
.id(function(d) {
return d.name;
})
.distance(50);
//.strength(0.5) to specify the pulling strength from each link
// strength from each node
// Saving a reference to node attribute
var nodeForce = d3.forceManyBody().strength(-30);
var simulation = d3
.forceSimulation(graph.nodes)
.force("links", linkForce)
.force("charge", nodeForce)
.force("center", d3.forceCenter(width / 2, height / 2))
.on("tick", ticked);
var div = d3
.select("body")
.append("div")
.attr("class", "tooltip")
.style("opacity", 0);
svg
.append("defs")
.append("marker")
.attrs({
id: "arrowhead",
viewBox: "-0 -5 10 10",
refX: 100,
refY: 0,
orient: "auto-start-reverse",
markerWidth: 3,
markerHeight: 3
})
.append("svg:path")
.attr("d", "M 0,-5 L 10 ,0 L 0,5")
.attr("fill", "black");
{
{
/* .call(
d3.zoom().on("zoom", function() {
console.log(d3.event.transform);
svg.attr("transform", d3.event.transform);
})
); */
}
}
var link = svg
.append("g")
.selectAll("line_class.line")
.data(graph.links)
.enter()
.append("line")
.attr("stroke-width", function(d) {
return Math.max(
((d.thickness - min_thickness) / (max_thickness - min_thickness + 0.01)) *
5,
3
);
})
.style("stroke", "pink")
.text("text", function(d) {
return d.linkname;
})
.on("mouseover", function(d) {
div
.transition()
.duration(200)
.style("opacity", 0.9);
div
.html("Name: " + d.linkname)
.style("left", d3.event.pageX + "px")
.style("top", d3.event.pageY - 28 + "px")
.style("width", "auto")
.style("height", "auto");
})
.on("mouseout", function(d) {
div
.transition()
.duration(500)
.style("opacity", 0);
})
.attr("marker-start", "url(#arrowhead)");
// .attr("marker-start", "url(#arrowhead)")
// .attr("marker-mid", "url(#arrowhead)")
// .attr("marker-end", "url(#arrowhead)");
// var line = link
var edgepaths = svg
.selectAll(".edgepath")
.data(graph.links)
.enter()
.append("path")
.attr("d", function(d) {
return (
"M " +
d.source.x +
" " +
d.source.y +
" L " +
d.target.x +
" " +
d.target.y
);
})
.attr("class", "edgepath")
.attr("fill-opacity", 0)
.attr("stroke-opacity", 0)
.attr("fill", "blue")
.attr("stroke", "red")
.attr("id", function(d, i) {
return "edgepath" + i;
})
.style("pointer-events", "none");
var radius = 4;
var node_data_array = [];
var node_name = "";
var node = svg
.append("g")
.selectAll("circle_class.circle")
.data(graph.nodes)
.enter()
.append("rect")
.attr("width", 40)
.attr("height", 20)
.attr("fill", "url(#gradient)")
.style("transform", "translate(-20px,-10px)")
.attr("stroke", "purple")
.attr("id", function(d) {
node_name = d.name;
return d.name;
})
.on("dblclick", connectedNodes);
var textElements_nodes = svg
.append("g")
.selectAll("text")
.data(graph.nodes)
.enter()
.append("text")
.text(function(d) {
return d.name;
})
.attr("font-size", 15)
.attr("dx", 15)
.attr("dy", -5);
node.call(dragDrop);
link.call(updateState1);
var path = document.querySelector("path"),
totalLength = path.getTotalLength(),
group = totalLength / 20,
start;
var arrowheads = d3
.select("svg")
.selectAll("use")
.data(
d3.range(20).map(function(d) {
return d * group + 50;
})
)
.enter()
.append("use")
.attr("xlink:href", "#arrowhead");
path.style.strokeDasharray = "50," + (group - 50);
requestAnimationFrame(update);
function update(t) {
if (!start) {
start = t;
}
var offset = (-group * ((t - start) % 900)) / 900;
path.style.strokeDashoffset = offset;
arrowheads.attr("transform", function(d) {
var l = d - offset;
if (l < 0) {
l = totalLength + l;
} else if (l > totalLength) {
l -= totalLength;
}
var p = pointAtLength(l);
return "translate(" + p + ") rotate( " + angleAtLength(l) + ")";
});
//requestAnimationFrame(update);
}
function pointAtLength(l) {
var xy = path.getPointAtLength(l);
return [xy.x, xy.y];
}
// Approximate tangent
function angleAtLength(l) {
var a = pointAtLength(Math.max(l - 0.01, 0)), // this could be slightly negative
b = pointAtLength(l + 0.01); // browsers cap at total length
return (Math.atan2(b[1] - a[1], b[0] - a[0]) * 180) / Math.PI;
}
// link.attr("marker-end", "url(#end)");
function updateState1() {
link.each(function(d) {
var colors = ["red", "green", "blue"];
var num = 0;
if (d.source.name.startsWith("Dummy")) {
num = 1;
console.log("Inside 1");
console.log(
"Source is ",
d.source.name,
" and target is ",
d.target.name
);
} else if (d.target.name.startsWith("Dummy")) {
num = 2;
console.log("Inside 2");
console.log(
"Source is ",
d.source.name,
" and target is ",
d.target.name
);
} else {
num = 0;
for (i = 0; i < graph.input_nodes.length; i++) {
if (graph.input_nodes[i].name == d.source.name) {
num = 1;
}
}
}
d3.select(this).style("stroke", function(d) {
return colors[num];
});
{
{
/* d3.select(this).attr("marker-end", "url(#arrowhead)"); */
}
}
{
{
/* d3.select(this).attr("marker-mid", "url(#arrowhead)"); */
}
}
// .attr("marker-end", "url(#arrowhead)");
});
}
function pointOnRect(x, y, minX, minY, maxX, maxY, validate) {
//assert minX <= maxX;
//assert minY <= maxY;
if (validate && minX < x && x < maxX && minY < y && y < maxY)
throw "Point " +
[x, y] +
"cannot be inside " +
"the rectangle: " +
[minX, minY] +
" - " +
[maxX, maxY] +
".";
var midX = (minX + maxX) / 2;
var midY = (minY + maxY) / 2;
// if (midX - x == 0) -> m == ±Inf -> minYx/maxYx == x (because value / ±Inf = ±0)
var m = (midY - y) / (midX - x);
if (x <= midX) {
// check "left" side
var minXy = m * (minX - x) + y;
if (minY <= minXy && minXy <= maxY) return { x: minX, y: minXy };
}
if (x >= midX) {
// check "right" side
var maxXy = m * (maxX - x) + y;
if (minY <= maxXy && maxXy <= maxY) return { x: maxX, y: maxXy };
}
if (y <= midY) {
// check "top" side
var minYx = (minY - y) / m + x;
if (minX <= minYx && minYx <= maxX) return { x: minYx, y: minY };
}
if (y >= midY) {
// check "bottom" side
var maxYx = (maxY - y) / m + x;
if (minX <= maxYx && maxYx <= maxX) return { x: maxYx, y: maxY };
}
// edge case when finding midpoint intersection: m = 0/0 = NaN
if (x === midX && y === midY) return { x: x, y: y };
// Should never happen :) If it does, please tell me!
throw "Cannot find intersection for " +
[x, y] +
" inside rectangle " +
[minX, minY] +
" - " +
[maxX, maxY] +
".";
}
var label_toggle = 0;
function show_hide_node_labels() {
if (label_toggle) {
textElements_nodes.style("visibility", "visible");
} else {
textElements_nodes.style("visibility", "hidden");
}
label_toggle = !label_toggle;
}
var toggle = 0;
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;
});
//DAT GUI for controls
var gui = new dat.GUI({ width: 300 });
config = {
linkStrength: 1,
linkDistance: 180,
nodeStrength: -30,
Width: 40,
Height: 20,
restart: reset,
showHideNodeLabels: show_hide_node_labels
};
var linkDistanceChanger = gui
.add(config, "linkDistance", 0, 400)
.step(1)
.name("Link Distance");
linkDistanceChanger.onChange(function(value) {
linkForce.distance(value);
simulation.alpha(1).restart();
});
var linkStrengthChanger = gui
.add(config, "linkStrength", 0, 1)
.name("Link Strength");
linkStrengthChanger.onChange(function(value) {
linkForce.strength(value);
simulation.alpha(1).restart();
});
var nodeStrengthChanger = gui
.add(config, "nodeStrength", -500, -1)
.step(1)
.name("Node Strength");
nodeStrengthChanger.onChange(function(value) {
nodeForce.strength(value);
simulation.alpha(1).restart();
});
var widthChanger = gui
.add(config, "Width", 4, 100)
.step(1)
.name("Node Width");
widthChanger.onChange(function(value) {
node.attr("width", value);
og_width = value;
// d3.select("#arrowhead").attrs({
// markerWidth: value * 0.4,
// markerHeight: value * 0.4
// });
simulation.alpha(1).restart();
});
var heightChanger = gui
.add(config, "Height", 2, 80)
.step(1)
.name("Node Height");
heightChanger.onChange(function(value) {
node.attr("height", value);
og_height = value;
// d3.select("#arrowhead").attrs({
// markerWidth: value * 0.4,
// markerHeight: value * 0.4
// });
simulation.alpha(1).restart();
});
gui.add(config, "showHideNodeLabels").name("Show/Hide Node Labels");
gui.add(config, "restart").name("Restart");
for (i = 0; i < gui.__ul.childNodes.length; i++) {
gui.__ul.childNodes[i].classList += " longtext";
}
function reset() {
window.location.reload();
}
//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;
});
textElements_nodes.style("opacity", function(o) {
return neighboring(d, o) | neighboring(o, d) ? 1 : 0.1;
});
toggle = 1;
} else {
//Put them back to opacity=1
node.style("opacity", 1);
link.style("opacity", 1);
textElements_nodes.style("opacity", 1);
toggle = 0;
}
}
function ticked() {
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;
})
.attr("d", function(d) {
var inter = pointOnRect(
d.source.x,
d.source.y,
d.target.x - 20,
d.target.y - 20,
d.target.x + 40 - 20,
d.target.y + 20 - 20
);
return (
"M" + d.source.x + "," + d.source.y + "L" + inter.x + "," + inter.y
);
});
node
.attr("x", function(d) {
return d.x;
})
.attr("y", function(d) {
return d.y;
});
textElements_nodes.attr("x", node => node.x).attr("y", node => node.y);
edgepaths.attr("d", function(d) {
var path =
"M " +
d.source.x +
" " +
d.source.y +
" L " +
d.target.x +
" " +
d.target.y;
return path;
});
var ticking = false;
}
var pulse = false;
function pulsed(rect) {
(function repeat() {
if (pulse) {
rect
.transition()
.duration(200)
.attr("stroke-width", 0)
.attr("stroke-opacity", 0)
.attr("width", config.Width)
.attr("height", config.Height)
.transition()
.duration(200)
.attr("stroke-width", 0)
.attr("stroke-opacity", 0.5)
.attr("width", config.Width * 3)
.attr("height", config.Height * 3)
.transition()
.duration(400)
.attr("stroke-width", 65)
.attr("stroke-opacity", 0)
.attr("width", config.Width)
.attr("height", config.Height)
.ease(d3.easeSin)
.on("end", repeat);
} else {
ticking = false;
rect
.attr("width", config.Width)
.attr("height", config.Height)
.attr("fill", "url(#gradient)")
.attr("stroke", "purple");
}
})();
}
function highlightNode() {
var userInput = document.getElementById("targetNode");
var temp = userInput.value;
var userInputRefined = temp.replace(/[/]/g, "\\/");
// make userInput work with "/" as they are considered special characters
// the char "/" is escaped with escape characters.
theNode = d3.select("#" + userInputRefined);
const isEmpty = theNode.empty();
if (isEmpty) {
document.getElementById("output").innerHTML = "Given node doesn't exist";
} else {
document.getElementById("output").innerHTML = "";
}
pulse = true;
if (pulse) {
pulsed(theNode);
}
scalingFactor = 0.5;
// Create a zoom transform from d3.zoomIdentity
var transform = d3.zoomIdentity
.translate(
screen.width / 2 - scalingFactor * theNode.attr("x"),
screen.height / 4 - scalingFactor * theNode.attr("y")
)
.scale(scalingFactor);
// Apply the zoom and trigger a zoom event:
svg1.call(zoom.transform, transform);
}
function minimizeNode() {
var userInput = document.getElementById("targetNode");
var temp = userInput.value;
var userInputRefined = temp.replace(/[/]/g, "\\/");
// make userInput work with "/" as they are considered special characters
// the char "/" is escaped with escape characters.
theNode = d3.select("#" + userInputRefined);
const isEmpty = theNode.empty();
if (isEmpty) {
document.getElementById("output").innerHTML = "Given node doesn't exist";
} else {
document.getElementById("output").innerHTML = "";
}
pulse = false;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.0.0/d3.min.js"></script>
<!DOCTYPE html>
<html>
<head>
<link rel="icon" href="data:;base64,iVBORw0KGgo=" />
<title>Force Layout Example 9</title>
<style>
div.tooltip {
position: absolute;
text-align: center;
width: 60px;
height: 28px;
padding: 2px;
font: 12px sans-serif;
background: lightsteelblue;
border: 0px;
border-radius: 8px;
pointer-events: none;
}
.longtext {
line-height: 13px;
height: 40px;
font-size: 120%;
}
rect.zoom-panel {
cursor: move;
fill: #fff;
pointer-events: all;
}
.bar {
fill: rgb(70, 180, 70);
}
.graph-svg-component {
background-color: green;
}
path {
fill: none;
stroke: #d3008c;
stroke-width: 2px;
}
#arrowhead {
fill: #d3008c;
stroke: none;
}
</style>
</head>
<body>
<svg></svg>
<div id="content"></div>
<script src="http://d3js.org/d3.v5.min.js"></script>
<script src="https://d3js.org/d3-dispatch.v1.min.js"></script>
<script src="https://d3js.org/d3-selection.v1.min.js"></script>
<script src="https://d3js.org/d3-drag.v1.min.js"></script>
<script src="https://d3js.org/d3-selection-multi.v1.min.js"></script>
<script src="https://d3js.org/d3-transition.v1.min.js"></script>
<script
type="text/javascript"
src="https://cdnjs.cloudflare.com/ajax/libs/dat-gui/0.7.6/dat.gui.min.js"
></script>
<link
href="https://ajax.googleapis.com/ajax/libs/jqueryui/1.12.1/themes/base/jquery-ui.css"
rel="stylesheet"
type="text/css"
/>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jqueryui/1.12.1/jquery-ui.min.js"></script>
</body>
</html>
I think this is what you want, this code snippet based on this bl.ocks.org snippet and it meant to be animated by I removed the requestAnimationFrame call from the update function.
regarding your original issue with nodes hiding the arrow, you can use refX and refY attribute on <marker> to change its offset.
var path = document.querySelector("path"),
totalLength = path.getTotalLength(),
group = totalLength / 20,
start;
var arrowheads = d3.select("svg").selectAll("use")
.data(d3.range(20).map(function(d){ return d * group + 50; }))
.enter()
.append("use")
.attr("xlink:href", "#arrowhead");
path.style.strokeDasharray = "50," + (group - 50);
requestAnimationFrame(update);
function update(t) {
if (!start) {
start = t;
}
var offset = -group * ((t - start) % 900) / 900;
path.style.strokeDashoffset = offset;
arrowheads.attr("transform",function(d){
var l = d - offset;
if (l < 0) {
l = totalLength + l;
} else if (l > totalLength) {
l -= totalLength;
}
var p = pointAtLength(l);
return "translate(" + p + ") rotate( " + angleAtLength(l) + ")";
});
//requestAnimationFrame(update);
}
function pointAtLength(l) {
var xy = path.getPointAtLength(l);
return [xy.x, xy.y];
}
// Approximate tangent
function angleAtLength(l) {
var a = pointAtLength(Math.max(l - 0.01,0)), // this could be slightly negative
b = pointAtLength(l + 0.01); // browsers cap at total length
return Math.atan2(b[1] - a[1], b[0] - a[0]) * 180 / Math.PI;
}
path {
fill: none;
stroke: #d3008c;
stroke-width: 2px;
}
#arrowhead {
fill: #d3008c;
stroke: none;
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
</head>
<body>
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="960" height="500">
<path d="M636.5,315c-0.4-18.7,1.9-27.9-5.3-35.9
c-22.7-25-107.3-2.8-118.3,35.9c-7,24.4,20.6,37.2,16,71c-4,29.6-30.8,60.7-56.5,61.1c-30.8,0.4-32.9-43.8-81.7-70.2
c-50.9-27.6-110.1-12.9-125.2-9.2c-66.1,16.4-82.2,56.9-109.2,47.3c-38-13.6-55.9-112.1-19.8-143.5c39-34,121.2,27.7,148.1-3.8
c18-21.1,3.1-74.3-25.2-105.3c-31.1-34.1-70.1-32.4-105.3-76.3c-8.2-10.2-16.9-23.8-15.3-39.7c1.2-11.4,7.5-23.3,15.3-29
c33.8-25,101.6,62.6,193.1,59.5c40.1-1.3,38.7-18.5,99.2-38.9c126.2-42.6,242.4-4.9,297.7,13c54.7,17.7,105.4,35,129.8,82.4
c13,25.3,22.9,67.7,4.6,87c-11.6,12.3-25.1,5.1-46.6,20.6c-2.8,2-28.9,21.4-32.1,49.6c-3.1,27.4,18.7,35,29,70.2
c8.8,30.1,8.5,77.8-18.3,99.2c-32.3,25.8-87,0.6-100-5.3c-69.6-32-67.2-88.4-73.3-109.2z"/>
<defs>
<path id="arrowhead" d="M7,0 L-7,-5 L-7,5 Z" />
</defs>
</svg>
<script src="https://d3js.org/d3.v4.min.js"></script>

How to display two d3 wordclouds on one page

I'm trying to put two (or even more -> that depends on the users settings) d3 Wordclouds to one page. However I'll get the following result:
It seems, that the word list won't be parsed correct.
My code looks like this:
(The php $Block variable specifies the position, where the recent wordcloud should be shown.)
var container = "svg_<?php echo $Block;?>";
var w = $('#word_cloud_<?php echo $Block;?>').width();
var h = w*0.75;
if($( window ).width()<400){
var maxRange=20;
}else if($( window ).width()<800){
var maxRange=40;
}else if($( window ).width()<1200){
var maxRange=60;
}else if($( window ).width()>=1200){
var maxRange=95;
}
var list_<?php echo $Block;?>=<?php echo $jsonWort; ?>;
var wordSize=12;
var layout;
generate_<?php echo $Block;?>(list_<?php echo $Block;?>);
function generate_<?php echo $Block;?>(list) {
//Blacklist wird gefiltert!
var blacklist=["ein","sind"];
list=list.filter(function(x) { return blacklist.indexOf(x) < 0 });
// Liste wird verarbeitet
result = { };
for(i = 0; i < list.length; ++i) {
if(!result[list[i]])
result[list[i]] = 0;
++result[list[i]];
}
var newList = _.uniq(list);
var frequency_list = [];
var len = newList.length;
for (var i = 0; i < len; i++) {
var temp = newList[i];
frequency_list.push({
text : temp,
freq : result[newList[i]],
time : 0
});
}
frequency_list.sort(function(a,b) { return parseFloat(b.freq) - parseFloat(a.freq) } );
for(var t = 0 ; t < len ; t++)
{
var addTime = (100 * t) +500;
frequency_list[t].time=addTime;
}
for(i in frequency_list){
if(frequency_list[i].freq*wordSize > 160)
wordSize = 3;
}
var sizeScale = d3.scale.linear().domain([0, d3.max(frequency_list, function(d) { return d.freq} )]).range([5, maxRange]);
layout= d3.layout.cloud().size([w, h])
.words(frequency_list)
.padding(5)
.rotate(function() { return ~~(Math.random() * 2) * 90; })
.font("Impact")
.fontSize(function(d) { return sizeScale(d.freq); })
.on("end",draw)
.start();
}
function draw(words) {
var fill = d3.scale.category20();
d3.select(container).remove();
d3.select("#word_cloud_<?php echo $Block;?>").append(container)
.attr("width", w)
.attr("height", h)
.append("g")
.attr("transform", "translate(" + [w/2, h/2] + ")")
.selectAll("text")
.data(words)
.enter().append("text")
.transition()
.duration(function(d) { return d.time} )
.attr('opacity', 1)
.style("font-size", function(d) { return d.size + "px"; })
.style("font-family", "Impact")
.style("fill", function(d, i) { return fill(i); })
.attr("text-anchor", "middle")
.attr("transform", function(d) {
return "rotate(" + d.rotate + ")";
})
.attr("transform", function(d) {
return "translate(" + [d.x, d.y] + ")rotate(" + d.rotate + ")";
})
.text(function(d) { return d.text; });
}
I believe that the mistake has to be somewhere in
var sizeScale = d3.scale.linear().domain([0, d3.max(frequency_list, function(d) { return d.freq} )]).range([5, maxRange]);
layout= d3.layout.cloud().size([w, h])
.words(frequency_list)
.padding(5)
.rotate(function() { return ~~(Math.random() * 2) * 90; })
.font("Impact")
.fontSize(function(d) { return sizeScale(d.freq); })
.on("end",draw)
.start();
But I'm not able to find it.
You overwrite it because of the line
d3.select(container).remove();
With d3 you are basically creating complex SVGs on the fly. You can have as many elements as you want. As long as you do not remove them, they'll remain there. But you need to also make sure that they do not overlap. See here and (very cool!) here

d3.js Sankey Link Transition not transitioning in-place (jsbin provided)

Here is my jsbin: https://jsbin.com/fepakitayi/edit?html,output
Press the "transition" button to transition graph.
Everything is working fine (text is transitioning, titles are transitioning, nodes are transitioning) and the links are transitioning but for some reason they scatter about and THEN change sizes; that is, they disconnect from the two nodes they are attached to ad reconnect to two different nodes. Is there a way to make the links transition to the new size in-place? For example, I'd like for them to transition WITH the nodes and only shift if their respective node shifts.
BTW, jsbin didn't have an option to add the d3.sankey library so I had to put that in with my js so it's a bit cluttered, but my code is at the bottom of the javascript section.
Relevant code:
After the on('click' event, I update the graph.links and then transition to the new graph.links with new stroke-width.
link
// svg.selectAll(".link")
.data(graph.links)
.transition()
.duration(1000)
.attr("class", "link")
.attr("d", path)
.style("stroke-width", function(d) { return Math.max(1, d.dy); })
I've searched google for d3.sankey transitions but didn't find much.
You've got data-binding troubles with your graph.links variable. Since you don't specify a key function, when you re-bind your data the links don't stay associated to the same nodes they belonged to before. An easy fix is:
link
.data(graph.links, function(d) {
return d.source.name + d.target.name; //<-- create a key that keeps a link associated with it's nodes
})
.transition()
.duration(1000)
.attr("class", "link")
.attr("d", path)
.style("stroke-width", function(d) { return Math.max(1, d.dy); });
Full working code here.
Trying to update the content of d3 sankey chart on mouse click. The new nodes and links are not getting updated. Below is the code and any inputs to fix it would be great.
The new nodes and links remains the same, if its updated with new nodes and links for the new set of data.
d3.sankey = function() {
var sankey = {},
nodeWidth = 24,
nodePadding = 8,
size = [1, 1],
nodes = [],
links = [];
sankey.nodeWidth = function(_) {
if (!arguments.length) return nodeWidth;
nodeWidth = +_;
return sankey;
};
sankey.nodePadding = function(_) {
if (!arguments.length) return nodePadding;
nodePadding = +_;
return sankey;
};
sankey.nodes = function(_) {
if (!arguments.length) return nodes;
nodes = _;
return sankey;
};
sankey.links = function(_) {
if (!arguments.length) return links;
links = _;
return sankey;
};
sankey.size = function(_) {
if (!arguments.length) return size;
size = _;
return sankey;
};
sankey.layout = function(iterations) {
computeNodeLinks();
computeNodeValues();
computeNodeBreadths();
computeNodeDepths(iterations);
computeLinkDepths();
return sankey;
};
sankey.relayout = function() {
computeLinkDepths();
return sankey;
};
sankey.link = function() {
var curvature = .5;
function link(d) {
var x0 = d.source.x + d.source.dx,
x1 = d.target.x,
xi = d3.interpolateNumber(x0, x1),
x2 = xi(curvature),
x3 = xi(1 - curvature),
y0 = d.source.y + d.sy + d.dy / 2,
y1 = d.target.y + d.ty + d.dy / 2;
return "M" + x0 + "," + y0 + "C" + x2 + "," + y0 + " " + x3 + "," + y1 + " " + x1 + "," + y1;
}
link.curvature = function(_) {
if (!arguments.length) return curvature;
curvature = +_;
return link;
};
return link;
};
// Populate the sourceLinks and targetLinks for each node.
// Also, if the source and target are not objects, assume they are indices.
function computeNodeLinks() {
nodes.forEach(function(node) {
node.sourceLinks = [];
node.targetLinks = [];
});
links.forEach(function(link) {
var source = link.source,
target = link.target;
if (typeof source === "number") source = link.source = nodes[link.source];
if (typeof target === "number") target = link.target = nodes[link.target];
source.sourceLinks.push(link);
target.targetLinks.push(link);
});
}
// Compute the value (size) of each node by summing the associated links.
function computeNodeValues() {
nodes.forEach(function(node) {
node.value = Math.max(
d3.sum(node.sourceLinks, value),
d3.sum(node.targetLinks, value)
);
});
}
// Iteratively assign the breadth (x-position) for each node.
// Nodes are assigned the maximum breadth of incoming neighbors plus one;
// nodes with no incoming links are assigned breadth zero, while
// nodes with no outgoing links are assigned the maximum breadth.
function computeNodeBreadths() {
var remainingNodes = nodes,
nextNodes,
x = 0;
while (remainingNodes.length) {
nextNodes = [];
remainingNodes.forEach(function(node) {
node.x = x;
node.dx = nodeWidth;
node.sourceLinks.forEach(function(link) {
if (nextNodes.indexOf(link.target) < 0) {
nextNodes.push(link.target);
}
});
});
remainingNodes = nextNodes;
++x;
}
//
moveSinksRight(x);
scaleNodeBreadths((size[0] - nodeWidth) / (x - 1));
}
function moveSourcesRight() {
nodes.forEach(function(node) {
if (!node.targetLinks.length) {
node.x = d3.min(node.sourceLinks, function(d) {
return d.target.x;
}) - 1;
}
});
}
function moveSinksRight(x) {
nodes.forEach(function(node) {
if (!node.sourceLinks.length) {
node.x = x - 1;
}
});
}
function scaleNodeBreadths(kx) {
nodes.forEach(function(node) {
node.x *= kx;
});
}
function computeNodeDepths(iterations) {
var nodesByBreadth = d3.nest()
.key(function(d) {
return d.x;
})
.sortKeys(d3.ascending)
.entries(nodes)
.map(function(d) {
return d.values;
});
//
initializeNodeDepth();
resolveCollisions();
for (var alpha = 1; iterations > 0; --iterations) {
relaxRightToLeft(alpha *= .99);
resolveCollisions();
relaxLeftToRight(alpha);
resolveCollisions();
}
function initializeNodeDepth() {
var ky = d3.min(nodesByBreadth, function(nodes) {
return (size[1] - (nodes.length - 1) * nodePadding) / d3.sum(nodes, value);
});
nodesByBreadth.forEach(function(nodes) {
nodes.forEach(function(node, i) {
node.y = i;
node.dy = node.value * ky;
});
});
links.forEach(function(link) {
link.dy = link.value * ky;
});
}
function relaxLeftToRight(alpha) {
nodesByBreadth.forEach(function(nodes, breadth) {
nodes.forEach(function(node) {
if (node.targetLinks.length) {
var y = d3.sum(node.targetLinks, weightedSource) / d3.sum(node.targetLinks, value);
node.y += (y - center(node)) * alpha;
}
});
});
function weightedSource(link) {
return center(link.source) * link.value;
}
}
function relaxRightToLeft(alpha) {
nodesByBreadth.slice().reverse().forEach(function(nodes) {
nodes.forEach(function(node) {
if (node.sourceLinks.length) {
var y = d3.sum(node.sourceLinks, weightedTarget) / d3.sum(node.sourceLinks, value);
node.y += (y - center(node)) * alpha;
}
});
});
function weightedTarget(link) {
return center(link.target) * link.value;
}
}
function resolveCollisions() {
nodesByBreadth.forEach(function(nodes) {
var node,
dy,
y0 = 0,
n = nodes.length,
i;
// Push any overlapping nodes down.
nodes.sort(ascendingDepth);
for (i = 0; i < n; ++i) {
node = nodes[i];
dy = y0 - node.y;
if (dy > 0) node.y += dy;
y0 = node.y + node.dy + nodePadding;
}
// If the bottommost node goes outside the bounds, push it back up.
dy = y0 - nodePadding - size[1];
if (dy > 0) {
y0 = node.y -= dy;
// Push any overlapping nodes back up.
for (i = n - 2; i >= 0; --i) {
node = nodes[i];
dy = node.y + node.dy + nodePadding - y0;
if (dy > 0) node.y -= dy;
y0 = node.y;
}
}
});
}
function ascendingDepth(a, b) {
return a.y - b.y;
}
}
function computeLinkDepths() {
nodes.forEach(function(node) {
node.sourceLinks.sort(ascendingTargetDepth);
node.targetLinks.sort(ascendingSourceDepth);
});
nodes.forEach(function(node) {
var sy = 0,
ty = 0;
node.sourceLinks.forEach(function(link) {
link.sy = sy;
sy += link.dy;
});
node.targetLinks.forEach(function(link) {
link.ty = ty;
ty += link.dy;
});
});
function ascendingSourceDepth(a, b) {
return a.source.y - b.source.y;
}
function ascendingTargetDepth(a, b) {
return a.target.y - b.target.y;
}
}
function center(node) {
return node.y + node.dy / 2;
}
function value(link) {
return link.value;
}
return sankey;
};
var source = [{
"source": "L1",
"sourceNpi": "1477640589",
"target": "L2",
"targetNpi": "1932170750",
"value": 29
}, {
"source": "L2",
"sourceNpi": "1386731404",
"target": "L3",
"targetNpi": "1932170750",
"value": 22
}];
var source1 = [{
"source": "L1",
"sourceNpi": "1477640589",
"target": "L2",
"targetNpi": "1932170750",
"value": 2
}, {
"source": "L2",
"sourceNpi": "1386731404",
"target": "L3",
"targetNpi": "1932170750",
"value": 15
},
{
"source": "L2",
"sourceNpi": "1386731404",
"target": "L4",
"targetNpi": "1932170750",
"value": 15
}
];
run();
function run() {
var units = "";
var margin = {
top: 10,
right: 10,
bottom: 10,
left: 10
},
width = 400 - margin.left - margin.right,
height = 400 - margin.top - margin.bottom;
var formatNumber = d3.format(",.0f"), // zero decimal places
format = function(d) {
return formatNumber(d) + " " + units;
},
color = d3.scale.category20();
//Clear the existing SVG element to overwrite it
d3.select('svg').remove();
// append the svg canvas to the page
var svg = d3.select("#chart").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform",
"translate(" + margin.left + "," + margin.top + ")");
// Set the sankey diagram properties
var sankey = d3.sankey()
.nodeWidth(36)
.nodePadding(40)
.size([width, height]);
var path = sankey.link();
var data = source;
graph = {
"nodes": [],
"links": []
};
data.forEach(function(d) {
graph.nodes.push({
"name": d.source
});
graph.nodes.push({
"name": d.target
});
graph.links.push({
"source": d.source,
"target": d.target,
"value": +d.value
});
});
// 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, function(d) {
return d.source.name + d.target.name;
})
.enter().append("path")
.attr("class", "link")
.attr("d", path)
.style("stroke-width", function(d) {
return Math.max(1, d.dy);
})
.sort(function(a, b) {
return b.dy - a.dy;
});
// add the link titles
link.append("title")
.text(function(d) {
return d.source.name + " → " +
d.target.name + "\n" + format(d.value);
});
// add in the nodes
var node = svg.append("g").selectAll(".node")
.data(graph.nodes)
.enter().append("g")
.attr("class", "node")
.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
})
.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) {
return d.dy;
})
.attr("width", sankey.nodeWidth())
.style("fill", function(d) {
return d.color = color(d.name.replace(/ .*/, ""));
})
.style("stroke", function(d) {
return d3.rgb(d.color).darker(2);
})
.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")
.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);
}
d3.select("#here")
.on("click", function() {
sankey.relayout(3);
data = source1;
graph = {
"nodes": [],
"links": []
};
data.forEach(function(d) {
graph.nodes.push({
name: d.source
});
graph.nodes.push({
name: d.target
});
graph.links.push({
source: d.source,
target: d.target,
value: d.value
});
});
console.log(" nodes " + graph.nodes);
// 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) {
console.log(graph.nodes.indexOf(graph.links[i].source) + " " + graph.nodes.indexOf(graph.links[i].target));
graph.links[i].source = graph.nodes.indexOf(graph.links[i].source);
graph.links[i].target = graph.nodes.indexOf(graph.links[i].target);
});
graph.nodes.forEach(function(d, i) {
console.log("for each" + d + i);
graph.nodes[i] = {
"name": d
};
});
// console.log(" nodes "+ graph.nodes[3].name);
//console.log(" nodes "+ graph.nodes[4].name);
sankey
.nodes(graph.nodes)
.links(graph.links)
.layout(32);
interpolate = null;
console.log("checked nodes" + graph.nodes);
//node.data().slice();
node.data(graph.nodes, function(d) {
console.log("inside node " + d.name);
return d.name;
})
.transition()
.duration(1000)
.attr("class", "node")
.attr("transform", function(d) {
console.log("translate(" + d.x + "," + d.y + ")" + d.name)
return "translate(" + d.x + "," + d.y + ")";
});
link
.data(graph.links, function(d) {
console.log("links " + d.source.name + d.target.name);
return d.source.name + d.target.name;
})
.transition()
.duration(1000)
.attr("class", "link")
.attr("d", path)
.style("stroke-width", function(d) {
return Math.max(1, d.dy);
});
svg.selectAll(".link>title")
.data(graph.links)
.transition()
.text(function(d) {
console.log(" links " + d.source.name + d.target.name);
return d.source.name + " → " +
d.target.name + "\n" + format(d.value);
});
svg.selectAll("rect").data(graph.nodes)
.transition()
.duration(1000)
.attr("height", function(d) {
return d.dy;
});
svg.selectAll(".node>rect>title")
.data(graph.nodes)
.transition()
.duration(1000)
.text(function(d) {
return d.name + "\n" + format(d.value);
});
svg.selectAll("text")
.data(graph.nodes)
.transition()
.duration(1000)
.attr("x", -6)
.attr("y", function(d) {
return d.dy / 2;
})
.attr("dy", ".35em")
.attr("text-anchor", "end")
.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");
node.exit().remove();
link.exit().remove();
})
// })
;
}
.node rect {
cursor: move;
fill-opacity: .9;
shape-rendering: crispEdges;
}
.node text {
pointer-events: none;
text-shadow: 0 1px 0 #fff;
}
.link {
fill: none;
stroke: #000;
stroke-opacity: .2;
}
.link:hover {
stroke-opacity: .5;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.6/d3.js"></script>
<button id="here" class="btn btn-default">transition</button>
<p id="chart"></p>
https://jsfiddle.net/1onlbirt/L29u04pf/6/

D3js Force Layout, dynamic links

thanks for reading -- I am designing an interactive foreign language teaching tool using the D3js force bubble layout. In essence, users can select a language textbook chapter, part of speech, or specific word and the interface will display the word(s) in a series of bubbles each clustered together by their part of speech (adjective, noun, verb, etc.). These bubbles are free floating, no links between them.
When you click a word I have it open up more bubbles with information about that specific word (lexeme, meaning in english, frequency, etc.), but I want those bubbles to be attached to the original bubble with links. I'm having a lot of trouble conceptualizing how to do this dynamically (source -> target), even though it should be pretty straightforward.
First I draw the bubbles based on user input, some of which I've left out so the code doesn't get too long:
var nodes = d3.range(n).map(function(j) { return createNode(colorList, j); });
function createNode(info, j){
var keys = objectKeys;
var cluster, r;
if (info[j].Lexeme.length < 3) {
var radius = info[j].Lexeme.length * 15;
}
else {
var radius = info[j].Lexeme.length * 5;
}
$.each(keys, function(x, y){
if (info[j].POS == x){
cluster = y;
}
});
d = {cluster: cluster, radius: radius, info: info[j]};
if (!clusters[cluster] || (r > clusters[cluster].radius)) clusters[cluster] = d;
return d;
}
d3.layout.pack()
.sort(null)
.size([width, height])
.children(function(d) { return d.values;})
.value(function(d) { return d.radius * d.radius; })
.nodes({values: d3.nest()
.key(function(d) { return d.cluster; })
.entries(nodes)});
var force = d3.layout.force()
.nodes(nodes)
.size([width, height])
.gravity(.00001)
.charge(0)
.on("tick", tick)
.start();
var svg = d3.select("#canvas").append("svg")
.attr("width", width)
.attr("height", height);
var node = svg.selectAll("circle")
.data(nodes)
.enter().append("circle")
.attr("class", function (d, i) { return "node_n" + i; })
.style("fill", function(d) {return d.info.fillColor;})
.on("click", nodeSelect)
.on("dblclick", infoNode)
.call(force.drag);
//Add the SVG Text Element to the svgContainer
var text = svg.selectAll("text")
.data(nodes)
.enter()
.append("text");
//Add SVG Text Element Attributes
text
.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; })
.text( function (d) { return d.info.Lexeme; })
.attr("font-family", "sans-serif")
.attr("text-anchor", "middle")
.attr("font-size", "13px")
.attr("fill", "#000000");
node.transition()
.duration(750)
.delay(function(d, i) { return i * 5; })
.attrTween("r", function(d) {
var i = d3.interpolate(0, d.radius);
return function(t) { return d.radius = i(t); };
});
function tick(e) {
node
.each(cluster(5 * e.alpha * e.alpha))
.each(collide(.3))
.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; });
text
.attr("transform", transform);
}
function transform(d) {
return "translate(" + d.x + "," + d.y + ")";
}
function nodeSelect(d) {
if (d3.select(this).style("opacity") == 1 && nodeSelected == 0) {
nodeSelected = 1;
d3.select(this).style("opacity", ".3");
}
else if (d3.select(this).style("opacity") == .3 && nodeSelected == 1){
nodeSelected = 0;
d3.select(this).style("opacity", "1");
}
}
// Move d to be adjacent to the cluster node.
function cluster(alpha) {
return function(d) {
var cluster = clusters[d.cluster];
if (cluster === d) return;
var x = d.x - cluster.x,
y = d.y - cluster.y,
l = Math.sqrt(x * x + y * y),
r = d.radius + cluster.radius;
if (l != r) {
l = (l - r) / l * alpha;
d.x -= x *= l;
d.y -= y *= l;
cluster.x += x;
cluster.y += y;
}
};
}
// Resolves collisions between d and all other circles.
function collide(alpha) {
var quadtree = d3.geom.quadtree(nodes);
return function(d) {
var r = d.radius + maxRadius + Math.max(padding, clusterPadding),
nx1 = d.x - r,
nx2 = d.x + r,
ny1 = d.y - r,
ny2 = d.y + r;
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),
r = d.radius + quad.point.radius + (d.cluster === quad.point.cluster ? padding : clusterPadding);
if (l < r) {
l = (l - r) / 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;
});
};
}
function infoNodes(d){
$.each(d.info, function(i, val){
if (val != "" && val !="NA" && i != "fillColor"){
var b = d.cluster;
var r = 50;
var q = {cluster: b, radius: r, info: val, fillColor: d.info.fillColor};
if (!clusters[cluster] || (r > clusters[cluster].radius)) clusters[cluster] = q;
nodes.push(q);
}
});
update(d);
}
function update(j) {
node = svg.selectAll("circle")
.data(nodes)
.call(force.drag);
node.enter().append("circle")
.attr("class", function (d, i) { return "info_" + d;})
.attr("cx", function (d) { return d.x; })
.attr("cy", function (d) { return d.y; })
.attr("r", function (d) { if (d.info.length < 5 || d.info.length == null) { return 30; } else { return d.info.length * 3.3; } })
.style("fill", function(d) {return d.fillColor;})
.call(force.drag);
text = svg.selectAll("text")
.data(nodes)
.call(force.drag);
text.enter().append("text")
.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; })
.text( function (d) { return d.info; })
.attr("font-family", "sans-serif")
.attr("text-anchor", "middle")
.attr("font-size", "13px")
.attr("fill", "#000000")
.call(force.drag);
force.start();
}
The function can draw info nodes and update, but I can't figure out how to draw info nodes attached to the node you click with lines that connect them.
Let me know if more explanation is needed.

How to hide outer ring in zoomable sunburst

I wanted to hide the outer ring in Sunburst, and I wanted to show it when the user drills down the sunburst. So that I can clearly show the data in outer ring clearly.... Thanks in advance
Here is my code
var width = 700,
height = width,
radius = width / 2,
x = d3.scale.linear().range([0, 2 * Math.PI]),
y = d3.scale.pow().exponent(1.3).domain([0, 1]).range([0, radius]),
padding = 5,
duration = 1000;
color = d3.scale.category20c();
var div = d3.select("#vis");
div.select("svg").remove();
div.select("div").remove();
div.select("p").remove();
div.select("img").remove();
var vis = div.append("svg")
.attr("width", width + padding * 2)
.attr("height", height + (padding * 2) + 2000)
.append("g")
.attr("transform", "translate(" + [radius + padding, radius + padding] + ")");
/*
div.append("p")
.attr("id", "intro")
.text("Click to zoom!");
*/
var partition = d3.layout.partition()
.sort(null)
.value(function(d) { return 5.8 - d.depth; });
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, d.y ? y(d.y) : d.y); })
.outerRadius(function(d) { return Math.max(0, y(d.y + d.dy)); });
var json = JSON.parse(jsonStr);
var nodes = partition.nodes({children: json});
var path = vis.selectAll("path").data(nodes);
path.enter().append("path")
.attr("id", function(d, i) { return "path-" + i; })
.attr("d", arc)
.attr("fill-rule", "evenodd")
.style("fill", colour)
.attr("title",function(d)
{
return d.depth ? d.desc: "";
})
.on("click", nodeClicked);
var text = vis.selectAll("text").data(nodes);
var textEnter = text.enter().append("text")
.style("fill-opacity", 1)
.style("fill", function(d)
{
return brightness(d3.rgb(colour(d))) < 125 ? "#eee" : "#000";
})
.attr("text-anchor", function(d)
{
return x(d.x + d.dx / 2) > Math.PI ? "end" : "start";
})
.attr("dy", ".2em")
.attr("transform", function(d)
{
var multiline = (d.name || "").split(" ").length > 1,
angle = x(d.x + d.dx / 2) * 180 / Math.PI - 90,
rotate = angle + (multiline ? -0.5 : 0);
return "rotate(" + rotate + ")translate(" + (y(d.y) + padding) + ")rotate(" + (angle > 90 ? -180 : 0) + ")";
})
.attr("title",function(d)
{
return d.depth ? d.desc: "";
})
.on("click", showModelDetailsForCriteria);
textEnter.append("tspan")
.attr("x", 0)
.text(function(d) { return d.depth ? d.name.split(" ")[0] : ""; });
textEnter.append("tspan")
.attr("x", 0)
.attr("dy", "1em")
.text(function(d) { return d.depth ? d.name.split(" ")[1] || "" : ""; });
function nodeClicked(d)
{
path.transition()
.duration(duration)
.attrTween("d", arcTween(d));
// Somewhat of a hack as we rely on arcTween updating the scales.
text.style("visibility", function(e)
{
return isParentOf(d, e) ? null : d3.select(this).style("visibility");
})
.transition()
.duration(duration)
.attrTween("text-anchor", function(d)
{
return function()
{
return x(d.x + d.dx / 2) > Math.PI ? "end" : "start";
};
})
.attrTween("transform", function(d)
{
var multiline = (d.name || "").split(" ").length > 1;
return function()
{
var angle = x(d.x + d.dx / 2) * 180 / Math.PI - 90,
rotate = angle + (multiline ? -0.5 : 0);
return "rotate(" + rotate + ")translate(" + (y(d.y) + padding) +
")rotate(" + (angle > 90 ? -180 : 0) + ")";
};
})
.style("fill-opacity", function(e)
{
return isParentOf(d, e) ? 1 : 1e-6;
})
.each("end", function(e)
{
d3.select(this).style("visibility", isParentOf(d, e) ? null : "hidden");
});
}
function isParentOf(p, c)
{
if (p === c) return true;
if (p.children)
{
return p.children.some(function(d)
{
return isParentOf(d, c);
});
}
return false;
}
function colour(d)
{
var nameStr = d.name;
if (d.name == 'Danger')
{
return "red";
}
else if (d.name == 'Stable')
{
return "green";
}
else if ((d.name !== undefined) && (nameStr.indexOf("Need Ana") >= 0))
{
return "orange";
}
else if ((d.name !== undefined) && (nameStr.indexOf("Not Mon") >= 0))
{
return "grey";
}
else
{
if (!d.parent)
{
return "#fff";
}
if (!useRAG)
{
if ((d.data.color !== null) && (d.data.color !== undefined) &&
((d.data.color == "red")||(d.data.color == "green")||(d.data.color == "amber")||(d.data.color == "grey")))
{
return d.data.color;
}
return color((d.children ? d : d.parent).name);
}
if (d.children)
{
var colours = d.children.map(colour);
var childCount = d.children.length;
var hueAddition = 10;
for (var index = 0; index < childCount; index++ )
{
hueAddition += (d3.hsl(colours[index])).h;
}
return d3.hsl((hueAddition) / childCount, (d3.hsl(colours[0])).s * 1.2,
(d3.hsl(colours[0])).l * 1.2);
}
return d.colour || "#fff";
}
}
// Interpolate the scales!
function arcTween(d)
{
var my = maxY(d),
xd = d3.interpolate(x.domain(), [d.x, d.x + d.dx]),
yd = d3.interpolate(y.domain(), [d.y, my]),
yr = d3.interpolate(y.range(), [d.y ? 20 : 0, radius]);
return function(d)
{
return function(t)
{
x.domain(xd(t)); y.domain(yd(t)).range(yr(t)); return arc(d);
};
};
}
function maxY(d)
{
return d.children ? Math.max.apply(Math, d.children.map(maxY)) : d.y + d.dy;
}
// http://www.w3.org/WAI/ER/WD-AERT/#color-contrast
function brightness(rgb)
{
return rgb.r * 0.299 + rgb.g * 0.587 + rgb.b * 0.114;
}
if (top != self) top.location.replace(location);
function showModelDetailsForCriteria(d)
{
var criterions = d.filter.split(",");
var confirmationMessage = "Do you want to view the model list for - ";
var startindex = 0;
if (criterions[startindex] !== null && criterions[startindex] !== undefined)
{
var statusInt = parseInt(criterions[startindex]);
var statusString = "Not Monitered";
if (statusInt === 0)
{
statusString = "Stable";
}
else if (statusInt == 1)
{
statusString = "Need Analysis";
}
else if (statusInt == 2)
{
statusString = "Danger";
}
confirmationMessage += "<BR>Health Status = " + statusString;
startindex++;
}
if (criterions[startindex] !== null && criterions[startindex] !== undefined)
{
confirmationMessage += "<BR>Model Owner = " + criterions[startindex];
startindex++;
}
if (criterions[startindex] !== null && criterions[startindex] !== undefined)
{
confirmationMessage += "<BR>Biz Application = " + criterions[startindex];
startindex++;
}
if (criterions[startindex] !== null && criterions[startindex] !== undefined)
{
confirmationMessage += "<BR>Class of Model = " + criterions[startindex];
startindex++;
}
if (criterions[startindex] !== null && criterions[startindex] !== undefined)
{
confirmationMessage += "<BR>Model Name = " + criterions[startindex];
startindex++;
}
if (criterions[startindex] !== null && criterions[startindex] !== undefined)
{
confirmationMessage += "<BR>Product Type = " + criterions[startindex];
startindex++;
}
app.confirm(confirmationMessage, false,
function()
{
nodeClicked(d);
},
function ()
{
//nodeClicked(d);
});
}
Look at this piece of code, you should be able to add the depth for each ring. You should know the max depth of the layout and that gives you your outer ring.
var path = vis.selectAll("path").data(nodes);
path.enter().append("path")
.attr("id", function(d, i) { return "path-" + i; })
.attr("class", function(d) { return "ring_" + d.depth; }) <--- add this line
.attr("d", arc)
.attr("fill-rule", "evenodd")
.style("fill", colour)
.attr("title",function(d)
{
return d.depth ? d.desc: "";
})
.on("click", nodeClicked);
Just in case you find some useful ideas here: http://protembla.com/wheel.html some unfinished viz I played with when I was just learning d3, so quality is not great!

Categories

Resources