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.
Related
I am building a tree map with D3 v4 and all good so far. However, some of the text within their respective rectangles goes out over the edge of the rectangle. I want to use text slice to cut off the text if it does this, and instead put in three dots.
As a test, I have been able to get the slice function to truncate text that goes beyond let's say 5 characters, but when I try to specify that I want the slice function to truncate based on the width of the corresponding rectangle, it doesn't work on all except one (which I think is because it goes out over the edge of the whole tree map.
I can't seem to find a way to pull in the width of the rectangles to the slice function in order to compare it to the width of the text.
// set the dimensions and margins of the graph
var margin = {top: 10, right: 10, bottom: 10, left: 10},
width = 945 - margin.left - margin.right,
height = 1145 - margin.top - margin.bottom;
// append the svg object to the body of the page
var svg = d3.select("#my_dataviz")
.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 + ")");
// Read data
d3.csv('https://raw.githubusercontent.com/rootseire/survey/main/treemap-data.csv', function(data) {
// stratify the data: reformatting for d3.js
var root = d3.stratify()
.id(function(d) {
return d.name; }) // Name of the entity (column name is name in csv)
.parentId(function(d) { return d.parent; }) // Name of the parent (column name is parent in csv)
(data);
root.sum(function(d) { return +d.value }) // Compute the numeric value for each entity
// Then d3.treemap computes the position of each element of the hierarchy
// The coordinates are added to the root object above
d3.treemap()
.size([width, height])
.padding(4)
(root)
// use this information to add rectangles:
svg
.selectAll("rect")
.data(root.leaves())
.enter()
.append("rect")
.attr('x', function (d) { return d.x0; })
.attr('y', function (d) { return d.y0; })
.attr('width', function (d) { return d.x1 - d.x0; })
.attr('height', function (d) { return d.y1 - d.y0; })
.style("stroke", "black")
.style("fill", "#94C162")
.attr("class", "label")
.on("mouseover", function(d) {
tip.style("opacity", 1)
.html("Genre: " + d.data.name + "<br/> Number: " + d.value + "<br/>")
.style("left", (d3.event.pageX-25) + "px")
.style("top", (d3.event.pageY-25) + "px")
})
.on("mouseout", function(d) {
tip.style("opacity", 0)
});
svg
.selectAll("text")
.data(root.leaves())
.enter()
.append("text")
.attr("x", function(d){ return d.x0+6}) // +10 to adjust position (more right)
.attr("y", function(d){ return d.y0+15}) // +20 to adjust position (lower)
.attr('dy', 0) // here
.text(function(d){ return d.data.name + ' (' + d.data.value +')' })
.attr("font-size", "15px")
.attr("fill", "black")
.each(slice);
})
// Define the div for the tooltip
var tip = d3.select("#my_dataviz").append("div")
.attr("class", "tooltip")
.style("opacity", 0)
// Add events to circles
d3.selectAll('.label')
.attr("x", function(t) {
return Math.max(0, 100-this.textLength.baseVal.value);
});
function slice(d) {
var self = d3.select(this),
textLength = self.node().getComputedTextLength(),
text = self.text();
while (textLength > text.getBoundingClientRect().width && text.length > 0) {
text = text.slice(0, 5);
self.text(text + '...');
textLength = self.node().getComputedTextLength();
}
}
.tooltip {
position: absolute;
pointer-events: none;
background: #000;
color: #fff;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.13.0/d3.min.js"></script>
<!DOCTYPE html>
<head>
<script type="text/javascript" src="https://raw.githubusercontent.com/rootseire/survey/main/word_wrap.js"></script>
</head>
<meta charset="utf-8">
<body>
<div id="my_dataviz"></div>
</body>
</html>
Any help greatly appreciated.
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 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";
});
I am using the following example as base and want to make it dynamic word cloud https://github.com/jasondavies/d3-cloud
define(['scripts/d3.v3', 'scripts/elasticsearch'], function (d3, elasticsearch) {
"use strict";
var client = new elasticsearch.Client();
client.search({
index: 'nfl',
size: 5,
body: {
// Begin query.
query: {
// Boolean query for matching and excluding items.
bool: {
must: { match: { "description": "TOUCHDOWN" }},
must_not: { match: { "qtr": 5 }}
}
},
// Aggregate on the results
aggs: {
touchdowns: {
terms: {
field: "qtr",
order: { "_term" : "asc" }
}
}
}
// End query.
}
}).then(function (resp) {
console.log(resp);
// D3 code goes here.
var touchdowns = resp.aggregations.touchdowns.buckets;
// d3 donut chart
var width = 600,
height = 300,
radius = Math.min(width, height) / 2;
var color = ['#ff7f0e', '#d62728', '#2ca02c', '#1f77b4'];
var arc = d3.svg.arc()
.outerRadius(radius - 60)
.innerRadius(120);
var pie = d3.layout.pie()
.sort(null)
.value(function (d) { return d.doc_count; });
var svg = d3.select("#donut-chart").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width/1.4 + "," + height/2 + ")");
var g = svg.selectAll(".arc")
.data(pie(touchdowns))
.enter()
.append("g")
.attr("class", "arc");
g.append("path")
.attr("d", arc)
.style("fill", function (d, i) {
return color[i];
});
g.append("text")
.attr("transform", function (d) { return "translate(" + arc.centroid(d) + ")"; })
.attr("dy", ".35em")
.style("text-anchor", "middle")
.style("fill", "white")
.text(function (d) { return d.data.key; });
});
this is example code from elasticsearch website how to use with d3
<!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([
"Hello", "world", "normally", "you", "want", "more", "words",
"than", "this"].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>
This is code from d3 jason davies about wordcloud
How to make d3 wordcloud to listen data from elasticsearch ?
You'll need to get the elasticsearch response data into a format that's easy to pass into the wordcloud sample code, something like this:
var data = [{
"text": "How",
"size": 20
}, {
"text": "to",
"size": 30
}, ... ]
See: http://jsfiddle.net/henbox/RUTpJ/659/
The response you get back from your Elasticsearch aggregation will look something like this:
You'll also see this in a console when the ES response is logged:
}).then(function (resp) {
console.log(resp);
...
So to manipulate that data, add:
var data = resp.aggregations.myaggregation.buckets.map(function(d) {
return {
text: d.key,
size: d.doc_count
};
});
Note that myaggregation is a name you will define. In your NFL example code above it's actually called touchdowns
Pushing this data straight into the wordcloud will cause problems, however. In the wordcloud example, the font-size is determined directly from the size, but there's a good chance your doc_counts are much too high, and will need to be scaled down.
For that, try D3 linear scale. In this case, it will scale the range of input values down to a value between 15 and 100, which can be used for font size:
var fontsize = d3.scale.linear()
.domain(d3.extent(data, function (d) {return d.size}))
.range([15, 100]);
Then, instead of
.style("font-size", function (d) {
return d.size + "px";
})
use:
.style("font-size", function (d) {
return fontsize(d.size) + "px";
})
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 });