Решения

Переключение между гридом и диаграммой

В стандартной настройке интерфейса легко переключаться между видами грида и диаграммы. Нужно просто изменить параметры show_grid или show_chart и затем вызвать метод render(), чтобы применить изменения.

function toggleGrid(){
    gantt.config.show_grid = !gantt.config.show_grid;
    gantt.render();
}

Related sample:  Gantt. Переключение грида (стандартный макет)

function toggleChart(){
    gantt.config.show_chart = !gantt.config.show_chart;
    gantt.render();
}

Related sample:  Gantt. Переключение шкалы времени (стандартный макет)

Если вы используете кастомный макет, вам понадобятся отдельные настройки для макетов с гридом и без него, или с временной шкалой и без неё. Для переключения между ними обновите свойство gantt.config.layout и переинициализируйте Gantt с помощью метода init().

let showGrid = true;
function toggleGrid() {
    showGrid = !showGrid;
    if (showGrid) {
        gantt.config.layout = gridAndChart; // макет с гридом и временной шкалой
    } else {
        gantt.config.layout = onlyChart; // макет только с временной шкалой
    }
    gantt.init("gantt_here");
}

Related sample:  Gantt. Переключение грида (кастомный макет)

let showChart = true;
function toggleChart() {
    showChart = !showChart;
    if (showChart) {
        gantt.config.layout = gridAndChart; // макет с гридом и временной шкалой
    } else {
        gantt.config.layout = onlyGrid; // макет только с гридом
    }
    gantt.init("gantt_here");
}

Related sample:  Gantt. Переключение временной шкалы (кастомный макет)

Переключение видов ресурсов

Подобно переключению видов грида и диаграммы, вы можете управлять видами ресурсов, настраивая различные конфигурации макетов. Вы изменяете свойство gantt.config.layout и обновляете Gantt с помощью метода init().

let resourceChart = true;
 
function layoutChange() {
    resourceChart = !resourceChart;
    gantt.config.layout = resourceChart ? resourceLayout : noresourceLayout;
    gantt.init("gantt_here");
};

Related sample:  Gantt. Переключение диаграммы загрузки ресурсов

let histogramView = true;
 
function layoutChange() {
    histogramView = !histogramView;
    gantt.config.layout = histogramView ? histogramLayout : simpleLayout;
    gantt.init("gantt_here");
};

Related sample:  Gantt. Переключение гистограммы ресурсов

В качестве альтернативы вы можете динамически генерировать макет, используя виды макета, и переинициализировать Gantt для отражения изменений.

Related sample:  Gantt. Генерация макета

Добавление бесконечной прокрутки к временной шкале

Чтобы реализовать бесконечную прокрутку, вам, вероятно, потребуется настроить отображаемый диапазон дат, используя свойства gantt.config.start_date и gantt.config.end_date.

Использование полосы прокрутки

Отслеживайте позицию прокрутки и расширяйте диапазон дат по мере необходимости. Имейте в виду, что частая перерисовка с помощью render() может повлиять на производительность, поэтому лучше делать это с задержкой.

gantt.init("gantt_here");
gantt.parse(tasks);
 
gantt.attachEvent("onGanttScroll", function (left, top) {
    const left_date = gantt.dateFromPos(left);
    const right_date = gantt.dateFromPos(left + gantt.$task.offsetWidth);
 
    gantt.config.start_date = gantt.config.start_date || gantt.getState().min_date;
    gantt.config.end_date = gantt.config.end_date || gantt.getState().max_date;
 
    const min_allowed_date = gantt.date.add(gantt.config.start_date, 1, "day");
    const max_allowed_date = gantt.date.add(gantt.config.end_date, -2, "day");
 
    let repaint = false;
    if (+left_date <= +min_allowed_date) {
        gantt.config.start_date = gantt.date.add(gantt.config.start_date, -2, "day");
        repaint = true;
    }
    if (+right_date >= +max_allowed_date) {
        gantt.config.end_date = gantt.date.add(gantt.config.end_date, 2, "day");
        repaint = true;
    }
 
    if (repaint) {
        setTimeout(function () {
            gantt.render();
            gantt.showDate(left_date);
        }, 20);
    }
});

Related sample:  Gantt. Бесконечная прокрутка при использовании полосы прокрутки

Во время перемещения временной шкалы

Проверяйте позицию прокрутки во время перемещения, и если она близка к краям временной шкалы, расширяйте диапазон дат.

gantt.attachEvent("onMouseMove", function (id, e) {
  if (!gantt.getState().drag_id && e.buttons == 1) {
    const left_date = gantt.dateFromPos(gantt.getScrollState().x);
    const right_date = gantt.dateFromPos(
      gantt.getScrollState().x + gantt.$task.offsetWidth - 1
    );
    if (left_date && +left_date <= +gantt.config.start_date) {
      gantt.config.start_date = gantt.date.add(gantt.config.start_date, -1, 'day');
      gantt.render();
    }
    if (right_date && +gantt.config.end_date < +gantt.date.add(right_date, 1, 'day')) {
      gantt.config.end_date = gantt.date.add(gantt.config.end_date, 1, 'day');
      gantt.render();
    }
  }
});

Related sample:  Gantt. Бесконечная прокрутка при перемещении временной шкалы

При перемещении задачи

Если диапазон дат не установлен, просто вызовите render(), когда задача перемещается вблизи краев временной шкалы.

gantt.init("gantt_here");
gantt.parse(tasks);
 
gantt.attachEvent("onTaskDrag", function (id, mode, task, original) {
    if (task.start_date <= gantt.getState().min_date ||
        task.end_date >= gantt.getState().max_date) {
        gantt.render();
    }
});

Related sample:  Gantt. Бесконечная прокрутка при перемещении задачи (настройки диапазона по умолчанию)

Если у вас установлен диапазон дат, настройте его по мере необходимости.

gantt.init("gantt_here");
gantt.parse(tasks);
 
gantt.config.start_date = new Date(2025, 02, 28);
gantt.config.end_date = new Date(2025, 03, 10);
gantt.render();
 
gantt.attachEvent("onTaskDrag", function (id, mode, task, original) {
    if (+task.start_date <= +gantt.config.start_date) {
        gantt.config.start_date = gantt.date.add(
            gantt.config.start_date, -1, gantt.config.duration_unit
        );
        gantt.render();
    }
    if (+task.end_date >= +gantt.config.end_date) {
        gantt.config.end_date = gantt.date.add(
            gantt.config.end_date, 1, gantt.config.duration_unit
        );
        gantt.render();
    }
});

Related sample:  Gantt. Бесконечная прокрутка при перемещении задачи (явные настройки диапазона)

Динамическая загрузка задач

Вы можете использовать событие onGanttScroll для обнаружения момента, когда вы достигли последней видимой задачи, и загрузки дополнительных задач с помощью метода parse().

gantt.attachEvent("onGanttScroll", function (left, top) {
    const visibleTasks = gantt.getVisibleTaskCount();
    const lastVisibleTask = gantt.getTaskByIndex(visibleTasks - 1);
 
    if (gantt.getTaskRowNode(lastVisibleTask.id)) {
        const tasks = load_tasks();
        gantt.parse(tasks);
    }
});

Related sample:  Gantt. Динамическая загрузка данных

Разворачивание или сворачивание всех задач

Чтобы открыть или закрыть все задачи, используйте методы open() и close() в функции eachTask(). Обертывание этого в batchUpdate() гарантирует, что диаграмма обновится только один раз.

function collapseAll() {
    gantt.batchUpdate(function () {
        gantt.eachTask(function (task) {
            gantt.close(task.id);
        });
    });
}
 
function expandAll() {
    gantt.batchUpdate(function () {
        gantt.eachTask(function (task) {
            gantt.open(task.id);
        });
    });
}

Related sample:  Gantt. Добавление кнопок сворачивания/разворачивания в заголовок Gantt

Related sample:  Gantt. Сворачивание/разворачивание всех задач

Отображение нескольких строк в ячейках грида или заголовках

Чтобы включить многострочный текст в заголовке грида, можно добавить следующий CSS:

.gantt_grid_head_text {
    line-height: 12px;
    white-space: normal;
}

Related sample:  Gantt. Многострочный текст в заголовке грида

Для ячеек грида используйте этот CSS:

.gantt_tree_content, .gantt_task_content {
    line-height: 12px;
    white-space: normal;
    overflow-wrap: break-word; 
}

Related sample:  Gantt. Многострочный текст в ячейках грида и временной шкале

Related sample:  Gantt. Многострочный текст в ячейках столбца грида

Добавление пользовательского столбца в грид

Чтобы включить пользовательский столбец, измените параметр gantt.config.columns. Свойство name должно соответствовать свойству задачи, которое вы хотите отобразить, или вы можете использовать функцию template() для пользовательского контента.

gantt.config.columns = [
    /*
    другие столбцы
    */
    {
        name: "progress", label: "Прогресс", width: 50, resize: true, align: "center", 
        template: function (task) {
            return Math.round(task.progress * 100) + "%";
        }
    },
    /*
    другие столбцы
    */
];

Related sample:  Gantt. Пользовательский столбец с шаблоном для прогресса задачи

Related sample:  Gantt. Пользовательский столбец с шаблоном для кнопок действий

Добавление пользовательской кнопки добавления (+)

Чтобы создать пользовательскую кнопку добавления, определите столбец в параметре gantt.config.columns. Избегайте названия add, чтобы предотвратить поведение по умолчанию. Используйте функцию template, чтобы вернуть HTML-кнопку с пользовательским обработчиком кликов.

gantt.config.columns = [
    /*
    другие столбцы
    */
    {
        name: "add_tasks", label: "+", width: 50, resize: true, align: "center", 
        template: function (task) {
            return `<button onclick='addTask(${task.id})';>`;
        }
    },
];

Related sample:  Gantt. Пользовательские столбцы с шаблонами для кнопок добавления (+)

Добавление пользовательской шкалы

Чтобы создать пользовательскую шкалу, необходимо определить пользовательскую единицу шкалы времени и реализовать логику расчета дат.

Вот пример создания пользовательской шкалы для рабочих смен (06:30, 18:30):

gantt.date.custom_scale_start = function (date) {
    return date;
};
 
gantt.date.add_custom_scale = function (date, inc) {
    let next = new Date(date)
    if (!next.getMinutes()) {
        gantt.date.day_start(next)
        next = gantt.date.add(next, 6, "hour");
        next = gantt.date.add(next, 30, "minute");
    }
    else {
        next = gantt.date.add(next, 12 * inc, "hour");
    }
    return next
};
 
gantt.config.scales = [
    { unit: "day", step: 1, date: "%d" },
    { unit: "custom_scale", step: 1, date: "%H:%i" },
];

Related sample:  Gantt. Пользовательские часы рабочих смен на шкале

Вот еще один пример, где вместо дней используются числа:

gantt.config.scales = [
    {
        unit: "day", step: 1, format: function (date) {
            return gantt.getScale().trace_indexes[+date] + 1
        }
    }
]

Related sample:  Gantt. Номера дней на шкале

Чтобы создать пользовательскую шкалу для 5-дневных рабочих недель, вы можете использовать следующий пример:

const weekScaleTemplate = function (date) {
    const dateToStr = gantt.date.date_to_str("%d");
    const endDate = gantt.date.add(gantt.date.add(date, 5, "day"), -1, "day");
    return dateToStr(date) + " - " + dateToStr(endDate);
};
 
gantt.date.five_days_start = function (date) {
    return date;
};
 
gantt.date.add_five_days = function (date, inc) {
    if (date.getDay() == 0 || date.getDay() == 6) {
        return gantt.date.add(date, 1 * inc, "day");
    }
    gantt.date.week_start(date);
    return gantt.date.add(date, 5 * inc, "day");
};
 
 
gantt.config.scales = [
    { unit: "month", step: 1, format: "%F, %Y" },
    { unit: "five_days", step: 1, format: weekScaleTemplate },
];
 
gantt.ignore_time = function (date) {
    return date.getDay() == 0 || date.getDay() == 6;
};

Related sample:  5-дневные рабочие недели на шкале

Для пользовательской шкалы, отображающей недели года (начиная с первого дня года), этот пример будет полезен:

gantt.date.custom_week_start = function (date) {
    return date;
};
 
