def plot_seeds(seeds, phases, domain, plot_files=[], plot_axes=True, color_by='material', colormap='viridis', **edge_kwargs): """Plot seeds This function creates formatted plots of a :class:`.SeedList`. Args: seeds (SeedList): Seed list to plot. phases (list): List of phase dictionaries. See :ref:`phase_dict_guide` for more details. domain (from :mod:`microstructpy.geometry`): Domain geometry. plot_files (list): *(optional)* List of files to save the output plot. Defaults to saving the plot to ``polymesh.png``. plot_axes (bool): *(optional)* Flag to turn the axes on or off. True shows the axes, False removes them. Defaults to True. color_by (str): *(optional)* {'material' | 'seed number' | 'material number'} Option to choose how the polygons/polyhedra are colored. Defaults to 'material'. colormap (str): *(optional)* Name of the matplotlib colormap to color the seeds. Ignored if `color_by='material'`. Defaults to 'viridis', the standard matplotlib colormap. See `Choosing Colormaps in Matplotlib`_ for more details. **edge_kwargs: additional keyword arguments that will be passed to :meth:`.SeedList.plot`. """ if not plot_files: return phase_names = [] given_names = False for i, phase in enumerate(phases): if 'name' in phase: given_names = True name = phase.get('name', 'Material ' + str(i + 1)) phase_names.append(name) seed_colors = _seed_colors(seeds, phases, color_by, colormap) n_dim = seeds[0].geometry.n_dim # Set up axes plt.clf() plt.close('all') fig = plt.figure() ax = fig.gca(projection={2: None, 3: Axes3D.name}[n_dim], label='seeds') if not plot_axes: if n_dim == 2: ax.set_axis_off() ax.get_xaxis().set_visible(False) ax.get_yaxis().set_visible(False) else: ax._axis3don = False # Plot seeds edge_kwargs.setdefault('edgecolors', {2: 'k', 3: 'none'}[n_dim]) if given_names and color_by == 'material': seeds.plot(material=phase_names, facecolors=seed_colors, loc=4, **edge_kwargs) else: seeds.plot(facecolors=seed_colors, **edge_kwargs) # Crop to Domain d_lims = domain.limits if n_dim == 2: plt.axis('square') plt.xlim(d_lims[0]) plt.ylim(d_lims[1]) elif n_dim == 3: plt.gca().set_xlim(d_lims[0]) plt.gca().set_ylim(d_lims[1]) plt.gca().set_zlim(d_lims[2]) _misc.axisEqual3D(plt.gca()) # Save plot for fname in plot_files: if n_dim == 3: fig.subplots_adjust(**_misc.plt_3d_adj) plt.savefig(fname, bbox_inches='tight', pad_inches=0.15) else: plt.savefig(fname, bbox_inches='tight', pad_inches=0) plt.close('all')
def plot(self, index_by='seed', material=[], loc=0, **kwargs): """Plot the seeds in the seed list. This function plots the seeds contained in the seed list. In 2D, the seeds are grouped into matplotlib collections to reduce the computational load. In 3D, matplotlib does not have patches, so each seed is rendered as its own surface. Additional keyword arguments can be specified and passed through to matplotlib. These arguments should be either single values (e.g. ``edgecolors='k'``), or lists of values that have the same length as the seed list. Args: index_by (str): *(optional)* {'material' | 'seed'} Flag for indexing into the other arrays passed into the function. For example, ``plot(index_by='material', color=['blue', 'red'])`` will plot the seeds with ``phase`` equal to 0 in blue, and seeds with ``phase`` equal to 1 in red. Defaults to 'seed'. material (list): *(optional)* Names of material phases. One entry per material phase (the ``index_by`` argument is ignored). If this argument is set, a legend is added to the plot with one entry per material. loc (int or str): *(optional)* The location of the legend, if 'material' is specified. This argument is passed directly through to :func:`matplotlib.pyplot.legend`. Defaults to 0, which is 'best' in matplotlib. **kwargs: Keyword arguments to pass to matplotlib """ seed_args = [{} for seed in self] for seed_num, seed in enumerate(self): phase_num = seed.phase for key, val in kwargs.items(): if type(val) in (list, np.array): if index_by == 'seed' and len(val) > seed_num: seed_args[seed_num][key] = val[seed_num] elif index_by == 'material' and len(val) > phase_num: seed_args[seed_num][key] = val[phase_num] else: seed_args[seed_num][key] = val n = self[0].geometry.n_dim if n == 2: ax = plt.gca() else: ax = plt.gcf().gca(projection=Axes3D.name) n_obj = _misc.ax_objects(ax) if n_obj > 0: xlim = ax.get_xlim() ylim = ax.get_ylim() else: xlim = [float('inf'), -float('inf')] ylim = [float('inf'), -float('inf')] if n == 3: if n_obj > 0: zlim = ax.get_zlim() else: zlim = [float('inf'), -float('inf')] for seed, args in zip(self, seed_args): seed.plot(**args) elif n == 2: ellipse_data = {'w': [], 'h': [], 'a': [], 'xy': []} ec_kwargs = {} rect_data = {'xy': [], 'w': [], 'h': [], 'angle': []} rect_kwargs = {} pc_verts = [] pc_kwargs = {} for seed, args in zip(self, seed_args): geom_name = type(seed.geometry).__name__.lower().strip() if geom_name == 'ellipse': a, b = seed.geometry.axes cen = np.array(seed.position) t = seed.geometry.angle_deg ellipse_data['w'].append(2 * a) ellipse_data['h'].append(2 * b) ellipse_data['a'].append(t) ellipse_data['xy'].append(cen) for key, val in args.items(): val_list = ec_kwargs.get(key, []) val_list.append(val) ec_kwargs[key] = val_list elif geom_name == 'circle': diam = seed.geometry.diameter cen = np.array(seed.position) ellipse_data['w'].append(diam) ellipse_data['h'].append(diam) ellipse_data['a'].append(0) ellipse_data['xy'].append(cen) for key, val in args.items(): val_list = ec_kwargs.get(key, []) val_list.append(val) ec_kwargs[key] = val_list elif geom_name in ['rectangle', 'square']: w, h = seed.geometry.side_lengths corner = seed.geometry.corner t = seed.geometry.angle_deg rect_data['w'].append(w) rect_data['h'].append(h) rect_data['angle'].append(t) rect_data['xy'].append(corner) for key, val in args.items(): val_list = rect_kwargs.get(key, []) val_list.append(val) rect_kwargs[key] = val_list elif geom_name == 'curl': xy = seed.geometry.plot_xy() pc_verts.append(xy) for key, val in args.items(): val_list = pc_kwargs.get(key, []) val_list.append(val) pc_kwargs[key] = val_list elif geom_name == 'nonetype': pass else: e_str = 'Cannot plot groups of ' + geom_name e_str += ' yet.' raise NotImplementedError(e_str) # abbreviate kwargs if all the same for key, val in ec_kwargs.items(): v1 = val[0] same = True for v in val: same &= v == v1 if same: ec_kwargs[key] = v1 for key, val in pc_kwargs.items(): v1 = val[0] same = True for v in val: same &= v == v1 if same: pc_kwargs[key] = v1 # Plot Circles and Ellipses ax = plt.gca() w = np.array(ellipse_data['w']) h = np.array(ellipse_data['h']) a = np.array(ellipse_data['a']) xy = np.array(ellipse_data['xy']) ec = collections.EllipseCollection(w, h, a, units='x', offsets=xy, transOffset=ax.transData, **ec_kwargs) ax.add_collection(ec) # Plot Rectangles rects = [ Rectangle(xy=xyi, width=wi, height=hi, angle=ai) for xyi, wi, hi, ai in zip(rect_data['xy'], rect_data['w'], rect_data['h'], rect_data['angle']) ] rc = collections.PatchCollection(rects, False, **rect_kwargs) ax.add_collection(rc) # Plot Polygons pc = collections.PolyCollection(pc_verts, **pc_kwargs) ax.add_collection(pc) ax.autoscale_view() # Add legend if material: p_kwargs = [{'label': m} for m in material] if index_by == 'seed': for seed_kwargs, seed in zip(seed_args, self): p = seed.phase p_kwargs[p].update(seed_kwargs) else: for key, val in kwargs.items(): if type(val) in (list, np.array): for i, elem in enumerate(val): p_kwargs[i][key] = elem else: for i in range(len(p_kwargs)): p_kwargs[i][key] = val # Replace plural keywords for p_kw in p_kwargs: for kw in _misc.mpl_plural_kwargs: if kw in p_kw: p_kw[kw[:-1]] = p_kw[kw] del p_kw[kw] handles = [patches.Patch(**p_kw) for p_kw in p_kwargs] ax.legend(handles=handles, loc=loc) # Adjust Axes seed_lims = [np.array(s.geometry.limits).flatten() for s in self] mins = np.array(seed_lims)[:, 0::2].min(axis=0) maxs = np.array(seed_lims)[:, 1::2].max(axis=0) xlim = (min(xlim[0], mins[0]), max(xlim[1], maxs[0])) ylim = (min(ylim[0], mins[1]), max(ylim[1], maxs[1])) if n == 2: plt.axis('square') ax.set_xlim(xlim) ax.set_ylim(ylim) if n == 3: zlim = (min(zlim[0], mins[2]), max(zlim[1], maxs[2])) ax.set_xlim(xlim) ax.set_ylim(ylim) ax.set_zlim(zlim) _misc.axisEqual3D(ax)
def plot_breakdown(self, index_by='seed', material=[], loc=0, **kwargs): """Plot the breakdowns of the seeds in seed list. This function plots the breakdowns of seeds contained in the seed list. In 2D, the breakdowns are grouped into matplotlib collections to reduce the computational load. In 3D, matplotlib does not have patches, so each breakdown is rendered as its own surface. Additional keyword arguments can be specified and passed through to matplotlib. These arguments should be either single values (e.g. ``edgecolors='k'``), or lists of values that have the same length as the seed list. Args: index_by (str): *(optional)* {'material' | 'seed'} Flag for indexing into the other arrays passed into the function. For example, ``plot(index_by='material', color=['blue', 'red'])`` will plot the seeds with ``phase`` equal to 0 in blue, and seeds with ``phase`` equal to 1 in red. Defaults to 'seed'. material (list): *(optional)* Names of material phases. One entry per material phase (the ``index_by`` argument is ignored). If this argument is set, a legend is added to the plot with one entry per material. loc (int or str): *(optional)* The location of the legend, if 'material' is specified. This argument is passed directly through to :func:`matplotlib.pyplot.legend`. Defaults to 0, which is 'best' in matplotlib. **kwargs: Keyword arguments to pass to matplotlib """ seed_args = [{} for seed in self] for seed_num, seed in enumerate(self): phase_num = seed.phase for key, val in kwargs.items(): if type(val) in (list, np.array): if index_by == 'seed' and len(val) > seed_num: seed_args[seed_num][key] = val[seed_num] elif index_by == 'material' and len(val) > phase_num: seed_args[seed_num][key] = val[phase_num] else: seed_args[seed_num][key] = val n = self[0].geometry.n_dim if n == 2: ax = plt.gca() else: ax = plt.gcf().gca(projection=Axes3D.name) n_obj = _misc.ax_objects(ax) if n_obj > 0: xlim = ax.get_xlim() ylim = ax.get_ylim() else: xlim = [float('inf'), -float('inf')] ylim = [float('inf'), -float('inf')] if n == 3: if n_obj > 0: zlim = ax.get_zlim() else: zlim = [float('inf'), -float('inf')] for seed, args in zip(self, seed_args): seed.plot_breakdown(**args) elif n == 2: breakdowns = np.zeros((0, 3)) ec_kwargs = {} for seed, args in zip(self, seed_args): breakdowns = np.concatenate((breakdowns, seed.breakdown)) n_c = len(seed.breakdown) for key, val in args.items(): val_list = ec_kwargs.get(key, []) val_list.extend(n_c * [val]) ec_kwargs[key] = val_list d = 2 * breakdowns[:, -1] xy = breakdowns[:, :-1] a = np.full(len(breakdowns), 0) # abbreviate kwargs if all the same for key, val in ec_kwargs.items(): v1 = val[0] same = True for v in val: same &= v == v1 if same: ec_kwargs[key] = v1 ax = plt.gca() ec = collections.EllipseCollection(d, d, a, units='x', offsets=xy, transOffset=ax.transData, **ec_kwargs) ax.add_collection(ec) ax.autoscale_view() # Add legend if material: p_kwargs = [{'label': m} for m in material] if index_by == 'seed': for seed_kwargs, seed in zip(seed_args, self): p = seed.phase p_kwargs[p].update(seed_kwargs) else: for key, val in kwargs.items(): if type(val) in (list, np.array): for i, elem in enumerate(val): p_kwargs[i][key] = elem else: for i in range(len(p_kwargs)): p_kwargs[i][key] = val # Replace plural keywords for p_kw in p_kwargs: for kw in _misc.mpl_plural_kwargs: if kw in p_kw: p_kw[kw[:-1]] = p_kw[kw] del p_kw[kw] handles = [patches.Patch(**p_kw) for p_kw in p_kwargs] if n == 2: ax.legend(handles=handles, loc=loc) else: plt.gca().legend(handles=handles, loc=loc) # Adjust Axes seed_lims = [np.array(s.geometry.limits).flatten() for s in self] mins = np.array(seed_lims)[:, 0::2].min(axis=0) maxs = np.array(seed_lims)[:, 1::2].max(axis=0) xlim = (min(xlim[0], mins[0]), max(xlim[1], maxs[0])) ylim = (min(ylim[0], mins[1]), max(ylim[1], maxs[1])) if n == 2: plt.axis('square') ax.set_xlim(xlim) ax.set_ylim(ylim) if n == 3: zlim = (min(zlim[0], mins[2]), max(zlim[1], maxs[2])) ax.set_xlim(xlim) ax.set_ylim(ylim) ax.set_zlim(zlim) _misc.axisEqual3D(ax)
def plot(self, index_by='element', material=[], loc=0, **kwargs): """Plot the mesh. This method plots the mesh using matplotlib. In 2D, this creates a :class:`matplotlib.collections.PolyCollection` and adds it to the current axes. In 3D, it creates a :class:`mpl_toolkits.mplot3d.art3d.Poly3DCollection` and adds it to the current axes. The keyword arguments are passed though to matplotlib. Args: index_by (str): *(optional)* {'element' | 'attribute'} Flag for indexing into the other arrays passed into the function. For example, ``plot(index_by='attribute', color=['blue', 'red'])`` will plot the elements with ``element_attribute`` equal to 0 in blue, and elements with ``element_attribute`` equal to 1 in red. Note that in 3D the facets are plotted instead of the elements, so kwarg lists must be based on ``facets`` and ``facet_attributes``. Defaults to 'element'. material (list): *(optional)* Names of material phases. One entry per material phase (the ``index_by`` argument is ignored). If this argument is set, a legend is added to the plot with one entry per material. Note that the ``element_attributes`` in 2D or the ``facet_attributes`` in 3D must be the material numbers for the legend to be formatted properly. loc (int or str): *(optional)* The location of the legend, if 'material' is specified. This argument is passed directly through to :func:`matplotlib.pyplot.legend`. Defaults to 0, which is 'best' in matplotlib. **kwargs: Keyword arguments that are passed through to matplotlib. """ n_dim = len(self.points[0]) if n_dim == 2: ax = plt.gca() else: ax = plt.gcf().gca(projection=Axes3D.name) n_obj = _misc.ax_objects(ax) if n_obj > 0: xlim = ax.get_xlim() ylim = ax.get_ylim() else: xlim = [float('inf'), -float('inf')] ylim = [float('inf'), -float('inf')] if n_dim == 2: simps = np.array(self.elements) pts = np.array(self.points) xy = pts[simps, :] plt_kwargs = {} for key, value in kwargs.items(): if type(value) in (list, np.array): plt_value = [] for e_num, e_att in enumerate(self.element_attributes): if index_by == 'element': ind = e_num elif index_by == 'attribute': ind = int(e_att) else: e_str = 'Cannot index by {}.'.format(index_by) raise ValueError(e_str) v = value[ind] plt_value.append(v) else: plt_value = value plt_kwargs[key] = plt_value pc = collections.PolyCollection(xy, **plt_kwargs) ax.add_collection(pc) ax.autoscale_view() else: if n_obj > 0: zlim = ax.get_zlim() else: zlim = [float('inf'), -float('inf')] xy = [np.array([self.points[kp] for kp in f]) for f in self.facets] plt_kwargs = {} for key, value in kwargs.items(): if type(value) in (list, np.array): plt_value = [] for f_num, f_att in enumerate(self.facet_attributes): if index_by == 'element': ind = f_num elif index_by == 'attribute': ind = int(f_att) else: e_str = 'Cannot index by {}.'.format(index_by) raise ValueError(e_str) if ind < len(value): v = value[ind] else: v = 'none' plt_value.append(v) else: plt_value = value plt_kwargs[key] = plt_value pc = Poly3DCollection(xy, **plt_kwargs) ax.add_collection(pc) # Add legend if material and index_by == 'attribute': p_kwargs = [{'label': m} for m in material] for key, value in kwargs.items(): if type(value) not in (list, np.array): for kws in p_kwargs: kws[key] = value for i, m in enumerate(material): if type(value) in (list, np.array): p_kwargs[i][key] = value[i] else: p_kwargs[i][key] = value # Replace plural keywords for p_kw in p_kwargs: for kw in _misc.mpl_plural_kwargs: if kw in p_kw: p_kw[kw[:-1]] = p_kw[kw] del p_kw[kw] handles = [patches.Patch(**p_kw) for p_kw in p_kwargs] ax.legend(handles=handles, loc=loc) # Adjust Axes mins = np.array(self.points).min(axis=0) maxs = np.array(self.points).max(axis=0) xlim = (min(xlim[0], mins[0]), max(xlim[1], maxs[0])) ylim = (min(ylim[0], mins[1]), max(ylim[1], maxs[1])) if n_dim == 2: plt.axis('square') plt.xlim(xlim) plt.ylim(ylim) elif n_dim == 3: zlim = (min(zlim[0], mins[2]), max(zlim[1], maxs[2])) ax.set_xlim(xlim) ax.set_ylim(ylim) ax.set_zlim(zlim) _misc.axisEqual3D(ax)