LevelingOptions#

Options for Project#level. They set which resources, tasks and dates leveling acts on, the bucket size it detects overallocation with, which contender moves first, and how a task may be moved. The same type is the level field of EvaluateOptions and SuggestOptions. new LevelingOptions() levels the whole project with the defaults.

Declaration#

package org.dhtmlx.plen;

public class LevelingOptions extends ValueRecord implements DeepCloneable<LevelingOptions> {
    public LevelingScope scope = null;
    public Basis basis = null;
    public LevelingOrder order = LevelingOrder.STANDARD;
    public boolean withinSlackOnly = false;
    public boolean clearFirst = true;
    public boolean allowSplits = true;
    public LevelingStep step = LevelingStep.MINIMAL;
}

LevelingOptions is a mutable record. equals and hashCode compare the public fields, and toString() prints them as LevelingOptions{scope=…, basis=…, …}.

Constructors#

LevelingOptions()#

public LevelingOptions()

Creates options with every field at its default.

Fields#

Field Type Default Description
scope LevelingScope null Limits the resources, tasks and date range leveling acts on. null levels the whole project.
basis Basis null Detection bucket, HOUR or DAY. null derives it from SchedulingConfig.durationUnit: DAY for DAY, HOUR for HOUR and MINUTE.
order LevelingOrder LevelingOrder.STANDARD Which contender of an overallocated bucket moves first.
withinSlackOnly boolean false Caps each delay or added pause at the task's total slack, so leveling never pushes past the base plan's end.
clearFirst boolean true Clears levelingDelay and pauses on in-scope tasks before leveling starts.
allowSplits boolean true Allows proposals that add or extend pauses.
step LevelingStep LevelingStep.MINIMAL How far one proposal moves a task.

scope#

public LevelingScope scope = null;

A LevelingScope that restricts the run. Overallocation outside the scope is left as it is; its windows come back in the result with reason out-of-scope.

basis#

public Basis basis = null;

The bucket size used to find overallocated periods. With null, the basis matches the one every plan result already uses for its overallocation report, so leveling and detection see the same buckets.

order#

public LevelingOrder order = LevelingOrder.STANDARD;

The selection order among contenders for one overallocated bucket. See LevelingOrder.

withinSlackOnly#

public boolean withinSlackOnly = false;

When true, a task is delayed or paused only within its current total slack. A task whose slack can't clear the bucket is skipped, and a window left that way carries reason slack-exhausted.

clearFirst#

public boolean clearFirst = true;

When true, the run first sets levelingDelay to 0 and clears pauses on every in-scope task that carries either, then levels from that state. When false, existing delays and pauses stay and new proposals add to them.

allowSplits#

public boolean allowSplits = true;

When false, every correction is a whole-task delay. Existing pauses are kept (unless clearFirst clears them), but none are created or extended.

step#

public LevelingStep step = LevelingStep.MINIMAL;

The size of one proposal. See LevelingStep.

Methods#

withScope, withBasis, withOrder, withWithinSlackOnly, withClearFirst, withAllowSplits, withStep#

public LevelingOptions withScope(LevelingScope value)
public LevelingOptions withBasis(Basis value)
public LevelingOptions withOrder(LevelingOrder value)
public LevelingOptions withWithinSlackOnly(boolean value)
public LevelingOptions withClearFirst(boolean value)
public LevelingOptions withAllowSplits(boolean value)
public LevelingOptions withStep(LevelingStep value)

Each setter assigns its field and returns this instance, for chaining.

copy#

public LevelingOptions copy()

Returns a shallow copy. The copy shares the scope object with this instance.

deepClone#

public LevelingOptions deepClone()

Returns a copy with its own LevelingScope and scope lists.

LevelingScope#

Restricts a leveling run to some resources, tasks and a date range. Each field set to null means no restriction on that axis.

public class LevelingScope extends ValueRecord implements DeepCloneable<LevelingScope> {
    public Vec<Id> resources = null;
    public Vec<Id> tasks = null;
    public Moment from = null;
    public Moment to = null;
}
Field Type Default Description
resources Vec<Id> null Resources whose overallocation is resolved. null means every resource.
tasks Vec<Id> null Tasks leveling may move. Tasks outside the list are never delayed or paused. null means every task.
from Moment null Start of the triggering range. A bucket is in scope when its start lies in [from, to). null means no lower bound.
to Moment null End of the triggering range (exclusive). null means no upper bound.

The date range selects which overallocation triggers a move. It doesn't limit where a moved task lands: a delay can place a task after to.

Unknown ids in resources or tasks make Project#level throw an InputException at path scope.resources or scope.tasks.

Setters: withResources, withTasks, withFrom, withTo, each returning this scope. withResources and withTasks also take a java.util.Collection<? extends Id> and copy it into a new Vec, so List.of(...) works; null sets the field to null. copy() is shallow and shares the lists; deepClone() copies them.

LevelingOrder#

Which contender of an overallocated bucket moves first. Every order ends with the same tie-break: the task later in input order moves.

public enum LevelingOrder { ID, STANDARD, PRIORITY }
Value Description
ID A task already running in the bucket stays; among the rest, the task later in input order moves.
STANDARD Default. A running task stays, then, in this order: a predecessor stays and its successor moves; more total slack moves; a later start moves; lower priority moves; a task with a dated constraint stays over one without.
PRIORITY Lower TaskData.priority moves first, even against a running task; equal priorities fall through to STANDARD.

LevelingStep#

How far one proposal moves the selected task.

public enum LevelingStep { MINIMAL, SKIP }
Value Description
MINIMAL Default. Each proposal moves the task past one bucket; selection runs again at the next bucket.
SKIP Each proposal moves the task past the whole overallocated window in one step. Takes fewer rounds, and the result can differ from MINIMAL.

Example#

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

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

        ProjectData data = new ProjectData()
            .withTasks(List.of(
                new TaskData(Id.str("design")).withStartDate(monday).withDuration(3.0),
                new TaskData(Id.str("review")).withStartDate(monday).withDuration(2.0)))
            .withResources(List.of(new ResourceData().withId(Id.str("dev1"))))
            .withAssignments(List.of(
                new AssignmentData().withTask(Id.str("design")).withResource(Id.str("dev1")).withValue(1.0),
                new AssignmentData().withTask(Id.str("review")).withResource(Id.str("dev1")).withValue(1.0)));

        Project project = new Project();
        project.solve(data, new SolveOptions());

        LevelingOptions options = new LevelingOptions()
            .withScope(new LevelingScope().withResources(List.of(Id.str("dev1"))))
            .withOrder(LevelingOrder.PRIORITY)
            .withAllowSplits(false);

        LevelingResult result = project.level(options);
        System.out.println("rounds: " + result.rounds);
        for (Mutation update : result.schedule.updates) {
            System.out.println(update.id + " " + update.startDate + " delay=" + update.levelingDelay);
        }
        // rounds: 3
        // review 2026-01-08 00:00 delay=3.0
    }
}