Skip to content

Commit

Permalink
spine: Move scientific to its own file
Browse files Browse the repository at this point in the history
  • Loading branch information
johannes-wolf committed Nov 29, 2024
1 parent 18baf6c commit 2ac6151
Show file tree
Hide file tree
Showing 4 changed files with 257 additions and 242 deletions.
252 changes: 10 additions & 242 deletions src/spine.typ
Original file line number Diff line number Diff line change
Expand Up @@ -4,115 +4,9 @@
#import "/src/ticks.typ"
#import "/src/projection.typ"
#import "/src/axis.typ"

/// Default axis style
///
/// #show-parameter-block("tick-limit", "int", default: 100, [Upper major tick limit.])
/// #show-parameter-block("minor-tick-limit", "int", default: 1000, [Upper minor tick limit.])
/// #show-parameter-block("auto-tick-factors", "array", [List of tick factors used for automatic tick step determination.])
/// #show-parameter-block("auto-tick-count", "int", [Number of ticks to generate by default.])
///
/// #show-parameter-block("stroke", "stroke", [Axis stroke style.])
/// #show-parameter-block("label.offset", "number", [Distance to move axis labels away from the axis.])
/// #show-parameter-block("label.anchor", "anchor", [Anchor of the axis label to use for it's placement.])
/// #show-parameter-block("label.angle", "angle", [Angle of the axis label.])
/// #show-parameter-block("axis-layer", "float", [Layer to draw axes on (see @@on-layer() )])
/// #show-parameter-block("grid-layer", "float", [Layer to draw the grid on (see @@on-layer() )])
/// #show-parameter-block("background-layer", "float", [Layer to draw the background on (see @@on-layer() )])
/// #show-parameter-block("padding", "number", [Extra distance between axes and plotting area. For schoolbook axes, this is the length of how much axes grow out of the plotting area.])
/// #show-parameter-block("tick.stroke", "stroke", [Major tick stroke style.])
/// #show-parameter-block("tick.minor-stroke", "stroke", [Minor tick stroke style.])
/// #show-parameter-block("tick.offset", ("number", "ratio"), [Major tick offset along the tick's direction, can be relative to the length.])
/// #show-parameter-block("tick.minor-offset", ("number", "ratio"), [Minor tick offset along the tick's direction, can be relative to the length.])
/// #show-parameter-block("tick.length", ("number"), [Major tick length.])
/// #show-parameter-block("tick.minor-length", ("number", "ratio"), [Minor tick length, can be relative to the major tick length.])
/// #show-parameter-block("tick.label.offset", ("number"), [Major tick label offset away from the tick.])
/// #show-parameter-block("tick.label.angle", ("angle"), [Major tick label angle.])
/// #show-parameter-block("tick.label.anchor", ("anchor"), [Anchor of major tick labels used for positioning.])
/// #show-parameter-block("tick.label.show", ("auto", "bool"), default: auto, [Set visibility of tick labels. A value of `auto` shows tick labels for all but mirrored axes.])
/// #show-parameter-block("grid.stroke", "stroke", [Major grid line stroke style.])
/// #show-parameter-block("grid.minor-stroke", "stroke", [Minor grid line stroke style.])
/// #show-parameter-block("break-point.width", "number", [Axis break width along the axis.])
/// #show-parameter-block("break-point.length", "number", [Axis break length.])
///
/// #show-parameter-block("shared-zero", ("bool", "content"), default: "$0$", [School-book style axes only: Content to display at the plots origin (0,0). If set to `false`, nothing is shown. Having this set, suppresses auto-generated ticks for $0$!])
#let default-style = (
mark: none,
stroke: (paint: black, cap: "square"),
fill: none,

padding: (0cm, 0cm),

show-zero: true,
zero-label: $0$,

axis-layer: 0,
tick-layer: 0,
grid-layer: 0,

