-
Notifications
You must be signed in to change notification settings - Fork 98
[WIP] Refactor chaco plot containers #674
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
ec33d33
7c5a4e4
fb701ab
1fbda3e
4a2926d
6086219
08ffe73
8200d2a
da794ca
4887dee
9b63c1b
1e16534
c1cc082
76a88df
8b6505d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -19,27 +19,25 @@ | |
| Array, | ||
| Either, | ||
| Enum, | ||
| Float, | ||
| Instance, | ||
| List, | ||
| Property, | ||
| String, | ||
| Str, | ||
| Trait, | ||
| Tuple, | ||
| Int, | ||
| ) | ||
| from enable.simple_layout import ( | ||
| simple_container_get_preferred_size, | ||
| simple_container_do_layout, | ||
| ) | ||
| from enable.api import Container, OverlayContainer | ||
| from enable.stacked_container import HStackedContainer, VStackedContainer | ||
| from enable.stacked_layout import stack_layout, stacked_preferred_size | ||
|
|
||
| try: | ||
| from enable.api import ConstraintsContainer | ||
| except ImportError: | ||
| ConstraintsContainer = None | ||
|
|
||
| # Local relative imports | ||
| from .base_plot_container import BasePlotContainer | ||
| from .plot_component import DEFAULT_DRAWING_ORDER | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. we might want to move |
||
|
|
||
|
|
||
| __all__ = [ | ||
|
|
@@ -49,17 +47,6 @@ | |
| "GridPlotContainer", | ||
| ] | ||
|
|
||
| DEFAULT_DRAWING_ORDER = [ | ||
| "background", | ||
| "image", | ||
| "underlay", | ||
| "plot", | ||
| "selection", | ||
| "border", | ||
| "annotation", | ||
| "overlay", | ||
| ] | ||
|
|
||
|
|
||
| # Enable constraints layout is only available if kiwisolver is installed! | ||
| if ConstraintsContainer is not None: | ||
|
|
@@ -72,18 +59,26 @@ class ConstraintsPlotContainer(ConstraintsContainer): | |
| "background", "image", "underlay", "plot" | ||
| ) | ||
| draw_order = Instance(list, args=(DEFAULT_DRAWING_ORDER,)) | ||
| draw_layer = String("plot") | ||
| draw_layer = Str("plot") | ||
| # !! Bits copied from BasePlotContainer !! | ||
|
|
||
| __all__.append("ConstraintsPlotContainer") | ||
|
|
||
|
|
||
| class OverlayPlotContainer(BasePlotContainer): | ||
| class OverlayPlotContainer(OverlayContainer): | ||
| """ | ||
| A plot container that stretches all its components to fit within its | ||
| space. All of its components must therefore be resizable. | ||
| """ | ||
|
|
||
| #: Redefine the container layers to name the main layer as "plot" instead | ||
| #: of the Enable default of "mainlayer" | ||
| container_under_layers = Tuple("background", "image", "underlay", "plot") | ||
|
|
||
| #: Redefine the draw layer to "plot" instead of "mainlayer" | ||
| draw_layer = Str("plot") | ||
|
|
||
| #: Redefine the draw order | ||
| draw_order = Instance(list, args=(DEFAULT_DRAWING_ORDER,)) | ||
|
|
||
| #: Do not use an off-screen backbuffer. | ||
|
|
@@ -92,280 +87,48 @@ class OverlayPlotContainer(BasePlotContainer): | |
| # Cache (width, height) of the container's preferred size. | ||
| _cached_preferred_size = Tuple | ||
|
|
||
| def get_preferred_size(self, components=None): | ||
| """Returns the size (width,height) that is preferred for this component. | ||
|
|
||
| Overrides PlotComponent | ||
| """ | ||
| return simple_container_get_preferred_size(self, components=components) | ||
|
|
||
| def _do_layout(self): | ||
| """Actually performs a layout (called by do_layout()).""" | ||
| simple_container_do_layout(self) | ||
|
|
||
|
|
||
| class StackedPlotContainer(BasePlotContainer): | ||
| """ | ||
| Base class for 1-D stacked plot containers, both horizontal and vertical. | ||
| """ | ||
|
|
||
| draw_order = Instance(list, args=(DEFAULT_DRAWING_ORDER,)) | ||
|
|
||
| # The dimension along which to stack components that are added to | ||
| # this container. | ||
| stack_dimension = Enum("h", "v", transient=True) | ||
|
|
||
| # The "other" dimension, i.e., the dual of the stack dimension. | ||
| other_dimension = Enum("v", "h", transient=True) | ||
|
|
||
| # The index into obj.position and obj.bounds that corresponds to | ||
| # **stack_dimension**. This is a class-level and not an instance-level | ||
| # attribute. It must be 0 or 1. | ||
| stack_index = 0 | ||
|
|
||
| def get_preferred_size(self, components=None): | ||
| """Returns the size (width,height) that is preferred for this component. | ||
|
|
||
| Overrides PlotComponent. | ||
| """ | ||
| if self.fixed_preferred_size is not None: | ||
| self._cached_preferred_size = self.fixed_preferred_size | ||
| return self.fixed_preferred_size | ||
|
|
||
| if self.resizable == "": | ||
| self._cached_preferred_size = self.outer_bounds[:] | ||
| return self.outer_bounds | ||
|
|
||
| if components is None: | ||
| components = self.components | ||
|
|
||
| ndx = self.stack_index | ||
| other_ndx = 1 - ndx | ||
|
|
||
| no_visible_components = True | ||
| total_size = 0 | ||
| max_other_size = 0 | ||
| for component in components: | ||
| if not self._should_layout(component): | ||
| continue | ||
|
|
||
| no_visible_components = False | ||
|
|
||
| pref_size = component.get_preferred_size() | ||
| total_size += pref_size[ndx] + self.spacing | ||
| if pref_size[other_ndx] > max_other_size: | ||
| max_other_size = pref_size[other_ndx] | ||
|
|
||
| if total_size >= self.spacing: | ||
| total_size -= self.spacing | ||
|
|
||
| if (self.stack_dimension not in self.resizable) and ( | ||
| self.stack_dimension not in self.fit_components | ||
| ): | ||
| total_size = self.bounds[ndx] | ||
| elif no_visible_components or (total_size == 0): | ||
| total_size = self.default_size[ndx] | ||
|
|
||
| if (self.other_dimension not in self.resizable) and ( | ||
| self.other_dimension not in self.fit_components | ||
| ): | ||
| max_other_size = self.bounds[other_ndx] | ||
| elif no_visible_components or (max_other_size == 0): | ||
| max_other_size = self.default_size[other_ndx] | ||
|
|
||
| if ndx == 0: | ||
| self._cached_preferred_size = ( | ||
| total_size + self.hpadding, | ||
| max_other_size + self.vpadding, | ||
| ) | ||
| else: | ||
| self._cached_preferred_size = ( | ||
| max_other_size + self.hpadding, | ||
| total_size + self.vpadding, | ||
| ) | ||
|
|
||
| return self._cached_preferred_size | ||
|
|
||
| def _do_stack_layout(self, components, align): | ||
| """Helper method that does the actual work of layout.""" | ||
|
|
||
| size = list(self.bounds) | ||
| if self.fit_components != "": | ||
| self.get_preferred_size() | ||
| if "h" in self.fit_components: | ||
| size[0] = self._cached_preferred_size[0] - self.hpadding | ||
| if "v" in self.fit_components: | ||
| size[1] = self._cached_preferred_size[1] - self.vpadding | ||
|
|
||
| ndx = self.stack_index | ||
| other_ndx = 1 - ndx | ||
| other_dim = self.other_dimension | ||
|
|
||
| # Assign sizes of non-resizable components, and compute the total size | ||
| # used by them (along the stack dimension). | ||
| total_fixed_size = 0 | ||
| resizable_components = [] | ||
| size_prefs = {} | ||
| total_resizable_size = 0 | ||
|
|
||
| for component in components: | ||
| if not self._should_layout(component): | ||
| continue | ||
| if self.stack_dimension not in component.resizable: | ||
| total_fixed_size += component.outer_bounds[ndx] | ||
| else: | ||
| preferred_size = component.get_preferred_size() | ||
| size_prefs[component] = preferred_size | ||
| total_resizable_size += preferred_size[ndx] | ||
| resizable_components.append(component) | ||
|
|
||
| new_bounds_dict = {} | ||
|
|
||
| # Assign sizes of all the resizable components along the stack dimension | ||
| if resizable_components: | ||
| space = self.spacing * (len(self.components) - 1) | ||
| avail_size = size[ndx] - total_fixed_size - space | ||
| if total_resizable_size > 0: | ||
| scale = avail_size / float(total_resizable_size) | ||
| for component in resizable_components: | ||
| tmp = list(component.outer_bounds) | ||
| tmp[ndx] = int(size_prefs[component][ndx] * scale) | ||
| new_bounds_dict[component] = tmp | ||
| else: | ||
| each_size = int(avail_size / len(resizable_components)) | ||
| for component in resizable_components: | ||
| tmp = list(component.outer_bounds) | ||
| tmp[ndx] = each_size | ||
| new_bounds_dict[component] = tmp | ||
|
|
||
| # Loop over all the components, assigning position and computing the | ||
| # size in the other dimension and its position. | ||
| cur_pos = 0 | ||
| for component in components: | ||
| if not self._should_layout(component): | ||
| continue | ||
|
|
||
| position = list(component.outer_position) | ||
| position[ndx] = cur_pos | ||
|
|
||
| bounds = new_bounds_dict.get( | ||
| component, list(component.outer_bounds) | ||
| ) | ||
| cur_pos += bounds[ndx] + self.spacing | ||
|
|
||
| if (bounds[other_ndx] > size[other_ndx]) or ( | ||
| other_dim in component.resizable | ||
| ): | ||
| # If the component is resizable in the other dimension or it exceeds the | ||
| # container bounds, set it to the maximum size of the container | ||
|
|
||
| position[other_ndx] = 0 | ||
| bounds[other_ndx] = size[other_ndx] | ||
| else: | ||
| position[other_ndx] = 0 | ||
| if align == "min": | ||
| pass | ||
| elif align == "max": | ||
| position[other_ndx] = size[other_ndx] - bounds[other_ndx] | ||
| elif align == "center": | ||
| position[other_ndx] = ( | ||
| size[other_ndx] - bounds[other_ndx] | ||
| ) / 2.0 | ||
|
|
||
| component.outer_position = position | ||
| component.outer_bounds = bounds | ||
| component.do_layout() | ||
|
|
||
| ### Persistence ########################################################### | ||
|
|
||
| # PICKLE FIXME: blocked with _pickles, but not sure that was correct. | ||
| def __getstate__(self): | ||
| state = super().__getstate__() | ||
| if "stack_index" in state: | ||
| del state["stack_index"] | ||
| return state | ||
|
|
||
|
|
||
| class HPlotContainer(StackedPlotContainer): | ||
| class HPlotContainer(HStackedContainer): | ||
| """ | ||
| A plot container that stacks all of its components horizontally. Resizable | ||
| components share the free space evenly. All components are stacked from | ||
| according to **stack_order* in the same order that they appear in the | ||
| **components** list. | ||
| """ | ||
|
|
||
| draw_order = Instance(list, args=(DEFAULT_DRAWING_ORDER,)) | ||
| #: Redefine the container layers to name the main layer as "plot" instead | ||
| #: of the Enable default of "mainlayer" | ||
| container_under_layers = Tuple("background", "image", "underlay", "plot") | ||
|
|
||
| #: The order in which components in the plot container are laid out. | ||
| stack_order = Enum("left_to_right", "right_to_left") | ||
| #: Redefine the draw layer to "plot" instead of "mainlayer" | ||
| draw_layer = Str("plot") | ||
|
|
||
| #: The amount of space to put between components. | ||
| spacing = Float(0.0) | ||
| #: Redefine the draw order | ||
| draw_order = Instance(list, args=(DEFAULT_DRAWING_ORDER,)) | ||
|
|
||
| #: The vertical alignment of objects that don't span the full height. | ||
| valign = Enum("bottom", "top", "center") | ||
|
|
||
| _cached_preferred_size = Tuple(transient=True) | ||
|
|
||
| def _do_layout(self): | ||
| """Actually performs a layout (called by do_layout()).""" | ||
| if self.stack_order == "left_to_right": | ||
| components = self.components | ||
| else: | ||
| components = self.components[::-1] | ||
|
|
||
| if self.valign == "bottom": | ||
| align = "min" | ||
| elif self.valign == "center": | ||
| align = "center" | ||
| else: | ||
| align = "max" | ||
|
|
||
| return self._do_stack_layout(components, align) | ||
|
|
||
|
|
||
| class VPlotContainer(StackedPlotContainer): | ||
| class VPlotContainer(VStackedContainer): | ||
| """ | ||
| A plot container that stacks plot components vertically. | ||
| """ | ||
|
|
||
| draw_order = Instance(list, args=(DEFAULT_DRAWING_ORDER,)) | ||
|
|
||
| #: Overrides StackedPlotContainer. | ||
| stack_dimension = "v" | ||
| #: Overrides StackedPlotContainer. | ||
| other_dimension = "h" | ||
| #: Overrides StackedPlotContainer. | ||
| stack_index = 1 | ||
|
|
||
| # VPlotContainer attributes | ||
| #: Redefine the container layers to name the main layer as "plot" instead | ||
| #: of the Enable default of "mainlayer" | ||
| container_under_layers = Tuple("background", "image", "underlay", "plot") | ||
|
|
||
| #: The horizontal alignment of objects that don't span the full width. | ||
| halign = Enum("left", "right", "center") | ||
| #: Redefine the draw layer to "plot" instead of "mainlayer" | ||
| draw_layer = Str("plot") | ||
|
|
||
| #: The order in which components in the plot container are laid out. | ||
| stack_order = Enum("bottom_to_top", "top_to_bottom") | ||
|
|
||
| #: The amount of space to put between components. | ||
| spacing = Float(0.0) | ||
|
|
||
| def _do_layout(self): | ||
| """Actually performs a layout (called by do_layout()).""" | ||
| if self.stack_order == "bottom_to_top": | ||
| components = self.components | ||
| else: | ||
| components = self.components[::-1] | ||
| if self.halign == "left": | ||
| align = "min" | ||
| elif self.halign == "center": | ||
| align = "center" | ||
| else: | ||
| align = "max" | ||
|
|
||
| return self._do_stack_layout(components, align) | ||
| #: Redefine the draw order | ||
| draw_order = Instance(list, args=(DEFAULT_DRAWING_ORDER,)) | ||
|
|
||
|
|
||
| class GridPlotContainer(BasePlotContainer): | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Note that we are now in a place where we can get rid of |
||
| class GridPlotContainer(Container): | ||
| """A GridPlotContainer consists of rows and columns in a tabular format. | ||
|
|
||
| Each cell's width is the same as all other cells in its column, and each | ||
|
|
@@ -376,6 +139,14 @@ class GridPlotContainer(BasePlotContainer): | |
| **shape** trait. | ||
| """ | ||
|
|
||
| #: Redefine the container layers to name the main layer as "plot" instead | ||
| #: of the Enable default of "mainlayer" | ||
| container_under_layers = Tuple("background", "image", "underlay", "plot") | ||
|
|
||
| #: Redefine the draw layer to "plot" instead of "mainlayer" | ||
| draw_layer = Str("plot") | ||
|
|
||
| #: Redefine the draw order | ||
| draw_order = Instance(list, args=(DEFAULT_DRAWING_ORDER,)) | ||
|
|
||
| #: The amount of space to put on either side of each component, expressed | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
enableneeds to expose these via the api module.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ref: enthought/enable#779