Exemple #1
0
    def add_collection3d(self, col, zs=0, zdir='z'):
        '''
        Add a 3d collection object to the plot.

        2D collection types are converted to a 3D version by
        modifying the object and adding z coordinate information.

        Supported are:
            - PolyCollection
            - LineColleciton
            - PatchCollection
        '''
        zvals = np.atleast_1d(zs)
        if len(zvals) > 0 :
            zsortval = min(zvals)
        else :
            zsortval = 0   # FIXME: Fairly arbitrary. Is there a better value?

        if type(col) is collections.PolyCollection:
            art3d.poly_collection_2d_to_3d(col, zs=zs, zdir=zdir)
            col.set_sort_zpos(zsortval)
        elif type(col) is collections.LineCollection:
            art3d.line_collection_2d_to_3d(col, zs=zs, zdir=zdir)
            col.set_sort_zpos(zsortval)
        elif type(col) is collections.PatchCollection:
            art3d.patch_collection_2d_to_3d(col, zs=zs, zdir=zdir)
            col.set_sort_zpos(zsortval)

        Axes.add_collection(self, col)
Exemple #2
0
 def plot_thresholded_envelope(self, ax_main: Axes, ax_dist: Axes):
     # e_t, with segment bands, and summary stats to the right :)
     logger.info("Plotting thresholded envelope..")
     self.plot_signal(self.e_t,
                      ax=ax_main,
                      color=envelope_color,
                      lw=thicc_lw)
     rm = self.reference_maker
     ax_main.hlines(rm.ripple_threshold_high, *self.time_range, lw=thin_lw)
     ax_main.hlines(rm.ripple_threshold_low, *self.time_range, lw=thin_lw)
     add_scalebar(ax_main)
     add_title(ax_main, "Thresholds $T$", threshold_color, y=0.58)
     segs = self.reference_segs_test
     visible_segs = segs.intersection(ax_main.get_xlim())
     bars = BrokenBarHCollection(
         xranges=[
             tup for tup in zip(visible_segs.start, visible_segs.duration)
         ],
         yrange=(0, rm.ripple_threshold_low),
         facecolors=segment_color,
         alpha=segment_alpha,
     )
     ax_main.add_collection(bars)
     # Find and plot crossings of lower threshold
     crossings_ix = nonzero(diff(self.e_t > rm.ripple_threshold_low))[0]
     crossings_t = crossings_ix / self.fs
     crossings_y = [rm.ripple_threshold_low] * len(crossings_t)
     ax_main.plot(crossings_t, crossings_y, ".", c="black")
     logger.info("Done")
     logger.info("Plotting envelope density..")
     self.plot_envelope_dist(ax_dist)
     logger.info("Done")
     ax_dist.set_ylim(ax_main.get_ylim())
def add_poly_meshplot(ax: Axes,
                      points: np.ndarray,
                      triangles: List[Tuple[int]],
                      values: np.ndarray,
                      vmax: Optional[float] = None,
                      vmin: Optional[float] = None,
                      cmap: str = 'coolwarm',
                      rasterized: bool = True):
    """ Add meshes with faces colored by the values

    :param Axes ax:
        The axis to add a meshplot to
    :param ndarray points:
        The n x 2 array of points
    :param list[tuple[int]]:
        The set of all perimeter indicies for this mesh
    :param ndarray values:
        The values to color the perimeters with
    """
    if vmin is None:
        if isinstance(values, dict):
            vmin = np.percentile(list(values.values()), 10)
        else:
            vmin = np.percentile(list(values), 10)
    if vmax is None:
        if isinstance(values, dict):
            vmax = np.percentile(list(values.values()), 90)
        else:
            vmax = np.percentile(list(values), 90)
    cmap = mplcm.get_cmap(cmap)
    norm = mplcm.colors.Normalize(vmax=vmax, vmin=vmin)
    scores = []
    patches = []

    points = np.array(points)
    max_idx = points.shape[0]

    for indices in triangles:
        tri = []
        score = []
        for i in indices:
            if i < 0 or i > max_idx:
                continue
            tri.append(points[i, :])
            score.append(values[i])
        if len(tri) < 3:
            continue
        mean = np.nanmean(score)
        if np.isnan(mean):
            continue
        scores.append(norm(mean))
        patch = Polygon(tri, closed=True, edgecolor='none')
        patches.append(patch)

    colors = cmap(scores)

    collection = PatchCollection(patches)
    ax.add_collection(collection)
    collection.set_color(colors)
    return ax
Exemple #4
0
    def geometric_realization(self, dim: int, ax: Axes) -> None:
        """
        Draw simplices of one dimension.
        
        Args:
            dim (int): simplex dimension
            ax (matplotlib.axes.Axes): axes to draw on
        
        Returns:
            None
        """
        if dim > self.simplex_dim:
            raise ValueError(
                f"Dimensionality is too high: max dim for this complex is {self.simplex_dim} but got {dim} as dim"
            )

        ax.set_xlim(self.vertices[:, 0].min() - 1,
                    self.vertices[:, 0].max() + 1)
        ax.set_ylim(self.vertices[:, 1].min() - 1,
                    self.vertices[:, 1].max() + 1)

        if dim == 0:  # points
            ax.scatter(self.vertices[:, 0], self.vertices[:, 1])
            return

        coords = [
            self._simplex_to_point_set(simplex)
            for simplex in self.simplices[dim]
        ]
        if dim == 1:  # edges
            ax.add_collection(LineCollection(coords))
        else:  # volumes
            for polygon in coords:
                ax.fill(polygon[:, 0], polygon[:, 1], color=(0, 0, 1, 0.1))
Exemple #5
0
 def _render_poly_array(self, ax: Axes, poly_array: np.array, mpl_kw: dict):
     """Render the poly array.
     Args:
         ax (Axes): The axis
         poly_array (np.array): The poly
         mpl_kw (dict): The parameters dictionary
     """
     if len(poly_array) > 0:
         poly_array = to_poly_patch(poly_array)
         ax.add_collection(PatchCollection(poly_array, **mpl_kw))
Exemple #6
0
    def render_path(self,
                    table: pd.DataFrame,
                    ax: Axes,
                    subtracted: bool = False,
                    extra_kw: dict = None):
        """Render a table of path geometry.
        Args:
            table (DataFrame): Element table
            ax (matplotlib.axes.Axes): Axis to render on
            kw (dict): Style params
        """
        if len(table) < 1:
            return

        # mask for all non zero width paths
        # TODO: could there be a problem with float vs int here?
        mask = (table.width == 0) | table.width.isna()
        # print(f'subtracted={subtracted}\n\n')
        # display(table)
        # display(imask)

        # convert to polys - handle non zero width
        table1 = table[~mask]

        mask2 = (table1.fillet == 0)

        table2 = table1[~mask2]

        for index, row in table2[table2.fillet.notnull()].iterrows():
            table1.loc[index, 'geometry'] = self.fillet_path(row)

        if len(table1) > 0:
            table1.geometry = table1[['geometry', 'width']].apply(lambda x: x[
                0].buffer(distance=float(x[1]) / 2.,
                          cap_style=CAP_STYLE.flat,
                          join_style=JOIN_STYLE.mitre,
                          resolution=int(self.options['resolution'])),
                                                                  axis=1)

            kw = self.get_style('poly', subtracted=subtracted, extra=extra_kw)

            # render components
            self.render_poly(table1, ax, subtracted=subtracted, extra_kw=kw)

        # handle zero width
        table1 = table[mask]
        # best way to plot?
        # TODO: speed and vectorize?
        if len(table1) > 0:
            kw = self.get_style('path', subtracted=subtracted, extra=extra_kw)
            line_segments = LineCollection(table1.geometry)
            ax.add_collection(line_segments)
