"""
ePSproc plotting functions with Holoviews + Bokeh.
Aim: simple plotters for different datatypes, interactive in Jupyter Notebook + HTML formats.
15/07/20 Debugged, still pretty basic but running.
05/07/20 v1 in development.
See
- https://epsproc.readthedocs.io/en/dev/tests/basicPlotting_dev_XC_030720.html
- Plotting test notebooks (/tests/plottingDev) for more.
- Dev code: http://localhost:8888/notebooks/github/ePSproc/epsproc/tests/plottingDev/basicPlotting_dev_280620.ipynb
Todo
- Plotting style mapping & options. Currently having HV issues here.
- Currently set only for XC datatypes from single dataSet, will want to enable stacking etc. here.
- Errorbar or spread plots, currently having issues getting these working for multidim data.
"""
import xarray as xr
# from matplotlib import pyplot as plt # For addtional plotting functionality - also need to import here for Seaborn styles to function.
# import holoviews as hv
# Optionals
# Additional plotters
# Seaborn for styles
try:
import seaborn as sns
snsFlag = True
except ImportError as e:
if e.msg != "No module named 'seaborn'":
raise
print('* Seaborn not found, SNS styles not available. ')
snsFlag = False
# Holoviews for plotting interface
try:
import holoviews as hv
from holoviews import opts
hvFlag = True
except ImportError as e:
if e.msg != "No module named 'holoviews'":
raise
print('* Holoviews not found, hvPlotters not available. ')
hvFlag = False
# Set plotters & options.
[docs]def setPlotters(hvBackend = 'bokeh', width = 500):
"""
Set some plot options - Seaborn style + HV defaults.
May have some issues with scope here - TBC. Should just run on function import?
Update: now moved to module import.
Parameters
-----------
hvBackend : str or list of strs, optional, default = 'bokeh'
Backend(s) for holoviews to load.
Can call bokeh, matplotlib or plotly
width : int, optional, default = 500
Setting for plot width, in pixels.
"""
# Plotting libs
# Optional - set seaborn for plot styling
if snsFlag:
import seaborn as sns
sns.set_context("paper") # "paper", "talk", "poster", sets relative scale of elements
# https://seaborn.pydata.org/tutorial/aesthetics.html
# sns.set(rc={'figure.figsize':(11.7,8.27)}) # Set figure size explicitly (inch)
# https://stackoverflow.com/questions/31594549/how-do-i-change-the-figure-size-for-a-seaborn-plot
# Wraps Matplotlib rcParams, https://matplotlib.org/tutorials/introductory/customizing.html
sns.set(rc={'figure.dpi':(120)})
from matplotlib import pyplot as plt # For addtional plotting functionality
# import bokeh
# import holoviews as hv
# from holoviews import opts
if hvFlag:
# Set HV extension
try:
hv.extension(hvBackend)
except ImportError as e:
if e.msg != "None of the backends could be imported":
raise
if hvBackend == 'bokeh':
print("Possible bokeh version issue, see https://github.com/holoviz/holoviews/issues/2012. (For Holoviews 1.12.5, Bokeh 1.4.0 works, Bokeh 2.0.0 doesn't.)")
# Set global HV options
# Setting frame_width here results in offset plots in layout - try setting later?
# opts.defaults(opts.Curve(frame_width=500, tools=['hover'], show_grid=True, padding=0.01))
opts.defaults(opts.Curve(width=width, tools=['hover'], show_grid=True, padding=0.01))
# return hv.output.info()
# Convert "standard" XS dataarray to dataset format.
[docs]def hvdsConv(dataXS):
"""
Basic conversion for XS data from Xarray to Holoviews.
This will drop stacked Sym dims, and sum of Total to reduce - may not be appropriate in all cases?
"""
ds = xr.Dataset({'sigma':dataXS.sel({'XC':'SIGMA'}).drop('XC'), 'beta':dataXS.sel({'XC':'BETA'}).drop('XC')})
hv_ds = hv.Dataset(ds.unstack().sum('Total')) # OK - reduce Sym dims.
return hv_ds, ds
# HV plotting routine for XS data
[docs]def XCplot(dataXS, lineDashList = {'L': 'dashed', 'M': 'solid', 'V': 'dashed'}):
"""
Plot XC data using Holoviews.
Currently optional stuff hard-coded here, will produce plots [sigma, beta] showing all data.
Rather crude, needs some more style mapping.
Parameters
-----------
dataXS : Xarray
Xarray dataarray containing XC data in standard format.
lineDashList : dict, optional, default = {'L': 'dashed', 'M': 'solid', 'V': 'dashed'}
Set line types for calculation gauge.
Returns
--------
layout : hv object
Examples
---------
>>> plotObj, _,_ = XCplot(dataXS[0])
>>> plotObj
"""
# Convert to HV dataset
hv_ds, ds = hvdsConv(dataXS)
# Set options
# THIS NEEDS work!
# lineDashList = {'L': 'dashed', 'M': 'solid', 'V': 'dashed'}
# lineColorList = {'PU': 'blue', 'SU': 'red', 'All': 'green'}
# This is working now... just need better cmapping
dsPlotSet = hv.Layout()
for vdim in ds.var():
plotList = []
# for vdim in ds.var():
for gauge in ds.Type:
# print(gauge.item())
# Explicit looping here works for setting desired parameters independently
# dsPlotSet += hv_ds.select(Type=gauge.item()).to(hv.Curve, kdims=["Eke"], vdims=vdim, dynamic=False).opts(line_dash=lineList[gauge.item()]).overlay(['Cont'])
# plotList.append(hv_ds.select(Type=gauge.item()).to(hv.Curve, kdims=["Eke"], vdims=[vdim], dynamic=False).opts(line_dash=lineList[gauge.item()]).overlay(['Cont']))
# Keep Type dim until *after* curve setting to allow for correct composition from list (otherwise will create Holomaps rather than curves)
# With cmap also on type
# plotList.append(hv_ds.to(hv.Curve, kdims=["Eke"], vdims=[vdim, 'Type'], dynamic=False).select(Type=gauge.item()).opts(line_dash=lineDashList[gauge.item()], color=lineColorList[gauge.item()]).overlay(['Cont']))
# Cmap on Cont
plotList.append(hv_ds.to(hv.Curve, kdims=["Eke"], vdims=[vdim, 'Type'], dynamic=False).select(Type=gauge.item()).opts(line_dash=lineDashList[gauge.item()]).overlay(['Cont']))
dsPlotSet += hv.Overlay(plotList) #.groupby('Cont') #.collate()
# (dsPlotSet + hv.Table(hv_ds)).cols(1)
# return (dsPlotSet + hv.Table(hv_ds)).cols(1), hv_ds, ds
# return (dsPlotSet).cols(1).opts(opts.Curve(frame_width=500)), hv.Table(hv_ds), hv_ds, ds
return (dsPlotSet).cols(1), hv.Table(hv_ds), hv_ds, ds