Creating array from html to display using D3 Wordcloud - javascript

I'm afraid this is a very basic Javascript question on building an array from HTML elements. I've had a search for answers, but I think there's important fundamentals that aren't clicking for me and I'm going in circles, so some outside, expert perspective would be appreciated!
We're using Moodle to run an online course, and in the introduction week, we want to collect some information on users with a database module, such as country, and then show them the group responses.
Moodle give a template with a placeholder [[data]], which you can wrap in html, in this case a div:
<div class="country">[[country]]</div>
The issue I'm having is getting the content of all these divs into an array. I found this method explained here, but my attempts to push/concat didn't seem to work:
var countryList = document.getElementsByClassName("country");
Array.prototype.forEach.call(countryList, function() {});
The reason I want it in an array is so I can push the content into an existing template using Jason Davies's d3.js powered word cloud. Here's a link to GitHub, and the very basic code for a simple cloud is copied below for reference.
Essentially I want to be able to make an array from HTML elements and combine it with the words array used below:
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;
});
}

You should be able to get the array you want using something like:
var countryList = document.querySelectorAll('.country');
var countryNames = Array.prototype.map.call(countryList, function(c){return c.textContent});

Related

Word cloud not displaying correctly with d3.v4

I'm trying out this example of an animated word cloud, using jasondavies's d3.layout.cloud.
It works with d3 version 3, but if you update it with any version above it won't display correctly. You can still see the transition of some words but the others remain in the middle with no scaling.
I'm putting the code here as reference.
<!DOCTYPE html>
<meta charset="utf-8">
<body>
<!--script src="http://d3js.org/d3.v3.min.js"></script-->
<script src="https://d3js.org/d3.v4.js"></script>
<script src="https://rawgit.com/jasondavies/d3-cloud/master/build/d3.layout.cloud.js"></script>
<script>
//Simple animated example of d3-cloud - https://github.com/jasondavies/d3-cloud
//Based on https://github.com/jasondavies/d3-cloud/blob/master/examples/simple.html
// Encapsulate the word cloud functionality
function wordCloud(selector) {
//var fill = d3.scale.category20();
//Construct the word cloud's SVG element
var svg = d3.select(selector).append("svg")
.attr("width", 500)
.attr("height", 500)
.append("g")
.attr("transform", "translate(250,250)");
//Draw the word cloud
function draw(words) {
var cloud = svg.selectAll("g text")
.data(words, function(d) { return d.text; })
//Entering words
cloud.enter()
.append("text")
.style("font-family", "Impact")
.style("fill", "black")
.attr("text-anchor", "middle")
.attr('font-size', 1)
.text(function(d) { return d.text; });
//Entering and existing words
cloud
.transition()
.duration(600)
.style("font-size", function(d) { return d.size + "px"; })
.attr("transform", function(d) {
return "translate(" + [d.x, d.y] + ")rotate(" + d.rotate + ")";
})
.style("fill-opacity", 1);
//Exiting words
cloud.exit()
.transition()
.duration(200)
.style('fill-opacity', 1e-6)
.attr('font-size', 1)
.remove();
}
//Use the module pattern to encapsulate the visualisation code. We'll
// expose only the parts that need to be public.
return {
//Recompute the word cloud for a new set of words. This method will
// asycnhronously call draw when the layout has been computed.
//The outside world will need to call this function, so make it part
// of the wordCloud return value.
update: function(words) {
d3.layout.cloud().size([500, 500])
.words(words)
.padding(5)
.rotate(function() { return ~~(Math.random() * 2) * 90; })
.font("Impact")
.fontSize(function(d) { return d.size; })
.on("end", draw)
.start();
}
}
}
//Some sample data - http://en.wikiquote.org/wiki/Opening_lines
var words = [
"You don't know about me without you have read a book called The Adventures of Tom Sawyer but that ain't no matter.",
"The boy with fair hair lowered himself down the last few feet of rock and began to pick his way toward the lagoon.",
"When Mr. Bilbo Baggins of Bag End announced that he would shortly be celebrating his eleventy-first birthday with a party of special magnificence, there was much talk and excitement in Hobbiton.",
"It was inevitable: the scent of bitter almonds always reminded him of the fate of unrequited love."
]
//Prepare one of the sample sentences by removing punctuation,
// creating an array of words and computing a random size attribute.
function getWords(i) {
return words[i]
.replace(/[!\.,:;\?]/g, '')
.split(' ')
.map(function(d) {
return {text: d, size: 10 + Math.random() * 60};
})
}
//This method tells the word cloud to redraw with a new set of words.
//In reality the new words would probably come from a server request,
// user input or some other source.
function showNewWords(vis, i) {
i = i || 0;
vis.update(getWords(i ++ % words.length))
setTimeout(function() { showNewWords(vis, i + 1)}, 2000)
}
//Create a new instance of the word cloud visualisation.
var myWordCloud = wordCloud('body');
//Start cycling through the demo data
showNewWords(myWordCloud);
</script>
Note: I edited the fill color from the example to be black. If you want to use the original color palette in d3 v4 or above, you need to use var fill = d3.scaleOrdinal(d3.schemeCategory10)
Ok so I did some digging, they basically merged the enter and update function into one using merge starting from version v4.
So for the above to work, you have to replace
//Entering words
cloud.enter()
.append("text")
.style("font-family", "Impact")
.style("fill", "black")
.attr("text-anchor", "middle")
.attr('font-size', 1)
.text(function(d) { return d.text; });
//Entering and existing words
cloud
.transition()
.duration(600)
.style("font-size", function(d) { return d.size + "px"; })
.attr("transform", function(d) {
return "translate(" + [d.x, d.y] + ")rotate(" + d.rotate + ")";
})
.style("fill-opacity", 1);
with
//Entering words
cloud.enter()
.append("text")
.style("font-family", "Impact")
.style("fill", "black")
.attr("text-anchor", "middle")
.attr('font-size', 1)
.text(function(d) { return d.text; })
//Entering and existing
.merge(cloud).transition()
.duration(600)
.style("font-size", function(d) { return d.size + "px"; })
.attr("transform", function(d) {
return "translate(" + [d.x, d.y] + ")rotate(" + d.rotate + ")";
})
.style("fill-opacity", 1);

