Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions doc/style.typ
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
#import "/src/lib.typ"

#import "@preview/tidy:0.4.3"
#import "@preview/t4t:0.3.2": is
#import "@preview/t4t:0.3.2": is as is_

#let show-function(fn, style-args) = {
[
#heading(fn.name, level: style-args.first-heading-level + 1)
#label(style-args.label-prefix + fn.name + "()")
]
let description = if is.sequence(fn.description) {
let description = if is_.sequence(fn.description) {
fn.description.children
} else {
(fn.description,)
Expand Down
Binary file modified manual.pdf
Binary file not shown.
1 change: 1 addition & 0 deletions manual.typ
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ module imported into the namespace.
= Plot

#doc-style.parse-show-module("/src/plot.typ")
#doc-style.parse-show-module("/src/plot/groupplots.typ")

#for m in ("line", "bar", "boxwhisker", "contour", "errorbar", "annotation", "formats", "violin", "legend") {
doc-style.parse-show-module("/src/plot/" + m + ".typ")
Expand Down
4 changes: 4 additions & 0 deletions src/lib.typ
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,7 @@
#import "/src/plot.typ"
#import "/src/chart.typ"
#import "/src/smartart.typ"

// Expose groupplots
#import "/src/plot/groupplots.typ": groupplots

35 changes: 35 additions & 0 deletions src/plot.typ
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,20 @@
/// ])
/// - fill-below (bool): If true, the filled shape of plots is drawn _below_ axes.
/// - name (string): The plots element name to be used when referring to anchors
/// - title (none, string, content): Plot title.
/// - title-style (dictionary): Style for the title.
/// #show-parameter-block("font", ("string"), default: "\"bold\"", [
/// Font style of the title.
/// ])
/// #show-parameter-block("padding", ("number"), default: ".2cm", [
/// Padding between title and plot.
/// ])
/// #show-parameter-block("anchor", ("string"), default: "\"south\"", [
/// Anchor of the title content.
/// ])
/// #show-parameter-block("offset", ("pair"), default: "(0,0)", [
/// Offset vector for fine-tuning the title position.
/// ])
/// - legend (none, auto, coordinate): The position the legend will be drawn at. See plot-legends for information about legends. If set to `<auto>`, the legend's "default-placement" styling will be used. If set to a `<coordinate>`, it will be taken as relative to the plot's origin.
/// - legend-anchor (auto, string): Anchor of the legend group to use as its origin.
/// If set to `auto` and `lengend` is one of the predefined legend anchors, the
Expand All @@ -197,6 +211,8 @@
plot-style: default-plot-style,
mark-style: default-mark-style,
fill-below: true,
title: none,
title-style: (:),
legend: auto,
legend-anchor: auto,
legend-style: (:),
Expand Down Expand Up @@ -428,6 +444,25 @@
axis-dict.y,)
}

// Draw Title
if title != none {
// Styles
let title-style = styles.resolve(ctx.style,
base: (
font: "bold",
padding: .2cm,
anchor: "south",
offset: (0, 0)
), merge: title-style, root: "plot.title")

let (w, h) = size
let padding = util.resolve-number(ctx, title-style.padding)
let pos = (w / 2, h + padding)
pos = vector.add(pos, title-style.offset)

draw.content(pos, title, anchor: title-style.anchor)
}

