The Gantt activity renderer is the main renderer of the ScheduleJS Viewer. This article will discuss how it is built and what are the specificities of this activity renderer.
The first step to building a renderer class is to inherit attributes and methods by extending a higher-order framework class.
We want to represent tasks only through their start and end time dimensions. The ScheduleJS base renderer class for doing this is the ActivityBarRenderer class.
We need to provide the custom-type arguments to the ActivityBarRenderer class so the attributes and methods provided by our custom Row and Activity classes will be accessible using the base class API.
Let’s create the ScheduleJsViewerTaskActivityRenderer class to draw every ScheduleJsViewerTaskActivity in their respective ScheduleJsViewerTaskRow.
// Import the base ActivityBarRenderer class from ScheduleJS import {ActivityBarRenderer} from "schedule"; // Import our custom Activity and Row types import {ScheduleJsViewerTaskActivity} from "..."; import {ScheduleJsViewerTaskRow} from "..."; // Create our custom renderer by extending the ActivityBarRenderer class export class ScheduleJsViewerTaskActivityRenderer extends ActivityBarRenderer{ }
As-is, the renderer can already be registered to draw our activities using the default behavior of the ActivityBarRenderer. Now let’s dive into how to customize it.
In ScheduleJS, an ActivityRenderer is a class we register programmatically using the Graphics API to draw a specific Activity on its Row. To organize our ScheduleJsViewerTaskActivityRenderer, we will separate its code into three sections:
Attributes are constants that will be reused throughout the renderer. As-is, these properties will only be edited directly in the renderer code. We can imagine a specific screen where the user could modify these settings directly in the UI.
// Attributes // Pixels sizings private readonly _parentActivityTrianglesWidthPx: number = 5; private readonly _parentActivityTrianglesHeightPx: number = 8; private readonly _defaultLineWidthPx: number = 0.5; // Colors palette private readonly _parentActivityColor: string = Color.GRAY.toCssString(); private readonly _strokeColor: string = Color.BLACK.toCssString(); private readonly _defaultActivityGreen: Color = Color.rgb(28, 187, 158); private readonly _defaultActivityBlue: Color = Color.rgb(53, 152, 214); private readonly _onHoverFillColor: string = Color.ORANGE.toCssString(); // Opacity ratio for baseline activities private readonly _baselineOpacityRatio: number = 0.6;
The constructor is tightly coupled to our renderer lifecycle method. In the ScheduleJS Viewer, we decided to instantiate the renderer whenever the user switches screens to define specificities and reuse our code in every tab that implements this renderer. It means the constructor function is run every time the user selects a screen featuring this renderer.
// Constructor // The renderer requires the graphics and the current tab variable constructor(graphics: GraphicsBase, private _currentRibbonMenuTab: ScheduleJsViewerRibbonMenuTabsEnum) { // The ActivityBarRenderer class requires the graphics and a name for the renderer super(graphics, ScheduleJsViewerRenderingConstants.taskActivityRendererName); // Default fill color when hovering an activity this.setFillHover(Color.web(this._onHoverFillColor)); // Default stroke color when hovering an activity this.setStrokeHover(Color.BLACK); // Default stroke color this.setStroke(Color.BLACK); // Default thickness this.setLineWidth(this._defaultLineWidthPx); // Default bar height this.setBarHeight(8); // Default fill color based on current tab switch (_currentRibbonMenuTab) { // Change color for the WBS tab case ScheduleJsViewerRibbonMenuTabsEnum.WBS: this._parentActivityColor = ScheduleJsViewerColors.brown; this.setFill(this._defaultActivityBlue); break; default: this._parentActivityColor = Color.GRAY.toCssString(); this.setFill(this._defaultActivityGreen); break; } }
The setFill, setStroke, setFillHover, setStrokeHover, setLineWidth, and setBarHeight are inherited and used to alter the default rendering characteristics of the ActivityBarRenderer class.
The default features of this renderer are the following:
The framework will automatically call the drawActivity method to render our activities on the canvas. All its parameters are dynamically filled, allowing you to react in real-time to the current state of your activities.
// Main drawing method drawActivity(activityRef: ActivityRef, position: ViewPosition, ctx: CanvasRenderingContext2D, x: number, y: number, w: number, h: number, selected: boolean, hover: boolean, highlighted: boolean, pressed: boolean ): ActivityBounds { // This method has to return ActivityBounds // True if current activity includes a comparison task const hasModifications = !!activityRef.getActivity().diffTask; // True if current row has children const isParent = activityRef.getRow().getChildren().length; // Set colors dynamically this._setActivityColor(activityRef, hasModifications); // Draw text this._drawActivityText(activityRef, ctx, x, y, w, h, hasModifications); // Run a custom method to draw parent activities or delegate to the default method return isParent ? this._drawParentActivity(activityRef, ctx, x, y, w, h, hover, hasModifications) : super.drawActivity(activityRef, position, ctx, x, y, w, h, selected, hover, highlighted, pressed); }
The drawing will occur this way:
Let’s take a closer look at how to freely draw your activity by designing your own methods with the _drawParentActivity method.
// Draw the parent activity private _drawParentActivity(activityRef: ActivityRef, ctx: CanvasRenderingContext2D, x: number, y: number, w: number, h: number, hover: boolean, hasModifications: boolean ): ActivityBounds { // Set padding const topPadding = h / 3.5; const leftPadding = 1; // Set CanvasRenderingContext2D ctx.lineWidth = this._defaultLineWidthPx; if (hover) { ctx.fillStyle = this._onHoverFillColor; ctx.strokeStyle = ScheduleJsViewerColors.brown; } else if (hasModifications) { ctx.fillStyle = Color.web(this._parentActivityColor).withOpacity(this._baselineOpacityRatio).toCssString(); ctx.strokeStyle = `rgba(0,0,0,${this._baselineOpacityRatio})`; } else { ctx.fillStyle = this._parentActivityColor; ctx.strokeStyle = this._strokeColor; } // Draw elements ScheduleJsViewerTaskActivityRenderer._drawParentActivityStartTriangle(ctx, x leftPadding, y topPadding, this._parentActivityTrianglesWidthPx, this._parentActivityTrianglesHeightPx); ScheduleJsViewerTaskActivityRenderer._drawParentActivityBody(ctx, x leftPadding, y topPadding, w, this._parentActivityTrianglesWidthPx, this._parentActivityTrianglesHeightPx); ScheduleJsViewerTaskActivityRenderer._drawParentActivityEndTriangle(ctx, x leftPadding, y topPadding, w, this._parentActivityTrianglesWidthPx, this._parentActivityTrianglesHeightPx); // Return positions to update where your activity should be responsive return new ActivityBounds(activityRef, x, y, w, h); }
Here we directly use the HTMLCanvas API to define our drawing strategy by setting up the CanvasRenderingContex2D. The only framework-related operation done in this method is creating some new ActivityBounds for the current parent Activity.
The framework creates a map using ActivityBounds under the hood to register all the activities on the screen. This map helps the developer by providing an element-like logic to build advanced user experiences based on accurate information while taking advantage of the performance of the HTMLCanvas API.
The draw elements methods like _drawParentActivityStartTriangle rely on the CanvasRenderingContext2D API to draw at the pixel level.
// Draw the start triangle element of the parent activity private static _drawParentActivityStartTriangle(ctx: CanvasRenderingContext2D, x: number, y: number, triangleWidth: number, triangleHeight: number): void { ctx.beginPath(); ctx.moveTo(x, y); ctx.lineTo(x , y triangleHeight); ctx.lineTo(x triangleWidth, y); ctx.lineTo(x, y); ctx.fill(); ctx.stroke(); ctx.closePath(); }
To register your brand-new renderer, use the graphics.setActivityRenderer method:
// Register the renderer graphics.setActivityRenderer(ScheduleJsViewerTaskActivity, GanttLayout, new ScheduleJsViewerTaskActivityRenderer(graphics, currentRibbonMenuTab));
To see the video of the final result you can go to see: Building an ActivityRenderer
Disclaimer: All resources provided are partly from the Internet. If there is any infringement of your copyright or other rights and interests, please explain the detailed reasons and provide proof of copyright or rights and interests and then send it to the email: [email protected] We will handle it for you as soon as possible.
Copyright© 2022 湘ICP备2022001581号-3