コード例 #1
0
ファイル: _pitch_plot.py プロジェクト: yumamoto164/mplsoccer
 def lines(self,
           xstart,
           ystart,
           xend,
           yend,
           color=None,
           n_segments=100,
           comet=False,
           transparent=False,
           alpha_start=0.01,
           alpha_end=1,
           cmap=None,
           ax=None,
           **kwargs):
     validate_ax(ax)
     linecollection = lines(xstart,
                            ystart,
                            xend,
                            yend,
                            color=color,
                            n_segments=n_segments,
                            comet=comet,
                            transparent=transparent,
                            alpha_start=alpha_start,
                            alpha_end=alpha_end,
                            cmap=cmap,
                            ax=ax,
                            vertical=self.vertical,
                            reverse_cmap=self.reverse_cmap,
                            **kwargs)
     return linecollection
コード例 #2
0
ファイル: _pitch_plot.py プロジェクト: yumamoto164/mplsoccer
    def plot(self, x, y, ax=None, **kwargs):
        """ Utility wrapper around matplotlib.axes.Axes.plot,
        which automatically flips the x and y coordinates if the pitch is vertical.

        Parameters
        ----------
        x, y : array-like or scalar.
            Commonly, these parameters are 1D arrays.
        ax : matplotlib.axes.Axes, default None
            The axis to plot on.
        **kwargs : All other keyword arguments are passed on to matplotlib.axes.Axes.plot.

        Returns
        -------
        lines : A list of Line2D objects representing the plotted data.

        Examples
        --------
        >>> from mplsoccer import Pitch
        >>> pitch = Pitch()
        >>> fig, ax = pitch.draw()
        >>> pitch.plot([30, 35, 20], [30, 19, 40], ax=ax)
        """
        validate_ax(ax)
        x, y = self._reverse_if_vertical(x, y)
        return ax.plot(x, y, **kwargs)
コード例 #3
0
ファイル: _pitch_plot.py プロジェクト: ElJdP/mplsoccer
    def label_heatmap(self, stats, ax=None, **kwargs):
        """ Labels the heatmap(s) and automatically flips the coordinates if the pitch is vertical.

        Parameters
        ----------
        stats : A dictionary or list of dictionaries.
            This should be calculated via bin_statistic_positional() or bin_statistic().
        ax : matplotlib.axes.Axes, default None
            The axis to plot on.
        **kwargs : All other keyword arguments are passed on to matplotlib.axes.Axes.annotate.

        Returns
        -------
        annotations : A list of matplotlib.text.Annotation.

        Examples
        --------
        >>> from mplsoccer import Pitch
        >>> import numpy as np
        >>> import matplotlib.patheffects as path_effects
        >>> pitch = Pitch(line_zorder=2, pitch_color='black')
        >>> fig, ax = pitch.draw()
        >>> x = np.random.uniform(low=0, high=120, size=100)
        >>> y = np.random.uniform(low=0, high=80, size=100)
        >>> stats = pitch.bin_statistic(x, y)
        >>> pitch.heatmap(stats, edgecolors='black', cmap='hot', ax=ax)
        >>> stats['statistic'] = stats['statistic'].astype(int)
        >>> path_eff = [path_effects.Stroke(linewidth=0.5, foreground='#22312b')]
        >>> text = pitch.label_heatmap(stats, color='white', ax=ax, fontsize=20, ha='center', \
va='center', path_effects=path_eff)
        """
        validate_ax(ax)

        if not isinstance(stats, list):
            stats = [stats]

        annotation_list = []
        for bin_stat in stats:
            # remove labels outside the plot extents
            mask_x_outside1 = bin_stat['cx'] < self.dim.pitch_extent[0]
            mask_x_outside2 = bin_stat['cx'] > self.dim.pitch_extent[1]
            mask_y_outside1 = bin_stat['cy'] < self.dim.pitch_extent[2]
            mask_y_outside2 = bin_stat['cy'] > self.dim.pitch_extent[3]
            mask_clip = mask_x_outside1 | mask_x_outside2 | mask_y_outside1 | mask_y_outside2
            mask_clip = np.ravel(mask_clip)

            text = np.ravel(bin_stat['statistic'])[~mask_clip]
            cx = np.ravel(bin_stat['cx'])[~mask_clip]
            cy = np.ravel(bin_stat['cy'])[~mask_clip]
            for idx, text_str in enumerate(text):
                annotation = self.annotate(text_str, (cx[idx], cy[idx]),
                                           ax=ax,
                                           **kwargs)
                annotation_list.append(annotation)

        return annotation_list
コード例 #4
0
ファイル: _pitch_plot.py プロジェクト: yumamoto164/mplsoccer
 def arrows(self, xstart, ystart, xend, yend, *args, ax=None, **kwargs):
     validate_ax(ax)
     quiver = arrows(xstart,
                     ystart,
                     xend,
                     yend,
                     *args,
                     ax=ax,
                     vertical=self.vertical,
                     **kwargs)
     return quiver
コード例 #5
0
ファイル: heatmap.py プロジェクト: ElJdP/mplsoccer
def heatmap_positional(stats, ax=None, vertical=False, **kwargs):
    """ Plots several heatmaps for the different Juegos de posición areas.

    Parameters
    ----------
    stats : A list of dictionaries.
        This should be calculated via bin_statistic_positional().
        The dictionary keys are 'statistic' (the calculated statistic),
        'x_grid' and 'y_grid (the bin's edges), and cx and cy (the bin centers).
    ax : matplotlib.axes.Axes, default None
        The axis to plot on.
    vertical : bool, default False
        If the orientation is vertical (True), then the code switches the x and y coordinates.

    **kwargs : All other keyword arguments are passed on to matplotlib.axes.Axes.pcolormesh.

    Returns
    -------
    mesh : matplotlib.collections.QuadMesh

    Examples
    --------
    >>> from mplsoccer import Pitch
    >>> import numpy as np
    >>> pitch = Pitch(line_zorder=2, pitch_color='black')
    >>> fig, ax = pitch.draw()
    >>> x = np.random.uniform(low=0, high=120, size=100)
    >>> y = np.random.uniform(low=0, high=80, size=100)
    >>> stats = pitch.bin_statistic_positional(x, y)
    >>> pitch.heatmap_positional(stats, edgecolors='black', cmap='hot', ax=ax)
    """
    validate_ax(ax)
    # make vmin/vmax nan safe with np.nanmax/ np.nanmin
    vmax = kwargs.pop(
        'vmax', np.nanmax([np.nanmax(stat['statistic']) for stat in stats]))
    vmin = kwargs.pop(
        'vmin', np.nanmin([np.nanmin(stat['statistic']) for stat in stats]))

    mesh_list = []
    for bin_stat in stats:
        mesh = heatmap(bin_stat,
                       vmin=vmin,
                       vmax=vmax,
                       ax=ax,
                       vertical=vertical,
                       **kwargs)
        mesh_list.append(mesh)

    return mesh_list
コード例 #6
0
ファイル: _pitch_plot.py プロジェクト: yumamoto164/mplsoccer
    def goal_angle(self, x, y, ax=None, goal='right', **kwargs):
        """ Plot a polygon with the angle to the goal using PatchCollection.
        See: https://matplotlib.org/stable/api/collections_api.html.
        Valid Collection keyword arguments: edgecolors, facecolors, linewidths, antialiaseds,
        transOffset, norm, cmap

        Parameters
        ----------
        x, y: array-like or scalar.
            Commonly, these parameters are 1D arrays. These should be the coordinates on the pitch.
        goal: str default 'right'.
            The goal to plot, either 'left' or 'right'.
        ax : matplotlib.axes.Axes, default None
            The axis to plot on.
        **kwargs : All other keyword arguments are passed on to
             matplotlib.collections.PathCollection.

        Returns
        -------
        PatchCollection : matplotlib.collections.PatchCollection

        Examples
        --------
        >>> from mplsoccer import Pitch
        >>> pitch = Pitch()
        >>> fig, ax = pitch.draw()
        >>> pitch.goal_angle(100, 30, alpha=0.5, color='red', ax=ax)
        """
        validate_ax(ax)
        valid_goal = ['left', 'right']
        if goal not in valid_goal:
            raise TypeError(
                f'Invalid argument: goal should be in {valid_goal}')
        x = np.ravel(x)
        y = np.ravel(y)
        if x.size != y.size:
            raise ValueError("x and y must be the same size")
        if goal == 'right':
            goal_coordinates = self.goal_right
        else:
            goal_coordinates = self.goal_left
        verts = np.zeros((x.size, 3, 2))
        verts[:, 0, 0] = x
        verts[:, 0, 1] = y
        verts[:, 1:, :] = np.expand_dims(goal_coordinates, 0)
        patch_collection = self.polygon(verts, ax=ax, **kwargs)
        return patch_collection
