viewof viz = {
var width = 960,
height = 500,
velocity = 0.0025,
sphere_radius = .4 * height,
sphere_center = {"x": width/2, "y": height/2},
color_scale = d3.interpolateLab("white", "cyan");
var wave_start = Math.PI / 2,
wave_length = 0.15 * 2 * Math.PI,
wave_peak_fraction = 0.5,
wave_velocity = -0.05,
max_expansion = 0.15;
var svg = d3.select(DOM.svg(width, height));
svg.append("rect")
.attr("x", 0)
.attr("y", 0)
.attr("width", width)
.attr("height", height)
.attr("fill", "#222");
function get_random_number(start, end) {
return ((Math.random() * (end - start)) + start);
}
function x_proj(r, theta, phi) {
return sphere_center.x + r * Math.sin(theta) * Math.cos(phi);
}
function y_proj(r, theta) {
return sphere_center.y + r * Math.cos(theta);
}
function depth_proj(theta, phi) {
return 1 + 0.5 * Math.sin(theta) * Math.sin(phi);
}
function generate_blobs(num_blobs) {
var blobs = [];
for (var i = 0; i < num_blobs; i++) {
var v_latent = get_random_number(0, 2 * Math.PI),
force_radius = get_random_number(0.01, 0.1),
force_angle = get_random_number(0.001, 0.002),
blob = {"size" : get_random_number(15, 20),
"opacity" : get_random_number(0.1, 0.5),
"theta" : Math.acos(get_random_number(-1, 1)), // Sample uniformly on sphere
"phi" : get_random_number(0, 2 * Math.PI),
"r": sphere_radius,
"color": color_scale(0.0)};
blobs.push(blob);
}
return blobs;
}
// Define the dynamics of how each blob moves
function move(d) {
d.phi += velocity;
d.phi = d.phi % (2 * Math.PI);
// Boolean to check whether d is within wave
var within_wave = false;
// Boolean to check whether wave is crossing over the period of 2pi
var exceed_period = wave_start > 2 * Math.PI - wave_length;
if (exceed_period) {
within_wave = (d.phi > wave_start || d.phi + 2 * Math.PI < wave_start + wave_length);
} else {
within_wave = (d.phi > wave_start && d.phi < wave_start + wave_length);
}
// If particle is within wave, modify its color and radius
if (within_wave) {
// phi_fraction measures how far along d.phi is between wave_start
// and wave_start + wave_length
// wave_fraction measures how far along wave bulge current particle is,
// which will define its radius expansion
var phi_fraction, wave_fraction;
if (exceed_period) {
// If d.phi + 2pi is still smaller than the end of the wave, then it
// means d.phi has already been mod 2pi'ed, so we need to shift it
// to calculate how far along the wave we are
if (d.phi + 2 * Math.PI < wave_start + wave_length) {
phi_fraction = (d.phi + 2 * Math.PI - wave_start) / wave_length;
} else {
phi_fraction = (d.phi - wave_start) / wave_length;
}
} else {
phi_fraction = (d.phi - wave_start) / wave_length;
}
// Wave height goes up linearly from wave_start until the wave peak
// wave_peak_fraction along the way, and goes linearly down
// afterwards
if (phi_fraction > wave_peak_fraction) {
wave_fraction = (1 - phi_fraction) / (1 - wave_peak_fraction);
} else {
wave_fraction = phi_fraction / wave_peak_fraction;
}
d.color = color_scale(wave_fraction);
// Radius expands proportional to where the particle is on the wave.
// Further if particle is near the poles (i.e. theta is close to 0 or
// pi), then particle should not expand as much
d.r = (1.0 + max_expansion * wave_fraction * Math.abs(Math.sin(d.theta))) * sphere_radius;
} else {
d.color = color_scale(0.0);
d.r = sphere_radius;
}
}
var blobs_data = generate_blobs(400);
var sphere = svg.selectAll("circle")
.data(blobs_data)
.enter()
.append("circle")
.attr("cx", function(d) { return x_proj(d.r, d.theta, d.phi); })
.attr("cy", function(d) { return y_proj(d.r, d.theta); })
.attr("r", function(d) { return d.size * depth_proj(d.theta, d.phi); })
.attr("opacity", function(d) { return d.opacity; })
.attr("fill", function(d) { return d.color; });
function redraw() {
wave_start += wave_velocity;
if (wave_start < 0) {
wave_start += (2 * Math.PI);
}
sphere.each(move)
.attr("cx", function(d) { return x_proj(d.r, d.theta, d.phi); })
.attr("cy", function(d) { return y_proj(d.r, d.theta) })
.attr("r", function(d) { return d.size * depth_proj(d.theta, d.phi); })
.attr("fill", function(d) { return d.color; });
}
d3.timer(function() {
redraw();
})
return svg.node();
}