gradient on path:hover when path is transitioned - javascript

I am pretty much new to d3 and i'm working on a d3 project with a friend for a couple of weeks now.
We built a website containing a sankey diagram and a filter that influences the thickness of links and nodes. Therefore the filter has updateSankey() as an event Handler for the change event.
The links are black with stroke-opacity: 0.15
Lately we tried to introduce a feature that appends a linear gradient to a path onmouseover and removes it onmouseout
To make this work we added an eventHandler to each path which calls a function on both the events. in the functions we append or remove the linear gradient. The gradient goes from the color of the source-node to the color of the target-node.
The problem: after filtering, when all the links have been transitioned the source-node and target-node inside the eventhandler isn't updated and therefore the gradient has wrong colors.
this is how it should look like, it works properly if i don't change the filter on the left
as soon as i change the filter on the left, the colors get messed up
I think we have to do a transition to update these colors, but i have absolutely no idea how and where i have to do this, so i would be glad if you guys could help me.
Down below you find all relevant functions as they currently are.
Greetings and thanks alot in advance
bäsi
/**
* Initialize Sankey
*/
function initSankey() {
/*simple initialisation of the sankey, should explain itself*/
svg = d3.select("svg"),
width = +svg.attr("width") - 2*marginleft,
height = +svg.attr("height") - margintop;
formatNumber = d3.format(",.0f"),
format = function (d) { return formatNumber(d) + " %"; },
color = d3.scaleOrdinal(d3.schemeCategory10);
sankey = d3.sankey()
.nodeWidth(15)
.nodePadding(10)
.extent([[1, 1], [width - 1, height - 6]])
.iterations(0);
t = d3.transition()
.duration(1500)
.ease(d3.easeLinear);
//set attributes for all links
titleGroup = svg.append("g")
.attr("class", "titles")
.attr("font-family", "sans-serif")
.attr("font-size", "150%");
diagram= svg.append("g")
.attr("class", "sankey")
.attr("transform", "translate(" + marginleft + "," + margintop + ")");
linkGroup = diagram.append("g")
.attr("class", "links")
.attr("fill", "none");
//.attr("stroke", "#000")
//.attr("stroke-opacity", 0.2);
//set attributes for all nodes
nodeGroup = diagram.append("g")
.attr("class", "nodes")
.attr("font-family", "sans-serif")
.attr("font-size", 10);
}
/**
* for the filtering and transition by selecting a filter we need to update the sankey and "draw" it new
* */
function updateSankey() {
flush();
filter();
calculateLinks();
switch (lang)
{
case "ger":
d3.json("data/labels-ger.json", helper);
break;
case "fra":
d3.json("data/labels-fr.json", helper);
break;
case "eng":
d3.json("data/labels-en.json", helper);
break;
default:
d3.json("data/labels.json", helper);
}
}
/**
* the main function for "drawing" the saneky, takes the customLinks that where calculated and returns the saneky
* */
function helper(error, labels) {
if (error)
throw error;
labels.links = customLinks;
sankey(labels);
var links = linkGroup.selectAll('path')
.data(labels.links);
//Set attributes for each link separately
links.enter().append("g")
.attr("id",function (d,i) {return "path"+i;})
.attr("from",function (d) { return d.source.name; })
.attr("to",function (d) { return d.target.name; })
.append("path")
.attr("stroke", "#000")
.attr("stroke-opacity", 0.15)
.attr("display", function (d) {
/* don't display a link if the link is smaller than 4%, else it will be just displayed*/
if(d.value < 4.0){return "none";}
else{return "inline";}
})
.attr("d", d3.sankeyLinkHorizontal())
.attr("stroke-width", function (d) {return Math.max(1, d.width); })
.on("mouseover",function (d,id) {
var pathGroup = svg.select('#path' + id);
var path = pathGroup.select("path");
/*var from = document.getElementById("path" + id).__data__.source;
var to = document.getElementById("path" + id).__data__.target;
console.log(from)
console.log(to)
*/
var pathGradient = pathGroup.append("defs")
.append("linearGradient")
.attr("id","grad" + id)
.attr("gradientUnit","userSpaceOnUse")
.attr("style","mix-blend-mode: multiply;")
.attr("x1","0%")
.attr("x2","100%")
.attr("y1","0%")
.attr("y2","0%");
pathGradient.append("stop")
.attr("class","from")
.attr("offset","0%")
.attr("style", function (d) {
var color = setColor(d.source);
return "stop-color:" + color + ";stop-opacity:1";
});
pathGradient.append("stop")
.attr("class","to")
.attr("offset","100%")
.attr("style",function (d) {
var color = setColor(d.target);
return "stop-color:" + color + ";stop-opacity:1";
});
path.attr("stroke","url(#grad"+id+")")
.attr("stroke-opacity","0.95");
})
//.attr("onmouseover",function (d,i) { return "appendGradient(" + i + ")" })
.on("mouseout",function (d, id) {
pathGroup = svg.select('#path' + id);
var path = pathGroup.select("path");
var pathGradient = pathGroup.select("defs")
.remove();
path.attr("stroke","#000")
.attr("stroke-opacity","0.15");
})
//.attr("onmouseout",function (d,i) { return "removeGradient(" + i + ")" })
.append("title")
.text(function (d) {
//tooltip info for the links
return d.source.name + " → " + d.target.name + "\n" + format(d.value); });
linkGroup.selectAll("g").transition(t)
.attr("id",function (d,i) {return "path"+i;})
.attr("from",function (d) { return d.source.name; })
.attr("to",function (d) { return d.target.name; });
links.transition(t)
.attr("display", function (d) {
//again if the link is smaller than 4% don't display it, we have to do this method again because of the
// transition, if another filter is selected
if(d.value < 4.0){return "none";}
else{return "inline";}
})
.attr("d", d3.sankeyLinkHorizontal())
.attr("stroke-width", function (d) { return Math.max(1, d.width); })
.select('title')
.text(function (d) {
//same argumentation as above, we need the method again for the transition
return d.source.name + " → " + d.target.name + "\n" + format(d.value); });
//remove the unneeded links
links.exit().remove();
var nodes = nodeGroup.selectAll('.node')
.data(labels.nodes);
var nodesEnter = nodes.enter()
.append("g")
.attr('class', 'node');
//set attributes for each node separately
nodesEnter.append("rect")
.attr("x", function (d) { return d.x0; })
.attr("y", function (d) { return d.y0; })
.attr("height", function (d) { return d.y1 - d.y0; })
.attr("width", function (d) {
var width = d.x1 - d.x0;
if(d.value > 0)
{
//this is used for the years above the nodes, every x position of all nodes is pushed in an array
columnCoord.push(d.x0 + width/2);
}
return width;
})
.attr("fill", setColor)
.attr("stroke", "#000")
.attr("fill-opacity", 0.5)
//specify Pop-Up when hovering over node
nodesEnter.append("title")
.text(function (d) { return d.name + "\n" + format(d.value); });
//Update selection
var nodesUpdate = nodes.transition(t);
//same as the links we have to state the methods again in the update
nodesUpdate.select("rect")
.attr("y", function (d) { return d.y0; })
.attr("x", function (d) { return d.x0; })
.attr("height", function (d) { return d.y1 - d.y0; });
nodesUpdate.select("title")
.text(function (d) { return d.name + "\n" + format(d.value); });
//Exit selection
nodes.exit().remove();
//we filter all arrays
columnCoord = filterArray(columnCoord);
if(!titlesDrawn)
{
drawTitles();
titlesDrawn = true;
}
}