コード例 #7
0
ファイル: _pitch_plot.py プロジェクト: yumamoto164/mplsoccer
    def polygon(self, verts, ax=None, **kwargs):
        """ Plot polygons using a PatchCollection.
        See: https://matplotlib.org/stable/api/collections_api.html.
        Automatically flips the x and y vertices if the pitch is vertical.

        Valid Collection keyword arguments: edgecolors, facecolors, linewidths, antialiaseds,
        transOffset, norm, cmap

        To use cmap use set_array as per this example:
        https://matplotlib.org/stable/gallery/shapes_and_collections/patch_collection.html.

        Parameters
        ----------
        verts: verts is a sequence of (verts0, verts1, ...)
            where verts_i is a numpy array of shape (number of vertices, 2).
        ax : matplotlib.axes.Axes, default None
            The axis to plot on.
        **kwargs : All other keyword arguments are passed on to
            matplotlib.collections.PatchCollection.

        Returns
        -------
        PatchCollection : matplotlib.collections.PatchCollection

        Examples
        --------
        >>> from mplsoccer import Pitch
        >>> import numpy as np
        >>> pitch = Pitch(label=True, axis=True)
        >>> fig, ax = pitch.draw()
        >>> shape1 = np.array([[50, 2], [80, 30], [40, 30], [40, 20]])
        >>> shape2 = np.array([[70, 70], [60, 50], [40, 40]])
        >>> verts = [shape1, shape2]
        >>> pitch.polygon(verts, color='red', alpha=0.3, ax=ax)
        """
        validate_ax(ax)
        patch_list = []
        for vert in verts:
            vert = np.asarray(vert)
            vert = self._reverse_vertices_if_vertical(vert)
            polygon = patches.Polygon(vert, closed=True)
            patch_list.append(polygon)
        patch_collection = PatchCollection(patch_list, **kwargs)
        patch_collection = ax.add_collection(patch_collection)
        return patch_collection
コード例 #8
0
ファイル: _pitch_plot.py プロジェクト: yumamoto164/mplsoccer
    def kdeplot(self, x, y, ax=None, **kwargs):
        """ Utility wrapper around seaborn.kdeplot,
        which automatically flips the x and y coordinates
        if the pitch is vertical and clips to the pitch boundaries.

        Parameters
        ----------
        x, y : array-like or scalar.
            Commonly, these parameters are 1D arrays.
        ax : matplotlib.axes.Axes, default None
            The axis to plot on.
        **kwargs : All other keyword arguments are passed on to seaborn.kdeplot.

        Returns
        -------
        contour : matplotlib.contour.ContourSet

        Examples
        --------
        >>> from mplsoccer import Pitch
        >>> import numpy as np
        >>> pitch = Pitch(line_zorder=2)
        >>> fig, ax = pitch.draw()
        >>> x = np.random.uniform(low=0, high=120, size=100)
        >>> y = np.random.uniform(low=0, high=80, size=100)
        >>> pitch.kdeplot(x, y, cmap='Reds', shade=True, levels=100, ax=ax)
        """
        validate_ax(ax)

        x = np.ravel(x)
        y = np.ravel(y)
        if x.size != y.size:
            raise ValueError("x and y must be the same size")

        x, y = self._reverse_if_vertical(x, y)

        contour_plot = sns.kdeplot(x=x,
                                   y=y,
                                   ax=ax,
                                   clip=self.kde_clip,
                                   **kwargs)
        return contour_plot
コード例 #9
0
ファイル: heatmap.py プロジェクト: ElJdP/mplsoccer
def heatmap(stats, ax=None, vertical=False, **kwargs):
    """ Utility wrapper around matplotlib.axes.Axes.pcolormesh
    which automatically flips the x_grid and y_grid coordinates if the pitch is vertical.

    See: https://matplotlib.org/stable/api/_as_gen/matplotlib.axes.Axes.pcolormesh.html

    Parameters
    ----------
    stats : dict.
        This should be calculated via bin_statistic().
        The keys are 'statistic' (the calculated statistic),
        'x_grid' and 'y_grid (the bin's edges), and cx and cy (the bin centers).
    ax : matplotlib.axes.Axes, default None
        The axis to plot on.
    vertical : bool, default False
        If the orientation is vertical (True), then the code switches the x and y coordinates.
    **kwargs : All other keyword arguments are passed on to matplotlib.axes.Axes.pcolormesh.

    Returns
    -------
    mesh : matplotlib.collections.QuadMesh

    Examples
    --------
    >>> from mplsoccer import Pitch
    >>> import numpy as np
    >>> pitch = Pitch(line_zorder=2, pitch_color='black')
    >>> fig, ax = pitch.draw()
    >>> x = np.random.uniform(low=0, high=120, size=100)
    >>> y = np.random.uniform(low=0, high=80, size=100)
    >>> stats = pitch.bin_statistic(x, y)
    >>> pitch.heatmap(stats, edgecolors='black', cmap='hot', ax=ax)
    """
    validate_ax(ax)
    if vertical:
        mesh = ax.pcolormesh(stats['y_grid'], stats['x_grid'],
                             stats['statistic'], **kwargs)
    else:
        mesh = ax.pcolormesh(stats['x_grid'], stats['y_grid'],
                             stats['statistic'], **kwargs)

    return mesh
