Skip to content

Commit 60d3fde

Browse files
Merge branch 'aggressive-refactoring' into bar-element-gallery-fixes
2 parents d00c204 + 9155ee8 commit 60d3fde

File tree

12 files changed

+117
-326
lines changed

12 files changed

+117
-326
lines changed

gallery/barchart.typ

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,5 +20,5 @@
2020
bar-width: .8,
2121
data2,
2222
labels: ([Low], [Medium], [High], [Very high]),
23-
legend: "inner-north-east",)
24-
})
23+
legend: "inner-north-east")
24+
})

manual.pdf

0 Bytes
Binary file not shown.

src/charts/bar/clustered.typ

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,10 @@
7575
((
7676
x: x,
7777
y: y,
78+
y-error: if y-error-keys != none {
79+
let err-key = y-error-keys.at(series-index, default: none)
80+
if err-key != none {observation.at(err-key, default: 0)}
81+
}
7882
),)
7983
}
8084
)
@@ -86,7 +90,7 @@
8690
series-data,
8791
x-key: "x",
8892
y-key: "y",
89-
y-error-key: none,
93+
y-error-key: if y-error-keys != none {"y-error"},
9094
label-key: label-key,
9195
bar-width: bar-width,
9296
bar-style: bar-style,

src/charts/bar/plotter.typ

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
series-data,
99
x-key: "x",
1010
y-key: "y",
11-
y-error-keys: none,
11+
y-error-key: none,
1212
y-offset-key: none,
1313
label-key: none,
1414
bar-width: 0.7,
@@ -56,7 +56,7 @@
5656
axes: axes,
5757
)
5858

