From 2a31df7d50cb176d220fec6586271015437cc6d4 Mon Sep 17 00:00:00 2001 From: LordBaryhobal Date: Sat, 10 Aug 2024 18:13:15 +0200 Subject: [PATCH] added parallel task with real / planned work updated timeliney to use latest CeTZ --- example.pdf | Bin 29547 -> 29711 bytes example.typ | 1 + src/gantt.typ | 3 +- src/timeliney.typ | 103 ++++++++++++++++++++++++++-------------------- 4 files changed, 61 insertions(+), 46 deletions(-) diff --git a/example.pdf b/example.pdf index 9acb7a65d8903b1137d726b2fdfe2371e4a42d2b..012b84451192ece21f99bd500607bab53cdffed7 100644 GIT binary patch delta 2975 zcmZ{mc{r4N8^_HUbf%JB$uh)PBJ<2XW641>VzOk(*al;yF=H7uI%O=WSJ5;cR78tH zqezpaD3X+-IFXR!C|gN%$a2s-T%Gs5PMvdK*Yo>5*Z2Ni_xE?-&mYg{!w^I;0#P(@ zwRbQBwh-V3iIRZBaYDuY8}JAe!rdQ^K$!31c(V6|a6%af;<5(nNb3uY)(YEmAq4Oo z#!4)LBtakqp`tp-z^w;q(haLf%je+M)?m;ogxCT09s+Za1Qdm;7Y7o9pb)IMj;w-&NIFyoM}m+LJh+aIP>YRW1|w+Z z6gxXxdMqOpu~N(*G^P_3a4U5Yk}!7w5C95{qT8$gC0bicn*5(5R${24i$SB7hekzU zmN$1go5?`XP>AKeRvv$_FGQbz=w$7V23M8FomYLcMJZaED#>bwGHs2N3)%aWF6gv( z8pn?wJ^Apox^ZiFJtbOUdz|9K>J$4W)+xZ`+!P3};NrH_nuJ+a@_I`lbaVguxG+i^&`Njh!E6`sZ}zbjEgUF3w9JzDw|9DZ^0u`7|a ze%Ia&U+(_G!zuFvt((4f6sr;o7b(j0oR3nO&X!)$ruD}k(!IQ}1E!OgTx8Ss9}f6D z>v;YBWNMH_e!AkTv_bccrID@j2XH-6F=PE|&3vH$ZdE=cf=?63v;ul+! z)S1kh@)yt-(<67vm2IMQ!nK`lk2j@#!0$||lXm@W;_E9xQnl>pFZYVHp{MTFP1Kfe z8)09EzbrFr^zwC2EGoP$i)~F@(nN9X+R73jZwCS^mWLk8jbYFu#r5hEX<^fcMpevkvICr(t1R%pERA|FTFmqK_KU3#%ChrI5u-q-8mnQ z4t#=&EL@X2;cd`L{rLWceDTaD{$ka^1pn|s{JGEbb9+hR!L6xV%m=?dAIRZ59oT%V zPqk>FjL4rWdHQD)^sIM%uFYl4pxw+(($8z)n$R@?H1$_zryWbAa9t&O1EsWM`*lQx zmV>Oto?DvJew8?x(igM=mP=?Y+UqWBo9xe|>tlc5;tRX%ZfWoJk$H=v38h`7f<8C3 zRJc9f!tXlmvq4&yCOoD7XiqDX-(%ta%n?cRuqlRCw44KjjY5$!qxo{F4%@jY@LFdQ zROxPec9W`Xd}Iy&py?qgy$jX_))lz=(Wsm9DXLFJkP}B=czVq8qy~+L>$O}iOnJ$` zJ_(AhPp&4x;}!!(nzNILH+nK=x;-}}dz}~F9k{dpPG60K^eNx18xp4P@XP5ltwS9D z5%;RrCEf!it9$pqnjK(Qm%A>A9vMA`alee7IBwgy_NM-)Sq-HW*5Qv@RJpkM9O~90 z%ynm$Ixn}Gg4+D3TXCK5V7h9VW9n>lK98x@!}I&-bv@NA-Zk7yMD?diD&;$YGe%Ot#lAb-XH zCVF#x^_LX4#knC^3uMHib<8fg3Ny4LXn4)E<}kfqclE*>@skpYq8*>xto7;O`fzM~ zi8to912UsbU0PGN=Uq#ddC|qr3R1aM)2SO9_iC~|c>=Bb0ohDy{0-oC-_-9-&F7mM z^5myADh^Sl_uU`XD9g+|u74;m_jaz6ld|aGFY5KxS~3KFvNE#gMOKUtta$WyD@~?TxM^A}x7YBk^Ju0)v0==XXUGlW zktysZVl=vy+#%L?y6Bf>EWA0=`?4C9hwZos{u=hS7g?`{n|vLMx!k3ee>S9$>On7J z{BcpXFRa0Nw0>Kl9(Y_#vED+qpd(uo{II$+m;$QCJ?XMv+7tLxY8;u>ZZY_>rnO+b zU_#-TTc?L+UB5z8;FoJ13;!91mGmymeuS0q5^mnI6paG$$6BaT?-L!zU?pKq{)Tn- z;=-%SiReE`tWRlfrC$IV}YH##rI> zpS8-BsSI@a4dY0EnnKZmS9+%?PE~?ty{AVY*INwS`13m-Bi9ILpNO)qMfCTF3i@{k zPoXKECQB`=D%aLMdDe`)#e=<$`Os|J-=F_D=Fdxuy|!M{&YYR@dYRETx@R}GH`AUD zrCm~Y@iW1VOnrTJ2={omX@)6@u;e`%X3L4#GPYD;^lN|QyZq$Sh_=nitcdyZHNS=q z-@*=^JFtzPnWGwiw8A7y^|9q7o4q61eb4L0N~`#qc|});hH$}CuF17bHFj}_?k3KW z^1Wb9k4$$~L=f zzY^IM6nMG5`IvFsK&F9DSbJ+*f6@DqCEri;y)QzUu#|_^rdfI8x1Vd(=LSEl^BQ=T zlvG1AcvPlzJg>5@pgVJM&_tP1)eP`*W~mN+yw|xuX|?+c9_BBePI0_Hzs`J9?pv@f zNChn1YXf?-7LS7|~WX(e$02t0TT1zwoP`dy;834!_G?@Q`(#|eb^`N+zb3+_idqSze z&r}CzA16DuE11bK`j^ae&K)RP!eh!{L6j$i1WrZKByO(Jn$~!d1pbx-!Cnhrc|qfd zI0+Nd!OfhKHt6qEzW`D z;m+2!NVFjV0#6bI1Y(>m&(X%gP%u|N2n=TA&N|Ix1hHsz7-3(7n3+N7Od2aNH2Z)q z0LH3jFC~HS03U1Gcnib>W!ESJ!8m2W2izk{+|LIzz^cG~vUsDl$P!2-SP%e}2yHwe z1``Gmfo`%w_ zX-jdFjtjnQYAd)O+qnLViMV#(l=`oI60m;ZyqPam->XtP!P?ID%0O4SVqE-=#{h@B zx3#+YGL&zv{$+oaZe3Q+j5%1H@?uJ(M@Hk_VJ_`$oZJkjXJ&qNVRL0yXi(0eHOb|h zDEk2mRK17-STAsU8sYlLuk_4uP&!ta+C-MH``JPy7(OItkYC^#%b*d0V-NR}bvavlH@rZa2IAA&%8q z9Imaspj;fhTJso)N^@ps$+>l^$3yvw2}p@YX-B{~O|bk4J*tfScVX$$a^jML(>za5 zXgz_5veCVOO?yI zod?5_aEqc^t9hv=E)lJah(U~$mvKZ%6lEd2%5d_zvBp}MLGpBU1hlzGAFNjRm=6_( z8}bxj8fB^A4b`S&e6m@lo4ibWK{oWq$oHqk+zp3&c;{ru6xf-syu|vi34@nfJ+DB8 zch|^y(_I~6NQrCg_u#_aXKJoxCNd#fh0H9^MWuuQJeTX>!hIqe1Cschp%t^>^mK6} zADw8}6}52ishZMavb0Fh3qy51>NY#YJ7c=8`r%I`Qys9pazVXsA*~Q5mG=T>hKYNS*8X zu8B=%UBtJdI$!kG^IyA}-}GF$zRn2XyDGoFP8BLnm0L0S*3|6PU}P+MMFH)5 zW`6PA+_!57rw!{4QJnh&zfrl4!uNME_*$WXugj`>@w>O7M2cK7WKWlw( z|ELNW(a-H#uz_^9PEe>)oIbL0pXuw=y^;!W@NsbD4}46I`mL9DaGRTmsdu)>o6;u{ zZH`8w-DaJGT5h|(d8C!A8Lps0XE_8?L*KR-8cpk-exY9LApsEBBu@T|VJd2RS z*ZC5j4r@#VyVHXn5tV`)HOgJE&&caSruVV0ahcgiJwvOb?lP|}v}RRj&}xoaDp}sO z@P|ls@*AR7Em3X@E$KfutIiZdGk3Ns+m#hhB7B~mwHpF$i(;zB?!B?UB<``g)+oVvN|+?EhW**Ji?vTn7^oFtgbN=er9WtW&=iw{1%ozr-C=V&^DS$B zc`m_RVU^FDi5oNE#0)PUCYPy43J#xDl@y6A(1xtMR2EAMdf)k=RIdnIbE$DvS4mqb z!5{Px3^InUra0+|%|IM;c;=Bf+G>tW^>^!t z?$3OZ#x^S>n9!2It-8TWp-tTB`Ljoas=a6roV+Ecipt(Zs1nM}jQs~NTHc_R*!KC* z4tI6irc&<9a1-z5(z@@zOVjhRRQB>tT`z{dZcl6;a~Ce^rHxAXSm6z(y=~y91plQe za0stB@0;s!l)J`I7s>-l3f|ygaGF>4!GBAwRgGGC^%fj+v@v$AO^e`qz@_hbVjbb5 zN?V>+hC@aCJ9QWcUgw#+kO&X$-`=FyxJgRGcVol7v%SH1?uKN4K~F|lTAx00Owy4~ zL~Pbr6fYm-`sjtY9-A@R9jhO{)jM@v;#ThH_7mDfTUB6Qy!_Ct?d-kH#PZCmF}6tz zIVPCHk4niZX+$zIbhxS;SH~&>??`4#)8gDwo(xlS9V`~3#2P;B$7cK@Yu2r=RN5L7 zU+ce?ST{FfX4m1>@xrk@L*x!TJ)~uRu1^){3%nprDHhEBjm6`Pbe{`8GCgZKtdqI< zLob}b+HzB8U-bmjYGfJRoED`?TES#`Dw0+~t@8)RUmu67WkQB_k`4v@R(jiUs1}1Q zYd=0oTfes_#2i2e0Nqpt02^)rWYP}+yl@3Kl>R>OSVhwVVi+u@bwCUqre{sYpfJWL zJQjtH2&s!ERX5!n42 zrG~)a1n{ySFs2OT!wUw7C6WH3p|AisI-M)Pu8)$@SiJG41<+U$TEJHy=iyLT0quRH zVT=hx0Wp4}5sd|8`LQPG@eg18|GOmM(SIpOAYeaj216vGNI(Mnh!<-A)48%(F)*}X wJOQT)mM56OEExKCY~5d4!+iAq_Hxgfn(D*;ehYZbR)7#mBoYJ;w>f3|U$4rX{r~^~ diff --git a/example.typ b/example.typ index b3c4c3a..e04aa04 100644 --- a/example.typ +++ b/example.typ @@ -59,6 +59,7 @@ [Report / Presentation], (date.start, date.finish, true), (datetime(year: 2024, month: 06, day: 24), 1), + (datetime(year: 2024, month: 06, day: 20), 1.2, false, true), (datetime(year: 2024, month: 08, day: 26), 1), ) diff --git a/src/gantt.typ b/src/gantt.typ index 1bb9a11..11f681a 100644 --- a/src/gantt.typ +++ b/src/gantt.typ @@ -141,10 +141,11 @@ // stroke: (paint: p, thickness: 4pt) ) )) - } else if real{ + } else if real { complemented_task.push(( from: weeks, to: end, + real: true, style: ( stroke: ( //dash: "dotted", diff --git a/src/timeliney.typ b/src/timeliney.typ index 0d899c7..e6a2774 100644 --- a/src/timeliney.typ +++ b/src/timeliney.typ @@ -1,4 +1,4 @@ -#import "@preview/cetz:0.1.2": canvas, draw +#import "@preview/cetz:0.2.2": canvas, draw, coordinate, util #let timeline( body, @@ -51,22 +51,22 @@ content( (rel: (0, 0)), task.name, - anchor: "top", + anchor: "north", name: "task" + str(i), - padding: spacing, + padding: if task.parallel {(x: spacing, y: 2 * spacing)} else {spacing}, ) anchor( "task" + str(i) + "-bottom", - (rel: (0, 0), to: "task" + str(i) + ".bottom", update: true), + (rel: (0, 0), to: "task" + str(i) + ".south", update: true), ) anchor( "task" + str(i) + "-top", - (rel: (0, 0), to: "task" + str(i) + ".top-left", update: false), + (rel: (0, 0), to: "task" + str(i) + ".north-west", update: false), ) anchor( "task" + str(i), - (rel: (0, 0), to: "task" + str(i) + ".right", update: false), + (rel: (0, 0), to: "task" + str(i) + ".east", update: false), ) flat_tasks.push(task) @@ -77,22 +77,22 @@ content( (rel: (0, 0)), t.name, - anchor: "top", + anchor: "north", name: "task" + str(i), padding: spacing, ) anchor( "task" + str(i) + "-bottom", - (rel: (0, 0), to: "task" + str(i) + ".bottom", update: true), + (rel: (0, 0), to: "task" + str(i) + ".south", update: true), ) anchor( "task" + str(i) + "-top", - (rel: (0, 0), to: "task" + str(i) + ".top-left", update: false), + (rel: (0, 0), to: "task" + str(i) + ".north-west", update: false), ) anchor( "task" + str(i), - (rel: (0, 0), to: "task" + str(i) + ".right", update: false), + (rel: (0, 0), to: "task" + str(i) + ".east", update: false), ) flat_tasks.push(t) @@ -107,22 +107,22 @@ content( (rel: (0, 0)), milestone.body, - anchor: "top", + anchor: "north", name: "milestone" + str(i), padding: spacing, ) anchor( "milestone" + str(i) + "-bottom", - (rel: (0, 0), to: "milestone" + str(i) + ".bottom", update: true), + (rel: (0, 0), to: "milestone" + str(i) + ".south", update: true), ) anchor( "milestone" + str(i) + "-right", - (rel: (0, 0), to: "milestone" + str(i) + ".right", update: false), + (rel: (0, 0), to: "milestone" + str(i) + ".east", update: false), ) anchor( "milestone" + str(i) + "-top", - (rel: (0, 0), to: "milestone" + str(i) + ".top", update: false), + (rel: (0, 0), to: "milestone" + str(i) + ".north", update: false), ) } } @@ -136,7 +136,7 @@ on-layer( 1, { - let (start_x, _, _) = coordinate.resolve(ctx, "titles.top-left") + let (_, (start_x,_ , _)) = coordinate.resolve(ctx, "titles.north-west") let end_x = 1 + start_x let i = 0 @@ -152,11 +152,11 @@ for task in group.tasks { if group_start == none { - let (_, start_y, _) = coordinate.resolve(ctx, "titles.task" + str(i) + "-top") + let (_, (_, start_y, _)) = coordinate.resolve(ctx, "titles.task" + str(i) + "-top") group_start = (start_x, start_y) } - let (_, end_y, _) = coordinate.resolve(ctx, "titles.task" + str(i) + "-bottom") + let (_, (_, end_y, _)) = coordinate.resolve(ctx, "titles.task" + str(i) + "-bottom") group_end = (end_x, end_y) i += 1 @@ -166,7 +166,7 @@ } if tasks-vline { - line("titles.top-right", "titles.bottom-right") + line("titles.north-east", "titles.south-east") } if box-milestones and milestone-layout == "aligned" { @@ -175,10 +175,10 @@ for (i, milestone) in milestones.enumerate() { if start == none { - let (_, start_y, _) = coordinate.resolve(ctx, "titles.milestone" + str(i) + "-top") + let (_, (_, start_y, _)) = coordinate.resolve(ctx, "titles.milestone" + str(i) + "-top") start = (start_x, start_y) } - let (_, end_y, _) = coordinate.resolve(ctx, "titles.milestone" + str(i) + "-bottom") + let (_, (_, end_y, _)) = coordinate.resolve(ctx, "titles.milestone" + str(i) + "-bottom") end = (end_x, end_y) } @@ -192,9 +192,9 @@ get-ctx( ctx => { - let (start_x, start_y, _) = coordinate.resolve(ctx, "titles.top-right") - let end_x = 1 + coordinate.resolve(ctx, "titles.top-left").at(0) - let end_y = coordinate.resolve(ctx, "titles.bottom").at(1) + let (_, (start_x, start_y, _)) = coordinate.resolve(ctx, "titles.north-east") + let end_x = 1 + coordinate.resolve(ctx, "titles.north-west").at(1).at(0) + let end_y = coordinate.resolve(ctx, "titles.south").at(1).at(1) group( { @@ -208,7 +208,7 @@ let start = ( a: (start_x, start_y + 16 * (i + 1) * pt), b: (end_x, start_y + 16 * (i + 1) * pt), - number: passed / n_cols, + number: passed / n_cols * 100%, ) if group_start == none { group_start = start } @@ -216,12 +216,12 @@ let end = ( a: (start_x, start_y + 16 * i * pt), b: (end_x, start_y + 16 * i * pt), - number: (passed + len) / n_cols, + number: (passed + len) / n_cols * 100%, ) group_end = end - content(start, end, anchor: "top-left", align(center + horizon, name)) + content(start, end, anchor: "north-west", align(center + horizon, name)) passed += len } @@ -240,21 +240,30 @@ // Draw the lines for (i, task) in flat_tasks.enumerate() { let start = "titles.task" + str(i) - let (_, task_start_y, _) = coordinate.resolve(ctx, "titles.task" + str(i)) - let (task_top_x, task_top_y, _) = coordinate.resolve(ctx, "titles.task" + str(i) + "-top") - let (_, task_bottom_y, _) = coordinate.resolve(ctx, "titles.task" + str(i) + "-bottom") + let (_, (_, task_start_y, _)) = coordinate.resolve(ctx, "titles.task" + str(i)) + let (_, (task_top_x, task_top_y, _)) = coordinate.resolve(ctx, "titles.task" + str(i) + "-top") + let (_, (_, task_bottom_y, _)) = coordinate.resolve(ctx, "titles.task" + str(i) + "-bottom") + let h = task_top_y - task_bottom_y for gantt_line in task.lines { + let y = task_start_y + if task.parallel { + if gantt_line.at("real", default: false) { + y -= h / 6 + } else { + y += h / 6 + } + } let start = ( - a: (start_x, task_start_y), - b: (end_x, task_start_y), - number: (gantt_line.from + offset) / n_cols, + a: (start_x, y), + b: (end_x, y), + number: (gantt_line.from + offset) / n_cols * 100%, ) let end = ( - a: (start_x, task_start_y), - b: (end_x, task_start_y), - number: (gantt_line.to + offset) / n_cols, + a: (start_x, y), + b: (end_x, y), + number: (gantt_line.to + offset) / n_cols * 100%, ) let style = line-style @@ -283,20 +292,20 @@ if show-grid == true or show-grid == "y" { for (i, task) in flat_tasks.enumerate() { - let (_, task_bottom_y, _) = coordinate.resolve(ctx, "titles.task" + str(i) + "-bottom") + let (_, (_, task_bottom_y, _)) = coordinate.resolve(ctx, "titles.task" + str(i) + "-bottom") line((start_x, task_bottom_y), (end_x, task_bottom_y), ..grid-style) } if milestone-layout == "aligned" { for (i, milestone) in milestones.enumerate() { - let (_, bottom_y, _) = coordinate.resolve(ctx, "titles.milestone" + str(i) + "-bottom") + let (_, (_, bottom_y, _)) = coordinate.resolve(ctx, "titles.milestone" + str(i) + "-bottom") line((start_x, bottom_y), (end_x, bottom_y), ..grid-style) } } } // Border all around the timeline - rect("titles.top-left", (end_x, end_y), stroke: black + 1pt) + rect("titles.north-west", (end_x, end_y), stroke: black + 1pt) }, ) } @@ -310,7 +319,7 @@ style: milestone-line-style, overhang: milestone-overhang, spacing: spacing, - anchor: "top", + anchor: "north", type: "milestone", ) = { if milestone-layout == "in-place" { @@ -321,14 +330,14 @@ let pos = (x: x, y: end_y - (spacing + overhang).pt() * pt) let box_x = x - let (w, h) = measure(body, ctx) + let (w, h) = util.measure(ctx, body) if x + w / 2 > end_x { box_x = end_x - w / 2 } if i != 0 { - let (prev_end_x, prev_start_y, _) = coordinate.resolve-anchor(ctx, "milestone" + str(i - 1) + ".top-right") - let prev_end_y = coordinate.resolve-anchor(ctx, "milestone" + str(i - 1) + ".bottom").at(1) + let (prev_end_x, prev_start_y, _) = coordinate.resolve-anchor(ctx, "milestone" + str(i - 1) + ".north-east") + let prev_end_y = coordinate.resolve-anchor(ctx, "milestone" + str(i - 1) + ".south").at(1) if box_x - w / 2 < prev_end_x and pos.y <= prev_start_y and pos.y + h >= prev_end_y { pos = (x: x, y: prev_end_y - spacing.pt() * pt * 2) @@ -346,7 +355,7 @@ ) } else if milestone-layout == "aligned" { let x = (end_x - start_x) * (at / n_cols) + start_x - let end_y = coordinate.resolve(ctx, "titles.milestone" + str(i) + "-right").at(1) + let end_y = coordinate.resolve(ctx, "titles.milestone" + str(i) + "-right").at(1).at(1) line((x, start_y), (x, end_y), (start_x, end_y), ..style) } } @@ -354,7 +363,7 @@ on-layer(-0.5, { if milestone-layout == "aligned" { set-ctx(ctx => { - ctx.prev.pt = coordinate.resolve(ctx, "titles.bottom") + ctx.prev.pt = coordinate.resolve(ctx, "titles.south").at(1) return ctx }) } @@ -417,9 +426,13 @@ #let task(name, style: none, ..lines) = { let processed_lines = () + let parallel = false for line in lines.pos() { if type(line) == dictionary { + if line.at("real", default: false) { + parallel = true + } processed_lines.push(line) } else { let (from, to) = line @@ -431,7 +444,7 @@ } } - ((type: "task", name: name, lines: processed_lines),) + ((type: "task", name: name, parallel: parallel, lines: processed_lines),) } #let taskgroup(title: none, tasks) = {