コード例 #10
0
ファイル: _pitch_plot.py プロジェクト: yumamoto164/mplsoccer
    def annotate(self, text, xy, xytext=None, ax=None, **kwargs):
        """ Utility wrapper around ax.annotate
        which automatically flips the xy and xytext coordinates if the pitch is vertical.

        Annotate the point xy with text.
        See: https://matplotlib.org/stable/api/_as_gen/matplotlib.axes.Axes.annotate.html

        Parameters
        ----------
        text : str
            The text of the annotation.
        xy : (float, float)
            The point (x, y) to annotate.
        xytext : (float, float), optional
            The position (x, y) to place the text at. If None, defaults to xy.
        ax : matplotlib.axes.Axes, default None
            The axis to plot on.
        **kwargs : All other keyword arguments are passed on to matplotlib.axes.Axes.annotate.

        Returns
        -------
        annotation : matplotlib.text.Annotation

        Examples
        --------
        >>> from mplsoccer import Pitch
        >>> pitch = Pitch()
        >>> fig, ax = pitch.draw()
        >>> pitch.annotate(text='center', xytext=(50, 50), xy=(60, 40), ha='center', va='center', \
                           ax=ax, arrowprops=dict(facecolor='black'))
        """
        validate_ax(ax)
        xy = self._reverse_annotate_if_vertical(xy)
        if xytext is not None:
            xytext = self._reverse_annotate_if_vertical(xytext)
        return ax.annotate(text, xy, xytext, **kwargs)
コード例 #11
0
ファイル: _pitch_plot.py プロジェクト: yumamoto164/mplsoccer
    def flow(self,
             xstart,
             ystart,
             xend,
             yend,
             bins=(5, 4),
             arrow_type='same',
             arrow_length=5,
             color=None,
             ax=None,
             **kwargs):
        """ Create a flow map by binning the data into cells and calculating the average
        angles and distances.

        Parameters
        ----------
        xstart, ystart, xend, yend: array-like or scalar.
            Commonly, these parameters are 1D arrays.
            These should be the start and end coordinates to calculate the angle between.
        bins : int or [int, int] or array_like or [array, array], optional
            The bin specification for binning the data to calculate the angles/ distances.
              * the number of bins for the two dimensions (nx = ny = bins),
              * the number of bins in each dimension (nx, ny = bins),
              * the bin edges for the two dimensions (x_edge = y_edge = bins),
              * the bin edges in each dimension (x_edge, y_edge = bins).
                If the bin edges are specified, the number of bins will be,
                (nx = len(x_edge)-1, ny = len(y_edge)-1).
        arrow_type : str, default 'same'
            The supported arrow types are: 'same', 'scale', and 'average'.
            'same' makes the arrows the same size (arrow_length).
            'scale' scales the arrow length by the average distance
            in the cell (up to a max of arrow_length).
            'average' makes the arrow size the average distance in the cell.
        arrow_length : float, default 5
            The arrow_length for the flow map. If the arrow_type='same',
            all the arrows will be arrow_length. If the arrow_type='scale',
            the arrows will be scaled by the average distance.
            If the arrow_type='average', the arrows_length is ignored
            This is automatically multipled by 100 if using a 'tracab' pitch
            (i.e. the default is 500).
        color : A matplotlib color, defaults to None.
            Defaults to None. In that case the marker color is
            determined by the cmap (default 'viridis').
            and the counts of the starting positions in each bin.
        ax : matplotlib.axes.Axes, default None
            The axis to plot on.
        **kwargs : All other keyword arguments are passed on to matplotlib.axes.Axes.quiver.

        Returns
        -------
        PolyCollection : matplotlib.quiver.Quiver

        Examples
        --------
        >>> from mplsoccer import Pitch
        >>> from mplsoccer.statsbomb import read_event, EVENT_SLUG
        >>> df = read_event(f'{EVENT_SLUG}/7478.json', related_event_df=False, \
                            shot_freeze_frame_df=False, tactics_lineup_df=False)['event']
        >>> team1, team2 = df.team_name.unique()
        >>> mask_team1 = (df.type_name == 'Pass') & (df.team_name == team1)
        >>> df = df[mask_team1].copy()
        >>> pitch = Pitch(line_zorder=2)
        >>> fig, ax = pitch.draw()
        >>> bs_heatmap = pitch.bin_statistic(df.x, df.y, statistic='count', bins=(6, 4))
        >>> hm = pitch.heatmap(bs_heatmap, ax=ax, cmap='Blues')
        >>> fm = pitch.flow(df.x, df.y, df.end_x, df.end_y, color='black', arrow_type='same', \
                            arrow_length=6, bins=(6, 4), headwidth=2, headlength=2, \
                            headaxislength=2, ax=ax)
        """
        validate_ax(ax)
        if self.dim.aspect != 1:
            standardized = True
            xstart, ystart = self.standardizer.transform(xstart, ystart)
            xend, yend = self.standardizer.transform(xend, yend)
        else:
            standardized = False

        # calculate  the binned statistics
        angle, distance = self.calculate_angle_and_distance(
            xstart, ystart, xend, yend, standardized=standardized)
        bs_distance = self.bin_statistic(xstart,
                                         ystart,
                                         values=distance,
                                         statistic='mean',
                                         bins=bins,
                                         standardized=standardized)
        bs_angle = self.bin_statistic(xstart,
                                      ystart,
                                      values=angle,
                                      statistic=circmean,
                                      bins=bins,
                                      standardized=standardized)

        # calculate the arrow length
        if self.pitch_type == 'tracab':
            arrow_length = arrow_length * 100
        if arrow_type == 'scale':
            new_d = (bs_distance['statistic'] * arrow_length /
                     np.nan_to_num(bs_distance['statistic']).max(initial=None))
        elif arrow_type == 'same':
            new_d = arrow_length
        elif arrow_type == 'average':
            new_d = bs_distance['statistic']
        else:
            valid_arrows = ['scale', 'same', 'average']
            raise TypeError(
                f'Invalid argument: arrow_type should be in {valid_arrows}')

        # calculate the end positions of the arrows
        endx = bs_angle['cx'] + (np.cos(bs_angle['statistic']) * new_d)
        if self.dim.invert_y and standardized is False:
            endy = bs_angle['cy'] - (np.sin(bs_angle['statistic']) * new_d
                                     )  # invert_y
        else:
            endy = bs_angle['cy'] + (np.sin(bs_angle['statistic']) * new_d)

        # get coordinates and convert back to the pitch coordinates if necessary
        cx, cy = bs_angle['cx'], bs_angle['cy']
        if standardized:
            cx, cy = self.standardizer.transform(cx, cy, reverse=True)
            endx, endy = self.standardizer.transform(endx, endy, reverse=True)

        # plot arrows
        if color is None:
            bs_count = self.bin_statistic(xstart,
                                          ystart,
                                          statistic='count',
                                          bins=bins,
                                          standardized=standardized)
            flow = self.arrows(cx,
                               cy,
                               endx,
                               endy,
                               bs_count['statistic'],
                               ax=ax,
                               **kwargs)
        else:
            flow = self.arrows(cx,
                               cy,
                               endx,
                               endy,
                               color=color,
                               ax=ax,
                               **kwargs)

        return flow
