Skip to content
Snippets Groups Projects
plot2d.py 6.87 KiB
from itertools import cycle
import matplotlib.patches as patches

from pystencils import makeSlice
from pystencils.plot2d import *


def boundary_handling(boundary_handling_obj, slice_obj=None, boundary_name_to_color=None, show_legend=True):
    """
    Shows boundary cells

    :param boundary_handling_obj: instance of :class:`lbmpy.boundaries.BoundaryHandling`
    :param slice_obj: for 3D boundary handling a slice expression has to be passed here to define the plane that
                      should be plotted
    :param boundary_name_to_color: optional dictionary mapping boundary names to colors
    :param show_legend: if True legend for color->boundary name is added
    """
    import matplotlib.pyplot as plt

    boundary_handling_obj.prepare()

    dh = boundary_handling_obj.data_handling
    flag_arr = dh.gather_array(boundary_handling_obj.flag_array_name, slice_obj, ghost_layers=True).squeeze()
    if flag_arr is None:
        return

    if len(flag_arr.shape) != 2 and slice_obj is None:
        raise ValueError("To plot a 3D boundary handling a slice has to be passed")

    if boundary_name_to_color:
        fixed_colors = boundary_name_to_color
    else:
        fixed_colors = {
            'fluid': '#56b4e9',
            'NoSlip': '#999999',
            'UBB': '#d55e00',
            'FixedDensity': '#009e73',
        }

    boundary_names = []
    flag_values = []
    for name, flagName in sorted(boundary_handling_obj.get_boundary_name_to_flag_dict().items(), key=lambda l: l[1]):
        boundary_names.append(name)
        flag_values.append(flagName)
    default_cycle = matplotlib.rcParams['axes.prop_cycle']
    color_values = [fixed_colors[name] if name in fixed_colors else c['color']
                    for c, name in zip(default_cycle, boundary_names)]

    colormap = matplotlib.colors.ListedColormap(color_values)
    bounds = np.array(flag_values, dtype=float) - 0.5
    bounds = list(bounds) + [bounds[-1] + 1]
    norm = matplotlib.colors.BoundaryNorm(bounds, colormap.N)

    flag_arr = flag_arr.swapaxes(0, 1)
    plt.imshow(flag_arr, interpolation='none', origin='lower',
               cmap=colormap, norm=norm)

    path_list = [matplotlib.patches.Patch(color=color, label=name) for color, name in zip(color_values, boundary_names)]
    plt.axis('equal')
    if show_legend:
        plt.legend(handles=path_list, bbox_to_anchor=(1.02, 0.5), loc=2, borderaxespad=0.)


def phase_plot(phase_field, linewidth=1.0):
    color_cycle = cycle(['#fe0002', '#00fe00', '#0000ff', '#ffa800', '#f600ff'])

    assert len(phase_field.shape) == 3

    for i in range(phase_field.shape[-1]):
        scalar_field_alpha_value(phase_field[..., i], next(color_cycle), clip=True, interpolation='bilinear')
    for i in range(phase_field.shape[-1]):
        scalar_field_contour(phase_field[..., i], levels=[0.5], colors='k', linewidths=[linewidth])


def phase_plot_for_step(phase_field_step, slice_obj=makeSlice[:, :], **kwargs):
    concentrations = phase_field_step.concentration[slice_obj]
    phase_plot(concentrations, **kwargs)


class LbGrid:
    """Visualizes a 2D LBM grid with matplotlib by drawing cells and pdf arrows"""

    def __init__(self, x_cells, y_cells):
        """Create a new grid with the given number of cells in x (horizontal) and y (vertical) direction"""

        self._xCells = x_cells
        self._yCells = y_cells

        self._patches = []
        for x in range(x_cells):
            for y in range(y_cells):
                self._patches.append(patches.Rectangle((x, y), 1.0, 1.0, fill=False, linewidth=3, color='#bbbbbb'))

        self._cellBoundaries = dict()  # mapping cell to rectangle patch
        self._arrows = dict()  # mapping (cell, direction) tuples to arrow patches

    def add_cell_boundary(self, cell, **kwargs):
        """Draws a rectangle around a single cell. Keyword arguments are passed to the matplotlib Rectangle patch"""
        if 'fill' not in kwargs:
            kwargs['fill'] = False
        if 'linewidth' not in kwargs:
            kwargs['linewidth'] = 3
        if 'color' not in kwargs:
            kwargs['color'] = '#bbbbbb'
        self._cellBoundaries[cell] = patches.Rectangle(cell, 1.0, 1.0, **kwargs)

    def add_cell_boundaries(self, **kwargs):
        """Draws a rectangle around all cells. Keyword arguments are passed to the matplotlib Rectangle patch"""
        for x in range(self._xCells):
            for y in range(self._yCells):
                self.add_cell_boundary((x, y), **kwargs)

    def add_arrow(self, cell, arrow_position, arrow_direction, **kwargs):
        """
        Draws an arrow in a cell. If an arrow exists already at this position, it is replaced.

        :param cell: cell coordinate as tuple (x,y)
        :param arrow_position: each cell has 9 possible positions specified as tuple e.g. upper left (-1, 1)
        :param arrow_direction: direction of the arrow as (x,y) tuple
        :param kwargs: arguments passed directly to the FancyArrow patch of matplotlib
        """
        cell_midpoint = (0.5 + cell[0], 0.5 + cell[1])

        if 'width' not in kwargs: kwargs['width'] = 0.005
        if 'color' not in kwargs: kwargs['color'] = 'k'

        if arrow_position == (0, 0):
            del kwargs['width']
            self._arrows[(cell, arrow_position)] = patches.Circle(cell_midpoint, radius=0.03, **kwargs)
        else:
            arrow_midpoint = (cell_midpoint[0] + arrow_position[0] * 0.25,
                              cell_midpoint[1] + arrow_position[1] * 0.25)
            length = 0.75
            arrow_start = (arrow_midpoint[0] - arrow_direction[0] * 0.25 * length,
                           arrow_midpoint[1] - arrow_direction[1] * 0.25 * length)

            patch = patches.FancyArrow(arrow_start[0], arrow_start[1],
                                       0.25 * length * arrow_direction[0],
                                       0.25 * length * arrow_direction[1],
                                       **kwargs)
            self._arrows[(cell, arrow_position)] = patch

    def fill_with_default_arrows(self, **kwargs):
        """Fills the complete grid with the default pdf arrows"""
        for x in range(self._xCells):
            for y in range(self._yCells):
                for dx in [-1, 0, 1]:
                    for dy in [-1, 0, 1]:
                        if 'color' not in kwargs: kwargs['color'] = '#bbbbbb'
                        if 'width' not in kwargs: kwargs['width'] = 0.006

                        self.add_arrow((x, y), (dx, dy), (dx, dy), **kwargs)

    def draw(self, ax):
        """Draw the grid into a given matplotlib axes object"""

        for p in self._patches:
            ax.add_patch(p)

        for arrowPatch in self._arrows.values():
            ax.add_patch(arrowPatch)

        offset = 0.1
        ax.set_xlim(-offset, self._xCells+offset)
        ax.set_xlim(-offset, self._xCells + offset)
        ax.set_ylim(-offset, self._yCells + offset)
        ax.set_aspect('equal')
        ax.set_axis_off()