bryntum / support

An issues-only repository for the Bryntum project management component suite which includes powerful Grid, Scheduler, Calendar, Kanban Task Board and Gantt chart components all built in pure JS / CSS / TypeScript
https://www.bryntum.com
53 stars 6 forks source link

Gantt Performance issue with loadDataAsync() #8517

Open kronaemmanuel opened 7 months ago

kronaemmanuel commented 7 months ago

Steps to reproduce:

bryntum.query('gantt').taskStore.loadDataAsync(arr)

Loading tasks takes quite some time, (if we go upto 10k events, firefox gives a warning to stop execution of script). After the events are loaded, it takes a while to refresh rows and display correct ui also.

[Forum post](https://forum.bryntum.com/viewtopic.php?f=44&t=27951&p=140191#p140191)

Hello, i'm try to load 3500 events on the Scheduler placed on 1088 resources.

I'm not using the CRUD implemented by your project. i'm loding the view ( resources on the left part) and the events.

Here the loadEvents : 

public async loadEvents(): Promise {

if (!this.scheduler || !this.planningGraph) { return false; }

this.compteur += 1;

console.time(LoadEvents_${this.compteur}); let loaded: boolean = false;

try {

 this.mask(ResourcesManager.manager.getString("LoadingEvents"));

 let data = await GanttPlannerSchedulerApi.loadPlanning(this.planningGraph.id, this.startDate, this.endDate);

 //this.scheduler.suspendRefresh();

 this.eventStore.suspendEvents();

 this.eventStore.removeAll(true);

 console.timeLog(`LoadEvents_${this.compteur}`);

 // Load ues of all events
 let allocationUes: Array<any> = [];
 let resourceUes: Array<any> = [];
 let sourceResourceUes: Array<any> = [];

 for (let eventData of data) {
     if (eventData.allocationData && eventData.allocationData.ueIdentifier) {
         allocationUes.push(eventData.allocationData.ueIdentifier);
     }

     if (eventData.sourceResourceUeIdentifiers && eventData.sourceResourceUeIdentifiers.length > 0) {
         sourceResourceUes.push(...eventData.sourceResourceUeIdentifiers);
     }

     if (eventData.resourceUeIdentifiers && eventData.resourceUeIdentifiers.length > 0) {
         resourceUes.push(...eventData.resourceUeIdentifiers);
     }
 }

 this.eventAllocationUes = [...new Set(allocationUes)];
 this.eventResouceUes = [...new Set(resourceUes)];
 this._eventSourceResouceUes = [...new Set(sourceResourceUes)];

 console.timeLog(`LoadEvents_${this.compteur}`);

 await this.eventStore.loadDataAsync(data);

 this.onDataChanged(data);

 console.timeLog(`LoadEvents_${this.compteur}`);
 //await this.eventStore.commit();

 loaded = true;

} catch (error: any) { if (error instanceof EdispatchException) { BootstrapDangerToast.show(ResourcesManager.manager.getString("Error"), error.title); } else { BootstrapDangerToast.show(ResourcesManager.manager.getString("Error"), error.toString()); } } finally {

 this.unmask();

 this.eventStore.resumeEvents();

 //this.scheduler.suspendRefresh();
 //   this.scrollToDate = this.startDate;
 // this.zoomToFit();
 // Todo
 this.scheduler.visibleDate = {
     date: this.startDate,
     block: 'center'
 };

}

console.timeEnd(LoadEvents_${this.compteur}); return loaded; }


and here the loadResources

public async loadResources(): Promise { console.time("LoadResources"); let loaded: boolean = false;

if (!this.scheduler || !this.planningGraph) { return loaded; }

try {

//this.scheduler.suspendRefresh();

//this.assignmentStore.suspendEvents();

this.resourceStore.suspendEvents();

this.resourceStore.eventStore.suspendEvents();

this.resourceStore.removeAll(true);

//this.eventStore.suspendEvents();

await this.resourceStore.loadDataAsync(this.planningGraph.graph);
//this.resourceStore.data = this.planningGraph.graph;

// await this.loadResourcesDetails();
//setTimeout(async () => {

//    // Load data after planning is loaded;
//    this.scheduler?.suspendRefresh();

//    await this.loadResourcesDetails();

//    this.scheduler?.resumeRefresh(true);
//}, 100);

loaded = true;

} catch (error) {

BootstrapDangerToast.show("Error loading resources");

} finally {

//await this.resourceStore.commit();

//await this.resourceStore.project.commitAsync();

//await this.scheduler.project.commitAsync();

this.resourceStore.resumeEvents();

this.resourceStore.eventStore.resumeEvents();
//this.eventStore.resumeEvents();

//this.assignmentStore.resumeEvents();

//await this.scheduler.resumeRefresh(false);

} console.timeLog("LoadResources");

console.timeEnd("LoadResources") return loaded; }


For the loadEvent i'm doing this 
        await this.eventStore.loadDataAsync(data);


When i look your big dataset example. I'm so far from your perf. I need 30 sec to get Ui response on the Gantt.

Do you have some clues to make it more reactive?
HLABATF commented 7 months ago

Hello, What is the most efficient way to reload completely different data from the server?

For example, i'm doing this: ` public async loadResources(): Promise { console.time("LoadResources"); let loaded: boolean = false;

 if (!this.scheduler || !this.planningGraph) {
     return loaded;
 }

 try {

     //this.scheduler.suspendRefresh();

     //this.assignmentStore.suspendEvents();

     this.resourceStore.suspendEvents();

     this.resourceStore.eventStore.suspendEvents();

     this.resourceStore.destroy();//.removeAll(true);

     //this.eventStore.suspendEvents();

     await this.resourceStore.loadDataAsync(this.planningGraph.graph);
     //this.resourceStore.data = this.planningGraph.graph;

     // await this.loadResourcesDetails();
     //setTimeout(async () => {

     //    // Load data after planning is loaded;
     //    this.scheduler?.suspendRefresh();

     //    await this.loadResourcesDetails();

     //    this.scheduler?.resumeRefresh(true);
     //}, 100);

     loaded = true;
 }
 catch (error) {

     BootstrapDangerToast.show("Error loading resources");

 }
 finally {

     //await this.resourceStore.commit();

     //await this.resourceStore.project.commitAsync();

     //await this.scheduler.project.commitAsync();

     this.resourceStore.resumeEvents();

     this.resourceStore.eventStore.resumeEvents();
     //this.eventStore.resumeEvents();

     //this.assignmentStore.resumeEvents();

     //await this.scheduler.resumeRefresh(false);

 }
 console.timeLog("LoadResources");

 console.timeEnd("LoadResources")
 return loaded;

}`

isglass commented 1 month ago

Hi,

Gantt uses a scheduling engine that represents the schedule as a graph. The (normally) most costly part of loading data is when it is entered into the graph, it is also similarly costly to unjoin data from the graph later.

Because of that, you should not first remove data and then plug the new data in. Performance should be much better if the dataset is simply replaced. The most efficient way to replace the data of a single store is therefor simply resourceStore.data = newDataSet (you should not need to suspend and resume events then).

If you receive data for multiple stores at the same time from your backend, you are instead better of using https://bryntum.com/products/gantt/docs/api/Gantt/model/ProjectModel#function-loadInlineData. That can populate multiple stores at once, updating the underlying graph and UI a minimal amount of times (as compared to once per store)