d3.js pie chart with angled/horizontal labels - javascript

I'm working on a pie chart mock. That I need to try and match the designs to have the label extruding out with a horizontal line attached to the slice ticks. Is this possible? It would be a bonus to have the black dots form on the segments.
http://jsfiddle.net/BxLHd/15/
Here is the code for the tick marks. Would it be a case of creating another set of lines that intersect?
//draw tick marks
var label_group = d3.select('#'+pieId+' .label_group');
lines = label_group.selectAll("line").data(filteredData);
lines.enter().append("svg:line")
.attr("x1", 0)
.attr("x2", 0)
.attr("y1", function(d){
if(d.value > threshold){
return -that.r-3;
}else{
return -that.r;
}
})
.attr("y2", function(d){
if(d.value > threshold){
return -that.r-8;
}
else{
return -that.r;
}
})
.attr("stroke", "gray")
.attr("transform", function(d) {
return "rotate(" + (d.startAngle+d.endAngle)/2 * (180/Math.PI) + ")";
});
lines.transition()
.duration(this.tweenDuration)
.attr("transform", function(d) {
return "rotate(" + (d.startAngle+d.endAngle)/2 * (180/Math.PI) + ")";
});
lines.exit().remove();

Here's a proof of concept (using a different example than yours as a basis as there's quite a lot of code in yours). This is the basic approach:
For each label, compute the start and end of the line underneath it. This is done by drawing the label and getting its bounding box.
This gives two points on the pointer path, the third is the center of the respective segment. This is computed while computing the positions of the labels.
These three points become part of the data. Now draw paths for each of the data elements, using the three points computed before.
Add an SVG marker at the end of each path for the dot.
Here's the code to do it, step by step.
.attr("x", function(d) {
var a = d.startAngle + (d.endAngle - d.startAngle)/2 - Math.PI/2;
d.cx = Math.cos(a) * (radius - 75);
return d.x = Math.cos(a) * (radius - 20);
})
.attr("y", function(d) {
var a = d.startAngle + (d.endAngle - d.startAngle)/2 - Math.PI/2;
d.cy = Math.sin(a) * (radius - 75);
return d.y = Math.sin(a) * (radius - 20);
})
This is computing the x and y positions of the labels outside the segments. We also compute the position of the final point of the pointer path, in the center of the segment. That is, both in the middle between start and end angle and between inner and outer radii. This is added to the data.
.text(function(d) { return d.value; })
.each(function(d) {
var bbox = this.getBBox();
d.sx = d.x - bbox.width/2 - 2;
d.ox = d.x + bbox.width/2 + 2;
d.sy = d.oy = d.y + 5;
});
After adding the text label (in this case, simply the value), we get for each the bounding box and compute the remaining two points for the path, just below the text to the left and just below to the right.
svg.selectAll("path.pointer").data(piedata).enter()
.append("path")
.attr("class", "pointer")
.style("fill", "none")
.style("stroke", "black")
.attr("marker-end", "url(#circ)")
.attr("d", function(d) {
if(d.cx > d.ox) {
return "M" + d.sx + "," + d.sy + "L" + d.ox + "," + d.oy + " " + d.cx + "," + d.cy;
} else {
return "M" + d.ox + "," + d.oy + "L" + d.sx + "," + d.sy + " " + d.cx + "," + d.cy;
}
});
Now we can actually add the paths. They are a straightforward connection of the three points computed before, with a marker added at the end. The only thing to watch out for is that, depending on whether the label is on the left or the right of the chart, the path needs to start at the lower left of the label or the lower right. This is the if statement here.
Complete demo here.

Here is the plugin code that should allow multiple instances of the pie chart - along with being able to update each pie chart with a new set of data.
I am open to ways to enhance the code. I feel it still looks a bit bulky - especially the way I am reseting the selector on update. Any suggestions to streamline this?
http://jsfiddle.net/Qh9X5/1318/
$(document).ready(function() {
(function( $ ){
var methods = {
el: "",
init : function(options) {
var clone = jQuery.extend(true, {}, options["data"]);
methods.el = this;
methods.setup(clone);
},
setup: function(dataset){
this.width = 300;
this.height = 300;
this.radius = Math.min(this.width, this.height) / 2;
this.color = d3.scale.category20();
this.pie = d3.layout.pie()
.sort(null);
this.arc = d3.svg.arc()
.innerRadius(this.radius - 100)
.outerRadius(this.radius - 50);
this.svg = d3.select(methods.el["selector"]).append("svg")
.attr("width", this.width)
.attr("height", this.height)
.append("g")
.attr("class", "piechart")
.attr("transform", "translate(" + this.width / 2 + "," + this.height / 2 + ")");
//this.update(dataset[0].segments);
},
oldPieData: "",
pieTween: function(d, i){
var that = this;
var theOldDataInPie = methods.oldPieData;
// Interpolate the arcs in data space
var s0;
var e0;
if(theOldDataInPie[i]){
s0 = theOldDataInPie[i].startAngle;
e0 = theOldDataInPie[i].endAngle;
} else if (!(theOldDataInPie[i]) && theOldDataInPie[i-1]) {
s0 = theOldDataInPie[i-1].endAngle;
e0 = theOldDataInPie[i-1].endAngle;
} else if(!(theOldDataInPie[i-1]) && theOldDataInPie.length > 0){
s0 = theOldDataInPie[theOldDataInPie.length-1].endAngle;
e0 = theOldDataInPie[theOldDataInPie.length-1].endAngle;
} else {
s0 = 0;
e0 = 0;
}
var i = d3.interpolate({startAngle: s0, endAngle: e0}, {startAngle: d.startAngle, endAngle: d.endAngle});
return function(t) {
var b = i(t);
return methods.arc(b);
};
},
removePieTween: function(d, i) {
var that = this;
s0 = 2 * Math.PI;
e0 = 2 * Math.PI;
var i = d3.interpolate({startAngle: d.startAngle, endAngle: d.endAngle}, {startAngle: s0, endAngle: e0});
return function(t) {
var b = i(t);
return methods.arc(b);
};
},
update: function(dataSet){
var that = this;
methods.el = this;
methods.svg = d3.select(methods.el["selector"] + " .piechart");
this.piedata = methods.pie(dataSet);
//__slices
this.path = methods.svg.selectAll("path.pie")
.data(this.piedata);
this.path.enter().append("path")
.attr("class", "pie")
.attr("fill", function(d, i) {
return methods.color(i);
})
.transition()
.duration(300)
.attrTween("d", methods.pieTween);
this.path
.transition()
.duration(300)
.attrTween("d", methods.pieTween);
this.path.exit()
.transition()
.duration(300)
.attrTween("d", methods.removePieTween)
.remove();
//__slices
//__labels
var labels = methods.svg.selectAll("text")
.data(this.piedata);
labels.enter()
.append("text")
.attr("text-anchor", "middle")
labels
.attr("x", function(d) {
var a = d.startAngle + (d.endAngle - d.startAngle)/2 - Math.PI/2;
d.cx = Math.cos(a) * (methods.radius - 75);
return d.x = Math.cos(a) * (methods.radius - 20);
})
.attr("y", function(d) {
var a = d.startAngle + (d.endAngle - d.startAngle)/2 - Math.PI/2;
d.cy = Math.sin(a) * (methods.radius - 75);
return d.y = Math.sin(a) * (methods.radius - 20);
})
.text(function(d) {
return d.value;
})
.each(function(d) {
var bbox = this.getBBox();
d.sx = d.x - bbox.width/2 - 2;
d.ox = d.x + bbox.width/2 + 2;
d.sy = d.oy = d.y + 5;
})
.transition()
.duration(300)
labels
.transition()
.duration(300)
labels.exit()
.transition()
.duration(300)
//__labels
//__pointers
methods.svg.append("defs").append("marker")
.attr("id", "circ")
.attr("markerWidth", 6)
.attr("markerHeight", 6)
.attr("refX", 3)
.attr("refY", 3)
.append("circle")
.attr("cx", 3)
.attr("cy", 3)
.attr("r", 3);
var pointers = methods.svg.selectAll("path.pointer")
.data(this.piedata);
pointers.enter()
.append("path")
.attr("class", "pointer")
.style("fill", "none")
.style("stroke", "black")
.attr("marker-end", "url(#circ)");
pointers
.attr("d", function(d) {
if(d.cx > d.ox) {
return "M" + d.sx + "," + d.sy + "L" + d.ox + "," + d.oy + " " + d.cx + "," + d.cy;
} else {
return "M" + d.ox + "," + d.oy + "L" + d.sx + "," + d.sy + " " + d.cx + "," + d.cy;
}
})
.transition()
.duration(300)
pointers
.transition()
.duration(300)
pointers.exit()
.transition()
.duration(300)
//__pointers
this.oldPieData = this.piedata;
}
};
$.fn.piechart = function(methodOrOptions) {
if ( methods[methodOrOptions] ) {
return methods[ methodOrOptions ].apply( this, Array.prototype.slice.call( arguments, 1 ));
} else if ( typeof methodOrOptions === 'object' || ! methodOrOptions ) {
// Default to "init"
return methods.init.apply( this, arguments );
} else {
$.error( 'Method ' + methodOrOptions + ' does not exist' );
}
};
})(jQuery);
var dataCharts = [
{
"data": [
{
"segments": [
53245, 28479, 19697, 24037, 40245
]
}
]
},
{
"data": [
{
"segments": [
855, 79, 97, 237, 245
]
}
]
},
{
"data": [
{
"segments": [
22, 79, 97, 12, 245
]
}
]
},
{
"data": [
{
"segments": [
122, 279, 197, 312, 545
]
}
]
}
];
var clone = jQuery.extend(true, {}, dataCharts);
//__invoke concentric
$('[data-role="piechart"]').each(function(index) {
var selector = "piechart"+index;
$(this).attr("id", selector);
var options = {
data: clone[0].data
}
$("#"+selector).piechart(options);
$("#"+selector).piechart('update', clone[0].data[0].segments);
});
$(".testers a").on( "click", function(e) {
e.preventDefault();
var clone = jQuery.extend(true, {}, dataCharts);
var min = 0;
var max = 3;
//__invoke pie chart
$('[data-role="piechart"]').each(function(index) {
pos = Math.floor(Math.random() * (max - min + 1)) + min;
$("#"+$(this).attr("id")).piechart('update', clone[pos].data[0].segments);
});
});
});