コード例 #12
0
ファイル: _pitch_plot.py プロジェクト: yumamoto164/mplsoccer
    def scatter(self,
                x,
                y,
                rotation_degrees=None,
                marker=None,
                ax=None,
                **kwargs):
        """ Utility wrapper around matplotlib.axes.Axes.scatter,
        which automatically flips the x and y coordinates if the pitch is vertical.
        You can optionally use a football marker with marker='football' and rotate markers with
        rotation_degrees.

        Parameters
        ----------
        x, y : array-like or scalar.
            Commonly, these parameters are 1D arrays.
        rotation_degrees: array-like or scalar, default None.
            Rotates the marker in degrees, clockwise. 0 degrees is facing the direction of play.
            In a horizontal pitch, 0 degrees is this way →, in a vertical pitch,
            0 degrees is this way ↑
        marker: MarkerStyle, optional
            The marker style. marker can be either an instance of the class or the
            text shorthand for a particular marker. Defaults to None, in which case it takes
            the value of rcParams["scatter.marker"] (default: 'o') = 'o'.
            If marker='football' plots a football shape with the pentagons the color
            of the edgecolors and hexagons the color of the 'c' argument; 'linewidths'
            also sets the linewidth of the football marker.
        ax : matplotlib.axes.Axes, default None
            The axis to plot on.
        **kwargs : All other keyword arguments are passed on to matplotlib.axes.Axes.scatter.

        Returns
        -------
        paths : matplotlib.collections.PathCollection
                or a tuple of (paths, paths) if marker='football'

        Examples
        --------
        >>> from mplsoccer import Pitch
        >>> pitch = Pitch()
        >>> fig, ax = pitch.draw()
        >>> pitch.scatter(30, 30, ax=ax)

        >>> from mplsoccer import Pitch
        >>> from mplsoccer import arrowhead_marker
        >>> pitch = Pitch()
        >>> fig, ax = pitch.draw()
        >>> pitch.scatter(30, 30, rotation_degrees=45, marker=arrowhead_marker, ax=ax)

        >>> from mplsoccer import Pitch
        >>> pitch = Pitch()
        >>> fig, ax = pitch.draw()
        >>> pitch.scatter(30, 30, marker='football', ax=ax)
        """
        validate_ax(ax)

        x = np.ma.ravel(x)
        y = np.ma.ravel(y)

        if x.size != y.size:
            raise ValueError("x and y must be the same size")

        x, y = self._reverse_if_vertical(x, y)

        if marker is None:
            marker = rcParams['scatter.marker']

        if marker == 'football' and rotation_degrees is not None:
            raise NotImplementedError(
                "rotated football markers are not implemented.")

        if marker == 'football':
            scatter_plot = scatter_football(x, y, ax=ax, **kwargs)
        elif rotation_degrees is not None:
            scatter_plot = scatter_rotation(x,
                                            y,
                                            rotation_degrees,
                                            marker=marker,
                                            vertical=self.vertical,
                                            ax=ax,
                                            **kwargs)
        else:
            scatter_plot = ax.scatter(x, y, marker=marker, **kwargs)
        return scatter_plot
コード例 #13
0
ファイル: _pitch_plot.py プロジェクト: yumamoto164/mplsoccer
    def hexbin(self, x, y, ax=None, **kwargs):
        """ Utility wrapper around matplotlib.axes.Axes.hexbin,
        which automatically flips the x and y coordinates if the pitch is vertical and
        clips to the pitch boundaries.

        Parameters
        ----------
        x, y : array-like or scalar.
            Commonly, these parameters are 1D arrays.
        ax : matplotlib.axes.Axes, default None
            The axis to plot on.
        mincnt : int > 0, default: 1
            If not None, only display cells with more than mincnt number of points in the cell.
        gridsize : int or (int, int), default: (17, 8) for Pitch/ (17, 17) for VerticalPitch
            If a single int, the number of hexagons in the x-direction. The number of hexagons
            in the y-direction is chosen such that the hexagons are approximately regular.
            Alternatively, if a tuple (nx, ny), the number of hexagons in the x-direction
            and the y-direction.
        **kwargs : All other keyword arguments are passed on to matplotlib.axes.Axes.hexbin.

        Returns
        -------
        polycollection : matplotlib.collections.PolyCollection

        Examples
        --------
        >>> from mplsoccer import Pitch
        >>> import numpy as np
        >>> pitch = Pitch(line_zorder=2)
        >>> fig, ax = pitch.draw()
        >>> x = np.random.uniform(low=0, high=120, size=100)
        >>> y = np.random.uniform(low=0, high=80, size=100)
        >>> pitch.hexbin(x, y, edgecolors='black', gridsize=(11, 5), cmap='Reds', ax=ax)
        """
        validate_ax(ax)
        x = np.ravel(x)
        y = np.ravel(y)
        if x.size != y.size:
            raise ValueError("x and y must be the same size")
        # according to seaborn hexbin isn't nan safe so filter out nan
        mask = np.isnan(x) | np.isnan(y)
        x = x[~mask]
        y = y[~mask]

        x, y = self._reverse_if_vertical(x, y)
        mincnt = kwargs.pop('mincnt', 1)
        gridsize = kwargs.pop('gridsize', self.hexbin_gridsize)
        extent = kwargs.pop('extent', self.hex_extent)
        hexbin = ax.hexbin(x,
                           y,
                           mincnt=mincnt,
                           gridsize=gridsize,
                           extent=extent,
                           **kwargs)
        rect = patches.Rectangle(
            (self.visible_pitch[0], self.visible_pitch[2]),
            self.visible_pitch[1] - self.visible_pitch[0],
            self.visible_pitch[3] - self.visible_pitch[2],
            fill=False)
        ax.add_patch(rect)
        hexbin.set_clip_path(rect)
        return hexbin
