Gantt (Angular)

Gantt charts illustrate project schedules. They show the start and finish dates of each task and typically add information such as the completion state of each task and dependencies between tasks. In this sample, we assign the gantItemFormatter() method to the itemFormatter property and the ganttChartRendered() method to the rendered event to create a Gantt chart using FlexChart.

Learn about FlexChart | FlexChart API Reference

This example uses Angular.

import 'bootstrap.css'; import '@mescius/wijmo.styles/wijmo.css'; import './styles.css'; // import '@angular/compiler'; import { Component, Inject, enableProdMode, NgModule } from '@angular/core'; import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; import { BrowserModule } from '@angular/platform-browser'; import { WjChartModule } from '@mescius/wijmo.angular2.chart'; import { DataService, TTask } from './app.data'; import { clamp, format, isArray, isNumber, Rect } from '@mescius/wijmo'; import { FlexChart, FlexChartCore, IRenderEngine, HitTestInfo, Palettes, RenderEventArgs } from '@mescius/wijmo.chart'; // @Component({ selector: 'app-component', templateUrl: 'src/app.component.html' }) export class AppComponent { data: TTask[]; palette: string[]; // constructor(@Inject(DataService) private dataService: DataService) { this.data = dataService.getData(); this.palette = this._getRandomPalette(); } // getTooltipContent(ht: HitTestInfo) { let str = format('<b>{name}</b>:<br/>{start:d} - {end:d}', { name: ht.x, start: ht.item.start, end: ht.item.end }); // if (ht.item && ht.item.percent != null) { str += format('<br/><i>percent complete: {percent}%</i>', ht.item); } // return str; } // // show the percentage complete for each task ganttItemFormatter = (engine: IRenderEngine, ht: HitTestInfo, defaultFormatter: Function) => { // draw the item as usual defaultFormatter(); // // show percentage done let task: TTask = ht.item; // if (isNumber(task.percent) && task.percent > 0) { let pct = clamp(task.percent, 0, 100) / 100, rc = this._getTaskRect(ht.series.chart, task).inflate(-8, -8); // engine.fill = pct == 1 ? 'green' : 'gold';//engine.stroke; engine.drawRect(rc.left, rc.top, rc.width * pct, rc.height); } } // // show the task dependencies ganttChartRendered = (chart: FlexChart, e: RenderEventArgs) => { let tasks: TTask[] = chart.collectionView.items; // tasks.forEach(task => { // for each task let parents = this._getTaskParents(task, tasks); // get the parent tasks // parents.forEach(parent => { // for each parent this._drawConnectingLine(e.engine, chart, task, parent); // draw connector }); }); } // // private _getRandomPalette() { let palettes = Object.keys(Palettes).filter(prop => isArray(Palettes[prop])); let rand = Math.floor(Math.random() * palettes.length); // return Palettes[palettes[rand]]; } // private _drawConnectingLine(engine: IRenderEngine, chart: FlexChart, task: TTask, parent: TTask) { let rc1 = this._getTaskRect(chart, parent), // parent rect rc2 = this._getTaskRect(chart, task), // task rect x1 = rc1.left + rc1.width / 2, // parent x center x2 = rc2.left, // task left y1 = rc1.bottom, // parent bottom y2 = rc2.top + rc2.height / 2; // task y center // // draw connecting line let xs = [x1, x1, x2], ys = [y1, y2, y2]; // engine.drawLines(xs, ys, 'connector', { stroke: 'black' }); // // draw arrow at the end let sz = 5; // xs = [x2 - 2 * sz, x2, x2 - 2 * sz]; ys = [y2 - sz, y2, y2 + sz]; // engine.drawPolygon(xs, ys, 'arrow', { fill: 'black' }); } // private _getTaskRect(chart: FlexChartCore, task: TTask) { let x1 = chart.axisX.convert(task.start.valueOf()), x2 = chart.axisX.convert(task.end.valueOf()), index = chart.collectionView.items.indexOf(task), y1 = chart.axisY.convert(index - .35), y2 = chart.axisY.convert(index + .35); // return new Rect(x1, y1, x2 - x1 + 1, y2 - y1 + 1); } // private _getTaskParents(task: TTask, tasks: TTask[]) { let parents: TTask[] = []; // if (task.parent) { task.parent.split(',').forEach(name => { for (let i = 0; i < tasks.length; i++) { if (tasks[i].name === name) { parents.push(tasks[i]); break; } } }); } // return parents; } } // @NgModule({ imports: [WjChartModule, BrowserModule], declarations: [AppComponent], providers: [DataService], bootstrap: [AppComponent] }) export class AppModule { } // enableProdMode(); // Bootstrap application with hash style navigation and global services. platformBrowserDynamic().bootstrapModule(AppModule);
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <title>MESCIUS Wijmo FlexChart Gantt</title> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <!-- Polyfills --> <script src="node_modules/core-js/client/shim.min.js"></script> <script src="node_modules/zone.js/fesm2015/zone.min.js"></script> <!-- SystemJS --> <script src="https://cdnjs.cloudflare.com/ajax/libs/systemjs/0.21.5/system.src.js" integrity="sha512-skZbMyvYdNoZfLmiGn5ii6KmklM82rYX2uWctBhzaXPxJgiv4XBwJnFGr5k8s+6tE1pcR1nuTKghozJHyzMcoA==" crossorigin="anonymous"></script> <script src="systemjs.config.js"></script> <script> // workaround to load 'rxjs/operators' from the rxjs bundle System.import('rxjs').then(function (m) { System.set(SystemJS.resolveSync('rxjs/operators'), System.newModule(m.operators)); System.import('./src/app.component'); }); </script> </head> <body> <app-component></app-component> </body> </html>
<div class="container-fluid"> <wj-flex-chart #chart [itemsSource]="data" chartType="Bar" bindingX="name" [tooltipContent]="getTooltipContent" [itemFormatter]="ganttItemFormatter" (rendered)="ganttChartRendered(chart, $event)" [palette]="palette"> <wj-flex-chart-series binding="start,end"></wj-flex-chart-series> <wj-flex-chart-axis wjProperty="axisY" [majorGrid]="false" [minorGrid]="true" [reversed]="true"> </wj-flex-chart-axis> </wj-flex-chart> </div>
import { Injectable } from '@angular/core'; // export type TTask = { name: string; start: Date; end: Date; parent: string; percent: number; }; // @Injectable() export class DataService { getData(): TTask[] { let year = new Date().getFullYear(); // return [ { name: 'Task1', start: new Date(year, 0, 1), end: new Date(year, 2, 31), parent: null, percent: 100 }, { name: 'Task2', start: new Date(year, 3, 1), end: new Date(year, 3, 30), parent: 'Task1', percent: 100 }, { name: 'Task3', start: new Date(year, 4, 1), end: new Date(year, 6, 31), parent: 'Task2', percent: 75 }, { name: 'Task4', start: new Date(year, 3, 1), end: new Date(year, 6, 31), parent: 'Task1', percent: 33 }, { name: 'Task5', start: new Date(year, 7, 1), end: new Date(year, 8, 30), parent: 'Task3,Task4', percent: 0 }, { name: 'Task6', start: new Date(year, 9, 1), end: new Date(year, 11, 31), parent: 'Task1,Task5', percent: 0 }, { name: 'Task7', start: new Date(year, 0, 1), end: new Date(year, 11, 31), parent: null, percent: 50 } ]; } }
body { margin-bottom: 24pt; }
(function (global) { SystemJS.config({ transpiler: './plugin-typescript.js', typescriptOptions: { "target": "ES2022", "module": "system", "emitDecoratorMetadata": true, "experimentalDecorators": true, }, baseURL: 'node_modules/', meta: { 'typescript': { "exports": "ts" }, '*.css': { loader: 'systemjs-plugin-css' } }, paths: { // paths serve as alias 'npm:': '' }, packageConfigPaths: [ '/node_modules/*/package.json', "/node_modules/@angular/*/package.json", "/node_modules/@mescius/*/package.json" ], map: { 'core-js': 'https://cdn.jsdelivr.net/npm/core-js@2.6.12/client/shim.min.js', 'typescript': 'https://cdnjs.cloudflare.com/ajax/libs/typescript/5.2.2/typescript.min.js', "rxjs": "https://cdnjs.cloudflare.com/ajax/libs/rxjs/7.8.1/rxjs.umd.min.js", 'systemjs-plugin-css': 'https://cdn.jsdelivr.net/npm/systemjs-plugin-css@0.1.37/css.js', '@mescius/wijmo': 'npm:@mescius/wijmo/index.js', '@mescius/wijmo.input': 'npm:@mescius/wijmo.input/index.js', '@mescius/wijmo.styles': 'npm:@mescius/wijmo.styles', '@mescius/wijmo.cultures': 'npm:@mescius/wijmo.cultures', '@mescius/wijmo.chart': 'npm:@mescius/wijmo.chart/index.js', '@mescius/wijmo.chart.analytics': 'npm:@mescius/wijmo.chart.analytics/index.js', '@mescius/wijmo.chart.animation': 'npm:@mescius/wijmo.chart.animation/index.js', '@mescius/wijmo.chart.annotation': 'npm:@mescius/wijmo.chart.annotation/index.js', '@mescius/wijmo.chart.finance': 'npm:@mescius/wijmo.chart.finance/index.js', '@mescius/wijmo.chart.finance.analytics': 'npm:@mescius/wijmo.chart.finance.analytics/index.js', '@mescius/wijmo.chart.hierarchical': 'npm:@mescius/wijmo.chart.hierarchical/index.js', '@mescius/wijmo.chart.interaction': 'npm:@mescius/wijmo.chart.interaction/index.js', '@mescius/wijmo.chart.radar': 'npm:@mescius/wijmo.chart.radar/index.js', '@mescius/wijmo.chart.render': 'npm:@mescius/wijmo.chart.render/index.js', '@mescius/wijmo.chart.webgl': 'npm:@mescius/wijmo.chart.webgl/index.js', '@mescius/wijmo.chart.map': 'npm:@mescius/wijmo.chart.map/index.js', '@mescius/wijmo.gauge': 'npm:@mescius/wijmo.gauge/index.js', '@mescius/wijmo.grid': 'npm:@mescius/wijmo.grid/index.js', '@mescius/wijmo.grid.detail': 'npm:@mescius/wijmo.grid.detail/index.js', '@mescius/wijmo.grid.filter': 'npm:@mescius/wijmo.grid.filter/index.js', '@mescius/wijmo.grid.search': 'npm:@mescius/wijmo.grid.search/index.js', '@mescius/wijmo.grid.grouppanel': 'npm:@mescius/wijmo.grid.grouppanel/index.js', '@mescius/wijmo.grid.multirow': 'npm:@mescius/wijmo.grid.multirow/index.js', '@mescius/wijmo.grid.transposed': 'npm:@mescius/wijmo.grid.transposed/index.js', '@mescius/wijmo.grid.transposedmultirow': 'npm:@mescius/wijmo.grid.transposedmultirow/index.js', '@mescius/wijmo.grid.pdf': 'npm:@mescius/wijmo.grid.pdf/index.js', '@mescius/wijmo.grid.sheet': 'npm:@mescius/wijmo.grid.sheet/index.js', '@mescius/wijmo.grid.xlsx': 'npm:@mescius/wijmo.grid.xlsx/index.js', '@mescius/wijmo.grid.selector': 'npm:@mescius/wijmo.grid.selector/index.js', '@mescius/wijmo.grid.cellmaker': 'npm:@mescius/wijmo.grid.cellmaker/index.js', '@mescius/wijmo.nav': 'npm:@mescius/wijmo.nav/index.js', '@mescius/wijmo.odata': 'npm:@mescius/wijmo.odata/index.js', '@mescius/wijmo.olap': 'npm:@mescius/wijmo.olap/index.js', '@mescius/wijmo.rest': 'npm:@mescius/wijmo.rest/index.js', '@mescius/wijmo.pdf': 'npm:@mescius/wijmo.pdf/index.js', '@mescius/wijmo.pdf.security': 'npm:@mescius/wijmo.pdf.security/index.js', '@mescius/wijmo.viewer': 'npm:@mescius/wijmo.viewer/index.js', '@mescius/wijmo.xlsx': 'npm:@mescius/wijmo.xlsx/index.js', '@mescius/wijmo.undo': 'npm:@mescius/wijmo.undo/index.js', '@mescius/wijmo.interop.grid': 'npm:@mescius/wijmo.interop.grid/index.js', '@mescius/wijmo.touch': 'npm:@mescius/wijmo.touch/index.js', '@mescius/wijmo.cloud': 'npm:@mescius/wijmo.cloud/index.js', '@mescius/wijmo.barcode': 'npm:@mescius/wijmo.barcode/index.js', '@mescius/wijmo.barcode.common': 'npm:@mescius/wijmo.barcode.common/index.js', '@mescius/wijmo.barcode.composite': 'npm:@mescius/wijmo.barcode.composite/index.js', '@mescius/wijmo.barcode.specialized': 'npm:@mescius/wijmo.barcode.specialized/index.js', "@mescius/wijmo.angular2.chart.analytics": "npm:@mescius/wijmo.angular2.chart.analytics/index.js", "@mescius/wijmo.angular2.chart.animation": "npm:@mescius/wijmo.angular2.chart.animation/index.js", "@mescius/wijmo.angular2.chart.annotation": "npm:@mescius/wijmo.angular2.chart.annotation/index.js", "@mescius/wijmo.angular2.chart.finance.analytics": "npm:@mescius/wijmo.angular2.chart.finance.analytics/index.js", "@mescius/wijmo.angular2.chart.finance": "npm:@mescius/wijmo.angular2.chart.finance/index.js", "@mescius/wijmo.angular2.chart.hierarchical": "npm:@mescius/wijmo.angular2.chart.hierarchical/index.js", "@mescius/wijmo.angular2.chart.interaction": "npm:@mescius/wijmo.angular2.chart.interaction/index.js", "@mescius/wijmo.angular2.chart.radar": "npm:@mescius/wijmo.angular2.chart.radar/index.js", '@mescius/wijmo.angular2.chart.map': 'npm:@mescius/wijmo.angular2.chart.map/index.js', "@mescius/wijmo.angular2.chart": "npm:@mescius/wijmo.angular2.chart/index.js", "@mescius/wijmo.angular2.core": "npm:@mescius/wijmo.angular2.core/index.js", "@mescius/wijmo.angular2.gauge": "npm:@mescius/wijmo.angular2.gauge/index.js", "@mescius/wijmo.angular2.grid.detail": "npm:@mescius/wijmo.angular2.grid.detail/index.js", "@mescius/wijmo.angular2.grid.filter": "npm:@mescius/wijmo.angular2.grid.filter/index.js", "@mescius/wijmo.angular2.grid.grouppanel": "npm:@mescius/wijmo.angular2.grid.grouppanel/index.js", "@mescius/wijmo.angular2.grid.search": "npm:@mescius/wijmo.angular2.grid.search/index.js", "@mescius/wijmo.angular2.grid.multirow": "npm:@mescius/wijmo.angular2.grid.multirow/index.js", "@mescius/wijmo.angular2.grid.sheet": "npm:@mescius/wijmo.angular2.grid.sheet/index.js", '@mescius/wijmo.angular2.grid.transposed': 'npm:@mescius/wijmo.angular2.grid.transposed/index.js', '@mescius/wijmo.angular2.grid.transposedmultirow': 'npm:@mescius/wijmo.angular2.grid.transposedmultirow/index.js', "@mescius/wijmo.angular2.grid": "npm:@mescius/wijmo.angular2.grid/index.js", "@mescius/wijmo.angular2.input": "npm:@mescius/wijmo.angular2.input/index.js", "@mescius/wijmo.angular2.olap": "npm:@mescius/wijmo.angular2.olap/index.js", "@mescius/wijmo.angular2.viewer": "npm:@mescius/wijmo.angular2.viewer/index.js", "@mescius/wijmo.angular2.nav": "npm:@mescius/wijmo.angular2.nav/index.js", "@mescius/wijmo.angular2.directivebase": "npm:@mescius/wijmo.angular2.directivebase/index.js", '@mescius/wijmo.angular2.barcode.common': 'npm:@mescius/wijmo.angular2.barcode.common/index.js', '@mescius/wijmo.angular2.barcode.composite': 'npm:@mescius/wijmo.angular2.barcode.composite/index.js', '@mescius/wijmo.angular2.barcode.specialized': 'npm:@mescius/wijmo.angular2.barcode.specialized/index.js', 'bootstrap.css': 'npm:bootstrap/dist/css/bootstrap.min.css', 'jszip': 'https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js', "@angular/common/http": "https://cdn.jsdelivr.net/npm/@angular/common@16.2.6/fesm2022/http.mjs", "@angular/core": "https://cdn.jsdelivr.net/npm/@angular/core@16.2.6/fesm2022/core.mjs", "@angular/platform-browser": "https://cdn.jsdelivr.net/npm/@angular/platform-browser@16.2.6/fesm2022/platform-browser.mjs", "@angular/common": "https://cdn.jsdelivr.net/npm/@angular/common@16.2.6/fesm2022/common.mjs", "@angular/compiler": "https://cdn.jsdelivr.net/npm/@angular/compiler@16.2.6/fesm2022/compiler.mjs", "@angular/forms": "https://cdn.jsdelivr.net/npm/@angular/forms@16.2.6/fesm2022/forms.mjs", "@angular/localize": "https://cdn.jsdelivr.net/npm/@angular/localize@16.2.6/fesm2022/localize.mjs", "@angular/platform-browser-dynamic": "https://cdn.jsdelivr.net/npm/@angular/platform-browser-dynamic@16.2.6/fesm2022/platform-browser-dynamic.mjs", }, // packages tells the System loader how to load when no filename and/or no extension packages: { "./src": { defaultExtension: 'ts' }, "node_modules": { defaultExtension: 'js' }, wijmo: { defaultExtension: 'js', } } }); })(this);