I am working on a timeline in d3. My expectations are that it shows the day of today, and then the user can zoom in/out to see more times and dates. However to start with it gets me a date in 2034 and then after zooming, it gets me something closer but still pretty wrong.
This is the code (wrapped in a react component):
import { useRef, useEffect } from 'react';
import * as d3 from 'd3';
const D3Timeline = () => {
const svgRef = useRef<SVGSVGElement>(null);
const initializedRef = useRef(false); // Flag to track initialization
const height = 100;
const width = 600;
useEffect(() => {
if (initializedRef.current) return; // Ensure initialization only happens once
initializedRef.current = true;
const svg = d3
.select(svgRef.current)
.attr('width', width)
.attr('height', height);
const now = new Date();
const oneYearAgo = new Date(
now.getFullYear(),
now.getMonth() - 1,
now.getDate()
);
const oneYearLater = new Date(
now.getFullYear(),
now.getMonth() + 1,
now.getDate()
);
const initialStartDate = new Date(now.getTime() - 6 * 60 * 60 * 1000); // 6 hours before now
const initialEndDate = new Date(now.getTime() + 6 * 60 * 60 * 1000); // 6 hours after now
const x = d3
.scaleTime()
.domain([oneYearAgo, oneYearLater])
.range([0, width]);
const xAxisGroup = svg
.append('g')
.attr('transform', `translate(0,${height - 20})`);
const centerLine = svg
.append('line')
.attr('x1', width / 2)
.attr('y1', 0)
.attr('x2', width / 2)
.attr('y2', height)
.attr('stroke', 'red')
.attr('stroke-width', 2);
const zoom = d3
.zoom()
.scaleExtent([1, 365]) // Allow zooming from 1 day to 3 years
.translateExtent([
[0, 0],
[width, height],
])
.extent([
[0, 0],
[width, height],
])
.on('zoom', zoomed);
// Initial axis rendering
xAxisGroup.call(
d3
.axisBottom(x)
.ticks(width / 80)
.tickSizeOuter(0)
);
// Calculate the initial scale factor to fit 12 hours in the viewport
const initialScale = width / (x(initialEndDate) - x(initialStartDate));
const initialTranslate = -x(now) * initialScale + width / 2;
const initialZoom = d3.zoomIdentity
.scale(initialScale)
.translate(initialTranslate, 0);
svg.call(zoom).call(zoom.transform, initialZoom);
function zoomed(event) {
const transform = event.transform;
const xz = transform.rescaleX(x);
xAxisGroup.call(
d3
.axisBottom(xz)
.ticks(width / 80)
.tickSizeOuter(0)
);
// Get the center date
const centerDate = xz.invert(width / 2);
console.log(centerDate);
}
// Handle zoom using the mouse wheel
svg.on('wheel', (event) => {
event.preventDefault();
const direction = event.deltaY > 0 ? 1.1 : 0.9;
const zoomLevel = d3.zoomTransform(svg.node()).k * direction;
const zoomTransform = d3.zoomIdentity.scale(zoomLevel);
svg.transition().duration(500).call(zoom.transform, zoomTransform);
});
}, []);
return <svg ref={svgRef}></svg>;
};
You can see it working in here:
https://stackblitz.com/edit/vitejs-vite-xgiskr?file=src%2Fcomponents%2FD3Timeline.tsx,src%2Fmain.tsx&terminal=dev