Skip to content

Commit

Permalink
Merge pull request #21 from JamesxX/log-mode-attempt-3
Browse files Browse the repository at this point in the history
Logarithm axes in plots library
  • Loading branch information
johannes-wolf authored Jul 29, 2024
2 parents 8a5f065 + e1c2f5f commit f150f9f
Show file tree
Hide file tree
Showing 8 changed files with 282 additions and 43 deletions.
133 changes: 101 additions & 32 deletions src/axes.typ
Original file line number Diff line number Diff line change
Expand Up @@ -225,11 +225,15 @@
// - unit (content): Tick label suffix
// - decimals (int): Tick float decimal length
// - label (content): Axis label
// - mode (string): Axis scaling function. Takes `lin` or `log`
// - base (number): Base for tick labels when logarithmically scaled.
#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 +280,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 +375,78 @@
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 n = range(int(min * s)-1, int(max * s + 1.5)+1)

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 {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 +508,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,35 +552,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 fx = sx / x-range
let fy = sy / y-range
let fz = 0 //sz / z-range

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 (x, y, ..) = vec

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

let (x,y,) = for (dim, axis) in (x-axis, y-axis).enumerate() {

let s = size.at(dim) - axis.inset.sum()
let o = axis.inset.at(0)

let transform-func(n) = if (axis.mode == "log") {
calc.log(calc.max(n, util.float-epsilon), base: axis.base)
} else {n}

let range = transform-func(axis.max) - transform-func(axis.min)

let f = s / range
((transform-func(vec.at(dim)) - transform-func(axis.min)) * f + o,)
}

return (x, y, 0)
}

// Draw inside viewport coordinates of two axes
Expand Down
5 changes: 5 additions & 0 deletions src/plot.typ
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,11 @@
/// #show-parameter-block("unit", ("none", "content"), default: "none", [
/// Suffix to append to all tick labels.
/// ])
/// #show-parameter-block("mode", ("none", "string"), default: "none", [
/// The scaling function of the axis. Takes `lin` (default) for linear scaling,
/// and `log` for logarithmic scaling.])
/// #show-parameter-block("base", ("none", "number"), default: "none", [
/// The base to be used when labeling axis ticks in logarithmic scaling])
/// #show-parameter-block("grid", ("bool", "string"), default: "false", [
/// If `true` or `"major"`, show grid lines for all major ticks. If set
/// to `"minor"`, show grid lines for minor ticks only.
Expand Down
6 changes: 3 additions & 3 deletions src/plot/annotation.typ
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#import "/src/cetz.typ": draw, process, util, matrix

#import "/src/cetz.typ"
#import cetz: draw, process, util, matrix
#import "util.typ"
#import "sample.typ"

Expand Down Expand Up @@ -41,7 +41,7 @@
axes: axes,
resize: resize,
background: background,
padding: util.as-padding-dict(padding),
padding: cetz.util.as-padding-dict(padding),
),)
}

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)

// 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.
166 changes: 166 additions & 0 deletions tests/axes/log-mode/test.typ
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@


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

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

// plot.add test with logarithmic scaling
#box(stroke: 2pt + red, canvas({
import draw: *

plot.plot(
size: (9, 6),
axis-style: "scientific",
y-mode: "log", y-base: 10,
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)},
samples: 100,
line: "raw",
label: $y=10^x$
)
plot.add(
domain: (1, 10),
x => {x},
samples: 100,
line: "raw",
hypograph: true,
label: $y=x$
)
}
)
}))

// Bode plot test
#box(stroke: 2pt + red,{
canvas({
import draw: *
cetz.draw.set-style(
grid: (stroke: (paint: luma(83.33%), thickness: 1pt, dash: "dotted")),
minor-grid: (stroke: (paint: luma(83.33%), thickness: 0.5pt, dash: "dotted")),
)
plot.plot(
size: (16, 6),
axis-style: "scientific",
x-format: none, x-label: none,
x-mode: "log",
x-min: 0.01, x-max: 100, x-tick-step: 1, x-minor-tick-step: 1,
y-label: [Magnitude ($upright(d B)$)],
y-min: -40, y-max: 10, y-tick-step: 10,
x-grid: "both",
y-grid: "both",
{
plot.add(domain: (0.01, 100), x => {0})
}
)
})
canvas({
import draw: *
cetz.draw.set-style(
grid: (stroke: (paint: luma(83.33%), thickness: 1pt, dash: "dotted")),
minor-grid: (stroke: (paint: luma(83.33%), thickness: 0.5pt, dash: "dotted")),
)
plot.plot(
size: (16, 6),
axis-style: "scientific",
x-mode: "log",
x-min: 0.01, x-max: 100, x-tick-step: 1, x-minor-tick-step: 1,
x-label: [Frequency ($upright(r a d)\/s$)],
y-label: [Phase ($upright(d e g)$)],
y-min: -90, y-max: 0, y-tick-step: 45,
x-grid: "both",
y-grid: "both",
{
plot.add(domain: (0.01, 100), x => {-40})
}
)
})
})

// Column chart test
#box(stroke: 2pt + red, canvas({
import draw: *

plot.plot(
size: (9, 6),
axis-style: "scientific",
y-mode: "log", y-base: 10,
y-format: "sci",
x-min: -0.5, x-max: 4.5, x-tick-step: 1,
y-min: 0.1, y-max: 10000, y-tick-step: 1, y-minor-tick-step: 1,
x-grid: "both",
y-grid: "both",
{
plot.add-bar(
(1, 10, 100, 1000, 10000).enumerate().map(((x,y))=>{(x,y)}),
bar-width: 0.8,
)
}
)
}))

// Scatter plot test
#box(stroke: 2pt + red, canvas({
import draw: *

plot.plot(
size: (9, 6),
axis-style: "scientific",
y-mode: "log", y-base: 100,
y-format: "sci",
x-min: -0.5, x-max: 4.5, x-tick-step: 1,
y-min: 0.1, y-max: 10000, y-tick-step: 1, y-minor-tick-step: 10,
x-grid: "both",
y-grid: "both",
{
plot.add(
((0, 1),(1,2),(1,3),(2, 100),(2,150),(3, 1000),),
style: (stroke: none),
mark: "o"
)
plot.annotate({
rect((0, 1), (calc.pi, 10), fill: rgb(50,50,200,50))
content((2, 3), [Annotation])
})
plot.annotate({
rect((0, 1000), (calc.pi, 10000), fill: rgb(50,50,200,50))
content((2, 3000), [Annotation])
})
}
)
}))

// Box plot test
#box(stroke: 2pt + red, canvas({
import draw: *

plot.plot(
size: (9, 6),
axis-style: "scientific",
y-mode: "log", y-base: 10,
y-format: "sci",
x-min: -0.5, x-max: 2.5, x-tick-step: 1,
y-min: 0.1, y-max: 15000, y-tick-step: 1, y-minor-tick-step: 1,
x-grid: "both",
y-grid: "both",
{
plot.add-boxwhisker(
(
(x: 0, min: 1, q1: 10, q2: 100, q3: 1000, max: 10000),
(x: 1, min: 100, q1: 200, q2: 300, q3: 400, max: 500),
(x: 2, min: 10, q1: 100, q2: 500, q3: 1000, max: 5000),
),
)
}
)
}))

12 changes: 4 additions & 8 deletions tests/plot/marks/test.typ
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
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit f150f9f

Please sign in to comment.