// Stroke + Mark data
for d in data {
if "axes" not in d { continue }
Expand Down
109 changes: 109 additions & 0 deletions src/plot/groupplots.typ
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@

import "/src/cetz.typ"

/// Create a group of plots.
///
/// This function allows arranging multiple plots in a grid layout.
/// It takes a variable number of arguments, alternating between options (dictionary) and plot content (body).
///
/// == Options
///
/// - columns (int): Number of columns in the grid.
/// - size (array): Size of each plot `(width, height)`.
/// - rows (auto, int): Number of rows. If `auto`, calculated from number of plots and columns.
/// - horizontal-sep (float): Horizontal separation between plots.
/// - vertical-sep (float): Vertical separation between plots.
/// - title (none, string, content): Global title for the group of plots.
/// - sublabels (none, string): Numbering pattern for sublabels (e.g., "(a)").
/// - ..options (any): Default options passed to every plot.
/// - ..cont (any): Alternating plot options (dictionary) and plot content (body).
///
/// == Example
///
/// ```typst
/// groupplots(
/// 3, (4, 4),
/// horizontal-sep: 1.5, vertical-sep: 1.5,
/// (title: "Plot 1"), { plot.add(...) },
/// (title: "Plot 2"), { plot.add(...) }
/// )
/// ```
#let groupplots(
columns,
size,
rows: auto,
horizontal-sep: 1,
vertical-sep: 1,
title: none,
sublabels: none,
group-style: (:),
..options
) = {
import "/src/plot.typ" as plot_mod
import "/src/cetz.typ"

let default-params = options.named()
default-params.insert("size", size)

let cont = options.pos()

assert(cont.len() > 0, message: "No plots provided to groupplots.")
assert(calc.rem(cont.len(), 2) == 0, message: "groupplots expects alternating options and body arguments.")

let plot-items = ()
let plot-titles = () // Keep track of titles for sublabels matching if needed, though plot handles its own title now.
let has-xlabel = false

for i in range(0, cont.len(), step: 2) {
let plot-args = cont.at(i)
let plot-content = cont.at(i + 1)

if type(plot-args) != dictionary {
if plot-args == () {
plot-args = (:)
} else {
panic("Expected a dictionary or empty array for plot arguments at index " + str(i) + ", found " + type(plot-args))
}
}

let merged-args = default-params + plot-args

if "x-label" in merged-args and merged-args.at("x-label") != none {
has-xlabel = true
}

if plot-content != none {
plot-items.push(plot_mod.plot(..merged-args, plot-content))
}
}

let n = plot-items.len()
let grid-rows = if rows == auto { calc.ceil(n / columns) } else { rows }
let (plot-width, plot-height) = size

cetz.draw.group(name: "groupplots", {
if title != none {
let total-width = columns * plot-width + (columns - 1) * horizontal-sep
cetz.draw.content((total-width / 2, plot-height + 1), text(weight: "bold", title), anchor: "south")
}

for j in range(0, n) {
let col = calc.rem(j, columns)
let row = calc.floor(j / columns)

let ox = col * (plot-width + horizontal-sep)
let oy = -(row * (plot-height + vertical-sep))

cetz.draw.group({
cetz.draw.set-origin((ox, oy))
plot-items.at(j)

// Sublabels
if sublabels != none {
let sublabel-y = if has-xlabel { -1.0 } else { -0.5 }
cetz.draw.content((plot-width / 2, sublabel-y), numbering(sublabels, j + 1), anchor: "north")
}
})
}
})
}
63 changes: 63 additions & 0 deletions tests/plot/groupplots/test.typ
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
#set page(width: auto, height: auto)
#import "/src/cetz.typ": *
#import "/src/lib.typ": *
#import "/tests/helper.typ": *

// Test default groupplots
#test-case({
groupplots(
2, (3, 3),
horizontal-sep: 1, vertical-sep: 1,
title: "Global Title", // global title
sublabels: "(a)",

// Plot 1: with title
(title: "Plot 1"), { plot.add(((0,0), (1,1))) },

// Plot 2: with title and custom style
(title: "Plot 2", title-style: (offset: (0, 0.5))), { plot.add(((0,0), (1,1))) },

// Plot 3: empty options (should use defaults)
(), { plot.add(((0,0), (1,1))) },

// Plot 4: with options but no title
(x-label: "X Label"), { plot.add(((0,0), (1,1))) }
)
})

// Test regular plot with title
#test-case({
plot.plot(
size: (3,3),
title: "Regular Plot Title",
title-style: (padding: 0.5cm),
{ plot.add(((0,0), (1,1))) }
)
})

// Test user example from cetz_groupplots.typ
#test-case({
let factor = -10
groupplots(
3, // 3 columns
(4, 4), // plot size
horizontal-sep: 1.5, vertical-sep: 1.5,
title: "Global Title",
sublabels: "(a)",
x-tick-step: 1,
y-tick-step: 1,
(x-tick-step: 0.5, title: "Plot 1"),
{ plot.add(((0,0), (1,1), (2,0.5), (4,3))) },
(),
{ plot.add(((0,0), (1,1), (2,0.5), (4,-3))) },
(x-tick-step: 2),
{ plot.add(((0,0), (1,1), (2,0.5), (4,3)))
plot.add(((0,0), (1,1), (2,0.5), (4,-3))) },
(x-tick-step: 4, y-tick-step: 10000),
{ plot.add(((0,0), (1,10000), (2,5000), (4,30000))) },
(x-tick-step: 4, y-tick-step: 1*calc.abs(factor)),
{ plot.add(((0,0), (1,1*factor), (2,0.5*factor), (4,3*factor))) },
(x2-tick-step: 4, y2-tick-step: 100000),
{ plot.add(axes: ("x2", "y2"), ((0,0), (1,100000), (2,50000), (4,300000))) }
)
})
4 changes: 2 additions & 2 deletions typst.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "cetz-plot"
version = "0.1.3"
name = "cetz-plot-fork"
version = "0.1.4"
compiler = "0.13.1"
repository = "https://github.com/cetz-package/cetz-plot"
entrypoint = "src/lib.typ"
Expand Down