# 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)