Oval/circular shape of the Jason Davies D3 Word Cloud generator

I am using Jason Davies's sample word cloud generator code to create a word cloud. I am able to draw it but the shape of the word cloud I would like is oval/circular. I am aware that there have been questions regarding the circular shape of Jason Davies's word cloud but they all were talking about making changes in the build code function place() and were also unanswered. I am wondering if there is a way to achieve the oval/circular shape modifying the following code:
var fill = d3.scale.category20();
var layout = cloud()
.size([500, 500])
.words([
"Hello", "world", "normally", "you", "want", "more", "words",
"than", "this"].map(function(d) {
return {text: d, size: 10 + Math.random() * 90, test: "haha"};
}))
.padding(5)
.rotate(function() { return ~~(Math.random() * 2) * 90; })
.font("Impact")
.fontSize(function(d) { return d.size; })
.on("end", draw);
layout.start();
function draw(words) {
d3.select("body").append("svg")
.attr("width", layout.size()[0])
.attr("height", layout.size()[1])
.append("g")
.attr("transform", "translate(" + layout.size()[0] / 2 + "," + layout.size()[1] / 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; });
}
In the readme of Jason Davies's Github, there is a mention of spiralfor word positioning and I am unable to understand since I am very new to the JavaScript. Does anyone has any suggestions? Thanks a lot.
Add the following: .spiral("archimedean")

Wordcloud update data/ working example of adding words to cloud?

