Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

pass sonars - checking difference #104

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions examples/pitch_plots/plot_formations.py
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,8 @@
# Pitch of pitches
# ----------------
# You can plot the formations as pitches using the ``kind='pitch'`` argument.
# I believe `UtdArena <https://twitter.com/UtdArena>`_ was the first
# person to introduce this type of visualization.
# Additional keyword arguments amend the inset pitch's appearance, e.g. ``line_color``.
#
# In this example, it is the first game that Messi played as a false-nine.
Expand Down
5 changes: 5 additions & 0 deletions mplsoccer/_pitch_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -1555,6 +1555,11 @@ def bin_statistic(self, x, y, values=None, statistic='count', bins=(5, 4),
normalize=False, standardized=False):
""" Calculate 2d binned statistics for arbritary shaped bins."""

@abstractmethod
def bin_statistic_sonar(self, x, y, angle, values=None, statistic='count', bins=(5, 4, 10),
normalize=False, standardized=False, center=True):
""" Calculate binned statistics sonars for arbritary shaped bins."""

@abstractmethod
def heatmap(self, stats, ax=None, **kwargs):
""" Implement drawing heatmaps for arbritary shaped bins."""
Expand Down
55 changes: 30 additions & 25 deletions mplsoccer/_pitch_plot.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,13 @@
from scipy.stats import circmean

from mplsoccer._pitch_base import BasePitch
from mplsoccer.heatmap import bin_statistic, bin_statistic_positional, heatmap, heatmap_positional
from mplsoccer.heatmap import bin_statistic, bin_statistic_positional, heatmap, \
heatmap_positional, bin_statistic_sonar
from mplsoccer.linecollection import lines
from mplsoccer.quiver import arrows
from mplsoccer.scatterutils import scatter_football, scatter_rotation
from mplsoccer.utils import validate_ax

_BinnedStatisticResult = namedtuple('BinnedStatisticResult',
('statistic', 'x_grid', 'y_grid', 'cx', 'cy'))


class BasePitchPlot(BasePitch):

Expand Down Expand Up @@ -382,6 +380,12 @@ def bin_statistic(self, x, y, values=None, statistic='count', bins=(5, 4),
return bin_statistic(x, y, values=values, dim=self.dim, statistic=statistic,
bins=bins, normalize=normalize, standardized=standardized)

@docstring.copy(bin_statistic_sonar)
def bin_statistic_sonar(self, x, y, angle, values=None, statistic='count', bins=(5, 4, 10),
normalize=False, standardized=False, center=True):
return bin_statistic_sonar(x, y, angle, values=values, dim=self.dim, statistic=statistic, bins=bins,
normalize=normalize, standardized=standardized, center=center)

@docstring.copy(heatmap)
def heatmap(self, stats, ax=None, **kwargs):
return heatmap(stats, ax=ax, vertical=self.vertical, **kwargs)
Expand Down Expand Up @@ -414,11 +418,11 @@ def label_heatmap(self, stats, str_format=None, exclude_zeros=False,
ax : matplotlib.axes.Axes, default None
The axis to plot on.

**kwargs : All other keyword arguments are passed on to matplotlib.axes.Axes.annotate.
**kwargs : All other keyword arguments are passed on to matplotlib.text.Text.

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

Examples
--------
Expand All @@ -431,35 +435,36 @@ def label_heatmap(self, stats, str_format=None, exclude_zeros=False,
>>> 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)
... va='center', path_effects=path_eff, str_format='{:.0f}')
"""
validate_ax(ax)
va = kwargs.pop('va', 'center')
ha = kwargs.pop('ha', 'center')

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_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
if exclude_zeros:
mask_clip = mask_clip | (np.isclose(bin_stat['statistic'], 0.))
mask_clip = mask_clip | (np.isclose(bin_stat.statistic, 0.))
mask_clip = np.ravel(mask_clip)

text = np.ravel(bin_stat['statistic'])[~mask_clip]
cx = np.ravel(bin_stat['cx'])[~mask_clip] + xoffset
cy = np.ravel(bin_stat['cy'])[~mask_clip] + yoffset
text = np.ravel(bin_stat.statistic)[~mask_clip]
cx = np.ravel(bin_stat.cx)[~mask_clip] + xoffset
cy = np.ravel(bin_stat.cy)[~mask_clip] + yoffset
for idx, text_str in enumerate(text):
if str_format is not None:
text_str = str_format.format(text_str)
annotation = self.annotate(text_str, (cx[idx], cy[idx]), ax=ax, **kwargs)
annotation = self.text(cx[idx], cy[idx], text_str, va=va, ha=ha, ax=ax, **kwargs)
annotation_list.append(annotation)

return annotation_list
Expand Down Expand Up @@ -748,25 +753,25 @@ def flow(self, xstart, ystart, xend, yend, bins=(5, 4), arrow_type='same', arrow
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))
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']
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)
endx = bs_angle.cx + (np.cos(bs_angle.statistic) * new_d)
if self.dim.invert_y and not standardized:
endy = bs_angle['cy'] - (np.sin(bs_angle['statistic']) * new_d) # invert_y
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)
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']
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)
Expand All @@ -776,7 +781,7 @@ def flow(self, xstart, ystart, xend, yend, bins=(5, 4), arrow_type='same', arrow
return self.arrows(cx, cy, endx, endy, color=color, ax=ax, **kwargs)
bs_count = self.bin_statistic(xstart, ystart, statistic='count',
bins=bins, standardized=standardized)
return self.arrows(cx, cy, endx, endy, bs_count['statistic'], ax=ax, **kwargs)
return self.arrows(cx, cy, endx, endy, bs_count.statistic, ax=ax, **kwargs)

def triplot(self, x, y, ax=None, **kwargs):
""" Utility wrapper around matplotlib.axes.Axes.triplot
Expand Down
Loading