Repository: enthought/chaco

Issue:
Replace use of ChacoPlotItem with just Item and ComponentEditor
There are a few examples which use `ChacoPlotItem` instead of the enable `ComponentEditor`. It is possible that the `ComponentEditor` came after the chaco `ChacoPlotItem` and related objects. I think we can rewrite the examples to use `ComponentEditor` instead of the chaco classes.

See related #640 


Discussion:
FTR, I spent a little bit of time on this and this is not a trivial task.

Resolving patch:
diff --git a/chaco/chaco_plot_editor.py b/chaco/chaco_plot_editor.py
deleted file mode 100644
--- a/chaco/chaco_plot_editor.py
+++ /dev/null
@@ -1,457 +0,0 @@
-# Enthought library imports
-from traits.etsconfig.api import ETSConfig
-from enable.api import (
-    black_color_trait,
-    LineStyle,
-    ColorTrait,
-    white_color_trait,
-    MarkerTrait,
-    Window,
-)
-from enable.trait_defs.ui.api import RGBAColorEditor
-from kiva.trait_defs.kiva_font_trait import KivaFont
-from traits.api import Enum, Str, Range, Trait, Tuple, Bool, Int, Any, Property
-from traitsui.api import EditorFactory, Item
-
-# Toolkit dependent imports
-from traitsui.toolkit import toolkit_object
-
-Editor = toolkit_object("editor:Editor")
-
-# Local relative imports
-from .axis import PlotAxis
-from .plot_containers import OverlayPlotContainer
-from .plot_factory import (
-    create_line_plot,
-    create_scatter_plot,
-    add_default_grids,
-    add_default_axes,
-)
-from .plot_label import PlotLabel
-
-# Somewhat unorthodox...
-from .tools.api import PanTool, ZoomTool
-
-# -------------------------------------------------------------------------------
-#  Trait definitions:
-# -------------------------------------------------------------------------------
-
-#: Range of values for an axis.
-AxisRange = Tuple((0.0, 1.0, 0.01), labels=["Low", "High", "Step"], cols=3)
-
-#: Range of axis bounds.
-AxisBounds = Tuple((0.0, 1.0), labels=["Min", "Max"], cols=2)
-
-#: Range for the height and width for the plot widget.
-PlotSize = Range(50, 1000, 180)
-
-#: Range of plot line weights.
-LineWeight = Range(1, 9, 3)
-
-#: The color editor to use for various color traits.
-color_editor = RGBAColorEditor()
-
-
-USE_DATA_UPDATE = 1
-
-
-class ChacoPlotItem(Item):
-    """A TraitsUI Item for a Chaco plot, for use in TraitsUI Views.
-
-    NOTE: ComponentEditor is preferred over this class, as it is more flexible.
-    """
-
-    #: Name of the trait that references the index data source.
-    index = Str
-    #: Name of the trait that references the value data source.
-    value = Str
-    #: Title of the plot (overlaid on the plot container).
-    title = Str("Plot Editor")
-
-    #: Bounds of the x-axis, used if **x_auto** is False.
-    x_bounds = AxisBounds
-    #: Set the x-axis bounds automatically?
-    x_auto = Bool(True)
-    #: Bounds of the y-axis, used if **y_auto** is False.
-    y_bounds = AxisBounds
-    #: Set the y-axis bounds automatically?
-    y_auto = Bool(True)
-
-    #: The orientation of the index axis.
-    orientation = Enum("h", "v")
-
-    # If these are None, then the index/value trait names are used
-
-    #: Label of the x-axis; if None, the **index** name is used.
-    x_label = Trait(None, None, Str)
-    #: Name of the trait on the object containing the label of the x-axis.
-    #: This takes precedence over **x_label**.
-    x_label_trait = Trait(None, None, Str)
-    #: Font for the label of the x-axis.
-    x_label_font = KivaFont("modern 10")
-    #: Color of the label of the x-axis.
-    x_label_color = black_color_trait
-    #: Label of the y-axis; if None, the **value** name is used.
-    y_label = Trait(None, None, Str)
-    #: Name of the trait on the object containing the label of the y-axis.
-    #: This takes precedence over **y_label**.
-    y_label_trait = Trait(None, None, Str)
-    #: Font for the label of the y-axis.
-    y_label_font = KivaFont("modern 10")
-    #: Color of the label of the y-axis.
-    y_label_color = black_color_trait
-
-    # General plot properties
-
-    #: Foreground olor of the plot.
-    color = ColorTrait("blue")
-    #: Background color of the plot.
-    bgcolor = white_color_trait
-    #: Background color of the plot (deprecated).
-    bg_color = Property  # backwards compatibility; deprecated
-    #: Color of the background padding.
-    padding_bg_color = ColorTrait("sys_window")
-
-    # Border properties
-
-    #: Width of the plot border
-    border_width = Int(1)
-    #: Is the border visible?
-    border_visible = Bool(False)
-    #: Line style of the border.
-    border_dash = LineStyle
-    #: Color of the border.
-    border_color = black_color_trait
-
-    #: The type of the plot.
-    type = Enum("line", "scatter")
-    #: The type of the plot as a string.
-    type_trait = Str
-
-    # plot-specific properties.  These might not apply to all plot types.
-
-    #: Type of marker (for plots that use markers).
-    marker = MarkerTrait
-    #: Size of marker (for plots that use markers).
-    marker_size = Int(4)
-    #: Marker outline color (for plots that user markers).
-    outline_color = black_color_trait
-
-    def __init__(self, index, value, type="line", **traits):
-        self.index = index
-        self.value = value
-        self.type = type
-        self.name = index
-        super(ChacoPlotItem, self).__init__(**traits)
-
-        self.editor = ChacoEditorFactory()
-
-        self.editor.plotitem = self
-
-    def _set_bg_color(self, val):
-        self.bgcolor = val
-
-    def _get_bg_color(self):
-        return self.bgcolor
-
-
-class ChacoEditorFactory(EditorFactory):
-    """Editor factory for plot editors."""
-
-    # ---------------------------------------------------------------------------
-    #  Trait definitions:
-    # ---------------------------------------------------------------------------
-
-    # Width of the plot editor.
-    width = PlotSize
-    # Height of the plot editor.
-    height = PlotSize
-    # The ChacoPlotItem associated with this factory.
-    plotitem = Any
-
-    # ---------------------------------------------------------------------------
-    #  'Editor' factory methods:
-    # ---------------------------------------------------------------------------
-
-    def simple_editor(self, ui, object, name, description, parent):
-        return ChacoPlotEditor(
-            parent,
-            factory=self,
-            ui=ui,
-            object=object,
-            name=name,
-            description=description,
-        )
-
-    def text_editor(self, ui, object, name, description, parent):
-        return ChacoPlotEditor(
-            parent,
-            factory=self,
-            ui=ui,
-            object=object,
-            name=name,
-            description=description,
-        )
-
-    def readonly_editor(self, ui, object, name, description, parent):
-        return ChacoPlotEditor(
-            parent,
-            factory=self,
-            ui=ui,
-            object=object,
-            name=name,
-            description=description,
-        )
-
-
-class ChacoPlotEditor(Editor):
-    """TraitsUI editor for displaying trait values in a Chaco plot."""
-
-    # ---------------------------------------------------------------------------
-    #  Finishes initializing the editor by creating the underlying toolkit
-    #  widget:
-    # ---------------------------------------------------------------------------
-
-    def init(self, parent):
-        """Finishes initializing the editor by creating the underlying toolkit
-        widget.
-        """
-        factory = self.factory
-        plotitem = factory.plotitem
-
-        container = OverlayPlotContainer(
-            padding=50,
-            fill_padding=True,
-            bgcolor=plotitem.padding_bg_color,
-            use_backbuffer=True,
-        )
-
-        if plotitem.title != "":
-            container.overlays.append(
-                PlotLabel(
-                    plotitem.title, component=container, overlay_position="top"
-                )
-            )
-
-        self._container = container
-        window = Window(parent, component=container)
-
-        # FIXME: Toolkit specifc code here. The AbstractWindow should have a
-        # 'set size' method as part of its API.
-        self.control = control = window.control
-        if ETSConfig.toolkit == "wx":
-            control.SetSize((factory.width, factory.height))
-        elif ETSConfig.toolkit == "qt4":
-            control.resize(factory.width, factory.height)
-        else:
-            raise NotImplementedError
-
-        # Attach listeners to the object's traits appropriately so we can
-        # update the plot when they change.  For the _update_axis_grids()
-        # callback, we have to wrap it in a lambda to keep traits from
-        # inferring the calling convention based on introspecting the argument
-        # list.
-        object = self.object
-        if USE_DATA_UPDATE == 1:
-            for name in (plotitem.index, plotitem.value):
-                object.observe(self._update_data, name)
-        for name in (plotitem.x_label_trait, plotitem.y_label_trait):
-            if name and getattr(object, name, None):
-                object.observe(lambda s: self._update_axis_grids(), name)
-        if plotitem.type_trait not in ("", None):
-            object.observe(self.update_editor, plotitem.type_trait)
-
-    # ---------------------------------------------------------------------------
-    #  Disposes of the contents of an editor:
-    # ---------------------------------------------------------------------------
-
-    def dispose(self):
-        """Disposes of the contents of the editor."""
-        object = self.object
-        plotitem = self.factory.plotitem
-
-        if USE_DATA_UPDATE == 1:
-            for name in (plotitem.index, plotitem.value):
-                object.observe(self._update_data, name, remove=True)
-        for name in (plotitem.type_trait,):
-            object.observe(self.update_editor, name, remove=True)
-        self._destroy_plot()
-        super(ChacoPlotEditor, self).dispose()
-
-    def _destroy_plot(self):
-        if self._container and self._plot:
-            plot = self._plot
-            del plot.index._data
-            del plot.index._cached_mask
-            del plot.value._data
-            del plot.value._cached_mask
-            self._container.remove(plot)
-            self._plot = None
-            plot.index = None
-            plot.value = None
-
-    # ---------------------------------------------------------------------------
-    #  Updates the editor when the object trait changes externally to the editor:
-    # ---------------------------------------------------------------------------
-
-    def update_editor(self, event=None):
-        """Updates the editor when the object trait changes externally to the
-        editor.
-        """
-
-        factory = self.factory
-        if factory is None:
-            return
-        plotitem = factory.plotitem
-
-        # Remove the old plot
-        if self._plot is not None:
-            self._destroy_plot()
-
-        try:
-            x_values = getattr(self.object, plotitem.index)
-            y_values = getattr(self.object, plotitem.value)
-        except:
-            self._container.request_redraw()
-            return
-
-        if plotitem.type_trait != "":
-            plot_type = getattr(self.object, plotitem.type_trait)
-        else:
-            plot_type = plotitem.type
-
-        if plotitem.x_auto == True:
-            index_bounds = None
-        else:
-            index_bounds = plotitem.x_bounds
-
-        if plotitem.y_auto == True:
-            value_bounds = None
-        else:
-            value_bounds = plotitem.y_bounds
-
-        # Class-level attribute mapping different plot_type strings to methods for
-        # creating different types of plots
-        plot_creator_map = {
-            "line": self._create_line_plot,
-            "scatter": self._create_scatter_plot,
-        }
-
-        if plot_type in plot_creator_map:
-            plot = plot_creator_map[plot_type](
-                plotitem,
-                (x_values, y_values),
-                index_bounds=index_bounds,
-                value_bounds=value_bounds,
-                orientation=plotitem.orientation,
-            )
-        else:
-            raise RuntimeError(
-                "Unknown plot type '%s' in ChacoPlotEditor." % plot_type
-            )
-
-        self._set_basic_properties(plot, plotitem)
-
-        self._add_axis_grids(plot, plotitem)
-
-        self._plot = plot
-        self._container.add(plot)
-        self._container.request_redraw()
-
-    def _update_data(self, event):
-        """Updates the editor when the object trait changes externally to the
-        editor.
-        """
-        if self._plot is None:
-            self.update_editor()
-        else:
-            x_values = getattr(self.object, self.factory.plotitem.index)
-            y_values = getattr(self.object, self.factory.plotitem.value)
-            self._plot.index.set_data(x_values)
-            self._plot.value.set_data(y_values)
-
-    def _set_basic_properties(self, plot, plotitem):
-        for attr in (
-            "color",
-            "bgcolor",
-            "border_visible",
-            "border_width",
-            "border_dash",
-            "border_color",
-        ):
-            setattr(plot, attr, getattr(plotitem, attr))
-
-    def _create_line_plot(self, plotitem, values, **kwargs):
-        plot = create_line_plot(values, **kwargs)
-        return plot
-
-    def _create_scatter_plot(self, plotitem, values, **kwargs):
-        plot = create_scatter_plot(values, **kwargs)
-        for attr in ("marker", "marker_size", "outline_color"):
-            setattr(plot, attr, getattr(plotitem, attr))
-        return plot
-
-    def _add_axis_grids(self, new_plot, plotitem):
-        value_axis, index_axis = add_default_axes(
-            new_plot, orientation=plotitem.orientation
-        )
-        add_default_grids(new_plot)
-        new_plot.tools.append(PanTool(new_plot))
-        zoom = ZoomTool(component=new_plot, tool_mode="box", always_on=False)
-        new_plot.overlays.append(zoom)
-
-        # Update the titles and labels
-        self._update_axis_grids(new_plot, plotitem)
-
-    def _update_axis_grids(self, plot=None, plotitem=None):
-        if self.factory is None:
-            return
-
-        if plot is None:
-            if self._plot is None:
-                return
-            else:
-                plot = self._plot
-        if plotitem is None:
-            plotitem = self.factory.plotitem
-
-        if plotitem.x_label_trait is not None:
-            htitle = getattr(self.object, plotitem.x_label_trait)
-        elif plotitem.x_label is not None:
-            htitle = plotitem.x_label
-        else:
-            htitle = plotitem.index
-
-        if plotitem.y_label_trait is not None:
-            vtitle = getattr(self.object, plotitem.y_label_trait)
-        elif plotitem.y_label is not None:
-            vtitle = plotitem.y_label
-        else:
-            vtitle = plotitem.value
-
-        if plotitem.orientation == "v":
-            htitle, vtitle = vtitle, htitle
-        plot.x_axis.title = htitle
-        plot.y_axis.title = vtitle
-
-        # This is sort of crappy.. since we are using BaseXYPlots and not
-        # Plot/DataViews, we actually can't easily get references to the plot's
-        # index and value axes.  So we have to search through the underlays for
-        # PlotAxis instances whose ranges match the index and value ranges.
-        for axis in plot.underlays + plot.overlays:
-            if (
-                isinstance(axis, PlotAxis)
-                and axis.mapper.range is plot.index_range
-            ):
-                axis.title_font = plotitem.x_label_font
-                axis.title_color = plotitem.x_label_color
-
-        for axis in plot.underlays + plot.overlays:
-            if (
-                isinstance(axis, PlotAxis)
-                and axis.mapper.range is plot.value_range
-            ):
-                axis.title_font = plotitem.y_label_font
-                axis.title_color = plotitem.y_label_color
-
-        plot.request_redraw()
diff --git a/examples/demo/advanced/data_stream.py b/examples/demo/advanced/data_stream.py
--- a/examples/demo/advanced/data_stream.py
+++ b/examples/demo/advanced/data_stream.py
@@ -15,6 +15,7 @@
 import numpy as np
 
 # Enthought imports
