Public
Edited
May 4, 2022
2 stars
Insert cell
Insert cell
Insert cell
function cToHex(c) {
var hex = c.toString(16);
return hex.length == 1 ? "0" + hex : hex;
}
Insert cell
function greyToHex(f) {
return "#" + cToHex(Math.round((1-f)*255))+ cToHex(Math.round((1-f)*255)) + cToHex(Math.round((1-f)*255));
}
Insert cell
canvas = {
camera;
renderer.setSize(viz_width, height);
renderer.setPixelRatio(devicePixelRatio);
try {
while (true) {
renderer.render(scene, camera);
let html_render = html`<div style="position: relative; overflow: hidden;">
${renderer.domElement}
<div style="display: ${tooltip_state.display}; position: absolute; pointer-events: none; left: ${tooltip_state.left}px; top: ${tooltip_state.top}px; font-size: 13px; width: 240px; text-align: center; line-height: 1; padding: 6px; background: white; font-family: sans-serif;"><div style="padding: 4px; margin-bottom: 4px;">${tooltip_state.name}</div><div style="padding: 4px; background:lightgrey"><div>${[0,1,2,3,4].map((i) => "<span style=\"color:"+greyToHex(JSON.parse(tooltip_state.group)[i].weight)+"\">"+JSON.parse(tooltip_state.group)[i].word+"</span><span> </span>")}</div></div>
</div>`
yield html_render;
}
} finally {
renderer.dispose();
}
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
viewof pointSize = Inputs.range([0, 8], {value: 1, step: 0.1, label: "Point Size"})
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(0xffffff);
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(
"https://blog.fastforwardlabs.com/images/2018/02/circle-1518727951930.png"
)
Insert cell
Insert cell
Insert cell
circle_sprite_aa= new THREE.TextureLoader().load(
"https://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_aa;
}
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);
if( datum.group > -1 ) {
let color = new THREE.Color(color_array[datum.group]);
colors.push(color);
} else {
let color = new THREE.Color(0xefefef);
colors.push(color);
}
}
pointsGeometry.colors = colors;
let pointsMaterial = new THREE.PointsMaterial({
size: pointSize,
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
)
);
if( datum.group > -1 ) {
geometry.colors = [new THREE.Color(color_array[datum.group])];
} else {
geometry.colors = [new THREE.Color(0xefefef)];
}

let material = new THREE.PointsMaterial({
size: 10,
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
empty_tooltip_group = "{\"id\": -1, \"max\": 0.0, \"0\": {\"word\": \"\", \"weight\": 0.0}, \"1\": {\"word\": \"\", \"weight\": 0.0}, \"2\": {\"word\": \"\", \"weight\": 0.0}, \"3\": {\"word\": \"\", \"weight\": 0.0}, \"4\": {\"word\": \"\", \"weight\": 0.0}}"
Insert cell
tooltip_state = {
return {display: "none", group: empty_tooltip_group } }
Insert cell
function showTooltip(mouse_position, datum) {
let tooltip_width = 240;
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;
if(datum.group>-1) {
tooltip_state.group = cluster_signature[datum.group].label_data;
} else {
tooltip_state.group = empty_tooltip_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, 1400)
Insert cell
height = 700
Insert cell
unmapped_data = FileAttachment("PSC_cluster_scatter.json").json()
Insert cell
generated_points = unmapped_data.map(d=> Object.assign(
{position: [d.x, d.y],
name: d.text,
group: d.cluster_id
}
))
Insert cell
raw_cluster_data = FileAttachment("PSC_clusters.json").json()
Insert cell
function compare( a, b ) {
if ( a.id < b.id ){
return -1;
}
if ( a.id > b.id ){
return 1;
}
return 0;
}

Insert cell
cluster_signature = raw_cluster_data.sort(compare)
Insert cell
point_num = generated_points.length
Insert cell
function buildColorArray(n) {
let color_array = []
for (let step = 0; step < n; step++) {
let c = Math.floor(Math.random()*16777215).toString(16);
color_array.push("#"+c)
}
return color_array
}
Insert cell
color_array = buildColorArray(point_num)
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