Overview

  • A special row frozen at the top of the Scheduler displays the number of events planned for each day.

  • Another row frozen at the bottom displays a bar chart that shows the utilization information graphically.

  • Requires DayPilot Pro for JavaScript 2020.4.4723 or higher

  • Includes a trial version of DayPilot Pro for JavaScript (see License below)

License

Licensed for testing and evaluation purposes. Please see the license agreement included in the sample project. You can use the source code of the tutorial if you are a licensed user of DayPilot Pro for JavaScript. Buy a license.

Resource Utilization Row

This sample project will show how to calculate resource utilization for a particular time column (a day in this case) in the JavaScript Scheduler component.

The following screenshot illustrates how the source data for the column summary. You can see that 6 of the resources contain a scheduled event for October 12, 2021:

javascript-scheduler-resource-utilization-by-column.png

We are going to calculate the total for each of the time columns and display the utilization in the “Summary” column:

javascript-scheduler-column-total-in-frozen-row.png

First, we need to define the “Summary” column. We add it to the top of the resources array which defines the rows to be displayed by the JavaScript Scheduler component. We add a frozen: "top" property to the summary row - that will freeze the row at the top of the Scheduler grid. It’s also necessary to add cellsAutoUpdated: true property to enable auto-reloading of the cell content when the Scheduler data is changed:

dp.resources = [
  {name: "Summary", id: "summary", frozen: "top", cellsAutoUpdated: true},
  {name: "Resource 1", id: "R1"},
  {name: "Resource 2", id: "R2"},
  {name: "Resource 3", id: "R3"},
  {name: "Resource 4", id: "R4"},
  {name: "Resource 5", id: "R5"},
  {name: "Resource 6", id: "R6"},
  {name: "Resource 7", id: "R7"},
  {name: "Resource 8", id: "R8"},
  {name: "Resource 9", id: "R9"},
];

Now we need to calculate and display the column total.

We will use onBeforeCellRender event handler, which is called for every cell of the Scheduler grid during update.

onBeforeCellRender: function(args) {
  if (args.cell.resource === "summary") {
    var events = dp.events.forRange(args.cell.start, args.cell.end).length;

    args.cell.backColor = "#ffffff";
    args.cell.areas = [
      {left:0, top: 0, right: 0, bottom: 0, style: "display: flex; justify-content: center; align-items: center;", text: events},
    ];
  }
}

First, we check if this is the summary row by checking args.cell.resource value.

Next, we calculate the total number of events that are displayed between args.cell.start and args.cell.end using events.forRange() method:

var events = dp.events.forRange(args.cell.start, args.cell.end).length;

And we display the calculated value in the cell using an active area. The active area lets us specify the position and center the text vertically and horizontally:

args.cell.areas = [
  {left:0, top: 0, right: 0, bottom: 0, style: "display: flex; justify-content: center; align-items: center;", text: events.length},
];

Availability Chart Row

javascript-scheduler-column-summary-availability-chart.png

We can use the same approach to display the utilization/availability information graphically using a chart at the bottom of the Scheduler grid.

We add a new frozen row at the bottom of the Scheduler grid:

dp.resources = [
  {name: "Summary", id: "summary", frozen: "top", cellsAutoUpdated: true},
  {name: "Resource 1", id: "R1"},
  // ...
  {name: "Resource 9", id: "R9"},
  {name: "Summary", id: "chart", frozen: "bottom", cellsAutoUpdated: true},
];

This time, we use frozen: "bottom" so the row is displayed below the main grid. We need to add cellsAutoUpdated: true as well.

Now we will extend the onBeforeCellRender event handler and add a red bar to the cells with used resources:

onBeforeCellRender: function(args) {

  // ...

  if (args.cell.resource === "chart") {
    var events = dp.events.forRange(args.cell.start, args.cell.end).length * 1.0;

    args.cell.backColor = "#ffffff";
    if (events > 0) {
      var max = dp.resources.filter(function(r) { return !r.frozen; }).length;
      var maxHeight = dp.eventHeight;
      var percentage = events/max;
      var height = percentage * maxHeight;

      args.cell.areas = [
        {
          bottom: 0,
          height: height,
          left: 0,
          right: 0,
          backColor: "#cc0000",
          style: "opacity: " + percentage
        }
      ];
    }
  }
}

We use the event data to calculate a utilization percentage:

var max = dp.resources.filter(function(r) { return !r.frozen; }).length;
var maxHeight = dp.eventHeight;
var percentage = events/max;

And we add a red bar using an active area. The bar height and opacity will be calculated from the utilization percentage:

var height = percentage * maxHeight;

args.cell.areas = [
  {
    bottom: 0,
    height: height,
    left: 0,
    right: 0,
    backColor: "#cc0000",
    style: "opacity: " + percentage
  }
];

Full Source Code

