@@ -6,6 +6,7 @@ const COLORS = {
66 Spine : { light : "#6eb5d9" , dark : "#3c7db0" } ,
77 Render : { light : "#b09cd8" , dark : "#8066a8" } ,
88 Write : { light : "#e8a756" , dark : "#c08030" } ,
9+ Boot : { light : "#e57373" , dark : "#c62828" } ,
910 Other : { light : "#bbb" , dark : "#666" } ,
1011} ;
1112
@@ -24,8 +25,32 @@ export function renderGantt(grouped) {
2425 const maxT = Math . max ( ...all . map ( t => t . end ) ) ;
2526 if ( maxT <= 0 ) return "" ;
2627
27- let rows = 0 ;
28- for ( const tasks of grouped . values ( ) ) rows += tasks . length ;
28+ // Any task with a lane ran on a worker — pull it into the Workers
29+ // section, tagged with its original section for bar colour. Leftover
30+ // Render tasks (dispatch, prepDest) fold into Spine.
31+ const seeds = [ ] , spine = [ ] , write = [ ] ;
32+ const laneTasks = [ ] ;
33+ for ( const [ section , tasks ] of grouped ) {
34+ for ( const t of tasks ) {
35+ if ( t . lane != null ) { t . _color = section ; laneTasks . push ( t ) ; }
36+ else if ( section === "Seeds" ) seeds . push ( t ) ;
37+ else if ( section === "Spine" || section === "Render" ) spine . push ( t ) ;
38+ else if ( section === "Write" ) write . push ( t ) ;
39+ }
40+ }
41+ const mainSections = [ [ "Seeds" , seeds ] , [ "Spine" , spine ] , [ "Write" , write ] ] ;
42+
43+ const lanes = new Map ( ) ;
44+ for ( const t of laneTasks ) {
45+ if ( ! lanes . has ( t . lane ) ) lanes . set ( t . lane , [ ] ) ;
46+ lanes . get ( t . lane ) . push ( t ) ;
47+ }
48+ for ( const tasks of lanes . values ( ) )
49+ tasks . sort ( ( a , b ) => a . workerStart - b . workerStart ) ;
50+ const sortedLanes = [ ...lanes . entries ( ) ] . sort ( ( a , b ) => a [ 0 ] - b [ 0 ] ) ;
51+
52+ let rows = sortedLanes . length ;
53+ for ( const [ , tasks ] of mainSections ) rows += tasks . length ;
2954 const h = AXIS_H + rows * ROW_H + 5 ;
3055 const xOf = t => SECTION_W + ( t / maxT ) * CHART_W ;
3156
@@ -57,35 +82,79 @@ export function renderGantt(grouped) {
5782 }
5883
5984 let y = AXIS_H ;
60- for ( const [ section , tasks ] of grouped ) {
85+
86+ // Seeds, Spine (with dispatch / prepDest folded in)
87+ for ( const [ section , tasks ] of mainSections . slice ( 0 , 2 ) ) {
6188 if ( tasks . length === 0 ) continue ;
89+ y = renderMainSection ( o , section , tasks , y , xOf ) ;
90+ }
91+
92+ // Workers — one row per lane, individual task bars
93+ if ( sortedLanes . length > 0 ) {
6294 o . push ( `<line x1="0" y1="${ y } " x2="${ SVG_W } " y2="${ y } " class="gg" stroke-width=".5"/>` ) ;
63- const cls = `gb-${ section . toLowerCase ( ) } ` ;
64- for ( let i = 0 ; i < tasks . length ; i ++ ) {
65- const t = tasks [ i ] ;
66- const bx = rd ( xOf ( t . start ) ) ;
67- const bw = rd ( Math . max ( xOf ( t . end ) - xOf ( t . start ) , 1 ) ) ;
68- const by = rd ( y + ( ROW_H - BAR_H ) / 2 ) ;
95+ for ( let li = 0 ; li < sortedLanes . length ; li ++ ) {
96+ const [ , tasks ] = sortedLanes [ li ] ;
6997 const ty = rd ( y + ROW_H / 2 + 3.5 ) ;
70- if ( i === 0 ) o . push ( `<text x="4" y="${ ty } " class="gs" font-size="12">${ esc ( section ) } </text>` ) ;
71- o . push ( `<rect x="${ bx } " y="${ by } " width="${ bw } " height="${ BAR_H } " class="${ cls } " rx="2"/>` ) ;
72- const lbl = taskLabel ( t ) ;
73- const textW = lbl . length * CHAR_W ;
74- if ( textW + BAR_PAD * 2 <= bw ) {
75- o . push ( `<text x="${ rd ( bx + BAR_PAD ) } " y="${ ty } " class="gl" font-size="11">${ esc ( lbl ) } </text>` ) ;
76- } else if ( bx + bw + 4 + textW <= SVG_W ) {
77- o . push ( `<text x="${ rd ( bx + bw + 4 ) } " y="${ ty } " class="gl" font-size="11">${ esc ( lbl ) } </text>` ) ;
78- } else {
79- o . push ( `<text x="${ rd ( bx - 4 ) } " y="${ ty } " text-anchor="end" class="gl" font-size="11">${ esc ( lbl ) } </text>` ) ;
98+ const by = rd ( y + ( ROW_H - BAR_H ) / 2 ) ;
99+ if ( li === 0 ) o . push ( `<text x="4" y="${ ty } " class="gs" font-size="12">Workers</text>` ) ;
100+ const bootBars = [ ] ;
101+ for ( const t of tasks ) {
102+ if ( t . _color === "Boot" ) { bootBars . push ( t ) ; continue ; }
103+ const bx = rd ( xOf ( t . workerStart ) ) ;
104+ const bw = rd ( Math . max ( xOf ( t . workerEnd ) - xOf ( t . workerStart ) , 1 ) ) ;
105+ o . push ( `<rect x="${ bx } " y="${ by } " width="${ bw } " height="${ BAR_H } " class="gb-${ ( t . _color || "render" ) . toLowerCase ( ) } " rx="2"/>` ) ;
106+ const lbl = workerLabel ( t ) ;
107+ if ( lbl . length * CHAR_W + BAR_PAD * 2 <= bw )
108+ o . push ( `<text x="${ rd ( bx + BAR_PAD ) } " y="${ ty } " class="gl" font-size="11">${ esc ( lbl ) } </text>` ) ;
109+ }
110+ const bootH = Math . round ( BAR_H * 0.75 ) ;
111+ for ( const t of bootBars ) {
112+ const bx = rd ( xOf ( t . workerStart ) ) ;
113+ const bw = rd ( Math . max ( xOf ( t . workerEnd ) - xOf ( t . workerStart ) , 1 ) ) ;
114+ o . push ( `<rect x="${ bx } " y="${ by } " width="${ bw } " height="${ bootH } " class="gb-boot" rx="2"/>` ) ;
115+ const lbl = workerLabel ( t ) ;
116+ if ( lbl . length * CHAR_W + BAR_PAD * 2 <= bw )
117+ o . push ( `<text x="${ rd ( bx + BAR_PAD ) } " y="${ ty } " class="gl" font-size="11">${ esc ( lbl ) } </text>` ) ;
80118 }
81119 y += ROW_H ;
82120 }
83121 }
84122
123+ // Write
124+ for ( const [ section , tasks ] of mainSections . slice ( 2 ) ) {
125+ if ( tasks . length === 0 ) continue ;
126+ y = renderMainSection ( o , section , tasks , y , xOf ) ;
127+ }
128+
85129 o . push ( `</g></svg>` ) ;
86130 return o . join ( "\n" ) ;
87131}
88132
133+ function renderMainSection ( o , section , tasks , y , xOf ) {
134+ o . push ( `<line x1="0" y1="${ y } " x2="${ SVG_W } " y2="${ y } " class="gg" stroke-width=".5"/>` ) ;
135+ const cls = `gb-${ section . toLowerCase ( ) } ` ;
136+ for ( let i = 0 ; i < tasks . length ; i ++ ) {
137+ const t = tasks [ i ] ;
138+ const bx = rd ( xOf ( t . start ) ) ;
139+ const bw = rd ( Math . max ( xOf ( t . end ) - xOf ( t . start ) , 1 ) ) ;
140+ const by = rd ( y + ( ROW_H - BAR_H ) / 2 ) ;
141+ const ty = rd ( y + ROW_H / 2 + 3.5 ) ;
142+ if ( i === 0 ) o . push ( `<text x="4" y="${ ty } " class="gs" font-size="12">${ esc ( section ) } </text>` ) ;
143+ o . push ( `<rect x="${ bx } " y="${ by } " width="${ bw } " height="${ BAR_H } " class="${ cls } " rx="2"/>` ) ;
144+ const lbl = taskLabel ( t ) ;
145+ const textW = lbl . length * CHAR_W ;
146+ if ( textW + BAR_PAD * 2 <= bw ) {
147+ o . push ( `<text x="${ rd ( bx + BAR_PAD ) } " y="${ ty } " class="gl" font-size="11">${ esc ( lbl ) } </text>` ) ;
148+ } else if ( bx + bw + 4 + textW <= SVG_W ) {
149+ o . push ( `<text x="${ rd ( bx + bw + 4 ) } " y="${ ty } " class="gl" font-size="11">${ esc ( lbl ) } </text>` ) ;
150+ } else {
151+ o . push ( `<text x="${ rd ( bx - 4 ) } " y="${ ty } " text-anchor="end" class="gl" font-size="11">${ esc ( lbl ) } </text>` ) ;
152+ }
153+ y += ROW_H ;
154+ }
155+ return y ;
156+ }
157+
89158function niceInterval ( max ) {
90159 for ( const c of [ 100 , 200 , 250 , 500 , 1000 , 2000 , 2500 , 5000 ] )
91160 if ( max / c <= 10 ) return c ;
@@ -109,5 +178,9 @@ function taskLabel(t) {
109178 return s ;
110179}
111180
181+ function workerLabel ( t ) {
182+ return t . id . replace ( / : .* / , "" ) . replace ( / w \d + $ / , "" ) ;
183+ }
184+
112185function rd ( n ) { return Math . round ( n * 10 ) / 10 ; }
113186function esc ( s ) { return s . replace ( / & / g, "&" ) . replace ( / < / g, "<" ) . replace ( / > / g, ">" ) ; }
0 commit comments