To conclude I've wrapped the very latest code for this in a jquery plugin. Its now possible to develop multiple pie charts with these labels.
LATEST CODE - ** http://jsfiddle.net/Qh9X5/1336/ - removes label properly on exit.
$(document).ready(function() {
(function( $ ){
var methods = {
el: "",
init : function(options) {
var clone = jQuery.extend(true, {}, options["data"]);
methods.el = this;
methods.setup(clone, options["width"], options["height"], options["r"], options["ir"]);
},
getArc: function(radius, innerradius){
var arc = d3.svg.arc()
.innerRadius(innerradius)
.outerRadius(radius);
return arc;
},
setup: function(dataset, w, h, r, ir){
var padding = 80;
this.width = w;
this.height = h;
this.radius = r
this.innerradius = ir;
this.color = d3.scale.category20();
this.pie = d3.layout.pie()
.sort(null)
.value(function(d) { return d.total; });
this.arc = this.getArc(this.radius, this.innerradius);
this.svg = d3.select(methods.el["selector"]).append("svg")
.attr("width", this.width + padding)
.attr("height", this.height + padding)
.append("g")
.attr("class", "piechart")
.attr("transform", "translate(" + ((this.width/2) + (padding/2)) + "," + ((this.height/2) + (padding/2)) + ")");
this.segments = this.svg.append("g")
.attr("class", "segments");
this.labels = this.svg.append("g")
.attr("class", "labels");
this.pointers = this.svg.append("g")
.attr("class", "pointers");
},
oldPieData: "",
pieTween: function(r, ir, d, i){
var that = this;
var theOldDataInPie = methods.oldPieData;
// Interpolate the arcs in data space
var s0;
var e0;
if(theOldDataInPie[i]){
s0 = theOldDataInPie[i].startAngle;
e0 = theOldDataInPie[i].endAngle;
} else if (!(theOldDataInPie[i]) && theOldDataInPie[i-1]) {
s0 = theOldDataInPie[i-1].endAngle;
e0 = theOldDataInPie[i-1].endAngle;
} else if(!(theOldDataInPie[i-1]) && theOldDataInPie.length > 0){
s0 = theOldDataInPie[theOldDataInPie.length-1].endAngle;
e0 = theOldDataInPie[theOldDataInPie.length-1].endAngle;
} else {
s0 = 0;
e0 = 0;
}
var i = d3.interpolate({startAngle: s0, endAngle: e0}, {startAngle: d.startAngle, endAngle: d.endAngle});
return function(t) {
var b = i(t);
return methods.getArc(r, ir)(b);
};
},
removePieTween: function(r, ir, d, i) {
var that = this;
s0 = 2 * Math.PI;
e0 = 2 * Math.PI;
var i = d3.interpolate({startAngle: d.startAngle, endAngle: d.endAngle}, {startAngle: s0, endAngle: e0});
return function(t) {
var b = i(t);
return methods.getArc(r, ir)(b);
};
},
update: function(dataSet){
var that = this;
methods.el = this;
var r = $(methods.el["selector"]).data("r");
var ir = $(methods.el["selector"]).data("ir");
methods.svg = d3.select(methods.el["selector"] + " .piechart");
methods.segments = d3.select(methods.el["selector"] + " .segments");
methods.labels = d3.select(methods.el["selector"] + " .labels");
methods.pointers = d3.select(methods.el["selector"] + " .pointers");
dataSet.forEach(function(d) {
d.total = +d.value;
});
this.piedata = methods.pie(dataSet);
//__slices
this.path = methods.segments.selectAll("path.pie")
.data(this.piedata);
this.path.enter().append("path")
.attr("class", "pie")
.attr("fill", function(d, i) {
return methods.color(i);
})
.transition()
.duration(300)
.attrTween("d", function(d, i) {
return methods.pieTween(r, ir, d, i);
});
this.path
.transition()
.duration(300)
.attrTween("d", function(d, i) {
return methods.pieTween(r, ir, d, i);
});
this.path.exit()
.transition()
.duration(300)
.attrTween("d", function(d, i) {
return methods.removePieTween(r, ir, d, i);
})
.remove();
//__slices
//__labels
var labels = methods.labels.selectAll("text")
.data(this.piedata);
labels.enter()
.append("text")
.attr("text-anchor", "middle")
labels
.attr("x", function(d) {
var a = d.startAngle + (d.endAngle - d.startAngle)/2 - Math.PI/2;
d.cx = Math.cos(a) * (ir+((r-ir)/2));
return d.x = Math.cos(a) * (r + 20);
})
.attr("y", function(d) {
var a = d.startAngle + (d.endAngle - d.startAngle)/2 - Math.PI/2;
d.cy = Math.sin(a) * (ir+((r-ir)/2));
return d.y = Math.sin(a) * (r + 20);
})
.text(function(d) {
return d.data.label;
})
.each(function(d) {
var bbox = this.getBBox();
d.sx = d.x - bbox.width/2 - 2;
d.ox = d.x + bbox.width/2 + 2;
d.sy = d.oy = d.y + 5;
})
.transition()
.duration(300)
labels
.transition()
.duration(300)
labels.exit()
.transition()
.duration(300)
//__labels
//__pointers
methods.pointers.append("defs").append("marker")
.attr("id", "circ")
.attr("markerWidth", 6)
.attr("markerHeight", 6)
.attr("refX", 3)
.attr("refY", 3)
.append("circle")
.attr("cx", 3)
.attr("cy", 3)
.attr("r", 3);
var pointers = methods.pointers.selectAll("path.pointer")
.data(this.piedata);
pointers.enter()
.append("path")
.attr("class", "pointer")
.style("fill", "none")
.style("stroke", "black")
.attr("marker-end", "url(#circ)");
pointers
.attr("d", function(d) {
if(d.cx > d.ox) {
return "M" + d.sx + "," + d.sy + "L" + d.ox + "," + d.oy + " " + d.cx + "," + d.cy;
} else {
return "M" + d.ox + "," + d.oy + "L" + d.sx + "," + d.sy + " " + d.cx + "," + d.cy;
}
})
.transition()
.duration(300)
pointers
.transition()
.duration(300)
pointers.exit()
.transition()
.duration(300)
//__pointers
this.oldPieData = this.piedata;
}
};
$.fn.piechart = function(methodOrOptions) {
if ( methods[methodOrOptions] ) {
return methods[ methodOrOptions ].apply( this, Array.prototype.slice.call( arguments, 1 ));
} else if ( typeof methodOrOptions === 'object' || ! methodOrOptions ) {
// Default to "init"
return methods.init.apply( this, arguments );
} else {
$.error( 'Method ' + methodOrOptions + ' does not exist' );
}
};
})(jQuery);
var dataCharts = [
{
"data": [
{
"segments": [
{
"label": "apple",
"value": 53245
},
{
"label": "cherry",
"value": 145
},
{
"label": "pear",
"value": 2245
},
{
"label": "bananana",
"value": 15325
}
]
}
]
},
{
"data": [
{
"segments": [
{
"label": "milk",
"value": 532
},
{
"label": "cheese",
"value": 145
},
{
"label": "grapes",
"value": 22
}
]
}
]
},
{
"data": [
{
"segments": [
{
"label": "pineapple",
"value": 1532
},
{
"label": "orange",
"value": 1435
},
{
"label": "grapes",
"value": 22
}
]
}
]
},
{
"data": [
{
"segments": [
{
"label": "lemons",
"value": 133
},
{
"label": "mango",
"value": 435
},
{
"label": "melon",
"value": 2122
}
]
}
]
}
];
var clone = jQuery.extend(true, {}, dataCharts);
//__invoke concentric
$('[data-role="piechart"]').each(function(index) {
var selector = "piechart"+index;
$(this).attr("id", selector);
var options = {
data: clone[0].data,
width: $(this).data("width"),
height: $(this).data("height"),
r: $(this).data("r"),
ir: $(this).data("ir")
}
$("#"+selector).piechart(options);
$("#"+selector).piechart('update', clone[0].data[0].segments);
});
$(".testers a").on( "click", function(e) {
e.preventDefault();
var clone = jQuery.extend(true, {}, dataCharts);
var min = 0;
var max = 3;
//__invoke pie chart
$('[data-role="piechart"]').each(function(index) {
pos = Math.floor(Math.random() * (max - min + 1)) + min;
$("#"+$(this).attr("id")).piechart('update', clone[pos].data[0].segments);
});
});
});

