Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions doc/changelog.qmd
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,10 @@ title: Changelog
text, so with free scales large margins no longer push the tick labels
into the neighbouring panel.

- Plot titles in a composition now sit at the same height even when the
plots differ in what lies between the title and the panel — facet
strips, top legends or a top-positioned x axis.

- Subclass geoms now inherit their parent geom's default parameters, so
parameters that a parent geom supports are no longer rejected.
[](:class:`~plotnine.geom_step`) accepts `lineend`, `linejoin` and `arrow`,
Expand Down
20 changes: 20 additions & 0 deletions plotnine/_mpl/layout_manager/_layout_tree.py
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,7 @@ def arrange_layout(self):
side-effects.
"""
self.align_axis_titles()
self.align_plot_titles()
self.align()
self.resize()

Expand Down Expand Up @@ -563,6 +564,25 @@ def axis_title_clearance(s):
for tree in self.sub_compositions:
tree.align_axis_titles()

def align_plot_titles(self):
"""
Align the plot titles across each row of the composition

The titles in a row line up when every plot has the same
distance between its title block and its panel. Equalising
the plot_title_clearance inserts the compensating space below
the title block, so a plot matches its neighbours' top
legends, facet strips or top axes without its title moving.
"""

def plot_title_clearance(s):
return s.plot_title_clearance

_align(self.top_spaces, plot_title_clearance, "plot_title_alignment")

for tree in self.sub_compositions:
tree.align_plot_titles()

def resize_widths(self):
"""
Resize the widths of the plots & panels in the composition
Expand Down
32 changes: 16 additions & 16 deletions plotnine/_mpl/layout_manager/_plot_side_space.py
Original file line number Diff line number Diff line change
Expand Up @@ -454,16 +454,6 @@ def _calculate(self):
if adjustment > 0:
self.plot_margin += adjustment

@property
def axis_title_clearance(self) -> float:
"""
Axis-title-to-panel clearance, excluding any facet strip
"""
# The strip sits outside the axis title's alignment band, so it
# does not count toward the title-to-panel clearance used to
# align axis titles across a composition.
return super().axis_title_clearance - self.strip_text

@property
def offset(self):
"""
Expand Down Expand Up @@ -544,6 +534,14 @@ class top_space(_plot_side_space):
plot_subtitle_margin_top: float = 0
plot_subtitle: float = 0
plot_subtitle_margin_bottom: float = 0
plot_title_alignment: float = 0
"""
Space added to align the plot title with others in a composition

This value is calculated during the layout process. The amount is
the difference between the largest and smallest plot_title_clearance
among the items in the composition.
"""
legend: float = 0
legend_box_spacing: float = 0
axis_title_margin_top: float = 0
Expand Down Expand Up @@ -621,14 +619,16 @@ def _calculate(self):
self.plot_margin += adjustment

@property
def axis_title_clearance(self) -> float:
def plot_title_clearance(self) -> float:
"""
Axis-title-to-panel clearance, excluding any facet strip
The distance between the plot title block and the panel

Everything between the title & subtitle and the panel — legend,
axis title, axis text, ticks and facet strip — counts toward
this distance. When it is equal across the plots in a row of a
composition, their titles sit at the same height.
"""
# The strip sits outside the axis title's alignment band, so it
# does not count toward the title-to-panel clearance used to
# align axis titles across a composition.
return super().axis_title_clearance - self.strip_text
return self.total - self.sum_upto("plot_title_alignment")

@property
def offset(self) -> float:
Expand Down
Binary file modified tests/baseline_images/test_plot_composition/facets.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
18 changes: 18 additions & 0 deletions tests/test_plot_composition.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,24 @@ def test_facets():
assert p == "facets"


def test_facets_title_align():
# The titles of a faceted plot (strip above the panel) and a plain
# plot should be at the same height.
p1 = plot.purple + g.points + facet_wrap("cat")
p2 = plot.brown + g.points + legend.bottom
p = p1 | p2
assert p == "facets_title_align"


def test_facets_legend_title_align():
# The titles of a faceted plot (strip above the panel) and a plain
# plot with a legend at the top should be at the same height.
p1 = plot.purple + g.points + facet_wrap("cat")
p2 = plot.brown + g.points + legend.top
p = p1 | p2
assert p == "facets_legend_title_align"


def test_complex_composition():
p1 = plot.red
p2 = plot.green + g.points + rotate.plot_title + legend.bottom
Expand Down
Loading