I am still pretty new to d3.js and I am diving into a wordcloud example using the
d3-cloud repo : https://github.com/jasondavies/d3-cloud
The example which is in there works for me, I turned it into a function so I can call it when data updates:
wordCloud : function(parameters,elementid){
var p = JSON.parse(JSON.stringify(parameters));
var fill = d3.scale.category20();
if (d3.select(elementid).selectAll("svg")[0][0] == undefined){
var svg = d3.select(elementid).append("svg")
.attr("width", 500)
.attr("height", 500)
.append("g")
.attr("transform", "translate(300,300)");
}else var svg = d3.select(elementid).selectAll("svg")
.attr("width", 500)
.attr("height", 500)
.select("g")
.attr("transform", "translate(300,300)");
d3.layout.cloud().size([300, 300])
.words(p.data)
.padding(5)
.rotate(function(d) {return ~~(Math.random()) * p.cloud.maxrotation; })
.font("Impact")
.fontSize(function(d) { return d.size; })
.on("end", draw)
.start();
function draw(words) {
console.log(words)
console.log(words.length)
svg.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")
.text(function(d) {console.log("enter text " + d.text) ; return d.text; });
svg.selectAll("text")
.data(words).transition().duration(2000).attr("transform", function(d) {
return "translate(" + [d.x, d.y] + ")rotate(" + Math.random() * p.cloud.maxrotation + ")";
})
}
}
The code works for me.
element id = the elements you bind to
parameters = all parameters which i should be able to set, including data (parameters.data).
Except for the packaging the code wasn't altered much from the original:
https://github.com/jasondavies/d3-cloud/blob/master/examples/simple.html
However when I add a new word to the wordcloud (so when I update the data), the new word is not recognized. I have put log output on several places and apparently in the draw function the data is incorrect but before it is ok.
for example:
original: [{"text":"this","size":5},{"text":"is","size":10},{"text":"a","size":50},{"text":"sentence","size":15}]
(the code adds other properties but this is for simplicity of explanation)
I add: "testing" with a size of 5
correct would be
[{"text":"this","size":5},{"text":"is","size":10},{"text":"a","size":50},{"text":"sentence","size":15},{"text":"testing","size":5}]
but I get results like:
[{"text":"a","size":50},{"text":"testing","size":5},{"text":"this","size":5},{"text":"sentence","size":15}]
--> new word added , an older one removed (don't know why) and array was mixed up.
QUESTION:
Anybody have an idea what I am doing wrong?
or
Does anybody have a working example of a d3.js wordcloud which you can update with new words by means of lets say an input box?
I think u got the same problem with me. The size of all the words do not fit in your svg and d3.layout.cloud somehow remove the oversized word. Try to increase the width and height of your svg or decrease the size of your word. What I done is check whether the x,y,width and height of the word is it out of the box. If yes, decrease the size or increase the width and height. Correct me if i am wrong

Update words(size change or remove words) in D3 word cloud without changing their position?

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 });

Updating a layout.pack in d3.js

