Public
Edited
May 9, 2023
1 fork
13 stars
Also listed in…
Math
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

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