Overview

  • Learn how to enable the zoom feature of the React Scheduler component.

  • Define your own zoom levels (e.g., from a year of day cells to a day of 15-minute cells).

  • Zoom quickly using a slider (“range” HTML input element).

  • Add “Zoom In” and “Zoom Out” buttons to zoom incrementally.

To see an introduction on using the React Scheduler component, please read the React Scheduler with Horizontal Timeline (Open-Source). It explains how to add the Scheduler to your React project and how to configure it to meet your requirements.

To learn how to implement a scheduling calendar with a timeline on the Y axis, please see the React Scheduler with a Vertical Timeline tutorial.

How to configure the timeline in the React Scheduler?

timeline

You can configure the timeline displayed by the React Scheduler component using the following properties:

  • The scale property sets the Scheduler grid resolution. The Scheduler will use cells with the selected duration. The following values are supported: "Minute", "Hour", "Day", "Week". You can also use "CellDuration" scale and specify the cell duration in minutes using cellDuration property. DayPilot Pro also supports Month and Year scales and a manual timeline mode in which you define individual time cells.

  • The timeHeaders property defines the time groups displayed in the X axis header. There can be multiple rows and it lets you show the time for each of the time columns. The default values are derived from the date/time and you can use your own date/time format defined using the format property. You can also customize the time header cells if needed. That way you get full control over the time header cell content (you can either specify text that will be HTML-escaped automatically or use raw HTML). You can also include additional elements (such as icons, buttons, etc.) using active areas. In React, you can also specify custom content as JSX (see the React Scheduler: Rendering JSX in Time Header and Upper-Left Corner tutorial).

  • The timeline appearance is also affected by the cell size. You can set the cell width in pixels using the cellWidth property.

For direct configuration, keep the timeline properties in React state and pass them to the Scheduler. The standalone src/scheduler/TimelineConfiguration.jsx example is available at /configuration when you run the downloaded project. Its “Next year” and “Hours this week” buttons exercise the handlers in the next two sections. The main page demonstrates the zoom controls.

import {useState} from 'react';
import {DayPilot, DayPilotScheduler} from '@daypilot/daypilot-lite-react';

const TimelineConfiguration = () => {
  const [scale, setScale] = useState("Day");
  const [cellWidth, setCellWidth] = useState(40);
  const [timeHeaders, setTimeHeaders] = useState([
    {groupBy: "Year"},
    {groupBy: "Month", format: "MMMM"},
    {groupBy: "Day", format: "d"},
  ]);
  const [startDate, setStartDate] = useState(DayPilot.Date.today().firstDayOfYear());
  const [days, setDays] = useState(DayPilot.Date.today().daysInYear());

  // ...
  return (
    <div>
      // ...
      <DayPilotScheduler
        scale={scale}
        cellWidth={cellWidth}
        timeHeaders={timeHeaders}
        startDate={startDate}
        days={days}
        // ...
      />
    </div>
  );
};

export default TimelineConfiguration;

The Days zoom level uses day cells with year, month, and day header rows:

const zoomLevels = [
  {
    name: "Days",
    properties: {
      scale: "Day",
      cellWidth: 40,
      timeHeaders: [{groupBy: "Year"}, {groupBy: "Month", format: "MMMM"}, {groupBy: "Day", format: "d"}],
      startDate: args => args.date.firstDayOfYear(),
      days: args => args.date.daysInYear(),
    }
  },
  // ...
];

How to change the date range visible in the React Scheduler?

You can use startDate and days properties to change the visible date range. Apply the new values by updating the React state variables, or use the Scheduler’s update() method. The standalone example advances the start date by one year and recalculates the year length, including leap years:

const clickNextYear = () => {
    const next = startDate.addYears(1);
    setStartDate(next);
    setDays(next.daysInYear());
  };

In the zoom example, the Days level calculates the same range from the anchor date using callbacks:

startDate: args => args.date.firstDayOfYear(),
days: args => args.date.daysInYear(),

DayPilot Pro also offers the infinite scrolling Scheduler mode that will shift the visible date range automatically as you approach the end of the current range.

How to change the React Scheduler timeline dynamically?

