-
Notifications
You must be signed in to change notification settings - Fork 27
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
Comments
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 |
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). |
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.mp4In 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 Happy to help! Awesome library, thanks 🌸 |
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.
The text was updated successfully, but these errors were encountered: