Carlos, I'm a novice also. My approach to Python + wxPython + "my
application of data" is to break things down like this:
I know you know this part but:
OOP Object Oriented Programming: means to put things into objects
(things) and in those objects also have how they do stuff (verbs).
Objects often interact with each other by accessing each other's
methods.
The things in objects like 'type of sedimentry' are object attributes
and the ways to access and manipulate those attributes are methods.
There are a couple types of methods. Some methods are meant only for
the thing itself to use, other methods are for outside objects to
call. The methods designed to be accessed from other objects is the
objects API, the interface into that object. Sound's like you just
need to grasp a well defined Structural Geology Model API.
So when you want to make an application of your data designed with OOP
you need to break down your applications things into seperate -groups-
of objects.
I'd make a group of objects (classes) for my data, and a seperate
group of objects for the graphical user interface (GUI), which means
wxPython widgets. For the most part I'd try to keep each -group-
naieve of the other group with as little exceptions as possible. The
exceptions will be your OpenStereo application's API.
(I am NOT any kind of authorative on these terms btw.)
I'd link these two groups of objects together with in an Application
Programming Interface (API) which is to say: the data model objects
have generic data methods to access data, and the gui widgets objects
have gui methods to do GUi related things only. And the "Application",
use this word as verb here, is the API for data methods to talk to gui
methods and gui methods to talk with data methods.
Don't confuse the API with the GUI; the gui is for the end-user. The
API is how a programmer interfaces data and how the programmer
interfaces with the GUI library. The programmer makes a NEW API for
his/her application. Your API will be the "OpenStereo" API, which is
part wx library, part data read/write functions, part charting
functions and currently part pubsub. I'll show you another API, that's
not to say a better one, just a different way to have data and gui
interact with each other.
Data is the "Model", an OOP term. The model is your data laid out in
some structure that makes sense only from a data perspective, with
little regard to the Graphical User Interface part, ie the GUI
objects. And you have ways to access that structure; sometimes
programmers call them "accessors" and sometimes they call the way to
access your data as "methods" (of a class).
I'd make a class for each data type. I'm sure you already have this
done. As you said you had a working app.
To give you a visual for what I mean; I've made some untested code.
I'm assuming you know all the stuff below, just haven't seen the GUI
peice, also called the "View" and the data piece, also called the
Model, seperated but linked.
# #####################################################
# geo_models.py
# in case the order of data in your files is relavant
import collections
class NameSpace(object):
'''Same as a module global namespace, just less abstract (to me),
less cluttered with non-relavant objects.'''
pass
class SedimentrySample(object):
'''A data model:
This class has nothing to do with the GUI,
only individual items of data.'''
sample_id = None
mass = None
density = None
volume = None
def __init__(self, sample_id, mass, density, volume):
self.sample_id = sample_id
self.mass = mass
self.density = density
self.volume = volume
class SedimentryDataSet(object):
'''A data model:
Non-GUI, dataset-only object/class.
This class is only a -set- of data.
Later you'll use this set to format your charts.'''
filename = None
samples = None
raw_data = None
def __init__(self, filename):
self.filename = filename
self.samples = collections.OrderedDict()
self.LoadFromFile( filename )
self.ParseDataIntoSedimentryObjects()
def LoadFromFile(self, filename):
with open(filename, 'r') as filedata:
self.raw_data = filedata.read()
self.ParseDataIntoSedimentryObjects()
def ParseDataIntoSedimentryObjects(self):
for line in self.raw_data:
# use your actual function on next line
sample = SedimentrySample(*line)
self.samples[sample_id] = sample
class Stereonet(object):
'''A data model:
pre GUI thing that can help with the API part later.'''
filename = None
chart_labels = None
chart_increments = None
data = None # plain data, not raw file data
# nor formatted for any specific chart layout
def __init__(self, filename, chart_labels,
chart_increments):
self.filename = filename
self.chart_labels = chart_labels
self.chart_increments = chart_increments
self.data = SedimentryDataSet( filename )
class RoseDiagram(Stereonet):
'''A data model:
inheret from Stereonet;
makes a consistanct DATA to GUI ->API<-'''
chart_data = None # data foramtted specifically for RoseDiagram
def __init__(self, filename, chart_labels,
chart_increments):
super(RoseDiagram, self).__init__(
filename = filename,
chart_labels = chart_labels,
chart_increments = chart_increments )
def CreateChartData(self):
'''A non-gui data-centric method'''
# note that self.data is inherited from class Stereonet()
self.chart_data = YourFunctionToSetUpChartData(self.data)
class Histogram(Stereonet): pass # you get the idea
# #####################################################
# wxGeoGUI.py
import sys
import wx
import geo_models
# These are the GUI Objects.
# Try to keep data out of their design as much as possible.
YOU_LIKE_DIRECT_ATTRIBUTE_ACCESS = False
class RoseDiagram(wx.Panel):
'''A GUI object'''
def __init__(self, parent, filename=None):
wx.Panel.__init__(self, parent)
# !! --Instead of pubsub you could do: -- !!
app = wx.GetApp()
if YOU_LIKE_DIRECT_ATTRIBUTE_ACCESS:
self.chart_data = app.data.chart_data.rose_diagram
else:
self.chart_data = app.GetRoseDiagram(filename)
self.DrawChart()
def DrawChart(self):
'''Do your drawing of chart_data here.'''
for sample in self.chart_data.items():
print('Write some code to draw it.')
class HistDiagram(wx.Panel): pass # similar to class RoseDiagram(wx.Panel)
# complete class definition as an exercise for the programmer
class StereonetPanel(wx.Panel):
'''A GUI object.'''
def __init__(self, parent):
wx.Panel.__init__(self, parent)
self.chartContainerPanel = wx.Panel(self, size=(500,500))
self.roseButton = wx.Button(self, label='RoseDiagram')
self.histButton = wx.Button(self, label='Histogram')
self.Place(self.chartContainerPanel,
self.roseButton self.histButton)
self.Bind(wx.EVT_BUTTON, self.OnRoseClick, self.roseButton)
self.Bind(wx.EVT_BUTTON, self.OnHistClick, self.histButton)
def Place(self, childWidgets):
# a GUI method
'''Tou code with sizers and stuff to layout your widgets.'''
pass
def OnRoseClick(self, event):
self.chartPanel = RoseDiagram(
self.chartContainerPanel, filename)
# event.Skip()
def OnHistClick(self, event):
self.chartPanel = HistDiagram(
self.chartContainerPanel, filename)
# event.Skip()
'''
Instead of using pubsub, sometimes THIS is where I link data and gui;
in the APP object; to me the "app" (application) is one part a view,
i.e. the gui, and the other part of the "app" is the data.
'''
class OpenStereoApp(wx.App):
'''This gui object links with the non-gui objects by making
the "Model" an attribute of the GUI object itself.'''
def OnInit(self, *args, **kwargs):
'''Instead of pubsub, this is usable;
although not as thread safe.'''
self.filename = sys.argv[1]
self.data = geo_models.Namespace()
self.data.chart_data = geo_models.Namespace()
# Here I am showing your that if your application's data model
# is complex, meaning lots of different data types, then you
# could break the "data" namespace into smaller namespaces.
···
#
# A "namespace" has "scope"; those terms are almost synomous.
#
# Although not shown here; you could flatten the data by having
# more then one app.data attribute, like app.chart_data,
# app.gui_settings, app.defaults, etc.
#
# You've been using pubsub to handle your "data access"
# scoping issues it seems from your question. This is not a bad
# thing. But you usually choose that approach for well defined
# reasons. Pubsub, publisher (of events/messages) is usually not
# directly intended for organizing data access. Although it can be.
# pubsub is more a messaging system; ie when things happen.
self.data.chart_data.rose_diagram = None
self.data.chart_data.hist_diagram = None
# -- Method One
self.LoadData( self.filename )
self.MainFrame = wx.Frame(None, title='OpenStereo Demo OOP App')
self.MainFrame.MainPanel = StereonetPanel( self.MainFrame )
self.MainLoop()
# -- Method One
def LoadData(self, filename):
self.data.chart_data.rose_diagram = geo_models.RoseDiagram( filename )
self.data.chart_data.hist_diagram = geo_models.HistDiagram( filename )
# -- Method two
def GetRoseDiagramData(self, filename):
'''Other GUI widgets call this method to access the data Model.'''
if not filename: filename = self.filename
self.data.chart_data.rose_diagram = geo_models.RoseDiagram( filename )
return self.data.chart_data.rose_diagram
def GetHistDiagramData(self, filename):
'''Other GUI widgets call this method to access the data Model.'''
if not filename: filename = self.filename
self.data.chart_data.hist_diagram = geo_models.HistDiagram( filename )
return self.data.chart_data.hist_diagram
if __name__ == '__main__':
app = OpenStereoApp(0)
BTW; when OOPing, you will greatly benefit from knowing about OOP
Patterns. Some people try to use the MVC or MVP or Observer OOP
patterns. I keep falling back into the wxPython Demo structure of
wxPython. Seems easier to follow.
ok ran out of time. Sorry for anything missing. btw the code above was
not tested.