Thursday, May 29, 2025

doodle python class observer design pattern with plotting data example

this is still a work in progress. but the idea was to use object oriented observer design pattern in an example. this one has an AnalysisFactory object communicating with Data objects when they should plot themselves.  there may be bugs so please modify/use at your own risk.

this was the result:


import numpy as np
from matplotlib import pyplot

class BaseData(object):
    def __init__(self, dataPath='', data=None):
        self._dataPath = dataPath #where to find data. supports a path to example database, csv, json 
        self._data = data #optional provide actual data to object like for NUMPYData, PYLISTData subclasses
    def doPlot(self, msg, ax=None):
        """
        Args:
            msg (str) message
            ax (matplotlib.axes.AxesSubplot) pyplot subplot object to use for adding plot
        """
        print("doing plot with message '{}'".format(msg))
        print("using raw data", self._data)
        
    def toNUMPYMatrix(self, msg):
        print("converting data to numpy matrix. assumes all numbers data {}".format(msg))
        return []

class CSVData(BaseData):
    pass

class SQLITEData(BaseData):
    pass

class NUMPYData(BaseData):
    def __init__(self, data):
        super(NUMPYData, self).__init__(data=data)

    def doPlot(self, msg, ax):
        print("class {0} doing plot with message '{1}'".format(self.__class__.__name__, msg))
        print("using raw data", self._data)
        #putting plot code here
        ax.plot(self._data)
        #pyplot.show()
        
class JSONData(BaseData):
    pass

class PYLISTData(BaseData):
    def __init__(self, data):
        super(PYLISTData, self).__init__(data=data)

    def doPlot(self, msg, ax):
        print("class {0} doing plot with message '{1}'".format(self.__class__.__name__, msg))
        print("using raw data", self._data)
        ax.plot(self._data)
        #pyplot.show()
        
class AnalysisFactory(object):
    def __init__(self):
        self._data = []
    
    def addData(self, dataNode):
        self._data.append(dataNode)
        
    def removeData(self, dataNode):
        self._data.remove(dataNode)
        
    def doPlots(self, msg):
        numFigures = len(self._data)
        if not numFigures:
            return
            
        #make figure to hold all plots
        fig, ax = pyplot.subplots(nrows=1, ncols=numFigures)
        if len(self._data) > 1:
            for i, data in enumerate(self._data):
                #print(type(ax[i]))
                data.doPlot(msg, ax[i])
        else:
            #support single plot
            data = self._data[0]
            data.doPlot(msg, ax)
            
        #show completed figure
        pyplot.show()
        
    def toNUMPYMatrix(self, msg):
        matrices = []
        for data in self._data:
            matrices.append(data.toNUMPYMatrix())

listData = PYLISTData(data=[1,2,3])
npData = NUMPYData(data=np.array([[1,2,3],[4,5,6]]))

analysis = AnalysisFactory()
analysis.addData(listData)
analysis.addData(npData)
analysis.doPlots("sending make plot update")

#class PYLISTData doing plot with message 'sending make plot update'
#('using raw data', [1, 2, 3])
#class NUMPYData doing plot with message 'sending make plot update'
#('using raw data', array([[1, 2, 3],
#       [4, 5, 6]])) 

Thanks for looking