I am trying to wrap my mind around d3's pack layout (http://bl.ocks.org/4063530).
I have the basic layout working but I would like to update it with new data. i.e. collect new data, bind it to the current layout.pack and update accordingly (update/exit/enter).
My attempts are here (http://jsfiddle.net/emepyc/n4xk8/14/):
var bPack = function(vis) {
var pack = d3.layout.pack()
.size([400,400])
.value(function(d) {return d.time});
var node = vis.data([data])
.selectAll("g.node")
.data(pack.nodes)
.enter()
.append("g")
.attr("class", function(d) { return d.children ? "node" : "leaf node"; })
.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; });
node.append("circle")
.attr("r", function(d) { return d.r });
node.filter(function(d) { return !d.children; }).append("text")
.attr("text-anchor", "middle")
.attr("dy", ".3em")
.text(function(d) { return d.analysis_id });
bPack.update = function(new_data) {
console.log("UPDATE");
node
.data([new_data])
.selectAll("g.node")
.data(pack.nodes);
node
.transition()
.duration(1000)
.attr("class", function(d) { return d.children ? "node" : "leaf node" })
.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")" });
node.selectAll("circle")
.data(new_data)
.transition()
.duration(1000)
.attr("r", function(d) { return d.r; });
};
Specific questions...
How do I bind the data? (since the data is not complex structure and not an array of data)
How can new nodes/leafs be added to the layout? And old ones removed?
Pointers to a working example would be greatly appreciated.
Working example is here.
Basically, there is code for initial load, where all circles, tooltips, etc. are created and positioned in initial places. As well, the layout (pack) is created.
Than, on each button press, new data is loaded into pack, and the pack is recalculated. That crucial code is here:
Here you bind (load) now data into pack layout: (in my example its random data, of course you'll have your data from json or code or similar):
pack.value(function(d) { return 1 +
Math.floor(Math.random()*501); });
Here the new layout is calculated:
pack.nodes(data);
After that, elements are transitioned to new positions, and its attributes are changed as you determine.
I just want to stress that I don't use enter/update/exit pattern or transformations (that you might see in others solutions), since I believe this introduces unnecessary complexity for examples like this.
Here are some pics with transition in action:
Start:
Transition:
End:
I had the same problem recently, and came across the General Update Pattern tutorials as well. These did not serve my purpose. I had a few hundred DOM elements in a graph (ForceLayout), and I was receiving REST data back with properties for each individual node. Refreshing by rebinding data led to reconstruction of the entire graph, as you said in response to mg1075's suggestion. It tooks minutes to finish updating the DOM in my case.
I ended up assign unique ids to elements that need updating later, and I cherry picked them with JQuery. My entire graph setup uses D3, but then my updates don't. This feels bad, but it works just fine. Instead of taking minutes from destroying and recreating most of my DOM, it takes something like 3 seconds (leaving out timing for REST calls). I don't see a reason that something like property updates could not be made possible in D3.
Perhaps if Mike Bostock added a remove() or proper subselection function to the enter() selection, we could follow a pure D3 pattern to do updates. While figuring this out I was trying to bind a subset of data, the data with the new properties added, and then subselecting to get at elements that need updating, but it didn't work, due to the limited and specific nature of the enter() selection.
Of relevance, if you have not already reviewed:
http://bl.ocks.org/3808218 - General Update Pattern, I
http://bl.ocks.org/3808221 - General Update Pattern, II
http://bl.ocks.org/3808234 - General Update Pattern, III
This sample fiddle has no transitions, but here is at least one approach for updating the data.
http://jsfiddle.net/jmKH6/
// VISUALIZATION
var svg = d3.select("#kk")
.append("svg")
.attr("width", 500)
.attr("height", 600)
.attr("class", "pack");
var g = svg.append("g")
.attr("transform", "translate(2,2)");
var pack = d3.layout.pack()
.size([400,400])
.value(function(d) {return d.time});
function update(data) {
var nodeStringLenth = d3.selectAll("g.node").toString().length;
if ( nodeStringLenth > 0) {
d3.selectAll("g.node")
.remove();
}
var node = g.data([data]).selectAll("g.node")
.data(pack.nodes);
node.enter()
.append("g")
.attr("class", function(d) { return d.children ? "node" : "leaf node"; })
.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; });
node.append("circle")
.attr("r", function(d) { return d.r });
node.filter(function(d) { return !d.children; }).append("text")
.attr("text-anchor", "middle")
.attr("dy", ".3em")
.text(function(d) { return d.analysis_id });
node
.exit()
.remove();
}
var myData = [data1, data2, data3];
update(data1);
setInterval(function() {
update( myData[Math.floor(Math.random() * myData.length)] ); // http://stackoverflow.com/questions/4550505/getting-random-value-from-an-array?lq=1
}, 1500);

Categories

Resources