diff --git a/buildoz.css b/buildoz.css index 0162ad6..6986a8d 100644 --- a/buildoz.css +++ b/buildoz.css @@ -371,3 +371,28 @@ bz-grafloweditor .inner-console section .cols-2 input{ max-height: 2em; align-self: center; } + + +bz-gantt{ + position: relative; + --row: 30px; +} +bz-gantt ul.tasks{ + list-style: none; + padding: 0; + margin: 0; + width: 100%; +} +bz-gantt ul.tasks li{ + box-sizing: border-box; + text-wrap: nowrap; + height: var(--row); + line-height: calc(var(--row) - 1px); + border-bottom: 1px dotted #555; +} + +bz-gantt.gantt bz-graflow{ + position: absolute; + left: 8em; + top: 0.5em; +} \ No newline at end of file diff --git a/bzGantt.js b/bzGantt.js new file mode 100644 index 0000000..0908b4b --- /dev/null +++ b/bzGantt.js @@ -0,0 +1,183 @@ +/** + * _ ___ 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(){ + const firstDate = new Date(this.tasks[0].start) + for(const task of this.tasks){ + const startDate = new Date(task.start) + if(startDate < firstDate) firstDate = startDate + } + + const flow = { "nodes": [], "links": [] } + let curY = 0 + for(const task of this.tasks){ + const startIdx = this.getDayIndex(firstDate, new Date(task.start)) + const endIdx = this.getDayIndex(firstDate, new Date(task.end)) + flow.nodes.push({ + "nodeType": "bar", + "id": task.id, + "coords": { + "x": startIdx * this.dayWidth, + "y": curY, + }, + "markup": { + "task": task.name, + "color": task.color || '#CCC', + "width": (startIdx ([item.dataset.id, { ...item.dataset, el:item }]))) if(node.subflow) { @@ -490,7 +490,7 @@ class BZgraflow extends Buildoz{ this.clear() let forceAutoplace = false for(const node of this.flow.nodes){ - if((!node.coords) || (!node.coords.x) ||(!node.coords.y)) forceAutoplace=true + if((!node.coords) || (node.coords.x == null) || (node.coords.y == null)) forceAutoplace=true this.addNode(node) } for(const link of this.flow.links){ diff --git a/graflow_examples/flows/gantt.json b/graflow_examples/flows/gantt.json new file mode 100644 index 0000000..609cdf4 --- /dev/null +++ b/graflow_examples/flows/gantt.json @@ -0,0 +1,62 @@ +{ + "nodesFile": "./nodesLib/nodesGantt.html", + "flow": { + "nodes":[ + { "nodeType": "bar", + "id": "t1", + "coords": { "x": 20, "y": 0}, + "markup": { "task": "Task 1", "color": "#0F0", "width": 160 } + }, + { "nodeType": "bar", + "id": "t2", + "coords": { "x": 510, "y": 180}, + "markup": { "task": "Task 2", "color": "#CCF", "width": 60 } + }, + { "nodeType": "bar", + "id": "t3", + "coords": { "x": 570, "y": 240}, + "markup": { "task": "Task 3", "color": "#FFC", "width": 100 } + }, + { "nodeType": "bar", + "id": "t4", + "coords": { "x": 20, "y": 60}, + "markup": { "task": "Task 4", "color": "#FCF", "width": 260 } + }, + { "nodeType": "bar", + "id": "t5", + "coords": { "x": 280, "y": 90}, + "markup": { "task": "Task 5", "color": "#CFF", "width": 30 } + }, + { "nodeType": "bar", + "id": "t6", + "coords": { "x": 310, "y": 120}, + "markup": { "task": "Task 6", "color": "#FFC", "width": 40 } + }, + { "nodeType": "bar", + "id": "t7", + "coords": { "x": 180, "y": 30}, + "markup": { "task": "Task 7", "color": "#FCF", "width": 80 } + }, + { "nodeType": "bar", + "id": "t8", + "coords": { "x": 350, "y": 150}, + "markup": { "task": "Task 8", "color": "#CFF", "width": 160 } + }, + { "nodeType": "bar", + "id": "t9", + "coords": { "x": 510, "y": 210}, + "markup": { "task": "Task 9", "color": "#FFC", "width": 60 } + } + ], + "links": [ + { "from": ["t1", "out1"], "to": ["t7", "inp1"] }, + { "from": ["t7", "out1"], "to": ["t5", "inp1"] }, + { "from": ["t8", "out1"], "to": ["t2", "inp1"] }, + { "from": ["t9", "out1"], "to": ["t3", "inp1"] }, + { "from": ["t4", "out1"], "to": ["t5", "inp1"] }, + { "from": ["t5", "out1"], "to": ["t6", "inp1"] }, + { "from": ["t6", "out1"], "to": ["t8", "inp1"] }, + { "from": ["t8", "out1"], "to": ["t9", "inp1"] } + ] + } +} diff --git a/graflow_examples/nodesLib/nodesGantt.html b/graflow_examples/nodesLib/nodesGantt.html new file mode 100644 index 0000000..4ef9b54 --- /dev/null +++ b/graflow_examples/nodesLib/nodesGantt.html @@ -0,0 +1,37 @@ + + + diff --git a/graflow_examples/test.html b/graflow_examples/test.html index 1812668..66db23b 100644 --- a/graflow_examples/test.html +++ b/graflow_examples/test.html @@ -126,6 +126,18 @@ X + + test7 + Gantt chart 1 + YES + + + Bezier + + + + +
diff --git a/graflow_examples/test7.html b/graflow_examples/test7.html new file mode 100644 index 0000000..dbd878b --- /dev/null +++ b/graflow_examples/test7.html @@ -0,0 +1,76 @@ + + + + graflow + + + + + + + + + + + +
+ + +
+ + diff --git a/graflow_examples/test8.html b/graflow_examples/test8.html new file mode 100644 index 0000000..ebf57f6 --- /dev/null +++ b/graflow_examples/test8.html @@ -0,0 +1,55 @@ + + + + graflow + + + + + + + + + + + + + + +