Popular features/Minute scale
Loading live demo…from dhtmlxcode.com
Popular features
Minute scale
Plan a single production day in minutes, switching between a full day view and fifteen minute detail across two shifts.
gantt.plugins({ auto_scheduling: true });
gantt.config.date_format = "%Y-%m-%d %H:%i";
gantt.config.date_grid = "%H:%i";
gantt.config.work_time = true;
gantt.config.row_height = 36;
gantt.config.bar_height = 24;
gantt.config.scale_height = 52;
gantt.config.duration_unit = "minute";
gantt.config.min_duration = 60 * 1000; // (1 minute)
gantt.config.show_tasks_outside_timescale = true;
gantt.config.auto_scheduling = {
enabled: true,
apply_constraints: false,
gap_behavior: "compress"
};
/* Durations remain integer minute values in task data. The formatter converts
* them to decimal hours for display and parses values entered with hour or
* minute units in the grid and lightbox. */
const durationFormatter = gantt.ext.formatters.durationFormatter({
enter: "minute",
store: "minute",
format: "auto",
short: false
});
const durationEditor = {
type: "duration",
map_to: "duration",
formatter: durationFormatter
};
/* Anchor to the next Monday so the demo never looks stale. Set visible
* timeline to one working day: 05:30 (before shift start) to 14:30 (after
* shift end). */
function mondayOnOrAfter(date) {
date = gantt.date.day_start(new Date(date));
while (date.getDay() !== 1) date = gantt.date.add(date, 1, "day");
return date;
}
let ANCHOR = mondayOnOrAfter(new Date());
gantt.config.start_date = gantt.date.add(ANCHOR, 5 * 60 + 30, "minute"); // 05:30, a gutter before the shift
gantt.config.end_date = gantt.date.add(ANCHOR, 14 * 60 + 30, "minute"); // 14:30, a gutter after it
/* Scale presets: full day (1-hour) and detail (15-minute). Swapped by
* the toolbar toggle. */
let SCALES_FULL_DAY = [
{ unit: "hour", step: 1, format: "%H:%i" },
{ unit: "minute", step: 15, format: "%i" }
];
let SCALES_DETAIL = [
{ unit: "hour", step: 1, format: "%H:%i" },
{ unit: "minute", step: 5, format: "%i" }
];
gantt.config.scales = SCALES_FULL_DAY;
gantt.config.min_column_width = 34;
/* Work time: two shifts with a lunch break in between. setWorkTime
* accepts an hours array with HH:MM-HH:MM strings and respects minute
* precision. */
gantt.setWorkTime({ hours: ["6:00-10:00", "10:30-14:00"] });
/* Non-working cells (break time, before 06:00, after 14:00) are shaded
* grey. Note: scale_cell_class receives (date), timeline_cell_class
* receives (item, date). */
gantt.templates.scale_cell_class = function (date) {
return gantt.isWorkTime({ date }) ? "" : "non_working";
};
gantt.templates.timeline_cell_class = function (task, date) {
return gantt.isWorkTime({ date, task }) ? "" : "non_working";
};
/* Grid: duration values are displayed as decimal hours and edited with the
* same formatter. Values such as "0.5 h", "45 min", and "1.25 hours" are
* converted back into integer minutes. */
gantt.config.columns = [
{ name: "text", label: "Task", tree: true, width: 240, resize: true },
{ name: "start_date", label: "Start", align: "center", width: 100, resize: true, template: function (task) {
return task.type === gantt.config.types.project ? "" : gantt.templates.date_grid(task.start_date, task);
}
},
{ name: "duration", label: "Duration", align: "center", width: 120, resize: true,
editor: durationEditor, template: function (task) {
return task.type === gantt.config.types.project ? "" : durationFormatter.format(task.duration || 0);
}
},
{ name: "add", width: 40, resize: true }
];
gantt.locale.labels.section_time = "Time period and duration";
gantt.config.lightbox.sections = [
{ name: "description", height: 50, map_to: "text", type: "textarea", focus: true },
{
name: "time",
height: 72,
map_to: "auto",
type: "duration",
time_format: ["%d", "%m", "%Y", "%H:%i"],
formatter: durationFormatter
}
];
/* Dataset: two production lines and maintenance, anchored to ANCHOR.
* Each line runs batch operations sequentially (finish-to-start links).
* Realistic task names and durations (15-90 minutes), displayed as
* 0.25-1.5 decimal hours. */
/* every leaf is seeded at 06:00 shift start; auto-scheduling packs each
* line into a sequential chain around the 10:00-10:30 break */
const dateToStr = gantt.date.date_to_str("%Y-%m-%d %H:%i");
let SEED = dateToStr(gantt.date.add(ANCHOR, 6 * 60, "minute"));
let data = {
tasks: [
/* ---- Shift A - Line 1 (Dairy flavors) ---- */
{ id: 1, text: "Shift A - Line 1", type: "project", open: true, progress: 0 },
{ id: 11, text: "Line startup & safety check", parent: 1, start_date: SEED, duration: 30, progress: 1 },
{ id: 12, text: "Batch run - Vanilla (1000 L)", parent: 1, start_date: SEED, duration: 60, progress: 0.8 },
{ id: 13, text: "Quality sample - Vanilla", parent: 1, start_date: SEED, duration: 15, progress: 1 },
{ id: 14, text: "Changeover to Strawberry", parent: 1, start_date: SEED, duration: 30, progress: 0.6 },
{ id: 15, text: "Batch run - Strawberry (1200 L)", parent: 1, start_date: SEED, duration: 75, progress: 0.4 },
{ id: 16, text: "Quality sample - Strawberry", parent: 1, start_date: SEED, duration: 15, progress: 0.2 },
{ id: 17, text: "CIP flush & drain - Line 1", parent: 1, start_date: SEED, duration: 30, progress: 0 },
{ id: 18, text: "Line restart after break", parent: 1, start_date: SEED, duration: 15, progress: 0 },
{ id: 19, text: "Batch run - Pistachio (800 L)", parent: 1, start_date: SEED, duration: 60, progress: 0 },
{ id: 20, text: "Quality sample - Pistachio", parent: 1, start_date: SEED, duration: 15, progress: 0 },
{ id: 21, text: "Changeover to Hazelnut", parent: 1, start_date: SEED, duration: 30, progress: 0 },
{ id: 22, text: "Batch run - Hazelnut (950 L)", parent: 1, start_date: SEED, duration: 60, progress: 0 },
/* ---- Shift A - Line 2 (Specialty blends) ---- */
{ id: 2, text: "Shift A - Line 2", type: "project", open: true, progress: 0 },
{ id: 31, text: "Line startup & health check", parent: 2, start_date: SEED, duration: 25, progress: 0.9 },
{ id: 32, text: "Batch run - Mango-Passion (900 L)", parent: 2, start_date: SEED, duration: 60, progress: 0.7 },
{ id: 33, text: "Quality sample - Tropical blend", parent: 2, start_date: SEED, duration: 20, progress: 0.8 },
{ id: 34, text: "Changeover to Coconut-Lime", parent: 2, start_date: SEED, duration: 35, progress: 0.5 },
{ id: 35, text: "Batch run - Coconut-Lime (850 L)", parent: 2, start_date: SEED, duration: 70, progress: 0.3 },
{ id: 36, text: "Quality sample - Coconut-Lime", parent: 2, start_date: SEED, duration: 20, progress: 0.1 },
{ id: 37, text: "CIP rinse & pressure wash", parent: 2, start_date: SEED, duration: 35, progress: 0 },
{ id: 38, text: "Line standby after break", parent: 2, start_date: SEED, duration: 20, progress: 0 },
{ id: 39, text: "Batch run - Berry fusion (750 L)", parent: 2, start_date: SEED, duration: 55, progress: 0 },
{ id: 40, text: "Quality sample - Berry fusion", parent: 2, start_date: SEED, duration: 20, progress: 0 },
/* ---- Maintenance window ---- */
{ id: 3, text: "Maintenance & QA", type: "project", open: true, progress: 0 },
{ id: 51, text: "Pump inspection & vibration check", parent: 3, start_date: SEED, duration: 45, progress: 0.5 },
{ id: 52, text: "Sensor calibration & validation", parent: 3, start_date: SEED, duration: 40, progress: 0.3 },
{ id: 53, text: "Spare parts inventory", parent: 3, start_date: SEED, duration: 30, progress: 0 },
{ id: 54, text: "Daily safety briefing", parent: 3, start_date: SEED, duration: 20, progress: 0 }
],
links: [
/* Line 1 dependencies (finish-to-start) */
{ id: 1, source: 11, target: 12, type: "0" },
{ id: 2, source: 12, target: 13, type: "0" },
{ id: 3, source: 13, target: 14, type: "0" },
{ id: 4, source: 14, target: 15, type: "0" },
{ id: 5, source: 15, target: 16, type: "0" },
{ id: 6, source: 16, target: 17, type: "0" },
{ id: 7, source: 17, target: 18, type: "0" },
{ id: 8, source: 18, target: 19, type: "0" },
{ id: 9, source: 19, target: 20, type: "0" },
{ id: 10, source: 20, target: 21, type: "0" },
{ id: 11, source: 21, target: 22, type: "0" },
/* Line 2 dependencies (finish-to-start) */
{ id: 12, source: 31, target: 32, type: "0" },
{ id: 13, source: 32, target: 33, type: "0" },
{ id: 14, source: 33, target: 34, type: "0" },
{ id: 15, source: 34, target: 35, type: "0" },
{ id: 16, source: 35, target: 36, type: "0" },
{ id: 17, source: 36, target: 37, type: "0" },
{ id: 18, source: 37, target: 38, type: "0" },
{ id: 19, source: 38, target: 39, type: "0" },
{ id: 20, source: 39, target: 40, type: "0" },
/* Maintenance dependencies */
{ id: 21, source: 51, target: 52, type: "0" },
{ id: 22, source: 52, target: 53, type: "0" },
{ id: 23, source: 53, target: 54, type: "0" }
]
};
/* Zoom toggle: swap scales and re-render. */
function applyZoom(preset) {
if (preset === "detail") {
gantt.config.scales = SCALES_DETAIL;
gantt.config.min_column_width = 20;
} else {
gantt.config.scales = SCALES_FULL_DAY;
gantt.config.min_column_width = 34;
}
DHX.ui.setActiveByAttribute("#zoom_seg .dhx_btn", "data-zoom", preset, "active");
gantt.render();
}
gantt.init("gantt_here");
gantt.parse(data);<!DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="Content-type" content="text/html; charset=utf-8">
<title>Minute scheduling and decimal durations: dhtmlxGantt</title>
<script src="../../codebase/dhtmlxgantt.js?v=10.0.0"></script>
<link rel="stylesheet" href="../../codebase/dhtmlxgantt.css?v=10.0.0">
<link rel="stylesheet" href="../common/demo-controls/dhx_controls.css">
<script src="../common/demo-controls/dhx_controls.js"></script>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap">
<style>
body { display: flex; flex-direction: column; }
#gantt_here { flex: 1 1 auto; min-height: 0; width: 100%; }
.gantt_task_line { border: none; border-radius: 7px; box-shadow: 0 1px 2px rgba(16,24,40,.18); }
.gantt_task_line .gantt_task_progress { border-radius: 7px 0 0 7px; }
.gantt_task_line.gantt_project { border-radius: 5px; }
.gantt_task_cell.non_working, .gantt_scale_cell.non_working { background-color: rgba(15,23,42,.04); }
.hint { font: 500 12px var(--dhx-font); color: var(--dhx-muted); }
.hint b { color: var(--dhx-ink-2); font-weight: 600; }
.toolbar_label { font: 600 13px var(--dhx-font); color: var(--dhx-ink-2); }
</style>
</head>
<body>
<div class="dhx_header" style="justify-content:center; gap:14px">
<span class="toolbar_label">Minute scheduling & decimal durations</span>
<div class="dhx_seg" id="zoom_seg">
<button class="dhx_btn active" data-zoom="day" onclick="applyZoom('day')">Full day</button>
<button class="dhx_btn" data-zoom="detail" onclick="applyZoom('detail')">5-min detail</button>
</div>
</div>
<div id="gantt_here"></div>
<div class="dhx_footer">
<span class="hint">Durations are stored in <b>minutes</b> but can be entered as <b>0.5 h</b>, <b>45 min</b>, or <b>1.25 hours</b>. The work calendar uses two shifts (06:00-10:00, 10:30-14:00).</span>
</div>
</body>
</html>