Published
Edited
Jun 15, 2018
3 stars
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
camera = {
let aspect = viz_width / height;
return new THREE.PerspectiveCamera(fov, aspect, near, far + 1);
}
Insert cell
Insert cell
Insert cell
scene = {
let scene = new THREE.Scene();
scene.background = new THREE.Color(0xefefef);
scene.add(points);
return scene;
}
Insert cell
Insert cell
Insert cell
function getScaleFromZ (camera_z_position) {
let half_fov = fov/2;
let half_fov_radians = toRadians(half_fov);
let half_fov_height = Math.tan(half_fov_radians) * camera_z_position;
let fov_height = half_fov_height * 2;
let scale = height / fov_height; // Divide visualization height by height derived from field of view
return scale;
}
Insert cell
Insert cell
function getZFromScale(scale) {
let half_fov = fov/2;
let half_fov_radians = toRadians(half_fov);
let scale_height = height / scale;
let camera_z_position = scale_height / (2 * Math.tan(half_fov_radians));
return camera_z_position;
}
Insert cell
Insert cell
function zoomHandler(d3_transform) {
let scale = d3_transform.k;
let x = -(d3_transform.x - viz_width/2) / scale;
let y = (d3_transform.y - height/2) / scale;
let z = getZFromScale(scale);
camera.position.set(x, y, z);
}
Insert cell
Insert cell
zoom = {
let d3_zoom = d3.zoom()
.scaleExtent([getScaleFromZ(far), getScaleFromZ(near)])
.on('zoom', () => {
let d3_transform = d3.event.transform;
zoomHandler(d3_transform);
});
return d3_zoom;
}
Insert cell
Insert cell
view = d3.select(renderer.domElement)
Insert cell
{
function setUpZoom() {
view.call(zoom);
let initial_scale = getScaleFromZ(far);
var initial_transform = d3.zoomIdentity.translate(viz_width/2, height/2).scale(initial_scale);
zoom.transform(view, initial_transform);
camera.position.set(0, 0, far);
}
setUpZoom();
return setUpZoom;
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
circle_sprite= new THREE.TextureLoader().load(
"http://blog.fastforwardlabs.com/images/2018/02/circle-1518727951930.png"
)
Insert cell
Insert cell
Insert cell
circle_sprite_aa= new THREE.TextureLoader().load(
"http://blog.fastforwardlabs.com/images/2018/02/circle_aa-1518730700478.png"
)
Insert cell
sprite_settings = {
// Non anti-aliased settings
let circle = {
map: circle_sprite,
transparent: true
}
// Anti-aliased settings
let circle_aa = {
map: circle_sprite_aa,
transparent: true,
alphaTest: 0.5
}
// Switch out which settings object is returned to change settings
return circle;
}
Insert cell
Insert cell
Insert cell
points = {
let pointsGeometry = new THREE.Geometry();
let colors = [];
for (let datum of generated_points) {
// Set vector coordinates from data
let vertex = new THREE.Vector3(datum.position[0], datum.position[1], 0);
pointsGeometry.vertices.push(vertex);
let color = new THREE.Color(color_array[datum.group]);
colors.push(color);
}
pointsGeometry.colors = colors;
let pointsMaterial = new THREE.PointsMaterial({
size: 8,
sizeAttenuation: false,
vertexColors: THREE.VertexColors,
});
// Add settings from sprite_settings
for (let setting in sprite_settings) {
pointsMaterial[setting] = sprite_settings[setting];
}
return new THREE.Points(pointsGeometry, pointsMaterial);
}
Insert cell
Insert cell
Insert cell
Insert cell
raycaster = new THREE.Raycaster()
Insert cell
{
view.on("mousemove", () => {
let [mouseX, mouseY] = d3.mouse(view.node());
let mouse_position = [mouseX, mouseY];
checkIntersects(mouse_position);
});
return view;
}
Insert cell
Insert cell
function mouseToThree(mouseX, mouseY) {
return new THREE.Vector3(
mouseX / viz_width * 2 - 1,
-(mouseY / height) * 2 + 1,
1
);
}
Insert cell
Insert cell
function checkIntersects(mouse_position) {
let mouse_vector = mouseToThree(...mouse_position);
raycaster.setFromCamera(mouse_vector, camera);
let intersects = raycaster.intersectObject(points);
if (intersects[0]) {
let sorted_intersects = sortIntersectsByDistanceToRay(intersects);
let intersect = sorted_intersects[0];
let index = intersect.index;
let datum = generated_points[index];
highlightPoint(datum);
showTooltip(mouse_position, datum);
} else {
removeHighlights();
hideTooltip();
}
}
Insert cell
Insert cell
{
raycaster.params.Points.threshold = 1;
return raycaster;
}
Insert cell
Insert cell
function sortIntersectsByDistanceToRay(intersects) {
return _.sortBy(intersects, "distanceToRay");
}
Insert cell
Insert cell
hoverContainer = new THREE.Object3D()
Insert cell
{
scene.add(hoverContainer);
return scene;
}
Insert cell
Insert cell
function highlightPoint(datum) {
removeHighlights();
let geometry = new THREE.Geometry();
geometry.vertices.push(
new THREE.Vector3(
datum.position[0],
datum.position[1],
0
)
);
geometry.colors = [ new THREE.Color(color_array[datum.group]) ];

let material = new THREE.PointsMaterial({
size: 26,
sizeAttenuation: false,
vertexColors: THREE.VertexColors
});
// Add settings from sprite settings
for (let setting in sprite_settings) {
material[setting] = sprite_settings[setting];
}
let point = new THREE.Points(geometry, material);
hoverContainer.add(point);
}
Insert cell
Insert cell
function removeHighlights() {
hoverContainer.remove(...hoverContainer.children);
}
Insert cell
Insert cell
{
view.on("mouseleave", () => {
removeHighlights()
})
return view;
}
Insert cell
Insert cell
tooltip_state = { return { display: "none" } }
Insert cell
function showTooltip(mouse_position, datum) {
let tooltip_width = 120;
let x_offset = -tooltip_width/2;
let y_offset = 30;
tooltip_state.display = "block";
tooltip_state.left = mouse_position[0] + x_offset;
tooltip_state.top = mouse_position[1] + y_offset;
tooltip_state.name = datum.name;
tooltip_state.group = datum.group;
}
Insert cell
function hideTooltip() {
tooltip_state.display = "none";
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
function toRadians (angle) {
return angle * (Math.PI / 180);
}
Insert cell
mutable viz_width = Math.min(width, 700)
Insert cell
height = 350
Insert cell
generated_points = {
let radius = 25;
// Random point in circle code from https://stackoverflow.com/questions/32642399/simplest-way-to-plot-points-randomly-inside-a-circle
function randomPosition(radius) {
var pt_angle = Math.random() * 2 * Math.PI;
var pt_radius_sq = Math.random() * radius * radius;
var pt_x = Math.sqrt(pt_radius_sq) * Math.cos(pt_angle);
var pt_y = Math.sqrt(pt_radius_sq) * Math.sin(pt_angle);
return [pt_x, pt_y];
}
let data_points = [];
for (let i = 0; i < point_num; i++) {
let position = randomPosition(radius);
let name = 'Point ' + i;
let group = Math.floor(Math.random() * 6);
let point = { position, name, group };
data_points.push(point);
}
return data_points;
}
Insert cell
point_num = 1000
Insert cell
color_array = [
"#1f78b4",
"#b2df8a",
"#33a02c",
"#fb9a99",
"#e31a1c",
"#fdbf6f",
"#ff7f00",
"#6a3d9a",
"#cab2d6",
"#ffff99"
]

Insert cell
renderer = new THREE.WebGLRenderer({antialias: true})
Insert cell
Insert cell
Insert cell
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more