pandas_GUI.plot_Pandas_GUI

  1def plot_pandas_GUI(df_info=None, show_text_col = False, **kwargs):
  2    """
  3    If passed no parameters this will look for all the dataframes in the user
  4    namespace and make them available for plotting. Once a
  5    dataframe is chosen only the numerical columns from that dataframe will
  6    be available for inclusion in the plotting expression.
  7
  8    This GUI produces code to use the plotly interactive plotting package.
  9
 10    If you wish to allow only certain dataframes or have them show up as
 11    user friendly names in the menus provide that information in the first
 12    paramater df_info.
 13
 14    To allow inclusion of text columns pass True for show_text_col.
 15
 16    :param bool show_text_col: (default = False). When True columns
 17    containing text will be shown.
 18
 19    :param list df_info: List of Lists [[object,globalname,userfriendly]],..]
 20      * object -- pandas.DataFrame
 21      * globalname -- string name of the object in the user global name space.
 22      * userfriendly -- string name to display for user selection.
 23
 24    :keyword string figname: string used to override default python name for
 25    figure.
 26
 27    :keyword bool findframes: default = True. If set to false and dataframes
 28    are passed in dfs_info, will not search for dataframes in the user
 29    namespace.
 30    """
 31    from ipywidgets import Layout, Box, HBox, VBox, GridBox, Tab, \
 32        Accordion, Dropdown, Label, Text, Button, Checkbox, FloatText, \
 33        RadioButtons, BoundedIntText, Output
 34    from ipywidgets import HTML as richLabel
 35    from IPython.display import display, HTML
 36    from IPython.display import Javascript as JS
 37    from IPython import get_ipython
 38    from JPSLUtils.utils import new_cell_immediately_below,\
 39        select_cell_immediately_below, move_cursor_in_current_cell, \
 40        insert_text_into_next_cell, insert_text_at_beginning_of_current_cell, \
 41        insert_newline_at_end_of_current_cell, select_containing_cell, \
 42        delete_selected_cell, iconselector, notice_group, notebookenv, \
 43        replace_text_of_next_cell
 44
 45    from .utils import find_pandas_dataframe_names, build_run_snip_widget
 46    from IPython import get_ipython
 47    global_dict = get_ipython().user_ns
 48    JPSLUtils = global_dict["JPSLUtils"]
 49    dfs_info = []
 50    if isinstance(df_info,list):
 51        for k in df_info:
 52            dfs_info.append(k)
 53    findframes = kwargs.pop('findframes',True)
 54    if findframes:
 55        for k in find_pandas_dataframe_names():
 56            dfs_info.append([global_dict[k],k,k])
 57    friendly_to_globalname = {k[2]:k[1] for k in dfs_info}
 58    friendly_to_object = {k[2]:k[0] for k in dfs_info}
 59
 60    figname = kwargs.pop('figname',None)
 61    from .utils import find_figure_names
 62    figlst = find_figure_names()
 63    if figname in figlst:
 64        raise UserWarning (str(figname) + ' already exists. Choose a '
 65                                          'different name for the figure.')
 66    if figname == None:
 67        figname = 'Figure_'+str(len(figlst)+1)
 68
 69    #### Define GUI Elements ####
 70    # Those followed by a * are required.
 71    output = Output()
 72    with output:
 73        display(HTML(
 74        "<h3 id ='pandasplotGUI' style='text-align:center;'>Pandas Plot "
 75        "Composer</h3> <div style='text-align:center;'>"
 76        "<span style='color:green;'>Steps with a * are required.</span> The "
 77        "code that will generate the plot is being "
 78        "built in the cell immediately below.</div><div "
 79        "style='text-align:center;'>This composer uses a subset of "
 80        "<a href ='https://plotly.com/python/line-and-scatter/#'> "
 81        "the plotly scatter plot</a> capabilities.</div>"))
 82
 83    longdesc = {'description_width':'initial'}
 84
 85    # Notices for the Final Check Tab.
 86    makeplot_notices = notice_group(['At least one trace required.',
 87                                     'Axes must have labels.'],
 88                                    'Notices:','','red')
 89    makeplot_notices.set_active([0,1])
 90
 91    # Strings for each tab, that are combined to make the code.
 92    importstr = '# CODE BLOCK generated using plot_pandas_GUI().\n# See ' \
 93        'https://jupyterphysscilab.github.io/jupyter_Pandas_GUI.\n' \
 94        'from plotly import graph_objects as go\n' + str(figname) + \
 95        ' = go.FigureWidget(layout_template=\"simple_white\")\n'
 96    step1strdefault = '\n# Trace declaration(s) and trace formatting\n'
 97    step1str = step1strdefault
 98    step2strdefault = '\n# Axes labels\n'
 99    step2str = step2strdefault
100    step3strdefault = '\n# Plot formatting\n'
101    step3str = step3strdefault
102    # 1. Pick Traces*
103    #   a. Select Y vs. X pairs* (DataFrame, X and Y, which must be from single
104    #       frame.
105    # Notices for the Pick Trace(s) tab.
106    notice_list = [
107        'Data set (DataFrame) required.',
108        'X- and Y-coordinates required.',
109        'Incomplete or inconsistent error specification(s).',
110        'Non-default trace formatting left from previous trace.',
111        'X-Error settings left from previous trace.',
112        'Y-Error settings left from previous trace.',
113    ]
114    trace_notices = notice_group(notice_list, 'Notices:','','red')
115    step1instr = richLabel(value = 'For each trace you wish to include: '
116                                   '<ol><li>Select a DataFrame (Data '
117                                   'set);</li>'
118                                   '<li>Select the column containing the X '
119                                   'values;</li>'
120                                   '<li>Select the column containing the Y '
121                                   'values;</li>'
122                                   '<li>Provide a name for the trace if you do'
123                                   ' not like the default. This text will be '
124                                   'used for the legend;</li>'
125                                   '<li> OPTIONAL - set additional formatting '
126                                   'and error display by expanding the '
127                                   'sections at the bottom of this tab;</li>'
128                                   '<li>Once everything is set use the '
129                                   '<b>"Add Trace"</b> button to '
130                                   'include it in your plot.</li></ol>')
131
132    step1instracc = Accordion(children = [step1instr])
133    step1instracc.set_title(0,'Instructions')
134    step1instracc.selected_index = None
135
136    # DataFrame selection
137    tempopts = []
138    tempopts.append('Choose data set.')
139    for k in dfs_info:
140        tempopts.append(k[2])
141    whichframe = Dropdown(options=tempopts,
142                                description='DataFrame: ',)
143
144    def update_columns(change):
145        df = friendly_to_object[change['new']]
146        tempcols = df.columns.values
147        if change['new'] == 'Choose data set.':
148            Xcoord.disabled = True
149            Ycoord.disabled = True
150            add_trace_but.disabled = True
151            add_trace_but.button_style = ''
152            trace_notices.activate_notice(0)
153            trace_notices.activate_notice(1)
154            add_trace_notices.value = trace_notices.notice_html()
155            return
156        tempopt = ['Choose column for coordinate.']
157        for k in tempcols:
158            if show_text_col:
159                tempopt.append(k)
160            else:
161                if df[k].dtype != 'O':
162                    tempopt.append(k)
163        Xcoord.options = tempopt
164        Xcoord.value = tempopt[0]
165        Ycoord.options = tempopt
166        Ycoord.value = tempopt[0]
167        Xcoord.disabled = False
168        Ycoord.disabled = False
169        trace_notices.activate_notice(1)
170        trace_notices.deactivate_notice(0)
171        add_trace_notices.value =trace_notices.notice_html()
172        pass
173    whichframe.observe(update_columns, names='value')
174
175    # Data selection
176    Xcoord = Dropdown(options=['Choose X-coordinate.'],
177                           description='X: ',
178                           disabled = True)
179    Ycoord = Dropdown(options=['Choose Y-coordinate.'],
180                           description='Y: ',
181                           disabled = True)
182    def trace_name_update(change):
183        if change['new'] != 'Choose column for coordinate.':
184            trace_name.value = Ycoord.value
185        if Xcoord.value != 'Choose column for coordinate.' and Ycoord.value \
186                != 'Choose column for coordinate.':
187            add_trace_but.disabled = False
188            add_trace_but.button_style = 'success'
189            modedrop.disabled = False
190            colordrop.disabled = False
191            yerrtype.disabled = False
192            xerrtype.disabled = False
193            trace_name.disabled = False
194            trace_notices.deactivate_notice(1)
195            add_trace_notices.value = trace_notices.notice_html()
196        else:
197            add_trace_but.disabled = True
198            add_trace_but.button_style = ''
199            modedrop.disabled = True
200            colordrop.disabled = True
201            yerrtype.disabled = True
202            xerrtype.disabled = True
203            trace_name.disabled = True
204            trace_notices.activate_notice(1)
205            add_trace_notices.value = trace_notices.notice_html()
206        pass
207
208    Ycoord.observe(trace_name_update,names='value')
209
210    # Trace name
211    trace_name = Text(placeholder = 'Trace name for legend',
212                      description = 'Trace name: ',
213                      disabled = True)
214
215    #   b. Trace Style (optional)
216    def trace_format_update(change):
217        trace_notices.deactivate_notice(3)
218        add_trace_notices.value = trace_notices.notice_html()
219        pass
220
221    modedrop = Dropdown(options = ['lines','markers','lines+markers'],
222                    description = 'Trace Style: ')
223    modedrop.observe(trace_format_update)
224    colordrop = Dropdown(options=['default','blue','orange','green','purple',
225                              'red','gold','brown','black'],
226                     description = 'Color: ')
227    colordrop.observe(trace_format_update)
228    iconlist = ['circle', 'square', 'caret-up', 'star', 'plus', 'times',
229                'caret-down', 'caret-left', 'caret-right']
230    icontoplotly = {'circle': 'circle', 'square': 'square',
231                   'caret-up': 'triangle-up', 'plus': 'cross',
232                   'times': 'x', 'caret-down': 'triangle-down',
233                   'caret-left': 'triangle-left', 'caret-right':
234                       'triangle-right', 'star': 'star'}
235    markerlabel = Label('Marker Choices: ')
236    marker_selector = iconselector(iconlist, selected = 'circle')
237    filled_open = Checkbox(value = True,
238                           description = 'Filled (uncheck for open)',
239                           style={'description_width':'initial'})
240    filled_open.observe(trace_format_update)
241    markersize = BoundedIntText(value = 6, min = 2, max = 25, step = 1,
242                                description = 'Marker Size (px): ',
243                                style=longdesc)
244    markersize.observe(trace_format_update)
245    markerhbox = HBox(children=[markerlabel,filled_open,markersize])
246    markervbox = VBox(children=[markerhbox, marker_selector.box])
247    line_style = Dropdown(options = ['solid','dot','dash','dashdot'],
248                          description = 'Line style: ')
249    line_style.observe(trace_format_update)
250    line_width = BoundedIntText(value = 2, min = 1, max = 25, step = 1,
251                                description = 'Linewidth (px): ',
252                                style=longdesc)
253    line_width.observe(trace_format_update)
254    linehbox = HBox(children=[line_style,line_width])
255
256    formatHbox1 = HBox(children=[modedrop,colordrop])
257    formatVbox = VBox(children=[formatHbox1,linehbox,markervbox])
258    step1formatacc = Accordion(children=[formatVbox])
259    step1formatacc.set_title(0,'Trace Formatting')
260    step1formatacc.selected_index = 0
261    yerrtype = Dropdown(options = ['none','percent','constant','data'],
262                        description = 'Error Type: ',
263                        disabled = True)
264
265    def error_settings_OK():
266        check = True
267        if (yerrtype.value == 'data') and (yerrdata.value == 'Choose error '
268                                                        'column.'):
269            check = False
270        if (xerrtype.value == 'data') and (xerrdata.value == 'Choose error '
271                                                        'column.'):
272            check = False
273        if (yerrtype.value == 'none' or yerrtype.value == 'percent' or
274            yerrtype.value == 'constant') and (xerrtype.value == 'none' or
275                                               xerrtype.value == 'percent'
276                                               or xerrtype.value =='constant'):
277            check = True
278        return check
279
280    def yerr_change(change):
281        df = friendly_to_object[whichframe.value]
282        if change['new'] == 'percent' or change['new'] == 'constant':
283            yerrvalue.disabled = False
284            yerrdata.disabled = True
285        if change['new'] == 'data':
286            yerrvalue.disabled = True
287            if yerrdata.value == 'Choose error column.':
288                add_trace_but.disabled = True
289                add_trace_but.button_style = ''
290            tempopts = ['Choose error column.']
291            tempcols = df.columns.values
292            for k in tempcols:
293                if df[k].dtype != 'O':
294                    tempopts.append(k)
295            yerrdata.options=tempopts
296            yerrdata.disabled = False
297        if change['new'] == 'none':
298            yerrvalue.disabled = True
299            yerrdata.disabled = True
300        if error_settings_OK():
301            add_trace_but.disabled = False
302            add_trace_but.button_style = 'success'
303            trace_notices.deactivate_notice(2)
304        else:
305            add_trace_but.disabled = True
306            add_trace_but.button_style = ''
307            trace_notices.activate_notice(2)
308        trace_notices.deactivate_notice(5)
309        add_trace_notices.value = trace_notices.notice_html()
310        pass
311
312    yerrtype.observe(yerr_change, names = 'value')
313
314    yerrvalue = FloatText(description = '% or constant: ', disabled = True,
315                          style=longdesc)
316    yerrdata = Dropdown(options = ['Choose error column.'],
317                        description = 'Error values: ',
318                        disabled = True)
319
320    def errdata_change(change):
321        if error_settings_OK():
322            add_trace_but.disabled = False
323            add_trace_but.button_style = 'success'
324            trace_notices.deactivate_notice(2)
325        else:
326            add_trace_but.disabled = True
327            add_trace_but.button_style = ''
328            trace_notices.activate_notice(2)
329        add_trace_notices.value = trace_notices.notice_html()
330        pass
331
332    yerrdata.observe(errdata_change, names = 'value')
333    yerrrow1 = HBox(children=[yerrtype,yerrvalue])
334    yerror = VBox(children=[yerrrow1,yerrdata])
335    xerrtype = Dropdown(options = ['none','percent','constant','data'],
336                        description = 'Error Type: ',
337                        disabled = True)
338    def xerr_change(change):
339        df = friendly_to_object[whichframe.value]
340        if change['new'] == 'percent' or change['new'] == 'constant':
341            xerrvalue.disabled = False
342            xerrdata.disabled = True
343        if change['new'] == 'data':
344            xerrvalue.disabled = True
345            if xerrdata.value == 'Choose error column.':
346                add_trace_but.disabled = True
347                add_trace_but.button_style = ''
348            tempopts = ['Choose error column.']
349            tempcols = df.columns.values
350            for k in tempcols:
351                if df[k].dtype != 'O':
352                    tempopts.append(k)
353            xerrdata.options = tempopts
354            xerrdata.disabled = False
355        if change['new'] == 'none':
356            xerrvalue.disabled = True
357            xerrdata.disabled = True
358        if error_settings_OK():
359            add_trace_but.disabled = False
360            add_trace_but.button_style = 'success'
361            trace_notices.deactivate_notice(2)
362        else:
363            add_trace_but.disabled = True
364            add_trace_but.button_style = ''
365            trace_notices.activate_notice(2)
366        trace_notices.deactivate_notice(4)
367        add_trace_notices.value = trace_notices.notice_html()
368        pass
369
370    xerrtype.observe(xerr_change, names = 'value')
371    xerrvalue = FloatText(description = '% or constant: ', disabled = True,
372                          style=longdesc)
373    xerrdata = Dropdown(options = ['Choose error column.'],
374                        description = 'Error values: ',
375                        disabled = True)
376
377    xerrdata.observe(errdata_change, names = 'value')
378    xerrrow1 = HBox(children=[xerrtype,xerrvalue])
379    xerror = VBox(children=[xerrrow1,xerrdata])
380    step1erracc = Accordion(children = [yerror,xerror])
381    step1erracc.set_title(0, 'Y error bars')
382    step1erracc.set_title(1, 'X error bars')
383    step1erracc.selected_index = None
384
385    # Add Trace button
386    add_trace_but = Button(description = 'Add Trace',
387                           disabled = True)
388    def do_add_trace(change):
389        nonlocal importstr, step1str, step2str, step3str
390        dfname = friendly_to_globalname[whichframe.value]
391        text = 'scat = go.Scatter(x = '+dfname+'[\'' \
392               +Xcoord.value+'\'],'
393        text += ' y = ' +dfname+'[\''+Ycoord.value+ \
394                                          '\'],\n'
395        text += '        mode = \''+modedrop.value+'\', name = \'' \
396                                               +trace_name.value+'\','
397        # in here add other formatting items using ifs.
398        if colordrop.value != 'default':
399            text +='\n        '
400            if str(modedrop.value).find('lines') > -1:
401                text += 'line_color = \''+colordrop.value+'\', '
402            if str(modedrop.value).find('markers') > -1:
403                text += 'marker_color = \'' + colordrop.value + '\', '
404        if str(modedrop.value).find('lines') > -1:
405            if line_style.value != 'solid':
406                text +='\n        '
407                text +='line_dash=\'' + line_style.value + '\', '
408            if line_width.value != 2:
409                text +='\n        '
410                text +='line_width=' + str(line_width.value) + ', '
411        if str(modedrop.value).find('markers') > -1:
412            if markersize.value != 6:
413                text += '\n        '
414                text += 'marker_size=' + str(markersize.value) + ', '
415            tmpmkr = icontoplotly[marker_selector.value]
416            if not filled_open.value:
417                text += '\n        '
418                tmpmkr +='-open'
419                text += 'marker_symbol=\'' + tmpmkr + '\', '
420            else:
421                if tmpmkr != 'circle':
422                    text += '\n        '
423                    text += 'marker_symbol=\'' + tmpmkr + '\', '
424        if yerrtype.value != 'none':
425            text +='\n        '
426            if yerrtype.value == 'data':
427                text += 'error_y_type=\'data\', ' \
428                        'error_y_array='+dfname
429                text += '[\''+yerrdata.value+'\'],'
430            else:
431                text += 'error_y_type=\''+yerrtype.value+'\', error_y_value='
432                text += str(yerrvalue.value)+','
433        if xerrtype.value != 'none':
434            text +='\n        '
435            if xerrtype.value == 'data':
436                text += 'error_x_type=\'data\', ' \
437                        'error_x_array='+dfname
438                text += '[\''+xerrdata.value+'\'],'
439            else:
440                text += 'error_x_type=\''+xerrtype.value+'\', error_x_value='
441                text += str(xerrvalue.value)+','
442        text += ')\n'
443        text += figname + '.add_trace(scat)\n'
444        step1str += text
445        if JPSLUtils.notebookenv == 'NBClassic':
446            replace_text_of_next_cell(importstr+step1str+step2str+step3str)
447        else:
448            codearea.sniptext.value = importstr+step1str+step2str+step3str
449        if (modedrop.value != 'lines') or (colordrop.value != 'default') or \
450            (line_style.value != 'solid') or (line_width.value != 2) or \
451            (icontoplotly[marker_selector.value] != 'circle') or \
452            (not filled_open.value) or (markersize.value != 6):
453            trace_notices.activate_notice(3)
454        if (xerrtype.value != 'none'):
455            trace_notices.activate_notice(4)
456        if (yerrtype.value != 'none'):
457            trace_notices.activate_notice(5)
458        add_trace_notices.value = trace_notices.notice_html()
459        makeplot_notices.deactivate_notice(0)
460        step4noticebox.value = makeplot_notices.notice_html()
461        pass
462    add_trace_but.on_click(do_add_trace)
463
464    trace_notices.set_active([0,1])
465    add_trace_notices = richLabel(value = trace_notices.notice_html())
466    step1tracebox = VBox(children=[whichframe,Xcoord,Ycoord,trace_name])
467    step1actionbox = VBox(children=[add_trace_but, add_trace_notices])
468    step1hbox = HBox(children=[step1tracebox,step1actionbox])
469    step1optbox = VBox(children=[step1formatacc, step1erracc])
470    step1opt = Accordion(children = [step1optbox])
471    step1opt.set_title(0, 'Optional (Trace formatting, error bars...)')
472    step1opt.selected_index = None
473    step1 = VBox(children=[step1instracc, step1hbox, step1opt])
474
475    # 2. Set Axes Labels (will use column names by default).
476    step2instr = richLabel(value = '<span style = "font-weight:bold;">You must '
477                                   'set the axes labels to something '
478                           'appropriate.</span> For example if the X - values '
479                           'represent time in seconds "Time (s)" is a good '
480                           'choice. If the Y - values for the traces all '
481                           'have the same units using the units as the label '
482                                   'is a good choice. If the Y - values '
483                                   'have different unit quantites the best '
484                                   'option is probably "values" and making '
485                                   'sure that the trace names used for the '
486                                   'legend contain the units for each trace.')
487    step2instracc = Accordion(children = [step2instr])
488    step2instracc.set_title(0,'Instructions')
489    step2instracc.selected_index = None
490    X_label = Text(placeholder = 'Provide an X-axis label (usually has units)',
491                   description = 'X-axis label: ',
492                   style = longdesc,
493                   layout=Layout(width='45%'))
494    Y_label = Text(placeholder = 'Provide a Y-axis label (usually has units)',
495                   description = 'Y-axis label: ',
496                   style = longdesc,
497                   layout=Layout(width='45%'))
498    step2hbox = HBox(children=[X_label,Y_label])
499    step2 = VBox(children=[step2instracc,step2hbox])
500    # 3.Title, Format ...
501    step3instr = richLabel(value='Overall format set here. <ul><li>Experiment '
502                                 'with '
503                                 'the Plot Styling templates to find your '
504                                 'favorite.</li>'
505                                 '<li>If the Aspect Ratio is set to `auto` the '
506                           'figure will fill the default output region. '
507                           'Other choices will allow you to pick the Plot '
508                           'Size. `Large` will use about 2/3 of an HD '
509                                 '(1920X1080) screen.</li>')
510    step3instracc = Accordion(children=[step3instr])
511    step3instracc.set_title(0, 'Instructions')
512    step3instracc.selected_index = None
513    plot_title = Text(value = figname,
514                       description = 'Plot title: ',
515                      layout = Layout(width='80%'))
516    def mirror_axes_change(change):
517        if change['new']:
518            mirror_ticks.disabled= False
519        else:
520            mirror_ticks.disabled= True
521            mirror_ticks.value = False
522        pass
523
524    mirror_axes = Checkbox(value = False,
525                           description = 'Display Mirror Axes',
526                           style = longdesc)
527    mirror_axes.observe(mirror_axes_change, names = 'value')
528    mirror_ticks = Checkbox(value = False,
529                            description = 'Mirror Tick Marks',
530                            disabled = True)
531    plot_template = Dropdown(options=['none','simple_white', 'ggplot2',
532                                    'seaborn',
533                                 'plotly', 'plotly_white', 'plotly_dark',
534                                 'presentation', 'xgridoff', 'ygridoff',
535                                 'gridon', 'simple_white+presentation',
536                                      'simple_white+gridon',
537                                      'simple_white+presentation+gridon'],
538                        value='simple_white',
539                        description = 'Plot Styling: ',
540                        style = longdesc)
541    def aspect_change(change):
542        if change['new'] != 'auto':
543            plot_size.disabled=False
544        else:
545            plot_size.disabled=True
546        pass
547
548    plot_aspect = Dropdown(options = ['auto', '16:9', '5:3', '7:5', '4:3',
549                                     '10:8', '1:1'],
550                      value = 'auto',
551                      description = 'Aspect Ratio: ',
552                      style = longdesc)
553    plot_aspect.observe(aspect_change, names = 'value')
554    plot_size = Dropdown(options = ['tiny', 'small', 'medium', 'large',
555                                    'huge'],
556                         value = 'large',
557                         description = 'Plot Size: ',
558                         style = longdesc,
559                         disabled = True)
560    step3hbox2 = HBox(children=[mirror_axes,mirror_ticks, plot_template,
561                                plot_aspect, plot_size])
562    step3 = VBox(children=[step3instracc, plot_title, step3hbox2])
563
564    # 4. Final Check*
565    step4instr = richLabel(value = 'Things to check before clicking making ' \
566                                   'the plot: <ul>' \
567                                   '<li>Fix any problems listed in ' \
568                                   '"Notices".</li>' \
569                                   '<li>Look at the code below to make sure ' \
570                                   'you have included all the traces you ' \
571                                   'intended to (look for "name").</li>' \
572                                   '<li>Check for any unpaired parentheses, ' \
573                                   'brackets or braces.</li>' \
574                                   '<li>Check that all single and double ' \
575                                   'quotes are paired.</li>' \
576                                   '<li>If you did any manual editing ' \
577                                   'double-check for typos.</li>')
578    step4noticebox = richLabel(value = makeplot_notices.notice_html())
579    def makeplt_click(change):
580        if JPSLUtils.notebookenv == 'NBClassic':
581            # These commented out lines do nothing because of timing issues.
582            # text = '\n# Force save widget states so that graph will still be\n'
583            # text += '# available when notebook next opened in trusted state.\n'
584            # text += 'import time\ntime.sleep(5)'
585            select_containing_cell('pandasplotGUI')
586            select_cell_immediately_below()
587            # insert_newline_at_end_of_current_cell(text)
588            # jscode = 'Jupyter.actions.call("widgets:save-with-widgets");'
589            # text = 'JPSLUtils.OTJS(\''+jscode+'\')'
590            # insert_newline_at_end_of_current_cell(text)
591        # run the cell to build the plot
592            JPSLUtils.OTJS('Jupyter.notebook.get_selected_cell().execute();')
593        # remove the GUI cell
594            select_containing_cell('pandasplotGUI')
595            delete_selected_cell()
596        pass
597
598    makeplotbut_lay = Layout(visibility="hidden")
599    if JPSLUtils.notebookenv == 'NBClassic':
600        makeplotbut_lay = Layout(visibility="visible")
601
602    makeplotbut = Button(description = 'Make Plot',
603                         layout = makeplotbut_lay,
604                         disabled = True)
605    makeplotbut.on_click(makeplt_click)
606    step4vbox = VBox(children=[makeplotbut,step4noticebox])
607    step4 = HBox(children=[step4instr,step4vbox])
608
609
610    steps = Tab(children=[step1, step2, step3, step4])
611    steps.set_title(0,'1. Pick Trace(s)*')
612    steps.set_title(1,'2. Label Axes*')
613    steps.set_title(2,'3. Title, Format ...')
614    steps.set_title(3,'4. Final Check*')
615    def tab_changed(change):
616        nonlocal importstr, step1strdefault, step1str, step2strdefault, \
617            step2str, step3strdefault, step3str
618        if change['new'] ==3:
619            if X_label.value == '' or Y_label.value == '':
620                makeplot_notices.activate_notice(1)
621                makeplotbut.disabled = True
622                makeplotbut.button_style = ''
623            else:
624                makeplot_notices.deactivate_notice(1)
625            step4noticebox.value = makeplot_notices.notice_html()
626        if len(makeplot_notices.get_active()) == 0:
627            makeplotbut.disabled = False
628            makeplotbut.button_style = 'success'
629        if change['new'] > 1:
630            # update step2str
631            step2str = step2strdefault
632            text = figname + '.update_xaxes(title= \'' + X_label.value + '\''
633
634            def get_mirror_text():
635                if mirror_axes.value:
636                    mirror_text = ', mirror = True)\n'
637                    if mirror_ticks.value:
638                        mirror_text = ', mirror= \'ticks\')\n'
639                else:
640                    mirror_text = ')\n'
641                return mirror_text
642
643            text += get_mirror_text()
644            step2str += text
645            text = figname + '.update_yaxes(title= \'' + Y_label.value + '\''
646            text += get_mirror_text()
647            step2str += text
648            # update step3str
649            step3str = step3strdefault
650            plot_width = 1200
651            plot_height = 675
652            if plot_title.value != '' or plot_template.value != 'simple_white':
653                text = figname + '.update_layout(title = \'' + plot_title.value + '\', '
654                text += 'template = \'' + plot_template.value + '\', '
655            if plot_aspect.value == 'auto':
656                text += 'autosize=True)\n'
657            else:
658                if plot_size.value == 'tiny':
659                    plot_width = 300
660                elif plot_size.value == 'small':
661                    plot_width = 450
662                elif plot_size.value == 'medium':
663                    plot_width = 800
664                elif plot_size.value == 'large':
665                    plot_width = 1200
666                elif plot_size.value == 'huge':
667                    plot_width = 2400
668                if plot_aspect.value == '16:9':
669                    plot_height = int(9 * plot_width / 16)
670                elif plot_aspect.value == '5:3':
671                    plot_height = int(3 * plot_width / 5)
672                elif plot_aspect.value == '7:5':
673                    plot_height = int(5 * plot_width / 7)
674                elif plot_aspect.value == '4:3':
675                    plot_height = int(3 * plot_width / 4)
676                elif plot_aspect.value == '10:8':
677                    plot_height = int(8 * plot_width / 10)
678                elif plot_aspect.value == '1:1':
679                    plot_height = plot_width
680                text += 'autosize=False,\n                        width = int('
681                text += str(plot_width)+'), height=int('
682                text += str(plot_height)+'))\n'
683            step3str += text
684            text = figname + '.show(config = {' \
685                    '\'toImageButtonOptions\': {\'format\': \'svg\'}})'
686            if JPSLUtils.notebookenv == 'NBClassic':
687                replace_text_of_next_cell(
688                    importstr+step1str+step2str+step3str+text)
689            else:
690                codearea.sniptext.value = importstr+step1str+step2str+step3str+text
691
692        pass
693
694    steps.observe(tab_changed, names = 'selected_index')
695    with output:
696        display(steps)
697    if JPSLUtils.notebookenv == 'NBClassic':
698        display(output)
699        select_containing_cell('pandasplotGUI')
700        new_cell_immediately_below()
701        insert_text_into_next_cell(importstr+step1str+step2str+step3str)
702    else:
703        codearea = build_run_snip_widget(
704            importstr+step1str+step2str+step3str, output)
705        with output:
706            display(codearea)
707        display(output)
708    pass
def plot_pandas_GUI(df_info=None, show_text_col=False, **kwargs):
  2def plot_pandas_GUI(df_info=None, show_text_col = False, **kwargs):
  3    """
  4    If passed no parameters this will look for all the dataframes in the user
  5    namespace and make them available for plotting. Once a
  6    dataframe is chosen only the numerical columns from that dataframe will
  7    be available for inclusion in the plotting expression.
  8
  9    This GUI produces code to use the plotly interactive plotting package.
 10
 11    If you wish to allow only certain dataframes or have them show up as
 12    user friendly names in the menus provide that information in the first
 13    paramater df_info.
 14
 15    To allow inclusion of text columns pass True for show_text_col.
 16
 17    :param bool show_text_col: (default = False). When True columns
 18    containing text will be shown.
 19
 20    :param list df_info: List of Lists [[object,globalname,userfriendly]],..]
 21      * object -- pandas.DataFrame
 22      * globalname -- string name of the object in the user global name space.
 23      * userfriendly -- string name to display for user selection.
 24
 25    :keyword string figname: string used to override default python name for
 26    figure.
 27
 28    :keyword bool findframes: default = True. If set to false and dataframes
 29    are passed in dfs_info, will not search for dataframes in the user
 30    namespace.
 31    """
 32    from ipywidgets import Layout, Box, HBox, VBox, GridBox, Tab, \
 33        Accordion, Dropdown, Label, Text, Button, Checkbox, FloatText, \
 34        RadioButtons, BoundedIntText, Output
 35    from ipywidgets import HTML as richLabel
 36    from IPython.display import display, HTML
 37    from IPython.display import Javascript as JS
 38    from IPython import get_ipython
 39    from JPSLUtils.utils import new_cell_immediately_below,\
 40        select_cell_immediately_below, move_cursor_in_current_cell, \
 41        insert_text_into_next_cell, insert_text_at_beginning_of_current_cell, \
 42        insert_newline_at_end_of_current_cell, select_containing_cell, \
 43        delete_selected_cell, iconselector, notice_group, notebookenv, \
 44        replace_text_of_next_cell
 45
 46    from .utils import find_pandas_dataframe_names, build_run_snip_widget
 47    from IPython import get_ipython
 48    global_dict = get_ipython().user_ns
 49    JPSLUtils = global_dict["JPSLUtils"]
 50    dfs_info = []
 51    if isinstance(df_info,list):
 52        for k in df_info:
 53            dfs_info.append(k)
 54    findframes = kwargs.pop('findframes',True)
 55    if findframes:
 56        for k in find_pandas_dataframe_names():
 57            dfs_info.append([global_dict[k],k,k])
 58    friendly_to_globalname = {k[2]:k[1] for k in dfs_info}
 59    friendly_to_object = {k[2]:k[0] for k in dfs_info}
 60
 61    figname = kwargs.pop('figname',None)
 62    from .utils import find_figure_names
 63    figlst = find_figure_names()
 64    if figname in figlst:
 65        raise UserWarning (str(figname) + ' already exists. Choose a '
 66                                          'different name for the figure.')
 67    if figname == None:
 68        figname = 'Figure_'+str(len(figlst)+1)
 69
 70    #### Define GUI Elements ####
 71    # Those followed by a * are required.
 72    output = Output()
 73    with output:
 74        display(HTML(
 75        "<h3 id ='pandasplotGUI' style='text-align:center;'>Pandas Plot "
 76        "Composer</h3> <div style='text-align:center;'>"
 77        "<span style='color:green;'>Steps with a * are required.</span> The "
 78        "code that will generate the plot is being "
 79        "built in the cell immediately below.</div><div "
 80        "style='text-align:center;'>This composer uses a subset of "
 81        "<a href ='https://plotly.com/python/line-and-scatter/#'> "
 82        "the plotly scatter plot</a> capabilities.</div>"))
 83
 84    longdesc = {'description_width':'initial'}
 85
 86    # Notices for the Final Check Tab.
 87    makeplot_notices = notice_group(['At least one trace required.',
 88                                     'Axes must have labels.'],
 89                                    'Notices:','','red')
 90    makeplot_notices.set_active([0,1])
 91
 92    # Strings for each tab, that are combined to make the code.
 93    importstr = '# CODE BLOCK generated using plot_pandas_GUI().\n# See ' \
 94        'https://jupyterphysscilab.github.io/jupyter_Pandas_GUI.\n' \
 95        'from plotly import graph_objects as go\n' + str(figname) + \
 96        ' = go.FigureWidget(layout_template=\"simple_white\")\n'
 97    step1strdefault = '\n# Trace declaration(s) and trace formatting\n'
 98    step1str = step1strdefault
 99    step2strdefault = '\n# Axes labels\n'
