DEV Community

Cover image for Dynamic rendering: Zoom-level
Gaetan Gasoline
Gaetan Gasoline

Posted on • Edited on

Dynamic rendering: Zoom-level

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 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 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);

}
Enter fullscreen mode Exit fullscreen mode

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));
Enter fullscreen mode Exit fullscreen mode

Designing a Zoom-level dynamic ActivityRenderer

Every ActivityRenderer triggers the inner ScheduleJS 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.

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

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 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 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;
}
Enter fullscreen mode Exit fullscreen mode

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

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 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

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

For more information on JS Gantt see: ScheduleJS

Top comments (0)