Published
Edited
Dec 31, 2019
53 stars
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
viewof replay2 = html`<button>Restart animation</button>`
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
canvas = d3.select("#gl1").call(zoomBehavior)
Insert cell
html`<canvas id="fov_image" style="background:#efefef" width=${width} height=${canvas_height}></canvas>`
Insert cell
Insert cell
d3.zoomIdentity
Insert cell
Insert cell
setUpZoom()
Insert cell
Insert cell
Insert cell
md`The zoom modifier just uses the time to continuously zoom in and out smoothly.`
Insert cell
zoom_modifier = 0//Math.sin(now / 2000)* 2.3 + 1.2
Insert cell
Insert cell
Insert cell
Insert cell
far_year = near_year + year_size
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
camera = new THREE.PerspectiveCamera(90, width / viz_height, 1, 1000);
Insert cell
Insert cell
function zoomEvent(zoom) {
let canvas_inputs = {}
const scale = zoom.k;
camera.position.x = -(zoom.x - width/2) / scale;
camera.position.y = (zoom.y - viz_height/2) / scale;
camera.position.z = get_camera_z(Math.log(scale));
camera.fov = needed_fov(viz_height, camera.position.z, scale)
// future compatibility
camera.near_plane = 1//camera.position.z * 2 // + near_year/1000
camera.far_plane = 10000//camera.position.z / 2//camera.position.z + far_year/1000
camera.updateProjectionMatrix()
renderer.render(scene, camera);

canvas_inputs.k = scale
canvas_inputs.x = camera.position.x
canvas_inputs.y = camera.position.y
canvas_inputs.z = camera.position.z
canvas_inputs.fov = camera.fov
canvas_inputs.near_plane = camera.near_plane
canvas_inputs.far_plane = camera.far_plane
drawCamera(canvas_inputs)
return canvas_inputs
}
Insert cell
needed_fov(850, 26, 200)
Insert cell
pointsMaterial = {
let point_size_adjuster = 1
if (binary == "Semantic zoom") {
point_size_adjuster = 2
} else if (binary == "Geometric zoom") {
point_size_adjuster = 0.25
} else if (binary == "Balanced zoom") {
point_size_adjuster = 0.85
}
return new THREE.PointsMaterial({
size: point_size * point_size_adjuster,
sizeAttenuation: sizeAttenuation,
vertexColors: THREE.VertexColors,
map: circle_sprite,
transparent: true
})
}
Insert cell
Insert cell
function needed_fov(viz_height, camera_z, scale) {
const fov_height = viz_height/scale
const half_fov_radians = Math.atan(fov_height/(2 * camera_z))
const half_fov = toDegrees(half_fov_radians)
return half_fov * 2
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
function drawCamera(canvas_inputs) {
let ctx = d3.select("#fov_image").node().getContext('2d')
let cx = width/2;
let cy = canvas_height/2;
ctx.clearRect(0, 0, width, canvas_height)

let scaled_z = canvas_inputs.z * canvas_scale;
let z_padding = canvas_z_padding * canvas_scale;
let y_adjust = -canvas_inputs.y * canvas_scale;

// Near and far threshold
ctx.beginPath();
ctx.moveTo(width - z_padding - scaled_z + canvas_inputs.near_plane * canvas_scale, 0)
ctx.lineTo(width - z_padding - scaled_z + canvas_inputs.far_plane * canvas_scale, 0)
ctx.lineTo(width - z_padding - scaled_z + canvas_inputs.far_plane * canvas_scale, canvas_height)
ctx.lineTo(width - z_padding - scaled_z + canvas_inputs.near_plane * canvas_scale, canvas_height)
ctx.closePath();
ctx.fillStyle = "#ddd";
ctx.fill();
// Z line
ctx.beginPath();
ctx.moveTo(width - z_padding, cy + y_adjust)
ctx.lineTo(width - z_padding - scaled_z, cy + y_adjust);
ctx.setLineDash([4, 4]);
ctx.lineWidth = 2;
ctx.stroke();
let half_fov = canvas_inputs.fov/2;
let half_fov_radians = toRadians(half_fov);
let fov_height = 2* Math.tan(half_fov_radians) * canvas_inputs.z;

// Field of view triangle
ctx.setLineDash([]);
ctx.beginPath();
ctx.moveTo(width - z_padding - scaled_z, cy + y_adjust)
ctx.lineTo(width - z_padding, cy - (fov_height / 2 * canvas_scale) + y_adjust);
ctx.lineTo(width - z_padding, cy + (fov_height / 2 * canvas_scale) + y_adjust);
ctx.closePath();
ctx.strokeStyle = "purple";
ctx.lineWidth = 3;
ctx.stroke();
// Camera circle
ctx.beginPath();
ctx.arc(width - z_padding - scaled_z, cy + y_adjust, 9, 0, Math.PI * 2, true);
ctx.lineWidth = 0;
ctx.fillStyle = "black";
ctx.fill();
// Screen indicator
ctx.beginPath();
ctx.moveTo(width - z_padding, cy - viz_height / 2 * canvas_scale);
ctx.lineTo(width - z_padding, cy + viz_height / 2 * canvas_scale);
ctx.lineWidth = 4;
ctx.strokeStyle = "blue";
ctx.stroke();
// Dots indicator
ctx.beginPath();
ctx.moveTo(width - z_padding, cy - points_radius * canvas_scale);
ctx.lineTo(width - z_padding, cy + points_radius * canvas_scale);
ctx.lineWidth = 6;
ctx.strokeStyle = "green";
ctx.stroke();
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
points = {
let pointsGeometry = new THREE.BufferGeometry();
const buffer = new ArrayBuffer(data.length * 3 * 4);
const color_buffer = new ArrayBuffer(data.length * 3 * 4);
const positions = new Float32Array(buffer)
const colors = new Float32Array(color_buffer);

data.forEach((datum, i) => {
positions.set(datum.position, i * 3)
const c = d3.color(colorscale(+datum.sentiment))
colors.set([c.r/255, c.g/255, c.b/255], i*3)
// colors.set([0, 0, 1], i*3)
})
let sizes = data.map(d => d.size)
sizes = Float32Array.from(sizes)
pointsGeometry.setAttribute('color', new THREE.BufferAttribute(colors, 3));
pointsGeometry.setAttribute('size', new THREE.BufferAttribute( sizes, 1 ))
pointsGeometry.setAttribute('position', new THREE.BufferAttribute( positions, 3))
return new THREE.Points(pointsGeometry, pointsMaterial);
}
Insert cell
colorscale = d3.scaleLinear().domain([-0.15, 0, 0.15]).range([d3.interpolatePuOr(0), d3.interpolatePuOr(.5), d3.interpolatePuOr(1)]).clamp(true)
Insert cell
Insert cell
d3 = require('d3@5')
Insert cell
function return_tile(depth, i, j) { return d3.tsv(`https://creatingdata.us/data/scatter/hathi/tiles/${depth}/${i}/${j}.tsv`).catch(err => Promise.resolve([])).then(d => d.map(e => {return {position: [+e.x, +e.y, -e.date/1000], ix: +e.ix, name: e.title, data: e, group: e.lc1.slice(1)}}))}
Insert cell
function children(depth, i, j) {
return [[depth * 2, i*2, j*2],[depth * 2, i*2+1, j*2],[depth * 2, i*2, j*2+1],[depth * 2, i*2+1, j*2+1]]
}
Insert cell
function load_points(n, start = [1, 0, 0], depth=0) {
//if (depth >= 257) {
// Debugging regress loop.
// return "YOWZA"
//}
const requests = []
return return_tile(...start).catch(err => []).then(
data => {
const last_point = data.slice(-1)[0]
// Break condition; only up to the nth point in the data.
if (last_point.ix > n) {
return data.filter(d => (+d.ix <= n))
}
// If unbroken, add points from all children tiles as well using this same function.
const kids = children(...start).map(ix => load_points(n, ix, depth+1))
return Promise.all(kids).then(kidders => {
return [].concat(...kidders, data);
})
})
}
Insert cell
old_data = load_points(500).then(p => {p.sort((a, b) => a.group == ''); return p})
Insert cell
import {slider, radio} from "@jashkenas/inputs"

Insert cell
data = d3.tsv("https://benschmidt.org/profCloud/d.tsv", function(row, i) {
row.position = [+row.x, +row.y, -1.900]
row.group = row.word.length
row.ix = i
row.size = 1/(i + 1)
return row
})
Insert cell
accent = d3.scaleOrdinal(d3.schemeAccent)
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