Create New RenderPlan#
The built-in plotting options in Marsilea may not be perfect for your presentation. This section will show you how to create your own visualization. It’s expected that you are familiar with Python’s class inheritance.
Every plot renders on the canvas in Marsilea is derived from
RenderPlan
.
To create a new visualization, we also need to inherit from
RenderPlan
.
Simple Lollipop plot#
Let’s create a Lollipop plot as example:
>>> import marsilea as ma
>>> from marsilea.base import RenderPlan
>>> class Lollipop(RenderPlan):
... pass
The current Lollipop
does nothing,
it does not know how to draw a lollipop.
>>> data = np.random.rand(20, 20)
>>> h = ma.Heatmap(data)
>>> h.add_top(Lollipop())
>>> h.render()

We need to implement the render_ax()
method,
which takes one argument: the spec
. The spec
contains
necessary information to draw the plot. It handles data and axes for you.
>>> class Lollipop(RenderPlan):
...
... def __init__(self, data):
... self.set_data(data)
...
... def render_ax(self, spec):
... ax = spec.ax
... data = spec.data
... lim = len(data)
... locs = np.arange(lim) + 0.5
... ax.stem(locs, data, basefmt="none")
... ax.set_axis_off()
... ax.set_xlim(0, lim)
...
>>> lp_data = np.arange(20) + 1
>>> h = ma.Heatmap(data)
>>> h.add_top(Lollipop(lp_data))
>>> h.render()

Drawing logics for different sides#
If you try add our lollipop to the left, it will not rotate accordingly.
>>> h = ma.Heatmap(data)
>>> h.add_left(Lollipop(lp_data))
>>> h.render()

We need to implement the drawing logic for different sides. Here we use
a pre-config RenderPlan
that are designed for stats plot: StatsBase
.
>>> from marsilea.plotter.base import StatsBase
>>> class Lollipop(StatsBase):
...
... def __init__(self, data):
... self.set_data(data)
...
... def render_ax(self, spec):
... ax = spec.ax
... data = spec.data
... lim = len(data)
... locs = np.arange(lim) + .5
... orientation = "vertical" if self.is_body else "horizontal"
... ax.stem(locs, data, basefmt="none", orientation=orientation)
... ax.set_axis_off()
... if self.is_body:
... ax.set_xlim(0, lim)
... if self.side == "left":
... ax.invert_xaxis()
... if self.is_flank:
... ax.set_ylim(lim, 0)
...
We use the is_body attribute to query the side where our Lollipop is drawn.
Below is list of attributes that you can use to know
which side that the RenderPlan
is drawn.
.side
: Get the current side of the RenderPlan.is_body
: Top, Bottom or Main.is_flank
: Left or Right
We make the orientation changed when the Lollipop
is rendered on different
side of heatmap.
Now we try add it to the left again.
>>> h = ma.Heatmap(data)
>>> h.add_left(Lollipop(lp_data))
>>> h.cut_rows(cut=[5, 10])
>>> h.render()

Make a legend#
If your RenderPlan
need to have legends,
you need to implement the
get_legends()
.
We also develop legendkit to help you create legend easily, it is used to handle legends in Marsilea.
>>> from legendkit import CatLegend
>>>
>>> class Lollipop(Lollipop):
...
... def get_legends(self):
... return CatLegend(colors=["b"], labels=["Lollipop"], handle="circle")
Now we can add the legends and let Marsilea automatically handle all the legends for you.
>>> h = ma.Heatmap(data)
>>> h.add_left(Lollipop(lp_data))
>>> h.add_legends()
>>> h.render()
