Our state of the art Gantt chart


Post by alex.l »

You need to create simple calendar that will have no non-working time and apply to your tasks.

As example, if you want to calculate 1 day as 24 hours, it will have no intervals at all

  "project" : {
    "calendar"     : "someCalendar",
    // ...
    "hoursPerDay"  : 24,
    "daysPerWeek"  : 7
  },
  "calendars" : {
    "rows" : [
      {
        "id"        : "general",
        "name"      : "General",
        "intervals" : [
          
],

In tasks

  "tasks" : {
    "rows" : [
      {
        "id"          : 1000,
        "name"        : "Launch SaaS Product",
        "percentDone" : 34,
        "startDate"   : "2019-01-14",
        "endDate"     : "2019-03-20",
        "duration"    : 47,
        "expanded"    : true,
        "calendar"    : "general",
        // ...

All the best,
Alex


Post by vconstruct »

Hi,
We tried adding a constraint date and a constraint type : "mustfinishon" on each task to lock the end date for each task, but it seems that bryntum is ignoring this can you please let us know if we are missing something?


Post by alex.l »

Please send your full configuration. It's hard to predict what exactly is going on in that situation.
Did you try to ignore project's calendar for tasks / specify own calendar for tasks will all time as working? Why it didn't work for you?
Why did you use calendars at all if you want to ignore them and don't schedule tasks?
Did you specify correct settings for project such as
https://bryntum.com/products/gantt/docs/api/Gantt/model/ProjectModel#field-hoursPerDay
https://bryntum.com/products/gantt/docs/api/Gantt/model/ProjectModel#field-daysPerWeek
https://bryntum.com/products/gantt/docs/api/Gantt/model/ProjectModel#field-daysPerMonth
to have predictable calculation of duration
You can also enable https://bryntum.com/products/gantt/docs/api/Gantt/model/ProjectModel#field-skipNonWorkingTimeWhenSchedulingManually for the project to ignore calendars for all manually scheduled tasks at once

All the best,
Alex


Post by vconstruct »

Hi,
we are using the importer class that we found in this bryntum example (https://bryntum.com/products/gantt/examples/msprojectimport/) to push data into the bryntum store.
We tried the following to see if the dates would not change after getting imported:

  1. We removed the calendar entirely but that didnt change anything
  2. Based on your suggestion to use a no non-working time calendar we simply passed an empty array to the interval feild for all the calendars that we pass with the dataset that we get from mpxj but just made all the activities into one day activities (same start date and end date)
  3. We over rode the following 3 fields inside the project model hoursPerDay = 24,daysPerMonth = 30,daysPerWeek = 7 and repeated the 2 scenarios mentioned above but the results were the same.

Since we are using nx framework for our application some of our configuration is in the application and some in the bryntum package that we are creating

I have provided the config in one of the previous responses in this thread here:

vconstruct wrote: Tue May 30, 2023 12:49 pm

Hi,
Below is the configuration that we have been using with bryntum gantt:

Column Def:

  const columnDef: Partial<ColumnStoreConfig>[] | object[] = [

{
  type: 'activityidcolumn',
  // eslint-disable-next-line @typescript-eslint/ban-ts-comment
  // @ts-ignore
  renderer({ record }) {
    const emptyString = " ";
    if(record._data.activityid){
      return record._data.activityid
    }
    else {
        return emptyString
    }
  },
  text: 'Activity Id',
},
{
  type: 'name',
  field: 'name',
  text: 'Activity Name',
  width: 350,
  editor: false,
  tooltipRenderer : false,
},
{
  type: 'statuscolumn',
  // eslint-disable-next-line @typescript-eslint/ban-ts-comment
  // @ts-ignore
  renderer({ record }) {
    const emptyString = " ";
    if(record._data.status){
      return record._data.status
    }
    else {
        return emptyString
    }
}
},
{ type: 'date', field: 'startDate', text: 'Start Date', editor: false},
{ type: 'date', field: 'endDate', text: 'End Date', editor: false},
{ type: 'duration', field: 'duration', editor: false , 
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
//@ts-ignore
  renderer: ({ record, column, isExport }) => {
    // return !record.duration ? parseInt(`0`) : record.duration
    if(isExport) {
      return !record.duration ? parseInt(`0`) : record.duration
    } else {
      return !record.duration ? `0 Days` : `${record.duration} Days`
    }
  }
},
{
  type: 'percentdone',
  field: 'percentDone',
  text: 'Progress',
  editor: false, 
  tooltipRenderer : false
},
{
  type: 'criticalcolumn',
  renderer: (record: any) =>{
    // eslint-disable-next-line @typescript-eslint/ban-ts-comment
    //@ts-ignore
    if(record.record._data.isCritical){
        return "Yes"
    }else{
        return "No"
    }
  },
},
{
  type   : 'widget',
  text : 'Trade Partners',
  editor: false,
  htmlEncode : false,
  renderer: ({record, isExport}:any) => {
   const assignments = record.getAssigned()
   if(isExport){
      let assignmentList = "";
      assignments?.forEach((assignment: { _data: { resource: { _data: { name: string; }; }; }; }) => {
        assignmentList += `${assignment._data.resource._data.name};`;
      });
      return assignmentList.slice(0,assignmentList.length-1);
    }
    if (assignments?.size === 1) {
      return  `<div class = "chipView"> ${ [...assignments][0]._data.resource._data.name} </div>`
    } else if(assignments?.size > 1) {
      return `<div class = "chipView"> Multiple </div>`
    } else {
      return ''
    }
  },
  // eslint-disable-next-line @typescript-eslint/ban-ts-comment
  // @ts-ignore 
  tooltipRenderer : ({record, column,cellElement}) => {
    let assignmentList = "";
    record.getAssigned()?.forEach((assignment: { _data: { resource: { _data: { name: string; }; }; }; }) => {
      assignmentList += `${assignment._data.resource._data.name}; `;
    });
    return assignmentList.slice(0,assignmentList.length-2);
  },
  filterType: 'text',
  filterable: {
    filterField: {
     type: 'text'
    },
    filterFn: ({ value, record }:any) => {
      if(record.getAssigned()?.size && value === ""){
        return true;
      }else if(record.getAssigned()?.size && value !== ""){
        let res = false;
        record.getAssigned().forEach((assignment: { _data: { resource: { _data: { name: string; }; }; }; }) => {
          if(assignment._data.resource._data.name.toLocaleLowerCase().includes(value.toString().toLocaleLowerCase()) ){
            res = true;
          }
        });
        return res;
      }else{
        return false;
      }
    }
  },
  sortable: false
},
{
  type : "hra",
  // eslint-disable-next-line @typescript-eslint/ban-ts-comment
  // @ts-ignore  
  renderer : ({ record, isExport }) => {
    if(isExport){
      let hraList = "";
      if(record._data?.hra){
        record._data.hra?.forEach((hra: { hra_id: string; hra_description: string; }) => {
          hraList += `${hra.hra_id} - ${hra.hra_description},`
        });
      }
      return hraList.slice(0,hraList.length-1);
    }
    let hra = "";
   
    if(record._data?.hra?.length > 1){
      
     return  `<div class = "chipView"> Multiple </div>`
    
    }else if(record._data?.hra?.length === 1){
      
    // eslint-disable-next-line @typescript-eslint/ban-ts-comment
    // @ts-ignore  
        hra += `<div class = "chipView">${record._data.hra[0].hra_id} - ${record._data.hra[0].hra_description}</div>`
    }
    return `<div class="chipViewRenderer">
                ${hra}
            </div>`
  },
  // eslint-disable-next-line @typescript-eslint/ban-ts-comment
  // @ts-ignore 
  tooltipRenderer : ({ record, column }) => {
    if(record._data.hra && record._data.hra.length > 0){
      let res = "";
      record._data.hra.forEach((hra: { hra_id: string; hra_description: string; }) => {
        res += `${hra.hra_id}-${hra.hra_description}; `
      });
      return res.slice(0,res.length-2);
    }
  },
  // eslint-disable-next-line @typescript-eslint/ban-ts-comment
  // @ts-ignore 
  filterable: ({ value, record }) => {
    if(record._data.hra && value === ""){
      return true;
    }else if(record._data.hra && value !== ""){
      let res = false;
      record._data.hra.forEach((hra: {hra_description: string ; hra_id: string; }) => {
        if(hra.hra_description.toLocaleLowerCase().includes(value.toString().toLocaleLowerCase()) || hra.hra_id.toLocaleLowerCase().includes(value.toString().toLocaleLowerCase())){
          res = true;
        }
      });
      return res;
    }else{
      return false;
    }
  },
  sortable: false
},
  ];

Selection Mode prop:

const selectionMode = {
    row : false,
    cell : false,
    rowCheckboxSelection : true,
    multiSelect : true,
    checkbox : true,
    showCheckAll : true,
    deselectFilteredOutRecords : true,
    includeChildren : true,
    preserveSelectionOnPageChange : true,
    preserveSelectionOnDatasetChange : true,
    deselectOnClick : true,
  }

Toolbar widget Options:

  const toolbarOptions: IToolbarModels = {
    ripple: true,
    zoomIn: true,
    zoomOut: true,
    zoomToFit: true,
    collapseAll: true,
    expandAll: true,
    filterActivities: true,
    hideEmptyWBS: true,
    hideNonCriticalTasks: true,
    hideGantt: true,
    hideCompletedTasks: true,
    showDateFilters: true,
    showHideDependency: true,
    export: true,
  };

Gantt Features prop:

const features: IFeatureModels = {
    // cellMenu : {
    //   disabled : true
    // },
    cellEdit: false,
    dependencyEdit : false,
    taskEdit : false,
    rowReorderFeature: false,
    taskDragFeature: false,
    taskResizeFeature: false,
    cellTooltip : {
      hoverDelay      : 0,
      textContent     : true,
      // eslint-disable-next-line @typescript-eslint/ban-ts-comment
      // @ts-ignore  
tooltipRenderer : ({ record, column }) => StringHelper.encodeHtml(record[column.field]), showOnHover : true, hideOnDelegateChange : true, hideDelay : 0 } };

React Gantt Component:

<BryntumGantt
          tbar={{}}
          onCellClick={(event?: any) =>
            props.scheduleInstance.rowSelection(event)
          }
          height={'inherit'}
          width={'inherit'}
          ref={(ganttRef: any) => {
            if (!props.scheduleInstance.getGanttInstance()) {
              props.scheduleInstance.setGanttInstance(ganttRef);
              // eslint-disable-next-line @typescript-eslint/ban-ts-comment
              //@ts-ignore
              ref.current = ganttRef;
            }
          }}
          percentBarFeature={{ allowResize: false }}
          taskDragFeature={false}
          taskResizeFeature={false}
          taskSegmentDragFeature={false}
          taskSegmentResizeFeature={false}
          rowReorderFeature={false}
          features={{
            excelExporter: {
              dateFormat: 'YYYY-MM-DD HH:mm',
              zipcelx,
            },
            filter: true,
            timeRanges: true,
            projectLines: false,
            cellTooltip: props.features.cellTooltip,
            ...props.features,
            taskMenu: false,
          }}
          dependenciesFeature={{
            allowCreate: false,
          }}
          dependencyEditFeature={false}
          taskEditFeature={getTaskEditorItems(
            emitNotesInput,
            saveTrigger,
            HRAListData,
            emitHraInputForAssign,
            addHRAsTrigger,
            TradepartnerList,
            emitTradepartnerInputForAssign,
            addTPTrigger,
            removeTPTrigger,
            emitTradepartnerInputForRemove,
            removeHRATrigger,
            emitHRAInputForRemove,
            props
          )}
          onAfterTaskEdit={(event: any) => {
            saveNotes(event);
            addTradePartners(event);
            addHRAs(event);
            removeTradePartner(event);
            removeHRA(event);
          }}
          listeners={{
            beforeTaskEditShow(event: any) {
              const { editor, taskRecord } = event;
              const widgetMap = editor.widgetMap;
              const TPCombo = widgetMap.tradePartnersTab.widgetMap.TPCombo;
              const HRACombo = widgetMap.HRATab.widgetMap.HRACombo;
              TPCombo.value = null;
              HRACombo.value = null;
              setSelectedActivity(taskRecord);
              loadCustomTabsData(editor, taskRecord, widgetMap, props);
            },
            // eslint-disable-next-line @typescript-eslint/ban-ts-comment
            // @ts-ignore
            cellDblClick({ record }) {
              props?.scheduleInstance.showTaskEditDialog(record._data.guid);
            },
            beforePresetChange(event: {
              source: any;
              preset: { options: { startDate: any; endDate: any } };
            }) {
              const gantt = event.source;
              event.preset.options.startDate = gantt.project.startDate;
              event.preset.options.endDate = gantt.project.endDate;
            },
          }}
          selectionMode={props.selectionMode}
          displaySchedulingIssueResolutionPopup={false}
        />

Post by alex.l »

I still cannot use data you provided because of:

  1. it is not formatted well, you can see warnings in console if you keep https://bryntum.com/products/gantt/docs/api/Gantt/model/ProjectModel#config-validateResponse enabled. I fixed all of the notes but
  2. many cyclic dependencies that I need to disable again and again and still can't see data.
    Screenshot 2023-06-02 at 12.48.28.png
    Screenshot 2023-06-02 at 12.48.28.png (95.62 KiB) Viewed 379 times

Could you please review it again? You can use our advanced demo as a base and replace launch-sass.json with your file. We have to find the way to launch an application and see the problem on our side to go forward with that.

So, using manuallyScheduled together with enabling https://bryntum.com/products/gantt/docs/api/Gantt/model/ProjectModel#field-skipNonWorkingTimeWhenSchedulingManually for project still didn't do that you expected?

All the best,
Alex


Post by vconstruct »

Hi Alex,

Apologies for the delay in our response since we had been trying to create a sample codebase which is similar to our current code architecture so that it would be easier to test, replicate and isolate issues. Please find the repo below

https://github.com/kinshuksri25/BryntumExample.git

Please run this code and you would see the forward scheduling issue we have mentioned.

We suspect that the reason the issue was occurring was because of the "commitAsync" function which would commit changes to the store, and because of the "add" functionality for the resource and assignment store.

Both of these functionalities have been tested on this codebase and we found the forward scheduling to occur whenever we call the "commitAsync" function or the "add" function even if we commit nothing.

you will find them inside the ganttInstance.ts file
path = {rootdirectory}\libs\gantt\src\lib\ganttInstance.ts
function name = initializeData
Line number = 60

We have also included the same dataset that we had shared earlier

The following tasks have been tested forward scheduling and you can use the same

activity guid = d018bebb-fbb3-7543-9d9d-e17d6b50d0db
activityID = A2100
activity name= test124

activity guid = 961e28fb-0dcc-6b46-955a-2a9bc7edc7ca
activityID = A1270
activity name = test123

activity guid = 474e2129-3c49-af49-b7be-dcead0f94f7c
activityID = A3810
activity name = test256

activity guid = 378bf5ac-ae1b-d540-babd-b88ce96379a6
activityID = A1440
activity name = test789

activity guid  = c2f82f0f-e5f3-5941-b8bf-9bad298b0072
activityID  = PV-1025N
activity name = test000

Please let us know if any other info is needed.

The application does not have any loader so you would have to wait a min or 2 for the data to populate in the gantt

Thanks


Post by alex.l »

Hi,

Actually, I don't see any difference via initial dates and dates I found in taskStore after data loaded. I uncommented both parts of your the code you mentioned in previous post.
I checked all mentioned tasks, compared startDate and endDate values with listed in data.ts file.
Sorry, but I need some clarifications, what exactly do you see. Please post actual and expected result.

Please make sure that in real app:

  1. You enabled manuallyScheduled flag for all tasks.
  2. You have https://bryntum.com/products/gantt/docs/api/Gantt/model/ProjectModel#field-skipNonWorkingTimeWhenSchedulingManually as false
  3. Try to add active: false for all dependencies to disable any potential affect. It should be already working as expected, but let's use all possible ways.
  4. Make sure you didn't use endDate together with duration value in initial dataset. Because duration always have a priority if provided and endDate might be recalculated according to available time in active calendar.
  5. If you have conflicts and auto resolving handlers, original data might be changed during conflict resolving and will have difference. Make sure you did nothing with data and data is fully valid.

All the best,
Alex


Post by vconstruct »

Apologies for the confusion, it seems a part of the test code that was supposed to be removed before sharing the repo was left behind which is why you were not getting the enddate discrepancy issue, i would request you to take the latest pull since we have rectified the changes in the repo.

I have attached the results for your consideration.

Attachments
This is the scenario where commitasync was commented out
This is the scenario where commitasync was commented out
matchingdata.png (61.46 KiB) Viewed 328 times
this was the scenario where commitasync was not commented out
this was the scenario where commitasync was not commented out
notMatchingDatapng.png (61.69 KiB) Viewed 328 times

Post by alex.l »

Thanks for the update and patience. I see you set calendar for every task. When I removed this together with following steps from my previous message, I see it's working as expected.

All the best,
Alex


Post by vconstruct »

alex.l wrote: Mon Jun 12, 2023 9:05 pm

Thanks for the update and patience. I see you set calendar for every task. When I removed this

Can you please elaborate what you mean.


Post Reply