Source code for pytplot.tplot

# Copyright 2018 Regents of the University of Colorado. All Rights Reserved.
# Released under the MIT license.
# This software was developed at the University of Colorado's Laboratory for Atmospheric and Space Physics.
# Verify current version before use at: https://github.com/MAVENSDC/PyTplot

from __future__ import division
import sys
import os
import pytplot
from bokeh.io import output_file, show, output_notebook, save
from . import HTMLPlotter
from bokeh.embed import components
from pytplot import tplot_utilities
import tempfile


def webengine_hack():
    """ This function is a hack to resolve an import error. 
    Without this, we sometimes get:
    ImportError: QtWebEngineWidgets must be imported before a QCoreApplication instance is created
    """
    from PyQt5 import QtWidgets
    app = QtWidgets.QApplication.instance()
    if app is not None:
        import sip
        app.quit()
        sip.delete(app)
    import sys
    from PyQt5 import QtCore, QtWebEngineWidgets
    QtCore.QCoreApplication.setAttribute(QtCore.Qt.AA_ShareOpenGLContexts)
    app = QtWidgets.qApp = QtWidgets.QApplication(sys.argv)
    return app


if pytplot.using_graphics:
    from .QtPlotter import PyTPlot_Exporter
    from pyqtgraph.Qt import QtCore, QtGui, QtCore
    import pyqtgraph as pg
    from . import QtPlotter
    from pytplot.AncillaryPlots import spec_slicer
    from pytplot.AncillaryPlots import position_mars_2d
    from pytplot.AncillaryPlots import position_mars_3d

