Skip to content
Closed
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
309 changes: 40 additions & 269 deletions chaco/plot_containers.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

enable needs to expose these via the api module.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we might want to move DEFAULT_DRAWING_ORDER somewhere else/somewhere better e.g. chaco.base instead of leaving it in chaco.plot_component



__all__ = [
Expand 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:
Expand All @@ -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.
Expand All @@ -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):
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The 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 BasePlotContainer completely.

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
Expand All @@ -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
Expand Down