I have created a minimal version that does almost nothing except plot a 
hard-coded data set and allow you to change the axis settings.  It does 
however use the actual gf4 code to handle the data and plotting, just with 
everything else removed.

Put the attached file in the same directory where you have gf4, and run it 
from a console window.  You can change the axes scale type from the "Plot" 
menu.  If log-log still does not display a log-log plot, at least we will 
have something much simpler to work with.

On Tuesday, May 17, 2022 at 10:38:07 PM UTC-4 tbp1...@gmail.com wrote:

> Here is another test you can do.  Enable the Test menu:
>
> in createmenus.py, find "test menu".  Uncomment the last line 
> (mainMenu.add_cascade(...).
>
> Then navigate to @file gf4.pyw-->class 
> PlotManager(AbstractPlotManager)-->Misc-->testMacro.  Add a new last line 
> to the string
> so that it reads
>
> def testMacro(self):
>     self.runMacro('''dsin
>                     copy2buff
>                     sqr
>                     plot
>                     ; comment
>                     overplotbuf
>                     # another comment
>                     loglog
>                     ''')
>
> This adds the command "loglog" to the macro definition.  Save everything, 
> then re-open gf4.  there should be a new "Test" menu.  select the "Run 
> Macro" item.  It will create a waveform and do things with it, and the last 
> step should switch the axes to log-log.  This command is the same one that 
> is (or should be) sent from the button window when you press the "loglog" 
> key.
>
> You can also verify that the "loglog" button is hooked up to the right 
> command string.  Run the cmdwin.py file as its own program from the command 
> line.  This produces a button window that is exactly what you see when you 
> run gf4, except that when you press a button, its command is printed to the 
> console instead of getting executed.
>
> On Tuesday, May 17, 2022 at 7:57:17 PM UTC-4 tbp1...@gmail.com wrote:
>
>> Strange.  Yes, let's zoom.  email me at the address you have so we can 
>> see what's what.  in the meantime, do the semilogX and semilogY buttons 
>> seem to change the scales as expected?
>>
>> I hope that matplotlib didn't change something.  My version should be 
>> very current since i just installed python 3.10.  also, my python 3.7 
>> version, which is several years old, also works as expected. my versions:
>>
>> Python      matplotlib
>> 3.10        3.5.2
>> 3.9          3.5.0
>> 3.7          3.5.2   # hmm, must have updated it recently
>> but even back when gf4 ran under Python 2.7, loglog plotting worked as 
>> expected.
>>
>> On Tuesday, May 17, 2022 at 7:20:11 PM UTC-4 Edward K. Ream wrote:
>>
>>> On Tue, May 17, 2022 at 4:05 PM tbp1...@gmail.com <tbp1...@gmail.com> 
>>> wrote:
>>>
>>>> yellow, that's good!  now as long as they flash when clicked...
>>>>
>>>
>>> Yes. All buttons flash when clicked.
>>>
>>> Traces show that plot is called, but neither the scales nor the plot 
>>> itself change after clicking LogLog.  Perhaps we could zoom sometime.
>>>
>>> Edward
>>>
>>

-- 
You received this message because you are subscribed to the Google Groups 
"leo-editor" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to leo-editor+unsubscr...@googlegroups.com.
To view this discussion on the web visit 
https://groups.google.com/d/msgid/leo-editor/3bfe41e7-134b-466d-bc17-f48b68d65d00n%40googlegroups.com.
#@+leo-ver=5-thin
#@+node:tom.20220518082230.1: * @file gf_min.py
#@@language python
#@@tabwidth -4
#@+others
#@+node:tom.20220518082247.1: ** imports
import os.path
import sys

import tkinter as Tk
import tkinter.font as tkFont
from tkinter import filedialog as fd

import matplotlib
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from matplotlib.backends.backend_tkagg import NavigationToolbar2Tk
from matplotlib.figure import Figure

from entry import TextFade

#from randnum import *
from AbstractPlotMgr import AbstractPlotManager
from AbstractPlotMgr import MAIN, BUFFER, STACKDEPTH, ERRBAND_HI, ERRBAND_LO

from colors import (BLACK, CYAN, GRAY, LIGHTGRAY, DEFAULTGRIDCOLOR,
                    ColorBgPairs)

from Dataset import Dataset
from Linestyle import (Linestyle, LINETHIN, LINEMED)

matplotlib.use('TkAgg')
#@+node:tom.20220518082506.1: ** Declarations
COMMENTS = (';', '#')

ENCODING = 'utf-8'
#@+node:tom.20220518082700.1: ** class PlotManager(AbstractPlotManager)
class PlotManager(AbstractPlotManager):

    # pylint: disable = import-outside-toplevel
    # pylint: disable = too-many-public-methods

    # Putting these imports at the top of the module doesn't work
    # Because they expect to be called as methods with a "self" param.
    from Plot import plot
    from Timehack import timehack
    from BuildCommands import buildCommands
    from MakeWaveforms import (
        makeExponential, makeSine, makeDampedSine,
        makeStep, makeDelta, makeRamp,
        makeSquarewave, makeRandomNoise,
        makeUniformNoise, makeGaussianNoise,
        pdfGaussian, cdfGaussian)

    #@+others
    #@+node:tom.20220518082700.2: *3* Decorators
    #@+node:tom.20220518082700.3: *4* doErrorBands
    def doErrorBands(method):
        '''A decorator method to process error bands in the same way that
        the y data is processed.  The original method call must return
        the Dataset it is being applied to, and the method it applies.
        '''
        # pylint: disable = no-self-argument
        # pylint: disable = not-callable
        def new_method(*args):
            # args[0] will be the calling instance (i.e., self)
            # Call original method
            _ds, func = method(args[0])

            if _ds and _ds.errorBands:
                func(_ds.errorBands[0])
                func(_ds.errorBands[1])

        return new_method

    #@+node:tom.20220518082700.4: *4* REQUIRE_MAIN
    def REQUIRE_MAIN(procedure):
        """A decorator method to check if there is data in the MAIN slot.

        If not, exit procedure with error message.  Otherwise,
        execute the procedure.

        ARGUMENT
        procedure -- an instance method that takes no parameters.
        """
        # pylint: disable = no-self-argument
        # pylint: disable = not-callable
        def new_proc(*args):
            # args[0] will be the calling instance (i.e., self)
            self = args[0]
            _main = self.stack[MAIN]
            if not (_main and len(_main.xdata)):
                msg = 'Missing Waveform'
                self.announce(msg)
                self.flashit()
                return
            procedure(*args)
        return new_proc

    #@+node:tom.20220518082700.5: *4* REQUIRE_MAIN_BUFF
    def REQUIRE_MAIN_BUFF(procedure):
        """A decorator method to check if there is data in the MAIN
        and BUFFER slots.

        If not, exit procedure with error message.  Otherwise,
        execute the procedure.

        ARGUMENT
        procedure -- an instance method that takes no parameters.
        """
        # pylint: disable = no-self-argument
        # pylint: disable = not-callable

        def new_proc(*args):
            # args[0] will be the calling instance (i.e., self)
            self = args[0]
            _main = self.stack[MAIN]
            _buff = self.stack[BUFFER]
            if not (_main and len(_main.xdata) and
                    _buff and len(_buff.xdata)):
                self.announce("Missing one or both waveforms")
                self.flashit()
                return
            procedure(self)
        return new_proc

    #@+node:tom.20220518082700.6: *3* __init__
    def __init__(self, root=None):
        super().__init__(root)
        self.toolbar = None

        self.linestyles = [Linestyle() for i in range(self.stackdepth)]
        self.linestyles[MAIN].linecolor = BLACK
        self.linestyles[MAIN].sym_mec = BLACK
        self.linestyles[MAIN].linewidth = LINEMED
        self.linestyles[BUFFER].linecolor = CYAN
        self.linestyles[BUFFER].sym_mec = BLACK
        self.linestyles[BUFFER].sym_mfc = CYAN
        self.linestyles[BUFFER].linewidth = LINEMED

        self.errorbar_linestyles = Linestyle()
        self.errorbar_linestyles.linecolor = GRAY
        self.errorbar_linestyles.linewidth = LINETHIN

        self.main_symbol_color = Tk.StringVar()
        self.buffer_symbol_color = Tk.StringVar()
        self.main_line_color = Tk.StringVar()
        self.buffer_line_color = Tk.StringVar()
        self.radio_main_linestyle = Tk.StringVar()
        self.radio_buffer_linestyle = Tk.StringVar()
        self.main_symbol_shape = Tk.StringVar()
        self.buffer_symbol_shape = Tk.StringVar()
        self.graph_bg_color = Tk.StringVar()

        self.initpath = '.'  # for File Dialog directory
        self.current_path = ''
        self.setMenus()

    #@+node:tom.20220518082700.7: *3* setMenus
    def setMenus(self):
        mainMenu = Tk.Menu(self.root)

        plotSubmenu = Tk.Menu(self.root)

        commands = (
            ("Plot", self.plotmain, 0),
        )

        for label, command, underline in commands:
            plotSubmenu.add_command(label = label, command = command, underline 
= underline)

        plotSubmenu.add_separator()

        commands = (
            ('Lin-lin', self.setLinLin),
            ('Semilog(Y)', self.setSemilogY),
            ('Semilog(X)', self.setSemilogX),
            ('Log-log', self.setLogLog)
        )

        for label, command in commands:
            plotSubmenu.add_radiobutton(label = label, command = command)

        mainMenu.add_cascade(label='Plot', menu=plotSubmenu)
        self.root.config(menu=mainMenu)

    #@+node:tom.20220518082700.9: *3* Widget Utilities
    #@+node:tom.20220518082700.11: *4* setWindowTitle
    def setWindowTitle(self, title=''):
        if self.root:
            self.root.wm_title(title)

    #@+node:tom.20220518082700.12: *4* label_select_all
    def label_select_all(self, event):
        '''Select all text in an edit widget (Tk.Entry) when <CNTRL-A>
        is pressed. It seems that the default key is <CTRL-/>
        (standard for linux) and this overrides that.  The
        "return 'break'" is essential, otherwise this gets ignored.
        '''

        event.widget.selection_range(0, Tk.END)
        return 'break'

    #@+node:tom.20220518082700.13: *4* setupFigure
    def setupFigure(self, title='GF4'):
        root = Tk.Tk()
        root.option_add('*tearOff', False)  # Tk specific menu option

        root.bind('<Alt-F4>', self.quit)

        f = Figure(figsize=(9, 6), dpi=100, facecolor=LIGHTGRAY)
        ax = None  # f.add_subplot(111, axisbg='0.3')

        canvas = FigureCanvasTkAgg(f, master=root)
        self.toolbar = NavigationToolbar2Tk(canvas, root)
        canvas.get_tk_widget().pack(side=Tk.TOP, fill=Tk.BOTH, expand=1)
        self.toolbar.pack()

        self._sv = Tk.StringVar()
        self._sv.set('')
        self.editWidget = Tk.Entry(root, textvariable=self._sv, width=80)
        self.editWidget.bind('<Return>', self.doneEditLabel)
        self.editWidget.bind('<Escape>', self.doneEditLabel)
        self.editWidget.bind('<Control-a>', self.label_select_all)

        self.root = root
        self.axes = ax
        self.figure = f
        self.canvas = canvas
        self.fix_ticks()
        self.setWindowTitle(title)

        canvas.mpl_connect('button_press_event', self.edit_label)

        self.toolbar.update()

        _ann = TextFade(root, height=1, font='size 12',
                        width=100, bd=0, pady=5, bg='LightBlue')
        _ann.insert(1.0, '')
        _ann.config(state=Tk.DISABLED)
        _ann.pack()

        self.announcer = _ann

        self.set_editable_labels()
        self.currentLabelEditing = None

    #@+node:tom.20220518082700.14: *4* fadeit
    def fadeit(self, widget=None):
        '''For TextFade widgets and similar with a fade() method.'''
        if widget and 'fade' not in dir(widget): return
        if not widget:
            widget = self.announcer
        widget.fade()

    #@+node:tom.20220518082700.15: *4* flashit
    def flashit(self, color='yellow', widget=None):
        '''For TextFade widgets and similar with a flash() method.'''
        if widget and 'flash' not in dir(widget): return
        if not widget:
            widget = self.announcer
        widget.flash(color)
        widget.after(1000)
        self.fadeit(widget)

    #@+node:tom.20220518082700.16: *4* announce
    def announce(self, msg='This is a test'):
        '''Show an message in the announcement area.'''
        _ann = self.announcer
        _ann.config(state=Tk.NORMAL)
        _ann.delete(1.0, Tk.END)
        _ann.insert(1.0, msg)
        _ann.config(state=Tk.DISABLED)

    #@+node:tom.20220518082700.17: *4* set_editable_labels
    def set_editable_labels(self):
        '''Create a list of those labels that can be edited with the standard
        single-line edit widget.  Return nothing.
        '''
        if not self.axes: return

        self.editableLabels.extend([
            self.axes.get_xaxis().get_label(),
            self.axes.get_yaxis().get_label()]
        )

    #@+node:tom.20220518082700.18: *4* edit_label
    def edit_label(self, event):
        '''Respond to a mouse-press event.  Check all the editable labels,
        plus other Text items in the figure, to see if the event belongs to
        one of them.  If so, display the label edit widget over the label.

        ARGUMENT
        event - a mathplotlib event;  MouseEvent is expected.

        RETURNS
        nothing.
        '''
        if not self.axes: return

        _labels = [item for item in self.axes.get_children()
                   if isinstance(item, matplotlib.text.Text)]
        _labels.extend(self.editableLabels)

        lab = None
        for _lab in _labels:
            if _lab.contains(event)[0]:
                lab = _lab
                break

        if lab is None:
            self.currentLabelEditing = None
            return

        self.currentLabelEditing = lab
        ew = self.editWidget
        self._sv.set(lab.get_text())

        #@+<< configure editwidget >>
        #@+node:tom.20220518082700.19: *5* << configure editwidget >>
        lab_fontsize = int(lab.get_size())
        tkfont = tkFont.Font(size=lab_fontsize)

        bbox = lab.get_window_extent()
        ul, lr = bbox.get_points()
        ulx, uly = ul
        lrx, lry = lr

        canvas = self.canvas
        dpi = canvas.figure._dpi
        canvas_bbox_ul = canvas.figure.bbox_inches.get_points()[1]
        canv_ulx, canv_uly = \
            int(dpi * canvas_bbox_ul[0]), int(dpi * canvas_bbox_ul[1])

        ew.configure(font=tkfont)
        #@-<< configure editwidget >>
        ew.place(x=ulx, y=canv_uly - lry)
        ew.selection_clear()
        ew.focus_set()
        ew.lift()

    #@+node:tom.20220518082700.20: *4* doneEditLabel
    def doneEditLabel(self, event):
        """Receive <Return> and <Escape> events.  For <Escape>, leave.
        For <Return>, also change the label's text, and set the Dataset's
        corresponding label, if any.
        """

        if not self.currentLabelEditing: return

        if event.keysym == 'Return':
            _newtext = event.widget.get()
            self.currentLabelEditing.set_text(_newtext)
            self.canvas.draw()

            if self.currentLabelEditing is self.axes.get_xaxis().get_label():
                self.stack[MAIN].xaxislabel = _newtext
            elif self.currentLabelEditing is self.axes.get_yaxis().get_label():
                self.stack[MAIN].yaxislabel = _newtext
            elif _newtext == self.axes.get_title():
                self.stack[MAIN].figurelabel = _newtext
        elif event.keysym == 'Escape':
            self.canvas.draw()

        event.widget.lower()
        event.widget.selection_clear()
        self.currentLabelEditing = None

    #@+node:tom.20220518082700.21: *3* Data Stack
    #@+node:tom.20220518082700.22: *4* copyToBuffer
    def copyToBuffer(self):
        if not self.stack[MAIN]:
            self.announce("No waveform to copy")
            self.flashit()
            return

        self.stack[BUFFER] = self.stack[MAIN].copy()

    #@+node:tom.20220518082700.23: *4* swap_data
    def swap_data(self):
        if not self.stack[MAIN] or not self.stack[BUFFER]:
            self.announce("Missing one or both waveforms  - nothing to swap")
            self.flashit()
            return

        _temp = self.stack[BUFFER].copy()
        self.stack[BUFFER] = self.stack[MAIN].copy()
        self.stack[MAIN] = _temp

    #@+node:tom.20220518082700.24: *4* paste_data
    def paste_data(self):
        if not self.stack[BUFFER]:
            self.announce("No waveform to paste")
            self.flashit()
            return

        self.stack[MAIN] = self.stack[BUFFER].copy()

    #@+node:tom.20220518082700.25: *4* drop_stack
    def drop_stack(self):
        '''Drop stack of datasets, leave top one unchanged.'''
        for i in range(STACKDEPTH - 1):
            self.stack[i] = self.stack[i + 1].copy()

    #@+node:tom.20220518082700.26: *4* push_with_copy
    def push_with_copy(self):
        '''Push dataset stack up, duplicate duplicate bottom:
            top-1 -> top        -2 -> -1
            ...
            1 -> 2
            0 -> 1
            0 stays unchanged
        '''

        for i in range(STACKDEPTH - 1, 0, -1):
            self.stack[i] = self.stack[i - 1].copy()

    #@+node:tom.20220518082700.27: *4* rotate_stack_up
    def rotate_stack_up(self):
        '''Rotate dataset stack up:
            1 -> 2
            2 -> 3
            ...
            top -> 1
        '''

        temp = self.stack[STACKDEPTH - 1].copy()
        self.push_with_copy()
        self.stack[0] = temp

    #@+node:tom.20220518082700.28: *4* rotate_stack_down
    def rotate_stack_down(self):
        '''Rotate stack downwards:
                top -> top-1
                ...
                2 -> 1
                1 -> top
        '''
        temp = self.stack[0].copy()
        self.drop_stack()
        self.stack[STACKDEPTH - 1] = temp

    #@+node:tom.20220518082700.29: *4* copy_to_top
    def copy_to_top(self):
        '''Copy active dataset to top of stack.'''
        self.stack[STACKDEPTH - 1] = self.stack[MAIN].copy()

    #@+node:tom.20220518082700.30: *4* copy_from_top
    def copy_from_top(self):
        '''Copy top dataset to bottom of stack ("main").'''
        self.stack[MAIN] = self.stack[STACKDEPTH - 1].copy()

    #@+node:tom.20220518082700.31: *4* store1
    def store1(self):
        """Store X dataset in a special location outside the stack."""
        _ds = self.stack[MAIN].copy()
        self.storage = _ds

    #@+node:tom.20220518082700.32: *4* recall1
    def recall1(self):
        """Recall stored dataset to X."""
        if self.storage:
            self.stack[MAIN] = self.storage.copy()
        else:
            self.announce("No stored data to recall")
            self.flashit()

    #@+node:tom.20220518082700.33: *3* Configure Graph
    #@+node:tom.20220518082700.34: *4* set_axis_bg
    def set_axis_bg(self):
        self.axes.set_facecolor('yellow')
    # =============================================================

    #@+node:tom.20220518082700.35: *4* setYMin
    def setYMin(self, val):
        if not self.axes: return
        axis = self.axes
        axis.set_ybound(self.stack[MAIN].ymin)

    #@+node:tom.20220518082700.36: *4* setSemilogX
    def setSemilogX(self):
        self.semilogY = False
        self.semilogX = True
        self.plot()

    #@+node:tom.20220518082700.37: *4* setSemilogY
    def setSemilogY(self):
        self.semilogY = True
        self.semilogX = False
        self.plot()

    #@+node:tom.20220518082700.38: *4* fix_ticks
    def fix_ticks(self):
        '''Make tick marks point out of the figure's frame rather than the
        default of inwards.
        '''
        if not self.axes: return

        # tick mark adjustments adapted from
        # http://osdir.com/ml/python.matplotlib.general/2005-01/msg00076.html
        axis = self.axes
        xticks = axis.get_xticklines()
        for tick in xticks:
            tick.set_markersize(6)

        yticks = axis.get_yticklines()
        for tick in yticks:
            tick.set_markersize(6)

        xlabels = axis.get_xticklabels()
        for label in xlabels:
            label.set_y(-0.02)
            label.set_size('small')

        ylabels = axis.get_yticklabels()
        for label in ylabels:
            label.set_x(-0.02)
            label.set_size('small')

    #@+node:tom.20220518082700.39: *4* setLogLog
    def setLogLog(self):
        self.semilogY = True
        self.semilogX = True
        self.plot()

    #@+node:tom.20220518082700.40: *4* setLinLin
    def setLinLin(self):
        self.semilogY = False
        self.semilogX = False
        self.plot()

    #@+node:tom.20220518082700.41: *4* setPlotLineWidth
    def setPlotLineWidth(self, position, width):
        self.linestyles[position].set_linewidth(width)

    #@+node:tom.20220518082700.42: *4* setLineColor
    def setLineColor(self, stackpos):
        '''Called only though a menu selection, so that self.x_line_color
        is set before this method is called.
        '''

        if stackpos == MAIN:
            _color = self.main_line_color.get()
        else:
            _color = self.buffer_line_color.get()
        self.linestyles[stackpos].set_linecolor(_color)

    #@+node:tom.20220518082700.43: *4* setLineColorMain
    def setLineColorMain(self):
        self.setLineColor(MAIN)

    #@+node:tom.20220518082700.44: *4* setLineColorBuffer
    def setLineColorBuffer(self):
        self.setLineColor(BUFFER)

    #@+node:tom.20220518082700.45: *4* setSymColor
    def setSymColor(self, stackpos):
        '''Called only though a menu selection, so that self.x_symbol_color
        is set before this method is called.
        '''

        if stackpos == MAIN:
            _color = self.main_symbol_color.get()
        else:
            _color = self.buffer_symbol_color.get()
        self.linestyles[stackpos].set_sym_color(_color)

    #@+node:tom.20220518082700.46: *4* setSymColorMain
    def setSymColorMain(self):
        self.setSymColor(MAIN)

    #@+node:tom.20220518082700.47: *4* setSymColorBuffer
    def setSymColorBuffer(self):
        self.setSymColor(BUFFER)

    #@+node:tom.20220518082700.48: *4* setBgColor
    def setBgColor(self):
        _color = self.graph_bg_color.get()
        self.axes.set_facecolor(_color)
        _gridcolor = ColorBgPairs.get(_color, DEFAULTGRIDCOLOR)
        self.gridcolor = _gridcolor

    #@+node:tom.20220518082700.49: *4* setMainLineWidth
    def setMainLineWidth(self):
        _width = float(self.radio_main_linestyle.get())
        self.setPlotLineWidth(MAIN, _width)

    #@+node:tom.20220518082700.50: *4* setBufferLineWidth
    def setBufferLineWidth(self):
        _width = float(self.radio_buffer_linestyle.get())
        self.setPlotLineWidth(BUFFER, _width)

    #@+node:tom.20220518082700.51: *4* setMarkerStyle
    def setMarkerStyle(self, stackpos):
        # pylint: disable = no-member
        if stackpos == MAIN:
            _ms = self.main_marker_style
        else:
            _ms = self.buffer_marker_style
        _style = int(_ms.get())
        _ls = self.linestyles[stackpos]
        if _style == 1:
            _ls.useLine = True
            _ls.useSym = False
        elif _style == 2:
            _ls.useLine = False
            _ls.useSym = True
        else:
            _ls.useLine = True
            _ls.useSym = True

    #@+node:tom.20220518082700.52: *4* setMainMarkerStyle
    def setMainMarkerStyle(self):
        self.setMarkerStyle(MAIN)

    #@+node:tom.20220518082700.53: *4* setBufferMarkerStyle
    def setBufferMarkerStyle(self):
        self.setMarkerStyle(BUFFER)

    #@+node:tom.20220518082700.54: *4* setSymShape
    def setSymShape(self, stackpos):
        if stackpos == MAIN:
            _shp = self.main_symbol_shape
        else:
            _shp = self.buffer_symbol_shape
        _shape = _shp.get()
        _ls = self.linestyles[stackpos]
        _ls.set_sym_style(_shape)

    #@+node:tom.20220518082700.55: *4* setSymShapeMain
    def setSymShapeMain(self):
        self.setSymShape(MAIN)

    #@+node:tom.20220518082700.56: *4* setSymShapeBuffer
    def setSymShapeBuffer(self):
        self.setSymShape(BUFFER)

    #@+node:tom.20220518082700.57: *4* setXlabel
    def setXlabel(self, label=''):
        self.axes.set_xlabel(label)

    #@+node:tom.20220518082700.58: *4* setYlabel
    def setYlabel(self, label=''):
        self.axes.set_ylabel(label)

    #@+node:tom.20220518082700.59: *4* setFigureTitle
    def setFigureTitle(self, title=''):
        #self.axes.set_title(title, size='x-large', y=1.025)
        self.axes.set_title(title, size='large', y=1.025)

    #@+node:tom.20220518082700.60: *3* Data Load/Save
    #@+node:tom.20220518082700.61: *4* set_data
    def set_data(self, dataset, stackpos=MAIN):
        self.stack[stackpos] = dataset.copy()

    #@+node:tom.20220518082700.62: *4* save_data
    def save_data(self, saveas=True):
        '''Write x,y data in MAIN to an ASCII file.  Labels and other meta data
        are also written.

        ARGUMENT
        saveas -- if True, use Save As dialog.  Otherwise, use original
                  filename if one exists, and use Save As instead.

        RETURNS
        nothing
        '''

        opt = {}

        if self.stack[MAIN].orig_filename:
            opt['initialfile'] = self.stack[MAIN].orig_filename
        if self.initpath:
            opt['initialdir'] = self.initpath

        if saveas:
            fname = fd.asksaveasfilename(**opt)
        else:
            fname = self.stack[MAIN].orig_filename or \
                    fd.asksaveasfilename(**opt)

        self.stack[MAIN].writeAsciiData(fname)
        self.stack[MAIN].orig_filename = fname

    #@+node:tom.20220518082700.63: *4* load_data
    def load_data(self, stackpos=MAIN):
        '''Open a file dialog for reading, and remember its directory and 
filename.
        Use that last stored directory or file as the initial directory when
        opening the file dialog.  Load the data from the selected file into
        the specified Dataset. Assumes data is in ASCII format.  Plot
        the data if no other data has yet been plotted.
        
        If there are more than two numeric columns,
        show a dialog for the user to select the two columns to use.

        The data set may be divided into parts by the special
        comment string ';;ENDDATASET'.  If so, load each such delineated
        part into stack positions MAIN, BUFFER, and the top-most
        position, if there are enough data parts.

        ARGUMENT
        stackpos -- integer specifying the stack position to load
                    the data into.

        RETURNS
        Nothing.
        '''

        if not self.current_path:
            f = fd.askopenfile(mode='r', initialdir=self.initpath)
        else:
            f = fd.askopenfile(mode='r', initialfile=self.current_path)
        if not f:
            return

        fname = f.name
        data = f.read()
        f.close()

        self.initpath = os.path.dirname(fname)
        self.current_path = fname

        blocks = data.split('ENDDATASET')
        numblocks = len(blocks)
        if numblocks < 1:
            self.announce('No data found')
            return

        for n in range(min(len(blocks), STACKDEPTH)):
            block = blocks[n]
            if not block.split():
                continue

            lines = block.split('\n')
            _data = Dataset()
            _data.orig_filename = fname
            err = _data.setAsciiData(lines, root = self.root)
            if err:
                self.announce(f'No data in block {n}')
                self.flashit()
                self.announce(f'No data in block {n}')
                return

            if n <= BUFFER:
                self.set_data(_data, n)
            elif n == BUFFER + 1:
                self.set_data(_data, STACKDEPTH - 1)

        if not self.axes:
            self.plot()

    #@+node:tom.20220518082700.64: *4* load_plot_data
    def load_plot_data(self, fname, overplot=False):
        '''Load the data from the specified file into the specified Dataset.
        Load the dataset into the MAIN stack buffer position.
        Assumes data is in ASCII format.  Plot the data if no other
        data has yet been plotted, otherwise plot or overplot it
        according to the value of the overplot parameter.

        ARGUMENT
        fname -- path to a file
        overplot -- Boolean

        RETURNS
        Nothing.
        '''

        with open(fname, encoding = ENCODING) as f:
            data = f.read()

        self.initpath = os.path.dirname(fname)

        blocks = data.split('ENDDATASET')
        numblocks = len(blocks)
        if numblocks < 1:
            self.announce('No data found')
            return

        for n in range(min(len(blocks), STACKDEPTH)):
            block = blocks[n]
            lines = block.split('\n')
            _data = Dataset()
            _data.orig_filename = fname
            err = _data.setAsciiData(lines)
            if err:
                self.announce('%s' % err)
                self.flashit()
                self.announce('%s' % err)
                return

            if n <= BUFFER:
                self.set_data(_data, n)
            elif n == BUFFER + 1:
                self.set_data(_data, STACKDEPTH - 1)

        if not self.axes:
            self.plot()
        else:
            if overplot: self.overplot()
            else: self.plot()

    #@+node:tom.20220518082700.111: *3* Plot Operations
    #@+node:tom.20220518082700.112: *4* overplotbuff
    def overplotbuff(self):
        self.overplot(BUFFER)

    #@+node:tom.20220518082700.113: *4* overplot_errorbands
    def overplot_errorbands(self, stackposition=MAIN):
        if not self.stack[stackposition].errorBands:
            self.announce('No errorband data to plot')
            self.flashit()
            return

        _ds = self.stack[stackposition]

        # Overplot error bands
        upper = _ds.errorBands[ERRBAND_HI]
        lower = _ds.errorBands[ERRBAND_LO]

        del self.stack[STACKDEPTH:]
        self.stack.append(upper)
        self.stack.append(lower)

        self.axes.fill_between(self.stack[MAIN].xdata,
                self.stack[STACKDEPTH + ERRBAND_HI].ydata,
                self.stack[STACKDEPTH + ERRBAND_LO].ydata,
                facecolor='lightgrey', alpha=0.1)

        for g in [STACKDEPTH + ERRBAND_HI, STACKDEPTH + ERRBAND_LO]:
            self.overplot(g)

    #@+node:tom.20220518082700.114: *4* overplot
    def overplot(self, stackposition=MAIN):
        self.plot(stackposition, False)

    #@+node:tom.20220518082700.131: *3* Trend
    #@+node:tom.20220518082700.139: *3* Misc
    #@+node:tom.20220518082700.144: *4* runMacro
    def runMacro(self, cmdlist=''):
        '''Given a string of commands, one per line, execute the commands
        in order.  First check to make sure all commands are valid;
        execute the sequence of commands if they are.  Lines whose first
        non-whitespace character is ';' or '#' are ignored as comment lines.

        Return nothing.

        ARGUMENT
        cmdlist --  a string of line-separated command names.

        RETURNS
        nothing
        '''

        if not cmdlist:
            self.announce('No command list to run')
            self.flashit()
            return

        cmds = cmdlist.splitlines()
        cmds = [c.strip() for c in cmds if c.strip()]
        cmds = [c for c in cmds if c[0] not in COMMENTS]
        unknowns = []

        for cmd in cmds:
            if not self.commands.get(cmd):
                unknowns.append(cmd)

        if unknowns:
            msg = 'unknown commands: %s' % (' '.join(unknowns))
            self.announce(msg)
            self.flashit()
            return

        for cmd in cmds:
            self.interpret(cmd)

    #@+node:tom.20220518082700.145: *4* testMacro
    def testMacro(self):
        self.runMacro('''dsin
                        copy2buff
                        sqr
                        plot
                        ; comment
                        overplotbuf
                        # another comment
                        loglog
                        ''')

    #@+node:tom.20220518082700.147: *4* hasToplevel
    def hasToplevel(self):
        for c in self.root.winfo_children():
            if c.winfo_class() == 'Toplevel':
                return True
        return False

    #@+node:tom.20220518082700.148: *4* test_announce
    def test_announce(self):
        self.announce('testing the Announcer')

    #@-others

#@+node:tom.20220518082717.1: ** __main__
if __name__ == '__main__':
    matplotlib.rcParams['xtick.direction'] = 'out'
    matplotlib.rcParams['ytick.direction'] = 'out'

    plotmgr = PlotManager()
    plotmgr.root.update_idletasks()

    plotmgr.announce('Using: %s' % (sys.executable))
    plotmgr.fadeit()

    ds = Dataset([1,5,10,20], [1,25,100,400])
    plotmgr.set_data(ds)
    plotmgr.plotmain()

    Tk.mainloop()
#@-others
#@-leo

Reply via email to