from VESIcal import core
from VESIcal import calibrations
from VESIcal.tasplot import add_LeMaitre_fields
import pandas as pd
import numpy as np
import warnings as w
import matplotlib as mpl
import matplotlib.pyplot as plt
# ---------- DEFINE CUSTOM PLOTTING FORMATTING ------------ #
try:
style = "seaborn-colorblind" # old style name
plt.style.use(style)
# Define color cycler based on plot style set here
# get style formatting set by plt.style.use():
the_rc = plt.style.library[style]
# list of colors by hex code:
color_list = the_rc['axes.prop_cycle'].by_key()['color'] * 10
color_cyler = the_rc['axes.prop_cycle'] # get the cycler
except Exception:
style = "seaborn-v0_8-colorblind" # new style name as of 3.6.0
plt.style.use(style)
# Define color cycler based on plot style set here
# get style formatting set by plt.style.use():
the_rc = plt.style.library[style]
# list of colors by hex code:
color_list = the_rc['axes.prop_cycle'].by_key()['color'] * 10
color_cyler = the_rc['axes.prop_cycle'] # get the cycler
plt.rcParams["mathtext.default"] = "regular"
plt.rcParams["mathtext.fontset"] = "dejavusans"
mpl.rcParams['patch.linewidth'] = 1
mpl.rcParams['axes.linewidth'] = 1
plt.rcParams['axes.titlesize'] = 20
plt.rcParams['axes.labelsize'] = 18
plt.rcParams['xtick.labelsize'] = 14
plt.rcParams['ytick.labelsize'] = 14
plt.rcParams['legend.fontsize'] = 14
mpl.rcParams['lines.markersize'] = 10
# ----------- MAGMASAT PLOTTING FUNCTIONS ----------- #
[docs]
def smooth_isobars_and_isopleths(isobars=None, isopleths=None):
"""
Takes in a dataframe with calculated isobar and isopleth information
(e.g., output from calculate_isobars_and_isopleths) and smooths the data
for plotting.
Parameters
----------
isobars: pandas DataFrame
OPTIONAL. DataFrame object containing isobar information as calculated
by calculate_isobars_and_isopleths.
isopleths: pandas DataFrame
OPTIONAL. DataFrame object containing isopleth information as
calculated by calculate_isobars_and_isopleths.
Returns
-------
pandas DataFrame
DataFrame with x and y values for all isobars and all isopleths.
Useful if a user wishes to do custom plotting with isobar and isopleth
data rather than using the built-in `plot_isobars_and_isopleths()`
function.
"""
np.seterr(divide='ignore', invalid='ignore') # turn off numpy warning
w.filterwarnings("ignore", message="Polyfit may be poorly conditioned")
if isobars is not None:
P_vals = isobars.Pressure.unique()
isobars_lists = isobars.values.tolist()
# add zero values to volatiles list
isobars_lists.append([0.0, 0.0, 0.0, 0.0])
isobars_pressure = []
isobars_H2O_liq = []
isobars_CO2_liq = []
# do some data smoothing
for pressure in P_vals:
Pxs = [item[1] for item in isobars_lists if item[0] == pressure]
Pys = [item[2] for item in isobars_lists if item[0] == pressure]
try:
# calcualte polynomial
Pz = np.polyfit(Pxs, Pys, 3)
Pf = np.poly1d(Pz)
# calculate new x's and y's
Px_new = np.linspace(Pxs[0], Pxs[-1], 50)
Py_new = Pf(Px_new)
# Save x's and y's
Px_new_list = list(Px_new)
isobars_H2O_liq += Px_new_list
Py_new_list = list(Py_new)
isobars_CO2_liq += Py_new_list
pressure_vals_for_list = [pressure]*len(Px_new)
isobars_pressure += pressure_vals_for_list
except Exception:
Px_list = list(Pxs)
isobars_H2O_liq += Px_list
Py_list = list(Pys)
isobars_CO2_liq += Py_list
pressure_vals_for_list = [pressure]*len(Pxs)
isobars_pressure += pressure_vals_for_list
isobar_df = pd.DataFrame({"Pressure": isobars_pressure,
"H2O_liq": isobars_H2O_liq,
"CO2_liq": isobars_CO2_liq})
if isopleths is not None:
XH2O_vals = isopleths.XH2O_fl.unique()
isopleths_lists = isopleths.values.tolist()
isopleths_XH2O_fl = []
isopleths_H2O_liq = []
isopleths_CO2_liq = []
for Xfl in XH2O_vals:
Xxs = [item[1] for item in isopleths_lists if item[0] == Xfl]
Xys = [item[2] for item in isopleths_lists if item[0] == Xfl]
try:
# calculate polynomial
Xz = np.polyfit(Xxs, Xys, 2)
Xf = np.poly1d(Xz)
# calculate new x's and y's
Xx_new = np.linspace(Xxs[0], Xxs[-1], 50)
Xy_new = Xf(Xx_new)
# Save x's and y's
Xx_new_list = list(Xx_new)
isopleths_H2O_liq += Xx_new_list
Xy_new_list = list(Xy_new)
isopleths_CO2_liq += Xy_new_list
XH2Ofl_vals_for_list = [Xfl]*len(Xx_new)
isopleths_XH2O_fl += XH2Ofl_vals_for_list
except Exception:
Xx_list = list(Xxs)
isopleths_H2O_liq += Xx_list
Xy_list = list(Xys)
isopleths_CO2_liq += Xy_list
XH2Ofl_vals_for_list = [Xfl]*len(Xxs)
isopleths_XH2O_fl += XH2Ofl_vals_for_list
isopleth_df = pd.DataFrame({"XH2O_fl": isopleths_XH2O_fl,
"H2O_liq": isopleths_H2O_liq,
"CO2_liq": isopleths_CO2_liq})
np.seterr(divide='warn', invalid='warn') # turn numpy warning back on
w.filterwarnings("always", message="Polyfit may be poorly conditioned")
if isobars is not None:
if isopleths is not None:
return isobar_df, isopleth_df
else:
return isobar_df
else:
if isopleths is not None:
return isopleth_df
[docs]
def plot(isobars=None, isopleths=None, degassing_paths=None, custom_H2O=None,
custom_CO2=None, isobar_labels=None, isopleth_labels=None,
degassing_path_labels=None, custom_labels=None,
custom_colors="VESIcal", custom_symbols=None, markersize=10,
figsize=(12, 8), save_fig=False, extend_isobars_to_zero=True,
smooth_isobars=False, smooth_isopleths=False, **kwargs):
"""
Custom automatic plotting of model calculations in VESIcal.
Isobars, isopleths, and degassing paths can be plotted. Labels can be
specified for each. Any combination of isobars, isopleths, and degassing
paths can be plotted.
Parameters
----------
isobars: pandas DataFrame or list
OPTIONAL. DataFrame object containing isobar information as calculated
by calculate_isobars_and_isopleths. Or a list of DataFrame objects.
isopleths: pandas DataFrame or list
OPTIONAL. DataFrame object containing isopleth information as
calculated by calculate_isobars_and_isopleths. Or a list of DataFrame
objects.
degassing_paths: list
OPTIONAL. List of DataFrames with degassing information as generated
by calculate_degassing_path().
custom_H2O: list
OPTIONAL. List of groups of H2O values to plot as points. For example
myfile.data['H2O'] is one group of H2O values. Must be passed with
custom_CO2 and must be same length as custom_CO2.
custom_CO2: list
OPTIONAL. List of groups of CO2 values to plot as points.For example
myfile.data['CO2'] is one group of CO2 values. Must be passed with
custom_H2O and must be same length as custom_H2O.
isobar_labels: list
OPTIONAL. Labels for the plot legend. Default is None, in which case
each plotted line will be given the generic legend name of
"Isobars n", with n referring to the nth isobars passed. Isobar
pressure is given in parentheses. The user can pass their own labels
as a list of strings. If more than one set of isobars is passed, the
labels should refer to each set of isobars, not each pressure.
isopleth_labels: list
OPTIONAL. Labels for the plot legend. Default is None, in which case
each plotted isopleth will be given the generic legend name of
"Isopleth n", with n referring to the nth isopleths passed. Isopleth
XH2O values are given in parentheses. The user can pass their own
labels as a list of strings. If more than one set of isopleths is
passed, the labels should refer to each set of isopleths, not each
XH2O value.
degassing_path_labels: list
OPTIONAL. Labels for the plot legend. Default is None, in which case
each plotted line will be given the generic legend name of "Pathn",
with n referring to the nth degassing path passed. The user can pass
their own labels as a list of strings.
custom_labels: list
OPTIONAL. Labels for the plot legend. Default is None, in which case
each group of custom points will be given the generic legend name of
"Customn", with n referring to the nth degassing path passed. The user
can pass their own labels as a list of strings.
custom_colors: list
OPTIONAL. Default value is "VESIcal", which uses VESIcal's color ramp.
A list of color values readable by matplotlib can be passed here if
custom symbol colors are desired. The length of this list must match
that of custom_H2O and custom_CO2.
custom_symbols: list
OPTIONAL. Default value is None, in which case data are plotted as
filled circles.. A list of symbol tyles readable by matplotlib can be
passed here if custom symbol types are desired. The length of this
list must match that of custom_H2O and custom_CO2.
markersize: int
OPTIONAL. Default value is 10. Same as markersize kwarg in matplotlib.
Any numeric value passed here will set the marker size for
(custom_H2O, custom_CO2) points.
figsize: tuple
OPTIONAL. Default value is (12,8). Sets the matplotlib.pyplot figsize
value as (x_dimension, y_dimension)
save_fig: False or str
OPTIONAL. Default value is False, in which case the figure will not be
saved. If a string is passed, the figure will be saved with the string
as the filename. The string must include the file extension.
extend_isobars_to_zero: bool
OPTIONAL. If True (default), isobars will be extended to zero, even if
there is a finite solubility at zero partial pressure.
smooth_isobars: bool
OPTIONAL. Default is False. If set to True, isobar data will be fit to
a polynomial and plotted. If False, the raw input data will be plotted.
smooth_isopleths: bool
OPTIONAL. Default is False. If set to True, isopleth data will be fit
to a polynomial and plotted. If False, the raw input data will be
plotted.
Returns
-------
fig, axes Matplotlib objects
fig and axes matploblib objects defining a plot with x-axis as H2O wt%
in the melt and y-axis as CO2 wt%in the melt. Isobars, or lines of
constant pressure at which the sample magma composition is saturated,
and isopleths, or lines of constant fluid composition at which the
sample magma composition is saturated, are plotted if passed.
Degassing paths, or the concentration of dissolved H2O and CO2 in a
melt equilibrated along a path of decreasing pressure, is plotted if
passed.
"""
# Turn off warnings:
np.seterr(divide='ignore', invalid='ignore') # turn off numpy warning
w.filterwarnings("ignore", message="Polyfit may be poorly conditioned")
def check_inputs(custom_H2O, custom_CO2):
if custom_H2O is not None:
if custom_CO2 is None:
raise core.InputError("If x data is passed, y data must also "
"be passed.")
else:
if len(custom_H2O) == len(custom_CO2):
pass
else:
raise core.InputError("x and y data must be same length")
if custom_CO2 is not None:
if custom_H2O is None:
raise core.InputError("If y data is passed, x data must also "
"be passed.")
def check_colors(custom_colors):
if custom_colors == "VESIcal":
use_colors = color_list
elif isinstance(custom_colors, list):
use_colors = custom_colors
else:
raise core.InputError("Argument custom_colors must be type list. "
"Just passing one item? Try putting square "
"brackets, [], around it.")
return use_colors
def calc_extend_isobars_to_zero(Pxs, Pys):
"""
Calculates new end-points for plotting isobars when
extend_isobars_to_zero option is set to True.
Parameters
----------
Pxs, Pys: list
List of x and y values corresponding to isobars.
"""
if Pxs[0]*Pys[0] != 0.0:
if Pxs[0] > Pys[0]:
# create new array of length n+1 if n is the length of the
# original array:
Px_new = np.zeros(np.shape(Pxs)[0]+1)
# set the first x value in the new array equal to 0:
Px_new[0] = 0
# fill the rest of the new array with the original array
# values:
Px_new[1:] = Pxs
# overwrite the original array with the new one:
Pxs = Px_new
Py_new = np.zeros(np.shape(Pys)[0]+1)
Py_new[0] = Pys[0]
Py_new[1:] = Pys
Pys = Py_new
else:
Px_new = np.zeros(np.shape(Pxs)[0]+1)
Px_new[0] = Pxs[0]
Px_new[1:] = Pxs
Pxs = Px_new
Py_new = np.zeros(np.shape(Pys)[0]+1)
Py_new[0] = 0
Py_new[1:] = Pys
Pys = Py_new
if Pxs[-1]*Pys[-1] != 0.0:
if Pxs[-1] < Pys[-1]:
Px_new = np.zeros(np.shape(Pxs)[0]+1)
Px_new[-1] = 0
Px_new[:-1] = Pxs
Pxs = Px_new
Py_new = np.zeros(np.shape(Pys)[0]+1)
Py_new[-1] = Pys[-1]
Py_new[:-1] = Pys
Pys = Py_new
else:
Px_new = np.zeros(np.shape(Pxs)[0]+1)
Px_new[-1] = Pxs[-1]
Px_new[:-1] = Pxs
Pxs = Px_new
Py_new = np.zeros(np.shape(Pys)[0]+1)
Py_new[-1] = 0
Py_new[:-1] = Pys
Pys = Py_new
return Pxs, Pys
# -------- HANDLE USER INPUT ERRORS, SET COLORS, SMOOTH LINES -------- ##
check_inputs(custom_H2O=custom_H2O, custom_CO2=custom_CO2)
use_colors = check_colors(custom_colors=custom_colors)
if smooth_isobars:
isobars = smooth_isobars_and_isopleths(isobars=isobars)
if smooth_isopleths:
isopleths = smooth_isobars_and_isopleths(isopleths=isopleths)
# -------- CREATE FIGURE -------- ##
fig, ax = plt.subplots(figsize=figsize)
if 'custom_x' in kwargs:
ax.set(xlabel=kwargs['xlabel'], ylabel=kwargs['ylabel'])
else:
ax.set(xlabel='H$_2$O wt%', ylabel='CO$_2$ wt%')
labels = []
# -------- PLOT ISOBARS -------- ##
if isobars is not None:
if isinstance(isobars, pd.DataFrame):
isobars = [isobars]
for i in range(len(isobars)):
P_vals = isobars[i].Pressure.unique()
isobars_lists = isobars[i].values.tolist()
# add zero values to volatiles list
isobars_lists.append([0.0, 0.0, 0.0, 0.0])
P_iter = 0
for pressure in P_vals:
P_iter += 1
Pxs = [item[1] for item in isobars_lists
if item[0] == pressure]
Pys = [item[2] for item in isobars_lists
if item[0] == pressure]
if extend_isobars_to_zero:
try:
Pxs, Pys = calc_extend_isobars_to_zero(Pxs, Pys)
except Exception:
pass
else:
print(extend_isobars_to_zero)
if len(isobars) > 1:
if P_iter == 1:
P_list = [int(i) for i in P_vals]
if isinstance(isobar_labels, list):
labels.append(str(isobar_labels[i]) + ' (' +
', '.join(map(str, P_list)) +
" bars)")
else:
labels.append('Isobars ' + str(i+1) + ' (' +
', '.join(map(str, P_list)) +
" bars)")
else:
labels.append('_nolegend_')
if len(isobars) > 1:
ax.plot(Pxs, Pys, color=color_list[i])
else:
ax.plot(Pxs, Pys)
if len(isobars) == 1:
labels += [str(P_val) + " bars" for P_val in P_vals]
# -------- PLOT ISOPLETHS -------- ##
if isopleths is not None:
if isinstance(isopleths, pd.DataFrame):
isopleths = [isopleths]
for i in range(len(isopleths)):
XH2O_vals = isopleths[i].XH2O_fl.unique()
isopleths_lists = isopleths[i].values.tolist()
H_iter = 0
for Xfl in XH2O_vals:
H_iter += 1
Xxs = [item[1] for item in isopleths_lists if item[0] == Xfl]
Xys = [item[2] for item in isopleths_lists if item[0] == Xfl]
if len(isopleths) > 1:
if H_iter == 1:
H_list = [i for i in XH2O_vals]
if isinstance(isopleth_labels, list):
labels.append(str(isopleth_labels[i]) + ' (' +
', '.join(map(str, H_list)) +
" XH2Ofluid)")
else:
labels.append('Isopleths ' + str(i+1) + ' (' +
', '.join(map(str, H_list)) +
" XH2Ofluid)")
else:
labels.append('_nolegend_')
ax.plot(Xxs, Xys, ls='dashed', color=color_list[i])
if len(isopleths) == 1:
H_list = [i for i in XH2O_vals]
if H_iter == 1:
labels.append('Isopleths (' +
', '.join(map(str, H_list)) +
" XH2Ofluid)")
else:
labels.append('_nolegend_')
ax.plot(Xxs, Xys, ls='dashed', color='k')
# -------- PLOT DEGASSING PATHS -------- ##
if degassing_paths is not None:
if isinstance(degassing_paths, pd.DataFrame):
degassing_paths = [degassing_paths]
degassing_colors = color_list.copy()
iterno = 0
plot_type = None
for i in range(len(degassing_paths)):
# handle whether labels are passed
if degassing_path_labels is None:
iterno += 1
labels.append('Path%s' % iterno)
else:
labels.append(degassing_path_labels[iterno])
iterno += 1
# handle if only one volatile present
if (degassing_paths[i]["H2O_liq"].max() > 0 and
degassing_paths[i]["CO2_liq"].max() > 0):
ax.plot(degassing_paths[i]["H2O_liq"],
degassing_paths[i]["CO2_liq"],
ls='dotted', color=degassing_colors[i])
ax.plot(degassing_paths[i]["H2O_liq"].max(),
degassing_paths[i]["CO2_liq"].max(),
'o', color=degassing_colors[i])
# warn user if trying to plot mixed types on same figure
if plot_type not in [None, "mixed"]:
w.warn("Warning: you are trying to plot two different plot types on the same"
" figure. Curves may not match axis labels.", stacklevel=2)
plot_type = "mixed"
elif (degassing_paths[i]["H2O_liq"].max() > 0 and
degassing_paths[i]["CO2_liq"].max() <= 0):
ax.plot(degassing_paths[i]["Pressure_bars"],
degassing_paths[i]["H2O_liq"],
ls='dotted', color=degassing_colors[i])
ax.plot(degassing_paths[i]["Pressure_bars"].max(),
degassing_paths[i]["H2O_liq"].max(),
'o', color=degassing_colors[i])
ax.set(xlabel='Pressure (bars)', ylabel='H$_2$O wt%')
# warn user if trying to plot mixed types on same figure
if plot_type not in [None, "H2O"]:
w.warn("Warning: you are trying to plot two different plot types on the same"
" figure. Curves may not match axis labels.", stacklevel=2)
plot_type = "H2O"
elif (degassing_paths[i]["H2O_liq"].max() <= 0 and
degassing_paths[i]["CO2_liq"].max() > 0):
ax.plot(degassing_paths[i]["Pressure_bars"],
degassing_paths[i]["CO2_liq"],
ls='dotted', color=degassing_colors[i])
ax.plot(degassing_paths[i]["Pressure_bars"].max(),
degassing_paths[i]["CO2_liq"].max(),
'o', color=degassing_colors[i])
ax.set(xlabel='Pressure (bars)', ylabel='CO$_2$ wt%')
# warn user if trying to plot mixed types on same figure
if plot_type not in [None, "CO2"]:
w.warn("Warning: you are trying to plot two different plot types on the same"
" figure. Curves may not match axis labels.", stacklevel=2)
plot_type = "CO2"
labels.append('_nolegend_')
# -------- PLOT CUSTOM H2O-CO2 -------- ##
if custom_H2O is not None and custom_CO2 is not None:
if isinstance(custom_H2O, pd.DataFrame):
custom_H2O = [custom_H2O]
if isinstance(custom_CO2, pd.DataFrame):
custom_CO2 = [custom_CO2]
if custom_symbols is None:
use_marker = ['o'] * len(custom_H2O)
else:
use_marker = custom_symbols
iterno = 0
for i in range(len(custom_H2O)):
if custom_labels is None:
iterno += 1
labels.append('Custom%s' % iterno)
ax.plot(custom_H2O[i], custom_CO2[i], use_marker[i],
color=use_colors[i], markersize=markersize)
else:
labels.append(custom_labels[iterno])
ax.plot(custom_H2O[i], custom_CO2[i], use_marker[i],
color=use_colors[i], markersize=markersize)
iterno += 1
# -------- PLOT CUSTOM X-Y -------- ##
if 'custom_x' in kwargs:
custom_x = kwargs['custom_x']
custom_y = kwargs['custom_y']
if isinstance(custom_x, pd.core.series.Series):
custom_x = [list(custom_x.values)]
if isinstance(custom_y, pd.core.series.Series):
custom_y = [list(custom_y.values)]
if custom_symbols is None:
use_marker = ['o'] * len(custom_x)
else:
use_marker = custom_symbols
iterno = 0
for i in range(len(custom_x)):
if custom_labels is None:
iterno += 1
labels.append('Custom%s' % iterno)
ax.plot(custom_x[i], custom_y[i], use_marker[i],
color=use_colors[i], markersize=markersize)
else:
labels.append(custom_labels[iterno])
ax.plot(custom_x[i], custom_y[i], use_marker[i],
color=use_colors[i], markersize=markersize)
iterno += 1
# -------- PLOT LEGEND -------- ##
ax.legend(labels, bbox_to_anchor=(1.01, 1), loc='upper left')
if 'custom_x' not in kwargs:
ax.set_xlim(left=0)
ax.set_ylim(bottom=0)
np.seterr(divide='warn', invalid='warn') # turn numpy warning back on
w.filterwarnings("always", message="Polyfit may be poorly conditioned")
# -------- SAVE FIGURE IF DESIRED -------- ##
if save_fig is not False:
fig.savefig(save_fig)
return fig, ax
[docs]
def scatterplot(custom_x, custom_y, xlabel=None, ylabel=None, **kwargs):
"""
Custom x-y plotting using VESIcal's built-in plot() function, built
Matplotlib's plot and scatter functions.
Parameters
----------
custom_x: list
List of groups of x-values to plot as points or lines
custom_y: list
List of groups of y-values to plot as points or lines
xlabel: str
OPTIONAL. What to display along the x-axis.
ylabel: str
OPTIONAL. What to display along the y-axis.
kwargs:
Can take in any key word agruments that can be passed to `plot()`.
Returns
-------
fig, ax matplotlib objects
X-y plot with custom x and y axis values and labels.
"""
if isinstance(custom_x, list) and isinstance(custom_y, list):
if len(custom_x) != len(custom_y):
raise core.InputError("X and y lists must be same length")
if xlabel is not None:
if isinstance(xlabel, str):
pass
else:
raise core.InputError("xlabel must be string")
if ylabel is not None:
if isinstance(ylabel, str):
pass
else:
raise core.InputError("ylabel must be string")
return plot(custom_x=custom_x, custom_y=custom_y, xlabel=xlabel,
ylabel=ylabel, **kwargs)
# ------- Define custom plotting tools for checking calibrations ------- #
[docs]
def calib_plot(user_data=None, model='all', plot_type='TAS', zoom=None,
figsize=(17, 8), legend=True, save_fig=False, **kwargs):
"""
Plots user data and calibration set of any or all models on any x-y plot
or a total alkalis vs silica (TAS) diagram. TAS diagram boundaries
provided by tasplot python module, copyright John A Stevenson.
Parameters
----------
user_data: BatchFile object, Sample object, pandas DataFrame, pandas Series,
or dict.
OPTIONAL. Default value is None, in which case only the model
calibration set is plotted. User provided sample data describing the
oxide composition of one or more samples. Multiple samples can be
passed as an BatchFile object or pandas DataFrame. A single sample can
be passed as a pandas Series.
model: str or list
OPTIONAL. Default value is 'all', in which case all model calibration
datasets will be plotted. 'Mixed' can be used to plot all mixed fluid
models. String of the name of the model calibration dataset to plot
(e.g., 'Shishkina'). Multiple models can be plotted by passing them as
strings within a list (e.g., ['Shishkina', 'Dixon']).
plot_type: str
OPTIONAL. Default value is 'TAS', which returns a total alkali vs
silica (TAS) diagram. Any two oxides can be plotted as an x-y plot by
setting plot_type='xy' and specifying x- and y-axis oxides, e.g.,
x='SiO2', y='Al2O3'.
zoom: str or list
OPTIONAL. Default value is None in which case axes will be set to the
default of 35<x<100 wt% and 0<y<25 wt% for TAS type plots and the best
values to show the data for xy type plots. Can pass "user_data" to
plot the figure where the x and y axes are scaled down to zoom in and
only show the region surrounding the user_data. A list of tuples may
be passed to manually specify x and y limits. Pass in data as
[(x_min, x_max), (y_min, y_max)]. For example, the default limits here
would be passed in as [(35,100), (0,25)].
figsize: tuple
OPTIONAL. Default value is (17,8). Sets the matplotlib.pyplot figsize
value as (x_dimension, y_dimension).
legend: bool
OPTIONAL. Default value is True. Can be set to False in which case the
legend will not be displayed.
save_fig: False or str
OPTIONAL. Default value is False, in which case the figure will not be
saved. If a string is passed, the figure will be saved with the string
as the filename. The string must include the file extension.
Returns
-------
matplotlib object
"""
# Get x and y axis limits, if user passed them
if zoom is None:
user_xmin = 35
user_xmax = 100
user_ymin = 0
user_ymax = 25
elif zoom == 'user_data':
if isinstance(user_data, pd.DataFrame):
print("'user_data' type zoom for more than one sample is not ",
"implemented yet.")
user_xmin = 35
user_xmax = 100
user_ymin = 0
user_ymax = 25
elif (isinstance(user_data, pd.core.series.Series) or
isinstance(user_data, dict)):
user_xmin = user_data['SiO2'] - 5
user_xmax = user_data['SiO2'] + 5
user_ymin = user_data['Na2O'] + user_data['K2O'] - 2
if user_ymin < 0:
user_ymin = 0
user_ymax = user_data['Na2O'] + user_data['K2O'] + 2
elif isinstance(zoom, list):
user_xmin, user_xmax = zoom[0]
user_ymin, user_ymax = zoom[1]
else:
raise core.InputError('Trying to pass zoom coords? Pass as ' +
'[(x, x), (y, y)]')
# Create the figure
fig, ax1 = plt.subplots(figsize=figsize)
font = {'family': 'sans-serif',
'color': 'black',
'weight': 'normal',
'size': 20,
}
# TAS figure
if plot_type == 'TAS':
# adjust x limits here if you want to focus on a specific part of
# compostional space:
ax1.set_xlim([user_xmin, user_xmax])
# adjust y limits here
ax1.set_ylim([user_ymin, user_ymax])
plt.xlabel('SiO$_2$, wt%', fontdict=font, labelpad=15)
plt.ylabel('Na$_2$O+K$_2$O, wt%', fontdict=font, labelpad=15)
# add LeMaitre fields
if zoom is None:
add_LeMaitre_fields(ax1)
elif plot_type == 'xy':
if 'x' in kwargs and 'y' in kwargs:
x = kwargs['x']
y = kwargs['y']
if zoom is not None:
ax1.set_xlim([user_xmin, user_xmax])
ax1.set_ylim([user_ymin, user_ymax])
plt.xlabel(str(x)+", wt%", fontdict=font, labelpad=15)
plt.ylabel(str(y)+", wt%", fontdict=font, labelpad=15)
else:
raise core.InputError("If plot_type is 'xy', then x and y "
"values must be passed as strings. For "
"example, x='SiO2', y='Al2O3'.")
# Plot Calibration Data
if model == 'all':
model = ['MagmaSat',
'Shishkina',
'Dixon',
'IaconoMarziano',
'Liu',
'AllisonCarbon',
'MooreWater']
if model == 'mixed':
model = ['MagmaSat',
'Shishkina',
'Dixon',
'IaconoMarziano',
'Liu']
if isinstance(model, str):
model = [model]
if isinstance(model, list):
# set legends to false
h2o_legend = False
co2_h2oco2_legend = False
# check which legends to turn to True
for modelname in model:
model_type = calibrations.return_calibration_type(modelname)
if model_type['H2O']:
h2o_legend = True
if model_type['CO2'] or model_type['Mixed']:
co2_h2oco2_legend = True
if h2o_legend:
plt.scatter([], [], marker='', label=r"$\bf{Pure \ H_2O:}$")
for modelname in model:
calibdata = calibrations.return_calibration(modelname)
model_type = calibrations.return_calibration_type(modelname)
if isinstance(calibdata, str):
w.warn(calibdata)
else:
if model_type['H2O']:
if plot_type == 'TAS':
try:
plt.scatter(calibdata['H2O']['SiO2'],
(calibdata['H2O']['Na2O'] +
calibdata['H2O']['K2O']),
marker='s', edgecolors='k',
facecolors=calibdata['facecolor'],
label=str(modelname))
except Exception:
plt.scatter(calibdata['H2O']['SiO2'],
calibdata['H2O']['Na2O+K2O'],
marker='s', edgecolors='k',
facecolors=calibdata['facecolor'],
label=str(modelname))
if plot_type == 'xy':
try:
plt.scatter(calibdata['H2O'][x],
calibdata['H2O'][y],
marker='s', edgecolors='k',
facecolors=calibdata['facecolor'],
label=str(modelname))
except Exception:
w.warn("The requested oxides were not found",
"in the calibration dataset for " +
str(modelname) + ".")
if co2_h2oco2_legend:
plt.scatter([], [], marker='', label=r"${\ }$")
if co2_h2oco2_legend:
plt.scatter([], [], marker='',
label=r"$\bf{\ CO_2 \ and \ H_2O\!-\!CO_2:}$")
for modelname in model:
calibdata = calibrations.return_calibration(modelname)
model_type = calibrations.return_calibration_type(modelname)
if isinstance(calibdata, str):
w.warn(calibdata)
else:
if model_type['CO2'] and model_type['Mixed']:
frames = [calibdata['CO2'], calibdata['Mixed']]
co2_and_mixed = pd.concat(frames)
if plot_type == 'TAS':
try:
plt.scatter(co2_and_mixed['SiO2'],
(co2_and_mixed['Na2O'] +
co2_and_mixed['K2O']),
marker='d', edgecolors='k',
facecolors=calibdata['facecolor'],
label=str(modelname))
except Exception:
plt.scatter(co2_and_mixed['SiO2'],
co2_and_mixed['Na2O+K2O'],
marker='d', edgecolors='k',
facecolors=calibdata['facecolor'],
label=str(modelname))
if plot_type == 'xy':
try:
plt.scatter(co2_and_mixed[x], co2_and_mixed[y],
marker='d', edgecolors='k',
facecolors=calibdata['facecolor'],
label=str(modelname))
except Exception:
w.warn("The requested oxides were not found in ",
"the calibration dataset for " +
str(modelname) + ".")
elif model_type['CO2'] or model_type['Mixed']:
if model_type['CO2']:
thistype = 'CO2'
if model_type['Mixed']:
thistype = 'Mixed'
if plot_type == 'TAS':
try:
plt.scatter(calibdata[thistype]['SiO2'],
(calibdata[thistype]['Na2O'] +
calibdata[thistype]['K2O']),
marker='d', edgecolors='k',
facecolors=calibdata['facecolor'],
label=str(modelname))
except Exception:
plt.scatter(calibdata[thistype]['SiO2'],
calibdata[thistype]['Na2O+K2O'],
marker='d', edgecolors='k',
facecolors=calibdata['facecolor'],
label=str(modelname))
if plot_type == 'xy':
try:
plt.scatter(calibdata[thistype][x],
calibdata[thistype][y],
marker='d', edgecolors='k',
facecolors=calibdata['facecolor'],
label=str(modelname))
except Exception:
w.warn("The requested oxides were not found in ",
"the calibration dataset for "
+ str(modelname) + ".")
else:
raise core.InputError("model must be of type str or list")
# Plot user data
if user_data is None:
pass
else:
if ((user_data.__class__.__module__, user_data.__class__.__name__) ==
('VESIcal', 'BatchFile')):
user_data = user_data.get_data()
# batchfile and VESIcal (__init__) are not imported to avoid
# circular imports
# use above notation to interrogate datatype
if ((user_data.__class__.__module__, user_data.__class__.__name__) ==
('VESIcal', 'Sample')):
user_data = user_data.get_composition()
# batchfile and VESIcal (__init__) are not imported to avoid
# circular imports
# use above notation to interrogate datatype
if plot_type == 'TAS':
_sample = user_data.copy()
try:
_sample["TotalAlkalis"] = _sample["Na2O"] + _sample["K2O"]
except Exception:
core.InputError("Na2O and K2O data must be in user_data")
plt.scatter(_sample['SiO2'], _sample['TotalAlkalis'],
s=150, edgecolors='w', facecolors='red', marker='P',
label='User Data')
if plot_type == 'xy':
_sample = user_data.copy()
plt.scatter(_sample[x], _sample[y],
s=150, edgecolors='w', facecolors='red', marker='P',
label='User Data')
if legend:
plt.legend(bbox_to_anchor=(1.04, 1), loc="upper left")
fig.tight_layout()
if isinstance(save_fig, str):
fig.savefig(save_fig)
return fig, ax1
def show():
"""
Local implementation of pyplot.show(). For displaying created plots.
"""
plt.show()