Exemple #7
0
    def _draw_edges(self, graph: Graph, positions: Dict[float], ax: Axes,
                    theme: MindMapTheme) -> None:
        edge_collection = []
        for a, b in graph.edges:
            a, b = positions[a], positions[b]

            if not theme.rectangular:
                edge_collection.append((a, b))
            else:
                c = (a[0], b[1])
                edge_collection.append((a, c))
                edge_collection.append((c, b))

        ax.add_collection(
            LineCollection(edge_collection,
                           linewidths=theme.edgewidth,
                           colors=(theme.edgecolor, )))
Exemple #8
0
    def add_wedge_patches_to_axis(W       :  Dict[int, List[KrSector]],
                                  ax      :  Axes,
                                  cmap    :  Colormap,
                                  alpha   :  float,
                                  rmax    :  float,
                                  scale   :  float,
                                  cr      :  Sequence[float],
                                  clims   :  Tuple[float, float])->PatchCollection:

        for sector, krws in W.items():
            wedges = [wedge_from_sector_(krw, rmax=rmax, scale=scale) for krw in krws]
            colors = set_map_sequential_colors(wedges, sector, cr)
            p = PatchCollection(wedges, cmap=cmap, alpha=alpha)
            p.set_array(np.array(colors))
            ax.add_collection(p)
            p.set_clim(clims)
        return p
def add_meshplot(ax: Axes,
                 points: np.ndarray,
                 mesh: Dict[int, List[int]],
                 linewidth: float = 1,
                 markersize: float = 2,
                 color: str = 'r',
                 rasterized: bool = True):
    """ Add a plot of the mesh

    :param Axes ax:
        The axis to add a meshplot to
    :param ndarray points:
        The n x 2 array of points
    :param dict[int, list] mesh:
        The dictionary mapping point indices to their neighbors in the mesh
    """

    points = np.array(points)

    meshlines = []

    # Merge all the links into a line collection
    for i, point in enumerate(points):
        if i not in mesh:
            continue
        connected_points = mesh[i]
        for j in connected_points:
            meshlines.append(
                np.array([
                    [points[i, 0], points[i, 1]],
                    [points[j, 0], points[j, 1]],
                ]))
    if rasterized:
        ax.set_rasterization_zorder(2.1)
    collection = LineCollection(meshlines, colors=color, linewidths=linewidth)
    collection.set_zorder(2)
    ax.add_collection(collection)
    ax.plot(points[:, 0],
            points[:, 1],
            linestyle='',
            marker='o',
            color=color,
            markersize=markersize,
            zorder=0)
    return ax
Exemple #10
0
def add_map_values_to_axis_(W       :  Dict[int, List[KrSector]],
                           M       :  Dict[int, List[float]],
                           ax      :  Axes,
                           cmap    :  Colormap,
                           alpha   :  float,
                           rmax    :  float,
                           scale   :  float,
                           clims   :  Tuple[float, float])->PatchCollection:

    for sector, krws in W.items():
        wedges = [wedge_from_sector_(krw, rmax=rmax, scale=scale) for krw in krws]
        colors = [M[sector][i] for i in range(len(wedges)) ]
        #print(colors)
        p = PatchCollection(wedges, cmap=cmap, alpha=alpha)
        p.set_array(np.array(colors))
        ax.add_collection(p)
        p.set_clim(clims)
    return p
Exemple #11
0
 def add_collection3d(self, col, zs=0, zdir='z'):
     '''
     Add a 3d collection object to the plot.
     2D collection types are converted to a 3D version by
     modifying the object and adding z coordinate information.
     Supported are:
         - PolyCollection
         - LineColleciton
         - PatchCollection
     '''
     if type(col) is collections.PolyCollection:
         art3d.poly_collection_2d_to_3d(col, zs=zs, zdir=zdir)
         col.set_sort_zpos(min(zs))
     elif type(col) is collections.LineCollection:
         art3d.line_collection_2d_to_3d(col, zs=zs, zdir=zdir)
         col.set_sort_zpos(min(zs))
     elif type(col) is collections.PatchCollection:
         art3d.patch_collection_2d_to_3d(col, zs=zs, zdir=zdir)
         col.set_sort_zpos(min(zs))
     Axes.add_collection(self, col)