gantt.date.add_custom_week = function (date, inc) {
    const year_start = new Date(date);
    gantt.date.year_start(year_start);
    const week_number = Math.round(gantt.calculateDuration(year_start, date) / 7);
 
    const next_week = gantt.date.add(year_start, week_number + 1, "week");
    if (next_week.getYear() != date.getYear()) {
        gantt.date.year_start(next_week)
    }
    return next_week;
};
 
 
const custom_week_template = function (date) {
    const year_start = gantt.date.year_start(new Date(date));
    const week_number = Math.round(gantt.calculateDuration(year_start, date) / 7) + 1;
 
    return "Week:" + week_number
}
 
gantt.config.scales = [
    { unit: 'custom_week', step: 1, template: custom_week_template },
    { unit: 'day', step: 1, format: "%d, %M" },
];

Related sample:  Gantt. Недели года на шкале

Копирование и вставка задач

Чтобы дублировать задачи, метод copy() создает глубокую копию объекта задачи. Вы можете затем изменить ID скопированной задачи и добавить её, используя addTask() или createTask().

Вот как добавить кнопку для клонирования задачи:

function clone_task(id) {
    const task = gantt.getTask(id);
    const clone = gantt.copy(task);
    clone.id = +(new Date());
    gantt.addTask(clone, clone.parent, clone.$index)
}
 
gantt.config.columns = [
    /*
    другие столбцы
    */
    {
        name: "clone", label: "клонировать", width: 44, template: function (task) {
            return "<input type=button value='V' onclick=clone_task(" + task.id + ")>"
        }
    }
];

Related sample:  Gantt. Клонирование задачи

Вот пример клонирования задачи вместе со всеми её подзадачами и связями:

let child_links;
let clone_original_ids_table;
 
function obtain_link_ids(id) {
  const task = gantt.getTask(id);
  const source_links = task.$source;
  for (let i = 0; i < source_links.length; i++) {
    child_links.push(source_links[i]);
  }
}
 
function create_clone_original_ids_table(original_id, clone_id) {
  clone_original_ids_table[original_id] = clone_id;
}
 
function clone_child_links() {
 for (let i = 0; i < child_links.length; i++) {
  const link = gantt.getLink(child_links[i]);
  if (clone_original_ids_table[link.source] && clone_original_ids_table[link.target]){
    const clone_link = {};
    clone_link.id = gantt.uid();
    clone_link.target = clone_original_ids_table[link.target];
    clone_link.source = clone_original_ids_table[link.source];
    clone_link.type = link.type;
    gantt.addLink(clone_link)
  }
 }
}
 
function clone_children(id, new_parent) {
  const children = gantt.getChildren(id)
  for (let i = 0; i < children.length; i++) {
    const child_original = gantt.getTask(children[i]);
    const child_clone = gantt.copy(child_original);
    child_clone.id = gantt.uid();
    child_clone.parent = new_parent;
    gantt.addTask(child_clone, child_clone.parent, child_clone.$index);
 
    obtain_link_ids(child_original.id);
    create_clone_original_ids_table(child_original.id, child_clone.id);
 
    if (gantt.hasChild(child_original.id)) clone_children(
      child_original.id, child_clone.id
    );
  }
}
 
function clone_task(id) {
  const task = gantt.getTask(id);
  const clone = gantt.copy(task);
  clone.id = gantt.uid();
  gantt.addTask(clone, clone.parent, clone.$index);
 
  child_links = [];
  obtain_link_ids(id);
 
  clone_original_ids_table = {};
  create_clone_original_ids_table(task.id, clone.id);
 
  if (gantt.hasChild(id)) {
    clone_children(id, clone.id)
  }
 
  clone_child_links()
}
 
gantt.config.order_branch = true;
gantt.config.order_branch_free = true;
 
gantt.config.columns = [
  /*
  другие столбцы
  */
  {
    name: "clone", label: "клонировать", width: 44, template: function (task) {
      return "<input type=button value='V' onclick=clone_task(" + task.id + ")>"
    }
  }
];

Related sample:  Gantt. Клонирование задачи со всеми её подзадачами и связями

Вот как включить копирование с помощью сочетаний клавиш (Ctrl + C для копирования, Ctrl + V для вставки в качестве подзадач):

gantt.plugins({
    keyboard_navigation: true,
    multiselect: true,
})
 
let tasks_to_copy = [];
 
