Skip to main content

Light Speed Visualization

· 3 min read

Light is the fastest thing in the universe, but how fast is it really? Scroll through the demo below to see how how fast it really is.

Demo​

Sources​

Source Code​

View source

LightSpeed.tsx
export default function LightSpeed() {
let { withBaseUrl } = useBaseUrlUtils();

useEffect(() => {
const c = 299792458;
const width = 500;
const height = 350;
const mpsToMph = 2.23694;
const margin = { top: 10, bottom: 30, left: 10, right: 10 };

let dataContainer: string | any[];
let canvas: d3.Selection<SVGGElement, unknown, HTMLElement, any>,
objects: d3.Selection<SVGGElement, unknown, SVGGElement, unknown>,
gX: d3.Selection<SVGGElement, unknown, HTMLElement, any>;

let x = d3.scaleLog().domain([6e1, c]).range([0, width]);
let xAxis = d3.axisBottom(x);

let currentScale = 10;
let currentTranslateX = 0;
let imageRatios;
let hsize = 80;
let scaleFactor = 12;
let objectNumber;
let alreadyLoaded = false;
let wrapper = d3.select(`.${styles.wrapper}`);

let svg = wrapper
.append("svg")
.attr(
"viewBox",
"0 0 " +
(width + margin.left + margin.right) +
" " +
(height + margin.top + margin.bottom)
)
.attr("width", "100%");
canvas = svg
.append("g")
.attr("id", "canvas")
.attr("width", width)
.attr("height", height)
.attr(
"transform",
"translate(" + margin.left + "," + margin.top + ")"
);
let tooltip = wrapper
.append("div")
.attr("class", styles.tooltip)
.style("opacity", 0);

function updateGraph() {
let newTransform = d3.zoomIdentity
.translate(currentTranslateX, height / 2)
.scale(currentScale);
let new_x = newTransform.rescaleX(x);
gX.call(xAxis.scale(new_x));
objects.data(dataContainer).attr("transform", (d) => {
return "translate(" + new_x(d.speed) + "," + height / 2 + ")";
});
}

function motion(event: WheelEvent) {
let dx = event.deltaX;
let dy = event.deltaY;
let amount: number;
if (Math.abs(dx) > Math.abs(dy)) {
amount = -dx;
} else {
amount = -dy;
}
currentTranslateX += amount;
currentTranslateX = Math.max(
-490 * currentScale,
currentTranslateX
);
currentTranslateX = Math.min(40, currentTranslateX);
event.preventDefault();
event.stopPropagation();
updateGraph();
}

function updateRectanglesIfComplete() {
let proceed = true;
for (let i = 0; i < dataContainer.length; i++) {
proceed = proceed && imageRatios[dataContainer[i].name] != null;
}
if (proceed) {
if (alreadyLoaded) return;
alreadyLoaded = true;

objects
.append("rect")
.attr("height", hsize)
.attr("width", function (d: any) {
return imageRatios[d.name] * hsize;
})
.attr("stroke", "white")
.style("stroke-width", 2)
.attr("y", function (d, i) {
return (
-hsize / 2 +
scaleFactor * i -
(scaleFactor * objectNumber) / 2
);
});

objects
.append("image")
.attr("height", hsize)
.attr("width", function (d: any) {
return imageRatios[d.name] * hsize;
})
.attr("y", function (d, i) {
return (
-hsize / 2 +
scaleFactor * i -
(scaleFactor * objectNumber) / 2
);
})
.attr("href", (d: any) => {
return withBaseUrl(`light-speed/${d.image}`);
});
} else {
setTimeout(updateRectanglesIfComplete, 1000);
}
}

d3.json(withBaseUrl("light-speed/FastThings.json")).then(
(data: any[]) => {
dataContainer = JSON.parse(JSON.stringify(data));
for (let i = 0; i < dataContainer.length; i++) {
dataContainer[i].speed =
Math.round(dataContainer[i].speed * mpsToMph * 100) /
100.0;
}
imageRatios = {};
for (let i = 0; i < dataContainer.length; i++) {
let img = new Image();
img.onload = function (this: HTMLImageElement, ev: Event) {
imageRatios[dataContainer[i].name] =
this.width / this.height;
updateRectanglesIfComplete();
};
img.src = withBaseUrl(
"light-speed/" + dataContainer[i].image
);
}

objects = canvas
.selectAll(".objects")
.data(data, function (d: any) {
return d.name;
})
.enter()
.append("g")
.attr("class", "objects")
.attr("transform", (d) => {
return (
"translate(" + x(d.speed) + "," + height / 2 + ")"
);
})
.on("mouseover", function (evt: MouseEvent, d) {
let tooltipText =
'<span style="font-weight: bold; font-size: 12px">' +
d.name +
"</span><br/>" +
'<span style="font-style: italic">' +
d.speed +
"</span>" +
" mph" +
"</br>" +
"</br>" +
d.description;
tooltip
.transition()
.duration(200)
.style("opacity", 0.9);
tooltip
.html(tooltipText)
.style("left", evt.offsetX + "px")
.style("top", evt.offsetY - 28 + "px");
})
.on("mouseout", function (d) {
tooltip.transition().duration(500).style("opacity", 0);
});

objectNumber = data.length;

gX = canvas
.append("g")
.attr("class", "axis axis-x")
.attr("transform", "translate(" + 0 + "," + height + ")")
.call(xAxis);
updateGraph();
svg.on("wheel.zoom", motion);
}
);
}, []);

return <div className={styles.wrapper} />;
}