Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Suggest to have zoom feature #24

Open
HarryF514 opened this issue Dec 29, 2020 · 3 comments
Open

Suggest to have zoom feature #24

HarryF514 opened this issue Dec 29, 2020 · 3 comments
Assignees
Milestone

Comments

@HarryF514
Copy link

image

if some milestones's range are much smaller than others, some of them look crowded. If a zoom in feature could be added. that would be great.

@walterra walterra self-assigned this Jan 1, 2021
@walterra walterra added this to the 2.0.0 milestone Jan 1, 2021
@PriyavKaneria
Copy link

I implemented a zoom feature on my own in svelte, if it helps anyone

<script lang="ts">
	// ... your code
	
	$: zoomBind = [0]; // I used this to bind to a shadcn slider so it's an array, feel free to use number type and single variable
	$: zoomLevel = zoomBind[0];
	$: zoomLevel != undefined && updateTimeline && updateTimeline();
	let mouseXPosition = 0;
	let updateTimeline: () => void;
	let timelineElement: HTMLDivElement;
	let timelineTranslateX = 0;
	onMount(() => {
		// setup timeline
		const vis = milestones('#timeline')
			.mapping({
				timestamp: 'timestamp',
				text: 'title'
			})
			.parseTime('%Y-%m-%d %H:%M:%S')
			.aggregateBy('year') // Start with years
			.optimize(true)
			.orientation('horizontal')
			.useLabels(true)
			.autoResize(true)
			.render(
				projects.map((p) => ({
					timestamp: p.timestamp,
					title: `${p.title}`,
					url: p.link
				}))
			);

		// Function to update timeline based on scroll
		updateTimeline = () => {
			let aggregationLevel;
			let fontSize;
			let labelFormat;

			// Dynamically set aggregation level and font size based on scrollY
			if (zoomLevel < 3) {
				aggregationLevel = 'year';
				fontSize = '14px'; // Normal font size for years
				labelFormat = '%Y'; // Adjust label format for years
			} else if (zoomLevel < 6) {
				aggregationLevel = 'month';
				fontSize = '18px'; // Zoomed in font size for months
				labelFormat = '%b %Y'; // Adjust label format for months
			} else {
				aggregationLevel = 'week';
				fontSize = '22px'; // Even larger font size for weeks
				labelFormat = '%b, %Y'; // Adjust label format for weeks
			}

			// Dynamically adjust timeline based on aggregation level and font size
			vis
				.aggregateBy(aggregationLevel)
				.useLabels(true)
				.labelFormat(labelFormat)
				.render(
					projects.map((p) => ({
						timestamp: p.timestamp,
						title: `${p.title}`,
						url: p.link
					}))
				);

			// Apply the font size to the timeline labels
			// document.getElementById('timeline')!.style.fontSize = fontSize;

			// Keep the mouse position at the center of the timeline
			// increase the width of the timeline to keep the mouse position at the center
			// scale it acc to timelineScroll
			const timelineScroll = zoomLevel * 100;
			timelineElement.parentElement!.style.width = `calc(100% + ${timelineScroll}px)`;
			// translate the timeline to keep the mouseXPosition at the center
			const timelineBoudingRect = timelineElement.getBoundingClientRect();
			const timelineWidth = timelineBoudingRect.width;
			const offsetLeft = timelineBoudingRect.left;
			timelineTranslateX = ((mouseXPosition - offsetLeft) * timelineScroll) / timelineWidth;

			timelineElement.style.transform = `translateX(-${timelineTranslateX}px)`;
			vis.render();
		};
	});

	const handleWheel = (event: WheelEvent) => {
		event.preventDefault();
		if (event.deltaY < 0 && zoomLevel < 10) {
			zoomBind[0] += 1;
		} else if (event.deltaY > 0 && zoomLevel > 0) {
			zoomBind[0] -= 1;
		}
		mouseXPosition = event.clientX;
		updateTimeline();
	};

	let dragging = false;
	let dragStartX = 0;
	const handleDrag = (event: MouseEvent) => {
		if (!dragging || zoomLevel === 0) return;
		event.preventDefault();

		// translate limits
		if (timelineTranslateX + dragStartX - event.clientX < 0) {
			return;
		} else if (timelineTranslateX + dragStartX - event.clientX > zoomLevel * 100) {
			return;
		}
		mouseXPosition = event.clientX;
		timelineElement.style.transform = `translateX(-${timelineTranslateX + dragStartX - event.clientX}px)`;
	};
	
</script>

	<div
		on:wheel={handleWheel}
		on:mousedown={(event) => {
			dragging = true;
			dragStartX = event.clientX;
		}}
		on:mouseup={() => {
			dragging = false;
			timelineTranslateX += dragStartX - mouseXPosition;
		}}
		on:mouseleave={() => {
			dragging = false;
			timelineTranslateX += dragStartX - mouseXPosition;
		}}
		on:mousemove={handleDrag}
		aria-hidden="false"
		class="mt-4 h-full cursor-pointer overflow-x-hidden"
	>
		<div
			class={`relative h-full w-full select-none overflow-x-hidden overflow-y-hidden px-16 ${
				dragging ? 'cursor-grabbing' : 'cursor-grab'
			}`}
			bind:this={timelineElement}
		>
			<div id="timeline" class="relative my-auto h-full w-full overflow-x-hidden text-xs"></div>
		</div>
		<!-- left fade out -->
		<div
			class={`w-28 absolute left-0 top-0 h-full bg-gradient-to-l from-transparent to-white`}
		/>
		<!-- right fade out -->
		<div
			class={`w-28 absolute right-0 top-0 h-full bg-gradient-to-r from-transparent to-white`}
		/>
		<div
			class={`items-top absolute bottom-16 ml-32 flex w-max`}
		>
			<label for="zoom" class="mx-4 text-lg"> Zoom out </label>
			<Slider bind:value={zoomBind} min={0} max={10} step={1} class="w-96" />
			<label for="zoom" class="mx-4 text-lg"> Zoom In </label>
		</div>
	</div>

The slider is completely optional, even the fade elements
It should be easy to convert this to make it work on react, or vanilla js using llms
Hope it helps!

@walterra
Copy link
Owner

This is amazing, thank you! Would you mind posting a screenshot of an example for reference? I'll try to find some time to look at the code and see if we can add something inspired by this to the lib itself (or make it part of the official examples).

@PriyavKaneria
Copy link

Sure, here is a demo video on how I am using this above code in my project. (It is part of a showcase of all the projects I have worked on till now in a timeline)

projects-timeline.mp4

In the first part of the video I am using mouse wheel to scroll. The label optimization is on (and I'm using old machine) hence, the lag in scroll
The container sizing and overflow might need slight changes if implemented independently, otherwise it should be good to go in svelte

Happy to help! Awesome library, thanks 🌸

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants