Skip to content

Commit 5c99ee6

Browse files
committed
WIP
1 parent d9bf4f2 commit 5c99ee6

File tree

3 files changed

+277
-0
lines changed

3 files changed

+277
-0
lines changed

src/axis.typ

+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
2+
/// Transform linear axis value to linear space (low, high)
3+
#let _transform-lin(ax, value, low, high) = {
4+
let range = high - low
5+
6+
return (value - ax.low) * (range / (ax.high - ax.low))
7+
}
8+
9+
/// Transform log axis value to linear space (low, high)
10+
#let _transform-log(ax, value, low, high) = {
11+
let range = high - low
12+
13+
let f(x) = {
14+
calc.log(calc.max(x, util.float-epsilon), base: ax.base)
15+
}
16+
17+
return (value - f(ax.low)) * (range / (f(ax.high) - f(ax.low)))
18+
}
19+
20+
#let linear(low, high) = (
21+
low: low, high: high, transform: _transform-lin,
22+
)
23+
24+
#let logarithmic(low, high, base) = (
25+
low: low, high: high, base: base, transform: _transform-log,
26+
)
27+
28+
/// Transform an axis value to a linear value between low and high
29+
/// - ax (axis): Axis
30+
/// - value (number): Value to transform from axis space to linear space
31+
/// - low (number): Linear minimum
32+
/// - high (number): Linear maximum
33+
#let transform(ax, value, low, high) = {
34+
return (ax.transform)(ax, value, low, high)
35+
}

src/projection.typ

+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
2+
/// Create a new cartesian projection between two vectors, low and high
3+
///
4+
/// - low (vector): Low vector
5+
/// - high (vector): High vector
6+
/// - x (axis): X axis
7+
/// - y (axis): Y axis
8+
/// - z (axis): Z axis
9+
/// -> function Transformation for one or more vectors
10+
#let cartesian(low, high, x, y, z) = {
11+
let axes = (x, y, z)
12+
13+
return (..v) = {
14+
return v.pos().map(v => {
15+
for i range(0, v.len()) {
16+
v.at(i) = (axes.at(i).transform)(axes.at(i), v.at(i), low.at(i), high.at(i))
17+
}
18+
})
19+
}
20+
}
21+
22+
/// - center (vector): Center vector
23+
/// - start (angle): Start angle (0deg for full circle)
24+
/// - stop (angle): Stop angle (360deg for full circle)
25+
/// - theta (axis): Theta axis
26+
/// - r (axis): R axis
27+
/// -> function Transformation for one or more vectors
28+
#let polar(center, radius, start, stop, theta, r) = {
29+
return (..v) => {
30+
let v = v.pos()
31+
// TODO
32+
return v
33+
}
34+
}

src/ticks.typ

