/** * _ ___ Another * / |/ (_)______ __ _____ * / / / __(_- [t.id, t])) const children = new Map(this.tasks.map(t => [t.id, []])) for(const task of this.tasks){ for(const dep of task.dependsOn ?? []){ if(children.has(dep)) children.get(dep).push(task) } } // Subtree weight: number of (unique) descendants of a task const descendants = new Map() const countDescendants = (task, seen) => { for(const child of children.get(task.id)){ if(seen.has(child.id)) continue seen.add(child.id) countDescendants(child, seen) } return(seen.size) } for(const task of this.tasks) descendants.set(task.id, countDescendants(task, new Set())) const byWeight = (a, b) => descendants.get(a.id) - descendants.get(b.id) // Depth-first walk, a task is placed once all its dependencies are placed const ordered = [] const placed = new Set() const ready = task => (task.dependsOn ?? []).every(dep => placed.has(dep) || !byId.has(dep)) const visit = task => { if(placed.has(task.id) || !ready(task)) return placed.add(task.id) ordered.push(task) for(const child of [...children.get(task.id)].sort(byWeight)) visit(child) } const roots = this.tasks.filter(t => !(t.dependsOn?.length)).sort(byWeight) for(const root of roots) visit(root) // Leftovers (circular dependencies) keep their original order for(const task of this.tasks) if(!placed.has(task.id)) ordered.push(task) this.tasks = ordered } getDayIndex(firstDate, date){ return Math.ceil((date-firstDate)/86400000) } buildFlow(){ let firstDate = new Date(this.tasks[0].start) let lastDate = new Date(this.tasks[0].end) for(const task of this.tasks){ const startDate = new Date(task.start) const endDate = new Date(task.end) if(startDate < firstDate) firstDate = startDate if(endDate > lastDate) lastDate = endDate } const flow = { "nodes": [], "links": [] } let curY = 0 const endIdx = this.getDayIndex(firstDate, lastDate) for(const task of this.tasks){ const startIdx = this.getDayIndex(firstDate, new Date(task.start)) const taskEndIdx = this.getDayIndex(firstDate, new Date(task.end)) const markup = {} for(const [key, value] of Object.entries(task)){ if(value == null || typeof value === 'object') continue markup[`task.${key}`] = value } if(!markup['task.color']) markup['task.color'] = '#CCC' markup.width = (startIdx