You can change the Scheduler timeline by adjusting the configuration properties. The Scheduler will display the new timeline after the update. In the standalone example, the handler changes the scale, cell width, and header rows together, then selects the current week:

const switchToHourScale = () => {
    setScale("Hour");
    setCellWidth(40);
    setTimeHeaders([
      {groupBy: "Month"},
      {groupBy: "Day", format: "dddd d"},
      {groupBy: "Hour"},
    ]);
    setStartDate(DayPilot.Date.today().firstDayOfWeek());
    setDays(7);
  };

The zoom example packages the same settings into the Hours level, using the anchor date to choose the week:

const zoomLevels = [
  // ...
  {
    name: "Hours",
    properties: {
      scale: "Hour",
      cellWidth: 40,
      timeHeaders: [{groupBy: "Month"}, {groupBy: "Day", format: "dddd d"}, {groupBy: "Hour"}],
      startDate: args => args.date.firstDayOfWeek(),
      days: () => 7,
    }
  },
  // ...
];

How to configure zoom levels in the React Scheduler?

The React Scheduler component includes built-in support for switching between different zoom levels.

It helps with switching the timeline to a new resolution:

  • You can set the zoom level number in order to switch to the new zoom level.

  • The Scheduler will apply the zoom level properties and calculate the required values from the current view.

  • The scrollbar position will be adjusted automatically to keep the current date at the left edge of the viewport in this example.

First, you need to define the zoom levels using the zoomLevels property. Define the array inside the component and pass it as zoomLevels={zoomLevels}:

const zoomLevels = [
  {
    name: "Days",
    properties: {
      scale: "Day",
      cellWidth: 40,
      timeHeaders: [{groupBy: "Year"}, {groupBy: "Month", format: "MMMM"}, {groupBy: "Day", format: "d"}],
      startDate: args => args.date.firstDayOfYear(),
      days: args => args.date.daysInYear(),
    }
  },
  {
    name: "Half-Days",
    properties: {
      scale: "CellDuration",
      cellDuration: 720,
      cellWidth: 40,
      timeHeaders: [{groupBy: "Month"}, {groupBy: "Day", format: "ddd d"}, {groupBy: "Cell", format: "tt"}],
      startDate: args => args.date.firstDayOfMonth(),
      days: args => args.date.daysInMonth(),
    }
  },
  {
    name: "Hours",
    properties: {
      scale: "Hour",
      cellWidth: 40,
      timeHeaders: [{groupBy: "Month"}, {groupBy: "Day", format: "dddd d"}, {groupBy: "Hour"}],
      startDate: args => args.date.firstDayOfWeek(),
      days: () => 7,
    }
  },
  {
    name: "15-Minute Cells",
    properties: {
      scale: "CellDuration",
      cellDuration: 15,
      cellWidth: 40,
      timeHeaders: [{groupBy: "Day", format: "dddd MMMM d, yyyy"}, {groupBy: "Hour"}, {groupBy: "Cell"}],
      startDate: args => args.date.getDatePart(),
      days: () => 1,
    }
  },
];

The zoom level object has the following structure:

  • Each zoom level can contain arbitrary properties at the top level (we use a name property to specify the zoom level name).

  • The properties property specifies the values that will be applied to the Scheduler when switching to this zoom level.

The properties can be either static (e.g. scale: "Hour") or calculated (startDate: args => args.date.firstDayOfWeek()). The calculated properties let you use dynamic values based on the Scheduler state and/or the date at the anchor position (args.date).

How to set the initial zoom level?

You can specify the initial zoom level using the zoom property:

const Scheduler = () => {
  // ...
  const [zoom, setZoom] = useState(0);
  // ...
  return (
    <div>
      // ...
      <DayPilotScheduler
        // ...
        zoom={zoom}
        // ...
      />
    </div>
  );
};

How to set the zoom anchor?

You can specify the zoom anchor (the date/time reference used to calculate the new timeline) using the zoomPosition property.

We are not going to change this value dynamically in our React application, so we pass zoomPosition="left" directly to the component. The date at the left edge of the viewport anchors each zoom change.

const Scheduler = () => {
  // ...
  return (
    <div>
      // ...
      <DayPilotScheduler
        zoomPosition="left"
        // ...
      />
    </div>
  );
};

How to zoom using a slider (“range” HTML input element)?

