I just got the word cloud template from T3. Now I added a youtube link to every word in the word cloud, but I want the video pop up while I clicked the words in the word cloud. How should I modify my code? Thanks a lot.
Here is my Javascript:
<script>
var fill = d3.scale.category20();
var words = [{"text":"Worry", "url":"http://google.com/"},
{"text":"Choices", "url":"http://bing.com/"},
]
var width = 1080;
var height = 500;
for (var i = 0; i < words.length; i++) {
words[i].size = 10 + Math.random() * 90;
}
d3.layout.cloud()
.size([width, height])
.words(words)
.padding(5)
.rotate(function() { return ~~ ((Math.random() * 6) - 3) * 30 + 8; })
.font("Impact")
.fontSize(function(d) { return d.size;})
.on("end", draw)
.start();
function draw(words) {
d3.select("#word-cloud")
.append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate("+ width/2 +","+ height/2 +")")
.selectAll("text")
.data(words)
.enter()
.append("text")
.style("font-size", function(d) { return d.size + "px"; })
.style("font-family", "Impact")
.style("fill", function(d, i) { return fill(i); })
.attr("text-anchor", "middle")
.attr("transform", function(d) {
return "translate(" + [d.x, d.y] + ")rotate(" + d.rotate + ")";
})
.text(function(d) { return d.text; })
.on("click", function (d, i){
window.open(d.url, "_blank");
});
}
My proposed approach:
Create a modal window
Open the modal when a word is clicked
Dynamically populate the embed code into the modal based on which word is clicked (I didn't do this part for you, but should be easily doable as it's part of the data to access).
I created a JSFiddle that gets you most of the way, with additional work needed here:
.on("click", function (d, i){
// Dynamically populate embed
// Open the modal
document.getElementById('myModal').style.display = "block";
});
Related
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;
}
}
I want to highlight a new array of words like "salmon" & "prey" that I want to provide to my word cloud, so how should I do it because I tried to use mark.js or Javascript with CSS but couldn't succeed, but now I think it is only possible here when I am drawing the word cloud. So can someone help me to provide me with a function or maybe some changes in my code to highlight the array (arrayToBeHighlight) of words:
var width = 750, height = 500;
var words = [["whales", 79], ["salmon", 56], ["Chinook", 30], ["book", 70],
["prey", 51]].map(function(d) {
return {text: d[0], size: d[1]};
});
var arrayToBeHighlight = [ ["salmon", 56], ["prey", 51] ];
**OR**
var arrayToBeHighlight = ["salmon", "prey"];
maxSize = d3.max(words, function(d) { return d.size; });
minSize = d3.min(words, function(d) { return d.size; });
var fontScale = d3.scale.linear().domain([minSize, maxSize]).range([10,70]);
var fill = d3.scale.category20();
d3.layout.cloud().size([width, height]).words(words).font("Impact")
.fontSize(function(d) { return fontScale(d.size) })
.on("end", drawCloud).start();
function drawCloud(words) {
d3.select("#wordCloud").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + (width / 2) + "," + (height / 2) +")")
.selectAll("text")
.data(words)
.enter().append("text")
.style("font-size", function(d) { return d.size + "px"; })
.style("font-family", "Impact")
.style("fill", function(d, i) { return fill(i); })
.attr("text-anchor", "middle")
.attr("transform", function(d) {
return "translate(" + [d.x, d.y] + ")rotate(" + d.rotate + ")";
})
.text(function(d) { return d.text; });
}
HTML Code
<div style="margin-left:20px" id="wordCloud"></div>
Things like mark.js work by creating a span around the word and setting a background color to mimic a highlighter. This doesn't work in SVG because text elements don't have a background color. Instead, you can fake it by inserting a rect before the text element:
texts.filter(function(d){
return arrayToBeHighlight.indexOf(d.text) != -1;
})
.each(function(d){
var bbox = this.getBBox(),
trans = d3.select(this).attr('transform');
g.insert("rect", "text")
.attr("transform", trans)
.attr("x", -bbox.width/2)
.attr("y", bbox.y)
.attr("width", bbox.width)
.attr("height", bbox.height)
.style("fill", "yellow");
});
Running code;
<!DOCTYPE html>
<html>
<head>
<script src="https://d3js.org/d3.v3.min.js"></script>
<script src="https://rawgit.com/jasondavies/d3-cloud/master/build/d3.layout.cloud.js"></script>
</head>
<body>
<div style="margin-left:20px" id="wordCloud"></div>
<script>
var width = 750,
height = 500;
var words = [
["whales", 79],
["salmon", 56],
["Chinook", 30],
["book", 70],
["prey", 51]
].map(function(d) {
return {
text: d[0],
size: d[1]
};
});
var arrayToBeHighlight = ["salmon", "prey"];
maxSize = d3.max(words, function(d) {
return d.size;
});
minSize = d3.min(words, function(d) {
return d.size;
});
var fontScale = d3.scale.linear().domain([minSize, maxSize]).range([10, 70]);
var fill = d3.scale.category20();
d3.layout.cloud().size([width, height]).words(words).font("Impact")
.fontSize(function(d) {
return fontScale(d.size)
})
.on("end", drawCloud).start();
function drawCloud(words) {
var g = d3.select("#wordCloud").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + (width / 2) + "," + (height / 2) + ")");
var texts = g.selectAll("text")
.data(words)
.enter().append("text")
.style("font-size", function(d) {
return d.size + "px";
})
.style("font-family", "Impact")
.style("fill", function(d, i) {
return fill(i);
})
.attr("text-anchor", "middle")
.attr("transform", function(d) {
return "translate(" + [d.x, d.y] + ")rotate(" + d.rotate + ")";
})
.text(function(d) {
return d.text;
});
texts.filter(function(d){
return arrayToBeHighlight.indexOf(d.text) != -1;
})
.each(function(d){
var bbox = this.getBBox(),
trans = d3.select(this).attr('transform');
g.insert("rect", "text")
.attr("transform", trans)
.attr("x", -bbox.width/2)
.attr("y", bbox.y)
.attr("width", bbox.width)
.attr("height", bbox.height)
.style("fill", "yellow");
});
}
</script>
</body>
</html>
The answer depends on your definition of highlight and how you want to highlight them.
One possibility is comparing the arrayToBeHighlight array with the datum when painting the words. For instance, turning them red:
.style("fill", function(d, i) {
return arrayToBeHighlight.indexOf(d.text) > -1 ? "red" : fill(i);
})
Here is the bl.ocks: http://bl.ocks.org/anonymous/d38d1fbb5919c04783934d430fb895c2/b42582053b03b178bb155c2bbaec5242374d051b
I am using the word cloud library in D3 by Jason Davies. This is the normal code which I am using and works fine for creating the word clouds.
d3.layout.cloud().size([width, height])
.words(d3.zip(vis_words, vis_freq).map(function(d) {
return {text: d[0], size: wordScale(d[1]) };
}))
.padding(1)
.rotate(function() { return ~~(Math.random() * 2) * 0; })
.font("times")
.fontSize(function(d) { return d.size; })
.on("end", draw)
.start();
function draw(words) {
d3.select(curr_id).append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width/2 + "," + height/2 + ")")
.selectAll("text")
.data(words)
.enter()
.append("text")
.transition()
.delay(function(d,i){
return i*100;
})
.duration(1000)
.ease("elastic")
.style("font-size", function(d) { return d.size + "px"; })
.style("font-family", "times")
.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.text; });
}
I have a time slider to select a specific value, based on which words in the word cloud have different frequency(given by size) or some of the words are not there at all. I need to update without redrawing the entire word cloud which I am currently doing. In a way, I want to keep the position of words fixed and just updating their size and whether they exist based on the value selected on a slider?
Should I enter an update function in the function draw for this? Am certainly new to D3 and any help would be great?
To do this, you would select the existing text elements and set the font-size property for them. The code to do this looks like this:
d3.select("svg").selectAll("text")
.style("font-size", function(d, i) { // do something });
I am using D3 Cloud to build a word cloud. Here is the sample code:
<!DOCTYPE html>
<meta charset="utf-8">
<body>
<script src="../lib/d3/d3.js"></script>
<script src="../d3.layout.cloud.js"></script>
<script>
var fill = d3.scale.category20();
d3.layout.cloud().size([300, 300])
.words(["This", "is", "some", "random", "text"].map(function(d) {
return {text: d, size: 10 + Math.random() * 90};
}))
.padding(5)
.rotate(function() { return ~~(Math.random() * 2) * 90; })
.font("Impact")
.fontSize(function(d) { return d.size; })
.on("end", draw)
.start();
function draw(words) {
d3.select("body").append("svg")
.attr("width", 300)
.attr("height", 300)
.append("g")
.attr("transform", "translate(150,150)")
.selectAll("text")
.data(words)
.enter().append("text")
.style("font-size", function(d) { return d.size + "px"; })
.style("font-family", "Impact")
.style("fill", function(d, i) { return fill(i); })
.attr("text-anchor", "middle")
.attr("transform", function(d) {
return "translate(" + [d.x, d.y] + ")rotate(" + d.rotate + ")";
})
.text(function(d) { return d.text; });
}
</script>
I want to create a hyperlink on each of the words("This", "is", "some", "random", "text"). So in the word cloud, when I click on each of the words, it goes to the link.
1) How do I function on each word?
2) Bonus if you could tell me how to change the size of the cloud to 800*300 instead of 300*300. As I have tried to change it's size in line "d3.layout.cloud().size([300, 300])" but it doesn't help. The text goes out of the box.
Hope you understood my question.
Thanks.
To make the words clickable all you need to do is set an on.("click", function(...){...}) listener which opens a new tab. You can also add styling to the text to make it look like a link. Here is some code:
var words = [{"text":"This", "url":"http://google.com/"},
{"text":"is", "url":"http://bing.com/"},
{"text":"some", "url":"http://somewhere.com/"},
{"text":"random", "url":"http://random.org/"},
{"text":"text", "url":"http://text.com/"}]
for (var i = 0; i < words.length; i++) {
words[i].size = 10 + Math.random() * 90;
}
...
d3.layout.cloud()
...
.words(words)
...
.start();
function draw(words) {
...
d3.select("body")
.append("svg")
...
.enter()
.append("text")
...
.text(function(d) { return d.text; })
.on("click", function (d, i){
window.open(d.url, "_blank");
});
}
I changed the format to make the code more manageable.
To change the width and height of the image you need to change three values:
d3.layout.cloud()
.size([width, height])
d3.select("body")
.append("svg")
.attr("width", width)
.attr("height", height)
...
.attr("transform", "translate("+ width/2 +","+ height/2 +")")
These attributes should be controlled by two variables to to keep the code simple.
Here is a fiddle.
I have the following WordCloud.
The original Code is from Jason Davies D3-JavaScript-WordCloud Example.
https://github.com/jasondavies/d3-cloud/blob/master/examples/simple.html
Most of the Code below is from this really helpfull tutorial (in German):
http://www.advitum.de/blog/2012/04/tagcloud-mit-php-und-javascript-erstellen-word-cloud-d3/
Thank You Lars Ebert!
EDIT: Now I have learned a little bit more. I have cleared nonsensical code.
My goal is to center the first word from array horizontally.
The first word is now centered horizontally, but now there is a gap in the Cloud.
Just at the x,y point where the word would be without positioning.
My new question is: How do I remove this gap?
Thank You.
var wordcloud, size = [800, 800]; //Cloud Size
var fillColor = d3.scale.category20b();
function loaded() {
d3.layout.cloud()
.size(size)
.words(words)
.font("Impact")
.fontSize(function(d) { return d.size;})
.rotate(function() { return ~~(Math.random() * 2) * 90; })
.on("end", draw)
.start();
}
function draw(words) {
wordcloud = d3.select("body")
.append("svg")
.attr("width", size[0])
.attr("height", size[1])
.append("g")
.attr("transform", "translate(" + (size[0]/2) + "," + (size[1]/2) + ")");
wordcloud.selectAll("text")
.data(words)
.enter()
.append("text")
.style("font-size", function(d) { return d.size + "px"; })
.style("fill", function(d) { return fillColor(d.text.toLowerCase()); })
.attr("text-anchor", "middle")
//Edit
.attr("transform", function(d, i) {
if(i == 0){
return "translate(" + [0, 0] + ")rotate(" + 0 + ")"; //handle first element
}else{
return "translate(" + [d.x, d.y] + ")rotate(" + d.rotate + ")"; //handle the rest
}
})
//--------------
.text(function(d) { return d.text; });
}
Use a negative margin equal to one of the size calculations:
wordcloud.selectAll("text")
...
...
...
.style("margin-left", function(d) {return d.size + "px"})