Skip to content

Commit 41ccfa7

Browse files
authored
Improve Plot Interfaces (#17)
1 parent 17474c8 commit 41ccfa7

File tree

10 files changed

+445
-118
lines changed

10 files changed

+445
-118
lines changed

CHANGELOG.rst

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,19 @@ and this project adheres to `Semantic Versioning`_.
88

99
`Unreleased`_ - 2020-05-11
1010
--------------------------
11+
Added
12+
'''''
13+
- Options to shorten input keyword argument lists for plot methods
14+
(addresses `#14`_)
15+
1116
Changed
1217
'''''''
13-
- Removed support for Python 2.7.
1418
- Ellipse of best fit method calls the `lsq-ellipse`_ package.
1519

20+
Removed
21+
'''''''
22+
- Removed support for Python 2.7.
23+
1624
`1.1.2`_ - 2019-11-07
1725
---------------------
1826
Fixed
@@ -74,3 +82,5 @@ Added
7482
.. _`Keep a Changelog`: https://keepachangelog.com/en/1.0.0/
7583
.. _`lsq-ellipse`: https://pypi.org/project/lsq-ellipse
7684
.. _`Semantic Versioning`: https://semver.org/spec/v2.0.0.html
85+
86+
.. _`#14`: https://github.com/kip-hart/MicroStructPy/issues/14

LICENSE.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
MIT License
22

3-
Copyright (c) 2019 Georgia Tech Research Corporation
3+
Copyright (c) 2020 Georgia Tech Research Corporation
44

55
Permission is hereby granted, free of charge, to any person obtaining a copy
66
of this software and associated documentation files (the "Software"), to deal

docs/source/conf.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
# -- Project information -----------------------------------------------------
2626

2727
project = 'MicroStructPy'
28-
copyright = '2019, Georgia Tech Research Corporation'
28+
copyright = '2020, Georgia Tech Research Corporation'
2929
author = 'Kenneth Hart'
3030

3131
# The short X.Y version

src/microstructpy/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,5 @@
55
import microstructpy.verification
66
from microstructpy._vs import _ver_str
77

8-
__version__ = '1.1.2'
8+
__version__ = '1.2.0'
99
__version__ = _ver_str(__version__)

src/microstructpy/_misc.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
"""Miscellaneous functions
22
3-
This private module contains miscalleaneous functions.
3+
This private module contains miscellaneous functions.
44
"""
55
import numpy as np
66

@@ -17,6 +17,9 @@
1717
demo_needs = {'basalt_circle.xml': ['aphanitic_cdf.csv', 'olivine_cdf.csv'],
1818
'from_image.py': ['aluminum_micro.png']}
1919

20+
mpl_plural_kwargs = {'edgecolors', 'facecolors', 'linewidths', 'antialiaseds',
21+
'offsets'}
22+
2023

2124
# --------------------------------------------------------------------------- #
2225
# #

src/microstructpy/cli.py

Lines changed: 93 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@
2121
import numpy as np
2222
import scipy.stats
2323
import xmltodict
24-
from matplotlib import patches
2524
from mpl_toolkits.mplot3d import Axes3D
2625

2726
from microstructpy import _misc
@@ -619,21 +618,11 @@ def plot_seeds(seeds, phases, domain, plot_files=[], plot_axes=True,
619618

620619
# Plot seeds
621620
edge_kwargs.setdefault('edgecolors', {2: 'k', 3: 'none'}[n_dim])
622-
seeds.plot(facecolors=seed_colors, **edge_kwargs)
623-
624-
# Add legend
625-
custom_seeds = [None for _ in phases]
626-
for seed in seeds:
627-
phase_num = seed.phase
628-
if custom_seeds[phase_num] is None:
629-
c = _phase_color(phase_num, phases)
630-
lbl = phase_names[phase_num]
631-
phase_patch = patches.Patch(fc=c, ec='k', label=lbl)
632-
custom_seeds[phase_num] = phase_patch
633-
634621
if given_names and color_by == 'material':
635-
handles = [h for h in custom_seeds if h is not None]
636-
ax.legend(handles=handles, loc=4)
622+
seeds.plot(material=phase_names, facecolors=seed_colors, loc=4,
623+
**edge_kwargs)
624+
else:
625+
seeds.plot(facecolors=seed_colors, **edge_kwargs)
637626

638627
# Set limits
639628
lims = domain.limits
@@ -671,6 +660,14 @@ def _phase_color(i, phases):
671660
return phases[i].get('color', 'C' + str(i % 10))
672661

673662

663+
def _phase_color_by(i, phases, color_by='material', colormap='viridis'):
664+
if color_by == 'material':
665+
return phases[i].get('color', 'C' + str(i % 10))
666+
elif color_by == 'material number':
667+
n = len(phases)
668+
return _cm_color(i / (n - 1), colormap)
669+
670+
674671
def _cm_color(f, colormap='viridis'):
675672
return plt.get_cmap(colormap)(f)
676673

@@ -739,7 +736,10 @@ def plot_poly(pmesh, phases, plot_files=['polymesh.png'], plot_axes=True,
739736
# Plot polygons
740737
fcs = _poly_colors(pmesh, phases, color_by, colormap, n_dim)
741738
if n_dim == 2:
742-
pmesh.plot(facecolors=fcs)
739+
if given_names and color_by == 'material':
740+
pmesh.plot(facecolors=fcs, material=phase_names)
741+
else:
742+
pmesh.plot(facecolors=fcs)
743743

744744
edge_color = edge_kwargs.pop('edgecolors', (0, 0, 0, 1))
745745
facet_colors = []
@@ -750,22 +750,14 @@ def plot_poly(pmesh, phases, plot_files=['polymesh.png'], plot_axes=True,
750750
facet_colors.append('none')
751751

752752
edge_kwargs.setdefault('capstyle', 'round')
753-
pmesh.plot_facets(color=facet_colors, **edge_kwargs)
753+
pmesh.plot_facets(color=facet_colors, index_by='facet', **edge_kwargs)
754754
else:
755755
edge_kwargs.setdefault('edgecolors', 'k')
756-
pmesh.plot(facecolors=fcs, **edge_kwargs)
757-
758-
# add legend
759-
if given_names:
760-
custom_seeds = [None for _ in phases]
761-
for phase_num in pmesh.phase_numbers:
762-
if custom_seeds[phase_num] is None:
763-
c = phase_colors[phase_num]
764-
lbl = phase_names[phase_num]
765-
phase_patch = patches.Patch(fc=c, ec='k', label=lbl)
766-
custom_seeds[phase_num] = phase_patch
767-
handles = [h for h in custom_seeds if h is not None]
768-
ax.legend(handles=handles, loc=4)
756+
if given_names and color_by == 'material':
757+
pmesh.plot(facecolors=fcs, index_by='seed', material=phase_names,
758+
**edge_kwargs)
759+
else:
760+
pmesh.plot(facecolors=fcs, index_by='seed', **edge_kwargs)
769761

770762
# format axes
771763
lims = np.array([np.min(pmesh.points, 0), np.max(pmesh.points, 0)]).T
@@ -790,35 +782,37 @@ def plot_poly(pmesh, phases, plot_files=['polymesh.png'], plot_axes=True,
790782
def _poly_colors(pmesh, phases, color_by, colormap, n_dim):
791783
if n_dim == 2:
792784
if color_by == 'material':
793-
return [_phase_color(n, phases) for n in pmesh.phase_numbers]
785+
r_colors = [_phase_color(n, phases) for n in pmesh.phase_numbers]
794786
elif color_by == 'seed number':
795787
n = max(pmesh.seed_numbers) + 1
796-
return [_cm_color(s / (n - 1), colormap) for s in
797-
pmesh.seed_numbers]
788+
r_colors = [_cm_color(s / (n - 1), colormap) for s in
789+
pmesh.seed_numbers]
798790
elif color_by == 'material number':
799791
n = len(phases)
800-
return [_cm_color(p / (n - 1), colormap) for p in
801-
pmesh.phase_numbers]
792+
r_colors = [_cm_color(p / (n - 1), colormap) for p in
793+
pmesh.phase_numbers]
794+
n_seeds = max(pmesh.seed_numbers) + 1
795+
s_colors = ['none' for i in range(n_seeds)]
796+
for seed_num, r_c in zip(pmesh.seed_numbers, r_colors):
797+
s_colors[seed_num] = r_c
798+
return s_colors
802799
else:
803-
poly_fcs = []
804-
for n_pair in pmesh.facet_neighbors:
805-
if min(n_pair) < 0:
806-
n_int = max(n_pair)
807-
if color_by == 'material':
808-
phase_num = pmesh.phase_numbers[n_int]
809-
color = _phase_color(phase_num, phases)
810-
elif color_by == 'seed number':
811-
n_seed = max(pmesh.seed_numbers) + 1
812-
seed_num = pmesh.seed_numbers[n_int]
813-
color = _cm_color(seed_num / (n_seed - 1), colormap)
814-
elif color_by == 'material number':
815-
n_phases = len(phases)
816-
phase_num = pmesh.phase_numbers[n_int]
817-
color = _cm_color(phase_num / (n_phases - 1), colormap)
800+
s2p = {s: p for s, p in zip(pmesh.seed_numbers, pmesh.phase_numbers)}
801+
n = max(s2p.keys()) + 1
802+
colors = []
803+
for s in range(n):
804+
if color_by == 'material':
805+
phase_num = s2p[s]
806+
color = _phase_color(phase_num, phases)
807+
elif color_by == 'seed number':
808+
color = _cm_color(s / (n - 1), colormap)
809+
elif color_by == 'material number':
810+
n_phases = len(phases)
811+
color = _cm_color(s2p[s] / (n_phases - 1), colormap)
818812
else:
819813
color = 'none'
820-
poly_fcs.append(color)
821-
return poly_fcs
814+
colors.append(color)
815+
return colors
822816

823817

824818
# --------------------------------------------------------------------------- #
@@ -883,27 +877,56 @@ def plot_tri(tmesh, phases, seeds, pmesh, plot_files=[], plot_axes=True,
883877
ax._axis3don = False
884878
fig.add_axes(ax)
885879

886-
# determine triangle element colors
887-
fcs = _tri_colors(tmesh, seeds, pmesh, phases, color_by, colormap, n_dim)
888-
phase_nums = range(len(phases))
889-
890880
# plot triangle mesh
891881
edge_kwargs.setdefault('linewidths', {2: 0.5, 3: 0.1}[n_dim])
892882
edge_kwargs.setdefault('edgecolors', 'k')
893-
tmesh.plot(facecolors=fcs, **edge_kwargs)
894-
895-
# add legend
896-
if any([given_names[phase_num] for phase_num in phase_nums]):
897-
custom_seeds = [None for _ in phases]
898-
for seed_num in tmesh.element_attributes:
899-
phase_num = seeds[seed_num].phase
900-
if custom_seeds[phase_num] is None:
901-
c = phase_colors[phase_num]
902-
lbl = phase_names[phase_num]
903-
phase_patch = patches.Patch(fc=c, ec='k', label=lbl)
904-
custom_seeds[phase_num] = phase_patch
905-
handles = [h for h in custom_seeds if h is not None]
906-
ax.legend(handles=handles, loc=4)
883+
if given_names and color_by in ('material', 'material number'):
884+
n = len(phases)
885+
cs = [_phase_color_by(i, phases, color_by, colormap) for i in range(n)]
886+
887+
old_e_att = np.copy(tmesh.element_attributes)
888+
old_f_att = np.copy(tmesh.facet_attributes)
889+
tmesh.element_attributes = [seeds[i].phase for i in old_e_att]
890+
891+
# Determine which facets are visible
892+
visible_facets = [i for i, fn in enumerate(pmesh.facet_neighbors)
893+
if min(fn) < 0]
894+
f_frontier = set(visible_facets)
895+
f_expl = set()
896+
r_expl = set(range(-6, 0))
897+
while f_frontier:
898+
new_facets = set()
899+
for f_num in f_frontier:
900+
regions = pmesh.facet_neighbors[f_num]
901+
new_regions = set(regions) - r_expl
902+
for r in new_regions:
903+
phase = phases[pmesh.phase_numbers[r]]
904+
p_type = phase.get('material_type', 'solid')
905+
if p_type in _misc.kw_void:
906+
new_facets |= set(pmesh.regions[r])
907+
r_expl.update(r)
908+
f_expl |= f_frontier
909+
910+
f_frontier |= new_facets
911+
f_frontier -= f_expl
912+
913+
plot_facets = [i for i, fn in enumerate(pmesh.facet_neighbors) if
914+
len(set(fn) - r_expl) == 1]
915+
plot_regions = [list(set(pmesh.facet_neighbors[i]) - r_expl)[0] for i
916+
in plot_facets]
917+
poly_phases = [pmesh.phase_numbers[r] for r in plot_regions]
918+
p_dict = {f: p for f, p in zip(plot_facets, poly_phases)}
919+
plot_phases = [p_dict.get(f, n) for f in tmesh.facet_attributes]
920+
tmesh.facet_attributes = plot_phases
921+
922+
tmesh.plot(facecolors=cs, index_by='attribute', material=phase_names,
923+
**edge_kwargs)
924+
925+
tmesh.element_attributes = old_e_att
926+
tmesh.facet_attributes = old_f_att
927+
else:
928+
fcs = _poly_colors(pmesh, phases, color_by, colormap, n_dim)
929+
tmesh.plot(facecolors=fcs, index_by='attribute', **edge_kwargs)
907930

908931
# format axes
909932
lims = np.array([np.min(tmesh.points, 0), np.max(tmesh.points, 0)]).T

src/microstructpy/geometry/ellipsoid.py

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,8 @@ def __init__(self, **kwargs):
107107
ratio_bc = 1 / kwargs['ratio_cb']
108108
if 'size' in kwargs:
109109
size = kwargs['size']
110+
elif 'volume' in kwargs:
111+
size = 2 * np.cbrt(3 * kwargs['volume'] / (4 * np.pi))
110112
if 'axes' in kwargs:
111113
self.a, self.b, self.c = kwargs['axes']
112114

@@ -166,6 +168,21 @@ def __init__(self, **kwargs):
166168
self.a = r3 / (self.b * self.c)
167169
elif (self.a is not None) and (self.c is not None):
168170
self.b = r3 / (self.a * self.c)
171+
elif (self.a is not None) and (ratio_bc is not None):
172+
r2 = r3 / self.a
173+
# r2 = b * c
174+
# r2 = c * ratio_bc * c = ratio_bc * c^2
175+
# c = sqrt(r2 / ratio_bc) and b = ratio_bc * c
176+
self.c = np.sqrt(r2 / ratio_bc)
177+
self.b = ratio_bc * self.c
178+
elif (self.b is not None) and (ratio_ac is not None):
179+
r2 = r3 / self.b
180+
self.c = np.sqrt(r2 / ratio_ac)
181+
self.a = ratio_ac * self.c
182+
elif (self.c is not None) and (ratio_ab is not None):
183+
r2 = r3 / self.c
184+
self.b = np.sqrt(r2 / ratio_ab)
185+
self.a = ratio_ab * self.b
169186
else:
170187
# r3 = a * b * c
171188
# r3 = a * (a / ratio_ab) * (a / ratio_ac)
@@ -589,7 +606,7 @@ def approximate(self, x1=None):
589606
return np.array([self.bound_max])
590607

591608
if x1 is None:
592-
x1 = 0.75 * min(self.axes)
609+
x1 = 0.25 * min(self.axes)
593610

594611
# Perform approximation such that a > b > c
595612
if (self.a >= self.b) and (self.b >= self.c):

0 commit comments

Comments
 (0)