Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Logarithm axes in plots library #21

Merged
merged 23 commits into from
Jul 29, 2024
Merged
Show file tree
Hide file tree
Changes from 13 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
145 changes: 111 additions & 34 deletions src/axes.typ
Original file line number Diff line number Diff line change
Expand Up @@ -228,8 +228,10 @@
#let axis(min: -1, max: 1, label: none,
ticks: (step: auto, minor-step: none,
unit: none, decimals: 2, grid: false,
format: "float")) = (
min: min, max: max, ticks: ticks, label: label, inset: (0, 0), show-break: false,
format: "float"
),
mode: auto, base: auto) = (
min: min, max: max, ticks: ticks, label: label, inset: (0, 0), show-break: false, mode: mode, base: base
)

// Format a tick value
Expand Down Expand Up @@ -276,6 +278,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))
} else {
value = format-float(value, tic-options.at("decimals", default: 2))
Expand Down Expand Up @@ -370,6 +373,87 @@
return l
}

// Compute list of linear ticks for axis
//
// - axis (axis): Axis
#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.max, ferr), base: axis.base)
)
let dt = max - min; if (dt == 0) { dt = 1 }
let ticks = axis.ticks

let tick-limit = style.tick-limit
let minor-tick-limit = style.minor-tick-limit
let l = ()

if ticks != none {
let major-tick-values = ()
if "step" in ticks and ticks.step != none {
assert(ticks.step >= 0,
message: "Axis tick step must be positive and non 0.")
if axis.min > axis.max { ticks.step *= -1 }

let s = 1 / ticks.step

let num-ticks = int(max * s + 1.5) - int(min * s)
assert(num-ticks <= tick-limit,
message: "Number of major ticks exceeds limit " + str(tick-limit))

let n = range(
int(min * s),
int(max * s + 1.5)
)

for t in n {
let v = (t / s - min) / dt
if t / s == 0 and not add-zero { continue }

if v >= 0 - ferr and v <= 1 + ferr {
l.push((v, format-tick-value( calc.pow(axis.base, t / s), ticks), true))
major-tick-values.push(v)
}
}
}

if "minor-step" in ticks and ticks.minor-step != none {
assert(ticks.minor-step >= 0,
message: "Axis minor tick step must be positive")
if axis.min > axis.max { ticks.minor-step *= -1 }

let s = 1 / ticks.step

// let num-ticks = int(max * s + 1.5) - int(min * s)
// assert(num-ticks <= minor-tick-limit,
// message: "Number of minor ticks exceeds limit " + str(minor-tick-limit))

let n = range(int(min * s), int(max * s + 1.5))
for t in n {

for vv in range(1, int(axis.base / ticks.minor-step)) {

let v = ( (calc.log(vv * ticks.minor-step, base: axis.base) + t)/ s - min) / dt
if v in major-tick-values {
// Prefer major ticks over minor ticks
continue
}

if v != none and v >= 0 and v <= 1 + ferr {
l.push((v, none, false))
}

}

}
}
}


return l
}

// Get list of fixed axis ticks
//
// - axis (axis): Axis object
Expand Down Expand Up @@ -431,7 +515,11 @@
}
}

let ticks = compute-linear-ticks(axis, style, add-zero: add-zero)
let ticks = if axis.mode == "log" {
compute-logarithmic-ticks(axis, style, add-zero: add-zero)
} else {
compute-linear-ticks(axis, style, add-zero: add-zero)
}
ticks += fixed-ticks(axis)
return ticks
}
Expand Down Expand Up @@ -471,39 +559,23 @@
// - vec (vector): Input vector to transform
// -> vector
#let transform-vec(size, x-axis, y-axis, z-axis, vec) = {
let (ox, oy, ..) = (0, 0, 0)
ox += x-axis.inset.at(0)
oy += y-axis.inset.at(0)

let (sx, sy) = size
sx -= x-axis.inset.sum()
sy -= y-axis.inset.sum()