slider

You can set the zoom level using a range HTML input control:

<input type="range" min="0" max="3" step="1" id="zoom-level" aria-label="Zoom level" value={zoom}
       onChange={(ev) => changeZoomLevel(ev.currentTarget.value)} />

We need to bind the range element value to the current zoom level (zoom state variable).

Changing the range element fires the onChange event. We will use this event to set the corresponding zoom level:

const changeZoomLevel = (value) => {
    const newZoom = parseInt(value, 10);
    setZoom(newZoom);
  };

This is how the range HTML input element can be integrated with the React Scheduler:

const Scheduler = () => {
  // ...
  const [zoom, setZoom] = useState(0);
  // ...
  const changeZoomLevel = (value) => {
    const newZoom = parseInt(value, 10);
    setZoom(newZoom);
  };
  // ...
  return (
    <div>
      <div className={"space"}>
        // ...
        <input type="range" min="0" max="3" step="1" id="zoom-level" aria-label="Zoom level" value={zoom}
               onChange={(ev) => changeZoomLevel(ev.currentTarget.value)} />
        // ...
        <span id="label">Zoom level: {zoomLevels[zoom].name}</span>
      </div>
      <DayPilotScheduler
        zoomPosition="left"
        // ...
        zoomLevels={zoomLevels}
        // ...
        zoom={zoom}
        events={events}
        resources={resources}
        controlRef={setScheduler}
      />
    </div>
  );
};

How to zoom in?

zoom in

The “plus” button JSX:

<button id="zoom-in" aria-label="Zoom in" onClick={timelineZoomIn}>
  <svg style={{width: "16px", height: "16px"}}>
    <use href={"icons/daypilot.svg#plus-2"}/>
  </svg>
</button>

The timelineZoomIn() method updates the Scheduler zoom level to zoom in:

const timelineZoomIn = () => {
    const max = zoomLevels.length - 1;
    const newZoom = Math.min(max, zoom + 1);
    setZoom(newZoom);
  };

In addition to changing the zoom value, we also need to check the range boundaries to make sure that the new zoom level does not exceed the maximum available zoom level.

How to zoom out?

zoom out

The “minus” button JSX:

<button id="zoom-out" aria-label="Zoom out" onClick={timelineZoomOut}>
  <svg style={{width: "16px", height: "16px"}}>
    <use href={"icons/daypilot.svg#minus-2"}/>
  </svg>
</button>

The timelineZoomOut() method updates the Scheduler zoom level to zoom out:

const timelineZoomOut = () => {
    const newZoom = Math.max(0, zoom - 1);
    setZoom(newZoom);
  };

Full Source Code

The Scheduler changes its start date when applying a zoom level. The startDate={scheduler?.startDate ?? "2028-01-01"} prop uses the initial date before the control mounts and its current start date afterward. This prevents a React update from resetting the timeline to January before the next zoom change.

Here is the full source code of our React application, which shows a Scheduler component with an adjustable zoom level (using “zoom in” and “zoom out” buttons and a slider).

import {useEffect, useState} from 'react';
import {DayPilot, DayPilotScheduler} from "@daypilot/daypilot-lite-react";