gantt.ext.keyboardNavigation.addShortcut("ctrl+c", function (e) {
    tasks_to_copy = [];
    gantt.eachSelectedTask(function (task_id) {
        tasks_to_copy.push(task_id);
    });
}, "taskRow");
gantt.ext.keyboardNavigation.addShortcut("ctrl+v", function (e) {
    const new_parent = gantt.getSelectedId();
    for (let i = 0; i < tasks_to_copy.length; i++) {
        const task = gantt.copy(gantt.getTask(tasks_to_copy[i]));
        task.id = +new Date() + '+' + Math.floor(Math.random() * 10);
        gantt.addTask(task, new_parent)
    }
    gantt.getTask(new_parent).$open = true;
    gantt.render()
}, "taskRow");

Related sample:  Gantt. Копирование и вставка задач через Ctrl+C, Ctrl+V

Добавление диаграмм ресурсов или пользовательских стилей в экспортируемый PDF

Чтобы включить пользовательские стили или дополнительные элементы в экспортируемый PDF, используйте режим raw и включайте стили в параметры header или footer.

Например, вы можете сохранить стили в переменной и включить их в заголовок:

const header = `
    .gantt_bar_task {
        background: orange;
    }
 
    .gantt_task_progress {
        background-color: rgba(33, 33, 33, 0.17);
    }
`
 
gantt.exportToPDF({
       header: "<style>" + header + "</style>"
});

Related sample:  Gantt. Экспорт Gantt в PDF (стили из переменной)

В качестве альтернативы вы можете извлечь стили из элемента <style> на странице:

gantt.exportToPDF({
    raw: true,
    header: "<style>" + document.getElementById("styles").innerHTML + "</style>"
});
 
<style id='styles'>
    .gantt_bar_task {
        background: orange;
    }
 
    .gantt_task_progress {
        background-color: rgba(33, 33, 33, 0.17);
    }
</style>

Related sample:  Gantt. Экспорт Gantt в PDF (стили из элемента <style>)

Related sample:  Gantt. Экспорт Gantt с пользовательскими значками в PDF

Чтобы включить легенду в экспортируемый PDF:

Related sample:  Gantt. Экспорт Gantt с легендой в PDF

Для экспорта диаграмм загрузки ресурсов или гистограмм:

Related sample:  Gantt. Экспорт Gantt с диаграммой загрузки ресурсов в PDF

Related sample:  Gantt. Экспорт Gantt с гистограммой ресурсов в PDF

Расчет прогресса задачи на основе подзадач

Простой способ вычислить прогресс родительской задачи — пересчитывать его каждый раз, когда обновляется подзадача. Метод eachParent() удобен для обхода родительских задач.

Вот пример, в котором прогресс родительских задач определяется исключительно прогрессом их подзадач:

gantt.config.auto_types = true;
 
gantt.templates.progress_text = function (start, end, task) {
    return "<span style='text-align:left;'>" + Math.round(task.progress * 100) 
          + "% </span>";
};
 
gantt.init("gantt_here");
gantt.parse({
    "data": [
        ...
    ]
});
 
gantt.attachEvent("onAfterTaskUpdate", function (id, task) {
    parentProgress(id)
});
gantt.attachEvent("onTaskDrag", function (id, mode, task, original) {
    if (mode == "progress") {
        parentProgress(id)
    }
});
gantt.attachEvent("onAfterTaskAdd", function (id) {
    parentProgress(id)
});
gantt.attachEvent("onAfterTaskDelete", function (id, task) {
    if (task.parent) {
        const siblings = gantt.getChildren(task.parent);
        if (siblings.length) {
            parentProgress(siblings[0])
        }
    }
});
 
function parentProgress(id) {
    gantt.eachParent(function (task) {
        const children = gantt.getChildren(task.id);
        let childProgress = 0;
        for (let i = 0; i < children.length; i++) {
            const child = gantt.getTask(children[i])
            childProgress += (child.progress * 100);
        }
        task.progress = childProgress / children.length / 100;
    }, id)
    gantt.render();
}

Related sample:  Gantt. Динамический расчет прогресса родительской задачи

В другом сценарии прогресс родительских задач может зависеть как от прогресса, так и от длительности подзадач:

function calculateSummaryProgress(task) {
    if (task.type != gantt.config.types.project)
        return task.progress;
    var totalToDo = 0;
    var totalDone = 0;
    gantt.eachTask(function (child) {
        if (child.type != gantt.config.types.project) {
            totalToDo += child.duration;
            totalDone += (child.progress || 0) * child.duration;
        }
    }, task.id);
    if (!totalToDo) return 0;
    else return totalDone / totalToDo;
}
 
function refreshSummaryProgress(id, submit) {
    if (!gantt.isTaskExists(id))
        return;
 
    var task = gantt.getTask(id);
    var newProgress = calculateSummaryProgress(task);
 
    if (newProgress !== task.progress) {
        task.progress = newProgress;
 
        if (!submit) {
            gantt.refreshTask(id);
        } else {
            gantt.updateTask(id);
        }
    }
 
    if (!submit && gantt.getParent(id) !== gantt.config.root_id) {
        refreshSummaryProgress(gantt.getParent(id), submit);
    }
}
 
 
gantt.attachEvent("onParse", function () {
    gantt.eachTask(function (task) {
        task.progress = calculateSummaryProgress(task);
    });
});
 
gantt.attachEvent("onAfterTaskUpdate", function (id) {
    refreshSummaryProgress(gantt.getParent(id), true);
});
 
gantt.attachEvent("onTaskDrag", function (id) {
    refreshSummaryProgress(gantt.getParent(id), false);
});
gantt.attachEvent("onAfterTaskAdd", function (id) {
    refreshSummaryProgress(gantt.getParent(id), true);
});
 
 
(function () {
    var idParentBeforeDeleteTask = 0;
    gantt.attachEvent("onBeforeTaskDelete", function (id) {
        idParentBeforeDeleteTask = gantt.getParent(id);
    });
    gantt.attachEvent("onAfterTaskDelete", function () {
        refreshSummaryProgress(idParentBeforeDeleteTask, true);
    });
})();
 
...
 
gantt.config.auto_types = true;
 
gantt.templates.progress_text = function (start, end, task) {
    return "<span style='text-align:left;'>" + Math.round(task.progress * 100) 
          + "% </span>";
};
 
gantt.templates.task_class = function (start, end, task) {
    if (task.type == gantt.config.types.project)
        return "hide_project_progress_drag";
};

Related sample:  Calculate Progress of Summary Tasks

Вертикальная перестановка задач на временной шкале

Метод addTaskLayer() позволяет отображать пользовательские HTML-элементы на временной шкале, обеспечивая вертикальное и горизонтальное перемещение задач.

Вот как вы можете сделать стандартную задачу переставляемой в гриде:

Related sample:  Gantt. Вертикальная перестановка задач на временной шкале

Для разделенных задач вы можете их переставлять и даже размещать несколько задач на одной строке:

Related sample:  Gantt. Вертикальная перестановка разделенных задач на временной шкале

Фиксирование столбцов в гриде

Фиксирование столбцов в гриде можно реализовать с помощью CSS. Столбец, который вы хотите заморозить, должен иметь позицию 'relative'. Его параметр 'left' должен соответствовать позиции полосы прокрутки, и это можно динамически обновлять через обработчик события полосы прокрутки:

gantt.attachEvent("onGanttReady", function () {
    const el = document.querySelector(".gantt_hor_scroll");
    if (el) {
        el.addEventListener('scroll', function () {
            document.documentElement.style.setProperty(
              '--gantt-frozen-column-scroll-left', el.scrollLeft + "px"
            );
        });
    }
});
 
const textEditor = { type: "text", map_to: "text" };
const start_dateEditor = { type: "date", map_to: "start_date" };
const end_dateEditor = { type: "date", map_to: "end_date" };
const durationEditor = { type: "number", map_to: "duration", min: 0, max: 100 };
 
 
gantt.config.columns = [
    { name: "text", tree: true, width: 150, resize: true, editor: textEditor },
    { name: "start_date", align: "center", width: 120, resize: true, 
      editor: start_dateEditor },
    { name: "end_date", label: "End Time", align: "center", width: 120, 
      resize: true, editor: end_dateEditor },
    { name: "duration", align: "center", width: 80, resize: true, 
      editor: durationEditor },
    { name: "progress", label: "Прогресс", width: 80, align: "center", 
      resize: true },
    {
        name: "custom", label: "Пользовательский", width: 180, align: "center", 
        resize: true, template: function (task) {
            return Math.round(Math.random() * 100)
        }
    },
    { name: "add", width: 44 }
];
 
