import itertools
from typing import Union
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.collections import LineCollection
from pycalphad import variables as v
from pycalphad.plot.utils import phase_legend
from pycalphad.plot import triangular # register triangular projection
from pycalphad.mapping.primitives import _get_phase_specific_variable
from pycalphad.mapping.strategy.step_strategy import StepStrategy
from pycalphad.mapping.strategy.binary_strategy import BinaryStrategy
from pycalphad.mapping.strategy.ternary_strategy import TernaryStrategy
from pycalphad.mapping.strategy.isopleth_strategy import IsoplethStrategy
import pycalphad.mapping.utils as map_utils
[docs]
def get_label(var: v.StateVariable):
# If user just passes v.NP rather than an instance of v.NP, then label is just NP
if var == v.NP:
return 'Phase Fraction'
elif isinstance(var, v.X):
if var.phase_name is None:
return 'X({})'.format(var.species.name.capitalize())
else:
return 'X({}, {})'.format(var.phase_name, var.species.name.capitalize())
elif isinstance(var, v.W):
if var.phase_name is None:
return 'W({})'.format(var.species.name.capitalize())
else:
return 'W({}, {})'.format(var.phase_name, var.species.name.capitalize())
elif isinstance(var, v.MU):
return 'MU({})'.format(var.species.name.capitalize())
# Otherwise, we can just use the display name
else:
return var.display_name
[docs]
def plot_step(strategy: StepStrategy, x: v.StateVariable = None, y: v.StateVariable = None, ax = None, legend_generator = phase_legend, set_nan_to_zero = True, *args, **kwargs):
"""
Plots step map using matplotlib
Parameters
----------
strategy : StepStrategy
x : v.StateVariable
y : v.StateVariable
ax : matplotlib axes (optional)
A new axis object will be made if not supplied
legend_generator : function that creates legend handles and colors for list of phases
Returns
-------
Matplotlib axis
"""
if ax is None:
fig, ax = plt.subplots()
# If x is None, then use axis variable and state that x is global
# (this is useful for v.X where we can distinguish v.X(sp,ph) vs. v.X(sp)
x_is_global = False
if x is None:
x = strategy.axis_vars[0]
if x == strategy.axis_vars[0]:
x_is_global = True
# If y is None, then use phase fractions
if y is None:
y = v.NP
step_data = strategy.get_data(x, y, x_is_global, set_nan_to_zero=set_nan_to_zero)
data = step_data['data']
xlim = step_data['xlim']
ylim = step_data['ylim']
handles, colors = legend_generator(sorted(data.keys()))
for p in data:
x_data = data[p]['x']
y_data = data[p]['y']
ax.plot(x_data, y_data, color=colors[p], lw=1, solid_capstyle="butt")
ax.set_xlim(xlim)
ax.set_ylim(ylim)
# Add legend
ax.legend(handles=handles, loc='center left', bbox_to_anchor=(1, 0.5))
plot_title = '-'.join([component.title() for component in sorted(strategy.components) if component != 'VA'])
ax.set_title(plot_title)
ax.set_xlabel(get_label(x))
ax.set_ylabel(get_label(y))
return ax
[docs]
def plot_invariants(ax, strategy: Union[BinaryStrategy, TernaryStrategy], x: v.StateVariable, y: v.StateVariable, phase_colors, label_end_points: bool = False, tie_triangle_color = (1, 0, 0, 1)):
"""
Plots node data from BinaryStrategy or TernaryStrategy onto matplotlib axis
Parameters
----------
ax : matplotlib axis
strategy : BinaryStrategy or TernaryStrategy
x : v.StateVariable
y : v.StateVariable
phase_colors : dict[str, color]
Color to plot end points if set
label_end_points : bool
tie_triangle_color : color
Color to plot node
"""
invariant_data = strategy.get_invariant_data(x, y)
for data in invariant_data:
x_data, y_data, phases = data['x'], data['y'], data['phases']
ax.plot(x_data + [x_data[0]], y_data + [y_data[0]], color=tie_triangle_color, zorder=2.5, lw=1, solid_capstyle="butt")
# If labeling end points, add a scatter point on each phase coordinate in the node
if label_end_points:
for xp, yp, p in zip(x_data, y_data, phases):
ax.scatter([xp], [yp], color=phase_colors[p], s=8, zorder=3)
[docs]
def plot_tielines(ax, strategy: Union[BinaryStrategy, TernaryStrategy], x: v.StateVariable, y: v.StateVariable, phase_colors, tielines = 1, tieline_color=(0, 1, 0, 1)):
"""
Plots tieline data from BinaryStrategy or TernaryStrategy onto matplotlib axis
Parameters
----------
ax : matplotlib axis
strategy : BinaryStrategy or TernaryStrategy
x : v.StateVariable
y : v.StateVariable
phase_colors : dict[str, color]
Color to plot end points if set
tielines : int or False
int - plots every n tielines
False - only plots phase boundaries
tieline_color : color
"""
zpf_data = strategy.get_tieline_data(x, y)
for data in zpf_data:
for p in data:
x_data, y_data = data[p]['x'], data[p]['y']
if not all((y_data == 0) | (y_data == np.nan)):
ax.plot(x_data, y_data, color=phase_colors[p], lw=1, solid_capstyle="butt")
if tielines:
x_list = [data[p]['x'] for p in data]
y_list = [data[p]['y'] for p in data]
tieline_collection = LineCollection(np.asarray([x_list, y_list]).T[::tielines, ...], zorder=1, linewidths=0.5, capstyle="butt", colors=[tieline_color for _ in range(len(x_list[0]))])
ax.add_collection(tieline_collection)
[docs]
def plot_binary(strategy: BinaryStrategy, x: v.StateVariable = None, y: v.StateVariable = None, ax = None, tielines = 1, label_nodes = False, legend_generator = phase_legend, tieline_color=(0, 1, 0, 1), tie_triangle_color=(1, 0, 0, 1), *args, **kwargs):
"""
Plots binary map using matplotlib
Parameters
----------
strategy : BinaryStrategy or TernaryStrategy
x : v.StateVariable
y : v.StateVariable
ax : matplotlib axes (optional)
A new axis object will be made if not supplied
tielines : int or False (optional)
Default = 1
int - plots every n tieline
False - only plots phase boundaries
label_node : bool (optional)
Default = False
Plots points on nodes for each phase if true
legend_generator : function that creates legend handles and colors for list of phases
tieline_color : color
tie_triangle_color : color
Returns
-------
Matplotlib axis
"""
if ax is None:
fig, ax = plt.subplots()
# If variables are not given, then plot composition vs. temperature
sorted_axis_var = map_utils._sort_axis_by_state_vars(strategy.axis_vars)
if x is None:
x = sorted_axis_var[1]
if y is None:
y = sorted_axis_var[0]
phases = sorted(strategy.get_all_phases())
handles, colors = legend_generator(phases)
plot_tielines(ax, strategy, x, y, phase_colors=colors, tielines=tielines, tieline_color=tieline_color)
plot_invariants(ax, strategy, x, y, phase_colors=colors, label_end_points=label_nodes, tie_triangle_color=tie_triangle_color)
# Adjusts axis limits
# 1. Autoscale axis
# 2. If x or y is a strategy axis variable
# Set lower limit to max of strategy limits to rescaled limits
# Set upper limit to min of strategy limits or rescaled limits
# 3. If x or y is not a strategy axis variable, then use the rescaled limits
ax.autoscale()
if x in strategy.axis_vars:
xlim = list(ax.get_xlim())
xlim[0] = np.amax((np.amin(strategy.axis_lims[x]), xlim[0]))
xlim[1] = np.amin((np.amax(strategy.axis_lims[x]), xlim[1]))
ax.set_xlim(xlim)
if y in strategy.axis_vars:
ylim = list(ax.get_ylim())
ylim[0] = np.amax((np.amin(strategy.axis_lims[y]), ylim[0]))
ylim[1] = np.amin((np.amax(strategy.axis_lims[y]), ylim[1]))
ax.set_ylim(ylim)
ax.legend(handles=handles, loc='center left', bbox_to_anchor=(1, 0.5))
plot_title = '-'.join([component.title() for component in sorted(strategy.components) if component != 'VA'])
ax.set_title(plot_title)
ax.set_xlabel(get_label(x))
ax.set_ylabel(get_label(y))
return ax
[docs]
def plot_ternary(strategy: TernaryStrategy, x: v.StateVariable = None, y: v.StateVariable = None, ax = None, tielines = 1, label_nodes = False, legend_generator = phase_legend, tieline_color=(0, 1, 0, 1), tie_triangle_color=(1, 0, 0, 1), *args, **kwargs):
"""
Plots ternary map using matplotlib
Pretty much the same as binary mapping but some extra stuff
to create defualt triangular axis, limit axis limits to (0,1) and
set y label position if axis is triangular
Parameters
----------
strategy : Ternary strategy
x : v.StateVariable
y : v.StateVariable
ax : matplotlib axes (optional)
A new axis object will be made if not supplied
tielines : int or False (optional)
Default = 1
int - plots every n tieline
False - only plots phase boundaries
label_node : bool (optional)
Default = False
Plots points on nodes for each phase if true
legend_generator : function that creates legend handles and colors for list of phases
tieline_color : color
tie_triangle_color : color
Returns
-------
Matplotlib axis
"""
if ax is None:
fig, ax = plt.subplots(subplot_kw={'projection': "triangular"})
plot_binary(strategy, x, y, ax, tielines=tielines, label_nodes=label_nodes, legend_generator=legend_generator, tieline_color=tieline_color, tie_triangle_color=tie_triangle_color, *args, **kwargs)
ax.set_xlim([0,1])
ax.set_ylim([0,1])
# Projection is stored in the default axis name, so only adjust y label if the axis is triangular
# Note: this is assuming that triangular is the only option for making a ternary plot and that the user doesn't change the default name
if 'triangular' in ax.name:
ax.yaxis.label.set_rotation(60) # rotate ylabel
ax.yaxis.set_label_coords(x=0.12, y=0.5) # move the label to a pleasing position
return ax
[docs]
def plot_isopleth(strategy: IsoplethStrategy, x: v.StateVariable = None, y: v.StateVariable = None, ax = None, legend_generator = phase_legend, tie_triangle_color=(1, 0, 0, 1), *args, **kwargs):
"""
Plots isopleth map using matplotlib
Parameters
----------
strategy : IsoplethStrategy
x : v.StateVariable
y : v.StateVariable
ax : matplotlib axes (optional)
A new axis object will be made if not supplied
legend_generator : function that creates legend handles and colors for list of phases
tie_triangle_color : color
Returns
-------
Matplotlib axis
"""
if ax is None:
fig, ax = plt.subplots()
sorted_axis_var = map_utils._sort_axis_by_state_vars(strategy.axis_vars)
if x is None:
x = sorted_axis_var[1]
if y is None:
y = sorted_axis_var[0]
phases = sorted(strategy.get_all_phases())
handles, colors = legend_generator(phases)
# Plot zpf lines
zpf_data = strategy.get_zpf_data(x, y)
xlim, ylim = zpf_data['xlim'], zpf_data['ylim']
for data in zpf_data['data']:
zero_phase = data['phase']
x_data, y_data = data['x'], data['y']
if not all((y_data == 0) | (y_data == np.nan)):
ax.plot(x_data, y_data, color=colors[zero_phase], lw=1, solid_capstyle="butt")
# Plot nodes
node_data = strategy.get_invariant_data(x, y)
for data in node_data:
x_data, y_data = data['x'], data['y']
ax.plot(x_data, y_data, color=tie_triangle_color, zorder=2.5, lw=1, solid_capstyle="butt")
# Set axis limits
# If variable is a strategy axis variables, then set limits to axis variable limits
# If variable is not a strategy axis variable, then set the lower (left, bottom) limits
# to the min of the zpf data
if x in strategy.axis_vars:
ax.set_xlim([np.amin(strategy.axis_lims[x]), np.amax(strategy.axis_lims[x])])
else:
ax.set_xlim(left=xlim[0])
if y in strategy.axis_vars:
ax.set_ylim([np.amin(strategy.axis_lims[y]), np.amax(strategy.axis_lims[y])])
else:
ax.set_ylim(bottom=ylim[0])
ax.legend(handles=handles, loc='center left', bbox_to_anchor=(1, 0.5))
plot_title = '-'.join([component.title() for component in sorted(strategy.components) if component != 'VA'])
ax.set_title(plot_title)
ax.set_xlabel(get_label(x))
ax.set_ylabel(get_label(y))
return ax