Compose.jl
Compose.jl copied to clipboard
Guidance on a hyperbolic drawing extension
I'd like to package and publish the submodule of this project that draws things on a view of the hyperbolic plane. It currently uses Compose as a backend, because:
- I like Compose's declarative style.
- This makes it easy to include hyperbolic drawings in a larger Compose layout.
One of my goals for the package is to integrate my hyperbolic drawing features more smoothly into the existing Compose interface. I'd love to have guidance from the Compose community on how to do this (and whether it's a sensible thing to do). With that goal in mind, I have a more specific question about Compose's internals below.
If this issue tracker is the wrong place for this discussion, please redirect me to the right one!
Subtyping Context
to manage views
Conceptually, each view of the hyperbolic plane gives its own way to turn hyperbolic forms into Euclidean ones that can be drawn on a page. Correspondingly, I'm inclined to represent each view as a subtype of Context
which takes hyperbolic forms as its children, and specifies how to turn them into ordinary Compose forms. A usage mock-up is shown below.
Can I?
Can a subtype of Context do this? Maybe all it would take is a new dispatch rule for compose!
, like this:
# PoincaréDiskContext <: HyperbolicView <: Container
# HyperbolicContext <: Container
# HyperbolicFormPrimitive <: FormPrimitive
function compose!(a::PoincaréDiskView, b::Form{P}) where P <: HyperbolicFormPrimitive
# use the poincaré disk model to turn `b` into an ordinary form
a.form_children = cons(applyview(a, b), a.form_children)
return a
end
function compose!(a::PoincaréDiskView, b::HyperbolicContext)
# apply the view recursively to the children of `b`
a.container_children = cons(applyview(a, b), a.container_children)
return a
end
compose!(a::Context, b::Form{P}) where P <: HyperbolicFormPrimitive
error(@sprintf("Invalid composition: hyperbolic primitives only nest inside HyperbolicView and HyperbolicContext containers."))
end
compose!(a::Context, b::HyperbolicContext)
error(@sprintf("Invalid composition: HyperbolicContext only nests inside HyperbolicView and HyperbolicContext."))
end
Does that seem like a sensible approach? Would any functions other than compose!
need new dispatch rules? Should the type structure be revised? (For example, maybe it's better to not make HyperbolicForm
a subtype of Form
).
Should I?
Some features of Compose, like the transformation system, carry over nicely to the hyperbolic plane. However, I'm worried that other parts of this hyperbolic drawing extension would go against the spirit of Compose. For example, the composition operators hstack
and vstack
, don't make much sense in the hyperbolic context, and the HyperbolicView
contexts described above affect the composition tree in a weird way. Have I chosen a good way to integrate hyperbolic drawing into Compose? Is that even a sensible thing to do?
Mock-up
# UpperHalfPlanePoint <: HyperbolicPoint
a = UpperHalfPlanePoint([im, 1])
b = UpperHalfPlanePoint([cis(π/3), 1])
c = UpperHalfPlanePoint([1, 0])
# HyperbolicView <: Container
# typeof(uhp_view) == UpperHalfPlaneContext <: HyperbolicView
# typeof(disk_view) == PoincaréDiskContext <: HyperbolicView
uhp_view = hyperbolicview(model=UpperHalfPlane(-1, 1, 2))
disk_view = hyperbolicview(model=PoincaréDisk())
# the contexts `uhp_view` and `disk_view` specify different ways to turn
# the hyperbolic `line` and `polygon` forms into ordinary `curve` and
# `bezigon` forms. `hypcontext` creates a HyperbolicContext
uhp_img = compose(uhp_view,
(hypcontext(), line([a, b]), stroke("black")),
(hypcontext(), polygon([a, b, c]), fill("deepskyblue"))
)
disk_img = compose(disk_view,
(hypcontext(), line([a, b]), stroke("black")),
(hypcontext(), polygon([a, b, c]), fill("deepskyblue"))
)
# since hyperbolic views are a kind of context, we can compose them as usual
img = compose(context(),
hstack(
compose(context(0.1, 0.1, 0.8, 0.8), rectangle(), stroke("red"), uhp_img),
compose(context(0.1, 0.1, 0.8, 0.8), circle(), stroke("red"), uhp_img)
)
)