gantt.config.layout = {
    css: "gantt_container",
    cols: [
        {
            rows: [
                {
                    view: "grid", scrollable: true, 
                    scrollX: "scrollHor1", scrollY: "scrollVer"
                },
                {
                    view: "scrollbar", id: "scrollHor1",
                    scroll: 'x', group: 'hor'
                },
            ]
        },
        { resizer: true, width: 1 },
        {
            rows: [
                {
                    view: "timeline", scrollX: "scrollHor", scrollY: "scrollVer"
                },
                {
                    view: "scrollbar", id: "scrollHor",
                    scroll: 'x', group: 'hor'
                },
            ]
        },
        { view: "scrollbar", id: "scrollVer" }
    ]
}

Настройки CSS для фиксированных столбцов:

:root {
    --gantt-frozen-column-scroll-left: 0px;
}
 
.gantt_cell:nth-child(1),
.gantt_grid_head_cell:nth-child(1) {
    background: Azure;
    position: relative;
    left: var(--gantt-frozen-column-scroll-left);
}
 
.gantt_grid_editor_placeholder[data-column-name="text"] {
    left: var(--gantt-frozen-column-scroll-left) !important;
}
 
.gantt_grid_head_cell:nth-child(1) {
    z-index: 1;
}

Related sample:  Gantt. Замороженный столбец в гриде (через CSS)

В качестве альтернативы вы можете использовать несколько видов грида, хотя это может не работать безупречно с встроенными редакторами:

gantt.config.columns = [
    { name: "start_date", align: "center", width: 80, resize: true },
    { name: "end_date", label: "Дата окончания", align: "center", width: 80, resize: true },
    { name: "duration", width: 60, align: "center", resize: true },
    { name: "progress", label: "Прогресс", width: 60, align: "center", resize: true },
    { name: "add", width: 44 }
];
 
 
const fixedColumn = {
    columns: [
        { name: "text", tree: true, width: 200, resize: true },
    ]
};
 
gantt.config.layout = {
  css: "gantt_container",
  cols: [
    {
      width: 400,
      //min_width: 100,
      rows: [
        {
          group: "gantt",
          cols: [
            {
              rows: [
                { view: 'grid', config: fixedColumn, bind: "task", 
                  scrollY: 'gridScrollY' }
              ]
            },
            {
              rows: [
                { view: 'grid', bind: "task", scrollX: 'gridScrollX', 
                  scrollable: true, scrollY: 'gridScrollY' },
                { view: 'scrollbar', id: 'gridScrollX' }
              ]
            },
            { view: 'scrollbar', id: 'gridScrollY' }
          ]
        }
      ]
    },
    { resizer: true, width: 1 },
    {
      rows: [
        {
          group: "gantt",
          cols: [
            {
              rows: [
                { view: "timeline", scrollX: "scrollHor", scrollY: "scrollVer" },
                { view: "scrollbar", id: "scrollHor" }
              ]
            },
            { view: 'scrollbar', id: 'scrollVer' }
          ]
        }
      ]
    }
  ]
}

Related sample:  Gantt. Фиксированный столбец в гриде (несколько видов грида)

Добавление легенды в Gantt

Хотя в Gantt нет прямой функции для добавления легенды, вы можете создать её вручную. Расширение Overlay может быть чем-то похожим, но не предоставляет большой гибкости.

Чтобы добавить легенду, вы можете создать HTML-элемент для неё и вставить его в контейнер Gantt:

gantt.$root.appendChild(legend);

Вот рабочий пример, где вы можете переключать легенду, нажимая кнопку "Переключить легенду":

Related sample:  Gantt. Добавление информационной легенды

Для интерактивных легенд вы можете прикрепить слушатели событий непосредственно к элементу легенды или использовать делегирование событий на уровне корня Gantt:

gantt.event(gantt.$root, "click", function(e){
    var closest = gantt.utils.dom.closest;
    if(closest(e.target, ".gantt-legend")) {
        gantt.message("Щелчок мыши внутри элемента легенды");
    }
});
К началу