5
5
import pandas as pd
6
6
from matplotlib .axes import Axes
7
7
from matplotlib .cm import get_cmap
8
- from matplotlib .colors import Normalize
8
+ from matplotlib .colors import LogNorm , Normalize
9
9
from matplotlib .patches import Rectangle
10
10
from pymatgen import Composition
11
11
@@ -29,7 +29,7 @@ def count_elements(formulas: list) -> pd.Series:
29
29
30
30
# ensure all elements are present in returned Series (with count zero if they
31
31
# weren't in formulas)
32
- ptable = pd .read_csv (ROOT + " /data/periodic_table.csv" )
32
+ ptable = pd .read_csv (f" { ROOT } /data/periodic_table.csv" )
33
33
# fill_value=0 required as max(NaN, any int) = NaN
34
34
srs = srs .combine (pd .Series (0 , index = ptable .symbol ), max , fill_value = 0 )
35
35
return srs
@@ -39,6 +39,7 @@ def ptable_elemental_prevalence(
39
39
formulas : List [str ] = None ,
40
40
elem_counts : pd .Series = None ,
41
41
log : bool = False ,
42
+ ax : Axes = None ,
42
43
cbar_title : str = None ,
43
44
cmap : str = "YlGn" ,
44
45
) -> None :
@@ -51,6 +52,7 @@ def ptable_elemental_prevalence(
51
52
formulas (list[str]): compositional strings, e.g. ["Fe2O3", "Bi2Te3"]
52
53
elem_counts (pd.Series): Map from element symbol to prevalence count
53
54
log (bool, optional): Whether color map scale is log or linear.
55
+ ax (Axes, optional): plt axes. Defaults to None.
54
56
cbar_title (str, optional): Optional Title for colorbar. Defaults to None.
55
57
cmap (str, optional): Matplotlib colormap name to use. Defaults to "YlGn".
56
58
@@ -65,38 +67,40 @@ def ptable_elemental_prevalence(
65
67
if formulas is not None :
66
68
elem_counts = count_elements (formulas )
67
69
68
- ptable = pd .read_csv (ROOT + " /data/periodic_table.csv" )
70
+ ptable = pd .read_csv (f" { ROOT } /data/periodic_table.csv" )
69
71
cmap = get_cmap (cmap )
70
72
71
73
n_rows = ptable .row .max ()
72
74
n_columns = ptable .column .max ()
73
75
74
- # TODO can we pass as as a kwarg and still ensure aspect ratio respected?
75
- plt .figure (figsize = (n_columns , n_rows ))
76
+ # TODO can we pass as a kwarg and still ensure aspect ratio respected?
77
+ fig = plt .figure (figsize = (0.75 * n_columns , 0.7 * n_rows ))
78
+
79
+ if ax is None :
80
+ ax = plt .gca ()
76
81
77
82
rw = rh = 0.9 # rectangle width/height
78
83
min_count = elem_counts .min ()
84
+ # replace([np.inf, -np.inf], np.nan) deals with missing or zero-values when
85
+ # plotting ptable_elemental_ratio
79
86
max_count = elem_counts .replace ([np .inf , - np .inf ], np .nan ).dropna ().max ()
80
87
81
- norm = Normalize (
82
- vmin = 0 if log else min_count ,
83
- vmax = np . log10 ( max_count ) if log else max_count ,
84
- )
88
+ if log :
89
+ norm = LogNorm ( max ( min_count , 1 ), max_count )
90
+ else :
91
+ norm = Normalize ( min_count , max_count )
85
92
86
93
text_style = dict (
87
94
horizontalalignment = "center" ,
88
95
verticalalignment = "center" ,
89
- fontsize = 20 ,
96
+ fontsize = 15 ,
90
97
fontweight = "semibold" ,
91
98
)
92
99
93
100
for symbol , row , column , _ in ptable .values :
94
101
row = n_rows - row
95
102
count = elem_counts [symbol ]
96
103
97
- if log and count > 0 :
98
- count = np .log10 (count )
99
-
100
104
# inf or NaN are expected when passing in elem_counts from ptable_elemental_ratio
101
105
if count == 0 : # not in formulas_a
102
106
color = "silver"
@@ -105,54 +109,30 @@ def ptable_elemental_prevalence(
105
109
elif pd .isna (count ):
106
110
color = "white" # not in either formulas_a nor formulas_b
107
111
else :
108
- color = cmap (norm (count )) if count != 0 else "silver"
112
+ color = cmap (norm (count )) if count > 0 else "silver"
109
113
110
114
if row < 3 :
111
115
row += 0.5
112
116
rect = Rectangle ((column , row ), rw , rh , edgecolor = "gray" , facecolor = color )
113
117
114
118
plt .text (column + rw / 2 , row + rw / 2 , symbol , ** text_style )
115
119
116
- plt .gca ().add_patch (rect )
117
-
118
- # color bar
119
- granularity = 20 # number of cells in the color bar
120
- bar_xpos , bar_ypos = 3.5 , 7.8 # bar position
121
- bar_width , bar_height = 9 , 0.35
122
- cell_width = bar_width / granularity
123
-
124
- for idx in np .arange (granularity ) + (1 if log else 0 ):
125
- value = idx * max_count / (granularity - 1 )
126
- if log and value > 0 :
127
- value = np .log10 (value )
128
-
129
- color = cmap (norm (value )) if value != 0 else "silver"
130
- x_loc = (idx - (1 if log else 0 )) / granularity * bar_width + bar_xpos
131
- rect = Rectangle (
132
- (x_loc , bar_ypos ), cell_width , bar_height , edgecolor = "gray" , facecolor = color
133
- )
120
+ ax .add_patch (rect )
134
121
135
- if idx in np . linspace ( 0 , granularity , granularity // 4 ) + (
136
- 1 if log else 0
137
- ) or idx == ( granularity - ( 0 if log else 1 )):
138
- text = f" { value :.1f } " if log else f" { value :.0f } "
139
- plt . text ( x_loc + cell_width / 2 , bar_ypos - 0.4 , text , ** text_style )
122
+ # colorbar position and size: [bar_xpos, bar_ypos, bar_width, bar_height]
123
+ # anchored at lower left corner
124
+ cb_ax = ax . inset_axes ([ 0.18 , 0.8 , 0.42 , 0.05 ], transform = ax . transAxes )
125
+ # format major and minor ticks
126
+ cb_ax . tick_params ( which = "both" , labelsize = 14 , width = 1 )
140
127
141
- plt .gca ().add_patch (rect )
142
-
143
- if log :
144
- plt .text (
145
- bar_xpos + cell_width / 2 , bar_ypos + 0.6 , int (min_count ), ** text_style
146
- )
147
- plt .text (x_loc + cell_width / 2 , bar_ypos + 0.6 , int (max_count ), ** text_style )
148
-
149
- if cbar_title is None :
150
- cbar_title = "log(Element Count)" if log else "Element Count"
151
-
152
- plt .text (bar_xpos + bar_width / 2 , bar_ypos + 0.7 , cbar_title , ** text_style )
128
+ cbar = fig .colorbar (
129
+ plt .cm .ScalarMappable (norm = norm , cmap = cmap ), orientation = "horizontal" , cax = cb_ax
130
+ )
131
+ cbar .outline .set_linewidth (1 )
132
+ cb_ax .set_title (cbar_title or "Element Count" , pad = 15 , ** text_style )
153
133
154
- plt .ylim (- 0.15 , n_rows + 0.1 )
155
- plt .xlim (0.85 , n_columns + 1. 1 )
134
+ plt .ylim (0.3 , n_rows + 0.1 )
135
+ plt .xlim (0.9 , n_columns + 1 )
156
136
157
137
plt .axis ("off" )
158
138
@@ -204,30 +184,18 @@ def ptable_elemental_ratio(
204
184
205
185
elem_counts = elem_counts_a / elem_counts_b
206
186
207
- cbar_title = "log(Element Ratio)" if log else "Element Ratio"
208
-
209
187
ptable_elemental_prevalence (
210
- elem_counts = elem_counts , log = log , cbar_title = cbar_title , ** kwargs
188
+ elem_counts = elem_counts , log = log , cbar_title = "Element Ratio" , ** kwargs
211
189
)
212
190
213
- text_style = {"fontsize" : 14 , "fontweight" : "semibold" }
214
-
215
- # add key for the colours
216
- plt .text (
217
- 0.8 ,
218
- 2 ,
219
- "gray: not in st list" ,
220
- ** text_style ,
221
- bbox = {"facecolor" : "silver" , "linewidth" : 0 },
222
- )
223
- plt .text (
224
- 0.8 ,
225
- 1.5 ,
226
- "blue: not in 2nd list" ,
227
- ** text_style ,
228
- bbox = {"facecolor" : "lightskyblue" , "linewidth" : 0 },
229
- )
230
- plt .text (0.8 , 1 , "white: not in either" , ** text_style )
191
+ # add legend for the colours
192
+ for y_pos , label , color , txt in [
193
+ [0.4 , "white" , "white" , "not in either" ],
194
+ [1.1 , "blue" , "lightskyblue" , "not in 2nd list" ],
195
+ [1.8 , "gray" , "silver" , "not in 1st list" ],
196
+ ]:
197
+ bbox = {"facecolor" : color , "edgecolor" : "gray" }
198
+ plt .text (0.8 , y_pos , f"{ label } : { txt } " , fontsize = 12 , bbox = bbox )
231
199
232
200
233
201
def hist_elemental_prevalence (
0 commit comments