ts-gantt
Version:
simple gantt chart written using typescript
306 lines (264 loc) • 14.5 kB
Markdown
# TsGantt
<p align="left">
<a href="https://www.npmjs.com/package/ts-gantt"><img
src="https://img.shields.io/npm/v/ts-gantt" alt="Npm"></a>
<a href="https://circleci.com/gh/yermolim/ts-gantt"><img
src="https://circleci.com/gh/yermolim/ts-gantt.svg?style=shield" alt="Build Status"></a>
<a href="https://codecov.io/gh/yermolim/ts-gantt"><img
src="https://img.shields.io/codecov/c/github/yermolim/ts-gantt/master.svg?style=flat-round" alt="Codecov"></a>
<a href="https://github.com/yermolim/ts-gantt/blob/master/LICENSE"><img
src="https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-round" alt="License"></a>
<br>
</p>
Simple library for creating gantt chart combined with task grid.

### Current features
<ul>
<li>highly customizable</li>
<li>contains two resizable parts: task grid and Gantt chart</li>
<li>resizable grid columns with drag'n'drop reordering support</li>
<li>tree-like structure with expandable and selectable rows</li>
<li>support for tasks with two date pairs (planned and actual)</li>
<li>single or multiple row selection mode</li>
<li>localization support
<ul>
<li>four out-of-box supported languages: English, Ukrainian, Russian, Japanese</li>
<li>custom locales support</li>
<li>instant locale switching</li>
</ul>
</li>
<li>configurable chart
<ul>
<li>four available chart scales: day, week, month, year</li>
<li>three available chart display modes: planned dates, actual dates, both</li>
<li>instant chart scale and display mode switching</li>
</ul>
</li>
<li>editing task dates and progress percentages by resizing the chart bars</li>
<li>written completely in Typescript</li>
<li>shadow DOM mode can be used to make sure it won't mess with your styling/layout</li>
<li>light codebase: only one dependency (lightweight <a href="https://github.com/iamkun/dayjs">Day.js<a> is used to work with dates)</li>
</ul>
## Getting started
### Install and initialize
#### With npm
```
npm install ts-gantt
```
```javascript
import { TsGantt } from "ts-gantt";
const chart = new TsGantt("#container-selector");
```
include stylesheet ('ts-gantt/dist/styles.min.css') in any suitable way
#### Or using CDN
```html
<link rel="stylesheet" href="https://unpkg.com/ts-gantt/dist/styles.min.css">
<script src="https://unpkg.com/ts-gantt/dist/ts-gantt.umd.min.js"></script>
```
```javascript
const chart = new tsGantt.TsGantt("#container-selector");
```
#### ⚠️for chart to function properly its container element must have relative, absolute or fixed position!
#### ⚠️if you use the shadow DOM mode for the chart, including the stylesheet is not needed, the styles bundled inside the main file will be used.
### Set your task list
your tasks must implement following interface
```javascript
interface TsGanttTaskModel {
id: string; // to avoid incorrect behaviour please use unique ids within array
parentId: string | null | undefined; // use if you need tree-like structure
name: string;
progress: number; // percentage from 0 to 100. higher or lower values will be truncated
datePlannedStart: Date | null | undefined;
datePlannedEnd: Date | null | undefined;
dateActualStart: Date | null | undefined;
dateActualEnd: Date | null | undefined;
localizedNames: {[key: string]: string} | null | undefined; // eg {"en": "Name", "uk": "Ім'я", "ru": "Имя"}
}
```
to pass your task array to chart use 'tasks' property setter
```javascript
chart.tasks = yourTaskArray;
```
task are updated in the same way. you should just pass actual task array when any change happens. change detection will find tasks that have been changed/added/removed and will replace/add/remove them in chart.
### Switch modes
#### Language
you can instantly switch chart language
```javascript
chart.locale = locale; // "en" | "uk" | "ru" | "ja" or any custom locale you provided in chart options
```
#### Timeline scale
you can instantly switch chart timeline scale
```javascript
chart.chartScale = scale; // "day" | "week" | "month" | "year"
```
#### Display mode (chart bars)
you can instantly switch chart bar display mode
```javascript
chart.chartDisplayMode = mode; // "planned" | "actual" | "both"
```
"planned" - show only planned dates bar on timeline <br/>
"actual" - show only actual dates bar on timeline
### Select tasks
select task rows programmatically
```javascript
chart.selectedTasks = [{id: "taskIdString"}];
```
get selected tasks
```javascript
const selectedTasks = chart.selectedTasks;
```
### Customize chart
you can customize chart in two ways:
<ul>
<li>edit or override styles in the styles file (if shadow DOM is not used)</li>
<li>provide custom options to 'TsGantt' class constructor</li>
</ul>
#### Css
preffered way to customize styling is to change css variable values. It'll work for both regular DOM and shadow DOM modes
```css
:root {
--tsg-table-min-width: 100px;
--tsg-chart-min-width: 100px;
--tsg-nesting-indent: 20px; /* indent width per nesting level */
--tsg-background-color: white;
--tsg-foreground-color: black;
--tsg-separator-color: rgb(80, 80, 80); /* color of movable vertical line between parts */
--tsg-header-color: rgb(210, 210, 210); /* header background color */
--tsg-border-color: rgb(190, 190, 190);
--tsg-symbol-color: rgb(80, 80, 80); /* color of row special symbols */
--tsg-selection-color: rgb(230, 230, 230); /* background color of selected row */
--tsg-scrollbar-track-color: #eeeeee; /* webkit browsers only */
--tsg-scrollbar-thumb-color: #b0b0b0; /* webkit browsers only */
--tsg-not-started-fg-color: dimgray; /* color of task row text depending on task state */
--tsg-in-progress-fg-color: black;
--tsg-overdue-fg-color: darkred;
--tsg-completed-fg-color: darkgreen;
--tsg-completed-late-fg-color: sienna;
--tsg-today-line-color: orangered; /* color of vertical line on chart that represents today */
--tsg-chart-bar-color-1: skyblue; /* chart bars colors */
--tsg-chart-bar-color-2: lightcoral;
--tsg-chart-bar-accent-1: darkcyan;
--tsg-chart-bar-accent-2: darkred;
--tsg-font-family: 'Calibri', sans-serif;
--tsg-font-size: 14px;
--tsg-line-height: 16px;
--tsg-max-cell-text-lines: 2; /* max lines of multiline text */
}
```
#### Options
you can apply your custom options by passing options object as second parameter to 'TsGantt' constructor
```javascript
const options = new TsGanttOptions({
multilineSelection: false,
enableChartEdit = true;
// other options you want to change
});
// or you can use assignment expressions (come in handy for getters and formatters that refence options object itself)
options.columnValueGetters[0] = task =>
task.localizedNames && task.localizedNames[options.locale] || task.name; // value getter implementation for first column
// esm chart init with options
const chart = new TsGantt("#container-selector", options);
// umd chart init with options
const chart = new tsGantt.TsGantt("#container-selector", options);
// ⚠️chart class in not designed to allow changes in options instance after the chart initialization.
// such changes can lead to unpredictable behavior.
// to change locale, scale and display mode use appropriate TsGantt instance methods.
// if it's very necessary to change other options after chart init then you should destroy old chart instance and create new one.
this.chart.destroy();
this.chart = new TsGantt("#container-selector", options);
```
<details><summary>ℹ️ complete list of 'TsGanttOptions' class properties you can use</summary>
<p>
```javascript
// some default values ommited for brevity. you can always see them in 'TsGanttOptions' source code
useShadowDom = false; // render chart using shadow DOM
multilineSelection = true; // allow multiple rows to be selected at the same time
useCtrlKeyForMultilineSelection = false; // enable using ctrl key to select multiple rows
drawTodayLine = true; // draw a vertical line on chart that represents today
highlightRowsDependingOnTaskState = true; // change row text color depending on task state
separatorWidthPx = 5; // vertical central line width
headerHeightPx = 90; // lower values are not recommended, but you can still try
rowHeightPx = 40; // lower values are not recommended, but you can still try
borderWidthPx = 1;
barStrokeWidthPx = 2;
barMarginPx = 2;
barCornerRadiusPx = 6;
// special row symbols. you can also use some HTML code
rowSymbols: TsGanttRowSymbols = {childless: "◆", collapsed: "⬘", expanded: "⬙"};
chartShowProgress = true; // indicating progress percentage on chart bar using different color
chartDisplayMode: "planned" | "actual" | "both";
chartScale: "day" | "week" | "month" | "year";
// optimal spare space on timeline edges in days
chartDateOffsetDays: {[key: string]: number} = {"day": 14, "week": 60, "month": 240, "year": 730};
// minimal spare space on timeline edges in days
// chart timeline is redrawn only when trespassing minimal distance to chart edge to nearest bar
chartDateOffsetDaysMin: {[key: string]: number} = {"day": 7, "week": 30, "month": 120, "year": 365};
// width of 1 day on timeline. not recommended to use lower values than default
chartDayWidthPx: {[key: string]: number} = {"day": 60, "week": 20, "month": 3, "year": 1};
locale = "en"; // default locale
localeDecimalSeparator: {[key: string]: string} = {en: ".", uk: ",", ru: ",", ja: "."};
// you can provide any format strings that are supported by dayjs
localeDateFormat: {[key: string]: string} = {en: "MM/DD/YYYY", uk: "DD.MM.YYYY", ru: "DD.MM.YYYY", ja: "YYYY/MM/DD"};
localeFirstWeekDay: {[key: string]: number} = {en: 0, uk: 1, ru: 1, ja: 0}; // Sunday is 0
localeDateMonths: {[key: string]: string[]}; // array of 12 string values for each locale. eg ["January", "February", ...etc]
localeDateDays: {[key: string]: string[]}; // array of 7 string values for each locale. eg ["Sunday", "Monday", ...etc]
localeDateDaysShort: {[key: string]: string[]}; // array of 7 string values for each locale. eg ["Su", "Mo", ...etc]
localeDateScale: {[key: string]: string[]}; // array of 3 string values for each locale. eg ["Weeks", "Months", "Years"]
localeDurationFormatters: {[key: string]: (duration: number) => string}; // duration formatter function for each locale
// Data columns setup.
// there are default 8 columns: "Name", "Progress", "Start date planned", "End date planned",
// "Start date actual", "End date actual", "Duration planned", "Duration actual".
// You can remove the columns or add your own ones, but you need too make sure to edit all of the following arrays respectively:
// you should provide a width, an alignment, a value getter, and localized headers.
// !!!the length of each column-related array should be equal to the columns count!!!
columnsMinWidthPx: number[]; // array of numeric values, one for each of the columns. 0 to disable column
columnsContentAlign: ("start" | "center" | "end")[]; // array of values, one for each of the columns
// default column value getters return localized values by taking into account all the properties assigned above
// but you can provide your own ones if you need more complex output
// returned value is assigned to cell's innerHTML property. so you can use html tags
columnValueGetters: ((a: TsGanttTask) => string)[]; // array of string value getters for each locale
// column header locales should be provided for all the locales intended for use
localeHeaders: {[key: string]: string[]}; // array of string values for each locale
//
taskComparer: (taskA: TsGanttTask, taskB: TsGanttTask) => number; // you can provide here your custom task comparer
enableChartEdit = false; // (experimental) allows making changes to tasks by dragging chart bar handles
// all the task models including the changed ones can be returned to your code using the TsGantt.tasks property getter
```
</p>
</details>
### Event callbacks
you can pass callbacks for chart row events using TsGantt properties shown below
```javascript
onRowClickCb: (model: TsGanttTaskModel, event: MouseEvent) => void;
onRowDoubleClickCb: (model: TsGanttTaskModel, event: MouseEvent) => void;
onRowContextMenuCb: (model: TsGanttTaskModel, event: MouseEvent) => void;
onSelectionChangeCb: (models: TsGanttTaskModel[]) => void;
```
context menu implementation is not provided, but you can implement your own using callback
### Adding a custom column to the end of the column list example
```javascript
const options = new tsGantt.TsGanttOptions();
options.columnsMinWidthPx.push(100);
options.columnsContentAlign.push("center");
// the "customColumnKey" below is the property of the model object
// which contains the value needed to be shown
options.columnValueGetters.push(task => (task["customColumnKey"] ?? ""));
options.localeHeaders.en.push("User column");
const ganttChart = new tsGantt.TsGantt("#gantt-container", options);
```
## TODO list
<ul>
<li><del>add optional multiple row selection</del> added in 0.2.0</li>
<li><del>make grid columns resizable</del> added in 0.2.2</li>
<li><del>add callbacks on chart events (on row click/double click, selection change)</del> added in 0.3.0</li>
<li><del>remove the hardcoded column number, allow adding custom columns</del> added in 0.4.0</li>
<li><del>move chart to shadow DOM</del> added as an option in 0.5.0</li>
<li><del>allow drag'n'drop grid column reordering</del> added in 0.6.0</li>
<li><del>add optional possibility to move/resize chart bars</del> added in 0.7.0</li>
<li>implement applying changes to the parent task when changing dates for its child</li>
<li>add event firing on task change</li>
<li>add optional tooltips on bar hover</li>
<li>increase code coverage</li>
<li>optimize task change detection</li>
<li>add row virtualization (move grid to custom table implementation)</li>
</ul>