Custom Parent Task Progress Calculation (Manual, Average, and Effort-Based Calculation)

I’m currently using the Gantt Chart component in my app (remote data loading, and virtualization).

The automatic behaviour for parent task progress calculated as the simple average of its children, works in some cases but doesn’t always match real project structures. I’d like to offer users more control and flexibility.

Desired Progress Calculation Options:

  1. Manual Parent Progress Entry

    • Allow users to set the parent task’s progress manually, overriding the automatic calculation.

  2. Effort-Based Progress Calculation

    • Enable progress to be calculated based on an “effort factor” per child task.

    • For example, larger tasks (in terms of duration, complexity, or hours) should influence the parent’s progress more than smaller ones.

    • Instead of a blind average, each child could have an “effort factor” assigned by the user, so child tasks contribute proportionally.

  3. Toggle Between Modes

    • Automatic average (default),

    • Manual parent progress,

    • Effort-based (pseudo-weighted) progress.

My Questions:

  • Is there an existing Gantt API or event hook (such as taskbarTemplate, actionBegin, etc.) to override parent progress behaviour?

This would greatly improve accuracy and user satisfaction in complex project scenarios.


7 Replies

SJ Sridharan Jayabalan Syncfusion Team July 1, 2025 04:35 PM UTC

Hi Kimmo,

 

Greetings from Syncfusion.

 

To meet your requirement, we have prepared sample with custom solution. It enables dynamic switching between different progress indicators such as Default, Manual, and Effort Factor in our Gantt chart. It uses React state to control which progress field is displayed and updates the chart accordingly. A custom taskbar template ensures the correct value is shown visually based on the selected mode. Refer to the below following steps:

  1. Initialize with Default Progress
    The Gantt chart is initially configured to use the standard 
    Progress field, with corresponding label settings "Progress" and task field mappings.
  1. Track Progress Mode Using State
    Three boolean states (
    isDefault, isManual, isEffortFactor) are used to determine which progress mode is active and control the chart’s behavior.
  1. Switch Modes via Button Clicks
    Clicking a mode button updates the relevant state and dynamically changes the 
    progress field and label in the Gantt chart configuration.
  1. Render Custom Taskbar Template
    A custom 
    ParentTaskbarTemplate conditionally displays the correct progress value (Progress, CustomProgress, or EffortFactor) based on the active mode.
  1. Force Gantt Chart Re-render
    The 
    key prop on the Gantt component is tied to the mode state, ensuring the chart re-renders immediately when the mode changes.

 

Refer to the code snippet and sample for your reference.

 

Code-Snippet:   

index.js:-

 

function App() {

  // State to track which progress mode is active

  const [isDefault, setIsDefault] = useState(true);

  const [isManual, setIsManual] = useState(false);

  const [isEffortFactor, setIsEffortFactor] = useState(false);

 

  // Label settings for the Gantt chart

  const [labelSettings, setLabelSettings] = useState({

    taskLabel: 'Progress',

  });

 

  // Task field mappings for the Gantt chart

  const [taskFields, setTaskFields] = useState({

    id: 'TaskID',

    name: 'TaskName',

    startDate: 'StartDate',

    duration: 'Duration',

    progress: 'Progress',

    parentID: 'ParentID',

  });

 

  // Custom template for rendering parent taskbars

  function ParentTaskbarTemplate(props) {

    return (

      <div

        className="e-gantt-parent-taskbar-inner-div e-gantt-parent-taskbar"

        style={{ height: '100%' }}

      >

        <div

          className="e-gantt-parent-progressbar-inner-div e-row-expand e-gantt-parent-progressbar"

          style={{

            width: props.ganttProperties.progressWidth + 'px',

            height: '100%',

          }}

        ></div>

        <span

          className="e-task-label"

          style={{

            position: 'absolute',

            fontSize: '12px',

            color: 'white',

            top: '5px',

            left: '10px',

            fontFamily: 'Segoe UI',

            cursor: 'move',

          }}

        >

          {/* Display the appropriate progress value based on selected mode */}

          {isDefault

            ? props.Progress

            : isManual

            ? props.taskData.CustomProgress

            : isEffortFactor

            ? props.EffortFactor

            : props.Progress}

        </span>

      </div>

    );

  }

 

  // Handlers to switch between progress modes

  const onManualClick = () => {

    setIsDefault(false);

    setIsEffortFactor(false);

    setIsManual(true);

    setTaskFields((prev) => ({ ...prev, progress: 'CustomProgress' }));

    setLabelSettings({ taskLabel: 'CustomProgress' });

  };

 

  const onDefaultClick = () => {

    setIsDefault(true);

    setIsEffortFactor(false);

    setIsManual(false);

    setTaskFields((prev) => ({ ...prev, progress: 'Progress' }));

    setLabelSettings({ taskLabel: 'Progress' });

  };

 

  const onEffortFactortClick = () => {

    setIsDefault(false);

    setIsEffortFactor(true);

    setIsManual(false);

    setTaskFields((prev) => ({ ...prev, progress: 'EffortFactor' }));

    setLabelSettings({ taskLabel: 'EffortFactor' });

  };

 

  return (

    <>

      {/* Buttons to switch progress modes */}

      <button onClick={onDefaultClick}>Default</button>

      <button onClick={onManualClick}>Manual</button>

      <button onClick={onEffortFactortClick}>Effort Factor</button>

 

      {/* Gantt chart component */}

      <GanttComponent

        id="Gantt"

        key={isManual} // Forces re-render when switching modes

        dataSource={data}

        taskFields={taskFields}

        labelSettings={labelSettings}

        parentTaskbarTemplate={ParentTaskbarTemplate}

        height="450px"

      />

    </>

  );

}

 

