How can I fill the intersection of multiple ellipses?

Related to a previous question I had, I am interested in filling the intersection of multiple ellipses.

Specifically, I need to figure out how to draw partial arcs of ellipses, using only two points, the radius, and the center.

Here is an example of the kind of diagram I want, which I created using a straight line approximation:

#import "@preview/cetz:0.4.2": *

#canvas({
  import draw: *
  
  circle((0,0), radius: (8, 4), name: "G")
  circle((rel: (-6, 0.4), to: "G.center"), radius: 0.25, name: "A", fill:black)
  

  circle("A", radius: 3, name: "inner", stroke: gray)
  circle("A", radius: 4, name: "outer", stroke: gray)

  
  intersections("i", "G", "inner")
  intersections("o", "G", "outer")
  
  merge-path(
    stroke: color.purple + 2pt,
    fill: color.purple.transparentize(80%),
    close: true,
    {
      arc-through("i.0", "inner.east", "i.1")
      line("i.1", "o.1")
      arc-through("o.1", "outer.east", "o.0")
      line("o.0", "i.0")
    }
  )
})

This does not work for larger segments:

#canvas({
  import draw: *
  
  circle((0,0), radius: (8, 4), name: "G")
  circle((rel: (-6, 0.4), to: "G.center"), radius: 0.25, fill: black, name: "A")
  

  circle("A", radius: 3, name: "inner", stroke: gray)
  circle("A", radius: 10, name: "outer", stroke: gray)

  
  intersections("i", "G", "inner")
  intersections("o", "G", "outer")
  
  merge-path(
    stroke: purple + 2pt,
    fill: purple.transparentize(80%),
    close: false,
    {
      arc-through("i.0", "inner.east", "i.1")
      line("i.1", "o.0")
      arc-through("o.0", "outer.east", "o.1")
      line("o.1", "i.0")
    }
  )
  // If I had the angle of these lines, could I draw the partial arc?
  line("G", "i.0")
  line("G", "o.0")
  line("G", "i.1")
  line("G", "o.1")
})

It really seems like with the center, radius and two points, I should have all of the information necessary to draw these partial arcs. I think my best idea was to compute the angle of the lines in the figure I added, but I thus far have not been able to put anything together that works.

Thank you in advance for any ideas.

2 Likes

Just clear which circle the arcs belong to.

#import "@preview/cetz:0.4.2": *

#canvas({
  import draw: *
  
  circle((0,0), radius: (8, 4), name: "G")
  circle((rel: (-6, 0.4), to: "G.center"), radius: 0.25, name: "A", fill:black)

  circle("A", radius: 3, name: "inner", stroke: gray)
  circle("A", radius: 9, name: "outer", stroke: gray)

  intersections("i", "G", "inner")
  intersections("o", "G", "outer")
  
  merge-path(
    stroke: color.purple + 2pt,
    fill: color.purple.transparentize(80%),
    close: true,
    {arc-through("i.0", "inner.east", "i.1")
     arc-through("i.1", "G.south", "o.0")
     arc-through("o.0", "outer.east", "o.1")
     arc-through("o.1", "G.north", "i.0")})})

1 Like

Since the arcs have to be elliptic, arc-through does not work here.
Instead you could find the angles on the ellipse and use the arc function with them:

#import "@preview/cetz:0.4.2"
#set page(width: auto, height: auto)

#cetz.canvas({
  import cetz.draw: *

  let radius = (8, 4)
  circle((0,0), radius: radius, name: "G")
  circle((rel: (-6, 0.4), to: "G.center"), radius: 0.25, name: "A", fill:black)

  circle("A", radius: 3, name: "inner", stroke: gray)
  circle("A", radius: 9, name: "outer", stroke: gray)

  intersections("i", "G", "inner")
  intersections("o", "G", "outer")

  get-ctx(ctx => {
    merge-path(
      stroke: color.purple + 2pt,
      fill: color.purple.transparentize(80%),
      close: true,
    {
      arc-through("i.0", "inner.east", "i.1")
      
      let (rx, ry) = radius
      let (_, center, a, b) = cetz.coordinate.resolve(ctx, "G.center", "i.1", "o.0")
      let a = calc.atan2(a.at(0) / rx, a.at(1) / ry)
      let b = calc.atan2(b.at(0) / rx, b.at(1) / ry)
      arc(center, start: a, stop: b, radius: radius, anchor: "origin", stroke: red + 4pt)

      arc-through("o.0", "outer.east", "o.1")
  
      let (_, a, b) = cetz.coordinate.resolve(ctx, "i.0", "o.1")
      let a = calc.atan2(a.at(0) / rx, a.at(1) / ry)
      let b = calc.atan2(b.at(0) / rx, b.at(1) / ry)
      arc(center, start: b, stop: a, radius: radius, anchor: "origin", stroke: red + 4pt)
    })
  })
})
3 Likes