Analytics#

The critical path and slack of a schedule. It's read from ScheduleResult.analytics, which is set only when the call requested at least one analytic through AnalyticsOptions. The engine creates the values; caller code only reads them.

Declaration#

package org.dhtmlx.plen;

public class Analytics extends ValueRecord implements DeepCloneable<Analytics>

Every field is public and mutable, and has a withX(value) setter that assigns it and returns this. equals, hashCode and toString come from ValueRecord and compare status, criticalPath and slack.

Fields#

Field Type Description
status AnalyticsStatus Whether the analytics could be computed. Check it before reading the other fields.
criticalPath Critical The critical tasks and links. Set when status is AVAILABLE and AnalyticsOptions.criticalPath is true, otherwise null.
slack Vec<TaskSlack> One entry per active leaf task and milestone. Set when status is AVAILABLE and AnalyticsOptions.slack is true, otherwise null.

status#

public AnalyticsStatus status = AnalyticsStatus.AVAILABLE;
public Analytics withStatus(AnalyticsStatus value)

INVALID when the dependency graph has a cycle. Both criticalPath and slack are then null, whatever was requested. The cycles themselves are in ScheduleResult.cycles.

criticalPath#

public Critical criticalPath = null;
public Analytics withCriticalPath(Critical value)

slack#

public Vec<TaskSlack> slack = null;
public Analytics withSlack(Vec<TaskSlack> value)
public Analytics withSlack(java.util.Collection<? extends TaskSlack> value)

The Collection overload copies the argument into a new Vec, or stores null for a null argument.

Methods#

deepClone#

public Analytics deepClone()

Returns a recursive copy: a new Critical and a new list of new TaskSlack entries. A null field stays null.

Returns - a new Analytics.

copy#

public Analytics copy()

Returns a shallow copy that shares criticalPath and the slack list with this instance.

Returns - a new Analytics.

AnalyticsStatus#

Whether the analytics in an Analytics value could be computed.

public enum AnalyticsStatus {
  AVAILABLE,
  INVALID
}
Value Description
AVAILABLE The requested fields are filled.
INVALID The dependency graph is cyclic. criticalPath and slack are null.

Critical#

The tasks and links on the critical path.

public class Critical extends ValueRecord implements DeepCloneable<Critical>
Field Type Default Description
tasks Vec<Id> empty Critical tasks: every leaf task or milestone with total slack of zero or less, plus every summary with at least one critical leaf below it. Completed tasks are never critical.
links Vec<Id> empty Links whose source and target are both critical and that leave no margin between them. Each link appears once.

Both fields have withTasks / withLinks setters that take a Vec<Id> or any java.util.Collection<? extends Id>. deepClone() copies both lists. copy() returns a shallow copy that shares them.

TaskSlack#

The slack of one task.

public class TaskSlack extends ValueRecord implements DeepCloneable<TaskSlack>
Field Type Default Description
task Id null The task.
total double 0.0 How far the task can move later without moving the project end. Negative when the task already runs past its latest date.
free double 0.0 How far the task can move later without moving any successor. Never larger than total.

Both values are in the project's durationUnit. A completed task (with useProgress on and progress == 1) has total and free of 0.0. Each field has a withX setter. deepClone() and copy() both return a new TaskSlack with the same values.

Critical path and slack covers how the engine computes both.

Example#

import java.util.List;
import org.dhtmlx.plen.*;

public class AnalyticsExample {
  public static void main(String[] args) throws Exception {
    Moment monday = Moment.fromYmdHm(2026, 1, 5, 8, 0);

    ProjectData data = new ProjectData()
        .withTasks(List.of(
            new TaskData(Id.str("design")).withStartDate(monday).withDuration(2.0),
            new TaskData(Id.str("build")).withStartDate(monday).withDuration(3.0),
            new TaskData(Id.str("docs")).withStartDate(monday).withDuration(1.0)))
        .withLinks(List.of(
            new LinkData().withId(Id.str("l1"))
                .withSource(Id.str("design"))
                .withTarget(Id.str("build"))));

    SolveOptions options = new SolveOptions()
        .withAnalytics(new AnalyticsOptions().withCriticalPath(true).withSlack(true));
    Analytics analytics = Engine.schedule(data, options).analytics;

    if (analytics.status == AnalyticsStatus.INVALID) {
      System.out.println("dependency cycle, no analytics");
      return;
    }

    System.out.println("critical tasks: " + analytics.criticalPath.tasks);
    System.out.println("critical links: " + analytics.criticalPath.links);
    for (TaskSlack s : analytics.slack) {
      System.out.println(s.task + ": total " + s.total + ", free " + s.free);
    }
    // critical tasks: [design, build]
    // critical links: [l1]
    // design: total 0.0, free 0.0
    // build: total 0.0, free 0.0
    // docs: total 4.0, free 4.0
  }
}