Exemple #12
0
    def add_collection3d(self, col, zs=0, zdir='z'):
        '''
        Add a 3d collection object to the plot.

        2D collection types are converted to a 3D version by
        modifying the object and adding z coordinate information.

        Supported are:
            - PolyCollection
            - LineColleciton
            - PatchCollection
        '''

        if type(col) is collections.PolyCollection:
            art3d.poly_collection_2d_to_3d(col, zs=zs, zdir=zdir)
            col.set_sort_zpos(min(zs))
        elif type(col) is collections.LineCollection:
            art3d.line_collection_2d_to_3d(col, zs=zs, zdir=zdir)
            col.set_sort_zpos(min(zs))
        elif type(col) is collections.PatchCollection:
            art3d.patch_collection_2d_to_3d(col, zs=zs, zdir=zdir)
            col.set_sort_zpos(min(zs))

        Axes.add_collection(self, col)
def _plot_spots(x: np.ndarray,
                y: np.ndarray,
                c: Union[str, np.ndarray],
                s: float,
                ax: Axes,
                vmin: float = None,
                vmax: float = None,
                **kwargs) -> PatchCollection:
    """This function is simplified from https://github.com/theislab/scanpy/blob/0dfd353abd968f3ecaafd5fac77a50a7e0dd87ee/scanpy/plotting/_utils.py#L1063-L1117,
    which originates from: https://gist.github.com/syrte/592a062c562cd2a98a83.
    The original code at gist is under `The BSD 3-Clause License <http://opensource.org/licenses/BSD-3-Clause>`_.

    x, y: coordinates; c: either a string representing one color or an array of real values for cmap; s: spot radius; vmin, vmax: colormap lower/upper limits; kwargs: all other parameters.
    """
    spots = PatchCollection(
        [Circle((x_, y_), s_) for x_, y_, s_ in np.broadcast(x, y, s)],
        **kwargs)
    if isinstance(c, str):
        spots.set_facecolor(c)
    else:
        spots.set_array(c)
        spots.set_clim(vmin, vmax)
    ax.add_collection(spots)
    return spots
Exemple #14
0
    def render_path(self,
                    table: pd.DataFrame,
                    ax: Axes,
                    subtracted: bool = False,
                    extra_kw: dict = None):
        """Render a table of path geometry.
        Args:
            table (DataFrame): Element table
            ax (matplotlib.axes.Axes): Axis to render on
            kw (dict): Style params
        """
        if len(table) < 1:
            return

        # mask for all non zero width paths
        # TODO: could there be a problem with float vs int here?
        mask = (table.width == 0) | table.width.isna()
        # print(f'subtracted={subtracted}\n\n')
        # display(table)
        # display(imask)

        # convert to polys - handle non zero width
        table1 = table[~mask]

        # if any are fillet, alter the path separately
        table1.loc[table1.fillet.notnull(),
                   'geometry'] = table1[table1.fillet.notnull()].apply(
                       self.fillet_path, axis=1)

        if len(table1) > 0:
            table1.geometry = table1[['geometry', 'width']].apply(lambda x: x[
                0].buffer(distance=float(x[1]) / 2.,
                          cap_style=CAP_STYLE.flat,
                          join_style=JOIN_STYLE.mitre,
                          resolution=int(self.options['resolution'])),
                                                                  axis=1)

            kw = self.get_style('poly', subtracted=subtracted, extra=extra_kw)

            # render components
            self.render_poly(table1, ax, subtracted=subtracted, extra_kw=kw)

        # handle zero width
        table1 = table[mask]
        # best way to plot?
        # TODO: speed and vectorize?
        if len(table1) > 0:
            kw = self.get_style('path', subtracted=subtracted, extra=extra_kw)
            line_segments = LineCollection(table1.geometry)
            ax.add_collection(line_segments)