コード例 #14
0
def lines(xstart,
          ystart,
          xend,
          yend,
          color=None,
          n_segments=100,
          comet=False,
          transparent=False,
          alpha_start=0.01,
          alpha_end=1,
          cmap=None,
          ax=None,
          vertical=False,
          reverse_cmap=False,
          **kwargs):
    """ Plots lines using matplotlib.collections.LineCollection.
    This is a fast way to plot multiple lines without loops.
    Also enables lines that increase in width or opacity by splitting
    the line into n_segments of increasing
    width or opacity as the line progresses.

    Parameters
    ----------
    xstart, ystart, xend, yend: array-like or scalar.
        Commonly, these parameters are 1D arrays.
        These should be the start and end coordinates of the lines.
    color : A matplotlib color or sequence of colors, defaults to None.
        Defaults to None. In that case the marker color is determined
        by the value rcParams['lines.color']
    n_segments : int, default 100
        If comet=True or transparent=True this is used to split the line
        into n_segments of increasing width/opacity.
    comet : bool default False
        Whether to plot the lines increasing in width.
    transparent : bool, default False
        Whether to plot the lines increasing in opacity.
    linewidth or lw : array-like or scalar, default 5.
        Multiple linewidths not supported for the comet or transparent lines.
    alpha_start: float, default 0.01
        The starting alpha value for transparent lines, between 0 (transparent) and 1 (opaque).
        If transparent = True the line will be drawn to
        linearly increase in opacity between alpha_start and alpha_end.
    alpha_end : float, default 1
        The ending alpha value for transparent lines, between 0 (transparent) and 1 (opaque).
        If transparent = True the line will be drawn to
        linearly increase in opacity between alpha_start and alpha_end.
    cmap : str, default None
        A matplotlib cmap (colormap) name
    vertical : bool, default False
        If the orientation is vertical (True), then the code switches the x and y coordinates.
    reverse_cmap : bool, default False
        Whether to reverse the cmap colors.
        If the pitch is horizontal and the y-axis is inverted then set this to True.
    ax : matplotlib.axes.Axes, default None
        The axis to plot on.
    **kwargs : All other keyword arguments are passed on to matplotlib.collections.LineCollection.

    Returns
    -------
    LineCollection : matplotlib.collections.LineCollection

    Examples
    --------
    >>> from mplsoccer import Pitch
    >>> pitch = Pitch()
    >>> fig, ax = pitch.draw()
    >>> pitch.lines(20, 20, 45, 80, comet=True, transparent=True, ax=ax)

    >>> from mplsoccer.linecollection import lines
    >>> import matplotlib.pyplot as plt
    >>> fig, ax = plt.subplots()
    >>> lines([0.1, 0.4], [0.1, 0.5], [0.9, 0.4], [0.8, 0.8], ax=ax)
    """
    validate_ax(ax)
    if not isinstance(comet, bool):
        raise TypeError(
            "Invalid argument: comet should be bool (True or False).")
    if not isinstance(transparent, bool):
        raise TypeError(
            "Invalid argument: transparent should be bool (True or False).")

    if alpha_start < 0 or alpha_start > 1:
        raise TypeError("alpha_start values should be within 0-1 range")
    if alpha_end < 0 or alpha_end > 1:
        raise TypeError("alpha_end values should be within 0-1 range")
    if alpha_start > alpha_end:
        msg = "Alpha start > alpha end. The line will increase in transparency nearer to the end"
        warnings.warn(msg)

    if 'colors' in kwargs.keys():
        warnings.warn(
            "lines method takes 'color' as an argument, 'colors' in ignored")

    if color is not None and cmap is not None:
        raise ValueError("Only use one of color or cmap arguments not both.")

    if 'lw' in kwargs.keys() and 'linewidth' in kwargs.keys():
        raise TypeError(
            "lines got multiple values for 'linewidth' argument (linewidth and lw)."
        )

    # set linewidth
    if 'lw' in kwargs.keys():
        lw = kwargs.pop('lw', 5)
    elif 'linewidth' in kwargs.keys():
        lw = kwargs.pop('linewidth', 5)
    else:
        lw = 5

    # to arrays
    xstart = np.ravel(xstart)
    ystart = np.ravel(ystart)
    xend = np.ravel(xend)
    yend = np.ravel(yend)
    lw = np.ravel(lw)

    if (comet or transparent) and (lw.size > 1):
        msg = "Multiple linewidths with a comet or transparent line is not implemented."
        raise NotImplementedError(msg)

    # set color
    if color is None and cmap is None:
        color = rcParams['lines.color']

    if (comet or transparent) and (cmap is None) and (
            to_rgba_array(color).shape[0] > 1):
        msg = "Multiple colors with a comet or transparent line is not implemented."
        raise NotImplementedError(msg)

    if xstart.size != ystart.size:
        raise ValueError("xstart and ystart must be the same size")
    if xstart.size != xend.size:
        raise ValueError("xstart and xend must be the same size")
    if ystart.size != yend.size:
        raise ValueError("ystart and yend must be the same size")

    if (lw.size > 1) and (lw.size != xstart.size):
        raise ValueError("lw and xstart must be the same size")

    if lw.size == 1:
        lw = lw[0]

    if vertical:
        ystart, xstart = xstart, ystart
        yend, xend = xend, yend

    # create linewidth
    if comet:
        lw = np.linspace(1, lw, n_segments)
        handler_first_lw = False
    else:
        handler_first_lw = True

    if (transparent is False) and (comet is False) and (cmap is None):
        multi_segment = False
    else:
        multi_segment = True

    if transparent:
        cmap = create_transparent_cmap(color, cmap, n_segments, alpha_start,
                                       alpha_end)

    if isinstance(cmap, str):
        cmap = get_cmap(cmap)

    if cmap is not None:
        handler_cmap = True
        line_collection = _lines_cmap(xstart,
                                      ystart,
                                      xend,
                                      yend,
                                      lw=lw,
                                      cmap=cmap,
                                      ax=ax,
                                      n_segments=n_segments,
                                      multi_segment=multi_segment,
                                      reverse_cmap=reverse_cmap,
                                      **kwargs)
    else:
        handler_cmap = False
        line_collection = _lines_no_cmap(xstart,
                                         ystart,
                                         xend,
                                         yend,
                                         lw=lw,
                                         color=color,
                                         ax=ax,
                                         n_segments=n_segments,
                                         multi_segment=multi_segment,
                                         **kwargs)

    line_collection_handler = HandlerLines(numpoints=n_segments,
                                           invert_y=reverse_cmap,
                                           first_lw=handler_first_lw,
                                           use_cmap=handler_cmap)
    Legend.update_default_handler_map(
        {line_collection: line_collection_handler})

    return line_collection
