Skip to content

Commit cdcaffa

Browse files
Do Not Consider Domain Boundaries for Contour Plots (#183)
2 parents eda2542 + 2d7e50b commit cdcaffa

4 files changed

Lines changed: 66 additions & 44 deletions

File tree

src/plot/contour.typ

Lines changed: 66 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,17 @@
1414
// with two arguments, the z value `z1` to compare against and
1515
// the z value `z2` of the data and must return a boolean: `(z1, z2) => boolean`.
1616
// - interpolate (bool): Enable cell interpolation for smoother lines
17+
// - connect-domain (bool): Treat the sample domain boundary as an exterior edge.
18+
// This closes regions against the plot boundary, which is useful for fills but
19+
// not for contour lines.
1720
// - contour-limit (int): Contour limit after which the algorithm panics
1821
// -> array: Array of contour point arrays
19-
#let find-contours(data, offset, op: auto, interpolate: true, contour-limit: 50) = {
22+
#let find-contours(data,
23+
offset,
24+
op: auto,
25+
interpolate: true,
26+
connect-domain: false,
27+
contour-limit: 50) = {
2028
assert(data != none and type(data) == array,
2129
message: "Data must be of type array")
2230
assert(type(offset) in (int, float),
@@ -58,6 +66,22 @@
5866
// Build a binary map that has 0 for unset and 1 for set cells
5967
let bin-data = data.map(r => r.map(is-set))
6068

69+
// Get case (0 to 15)
70+
let get-case(tl, tr, bl, br) = {
71+
int(tl) * 8 + int(tr) * 4 + int(br) * 2 + int(bl)
72+
}
73+
74+
let lerp(a, b) = {
75+
if a == b { return a }
76+
else if a == none { return 1 }
77+
else if b == none { return 0 }
78+
return (offset - a) / (b - a)
79+
}
80+
81+
let segments = ()
82+
let x-range = if connect-domain { range(-1, n-cols) } else { range(0, n-cols - 1) }
83+
let y-range = if connect-domain { range(-1, n-rows) } else { range(0, n-rows - 1) }
84+
6185
// Get binary data at x, y
6286
let get-bin(x, y) = {
6387
if x >= 0 and x < n-cols and y >= 0 and y < n-rows {
@@ -74,28 +98,12 @@
7498
return none
7599
}
76100

77-
// Get case (0 to 15)
78-
let get-case(tl, tr, bl, br) = {
79-
int(tl) * 8 + int(tr) * 4 + int(br) * 2 + int(bl)
80-
}
81-
82-
let lerp(a, b) = {
83-
if a == b { return a }
84-
else if a == none { return 1 }
85-
else if b == none { return 0 }
86-
return (offset - a) / (b - a)
87-
}
88-
89-
// List of all found contours
90-
let contours = ()
91-
92-
let segments = ()
93-
for y in range(-1, n-rows) {
94-
for x in range(-1, n-cols) {
101+
for y in y-range {
102+
for x in x-range {
95103
let tl = get-bin(x, y)
96-
let tr = get-bin(x+1, y)
97-
let bl = get-bin(x, y+1)
98-
let br = get-bin(x+1, y+1)
104+
let tr = get-bin(x + 1, y)
105+
let bl = get-bin(x, y + 1)
106+
let br = get-bin(x + 1, y + 1)
99107

100108
// Corner data
101109
//
@@ -105,9 +113,9 @@
105113
// | |
106114
// sw-----se
107115
let nw = get-data(x, y)
108-
let ne = get-data(x+1, y)
109-
let se = get-data(x+1, y+1)
110-
let sw = get-data(x, y+1)
116+
let ne = get-data(x + 1, y)
117+
let se = get-data(x + 1, y + 1)
118+
let sw = get-data(x, y + 1)
111119

112120
// Interpolated edge points
113121
//
@@ -204,25 +212,39 @@
204212
return contours
205213
}
206214

215+
// Convert contour data points from sample-space to plot coordinates.
216+
#let _scale-contours(contours, x-min, y-min, dx, dy, z) = {
217+
contours.map(contour => (
218+
z: z,
219+
line-data: contour.map(pt => (
220+
pt.at(0) * dx + x-min,
221+
pt.at(1) * dy + y-min,
222+
)),
223+
))
224+
}
225+
207226
// Prepare line data
208227
#let _prepare(self, ctx) = {
209228
let (x, y) = (ctx.x, ctx.y)
210229

211-
self.contours = self.contours.map(c => {
230+
self.stroke-contours = self.stroke-contours.map(c => {
212231
c.stroke-paths = util.compute-stroke-paths(c.line-data, x, y)
213-
214-
if self.fill {
215-
c.fill-paths = util.compute-fill-paths(c.line-data, x, y)
216-
}
217232
return c
218233
})
219234

235+
if self.fill {
236+
self.fill-contours = self.fill-contours.map(c => {
237+
c.fill-paths = util.compute-fill-paths(c.line-data, x, y)
238+
return c
239+
})
240+
}
241+
220242
return self
221243
}
222244

223245
// Stroke line data
224246
#let _stroke(self, ctx) = {
225-
for c in self.contours {
247+
for c in self.stroke-contours {
226248
for p in c.stroke-paths {
227249
draw.line(..p, fill: none, close: p.first() == p.last())
228250
}
@@ -232,7 +254,7 @@
232254
// Fill line data
233255
#let _fill(self, ctx) = {
234256
if not self.fill { return }
235-
for c in self.contours {
257+
for c in self.fill-contours {
236258
for p in c.fill-paths {
237259
draw.line(..p, stroke: none, close: p.first() == p.last())
238260
}
@@ -310,26 +332,26 @@
310332
let (y-min, y-max) = y-domain
311333
let dy = (y-max - y-min) / (data.len() - 1)
312334

313-
let contours = ()
335+
let stroke-contours = ()
336+
let fill-contours = ()
314337
let z = if type(z) == array { z } else { (z,) }
315338
for z in z {
316-
for contour in find-contours(data, z, op: op, interpolate: interpolate, contour-limit: limit) {
317-
let line-data = contour.map(pt => {
318-
(pt.at(0) * dx + x-min,
319-
pt.at(1) * dy + y-min)
320-
})
321-
322-
contours.push((
323-
z: z,
324-
line-data: line-data,
325-
))
339+
stroke-contours += _scale-contours(
340+
find-contours(data, z, op: op, interpolate: interpolate, contour-limit: limit),
341+
x-min, y-min, dx, dy, z)
342+
343+
if fill {
344+
fill-contours += _scale-contours(
345+
find-contours(data, z, op: op, interpolate: interpolate, connect-domain: true, contour-limit: limit),
346+
x-min, y-min, dx, dy, z)
326347
}
327348
}
328349

329350
return ((
330351
type: "contour",
331352
label: label,
332-
contours: contours,
353+
stroke-contours: stroke-contours,
354+
fill-contours: fill-contours,
333355
axes: axes,
334356
x-domain: x-domain,
335357
y-domain: y-domain,

tests/plot/contour/ref/1.png

-1.13 KB
Loading

tests/plot/legend/ref/1.png

155 Bytes
Loading

tests/plot/vertical/ref/1.png

15 Bytes
Loading

0 commit comments

Comments
 (0)