Dynamic rendering: Zoom-level
Tags: javascript, webdev, devops, productivity
Published: 2024-06-20 07:28:42

Renderers can adapt programmatically to the application state and result in different ways of drawing your activities. This article will cover the AssignmentActivityRenderer implemented in the ScheduleJS Viewer and explain its mechanics.

## What are [ScheduleJS](https://schedulejs.com) renderers?

The concept of an `ActivityRenderer` represents a pluggable renderer dedicated to activity drawing. It focuses on rendering any kind of data in a row. In general, renderers hold drawing strategies related to activities. Note that the `ActivityRenderer` class extends the `Renderer` class. This architecture lets the developer fine-tune his drawing strategy at multiple levels while giving access to time and position calculation methods that will help.

## The [ScheduleJS](https://schedulejs.com) ActivityRenderer class
Below is an example of how you can create an activity renderer in ScheduleJS: 

```
// Create your own pluggable ActivityRenderer based on the ActivityBarRenderer class
export class MyActivityRenderer extends ActivityBarRenderer<MyActivity, Row> {

  // Override the drawActivity method of the ActivityBarRenderer
  protected drawActivity(activityRef: ActivityRef<Action>, position: ViewPosition, ctx: CanvasRenderingContext2D, x: number, y: number, w: number, h: number, selected: boolean, hover: boolean, highlighted: boolean, pressed: boolean): ActivityBounds {
    
    // Draw your activity by using the canvas rendering context 'ctx' API
    this._drawMyActivity(ctx, activityRef, x, y, w, h, selected, hover, highlighted, pressed);
    
    // The returned ActivityBounds represents how much space the activity takes (useful for interactions)
    return new ActivityBounds(activityRef, x, y, w, h);
    
}
```
Here is the list of the three advanced activity renderer classes you can extend to start creating your own renderer faster:

- ActivityBarRenderer: used in Gantt layouts, it represents a bar on the graphics
- ChartActivityRenderer: handles chart-related drawings with a vertical dimension
- CalendarActivityRenderer: draws behind the rows and optimized for read-only
Once you are done with designing your renderer, you can register it using the Graphics API:

```
// Register MyActivityRenderer for activities of type MyActivity in the context of a GanttLayout
const graphics = this.gantt.getGraphics();
graphics.setActivityRenderer(MyActivity, GanttLayout, new MyActivityRenderer(graphics));
```

## Designing a Zoom-level dynamic ActivityRenderer

Every `ActivityRenderer` triggers the inner [ScheduleJS](https://schedulejs.com) drawing engine to draw activities on the screen, letting the developer focus on the high-level decisions for the application. The strategy is to automatically trigger as few redraws as possible when you are operating on the graphics. You can also request manual redraws for specific use cases like data loading, real-time updates, and indirect relations. Doing so allows the developer to optimize the rendering strategy and offers higher resolution and performance.

This article will focus on the `AssignmentActivityRenderer` of the [ScheduleJS Viewer](https://schedulejs.com/en/schedule-js-viewer-2/).

As you can see below, the `AssignmentActivityRenderer` is a simple renderer that draws numbers on the canvas to indicate the Budgeted Units and Actual Units of a project or a task.


![Activity renderer JS Gantt](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1qoapdhqmwhebrthxxkw.png)

Depending on the Zoom-level, we want to visualize the assignments business units per day, week, and month. To implement this in a simple way, we relied on the powerful [ScheduleJS](url) internal data structures and decided to calculate additional activities for all the available timespan.

The .xer data structure only indicates daily values so we had to create the weekly and monthly values by clumping the corresponding daily values. To handle this, the [ScheduleJS Viewer](https://schedulejs.com/en/schedule-js-viewer-2/) uses a `temporalUnit` field in its `AssignmentActivity` model to indicate which `AssignmentActivity` corresponds to which `ChronoUnit`.

```
export class AssignmentActivity extends MutableActivityBase {
    // Attach a temporalUnit to the assignment activity
    temporalUnit: ChronoUnit;
    value: number;
}
```
Now we have to tell the renderer which resolution is currently used. To do this, the Dateline API comes in handy.

As you can see below, the dateline is composed of two rows:

- The first row of cells gives information on the current timespan (ex: Week 49, Monday 29, November 2021)
- The second row of cells breaks down the top cell in multiple smaller cells (ex: Day 29)


![Row Gantt example](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kwzbgdo1us7gicpa4h7k.png)

To draw our activities as text, we will use the Canvas `ctx.fillText` method.

Now, to select which activity has to be drawn at any time, we decide to trigger this method conditionally after we confirmed the activity `temporalUnit` is the same temporal unit used by the Dateline 2nd row cells. As the renderer draws every frame, adding this condition to our renderer will only draw either the Monthly, Weekly, or Daily `AssignmentActivities` at a given time.

## A few words on the [ScheduleJS](https://schedulejs.com/) data structure

Graphics in ScheduleJS are made to support millions of activities at any given time. To make sure our graphics keep the best performance possible, ScheduleJS implements a binary tree representation of the data in memory to quickly access any activity node and reduce processing time.

The implementation described above takes advantage of this feature to calculate and store every information required for the graphics before runtime to further increase navigation smoothness.


![ScheduleJS](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/a75b810331xyqe0zpepb.png)

This example is a simple use case of a dynamic renderer. The Zoom-level is just one of the various public variables that you can find in a ScheduleJS application. The same logic can also be applied to any external variables to draw conditionally and build your own user interface and experience.

Final result
If you'd like to see the final result, don't hesitate to take a look at: [Dynamic rendering: Zoom-level](https://schedulejs.com/en/dynamic-rendering-zoom-level/)

For more information on JS Gantt see: [ScheduleJS](https://schedulejs.com)