Skip to content

Instantly share code, notes, and snippets.

@allisonking
Last active September 20, 2017 00:41
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save allisonking/ece2f8a08a626b7067381317a385a245 to your computer and use it in GitHub Desktop.
Save allisonking/ece2f8a08a626b7067381317a385a245 to your computer and use it in GitHub Desktop.
d3 word cloud with text hover

This uses Jason Davies' word cloud library library to render the 100 most popular words in titles of fan fiction that are tagged as being about Sirius Black and Remus Lupin. Hover over a word to see randomly chosen titles that have that word in them.

(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g=(g.d3||(g.d3 = {}));g=(g.layout||(g.layout = {}));g.cloud = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
// Word cloud layout by Jason Davies, https://www.jasondavies.com/wordcloud/
// Algorithm due to Jonathan Feinberg, http://static.mrfeinberg.com/bv_ch03.pdf
var dispatch = require("d3-dispatch").dispatch;
var cloudRadians = Math.PI / 180,
cw = 1 << 11 >> 5,
ch = 1 << 11;
module.exports = function() {
var size = [256, 256],
text = cloudText,
font = cloudFont,
fontSize = cloudFontSize,
fontStyle = cloudFontNormal,
fontWeight = cloudFontNormal,
rotate = cloudRotate,
padding = cloudPadding,
spiral = archimedeanSpiral,
words = [],
timeInterval = Infinity,
event = dispatch("word", "end"),
timer = null,
random = Math.random,
cloud = {},
canvas = cloudCanvas;
cloud.canvas = function(_) {
return arguments.length ? (canvas = functor(_), cloud) : canvas;
};
cloud.start = function() {
var contextAndRatio = getContext(canvas()),
board = zeroArray((size[0] >> 5) * size[1]),
bounds = null,
n = words.length,
i = -1,
tags = [],
data = words.map(function(d, i) {
d.text = text.call(this, d, i);
d.font = font.call(this, d, i);
d.style = fontStyle.call(this, d, i);
d.weight = fontWeight.call(this, d, i);
d.rotate = rotate.call(this, d, i);
d.size = ~~fontSize.call(this, d, i);
d.padding = padding.call(this, d, i);
return d;
}).sort(function(a, b) { return b.size - a.size; });
if (timer) clearInterval(timer);
timer = setInterval(step, 0);
step();
return cloud;
function step() {
var start = Date.now();
while (Date.now() - start < timeInterval && ++i < n && timer) {
var d = data[i];
d.x = (size[0] * (random() + .5)) >> 1;
d.y = (size[1] * (random() + .5)) >> 1;
cloudSprite(contextAndRatio, d, data, i);
if (d.hasText && place(board, d, bounds)) {
tags.push(d);
event.call("word", cloud, d);
if (bounds) cloudBounds(bounds, d);
else bounds = [{x: d.x + d.x0, y: d.y + d.y0}, {x: d.x + d.x1, y: d.y + d.y1}];
// Temporary hack
d.x -= size[0] >> 1;
d.y -= size[1] >> 1;
}
}
if (i >= n) {
cloud.stop();
event.call("end", cloud, tags, bounds);
}
}
}
cloud.stop = function() {
if (timer) {
clearInterval(timer);
timer = null;
}
return cloud;
};
function getContext(canvas) {
canvas.width = canvas.height = 1;
var ratio = Math.sqrt(canvas.getContext("2d").getImageData(0, 0, 1, 1).data.length >> 2);
canvas.width = (cw << 5) / ratio;
canvas.height = ch / ratio;
var context = canvas.getContext("2d");
context.fillStyle = context.strokeStyle = "red";
context.textAlign = "center";
return {context: context, ratio: ratio};
}
function place(board, tag, bounds) {
var perimeter = [{x: 0, y: 0}, {x: size[0], y: size[1]}],
startX = tag.x,
startY = tag.y,
maxDelta = Math.sqrt(size[0] * size[0] + size[1] * size[1]),
s = spiral(size),
dt = random() < .5 ? 1 : -1,
t = -dt,
dxdy,
dx,
dy;
while (dxdy = s(t += dt)) {
dx = ~~dxdy[0];
dy = ~~dxdy[1];
if (Math.min(Math.abs(dx), Math.abs(dy)) >= maxDelta) break;
tag.x = startX + dx;
tag.y = startY + dy;
if (tag.x + tag.x0 < 0 || tag.y + tag.y0 < 0 ||
tag.x + tag.x1 > size[0] || tag.y + tag.y1 > size[1]) continue;
// TODO only check for collisions within current bounds.
if (!bounds || !cloudCollide(tag, board, size[0])) {
if (!bounds || collideRects(tag, bounds)) {
var sprite = tag.sprite,
w = tag.width >> 5,
sw = size[0] >> 5,
lx = tag.x - (w << 4),
sx = lx & 0x7f,
msx = 32 - sx,
h = tag.y1 - tag.y0,
x = (tag.y + tag.y0) * sw + (lx >> 5),
last;
for (var j = 0; j < h; j++) {
last = 0;
for (var i = 0; i <= w; i++) {
board[x + i] |= (last << msx) | (i < w ? (last = sprite[j * w + i]) >>> sx : 0);
}
x += sw;
}
delete tag.sprite;
return true;
}
}
}
return false;
}
cloud.timeInterval = function(_) {
return arguments.length ? (timeInterval = _ == null ? Infinity : _, cloud) : timeInterval;
};
cloud.words = function(_) {
return arguments.length ? (words = _, cloud) : words;
};
cloud.size = function(_) {
return arguments.length ? (size = [+_[0], +_[1]], cloud) : size;
};
cloud.font = function(_) {
return arguments.length ? (font = functor(_), cloud) : font;
};
cloud.fontStyle = function(_) {
return arguments.length ? (fontStyle = functor(_), cloud) : fontStyle;
};
cloud.fontWeight = function(_) {
return arguments.length ? (fontWeight = functor(_), cloud) : fontWeight;
};
cloud.rotate = function(_) {
return arguments.length ? (rotate = functor(_), cloud) : rotate;
};
cloud.text = function(_) {
return arguments.length ? (text = functor(_), cloud) : text;
};
cloud.spiral = function(_) {
return arguments.length ? (spiral = spirals[_] || _, cloud) : spiral;
};
cloud.fontSize = function(_) {
return arguments.length ? (fontSize = functor(_), cloud) : fontSize;
};
cloud.padding = function(_) {
return arguments.length ? (padding = functor(_), cloud) : padding;
};
cloud.random = function(_) {
return arguments.length ? (random = _, cloud) : random;
};
cloud.on = function() {
var value = event.on.apply(event, arguments);
return value === event ? cloud : value;
};
return cloud;
};
function cloudText(d) {
return d.text;
}
function cloudFont() {
return "serif";
}
function cloudFontNormal() {
return "normal";
}
function cloudFontSize(d) {
return Math.sqrt(d.value);
}
function cloudRotate() {
return (~~(Math.random() * 6) - 3) * 30;
}
function cloudPadding() {
return 1;
}
// Fetches a monochrome sprite bitmap for the specified text.
// Load in batches for speed.
function cloudSprite(contextAndRatio, d, data, di) {
if (d.sprite) return;
var c = contextAndRatio.context,
ratio = contextAndRatio.ratio;
c.clearRect(0, 0, (cw << 5) / ratio, ch / ratio);
var x = 0,
y = 0,
maxh = 0,
n = data.length;
--di;
while (++di < n) {
d = data[di];
c.save();
c.font = d.style + " " + d.weight + " " + ~~((d.size + 1) / ratio) + "px " + d.font;
var w = c.measureText(d.text + "m").width * ratio,
h = d.size << 1;
if (d.rotate) {
var sr = Math.sin(d.rotate * cloudRadians),
cr = Math.cos(d.rotate * cloudRadians),
wcr = w * cr,
wsr = w * sr,
hcr = h * cr,
hsr = h * sr;
w = (Math.max(Math.abs(wcr + hsr), Math.abs(wcr - hsr)) + 0x1f) >> 5 << 5;
h = ~~Math.max(Math.abs(wsr + hcr), Math.abs(wsr - hcr));
} else {
w = (w + 0x1f) >> 5 << 5;
}
if (h > maxh) maxh = h;
if (x + w >= (cw << 5)) {
x = 0;
y += maxh;
maxh = 0;
}
if (y + h >= ch) break;
c.translate((x + (w >> 1)) / ratio, (y + (h >> 1)) / ratio);
if (d.rotate) c.rotate(d.rotate * cloudRadians);
c.fillText(d.text, 0, 0);
if (d.padding) c.lineWidth = 2 * d.padding, c.strokeText(d.text, 0, 0);
c.restore();
d.width = w;
d.height = h;
d.xoff = x;
d.yoff = y;
d.x1 = w >> 1;
d.y1 = h >> 1;
d.x0 = -d.x1;
d.y0 = -d.y1;
d.hasText = true;
x += w;
}
var pixels = c.getImageData(0, 0, (cw << 5) / ratio, ch / ratio).data,
sprite = [];
while (--di >= 0) {
d = data[di];
if (!d.hasText) continue;
var w = d.width,
w32 = w >> 5,
h = d.y1 - d.y0;
// Zero the buffer
for (var i = 0; i < h * w32; i++) sprite[i] = 0;
x = d.xoff;
if (x == null) return;
y = d.yoff;
var seen = 0,
seenRow = -1;
for (var j = 0; j < h; j++) {
for (var i = 0; i < w; i++) {
var k = w32 * j + (i >> 5),
m = pixels[((y + j) * (cw << 5) + (x + i)) << 2] ? 1 << (31 - (i % 32)) : 0;
sprite[k] |= m;
seen |= m;
}
if (seen) seenRow = j;
else {
d.y0++;
h--;
j--;
y++;
}
}
d.y1 = d.y0 + seenRow;
d.sprite = sprite.slice(0, (d.y1 - d.y0) * w32);
}
}
// Use mask-based collision detection.
function cloudCollide(tag, board, sw) {
sw >>= 5;
var sprite = tag.sprite,
w = tag.width >> 5,
lx = tag.x - (w << 4),
sx = lx & 0x7f,
msx = 32 - sx,
h = tag.y1 - tag.y0,
x = (tag.y + tag.y0) * sw + (lx >> 5),
last;
for (var j = 0; j < h; j++) {
last = 0;
for (var i = 0; i <= w; i++) {
if (((last << msx) | (i < w ? (last = sprite[j * w + i]) >>> sx : 0))
& board[x + i]) return true;
}
x += sw;
}
return false;
}
function cloudBounds(bounds, d) {
var b0 = bounds[0],
b1 = bounds[1];
if (d.x + d.x0 < b0.x) b0.x = d.x + d.x0;
if (d.y + d.y0 < b0.y) b0.y = d.y + d.y0;
if (d.x + d.x1 > b1.x) b1.x = d.x + d.x1;
if (d.y + d.y1 > b1.y) b1.y = d.y + d.y1;
}
function collideRects(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 archimedeanSpiral(size) {
var e = size[0] / size[1];
return function(t) {
return [e * (t *= .1) * Math.cos(t), t * Math.sin(t)];
};
}
function rectangularSpiral(size) {
var dy = 4,
dx = dy * size[0] / size[1],
x = 0,
y = 0;
return function(t) {
var sign = t < 0 ? -1 : 1;
// See triangular numbers: T_n = n * (n + 1) / 2.
switch ((Math.sqrt(1 + 4 * sign * t) - sign) & 3) {
case 0: x += dx; break;
case 1: y += dy; break;
case 2: x -= dx; break;
default: y -= dy; break;
}
return [x, y];
};
}
// TODO reuse arrays?
function zeroArray(n) {
var a = [],
i = -1;
while (++i < n) a[i] = 0;
return a;
}
function cloudCanvas() {
return document.createElement("canvas");
}
function functor(d) {
return typeof d === "function" ? d : function() { return d; };
}
var spirals = {
archimedean: archimedeanSpiral,
rectangular: rectangularSpiral
};
},{"d3-dispatch":2}],2:[function(require,module,exports){
// https://d3js.org/d3-dispatch/ Version 1.0.2. Copyright 2016 Mike Bostock.
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
typeof define === 'function' && define.amd ? define(['exports'], factory) :
(factory((global.d3 = global.d3 || {})));
}(this, (function (exports) { 'use strict';
var noop = {value: function() {}};
function dispatch() {
for (var i = 0, n = arguments.length, _ = {}, t; i < n; ++i) {
if (!(t = arguments[i] + "") || (t in _)) throw new Error("illegal type: " + t);
_[t] = [];
}
return new Dispatch(_);
}
function Dispatch(_) {
this._ = _;
}
function parseTypenames(typenames, types) {
return typenames.trim().split(/^|\s+/).map(function(t) {
var name = "", i = t.indexOf(".");
if (i >= 0) name = t.slice(i + 1), t = t.slice(0, i);
if (t && !types.hasOwnProperty(t)) throw new Error("unknown type: " + t);
return {type: t, name: name};
});
}
Dispatch.prototype = dispatch.prototype = {
constructor: Dispatch,
on: function(typename, callback) {
var _ = this._,
T = parseTypenames(typename + "", _),
t,
i = -1,
n = T.length;
// If no callback was specified, return the callback of the given type and name.
if (arguments.length < 2) {
while (++i < n) if ((t = (typename = T[i]).type) && (t = get(_[t], typename.name))) return t;
return;
}
// If a type was specified, set the callback for the given type and name.
// Otherwise, if a null callback was specified, remove callbacks of the given name.
if (callback != null && typeof callback !== "function") throw new Error("invalid callback: " + callback);
while (++i < n) {
if (t = (typename = T[i]).type) _[t] = set(_[t], typename.name, callback);
else if (callback == null) for (t in _) _[t] = set(_[t], typename.name, null);
}
return this;
},
copy: function() {
var copy = {}, _ = this._;
for (var t in _) copy[t] = _[t].slice();
return new Dispatch(copy);
},
call: function(type, that) {
if ((n = arguments.length - 2) > 0) for (var args = new Array(n), i = 0, n, t; i < n; ++i) args[i] = arguments[i + 2];
if (!this._.hasOwnProperty(type)) throw new Error("unknown type: " + type);
for (t = this._[type], i = 0, n = t.length; i < n; ++i) t[i].value.apply(that, args);
},
apply: function(type, that, args) {
if (!this._.hasOwnProperty(type)) throw new Error("unknown type: " + type);
for (var t = this._[type], i = 0, n = t.length; i < n; ++i) t[i].value.apply(that, args);
}
};
function get(type, name) {
for (var i = 0, n = type.length, c; i < n; ++i) {
if ((c = type[i]).name === name) {
return c.value;
}
}
}
function set(type, name, callback) {
for (var i = 0, n = type.length; i < n; ++i) {
if (type[i].name === name) {
type[i] = noop, type = type.slice(0, i).concat(type.slice(i + 1));
break;
}
}
if (callback != null) type.push({name: name, value: callback});
return type;
}
exports.dispatch = dispatch;
Object.defineProperty(exports, '__esModule', { value: true });
})));
},{}]},{},[1])(1)
});
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>After All This Time?</title>
<!-- Import D3 -->
<script src="https://d3js.org/d3.v4.min.js"></script>
<!-- Random number generator -->
<script src="//cdnjs.cloudflare.com/ajax/libs/seedrandom/2.4.3/lib/alea.min.js"></script>
<!-- d3-cloud library -->
<script src="d3.layout.cloud.js"></script>
<link rel="stylesheet" type='text/css' href="style.css">
</head>
<body>
<div id='cloud-container'></div>
<script src="wordcloud.js"></script>
<script>
WordCloud({
container: '#cloud-container',
data: 'wolfstar.json'
});
</script>
</body>
</html>
.label-background-strong {
fill: white;
fill-opacity: .8;
}
#wolfstar-container {
cursor: default;
}
svg text{
font-family: Helvetica, sans-serif;
font-size: 14px;
}
{"count":{"Love":389,"Sirius":344,"Marauders":284,"Remus":244,"Black":218,"Moon":178,"Time":172,"Christmas":140,"One":139,"Life":139,"Never":127,"Night":125,"Lupin":118,"Day":111,"First":110,"Marauder":108,"Year":97,"Last":94,"Moony":92,"Wolf":89,"Potter":88,"Kiss":88,"Chocolate":81,"New":80,"Things":79,"Heart":78,"Story":77,"Padfoot":76,"Lost":75,"Little":71,"Harry":70,"Hogwarts":70,"love":69,"Home":66,"Werewolf":66,"Two":66,"Memories":65,"James":64,"Broken":64,"Best":63,"Eyes":63,"Good":63,"Back":61,"Secret":60,"Star":59,"Happy":58,"Summer":57,"Friends":56,"Days":56,"World":56,"Truth":55,"Years":55,"Know":55,"Dog":54,"Always":54,"Secrets":53,"Stars":52,"Go":50,"Letters":48,"Way":48,"Rain":47,"Will":47,"Moonlight":46,"Forever":46,"Dark":45,"Together":45,"Full":45,"Past":44,"Morning":44,"Marauder's":44,"Perfect":43,"Friend":42,"Light":41,"Come":41,"Marauders'":41,"Away":41,"End":40,"Puppy":40,"Something":40,"Better":40,"Man":40,"Death":39,"Letter":39,"Words":38,"Kisses":38,"Lily":38,"Fall":37,"Beautiful":37,"Behind":36,"Mischief":36,"Four":36,"Veil":36,"Cold":36,"Dear":36,"Another":35,"Gone":35,"Bad":35,"Wolfstar":35,"Memory":35,"Nothing":35},"sample_title":{"Love":["Love Is","Marauder Love Story","Accidentally in Love"],"Sirius":["Sirius Sins","Goldfish and Sirius","A Day With Remus And Sirius"],"Marauders":["The Marauders","The Tale of the Marauders Map - We Know We're Called Gred and Forge","Becoming the Marauders"],"Remus":["Secret Writings of Remus Lupin","Remus Has Pretty Hair","Remus Cousin"],"Black":["Black Pudding (Pre-TK-verse)","Jessica Black and the Book of Names","A Sirius Black Love Story - This Must Be It"],"Moon":["Birth By Moon","With the Full Moon","Under the Christmas Moon"],"Time":["Time Is Running Out","A Marauder's Time","Twelve Moments in Time"],"Christmas":["When Kevin O'Reilly Nearly Ruined Christmas","A Very Sirius Christmas","Last Christmas"],"One":["One More Night","Never One to Falter","One Day More"],"Life":["My Life as a Danielle Steel Novel","I've Had The Time Of My Life","A Waking Dream of Life and Light"],"Never":["Never Chew A Pickle","Never Forget","Never Trust A Promise"],"Night":["Night and Day","Throughout the Night","The First Night Away"],"Lupin":["The Several Train Rides of Remus Lupin","Remus Lupin was not a Girl","15 Reasons Why Remus Lupin Loves Sirius Black"],"Day":["A Typical Winter's Day","Jour de Neige Snow Day","Twas the Hottest Day of the Year Thus Far"],"First":["The Marauders First Year","First Glance","The First Year Journal of Remus J Lupin"],"Marauder":["To Kill A Marauder","Karina Potter, Mini Marauder","If you can't be good, Be a Marauder"],"Year":["Jaguar Brazil: Year One","From Monster to Moony: First Year","The Marauders: Year 1"],"Last":["Their First Last Time","The Last Marauder","Last Kiss"],"Moony":["Moody and Moony","Goodbye Moony","Why Padfoot Should Tell Moony"],"Wolf":["Wolf Star short Stories","Cry of the Wolf","Hollow Wolf"],"Potter":["Hayden Hormonal Potter","Harry Potter","A Harry Potter Collection of Fairy Tales"],"Kiss":["A Kiss Goodbye","To Bring Up A Kiss","Just A Kiss"],"Chocolate":["Of Butterbeer, Beetles, Chocolate and Kisses","Chocolate Christmas","Chocolate and Confusion"],"New":["There is a New Prankster in Town","Of Lions and Wards: A New Home","New Beginnings"],"Things":["The Things we Share","Things Better Left Unsaid","Fifteen Things That May Have Happened at Hogwarts"],"Heart":["The Tell-Tale Candy Heart","Wild Heart","The Werewolf's Human Heart"],"Story":["Five Friends - A Marauder Story - Year Two","Fulfilling a Prophecy: The Story of Lily and James","A Love Story"],"Padfoot":["Thanks, Padfoot","Moony, Wormtail, Padfoot and Prongs","Padfoot and Moony are still best friends"],"Lost":["Lost Loves","Lost to those CIA of the WW Bastards","Lost and Found"],"Little":["A Little Help","A Furry Little Problem","If Just For a Little While"],"Harry":["Harry Potter and the truth of darkness","Harry Potter and the Godfathers","Harry Potter FanFic - The Marauders argue with McGonagall about a rule"],"Hogwarts":["A Maurauder's Hogwarts","Fifteen Things That May Have Happened at Hogwarts","Beginnings at Hogwarts"],"love":["After heartache, before love","i've loved you for a thousand years, i'll love you for a thousand more","When Remus falls in love"],"Home":["Of Hearth and Home","Home","Home"],"Werewolf":["When the Werewolf Comes Out of the Shrieking Shack","That Adorable Werewolf","Confessions Of A Teenage Werewolf"],"Two":["Two Roads to Rune","Five Friends - A Marauder Story - Year Two","Two Men and A Baby Gift"],"Memories":["Memories from photos","Chocolate Memories","Memories"],"James":["Ten Things James Potter Knows About Sirius Black","Of Lily and James","A World With James Potter"],"Broken":["A Broken Glass Kind of Magic","Broken Promises","Ending Things On Broken Petals"],"Best":["Our Best Mate Is A Werewolf (Marauders One Shot)","My Best Friend's Boyfriend","The Marauder's Best Prank Ever"],"Eyes":["The Fire In Your Eyes","Behind Grey Eyes","Your Eyes"],"Good":["Look Good in my Shirt","Good OldFashioned Lover Boys","Well and Good"],"Back":["Back For Good","Don't Look Back In Anger","Welcome Back And All That Rubbish"],"Secret":["The Secret Keeper","The Secret In His Eyes","The Secret"],"Star":["A Falling Star Among the Moonbeams","His Brightest Star","Halfway To the Star"],"Happy":["Happy Birthday, Remus!","Happy Birthday!","Happy Sexual"],"Summer":["Summer Break","Backlighting :: Summer 1976","Racing the Summer Moon"],"Friends":["The Ballad of Me and My Friends","The Best of Friends","Best of Friends"],"Days":["The Magic of Ordinary Days","Deadly Days","Dog Days"],"World":["Nothing in the World","Half A World Away","This Cold, Cruel World"],"Truth":["The Truth About Remus Lupin - A Marauders Story","Until You Know The Truth","Truth or Dare"],"Years":["All Those Years","Seven Years","9 Years with the Marauders"],"Know":["No Know Beans","Things You Should Know","You Know I Will"],"Dog":["Devil Dog","A Barking Dog Doesn't Bite","Wet Dog"],"Always":["Always an End, And a Beginning","Always","Always be together"],"Secrets":["I'll Keep Your Secrets","Formidable Secrets","Easter Secrets"],"Stars":["The Stars Look Down","Fallen Stars","Counting Stars"],"Go":["Here We Go Again","As Lovers Go","The Lengths We Go"],"Letters":["Letters to Sirius","Letters","Letters With Padfoot and Moony"],"Way":["Just the Way He Used to","It Begins This Way","The Way We Were"],"Rain":["Smoking in the Rain","Rain","A Cup of Rain"],"Will":["This Will Destroy You","Will You Be My Valentine?","Will you ever understand?"],"Moonlight":["Fifty Shades of Moonlight","Moonlight Torments sparkly euphoria","In the Moonlight"],"Forever":["Forever and a day","Forever","Forever Yours"],"Dark":["Always Secret, Dark and Deep","Deep Dark","The Dark Heir"],"Together":["Together Again","All Together Now","Together As One"],"Full":["A Very Sirius Full Moon","Curse of the Full Moon","Future of the Full Moon"],"Past":["You Can't Change the Past","The Past isn't Set in Stone","A Window to the Past"],"Morning":["The Morning After","Mourning Morning","When The Morning Comes"],"Marauder's":["The Marauder's Son","The Marauder's and the Pillar of Storge","The Marauder's Map: A collection of memories"],"Perfect":["Perfect","The Perfect Guy","The Perfect Son"],"Friend":["The Friend","Good-bye My Friend","A Faithful Friend"],"Light":["Little Bits of Light","Dark and Light Combine","The Absence of Light"],"Come":["Come What May : A MarauderyMoulinRougey fic","What Dreams May Come","Just Come Out With It"],"Marauders'":["Marauders' Internal Affairs","The Marauders' Faults and Pride","Classified Marauders' Information"],"Away":["Take It All Away","Slipped Away","Sailed Away With My Heart"],"End":["Until the Very End","End of Days","End Of The Line"],"Puppy":["Lovesick Puppy","Puppy Love: Remus and Sirius Drabbles","Silly Puppy"],"Something":["I Know Something You Don't Know","Something You Don't","Something Smells Like Tuna"],"Better":["Better Than Make Believe","Life Can Only Get Better","For Good or Bad, Better or Worse"],"Man":["Last Man Standing","A Patient Man","The Last Thoughs Of A Man Who Lost You"],"Death":["Death is nothing","Death Bites","The Best Death"],"Letter":["The Dangers of Writing a Love Letter","The Letter","A Letter to the Dead"],"Words":["Words","Words that Linger","The Words That Were Never Spoken"],"Kisses":["Healing Kisses","Lamps and Kisses","Of Butterbeer, Beetles, Chocolate and Kisses"],"Lily":["The marauders and Lily read the books","Lily Evans had enough","When Lily and Sirius Make a Deal"],"Fall":["Fall","After the Fall","Even Angels Fall Down"],"Beautiful":["Beautiful","Beautiful Day","Beautiful Supernova"],"Behind":["Behind the Veil","Behind the Magic, Beneath the Moon","Voices Behind the Veil"],"Mischief":["First Year of Mischief","Mischief Marauders","Marauder Mayhem, Mishaps and Mischief"],"Four":["Buy One, Get All Four","Four Stages","The Four Marauders"],"Veil":["Behind the Veil","Beyond the Veil","The Veil"],"Cold":["Best Served Cold","Before It Goes Cold","Cold Nose"],"Dear":["Dear Diary","Dear Prudence","Dear Remus,"],"Another":["Another Point Of View","Another Prisoner, Another Professor","In Another Universe"],"Gone":["Sirius is Gone","Gone With The Wind","Revenge Gone Wrong"],"Bad":["Good Girls and Boys Go Bad","Bad Apples","Bad Days"],"Wolfstar":["Wolfstar Shorties","Wolfstar - Oneshots","Wolfstar"],"Memory":["The Prisoner of Memory","Memory","Losing Your Memory"],"Nothing":["I Solemnly Swear I Drink Nothing But Tea Marauders Era","Tears Are Nothing Compared to This","Much Ado About (For) Nothing"]}}
function WordCloud(options) {
var margin = {top: 70, right: 100, bottom: 0, left: 100},
w = 1200 - margin.left - margin.right,
h = 400 - margin.top - margin.bottom;
// create the svg
var svg = d3.select(options.container).append("svg")
.attr('height', h + margin.top + margin.bottom)
.attr('width', w + margin.left + margin.right)
// set the ranges for the scales
var xScale = d3.scaleLinear().range([10, 100]);
var focus = svg.append('g')
.attr("transform", "translate(" + [w/2, h/2+margin.top] + ")")
var colorMap = ['red', '#a38b07'];
// seeded random number generator
var arng = new alea('hello.');
var data;
d3.json(options.data, function(error, d) {
if (error) throw error;
data = d;
var word_entries = d3.entries(data['count']);
xScale.domain(d3.extent(word_entries, function(d) {return d.value;}));
makeCloud();
function makeCloud() {
d3.layout.cloud().size([w, h])
.timeInterval(20)
.words(word_entries)
.fontSize(function(d) { return xScale(+d.value); })
.text(function(d) { return d.key; })
.font("Impact")
.random(arng)
.on("end", function(output) {
// sometimes the word cloud can't fit all the words- then redraw
// https://github.com/jasondavies/d3-cloud/issues/36
if (word_entries.length !== output.length) {
console.log("not all words included- recreating");
makeCloud();
return undefined;
} else { draw(output); }
})
.start();
}
d3.layout.cloud().stop();
});
function draw(words) {
focus.selectAll("text")
.data(words)
.enter().append("text")
.style("font-size", function(d) { return xScale(d.value) + "px"; })
.style("font-family", "Impact")
.style("fill", function(d, i) { return colorMap[~~(arng() *2)]; })
.attr("text-anchor", "middle")
.attr("transform", function(d) {
return "translate(" + [d.x, d.y] + ")rotate(" + d.rotate + ")";
})
.text(function(d) { return d.key; })
.on('mouseover', handleMouseOver)
.on('mouseout', handleMouseOut);
}
function handleMouseOver(d) {
var group = focus.append('g')
.attr('id', 'story-titles');
var base = d.y - d.size;
group.selectAll('text')
.data(data['sample_title'][d.key])
.enter().append('text')
.attr('x', d.x)
.attr('y', function(title, i) {
return (base - i*14);
})
.attr('text-anchor', 'middle')
.text(function(title) { return title; });
var bbox = group.node().getBBox();
var bboxPadding = 5;
// place a white background to see text more clearly
var rect = group.insert('rect', ':first-child')
.attr('x', bbox.x)
.attr('y', bbox.y)
.attr('width', bbox.width + bboxPadding)
.attr('height', bbox.height + bboxPadding)
.attr('rx', 10)
.attr('ry', 10)
.attr('class', 'label-background-strong');
}
function handleMouseOut(d) {
d3.select('#story-titles').remove();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment