3.3.5 - Activity Rendering
Introduction
The graphics element uses the HTML canvas API. This is due to the complex nature of a Gantt chart and due to the large data volumes often observed inside of them. Directly rendering large quantities of activities into a canvas is much faster than constantly updating the DOM and reapplying CSS styling. ScheduleJS implements a pluggable renderer architecture where renderer instances can be mapped to Activity types.
The following code is an example of how to register a custom renderer for a given "Flight" activity type. Please note that the graphics view is capable of displaying activities in different layouts, hence the layout type must also be passed to the method.
const graphics = this.gantt.getGraphics();
const activityRenderer = new ActivityBarRenderer(graphics, "FlightRenderer");
// Change the Activity bar height.
activityRenderer.setBarHeight(5);
// Register the ActivityRenderer.
graphics.setActivityRenderer(Flight, GanttLayout, activityRenderer);
We usually also pass the graphics to the renderer at construction time.
The following methods on graphics are used for working with renderers:
Method | Description |
---|---|
graphics.setActivityRenderer(Activity, Layout, Renderer) | Registers a new renderer for the given activity and layout type. |
graphics.getActivityRenderer(Activity, Layout) | Returns a renderer for the given activity and layout type. |
Drawing
Activity renderers have a single entry point for drawing, a method called draw(). This method should not be overridden. Once invoked it will call various protected methods to perform the actual drawing:
- draw() → calls
- protected drawActivity() → calls
- protected drawBackground()
- protected drawBorder()
- protected drawActivity() → calls
Derived classes are free to override any of the three protected methods to customize the activity appearance.
Most drawing methods have the same arguments:
activityRef: ActivityRef<Activity> The reference Activity for current drawing.
ctx: CanvasRenderingContext2D The canvas context into which to draw.
x: number The location of the start time of the activity.
y: number The y coordinate (0 when drawn on row or line location).
w: number End time location minus start time location.
h: number Row or line height.
selected: boolean Is activity currently selected?
hover: boolean Is mouse cursor currently hovering over it?
highlighted: boolean Is activity currently blinking?
pressed: boolean Is user currently pressing on it?
Default Renderers
The following table lists the various activity renderers that are provided by default.
Renderer Class | Description |
---|---|
ActivityRenderer | The most basic renderer for activities. Draws a filled rectangle at the location of the activity. All default renderers are subclasses of this type. |
ActivityBarRenderer | Draws a bar instead of filling the entire area. The height of the bar can be specified. Also supports text in several locations inside and outside the bar. |
ChartActivityRenderer | Draws a ChartActivity vertically depending on its chart value. |
CompletableActivityRenderer | Subclass of the bar renderer. Draws a CompletableActivity as a bar with a section of its background filled with another color. The size of the section depends on the percentage complete value of the activity. |
Here is a Flight renderer example:
import {ActivityBarRenderer, ActivityBarRendererTextPosition, ActivityBounds, ActivityRef, Color, ViewPosition} from "schedule";
import {Flight, Aircraft} from "./aircraft.model";
export class FlightRenderer extends ActivityBarRenderer<Flight, Aircraft> {
constructor(graphics) {
super(graphics, "Flight activity renderer");
}
// Override the drawActivity method.
protected drawActivity(activityRef: ActivityRef<Flight>, position: ViewPosition, ctx: CanvasRenderingContext2D,x: number, y: number, w: number, h: number, selected: boolean, hover: boolean, highlighted: boolean, pressed: boolean): ActivityBounds {
const layerName = activityRef.getLayer().getName(); // Get rendered Activity layer name.
// Use ActivityBarRenderer properties to style the Activity.
this.setBarHeight(h * 0.75);
this.setCornersRounded(true);
this.setStroke(Color.BLACK);
// Conditional box and text color depending on the Layer.
if (layerName === "Cargo") {
this.setFill(Color.CORNFLOWERBLUE);
this.setTextFill(Color.WHITE);
}
else if (layerName === "Training") {
this.setFill(Color.ALICEBLUE);
this.setTextFill(Color.BLACK);
}
else if (layerName === "Charter") {
this.setFill(Color.ANTIQUEWHITE);
this.setTextFill(Color.BLACK);
}
// Draw the rectangle and store ActivityBounds.
const bounds = super.drawActivity(activityRef, position, ctx, x, y, w, h, selected, hover, highlighted, pressed);
// Then draw the text.
ctx.font = "italic 11px Verdana";
this.drawText(activityRef, layerName, ActivityBarRendererTextPosition.CENTER, ctx, x, y, w, h, selected, hover, highlighted, pressed);
// This method has to return the ActivityBounds.
return bounds;
}
}
The text has to be drawn after the rectangle because we are using HTML canvas API to stack things on one another.
Activity Bounds
Every activity renderer is responsible for returning an instance of ActivityBounds after drawing the activity. These bounds are an essential piece for the framework and many operations will only work properly if these bounds are valid. They are being used for editing activities, for hit point detection, for laying out links, for context menus, and so on. The following table lists the attributes of the ActivityBounds class.
Methods | Description |
---|---|
width | The width of the Activity in pixels. |
height | The height of the Activity in pixels. |
minX | The minimal x coordinate of the Activity in pixels based on Layout. |
maxX | The maximal x coordinate of the Activity in pixels, based on Layout. |
minY | The minimal y coordinate of the Activity in pixels, based on Layout. |
maxY | The maximal y coordinate of the Activity in pixels, based on Layout. |
bounds.getActivity() | The Activity for which these are the bounds. |
bounds.getActivityRef() | The ActivityReference referring to the activity. |
bounds.getLayer() | The Model Layer on which the activity was drawn. |
bounds.getLayout() | The Layout that was used when the activity was drawn. |
bounds.getLineIndex() | The index of the line on which the activity is located (-1 if activity is on the row, not a line). |
bounds.getRow() | The Row where the activity was drawn. |
bounds.setLayout(Layout) | Sets the Layout to use when the activity will be drawn. |
Properties
All renderers define several properties that can be used to customize their appearance. Many of these properties are dependent on the "pseudo state" of the activity: hover, pressed, selected, highlighted. To make it easier to lookup the right Color at the right time several convenience methods are available:
Renderer | Method | Description |
---|---|---|
Renderer | this.getFill(selected, hover, highlighted, pressed) | Returns the Color to use for the activity background depending on pseudo states passed. |
ActivityRenderer | this.getStroke(selected, hover, highlighted, pressed) | Returns the Color to use for the activity border depending on pseudo states passed. |
ActivityBarRenderer | this.getTextFill(selected, hover, highlighted, pressed) | Returns the Color to use for text depending on pseudo states passed. |