let x-range = x-axis.max - x-axis.min
let y-range = y-axis.max - y-axis.min
let z-range = 0 //z-axis.max - z-axis.min
let (x,y,) = for (dim, axis) in (x-axis, y-axis).enumerate() {

let fx = sx / x-range
let fy = sy / y-range
let fz = 0 //sz / z-range
let s = size.at(dim) - axis.inset.sum()
let o = axis.inset.at(0)

let x-low = calc.min(x-axis.min, x-axis.max)
let x-high = calc.max(x-axis.min, x-axis.max)
let y-low = calc.min(y-axis.min, y-axis.max)
let y-high = calc.max(y-axis.min, y-axis.max)
//let z-low = calc.min(z-axis.min, z-axis.max)
//let z-hihg = calc.max(z-axis.min, z-axis.max)
let transform-func(n) = if (axis.mode == "log") {
calc.log(calc.max(n, util.float-epsilon), base: axis.base)
} else {n}

let (x, y, ..) = vec
let range = transform-func(axis.max) - transform-func(axis.min)

if x < x-low or x > x-high or y < y-low or x > x-high {
return none
let f = s / range
((transform-func(vec.at(dim)) - transform-func(axis.min)) * f + o,)
}

return (
(x - x-axis.min) * fx + ox,
(y - y-axis.min) * fy + oy,
0) //(z - z-axis.min) * fz + oz)
return (x, y, 0)
}

// Draw inside viewport coordinates of two axes
Expand All @@ -523,11 +595,16 @@
ctx.transform = transform

drawables = drawables.map(d => {
d.segments = d.segments.map(((kind, ..pts)) => {
(kind, ..pts.map(pt => {
transform-vec(size, x, y, none, pt)
}))
})
if "segments" in d {
d.segments = d.segments.map(((kind, ..pts)) => {
(kind, ..pts.map(pt => {
transform-vec(size, x, y, none, pt)
}))
})
}
if "pos" in d {
d.pos = transform-vec(size, x, y, none, d.pos)
}
return d
})

Expand Down
3 changes: 3 additions & 0 deletions src/plot/util.typ
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,9 @@
axis.min -= 1; axis.max += 1
}

axis.mode = get-axis-option(name, "mode", "lin")
axis.base = get-axis-option(name, "base", 10)
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I imagine this could be useful for other things, like the ticks PR you have at the moment #18

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we merge #18, you could pick the default formatter based on the axis options 👍.
The formatter itself does not know about the axis.


// Configure axis orientation
axis.horizontal = get-axis-option(name, "horizontal",
get-default-axis-horizontal(name))
Expand Down
Binary file added tests/axes/log-mode/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.
35 changes: 35 additions & 0 deletions tests/axes/log-mode/test.typ
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@


#set page(width: auto, height: auto)

#import "/tests/helper.typ": *
#import "/src/lib.typ": *
#import cetz: draw, canvas
#import cetz-plot: axes,

#box(stroke: 2pt + red, canvas({
import draw: *

plot.plot(
size: (9, 6),
axis-style: "scientific",
y-mode: "log", y-base: 10,
// x-mode: "log",
// x-format: "sci",
y-format: "sci",
x-min: 1, x-max: 10, x-tick-step: 1,
y-min: 1, y-max: 10000, y-tick-step: 1, y-minor-tick-step: 1,
x-grid: "both",
y-grid: "both",
{
plot.add(domain: (0, 10), x => {calc.pow(10, x)})
plot.add(
domain: (1, 10),
x => {x},
samples: 100,
line: "raw",
// epigraph: true,
)
}
)
}))
12 changes: 4 additions & 8 deletions tests/plot/marks/test.typ
johannes-wolf marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,10 @@

plot.plot(
size: (5,5),
x-min: 0,
x-max: 1,
y-min: 0,
y-max: 1,
x2-min: 1,
x2-max: 0,
y2-min: 1,
y2-max: 0,
x-min: 0, x-max: 1,
y-min: 0, y-max: 1,
x2-min: 1, x2-max: 0,
y2-min: 1, y2-max: 0,
for axes in axis-options {
plot.add(
axes: axes,
Expand Down
Binary file modified tests/plot/ref/1.png
jamesrswift marked this conversation as resolved.
Show resolved Hide resolved
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading