Create a smooth draggable bar-graph using d3js - javascript

I am attempting to create a bar graph that can be updated by dragging the bars. I have been able to create such a graph but the dragging is not smooth. View my attempt in fiddle
Here is my code:
var data = [{x:-6.1, y: "I"}, {x:-4.5, y: "H"}, {x: -0.4, y: "G"},{x:0.8, y: "F"}, {x:3.8, y: "E"}, {x: 5.3, y: "D"}];
drawDraggableBarGraph= function(data, drawSmoothCurve, element) {
var bar, barHeight, datae, h, height, j, len, lineFunc, margin, objects, parsedX, svg, w, width, xAxis, xMax, xMin, xScale;
$(element).empty();
svg = d3.select(element).append("svg");
margin = {
top: 10,
bottom: 20,
left: 30,
right: 10
};
w = $(element).width();
h = 200;
width = w - margin.left - margin.right;
height = h - margin.top - margin.bottom;
for (j = 0, len = data.length; j < len; j++) {
datae = data[j];
parsedX = parseFloat(datae.x);
datae.x = parsedX;
}
svg = svg.attr("width", w).attr("height", h).append("g").attr("transform", "translate(" + margin.left + ", " + margin.top + ")");
xMin = d3.min(data, function(d) {
return d.x;
});
xMax = d3.max(data, function(d) {
return d.x;
});
xScale = d3.scale.linear().domain([xMin > 0 ? xMin * 0.95 : xMin * 1.05, xMax * 1.05]).range([0, width]);
barHeight = (height / data.length) * 0.9;
xAxis = d3.svg.axis().scale(xScale).orient("bottom").tickSize(-height);
svg.append("g").attr("class", "x axis").attr("transform", "translate(0," + height + ")").call(xAxis);
objects = svg.append('svg').attr('width', width).attr('height', height);
bar = objects.selectAll("g").data(data).enter().append("g").attr("transform", function(d, i) {
return "translate(0," + (i * barHeight + height * 0.05) + ")";
}).attr('fill', function(d, i) {
return d3.scale.category20().range()[i];
});
bar.append('rect').attr("x", 0).attr('width', width).attr('height', barHeight - 1).attr('fill', 'transparent');
bar.append("rect").attr("x", function(d) {
if (d.x > 0) {
return xScale(0);
} else {
return xScale(d.x);
}
}).attr("width", function(d) {
if (d.x > 0) {
return xScale(d.x) - xScale(0);
} else {
return xScale(0) - xScale(d.x);
}
}).attr("height", barHeight - 1).attr('class', function(d) {
return "drag-bar-" + d.y;
});
bar.on('mouseover', function(d) {
jQuery("line.default-hidden").hide();
return $(this).children('line').show();
}).on('mouseout', function(d) {
return jQuery("line.default-hidden").hide();
});
bar.append("text").attr("dy", ".75em").attr("y", barHeight / 2 - 7).attr("x", function(d) {
if (d.x > 0) {
return xScale(0) - 10;
} else {
return xScale(0) + 10;
}
}).attr("text-anchor", "middle").text(function(d) {
return d.y;
});
if (drawSmoothCurve) {
// Try commenting out from here
lineFunc = d3.svg.line().x(function(d) {
return xScale(d.x);
}).y((function(_this) {
return function(d, i) {
return i * barHeight + barHeight / 2 + height * 0.05;
};
})(this)).interpolate("basis");
objects.append("path").attr("d", lineFunc(data)).attr("stroke", "#666666").attr("stroke-width", 1).attr("fill", "none");
// Till here
bar.append('line').attr('x1', function(d) {
return xScale(d.x);
}).attr('x2', function(d) {
return xScale(d.x);
}).attr('y1', -1).attr('y2', barHeight).attr('stroke', 'black').attr('stroke-width', '6').attr('class', function(d) {
return "default-hidden drag-line-" + d.y;
}).on("mousedown", function(d) {
return d3.select(this).classed('drag-enabled', true);
}).on('mousemove', function(d) {
var newX, p;
if ($(this).attr('class').indexOf('drag-enabled') < 0) {
return;
}
p = d3.mouse(this);
newX = p[0];
return d3.select(this).attr('x1', newX).attr('x2', newX);
}).on('mouseup', (function(_this) {
return function(d) {
var attrY, classList, k, klass, l, len1, len2, newBarX;
d3.select(this).classed('drag-enabled', false);
newBarX = d3.mouse(this)[0];
classList = this.classList;
attrY = null;
for (k = 0, len1 = classList.length; k < len1; k++) {
klass = classList[k];
if (klass.indexOf('drag-line') >= 0) {
attrY = klass.slice(klass.lastIndexOf('-') + 1);
}
}
data = data;
for (l = 0, len2 = data.length; l < len2; l++) {
datae = data[l];
if (datae.y === attrY) {
datae.x = xScale.invert(newBarX).toFixed(2);
}
}
return drawDraggableBarGraph(data, true,".sliding-bar");
};
})(this));
}
}
drawDraggableBarGraph(data, true,".sliding-bar");
I am trying to make the process of updating values user-friendly but halts while dragging kills it somehow.
Any suggestions to make it smooth

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 stop text transitioning off the screen

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.

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/

Animate objects in force layout in D3.js

I need to create a data visualisation which would look like a bunch of floating bubbles with text inside of the bubble.
I have a partially working example which uses mock data prepared here:
JSfiddle
// helpers
var random = function(min, max) {
if (max == null) {
max = min;
min = 0;
}
return min + Math.floor(Math.random() * (max - min + 1));
};
// mock data
var colors = [
{
fill: 'rgba(242,216,28,0.3)',
stroke: 'rgba(242,216,28,1)'
},
{
fill: 'rgba(207,203,196,0.3)',
stroke: 'rgba(207,203,196,1)'
},
{
fill: 'rgba(0,0,0,0.2)',
stroke: 'rgba(100,100,100,1)'
}
];
var data = [];
for(var j = 0; j <= 2; j++) {
for(var i = 0; i <= 4; i++) {
var text = 'text' + i;
var category = 'category' + j;
var r = random(50, 100);
data.push({
text: text,
category: category,
r: r,
r_change_1: r + random(-20, 20),
r_change_2: r + random(-20, 20),
fill: colors[j].fill,
stroke: colors[j].stroke
});
}
}
// mock debug
//console.table(data);
// collision detection
// derived from http://bl.ocks.org/mbostock/1748247
function collide(alpha) {
var quadtree = d3.geom.quadtree(data);
return function(d) {
var r = d.r + 10,
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.r * 2;
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;
});
};
}
// initialize
var container = d3.select('.bubble-cloud');
var $container = $('.bubble-cloud');
var containerWidth = $container.width();
var containerHeight = $container.height();
var svgContainer = container
.append('svg')
.attr('width', containerWidth)
.attr('height', containerHeight);
// prepare layout
var force = d3.layout
.force()
.size([containerWidth, containerHeight])
.gravity(0)
.charge(0)
;
// load data
force.nodes(data)
.start()
;
// create item groups
var node = svgContainer.selectAll('.node')
.data(data)
.enter()
.append('g')
.attr('class', 'node')
.call(force.drag);
// create circles
node.append('circle')
.classed('circle', true)
.attr('r', function (d) {
return d.r;
})
.style('fill', function (d) {
return d.fill;
})
.style('stroke', function (d) {
return d.stroke;
});
// create labels
node.append('text')
.text(function(d) {
return d.text
})
.classed('text', true)
.style({
'fill': '#ffffff',
'text-anchor': 'middle',
'font-size': '12px',
'font-weight': 'bold',
'font-family': 'Tahoma, Arial, sans-serif'
})
;
node.append('text')
.text(function(d) {
return d.category
})
.classed('category', true)
.style({
'fill': '#ffffff',
'font-family': 'Tahoma, Arial, sans-serif',
'text-anchor': 'middle',
'font-size': '9px'
})
;
node.append('line')
.classed('line', true)
.attr('x1', 0)
.attr('y1', 0)
.attr('x2', 50)
.attr('y2', 0)
.attr('stroke-width', 1)
.attr('stroke', function (d) {
return d.stroke;
})
;
// put circle into movement
force.on('tick', function(){
d3.selectAll('circle')
.each(collide(.5))
.attr('cx', function (d) {
// boundaries
if(d.x <= d.r) {
d.x = d.r + 1;
}
if(d.x >= containerWidth - d.r) {
d.x = containerWidth - d.r - 1;
}
return d.x;
})
.attr('cy', function (d) {
// boundaries
if(d.y <= d.r) {
d.y = d.r + 1;
}
if(d.y >= containerHeight - d.r) {
d.y = containerHeight - d.r - 1;
}
return d.y;
});
d3.selectAll('line')
.attr('x1', function (d) {
return d.x - d.r + 10;
})
.attr('y1', function (d) {
return d.y;
})
.attr('x2', function (d) {
return d.x + d.r - 10;
})
.attr('y2', function (d) {
return d.y;
});
d3.selectAll('.text')
.attr('x', function (d) {
return d.x;
})
.attr('y', function (d) {
return d.y - 10;
});
d3.selectAll('.category')
.attr('x', function (d) {
return d.x;
})
.attr('y', function (d) {
return d.y + 20;
});
});
// animate
var interval = setInterval(function(){
// moving of the circles
// ...
}, 5 * 1000);
However I am now facing problem with animation. I cannot figure out how can I animate nodes in force diagram. I tried to adjust values of the data object and then invoke .tick() method inside setInterval method, however it didn't help. I am utilizing D3 force layout.
My questions are:
How to make the bubbles "float" around the screen, i.e. how to
animate them?
How to animate changes of circle radius?
Thank you for your ideas.
Actually, I think this one feels nicer...
Key points
Charge set to 0, friction set to 0.9
Schedule parallel transitions on the radius and line in the timer callback
Use the dynamic radius for calculating collisions
Use a transform on the nodes (g element) to decouple text and line positioning from node position, adjust the transform x and y, only in the tick callback
Remove the CSS transitions and add d3 transitions so that you can synchronise everything
changed this r = d.rt + 10 to this r = d.rt + rmax in the collision function to tighten up the control on overlaps
Closed loop speed regulator. Even though friction is set to 0.9 to dampen movement, the speed regulator will keep them moving
Use parallel transitions to coordinate geometry changes
Added a small amount of gravity
working example
// helpers
var random = function(min, max) {
if (max == null) {
max = min;
min = 0;
}
return min + Math.floor(Math.random() * (max - min + 1));
},
metrics = d3.select('.bubble-cloud').append("div")
.attr("id", "metrics")
.style({"white-space": "pre", "font-size": "8px"}),
elapsedTime = outputs.ElapsedTime("#metrics", {
border: 0, margin: 0, "box-sizing": "border-box",
padding: "0 0 0 6px", background: "black", "color": "orange"
})
.message(function(value) {
var this_lap = this.lap().lastLap, aveLap = this.aveLap(this_lap)
return 'alpha:' + d3.format(" >7,.3f")(value)
+ '\tframe rate:' + d3.format(" >4,.1f")(1 / aveLap) + " fps"
}),
hist = d3.ui.FpsMeter("#metrics", {display: "inline-block"}, {
height: 8, width: 100,
values: function(d){return 1/d},
domain: [0, 60]
}),
// mock data
colors = [
{
fill: 'rgba(242,216,28,0.3)',
stroke: 'rgba(242,216,28,1)'
},
{
fill: 'rgba(207,203,196,0.3)',
stroke: 'rgba(207,203,196,1)'
},
{
fill: 'rgba(0,0,0,0.2)',
stroke: 'rgba(100,100,100,1)'
}
];
// initialize
var container = d3.select('.bubble-cloud');
var $container = $('.bubble-cloud');
var containerWidth = 600;
var containerHeight = 180 - elapsedTime.selection.node().clientHeight;
var svgContainer = container
.append('svg')
.attr('width', containerWidth)
.attr('height', containerHeight);
var data = [],
rmin = 15,
rmax = 30;
d3.range(0, 3).forEach(function(j){
d3.range(0, 6).forEach(function(i){
var r = random(rmin, rmax);
data.push({
text: 'text' + i,
category: 'category' + j,
x: random(rmax, containerWidth - rmax),
y: random(rmax, containerHeight - rmax),
r: r,
fill: colors[j].fill,
stroke: colors[j].stroke,
get v() {
var d = this;
return {x: d.x - d.px || 0, y: d.y - d.py || 0}
},
set v(v) {
var d = this;
d.px = d.x - v.x;
d.py = d.y - v.y;
},
get s() {
var v = this.v;
return Math.sqrt(v.x * v.x + v.y * v.y)
},
set s(s1){
var s0 = this.s, v0 = this.v;
if(!v0 || s0 == 0) {
var theta = Math.random() * Math.PI * 2;
this.v = {x: Math.cos(theta) * s1, y: Math.sin(theta) * s1}
} else this.v = {x: v0.x * s1/s0, y: v0.y * s1/s0};
},
set sx(s) {
this.v = {x: s, y: this.v.y}
},
set sy(s) {
this.v = {y: s, x: this.v.x}
},
});
})
});
// collision detection
// derived from http://bl.ocks.org/mbostock/1748247
function collide(alpha) {
var quadtree = d3.geom.quadtree(data);
return function(d) {
var r = d.rt + rmax,
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.rt + quad.point.rt;
if (l < r) {
l = (l - r) / l * (1 + 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;
});
};
}
// prepare layout
var force = d3.layout
.force()
.size([containerWidth, containerHeight])
.gravity(0.001)
.charge(0)
.friction(.8)
.on("start", function() {
elapsedTime.start(100);
});
// load data
force.nodes(data)
.start();
// create item groups
var node = svgContainer.selectAll('.node')
.data(data)
.enter()
.append('g')
.attr('class', 'node')
.call(force.drag);
// create circles
var circles = node.append('circle')
.classed('circle', true)
.attr('r', function (d) {
return d.r;
})
.style('fill', function (d) {
return d.fill;
})
.style('stroke', function (d) {
return d.stroke;
})
.each(function(d){
// add dynamic r getter
var n= d3.select(this);
Object.defineProperty(d, "rt", {get: function(){
return +n.attr("r")
}})
});
// create labels
node.append('text')
.text(function(d) {
return d.text
})
.classed('text', true)
.style({
'fill': '#ffffff',
'text-anchor': 'middle',
'font-size': '6px',
'font-weight': 'bold',
'text-transform': 'uppercase',
'font-family': 'Tahoma, Arial, sans-serif'
})
.attr('x', function (d) {
return 0;
})
.attr('y', function (d) {
return - rmax/5;
});
node.append('text')
.text(function(d) {
return d.category
})
.classed('category', true)
.style({
'fill': '#ffffff',
'font-family': 'Tahoma, Arial, sans-serif',
'text-anchor': 'middle',
'font-size': '4px'
})
.attr('x', function (d) {
return 0;
})
.attr('y', function (d) {
return rmax/4;
});
var lines = node.append('line')
.classed('line', true)
.attr({
x1: function (d) {
return - d.r + rmax/10;
},
y1: function (d) {
return 0;
},
x2: function (d) {
return d.r - rmax/10;
},
y2: function (d) {
return 0;
}
})
.attr('stroke-width', 1)
.attr('stroke', function (d) {
return d.stroke;
})
.each(function(d){
// add dynamic x getter
var n= d3.select(this);
Object.defineProperty(d, "lxt", {get: function(){
return {x1: +n.attr("x1"), x2: +n.attr("x2")}
}})
});
// put circle into movement
force.on('tick', function t(e){
var s0 = 0.25, k = 0.3;
a = e.alpha ? e.alpha : force.alpha();
elapsedTime.mark(a);
if(elapsedTime.aveLap.history.length)
hist(elapsedTime.aveLap.history);
for ( var i = 0; i < 3; i++) {
circles
.each(collide(a))
.each(function(d) {
var moreThan, v0;
// boundaries
//reflect off the edges of the container
// check for boundary collisions and reverse velocity if necessary
if((moreThan = d.x > (containerWidth - d.rt)) || d.x < d.rt) {
d.escaped |= 2;
// if the object is outside the boundaries
// manage the sign of its x velocity component to ensure it is moving back into the bounds
if(~~d.v.x) d.sx = d.v.x * (moreThan && d.v.x > 0 || !moreThan && d.v.x < 0 ? -1 : 1);
// if vx is too small, then steer it back in
else d.sx = (~~Math.abs(d.v.y) || Math.min(s0, 1)*2) * (moreThan ? -1 : 1);
// clear the boundary without affecting the velocity
v0 = d.v;
d.x = moreThan ? containerWidth - d.rt : d.rt;
d.v = v0;
// add a bit of hysteresis to quench limit cycles
} else if (d.x < (containerWidth - 2*d.rt) && d.x > 2*d.rt) d.escaped &= ~2;
if((moreThan = d.y > (containerHeight - d.rt)) || d.y < d.rt) {
d.escaped |= 4;
if(~~d.v.y) d.sy = d.v.y * (moreThan && d.v.y > 0 || !moreThan && d.v.y < 0 ? -1 : 1);
else d.sy = (~~Math.abs(d.v.x) || Math.min(s0, 1)*2) * (moreThan ? -1 : 1);
v0 = d.v;
d.y = moreThan ? containerHeight - d.rt : d.rt;
d.v = v0;
} else if (d.y < (containerHeight - 2*d.rt) && d.y > 2*d.rt) d.escaped &= ~4;
});
}
// regulate the speed of the circles
data.forEach(function reg(d){
if(!d.escaped) d.s = (s0 - d.s * k) / (1 - k);
});
node.attr("transform", function position(d){return "translate(" + [d.x, d.y] + ")"});
force.alpha(0.05);
});
// animate
window.setInterval(function(){
var tinfl = 3000, tdefl = 1000, inflate = "elastic", deflate = "cubic-out";
for(var i = 0; i < data.length; i++) {
if(Math.random()>0.8) data[i].r = random(rmin,rmax);
}
var changes = circles.filter(function(d){return d.r != d.rt});
changes.filter(function(d){return d.r > d.rt})
.transition("r").duration(tinfl).ease(inflate)
.attr('r', function (d) {
return d.r;
});
changes.filter(function(d){return d.r < d.rt})
.transition("r").duration(tdefl).ease(deflate)
.attr('r', function (d) {
return d.r;
});
// this runs with an error of less than 1% of rmax
changes = lines.filter(function(d){return d.r != d.rt});
changes.filter(function(d){return d.r > d.rt})
.transition("l").duration(tinfl).ease(inflate)
.attr({
x1: function lx1(d) {
return -d.r + rmax / 10;
},
x2: function lx2(d) {
return d.r - rmax / 10;
}
});
changes.filter(function(d){return d.r < d.rt})
.transition("l").duration(tdefl).ease(deflate)
.attr({
x1: function lx1(d) {
return -d.r + rmax / 10;
},
x2: function lx2(d) {
return d.r - rmax / 10;
}
});
}, 2 * 500);
body {
background: black;
margin:0;
padding:0;
}
.bubble-cloud {
background: url("http://dummyimage.com/100x100/111/333?text=sample") 0 0;
width: 600px;
height: 190px;
overflow: hidden;
position: relative;
margin:0 auto;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<script src="https://gitcdn.xyz/repo/cool-Blue/d3-lib/master/elapsedTime/elapsed-time-2.0.js"></script>
<script src="https://gitcdn.xyz/repo/cool-Blue/d3-lib/master/plot/plot-transform.js"></script>
<script src="https://gitcdn.xyz/repo/cool-Blue/d3-lib/master/plot/fps-histogram.js"></script>
<link rel="stylesheet" type="text/css" href="https://gitcdn.xyz/repo/cool-Blue/d3-lib/master/plot/fps-histogram.css">
<div class="bubble-cloud"></div>
I like to use this formula for the spacing dynamic...
l = (l - r) / l * (1+ alpha);
and then use an alpha of about 0.05
No need for gravity or charge in my view, the only thing I change is to set friction to 1. This means that velocity is maintained, but if your clients are getting motion sick then knock it back to 0.99.
EDIT:
changed to a slightly softer and more correct collision model
l = (l - r) / l * (1/2 + alpha);
Also added a little gravity to make it "cloud-like" and friction (see above)
CSS transitions
I also tried to use CSS transitions but support seems patchy to say the least on SVG elements.
Transition works on circle radius but not on line in chrome (45.0) and Opera
In IE 11 and FF (40.0.3) none of the CSS transitions work for me
I would be interested in any feed-back on browser compatibility as i couldn't find much on the internet about this.
I experimented with velocity.js on the back of this and I think I prefer it for the transitions.

Transition not working

My goal is that given a value in seconds(resp_time), I want to create a counter in anticlock direction that would end once resp_time becomes 0.
I am following this tutorial: http://bl.ocks.org/mbostock/1096355 to create a polar clock. But I need the arc to decrease as in go anti-clockwise. I tried updating the endAngle value for the same, but it doesn't seem to work.
Here's my code:
var width = 960,
height = 800,
radius = Math.min(width, height) / 1.9,
spacing = .09;
var resp_time = 61;
var rh = parseInt(resp_time/3600), rm = parseInt((resp_time- rh*3600)/60), rs = parseInt((resp_time- rh*3600 - rm*60)%60);
var color = d3.scale.linear()
.range(["hsl(-180,50%,50%)", "hsl(180,50%,50%)"])
.interpolate(interpolateHsl);
var t;
var arc = d3.svg.arc()
.startAngle(0)
.endAngle(function(d) { return d.value * 2 * Math.PI; })
.innerRadius(function(d) { return d.index * radius; })
.outerRadius(function(d) { return (d.index + spacing) * radius; });
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
var field = svg.selectAll("g")
.data(fields)
.enter().append("g");
field.append("path");
field.append("text");
d3.transition().duration(0).each(tick);
d3.select(self.frameElement).style("height", height + "px");
function tick() {
field = field
.each(function(d) { this._value = d.value; })
.data(fields)
.each(function(d) { d.previousValue = this._value; });
field.select("path")
.transition()
.ease("elastic")
.attrTween("d", arcTween)
.style("fill", function(d) { return color(d.value); });
field.select("text")
.attr("dy", function(d) { return d.value < .5 ? "-.5em" : "1em"; })
.text(function(d) { return d.text; })
.transition()
.ease("elastic")
.attr("transform", function(d) {
return "rotate(" + 360 * d.value + ")"
+ "translate(0," + -(d.index + spacing / 2) * radius + ")"
+ "rotate(" + (d.value < .5 ? -90 : 90) + ")"
});
if (resp_time > 0)
{
resp_time = resp_time - 1;
rh = parseInt(resp_time/3600), rm = parseInt((resp_time- rh*3600)/60), rs = parseInt((resp_time- rh*3600 - rm*60)%60);
t = setTimeout(tick, 1000);
}
}
function arcTween(d) {
console.log(d);
var i = d3.interpolateNumber(d.previousValue, d.value);
return function(t) { d.value = i(t); return arc(d); };
}
function fields() {
console.log(rs);
return [
{index: .3, text: rs+"s", value: rs},
{index: .2, text: rm+"m", value: rm},
{index: .1, text: rh+"h", value: rh}
];
}
function interpolateHsl(a, b) {
var i = d3.interpolateString(a, b);
return function(t) {
return d3.hsl(i(t));
};
}
This is just resulting in 3 static concentric circles(since I'm plotting only the minute, seconds and hours) with no transition.
I'm not sure how to proceed from here. What change should I make to get it working? Please help me out.
d.value in this code should be in the range [0, 1], so you need to divide by the maximum values in the fields generator:
function fields() {
console.log(rs);
return [
{index: .3, text: rs+"s", value: rs/60},
{index: .2, text: rm+"m", value: rm/60},
{index: .1, text: rh+"h", value: rh/24}
];
}
I noticed this because console.log(rs) was printing out values in the range of [0, 60] whereas arc.endAngle expects a radian value. If you substitute [0, 60] with the endAngle provider in the code, you get an over wound clock hand.
var arc = d3.svg.arc()
.startAngle(0)
.endAngle(function(d) { return d.value * 2 * Math.PI; })
var width = 960,
height = 800,
radius = Math.min(width, height) / 1.9,
spacing = .09;
var resp_time = 61;
var rh = parseInt(resp_time/3600), rm = parseInt((resp_time- rh*3600)/60), rs = parseInt((resp_time- rh*3600 - rm*60)%60);
var color = d3.scale.linear()
.range(["hsl(-180,50%,50%)", "hsl(180,50%,50%)"])
.interpolate(interpolateHsl);
var t;
var arc = d3.svg.arc()
.startAngle(0)
.endAngle(function(d) { return d.value * 2 * Math.PI; })
.innerRadius(function(d) { return d.index * radius; })
.outerRadius(function(d) { return (d.index + spacing) * radius; });
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
var field = svg.selectAll("g")
.data(fields)
.enter().append("g");
field.append("path");
field.append("text");
d3.transition().duration(0).each(tick);
d3.select(self.frameElement).style("height", height + "px");
function tick() {
field = field
.each(function(d) { this._value = d.value; })
.data(fields)
.each(function(d) { d.previousValue = this._value; });
field.select("path")
.transition()
.ease("elastic")
.attrTween("d", arcTween)
.style("fill", function(d) { return color(d.value); });
field.select("text")
.attr("dy", function(d) { return d.value < .5 ? "-.5em" : "1em"; })
.text(function(d) { return d.text; })
.transition()
.ease("elastic")
.attr("transform", function(d) {
return "rotate(" + 360 * d.value + ")"
+ "translate(0," + -(d.index + spacing / 2) * radius + ")"
+ "rotate(" + (d.value < .5 ? -90 : 90) + ")"
});
if (resp_time > 0)
{
resp_time = resp_time - 1;
rh = parseInt(resp_time/3600), rm = parseInt((resp_time- rh*3600)/60), rs = parseInt((resp_time- rh*3600 - rm*60)%60);
t = setTimeout(tick, 1000);
}
}
function arcTween(d) {
console.log(d);
var i = d3.interpolateNumber(d.previousValue, d.value);
return function(t) { d.value = i(t); return arc(d); };
}
function fields() {
console.log(rs);
return [
{index: .3, text: rs+"s", value: rs/60},
{index: .2, text: rm+"m", value: rm/60},
{index: .1, text: rh+"h", value: rh/24}
];
}
function interpolateHsl(a, b) {
var i = d3.interpolateString(a, b);
return function(t) {
return d3.hsl(i(t));
};
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>

Categories

Resources