+208
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,208 @@
1+
// Compute list of linear ticks for axis
2+
//
3+
// - axis (axis): Axis
4+
#let compute-linear-ticks(axis, style, add-zero: true) = {
5+
let (min, max) = (axis.min, axis.max)
6+
let dt = max - min; if (dt == 0) { dt = 1 }
7+
let ticks = axis.ticks
8+
let ferr = util.float-epsilon
9+
let tick-limit = style.tick-limit
10+
let minor-tick-limit = style.minor-tick-limit
11+
12+
let l = ()
13+
if ticks != none {
14+
let major-tick-values = ()
15+
if "step" in ticks and ticks.step != none {
16+
assert(ticks.step >= 0,
17+
message: "Axis tick step must be positive and non 0.")
18+
if axis.min > axis.max { ticks.step *= -1 }
19+
20+
let s = 1 / ticks.step
21+
22+
let num-ticks = int(max * s + 1.5) - int(min * s)
23+
assert(num-ticks <= tick-limit,
24+
message: "Number of major ticks exceeds limit " + str(tick-limit))
25+
26+
let n = range(int(min * s), int(max * s + 1.5))
27+
for t in n {
28+
let v = (t / s - min) / dt
29+
if t / s == 0 and not add-zero { continue }
30+
31+
if v >= 0 - ferr and v <= 1 + ferr {
32+
l.push((v, format-tick-value(t / s, ticks), true))
33+
major-tick-values.push(v)
34+
}
35+
}
36+
}
37+
38+
if "minor-step" in ticks and ticks.minor-step != none {
39+
assert(ticks.minor-step >= 0,
40+
message: "Axis minor tick step must be positive")
41+
if axis.min > axis.max { ticks.minor-step *= -1 }
42+
43+
let s = 1 / ticks.minor-step
44+
45+
let num-ticks = int(max * s + 1.5) - int(min * s)
46+
assert(num-ticks <= minor-tick-limit,
47+
message: "Number of minor ticks exceeds limit " + str(minor-tick-limit))
48+
49+
let n = range(int(min * s), int(max * s + 1.5))
50+
for t in n {
51+
let v = (t / s - min) / dt
52+
if v in major-tick-values {
53+
// Prefer major ticks over minor ticks
54+
continue
55+
}
56+
57+
if v != none and v >= 0 and v <= 1 + ferr {
58+
l.push((v, none, false))
59+
}
60+
}
61+
}
62+
63+
}
64+
65+
return l
66+
}
67+
68+
// Compute list of linear ticks for axis
69+
//
70+
// - axis (axis): Axis
71+
#let compute-logarithmic-ticks(axis, style, add-zero: true) = {
72+
let ferr = util.float-epsilon
73+
let (min, max) = (
74+
calc.log(calc.max(axis.min, ferr), base: axis.base),
75+
calc.log(calc.max(axis.max, ferr), base: axis.base)
76+
)
77+
let dt = max - min; if (dt == 0) { dt = 1 }
78+
let ticks = axis.ticks
79+
80+
let tick-limit = style.tick-limit
81+
let minor-tick-limit = style.minor-tick-limit
82+
let l = ()
83+
84+
if ticks != none {
85+
let major-tick-values = ()
86+
if "step" in ticks and ticks.step != none {
87+
assert(ticks.step >= 0,
88+
message: "Axis tick step must be positive and non 0.")
89+
if axis.min > axis.max { ticks.step *= -1 }
90+
91+
let s = 1 / ticks.step
92+
93+
let num-ticks = int(max * s + 1.5) - int(min * s)
94+
assert(num-ticks <= tick-limit,
95+
message: "Number of major ticks exceeds limit " + str(tick-limit))
96+
97+
let n = range(
98+
int(min * s),
99+
int(max * s + 1.5)
100+
)
101+
102+
for t in n {
103+
let v = (t / s - min) / dt
104+
if t / s == 0 and not add-zero { continue }
105+
106+
if v >= 0 - ferr and v <= 1 + ferr {
107+
l.push((v, format-tick-value( calc.pow(axis.base, t / s), ticks), true))
108+
major-tick-values.push(v)
109+
}
110+
}
111+
}
112+
113+
if "minor-step" in ticks and ticks.minor-step != none {
114+
assert(ticks.minor-step >= 0,
115+
message: "Axis minor tick step must be positive")
116+
if axis.min > axis.max { ticks.minor-step *= -1 }
117+
118+
let s = 1 / ticks.step
119+
let n = range(int(min * s)-1, int(max * s + 1.5)+1)
120+
121+
for t in n {
122+
for vv in range(1, int(axis.base / ticks.minor-step)) {
123+
124+
let v = ( (calc.log(vv * ticks.minor-step, base: axis.base) + t)/ s - min) / dt
125+
if v in major-tick-values {continue}
126+
127+
if v != none and v >= 0 and v <= 1 + ferr {
128+
l.push((v, none, false))
129+
}
130+
131+
}
132+
133+
}
134+
}
135+
}
136+
137+
return l
138+
}
139+
140+
// Get list of fixed axis ticks
141+
//
142+
// - axis (axis): Axis object
143+
#let fixed-ticks(axis) = {
144+
let l = ()
145+
if "list" in axis.ticks {
146+
for t in axis.ticks.list {
147+
let (v, label) = (none, none)
148+
if type(t) in (float, int) {
149+
v = t
150+
label = format-tick-value(t, axis.ticks)
151+
} else {
152+
(v, label) = t
153+
}
154+
155+
v = value-on-axis(axis, v)
156+
if v != none and v >= 0 and v <= 1 {
157+
l.push((v, label, true))
158+
}
159+
}
160+
}
161+
return l
162+
}
163+
164+
// Compute list of axis ticks
165+
//
166+
// A tick triple has the format:
167+
// (rel-value: float, label: content, major: bool)
168+
//
169+
// - axis (axis): Axis object
170+
#let compute-ticks(axis, style, add-zero: true) = {
171+
let find-max-n-ticks(axis, n: 11) = {
172+
let dt = calc.abs(axis.max - axis.min)
173+
let scale = calc.floor(calc.log(dt, base: 10) - 1)
174+
if scale > 5 or scale < -5 {return none}
175+
176+
let (step, best) = (none, 0)
177+
for s in style.auto-tick-factors {
178+
s = s * calc.pow(10, scale)
179+
180+
let divs = calc.abs(dt / s)
181+
if divs >= best and divs <= n {
182+
step = s
183+
best = divs
184+
}
185+
}
186+
return step
187+
}
188+
189+
if axis == none or axis.ticks == none { return () }
190+
if axis.ticks.step == auto {
191+
axis.ticks.step = find-max-n-ticks(axis, n: style.auto-tick-count)
192+
}
193+
if axis.ticks.minor-step == auto {
194+
axis.ticks.minor-step = if axis.ticks.step != none {
195+
axis.ticks.step / 5
196+
} else {
197+
none
198+
}
199+
}
200+
201+
let ticks = if axis.mode == "log" {
202+
compute-logarithmic-ticks(axis, style, add-zero: add-zero)
203+
} else {
204+
compute-linear-ticks(axis, style, add-zero: add-zero)
205+
}
206+
ticks += fixed-ticks(axis)
207+
return ticks
208+
}

0 commit comments

Comments
 (0)