And here is the full source code of the example that shows a Scheduler with resource utilization/availability information in frozen rows above and below the main Scheduler grid:

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8"/>
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>JavaScript Scheduler: Column Summary</title>

  <style type="text/css">
    p, body, td { font-family: Tahoma, Arial, Helvetica, sans-serif; font-size: 10pt; }
    body { padding: 0px; margin: 0px; background-color: #ffffff; }
    a { color: #1155a3; }
    .space { margin: 10px 0px 10px 0px; }
    .header { background: #003267; background: linear-gradient(to right, #011329 0%,#00639e 44%,#011329 100%); padding:20px 10px; color: white; box-shadow: 0px 0px 10px 5px rgba(0,0,0,0.75); }
    .header a { color: white; }
    .header h1 a { text-decoration: none; }
    .header h1 { padding: 0px; margin: 0px; }
    .main { padding: 10px; margin-top: 10px; }
    .generated { color: #999; }
    .generated a { color: #999; }
  </style>

  <!-- DayPilot library -->
  <script src="js/daypilot/daypilot-all.min.js"></script>
</head>
<body>
<div class="header">
  <h1><a href='https://code.daypilot.org/93064/javascript-scheduler-column-summary'>JavaScript Scheduler: Column Summary</a></h1>
  <div><a href="https://javascript.daypilot.org/">DayPilot for JavaScript</a> - HTML5 Calendar/Scheduling Components for JavaScript/Angular/React/Vue</div>
</div>

<div class="main">
  <div id="dp"></div>
  <div class="generated">Generated using <a href="https://builder.daypilot.org/">DayPilot UI Builder</a>.</div>
</div>

<script>
  var dp = new DayPilot.Scheduler("dp", {
    timeHeaders: [{"groupBy":"Month"},{"groupBy":"Day","format":"d"}],
    scale: "Day",
    days: 31,
    startDate: "2021-10-01",
    timeRangeSelectedHandling: "Enabled",
    onTimeRangeSelected: function (args) {
      var dp = this;
      DayPilot.Modal.prompt("Create a new event:", "Event 1").then(function(modal) {
        dp.clearSelection();
        if (!modal.result) { return; }
        dp.events.add(new DayPilot.Event({
          start: args.start,
          end: args.end,
          id: DayPilot.guid(),
          resource: args.resource,
          text: modal.result
        }));
      });
    },
    treeEnabled: true,
    onBeforeCellRender: function(args) {
      if (args.cell.resource === "summary") {
        var events = dp.events.forRange(args.cell.start, args.cell.end);

        args.cell.backColor = "#ffffff";
        args.cell.areas = [
          {left:0, top: 0, right: 0, bottom: 0, style: "display: flex; justify-content: center; align-items: center;", text: events.length},
        ];
      }
      if (args.cell.resource === "chart") {
        var events = dp.events.forRange(args.cell.start, args.cell.end).length * 1.0;
        var max = dp.resources.filter(function(r) { return !r.frozen; }).length;
        var maxHeight = dp.eventHeight;
        var percentage = events/max;
        var height = percentage * maxHeight;

        args.cell.backColor = "#ffffff";
        args.cell.areas = [
          {
            bottom: 0,
            height: height,
            left: 0,
            right: 0,
            backColor: "#cc0000",
            style: "opacity: " + percentage
          }
        ];
      }
    }
  });
  dp.resources = [
    {name: "Summary", id: "summary", frozen: "top", cellsAutoUpdated: true},
    {name: "Resource 1", id: "R1"},
    {name: "Resource 2", id: "R2"},
    {name: "Resource 3", id: "R3"},
    {name: "Resource 4", id: "R4"},
    {name: "Resource 5", id: "R5"},
    {name: "Resource 6", id: "R6"},
    {name: "Resource 7", id: "R7"},
    {name: "Resource 8", id: "R8"},
    {name: "Resource 9", id: "R9"},
    {name: "Summary", id: "chart", frozen: "bottom", cellsAutoUpdated: true},
  ];
  dp.events.list = [{
    start: "2021-10-07T00:00:00",
    end: "2021-10-15T00:00:00",
    id: "3037623f-d795-9da9-c16e-e92c87eff0ef",
    resource: "R3",
    text: "Event 1"
  }, {
    start: "2021-10-10T00:00:00",
    end: "2021-10-19T00:00:00",
    id: "2a7374b0-18ae-2d4d-a525-24680459d12f",
    resource: "R4",
    text: "Event 2"
  }, {
    start: "2021-10-08T00:00:00",
    end: "2021-10-13T00:00:00",
    id: "d2dd3702-934d-535d-87f1-f1c677475c80",
    resource: "R7",
    text: "Event 3"
  }, {
    start: "2021-10-11T00:00:00",
    end: "2021-10-17T00:00:00",
    id: "0ac8b9fe-6fce-b61a-9e80-bed4a7849d38",
    resource: "R8",
    text: "Event 4"
  }, {
    start: "2021-10-09T00:00:00",
    end: "2021-10-15T00:00:00",
    id: "92eee604-debe-606f-5c7a-9980ee5abb0a",
    resource: "R5",
    text: "Event 5"
  }, {
    start: "2021-10-10T00:00:00",
    end: "2021-10-15T00:00:00",
    id: "5fa94a68-d1b3-08f1-55f1-b030b55dcee7",
    resource: "R6",
    text: "Event 6"
  }, {
    start: "2021-10-09T00:00:00",
    end: "2021-10-15T00:00:00",
    id: "ef0ec3a2-6e72-62c9-befc-a17b9063c687",
    resource: "R2",
    text: "Event 7"
  }];
  dp.init();
</script>

</body>
</html>