59-
if y-error-keys != none {
59+
if y-error-key != none {
6060
add.errorbar(
6161
data,
6262
x-key: x-key,y-key: y-key, y-error-key: y-error-key,

src/charts/bar/stacked.typ

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,9 +53,9 @@
5353
axes: ("x", "y"),
5454
..plot-args
5555
) = {
56-
let series-count = y-keys.len()
57-
let cluster-width = series-count * bar-width
58-
let offsets = (0,)*series-count
56+
let cluster-count = data.len()
57+
let cluster-width = cluster-count * bar-width
58+
let offsets = (0,)*cluster-count
5959

6060
let series-data = ()
6161

Lines changed: 69 additions & 133 deletions
Original file line numberDiff line numberDiff line change
@@ -1,168 +1,104 @@
1+
#import "/src/cetz.typ"
2+
13
/// Clip line-strip in rect
24
///
35
/// - points (array): Array of vectors representing a line-strip
46
/// - low (vector): Lower clip-window coordinate
57
/// - high (vector): Upper clip-window coordinate
8+
/// - fill (bool): Return fillable shapes
9+
/// - generate-edge-points (bool): Generate interpolated points on clipped edges
610
/// -> array List of line-strips representing the paths insides the clip-window
7-
#let clipped-paths-rect(points, ctx, fill: false) = {
11+
#let clipped-paths-rect(points, ctx, fill: false, generate-edge-points: false) = {
812
let (low, high) = ctx.clip
913
let (min-x, max-x) = (calc.min(low.at(0), high.at(0)),
1014
calc.max(low.at(0), high.at(0)))
1115
let (min-y, max-y) = (calc.min(low.at(1), high.at(1)),
1216
calc.max(low.at(1), high.at(1)))
1317

14-
let in-rect(pt) = {
15-
return (pt.at(0) >= min-x and pt.at(0) <= max-x and
16-
pt.at(1) >= min-y and pt.at(1) <= max-y)
17-
}
18-
19-
let interpolated-end(a, b) = {
20-
if in-rect(a) and in-rect(b) {
21-
return b
22-
}
23-
24-
let (x1, y1, ..) = a
25-
let (x2, y2, ..) = b
26-
27-
if x2 - x1 == 0 {
28-
return (x2, calc.min(max-y, calc.max(y2, min-y)))
29-
}
30-
31-
if y2 - y1 == 0 {
32-
return (calc.min(max-x, calc.max(x2, min-x)), y2)
33-
}
34-
35-
let m = (y2 - y1) / (x2 - x1)
36-
let n = y2 - m * x2
37-
38-
let x = x2
39-
let y = y2
40-
41-
y = calc.min(max-y, calc.max(y, min-y))
42-
x = (y - n) / m
43-
44-
x = calc.min(max-x, calc.max(x, min-x))
45-
y = m * x + n
46-
47-
return (x, y)
18+
let in-rect((x, y)) = {
19+
return (x >= min-x and x <= max-x and
20+
y >= min-y and y <= max-y)
4821
}
4922

50-
// Append path to paths and return paths
51-
//
52-
// If path starts or ends with a vector of another part, merge those
53-
// paths instead appending path as a new path.
54-
let append-path(paths, path) = {
55-
if path.len() <= 1 {
56-
return paths
57-
}
58-
59-
let cmp(a, b) = {
60-
return a.map(calc.round.with(digits: 8)) == b.map(calc.round.with(digits: 8))
61-
}
23+
let edges = (
24+
((min-x, min-y), (min-x, max-y)),
25+
((max-x, min-y), (max-x, max-y)),
26+
((min-x, min-y), (max-x, min-y)),
27+
((min-x, max-y), (max-x, max-y)),
28+
)
6229

63-
let added = false
64-
for i in range(0, paths.len()) {
65-
let p = paths.at(i)
66-
if cmp(p.first(), path.last()) {
67-
paths.at(i) = path + p
68-
added = true
69-
} else if cmp(p.first(), path.first()) {
70-
paths.at(i) = path.rev() + p
71-
added = true
72-
} else if cmp(p.last(), path.first()) {
73-
paths.at(i) = p + path
74-
added = true
75-
} else if cmp(p.last(), path.last()) {
76-
paths.at(i) = p + path.rev()
77-
added = true
30+
let interpolated-end(a, b) = {
31+
for (edge-a, edge-b) in edges {
32+
let pt = cetz.intersection.line-line(a, b, edge-a, edge-b)
33+
if pt != none {
34+
return pt
7835
}
79-
if added { break }
8036
}
81-
82-
if not added {
83-
paths.push(path)
84-
}
85-
return paths
8637
}
8738

88-
let clamped-pt(pt) = {
89-
return (calc.max(min-x, calc.min(pt.at(0), max-x)),
90-
calc.max(min-y, calc.min(pt.at(1), max-y)))
91-
}
9239

93-
let paths = ()
40+
// Find lines crossing the rect bounds
41+
// by storing all crossings as tuples (<index>, <goes-inside>, <point-on-border>)
42+
let crossings = ()
9443

95-
let path = ()
96-
let prev = points.at(0)
97-
let was-inside = in-rect(prev)
44+
// Push a pseudo entry for the last point, if it is insides the bounds.
45+
let was-inside = in-rect(points.at(0))
9846
if was-inside {
99-
path.push(prev)
100-
} else if fill {
101-
path.push(clamped-pt(prev))
47+
crossings.push((0, true, points.first()))
10248
}
10349

50+
// Find crossings and compute interseciton points.
10451
for i in range(1, points.len()) {
105-
let prev = points.at(i - 1)
106-
let pt = points.at(i)
107-
108-
let is-inside = in-rect(pt)
52+
let current-inside = in-rect(points.at(i))
53+
if current-inside != was-inside {
54+
crossings.push((
55+
i,
56+
current-inside,
57+
interpolated-end(points.at(i - 1), points.at(i))))
58+
was-inside = current-inside
59+
}
60+
}
10961

110-
let (x1, y1) = prev
111-
let (x2, y2) = pt
62+
// Push a pseudo entry for the last point, if it is insides the bounds.
63+
if in-rect(points.last()) and crossings.last().at(1) {
64+
crossings.push((points.len() - 1, false, points.last()))
65+
}
11266

113-
// Ignore lines if both ends are outsides the x-window and on the
114-
// same side.
115-
if (x1 < min-x and x2 < min-x) or (x1 > max-x and x2 > max-x) {
116-
if fill {
117-
let clamped = clamped-pt(pt)
118-
if path.last() != clamped {
119-
path.push(clamped)
67+
// Generate paths
68+
let paths = ()
69+
for i in range(1, crossings.len()) {
70+
let (a-index, a-dir, a-pt) = crossings.at(i - 1)
71+
let (b-index, b-dir, b-pt) = crossings.at(i)
72+
73+
if a-dir {
74+
let path = ()
75+
76+
// If we must generate edge points, take the previous crossing
77+
// as source point and interpolate between that and the current one.
78+
if generate-edge-points and i > 2 {
79+
let (c-index, c-dir, c-pt) = crossings.at(i - 2)
80+
81+
let n = a-index - c-index
82+
if n > 1 {
83+
path += range(0, n).map(t => {
84+
cetz.vector.lerp(c-pt, a-pt, t / (n - 1))
85+
})
12086
}
12187
}
122-
was-inside = false
123-
continue
124-
}
12588

126-
if is-inside {
127-
if was-inside {
128-
path.push(pt)
129-
} else {
130-
path.push(interpolated-end(pt, prev))
131-
path.push(pt)
132-
}
133-
} else {
134-
if was-inside {
135-
path.push(interpolated-end(prev, pt))
136-
} else {
137-
let (a, b) = (interpolated-end(pt, prev),
138-
interpolated-end(prev, pt))
139-
if in-rect(a) and in-rect(b) {
140-
path.push(a)
141-
path.push(b)
142-
} else if fill {
143-
let clamped = clamped-pt(pt)
144-
if path.last() != clamped {
145-
path.push(clamped)
146-
}
147-
}
148-
}
89+
// Append the path insides the bounds
90+
path.push(a-pt)
91+
path += points.slice(a-index, b-index)
92+
path.push(b-pt)
14993

150-
if path.len() > 0 and not fill {
151-
paths = append-path(paths, path)
152-
path = ()
94+
// Insert the last end point to connect
95+
// to a filled area.
96+
if fill and paths.len() > 0 {
97+
path.insert(0, paths.last().last())
15398
}
154-
}
155-
156-
was-inside = is-inside
157-
}
15899

159-
// Append clamped last point if filling
160-
if fill and not in-rect(points.last()) {
161-
path.push(clamped-pt(points.last()))
162-
}
163-
164-
if path.len() > 1 {
165-
paths = append-path(paths, path)
100+
paths.push(path)
101+
}
166102
}
167103

168104
return paths
@@ -181,4 +117,4 @@
181117
/// - low (vector): Lower clip-window coordinate
182118
/// - high (vector): Upper clip-window coordinate
183119
/// -> array List of fill paths
184-
#let compute-fill-paths = clipped-paths-rect.with(fill: true)
120+
#let compute-fill-paths = clipped-paths-rect.with(fill: true)

0 commit comments

Comments
 (0)