Sample - Hjqtqzbp (duplicated) - StackBlitz

Demo - Gantt Chart · Taskbar Template · Syncfusion React UI Components

Documentation - Taskbar in React Gantt component | Syncfusion

If we misunderstood your query, share us detailed information about your requirement with screenshot or video demo.


Regards,

Sridharan



KK Kimmo Kallioniemi July 3, 2025 05:32 AM UTC

Thanks for your response. But this does not fully address the deeper issues, especially around actual calculation logic for parent task progress in Syncfusion Gantt, especially in the edit mode.

This does not prevent Syncfusion from recalculating parent progress, automatically based on the default average of children. Syncfusion will still internally override the parent progress unless this behaviour is explicitly disabled or overridden.

Implement custom calculation logic (e.g., weighted/effort-based) dynamically. Changing the label or field doesn’t change how Syncfusion actually calculates the progress. You'd need to calculate it manually and supply that calculated value in a data pre-processing step.

Block automatic recalculation when child tasks updateIf a child task changes, Syncfusion will likely still recalculate the parent progress unless you override it at the source level.



SJ Sridharan Jayabalan Syncfusion Team July 3, 2025 03:09 PM UTC

Kimmo,

To support your query, we ensured that the custom logic is maintained in the edit action as well. Below are the steps we followed to implement your dynamic progress display feature:
Step 1: We added a dropdown column called "CustomProgress" with three options: Default, Manual, and EffortFactor.
Step 2: When you select an option from the dropdown, the system immediately saves your choice and remembers it for that specific task.
Step 3: The Gantt chart then automatically refreshes to show the updated progress bars based on your selection.
Step 4: The taskbar templates check which mode you selected - if you chose "Manual", it displays the ManualProgress value; if you chose "Default", it shows the standard calculated progress.
Step 5: This happens instantly without any page reload, so you see the progress bars change in real-time as soon as you make your selection.
Step 6: All your different progress values are safely stored, so you can switch between modes anytime without losing any data.
The end result is that each task can have its own progress calculation method, and you can see the changes immediately in the visual timeline.
Note - You can modify the progress calculation inside the ParentTaskbarTemplate method.

Code Snippet:-

index.js:-

