You can read about general customization rules in the corresponding article.
To add a custom column(s), create a new class by inheriting it from the default gantt.views.tree. Inside the config method split the default UI and add configuration for the new column:
class CustomTree extends gantt.views.tree {
config() {
const compact = this.getParam("compact", true);
const ui = super.config();
ui.columns.splice(compact ? 3 : 2, 0, {
id: "progress",
header: "~",
template: "#progress#%", // progress field comes from dataset
width: 40,
});
if (!compact) ui.width += 40;
return ui;
}
}
Then, override the default class via the corresponding property:
webix.ui({
view: "gantt",
override: new Map([[gantt.views.tree, CustomTree]]),
});
Related sample: Gantt: Extra Column
To add a custom tooltip for tasks, create a new class by inheriting it from the default gantt.views["chart/bars"]. Inside it, call the GetTooltip() method and return a new tooltip with the desired data template to display:
class CustomBarsView extends gantt.views["chart/bars"] {
GetTooltip(obj) {
if (obj.type == "milestone") return `You've reached ${obj.text}`;
const type = obj.type.charAt(0).toUpperCase() + obj.type.slice(1);
const days = obj.duration > 1 ? "days" : "day";
const title = obj.text ? obj.text : "(no title)";
return `${type}: ${title}<br>
Scheduled for ${obj.duration} ${days}<br>
${obj.details ?? ""}`;
}
}
Then, override the default class via the corresponding property:
webix.ui({
view: "gantt",
override: new Map([[gantt.views["chart/bars"], CustomBarsView]]),
});
Related sample: Gantt: Custom Tooltip
In implement zooming functionality for scales, you should provide configuration for the new scale and refresh Gantt widget.
First, define a function that will reset scales according to the mode set:
function resetScales(v, o) {
const current = gantt1.getService("local").getScales();
if (!(originalEndDate || originalStartDate)) {
originalStartDate = webix.Date.add(current.start, 1, o, true);
originalEndDate = webix.Date.add(current.end, -1, o, true);
}
const cellWidth = cellWidths[v];
const scales = getScales(v);
const start = webix.Date.add(originalStartDate, -1, v, true);
const end = webix.Date.add(originalEndDate, 1, v, true);
gantt1
.getService("local")
.setScales(
start,
end,
!(v == "day"),
cellWidth,
current.cellHeight,
scales
);
}
// returns new scales that will be rendered
function getScales(minScale) {
const scales = [];
switch (minScale) {
case "year":
scales.push(yearScale);
break;
case "quarter":
scales.push(yearScale, quarterScale);
break;
// other cases
}
return scales;
}
To be able to actually zoom the chart, define a richselect with options for zooming. When the control value changes, the resetScales function is called:
const zoom = {
view: "richselect",
label: "Zoom:",
value: "month",
width: 300,
options: ["hour", "day", "week", "month", "quarter", "year"],
on: {
onChange: resetScales,
},
};
Then add a toolbar and place the richselect inside the elements field:
webix.ui({
rows: [
{
view: "toolbar",
id: "toolbar",
css: "webix_dark",
paddingX: 10,
elements: [zoom, {}],
},
{
view: "gantt",
id: "gantt1",
url: "https://docs.webix.com/gantt-backend/",
// other properties
},
],
});
Related sample: Gantt: Scale Zoom
This example shows how to get rid of the right panel and:
The changes concern desktop mode only, the compact view remains unchanged.
Task Info view will be placed into a popup, so we need to make several adjustments beforehand:
class CustomInfo extends gantt.views["task/info"] {
// styling
config() {
this.Compact = this.getParam("compact", true);
const ui = super.config();
if (!this.Compact) {
ui.body.rows[0].padding = 4; //toolbar
ui.body.rows[1].padding = 4; //main area
ui.body.rows[1].rows[0].autoheight = true; //template
ui.body.rows[1].rows[1].inputWidth = 0; //button
}
return ui;
}
// "Edit" button action
EditTask() {
if (!this.Compact) {
this.getParentView().Hide();
this.app.show("top/task.form");
} else super.EditTask();
}
// less details
InfoTemplate(obj) {
obj.targets = obj.sources = [];
delete obj.details;
return super.InfoTemplate(obj);
}
}
After that create the InfoPopup class with a Popup component that contains the above CustomInfo view. Also, define methods to show/hide the popup:
class InfoPopup extends gantt.views.JetView {
config() {
return {
view: "popup", width: 350,
body: CustomInfo,
};
}
Show(node) {
this.getRoot().show(node);
}
Hide() {
this.getRoot().hide();
}
}
The next step is to show and hide the Info Popup upon clicking the tasks. It can be implemented by redefining the default gantt.views["chart/bars"] class:
class CustomBarsView extends gantt.views["chart/bars"] {
config() {
const ui = super.config();
// show InfoPopup on clicking the task
ui.cells[1].on.onItemClick = (id, e, node) => {
this.State.$batch({
parent: null, selected: id,
});
this.Info.Show(node);
};
return ui;
}
init(view) {
super.init(view);
// create a popup window
this.Info = this.ui(InfoPopup);
// handle hide event
this.on(this.app, "edit:stop", () => {
this.State.selected = this.State.parent = null;
this.Info.Hide();
});
}
}
If needed, you can split Form UI into columns and show the Notes field on the right side. Also, as the form now overlaps Tasks area, on-change editing can be removed.
class CustomForm extends scheduler.views["event/form"] {
config() {
let ui = super.config();
if (!this.Compact) {
//textarea
const notes = ui.body.rows[1].elements.splice(5, 1)[0];
notes.labelPosition = "top";
notes.height = 334;
//form with 3 columns
const form = ui.body.rows[1];
form.cols = [
{ margin: form.margin, rows: ui.body.rows[1].elements },
{ rows: [notes, {}] },
{ width: 300 },
];
form.margin = 20;
delete ui.body.rows[1].elements;
ui.body.rows[1] = form;
//bar
ui.body.rows[0].padding.right = 335;
}
return ui;
}
}
After that, adjust saving logic for the CustomForm:
// "Done" button action
Done(exit) {
// save data if a new task is created or the existing one is updated
if (this.Form.isDirty())
this.UpdateTask().then(() => this.Back(exit));
else this.Back(exit);
}
// "Close" icon action
Close() {
// ask to save form data in case of changes
if (this.Form.isDirty()) {
webix.confirm("Save changes?")
.then(() => this.Done(true))
.catch(() => this.Back(true));
} else this.Back(true);
}
// clear the form and show the Tasks area
Back(exit) {
if (!this.Compact) {
this.Form.clear();
this.State.selected = this.State.parent = null;
this.app.show("top/chart");
} else super.Back(close);
}
To be able to show the form instead of Tasks area, you need to replace a static subview with a dynamic one and then show Tasks area during initialization.
class CustomTopView extends gantt.views.top {
config() {
const ui = super.config();
// insert dynamic subview to show chart or form
ui.cells[0].cols[2] = { $subview: true };
return ui;
}
init(view) {
super.init(view);
//show chart
this.show("chart/bars");
}
}
Then you will be able to open the form there from the CustomInfo:
// EditTask method from the CustomInfo class described above
this.app.show("top/task.form");
And open a form when the new task is created:
// if new task is created, open the form
ShowTask(path) {
if (this.Compact) super.ShowTask(path);
else if (path === "form")
this.show("task.form");
}
// preserve default hide logic only for compact mode
HideTask() {
if (this.Compact);
super.HideTask();
}
Finally, override the default classes via the corresponding property:
webix.ui({
view: "gantt",
override: new Map([
[gantt.views["chart/bars"], CustomBarsView],
[gantt.views.top, CustomTopView],
[gantt.views["task/info"], CustomInfo],
[gantt.views["task/form"], CustomForm]
])
});
Related sample: Gantt: Info Window and Wide Form
This example shows how to:
First of all, include a hour scale in the array of Gantt scales.
view:"gantt",
scales: [
{ unit: "day", format: "%M %d" },
{ unit: "hour", format: "%H:00" },
]
Then you will need to modify several inner classes to display task time in the interface.
To be able to set task time, you should create a new class by inheriting it from gantt.views["task/form"]. Modify JSON configuration of the form to enable timepicker for the inner calendars:
class CustomForm extends gantt.views["task/form"] {
config() {
const ui = super.config();
const form = ui.body.rows[1];
const startCal = form.elements[2];
startCal.timepicker = startCal.suggest.body.timepicker = true;
const endCal = form.elements[3];
endCal.timepicker = endCal.suggest.body.timepicker = true;
return ui;
}
}
To display specified time in the task info window, create a new class by inheriting it from gantt.views["task/info"]. Return a string in the full-date format from its DateFormat method:
class CustomInfo extends gantt.views["task/info"] {
DateFormat(date) {
return webix.i18n.fullDateFormatStr(date);
}
}
To display date and time in the Task tree, create a new class by inheriting it from gantt.views.tree. To render a full-date string, modify the JSON-configuration that is returned by the config method of the parent class:
class CustomTree extends gantt.views.tree {
config() {
const tree = super.config();
tree.columns[1].format = webix.i18n.fullDateFormatStr;
tree.columns[1].minWidth = 170;
return tree;
}
// one more method
}
Next step is to redefine task tooltip, so that task date and duration will include time values. Create a new class by inheriting it from gantt.views["chart/bars"]. Return a template containing info about task time from the GetTooltip method:
class CustomBars extends gantt.views["chart/bars"] {
GetTooltip(obj, _) {
const parser = webix.i18n.fullDateFormatStr;
let tip = `${obj.text || _("(no title)")}<br>
<br>${_("Start date")}: ${parser(obj.start_date)}`;
if (obj.type != "milestone") {
const duration =
obj.duration < 1
? this.GetPreciseDuration(obj, _)
: this.GetDayDuration(obj, _);
tip += `<br>${_("End date")}: ${parser(obj.end_date)}
<br>${_("Lasts")} ${duration}`;
}
return tip;
}
/* gets duration of tasks that last less than 1 day and
returns task duration in hours (for hour scale and scales with *precise: true*).
Otherwise returns duration as 1 day */
GetPreciseDuration(obj, _) {
const { minUnit, precise } = this.Local.getScales();
const showHours = minUnit === "hour" || precise;
return showHours
? `${Math.round(obj.duration * 24)} ${_("hours")}`
: `1 ${_("day")}`;
}
GetDayDuration(obj, _) {
return `${obj.duration} ${obj.duration > 1 ? _("days") : _("day")}`;
}
}
Finally, do not forget to override default classes:
webix.ui({
view: "gantt",
url: "https://docs.webix.com/gantt-backend/",
override: new Map([
[gantt.views.tree, CustomTree],
[gantt.views["task/info"], CustomInfo],
[gantt.views["task/form"], CustomForm],
[gantt.views["chart/bars"], CustomBars],
]),
});
Related sample: Gantt: Setting Time of Tasks
This example demonstrates how to set planned dates as a snapshot of the current positions of tasks.
To enable planned dates, set the baseline property to true:
webix.ui({
view: "gantt",
baseline: true,
...
})
We need some kind of control to make a snapshot. Let's make a button called "Create a snapshot". On click, a custom createSnapshot () method will be called:
{
view: "button",
value: "Create a snapshot",
...
click: () => createSnapshot(),
},
Using the serialize() method, we get the current state of Gantt:
function createSnapshot() {
const localTasks = $$("gantt")
.$app.getService("local")
.tasks();
const data = localTasks.serialize();
...
To save the start and end dates as planned_start and planned_end, and the duration as planned_duration, we declare a separate setBaseline() function:
function setBaseline(branch) {
branch.forEach(t => {
if (t.type != "milestone") {
t.planned_start = t.start_date;
t.planned_end = t.end_date;
t.planned_duration = t.duration;
if (t.data) setBaseline(t.data);
}
});
}
Then we will call it inside the createSnapshot() method to set baseline properties for all tasks:
...
setBaseline(data);
...
In the end, we need to clear all collections with the clearAll() method and use the parse() method to load our modified data into the component:
...
localTasks.clearAll();
localTasks.parse(data);
}
Related sample: Gantt: Creating Snapshots
Back to top