gantt_webgpu
v0.0.7
Published
Диаграмма Гантта реализованная при помощи TS и WebGPU
Readme
Диаграмма Гантта
Диаграмма Гантта реализованная при помощи TS и WebGPU

Ключевые особенности проекта
- аппаратное ускорение построения диаграммы. Визуализатор написан на WebGPU, являющийся правопроемником WebGL. Это означает максимально возможную производительность графики, а так же потенциал для тяжелых вычислений в Web-е
- эффективная сплит-архитектура. Управляющий код находится в основном потоке, а визуализатор выделен в отдельный worker. Это позволяет использовать тяжелые вычисления или например анимацию, не боясь заморозить основной интерфейс системы. Другими словами над нагрузкой работают и несколько потоков CPU и множество ядер GPU одновременно.
- эффективное управление памятью. Несмотря на многопоточность, основные данные, такие как задачи и связи, не дублируются. Для этого применяется техника передачи владения памятью.
- чистый JS и отсутствие дополнительных зависимостей. Это означает легковесность, отсутствие конфликтов, а так же простоту использования вместе с вашими любимыми фреймворками.
- возможность отображения дополнительных данных, например нескольких вариаций задачи в одной строке.
- уменьшение ментальной нагрузки на пользователя. Это реализовано путем исключения отображения связей, у которых на экране нет ни исходной задачи, ни целевой. Это снижает "графическую зашумленность" диаграммы, когда при большом кол-ве построений невозможно отличить одну связь от другой.
Ниже представлен пример визуализации всего лишь нескольких элементов со связями, но библиотека испытывалась и для построения диаграммы с кол-вом элементов (задачи и связи) более 150 тысяч. Уверен, что она справится и с горяздо большей нагрузкой, но человеческое восприятие уже с трудом извлечет из этого пользу (при действительно больших наборах данных следует смотреть в сторону кластерной группировки).
Внимание!
В настоящий момент (13.06.2025) WebGPU является новейши API, находящимся в стадии разработки, а потому его поддержка в браузерах ограничена. Наимболее полная фунциональность доступна в Chrome. Но даже в нем требуется её активация через флаги: chrome://flags/ Будьте готовы каждый раз при запуске браузера получать сообщение: "Вы используете неподдерживемый флаг командной строки: --enable-unsafe-webgpu. Стабильность и безопасность будут нарушены".
Одновременно трудно представить на каких современных компьютерах WebGPU может не заработать, но если все же такое произошло, то пожно проверить конфигурацию его графической подсистемы: chrome://gpu/ WebGPU должна быть "Hardware accelerated"
Кроме того, в процессе разработки WebGPU API может и будет изменяться, что в свою очередь может привести к частичной или полной неработоспособности данного проекта.
Установка и использование
Импортируем библиотеку
mkdir gantt_test
cd gantt_test
npm create vite@latest .
npm install gantt_webgpuVite использован для удобства транспиляции и запуска демо.
Создаем новый проект с примерно такой структурой, а так же изменяем и добавляем файлы:
gantt_test/
├─ src/
│ ├─ mocks/
│ │ ├─ links.json
│ │ └─ tasks.json
│ ├─ types/
│ │ └─ types.ts
│ ├─ main.ts
│ ├─ style.css
├─ index.html
├─ ...
index.html
<!doctype html>
<html lang="en">
<head>
<title>GANTT_WebGPU</title>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" href="/src/style.css">
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>src/main.ts
import { GanttWebGPU } from 'gantt_webgpu'
import type { LayoutCfg } from './types/types'
async function loadAndInit() {
const g = new GanttWebGPU()
g.init(
'app',
{ displayTopPanel: true } as LayoutCfg
)
// Загрузка данных
const tasksResponse = await fetch('./src/mocks/tasks.json')
const linksResponse = await fetch('./src/mocks/links.json')
let tasks = await tasksResponse.json()
let links = await linksResponse.json()
g.loadTasks(tasks)
g.loadLinks(links)
// Сброс ссылок для освобождения памяти
tasks = null
links = null
// Отправка данных в GPU и первый рендер
g.transferData()
g.render()
topPanel.innerHTML =
"<button id='expandToggler' data-attr='off'>Expand</button>" +
"<div id='statistic'></div>"
const expandToggler = document.getElementById('expandToggler')!
expandToggler.onclick = () => {
const buttonState = expandToggler.getAttribute('data-attr')
if (buttonState === 'off') {
expandToggler.setAttribute('data-attr', 'on')
expandToggler.style.backgroundColor = '#CCFF00'
g.changeVisibility({ isVisibilityAll: false })
g.render()
} else if (buttonState === 'on') {
expandToggler.setAttribute('data-attr', 'off')
expandToggler.style.backgroundColor = 'transparent'
g.changeVisibility({ isVisibilityAll: true })
g.render()
}
}
const statisticValue = g.getStatistic()
const statistic = document.getElementById('statistic')!
const total =
statisticValue.count.tasks +
statisticValue.count.links +
statisticValue.count.variants
statistic.style.flex = '1'
statistic.style.textAlign = 'end'
statistic.innerHTML = `
Tasks: ${statisticValue.count.tasks} | Links: ${statisticValue.count.links} | Variants: ${statisticValue.count.variants} | Total: ${total}
`
}
loadAndInit()src/style.css
html, body, #app {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
}types.ts
export interface Task {
id: string
parent: string
text: string
start_date: string
end_date: string
prev_sibling_id: string
[key: string]: any
}
export interface Link {
source: string
target: string
type: number
[key: string]: any
}
/** Управление активностью встроенных пенелей */
export interface LayoutCfg {
displayLeftPanel: boolean
displayTopPanel: boolean
displayBottomPanel: boolean
displayLinks: boolean
}src/moсks/tasks.json
[
{
"id": "00",
"text": "Начало",
"start_date": "2024-01-09",
"end_date": "2024-02-09",
"type": "milestone",
"parent": 0,
"prev_sibling_id": null
},
{
"id": "11",
"text": "Родитель 1",
"start_date": "2024-02-09",
"end_date": "2024-03-20",
"type": "project",
"parent": 0,
"prev_sibling_id": "00"
},
{
"id": "22",
"text": "Субэлемент 2",
"start_date": "2024-01-19",
"end_date": "2024-02-30",
"type": "task",
"parent": "11",
"prev_sibling_id": null
},
{
"id": "33",
"text": "Родитель 3",
"start_date": "2024-03-10",
"end_date": "2024-04-20",
"type": "project",
"parent": "22",
"prev_sibling_id": null
},
{
"id": "44",
"text": "Субэлемент 4",
"start_date": "2024-04-10",
"end_date": "2024-12-05",
"type": "task",
"parent": "33",
"prev_sibling_id": null
},
{
"id": "55",
"text": "Субэлемент 5",
"start_date": "2024-12-05",
"end_date": "2025-05-20",
"type": "task",
"parent": "33",
"prev_sibling_id": "44"
},
{
"id": "66",
"text": "Субэлемент 6",
"start_date": "2025-05-10",
"end_date": "2025-08-03",
"type": "task",
"parent": "22",
"prev_sibling_id": "33"
},
{
"id": "77",
"text": "Окончание",
"start_date": "2024-01-09",
"end_date": "2024-02-09",
"type": "milestone",
"parent": 0,
"prev_sibling_id": "11"
}
]src/moсks/links.json
[
{
"id": "3",
"source": "22",
"target": "55",
"type": 1,
"lag": 0
},
{
"id": "4",
"source": "66",
"target": "00",
"type": 1,
"lag": 0
}
]Статус проекта
В процессе разработки.
