Public
Edited
Apr 17, 2023
1 fork
7 stars
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
changeablePlot = solarSystemPlot(generateOrbits(sol), {
seed: seedRange,
nudge: { x: 0.5, y: 0.2 }
})
Insert cell
Insert cell
originalPlot = solarSystemPlot(generateOrbits(sol), {
simulate: true
})
Insert cell
Insert cell
<img src="${await FileAttachment("image.png").url()}" width="75%"/>
Insert cell
Insert cell
Insert cell
Insert cell
<style>
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;900&display=swap');
.orbit {
fill: none;
stroke: black;
stroke-width: 0.5px;
}
.planet {
fill: #730517;
fill-opacity: 0.75;
stroke-width: 1px;
stroke: black;
}
.planet-label{
font-family: Inter;
font-weight: 700;
font-size: 14px;
}
</style>
Insert cell
Insert cell
function solarSystemPlot(
orbits,
{
seed = 0.42,
width = 400,
height = 400,
simulate = false,
showLabels = true,
animate = false,
updateMilliseconds = 50,
minSpeed = 2,
maxSpeed = 4,
margin = { top: 50, right: 80, bottom: 50, left: 50 },
nudge = { x: 0.3, y: -0.25 },
font = {
family: "Inter",
size: "14px",
weight: "700"
}
} = {}
) {
let planetPositions = randomizePlanetPositions(orbits, seed);

const maxRadius = d3.max(orbits.map((d) => d.radius));

const scaleX = d3
.scaleLinear()
.domain([-maxRadius, maxRadius])
.range([-width / 2, width / 2]);

const scaleY = d3
.scaleLinear()
.domain([-maxRadius, maxRadius])
.range([-height / 2, height / 2]);

const domSvg = html`<svg></svg>`;

const svg = d3
.select(domSvg)
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr(
"transform",
`translate(${width / 2 + margin.left}, ${height / 2 + margin.top})`
);

function adjustX(val) {
return val + nudge.x;
}

function adjustY(val) {
return val + nudge.y;
}

const orbitRings = svg
.selectAll("circle.orbit")
.data(orbits)
.enter()
.append("circle")
.attr("class", "orbit")
.attr("r", (d) => scaleX(d.radius))
.attr("cx", 0)
.attr("cy", 0);

const planets = svg
.selectAll("circle.planet")
.data(planetPositions)
.enter()
.append("circle")
.attr("class", "planet")
.attr("r", 6)
.attr("cx", (d) => scaleX(d.x))
.attr("cy", (d) => scaleY(d.y));

if (animate) {
function generateRandomSpeeds(planetNames) {
const speeds = {};
planetNames.forEach((planet) => {
speeds[planet] = Math.random() * (maxSpeed - minSpeed) + minSpeed;
});

return speeds;
}

let planetSpeeds = generateRandomSpeeds(
planetPositions.map((d) => d.planet)
);

function updatePlanetPositions(orbits, elapsedTime) {
const updatedPositions = orbits.map((orbit) => {
const speed = planetSpeeds[orbit.planet] || 1;
const angle = (elapsedTime * speed * Math.PI) / 180;
const x = orbit.radius * Math.cos(angle);
const y = orbit.radius * Math.sin(angle);
return { ...orbit, x, y };
});

return updatedPositions;
}

function updateVisualization() {
const elapsedTime = Date.now() / 1000;
planetPositions = updatePlanetPositions(orbits, elapsedTime);

planets
.data(planetPositions)
.attr("cx", (d) => scaleX(d.x))
.attr("cy", (d) => scaleY(d.y));
}

d3.interval(() => {
updateVisualization();
}, updateMilliseconds);

showLabels = false;
}

if (showLabels) {
const labels = svg
.selectAll("text")
.data(planetPositions)
.enter()
.append("text")
.text((d) => d.planet)
.attr("x", (d) => scaleX(adjustX(d.x)))
.attr("y", (d) => scaleY(adjustY(d.y)))
.attr("class", "planet-label")
.attr("text-anchor", "start");

if (simulate) {
function ticked() {
labels.attr("x", (d) => d.x).attr("y", (d) => d.y);
}

const simulation = d3
.forceSimulation(planetPositions)
.force("collision", d3.forceCollide(12)) // Prevents label overlap
.force("x", d3.forceX((d) => scaleX(adjustX(d.x))).strength(0.1))
.force("y", d3.forceY((d) => scaleY(adjustY(d.y))).strength(0.1))
.on("tick", ticked);
}
}

return domSvg;
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
trad9AUSqrt = [0.39, 0.72, 1.0, 1.52, 5.2, 9.58, 19.18, 30.07, 39.48].map((d) =>
Math.sqrt(d)
)
Insert cell
trad9OrbsSq = trad9.map((d, i) => {
return {
planet: d,
radius: trad9AUSqrt[i]
};
})
Insert cell
Insert cell
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