[docs]def tplot(name, var_label=None, slice=False, combine_axes=True, nb=False, save_file=None, gui=False, qt=False, bokeh=False, save_png=None, display=True, testing=False, extra_functions=[], extra_function_args=[], vert_spacing=None, pos_2d=False, pos_3d=False, exec_qt=True, window_name='Plot', interactive=False): """ This is the function used to display the tplot variables stored in memory. The default output is to show the plots stacked on top of one another inside of a qt window Parameters: name : str / list List of tplot variables that will be plotted var_label : str, optional The name of the tplot variable you would like as a second x axis. slice : bool, optional If True, a secondary interactive plot will be generated next to spectrogram plots. Mousing over the spectrogram will display a slice of data from that time on the interactive chart. combine_axes : bool, optional If True, the axes are combined so that they all display the same x range. This also enables scrolling/zooming/panning on one plot to affect all of the other plots simultaneously. nb : bool, optional If True, the plot will be displayed inside of a current Jupyter notebook session. save_file : str, optional A full file name and path. If this option is set, the plot will be automatically saved to the file name provided in an HTML format. The plots can then be opened and viewed on any browser without any requirements. bokeh : bool, optional If True, plots data using bokeh Else (bokeh=False or omitted), plots data using PyQtGraph extra_functions: func, optional This is an extra function that gets called just prior to the data being plotted. This is useful if you'd like to build your own Qt display that reacts to the mouse movement. Built in displays can be found in the AncillaryPlots folder. extra_function_args: list of tuples, optional These are the arguments to give your extra_functions vert_spacing: int Thes distance in pixels you'd like the plots to be gui : bool, optional If True, then this function will output the 2 HTML components of the generated plots as string variables. This is useful if you are embedded the plots in your own GUI. For more information, see http://bokeh.pydata.org/en/latest/docs/user_guide/embed.html qt : bool, optional If True, then this function will display the plot inside of the Qt window. From this window, you can choose to export the plots as either an HTML file, or as a PNG. save_png : str, optional A full file name and path. If this option is set, the plot will be automatically saved to the file name provided in a PNG format. display: bool, optional If True, then this function will display the plotted tplot variables. Necessary to make this optional so we can avoid it in a headless server environment. testing: bool, optional If True, doesn't run the '(hasattr(sys, 'ps1'))' line that makes plots interactive - i.e., avoiding issues Returns: None Examples: >>> #Plot a single line in bokeh >>> import pytplot >>> x_data = [2,3,4,5,6] >>> y_data = [1,2,3,4,5] >>> pytplot.store_data("Variable1", data={'x':x_data, 'y':y_data}) >>> pytplot.tplot("Variable1",bokeh=True) >>> #Display two plots >>> x_data = [1,2,3,4,5] >>> y_data = [[1,5],[2,4],[3,3],[4,2],[5,1]] >>> pytplot.store_data("Variable2", data={'x':x_data, 'y':y_data}) >>> pytplot.tplot(["Variable1", "Variable2"]) >>> #Display 2 plots, using Variable1 as another x axis >>> x_data = [1,2,3] >>> y_data = [ [1,2,3] , [4,5,6], [7,8,9] ] >>> v_data = [1,2,3] >>> pytplot.store_data("Variable3", data={'x':x_data, 'y':y_data, 'v':v_data}) >>> pytplot.options("Variable3", 'spec', 1) >>> pytplot.tplot(["Variable2", "Variable3"], var_label='Variable1') >>> #Plot all 3 tplot variables, sending the output to an HTML file >>> pytplot.tplot(["Variable1", "Variable2", "Variable3"], save_file='C:/temp/pytplot_example.html') >>> #Plot all 3 tplot variables, sending the HTML output to a pair of strings >>> div, component = pytplot.tplot(["Variable1", "Variable2", "Variable3"], gui=True) """ if interactive: slice=True if not pytplot.using_graphics and save_file is None: print("Qt was not successfully imported. Specify save_file to save the file as a .html file.") return # Check a bunch of things if not isinstance(name, list): name = [name] num_plots = 1 else: num_plots = len(name) for i in range(num_plots): if isinstance(name[i], int): name[i] = list(pytplot.data_quants.keys())[name[i]] if name[i] not in pytplot.data_quants.keys(): print(str(name[i]) + " is currently not in pytplot") return if isinstance(var_label, int): var_label = list(pytplot.data_quants.keys())[var_label] if vert_spacing is None: if 'vertical_spacing' in pytplot.tplot_opt_glob: vert_spacing = pytplot.tplot_opt_glob['vertical_spacing'] else: vert_spacing = 25 # Just a default that looks ok if bokeh: layout = HTMLPlotter.generate_stack(name, var_label=var_label, combine_axes=combine_axes, slice=slice) # Output types if gui: script, div = components(layout) return script, div elif nb: output_notebook() show(layout) return elif save_file is not None: output_file(save_file, mode='inline') save(layout) return elif qt: try: from PyQt5.QtWebKitWidgets import QWebView as WebView except: try: from PyQt5.QtWebEngineWidgets import QWebEngineView as WebView except: app = webengine_hack() from PyQt5.QtWebEngineWidgets import QWebEngineView as WebView available_qt_window = tplot_utilities.get_available_qt_window() dir_path = tempfile.gettempdir() # send to user's temp directory output_file(os.path.join(dir_path, "temp.html"), mode='inline') save(layout) new_layout = WebView() available_qt_window.resize(pytplot.tplot_opt_glob['window_size'][0] + 100, pytplot.tplot_opt_glob['window_size'][1] + 100) new_layout.resize(pytplot.tplot_opt_glob['window_size'][0], pytplot.tplot_opt_glob['window_size'][1]) dir_path = tempfile.gettempdir() # send to user's temp directory new_layout.setUrl(QtCore.QUrl.fromLocalFile(os.path.join(dir_path, "temp.html"))) available_qt_window.newlayout(new_layout) available_qt_window.show() available_qt_window.activateWindow() if testing: return if not (hasattr(sys, 'ps1')) or not hasattr(QtCore, 'PYQT_VERSION'): QtGui.QApplication.instance().exec_() else: try: magic = get_ipython().magic magic(u'%gui qt5') except: pass return else: dir_path = tempfile.gettempdir() # send to user's temp directory output_file(os.path.join(dir_path, "temp.html"), mode='inline') if not testing: show(layout) return else: if save_png is not None: layout = QtPlotter.generate_stack(name, var_label=var_label, combine_axes=combine_axes, vert_spacing=vert_spacing) layout.resize(pytplot.tplot_opt_glob['window_size'][0], pytplot.tplot_opt_glob['window_size'][1]) for i, item in enumerate(layout.items()): if type(item) == pg.graphicsItems.GraphicsLayout.GraphicsLayout: layout.items()[i].resize(pytplot.tplot_opt_glob['window_size'][0], pytplot.tplot_opt_glob['window_size'][1]) exporter = PyTPlot_Exporter.PytplotExporter(layout) exporter.parameters()['width'] = pytplot.tplot_opt_glob['window_size'][0] exporter.parameters()['height'] = pytplot.tplot_opt_glob['window_size'][1] exporter.export(save_png) if display: # This layout is used when the user wants a png image saved. layout_orig = QtPlotter.generate_stack(name, var_label=var_label, combine_axes=combine_axes, vert_spacing=vert_spacing) layout_orig.resize(pytplot.tplot_opt_glob['window_size'][0], pytplot.tplot_opt_glob['window_size'][1]) for i, item in enumerate(layout_orig.items()): if type(item) == pg.graphicsItems.GraphicsLayout.GraphicsLayout: layout_orig.items()[i].resize(pytplot.tplot_opt_glob['window_size'][0], pytplot.tplot_opt_glob['window_size'][1]) try: #TODO: This exporter requires h5py to be installed because that is what pyqtgraph requires. exporter = QtPlotter.PytplotExporter(layout_orig) except: exporter = None # Set up displayed plot window and grab plots to plot on it available_qt_window = tplot_utilities.get_available_qt_window(name=window_name) layout = QtPlotter.generate_stack(name, var_label=var_label, combine_axes=combine_axes, vert_spacing=vert_spacing) available_qt_window.newlayout(layout) available_qt_window.resize(pytplot.tplot_opt_glob['window_size'][0], pytplot.tplot_opt_glob['window_size'][1]) # Implement button that lets you save the PNG available_qt_window.init_savepng(exporter) # Show the plot window and plot available_qt_window.show() available_qt_window.activateWindow() # This function is responsible for calling all of the extra plotting routines that a user might like with # their data plots extra_function_handler(extra_functions, extra_function_args, name, slice, pos_2d, pos_3d) if testing: return # (hasattr(sys, 'ps1')) checks to see if we're in ipython if (not (hasattr(sys, 'ps1')) or not hasattr(QtCore, 'PYQT_VERSION')) and exec_qt: QtGui.QApplication.instance().exec_() pass else: try: magic = get_ipython().magic magic(u'%gui qt5') except: pass return
def extra_function_handler(extra_functions, extra_functions_args, names, slice, pos_2d, pos_3d): functions_to_call = extra_functions function_args_to_call = extra_functions_args # Handles the old way of calling the spec slicing plots, (if anyone still uses that way) if slice: # Call 2D spice slicing window; This will only plot something when spectrograms are involved. functions_to_call.append(spec_slicer.spec_slicer) function_args_to_call.append([None, None, True]) if pos_2d: functions_to_call.append(position_mars_2d.position_mars_2d) function_args_to_call.append([None]) if pos_3d: functions_to_call.append(position_mars_3d.position_mars_3d) function_args_to_call.append([None]) static_list = [i for i in names if 'static' in pytplot.data_quants[i].attrs['plot_options']['extras']] for tplot_var in static_list: # Call 2D static window; This will only plot something when spectrograms are involved. functions_to_call.append(spec_slicer.spec_slicer) function_args_to_call.append( [tplot_var, pytplot.data_quants[tplot_var].attrs['plot_options']['extras']['static'], False]) static_tavg_list = [i for i in names if 'static_tavg' in pytplot.data_quants[i].attrs['plot_options']['extras']] for tplot_var in static_tavg_list: # Call 2D static window for time-averaged values; This will only plot something when spectrograms # are involved functions_to_call.append(spec_slicer.spec_slicer) function_args_to_call.append([tplot_var,pytplot.data_quants[tplot_var].attrs['plot_options']['extras']['static_tavg'], False]) for f, args in zip(functions_to_call, function_args_to_call): f(*args)