# DEFAULT['renderer_mpl'] = Dict(
#     annot_conectors=Dict(
#         ofst=[0.025, 0.025],
#         annotate_kw=dict(  # called by ax.annotate
#             color='r',
#             arrowprops=dict(color='r', shrink=0.1, width=0.05, headwidth=0.1)
#         ),
#         line_kw=dict(lw=2, c='r')
#     ),
# )

# class QRendererMPL(QRendererGui):
#     """
#     Renderer for matplotlib in a GUI environment.

#     TODO: How do we handle component selection, etc.
#     """
#     name = 'mpl'
#     element_extensions = dict()

#     def render_shapely(self, obj, kw=None):
#         # TODO: simplify, specialize, and update this function
#         # right now, this is just calling the V0.1 old style
#         render(obj, ax=self.ax, kw= {} or kw)

#     def render_connectors(self):
#         '''
#         Plots all connectors on the active axes. Draws the 1D line that
#         represents the "port" of a connector point. These are referenced for smart placement
#             of Metal components, such as when using functions like Metal_CPW_Connect.

#         TODO: add some filter for sense of what components are visible?
#               or on what chip the connectors are
#         '''

#         for name, conn in self.design.connectors.items():

#             line = LineString(conn.points)

#             self.render_shapely(line, kw=DEFAULT.annot_conectors.line_kw)

#             self.ax.annotate(name, xy=conn.middle[:2], xytext=conn.middle +
#                              np.array(DEFAULT.annot_conectors.ofst),
#                              **DEFAULT.annot_conectors.annotate_kw)
def add_gradient_line(ax: Axes,
                      x: np.ndarray,
                      y: np.ndarray,
                      v: np.ndarray,
                      cmap: str = 'gist_rainbow',
                      linewidth: float = 2,
                      vmin: Optional[float] = None,
                      vmax: Optional[float] = None):
    """ Add a set of lines with a colormap based on the coordinates

    :param Axes ax:
        The axis to add a line to
    :param ndarray x:
        The n point x coordinates
    :param ndarray y:
        The n point y coordinates
    :param ndarray v:
        The n point color matrix to plot
    :param str cmap:
        The matplotlib colormap to use
    :param int linewidth:
        The line width to plot
    :param float vmin:
        The minimum value for the color map
    :param float vmax:
        The maximum value for the color map
    """
    coords = np.stack([x, y], axis=1)
    v = np.squeeze(v)

    assert coords.ndim == 2
    assert coords.shape[1] == 2
    assert coords.shape[0] > 1
    if v.ndim != 1:
        raise ValueError(f'Expected 1D colors but got shape {v.shape}')

    if v.shape[0] != coords.shape[0]:
        raise ValueError(
            f'Got coords with shape {coords.shape} but colors with shape {v.shape}'
        )

    if vmin is None:
        vmin = np.min(v)
    if vmax is None:
        vmax = np.max(v)

    # Convert from vertex-centric to edge-centric
    coords = coords[:, np.newaxis, :]
    stack_coords = np.stack([coords[:-1, 0, :], coords[1:, 0, :]], axis=1)

    assert stack_coords.shape == (coords.shape[0] - 1, 2, 2)

    # Convert each segment to a color on the gradient
    cm = plt.get_cmap(cmap)
    cnorm = mplcolors.Normalize(vmin=vmin, vmax=vmax)
    scalar_map = mplcm.ScalarMappable(norm=cnorm, cmap=cm)

    index = (v[:-1] + v[1:]) / 2

    coll = LineCollection(stack_coords,
                          colors=scalar_map.to_rgba(index),
                          linewidth=linewidth)
    ax.add_collection(coll)
    return ax
