Skip to content

Commit

Permalink
plot: Add predefined tick formatters
Browse files Browse the repository at this point in the history
  • Loading branch information
johannes-wolf committed Jul 29, 2024
1 parent f150f9f commit 7ec1298
Show file tree
Hide file tree
Showing 9 changed files with 199 additions and 44 deletions.
Binary file modified gallery/line.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
47 changes: 30 additions & 17 deletions gallery/line.typ
Original file line number Diff line number Diff line change
@@ -1,26 +1,39 @@
#import "@preview/cetz:0.2.2": canvas
#import "@preview/cetz:0.2.2": canvas, draw
#import "@preview/cetz-plot:0.1.0": plot

#set page(width: auto, height: auto, margin: .5cm)

#let style = (stroke: black, fill: rgb(0, 0, 200, 75))

#canvas(length: 1cm, {
plot.plot(size: (8, 6),
x-tick-step: none,
x-ticks: ((-calc.pi, $-pi$), (0, $0$), (calc.pi, $pi$)),
y-tick-step: 1,
#let f1(x) = calc.sin(x)
#let fn = (
($ x - x^3"/"3! $, x => x - calc.pow(x, 3)/6),
($ x - x^3"/"3! - x^5"/"5! $, x => x - calc.pow(x, 3)/6 + calc.pow(x, 5)/120),
($ x - x^3"/"3! - x^5"/"5! - x^7"/"7! $, x => x - calc.pow(x, 3)/6 + calc.pow(x, 5)/120 - calc.pow(x, 7)/5040),
)

#set text(size: 10pt)

#canvas({
import draw: *

// Set-up a thin axis style
set-style(axes: (stroke: .5pt, tick: (stroke: .5pt)),
legend: (stroke: none, orientation: ttb, item: (spacing: .3), scale: 80%))

plot.plot(size: (12, 8),
x-tick-step: calc.pi/2,
x-format: plot.formats.multiple-of,
y-tick-step: 2, y-min: -2.5, y-max: 2.5,
legend: "inner-north",
{
plot.add(
style: style,
domain: (-calc.pi, calc.pi), calc.sin)
plot.add(
hypograph: true,
style: style,
domain: (-calc.pi, calc.pi), calc.cos)
plot.add(
hypograph: true,
style: style,
domain: (-calc.pi, calc.pi), x => calc.cos(x + calc.pi))
let domain = (-1.1 * calc.pi, +1.1 * calc.pi)

for ((title, f)) in fn {
plot.add-fill-between(f, f1, domain: domain,
style: (stroke: none), label: title)
}
plot.add(f1, domain: domain, label: $ sin x $,
style: (stroke: black))
})
})
2 changes: 1 addition & 1 deletion justfile
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,4 @@ manual:
typst c manual.typ manual.pdf

