diff --git a/src/schedule/sample.json b/src/schedule/sample.json
index 7469be39..074621a4 100644
--- a/src/schedule/sample.json
+++ b/src/schedule/sample.json
@@ -115,6 +115,22 @@
]
}
},
+ {
+ "url": "scheduler-with-chart",
+ "name": "Scheduler With Chart",
+ "category": "Product Use Case",
+ "type": "new",
+ "description": "Fleet Management Dashboard combining a Timeline Month Scheduler with a real-time Analytics Chart (scheduler-with-chart.html + scheduler-with-chart.js).",
+ "api": {
+ "Schedule": [
+ "eventSettings",
+ "selectedDate",
+ "group",
+ "resources",
+ "actionComplete"
+ ]
+ }
+ },
{
"url": "local-data",
"name": "Local Data",
diff --git a/src/schedule/scheduler-with-chart-stack.json b/src/schedule/scheduler-with-chart-stack.json
new file mode 100644
index 00000000..cc235c25
--- /dev/null
+++ b/src/schedule/scheduler-with-chart-stack.json
@@ -0,0 +1,7 @@
+{
+ "html": "scheduler-with-chart.html",
+ "files": [
+ { "path": "scheduler-with-chart.js" },
+ { "path": "datasource.json" }
+ ]
+}
\ No newline at end of file
diff --git a/src/schedule/scheduler-with-chart.html b/src/schedule/scheduler-with-chart.html
new file mode 100644
index 00000000..70722577
--- /dev/null
+++ b/src/schedule/scheduler-with-chart.html
@@ -0,0 +1,449 @@
+
+
+
+
+
+
+
+
+
+
+
+ Fleet Management Dashboard: A modern timeline scheduler with real-time analytics visualization.
+ Manage driver schedules and monitor fleet metrics through an integrated analytics dashboard.
+
+
+
+
Overview
+
+ This sample demonstrates a professional fleet management solution combining a Timeline Month
+ Scheduler with a real-time Analytics Chart. The left panel displays scheduled routes
+ grouped by driver, while the right panel provides actionable insights through multiple analytical views.
+
+
+
Key Components
+
+ - Timeline Scheduler: Visualizes driver routes and events in a monthly timeline view with
+ resource grouping
+ - Analytics Dashboard: Three switchable views for data analysis:
+
+ - Trip Count - Monitor the number of trips per driver
+ - Truck Capacity - View truck capacity specifications per driver
+ - Longest Trips - Track maximum trip duration in hours
+
+
+ - Real-time Sync: Chart updates instantly when events are created, edited, or deleted
+ - Visual Consistency: Chart bars automatically inherit driver colors from the scheduler
+
+
+
Technical Highlights
+
+ - TimelineMonth view with multi-driver resource grouping
+ - Custom resource header and informational tooltip templates
+ - Runtime event generation and data binding
+ - Color-coded visualization with automatic resource-to-chart mapping
+ - Interactive dropdown mode switcher for analytics filtering
+ - Responsive design for desktop and tablet devices
+ - Modern UI with gradient effects and smooth animations
+
+
+
How to Use
+
+ - View scheduled trips in the timeline on the left
+ - Use the dropdown to switch between different analytical views
+ - Hover over chart bars to see detailed metrics
+ - Create, edit, or delete events to see real-time chart updates
+ - Monitor fleet performance across multiple dimensions
+
+
+
diff --git a/src/schedule/scheduler-with-chart.js b/src/schedule/scheduler-with-chart.js
new file mode 100644
index 00000000..8939e8a1
--- /dev/null
+++ b/src/schedule/scheduler-with-chart.js
@@ -0,0 +1,267 @@
+if (typeof loadCultureFiles === 'function') loadCultureFiles();
+
+this.default = function () {
+ if (typeof loadCultureFiles === 'function') loadCultureFiles();
+
+ var Browser = ej.base.Browser;
+ var extend = ej.base.extend;
+
+ // Inject required modules into global ej instances
+ ej.charts.Chart.Inject(ej.charts.ColumnSeries, ej.charts.Category, ej.charts.Legend, ej.charts.Tooltip, ej.charts.Highlight);
+ ej.schedule.Schedule.Inject(ej.schedule.TimelineMonth, ej.schedule.Resize, ej.schedule.DragAndDrop);
+ var truckEvents = [];
+ // Drivers master (used both by Scheduler resources and for lookups)
+ var driversMaster = [
+ { driver: 'Ben Smith', id: 1, color: '#ea7a57', truck: 'Volvo FH16', capacity: '325 t' },
+ { driver: 'Sarah Johnson', id: 2, color: '#7fa900', truck: 'Scania R730', capacity: '310 t' },
+ { driver: 'Mike Chen', id: 3, color: '#5978ee', truck: 'Mercedes Actros', capacity: '290 t' },
+ { driver: 'Emma Davis', id: 4, color: '#fec200', truck: 'MAN TGX', capacity: '280 t' },
+ { driver: 'Carlos Rodriguez', id: 5, color: '#df5286', truck: 'DAF XF', capacity: '300 t' },
+ { driver: 'Olivia Wilson', id: 6, color: '#00bdae', truck: 'Kenworth T680', capacity: '315 t' },
+ { driver: 'James Taylor', id: 7, color: '#865fcf', truck: 'Peterbilt 579', capacity: '305 t' },
+ { driver: 'Sophia Martinez', id: 8, color: '#1aaa55', truck: 'Freightliner Cascadia', capacity: '295 t' },
+ { driver: 'Daniel Lee', id: 9, color: '#df5286', truck: 'Mack Anthem', capacity: '285 t' },
+ { driver: 'Ava Thompson', id: 10, color: '#710193', truck: 'International LT', capacity: '275 t' }
+ ];
+ var driversById = new Map(driversMaster.map(function (d) { return [d.id, d.driver]; }));
+
+ // Function to generate chart data based on mode
+ function generateChartData(mode) {
+ if (mode === 'capacity') {
+ return driversMaster.map(function (d) {
+ return {
+ Driver: d.driver,
+ Value: parseFloat(d.capacity.replace(' t', ''))
+ };
+ });
+ }
+ else if (mode === 'tripcount') {
+ var countMap = {};
+ for (var i = 0; i < truckEvents.length; i++) {
+ var driver = truckEvents[i].Driver || driversById.get(truckEvents[i].DriverID);
+ if (driver) {
+ countMap[driver] = (countMap[driver] || 0) + 1;
+ }
+ }
+ return Object.keys(countMap).map(function (k) { return ({ Driver: k, Value: countMap[k] }); });
+ }
+ else if (mode === 'longest') {
+ var maxMap = {};
+ for (var j = 0; j < truckEvents.length; j++) {
+ var drv = truckEvents[j].Driver || driversById.get(truckEvents[j].DriverID);
+ if (drv && truckEvents[j].StartTime && truckEvents[j].EndTime) {
+ var duration = (new Date(truckEvents[j].EndTime).getTime() - new Date(truckEvents[j].StartTime).getTime()) / (1000 * 3600); // hours
+ maxMap[drv] = Math.max(maxMap[drv] || 0, duration);
+ }
+ }
+ return Object.keys(maxMap).map(function (k) { return ({ Driver: k, Value: maxMap[k] }); });
+ }
+ return [];
+ }
+
+ var currentMode = 'tripcount';
+ var chartDataSet = generateChartData(currentMode);
+
+ // Scheduler
+ var scheduleInstance = new ej.schedule.Schedule({
+ width: 'calc(100% - 360px)',
+ height: '650px',
+ selectedDate: new Date(2026, 0, 12),
+ allowOverlap: false,
+ resourceHeaderTemplate: '#resourceTemplate',
+ headerIndentTemplate: '#headerIndentTemplate',
+ views: ['TimelineMonth'],
+ group: {
+ resources: ['TruckDetails'],
+ headerTooltipTemplate: '#tooltipTemplate'
+ },
+ resources: [
+ {
+ field: 'DriverID',
+ title: 'Driver',
+ name: 'TruckDetails',
+ allowMultiple: false,
+ dataSource: driversMaster,
+ textField: 'driver',
+ idField: 'id',
+ colorField: 'color'
+ }
+ ],
+ eventSettings: { dataSource: truckEvents },
+ actionComplete: function (args) { return onScheduleActionComplete(args); }
+ });
+ scheduleInstance.appendTo('#scheduler');
+
+ // Chart - Modern styling
+ var tripChart = new ej.charts.Chart({
+ primaryXAxis: {
+ valueType: 'Category',
+ interval: 1,
+ labelIntersectAction: Browser.isDevice ? 'None' : 'Trim',
+ labelRotation: Browser.isDevice ? -45 : 315,
+ majorGridLines: { width: 1, color: '#e9ecef', dashArray: '4' },
+ majorTickLines: { width: 0 },
+ labelStyle: {
+ size: '12px',
+ fontWeight: '500',
+ color: '#495057'
+ }
+ },
+ chartArea: { border: { width: 0 }, margin: { bottom: 20, top: 30, left: 50, right: 30 } },
+ primaryYAxis: {
+ interval: null,
+ majorTickLines: { width: 0 },
+ lineStyle: { width: 0 },
+ title: 'Count',
+ labelStyle: {
+ size: '11px',
+ color: '#6c757d'
+ },
+ majorGridLines: {
+ width: 1,
+ color: '#e9ecef',
+ dashArray: '4'
+ }
+ },
+ series: [
+ {
+ type: 'Column',
+ xName: 'Driver',
+ yName: 'Value',
+ columnSpacing: 0.2,
+ columnWidth: 0.85,
+ legendShape: 'Rectangle',
+ dataSource: chartDataSet,
+ cornerRadius: { topLeft: 8, topRight: 8 },
+ name: 'Value',
+ marker: {
+ visible: false
+ },
+ border: {
+ width: 1,
+ color: 'rgba(255, 255, 255, 0.3)'
+ }
+ }
+ ],
+ width: '100%',
+ height: '550px',
+ title: 'Trip Count',
+ tooltip: {
+ enable: true,
+ header: '${point.x}',
+ format: 'Value: ${point.y}',
+ enableHighlight: true
+ },
+ legendSettings: {
+ visible: false
+ },
+ axisLabelRender: function (args) {
+ var numeric = Number(String(args.text).replace(/,/g, ''));
+ if (!isNaN(numeric) && numeric >= 1000) {
+ args.text = (numeric / 1000).toFixed(1) + 'K';
+ }
+ },
+ tooltipRender: function (args) {
+ if (args.text) {
+ var unit = '';
+ if (currentMode === 'capacity')
+ unit = ' t';
+ else if (currentMode === 'longest')
+ unit = ' hours';
+ }
+ },
+ pointRender: function (args) {
+ var driverName = (args.point && args.point.x);
+ if (driverName) {
+ var resource = driversMaster.find(function (d) { return d.driver === driverName; });
+ if (resource && resource.color) {
+ args.fill = resource.color;
+ args.border = {
+ color: 'rgba(255, 255, 255, 0.4)',
+ width: 1.5
+ };
+ }
+ }
+ }
+ });
+ tripChart.appendTo('#chart');
+
+ (function tryPaths(i) {
+ var candidatePaths = ['./datasource.json', 'datasource.json', '/src/schedule/datasource.json', '/src/schedule/datasource.json'];
+ if (i >= candidatePaths.length) {
+ // nothing found — create a sensible fallback of events using the same driver names
+ if (!truckEvents || truckEvents.length === 0) {
+ var baseDate = scheduleInstance.selectedDate || new Date();
+ var baseYear = baseDate.getFullYear();
+ var baseMonth = baseDate.getMonth();
+ var baseDay = baseDate.getDate();
+ var fallback = [];
+ for (var idx = 0; idx < driversMaster.length; idx++) {
+ var d = driversMaster[idx];
+ var startHour = 8 + (idx % 8); // stagger start times
+ var start = new Date(baseYear, baseMonth, baseDay, startHour, 0, 0);
+ var end = new Date(start.getTime() + (2 * 60 * 60 * 1000)); // 2 hour duration
+ fallback.push({
+ Id: idx + 1,
+ Subject: d.driver,
+ StartTime: start,
+ EndTime: end,
+ IsAllDay: false,
+ DriverID: d.id
+ });
+ }
+ truckEvents = extend([], fallback, null, true);
+ }
+ scheduleInstance.eventSettings.dataSource = truckEvents;
+ scheduleInstance.dataBind();
+ updateChart();
+ return;
+ }
+ fetch(candidatePaths[i]).then(function (r) { return r.json(); }).then(function (json) {
+ if (json && json.truckData) {
+ truckEvents = extend([], json.truckData, null, true);
+ }
+ else if (json) {
+ truckEvents = extend([], json, null, true);
+ }
+ scheduleInstance.eventSettings.dataSource = truckEvents;
+ scheduleInstance.dataBind();
+ updateChart();
+ }).catch(function () { return tryPaths(i + 1); });
+ })(0);
+
+ // Dropdown for chart modes
+ var ddlData = [
+ { text: 'Truck Capacity', value: 'capacity' },
+ { text: 'Trip Count', value: 'tripcount' },
+ { text: 'Longest Trips', value: 'longest' }
+ ];
+ var modeDropdown = new ej.dropdowns.DropDownList({
+ dataSource: ddlData,
+ fields: { text: 'text', value: 'value' },
+ value: 'tripcount',
+ width: '100%',
+ change: function (args) {
+ currentMode = args.value;
+ updateChart();
+ }
+ });
+ modeDropdown.appendTo('#chart-ddl');
+
+ function updateChart() {
+ chartDataSet = generateChartData(currentMode);
+ tripChart.series[0].dataSource = chartDataSet;
+ tripChart.series[0].name = modeDropdown.text || 'Value';
+ tripChart.title = modeDropdown.text || '';
+ tripChart.primaryYAxis.title = currentMode === 'capacity' ? 'Capacity (t)' :
+ currentMode === 'longest' ? 'Duration (hours)' : 'Count';
+ tripChart.refresh();
+ }
+
+ // Handle scheduler action events to keep chart in sync
+ function onScheduleActionComplete(args) {
+ if (args.requestType === 'eventChanged' || args.requestType === 'eventCreated' || args.requestType === 'eventRemoved') {
+ updateChart();
+ }
+ }
+};