Exemple #16
0
    def plot(self, ax: axes.Axes) -> None:
        volumes = self._quotes.get("volume")
        if volumes is None:
            return

        mn, mx = ax.get_ylim()

        h = np.amax(self._quotes.loc[:, "high"])
        l = np.amin(self._quotes.loc[:, "low"])

        lh = np.amax(self._quotes.iloc[-30:].loc[:, "high"])
        ll = np.amin(self._quotes.iloc[-30:].loc[:, "low"])

        pos_top = False
        if abs(l - ll) > abs(h - lh):
            pos_top = False
        else:
            pos_top = True

        # max_height = (mx - mn) * self._chart_height_ratio

        nx, ny = ax.transData.inverted().transform_point(
            (0, int(1080 * self._chart_height_ratio))
        )
        ny = min(max(ny, mn), mx)

        max_height = ny - mn

        # volumes_max = volumes.max()
        volumes_max = volumes.quantile(self._quantile_clamp_ratio)
        volumes = volumes.clip(upper=volumes_max)

        volumes = ((volumes / volumes_max)) * max_height
        # volumes = ((volumes / volumes_max)) * (max_height - mn)
        # volumes += mn

        # interests = self._quotes.get("open interest")

        # if interests is not None:
        # interests = ((interests / interests.max())) * max_height

        length = len(self._quotes)

        bodies = np.ndarray(shape=length, dtype=object)

        xs = np.arange(length)

        for index, df in enumerate(self._quotes.itertuples()):

            p_open = df.open
            p_close = df.close

            color = self._color_unchanged

            if p_close > p_open:
                color = self._color_up
            elif p_close < p_open:
                color = self._color_down

            if not pos_top:
                body = patches.Rectangle(
                    xy=(index - (self._body_width / 2.0), mn),
                    width=self._body_width,
                    # height=volumes.iloc[index] - mn,
                    height=volumes.iloc[index],
                    facecolor=color,
                    edgecolor=color,
                    alpha=self._alpha,
                )

                if self._plot_average:
                    ax.plot(
                        xs,
                        (mn + volumes).rolling(self._average_n).mean(),
                        color=self._color_unchanged,
                        alpha=self._line_alpha,
                        linewidth=self._line_width,
                    )

                # ax.plot(
                # xs,
                # (mn + interests),
                # color="r",
                # alpha=self._alpha,
                # linewidth=self._line_width,
                # )

            else:
                body = patches.Rectangle(
                    xy=(index - (self._body_width / 2.0), mx),
                    width=self._body_width,
                    # height=volumes.iloc[index] - mn,
                    height=-volumes.iloc[index],
                    facecolor=color,
                    edgecolor=color,
                    alpha=self._alpha,
                )

                if self._plot_average:
                    ax.plot(
                        xs,
                        (mx - volumes).rolling(self._average_n).mean(),
                        color=self._color_unchanged,
                        alpha=self._line_alpha,
                        linewidth=self._line_width,
                    )

                # ax.plot(
                # xs,
                # (mx - interests),
                # color="r",
                # alpha=self._alpha,
                # linewidth=self._line_width,
                # )

            bodies[index] = body

        ax.add_collection(
            PatchCollection(
                bodies,
                match_original=True,
                zorder=3,
            ),
        )
