Skip to content

Commit 39ef220

Browse files
committed
WIP change ticks
1 parent 2b5f749 commit 39ef220

File tree

5 files changed

+211
-164
lines changed

5 files changed

+211
-164
lines changed

src/axis.typ

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,30 +20,33 @@
2020
}
2121

2222
/// Linear Axis Constructor
23-
#let linear(name, min, max) = (
23+
#let linear(name, min, max, ..options) = (
2424
label: [#name],
2525
name: name, min: min, max: max, base: 10, transform: _transform-lin,
2626
auto-domain: (none, none),
2727
ticks: (step: auto, minor-step: none, format: auto, list: none),
2828
grid: none,
2929
compute-ticks: ticks.compute-ticks.with("lin"),
30-
)
30+
) + options.named()
3131

3232
/// Log Axis Constructor
33-
#let logarithmic(name, min, max, base) = (
33+
#let logarithmic(name, min, max, base, ..options) = (
3434
label: [#name],
3535
name: name, min: min, max: max, base: base, transform: _transform-log,
3636
auto-domain: (none, none),
3737
ticks: (step: auto, minor-step: none, format: auto, list: none),
3838
grid: none,
3939
compute-ticks: ticks.compute-ticks.with("log"),
40-
)
40+
) + options.named()
4141

4242
// Prepare axis
4343
#let prepare(ptx, ax) = {
4444
if ax.min == none { ax.min = ax.auto-domain.at(0) }
4545
if ax.max == none { ax.max = ax.auto-domain.at(1) }
46-
if "compute-ticks" in ax { ax.computed-ticks = (ax.compute-ticks)(ax) }
46+
if ax.min == none or ax.max == none { ax.min = -1e-6; ax.max = +1e-6 }
47+
if "compute-ticks" in ax {
48+
ax.computed-ticks = (ax.compute-ticks)(ax)
49+
}
4750
return ax
4851
}
4952

src/plot.typ

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,9 @@
3939
/// - name (str): Axis name
4040
/// - min: (none, float): Minimum
4141
/// - max: (none, float): Maximum
42-
#let lin-axis(name, min: none, max: none) = {
42+
#let lin-axis(name, min: none, max: none, ..options) = {
4343
((priority: -100, fn: (ptx) => {
44-
ptx.axes.insert(name, axis.linear(name, min, max))
44+
ptx.axes.insert(name, axis.linear(name, min, max, ..options))
4545
return ptx
4646
}),)
4747
}
@@ -51,9 +51,9 @@
5151
/// - min: (none, float): Minimum
5252
/// - max: (none, float): Maximum
5353
/// - base: (int): Log base
54-
#let log-axis(name, min: none, max: none, base: 10) = {
54+
#let log-axis(name, min: none, max: none, base: 10, ..options) = {
5555
((priority: -100, fn: (ptx) => {
56-
ptx.axes.insert(name, axis.logarithmic(name, min, max, base))
56+
ptx.axes.insert(name, axis.logarithmic(name, min, max, base, ..options))
5757
return ptx
5858
}),)
5959
}
@@ -63,7 +63,9 @@
6363
scientific: (ptx) => {
6464
lin-axis("x")
6565
lin-axis("y")
66-
sub-plot.new("x", "y")
66+
lin-axis("u")
67+
lin-axis("v")
68+
sub-plot.new("x", "y", "u", "v")
6769
},
6870
school-book: (ptx) => {
6971
lin-axis("x")

src/plot/util.typ

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -204,8 +204,24 @@
204204
/// - axes (list): List of axes
205205
/// -> array List of stroke paths
206206
#let compute-stroke-paths(points, axes) = {
207+
if not axes.any(ax => ax.at("clip", default: true)) {
208+
return (points,)
209+
}
210+
207211
let (x, y, ..) = axes
208-
clipped-paths(points, (x.min, y.min), (x.max, y.max), fill: false)
212+
let (x-min, x-max) = if x.at("clip", default: true) {
213+
(x.min, x.max)
214+
} else {
215+
(-float.inf, float.inf)
216+
}
217+
218+
let (y-min, y-max) = if y.at("clip", default: true) {
219+
(y.min, y.max)
220+
} else {
221+
(-float.inf, float.inf)
222+
}
223+
224+
clipped-paths(points, (x-min, y-min), (x-max, y-max), fill: false)
209225
}
210226

