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

One platform to build and deploy the best data apps

Experiment and prototype by building visualizations in live JavaScript notebooks. Collaborate with your team and decide which concepts to build out.
Use Observable Framework to build data apps locally. Use data loaders to build in any language or library, including Python, SQL, and R.
Seamlessly deploy to Observable. Test before you ship, use automatic deploy-on-commit, and ensure your projects are always up-to-date.
Learn more