d3-cloud-rails 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +77 -0
- data/Rakefile +19 -0
- data/app/assets/javascripts/d3-cloud.js +392 -0
- data/app/assets/javascripts/d3-cloud.min.js +1 -0
- data/lib/d3-cloud-rails.rb +1 -0
- data/lib/d3_cloud/rails.rb +2 -0
- data/lib/d3_cloud/rails/engine.rb +6 -0
- data/lib/d3_cloud/rails/version.rb +5 -0
- metadata +82 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: c2a9280e5f2a9b7b97904fcd16263498912e58aa
|
4
|
+
data.tar.gz: f21841cfe1989f5120aa9ca235d3a914f52ab337
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: cb2cd746d4c19a4a98136553c5b1ee6d4412fa519cf750d799c72947f60ef8590749537fa651dd8b649004025fe3b7f5ba776f2748605b3d81c8bcd5ddf12607
|
7
|
+
data.tar.gz: bf2e4580c694a5ed3ef2be6d75b085cad4d2a408609f4b2575e219c66e3ebebe89c65a6abc0586bdbc81bcf88624aa359e1a0962a77b75f8b1e581bb67fe4c19
|
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright 2018 Anthony Hernandez
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,77 @@
|
|
1
|
+
# D3::Cloud::Rails
|
2
|
+
This is a Rails plugin for generating wordle-like word clouds using D3.js. The work is based almost entirely on that done by jasondavies.
|
3
|
+
You can find the original javascript project along with the API [here](https://github.com/jasondavies/d3-cloud).
|
4
|
+
|
5
|
+
|
6
|
+
## Usage
|
7
|
+
You must have D3.js installed to use this gem. The author recommends [d3-rails](https://github.com/iblue/d3-rails).
|
8
|
+
|
9
|
+
In `app/assets/javascripts/application.js`:
|
10
|
+
|
11
|
+
```js
|
12
|
+
//= require d3
|
13
|
+
//= require d3-cloud
|
14
|
+
```
|
15
|
+
|
16
|
+
```js
|
17
|
+
$(function(){
|
18
|
+
var frequency_list = [{"text":"study","size":40},{"text":"motion","size":15},{"text":"forces","size":10},{"text":"electricity","size":15},{"text":"movement","size":10},{"text":"relation","size":5},{"text":"things","size":10},{"text":"force","size":5},{"text":"ad","size":5},{"text":"energy","size":85},{"text":"living","size":5},{"text":"nonliving","size":5},{"text":"laws","size":15},{"text":"speed","size":45},{"text":"velocity","size":30},{"text":"define","size":5},{"text":"constraints","size":5},{"text":"universe","size":10},{"text":"physics","size":120},{"text":"describing","size":5},{"text":"matter","size":90},{"text":"physics-the","size":5},{"text":"world","size":10},{"text":"works","size":10},{"text":"science","size":70},{"text":"interactions","size":30},{"text":"studies","size":5},{"text":"properties","size":45},{"text":"nature","size":40},{"text":"branch","size":30},{"text":"concerned","size":25},{"text":"source","size":40},{"text":"google","size":10},{"text":"defintions","size":5},{"text":"two","size":15},{"text":"grouped","size":15},{"text":"traditional","size":15},{"text":"fields","size":15},{"text":"acoustics","size":15},{"text":"optics","size":15},{"text":"mechanics","size":20},{"text":"thermodynamics","size":15},{"text":"electromagnetism","size":15},{"text":"modern","size":15},{"text":"extensions","size":15},{"text":"thefreedictionary","size":15},{"text":"interaction","size":15},{"text":"org","size":25},{"text":"answers","size":5},{"text":"natural","size":15},{"text":"objects","size":5},{"text":"treats","size":10},{"text":"acting","size":5},{"text":"department","size":5},{"text":"gravitation","size":5},{"text":"heat","size":10},{"text":"light","size":10},{"text":"magnetism","size":10},{"text":"modify","size":5},{"text":"general","size":10},{"text":"bodies","size":5},{"text":"philosophy","size":5},{"text":"brainyquote","size":5},{"text":"words","size":5},{"text":"ph","size":5},{"text":"html","size":5},{"text":"lrl","size":5},{"text":"zgzmeylfwuy","size":5},{"text":"subject","size":5},{"text":"distinguished","size":5},{"text":"chemistry","size":5},{"text":"biology","size":5},{"text":"includes","size":5},{"text":"radiation","size":5},{"text":"sound","size":5},{"text":"structure","size":5},{"text":"atoms","size":5},{"text":"including","size":10},{"text":"atomic","size":10},{"text":"nuclear","size":10},{"text":"cryogenics","size":10},{"text":"solid-state","size":10},{"text":"particle","size":10},{"text":"plasma","size":10},{"text":"deals","size":5},{"text":"merriam-webster","size":5},{"text":"dictionary","size":10},{"text":"analysis","size":5},{"text":"conducted","size":5},{"text":"order","size":5},{"text":"understand","size":5},{"text":"behaves","size":5},{"text":"en","size":5},{"text":"wikipedia","size":5},{"text":"wiki","size":5},{"text":"physics-","size":5},{"text":"physical","size":5},{"text":"behaviour","size":5},{"text":"collinsdictionary","size":5},{"text":"english","size":5},{"text":"time","size":35},{"text":"distance","size":35},{"text":"wheels","size":5},{"text":"revelations","size":5},{"text":"minute","size":5},{"text":"acceleration","size":20},{"text":"torque","size":5},{"text":"wheel","size":5},{"text":"rotations","size":5},{"text":"resistance","size":5},{"text":"momentum","size":5},{"text":"measure","size":10},{"text":"direction","size":10},{"text":"car","size":5},{"text":"add","size":5},{"text":"traveled","size":5},{"text":"weight","size":5},{"text":"electrical","size":5},{"text":"power","size":5}];
|
19
|
+
|
20
|
+
|
21
|
+
var color = d3.scaleSqrt()
|
22
|
+
.domain([0,1,2,3,4,5,6,10,15,20,100])
|
23
|
+
.range(["#ddd", "#ccc", "#bbb", "#aaa", "#999", "#888", "#777", "#666", "#555", "#444", "#333", "#222"]);
|
24
|
+
|
25
|
+
d3.cloud.size([800, 300])
|
26
|
+
.words(frequency_list)
|
27
|
+
.fontSize(function(d) { return d.size; })
|
28
|
+
.on("end", draw)
|
29
|
+
.start();
|
30
|
+
|
31
|
+
function draw(words) {
|
32
|
+
d3.select("body").append("svg")
|
33
|
+
.attr("width", 850)
|
34
|
+
.attr("height", 350)
|
35
|
+
.attr("class", "wordcloud")
|
36
|
+
.append("g")
|
37
|
+
// without the transform, words words would get cutoff to the left and top, they would
|
38
|
+
// appear outside of the SVG area
|
39
|
+
.attr("transform", "translate(320,200)")
|
40
|
+
.selectAll("text")
|
41
|
+
.data(words)
|
42
|
+
.enter().append("text")
|
43
|
+
.style("font-size", function(d) { return d.size + "px"; })
|
44
|
+
.style("fill", function(d, i) { return color(i); })
|
45
|
+
.attr("transform", function(d) {
|
46
|
+
return "translate(" + [d.x, d.y] + ")rotate(" + d.rotate + ")";
|
47
|
+
})
|
48
|
+
.text(function(d) { return d.text; });
|
49
|
+
}
|
50
|
+
});
|
51
|
+
|
52
|
+
```
|
53
|
+
|
54
|
+

|
55
|
+
|
56
|
+
## Installation
|
57
|
+
Add this line to your application's Gemfile:
|
58
|
+
|
59
|
+
```ruby
|
60
|
+
gem 'd3-cloud-rails'
|
61
|
+
```
|
62
|
+
|
63
|
+
And then execute:
|
64
|
+
```bash
|
65
|
+
$ bundle
|
66
|
+
```
|
67
|
+
|
68
|
+
Or install it yourself as:
|
69
|
+
```bash
|
70
|
+
$ gem install d3-cloud-rails
|
71
|
+
```
|
72
|
+
|
73
|
+
## Contributing
|
74
|
+
Please feel free to help maintain this gem. Simply fork the repo and add any necessary changes / updates, and then open a PR stating what you did and why.
|
75
|
+
|
76
|
+
## License
|
77
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
begin
|
2
|
+
require 'bundler/setup'
|
3
|
+
rescue LoadError
|
4
|
+
puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
|
5
|
+
end
|
6
|
+
|
7
|
+
require 'rdoc/task'
|
8
|
+
|
9
|
+
RDoc::Task.new(:rdoc) do |rdoc|
|
10
|
+
rdoc.rdoc_dir = 'rdoc'
|
11
|
+
rdoc.title = 'D3::Cloud::Rails'
|
12
|
+
rdoc.options << '--line-numbers'
|
13
|
+
rdoc.rdoc_files.include('README.md')
|
14
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
15
|
+
end
|
16
|
+
|
17
|
+
load 'rails/tasks/statistics.rake'
|
18
|
+
|
19
|
+
require 'bundler/gem_tasks'
|
@@ -0,0 +1,392 @@
|
|
1
|
+
var cloudRadians = Math.PI / 180, cw = 1 << 11 >> 5, ch = 1 << 11;
|
2
|
+
$(function() {
|
3
|
+
var size = [256, 256],
|
4
|
+
text = cloudText,
|
5
|
+
font = cloudFont,
|
6
|
+
fontSize = cloudFontSize,
|
7
|
+
fontStyle = cloudFontNormal,
|
8
|
+
fontWeight = cloudFontNormal,
|
9
|
+
rotate = cloudRotate,
|
10
|
+
padding = cloudPadding,
|
11
|
+
spiral = archimedeanSpiral,
|
12
|
+
words = [],
|
13
|
+
timeInterval = Infinity,
|
14
|
+
event = d3.dispatch("word", "end"),
|
15
|
+
timer = null,
|
16
|
+
random = Math.random,
|
17
|
+
cloud = {},
|
18
|
+
canvas = cloudCanvas;
|
19
|
+
|
20
|
+
cloud.canvas = function(_) {
|
21
|
+
return arguments.length ? (canvas = functor(_), cloud) : canvas;
|
22
|
+
};
|
23
|
+
|
24
|
+
cloud.start = function() {
|
25
|
+
var contextAndRatio = getContext(canvas()),
|
26
|
+
board = zeroArray((size[0] >> 5) * size[1]),
|
27
|
+
bounds = null,
|
28
|
+
n = words.length,
|
29
|
+
i = -1,
|
30
|
+
tags = [],
|
31
|
+
data = words.map(function(d, i) {
|
32
|
+
d.text = text.call(this, d, i);
|
33
|
+
d.font = font.call(this, d, i);
|
34
|
+
d.style = fontStyle.call(this, d, i);
|
35
|
+
d.weight = fontWeight.call(this, d, i);
|
36
|
+
d.rotate = rotate.call(this, d, i);
|
37
|
+
d.size = ~~fontSize.call(this, d, i);
|
38
|
+
d.padding = padding.call(this, d, i);
|
39
|
+
return d;
|
40
|
+
}).sort(function(a, b) { return b.size - a.size; });
|
41
|
+
|
42
|
+
if (timer) clearInterval(timer);
|
43
|
+
timer = setInterval(step, 0);
|
44
|
+
step();
|
45
|
+
|
46
|
+
return cloud;
|
47
|
+
|
48
|
+
function step() {
|
49
|
+
var start = Date.now();
|
50
|
+
while (Date.now() - start < timeInterval && ++i < n && timer) {
|
51
|
+
var d = data[i];
|
52
|
+
d.x = (size[0] * (random() + .5)) >> 1;
|
53
|
+
d.y = (size[1] * (random() + .5)) >> 1;
|
54
|
+
cloudSprite(contextAndRatio, d, data, i);
|
55
|
+
if (d.hasText && place(board, d, bounds)) {
|
56
|
+
tags.push(d);
|
57
|
+
event.call("word", cloud, d);
|
58
|
+
if (bounds) cloudBounds(bounds, d);
|
59
|
+
else bounds = [{x: d.x + d.x0, y: d.y + d.y0}, {x: d.x + d.x1, y: d.y + d.y1}];
|
60
|
+
// Temporary hack
|
61
|
+
d.x -= size[0] >> 1;
|
62
|
+
d.y -= size[1] >> 1;
|
63
|
+
}
|
64
|
+
}
|
65
|
+
if (i >= n) {
|
66
|
+
cloud.stop();
|
67
|
+
event.call("end", cloud, tags, bounds);
|
68
|
+
}
|
69
|
+
}
|
70
|
+
}
|
71
|
+
|
72
|
+
cloud.stop = function() {
|
73
|
+
if (timer) {
|
74
|
+
clearInterval(timer);
|
75
|
+
timer = null;
|
76
|
+
}
|
77
|
+
return cloud;
|
78
|
+
};
|
79
|
+
|
80
|
+
function getContext(canvas) {
|
81
|
+
canvas.width = canvas.height = 1;
|
82
|
+
var ratio = Math.sqrt(canvas.getContext("2d").getImageData(0, 0, 1, 1).data.length >> 2);
|
83
|
+
canvas.width = (cw << 5) / ratio;
|
84
|
+
canvas.height = ch / ratio;
|
85
|
+
|
86
|
+
var context = canvas.getContext("2d");
|
87
|
+
context.fillStyle = context.strokeStyle = "red";
|
88
|
+
context.textAlign = "center";
|
89
|
+
|
90
|
+
return {context: context, ratio: ratio};
|
91
|
+
}
|
92
|
+
|
93
|
+
function place(board, tag, bounds) {
|
94
|
+
var perimeter = [{x: 0, y: 0}, {x: size[0], y: size[1]}],
|
95
|
+
startX = tag.x,
|
96
|
+
startY = tag.y,
|
97
|
+
maxDelta = Math.sqrt(size[0] * size[0] + size[1] * size[1]),
|
98
|
+
s = spiral(size),
|
99
|
+
dt = random() < .5 ? 1 : -1,
|
100
|
+
t = -dt,
|
101
|
+
dxdy,
|
102
|
+
dx,
|
103
|
+
dy;
|
104
|
+
|
105
|
+
while (dxdy = s(t += dt)) {
|
106
|
+
dx = ~~dxdy[0];
|
107
|
+
dy = ~~dxdy[1];
|
108
|
+
|
109
|
+
if (Math.min(Math.abs(dx), Math.abs(dy)) >= maxDelta) break;
|
110
|
+
|
111
|
+
tag.x = startX + dx;
|
112
|
+
tag.y = startY + dy;
|
113
|
+
|
114
|
+
if (tag.x + tag.x0 < 0 || tag.y + tag.y0 < 0 ||
|
115
|
+
tag.x + tag.x1 > size[0] || tag.y + tag.y1 > size[1]) continue;
|
116
|
+
// TODO only check for collisions within current bounds.
|
117
|
+
if (!bounds || !cloudCollide(tag, board, size[0])) {
|
118
|
+
if (!bounds || collideRects(tag, bounds)) {
|
119
|
+
var sprite = tag.sprite,
|
120
|
+
w = tag.width >> 5,
|
121
|
+
sw = size[0] >> 5,
|
122
|
+
lx = tag.x - (w << 4),
|
123
|
+
sx = lx & 0x7f,
|
124
|
+
msx = 32 - sx,
|
125
|
+
h = tag.y1 - tag.y0,
|
126
|
+
x = (tag.y + tag.y0) * sw + (lx >> 5),
|
127
|
+
last;
|
128
|
+
for (var j = 0; j < h; j++) {
|
129
|
+
last = 0;
|
130
|
+
for (var i = 0; i <= w; i++) {
|
131
|
+
board[x + i] |= (last << msx) | (i < w ? (last = sprite[j * w + i]) >>> sx : 0);
|
132
|
+
}
|
133
|
+
x += sw;
|
134
|
+
}
|
135
|
+
delete tag.sprite;
|
136
|
+
return true;
|
137
|
+
}
|
138
|
+
}
|
139
|
+
}
|
140
|
+
return false;
|
141
|
+
}
|
142
|
+
|
143
|
+
cloud.timeInterval = function(_) {
|
144
|
+
return arguments.length ? (timeInterval = _ == null ? Infinity : _, cloud) : timeInterval;
|
145
|
+
};
|
146
|
+
|
147
|
+
cloud.words = function(_) {
|
148
|
+
return arguments.length ? (words = _, cloud) : words;
|
149
|
+
};
|
150
|
+
|
151
|
+
cloud.size = function(_) {
|
152
|
+
return arguments.length ? (size = [+_[0], +_[1]], cloud) : size;
|
153
|
+
};
|
154
|
+
|
155
|
+
cloud.font = function(_) {
|
156
|
+
return arguments.length ? (font = functor(_), cloud) : font;
|
157
|
+
};
|
158
|
+
|
159
|
+
cloud.fontStyle = function(_) {
|
160
|
+
return arguments.length ? (fontStyle = functor(_), cloud) : fontStyle;
|
161
|
+
};
|
162
|
+
|
163
|
+
cloud.fontWeight = function(_) {
|
164
|
+
return arguments.length ? (fontWeight = functor(_), cloud) : fontWeight;
|
165
|
+
};
|
166
|
+
|
167
|
+
cloud.rotate = function(_) {
|
168
|
+
return arguments.length ? (rotate = functor(_), cloud) : rotate;
|
169
|
+
};
|
170
|
+
|
171
|
+
cloud.text = function(_) {
|
172
|
+
return arguments.length ? (text = functor(_), cloud) : text;
|
173
|
+
};
|
174
|
+
|
175
|
+
cloud.spiral = function(_) {
|
176
|
+
return arguments.length ? (spiral = spirals[_] || _, cloud) : spiral;
|
177
|
+
};
|
178
|
+
|
179
|
+
cloud.fontSize = function(_) {
|
180
|
+
return arguments.length ? (fontSize = functor(_), cloud) : fontSize;
|
181
|
+
};
|
182
|
+
|
183
|
+
cloud.padding = function(_) {
|
184
|
+
return arguments.length ? (padding = functor(_), cloud) : padding;
|
185
|
+
};
|
186
|
+
|
187
|
+
cloud.random = function(_) {
|
188
|
+
return arguments.length ? (random = _, cloud) : random;
|
189
|
+
};
|
190
|
+
|
191
|
+
cloud.on = function() {
|
192
|
+
var value = event.on.apply(event, arguments);
|
193
|
+
return value === event ? cloud : value;
|
194
|
+
};
|
195
|
+
|
196
|
+
function cloudText(d) {
|
197
|
+
return d.text;
|
198
|
+
}
|
199
|
+
|
200
|
+
function cloudFont() {
|
201
|
+
return "serif";
|
202
|
+
}
|
203
|
+
|
204
|
+
function cloudFontNormal() {
|
205
|
+
return "normal";
|
206
|
+
}
|
207
|
+
|
208
|
+
function cloudFontSize(d) {
|
209
|
+
return Math.sqrt(d.value);
|
210
|
+
}
|
211
|
+
|
212
|
+
function cloudRotate() {
|
213
|
+
return (~~(Math.random() * 6) - 3) * 30;
|
214
|
+
}
|
215
|
+
|
216
|
+
function cloudPadding() {
|
217
|
+
return 1;
|
218
|
+
}
|
219
|
+
|
220
|
+
// Fetches a monochrome sprite bitmap for the specified text.
|
221
|
+
// Load in batches for speed.
|
222
|
+
function cloudSprite(contextAndRatio, d, data, di) {
|
223
|
+
if (d.sprite) return;
|
224
|
+
var c = contextAndRatio.context,
|
225
|
+
ratio = contextAndRatio.ratio;
|
226
|
+
|
227
|
+
c.clearRect(0, 0, (cw << 5) / ratio, ch / ratio);
|
228
|
+
var x = 0,
|
229
|
+
y = 0,
|
230
|
+
maxh = 0,
|
231
|
+
n = data.length;
|
232
|
+
--di;
|
233
|
+
while (++di < n) {
|
234
|
+
d = data[di];
|
235
|
+
c.save();
|
236
|
+
c.font = d.style + " " + d.weight + " " + ~~((d.size + 1) / ratio) + "px " + d.font;
|
237
|
+
var w = c.measureText(d.text + "m").width * ratio,
|
238
|
+
h = d.size << 1;
|
239
|
+
if (d.rotate) {
|
240
|
+
var sr = Math.sin(d.rotate * cloudRadians),
|
241
|
+
cr = Math.cos(d.rotate * cloudRadians),
|
242
|
+
wcr = w * cr,
|
243
|
+
wsr = w * sr,
|
244
|
+
hcr = h * cr,
|
245
|
+
hsr = h * sr;
|
246
|
+
w = (Math.max(Math.abs(wcr + hsr), Math.abs(wcr - hsr)) + 0x1f) >> 5 << 5;
|
247
|
+
h = ~~Math.max(Math.abs(wsr + hcr), Math.abs(wsr - hcr));
|
248
|
+
} else {
|
249
|
+
w = (w + 0x1f) >> 5 << 5;
|
250
|
+
}
|
251
|
+
if (h > maxh) maxh = h;
|
252
|
+
if (x + w >= (cw << 5)) {
|
253
|
+
x = 0;
|
254
|
+
y += maxh;
|
255
|
+
maxh = 0;
|
256
|
+
}
|
257
|
+
if (y + h >= ch) break;
|
258
|
+
c.translate((x + (w >> 1)) / ratio, (y + (h >> 1)) / ratio);
|
259
|
+
if (d.rotate) c.rotate(d.rotate * cloudRadians);
|
260
|
+
c.fillText(d.text, 0, 0);
|
261
|
+
if (d.padding) c.lineWidth = 2 * d.padding, c.strokeText(d.text, 0, 0);
|
262
|
+
c.restore();
|
263
|
+
d.width = w;
|
264
|
+
d.height = h;
|
265
|
+
d.xoff = x;
|
266
|
+
d.yoff = y;
|
267
|
+
d.x1 = w >> 1;
|
268
|
+
d.y1 = h >> 1;
|
269
|
+
d.x0 = -d.x1;
|
270
|
+
d.y0 = -d.y1;
|
271
|
+
d.hasText = true;
|
272
|
+
x += w;
|
273
|
+
}
|
274
|
+
var pixels = c.getImageData(0, 0, (cw << 5) / ratio, ch / ratio).data,
|
275
|
+
sprite = [];
|
276
|
+
while (--di >= 0) {
|
277
|
+
d = data[di];
|
278
|
+
if (!d.hasText) continue;
|
279
|
+
var w = d.width,
|
280
|
+
w32 = w >> 5,
|
281
|
+
h = d.y1 - d.y0;
|
282
|
+
// Zero the buffer
|
283
|
+
for (var i = 0; i < h * w32; i++) sprite[i] = 0;
|
284
|
+
x = d.xoff;
|
285
|
+
if (x == null) return;
|
286
|
+
y = d.yoff;
|
287
|
+
var seen = 0,
|
288
|
+
seenRow = -1;
|
289
|
+
for (var j = 0; j < h; j++) {
|
290
|
+
for (var i = 0; i < w; i++) {
|
291
|
+
var k = w32 * j + (i >> 5),
|
292
|
+
m = pixels[((y + j) * (cw << 5) + (x + i)) << 2] ? 1 << (31 - (i % 32)) : 0;
|
293
|
+
sprite[k] |= m;
|
294
|
+
seen |= m;
|
295
|
+
}
|
296
|
+
if (seen) seenRow = j;
|
297
|
+
else {
|
298
|
+
d.y0++;
|
299
|
+
h--;
|
300
|
+
j--;
|
301
|
+
y++;
|
302
|
+
}
|
303
|
+
}
|
304
|
+
d.y1 = d.y0 + seenRow;
|
305
|
+
d.sprite = sprite.slice(0, (d.y1 - d.y0) * w32);
|
306
|
+
}
|
307
|
+
}
|
308
|
+
|
309
|
+
// Use mask-based collision detection.
|
310
|
+
function cloudCollide(tag, board, sw) {
|
311
|
+
sw >>= 5;
|
312
|
+
var sprite = tag.sprite,
|
313
|
+
w = tag.width >> 5,
|
314
|
+
lx = tag.x - (w << 4),
|
315
|
+
sx = lx & 0x7f,
|
316
|
+
msx = 32 - sx,
|
317
|
+
h = tag.y1 - tag.y0,
|
318
|
+
x = (tag.y + tag.y0) * sw + (lx >> 5),
|
319
|
+
last;
|
320
|
+
for (var j = 0; j < h; j++) {
|
321
|
+
last = 0;
|
322
|
+
for (var i = 0; i <= w; i++) {
|
323
|
+
if (((last << msx) | (i < w ? (last = sprite[j * w + i]) >>> sx : 0))
|
324
|
+
& board[x + i]) return true;
|
325
|
+
}
|
326
|
+
x += sw;
|
327
|
+
}
|
328
|
+
return false;
|
329
|
+
}
|
330
|
+
|
331
|
+
function cloudBounds(bounds, d) {
|
332
|
+
var b0 = bounds[0],
|
333
|
+
b1 = bounds[1];
|
334
|
+
if (d.x + d.x0 < b0.x) b0.x = d.x + d.x0;
|
335
|
+
if (d.y + d.y0 < b0.y) b0.y = d.y + d.y0;
|
336
|
+
if (d.x + d.x1 > b1.x) b1.x = d.x + d.x1;
|
337
|
+
if (d.y + d.y1 > b1.y) b1.y = d.y + d.y1;
|
338
|
+
}
|
339
|
+
|
340
|
+
function collideRects(a, b) {
|
341
|
+
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;
|
342
|
+
}
|
343
|
+
|
344
|
+
function archimedeanSpiral(size) {
|
345
|
+
var e = size[0] / size[1];
|
346
|
+
return function(t) {
|
347
|
+
return [e * (t *= .1) * Math.cos(t), t * Math.sin(t)];
|
348
|
+
};
|
349
|
+
}
|
350
|
+
|
351
|
+
function rectangularSpiral(size) {
|
352
|
+
var dy = 4,
|
353
|
+
dx = dy * size[0] / size[1],
|
354
|
+
x = 0,
|
355
|
+
y = 0;
|
356
|
+
return function(t) {
|
357
|
+
var sign = t < 0 ? -1 : 1;
|
358
|
+
// See triangular numbers: T_n = n * (n + 1) / 2.
|
359
|
+
switch ((Math.sqrt(1 + 4 * sign * t) - sign) & 3) {
|
360
|
+
case 0: x += dx; break;
|
361
|
+
case 1: y += dy; break;
|
362
|
+
case 2: x -= dx; break;
|
363
|
+
default: y -= dy; break;
|
364
|
+
}
|
365
|
+
return [x, y];
|
366
|
+
};
|
367
|
+
}
|
368
|
+
|
369
|
+
// TODO reuse arrays?
|
370
|
+
function zeroArray(n) {
|
371
|
+
var a = [],
|
372
|
+
i = -1;
|
373
|
+
while (++i < n) a[i] = 0;
|
374
|
+
return a;
|
375
|
+
}
|
376
|
+
|
377
|
+
function cloudCanvas() {
|
378
|
+
return document.createElement("canvas");
|
379
|
+
}
|
380
|
+
|
381
|
+
function functor(d) {
|
382
|
+
return typeof d === "function" ? d : function() { return d; };
|
383
|
+
}
|
384
|
+
|
385
|
+
var spirals = {
|
386
|
+
archimedean: archimedeanSpiral,
|
387
|
+
rectangular: rectangularSpiral
|
388
|
+
};
|
389
|
+
|
390
|
+
if (typeof module === "object" && module.exports) module.exports = cloud;
|
391
|
+
else d3.cloud = cloud;
|
392
|
+
});
|
@@ -0,0 +1 @@
|
|
1
|
+
var cloudRadians=Math.PI/180,cw=1<<11>>5,ch=1<<11;$(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=d3.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}]}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}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+=1){last=0;for(var i=0;i<=w;i+=1){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};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}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-=1;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;for(var i=0;i<h*w32;i+=1){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+=1){for(var i=0;i<w;i+=1){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+=1;h-=1;j-=1;y+=1}}d.y1=d.y0+seenRow;d.sprite=sprite.slice(0,(d.y1-d.y0)*w32)}}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+=1){last=0;for(var i=0;i<=w;i+=1){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;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]}}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};if(typeof module==="object"&&module.exports){module.exports=cloud}else{d3.cloud=cloud}});
|
@@ -0,0 +1 @@
|
|
1
|
+
require 'd3_cloud/rails'
|
metadata
ADDED
@@ -0,0 +1,82 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: d3-cloud-rails
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.2
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Anthony Hernandez
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2018-03-03 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rails
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '5.0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '5.0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: d3-rails
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '4.10'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '4.10'
|
41
|
+
description: Makes a wordle-like wordcloud script using D3.js available to the asset
|
42
|
+
pipeline.
|
43
|
+
email:
|
44
|
+
- roguegdi27@gmail.com
|
45
|
+
executables: []
|
46
|
+
extensions: []
|
47
|
+
extra_rdoc_files: []
|
48
|
+
files:
|
49
|
+
- MIT-LICENSE
|
50
|
+
- README.md
|
51
|
+
- Rakefile
|
52
|
+
- app/assets/javascripts/d3-cloud.js
|
53
|
+
- app/assets/javascripts/d3-cloud.min.js
|
54
|
+
- lib/d3-cloud-rails.rb
|
55
|
+
- lib/d3_cloud/rails.rb
|
56
|
+
- lib/d3_cloud/rails/engine.rb
|
57
|
+
- lib/d3_cloud/rails/version.rb
|
58
|
+
homepage: https://www.github.com/hernanat/d3-cloud-rails
|
59
|
+
licenses:
|
60
|
+
- MIT
|
61
|
+
metadata: {}
|
62
|
+
post_install_message:
|
63
|
+
rdoc_options: []
|
64
|
+
require_paths:
|
65
|
+
- lib
|
66
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
67
|
+
requirements:
|
68
|
+
- - ">="
|
69
|
+
- !ruby/object:Gem::Version
|
70
|
+
version: '0'
|
71
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
requirements: []
|
77
|
+
rubyforge_project:
|
78
|
+
rubygems_version: 2.6.13
|
79
|
+
signing_key:
|
80
|
+
specification_version: 4
|
81
|
+
summary: A Rails plugin for creating wordclouds using D3.js.
|
82
|
+
test_files: []
|