-
Notifications
You must be signed in to change notification settings - Fork 15
Expand file tree
/
Copy pathec_plotter.py
More file actions
180 lines (156 loc) · 7.33 KB
/
Copy pathec_plotter.py
File metadata and controls
180 lines (156 loc) · 7.33 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
import numpy as np
from .base_mpl_plotter import MPLPlotter
from .plotting_tools import color_axis
class ECPlotter(MPLPlotter):
"""A matplotlib plotter specialized in electrochemistry measurements."""
def __init__(self, measurement=None):
"""Initiate the ECPlotter with its default Meausurement to plot"""
self.measurement = measurement
def plot_measurement(
self,
*,
measurement=None,
tspan=None,
V_str=None,
J_str=None,
axes=None,
V_color="k",
J_color="r",
**kwargs,
):
"""Plot two variables on two y-axes vs time
All arguments are optional. By default it plots potential in black on the left
y-axis and current in red on the right y-axis, using data from its entire
measurement. The axes are colored to match the traces and labled witht the
respective series names.
Args:
measurement (Measurement): The measurement to plot, if not the one the
plotter was initiated with.
tspan (iter of float): The timespan (wrt to measurement.tstamp) to plot.
V_str (string): The name of the ValueSeries to plot on the left y-axis.
Defaults to measurement.V_str, which for an ECMeasurement is the name
of its most calibrated/correct potential.
J_str (string): The name of the ValueSeries to plot on the right y-axis.
Defaults to measurement.J_str, which for an ECMeasurement is the name
of its most normalized/correct current.
axes (list of matplotlib.Axis): Two axes to plot on, if not the default
new twinx()'d axes. axes[0] is for V_str and axes[1] for J_str.
V_color (str): The color to plot the series called V_str. Defaults to black.
J_color (str): The color to plot the series called J_str. Defaults to red.
kwargs (dict): Additional key-word arguments are passed to matplotlib's
plot() function, for both potential and current.
Returns list of matplotlib.pyplot.Axis: The axes plotted on.
"""
measurement = measurement or self.measurement
V_str = V_str or measurement.potential.name
J_str = J_str or measurement.current.name
t_v, v = measurement.grab(V_str, tspan=tspan, include_endpoints=False)
t_j, j = measurement.grab(J_str, tspan=tspan, include_endpoints=False)
if axes:
ax1, ax2 = axes
else:
ax1 = self.new_ax()
ax2 = ax1.twinx()
axes = [ax1, ax2]
ax1.plot(t_v, v, "-", color=V_color, label=V_str, **kwargs)
ax2.plot(t_j, j, "-", color=J_color, label=J_str, **kwargs)
ax1.set_xlabel("time / [s]")
ax1.set_ylabel(V_str)
ax2.set_ylabel(J_str)
color_axis(ax1, V_color, lr="left")
color_axis(ax2, J_color, lr="right")
return axes
def plot_vs_potential(
self, measurement=None, tspan=None, V_str=None, J_str=None, ax=None, **kwargs
):
"""Plot an ECMeasurement with electrode potential on the x-axis.
This can actually plot with anything on the x-axis, by specifying what you want
on the x-axis using V_str. The y-axis variable, which can be specified by J_str,
is interpolated onto the time corresponding to the x-axis variable.
TODO: This is a special case of the not-yet-implemented generalized
`plot_vs`. Consider an inheritance structure to reduce redundancy in
future plotters.
All arguments are optional. By default it will plot current vs potential in
black on a single axis for the whole experiment.
TODO: color gradient (cmap=inferno) from first to last cycle.
Args:
measurement (Measurement): What to plot. Defaults to the measurement the
plotter was initialized with
tspan (iter of float): The timespan, relative to vs measurement.tstamp, on
which to plot.
V_str (str): Name of the x-axis ValueSeries. Defaults to calibrated potential
J_str (str): Name of the y-axis ValueSeries. Defaults to normalized current.
ax (matplotlib.pyplot.Axis): The axis to plot on, if not a new one.
kwargs: Additional key-word arguments are passed to matplotlib's
plot() function, including `color`.
Returns matplotlib.pyplot.axis: The axis plotted on.
"""
measurement = measurement or self.measurement
V_str = V_str or measurement.potential.name
J_str = J_str or measurement.current.name
t_v, v = measurement.grab(V_str, tspan=tspan, include_endpoints=False)
t_j, j = measurement.grab(J_str, tspan=tspan, include_endpoints=False)
j_v = np.interp(t_v, t_j, j)
if not ax:
ax = self.new_ax()
if "color" not in kwargs:
kwargs["color"] = "k"
ax.plot(v, j_v, **kwargs)
ax.set_xlabel(V_str)
ax.set_ylabel(J_str)
return ax
class CVDiffPlotter(MPLPlotter):
"""A matplotlib plotter for highlighting the difference between two cv's."""
def __init__(self, measurement=None):
"""Initiate the ECPlotter with its default CyclicVoltammagramDiff to plot"""
self.measurement = measurement
def plot(self, measurement=None, ax=None):
measurement = measurement or self.measurement
ax = ECPlotter.plot_vs_potential(
self, measurement=measurement.cv_1, axes=ax, color="g"
)
ax = ECPlotter.plot_vs_potential(
self, measurement=measurement.cv_2, ax=ax, color="k", linestyle="--"
)
t1, v1 = measurement.cv_1.grab("potential")
j1 = measurement.cv_1.grab_for_t("current", t=t1)
j_diff = measurement.grab_for_t("current", t=t1)
# a mask which is true when cv_1 had bigger current than cv_2:
v_scan = measurement.scan_rate.data
mask = np.logical_xor(0 < j_diff, v_scan < 0)
ax.fill_between(v1, j1 - j_diff, j1, where=mask, alpha=0.2, color="g")
ax.fill_between(
v1,
j1 - j_diff,
j1,
where=np.logical_not(mask),
alpha=0.1,
hatch="//",
color="g",
)
return ax
def plot_measurement(self, measurement=None, axes=None, **kwargs):
measurement = measurement or self.measurement
return ECPlotter.plot_measurement(
self, measurement=measurement, axes=axes, **kwargs
)
def plot_diff(self, measurement=None, tspan=None, ax=None):
measurement = measurement or self.measurement
t, v = measurement.grab("potential", tspan=tspan, include_endpoints=False)
j_diff = measurement.grab_for_t("current", t)
v_scan = measurement.scan_rate.data
# a mask which is true when cv_1 had bigger current than cv_2:
mask = np.logical_xor(0 < j_diff, v_scan < 0)
if not ax:
ax = self.new_ax()
ax.plot(v[mask], j_diff[mask], "k-", label="cv1 > cv2")
ax.plot(
v[np.logical_not(mask)],
j_diff[np.logical_not(mask)],
"k--",
label="cv1 < cv2",
)
return ax
def plot_vs_potential(self):
"""FIXME: This is needed to satisfy ECMeasurement.__init__"""
pass