100    step2str = step2strdefault
101    step3strdefault = '\n# Plot formatting\n'
102    step3str = step3strdefault
103    # 1. Pick Traces*
104    #   a. Select Y vs. X pairs* (DataFrame, X and Y, which must be from single
105    #       frame.
106    # Notices for the Pick Trace(s) tab.
107    notice_list = [
108        'Data set (DataFrame) required.',
109        'X- and Y-coordinates required.',
110        'Incomplete or inconsistent error specification(s).',
111        'Non-default trace formatting left from previous trace.',
112        'X-Error settings left from previous trace.',
113        'Y-Error settings left from previous trace.',
114    ]
115    trace_notices = notice_group(notice_list, 'Notices:','','red')
116    step1instr = richLabel(value = 'For each trace you wish to include: '
117                                   '<ol><li>Select a DataFrame (Data '
118                                   'set);</li>'
119                                   '<li>Select the column containing the X '
120                                   'values;</li>'
121                                   '<li>Select the column containing the Y '
122                                   'values;</li>'
123                                   '<li>Provide a name for the trace if you do'
124                                   ' not like the default. This text will be '
125                                   'used for the legend;</li>'
126                                   '<li> OPTIONAL - set additional formatting '
127                                   'and error display by expanding the '
128                                   'sections at the bottom of this tab;</li>'
129                                   '<li>Once everything is set use the '
130                                   '<b>"Add Trace"</b> button to '
131                                   'include it in your plot.</li></ol>')
132
133    step1instracc = Accordion(children = [step1instr])
134    step1instracc.set_title(0,'Instructions')
135    step1instracc.selected_index = None
136
137    # DataFrame selection
138    tempopts = []
139    tempopts.append('Choose data set.')
140    for k in dfs_info:
141        tempopts.append(k[2])
142    whichframe = Dropdown(options=tempopts,
143                                description='DataFrame: ',)
144
145    def update_columns(change):
146        df = friendly_to_object[change['new']]
147        tempcols = df.columns.values
148        if change['new'] == 'Choose data set.':
149            Xcoord.disabled = True
150            Ycoord.disabled = True
151            add_trace_but.disabled = True
152            add_trace_but.button_style = ''
153            trace_notices.activate_notice(0)
154            trace_notices.activate_notice(1)
155            add_trace_notices.value = trace_notices.notice_html()
156            return
157        tempopt = ['Choose column for coordinate.']
158        for k in tempcols:
159            if show_text_col:
160                tempopt.append(k)
161            else:
162                if df[k].dtype != 'O':
163                    tempopt.append(k)
164        Xcoord.options = tempopt
165        Xcoord.value = tempopt[0]
166        Ycoord.options = tempopt
167        Ycoord.value = tempopt[0]
168        Xcoord.disabled = False
169        Ycoord.disabled = False
170        trace_notices.activate_notice(1)
171        trace_notices.deactivate_notice(0)
172        add_trace_notices.value =trace_notices.notice_html()
173        pass
174    whichframe.observe(update_columns, names='value')
175
176    # Data selection
177    Xcoord = Dropdown(options=['Choose X-coordinate.'],
178                           description='X: ',
179                           disabled = True)
180    Ycoord = Dropdown(options=['Choose Y-coordinate.'],
181                           description='Y: ',
182                           disabled = True)
183    def trace_name_update(change):
184        if change['new'] != 'Choose column for coordinate.':
185            trace_name.value = Ycoord.value
186        if Xcoord.value != 'Choose column for coordinate.' and Ycoord.value \
187                != 'Choose column for coordinate.':
188            add_trace_but.disabled = False
189            add_trace_but.button_style = 'success'
190            modedrop.disabled = False
191            colordrop.disabled = False
192            yerrtype.disabled = False
193            xerrtype.disabled = False
194            trace_name.disabled = False
195            trace_notices.deactivate_notice(1)
196            add_trace_notices.value = trace_notices.notice_html()
197        else:
198            add_trace_but.disabled = True
199            add_trace_but.button_style = ''
200            modedrop.disabled = True
201            colordrop.disabled = True
202            yerrtype.disabled = True
203            xerrtype.disabled = True
204            trace_name.disabled = True
205            trace_notices.activate_notice(1)
206            add_trace_notices.value = trace_notices.notice_html()
207        pass
208
209    Ycoord.observe(trace_name_update,names='value')
210
211    # Trace name
212    trace_name = Text(placeholder = 'Trace name for legend',
213                      description = 'Trace name: ',
214                      disabled = True)
215
216    #   b. Trace Style (optional)
217    def trace_format_update(change):
218        trace_notices.deactivate_notice(3)
219        add_trace_notices.value = trace_notices.notice_html()
220        pass
221
222    modedrop = Dropdown(options = ['lines','markers','lines+markers'],
223                    description = 'Trace Style: ')
224    modedrop.observe(trace_format_update)
225    colordrop = Dropdown(options=['default','blue','orange','green','purple',
226                              'red','gold','brown','black'],
227                     description = 'Color: ')
228    colordrop.observe(trace_format_update)
229    iconlist = ['circle', 'square', 'caret-up', 'star', 'plus', 'times',
230                'caret-down', 'caret-left', 'caret-right']
231    icontoplotly = {'circle': 'circle', 'square': 'square',
232                   'caret-up': 'triangle-up', 'plus': 'cross',
233                   'times': 'x', 'caret-down': 'triangle-down',
234                   'caret-left': 'triangle-left', 'caret-right':
235                       'triangle-right', 'star': 'star'}
236    markerlabel = Label('Marker Choices: ')
237    marker_selector = iconselector(iconlist, selected = 'circle')
238    filled_open = Checkbox(value = True,
239                           description = 'Filled (uncheck for open)',
240                           style={'description_width':'initial'})
241    filled_open.observe(trace_format_update)
242    markersize = BoundedIntText(value = 6, min = 2, max = 25, step = 1,
243                                description = 'Marker Size (px): ',
244                                style=longdesc)
245    markersize.observe(trace_format_update)
246    markerhbox = HBox(children=[markerlabel,filled_open,markersize])
247    markervbox = VBox(children=[markerhbox, marker_selector.box])
248    line_style = Dropdown(options = ['solid','dot','dash','dashdot'],
249                          description = 'Line style: ')
250    line_style.observe(trace_format_update)
251    line_width = BoundedIntText(value = 2, min = 1, max = 25, step = 1,
252                                description = 'Linewidth (px): ',
253                                style=longdesc)
254    line_width.observe(trace_format_update)
255    linehbox = HBox(children=[line_style,line_width])
256
257    formatHbox1 = HBox(children=[modedrop,colordrop])
258    formatVbox = VBox(children=[formatHbox1,linehbox,markervbox])
259    step1formatacc = Accordion(children=[formatVbox])
260    step1formatacc.set_title(0,'Trace Formatting')
261    step1formatacc.selected_index = 0
262    yerrtype = Dropdown(options = ['none','percent','constant','data'],
263                        description = 'Error Type: ',
264                        disabled = True)
265
266    def error_settings_OK():
267        check = True
268        if (yerrtype.value == 'data') and (yerrdata.value == 'Choose error '
269                                                        'column.'):
270            check = False
271        if (xerrtype.value == 'data') and (xerrdata.value == 'Choose error '
272                                                        'column.'):
273            check = False
274        if (yerrtype.value == 'none' or yerrtype.value == 'percent' or
275            yerrtype.value == 'constant') and (xerrtype.value == 'none' or
276                                               xerrtype.value == 'percent'
277                                               or xerrtype.value =='constant'):
278            check = True
279        return check
280
281    def yerr_change(change):
282        df = friendly_to_object[whichframe.value]
283        if change['new'] == 'percent' or change['new'] == 'constant':
284            yerrvalue.disabled = False
285            yerrdata.disabled = True
286        if change['new'] == 'data':
287            yerrvalue.disabled = True
288            if yerrdata.value == 'Choose error column.':
289                add_trace_but.disabled = True
290                add_trace_but.button_style = ''
291            tempopts = ['Choose error column.']
292            tempcols = df.columns.values
293            for k in tempcols:
294                if df[k].dtype != 'O':
295                    tempopts.append(k)
296            yerrdata.options=tempopts
297            yerrdata.disabled = False
298        if change['new'] == 'none':
299            yerrvalue.disabled = True
300            yerrdata.disabled = True
301        if error_settings_OK():
302            add_trace_but.disabled = False
303            add_trace_but.button_style = 'success'
304            trace_notices.deactivate_notice(2)
305        else:
306            add_trace_but.disabled = True
307            add_trace_but.button_style = ''
308            trace_notices.activate_notice(2)
309        trace_notices.deactivate_notice(5)
310        add_trace_notices.value = trace_notices.notice_html()
311        pass
312
313    yerrtype.observe(yerr_change, names = 'value')
314
315    yerrvalue = FloatText(description = '% or constant: ', disabled = True,
316                          style=longdesc)
317    yerrdata = Dropdown(options = ['Choose error column.'],
318                        description = 'Error values: ',
319                        disabled = True)
320
321    def errdata_change(change):
322        if error_settings_OK():
323            add_trace_but.disabled = False
324            add_trace_but.button_style = 'success'
325            trace_notices.deactivate_notice(2)
326        else:
327            add_trace_but.disabled = True
328            add_trace_but.button_style = ''
329            trace_notices.activate_notice(2)
330        add_trace_notices.value = trace_notices.notice_html()
331        pass
332
333    yerrdata.observe(errdata_change, names = 'value')
334    yerrrow1 = HBox(children=[yerrtype,yerrvalue])
335    yerror = VBox(children=[yerrrow1,yerrdata])
336    xerrtype = Dropdown(options = ['none','percent','constant','data'],
337                        description = 'Error Type: ',
338                        disabled = True)
339    def xerr_change(change):
340        df = friendly_to_object[whichframe.value]
341        if change['new'] == 'percent' or change['new'] == 'constant':
342            xerrvalue.disabled = False
343            xerrdata.disabled = True
344        if change['new'] == 'data':
345            xerrvalue.disabled = True
346            if xerrdata.value == 'Choose error column.':
347                add_trace_but.disabled = True
348                add_trace_but.button_style = ''
349            tempopts = ['Choose error column.']
350            tempcols = df.columns.values
351            for k in tempcols:
352                if df[k].dtype != 'O':
353                    tempopts.append(k)
354            xerrdata.options = tempopts
355            xerrdata.disabled = False
356        if change['new'] == 'none':
357            xerrvalue.disabled = True
358            xerrdata.disabled = True
359        if error_settings_OK():
360            add_trace_but.disabled = False
361            add_trace_but.button_style = 'success'
362            trace_notices.deactivate_notice(2)
363        else:
364            add_trace_but.disabled = True
365            add_trace_but.button_style = ''
366            trace_notices.activate_notice(2)
367        trace_notices.deactivate_notice(4)
368        add_trace_notices.value = trace_notices.notice_html()
369        pass
370
371    xerrtype.observe(xerr_change, names = 'value')
372    xerrvalue = FloatText(description = '% or constant: ', disabled = True,
373                          style=longdesc)
374    xerrdata = Dropdown(options = ['Choose error column.'],
375                        description = 'Error values: ',
376                        disabled = True)
377
378    xerrdata.observe(errdata_change, names = 'value')
379    xerrrow1 = HBox(children=[xerrtype,xerrvalue])
380    xerror = VBox(children=[xerrrow1,xerrdata])
381    step1erracc = Accordion(children = [yerror,xerror])
382    step1erracc.set_title(0, 'Y error bars')
383    step1erracc.set_title(1, 'X error bars')
384    step1erracc.selected_index = None
385
386    # Add Trace button
387    add_trace_but = Button(description = 'Add Trace',
388                           disabled = True)
389    def do_add_trace(change):
390        nonlocal importstr, step1str, step2str, step3str
391        dfname = friendly_to_globalname[whichframe.value]
392        text = 'scat = go.Scatter(x = '+dfname+'[\'' \
393               +Xcoord.value+'\'],'
394        text += ' y = ' +dfname+'[\''+Ycoord.value+ \
395                                          '\'],\n'
396        text += '        mode = \''+modedrop.value+'\', name = \'' \
397                                               +trace_name.value+'\','
398        # in here add other formatting items using ifs.
399        if colordrop.value != 'default':
400            text +='\n        '
401            if str(modedrop.value).find('lines') > -1:
402                text += 'line_color = \''+colordrop.value+'\', '
403            if str(modedrop.value).find('markers') > -1:
404                text += 'marker_color = \'' + colordrop.value + '\', '
405        if str(modedrop.value).find('lines') > -1:
406            if line_style.value != 'solid':
407                text +='\n        '
408                text +='line_dash=\'' + line_style.value + '\', '
409            if line_width.value != 2:
410                text +='\n        '
411                text +='line_width=' + str(line_width.value) + ', '
412        if str(modedrop.value).find('markers') > -1:
413            if markersize.value != 6:
414                text += '\n        '
415                text += 'marker_size=' + str(markersize.value) + ', '
416            tmpmkr = icontoplotly[marker_selector.value]
417            if not filled_open.value:
418                text += '\n        '
419                tmpmkr +='-open'
420                text += 'marker_symbol=\'' + tmpmkr + '\', '
421            else:
422                if tmpmkr != 'circle':
423                    text += '\n        '
424                    text += 'marker_symbol=\'' + tmpmkr + '\', '
425        if yerrtype.value != 'none':
426            text +='\n        '
427            if yerrtype.value == 'data':
428                text += 'error_y_type=\'data\', ' \
429                        'error_y_array='+dfname
430                text += '[\''+yerrdata.value+'\'],'
431            else:
432                text += 'error_y_type=\''+yerrtype.value+'\', error_y_value='
433                text += str(yerrvalue.value)+','
434        if xerrtype.value != 'none':
435            text +='\n        '
436            if xerrtype.value == 'data':
437                text += 'error_x_type=\'data\', ' \
438                        'error_x_array='+dfname
439                text += '[\''+xerrdata.value+'\'],'
440            else:
441                text += 'error_x_type=\''+xerrtype.value+'\', error_x_value='
442                text += str(xerrvalue.value)+','
443        text += ')\n'
444        text += figname + '.add_trace(scat)\n'
445        step1str += text
446        if JPSLUtils.notebookenv == 'NBClassic':
447            replace_text_of_next_cell(importstr+step1str+step2str+step3str)
448        else:
449            codearea.sniptext.value = importstr+step1str+step2str+step3str
450        if (modedrop.value != 'lines') or (colordrop.value != 'default') or \
451            (line_style.value != 'solid') or (line_width.value != 2) or \
452            (icontoplotly[marker_selector.value] != 'circle') or \
453            (not filled_open.value) or (markersize.value != 6):
454            trace_notices.activate_notice(3)
455        if (xerrtype.value != 'none'):
456            trace_notices.activate_notice(4)
457        if (yerrtype.value != 'none'):
458            trace_notices.activate_notice(5)
459        add_trace_notices.value = trace_notices.notice_html()
460        makeplot_notices.deactivate_notice(0)
461        step4noticebox.value = makeplot_notices.notice_html()
462        pass
463    add_trace_but.on_click(do_add_trace)
464
465    trace_notices.set_active([0,1])
466    add_trace_notices = richLabel(value = trace_notices.notice_html())
467    step1tracebox = VBox(children=[whichframe,Xcoord,Ycoord,trace_name])
468    step1actionbox = VBox(children=[add_trace_but, add_trace_notices])
469    step1hbox = HBox(children=[step1tracebox,step1actionbox])
470    step1optbox = VBox(children=[step1formatacc, step1erracc])
471    step1opt = Accordion(children = [step1optbox])
472    step1opt.set_title(0, 'Optional (Trace formatting, error bars...)')
473    step1opt.selected_index = None
474    step1 = VBox(children=[step1instracc, step1hbox, step1opt])
475
476    # 2. Set Axes Labels (will use column names by default).
477    step2instr = richLabel(value = '<span style = "font-weight:bold;">You must '
478                                   'set the axes labels to something '
479                           'appropriate.</span> For example if the X - values '
480                           'represent time in seconds "Time (s)" is a good '
481                           'choice. If the Y - values for the traces all '
482                           'have the same units using the units as the label '
483                                   'is a good choice. If the Y - values '
484                                   'have different unit quantites the best '
485                                   'option is probably "values" and making '
486                                   'sure that the trace names used for the '
487                                   'legend contain the units for each trace.')
488    step2instracc = Accordion(children = [step2instr])
489    step2instracc.set_title(0,'Instructions')
490    step2instracc.selected_index = None
491    X_label = Text(placeholder = 'Provide an X-axis label (usually has units)',
492                   description = 'X-axis label: ',
493                   style = longdesc,
494                   layout=Layout(width='45%'))
495    Y_label = Text(placeholder = 'Provide a Y-axis label (usually has units)',
496                   description = 'Y-axis label: ',
497                   style = longdesc,
498                   layout=Layout(width='45%'))
499    step2hbox = HBox(children=[X_label,Y_label])
500    step2 = VBox(children=[step2instracc,step2hbox])
501    # 3.Title, Format ...
502    step3instr = richLabel(value='Overall format set here. <ul><li>Experiment '
503                                 'with '
504                                 'the Plot Styling templates to find your '
505                                 'favorite.</li>'
506                                 '<li>If the Aspect Ratio is set to `auto` the '
507                           'figure will fill the default output region. '
508                           'Other choices will allow you to pick the Plot '
509                           'Size. `Large` will use about 2/3 of an HD '
510                                 '(1920X1080) screen.</li>')
511    step3instracc = Accordion(children=[step3instr])
512    step3instracc.set_title(0, 'Instructions')
513    step3instracc.selected_index = None
514    plot_title = Text(value = figname,
515                       description = 'Plot title: ',
516                      layout = Layout(width='80%'))
517    def mirror_axes_change(change):
518        if change['new']:
519            mirror_ticks.disabled= False
520        else:
521            mirror_ticks.disabled= True
522            mirror_ticks.value = False
523        pass
524
525    mirror_axes = Checkbox(value = False,
526                           description = 'Display Mirror Axes',
527                           style = longdesc)
528    mirror_axes.observe(mirror_axes_change, names = 'value')
529    mirror_ticks = Checkbox(value = False,
530                            description = 'Mirror Tick Marks',
531                            disabled = True)
532    plot_template = Dropdown(options=['none','simple_white', 'ggplot2',
533                                    'seaborn',
534                                 'plotly', 'plotly_white', 'plotly_dark',
535                                 'presentation', 'xgridoff', 'ygridoff',
536                                 'gridon', 'simple_white+presentation',
537                                      'simple_white+gridon',
538                                      'simple_white+presentation+gridon'],
539                        value='simple_white',
540                        description = 'Plot Styling: ',
541                        style = longdesc)
542    def aspect_change(change):
543        if change['new'] != 'auto':
544            plot_size.disabled=False
545        else:
546            plot_size.disabled=True
547        pass
548
549    plot_aspect = Dropdown(options = ['auto', '16:9', '5:3', '7:5', '4:3',
550                                     '10:8', '1:1'],
551                      value = 'auto',
552                      description = 'Aspect Ratio: ',
553                      style = longdesc)
554    plot_aspect.observe(aspect_change, names = 'value')
555    plot_size = Dropdown(options = ['tiny', 'small', 'medium', 'large',
556                                    'huge'],
557                         value = 'large',
558                         description = 'Plot Size: ',
559                         style = longdesc,
560                         disabled = True)
561    step3hbox2 = HBox(children=[mirror_axes,mirror_ticks, plot_template,
562                                plot_aspect, plot_size])
563    step3 = VBox(children=[step3instracc, plot_title, step3hbox2])
564
565    # 4. Final Check*
566    step4instr = richLabel(value = 'Things to check before clicking making ' \
567                                   'the plot: <ul>' \
568                                   '<li>Fix any problems listed in ' \
569                                   '"Notices".</li>' \
570                                   '<li>Look at the code below to make sure ' \
571                                   'you have included all the traces you ' \
572                                   'intended to (look for "name").</li>' \
573                                   '<li>Check for any unpaired parentheses, ' \
574                                   'brackets or braces.</li>' \
575                                   '<li>Check that all single and double ' \
576                                   'quotes are paired.</li>' \
577                                   '<li>If you did any manual editing ' \
578                                   'double-check for typos.</li>')
579    step4noticebox = richLabel(value = makeplot_notices.notice_html())
580    def makeplt_click(change):
581        if JPSLUtils.notebookenv == 'NBClassic':
582            # These commented out lines do nothing because of timing issues.
583            # text = '\n# Force save widget states so that graph will still be\n'
584            # text += '# available when notebook next opened in trusted state.\n'
585            # text += 'import time\ntime.sleep(5)'
586            select_containing_cell('pandasplotGUI')
587            select_cell_immediately_below()
588            # insert_newline_at_end_of_current_cell(text)
589            # jscode = 'Jupyter.actions.call("widgets:save-with-widgets");'
590            # text = 'JPSLUtils.OTJS(\''+jscode+'\')'
591            # insert_newline_at_end_of_current_cell(text)
592        # run the cell to build the plot
593            JPSLUtils.OTJS('Jupyter.notebook.get_selected_cell().execute();')
594        # remove the GUI cell
595            select_containing_cell('pandasplotGUI')
596            delete_selected_cell()
597        pass
598
599    makeplotbut_lay = Layout(visibility="hidden")
600    if JPSLUtils.notebookenv == 'NBClassic':
601        makeplotbut_lay = Layout(visibility="visible")
602
603    makeplotbut = Button(description = 'Make Plot',
604                         layout = makeplotbut_lay,
605                         disabled = True)
606    makeplotbut.on_click(makeplt_click)
607    step4vbox = VBox(children=[makeplotbut,step4noticebox])
608    step4 = HBox(children=[step4instr,step4vbox])
609
610
611    steps = Tab(children=[step1, step2, step3, step4])
612    steps.set_title(0,'1. Pick Trace(s)*')
613    steps.set_title(1,'2. Label Axes*')
614    steps.set_title(2,'3. Title, Format ...')
615    steps.set_title(3,'4. Final Check*')
616    def tab_changed(change):
617        nonlocal importstr, step1strdefault, step1str, step2strdefault, \
618            step2str, step3strdefault, step3str
619        if change['new'] ==3:
620            if X_label.value == '' or Y_label.value == '':
621                makeplot_notices.activate_notice(1)
622                makeplotbut.disabled = True
623                makeplotbut.button_style = ''
624            else:
625                makeplot_notices.deactivate_notice(1)
626            step4noticebox.value = makeplot_notices.notice_html()
627        if len(makeplot_notices.get_active()) == 0:
628            makeplotbut.disabled = False
629            makeplotbut.button_style = 'success'
630        if change['new'] > 1:
631            # update step2str
632            step2str = step2strdefault
633            text = figname + '.update_xaxes(title= \'' + X_label.value + '\''
634
635            def get_mirror_text():
636                if mirror_axes.value:
637                    mirror_text = ', mirror = True)\n'
638                    if mirror_ticks.value:
639                        mirror_text = ', mirror= \'ticks\')\n'
640                else:
641                    mirror_text = ')\n'
642                return mirror_text
643
644            text += get_mirror_text()
645            step2str += text
646            text = figname + '.update_yaxes(title= \'' + Y_label.value + '\''
647            text += get_mirror_text()
648            step2str += text
649            # update step3str
650            step3str = step3strdefault
651            plot_width = 1200
652            plot_height = 675
653            if plot_title.value != '' or plot_template.value != 'simple_white':
654                text = figname + '.update_layout(title = \'' + plot_title.value + '\', '
655                text += 'template = \'' + plot_template.value + '\', '
656            if plot_aspect.value == 'auto':
657                text += 'autosize=True)\n'
658            else:
659                if plot_size.value == 'tiny':
660                    plot_width = 300
661                elif plot_size.value == 'small':
662                    plot_width = 450
663                elif plot_size.value == 'medium':
664                    plot_width = 800
665                elif plot_size.value == 'large':
666                    plot_width = 1200
667                elif plot_size.value == 'huge':
668                    plot_width = 2400
669                if plot_aspect.value == '16:9':
670                    plot_height = int(9 * plot_width / 16)
671                elif plot_aspect.value == '5:3':
672                    plot_height = int(3 * plot_width / 5)
673                elif plot_aspect.value == '7:5':
674                    plot_height = int(5 * plot_width / 7)
675                elif plot_aspect.value == '4:3':
676                    plot_height = int(3 * plot_width / 4)
677                elif plot_aspect.value == '10:8':
678                    plot_height = int(8 * plot_width / 10)
679                elif plot_aspect.value == '1:1':
680                    plot_height = plot_width
681                text += 'autosize=False,\n                        width = int('
682                text += str(plot_width)+'), height=int('
683                text += str(plot_height)+'))\n'
684            step3str += text
685            text = figname + '.show(config = {' \
686                    '\'toImageButtonOptions\': {\'format\': \'svg\'}})'
687            if JPSLUtils.notebookenv == 'NBClassic':
688                replace_text_of_next_cell(
689                    importstr+step1str+step2str+step3str+text)
690            else:
691                codearea.sniptext.value = importstr+step1str+step2str+step3str+text
692
693        pass
694
695    steps.observe(tab_changed, names = 'selected_index')
696    with output:
697        display(steps)
698    if JPSLUtils.notebookenv == 'NBClassic':
699        display(output)
700        select_containing_cell('pandasplotGUI')
701        new_cell_immediately_below()
702        insert_text_into_next_cell(importstr+step1str+step2str+step3str)
703    else:
704        codearea = build_run_snip_widget(
705            importstr+step1str+step2str+step3str, output)
706        with output:
707            display(codearea)
708        display(output)
709    pass

If passed no parameters this will look for all the dataframes in the user namespace and make them available for plotting. Once a dataframe is chosen only the numerical columns from that dataframe will be available for inclusion in the plotting expression.

This GUI produces code to use the plotly interactive plotting package.

If you wish to allow only certain dataframes or have them show up as user friendly names in the menus provide that information in the first paramater df_info.

To allow inclusion of text columns pass True for show_text_col.

Parameters
  • bool show_text_col: (default = False). When True columns containing text will be shown.

  • list df_info: List of Lists [[object,globalname,userfriendly]],..]

    • object -- pandas.DataFrame
    • globalname -- string name of the object in the user global name space.
    • userfriendly -- string name to display for user selection.

:keyword string figname: string used to override default python name for figure.

:keyword bool findframes: default = True. If set to false and dataframes are passed in dfs_info, will not search for dataframes in the user namespace.