Related

How to transition linear gradient for a path

I'm working on a sankey diagram that updates whenever I change the filter (radiobuttons) using d3.js and a sankey-plugin. Now I'm trying to add a feature that, whenever I hover over one of the path, I append a linear gradient to the path going from the color of the source node to the color of the target node. If I don't use the filter everything works fine, however the gradient-coloring doesn't work if I apply the filter (colors are set wrong) because the links are transitioned. I think that I have to transition the linear-gradient somehow, but I don't understand how I have to do this.
I wrote a little script that shows the problem, before clicking the button colors are correct, and after it's messed up.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="https://unpkg.com/d3-sankey#0.6"></script>
</head>
<body>
<svg id="diagram" height="150" width="600"></svg>
<button onclick="updateSankey()">Click Me!</button>
<style>
#diagram{
border: 1px solid black;
}
</style>
<script>
var target = 0;
var sankeyLinks;
var sankeyData = {nodes:[], links:[]};
calculateLinks();
initSankey();
updateSankey();
function initSankey() {
/*simple initialisation of the sankey, should explain itself*/
svg = d3.select("svg"),
width = +svg.attr("width"),
height = +svg.attr("height");
formatNumber = d3.format(",.0f"),
format = function (d) { return formatNumber(d) + " %"; },
color = d3.scaleOrdinal(d3.schemeCategory10);
sankey = d3.sankey()
.nodeWidth(15)
.nodePadding(10)
.extent([[1, 1], [width - 1, height - 6]])
.iterations(0);
t = d3.transition()
.duration(1500)
.ease(d3.easeLinear);
//set attributes for all links
titleGroup = svg.append("g")
.attr("class", "titles")
.attr("font-family", "sans-serif")
.attr("font-size", "150%");
diagram= svg.append("g")
.attr("class", "sankey")
// .attr("transform", "translate(" + marginleft + "," + margintop + ")");
linkGroup = diagram.append("g")
.attr("class", "links")
.attr("fill", "none");
//.attr("stroke", "#000")
//.attr("stroke-opacity", 0.2);
//set attributes for all nodes
nodeGroup = diagram.append("g")
.attr("class", "nodes")
.attr("font-family", "sans-serif")
.attr("font-size", 10);
}
function calculateLinks() {
if(target == 0)
{
target = 1;
sankeyLinks = [{source:0, target:1, value:5},{source:0, target:2, value:10},{source:0, target:3, value:15}];
}
else
{
target = 0;
sankeyLinks = [{source:0, target:2, value:15},{source:0, target:1, value:20},{source:0, target:3, value:10}];
}
}
function updateSankey() {
calculateLinks();
sankeyData.links = sankeyLinks;
sankeyData.nodes = [{name: "first"}, {name:"second"}, {name:"third"}, {name: "fourth"}];
sankey(sankeyData);
var links = linkGroup.selectAll('path')
.data(sankeyData.links);
//Set attributes for each link separately
links.enter().append("g")
.attr("id",function (d,i) {return "path"+i;})
.append("path")
.attr("stroke", "#000")
.attr("stroke-opacity", 0.15)
.attr("d", d3.sankeyLinkHorizontal())
.attr("stroke-width", function (d) {return Math.max(1, d.width); })
.on("mouseover",function (d,id) {
var pathGroup = svg.select('#path' + id);
var path = pathGroup.select("path");
path.attr("stroke","url(#grad"+id+")")
.attr("stroke-opacity","0.95");
})
.on("mouseout",function (d, id) {
pathGroup = svg.select('#path' + id);
var path = pathGroup.select("path");
path.attr("stroke","#000")
.attr("stroke-opacity","0.15");
})
.append("title")
.text(function (d) {
//tooltip info for the links
return d.source.name + " → " + d.target.name + "\n" + format(d.value); });
var pathGradient = svg.select(".links")
.selectAll("g")
.append("defs")
.append("linearGradient")
.attr("id",function (d, id) {
return "grad" + id;
})
//.attr("from", function () {return this.parentElement.parentElement.childNodes[0].getAttribute("from");})
//.attr("to", function () {return this.parentElement.parentElement.childNodes[0].getAttribute("to");})
.attr("gradientUnit","userSpaceOnUse")
.attr("style","mix-blend-mode: multiply;")
.attr("x1","0%")
.attr("x2","100%")
.attr("y1","0%")
.attr("y2","0%");
pathGradient.append("stop")
.attr("class","from")
.attr("offset","0%")
.attr("style", function (d) {
var color = setColor(d.source);
return "stop-color:" + color + ";stop-opacity:1";
});
pathGradient.append("stop")
.attr("class","to")
.attr("offset","100%")
.attr("style",function (d) {
var color = setColor(d.target);
return "stop-color:" + color + ";stop-opacity:1";
});
links.transition(t)
.attr("d", d3.sankeyLinkHorizontal())
.attr("stroke-width", function (d) { return Math.max(1, d.width); })
.select('title')
.text(function (d) {
//same argumentation as above, we need the method again for the transition
return d.source.name + " → " + d.target.name + "\n" + format(d.value); });
links.exit().remove();
var nodes = nodeGroup.selectAll('.node')
.data(sankeyData.nodes);
var nodesEnter = nodes.enter()
.append("g")
.attr('class', 'node');
//set attributes for each node separately
nodesEnter.append("rect")
.attr("x", function (d) { return d.x0; })
.attr("y", function (d) { return d.y0; })
.attr("height", function (d) { return d.y1 - d.y0; })
.attr("width", function (d) {
var width = d.x1 - d.x0;
return width;
})
.attr("fill", setColor)
.attr("stroke", "#000")
.attr("fill-opacity", 0.5)
//specify Pop-Up when hovering over node
nodesEnter.append("title")
.text(function (d) { return d.name + "\n" + format(d.value); });
//Update selection
var nodesUpdate = nodes.transition(t);
//same as the links we have to state the methods again in the update
nodesUpdate.select("rect")
.attr("y", function (d) { return d.y0; })
.attr("x", function (d) { return d.x0; })
.attr("height", function (d) { return d.y1 - d.y0; });
nodesUpdate.select("title")
.text(function (d) { return d.name + "\n" + format(d.value); });
//Exit selection
nodes.exit().remove();
}
function setColor(d) {
switch (d.name) {
case "first":
return "#f00";
case "second":
return "#ff0";
case "third":
return "#f0f";
case "fourth":
return "#0ff";
default:
return "#0f0";
}
}
</script>
</body>
</html>
After clicking the button once, the path from the red to the purple node has a linear gradient from red to yellow, even though I'd like it to go from red to purple.
I already realised, that I could write e.g. .iterations(15) instead of .iterations(0) in initSankey() to fix this. In the actual project I can't do this since I have to force the order of the nodes.
I hope I am clear enough in my explanation, if not, feel free to ask.
I would be extremely pleased if someone could tell me how to fix this problem.
PS. in this snippet the link on top disappears on hover, I have fixed this in the real project, here it's not a big deal.
Your issue was that the gradient urls were based on i, which could be different for a particular link with each update (ie, the order of the links may be different, so have a different i value); and data updates were not based on a constant unique id for a link.
In the snippet, I've added a unique name value for the links in the calculateLinks function, which is then used for the data joins and creating the def gradients, which means they remain constant with each update.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="https://unpkg.com/d3-sankey"></script>
</head>
<body>
<svg id="diagram" height="150" width="600"></svg>
<button onclick="updateSankey()">Click Me!</button>
<style>
#diagram{
border: 1px solid black;
}
</style>
<script>
var target = 0;
var sankeyLinks;
var sankeyData = {nodes:[], links:[]};
calculateLinks();
initSankey();
updateSankey();
function initSankey() {
svg = d3.select("svg"),
width = +svg.attr("width"),
height = +svg.attr("height");
formatNumber = d3.format(",.0f"),
format = function (d) { return formatNumber(d) + " %"; },
sankey = d3.sankey()
.nodeWidth(15)
.nodePadding(10)
.size([width - 1, height - 6])
t = d3.transition()
.duration(1500)
.ease(d3.easeLinear);
//set attributes for all links
titleGroup = svg.append("g")
.attr("class", "titles")
.attr("font-family", "sans-serif")
.attr("font-size", "150%");
diagram= svg.append("g")
.attr("class", "sankey")
svg.append("defs")
linkGroup = diagram.append("g")
.attr("class", "links")
.attr("fill", "none");
//set attributes for all nodes
nodeGroup = diagram.append("g")
.attr("class", "nodes")
.attr("font-family", "sans-serif")
.attr("font-size", 10);
}
function calculateLinks() {
if(target == 0)
{
target = 1;
sankeyLinks = [
{name: "firstsecond",source:0, target:1, value:5},
{name: "firstthird",source:0, target:2, value:10},
{name: "firstfourth",source:0, target:3, value:15}];
}
else
{
target = 0;
sankeyLinks = [
{name: "firstthird", source:0, target:2, value:15},
{name: "firstsecond", source:0, target:1, value:20},
{name: "firstfourth", source:0, target:3, value:10}
];
}
}
function updateSankey() {
calculateLinks();
sankeyData.links = sankeyLinks;
sankeyData.nodes = [{name: "first"}, {name:"second"}, {name:"third"}, {name: "fourth"}];
sankey(sankeyData);
var pathGradient = svg.select("defs").selectAll("linearGradient")
.data(sankeyData.links, function(d){ return d.name })
.enter()
.append("linearGradient")
.attr("id",function (d) {
return "grad" + d.name;
})
.attr("gradientUnit","userSpaceOnUse")
.attr("x1","0%")
.attr("x2","100%")
.attr("y1","0%")
.attr("y2","0%");
pathGradient.append("stop")
.attr("class","from")
.attr("offset","0%")
.attr("style", function (d) {
var color = setColor(d.source);
return "stop-color:" + color;
});
pathGradient.append("stop")
.attr("class","to")
.attr("offset","100%")
.attr("style",function (d) {
var color = setColor(d.target);
return "stop-color:" + color;
});
var links = linkGroup.selectAll('path')
.data(sankeyData.links, function(d){ return d.name });
//Set attributes for each link separately
var linksenter = links.enter()
.append("g")
.attr("id",function (d) {return "path" + d.name;})
.append("path")
.style("stroke", "#000")
.style("stroke-opacity", 0.15)
.attr("stroke-width", function (d) {return Math.max(1, d.width); })
.on("mouseover",function (d) {
var pathGroup = svg.select('#path' + d.name);
var path = pathGroup.select("path");
path.style("stroke","url(#grad" + d.name + ")")
.style("stroke-opacity","0.95");
})
.on("mouseout",function (d, id) {
pathGroup = svg.select('#path' + d.source.name + d.target.name);
var path = pathGroup.select("path");
path.style("stroke","#000")
.style("stroke-opacity","0.15");
})
linksenter.merge(links).attr("d", d3.sankeyLinkHorizontal())
links.transition(t)
.attr("d", d3.sankeyLinkHorizontal())
.attr("stroke-width", function (d) { return Math.max(1, d.width); })
.select('title')
.text(function (d) {
return d.source.name + " → " + d.target.name + "\n" + format(d.value); });
var nodes = nodeGroup.selectAll('.node')
.data(sankeyData.nodes, function(d){ return d.name });
var nodesEnter = nodes.enter()
.append("g")
.attr('class', 'node');
nodesEnter.append("rect")
.attr("x", function (d) { return d.x0; })
.attr("y", function (d) { return d.y0; })
.attr("height", function (d) { return d.y1 - d.y0; })
.attr("width", function (d) {
var width = d.x1 - d.x0;
return width;
})
.attr("fill", setColor)
.attr("stroke", "#000")
.attr("fill-opacity", 0.5)
//specify Pop-Up when hovering over node
nodesEnter.append("title")
.text(function (d) { return d.name + "\n" + format(d.value); });
//Update selection
var nodesUpdate = nodes.transition(t);
//same as the links we have to state the methods again in the update
nodesUpdate.select("rect")
.attr("y", function (d) { return d.y0; })
.attr("x", function (d) { return d.x0; })
.attr("height", function (d) { return d.y1 - d.y0; });
nodesUpdate.select("title")
.text(function (d) { return d.name + "\n" + format(d.value); });
}
function setColor(d) {
switch (d.name) {
case "first":
return "#f00";
case "second":
return "#ff0";
case "third":
return "#f0f";
case "fourth":
return "#0ff";
default:
return "#0f0";
}
}
</script>
</body>
</html>