const Editing = () => {
  const [taskModes, setTaskModes] = useState({});
  const [ganttData, setGanttData] = useState(customData);

  let elem;
  let ganttInstance;
  let dropdownlistObj;
  const sportsData = ['Default', 'Manual', 'EffortFactor'];

// custom drop down
  let dropdownlist = {
    create: () => {
      elem = document.createElement('input');
      return elem;
    },
    read: () => {
      return dropdownlistObj.value;
    },
    destroy: () => {
      dropdownlistObj.destroy();
    },
    write: (args) => {
      dropdownlistObj = new DropDownList({
        dataSource: sportsData,
        value: args.rowData[args.column.field] || 'Default',
        floatLabelType: 'Auto',
        change: (changeArgs) => {
          const taskId = args.rowData.TaskID;
          const selectedMode = changeArgs.value;

          // Update the task modes state
          setTaskModes((prev) => ({
            ...prev,
            [taskId]: selectedMode,
          }));

          // Update the data source
          args.rowData[args.column.field] = selectedMode;

          // Update the gantt data state
          setGanttData((prevData) => {
            const updateTaskMode = (tasks) => {
              return tasks.map((task) => {
                if (task.TaskID === taskId) {
                  return { ...task, CustomProgress: selectedMode };
                }
                if (task.subtasks) {
                  return { ...task, subtasks: updateTaskMode(task.subtasks) };
                }
                return task;
              });
            };
            return updateTaskMode(prevData);
          });

          // Refresh the Gantt to update the template
          setTimeout(() => {
            if (ganttInstance) {
              ganttInstance.refresh();
            }
          }, 100);
        },
      });
      dropdownlistObj.appendTo(elem);
    },
  };

  function ParentTaskbarTemplate(props) {
    const taskId = props.TaskID;
    const selectedMode = taskModes[taskId] || props.CustomProgress || 'Default';

    // Determine which progress value to display
    let displayProgress;
    if (selectedMode === 'Manual') {
      displayProgress = props.ManualProgress || 0;
    } else if (selectedMode === 'EffortFactor') {
      // your custom logic here for effor factor calculation
      displayProgress = props.EffortFactorProgress || 0;
    } else {
      // Default calculation - use the standard Progress field
      displayProgress = props.Progress || 0;
    }

    return (
      <div
        className="e-gantt-parent-taskbar-inner-div e-gantt-parent-taskbar"
        style={{ height: '100%' }}
      >
        <div
          className="e-gantt-parent-progressbar-inner-div e-row-expand e-gantt-parent-progressbar"
          style={{ width: props.ganttProperties.progressWidth + 'px', height: '100%',}}
        ></div>
        <span
          className="e-task-label"
          style={{
            position: 'absolute', fontSize: '12px', color: 'white', top: '5px', left: '10px',
            fontFamily: 'Segoe UI', cursor: 'move',
          }}
        >
          {displayProgress}% ({selectedMode})
        </span>
      </div>
    );
  }

  return (
        <GanttComponent
          parentTaskbarTemplate={ParentTaskbarTemplate}
        >
          <ColumnsDirective>
            <ColumnDirective
              field="CustomProgress"
              headerText="Progress Mode"
              width="150"
              edit={dropdownlist}
            ></ColumnDirective>
          </ColumnsDirective>
          <Inject services={[Edit, Selection, Toolbar, DayMarkers]} />
        </GanttComponent>
  );
};

Modified Sample - Odgdcks3 (duplicated) - StackBlitz



Regards,

Sridharan



KK Kimmo Kallioniemi July 19, 2025 07:05 PM UTC

When I update the progress of a child task, the visual fill (width) of the parent task's taskbar also changes, even though the parent task's progress value remains the same. How can I prevent the parent taskbar's visual fill from updating when child task progress changes?



SJ Sridharan Jayabalan Syncfusion Team July 21, 2025 12:19 PM UTC

Kimmo,

 

We understand your query as: 'While the progress mode is set to Manual, the parent taskbar's progress width should not automatically update when the child task's progress changes.'

 

To achieve this behavior, we recommend adding a condition to control the parent task's progress width manually. Specifically, you can use the ManualProgress property to set the desired width explicitly. Please refer to the following code snippet and sample for implementation details

 

Code-Snippet:   

  function ParentTaskbarTemplate(props) {

   ...................................

    return (

      <div

        className="e-gantt-parent-taskbar-inner-div e-gantt-parent-taskbar"

        style={{ height: '100%' }}

      >

        <div

          className="e-gantt-parent-progressbar-inner-div e-row-expand e-gantt-parent-progressbar"

          style={{

            width:

              props.CustomProgress == 'Manual' ? props.ManualProgress

                : props.ganttProperties.progressWidth + 'px',

            height: '100%',

          }}

        ></div>

        <span

          className="e-task-label"

          style={{

            position: 'absolute',

            fontSize: '12px',

            color: 'white',

            top: '5px',

            left: '10px',

            fontFamily: 'Segoe UI',

            cursor: 'move',

          }}

        >

          {displayProgress}% ({selectedMode})

        </span>

      </div>

    );

  }

 

Modified Sample - Odgdcks3 (duplicated) - StackBlitz

 

 

Regards,

Sridharan



KK Kimmo Kallioniemi July 22, 2025 05:33 AM UTC

 width:props.CustomProgress == 'Manual'
                ? props.ganttProperties.width * (props.ManualProgress / 100)

I was able to set the width with this. Is it possible to add the dragging of the p



SJ Sridharan Jayabalan Syncfusion Team July 23, 2025 02:44 PM UTC

Kimmo,


For your query, we suspect that you might be referring to progress resizing for the parent taskbar. However, this is currently not supported, as the parent task’s progress is automatically calculated based on its child tasks.


Alternatively, if you're referring to resizing the parent taskbar itself (i.e., adjusting its start or end dates by dragging), this is also not supported. Parent taskbars do not support left or right resizing. But we have manual taskbar feature which offers you right resize alone. Refer to the below documentation links:

https://ej2.syncfusion.com/react/demos/#/tailwind3/gantt/taskMode

https://ej2.syncfusion.com/react/documentation/api/gantt/#taskmode


If your requirement is different from the above, please share a screenshot or visual reference of the exact behavior you're trying to achieve. This will help us better understand your scenario and provide a more accurate solution.



Regards,

Sridharan


Loader.
Up arrow icon