modest_canvas_rails 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +13 -0
- data/.rspec +2 -0
- data/.travis.yml +5 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +73 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/changelog.md +0 -0
- data/lib/modest_canvas_rails.rb +2 -0
- data/lib/modest_canvas_rails/engine.rb +4 -0
- data/lib/modest_canvas_rails/version.rb +3 -0
- data/modest_canvas_rails.gemspec +27 -0
- data/vendor/assets/javascripts/modest_canvas_rails/d3.js +8 -0
- data/vendor/assets/javascripts/modest_canvas_rails/donut_chart.js +132 -0
- data/vendor/assets/javascripts/modest_canvas_rails/edge_bundling.js +149 -0
- data/vendor/assets/javascripts/modest_canvas_rails/index.js +5 -0
- data/vendor/assets/javascripts/modest_canvas_rails/scatter_plot.js +117 -0
- data/vendor/assets/javascripts/modest_canvas_rails/word_cloud.js +478 -0
- data/vendor/assets/stylesheets/modest_canvas_rails/d3.css +13 -0
- data/vendor/assets/stylesheets/modest_canvas_rails/donut_chart.css +16 -0
- data/vendor/assets/stylesheets/modest_canvas_rails/edge_bundling.css +39 -0
- data/vendor/assets/stylesheets/modest_canvas_rails/index.css +7 -0
- data/vendor/assets/stylesheets/modest_canvas_rails/scatter_plot.css +44 -0
- data/vendor/assets/stylesheets/modest_canvas_rails/word_cloud.css +13 -0
- metadata +113 -0
@@ -0,0 +1,117 @@
|
|
1
|
+
var ModestCanvas = typeof ModestCanvas != "undefined" ? ModestCanvas : {};
|
2
|
+
ModestCanvas.scatterPlot = function(container, scatterData, args){
|
3
|
+
var elementContainer = d3.select(container);
|
4
|
+
var margin = {top: 20, right: 20, bottom: 30, left: 40},
|
5
|
+
width = elementContainer.node().getBoundingClientRect().width - margin.left - margin.right,
|
6
|
+
height = 400 - margin.top - margin.bottom;
|
7
|
+
|
8
|
+
var svg = elementContainer
|
9
|
+
.classed('d3_scatterplot', true)
|
10
|
+
.append("svg")
|
11
|
+
.attr("preserveAspectRatio", "xMinYMin meet")
|
12
|
+
.attr("viewBox", "0 0 " + (width + margin.left + margin.right) + " " + (height + margin.top + margin.bottom))
|
13
|
+
.append("g")
|
14
|
+
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
|
15
|
+
|
16
|
+
var minCircleFillColor = args.minCircleFillColor || "#000000";
|
17
|
+
var maxCircleFillColor = args.maxCircleFillColor || "#BBBBBB";
|
18
|
+
|
19
|
+
var color = d3.scaleLinear().domain(scatterData.colors.domain).range([minCircleFillColor, maxCircleFillColor]);
|
20
|
+
|
21
|
+
var xValue = function(d) { return d.coordinates.x;},
|
22
|
+
xScale = d3.scaleLinear().range([0, width]),
|
23
|
+
xMap = function(d) { return xScale(xValue(d));},
|
24
|
+
xAxis = d3.axisBottom(xScale);
|
25
|
+
|
26
|
+
var yValue = function(d) { return d.coordinates.y;},
|
27
|
+
yScale = d3.scaleLinear().range([height, 0]),
|
28
|
+
yMap = function(d) { return yScale(yValue(d));},
|
29
|
+
yAxis = d3.axisLeft(yScale);
|
30
|
+
|
31
|
+
if(scatterData.axes.enabled == true){
|
32
|
+
xScale.domain([d3.min(scatterData.values, xValue) - 0.2, d3.max(scatterData.values, xValue) + 0.2]);
|
33
|
+
yScale.domain([d3.min(scatterData.values, yValue) - 1, d3.max(scatterData.values, yValue) + 1]);
|
34
|
+
|
35
|
+
if(scatterData.axes.x.customTicks != undefined){
|
36
|
+
xAxis.tickFormat(function(d){return (scatterData.axes.x.customTicks[d] != undefined) ? scatterData.axes.x.customTicks[d] : "";});
|
37
|
+
}
|
38
|
+
|
39
|
+
if(scatterData.axes.y.customTicks != undefined){
|
40
|
+
yAxis.tickFormat(function(d){return (scatterData.axes.y.customTicks[d] != undefined) ? scatterData.axes.y.customTicks[d] : "";});
|
41
|
+
}
|
42
|
+
|
43
|
+
svg.append("g")
|
44
|
+
.attr("class", "x axis")
|
45
|
+
.attr("transform", "translate(0," + height + ")")
|
46
|
+
.call(xAxis)
|
47
|
+
.append("text")
|
48
|
+
.attr("class", "label")
|
49
|
+
.attr("x", width)
|
50
|
+
.attr("y", -6)
|
51
|
+
.style("text-anchor", "end")
|
52
|
+
.text(scatterData.axes.x.label);
|
53
|
+
|
54
|
+
svg.append("g")
|
55
|
+
.attr("class", "y axis")
|
56
|
+
.call(yAxis)
|
57
|
+
.append("text")
|
58
|
+
.attr("class", "label")
|
59
|
+
.attr("transform", "rotate(-90)")
|
60
|
+
.attr("y", 6)
|
61
|
+
.attr("dy", ".71em")
|
62
|
+
.style("text-anchor", "end")
|
63
|
+
.text(scatterData.axes.y.label);
|
64
|
+
}
|
65
|
+
|
66
|
+
var circles = svg.selectAll(".dot")
|
67
|
+
.data(scatterData.values)
|
68
|
+
.enter().append("circle")
|
69
|
+
.attr("class", "dot")
|
70
|
+
.attr("r", 5.5)
|
71
|
+
.attr("cx", xMap)
|
72
|
+
.attr("cy", yMap)
|
73
|
+
.attr("onclick", function(d) {return (d.point.attributes.onclick != undefined) ? d.point.attributes.onclick : "";})
|
74
|
+
.style("fill", function(d) { return (scatterData.colors.x_based == true) ? color(d.coordinates.x) : color(d.coordinates.y);});
|
75
|
+
|
76
|
+
if(scatterData.tooltip.enabled == true){
|
77
|
+
var tooltip = elementContainer.append("div")
|
78
|
+
.attr("class", "tooltip")
|
79
|
+
.style("opacity", 0);
|
80
|
+
circles.on("mouseover", function(d) {
|
81
|
+
tooltip.transition()
|
82
|
+
.duration(200)
|
83
|
+
.style("opacity", .9);
|
84
|
+
tooltip.html(d.tooltip.text)
|
85
|
+
.style("left", (d3.event.pageX - elementContainer.node().getBoundingClientRect().left - margin.left + 45) + "px")
|
86
|
+
.style("top", (d3.event.pageY - elementContainer.node().getBoundingClientRect().top + margin.top - 28) + "px");
|
87
|
+
});
|
88
|
+
circles.on("mouseout", function(d) {
|
89
|
+
tooltip.transition()
|
90
|
+
.duration(500)
|
91
|
+
.style("opacity", 0);
|
92
|
+
});
|
93
|
+
}
|
94
|
+
|
95
|
+
if(scatterData.legend.enabled == true){
|
96
|
+
var legend = svg.selectAll(".legend")
|
97
|
+
.data(scatterData.legend.domain)
|
98
|
+
.enter().append("g")
|
99
|
+
.attr("class", "legend")
|
100
|
+
.attr("transform", function(d, i) { return "translate(0," + i * 20 + ")"; });
|
101
|
+
|
102
|
+
legend.append("rect")
|
103
|
+
.attr("x", width - 18)
|
104
|
+
.attr("rx", 18)
|
105
|
+
.attr("ry", 18)
|
106
|
+
.attr("width", 18)
|
107
|
+
.attr("height", 18)
|
108
|
+
.style("fill", function(d){return color(d.for);});
|
109
|
+
|
110
|
+
legend.append("text")
|
111
|
+
.attr("x", width - 24)
|
112
|
+
.attr("y", 9)
|
113
|
+
.attr("dy", ".35em")
|
114
|
+
.style("text-anchor", "end")
|
115
|
+
.text(function(d) { return d.label;})
|
116
|
+
}
|
117
|
+
}
|
@@ -0,0 +1,478 @@
|
|
1
|
+
// Word cloud layout by Jason Davies, http://www.jasondavies.com/word-cloud/
|
2
|
+
// Algorithm due to Jonathan Feinberg, http://static.mrfeinberg.com/bv_ch03.pdf
|
3
|
+
var ModestCanvas = typeof ModestCanvas != "undefined" ? ModestCanvas : {};
|
4
|
+
ModestCanvas.wordCloud = function(container, cloudData, args){
|
5
|
+
var jsonData = cloudData;
|
6
|
+
d3WordCloudInit();
|
7
|
+
if(args == undefined){args = {}}
|
8
|
+
var maxFontSize = args.maxFontSize || 50;
|
9
|
+
var minFontSize = args.minFontSize || 14;
|
10
|
+
var minCategoryFillColor = args.minCategoryFillColor || "#7c68a9";
|
11
|
+
var maxCategoryFillColor = args.maxCategoryFillColor || "#80696a";
|
12
|
+
|
13
|
+
var elementContainer = d3.select(container);
|
14
|
+
var margin = {top: 20, right: 20, bottom: 40, left: 20},
|
15
|
+
width = elementContainer.node().getBoundingClientRect().width - margin.left - margin.right,
|
16
|
+
height = 400 - margin.top - margin.bottom;
|
17
|
+
var categories = d3.nest().key(function(d) { return d.category; }).map(jsonData).keys();
|
18
|
+
var maxFrequency = Math.max.apply(Math, d3.nest().key(function(d) { return d.frequency; }).map(jsonData).keys());
|
19
|
+
var minFrequency = Math.min.apply(Math, d3.nest().key(function(d) { return d.frequency; }).map(jsonData).keys());
|
20
|
+
var fontSize = d3.scalePow().exponent(2).domain([minFrequency, maxFrequency]).range([minFontSize,maxFontSize]);
|
21
|
+
var color = d3.scaleOrdinal().domain(categories).range(d3.range(categories.length).map(d3.scaleLinear().domain([0, categories.length - 1]).range([minCategoryFillColor, maxCategoryFillColor]).interpolate(d3.interpolateLab)));
|
22
|
+
var layout = d3.cloud()
|
23
|
+
.timeInterval(10)
|
24
|
+
.size([width, height])
|
25
|
+
.words(jsonData)
|
26
|
+
.rotate(function(d) { return Math.random() > 0.5 ? 0 : -90; })
|
27
|
+
.fontSize(function(d,i) { return fontSize(d.frequency); })
|
28
|
+
.text(function(d) { return d.word; })
|
29
|
+
.spiral("archimedean")
|
30
|
+
.on("end", draw)
|
31
|
+
.start();
|
32
|
+
|
33
|
+
var svg = elementContainer
|
34
|
+
.classed('d3_word_cloud', true)
|
35
|
+
.append("svg")
|
36
|
+
.classed('d3_word_cloud_svg', true)
|
37
|
+
.attr("preserveAspectRatio", "xMinYMin meet")
|
38
|
+
.attr("viewBox", "0 0 " + (width + margin.left + margin.right) + " " + (height + margin.top + margin.bottom))
|
39
|
+
.append("g")
|
40
|
+
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
|
41
|
+
|
42
|
+
var wordcloud = svg.append("g")
|
43
|
+
.attr('class','wordcloud')
|
44
|
+
.attr("transform", "translate(" + width/2 + "," + height/2 + ")");
|
45
|
+
|
46
|
+
if(categories.length && args.hideCategoryAxis != true){
|
47
|
+
var x0 = d3.scaleBand()
|
48
|
+
.range([0, width])
|
49
|
+
.round(.1)
|
50
|
+
.domain(categories);
|
51
|
+
|
52
|
+
var xAxis = d3.axisBottom()
|
53
|
+
.scale(x0);
|
54
|
+
|
55
|
+
svg.append("g")
|
56
|
+
.attr("class", "x axis")
|
57
|
+
.classed('categories_axis', true)
|
58
|
+
.attr("transform", "translate(0," + height + ")")
|
59
|
+
.call(xAxis)
|
60
|
+
.selectAll('text')
|
61
|
+
.style('fill',function(d) { return color(d); });
|
62
|
+
}
|
63
|
+
|
64
|
+
function draw(words) {
|
65
|
+
wordcloud.selectAll("text")
|
66
|
+
.data(words)
|
67
|
+
.enter().append("text")
|
68
|
+
.attr('class','word')
|
69
|
+
.style("font-size", function(d) { return d.size + "px"; })
|
70
|
+
.style("fill", function(d) {
|
71
|
+
return color(d.category);
|
72
|
+
})
|
73
|
+
.attr("text-anchor", "middle")
|
74
|
+
.attr("transform", function(d) { return "translate(" + [d.x, d.y] + ")rotate(" + d.rotate + ")"; })
|
75
|
+
.text(function(d) { return d.text; });
|
76
|
+
};
|
77
|
+
|
78
|
+
function d3WordCloudInit() {
|
79
|
+
if (typeof define === "function" && define.amd) define(["d3"], cloud);
|
80
|
+
else cloud(this.d3);
|
81
|
+
var cloudRadians = Math.PI / 180,
|
82
|
+
cw = 1 << 11 >> 5,
|
83
|
+
ch = 1 << 11;
|
84
|
+
|
85
|
+
function cloud(d3) {
|
86
|
+
d3.cloud = function cloud() {
|
87
|
+
var size = [256, 256],
|
88
|
+
text = cloudText,
|
89
|
+
font = cloudFont,
|
90
|
+
fontSize = cloudFontSize,
|
91
|
+
fontStyle = cloudFontNormal,
|
92
|
+
fontWeight = cloudFontNormal,
|
93
|
+
rotate = cloudRotate,
|
94
|
+
padding = cloudPadding,
|
95
|
+
spiral = archimedeanSpiral,
|
96
|
+
words = [],
|
97
|
+
timeInterval = Infinity,
|
98
|
+
event = d3.dispatch("word", "end"),
|
99
|
+
timer = null,
|
100
|
+
random = Math.random,
|
101
|
+
cloud = {},
|
102
|
+
canvas = cloudCanvas;
|
103
|
+
|
104
|
+
cloud.canvas = function(_) {
|
105
|
+
return arguments.length ? (canvas = functor(_), cloud) : canvas;
|
106
|
+
};
|
107
|
+
|
108
|
+
cloud.start = function() {
|
109
|
+
var contextAndRatio = getContext(canvas()),
|
110
|
+
board = zeroArray((size[0] >> 5) * size[1]),
|
111
|
+
bounds = null,
|
112
|
+
n = words.length,
|
113
|
+
i = -1,
|
114
|
+
tags = [],
|
115
|
+
data = words.map(function(d, i) {
|
116
|
+
d.text = text.call(this, d, i);
|
117
|
+
d.font = font.call(this, d, i);
|
118
|
+
d.style = fontStyle.call(this, d, i);
|
119
|
+
d.weight = fontWeight.call(this, d, i);
|
120
|
+
d.rotate = rotate.call(this, d, i);
|
121
|
+
d.size = ~~fontSize.call(this, d, i);
|
122
|
+
d.padding = padding.call(this, d, i);
|
123
|
+
return d;
|
124
|
+
}).sort(function(a, b) { return b.size - a.size; });
|
125
|
+
|
126
|
+
if (timer) clearInterval(timer);
|
127
|
+
timer = setInterval(step, 0);
|
128
|
+
step();
|
129
|
+
|
130
|
+
return cloud;
|
131
|
+
|
132
|
+
function step() {
|
133
|
+
var start = Date.now();
|
134
|
+
while (Date.now() - start < timeInterval && ++i < n && timer) {
|
135
|
+
var d = data[i];
|
136
|
+
d.x = (size[0] * (random() + .5)) >> 1;
|
137
|
+
d.y = (size[1] * (random() + .5)) >> 1;
|
138
|
+
cloudSprite(contextAndRatio, d, data, i);
|
139
|
+
if (d.hasText && place(board, d, bounds)) {
|
140
|
+
tags.push(d);
|
141
|
+
event.call("word", cloud, d);
|
142
|
+
if (bounds) cloudBounds(bounds, d);
|
143
|
+
else bounds = [{x: d.x + d.x0, y: d.y + d.y0}, {x: d.x + d.x1, y: d.y + d.y1}];
|
144
|
+
// Temporary hack
|
145
|
+
d.x -= size[0] >> 1;
|
146
|
+
d.y -= size[1] >> 1;
|
147
|
+
}
|
148
|
+
}
|
149
|
+
if (i >= n) {
|
150
|
+
cloud.stop();
|
151
|
+
event.call("end", cloud, tags, bounds);
|
152
|
+
}
|
153
|
+
}
|
154
|
+
}
|
155
|
+
|
156
|
+
cloud.stop = function() {
|
157
|
+
if (timer) {
|
158
|
+
clearInterval(timer);
|
159
|
+
timer = null;
|
160
|
+
}
|
161
|
+
return cloud;
|
162
|
+
};
|
163
|
+
|
164
|
+
function getContext(canvas) {
|
165
|
+
canvas.width = canvas.height = 1;
|
166
|
+
var ratio = Math.sqrt(canvas.getContext("2d").getImageData(0, 0, 1, 1).data.length >> 2);
|
167
|
+
canvas.width = (cw << 5) / ratio;
|
168
|
+
canvas.height = ch / ratio;
|
169
|
+
|
170
|
+
var context = canvas.getContext("2d");
|
171
|
+
context.fillStyle = context.strokeStyle = "red";
|
172
|
+
context.textAlign = "center";
|
173
|
+
|
174
|
+
return {context: context, ratio: ratio};
|
175
|
+
}
|
176
|
+
|
177
|
+
function place(board, tag, bounds) {
|
178
|
+
var perimeter = [{x: 0, y: 0}, {x: size[0], y: size[1]}],
|
179
|
+
startX = tag.x,
|
180
|
+
startY = tag.y,
|
181
|
+
maxDelta = Math.sqrt(size[0] * size[0] + size[1] * size[1]),
|
182
|
+
s = spiral(size),
|
183
|
+
dt = random() < .5 ? 1 : -1,
|
184
|
+
t = -dt,
|
185
|
+
dxdy,
|
186
|
+
dx,
|
187
|
+
dy;
|
188
|
+
|
189
|
+
while (dxdy = s(t += dt)) {
|
190
|
+
dx = ~~dxdy[0];
|
191
|
+
dy = ~~dxdy[1];
|
192
|
+
|
193
|
+
if (Math.min(Math.abs(dx), Math.abs(dy)) >= maxDelta) break;
|
194
|
+
|
195
|
+
tag.x = startX + dx;
|
196
|
+
tag.y = startY + dy;
|
197
|
+
|
198
|
+
if (tag.x + tag.x0 < 0 || tag.y + tag.y0 < 0 ||
|
199
|
+
tag.x + tag.x1 > size[0] || tag.y + tag.y1 > size[1]) continue;
|
200
|
+
// TODO only check for collisions within current bounds.
|
201
|
+
if (!bounds || !cloudCollide(tag, board, size[0])) {
|
202
|
+
if (!bounds || collideRects(tag, bounds)) {
|
203
|
+
var sprite = tag.sprite,
|
204
|
+
w = tag.width >> 5,
|
205
|
+
sw = size[0] >> 5,
|
206
|
+
lx = tag.x - (w << 4),
|
207
|
+
sx = lx & 0x7f,
|
208
|
+
msx = 32 - sx,
|
209
|
+
h = tag.y1 - tag.y0,
|
210
|
+
x = (tag.y + tag.y0) * sw + (lx >> 5),
|
211
|
+
last;
|
212
|
+
for (var j = 0; j < h; j++) {
|
213
|
+
last = 0;
|
214
|
+
for (var i = 0; i <= w; i++) {
|
215
|
+
board[x + i] |= (last << msx) | (i < w ? (last = sprite[j * w + i]) >>> sx : 0);
|
216
|
+
}
|
217
|
+
x += sw;
|
218
|
+
}
|
219
|
+
delete tag.sprite;
|
220
|
+
return true;
|
221
|
+
}
|
222
|
+
}
|
223
|
+
}
|
224
|
+
return false;
|
225
|
+
}
|
226
|
+
|
227
|
+
cloud.timeInterval = function(_) {
|
228
|
+
return arguments.length ? (timeInterval = _ == null ? Infinity : _, cloud) : timeInterval;
|
229
|
+
};
|
230
|
+
|
231
|
+
cloud.words = function(_) {
|
232
|
+
return arguments.length ? (words = _, cloud) : words;
|
233
|
+
};
|
234
|
+
|
235
|
+
cloud.size = function(_) {
|
236
|
+
return arguments.length ? (size = [+_[0], +_[1]], cloud) : size;
|
237
|
+
};
|
238
|
+
|
239
|
+
cloud.font = function(_) {
|
240
|
+
return arguments.length ? (font = functor(_), cloud) : font;
|
241
|
+
};
|
242
|
+
|
243
|
+
cloud.fontStyle = function(_) {
|
244
|
+
return arguments.length ? (fontStyle = functor(_), cloud) : fontStyle;
|
245
|
+
};
|
246
|
+
|
247
|
+
cloud.fontWeight = function(_) {
|
248
|
+
return arguments.length ? (fontWeight = functor(_), cloud) : fontWeight;
|
249
|
+
};
|
250
|
+
|
251
|
+
cloud.rotate = function(_) {
|
252
|
+
return arguments.length ? (rotate = functor(_), cloud) : rotate;
|
253
|
+
};
|
254
|
+
|
255
|
+
cloud.text = function(_) {
|
256
|
+
return arguments.length ? (text = functor(_), cloud) : text;
|
257
|
+
};
|
258
|
+
|
259
|
+
cloud.spiral = function(_) {
|
260
|
+
return arguments.length ? (spiral = spirals[_] || _, cloud) : spiral;
|
261
|
+
};
|
262
|
+
|
263
|
+
cloud.fontSize = function(_) {
|
264
|
+
return arguments.length ? (fontSize = functor(_), cloud) : fontSize;
|
265
|
+
};
|
266
|
+
|
267
|
+
cloud.padding = function(_) {
|
268
|
+
return arguments.length ? (padding = functor(_), cloud) : padding;
|
269
|
+
};
|
270
|
+
|
271
|
+
cloud.random = function(_) {
|
272
|
+
return arguments.length ? (random = _, cloud) : random;
|
273
|
+
};
|
274
|
+
|
275
|
+
cloud.on = function() {
|
276
|
+
var value = event.on.apply(event, arguments);
|
277
|
+
return value === event ? cloud : value;
|
278
|
+
};
|
279
|
+
|
280
|
+
return cloud;
|
281
|
+
};
|
282
|
+
|
283
|
+
function cloudText(d) {
|
284
|
+
return d.text;
|
285
|
+
}
|
286
|
+
|
287
|
+
function cloudFont() {
|
288
|
+
return "serif";
|
289
|
+
}
|
290
|
+
|
291
|
+
function cloudFontNormal() {
|
292
|
+
return "normal";
|
293
|
+
}
|
294
|
+
|
295
|
+
function cloudFontSize(d) {
|
296
|
+
return Math.sqrt(d.value);
|
297
|
+
}
|
298
|
+
|
299
|
+
function cloudRotate() {
|
300
|
+
return (~~(Math.random() * 6) - 3) * 30;
|
301
|
+
}
|
302
|
+
|
303
|
+
function cloudPadding() {
|
304
|
+
return 1;
|
305
|
+
}
|
306
|
+
|
307
|
+
// Fetches a monochrome sprite bitmap for the specified text.
|
308
|
+
// Load in batches for speed.
|
309
|
+
function cloudSprite(contextAndRatio, d, data, di) {
|
310
|
+
if (d.sprite) return;
|
311
|
+
var c = contextAndRatio.context,
|
312
|
+
ratio = contextAndRatio.ratio;
|
313
|
+
|
314
|
+
c.clearRect(0, 0, (cw << 5) / ratio, ch / ratio);
|
315
|
+
var x = 0,
|
316
|
+
y = 0,
|
317
|
+
maxh = 0,
|
318
|
+
n = data.length;
|
319
|
+
--di;
|
320
|
+
while (++di < n) {
|
321
|
+
d = data[di];
|
322
|
+
c.save();
|
323
|
+
c.font = d.style + " " + d.weight + " " + ~~((d.size + 1) / ratio) + "px " + d.font;
|
324
|
+
var w = c.measureText(d.text + "m").width * ratio,
|
325
|
+
h = d.size << 1;
|
326
|
+
if (d.rotate) {
|
327
|
+
var sr = Math.sin(d.rotate * cloudRadians),
|
328
|
+
cr = Math.cos(d.rotate * cloudRadians),
|
329
|
+
wcr = w * cr,
|
330
|
+
wsr = w * sr,
|
331
|
+
hcr = h * cr,
|
332
|
+
hsr = h * sr;
|
333
|
+
w = (Math.max(Math.abs(wcr + hsr), Math.abs(wcr - hsr)) + 0x1f) >> 5 << 5;
|
334
|
+
h = ~~Math.max(Math.abs(wsr + hcr), Math.abs(wsr - hcr));
|
335
|
+
} else {
|
336
|
+
w = (w + 0x1f) >> 5 << 5;
|
337
|
+
}
|
338
|
+
if (h > maxh) maxh = h;
|
339
|
+
if (x + w >= (cw << 5)) {
|
340
|
+
x = 0;
|
341
|
+
y += maxh;
|
342
|
+
maxh = 0;
|
343
|
+
}
|
344
|
+
if (y + h >= ch) break;
|
345
|
+
c.translate((x + (w >> 1)) / ratio, (y + (h >> 1)) / ratio);
|
346
|
+
if (d.rotate) c.rotate(d.rotate * cloudRadians);
|
347
|
+
c.fillText(d.text, 0, 0);
|
348
|
+
if (d.padding) c.lineWidth = 2 * d.padding, c.strokeText(d.text, 0, 0);
|
349
|
+
c.restore();
|
350
|
+
d.width = w;
|
351
|
+
d.height = h;
|
352
|
+
d.xoff = x;
|
353
|
+
d.yoff = y;
|
354
|
+
d.x1 = w >> 1;
|
355
|
+
d.y1 = h >> 1;
|
356
|
+
d.x0 = -d.x1;
|
357
|
+
d.y0 = -d.y1;
|
358
|
+
d.hasText = true;
|
359
|
+
x += w;
|
360
|
+
}
|
361
|
+
var pixels = c.getImageData(0, 0, (cw << 5) / ratio, ch / ratio).data,
|
362
|
+
sprite = [];
|
363
|
+
while (--di >= 0) {
|
364
|
+
d = data[di];
|
365
|
+
if (!d.hasText) continue;
|
366
|
+
var w = d.width,
|
367
|
+
w32 = w >> 5,
|
368
|
+
h = d.y1 - d.y0;
|
369
|
+
// Zero the buffer
|
370
|
+
for (var i = 0; i < h * w32; i++) sprite[i] = 0;
|
371
|
+
x = d.xoff;
|
372
|
+
if (x == null) return;
|
373
|
+
y = d.yoff;
|
374
|
+
var seen = 0,
|
375
|
+
seenRow = -1;
|
376
|
+
for (var j = 0; j < h; j++) {
|
377
|
+
for (var i = 0; i < w; i++) {
|
378
|
+
var k = w32 * j + (i >> 5),
|
379
|
+
m = pixels[((y + j) * (cw << 5) + (x + i)) << 2] ? 1 << (31 - (i % 32)) : 0;
|
380
|
+
sprite[k] |= m;
|
381
|
+
seen |= m;
|
382
|
+
}
|
383
|
+
if (seen) seenRow = j;
|
384
|
+
else {
|
385
|
+
d.y0++;
|
386
|
+
h--;
|
387
|
+
j--;
|
388
|
+
y++;
|
389
|
+
}
|
390
|
+
}
|
391
|
+
d.y1 = d.y0 + seenRow;
|
392
|
+
d.sprite = sprite.slice(0, (d.y1 - d.y0) * w32);
|
393
|
+
}
|
394
|
+
}
|
395
|
+
|
396
|
+
// Use mask-based collision detection.
|
397
|
+
function cloudCollide(tag, board, sw) {
|
398
|
+
sw >>= 5;
|
399
|
+
var sprite = tag.sprite,
|
400
|
+
w = tag.width >> 5,
|
401
|
+
lx = tag.x - (w << 4),
|
402
|
+
sx = lx & 0x7f,
|
403
|
+
msx = 32 - sx,
|
404
|
+
h = tag.y1 - tag.y0,
|
405
|
+
x = (tag.y + tag.y0) * sw + (lx >> 5),
|
406
|
+
last;
|
407
|
+
for (var j = 0; j < h; j++) {
|
408
|
+
last = 0;
|
409
|
+
for (var i = 0; i <= w; i++) {
|
410
|
+
if (((last << msx) | (i < w ? (last = sprite[j * w + i]) >>> sx : 0))
|
411
|
+
& board[x + i]) return true;
|
412
|
+
}
|
413
|
+
x += sw;
|
414
|
+
}
|
415
|
+
return false;
|
416
|
+
}
|
417
|
+
|
418
|
+
function cloudBounds(bounds, d) {
|
419
|
+
var b0 = bounds[0],
|
420
|
+
b1 = bounds[1];
|
421
|
+
if (d.x + d.x0 < b0.x) b0.x = d.x + d.x0;
|
422
|
+
if (d.y + d.y0 < b0.y) b0.y = d.y + d.y0;
|
423
|
+
if (d.x + d.x1 > b1.x) b1.x = d.x + d.x1;
|
424
|
+
if (d.y + d.y1 > b1.y) b1.y = d.y + d.y1;
|
425
|
+
}
|
426
|
+
|
427
|
+
function collideRects(a, b) {
|
428
|
+
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;
|
429
|
+
}
|
430
|
+
|
431
|
+
function archimedeanSpiral(size) {
|
432
|
+
var e = size[0] / size[1];
|
433
|
+
return function(t) {
|
434
|
+
return [e * (t *= .1) * Math.cos(t), t * Math.sin(t)];
|
435
|
+
};
|
436
|
+
}
|
437
|
+
|
438
|
+
function rectangularSpiral(size) {
|
439
|
+
var dy = 4,
|
440
|
+
dx = dy * size[0] / size[1],
|
441
|
+
x = 0,
|
442
|
+
y = 0;
|
443
|
+
return function(t) {
|
444
|
+
var sign = t < 0 ? -1 : 1;
|
445
|
+
// See triangular numbers: T_n = n * (n + 1) / 2.
|
446
|
+
switch ((Math.sqrt(1 + 4 * sign * t) - sign) & 3) {
|
447
|
+
case 0: x += dx; break;
|
448
|
+
case 1: y += dy; break;
|
449
|
+
case 2: x -= dx; break;
|
450
|
+
default: y -= dy; break;
|
451
|
+
}
|
452
|
+
return [x, y];
|
453
|
+
};
|
454
|
+
}
|
455
|
+
|
456
|
+
// TODO reuse arrays?
|
457
|
+
function zeroArray(n) {
|
458
|
+
var a = [],
|
459
|
+
i = -1;
|
460
|
+
while (++i < n) a[i] = 0;
|
461
|
+
return a;
|
462
|
+
}
|
463
|
+
|
464
|
+
function cloudCanvas() {
|
465
|
+
return document.createElement("canvas");
|
466
|
+
}
|
467
|
+
|
468
|
+
function functor(d) {
|
469
|
+
return typeof d === "function" ? d : function() { return d; };
|
470
|
+
}
|
471
|
+
|
472
|
+
var spirals = {
|
473
|
+
archimedean: archimedeanSpiral,
|
474
|
+
rectangular: rectangularSpiral
|
475
|
+
}
|
476
|
+
}
|
477
|
+
}
|
478
|
+
}
|