How to auto-sort a bar-chart with a toggle function

I've uploaded a block (FIXED) where you can toggle a sorting function.
What I want to add now is some kind of if statement when the checkbox is on, and when it is on I want the bars to sort automatically when you change year or category, and when you toggle it again it stops auto-sorting.
I thought a simple
if (document.getElementsByClassName('myCheckbox').checked) {
change();
}
Within the update function would work but nothing happens.
Any help is appreciated!
I started an answer your direct question, but soon realized that your code needed a bit of refactor. You had a bit too much copy/paste going on with redundant code and too many things drawing. When coding with d3 you should try for a single function that does all the drawing.
Here's the code running.
Here's a snippet of the new one update function to rule them all:
function update() {
file = d3.select('#year').property('value') == 'data2017' ? 'data.csv' : 'data2.csv';
catInt = d3.select('#category').property('value');
d3.csv(file, type, function(error,data) {
if(error) throw error;
var sortIndex = data.map(function(d){ return d.month});
// Update domain
y.domain([0, d3.max(data, function(d) {
return d["Category" + catInt]; })
]).nice();
// Update axis
g.selectAll(".axis.axis--y").transition()
.duration(750)
.call(yAxis);
g.selectAll(".axis.grid--y").transition()
.duration(750)
.call(yGrid);
// Sums and averages
let sumOfAll = d3.sum(data, function(d) {
return d["Category" + catInt];
});
let avgValue = d3.sum(data, function(d) {
return d["Category" + catInt];
}) / data.length;
//sort data
data.sort( d3.select("#myCheckbox").property("checked")
? function(a, b) { return b["Category" + catInt] - a["Category" + catInt]; }
: function(a, b) { return sortIndex.indexOf(a.month) - sortIndex.indexOf(b.month);})
// set x domain
x.domain(data.map(function(d) { return d.month; }));
g.selectAll(".axis.axis--x").transition()
.duration(750)
.call(xAxis);
// Update rectangles
let bars = g.selectAll(".barEnter")
.data(data, function(d){
return d.month;
});
bars = bars
.enter()
.append("rect")
.attr("class", "barEnter") // Enter data reference
.attr("width", x.bandwidth())
.merge(bars);
bars.transition()
.duration(750)
.attr("height", function(d) {
return height - y(d["Category" + catInt]);
})
.attr("x", function(d) {
return x(d.month);
})
.attr("y", function(d) {
return y(d["Category" + catInt]);
});
bars.exit().remove();
// Update text on rectangles
let textUpdate = g.selectAll(".textEnter")
.data(data, function(d){
return d.month;
});
textUpdate = textUpdate.enter()
.append("text")
.style("text-shadow","1px 1px #777")
.attr("class", "textEnter") // Enter data reference
.attr("text-anchor","middle")
.attr("font-size",11)
.attr("fill","#fff")
.merge(textUpdate);
textUpdate.transition()
.duration(750)
.attr("y", function(d) {
return y(d["Category" + catInt]) + 15;
})
// Update text value
.text( function(d) {
return d["Category" + catInt];
})
.attr("x", function(d) {
return x(d.month) + x.bandwidth()/2;
})
// Update sum and avg value
g.selectAll("#totalValue").transition()
.duration(750)
.text(sumOfAll + " Category " + catInt)
g.selectAll("#avgValue").transition()
.duration(750)
.text(formatValue(avgValue))
});
}

D3 Sankey Diagram: Adding New Node and Link with Transition

I am creating a sankey diagram using D3. I am trying to redraw the diagram with additional node and link and using transition to animate the previous diagram to the new diagram. I was able to add in new node and link but the old nodes and links did not change position. Since the new node and link could be added at any place within the diagram, I do not want to clear and redraw the entire svg, but use transition to get from the old diagram to the new one. The code to draw the sankey diagram is this:
function draw(data){
// Set the sankey diagram properties
var sankey = d3sankey()
.nodeWidth(17)
.nodePadding(27)
.size([width, height]);
var path = sankey.link();
var graph = data;
sankey.nodes(graph.nodes)
.links(graph.links)
.layout(32);
sankey.relayout();
// add in the links
link.selectAll(".link")
.data(graph.links)
.enter().append("path")
.attr("class", "link")
.attr("d", path)
.style("fill", "none")
.style("stroke", function(d){
return "grey";
})
.style("stroke-opacity", "0.4")
.on("mouseover", function() { d3.select(this).style("stroke-opacity", "0.7") } )
.on("mouseout", function() { d3.select(this).style("stroke-opacity", "0.4") } )
.style("stroke-width", function (d) {
return Math.max(1, d.dy);
})
.sort(function (a, b) {
return b.dy - a.dy;
});
link.transition().duration(750);
//link.exit();
// add in the nodes
var node = nodes.selectAll(".node")
.data(graph.nodes)
.enter().append("g")
.attr("class", "node")
.attr("transform", function (d) {
return "translate(" + d.x + "," + d.y + ")";
});
// 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("fill-opacity", ".9")
.style("shape-rendering", "crispEdges")
.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("text-shadow", "0 1px 0 #fff")
.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.transition().duration(750);
}
The JSFiddle
Is it possible to use transition to add in new node and link and reposition
old nodes and links?
Thanks!
I was able to do this by using moving the nodes and links to new position. The code for that is:
var nodes = d3.selectAll(".node")
.transition().duration(750)
.attr('opacity', 1.0)
.attr("transform", function (d) {
if(d.node == 3){
console.log(d.x, d.y);
}
return "translate(" + d.x + "," + d.y + ")";
});
var nodeRects = d3.selectAll(".node rect")
.attr("height", function (d) {
if(d.node == 3){
console.log(d.dy);
}
return d.dy;
})
var links = d3.selectAll(".link")
.transition().duration(750)
.attr('d', path)
.attr('opacity', 1.0)
Updated JSFiddle

updating/modifying d3 word cloud with new data

I'm trying to figure out how to modify and update from a selection of data with d3.js wordcloud.
Currently, I'm showing the top 10 results from a selection of data depending on indexed keys. I'd like to be able to switch this data depending on the keys, or if I want the top 10 or bottom 10 words.
here is a plnk so far;
http://plnkr.co/edit/cDTeGDaOoO5bXBZTHlhV?p=preview
I've been trying to refer to these guides, General Update Pattern, III and Animated d3 word cloud. However, I'm struggling to comprehend how to introduce a final update function, as almost all guides referring to this usually use a setTimeout to demonstrate how to update, and my brain just won't make the connection.
Any advice is most welcome!
Cheers,
(code here)
var width = 455;
var height = 310;
var fontScale = d3.scale.linear().range([0, 30]);
var fill = d3.scale.category20();
var svg = d3.select("#vis").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + (width / 2) + "," + (height / 2) + ")")
// .selectAll("text")
d3.json("data.json", function(error, data) {
if (error) {
console.log(error)
}
else {
data = data
}
function sortObject(obj) {
var newValue = [];
var orgS = "MC";
var dateS = "Jan";
for (var question = 0; question < data.questions.length; question++) {
var organization = data.organizations.indexOf(orgS);
var date = data.dates.indexOf(dateS);
newValue.push({
label: data.questions[question],
value: data.values[question][organization][date]
});
}
newValue.sort(function(a, b) {
return b.value - a.value;
});
newValue.splice(10, 50)
return newValue;
}
var newValue = sortObject();
fontScale.domain([
d3.min(newValue, function(d) {
return d.value
}),
d3.max(newValue, function(d) {
return d.value
}),
]);
d3.layout.cloud().size([width, height])
.words(newValue)
.rotate(0)
.text(function(d) {
return d.label;
})
.font("Impact")
.fontSize(function(d) {
return fontScale(d.value)
})
.on("end", draw)
.start();
function draw(words) {
var selectVis = svg.selectAll("text")
.data(words)
selectVis
.enter().append("text")
.style("font-size", function(d) {
return fontScale(d.value)
})
.style("font-family", "Impact")
.style("fill", function(d, i) {
return fill(i);
})
.attr("text-anchor", "middle")
.attr("transform", function(d) {
return "translate(" + [d.x, d.y] + ")rotate(" + d.rotate + ")";
})
.text(function(d) {
return d.label;
})
selectVis
.transition()
.duration(600)
.style("font-size", function(d) {
return fontScale(d.value)
})
.attr("transform", function(d) {
return "translate(" + [d.x, d.y] + ")rotate(" + d.rotate + ")";
})
.style("fill-opacity", 1);
selectVis.exit()
.transition()
.duration(200)
.style('fill-opacity', 1e-6)
.attr('font-size', 1)
.remove();
}
});
I did not see any update function within your code so I added that functionality in order to watch how the update works.
// Add a select elemnt to the page
var dropDown = d3.select("#drop")
.append("select")
.attr("name", "food-venues");
// Join with your venues
var foodVenues = data.organizations.map(function(d, i) {
return d;
})
// Append the venues as options
var options = dropDown.selectAll("option")
.data(foodVenues)
.enter()
.append("option")
.text(function(d) {
return d;
})
.attr("value", function(d) {
return d;
})
// On change call the update function
dropDown.on("change", update);
In order for a d3 word cloud to update correctly you need to calculate again the layout with the desired data
function update() {
// Using your function and the value of the venue to filter data
var filteredData = sortObject(data, this.value);
// Calculate the new domain with the new values
fontScale.domain([
d3.min(newValue, function(d) {
return d.value
}),
d3.max(newValue, function(d) {
return d.value
}),
]);
// Calculate the layout with new values
d3.layout.cloud()
.size([width, height])
.words(filteredData)
.rotate(0)
.text(function(d) {
return d.label;
})
.font("Impact")
.fontSize(function(d) {
return fontScale(d.value)
})
.on("end", draw)
.start();
}
I modified your sortObject function to receive an extra parameter which is the desired venue:
function sortObject(obj, venue) {
var newValue = [];
var orgS = venue || "MC";
// ....
}
Here is the working plnkr: http://plnkr.co/edit/B20h2bNRkyTtfs4SxE0v?p=preview
You should be able to use this approach to update with your desired restrictions. You may be able to add a checkbox with a event listener that will trigger the update function.
In your html:
<input checked type="checkbox" id="top" value="true"> <label for="top">Show top words</label>
In your javascript:
var topCheckbox = d3.select('#top')
.on("change", function() {
console.log('update!')
});

