# <<BEGIN-copyright>>
# <<END-copyright>>
from fudge.core.utilities.brb import uniquify
from pqu import PQU
__metaclass__ = type
__docformat__ = "restructuredtext en"
INCLASSAXISSETTING = False
#--------------------------------------------------------
#
# global data
#
#--------------------------------------------------------
'''
A few words about MatPlotLib symbols ("markers" in MatPlotLib-speak)...
. point
, pixel
o circle
v triangle_down
^ triangle_up
< triangle_left
> triangle_right
1 tri_down -- DAB 11/17/2014: doesn't show up
2 tri_up -- DAB 11/17/2014: doesn't show up
3 tri_left -- DAB 11/17/2014: doesn't show up
4 tri_right -- DAB 11/17/2014: doesn't show up
8 octagon
s square
p pentagon
* star
h hexagon1
H hexagon2
+ plus -- DAB 11/17/2014: barely visable
x x -- DAB 11/17/2014: barely visable
D diamond
d thin_diamond
| vline -- DAB 11/17/2014: doesn't show up
_ hline -- DAB 11/17/2014: doesn't show up
'''
defaultSymbols = [ 'o', 'v', '^', '<', '>', 's', 'p', '*', 'h', 'H', '+', 'x', 'D', 'd' ]
nSymbols = len( defaultSymbols )
defaultLineStyles = [ '-', '--', '-.', ':' ]
nLineStyles=len(defaultLineStyles)
'''
A few words about MatPlotLib colors....
Commands which take color arguments can use several formats to specify the colors. For the basic built-in colors, you can use a single letter
b: blue
g: green
r: red
c: cyan
m: magenta
y: yellow
k: black
w: white
Gray shades can be given as a string encoding a float in the 0-1 range, e.g.:
color = '0.75'
For a greater range of colors, you have two options. You can specify the color using an html hex string, as in:
color = '#eeefff'
or you can pass an R , G , B tuple, where each of R , G , B are in the range [0,1].
Finally, legal html names for colors, like 'red', 'burlywood' and 'chartreuse' are supported.'''
defaultColors = [ 'k','r','g','b','y','brown','gray','violet','cyan',
'magenta','orange','indigo','maroon','turquoise',
'darkgreen','0.20','0.40','0.60','0.80' ]
nColors = len( defaultColors )
[docs]def convertUnit( unitFrom, unitTo, xs, xErrs, legend ) :
if unitTo is None: return None
if unitFrom is None: return None
unitFrom = unitFrom.replace('EV', 'eV')
if 'PER-CENT' in unitFrom: unitFrom='PERCENT'
elif 'no-dim' in unitFrom: unitFrom=''
elif 'DEG-K' in unitFrom: unitFrom='K'
elif 'KeV' in unitFrom: unitFrom='keV'
unit = unitFrom
if( ( unitFrom is not None ) and ( unitTo is not None ) and ( unitFrom != unitTo ) ) :
try:
conversionFactor = PQU.valueOrPQ( 1.0, unitFrom = unitFrom, unitTo = unitTo, asPQU = False )
except TypeError as err:
if legend is None: legend="name suppressed"
raise TypeError( "In plot2d.convertUnit, " + err.message + ', error from PQU: ' + unitFrom + ' -> ' + unitTo + ' for dataset ' + str( legend ) )
except NameError as err:
if legend is None: legend="name suppressed"
raise NameError( "In plot2d.convertUnit, " + err.message + ', error from PQU: ' + unitFrom + ' -> ' + unitTo + ' for dataset ' + str( legend ) )
if( xErrs is not None ) :
for i1, x1 in enumerate( xErrs ) : xErrs[i1] = conversionFactor * x1
for i1, x1 in enumerate( xs ) : xs[i1] = conversionFactor * x1
unit = unitTo
return( unit )
#--------------------------------------------------------
#
# Classes for new, matplotlib based plotting
#
#--------------------------------------------------------
[docs]class TextSettings:
'''
Base class for text objects in plots
Member data (set these with __init__ function):
* **text** the text itself
* **font** the font (e.g. Helvetica) (optional)
* **fontSize** font size in points (optional, defaults to 14)
* **fontWeight** font weight (e.g. bold) (optional)
'''
def __init__(self, text=None, font=None, fontSize=12, fontWeight='bold' ):
self.text = text
self.font = font
self.fontSize = fontSize
self.fontWeight = fontWeight
[docs]class AxisSettings( TextSettings ):
'''
Member data (set these with __init__ function) ::
- `axisMin` : min value of the axis
This is optional and defaults to `None` causing autoscaling.
If given as float, we assume in units of `unit`.
If given as a PQU instance, we'll
attempt to convert it to `unit`.
- `axisMax` : max value of the axis. See axisMin for usage
- `isLog` : (optional, defaults to False) False == linear, True == semilog
- `unit` : (optional, defaults to None), must be None or something that is usable in a
PQU instance
- `gridOn` : (optional)
'''
def __init__( self, axisMin=None, axisMax=None, autoscale=True, isLog=False, unit=None,
gridOn=False, label=None, font=None, fontSize=20, fontWeight='normal' ):
TextSettings.__init__(self, text=label, font=font, fontSize=fontSize, fontWeight=fontWeight )
if isinstance( axisMin, PQU.PQU ):
if unit is not None: self.axisMin = axisMin.getValueAs( unit )
else: self.axisMin = axisMin.getValue( )
else: self.axisMin = axisMin
if isinstance( axisMax, PQU.PQU ):
if unit is not None: self.axisMax = axisMax.getValueAs( unit )
else: self.axisMax = axisMax.getValue( )
else: self.axisMax = axisMax
self.isLog = isLog
self.gridOn = gridOn
self.autoscale = autoscale
if axisMin is not None and axisMax is not None:
self.autoscale = False
self.unit = unit
self.label = self.text
if self.unit is not None:
if self.unit == '': self.label += ' (dimensionless)'
else: self.label += ' (' + self.unit + ')'
def __str__( self ) :
units = ''
if( self.unit is not None ) : units = " %s" % self.unit
mode = 'linear'
if( self.isLog ) : mode = 'log'
if( self.autoscale ) :
s = 'Axis "%s" is autoscaled' % self.label
else :
s = 'Axis "%s" spans %s to %s%s' % ( self.label, self.axisMin, self.axisMax, units )
s += ' as %s' % mode
if( self.gridOn ) : s += ' with grids'
font = 'default'
if( self.font is None ) : # Need to fix this
pass
s += '. Font is "%s" with size %s and weight "%s".' % ( font, self.fontSize, self.fontWeight )
return( s )
[docs] def setupAxis( self, theAxis ):
theAxis.grid( self.gridOn, linewidth=1.5 )
if INCLASSAXISSETTING: # this doesn't' work, should I remove it?
if self.isLog: theAxis.set_scale( 'log', linewidth=2 )
else: theAxis.set_scale( 'linear', linewidth=2 )
if not self.autoscale:
theAxis.set_data_interval( self.axisMin, self.axisMax )
theAxis.set_view_interval( self.axisMin, self.axisMax )
[docs]class DataSetParameters:
'''
Base class for all DataSet classes (DataSet2d, DataSet3d, ...)
Member data (set these with __init__ function):
* legend (optional, defaults to '')
* symbol
* symbolColor
* symbolSize
* lineStyle
* lineColor
* lineWidth
Commands which take color arguments can use several formats to specify the colors.
For the basic builtin colors, you can use a single letter
========= ====================
character description
========= ====================
b blue
g green
r red
c cyan
m magenta
y yellow
k black
w white
========= ====================
Gray shades can be given as a string encoding a float in the 0-1 range, e.g.:
>>> color = '0.75'
For a greater range of colors, you have two options. You can specify the color
using an html hex string, as in:
>>> color = '#eeefff'
or you can pass an R , G , B tuple, where each of R , G , B are in the range [0,1].
Finally, legal html names for colors, like "red", "burlywood" and "chartreuse" are
supported.
The following format string characters are accepted to control the line style or
marker:
========= ====================
character description
========= ====================
'-' solid line style
'--' dashed line style
'-.' dash-dot line style
':' dotted line style
'.' point marker
',' pixel marker
'o' circle marker
'v' triangle_down marker
'^' triangle_up marker
'<' triangle_left marker
'>' triangle_right marker
'1' tri_down marker
'2' tri_up marker
'3' tri_left marker
'4' tri_right marker
's' square marker
'p' pentagon marker
'*' star marker
'h' hexagon1 marker
'H' hexagon2 marker
'+' plus marker
'x' x marker
'D' diamond marker
'd' thin_diamond marker
'|' vline marker
'_' hline marker
========= ====================
'''
def __init__( self, legend='', symbol=None, symbolSize=5, color=None, lineStyle=None,
lineWidth=None, errorbarCapSize=3, errorbarLineWidth=1.5, errorbarColor=None,
formatString=None, dataType=None ):
self.legend = legend
self.symbol = symbol
self.color = color
self.symbolSize = symbolSize # not yet used
self.lineStyle = lineStyle
self.lineWidth = lineWidth
self.errorbarCapSize = errorbarCapSize
self.errorbarLineWidth = errorbarLineWidth
self.errorbarColor = errorbarColor
self.formatString = formatString
[docs] def addToPlot( self, thePlot ): raise NotImplementedError( "Implement in derived classes" )
[docs]class DataSet2d( DataSetParameters ):
'''
Member data (set these with __init__ function):
* data: the data as either a list of [ x, y ] pairs or as an object that has the method copyDataToXsAndYs
(and also toPointwise_withLinearXYs if infill is True).
* uncertainty: the uncertainty on the data as either a list of [ x, y ] pairs or as an object that has
the method evaluate.
* dataType ('scatter', 'line', or None): by default lists of points are rendered as 'scatter' and the
others as 'line'. Override the default behavior with this keyword.
* xUnits: If the data object has an axes member, units are taken from it. Otherwise this argument is used
to for the x units. When the units are given as None and the data/uncertainty do not have an axes member
units are assumed to match those of the plot axis. The default is None.
* yUnits: Units for the y-axis. See xUnits for more information.
type help(DataSetParameters) to find out about how to set the line properties, etc.
All of this information is passed on though the __init__ function of DataSet2d to
DataSetParameters.
'''
def __init__( self, data, uncertainty = None, xUnit = None, yUnit = None, dataType = None, infill = False, **kw ):
self.xerr = None
self.yerr = None
self.xUnit = xUnit
self.yUnit = yUnit
if( ( type( data ) == list ) or ( type( data ) == tuple ) ) :
self.x = [ p[0] for p in data ]
self.y = [ p[1] for p in data ]
if dataType is None: self.dataType = 'scatter'
else: self.dataType = dataType
if 'lineStyle' not in kw: kw[ 'lineStyle' ] = '-'
if( ( type( uncertainty ) == list ) or ( type( uncertainty ) == tuple ) ) :
try: self.xerr = [ p[0] for p in uncertainty ]
except IndexError: pass
try: self.yerr = [ p[1] for p in uncertainty ]
except IndexError: pass
else :
if dataType is None: self.dataType = 'line'
else: self.dataType = dataType
if( hasattr( data, 'axes' ) ) :
self.xUnit = data.axes[1].unit
self.yUnit = data.axes[0].unit
if( infill ) :
if( not( hasattr( data, 'toPointwise_withLinearXYs' ) ) ) : raise TypeError( "Unsupported type for infilling: %s" % type( data ) )
data = data.toPointwise_withLinearXYs( accuracy = 1e-3, upperEps = 1e-6 )
if( hasattr( data, 'copyDataToXsAndYs' ) ) :
self.x, self.y = data.copyDataToXsAndYs( )
if( uncertainty is not None ): self.yerr = [ uncertainty.evaluate( xx ) for xx in self.x ]
else :
raise TypeError( "Unsupported type: " + str( type( data ) ) )
if( 'legend' not in kw ) :
if( hasattr( data, 'legend' ) ) :
kw_ = {}
for key in kw : kw_[key] = kw[key]
kw_['legend'] = data.legend
kw = kw_
if( dataType is not None ) : self.dataType = dataType
DataSetParameters.__init__( self, **kw )
[docs] def convertUnits( self, xUnit = None, yUnit = None ):
'''Convert self's units to xUnit and yUnit'''
self.xUnit = convertUnit( self.xUnit, xUnit, self.x, self.xerr, self.legend )
self.yUnit = convertUnit( self.yUnit, yUnit, self.y, self.yerr, self.legend )
[docs] def addToPlot( self, thePlot, logY = False, minY = 1e-12, verbose=False ):
if self.dataType == 'scatter':
thePlot.errorbar( self.x, self.y, yerr=self.yerr, xerr=self.xerr,
**self.getFormatMap( doErrors = self.xerr is not None or self.yerr is not None ) )
elif self.dataType == 'line':
nRange = range( len( self.y ) )
if not logY:
thePlot.plot( self.x, self.y, **self.getFormatMap( ) )
if self.yerr is not None:
lowerbound = [ self.y[i] - abs( self.yerr[i] ) for i in nRange ]
upperbound = [ self.y[i] + abs( self.yerr[i] ) for i in nRange ]
thePlot.fill_between( self.x, lowerbound, upperbound, facecolor = self.color, alpha = 0.25 )
else:
theYs = []
if self.yerr is not None:
theYLowBounds = []
theYUpBounds = []
for i in range( len( self.y ) ):
if self.y[i] <= 0.0:
if verbose: print "WARNING: y value <= 0.0 (",self.y[i],") at index",i,"on a log plot"
theYs.append( minY )
else: theYs.append( self.y[i] )
if self.yerr is not None:
lb = self.y[i] - abs( self.yerr[i] )
if lb <= 0.0:
if verbose: print "WARNING: y-yerr value <= 0.0 (",lb,") at index",i,"on a log plot"
theYLowBounds.append( minY )
else: theYLowBounds.append( lb )
ub = self.y[i] + abs( self.yerr[i] )
if ub <= 0.0:
if verbose: print "WARNING: y+yerr value <= 0.0 (",ub,") at index",i,"on a log plot"
theYUpBounds.append( minY )
else: theYUpBounds.append( ub )
thePlot.plot( self.x, theYs, **self.getFormatMap( ) )
if self.yerr is not None: thePlot.fill_between( self.x, theYLowBounds, theYUpBounds, facecolor = self.color, alpha = 0.25 )
else: raise TypeError( 'dataType must be "scatter" or "line", found ' + str( self.dataType ) )
@property
def dimension( self ) :
return( 2 )
[docs]class DataSet3d( DataSetParameters ):
'''
Member data (set these with __init__ function):
* data
* dataType (None)
type help(DataSetParameters) to find out about how to set the line properties, etc.
All of this information is passed on though the __init__ function of DataSet2d to
DataSetParameters.
'''
def __init__( self, data, dataType = None, xUnit = None, yUnit = None, zUnit = None, **kw ):
data_ = data
if( hasattr( data, 'toPointwise_withLinearXYs' ) ) : data_ = data.toPointwise_withLinearXYs( accuracy = 1e-5, upperEps = 1e-8 )
self.x, self.y, self.z = data_.copyDataToGridWsAndXsAndYs()
self.xUnit, self.yUnit, self.zUnit = xUnit, yUnit, zUnit
if( hasattr( data, 'axes' ) ) :
self.xUnit = data.axes[2].unit
self.yUnit = data.axes[1].unit
self.zUnit = data.axes[0].unit
self.dataType = 'line'
if( dataType is not None ) : self.dataType = dataType
DataSetParameters.__init__( self, **kw )
[docs] def convertUnits( self, xUnit = None, yUnit = None, zUnit = None ):
'''Convert self's units to xUnit and yUnit and zUnit'''
self.xUnit = convertUnit( self.xUnit, xUnit, self.x, None, self.legend )
self.yUnit = convertUnit( self.yUnit, yUnit, self.y, None, self.legend )
self.zUnit = convertUnit( self.zUnit, zUnit, self.z, None, self.legend )
[docs] def addToPlot( self, thePlot, plotType = 'contour', numContours = 10, logX = False, logY = False ):
'''Default plotType set to 'contour' so that can use the __makePlot2d function to drive both the regular
2d plotting and contour plotting'''
if self.dataType == 'line':
if plotType == 'contour':
import matplotlib.pyplot as plt
plt.clabel( thePlot.contour( self.x, self.y, self.z, numContours, **self.getFormatMap( ) ), inline=1, fontsize=10 )
else :
raise TypeError( 'plotType ' + plotType + ' not supported' )
else: raise TypeError( 'dataType must be "scatter" or "line", found ' + str( self.dataType ) )
[docs] def getW_XYs( self ) :
from xData import axes as axesModule
from xData import XYs as XYsModule
from xData import multiD_XYs as multiD_XYsModule
xUnit, yUnit, zUnit = '', '', ''
if( self.xUnit is not None ) : xUnit = self.xUnit
if( self.yUnit is not None ) : yUnit = self.yUnit
if( self.zUnit is not None ) : zUnit = self.zUnit
axes_3d = axesModule.axes( 3 )
axes_3d[0] = axesModule.axis( 'z', 0, zUnit )
axes_3d[1] = axesModule.axis( 'y', 0, yUnit )
axes_3d[2] = axesModule.axis( 'x', 0, xUnit )
w_xys = multiD_XYsModule.XYs2d( axes = axes_3d )
axes_2d = axesModule.axes( )
axes_2d[0] = axes_3d[0]
axes_2d[1] = axes_3d[1]
for ix, x in enumerate( self.x ) :
xys = [ [ y, self.z[iy][ix] ] for iy, y in enumerate( self.y ) ]
w_xys[ix] = XYsModule.XYs1d( xys, axes = axes_2d, value = x )
return( w_xys )
@property
def dimension( self ) :
return( 3 )
def __makePlot2d( datasets, xAxisSettings=None, yAxisSettings=None, theTitle=None,
legendOn=False, legendXY=( 0.05, 0.95 ), figsize=( 20, 10 ), outFile=None, infill = False,
thePlot=None, useBokeh=False, **kw ):
'''
Main driver routine for all 2d plots (regular, contour, slices, ...)
'''
# Sometimes we'll adjust the layout of the plot external to this routine so the backend may already be set
if outFile is not None:
defaultBackend = 'Agg'
backendMap = { 'png':'AGG', 'ps':'PS', 'eps':'PS', 'pdf':'PDF', 'svg':'SVG' }
import matplotlib
if matplotlib.get_backend() != defaultBackend: matplotlib.use( defaultBackend )
import matplotlib.pyplot as plt
# Check the axis settings. If they are set to None and we can safely initialize them using information in the
# appropriate XYs1d data structure, then do so
if xAxisSettings is None:
xUnit = None
xLabel = None
for ds in datasets:
if hasattr(ds, 'axes'):
if xUnit is None: xUnit = ds.axes[1].unit
if xUnit != ds.axes[1].unit: raise ValueError(
"Incompatible units found in x axes of datasets, %s vs. %s" % (xUnit, ds.axes[1].unit))
if xLabel is None: xLabel = ds.axes[1].label
xAxisSettings=AxisSettings(unit=xUnit,label=xLabel)
if yAxisSettings is None:
yUnit = None
yLabel = None
for ds in datasets:
if hasattr(ds, 'axes'):
if yUnit is None: yUnit = ds.axes[0].unit
if yUnit != ds.axes[0].unit: raise ValueError(
"Incompatible units found in y axes of datasets, %s vs. %s" % (yUnit, ds.axes[0].unit))
if yLabel is None: yLabel = ds.axes[0].label
yAxisSettings=AxisSettings(unit=yUnit,label=yLabel)
# Create a plotting instance in a figure if one is needed.
# If the user passed in a plot, we'll return that.
# Otherwise, all control of the plot will be handled in here.
if thePlot is None:
returnPlotInstance = False
# Set up the plot canvas
fig = plt.figure( figsize=figsize )
# Add the plot title
if theTitle is not None: fig.suptitle( theTitle.text, fontsize=theTitle.fontSize, fontweight=theTitle.fontWeight )
# Set up the plot region
thePlot = fig.add_subplot( 111 )
fig.subplots_adjust( top=0.85 )
else: returnPlotInstance = True
# Add the data to the plot
for dataset in datasets :
try:
dataset.convertUnits( xUnit = xAxisSettings.unit, yUnit = yAxisSettings.unit )
dataset.addToPlot( thePlot, logY = yAxisSettings.isLog, **kw )
except Exception, err:
if 'error from PQU' in err.message: pass # harmless, data in incompatable units so we can't plot it
else: raise err
# print "WARNING: could not add dataset "+str(dataset.legend)+", found error:"
# print err.__class__, err
# Put on the plot legend
if legendOn:
thePlot.legend( bbox_to_anchor = legendXY, loc = 'upper left', borderaxespad = 0., markerscale = 1.0, ncol = max( 1, len( datasets )/20 ), fancybox = True, framealpha = 0.5 ) #, scatterpoints = 1, frameon = False ) # these last 3 options don't work?
# Set up the axes
xAxisSettings.setupAxis( thePlot.get_xaxis() )
yAxisSettings.setupAxis( thePlot.get_yaxis() )
if not INCLASSAXISSETTING:
# Set the scales using the SubPlot calls (direct calls to XAxis and YAxis don't work because the scale information is
# contained in the Axes class instance that contains both the XAxis and YAxis instances. The Axis.set_limit functions
# actually call the stuff in the Axes instance and there is no equivalent set_scale functions).
if xAxisSettings.isLog: thePlot.set_xscale( 'log' )
else: thePlot.set_xscale( 'linear' )
if yAxisSettings.isLog: thePlot.set_yscale( 'log' )
else: thePlot.set_yscale( 'linear' )
if xAxisSettings.autoscale: thePlot.set_autoscalex_on(True)
else:
thePlot.set_xlim( ( xAxisSettings.axisMin, xAxisSettings.axisMax ) )
thePlot.set_xbound( ( xAxisSettings.axisMin, xAxisSettings.axisMax ) )
if yAxisSettings.autoscale: thePlot.set_autoscaley_on(True)
else:
thePlot.set_ylim( ( yAxisSettings.axisMin, yAxisSettings.axisMax ) )
thePlot.set_ybound( ( yAxisSettings.axisMin, yAxisSettings.axisMax ) )
thePlot.set_xlabel( xAxisSettings.label, size = xAxisSettings.fontSize, weight = xAxisSettings.fontWeight, family = xAxisSettings.font )
thePlot.set_ylabel( yAxisSettings.label, size = yAxisSettings.fontSize, weight = yAxisSettings.fontWeight, family = yAxisSettings.font )
for label in thePlot.get_xticklabels():
label.set_size( xAxisSettings.fontSize )
label.set_weight( xAxisSettings.fontWeight )
label.set_family( xAxisSettings.font )
for label in thePlot.get_yticklabels():
label.set_size( yAxisSettings.fontSize )
label.set_weight( yAxisSettings.fontWeight )
label.set_family( yAxisSettings.font )
# the following worked, but when you zoom in on the plot, the axes don't go too:
#thePlot.set_xticklabels( thePlot.get_xticks(), size = xAxisSettings.fontSize, weight = xAxisSettings.fontWeight, family = xAxisSettings.font )
#thePlot.set_yticklabels( thePlot.get_yticks(), size = yAxisSettings.fontSize, weight = yAxisSettings.fontWeight, family = yAxisSettings.font )
thePlot.minorticks_on( )
if returnPlotInstance: return thePlot
else:
# Generate the output
if outFile is not None:
if useBokeh:
#
# Experimental bokeh usage
#
import bokeh.mpl, bokeh.plotting
bokeh.plotting.output_file(outFile.replace('.png','.html'))
bokeh.plotting.show(bokeh.mpl.to_bokeh())
else:
plt.savefig(outFile)
plt.clf()
else:
plt.show()
[docs]def makePlot2d( datasets, xAxisSettings = None, yAxisSettings = None, title = '', legendOn = False, outFile = None, legendXY = ( 0.05, 0.95 ), figsize=( 20, 10 ), infill = False, useBokeh=False ):
'''
Plain, vanilla, 2d plots.
Arguments ::
* datasets (required), list of DataSet objects to plot
* xAxisSettings (optional)
* yAxisSettings (optional)
* title (optional, defaults to '' )
* legendOn (optional, defaults to True)
* outFile, if not None, use this to output plot instead of generating a matplotlib window
* legendXY, upper left corner of legend (optional, defaults to ( 0.05, 0.95 ))
* figsize (optional, defaults to ( 20, 10 ))
* infill
'''
# Process the function arguments
if isinstance( title, TextSettings ): theTitle = title
else: theTitle = TextSettings( title, fontSize = 24 )
if not isinstance( xAxisSettings, AxisSettings ): xAxisSettings = AxisSettings( )
if not isinstance( yAxisSettings, AxisSettings ): yAxisSettings = AxisSettings( )
if not isinstance( datasets, ( list, tuple ) ): datasets = [ datasets ]
_datasets = [ ]
for dataset in datasets:
if( not( isinstance( dataset, DataSet2d ) ) ) : dataset = DataSet2d( dataset, infill = infill )
_datasets.append( dataset )
__makePlot2d( _datasets, xAxisSettings, yAxisSettings, theTitle = theTitle, legendOn = legendOn, legendXY = legendXY, figsize = figsize, outFile = outFile, useBokeh=useBokeh )
[docs]def makePlot2dContour( datasets, xAxisSettings = None, yAxisSettings = None, numContours = 10, title = '', figsize=( 20, 10 ), legendOn = False, outFile = None, useBokeh=False ):
'''
Contour plots:
* datasets (required): list of DataSet objects to plot, one works better than many for a contour plot
* xAxisSettings (optional)
* yAxisSettings (optional)
* numContours (optional, defaults to 10): number of contours in the plot assuming linear spacing between intervals from the min to max data values
* title (optional, defaults to '' )
* legendOn (optional, defaults to True)
* outFile (optional)
'''
# Process the function arguments
if isinstance( title, TextSettings ): theTitle = title
else: theTitle = TextSettings( title, fontSize = 24 )
if not isinstance( xAxisSettings, AxisSettings ): xAxisSettings = AxisSettings( )
if not isinstance( yAxisSettings, AxisSettings ): yAxisSettings = AxisSettings( )
if not isinstance( datasets, ( list, tuple ) ): datasets = [ datasets ]
_datasets = [ ]
for dataset in datasets:
if( isinstance( dataset, DataSet3d ) ) :
_datasets.append( dataset )
else :
_datasets.append( DataSet3d( dataset ) )
if len( _datasets ) != 1: print 'Warning: Contour plots with more than one dataset may not be very readable'
__makePlot2d( _datasets, xAxisSettings, yAxisSettings, theTitle = theTitle, legendOn = legendOn, outFile = outFile, figsize = figsize, numContours = numContours, useBokeh=useBokeh )
[docs]def makePlot2dSlice( datasets, xyAxisSettings = None, zAxisSettings = None, sliceXCoords = None, sliceYCoords = None, sliceUnits = '', title = '', legendOn = True, legendXY = ( 0.05, 0.95 ), figsize=( 20, 10 ), outFile = None, useBokeh=False ):
'''
Make a slice of a dataset of 3d data objects along a fixed x line or a fixed y line.
Any 2d objects get plotted as if they are xy data in the correct plane for plotting.
In other words, at fixed sliceXCoord = 2, all 3d functions F( x, y ) get plotted
as y vs. F( 2.0, y ) and all 2d functions G( x ) are plotted as x vs. G( x ) even
if you meant something different.
* datasets (required): list of DataSet objects to plot
* xyAxisSettings (optional)
* zAxisSettings (optional)
* sliceXCoords: a list or value
* sliceYCoords: a list or value
* title (optional, defaults to '' )
* legendOn (optional, defaults to True)
* outFile (optional)
'''
# Process the function arguments
if isinstance( title, TextSettings ): theTitle = title
else: theTitle = TextSettings( title, fontSize = 24 )
if not isinstance( xyAxisSettings, AxisSettings ): xyAxisSettings = AxisSettings( )
if not isinstance( zAxisSettings, AxisSettings ): zAxisSettings = AxisSettings( )
if sliceXCoords is None and sliceYCoords is None:
raise ValueError( "sliceXCoords or sliceYCoords must have a list or value, otherwise where do I slice?" )
if sliceXCoords is not None and sliceYCoords is not None:
raise ValueError( "sliceXCoords or sliceYCoords both have a list or value, which one do I slice?" )
if not isinstance( datasets, ( list, tuple ) ): datasets = [ datasets ]
_datasets = [ ]
for dataset in datasets:
dimension = dataset.dimension
if( dimension == 2 ) :
_datasets.append( dataset )
elif( dimension == 3 ) :
if( isinstance( dataset, DataSet3d ) ) :
asDataSet3d = dataset
likeW_XYs = dataset.getW_XYs( )
else :
asDataSet3d = DataSet3d( dataset )
likeW_XYs = dataset
if( sliceXCoords is not None ) :
for x in sliceXCoords :
_datasets.append( DataSet2d( likeW_XYs.getValue( x ), legend = asDataSet3d.legend + ' @ ' + str( x ) + ' ' + sliceUnits ) )
if( sliceYCoords is not None ) :
for y in sliceYCoords :
_datasets.append( DataSet2d( [ [ x, likeW_XYs.evaluate( x ).evaluate( y ) ] for x in asDataSet3d.x ], legend = asDataSet3d.legend + ' @ ' + str( y ) + ' ' + sliceUnits ) )
else :
raise TypeError( 'Can only put 2d or 3d objects in slice plots' )
__makePlot2d( _datasets, xyAxisSettings, zAxisSettings, theTitle = theTitle, legendOn = legendOn, outFile = outFile, legendXY = legendXY, figsize = figsize, useBokeh=useBokeh )
#--------------------------------------------------------
#
# testing
#
#--------------------------------------------------------
[docs]def plotTests( tests = 11*[ False ] ):
from fudge import fudgeParameters
fudgeParameters.VerboseMode = 0
from fudge.legacy.endl.endlProject import endlProject
from fudge.legacy.endl.endl3dmathClasses import endl3dmath
from fudge import __path__
from xData import axes as axesModule
from xData import XYs as XYsModule
from xData import multiD_XYs as multiD_XYsModule
testData = '''
# Authors: T.S.Suzuki, Y.Nagai, T.Shima, T.Kikuchi, H.Sato, T.Kii, M.Igashira
# Title: First Measurement Of A P(N,Gamma)D Reaction Cross Section Between 10 And 80 Kev
# Year: 1995
# Institute: Tokyo Inst.of Technology, Tokyo
# Reference: Astrophysical Journal 439, (L), 59 (1995)
# Subent: 22310002
# Reaction: Cross section for 1H(n,gamma)2H
# Note: the d(Energy) errorbars are fake and are used for plot testing
# Energy Data d(Energy) d(Data)
# MeV barns MeV barns
0.02 0.000353 0.001 2.6e-05
0.02 0.000329 0.001 2.6e-05
0.02 0.000287 0.0 2.2e-05
0.02 0.000304 0.0 1.8e-05
0.04 0.00023 0.001 1.5e-05
0.04 0.000198 0.001 1.2e-05
0.04 0.000177 0.0015 1e-05
0.04 0.000207 0.0 1.3e-05
0.064 0.000156 0.0 1.1e-05
0.064 0.00015 0.0 7e-06
0.064 0.000158 0.0 1.1e-05
0.064 0.00014 0.0 9e-06
'''
e=endlProject( __path__[0] + "/legacy/endl/test/testdb" )
za=e.readZA(1001)
za.read( )
xAxis = AxisSettings( isLog = True, label = '$E_n$', axisMin = 0.5e-2, axisMax = 1.5e-1, gridOn = True, autoscale = False, unit = 'MeV' )
yAxis = AxisSettings( isLog = True, label = '$\sigma(E_n)$', gridOn = True, unit = 'b' )
d = []
u = []
for line in testData.split( '\n' ):
if line.strip().startswith( '#' ): continue
sline = map( float, line.split() )
if len( sline ) != 0:
d.append( sline[0:2] )
u.append( sline[2:4] )
xyAxes = axesModule.axes( labelsUnits = { 1 : ( xAxis.label, xAxis.unit ), 0 : ( yAxis.label, yAxis.unit ) } )
# Simple test, here we make a plot of one set
if tests[0]:
xSec = za.findData( I = 0, C = 46 )
makePlot2d( [ xSec ], xAxisSettings = xAxis, yAxisSettings = yAxis, title = '$^1$H$(n,\gamma)$ Cross Section', outFile = None )
xys = XYsModule.XYs1d( xSec.data, axes = xyAxes )
makePlot2d( ( xys ), xAxisSettings = xAxis, yAxisSettings = yAxis, title = '$^1$H$(n,\gamma)$ Cross Section', outFile = None )
dataset = DataSet2d( xys, xUnit = xAxis.unit, yUnit = yAxis.unit )
dataset.convertUnits( 'eV', 'mb' )
makePlot2d( ( dataset ), xAxisSettings = xAxis, yAxisSettings = yAxis, title = '$^1$H$(n,\gamma)$ Cross Section', outFile = None )
# Plot all the cross section data in the fudge2 test library, but unthemed!
if tests[1]:
makePlot2d( za.findDatas( I = 0 ), outFile = None )
# Plot all the cross section data in the fudge2 test library
if tests[2]:
xSecs = za.findDatas( I = 0 )
makePlot2d( xSecs, xAxisSettings = xAxis, yAxisSettings = yAxis, title = '$^1$H$(n,*)$ Cross Sections', outFile = None )
xySecs = [ XYsModule.XYs1d( xSec.data, axes = xyAxes ) for xSec in xSecs ]
xySecs = ( xySecs[0], xySecs[1], xySecs[2] )
makePlot2d( xySecs, xAxisSettings = xAxis, yAxisSettings = yAxis, title = '$^1$H$(n,*)$ Cross Sections', outFile = None )
# Fancy test, here we make a plot of one dataset (the testData above)
if tests[3]:
endfData = za.findData( I=0, C=46 ) #.slicex(domainMin=1e-2,domainMax=1e-1)
endfUnc = 0.1 * endfData # 10% error bars
makePlot2d( [ \
DataSet2d( data = endfData, uncertainty = endfUnc, legend = 'ENDF/B-VII.0', color='g', lineStyle = '-' ),
DataSet2d( data = d, uncertainty = u, legend = 'T.S.Suzuki, et al. (1995) EXFOR entry # 22310002', color='g', symbol = 'o' ),
], xAxisSettings = xAxis, yAxisSettings = yAxis, title = '$^1$H$(n,\gamma)$ Cross Section', legendOn = True, outFile = None )
# Contour test
if tests[4]: # Contour plots are meaningful if there is only one dataset plotted, how do we enforce this?
endfData = za.findData( yo = 1, I = 1, C = 10 )
EAxis = AxisSettings( isLog = False, label = '$E_n$ (MeV)', axisMin = 1.0, axisMax = 20.0, gridOn = True, autoscale = False, unit = 'MeV' )
muAxis = AxisSettings( isLog = False, label = '$\mu$ = cos( $\\theta$ )', axisMin = -1.0, axisMax = 1.0, autoscale = False, gridOn = True )
makePlot2dContour( DataSet3d( data = endfData, legend = 'ENDF/B-VII.0', xUnit = EAxis.unit, yUnit = muAxis.unit ),
xAxisSettings = EAxis, yAxisSettings = muAxis, title = '$^1$H$(n,el)$ Angular Distribution', outFile = None )
w_xys = multiD_XYsModule.XYs2d( axes = axesModule.axes( rank = 3, labelsUnits = { 2 : ( '$E_n$', 'MeV' ) } ) )
for w, xy in endfData.data : w_xys.append( XYsModule.XYs1d( xy, axes = axesModule.axes( ), value = w ) )
dataset = DataSet3d( data = w_xys, legend = 'ENDF/B-VII.0' )
dataset.convertUnits( 'eV', None, None )
makePlot2dContour( dataset, xAxisSettings = EAxis, yAxisSettings = muAxis,
title = '$^1$H$(n,el)$ Angular Distribution', outFile = None )
# Slice tests
if( tests[5] or tests[6] or tests[7] or tests[8] or tests[9] ) :
endfDataI0 = za.findData( yo = 0, I = 0, C = 10 ).slicex(1.0,20.0) # simplify the plot
endfDataI1 = za.findData( yo = 1, I = 1, C = 10 )
Es = [ p[0] for p in endfDataI0.data ] # in [MeV]
mus = [ ] # in [mu]
for t in endfDataI1.data: mus += [ p[0] for p in t[1] ]
mus = sorted( uniquify( mus ) )
table = []
for E in Es:
muDist = []
for mu in mus: muDist.append( [ mu, endfDataI0.getValue( E ) * endfDataI1.getValue( E, mu ) ] )
table.append( [ E, muDist ] )
endfDataCombined = endl3dmath( data = table )
EAxis = AxisSettings( isLog = True, label = '$E_n$ (MeV)', axisMin = 1.0, axisMax = 20.0, gridOn = True, autoscale = False )
muAxis = AxisSettings( isLog = False, label = '$\mu = \cos{( \\theta )}$', axisMin = -1.0, axisMax = 1.0, autoscale = False, gridOn = True )
zAxis = AxisSettings( isLog = True, label = '$d\sigma(E)/d\mu$ (b)', axisMin = 0.0, axisMax = 5.0, autoscale = False, gridOn = True )
# unthemed slice tests
if tests[5] :
makePlot2dSlice( endfDataCombined, xyAxisSettings = EAxis, zAxisSettings = zAxis, sliceXCoords = None, sliceYCoords = [ -1.0, -0.75, -0.5, -.25, 0.0, .25, .5, .75, 1.0 ], title = '', outFile = None )
dataSet3d = DataSet3d( data = endfDataCombined, legend = 'ENDF/B-VII.0' )
makePlot2dSlice( dataSet3d.getW_XYs( ), xyAxisSettings = EAxis, zAxisSettings = zAxis, sliceXCoords = None, sliceYCoords = [ -1.0, -0.75, -0.5, -.25, 0.0, .25, .5, .75, 1.0 ], title = '', outFile = None )
if tests[6]: makePlot2dSlice( endfDataCombined, xyAxisSettings = muAxis, zAxisSettings = zAxis, sliceXCoords = [ 1.0, 5.0, 10.0, 15.0, 20.0 ], sliceYCoords = None, title = '', outFile = None )
# themed slice test and contour test
if tests[7]: makePlot2dContour( DataSet3d( data = endfDataCombined, legend = 'ENDF/B-VII.0' ), xAxisSettings = EAxis, yAxisSettings = muAxis, numContours = 10, title = '$d\sigma(E)/d\mu$ for $^1$H$(n,el)$', outFile = None )
if tests[8]: makePlot2dSlice( DataSet3d( data = endfDataCombined, legend = 'ENDF/B-VII.0' ), xyAxisSettings = EAxis, zAxisSettings = zAxis, sliceXCoords = None, sliceYCoords = [ -1.0, -0.75, -0.5, -.25, 0.0, .25, .5, .75, 1.0 ], title = '', outFile = None )
# themed slice test, with test 2d experimental data
if tests[9]:
makePlot2dSlice( [ \
DataSet3d( data = endfDataCombined, legend = 'ENDF/B-VII.0' ), \
DataSet2d( data = d, uncertainty = u, legend = 'T.S.Suzuki, et al. (1995) EXFOR entry # 22310002', color='g', symbol = 'o' ),\
], xyAxisSettings = muAxis, zAxisSettings = zAxis, sliceXCoords = [ 1.0, 5.0, 10.0, 15.0, 20.0 ], sliceYCoords = None, sliceUnits = 'MeV', title = 'Slice test', outFile = None )
#--------------------------------------------------------
#
# main!
#
#--------------------------------------------------------
if __name__ == "__main__":
for i in xrange( 10 ) :
# if( i != 8 ) : continue
tests = 10 * [ False ]
tests[i] = True
if( i in [ 4, 5, 9 ] ) :
print 'Test %d needs to be fixed.' % i
continue # Dave, fix me.
plotTests( tests = tests )