+from enable.api import ComponentEditor
 from traits.api import (
     Array,
     Callable,
@@ -23,13 +24,14 @@
     HasTraits,
     Instance,
     Int,
+    observe,
     Trait,
 )
-from traitsui.api import Group, HGroup, Item, View, spring, Handler
+from traitsui.api import Group, HGroup, Item, UItem, View, spring, Handler
 from pyface.timer.api import Timer
 
 # Chaco imports
-from chaco.chaco_plot_editor import ChacoPlotItem
+from chaco.api import ArrayPlotData, Plot
 
 
 class Viewer(HasTraits):
@@ -38,30 +40,44 @@ class Viewer(HasTraits):
     Chaco plot.
     """
 
-    index = Array
+    index = Array()
 
-    data = Array
+    data = Array()
 
     plot_type = Enum("line", "scatter")
 
-    view = View(
-        ChacoPlotItem(
-            "index",
-            "data",
-            type_trait="plot_type",
-            resizable=True,
-            x_label="Time",
-            y_label="Signal",
-            color="blue",
-            bgcolor="white",
-            border_visible=True,
-            border_width=1,
-            padding_bg_color="lightgray",
-            width=800,
-            height=380,
-            marker_size=2,
-            show_label=False,
-        ),
+    plot = Instance(Plot)
+
+    def _plot_default(self):
+        plot = Plot(ArrayPlotData(x=self.index, y=self.data))
+        plot.x_axis.title = "Time"
+        plot.y_axis.title = "Signal"
+
+        plot.plot(
+            ("x", "y"), type=self.plot_type, name=self.plot_type, color="blue"
+        )
+
+        return plot
+
+    @observe("index,data")
+    def _update_plot_data(self, event):
+        if event.name == "index":
+            self.plot.data.set_data("x", self.index)
+        else:
+            self.plot.data.set_data("y", self.data)
+
+    @observe("plot_type")
+    def _update_plot_type(self, event):
+        old_plot_type, new_plot_type = event.old, event.new
+
+        self.plot.delplot(old_plot_type)
+        self.plot.plot(
+            ("x", "y"), type=new_plot_type, name=new_plot_type, color="blue"
+        )
+        self.plot.invalidate_and_redraw()
+
+    traits_view = View(
+        UItem("plot", editor=ComponentEditor()),
         HGroup(spring, Item("plot_type", style="custom"), spring),
         resizable=True,
         buttons=["OK"],
@@ -117,7 +133,7 @@ def timer_tick(self, *args):
         # grab the existing data, truncate it, and append the new point.
         # This isn't the most efficient thing in the world but it works.
         cur_data = self.viewer.data
-        new_data = np.hstack((cur_data[-self.max_num_points + 1 :], [new_val]))
+        new_data = np.hstack((cur_data[-self.max_num_points + 1:], [new_val]))
         new_index = np.arange(
             self.num_ticks - len(new_data) + 1, self.num_ticks + 0.01
         )
diff --git a/examples/demo/basic/traits_editor.py b/examples/demo/basic/traits_editor.py
deleted file mode 100644
--- a/examples/demo/basic/traits_editor.py
+++ /dev/null
@@ -1,88 +0,0 @@
-""" 1D Function plotter.
-
-This example creates a simple 1D function examiner, illustrating the use of
-ChacoPlotEditors for displaying simple plot relations, as well as TraitsUI
-integration. Any 1D numpy/scipy.special function should work in the function
-text box.
- - Left-drag pans the plot.
- - Mousewheel up and down zooms the plot in and out.
- - Pressing "z" brings up the Zoom Box, and you can click-drag a rectangular
-   region to zoom.  If you use a sequence of zoom boxes, pressing alt-left-arrow
-   and alt-right-arrow moves you forwards and backwards through the "zoom
-   history".
-"""
-
-# Major library imports
-from numpy import linspace, pi
-
-# Enthought library imports
-from traits.api import Array, Dict, Enum, HasTraits, Str
-from traitsui.api import Item, View
-
-# Chaco imports
-from chaco.chaco_plot_editor import ChacoPlotEditor, ChacoPlotItem
-
-
-class Foo(HasTraits):
-
-    # Public Traits
-    xdata = Array
-    plot_type = Enum("scatter", "line")
-    eq = Str("sin(x)")
-
-    # Default TraitsUI view
-    traits_view = View(
-        ChacoPlotItem(
-            "xdata",
-            "_ydata",
-            type_trait="plot_type",
-            # Basic axis and label properties
-            show_label=False,
-            resizable=True,
-            orientation="h",
-            x_label="Index data",
-            y_label="Value data",
-            # Plot properties
-            color="green",
-            bgcolor="white",
-            # Specific to scatter plot
-            marker="circle",
-            marker_size=2,
-            outline_color="none",
-            # Border, padding properties
-            border_visible=True,
-            border_width=1,
-            padding_bg_color="lightgray",
-        ),
-        Item("plot_type"),
-        Item("eq"),
-        resizable=True,
-        width=500,
-        height=500,
-    )
-
-    # Private Traits
-    _d = Dict
-    _ydata = Array
-
-    def __init__(self, **kwtraits):
-        super(Foo, self).__init__(**kwtraits)
-        self._d = dict(x=self.xdata)
-        exec("from scipy import *", self._d)
-        exec("from scipy.special import *", self._d)
-        self._ydata = eval(self.eq, self._d)
-
-    def _eq_changed(self, old, new):
-        try:
-            self._ydata = eval(new, self._d)
-        except:
-            pass
-
-
-# ===============================================================================
-# # demo object that is used by the demo.py application.
-# ===============================================================================
-demo = Foo(xdata=linspace(-2 * pi, 2 * pi, 100), eq="sin(x)")
-
-if __name__ == "__main__":
-    demo.edit_traits(kind="modal")


Test patch: