More than one ArrayPlotData() in one Plot

classic Classic list List threaded Threaded
4 messages Options
Reply | Threaded
Open this post in threaded view
|

More than one ArrayPlotData() in one Plot

Martin Gustafsson
Hello,

This is an issue with the way ArrayPlotData is used to mediate between array and Chaco plots:

I have written some fairly simple HasTraits-derived classes for holding generic measurement data, for example DataXY, which has two Array() traits, along with some metadata such as units and labels. I also have a plotting class (or several) based on HasTraits, which takes a DataXY() instance as a constructor argument, and which uses both the data arrays and the metadata in the DataXY() instance to make a complete plot. Since I want the plot to update when the data inside DataXY() is renewed, DataXY() has an ArrayPlotData trait, on which it runs set_data() whenever the data arrays are renewed. This is what goes into the Chaco plot. So far it's all good.

The problem arises when I want to show several instances of DataXY in the same plot. The ArrayPlotData() is supplied to the Plot constructor rather than with each individual call to Plot().plot, which only takes named references to arrays which reside in this one ArrayPlotData() instance. That doesn't work, since each DataXY() comes with its own ArrayPlotData().

Do you have any ideas on how to resolve this?

Best wishes
Martin
_______________________________________________
Enthought-Dev mailing list
[hidden email]
https://mail.enthought.com/mailman/listinfo/enthought-dev
Reply | Threaded
Open this post in threaded view
|

Re: More than one ArrayPlotData() in one Plot

Adam Hughes
If you could supply some code, that would help a lot, but if I understand correctly from your explanation, then would this solution work:

Make a new class called MultiDataXY().  Pass to it a list of DataXY() instances.  Make a single ArrayPlotData source in MultiDataXY and contstruct your DataXY instances with this.  For example:

class MultiDataXY:
    dataxys = List()
    plotdata = Instance(ArrayPlotData)
  
    def plot(self):
       for xy in self.dataxys:
           dataxys.append(DataXY(self.plotdata))

   




On Mon, Jul 1, 2013 at 3:51 PM, Martin Gustafsson <[hidden email]> wrote:
Hello,

This is an issue with the way ArrayPlotData is used to mediate between array and Chaco plots:

I have written some fairly simple HasTraits-derived classes for holding generic measurement data, for example DataXY, which has two Array() traits, along with some metadata such as units and labels. I also have a plotting class (or several) based on HasTraits, which takes a DataXY() instance as a constructor argument, and which uses both the data arrays and the metadata in the DataXY() instance to make a complete plot. Since I want the plot to update when the data inside DataXY() is renewed, DataXY() has an ArrayPlotData trait, on which it runs set_data() whenever the data arrays are renewed. This is what goes into the Chaco plot. So far it's all good.

The problem arises when I want to show several instances of DataXY in the same plot. The ArrayPlotData() is supplied to the Plot constructor rather than with each individual call to Plot().plot, which only takes named references to arrays which reside in this one ArrayPlotData() instance. That doesn't work, since each DataXY() comes with its own ArrayPlotData().

Do you have any ideas on how to resolve this?

Best wishes
Martin
_______________________________________________
Enthought-Dev mailing list
[hidden email]
https://mail.enthought.com/mailman/listinfo/enthought-dev



--
Adam Hughes
Physics Ph.D Candidate
George Washington University

_______________________________________________
Enthought-Dev mailing list
[hidden email]
https://mail.enthought.com/mailman/listinfo/enthought-dev
Reply | Threaded
Open this post in threaded view
|

Re: More than one ArrayPlotData() in one Plot

Martin Gustafsson
Hi Adam,

Below is some code that illustrates the present situation. I'd like to extend the PlotXY class to take multiple DataXY() instances and respond to data updates in all of them.

If I make a new ArrayPlotData instance which combines the data of several DataXY():s, I'm not sure how to get the  DataXY().set_data('data', self.data) to trigger plot updates. I guess I can make the plot class listen to the data_changed events of all DataXY():s' ArrayPlotData traits, and then update a second ArrayPlotData that's internal to the plot class. If there is a less cumbersome way, I'd be interested though.

Best wishes
Martin

--------
 class DataXY(HasTraits):
    data = Array()
    plotdata = Instance(ArrayPlotData())
    def __init__(self):
        super(DataXY, self).__init__()
        self.data = np.random.rand(101)
        self.plotdata = ArrayPlotData(data=self.data)
        self.on_trait_change(self.update_plots, 'data')
    def update_plots(self):
        self.plotdata.set_data('data', self.data)
class PlotXY(HasTraits):
    data = Instance(DataXY)
    plot = Instance(Plot)
    traits_view = View(Item('plot', editor=ComponentEditor()))
    def __init__(self, data):
        super(PlotXY, self).__init__()
        self.data = data
        self.plot = Plot(data.plotdata)
        self.plot.plot('data')
        self.configure_traits()
 if __name__ == '__main__':
    d = DataXY()
    p = PlotXY(d) # Plots the initial array
    d.data = np.random.rand(101) # Plots a new set of random data
--------

2 jul 2013 kl. 01.23 skrev Adam Hughes:

> If you could supply some code, that would help a lot, but if I understand correctly from your explanation, then would this solution work:
>
> Make a new class called MultiDataXY().  Pass to it a list of DataXY() instances.  Make a single ArrayPlotData source in MultiDataXY and contstruct your DataXY instances with this.  For example:
>
> class MultiDataXY:
>     dataxys = List()
>     plotdata = Instance(ArrayPlotData)
>  
>     def plot(self):
>        for xy in self.dataxys:
>            dataxys.append(DataXY(self.plotdata))
>
>    
>
>
>
>
> On Mon, Jul 1, 2013 at 3:51 PM, Martin Gustafsson <[hidden email]> wrote:
> Hello,
>
> This is an issue with the way ArrayPlotData is used to mediate between array and Chaco plots:
>
> I have written some fairly simple HasTraits-derived classes for holding generic measurement data, for example DataXY, which has two Array() traits, along with some metadata such as units and labels. I also have a plotting class (or several) based on HasTraits, which takes a DataXY() instance as a constructor argument, and which uses both the data arrays and the metadata in the DataXY() instance to make a complete plot. Since I want the plot to update when the data inside DataXY() is renewed, DataXY() has an ArrayPlotData trait, on which it runs set_data() whenever the data arrays are renewed. This is what goes into the Chaco plot. So far it's all good.
>
> The problem arises when I want to show several instances of DataXY in the same plot. The ArrayPlotData() is supplied to the Plot constructor rather than with each individual call to Plot().plot, which only takes named references to arrays which reside in this one ArrayPlotData() instance. That doesn't work, since each DataXY() comes with its own ArrayPlotData().
>
> Do you have any ideas on how to resolve this?
>
> Best wishes
> Martin
> _______________________________________________
> Enthought-Dev mailing list
> [hidden email]
> https://mail.enthought.com/mailman/listinfo/enthought-dev
>
>
>
> --
> Adam Hughes
> Physics Ph.D Candidate
> George Washington University
> _______________________________________________
> Enthought-Dev mailing list
> [hidden email]
> https://mail.enthought.com/mailman/listinfo/enthought-dev

_______________________________________________
Enthought-Dev mailing list
[hidden email]
https://mail.enthought.com/mailman/listinfo/enthought-dev
Reply | Threaded
Open this post in threaded view
|

Re: More than one ArrayPlotData() in one Plot

Jonathan Rocher
Martin, 

Finding this thread again, I realize you never got more feedback on your code. Let me give you my 2 cents. 

My first comment would be that you shouldn't need to separate the ArrayPlotData and the Plot in different classes. There needs to be only 1 ArrayPlotData behind a Plot and I would leave both in the same class. The next question is then: what do you need the DataXY for?

I am going to assume that you need that dataXY as a data manager, that you want separated from the plotting class. Therefore I will make your dataXY.data a list of arrays instead of just 1 array. You can easily listen to changes to the elements of a List (or Dict) using a listener of the form _<list_name>_items_changed [0]. 

In my solution below, I am listening to changes to the items in this list using the on_trait_change decorator [1] inside the plot class instead of inside the dataXY. That would make sense if this listener was only useful to trigger a plot update. With that listener, when I change, say, the first array in this list, I trigger an update of the ArrayPlotData attribute which automatically triggers an update of the Plot. And I started to put some code that would deal with adding new arrays to the list.

As a separate comment, note also in my code below a very important modification of your __init__ (adding the optional arguments). This should be done for every HasTraits class for initialization and listeners to work correctly [2]. Note also my use of the _**_default methods that make usually not necessary to have __init__ methods. 

HTH,
Jonathan



class DataXY(HasTraits):
    """ Data manager
    """
    data = List(Array)

    def __init__(self, **kwargs):
        super(DataXY, self).__init__(**kwargs)
        self.data.append(np.random.rand(101))

class PlotXY(HasTraits):
    """ Data plotter
    """
    data = Instance(DataXY)

    plot = Instance(Plot)
    plotdata = Instance(ArrayPlotData)

    traits_view = View(Item('plot', editor=ComponentEditor(),
                       show_label=False))

    def _plotdata_default(self):
        plotdata = ArrayPlotData()
        plotdata['data_0'] = self.data.data[0]
        return plotdata

    def _plot_default(self):
        plot = Plot(self.plotdata)
        plot.plot('data_0')
        return plot

    def update_plot_data(self):
        for i, arr in enumerate(self.data.data):
            self.plotdata.set_data(("data_"+str(i)), arr)

    # This will listen to changes to the list or its items.
    @on_trait_change("data.data[]")
    def update_plot(self, object, name, old, new):
        print "the list of data has changed from %s to %s" % (old, new)
        self.update_plot_data()
        # if we add new data, we need to add new renderers to the Plot
        if isinstance(old, list) and isinstance(new, list) and len(old) != len(new):
            self.add_remove_plots(len(new)-len(old))

    def add_remove_plots(self, num_added_plot):
        print "%s datasets added!" % num_added_plot
        # Add/remove renderers on the Plot?


if __name__ == '__main__':
    from time import sleep
    d = DataXY()
    p = PlotXY(data = d) # Plots the initial array
    p.edit_traits() # non-blocking 
    sleep(3)
    d.data[0] = 0.01 * np.arange(101) # Plots a new set of data


On Tue, Jul 2, 2013 at 9:18 PM, Martin Gustafsson <[hidden email]> wrote:
Hi Adam,

Below is some code that illustrates the present situation. I'd like to extend the PlotXY class to take multiple DataXY() instances and respond to data updates in all of them.

If I make a new ArrayPlotData instance which combines the data of several DataXY():s, I'm not sure how to get the  DataXY().set_data('data', self.data) to trigger plot updates. I guess I can make the plot class listen to the data_changed events of all DataXY():s' ArrayPlotData traits, and then update a second ArrayPlotData that's internal to the plot class. If there is a less cumbersome way, I'd be interested though.

Best wishes
Martin

--------
 class DataXY(HasTraits):
    data = Array()
    plotdata = Instance(ArrayPlotData())
    def __init__(self):
        super(DataXY, self).__init__()
        self.data = np.random.rand(101)
        self.plotdata = ArrayPlotData(data=self.data)
        self.on_trait_change(self.update_plots, 'data')
    def update_plots(self):
        self.plotdata.set_data('data', self.data)
class PlotXY(HasTraits):
    data = Instance(DataXY)
    plot = Instance(Plot)
    traits_view = View(Item('plot', editor=ComponentEditor()))
    def __init__(self, data):
        super(PlotXY, self).__init__()
        self.data = data
        self.plot = Plot(data.plotdata)
        self.plot.plot('data')
        self.configure_traits()
 if __name__ == '__main__':
    d = DataXY()
    p = PlotXY(d) # Plots the initial array
    d.data = np.random.rand(101) # Plots a new set of random data
--------

2 jul 2013 kl. 01.23 skrev Adam Hughes:

> If you could supply some code, that would help a lot, but if I understand correctly from your explanation, then would this solution work:
>
> Make a new class called MultiDataXY().  Pass to it a list of DataXY() instances.  Make a single ArrayPlotData source in MultiDataXY and contstruct your DataXY instances with this.  For example:
>
> class MultiDataXY:
>     dataxys = List()
>     plotdata = Instance(ArrayPlotData)
>
>     def plot(self):
>        for xy in self.dataxys:
>            dataxys.append(DataXY(self.plotdata))
>
>
>
>
>
>
> On Mon, Jul 1, 2013 at 3:51 PM, Martin Gustafsson <[hidden email]> wrote:
> Hello,
>
> This is an issue with the way ArrayPlotData is used to mediate between array and Chaco plots:
>
> I have written some fairly simple HasTraits-derived classes for holding generic measurement data, for example DataXY, which has two Array() traits, along with some metadata such as units and labels. I also have a plotting class (or several) based on HasTraits, which takes a DataXY() instance as a constructor argument, and which uses both the data arrays and the metadata in the DataXY() instance to make a complete plot. Since I want the plot to update when the data inside DataXY() is renewed, DataXY() has an ArrayPlotData trait, on which it runs set_data() whenever the data arrays are renewed. This is what goes into the Chaco plot. So far it's all good.
>
> The problem arises when I want to show several instances of DataXY in the same plot. The ArrayPlotData() is supplied to the Plot constructor rather than with each individual call to Plot().plot, which only takes named references to arrays which reside in this one ArrayPlotData() instance. That doesn't work, since each DataXY() comes with its own ArrayPlotData().
>
> Do you have any ideas on how to resolve this?
>
> Best wishes
> Martin
> _______________________________________________
> Enthought-Dev mailing list
> [hidden email]
> https://mail.enthought.com/mailman/listinfo/enthought-dev
>
>
>
> --
> Adam Hughes
> Physics Ph.D Candidate
> George Washington University
> _______________________________________________
> Enthought-Dev mailing list
> [hidden email]
> https://mail.enthought.com/mailman/listinfo/enthought-dev

_______________________________________________
Enthought-Dev mailing list
[hidden email]
https://mail.enthought.com/mailman/listinfo/enthought-dev



--
Jonathan Rocher, PhD
Scientific software developer
SciPy2013 conference co-chair
Enthought, Inc.
[hidden email]
1-512-536-1057
http://www.enthought.com


_______________________________________________
Enthought-Dev mailing list
[hidden email]
https://mail.enthought.com/mailman/listinfo/enthought-dev