tick: (
stroke: black + 1pt,
minor-stroke: black + .5pt,

offset: 0cm,
length: .2cm,
minor-offset: 0cm,
minor-length: .1cm,
flip: false,

label: (
"show": auto,
offset: .1cm,
angle: 0deg,
anchor: "center",
draw: (pos, body, angle, anchor) => {
draw.content(pos, body, angle: angle, anchor: anchor)
},
),
),

grid: (
stroke: black + .5pt,
minor-stroke: black + .25pt,
),

// Overrides
x: (
tick: (
label: (
anchor: "north",
),
),
),
y: (
tick: (
label: (
anchor: "east",
),
),
),
u: (
tick: (
label: (
anchor: "south",
),
),
),
v: (
tick: (
label: (
anchor: "west",
),
),
),
distal: (
tick: (
label: (
anchor: "east",
)
)
),
)
#import "/src/style.typ": prepare-style, get-axis-style, default-style
#import "/src/spine/scientific.typ": cartesian-scientific
#import "/src/spine/util.typ": cartesian-axis-projection

/// Default schoolbook style
#let default-style-schoolbook = cetz.util.merge-dictionary(default-style, (
Expand All @@ -127,132 +21,6 @@
),
))

#let _prepare-style(ptx, style) = {
let ctx = ptx.cetz-ctx
let resolve-number = cetz.util.resolve-number.with(ctx)
let relative-to(val, to) = {
return if type(val) == ratio {
val * to
} else {
val
}
}
let resolve-relative-number(val, to) = {
return resolve-number(relative-to(val, to))
}

if type(style.padding) != array {
style.padding = (style.padding,) * 2
}
style.padding = style.padding.map(resolve-number)

style.tick.offset = resolve-number(style.tick.offset)
style.tick.length = resolve-number(style.tick.length)
style.tick.minor-offset = resolve-relative-number(style.tick.minor-offset, style.tick.offset)
style.tick.minor-length = resolve-relative-number(style.tick.minor-length, style.tick.length)

style.tick.label.offset = resolve-number(style.tick.label.offset)

return style
}

#let _get-axis-style(ptx, style, name) = {
return _prepare-style(ptx, if name in style {
cetz.util.merge-dictionary(style, style.at(name, default: (:)))
} else {
style
})
}

///
#let cartesian-axis-projection(ax, start, stop) = {
let dir = vector.norm(vector.sub(stop, start))
let dist = vector.dist(start, stop)
return (value) => {
vector.add(start, vector.scale(dir, axis.transform(ax, value, 0, dist)))
}
}


///
#let cartesian-scientific(projections: none, name: none, style: (:)) = {
return (
name: name,
draw: (ptx) => {
let xy-proj = projections.at(0)
let uv-proj = projections.at(1, default: xy-proj)
let has-uv = projections.len() > 1
let (x, y) = xy-proj.axes
let (u, v) = uv-proj.axes

let style = _prepare-style(ptx, cetz.styles.resolve(ptx.cetz-ctx.style,
root: "axes", merge: style, base: default-style))
let x-style = _get-axis-style(ptx, style, "x")
let y-style = _get-axis-style(ptx, style, "y")
let u-style = _get-axis-style(ptx, style, "u")
let v-style = _get-axis-style(ptx, style, "v")

let (x-low, x-high, y-low, y-high) = (xy-proj.transform)(
(x.min, y.min), (x.max, y.min),
(x.min, y.min), (x.min, y.max),
)
let (u-low, u-high, v-low, v-high) = (uv-proj.transform)(
(u.min, v.max), (u.max, v.max),
(u.max, v.min), (u.max, v.max),
)

let move-vec(v, direction, length) = {
vector.add(v, direction.enumerate().map(((i, v)) => v * length.at(i)))
}

// Outset axes
x-low = move-vec(x-low, (0, -1), x-style.padding)
x-high = move-vec(x-high, (0, -1), x-style.padding)
y-low = move-vec(y-low, (-1, 0), y-style.padding)
y-high = move-vec(y-high, (-1, 0), y-style.padding)
u-low = move-vec(u-low, (0, 1), u-style.padding)
u-high = move-vec(u-high, (0, 1), u-style.padding)
v-low = move-vec(v-low, (1, 0), v-style.padding)
v-high = move-vec(v-high, (1, 0), v-style.padding)

// Frame corners (FIX for uv axes)
let south-west = move-vec(x-low, (-1, 0), x-style.padding)
let south-east = move-vec(x-high, (+1, 0), x-style.padding)
let north-west = move-vec(u-low, (-1, 0), u-style.padding)
let north-east = move-vec(u-high, (+1, 0), u-style.padding)

// Grid lengths
let x-grid-length = u-low.at(1) - x-low.at(1)
let y-grid-length = v-low.at(0) - y-low.at(0)
let u-grid-length = x-low.at(1) - u-low.at(1)
let v-grid-length = y-low.at(0) - v-low.at(0)

let axes = (
(x, (0,+1), (0,x-grid-length), cartesian-axis-projection(x, x-low, x-high), x-style, false),
(y, (+1,0), (y-grid-length,0), cartesian-axis-projection(y, y-low, y-high), y-style, false),
(u, (0,-1), (0,u-grid-length), cartesian-axis-projection(u, u-low, u-high), u-style, not has-uv),
(v, (-1,0), (v-grid-length,0), cartesian-axis-projection(v, v-low, v-high), v-style, not has-uv),
)

draw.group(name: "spine", {
for (ax, dir, grid-dir, proj, style, mirror) in axes {
draw.on-layer(style.axis-layer, {
draw.line(proj(ax.min), proj(ax.max), stroke: style.stroke, mark: style.mark)
})
if "computed-ticks" in ax {
if not mirror {
ticks.draw-cartesian-grid(proj, grid-dir, ax, ax.computed-ticks, style)
}
ticks.draw-cartesian(proj, dir, ax.computed-ticks, style, is-mirror: mirror)
}
}
})

// TODO: Draw labels
},
)
}

///
#let schoolbook(projections: none, name: none, zero: (0, 0), ..style) = {
return (
Expand All @@ -264,10 +32,10 @@
let y = axes.at(1)
let z = axes.at(2, default: none)

let style = _prepare-style(ptx, cetz.styles.resolve(ptx.cetz-ctx.style,
let style = prepare-style(ptx, cetz.styles.resolve(ptx.cetz-ctx.style,
root: "axes", merge: style.named(), base: default-style-schoolbook))
let x-style = _get-axis-style(ptx, style, "x")
let y-style = _get-axis-style(ptx, style, "y")
let x-style = get-axis-style(ptx, style, "x")
let y-style = get-axis-style(ptx, style, "y")

let zero-x = calc.max(x.min, calc.min(0, x.max))
let zero-y = calc.max(y.min, calc.min(0, y.max))
Expand Down Expand Up @@ -352,10 +120,10 @@

let radius = vector.dist(origin, start)

let style = _prepare-style(ptx, cetz.styles.resolve(ptx.cetz-ctx.style,
root: "axes", merge: style.named(), base: default-style))
let angular-style = _get-axis-style(ptx, style, "angular")
let distal-style = _get-axis-style(ptx, style, "distal")
let style = prepare-style(ptx, cetz.styles.resolve(ptx.cetz-ctx.style,
root: "axes", merge: style.named(), base: style.default-style))
let angular-style = get-axis-style(ptx, style, "angular")
let distal-style = get-axis-style(ptx, style, "distal")

let r-padding = angular-style.padding.first()
let r-start = origin
Expand Down
86 changes: 86 additions & 0 deletions src/spine/scientific.typ
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
#import "/src/cetz.typ"
#import "/src/axis.typ"
#import "/src/ticks.typ"
#import "/src/style.typ": prepare-style, get-axis-style, default-style
#import "/src/spine/util.typ": cartesian-axis-projection

#import cetz: vector, draw

///
#let cartesian-scientific(projections: none, name: none, style: (:)) = {
return (
name: name,
draw: (ptx) => {
let xy-proj = projections.at(0)
let uv-proj = projections.at(1, default: xy-proj)
let has-uv = projections.len() > 1
let (x, y) = xy-proj.axes
let (u, v) = uv-proj.axes

let style = prepare-style(ptx, cetz.styles.resolve(ptx.cetz-ctx.style,
root: "axes", merge: style, base: default-style))
let x-style = get-axis-style(ptx, style, "x")
let y-style = get-axis-style(ptx, style, "y")
let u-style = get-axis-style(ptx, style, "u")
let v-style = get-axis-style(ptx, style, "v")

let (x-low, x-high, y-low, y-high) = (xy-proj.transform)(
(x.min, y.min), (x.max, y.min),
(x.min, y.min), (x.min, y.max),
)
let (u-low, u-high, v-low, v-high) = (uv-proj.transform)(
(u.min, v.max), (u.max, v.max),
(u.max, v.min), (u.max, v.max),
)

let move-vec(v, direction, length) = {
vector.add(v, direction.enumerate().map(((i, v)) => v * length.at(i)))
}

// Outset axes
x-low = move-vec(x-low, (0, -1), x-style.padding)
x-high = move-vec(x-high, (0, -1), x-style.padding)
y-low = move-vec(y-low, (-1, 0), y-style.padding)
y-high = move-vec(y-high, (-1, 0), y-style.padding)
u-low = move-vec(u-low, (0, 1), u-style.padding)
u-high = move-vec(u-high, (0, 1), u-style.padding)
v-low = move-vec(v-low, (1, 0), v-style.padding)
v-high = move-vec(v-high, (1, 0), v-style.padding)

// Frame corners (FIX for uv axes)
let south-west = move-vec(x-low, (-1, 0), x-style.padding)
let south-east = move-vec(x-high, (+1, 0), x-style.padding)
let north-west = move-vec(u-low, (-1, 0), u-style.padding)
let north-east = move-vec(u-high, (+1, 0), u-style.padding)

// Grid lengths
let x-grid-length = u-low.at(1) - x-low.at(1)
let y-grid-length = v-low.at(0) - y-low.at(0)
let u-grid-length = x-low.at(1) - u-low.at(1)
let v-grid-length = y-low.at(0) - v-low.at(0)

let axes = (
(x, (0,+1), (0,x-grid-length), cartesian-axis-projection(x, x-low, x-high), x-style, false),
(y, (+1,0), (y-grid-length,0), cartesian-axis-projection(y, y-low, y-high), y-style, false),
(u, (0,-1), (0,u-grid-length), cartesian-axis-projection(u, u-low, u-high), u-style, not has-uv),
(v, (-1,0), (v-grid-length,0), cartesian-axis-projection(v, v-low, v-high), v-style, not has-uv),
)

draw.group(name: "spine", {
for (ax, dir, grid-dir, proj, style, mirror) in axes {
draw.on-layer(style.axis-layer, {
draw.line(proj(ax.min), proj(ax.max), stroke: style.stroke, mark: style.mark)
})
if "computed-ticks" in ax {
if not mirror {
ticks.draw-cartesian-grid(proj, grid-dir, ax, ax.computed-ticks, style)
}
ticks.draw-cartesian(proj, dir, ax.computed-ticks, style, is-mirror: mirror)
}
}
})

// TODO: Draw labels
},
)
}
12 changes: 12 additions & 0 deletions src/spine/util.typ
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#import "/src/cetz.typ": vector
#import "/src/axis.typ"

/// Returns a function that interpolates from an axis value
/// between start and stop
#let cartesian-axis-projection(ax, start, stop) = {
let dir = vector.norm(vector.sub(stop, start))
let dist = vector.dist(start, stop)
return (value) => {
vector.add(start, vector.scale(dir, axis.transform(ax, value, 0, dist)))
}
}
Loading

0 comments on commit 2ac6151

Please sign in to comment.