const Scheduler = () => {
  const [scheduler, setScheduler] = useState(null);
  const [zoom, setZoom] = useState(0);
  const [resources] = useState([
    {name: "Resource A", id: "A"},
    {name: "Resource B", id: "B"},
    {name: "Resource C", id: "C"},
    {name: "Resource D", id: "D"},
    {name: "Resource E", id: "E"},
    {name: "Resource F", id: "F"},
    {name: "Resource G", id: "G"}
  ]);

  const [events] = useState([
    {
      id: 1,
      text: "Event 1",
      start: "2028-08-02T00:00:00",
      end: "2028-08-05T00:00:00",
      resource: "A"
    },
    {
      id: 2,
      text: "Event 2",
      start: "2028-08-03T00:00:00",
      end: "2028-08-10T00:00:00",
      resource: "C",
      barColor: "#38761d",
      barBackColor: "#93c47d"
    },
    {
      id: 3,
      text: "Event 3",
      start: "2028-08-04T00:00:00",
      end: "2028-08-08T00:00:00",
      resource: "D",
      barColor: "#f1c232",
      barBackColor: "#f1c232"
    },
    {
      id: 4,
      text: "Event 3",
      start: "2028-08-02T00:00:00",
      end: "2028-08-08T00:00:00",
      resource: "E",
      barColor: "#cc0000",
      barBackColor: "#ea9999"
    }
  ]);


  const zoomLevels = [
    {
      name: "Days",
      properties: {
        scale: "Day",
        cellWidth: 40,
        timeHeaders: [{groupBy: "Year"}, {groupBy: "Month", format: "MMMM"}, {groupBy: "Day", format: "d"}],
        startDate: args => args.date.firstDayOfYear(),
        days: args => args.date.daysInYear(),
      }
    },
    {
      name: "Half-Days",
      properties: {
        scale: "CellDuration",
        cellDuration: 720,
        cellWidth: 40,
        timeHeaders: [{groupBy: "Month"}, {groupBy: "Day", format: "ddd d"}, {groupBy: "Cell", format: "tt"}],
        startDate: args => args.date.firstDayOfMonth(),
        days: args => args.date.daysInMonth(),
      }
    },
    {
      name: "Hours",
      properties: {
        scale: "Hour",
        cellWidth: 40,
        timeHeaders: [{groupBy: "Month"}, {groupBy: "Day", format: "dddd d"}, {groupBy: "Hour"}],
        startDate: args => args.date.firstDayOfWeek(),
        days: () => 7,
      }
    },
    {
      name: "15-Minute Cells",
      properties: {
        scale: "CellDuration",
        cellDuration: 15,
        cellWidth: 40,
        timeHeaders: [{groupBy: "Day", format: "dddd MMMM d, yyyy"}, {groupBy: "Hour"}, {groupBy: "Cell"}],
        startDate: args => args.date.getDatePart(),
        days: () => 1,
      }
    },
  ];

  const onTimeRangeSelected = async (args) => {
    const scheduler = args.control;
    const modal = await DayPilot.Modal.prompt("Create a new event:", "Event 1");
    scheduler.clearSelection();
    if (modal.canceled) { return; }
    scheduler.events.add({
      start: args.start,
      end: args.end,
      id: DayPilot.guid(),
      resource: args.resource,
      text: modal.result
    });
  };

  const changeZoomLevel = (value) => {
    const newZoom = parseInt(value, 10);
    setZoom(newZoom);
  };

  const timelineZoomOut = () => {
    const newZoom = Math.max(0, zoom - 1);
    setZoom(newZoom);
  };

  const timelineZoomIn = () => {
    const max = zoomLevels.length - 1;
    const newZoom = Math.min(max, zoom + 1);
    setZoom(newZoom);
  };

  useEffect(() => {
    scheduler?.scrollTo("2028-08-01");
  }, [scheduler]);

  return (
    <div>
      <div className={"space"}>
        <button id="zoom-out" aria-label="Zoom out" onClick={timelineZoomOut}>
          <svg style={{width: "16px", height: "16px"}}>
            <use href={"icons/daypilot.svg#minus-2"}/>
          </svg>
        </button>

        <input type="range" min="0" max="3" step="1" id="zoom-level" aria-label="Zoom level" value={zoom}
               onChange={(ev) => changeZoomLevel(ev.currentTarget.value)} />

        <button id="zoom-in" aria-label="Zoom in" onClick={timelineZoomIn}>
          <svg style={{width: "16px", height: "16px"}}>
            <use href={"icons/daypilot.svg#plus-2"}/>
          </svg>
        </button>

        <span id="label">Zoom level: {zoomLevels[zoom].name}</span>
      </div>
      <DayPilotScheduler
        zoomPosition="left"
        startDate={scheduler?.startDate ?? "2028-01-01"}
        rowHeaderWidth={100}
        rowMarginTop={4}
        rowMarginBottom={4}
        eventBorderRadius={5}
        eventPadding={5}
        zoomLevels={zoomLevels}
        useEventBoxes="Never"
        timeRangeSelectedHandling="Enabled"
        onTimeRangeSelected={onTimeRangeSelected}
        zoom={zoom}
        events={events}
        resources={resources}
        controlRef={setScheduler}
      />
    </div>
  );
};
export default Scheduler;