211227
/// Compute clipped fill path
@@ -214,7 +230,23 @@
214230
/// - axes (list): List of axes
215231
/// -> array List of fill paths
216232
#let compute-fill-paths(points, axes) = {
233+
if not axes.any(ax => ax.at("clip", default: true)) {
234+
return (points,)
235+
}
236+
217237
let (x, y, ..) = axes
238+
let (x-min, x-max) = if x.at("clip", default: true) {
239+
(x.min, x.max)
240+
} else {
241+
(-float.inf, float.inf)
242+
}
243+
244+
let (y-min, y-max) = if y.at("clip", default: true) {
245+
(y.min, y.max)
246+
} else {
247+
(-float.inf, float.inf)
248+
}
249+
218250
clipped-paths(points, (x.min, y.min), (x.max, y.max), fill: true)
219251
}
220252

@@ -354,6 +386,7 @@
354386
for (name, ax) in axes {
355387
ax.min = get-opt(name, "min", ax.min)
356388
ax.max = get-opt(name, "max", ax.max)
389+
ax.clip = get-opt(name, "clip", ax.at("clip", default: true))
357390
ax.label = get-opt(name, "label", ax.label)
358391
ax.transform = get-opt(name, "transform", ax.transform)
359392
ax.ticks.list = get-opt(name, "list", ax.ticks.list)

src/spine.typ

Lines changed: 84 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
#import "/src/ticks.typ"
55
#import "/src/projection.typ"
6+
#import "/src/axis.typ"
67

78
/// Default axis style
89
///
@@ -85,21 +86,19 @@
8586
),
8687
y: (
8788
tick: (
88-
flip: true,
8989
label: (
9090
anchor: "east",
9191
),
9292
),
9393
),
94-
x2: (
94+
u: (
9595
tick: (
96-
flip: true,
9796
label: (
9897
anchor: "south",
9998
),
10099
),
101100
),
102-
y2: (
101+
v: (
103102
tick: (
104103
label: (
105104
anchor: "west",
@@ -108,7 +107,6 @@
108107
),
109108
distal: (
110109
tick: (
111-
flip: true,
112110
label: (
113111
anchor: "east",
114112
)
@@ -160,73 +158,97 @@
160158

161159
#let _get-axis-style(ptx, style, name) = {
162160
return _prepare-style(ptx, if name in style {
163-
cetz.util.merge-dictionary(style, style.at(name))
161+
cetz.util.merge-dictionary(style, style.at(name, default: (:)))
164162
} else {
165163
style
166164
})
167165
}
168166

167+
///
168+
#let cartesian-axis-projection(ax, start, stop) = {
169+
let dir = vector.norm(vector.sub(stop, start))
170+
let dist = vector.dist(start, stop)
171+
return (value) => {
172+
vector.add(start, vector.scale(dir, axis.transform(ax, value, 0, dist)))
173+
}
174+
}
175+
169176

170177
///
171178
#let cartesian-scientific(projections: none, name: none, style: (:)) = {
172179
return (
173180
name: name,
174181
draw: (ptx) => {
175-
let proj = projections.at(0)
176-
let axes = proj.axes
177-
let x = axes.at(0)
178-
let y = axes.at(1)
182+
let xy-proj = projections.at(0)
183+
let uv-proj = projections.at(1, default: xy-proj)
184+
let has-uv = projections.len() > 1
185+
let (x, y) = xy-proj.axes
186+
let (u, v) = uv-proj.axes
179187

180188
let style = _prepare-style(ptx, cetz.styles.resolve(ptx.cetz-ctx.style,
181189
root: "axes", merge: style, base: default-style))
182190
let x-style = _get-axis-style(ptx, style, "x")
183191
let y-style = _get-axis-style(ptx, style, "y")
184-
let x2-style = _get-axis-style(ptx, style, "x2")
185-
let y2-style = _get-axis-style(ptx, style, "y2")
192+
let u-style = _get-axis-style(ptx, style, "u")
193+
let v-style = _get-axis-style(ptx, style, "v")
186194

187-
let (south-west, south-east, north-west, north-east) = (proj.transform)(
195+
let (x-low, x-high, y-low, y-high) = (xy-proj.transform)(
188196
(x.min, y.min), (x.max, y.min),
189-
(x.min, y.max), (x.max, y.max),
197+
(x.min, y.min), (x.min, y.max),
198+
)
199+
let (u-low, u-high, v-low, v-high) = (uv-proj.transform)(
200+
(u.min, v.max), (u.max, v.max),
201+
(u.max, v.min), (u.max, v.max),
190202
)
191203

192-
let x-padding = x-style.padding
193-
let y-padding = y-style.padding
194-
195-
let x-low = vector.add(south-west, (-x-padding.at(0), -y-padding.at(0)))
196-
let x-high = vector.add(south-east, (+x-padding.at(1), -y-padding.at(0)))
197-
let y-low = vector.add(south-west, (-x-padding.at(0), -y-padding.at(0)))
198-
let y-high = vector.add(north-west, (-x-padding.at(0), y-padding.at(1)))
204+
let move-vec(v, direction, length) = {
205+
vector.add(v, direction.enumerate().map(((i, v)) => v * length.at(i)))
206+
}
199207

200-
let x2-low = vector.add(north-west, (-x-padding.at(0), y-padding.at(1)))
201-
let x2-high = vector.add(north-east, (+x-padding.at(1), y-padding.at(1)))
202-
let y2-low = vector.add(south-east, (x-padding.at(1), -y-padding.at(0)))
203-
let y2-high = vector.add(north-east, (x-padding.at(1), y-padding.at(1)))
208+
// Outset axes
209+
x-low = move-vec(x-low, (0, -1), x-style.padding)
210+
x-high = move-vec(x-high, (0, -1), x-style.padding)
211+
y-low = move-vec(y-low, (-1, 0), y-style.padding)
212+
y-high = move-vec(y-high, (-1, 0), y-style.padding)
213+
u-low = move-vec(u-low, (0, 1), u-style.padding)
214+
u-high = move-vec(u-high, (0, 1), u-style.padding)
215+
v-low = move-vec(v-low, (1, 0), v-style.padding)
216+
v-high = move-vec(v-high, (1, 0), v-style.padding)
217+
218+
// Frame corners (FIX for uv axes)
219+
let south-west = move-vec(x-low, (-1, 0), x-style.padding)
220+
let south-east = move-vec(x-high, (+1, 0), x-style.padding)
221+
let north-west = move-vec(u-low, (-1, 0), u-style.padding)
222+
let north-east = move-vec(u-high, (+1, 0), u-style.padding)
223+
224+
// Grid lengths
225+
let x-grid-length = u-low.at(1) - x-low.at(1)
226+
let y-grid-length = v-low.at(0) - y-low.at(0)
227+
let u-grid-length = x-low.at(1) - u-low.at(1)
228+
let v-grid-length = y-low.at(0) - v-low.at(0)
204229

205230
let axes = (
206-
(x, 0, south-west, south-east, x-low, x-high, x-style, false),
207-
(y, 1, south-west, north-west, y-low, y-high, y-style, false),
208-
(x, 0, north-west, north-east, x2-low, x2-high, x2-style, true),
209-
(y, 1, south-east, north-east, y2-low, y2-high, y2-style, true),
231+
(x, (0,+1), (0,x-grid-length), cartesian-axis-projection(x, x-low, x-high), x-style, false),
232+
(y, (+1,0), (y-grid-length,0), cartesian-axis-projection(y, y-low, y-high), y-style, false),
233+
(u, (0,-1), (0,u-grid-length), cartesian-axis-projection(u, u-low, u-high), u-style, not has-uv),
234+
(v, (-1,0), (v-grid-length,0), cartesian-axis-projection(v, v-low, v-high), v-style, not has-uv),
210235
)
211236

212-
for (ax, component, low, high, frame-low, frame-high, style, mirror) in axes {
213-
draw.on-layer(style.axis-layer, {
214-
draw.line(frame-low, frame-high, stroke: style.stroke, mark: style.mark)
215-
})
216-
if "computed-ticks" in ax {
217-
let low = low.enumerate().map(((i, v)) => {
218-
if i == component { v } else { frame-low.at(i) }
237+
draw.group(name: "spine", {
238+
for (ax, dir, grid-dir, proj, style, mirror) in axes {
239+
draw.on-layer(style.axis-layer, {
240+
draw.line(proj(ax.min), proj(ax.max), stroke: style.stroke, mark: style.mark)
219241
})
220-
let high = high.enumerate().map(((i, v)) => {
221-
if i == component { v } else { frame-low.at(i) }
222-
})
223-
224-
if not mirror {
225-
ticks.draw-cartesian-grid(low, high, component, ax, ax.computed-ticks, (0,0), (1,0), style)
242+
if "computed-ticks" in ax {
243+
if not mirror {
244+
ticks.draw-cartesian-grid(proj, grid-dir, ax, ax.computed-ticks, style)
245+
}
246+
ticks.draw-cartesian(proj, dir, ax.computed-ticks, style, is-mirror: mirror)
226247
}
227-
ticks.draw-cartesian(low, high, ax.computed-ticks, style, is-mirror: mirror)
228248
}
229-
}
249+
})
250+
251+
// TODO: Draw labels
230252
},
231253
)
232254
}
@@ -247,10 +269,17 @@
247269
let x-style = _get-axis-style(ptx, style, "x")
248270
let y-style = _get-axis-style(ptx, style, "y")
249271

272+
let zero-x = calc.max(x.min, calc.min(0, x.max))
273+
let zero-y = calc.max(y.min, calc.min(0, y.max))
274+
let zero-pt = (
275+
calc.max(x.min, calc.min(zero.at(0), x.max)),
276+
calc.max(y.min, calc.min(zero.at(1), y.max)),
277+
)
278+
250279
let (zero, min-x, max-x, min-y, max-y) = (proj.transform)(
251-
zero,
252-
vector.add(zero, (x.min, 0)), vector.add(zero, (x.max, 0)),
253-
vector.add(zero, (0, y.min)), vector.add(zero, (0, y.max)),
280+
zero-pt,
281+
vector.add(zero-pt, (x.min, zero-y)), vector.add(zero-pt, (x.max, zero-y)),
282+
vector.add(zero-pt, (zero-x, y.min)), vector.add(zero-pt, (zero-x, y.max)),
254283
)
255284

256285
let x-padding = x-style.padding
@@ -272,15 +301,17 @@
272301
let outset-min-y = vector.scale(outset-lo-y, -1)
273302
let outset-max-y = vector.scale(outset-hi-y, +1)
274303

304+
275305
draw.on-layer(x-style.axis-layer, {
276306
draw.line((rel: outset-min-x, to: min-x),
277307
(rel: outset-max-x, to: max-x),
278308
mark: x-style.mark,
279309
stroke: x-style.stroke)
280310
})
281311
if "computed-ticks" in x {
282-
ticks.draw-cartesian-grid(min-x, max-x, 0, x, x.computed-ticks, min-y, max-y, x-style)
283-
ticks.draw-cartesian(min-x, max-x, x.computed-ticks, x-style)
312+
//ticks.draw-cartesian-grid(grid-proj, grid-dir, ax, ax.computed-ticks, style)
313+
let tick-proj = cartesian-axis-projection(x, min-x, max-x)
314+
ticks.draw-cartesian(tick-proj, (0,+1), x.computed-ticks, x-style)
284315
}
285316

286317
draw.on-layer(y-style.axis-layer, {
@@ -290,8 +321,9 @@
290321
stroke: y-style.stroke)
291322
})
292323
if "computed-ticks" in y {
293-
ticks.draw-cartesian-grid(min-y, max-y, 1, y, y.computed-ticks, min-x, max-x, y-style)
294-
ticks.draw-cartesian(min-y, max-y, y.computed-ticks, y-style)
324+
//ticks.draw-cartesian-grid(min-y, max-y, 1, y, y.computed-ticks, min-x, max-x, y-style)
325+
let tick-proj = cartesian-axis-projection(y, min-y, max-y)
326+
ticks.draw-cartesian(tick-proj, (+1,0), y.computed-ticks, y-style)
295327
}
296328
}
297329
)
@@ -332,7 +364,7 @@
332364
if "computed-ticks" in distal {
333365
// TODO
334366
ticks.draw-distal-grid(proj, distal.computed-ticks, distal-style)
335-
ticks.draw-cartesian(r-start, r-end, distal.computed-ticks, distal-style)
367+
//ticks.draw-cartesian(r-start, r-end, distal.computed-ticks, distal-style)
336368
}
337369

338370
if start == stop {

0 commit comments

Comments
 (0)