def draw_graph_edges(figure: figure.Figure,
                     axis: axes.Axes,
                     graph: nx.Graph,
                     attribute_name: Optional[str] = None,
                     colorbar: bool = True):
    """draws graph edges from node to node, colored by weight

    Parameters
    ----------
    figure: matplotlib.figure.Figure
        a matplotlib Figure
    axis: matplotlib.axes.Axes
        a matplotlib Axes, part of Figure
    graph: nx.Graph
        a networkx graph, assumed to have edges formed like
        graph.add_edge((0, 1), (0, 2), weight=1.234)
    attibute_name: str
        which edge attribute to plot. If None, will try to find the name

    Notes
    -----
    modifes figure and axis in-place

    """
    if attribute_name is None:
        names = find_graph_edge_attribute_names(graph)
        if len(names) != 1:
            raise ValueError("'attribute_name' was not specified, but when "
                             "searching for one and only one name, found "
                             f"{len(names)}: {names}. Specify "
                             "'attribute_name'")
        attribute_name = names[0]

    # graph is (row, col), transpose to get (x, y)
    edges = nx.get_edge_attributes(graph, name=attribute_name)
    segments = [np.array([edge[0][::-1], edge[1][::-1]]) for edge in edges]
    weights = np.array(list(edges.values()))
    line_coll = LineCollection(segments,
                               linestyle='solid',
                               cmap="plasma",
                               linewidths=0.3)
    line_coll.set_array(weights)
    vals = np.array(graph.nodes)
    mnvals = vals.min(axis=0)
    mxvals = vals.max(axis=0)
    ppvals = vals.ptp(axis=0)
    buffx = 0.02 * ppvals[1]
    buffy = 0.02 * ppvals[0]

    line_coll.set_linewidth(0.3 * 512 / ppvals[0])
    axis.add_collection(line_coll)
    axis.set_xlim(mnvals[1] - buffx, mxvals[1] + buffx)
    axis.set_ylim(mnvals[0] - buffy, mxvals[0] + buffy)

    # invert yaxis for image-like orientation
    axis.invert_yaxis()
    axis.set_aspect("equal")
    if colorbar:
        divider = make_axes_locatable(axis)
        cax = divider.append_axes("right", size="5%", pad=0.05)
        figure.colorbar(line_coll, ax=axis, cax=cax)
    axis.set_title(attribute_name)
Exemple #18
0
    def plot(self, ax: axes.Axes) -> None:

        length = len(self._quotes)

        bodies = np.ndarray(shape=length, dtype=object)
        shadows = np.ndarray(shape=length, dtype=object)

        for index, df in enumerate(self._quotes.itertuples()):

            p_open = df.open
            p_high = df.high
            p_low = df.low
            p_close = df.close

            p_shadow_top = p_high
            p_shadow_bottom = p_low

            p_body_top: float
            p_body_bottom: float

            if p_open > p_close:
                p_body_top = p_open
                p_body_bottom = p_close
            else:
                p_body_top = p_close
                p_body_bottom = p_open

            assert p_body_top is not None
            assert p_body_bottom is not None

            if abs(p_open - p_close) < self._minimum_height:
                mid = (p_open + p_close) / 2.0
                mid_height = self._minimum_height / 2.0
                p_body_top = mid + mid_height
                p_body_bottom = mid - mid_height

            if abs(p_shadow_top - p_shadow_bottom) < self._minimum_height:
                mid = (p_shadow_top + p_shadow_bottom) / 2.0
                mid_height = self._minimum_height / 2.0
                p_shadow_top = mid + mid_height
                p_shadow_bottom = mid - mid_height

            color = self._color_unchanged

            if p_close > p_open:
                color = self._color_up
            elif p_close < p_open:
                color = self._color_down

            shadow = patches.Rectangle(
                xy=(index - (self._shadow_width / 2.0), p_shadow_bottom),
                width=self._shadow_width,
                height=p_shadow_top - p_shadow_bottom,
                facecolor=color,
                edgecolor=color,
            )

            body = patches.Rectangle(
                xy=(index - (self._body_width / 2.0), p_body_bottom),
                width=self._body_width,
                height=p_body_top - p_body_bottom,
                facecolor=color,
                edgecolor=color,
            )

            bodies[index] = body
            shadows[index] = shadow

        ax.add_collection(
            PatchCollection(bodies, match_original=True, zorder=self._zorder))
        ax.add_collection(
            PatchCollection(shadows, match_original=True, zorder=self._zorder))