Reputation: 4498
I created a d3.js word cloud, based on a csv.
In the csv I have three columns:
words.csv:
word count index
word1 457 239
word2 373 155
word3 76 -142
word4 345 127
word5 12 -206
word6 46 -172
I wrote the following code (with help from youtube and jason davies' world cloud package) and it works to create the word cloud, but I don't know how to change the color of the words based on the index.
<script>
var width = 1500,
height =400;
var wordScale = d3.scale.linear().range([10,100]);
var fill = d3.scale.category20c();
d3.csv("words.csv", function(data) {
var subjects = data
.map(function(d) {return {text: d.word, size: +d.count} })
.sort(function(a,b) {return d3.descending (a.size, b.size); })
.slice(0,100);
wordScale.domain([
d3.min(subjects, function(d) {return d.size; }),
d3.max(subjects, function(d) {return d.size; }),
]);
// var layout = cloud()
d3.layout.cloud().size([width, height])
.words(subjects)
.padding(1)
.rotate(function() { return ~~(Math.random() * 2) * 0; })
.font("Impact")
.fontSize(function(d) { return wordScale(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)+")")
//where the center is
.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 need to change this line in the code `var fill = d3.scale.category20c();`
The goal would be that all words above the average (so positive index) are green and all words below the average (negative index) are grey. If the transparency of the color/fill could be dependent on how much above or below the average the count is that would be amazing - but not necessary.
word count index color
word1 457 239 green
word2 373 155 green
word3 76 -142 grey
word4 345 127 green
word5 12 -206 grey
word6 46 -172 grey
Thank you!
UPDATE:
Tried the following based on Dan's answer but with index:
mycolor = d3.rgb("#00cc66");
var width = 1500,
height =400;
var wordScale = d3.scale.linear().range([10,90]);
var colorScale = d3.scale.linear().range([10,90]);
// var fill = d3.scale.category20c();
d3.csv("words.csv", function(data) {
var subjects = data
.map(function(d) {return {text: d.word, size: +d.count, clr: d.index } })
.sort(function(a,b) {return d3.descending (a.size, b.size); })
.slice(0,100);
wordScale.domain([
d3.min(subjects, function(d) {return d.size; }),
d3.max(subjects, function(d) {return d.size; }),
]);
colorScale.domain([
d3.min(subjects, function(d) {return d.clr; }),
d3.max(subjects, function(d) {return d.clr; }),
]);
// var layout = cloud()
d3.layout.cloud().size([width, height])
.words(subjects)
.padding(1)
.rotate(function() { return ~~(Math.random() * 2) * 0; })
.font("Impact")
.fontSize(function(d) { return wordScale(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)+")")
//where the center is
.selectAll("text")
.data(words)
.enter().append("text")
.style("fill", function(d) { return d.clr >= 50 ? mycolor : "grey";})
.style("opacity", function(d) { return d.clr / 100 })
.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; });
}
Unfortunately, nothing is displaying when I run it (also no error message).
Upvotes: 1
Views: 1455
Reputation: 353
For your code above, i.e; assuming wordScale is between 10 and 100:
In the draw function, in the .append("text")
section of code,
Edit the fill function as follows. This will turn green / grey based on size being higher / lower than the mid point (55). Feel free to use better color values than the hardcoded ones below:
.style("fill", function(d) { return d.size >= 55 ? "green" : "grey";})
To fulfil your other request, add the following style immediately below the fill
. This will set the opacity relative to the size (between 0.1 and 1). Again, you can adjust these values as required.
.style("opacity", function(d) { return d.size / 100 })
If you make these edits, you can also drop the var fill
at the top of your code.
UPDATED ANSWER:
Note, I couldn't get Jason Davies' to load remotely without a script error, but no time to debug. Scroll down after his script for the actual answer. I also haven't added any rotation element to this.
!function(a){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=a();else if("function"==typeof define&&define.amd)define([],a);else{var b;b="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:this,b=b.d3||(b.d3={}),b=b.layout||(b.layout={}),b.cloud=a()}}(function(){var a;return function a(b,c,d){function e(g,h){if(!c[g]){if(!b[g]){var i="function"==typeof require&&require;if(!h&&i)return i(g,!0);if(f)return f(g,!0);var j=new Error("Cannot find module '"+g+"'");throw j.code="MODULE_NOT_FOUND",j}var k=c[g]={exports:{}};b[g][0].call(k.exports,function(a){var c=b[g][1][a];return e(c||a)},k,k.exports,a,b,c,d)}return c[g].exports}for(var f="function"==typeof require&&require,g=0;g<d.length;g++)e(d[g]);return e}({1:[function(a,b,c){function h(a){return a.text}function i(){return"serif"}function j(){return"normal"}function k(a){return Math.sqrt(a.value)}function l(){return 30*(~~(6*Math.random())-3)}function m(){return 1}function n(a,b,c,d){if(!b.sprite){var h=a.context,i=a.ratio;h.clearRect(0,0,(f<<5)/i,g/i);var j=0,k=0,l=0,m=c.length;for(--d;++d<m;){b=c[d],h.save(),h.font=b.style+" "+b.weight+" "+~~((b.size+1)/i)+"px "+b.font;var n=h.measureText(b.text+"m").width*i,o=b.size<<1;if(b.rotate){var p=Math.sin(b.rotate*e),q=Math.cos(b.rotate*e),r=n*q,s=n*p,t=o*q,u=o*p;n=Math.max(Math.abs(r+u),Math.abs(r-u))+31>>5<<5,o=~~Math.max(Math.abs(s+t),Math.abs(s-t))}else n=n+31>>5<<5;if(o>l&&(l=o),j+n>=f<<5&&(j=0,k+=l,l=0),k+o>=g)break;h.translate((j+(n>>1))/i,(k+(o>>1))/i),b.rotate&&h.rotate(b.rotate*e),h.fillText(b.text,0,0),b.padding&&(h.lineWidth=2*b.padding,h.strokeText(b.text,0,0)),h.restore(),b.width=n,b.height=o,b.xoff=j,b.yoff=k,b.x1=n>>1,b.y1=o>>1,b.x0=-b.x1,b.y0=-b.y1,b.hasText=!0,j+=n}for(var v=h.getImageData(0,0,(f<<5)/i,g/i).data,w=[];--d>=0;)if(b=c[d],b.hasText){for(var n=b.width,x=n>>5,o=b.y1-b.y0,y=0;y<o*x;y++)w[y]=0;if(null==(j=b.xoff))return;k=b.yoff;for(var z=0,A=-1,B=0;B<o;B++){for(var y=0;y<n;y++){var C=x*B+(y>>5),D=v[(k+B)*(f<<5)+(j+y)<<2]?1<<31-y%32:0;w[C]|=D,z|=D}z?A=B:(b.y0++,o--,B--,k++)}b.y1=b.y0+A,b.sprite=w.slice(0,(b.y1-b.y0)*x)}}}function o(a,b,c){c>>=5;for(var k,d=a.sprite,e=a.width>>5,f=a.x-(e<<4),g=127&f,h=32-g,i=a.y1-a.y0,j=(a.y+a.y0)*c+(f>>5),l=0;l<i;l++){k=0;for(var m=0;m<=e;m++)if((k<<h|(m<e?(k=d[l*e+m])>>>g:0))&b[j+m])return!0;j+=c}return!1}function p(a,b){var c=a[0],d=a[1];b.x+b.x0<c.x&&(c.x=b.x+b.x0),b.y+b.y0<c.y&&(c.y=b.y+b.y0),b.x+b.x1>d.x&&(d.x=b.x+b.x1),b.y+b.y1>d.y&&(d.y=b.y+b.y1)}function q(a,b){return a.x+a.x1>b[0].x&&a.x+a.x0<b[1].x&&a.y+a.y1>b[0].y&&a.y+a.y0<b[1].y}function r(a){var b=a[0]/a[1];return function(a){return[b*(a*=.1)*Math.cos(a),a*Math.sin(a)]}}function s(a){var b=4,c=b*a[0]/a[1],d=0,e=0;return function(a){var f=a<0?-1:1;switch(Math.sqrt(1+4*f*a)-f&3){case 0:d+=c;break;case 1:e+=b;break;case 2:d-=c;break;default:e-=b}return[d,e]}}function t(a){for(var b=[],c=-1;++c<a;)b[c]=0;return b}function u(){return document.createElement("canvas")}function v(a){return"function"==typeof a?a:function(){return a}}var d=a("d3-dispatch").dispatch,e=Math.PI/180,f=64,g=2048;b.exports=function(){function I(a){a.width=a.height=1;var b=Math.sqrt(a.getContext("2d").getImageData(0,0,1,1).data.length>>2);a.width=(f<<5)/b,a.height=g/b;var c=a.getContext("2d");return c.fillStyle=c.strokeStyle="red",c.textAlign="center",{context:c,ratio:b}}function J(b,c,d){for(var l,m,n,f=(a[0],a[1],c.x),g=c.y,h=Math.sqrt(a[0]*a[0]+a[1]*a[1]),i=A(a),j=F()<.5?1:-1,k=-j;(l=i(k+=j))&&(m=~~l[0],n=~~l[1],!(Math.min(Math.abs(m),Math.abs(n))>=h));)if(c.x=f+m,c.y=g+n,!(c.x+c.x0<0||c.y+c.y0<0||c.x+c.x1>a[0]||c.y+c.y1>a[1])&&(!d||!o(c,b,a[0]))&&(!d||q(c,d))){for(var y,p=c.sprite,r=c.width>>5,s=a[0]>>5,t=c.x-(r<<4),u=127&t,v=32-u,w=c.y1-c.y0,x=(c.y+c.y0)*s+(t>>5),z=0;z<w;z++){y=0;for(var B=0;B<=r;B++)b[x+B]|=y<<v|(B<r?(y=p[z*r+B])>>>u:0);x+=s}return delete c.sprite,!0}return!1}var a=[256,256],b=h,c=i,e=k,s=j,x=j,y=l,z=m,A=r,B=[],C=1/0,D=d("word","end"),E=null,F=Math.random,G={},H=u;return G.canvas=function(a){return arguments.length?(H=v(a),G):H},G.start=function(){function l(){for(var b=Date.now();Date.now()-b<C&&++i<h&&E;){var c=k[i];c.x=a[0]*(F()+.5)>>1,c.y=a[1]*(F()+.5)>>1,n(d,c,k,i),c.hasText&&J(f,c,g)&&(j.push(c),D.call("word",G,c),g?p(g,c):g=[{x:c.x+c.x0,y:c.y+c.y0},{x:c.x+c.x1,y:c.y+c.y1}],c.x-=a[0]>>1,c.y-=a[1]>>1)}i>=h&&(G.stop(),D.call("end",G,j,g))}var d=I(H()),f=t((a[0]>>5)*a[1]),g=null,h=B.length,i=-1,j=[],k=B.map(function(a,d){return a.text=b.call(this,a,d),a.font=c.call(this,a,d),a.style=s.call(this,a,d),a.weight=x.call(this,a,d),a.rotate=y.call(this,a,d),a.size=~~e.call(this,a,d),a.padding=z.call(this,a,d),a}).sort(function(a,b){return b.size-a.size});return E&&clearInterval(E),E=setInterval(l,0),l(),G},G.stop=function(){return E&&(clearInterval(E),E=null),G},G.timeInterval=function(a){return arguments.length?(C=null==a?1/0:a,G):C},G.words=function(a){return arguments.length?(B=a,G):B},G.size=function(b){return arguments.length?(a=[+b[0],+b[1]],G):a},G.font=function(a){return arguments.length?(c=v(a),G):c},G.fontStyle=function(a){return arguments.length?(s=v(a),G):s},G.fontWeight=function(a){return arguments.length?(x=v(a),G):x},G.rotate=function(a){return arguments.length?(y=v(a),G):y},G.text=function(a){return arguments.length?(b=v(a),G):b},G.spiral=function(a){return arguments.length?(A=w[a]||a,G):A},G.fontSize=function(a){return arguments.length?(e=v(a),G):e},G.padding=function(a){return arguments.length?(z=v(a),G):z},G.random=function(a){return arguments.length?(F=a,G):F},G.on=function(){var a=D.on.apply(D,arguments);return a===D?G:a},G};var w={archimedean:r,rectangular:s}},{"d3-dispatch":2}],2:[function(b,c,d){!function(b,e){"object"==typeof d&&void 0!==c?e(d):"function"==typeof a&&a.amd?a(["exports"],e):e(b.d3=b.d3||{})}(this,function(a){"use strict";function c(){for(var e,a=0,b=arguments.length,c={};a<b;++a){if(!(e=arguments[a]+"")||e in c)throw new Error("illegal type: "+e);c[e]=[]}return new d(c)}function d(a){this._=a}function e(a,b){return a.trim().split(/^|\s+/).map(function(a){var c="",d=a.indexOf(".");if(d>=0&&(c=a.slice(d+1),a=a.slice(0,d)),a&&!b.hasOwnProperty(a))throw new Error("unknown type: "+a);return{type:a,name:c}})}function f(a,b){for(var e,c=0,d=a.length;c<d;++c)if((e=a[c]).name===b)return e.value}function g(a,c,d){for(var e=0,f=a.length;e<f;++e)if(a[e].name===c){a[e]=b,a=a.slice(0,e).concat(a.slice(e+1));break}return null!=d&&a.push({name:c,value:d}),a}var b={value:function(){}};d.prototype=c.prototype={constructor:d,on:function(a,b){var h,c=this._,d=e(a+"",c),i=-1,j=d.length;{if(!(arguments.length<2)){if(null!=b&&"function"!=typeof b)throw new Error("invalid callback: "+b);for(;++i<j;)if(h=(a=d[i]).type)c[h]=g(c[h],a.name,b);else if(null==b)for(h in c)c[h]=g(c[h],a.name,null);return this}for(;++i<j;)if((h=(a=d[i]).type)&&(h=f(c[h],a.name)))return h}},copy:function(){var a={},b=this._;for(var c in b)a[c]=b[c].slice();return new d(a)},call:function(a,b){if((e=arguments.length-2)>0)for(var e,f,c=new Array(e),d=0;d<e;++d)c[d]=arguments[d+2];if(!this._.hasOwnProperty(a))throw new Error("unknown type: "+a);for(f=this._[a],d=0,e=f.length;d<e;++d)f[d].value.apply(b,c)},apply:function(a,b,c){if(!this._.hasOwnProperty(a))throw new Error("unknown type: "+a);for(var d=this._[a],e=0,f=d.length;e<f;++e)d[e].value.apply(b,c)}},a.dispatch=c,Object.defineProperty(a,"__esModule",{value:!0})})},{}]},{},[1])(1)});
/// ACTUAL START OF ANSWER ///
// fake JSON data in place of CSV file.
data = [{
word: "word1",
count: 457,
index: 239
},
{
word: "word2",
count: 373,
index: 155
},
{
word: "word3",
count: 76,
index: -142
},
{
word: "word4",
count: 345,
index: 127
},
{
word: "word5",
count: 12,
index: -206
},
{
word: "word6",
count: 46,
index: -172
}
]
// the script
var width = 700, // amended to make result easier to see
height =400;
var wordScale = d3.scale.linear().range([10,100]);
// d3.csv("words.csv", function(data) { // uncomment when using CSV
var subjects = data
.map(function(d) {return {text: d.word, size: +d.count, in: +d.index} }) // note use of +d.index, to ensure it's held as a number when read from csv, same as +d.count
.sort(function(a,b) {return d3.descending (a.size, b.size); })
.slice(0,100);
wordScale.domain([
d3.min(subjects, function(d) {return d.size; }),
d3.max(subjects, function(d) {return d.size; }),
]);
// var layout = cloud()
d3.layout.cloud().size([width, height])
.words(subjects)
.padding(1)
.rotate(function() { return ~~(Math.random() * 2) * 0; })
.font("Impact")
.fontSize(function(d) { return wordScale(d.size); })
.on("end", draw)
.start();
// }); uncomment when using csv
function draw(words) {
d3.select("#word-cloud").append("svg")
.attr("width",width)
.attr("height",height)
.append("g")
.attr("transform", "translate("+(width /2)+","+(height /2)+")")
//where the center is
.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 d.in > 0 ? "#00cc66" : "grey" })
.style("opacity", function(d) { return d.in > 0 ? d.size/100 : (50 - d.size) / 100 } ) // bit of a hack to get furthest from index to become darker
.attr("text-anchor", "middle")
.attr("transform", function(d) {
return "translate(" + [d.x, d.y] + ")rotate(" + d.rotate + ")";
})
.text(function(d) { return d.text; });
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<!--
word count index
word1 457 239
word2 373 155
word3 76 -142
word4 345 127
word5 12 -206
word6 46 -172
-->
<div id="word-cloud"></div>
Upvotes: 2