D3js force duplicate nodes on enter()

I am having some issues with d3js and I can't figure out what is going on. The idea is to draw initial graph from some endpoint data (first img), that's fine works well. Each node is clickable, on click ajax call is made for that node and data is returned, based on some criteria at that point nodes.push(xx), links.push(xx) happens to add new nodes and restart() is called to draw new nodes and links. The issue is that the main graph is doing the correct thing (Not showed on screenshots as I had to put fake data on the first graph i.e. calling an endpoint /record/id/first doesn't return a data) but there are bunch of random nodes showing up in the right bottom corner.
You can also see on the example below, even if the data doesn't change after clicking on first/second/third something wrong goes with node.enter() after restart() with the same data passed in...
JS FIDDLE: http://jsfiddle.net/5754j86e/
var w = 1200,
h = 1200;
var nodes = [];
var links = [];
var node;
var link;
var texts;
var ids = [];
var circleWidth = 10;
var initialIdentifier = "marcin";
nodes = initialBuildNodes(initialIdentifier, sparql);
links = initialBuildLinks(sparql);
//Add SVG
var svg = d3.select('#chart').append('svg')
.attr('width', w)
.attr('height', h);
var linkGroup = svg.append("svg:g").attr("id", "link-group");
var nodeGroup = svg.append("svg:g").attr("id", "node-group");
var textGroup = svg.append("svg:g").attr("id", "text-group");
//Add Force Layout
var force = d3.layout.force()
.size([w, h])
.gravity(.05)
.charge(-1040);
force.linkDistance(120);
restart();
function restart() {
force.links(links)
console.log("LINKS ARE: ", links)
link = linkGroup.selectAll(".link").data (links);
link.enter().append('line')
.attr("class", "link");
link.exit().remove();
force.nodes(nodes)
console.log("NODES ARE: ", nodes)
node = nodeGroup.selectAll(".node").data (nodes);
node.enter().append("svg:g")
.attr("class", "node")
.call(force.drag);
node.append('circle')
.attr('cx', function(d) { return d.x; })
.attr('cy', function(d) { return d.y; })
.attr('r', circleWidth )
.attr('fill', function(d, i) {
if (i>0) { return palette.pink }
else { return palette.blue }
})
.on("click", function(d) {
nodeClicked (d);
})
.on('mouseenter', function(d){
nodeMouseEnter(d)
})
.on('mouseout', function(d){
nodeMouseOut(d)
});
node.exit().remove();
var annotation = textGroup.selectAll(".annotation").data (nodes);
annotation.enter().append("svg:g")
.attr("class", "annotation")
.append("text")
.attr("x", function(d) { return d.radius + 4 })
.attr("y", ".31em")
.attr("class", "label")
.text(function(d) { return d.name; });
annotation.exit().remove();
force.start();
}
function nodeClicked (d) {
// AJAX CALL happens here and bunch of nodes.push({name: "new name"}) happen
}
force.on('tick', function(e) {
link
.attr('x1', function(d) { return d.source.x })
.attr('y1', function(d) { return d.source.y })
.attr('x2', function(d) { return d.target.x })
.attr('y2', function(d) { return d.target.y })
node.attr('transform', function(d, i) {
return 'translate('+ d.x +', '+ d.y +')';
})
svg.selectAll(".annotation").attr("transform", function(d) {
var labelx = d.x + 13;
return "translate(" + labelx + "," + d.y + ")";
})
});
Okay I got it, based on the docs (https://github.com/mbostock/d3/wiki/Selections#enter):
var update_sel = svg.selectAll("circle").data(data)
update_sel.attr(/* operate on old elements only */)
update_sel.enter().append("circle").attr(/* operate on new elements only */)
update_sel.attr(/* operate on old and new elements */)
update_sel.exit().remove() /* complete the enter-update-exit pattern */
From my code you can see I do enter() and then once again I add circle on node in a separate statement.
node = nodeGroup.selectAll(".node").data (nodes);
node.enter().append("svg:g")
.attr("class", "node")
.call(force.drag);
node.append('circle')
.attr('cx', function(d) { return d.x; })
.attr('cy', function(d) { return d.y; })
.attr('r', circleWidth )
.attr('fill', function(d, i) {
if (i>0) { return palette.pink }
else { return palette.blue }
});
Adding circle should be within the scope of enter() otherwise it happens to all nodes not only the new nodes therefore it should be :
node.enter().append("svg:g")
.attr("class", "node")
.call(force.drag)
.append('circle')
.attr('cx', function(d) { return d.x; })
.attr('cy', function(d) { return d.y; })
.attr('r', circleWidth )
.attr('fill', function(d, i) {
if (i>0) { return palette.pink }
else { return palette.blue }
});

Categories

Resources