Related

D3.js - old links not getting deleted after adding a new node and trasitioning

I am working on getting a treeview working with some conditional logics. I currently have the following working fiddle: https://jsfiddle.net/zgv9ajn4/4/
From the jsfiddle you can see the below image. On selecting the node with the name "division" I am showing certain orange svgs, and on click of the right-bottom svg I am adding a new child node to the "division" node.
Before Adding child Node and on clicking of the node "division":
After Adding the Child Node:-
As you can see there are some hanging links/nodes and they are not getting removed.
var margin = {
top: 20,
right: 120,
bottom: 20,
left: 120
},
width = 960 - margin.right - margin.left,
height = 800 - margin.top - margin.bottom;
function generateEmptyDecisionBox(condition) {
return condition === 'False' ? [{
"name": "newDecision",
"id": "newId",
"type": "decision",
"value": "notSure",
"condition": `${condition}`,
}, {}] : [{}, {
"name": "newDecision",
"id": "newId",
"type": "decision",
"value": "notSure",
"condition": `${condition}`,
}];
}
var selectedNode;
var selectedLink;
var root = {
"name": "Root",
"type": "decision",
"children": [{
"name": "analytics",
"condition": "False",
"type": "decision",
"value": "a+b",
"children": [{
"name": "distinction",
"type": "action",
"condition": "True",
"value": "5",
}, {
"name": "nonDistinction",
"type": "action",
"condition": "false",
"value": "4",
"children": [],
}],
},
{
"condition": "True",
"name": "division",
"type": "decision",
"value": "a-b",
"children": {
"name": "analytics",
"condition": "False",
"type": "decision",
"value": "a+b",
"children": [{
"name": "distinction",
"type": "action",
"condition": "True",
"value": "5",
}, {
"name": "nonDistinction",
"type": "decision",
"condition": "false",
"value": "4",
"children": {
"name": "analytics",
"condition": "False",
"type": "decision",
"value": "a+b",
"children": [{
"name": "distinction",
"type": "action",
"condition": "True",
"value": "5",
}, {
"name": "nonDistinction",
"type": "action",
"condition": "false",
"value": "4"
}],
},
}],
},
}
]
};
var i = 0,
duration = 500,
rectW = 120,
rectH = 60;
var tree = d3.layout.tree().nodeSize([150, 90]);
//LINK FUNCTION TO DRAW LINKS
var linkFunc = function(d) {
console.log("linkFunc", d);
var source = {
x: d.source.x + rectW / 2,
y: d.source.y + (rectH / 2)
};
var target = {
x: d.target.x + (rectW / 2),
y: d.target.y + 3,
};
// This is where the line bends
var inflection = {
x: target.x,
y: source.y
};
var radius = 5;
var result = "M" + source.x + ',' + source.y;
if (source.x < target.x && d.target.type) {
// Child is to the right of the parent
result += ' H' + (inflection.x - radius);
} else if (d.target.type) {
result += ' H' + (inflection.x + radius);
} else {
return;
}
// Curve the line at the bend slightly
result += ' Q' + inflection.x + ',' + inflection.y + ' ' + inflection.x + ',' + (inflection.y + radius);
result += 'V' + target.y;
return result;
}
// END OF LINK FUNC //
var diagonal = d3.svg.diagonal()
.projection(function(d) {
return [d.x + rectW / 2, d.y + rectH / 2];
});
// DRAW TREE //
var svg = d3.select(".tree-diagram").append("svg").attr("width", 1000).attr("height", 1000)
.call(zm = d3.behavior.zoom().scaleExtent([1, 3]).on("zoom", redraw)).append("g")
.attr("transform", "translate(" + 350 + "," + 20 + ")");
// ADD ARROW TO THE BOTTOM POINTING TO THE NEXT DECISION.
svg.append("svg:defs").selectAll("marker")
.data(["end"]) // Different link/path types can be defined here
.enter().append("svg:marker") // This section adds in the arrows
.attr("id", String)
.attr("viewBox", "0 -5 10 10")
.attr("refX", 15)
.attr("refY", 0.5)
.attr("markerWidth", 4)
.attr("markerHeight", 4)
.attr("orient", "auto")
.append("svg:path")
.attr("d", "M0,-5L10,0L0,5");
//necessary so that zoom knows where to zoom and unzoom from
zm.translate([350, 20]);
root.x0 = 0;
root.y0 = 0;
update(root);
d3.select(".tree-diagram").style("height", "1000px");
// END OF DRAW TREEE //
function update(source) {
// Compute the new tree layout.
var nodes = tree.nodes(root).reverse(),
links = tree.links(nodes);
// Normalize for fixed-depth.
nodes.forEach(function(d) {
d.y = d.depth * 90;
});
// Update the nodes…
var node = svg.selectAll("g.node")
.data(nodes, function(d) {
return d.id || (d.id = ++i);
});
// Enter any new nodes at the parent's previous position.
var nodeEnter = node.enter().append("g")
.attr('transform', 'translate(' + source.x0 + ', ' + source.y0 + ')')
.attr("class", "node")
.on("click", click)
// .on("blur", onNodeBlur);
nodeEnter.append('path')
.attr("d", function(d) {
if (d.type === 'decision') {
return 'M 60 0 120 30 60 60 0 30 Z';
} else if (d.type === 'action') {
return 'M 0 0 120 0 120 60 0 60 Z';
} else {
return 'M -100 -10 -10 -10 -10 -10 -10 -10Z'
}
}).attr("stroke-width", 1)
.attr('class', 'myPaths')
.style("fill", function(d) {
return "lightsteelblue";
});
nodeEnter.append("text")
.attr("x", rectW / 2)
.attr("y", rectH / 2)
.attr("dy", ".35em")
.attr("text-anchor", "middle")
.text(function(d) {
return d.name;
});
var nodeUpdate = node.transition()
.duration(duration)
.attr("transform", function(d) {
return "translate(" + (d.x) + "," + (d.y) + ")";
});
var nodeExit = node.exit().transition()
.duration(duration)
.attr("transform", function(d) {
return "translate(" + source.x + "," + source.y + ")";
})
.remove();
nodeExit.select("path")
.attr("d", function(d) {
if (d.type === 'decision') {
return 'M 60 0 120 30 60 60 0 30 Z';
} else if (d.type === 'action') {
return 'M 0 0 120 0 120 60 0 60 Z';
} else {
return 'M -100 -10 -10 -10 -10 -10 -10 -10Z'
}
}).attr("stroke-width", 1)
.attr('class', 'myPaths')
.style("fill", function(d) {
return "lightsteelblue";
});
nodeExit.select("text");
// Update the links…
var link = svg.selectAll("path.link")
.data(links, function(d) {
return d.target.id;
}).classed('link1', true);
// Enter any new links at the parent's previous position.
var linkEnter = link.enter().insert("g", "g")
.attr("class", "link");
linkEnter.append('path')
.attr("d", linkFunc)
.on('click', function(d, i) {
selectedLink = d;
// Use the native SVG interface to get the bounding box to
// calculate the center of the path
var bbox = this.getBBox();
var x;
var y;
if (d.source.x < d.target.x) {
// Child is to the right of the parent
x = bbox.x + bbox.width;
y = bbox.y;
plusButton
.attr('transform', 'translate(' + x + ', ' + y + ')')
.classed('hide', false);
} else {
x = bbox.x;
y = bbox.y;
plusButton
.attr('transform', 'translate(' + x + ', ' + y + ')')
.classed('hide', false);
}
})
.on('blur', function(d, i) {
plusButton
.classed('hide', true);
}).attr("marker-end", "url(#end)");
// Add Link Texts.
linkEnter.append('text')
.text(function(d, i) {
if (d.source.x < d.target.x) {
return 'True';
} else {
return 'False';
}
}).attr('transform', function(d) {
console.log(d);
/* var bbox = this.getBBox();
x= bbox.x + bbox.width;
y= bbox.y; */
if (d.source.x < d.target.x && d.target.type) {
console.log("comes in here for source < target");
return 'translate(' + (d.source.x + rectW) + ',' + (d.source.y + rectH) + ')';
} else if (d.target.type) {
return 'translate(' + (d.source.x - rectW / 2) + ',' + (d.source.y + rectH) + ')';
} else {
return;
}
});
// Transition links to their new position.
link.transition()
.duration(duration)
.attr("d", linkFunc);
// Transition links to their new position.
// Transition exiting nodes to the parent's new position.
link.exit().transition()
.duration(duration)
.attr("d", linkFunc)
.remove();
// Stash the old positions for transition.
nodes.forEach(function(d) {
d.x0 = d.x;
d.y0 = d.y;
});
}
// ON CLICK OF NODES
function click(d) {
if (d.type === 'action') {
return;
}
selectedNode = d;
var m = d.x;
var h = d.y;
var m = d.x + 110;
var h = d.y + 35;
diamondImage
.attr('transform', 'translate(' + m + ', ' + h + ')')
.classed('hide', false);
var m = d.x + 110;
var h = d.y;
rectangleShape
.attr('transform', 'translate(' + m + ', ' + h + ')')
.classed('hide', false);
var m = d.x - 20;
var h = d.y + 35;
diamondImageFalse
.attr('transform', 'translate(' + m + ', ' + h + ')')
.classed('hide', false);
var m = d.x - 20;
var h = d.y;
rectangleShapeFalse
.attr('transform', 'translate(' + m + ', ' + h + ')')
.classed('hide', false);
}
/* function onNodeBlur(){
diamondImage
.classed('hide', true);
rectangleShape
.classed('hide', true);
diamondImageFalse
.classed('hide',true);
rectangleShapeFalse
.classed('hide',true);
}
*/
//Redraw for zoom
function redraw() {
//console.log("here", d3.event.translate, d3.event.scale);
svg.attr("transform",
"translate(" + d3.event.translate + ")" +
" scale(" + d3.event.scale + ")");
}
// oN CALL
function addElement(d, truthy) {
console.log(d);
d.children = null;
d.children = generateEmptyDecisionBox(truthy);
update(root);
}
// draw elements //
function drawDiamond(centroid) {
// Start at the top
console.log(centroid);
console.log("rectH", rectH, rectW)
// Start at the top
var result = 'M' + centroid.x + ',' + (centroid.y - rectH / 2);
// Go right
result += 'L' + (centroid.x + rectW / 2) + ',' + centroid.y;
// Bottom
result += 'L' + centroid.x + ',' + (centroid.y + rectH / 2);
// Left
result += 'L' + (centroid.x - rectW / 2) + ',' + centroid.y;
// Close the shape
result += 'Z';
return result;
}
function drawRect(centroid) {
// Start at the top left
console.log(centroid);
var result = 'M' + (centroid.x - rectW / 2) + ',' + (centroid.y - rectH / 2);
// Go right
result += 'h' + rectW;
// Go down
result += 'v' + rectH;
// Left
result += 'h-' + rectW;
// Close the shape
result += 'Z';
console.log(result);
return result;
}
var plusButton = svg
.append('g')
.classed('button', true)
.classed('hide', true)
.on('click', function() {
console.log("hello");
/* addElement(selectedLink.source); */
console.log("Clicked on Diamond");
console.log("set hide to true");
removeAllButtonElements();
});
plusButton
.append('rect')
.attr('transform', 'translate(-8, -8)') // center the button inside the `g`
.attr('width', 16)
.attr('height', 16)
.attr('rx', 2);
plusButton
.append('path')
.attr('d', 'M-6 0 H6 M0 -6 V6');
var rectangleShape = svg.append('g')
.classed('conditionImage', true)
.classed('hide', true)
.on('click', function() {
removeAllButtonElements();
})
rectangleShape
.append('rect')
.attr('width', 30)
.attr('height', 20)
.style('fill', 'orange');
var diamondImage = svg.append('g')
.classed('conditionSvg', true)
.classed('hide', true)
.classed('scale', true)
.on('click', function() {
console.log("Clicked on Diamond");
console.log("set hide to true");
addElement(selectedNode, 'True');
removeAllButtonElements();
});
diamondImage
.append('path')
.attr('d', 'M 15 0 30 15 15 30 0 15 Z')
.style("fill", 'orange');
var rectangleShapeFalse = svg.append('g')
.classed('conditionImage', true)
.classed('hide', true)
.on('click', function() {
console.log("rectangle clicked");
removeAllButtonElements();
})
rectangleShapeFalse
.append('rect')
.attr('width', 30)
.attr('height', 20)
.style('fill', 'orange');
var diamondImageFalse = svg.append('g')
.classed('conditionImage', true)
.classed('hide', true)
.classed('scale', true)
.on('click', function() {
console.log("Clicked on Diamond");
console.log("set hide to true");
addElement(selectedNode, 'False');
removeAllButtonElements();
});
diamondImageFalse
.append('path')
.attr('d', 'M 15 0 30 15 15 30 0 15 Z')
.style("fill", 'orange');
function removeAllButtonElements() {
plusButton.classed('hide', true);
diamondImage.classed('hide', true);
rectangleShape.classed('hide', true);
diamondImageFalse.classed('hide', true);
rectangleShapeFalse.classed('hide', true);
}
// draw elements end ..
.node {
cursor: pointer;
outline: none !important;
}
.node text {
font: 10px sans-serif;
}
.button>path {
stroke: blue;
stroke-width: 1.5;
/* outline: none; */
}
.button>rect {
fill: #ddd;
stroke: grey;
stroke-width: 1px;
}
.conditionalSvg {
outline: none;
display: none;
}
.hide {
display: none;
opacity: 0 !important;
pointer-events: none;
}
.link:hover {
ouline: none !important;
cursor: pointer;
stroke-width: 3px;
}
.link path {
outline: none !important;
fill: none;
stroke: darkgray;
stroke-width: 2px;
}
.link path:hover {
cursor: pointer;
stroke-width: 4px;
}
.link text {
font: 10px sans-serif;
}
.colorBlue {
background-color: blue;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.min.js"></script>
<div class="tree-diagram"></div>
Although the new node is being added and the old nodes are being transitioned to the latest positions, the old nodes/links are not being removed.
Note that your nodes are moving fine, just the links are not. That is because you find them using path.link, but the real structure is g.link path, since you first add a g element with class link, and then a path. You could fix that by changing path.link to g.link, but then you need to select the path and text separately.
I fixed this by calling .attr('d', linkFunc) on all links, not just the new ones, and by calling .text() and .attr('transform' on all texts.
Finally, I changed the text transform to use the x of the target, since a target determines where the line goes vertical, not the source.
var margin = {
top: 20,
right: 120,
bottom: 20,
left: 120
},
width = 960 - margin.right - margin.left,
height = 800 - margin.top - margin.bottom;
function generateEmptyDecisionBox(condition) {
return condition === 'False' ? [{
"name": "newDecision",
"id": "newId",
"type": "decision",
"value": "notSure",
"condition": `${condition}`,
}, {}] : [{}, {
"name": "newDecision",
"id": "newId",
"type": "decision",
"value": "notSure",
"condition": `${condition}`,
}];
}
var selectedNode;
var selectedLink;
var root = {
"name": "Root",
"type": "decision",
"children": [{
"name": "analytics",
"condition": "False",
"type": "decision",
"value": "a+b",
"children": [{
"name": "distinction",
"type": "action",
"condition": "True",
"value": "5",
}, {
"name": "nonDistinction",
"type": "action",
"condition": "false",
"value": "4",
"children": [],
}],
},
{
"condition": "True",
"name": "division",
"type": "decision",
"value": "a-b",
"children": {
"name": "analytics",
"condition": "False",
"type": "decision",
"value": "a+b",
"children": [{
"name": "distinction",
"type": "action",
"condition": "True",
"value": "5",
}, {
"name": "nonDistinction",
"type": "decision",
"condition": "false",
"value": "4",
"children": {
"name": "analytics",
"condition": "False",
"type": "decision",
"value": "a+b",
"children": [{
"name": "distinction",
"type": "action",
"condition": "True",
"value": "5",
}, {
"name": "nonDistinction",
"type": "action",
"condition": "false",
"value": "4"
}],
},
}],
},
}
]
};
var i = 0,
duration = 500,
rectW = 120,
rectH = 60;
var tree = d3.layout.tree().nodeSize([150, 90]);
//LINK FUNCTION TO DRAW LINKS
var linkFunc = function(d) {
console.log("linkFunc", d);
var source = {
x: d.source.x + rectW / 2,
y: d.source.y + (rectH / 2)
};
var target = {
x: d.target.x + (rectW / 2),
y: d.target.y + 3,
};
// This is where the line bends
var inflection = {
x: target.x,
y: source.y
};
var radius = 5;
var result = "M" + source.x + ',' + source.y;
if (source.x < target.x && d.target.type) {
// Child is to the right of the parent
result += ' H' + (inflection.x - radius);
} else if (d.target.type) {
result += ' H' + (inflection.x + radius);
} else {
return;
}
// Curve the line at the bend slightly
result += ' Q' + inflection.x + ',' + inflection.y + ' ' + inflection.x + ',' + (inflection.y + radius);
result += 'V' + target.y;
return result;
}
// END OF LINK FUNC //
var diagonal = d3.svg.diagonal()
.projection(function(d) {
return [d.x + rectW / 2, d.y + rectH / 2];
});
// DRAW TREE //
var svg = d3.select(".tree-diagram").append("svg").attr("width", 1000).attr("height", 1000)
.call(zm = d3.behavior.zoom().scaleExtent([1, 3]).on("zoom", redraw)).append("g")
.attr("transform", "translate(" + 350 + "," + 20 + ")");
// ADD ARROW TO THE BOTTOM POINTING TO THE NEXT DECISION.
svg.append("svg:defs").selectAll("marker")
.data(["end"]) // Different link/path types can be defined here
.enter().append("svg:marker") // This section adds in the arrows
.attr("id", String)
.attr("viewBox", "0 -5 10 10")
.attr("refX", 15)
.attr("refY", 0.5)
.attr("markerWidth", 4)
.attr("markerHeight", 4)
.attr("orient", "auto")
.append("svg:path")
.attr("d", "M0,-5L10,0L0,5");
//necessary so that zoom knows where to zoom and unzoom from
zm.translate([350, 20]);
root.x0 = 0;
root.y0 = 0;
update(root);
d3.select(".tree-diagram").style("height", "1000px");
// END OF DRAW TREEE //
function update(source) {
// Compute the new tree layout.
var nodes = tree.nodes(root).reverse(),
links = tree.links(nodes);
// Normalize for fixed-depth.
nodes.forEach(function(d) {
d.y = d.depth * 90;
});
// Update the nodes…
var node = svg.selectAll("g.node")
.data(nodes, function(d) {
return d.id || (d.id = ++i);
});
// Enter any new nodes at the parent's previous position.
var nodeEnter = node.enter().append("g")
.attr('transform', 'translate(' + source.x0 + ', ' + source.y0 + ')')
.attr("class", "node")
.on("click", click)
// .on("blur", onNodeBlur);
nodeEnter.append('path')
.attr("d", function(d) {
if (d.type === 'decision') {
return 'M 60 0 120 30 60 60 0 30 Z';
} else if (d.type === 'action') {
return 'M 0 0 120 0 120 60 0 60 Z';
} else {
return 'M -100 -10 -10 -10 -10 -10 -10 -10Z'
}
}).attr("stroke-width", 1)
.attr('class', 'myPaths')
.style("fill", function(d) {
return "lightsteelblue";
});
nodeEnter.append("text")
.attr("x", rectW / 2)
.attr("y", rectH / 2)
.attr("dy", ".35em")
.attr("text-anchor", "middle")
.text(function(d) {
return d.name;
});
var nodeUpdate = node.transition()
.duration(duration)
.attr("transform", function(d) {
return "translate(" + (d.x) + "," + (d.y) + ")";
});
var nodeExit = node.exit().transition()
.duration(duration)
.attr("transform", function(d) {
return "translate(" + source.x + "," + source.y + ")";
})
.remove();
nodeExit.select("path")
.attr("d", function(d) {
if (d.type === 'decision') {
return 'M 60 0 120 30 60 60 0 30 Z';
} else if (d.type === 'action') {
return 'M 0 0 120 0 120 60 0 60 Z';
} else {
return 'M -100 -10 -10 -10 -10 -10 -10 -10Z'
}
}).attr("stroke-width", 1)
.attr('class', 'myPaths')
.style("fill", function(d) {
return "lightsteelblue";
});
nodeExit.select("text");
// Update the links…
var link = svg.selectAll(".link")
.data(links, function(d) {
return d.target.id;
}).classed('link1', true);
// Enter any new links at the parent's previous position.
var linkEnter = link.enter()
.insert("g", "g")
.attr("class", "link");
linkEnter.append('path')
.on('click', function(d, i) {
selectedLink = d;
// Use the native SVG interface to get the bounding box to
// calculate the center of the path
var bbox = this.getBBox();
var x;
var y;
if (d.source.x < d.target.x) {
// Child is to the right of the parent
x = bbox.x + bbox.width;
y = bbox.y;
plusButton
.attr('transform', 'translate(' + x + ', ' + y + ')')
.classed('hide', false);
} else {
x = bbox.x;
y = bbox.y;
plusButton
.attr('transform', 'translate(' + x + ', ' + y + ')')
.classed('hide', false);
}
})
.on('blur', function(d, i) {
plusButton
.classed('hide', true);
}).attr("marker-end", "url(#end)");
// Add Link Texts.
linkEnter.append('text');
link.select('path')
.attr("d", linkFunc);
link.select('text')
.text(function(d, i) {
if (d.source.x < d.target.x) {
return 'True';
} else {
return 'False';
}
})
.attr('transform', function(d) {
console.log(d);
if (d.source.x < d.target.x && d.target.type) {
console.log("comes in here for source < target");
return 'translate(' + (d.target.x + rectW / 2) + ',' + (d.source.y + rectH) + ')';
} else if (d.target.type) {
return 'translate(' + (d.target.x + rectW / 2) + ',' + (d.source.y + rectH) + ')';
} else {
return;
}
});
// Transition links to their new position.
link.transition()
.duration(duration)
.attr("d", linkFunc);
// Transition links to their new position.
// Transition exiting nodes to the parent's new position.
link.exit().transition()
.duration(duration)
.attr("d", linkFunc)
.remove();
// Stash the old positions for transition.
nodes.forEach(function(d) {
d.x0 = d.x;
d.y0 = d.y;
});
}
// ON CLICK OF NODES
function click(d) {
if (d.type === 'action') {
return;
}
selectedNode = d;
var m = d.x;
var h = d.y;
var m = d.x + 110;
var h = d.y + 35;
diamondImage
.attr('transform', 'translate(' + m + ', ' + h + ')')
.classed('hide', false);
var m = d.x + 110;
var h = d.y;
rectangleShape
.attr('transform', 'translate(' + m + ', ' + h + ')')
.classed('hide', false);
var m = d.x - 20;
var h = d.y + 35;
diamondImageFalse
.attr('transform', 'translate(' + m + ', ' + h + ')')
.classed('hide', false);
var m = d.x - 20;
var h = d.y;
rectangleShapeFalse
.attr('transform', 'translate(' + m + ', ' + h + ')')
.classed('hide', false);
}
/* function onNodeBlur(){
diamondImage
.classed('hide', true);
rectangleShape
.classed('hide', true);
diamondImageFalse
.classed('hide',true);
rectangleShapeFalse
.classed('hide',true);
}
*/
//Redraw for zoom
function redraw() {
//console.log("here", d3.event.translate, d3.event.scale);
svg.attr("transform",
"translate(" + d3.event.translate + ")" +
" scale(" + d3.event.scale + ")");
}
// oN CALL
function addElement(d, truthy) {
console.log(d);
d.children = null;
d.children = generateEmptyDecisionBox(truthy);
update(root);
}
// draw elements //
function drawDiamond(centroid) {
// Start at the top
console.log(centroid);
console.log("rectH", rectH, rectW)
// Start at the top
var result = 'M' + centroid.x + ',' + (centroid.y - rectH / 2);
// Go right
result += 'L' + (centroid.x + rectW / 2) + ',' + centroid.y;
// Bottom
result += 'L' + centroid.x + ',' + (centroid.y + rectH / 2);
// Left
result += 'L' + (centroid.x - rectW / 2) + ',' + centroid.y;
// Close the shape
result += 'Z';
return result;
}
function drawRect(centroid) {
// Start at the top left
console.log(centroid);
var result = 'M' + (centroid.x - rectW / 2) + ',' + (centroid.y - rectH / 2);
// Go right
result += 'h' + rectW;
// Go down
result += 'v' + rectH;
// Left
result += 'h-' + rectW;
// Close the shape
result += 'Z';
console.log(result);
return result;
}
var plusButton = svg
.append('g')
.classed('button', true)
.classed('hide', true)
.on('click', function() {
console.log("hello");
/* addElement(selectedLink.source); */
console.log("Clicked on Diamond");
console.log("set hide to true");
removeAllButtonElements();
});
plusButton
.append('rect')
.attr('transform', 'translate(-8, -8)') // center the button inside the `g`
.attr('width', 16)
.attr('height', 16)
.attr('rx', 2);
plusButton
.append('path')
.attr('d', 'M-6 0 H6 M0 -6 V6');
var rectangleShape = svg.append('g')
.classed('conditionImage', true)
.classed('hide', true)
.on('click', function() {
removeAllButtonElements();
})
rectangleShape
.append('rect')
.attr('width', 30)
.attr('height', 20)
.style('fill', 'orange');
var diamondImage = svg.append('g')
.classed('conditionSvg', true)
.classed('hide', true)
.classed('scale', true)
.on('click', function() {
console.log("Clicked on Diamond");
console.log("set hide to true");
addElement(selectedNode, 'True');
removeAllButtonElements();
});
diamondImage
.append('path')
.attr('d', 'M 15 0 30 15 15 30 0 15 Z')
.style("fill", 'orange');
var rectangleShapeFalse = svg.append('g')
.classed('conditionImage', true)
.classed('hide', true)
.on('click', function() {
console.log("rectangle clicked");
removeAllButtonElements();
})
rectangleShapeFalse
.append('rect')
.attr('width', 30)
.attr('height', 20)
.style('fill', 'orange');
var diamondImageFalse = svg.append('g')
.classed('conditionImage', true)
.classed('hide', true)
.classed('scale', true)
.on('click', function() {
console.log("Clicked on Diamond");
console.log("set hide to true");
addElement(selectedNode, 'False');
removeAllButtonElements();
});
diamondImageFalse
.append('path')
.attr('d', 'M 15 0 30 15 15 30 0 15 Z')
.style("fill", 'orange');
function removeAllButtonElements() {
plusButton.classed('hide', true);
diamondImage.classed('hide', true);
rectangleShape.classed('hide', true);
diamondImageFalse.classed('hide', true);
rectangleShapeFalse.classed('hide', true);
}
// draw elements end ..
.node {
cursor: pointer;
outline: none !important;
}
.node text {
font: 10px sans-serif;
}
.button>path {
stroke: blue;
stroke-width: 1.5;
/* outline: none; */
}
.button>rect {
fill: #ddd;
stroke: grey;
stroke-width: 1px;
}
.conditionalSvg {
outline: none;
display: none;
}
.hide {
display: none;
opacity: 0 !important;
pointer-events: none;
}
.link:hover {
ouline: none !important;
cursor: pointer;
stroke-width: 3px;
}
.link path {
outline: none !important;
fill: none;
stroke: darkgray;
stroke-width: 2px;
}
.link path:hover {
cursor: pointer;
stroke-width: 4px;
}
.link text {
font: 10px sans-serif;
}
.colorBlue {
background-color: blue;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.min.js"></script>
<div class="tree-diagram"></div>

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>

Feed Tableau data using getData() into D3 chart

Does anyone have any suggestions for formatting and feeding underlying data using Tableau's getData() function into this D3 Doughnut Chart? I understand I need to create an array for the underlying data but am unsure how to do so. The underlying data is in this JSON format:
[[
{
"value":"Thais Sissman",
"formattedValue":"Thais Sissman"
},
{
"value":"-0.6860335195530726",
"formattedValue":"-68.6%"
},
{
"value":"-3.3156",
"formattedValue":"($3)"
},
{
"value":"793",
"formattedValue":"793"
},
{
"value":"4.833000000000001",
"formattedValue":"$5"
}
],
[
{
"value":"Lela Donovan",
"formattedValue":"Lela Donovan"
},
{
"value":"0.0875",
"formattedValue":"8.8%"
},
{
"value":"0.4641",
"formattedValue":"$0"
},
{
"value":"792",
"formattedValue":"792"
},
{
"value":"5.304",
"formattedValue":"$5"
}
]]
Here is my JavaScript code:
$(document).ready(function initViz() {
var containerDiv = document.getElementById("vizContainer"),
url = "https://SERVER-NAME/t/gfm/views/Superstore/Customers?:embed=y&:showShareOptions=true&:display_count=no&:showVizHome=no",
options = {
hideTabs: true,
hideToolbar: true,
onFirstInteractive: function () {
document.getElementById('getData').disabled = false; // Enable our button
}
};
viz = new tableau.Viz(containerDiv, url, options);
});
//when viz is loaded (onFirstInteractive), request data
function getSummaryData() {
options = {
maxRows: 0, // Max rows to return. Use 0 to return all rows
ignoreAliases: false,
ignoreSelection: true,
includeAllColumns: false
};
sheet = viz.getWorkbook().getActiveSheet();
//if active tab is a worksheet, get data from that sheet
if (sheet.getSheetType() === 'worksheet') {
sheet.getSummaryDataAsync(options).then(function (t) {
buildMenu(t);
});
//if active sheet is a dashboard get data from a specified sheet
} else {
worksheetArray = viz.getWorkbook().getActiveSheet().getWorksheets();
for (var i = 0; i < worksheetArray.length; i++) {
worksheet = worksheetArray[i];
sheetName = worksheet.getName();
if (sheetName == 'CustomerRank') {
worksheetArray[i].getSummaryDataAsync(options).then(function (t) {
var data = t.getData();
var columns = t.getColumns();
table = t;
var tgt = document.getElementById("dataTarget");
tgt.innerHTML = "<h4>Underlying Data:</h4><p>" + JSON.stringify(table.getData()) + "</p>";
buildVisual(t);
});
}
}
}
}
function buildVisual(table) {
//the data returned from the tableau API
var columns = table.getColumns();
var data = table.getData();
//convert to field:values convention
function reduceToObjects(cols,data) {
var fieldNameMap = $.map(cols, function(col) { return col.getFieldName(); });
var dataToReturn = $.map(data, function(d) {
return d.reduce(function(memo, value, idx) {
memo[fieldNameMap[idx]] = value.value; return memo;
}, {});
});
return dataToReturn;
}
var niceData = reduceToObjects(columns, data);
var svg = d3.select('body')
.append('svg')
.attr('width', 400)
.attr('height', 200)
.attr('id', 'chart');
// add an SVG group element for each user
var series = svg.selectAll('g.series')
.data(d3.keys(dataTarget))
.enter()
.append('g')
.attr('class', 'series');
var width = 300,
height = 300,
radius = Math.min(width, height) / 2;
var color = d3.scale.category20();
var pie = d3.layout.pie()
.sort(null);
var piedata = pie(niceData.data);
var arc = d3.svg.arc()
.innerRadius(radius - 100)
.outerRadius(radius - 50);
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
var path = svg.selectAll("path")
.data(piedata)
.enter().append("path")
.attr("fill", function(d, i) { return color(i); })
.attr("d", arc);
svg.selectAll("text").data(piedata)
.enter()
.append("text")
.attr("text-anchor", "middle")
.attr("x", function(d) {
var a = d.startAngle + (d.endAngle - d.startAngle)/2 - Math.PI/2;
d.cx = Math.cos(a) * (radius - 75);
return d.x = Math.cos(a) * (radius - 20);
})
.attr("y", function(d) {
var a = d.startAngle + (d.endAngle - d.startAngle)/2 - Math.PI/2;
d.cy = Math.sin(a) * (radius - 75);
return d.y = Math.sin(a) * (radius - 20);
})
.text(function(d) { return d.value; })
.each(function(d) {
var bbox = this.getBBox();
d.sx = d.x - bbox.width/2 - 2;
d.ox = d.x + bbox.width/2 + 2;
d.sy = d.oy = d.y + 5;
});
svg.append("defs").append("marker")
.attr("id", "circ")
.attr("markerWidth", 6)
.attr("markerHeight", 6)
.attr("refX", 3)
.attr("refY", 3)
.append("circle")
.attr("cx", 3)
.attr("cy", 3)
.attr("r", 3);
svg.selectAll("path.pointer").data(piedata).enter()
.append("path")
.attr("class", "pointer")
.style("fill", "none")
.style("stroke", "black")
.attr("marker-end", "url(#circ)")
.attr("d", function(d) {
if(d.cx > d.ox) {
return "M" + d.sx + "," + d.sy + "L" + d.ox + "," + d.oy + " " + d.cx + "," + d.cy;
} else {
return "M" + d.ox + "," + d.oy + "L" + d.sx + "," + d.sy + " " + d.cx + "," + d.cy;
}
});
}

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/

d3.js rainbow chart animation enhancements

I've created this arc chart. I'd like to animate the labels better, have them tween with the arc animations. I've placed the labels inside to avoid being covered up.
jsFiddle
var arcGenerator = {
radius: 100,
oldData: "",
init: function(data){
var clone = jQuery.extend(true, {}, data);
this.oldData = this.setData(clone, false);
this.setup(this.setData(data, true));
},
update: function(data){
var clone = jQuery.extend(true, {}, data);
this.animate(this.setData(data, true));
this.oldData = this.setData(clone, false);
},
animate: function(data){
var that = this;
var chart = d3.select(".arcchart");
that.generateArcs(chart, data);
},
setData: function(data, isSorted){
var diameter = 2 * Math.PI * this.radius;
var localData = new Array();
var displacement = 0;
var oldBatchLength = 0;
$.each(data, function(index, value) {
var riseLevels = value.segments;
var riseLevelCount = riseLevels.length;
if(oldBatchLength !=undefined){
displacement+=oldBatchLength;
}
var arcBatchLength = 2*Math.PI;
var arcPartition = arcBatchLength/riseLevelCount;
$.each(riseLevels, function( ri, value ) {
var startAngle = (ri*arcPartition);
var endAngle = ((ri+1)*arcPartition);
if(index!=0){
startAngle+=displacement;
endAngle+=displacement;
}
riseLevels[ri]["startAngle"] = startAngle;
riseLevels[ri]["endAngle"] = endAngle;
});
oldBatchLength = arcBatchLength;
localData.push(riseLevels);
});
var finalArray = new Array();
$.each(localData, function(index, value) {
$.each(localData[index], function(i, v) {
finalArray.push(v);
});
});
return finalArray;
},
generateArcs: function(chart, data){
var that = this;
//_arc paths
//append previous value to it.
$.each(data, function(index, value) {
if(that.oldData[index] != undefined){
data[index]["previousEndAngle"] = that.oldData[index].endAngle;
}
else{
data[index]["previousEndAngle"] = 0;
}
});
var arcpaths = that.arcpaths.selectAll("path")
.data(data);
arcpaths.enter().append("svg:path")
.attr("class", function(d, i){
return d.machineType;
})
.style("fill", function(d, i){
return d.color;
})
.transition()
.ease("elastic")
.duration(750)
.attrTween("d", arcTween);
arcpaths.transition()
.ease("elastic")
.style("fill", function(d, i){
return d.color;
})
.duration(750)
.attrTween("d",arcTween);
arcpaths.exit().transition()
.ease("bounce")
.duration(750)
.attrTween("d", arcTween)
.remove();
function arcTween(b) {
var prev = JSON.parse(JSON.stringify(b));
prev.endAngle = b.previousEndAngle;
var i = d3.interpolate(prev, b);
return function(t) {
return that.getArc()(i(t));
};
}
//_arc paths
var r = that.radius - 50;
var ir = that.radius + 90;
//__labels
var labels = that.labels.selectAll("text")
.data(data);
labels.enter()
.append("text")
.attr("text-anchor", "middle")
labels
.attr("x", function(d) {
var a = d.startAngle + (d.endAngle - d.startAngle)/2 - Math.PI/2;
d.cx = Math.cos(a) * (ir+((r-ir)/2));
return d.x = Math.cos(a) * (r + 20);
})
.attr("y", function(d) {
var a = d.startAngle + (d.endAngle - d.startAngle)/2 - Math.PI/2;
d.cy = Math.sin(a) * (ir+((r-ir)/2));
return d.y = Math.sin(a) * (r + 20);
})
.text(function(d) {
return d.color;
})
.each(function(d) {
var bbox = this.getBBox();
d.sx = d.x - bbox.width/2 - 2;
d.ox = d.x + bbox.width/2 + 2;
d.sy = d.oy = d.y + 5;
})
.transition()
.duration(300)
labels
.transition()
.duration(300)
labels.exit().remove();
//__labels
//__pointers
that.pointers.append("defs").append("marker")
.attr("id", "circ")
.attr("markerWidth", 6)
.attr("markerHeight", 6)
.attr("refX", 3)
.attr("refY", 3)
.append("circle")
.attr("cx", 3)
.attr("cy", 3)
.attr("r", 3);
var pointers = that.pointers.selectAll("path.pointer")
.data(data);
pointers.enter()
.append("path")
.attr("class", "pointer")
.style("fill", "none")
.style("stroke", "black")
.attr("marker-end", "url(#circ)");
pointers
.attr("d", function(d) {
if(d.cx > d.ox) {
return "M" + d.sx + "," + d.sy + "L" + d.ox + "," + d.oy + " " + d.cx + "," + d.cy;
} else {
return "M" + d.ox + "," + d.oy + "L" + d.sx + "," + d.sy + " " + d.cx + "," + d.cy;
}
})
.transition()
.duration(300)
pointers
.transition()
.duration(300)
pointers.exit().remove();
//__pointers
},
setup: function(data){
var chart = d3.select("#threshold").append("svg:svg")
.attr("class", "chart")
.attr("width", 420)
.attr("height", 420)
.append("svg:g")
.attr("class", "arcchart")
.attr("transform", "translate(200,200)");
this.arcpaths = chart.append("g")
.attr("class", "arcpaths");
this.labels = chart.append("g")
.attr("class", "labels");
this.pointers = chart.append("g")
.attr("class", "pointer");
this.generateArcs(chart, data);
},
getArc: function(){
var that = this;
var arc = d3.svg.arc()
.innerRadius(function(d, i){
return that.radius;
})
.outerRadius(function(d){
var maxHeight = 100;
var ratio = (d.height/maxHeight * 100)+that.radius;
return ratio;
})
.startAngle(function(d, i){
return d.startAngle;
})
.endAngle(function(d, i){
return d.endAngle;
});
return arc;
}
}
$(document).ready(function() {
var dataCharts = [
{
"data": [
{
"segments": [
{
height: 10,
color: "grey"
},
{
height: 40,
color: "darkgrey"
},
{
height: 33,
color: "grey"
},
{
height: 50,
color: "darkgrey"
},
{
height: 33,
color: "grey"
},
{
height: 10,
color: "darkgrey"
},
{
height: 50,
color: "grey"
},
{
height: 45,
color: "darkgrey"
},
{
height: 10,
color: "grey"
},
{
height: 40,
color: "darkgrey"
}
]
}
]
},
{
"data": [
{
"segments": [
{
height: 50,
color: "red"
},
{
height: 100,
color: "yellow"
},
{
height: 10,
color: "green"
}
]
}
]
}
];
var clone = jQuery.extend(true, {}, dataCharts);
arcGenerator.init(clone[0].data);
$(".testers a").on( "click", function(e) {
e.preventDefault();
var clone = jQuery.extend(true, {}, dataCharts);
var pos = $(this).parent("li").index();
arcGenerator.update(clone[pos].data);
});
});
There are two parts for this. First, the animation of the pointer lines. This is relatively easy and the only thing you're missing is that the .transition() is in the wrong place:
pointers
.transition()
.duration(300)
.attr("d", function(d) {
// etc
The second part is the animation of the text labels. This is a bit more difficult because their computation includes some side effects that allow the correct computation of the pointer lines. This comes in two parts -- the computation of the position and the computation of the extent of the displayed text. With that in mind, the changes are relatively straightforward, we just need to make sure that those computations take place before the transition starts:
labels.text(function(d) {
return d.color;
}).each(function(d) {
var a = d.startAngle + (d.endAngle - d.startAngle)/2 - Math.PI/2;
d.cx = Math.cos(a) * (ir+((r-ir)/2));
d.cy = Math.sin(a) * (ir+((r-ir)/2));
d.x = d.x || Math.cos(a) * (r + 20);
d.y = d.y || Math.sin(a) * (r + 20);
var bbox = this.getBBox();
d.sx = d.x - bbox.width/2 - 2;
d.ox = d.x + bbox.width/2 + 2;
d.sy = d.oy = d.y + 5;
})
First, the text itself is set. This is required to be able to use .getBBox() to determine its dimensions. Then, all the values required by the pointer paths are computed -- these bits of code were previously in the computation of the position of the text, but that's what we want to transition to so those values are set later (except for new text labels that don't have coordinates set).
All that remains now is to animate the change of coordinates of the text in the same way as before:
.transition()
.duration(300)
.attr("x", function(d) {
var a = d.startAngle + (d.endAngle - d.startAngle)/2 - Math.PI/2;
return d.x = Math.cos(a) * (r + 20);
})
.attr("y", function(d) {
var a = d.startAngle + (d.endAngle - d.startAngle)/2 - Math.PI/2;
return d.y = Math.sin(a) * (r + 20);
});
Complete example here.

Categories

Resources