Public
Edited
May 9, 2023
1 fork
13 stars
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
function* setup() {
// Basic setup
let this_width = 0.8 * width;
let height = 0.7 * d3.min([this_width, window.screen.height]);
let container = d3
.create('div')
.style('width', this_width.toString() + 'px')
.style('height', height.toString() + 'px');

let scene = container
.append('x3d')
.attr('width', this_width.toString() + 'px')
.attr('height', height.toString() + 'px')
.append('scene');
scene
.append('viewpoint')
.attr('position', '11.08014 -3.40616 4.08709')
.attr('orientation', '0.61973 0.43866 0.65078 1.70872');

// Add the cone
let coneTransform = scene
.append('transform')
.attr('rotation', `1 0 0 ${Math.PI / 2}`);
let transform1 = coneTransform
.append('transform')
.attr('translation', '0 -1.5 0');
let shape = transform1.append('shape');
shape
.append('appearance')
.append('material')
.attr('diffuseColor', `0.2 0.2 0.8`)
.attr('specularColor', '0.2 0.2 0.2');
shape
.append('cone')
.attr('subdivision', '128')
.attr('height', 3)
.attr('bottomRadius', 3)
.attr('bottom', 'false')
.attr('solid', 'false');
let transform2 = coneTransform
.append('transform')
.attr('translation', '0 1.5 0')
.attr('rotation', `0 0 1 ${Math.PI}`);
shape = transform2.append('shape');
shape
.append('appearance')
.append('material')
.attr('diffuseColor', `0.2 0.2 0.8`)
.attr('specularColor', '0.2 0.2 0.2');
shape
.append('cone')
.attr('subdivision', '128')
.attr('height', 3)
.attr('bottomRadius', 3)
.attr('bottom', 'false')
.attr('solid', 'false');

// Add the plane
let translation = scene
.append('transform')
.attr('id', 'plane_translation')
.attr('translation', '0 0 -1');
let plane_rotation = translation
.append('transform')
.attr('id', 'plane_rotation')
.attr('rotation', `1 0 0 ${Math.PI / 6}`);
let planeShape = plane_rotation.append('shape').attr('id', 'plane');
planeShape
.append('appearance')
.append('material')
.attr('transparency', '0.25')
.attr('diffuseColor', `0.8 0.8 0.2`)
.attr('specularColor', '0.2 0.2 0.2');
planeShape
.append('plane')
.attr('solid', 'false')
.attr('size', '8 8');

// Add all the sections as curves on the cone.
// Initial transparency will be set to 1; curve
// will only be shown once selected.
let sections = scene.append('group').attr('id', 'sections');

// Add the point
let point = sections.append('group').attr('id', 'point');
shape = point.append('shape');
shape
.append('appearance')
.append('material')
.attr('transparency', 1)
.attr('diffuseColor', '0 0 0');
shape.append('sphere').attr('radius', '0.05');

// Add the circle
let circle_points = d3.range(-1, 102).map(function(t) {
return [
2 * Math.cos((2 * Math.PI * t) / 100),
2 * Math.sin((2 * Math.PI * t) / 100),
-2
];
});
sections.append(() =>
create_tube(circle_points, 0.02, {
cross_vector: [0, 0, 1],
m: 6,
diffuseColor: '0 0 0',
transparency: '1',
id: 'circle'
})
);

// Add the ellipse
let ellipse_points = d3.range(-1, 102).map(function(t) {
let tt = (2 * Math.PI * t) / 100;
let sqrt3 = Math.sqrt(3);
let cost = Math.cos(tt);
return [
Math.sqrt(1.5) * Math.sin(tt),
-(sqrt3 + 3 * cost) / 2,
-(3 + sqrt3 * cost) / 2
];
});
sections.append(() =>
create_tube(ellipse_points, 0.02, {
cross_vector: [0, -Math.sqrt(3) / 2, 1 / 2],
m: 12,
diffuseColor: '0 0 0',
transparency: '1',
id: 'ellipse'
})
);

// Add the parabola
let parabola_points = d3.range(-21, 22).map(function(x) {
let xx = (Math.sqrt(5) * x) / 20;
return [xx, (1 - xx ** 2) / 2, (-1 - xx ** 2) / 2];
});
sections.append(() =>
create_tube(parabola_points, 0.02, {
cross_vector: [0, -1, 1],
m: 12,
diffuseColor: '0 0 0',
transparency: '1',
id: 'parabola'
})
);

// Add the hyperbola in two parts
let hyperbola = sections.append('group').attr('id', 'hyperbola');

let xwidth = 20 * Math.acosh(3);
let dx = xwidth / 40;

let hyperbola_points1 = d3
.range(-xwidth - dx, xwidth + 2 * dx, dx)
.map(function(t) {
let tt = t / 20;
return [-Math.sinh(tt), 1, Math.cosh(tt)];
});
hyperbola.append(() =>
create_tube(hyperbola_points1, 0.02, {
cross_vector: [0, 1, 0],
m: 12,
diffuseColor: '0 0 0',
transparency: '1',
id: 'hyperbola_points1'
})
);
let hyperbola_points2 = hyperbola_points1.map(([x, y, z]) => [x, y, -z]);
hyperbola.append(() =>
create_tube(hyperbola_points2, 0.02, {
cross_vector: [0, 1, 0],
m: 12,
diffuseColor: '0 0 0',
transparency: '1',
id: 'hyperbola_points2'
})
);

// Add the single line
let single_line_transform = sections
.append('transform')
.attr('id', 'line')
.attr('rotation', `1 0 0 ${Math.PI / 4}`);
shape = single_line_transform.append('shape');
shape
.append('appearance')
.append('material')
.attr('transparency', 1)
.attr('diffuseColor', '0 0 0');
shape
.append('cylinder')
.attr('height', `${Math.sqrt(2) * 6}`)
.attr('radius', '0.02');

// Add the intersecting lines in two parts
let lines = sections.append('group').attr('id', 'lines');
let line1_transform = lines
.append('transform')
.attr(
'rotation',
`0 ${-Math.sqrt(3) / 2} 0.5 ${Math.acos(Math.sqrt(2 / 3))}`
)
.append('transform')
.attr('rotation', `1 0 0 ${Math.PI / 3}`);
shape = line1_transform.append('shape');
shape
.append('appearance')
.append('material')
.attr('transparency', 1)
.attr('diffuseColor', '0 0 0');
shape
.append('cylinder')
.attr('height', `${Math.sqrt(2) * 6}`)
.attr('radius', '0.02');
let line2_transform = lines
.append('transform')
.attr(
'rotation',
`0 ${-Math.sqrt(3) / 2} 0.5 ${-Math.acos(Math.sqrt(2 / 3))}`
)
.append('transform')
.attr('rotation', `1 0 0 ${Math.PI / 3}`);
shape = line2_transform.append('shape');
shape
.append('appearance')
.append('material')
.attr('transparency', 1)
.attr('diffuseColor', '0 0 0');
shape
.append('cylinder')
.attr('height', `${Math.sqrt(2) * 6}`)
.attr('radius', '0.02');

// Yield and reload
yield container.node();
x3dom.reload();
}
Insert cell
Insert cell
// Use the slider value to set the plane transparency
transparency = {
d3.select(x3d)
.select('#plane')
.select('material')
.attr('transparency', `${plane_transparency}`);
}
Insert cell
// Use the radio button to set the section
select = {
// Dim whatever section is currently shown
d3.select(x3d)
.select("#sections")
.selectAll("material")
.transition()
.duration("150")
.attr("transparency", 1);

// Get the transforms of the plane with their current values
let translation_node = d3.select(x3d).select("#plane_translation");
let rotation_node = d3.select(x3d).select("#plane_rotation");
let current_translation = translation_node.attr("translation");
let current_rotation = rotation_node.attr("rotation");

// Set up the values for the new transform base on which
// radio button is pushed.
let new_translation, new_rotation;
if (section == "point") {
new_translation = "0 0 0";
new_rotation = "1 0 0 0";
} else if (section == "circle") {
new_translation = "0 0 -2";
new_rotation = "1 0 0 0";
} else if (section == "ellipse") {
new_translation = "0 0 -1";
new_rotation = `1 0 0 ${Math.PI / 6}`;
} else if (section == "hyperbola") {
new_translation = "0 1 0";
new_rotation = `1 0 0 ${Math.PI / 2}`;
} else if (section == "parabola") {
new_translation = "0 0 -1";
new_rotation = `1 0 0 ${Math.PI / 4}`;
} else if (section == "lines") {
new_translation = "0 0 0";
new_rotation = `1 0 0 ${Math.PI / 3}`;
} else if (section == "line") {
new_translation = "0 0 0";
new_rotation = `1 0 0 ${Math.PI / 4}`;
}

// Interpolate from the current transform to the new
let i = 0;
let translation_interpolator = d3.interpolate(
current_translation,
new_translation
);
let rotation_interpolator = d3.interpolate(current_rotation, new_rotation);
while (i < 20) {
yield Promises.delay(5).then(function () {
i++;
translation_node.attr("translation", translation_interpolator(i / 20));
rotation_node.attr("rotation", rotation_interpolator(i / 20));
});
}

// Reveal the new intersection
d3.select(x3d)
.select("#" + section)
.selectAll("material")
.attr("transparency", "1")
.transition()
.duration(150)
.attr("transparency", "0");
}
Insert cell
Insert cell
function make_axes() {
let axis_group = d3.create('group');
let yaxis_transform = axis_group
.append('transform')
.attr('translation', '0 0 0')
.attr('DEF', 'axis');
let yaxis_shape = yaxis_transform.append('shape');
yaxis_shape
.append('appearance')
.append('material')
.attr('diffuseColor', '0 0 0 0.5')
.attr('transparency', 0.2);
yaxis_shape
.append('cylinder')
.attr('radius', 0.01)
.attr('subdivision', '32')
.attr('height', '8');

let xaxis_transform = axis_group
.append('transform')
.attr('rotation', '0,0,1,1.570796')
.attr('translation', '-2,0,0')
.attr('scale', '1 1.8 1')
.append('transform')
.attr('USE', 'axis');

let zaxis_transform = axis_group
.append('transform')
.attr('rotation', '1,0,0,1.570796')
.attr('scale', '1 1.3 1')
.append('transform')
.attr('USE', 'axis');

return axis_group;
}
Insert cell
function create_indexedLineSet(pts) {
let shape = d3.create('shape');
let appearance = shape.append('appearance');
let material = appearance.append('material');
let indexedLineSet = shape
.append('IndexedLineSet')
.attr('coordIndex', d3.range(pts.length).toString());
indexedLineSet
.append('coordinate')
.attr('point', String.concat(pts).replace(/,/g, ' '));
return shape;
}
Insert cell
Insert cell
d3 = require("d3")
Insert cell
x3dom = require('x3dom').catch(() => window['x3dom'])
Insert cell
import { radio, slider } from "@jashkenas/inputs"
Insert cell
import { create_tube } from "@mcmcclur/space-curves-and-tubes"
Insert cell
// Supress the annoying dashed box that appears around X3Dom display.
html`<style>
canvas {
outline: none;
}
</style>`
Insert cell
d3.select(x3d).select("X3D").node().outerHTML
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