gallery:
for f in "{{gallery_dir}}"/*.typ; do typst c "$f" "${f/typ/png}"; done
for f in "{{gallery_dir}}"/*.typ; do typst c --root . "$f" "${f/typ/png}"; done
31 changes: 5 additions & 26 deletions src/axes.typ
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#import "/src/cetz.typ": util, draw, vector, matrix, styles, process, drawable, path-util, process
#import "/src/plot/formats.typ"

#let typst-content = content

Expand Down Expand Up @@ -250,27 +251,6 @@
$#round(value, digits)$
}

let format-sci(value, digits) = {
let exponent = if value != 0 {
calc.floor(calc.log(calc.abs(value), base: 10))
} else {
0
}

let ee = calc.pow(10, calc.abs(exponent + 1))
if exponent > 0 {
value = value / ee * 10
} else if exponent < 0 {
value = value * ee * 10
}

value = round(value, digits)
if exponent <= -1 or exponent >= 1 {
return $#value times 10^#exponent$
}
return $#value$
}

if type(value) != typst-content {
let format = tic-options.at("format", default: "float")
if format == none {
Expand All @@ -280,8 +260,7 @@
} else if type(format) == function {
value = (format)(value)
} else if format == "sci" {
// Todo: Handle logarithmic including arbitrary base
value = format-sci(value, tic-options.at("decimals", default: 2))
value = formats.sci(value, digits: tic-options.at("decimals", default: 2))
} else {
value = format-float(value, tic-options.at("decimals", default: 2))
}
Expand Down Expand Up @@ -381,7 +360,7 @@
#let compute-logarithmic-ticks(axis, style, add-zero: true) = {
let ferr = util.float-epsilon
let (min, max) = (
calc.log(calc.max(axis.min, ferr), base: axis.base),
calc.log(calc.max(axis.min, ferr), base: axis.base),
calc.log(calc.max(axis.max, ferr), base: axis.base)
)
let dt = max - min; if (dt == 0) { dt = 1 }
Expand Down Expand Up @@ -439,11 +418,11 @@
}

}

}
}
}

return l
}

Expand Down
1 change: 1 addition & 0 deletions src/plot.typ
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
#import "/src/plot/bar.typ": add-bar
#import "/src/plot/errorbar.typ": add-errorbar
#import "/src/plot/mark.typ"
#import "/src/plot/formats.typ"
#import plot-legend: add-legend

#let default-colors = (blue, red, green, yellow, black)
Expand Down
119 changes: 119 additions & 0 deletions src/plot/formats.typ
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
// Compare two floats
#let _compare(a, b, eps: 1e-6) = {
return calc.abs(a - b) <= eps
}

// Pre-computed table of fractions
#let _common-denoms = range(2, 11 + 1).map(d => {
(d, range(1, d).map(n => n/d))
})

#let _find-fraction(v, denom: auto, eps: 1e-6) = {
let i = calc.floor(v)
let f = v - i
if _compare(f, 0, eps: eps) {
return $#v$
}

let denom = if denom != auto {
for n in range(1, denom) {
if _compare(f, n/denom, eps: eps) {
denom
}
}
} else {
(() => {
for ((denom, tab)) in _common-denoms {
for vv in tab {
if _compare(f, vv, eps: eps) {
return denom
}
}
}
})()
}

if denom != none {
return if v < 0 { $-$ } else {} + $#calc.round(calc.abs(v) * denom)/#denom$
}
}

/// Fraction tick formatter
///
/// - value (number): Value to format
/// - denom (auto, int): Denominator for result fractions. If set to `auto`,
/// a hardcoded fraction table is used for finding fractions with a
/// denominator <= 11.
/// - eps (number): Epsilon used for comparison
/// -> Content if a matching fraction could be found or none
#let fraction(value, denom: auto, eps: 1e-6) = {
return _find-fraction(value, denom: denom, eps: eps)
}

/// Multiple of tick formatter
///
/// ```example
/// plot.plot(x-format: plot.formats.multiple-of,
/// x-tick-step: calc.pi/4, {
/// plot.add(calc.sin, domain: (-calc.pi, 1.5 * calc.pi))
/// })
/// ```
///
/// - value (number): Value to format
/// - factor (number): Factor value is expected to be a multiple of.
/// - symbol (content): Suffix symbol. For `value` = 0, the symbol is not
/// appended.
/// - fraction (none, true, int): If not none, try finding matching fractions
/// using the same mechanism as `fraction`. If set to an integer, that integer
/// is used as denominator. If set to `none` or `false`, or if no fraction
/// could be found, a real number with `digits` digits is used.
/// - digits (int): Number of digits to use for rounding
/// - eps (number): Epsilon used for comparison
/// -> Content if a matching fraction could be found or none
#let multiple-of(value, factor: calc.pi, symbol: $pi$, fraction: true, digits: 2, eps: 1e-6) = {
if _compare(value, 0, eps: eps) {
return $0$
}

let a = value / factor
if _compare(a, 1, eps: eps) {
return symbol
} else if _compare(a, -1, eps: eps) {
return $-$ + symbol
}

if fraction != none {
let frac = _find-fraction(a, denom: if fraction == true { auto } else { fraction })
if frac != none {
return frac + symbol
}
}

return $#calc.round(a, digits: digits)$ + symbol
}

/// Scientific notation tick formatter
///
/// - value (number): Value to format
/// - digits (int): Number of digits for rouding the factor
/// -> Content
#let sci(value, digits: 2) = {
let exponent = if value != 0 {
calc.floor(calc.log(calc.abs(value), base: 10))
} else {
0
}

let ee = calc.pow(10, calc.abs(exponent + 1))
if exponent > 0 {
value = value / ee * 10
} else if exponent < 0 {
value = value * ee * 10
}

value = calc.round(value, digits: digits)
if exponent <= -1 or exponent >= 1 {
return $#value times 10^#exponent$
}
return $#value$
}
4 changes: 4 additions & 0 deletions src/plot/legend.typ
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
)
),
radius: 0,
scale: 100%,
)

// Map position to legend group anchor
Expand Down Expand Up @@ -117,6 +118,9 @@
assert(style.orientation in (ttb, ltr),
message: "Unsupported legend orientation.")

// Scaling
draw.scale(style.scale)

// Position
let position = if position == auto {
style.default-position
Expand Down
Binary file added tests/plot/format/ref/1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
39 changes: 39 additions & 0 deletions tests/plot/format/test.typ
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
#set page(width: auto, height: auto)
#import "/tests/helper.typ": *
#import cetz: draw
#import cetz-plot: plot

#let data = ((-calc.pi, -1), (+calc.pi, +1))

#test-case({
plot.plot(
size: (8, 4),
x-min: -2 * calc.pi,
x-max: +2 * calc.pi,
x-tick-step: calc.pi/2,
x-format: plot.formats.multiple-of, {
plot.add(data)
})
})

#test-case({
plot.plot(
size: (8, 4),
x-min: -2,
x-max: +2,
x-tick-step: 1/3,
x-format: plot.formats.fraction, {
plot.add(data)
})
})

#test-case({
plot.plot(
size: (8, 4),
x-min: -2,
x-max: +2,
x-tick-step: 1/3,
x-format: plot.formats.fraction.with(denom: 33), {
plot.add(data)
})
})

0 comments on commit 7ec1298

Please sign in to comment.