diff --git a/example.pdf b/example.pdf index 9acb7a6..012b844 100644 Binary files a/example.pdf and b/example.pdf differ 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) = {