コード例 #15
0
ファイル: quiver.py プロジェクト: yumamoto164/mplsoccer
def arrows(xstart,
           ystart,
           xend,
           yend,
           *args,
           ax=None,
           vertical=False,
           **kwargs):
    """ Utility wrapper around matplotlib.axes.Axes.quiver.
    Quiver uses locations and direction vectors usually.
    Here these are instead calculated automatically
    from the start and endpoints of the arrow.
    The function also automatically flips the x and y coordinates if the pitch is vertical.

    Plot a 2D field of arrows.
    See: https://matplotlib.org/stable/api/_as_gen/matplotlib.axes.Axes.quiver.html

    Parameters
    ----------
    xstart, ystart, xend, yend: array-like or scalar.
        Commonly, these parameters are 1D arrays.
        These should be the start and end coordinates of the lines.
    C: 1D or 2D array-like, optional
        Numeric data that defines the arrow colors by colormapping via norm and cmap.
        This does not support explicit colors.
        If you want to set colors directly, use color instead.
        The size of C must match the number of arrow locations.
    ax : matplotlib.axes.Axes, default None
        The axis to plot on.
    vertical : bool, default False
        If the orientation is vertical (True), then the code switches the x and y coordinates.
    width : float, default 4
        Arrow shaft width in points.
    headwidth : float, default 3
        Head width as a multiple of the arrow shaft width.
    headlength : float, default 5
        Head length as a multiple of the arrow shaft width.
    headaxislength : float, default: 4.5
        Head length at the shaft intersection.
        If this is equal to the headlength then the arrow will be a triangular shape.
        If greater than the headlength then the arrow will be wedge shaped.
        If less than the headlength the arrow will be swept back.
    color : color or color sequence, optional
        Explicit color(s) for the arrows. If C has been set, color has no effect.
    linewidth or linewidths or lw : float or sequence of floats
        Edgewidth of arrow.
    edgecolor or ec or edgecolors : color or sequence of colors or 'face'
    alpha : float or None
        Transparency of arrows.
    **kwargs : All other keyword arguments are passed on to matplotlib.axes.Axes.quiver.

    Returns
    -------
    PolyCollection : matplotlib.quiver.Quiver

    Examples
    --------
    >>> from mplsoccer import Pitch
    >>> pitch = Pitch()
    >>> fig, ax = pitch.draw()
    >>> pitch.arrows(20, 20, 45, 80, ax=ax)

    >>> from mplsoccer.quiver import arrows
    >>> import matplotlib.pyplot as plt
    >>> fig, ax = plt.subplots()
    >>> arrows([0.1, 0.4], [0.1, 0.5], [0.9, 0.4], [0.8, 0.8], ax=ax)
    >>> ax.set_xlim(0, 1)
    >>> ax.set_ylim(0, 1)
    """
    validate_ax(ax)

    # set so plots in data units
    units = kwargs.pop('units', 'inches')
    scale_units = kwargs.pop('scale_units', 'xy')
    angles = kwargs.pop('angles', 'xy')
    scale = kwargs.pop('scale', 1)
    width = kwargs.pop('width', 4)
    # fixed a bug here. I changed the units to inches and divided by 72
    # so the width is in points, i.e. 1/72th of an inch
    width = width / 72.

    xstart = np.ravel(xstart)
    ystart = np.ravel(ystart)
    xend = np.ravel(xend)
    yend = np.ravel(yend)

    if xstart.size != ystart.size:
        raise ValueError("xstart and ystart must be the same size")
    if xstart.size != xend.size:
        raise ValueError("xstart and xend must be the same size")
    if ystart.size != yend.size:
        raise ValueError("ystart and yend must be the same size")

    # vectors for direction
    u = xend - xstart
    v = yend - ystart

    if vertical:
        ystart, xstart = xstart, ystart
        v, u = u, v

    q = ax.quiver(xstart,
                  ystart,
                  u,
                  v,
                  *args,
                  units=units,
                  scale_units=scale_units,
                  angles=angles,
                  scale=scale,
                  width=width,
                  **kwargs)

    quiver_handler = HandlerQuiver()
    Legend.update_default_handler_map({q: quiver_handler})

    return q