Skip to content

Commit

Permalink
Add hvplot_pts; fixes 332
Browse files Browse the repository at this point in the history
  • Loading branch information
anitagraser committed May 9, 2024
1 parent 4c26c8c commit fbab0e0
Show file tree
Hide file tree
Showing 6 changed files with 218 additions and 21 deletions.
23 changes: 15 additions & 8 deletions movingpandas/tests/test_trajectory.py
Original file line number Diff line number Diff line change
Expand Up @@ -780,30 +780,37 @@ def test_plot_exists(self):
def test_hvplot_exists(self):
import holoviews

plot = self.default_traj_latlon.hvplot(geo=True)
plot = self.default_traj_latlon.hvplot()
assert isinstance(plot, holoviews.core.overlay.Overlay)
assert len(plot.Path.ddims) == 2

plot = self.default_traj_latlon.hvplot(geo=True, color="red")
plot = self.default_traj_latlon.hvplot(color="red")
assert isinstance(plot, holoviews.core.overlay.Overlay)

plot = self.default_traj_latlon.hvplot(geo=True, c="traj_id")
plot = self.default_traj_latlon.hvplot(c="traj_id")
assert isinstance(plot, holoviews.core.overlay.Overlay)

plot = self.default_traj_latlon.hvplot(
geo=True, c="traj_id", colormap={1: "red"}
)
plot = self.default_traj_latlon.hvplot(c="traj_id", colormap={1: "red"})
assert isinstance(plot, holoviews.core.overlay.Overlay)

plot = self.default_traj_latlon.hvplot_pts()
assert isinstance(plot, holoviews.core.overlay.Overlay)

plot = self.default_traj_latlon.hvplot_pts(color="red")
assert isinstance(plot, holoviews.core.overlay.Overlay)

@requires_holoviews
def test_hvplot_with_speed_exists(self):
import holoviews

plot = self.default_traj_latlon.hvplot(geo=True, c="speed")
plot = self.default_traj_latlon.hvplot(c="speed")
assert isinstance(plot, holoviews.core.overlay.Overlay)
assert len(plot.Path.ddims) == 3

plot = self.default_traj_latlon.hvplot(geo=True, c="speed", cmap="Reds")
plot = self.default_traj_latlon.hvplot(c="speed", cmap="Reds")
assert isinstance(plot, holoviews.core.overlay.Overlay)

plot = self.default_traj_latlon.hvplot_pts(c="speed")
assert isinstance(plot, holoviews.core.overlay.Overlay)

@requires_holoviews
Expand Down
12 changes: 12 additions & 0 deletions movingpandas/tests/test_trajectory_collection.py
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,18 @@ def test_hvplot_exists(self):
plot = self.collection_latlon.hvplot(c="id", colormap={1: "red", 2: "blue"})
assert isinstance(plot, holoviews.core.overlay.Overlay)

plot = self.collection_latlon.hvplot_pts()
assert isinstance(plot, holoviews.core.overlay.Overlay)

plot = self.collection_latlon.hvplot_pts(c="id")
assert isinstance(plot, holoviews.core.overlay.Overlay)

plot = self.collection_latlon.hvplot_pts(c="speed")
assert isinstance(plot, holoviews.core.overlay.Overlay)

plot = self.collection_latlon.hvplot_pts(c="id", colormap={1: "red", 2: "blue"})
assert isinstance(plot, holoviews.core.overlay.Overlay)

def test_plot_existing_column(self):
from matplotlib.axes import Axes

Expand Down
22 changes: 22 additions & 0 deletions movingpandas/trajectory.py
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,28 @@ def hvplot(self, *args, **kwargs):
""" # noqa: E501
return _TrajectoryPlotter(self, *args, **kwargs).hvplot()

def hvplot_pts(self, *args, **kwargs):
"""
Generate an interactive plot of trajectory points.
Parameters
----------
args :
These parameters will be passed to the TrajectoryPlotter
kwargs :
These parameters will be passed to the TrajectoryPlotter
To customize the plots, check the list of supported colormaps_.
.. _colormaps: https://holoviews.org/user_guide/Colormaps.html#available-colormaps
Examples
--------
Plot points colored by speed (with legend and specified figure size):
>>> collection.hvplot_pts(c='speed', width=700, height=400, colorbar=True)
""" # noqa: E501
return _TrajectoryPlotter(self, *args, **kwargs).hvplot_pts()

def is_valid(self):
"""
Return whether the trajectory meets minimum requirements.
Expand Down
24 changes: 23 additions & 1 deletion movingpandas/trajectory_collection.py
Original file line number Diff line number Diff line change
Expand Up @@ -805,7 +805,7 @@ def plot(self, *args, **kwargs):

def hvplot(self, *args, **kwargs):
"""
Generate an interactive plot.
Generate an interactive trajectory plot.
Parameters
----------
Expand All @@ -830,6 +830,28 @@ def hvplot(self, *args, **kwargs):
""" # noqa: E501
return _TrajectoryPlotter(self, *args, **kwargs).hvplot()

def hvplot_pts(self, *args, **kwargs):
"""
Generate an interactive plot of trajectory points.
Parameters
----------
args :
These parameters will be passed to the TrajectoryPlotter
kwargs :
These parameters will be passed to the TrajectoryPlotter
To customize the plots, check the list of supported colormaps_.
.. _colormaps: https://holoviews.org/user_guide/Colormaps.html#available-colormaps
Examples
--------
Plot points colored by speed (with legend and specified figure size):
>>> collection.hvplot_pts(c='speed', width=700, height=400, colorbar=True)
""" # noqa: E501
return _TrajectoryPlotter(self, *args, **kwargs).hvplot_pts()


def _get_location_at(traj, t, columns=None):
loc = {
Expand Down
137 changes: 126 additions & 11 deletions movingpandas/trajectory_plotter.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ def __init__(self, data, *args, **kwargs):
self.hvplot_is_geo = kwargs.pop("geo", True)
self.hvplot_tiles = kwargs.pop("tiles", "OSM")

self.plot_lines = kwargs.pop("plot_lines", True)
self.marker_size = kwargs.pop("marker_size", 200)
self.marker_color = kwargs.pop("marker_color", None)
self.line_width = kwargs.pop("line_width", 3.0)
Expand Down Expand Up @@ -55,7 +56,7 @@ def plot(self):
if not self.ax:
self.ax = plt.figure(figsize=self.figsize).add_subplot(1, 1, 1)
tc = self.preprocess_data()
line_plot = self.plot_lines(tc)
line_plot = self._plot_lines(tc)

to_drop = [x for x in tc.get_column_names() if x not in self.column_names]
tc.drop(columns=to_drop)
Expand All @@ -78,7 +79,7 @@ def preprocess_data(self):

return tc

def plot_lines(self, tc):
def _plot_lines(self, tc):
line_gdf = tc.to_line_gdf()

if self.column and self.colormap:
Expand Down Expand Up @@ -119,9 +120,9 @@ def hvplot(self): # noqa F811

tc = self.preprocess_data()

plot = self.hvplot_lines(tc)
plot = self._hvplot_lines(tc)
if self.marker_size > 0:
plot = plot * self.hvplot_end_points(tc)
plot = plot * self._hvplot_end_points(tc)

to_drop = [x for x in tc.get_column_names() if x not in self.column_names]
tc.drop(columns=to_drop)
Expand All @@ -131,12 +132,12 @@ def hvplot(self): # noqa F811
else:
return plot

def hvplot_end_points(self, tc):
def _hvplot_end_points(self, tc):
from holoviews import dim, Overlay

try:
end_pts = tc.get_end_locations(with_direction=True)
except AttributeError:
except AttributeError: # if tc is actually a Trajectory
tc.add_direction(name=self.direction_col_name, overwrite=True)
end_pts = tc.df.tail(1).copy()

Expand Down Expand Up @@ -184,7 +185,7 @@ def hvplot_end_points(self, tc):
plots.append(tmp)
return Overlay(plots)

def hvplot_lines(self, tc):
def _hvplot_lines(self, tc):
cols = [self.traj_id_col_name, self.geom_col_name]
if "hover_cols" in self.kwargs:
cols = cols + self.kwargs["hover_cols"]
Expand All @@ -193,17 +194,17 @@ def hvplot_lines(self, tc):
cols = list(set(cols))

if self.column is None:
return self.hvplot_traj_gdf(tc)
return self._hvplot_traj_gdf(tc)
else:
return self.hvplot_line_gdf(tc, cols)
return self._hvplot_line_gdf(tc, cols)

def get_color(self, i):
if self.color:
return self.color
else:
return self.MPD_PALETTE[i]

def hvplot_traj_gdf(self, tc):
def _hvplot_traj_gdf(self, tc):
from holoviews import Cycle, Overlay

Cycle.default_cycles["default_colors"] = self.MPD_PALETTE
Expand All @@ -228,7 +229,7 @@ def hvplot_traj_gdf(self, tc):
plots.append(tmp)
return Overlay(plots)

def hvplot_line_gdf(self, tc, cols):
def _hvplot_line_gdf(self, tc, cols):
line_gdf = tc.to_line_gdf(columns=cols)

ids = None
Expand All @@ -249,6 +250,120 @@ def hvplot_line_gdf(self, tc, cols):
**self.kwargs
)

def hvplot_pts(self):
try:
import hvplot.pandas # noqa F401, seems necessary for the following import to work
import colorcet as cc
from holoviews import opts, dim, Overlay
from bokeh.palettes import Category10_10
except ImportError as error:
raise ImportError(
"Missing optional dependencies. To use interactive plotting, "
"install hvplot and GeoViews (see "
"https://hvplot.holoviz.org/getting_started/installation.html and "
"https://geoviews.org)."
) from error

opts.defaults(opts.Overlay(**self.hv_defaults))
self.MPD_PALETTE = list(Category10_10) + cc.palette["glasbey"]
self.color = self.kwargs.pop("color", None)

try:
tc = self.data.copy()
if self.direction_col_name not in tc.trajectories[0].df.columns:
tc.add_direction(name=self.direction_col_name)
if self.column:
if self.column == self.speed_col_name and self.speed_col_missing:
tc.add_speed()
pts_gdf = tc.to_point_gdf()
except AttributeError:
traj = self.data.copy()
if self.direction_col_name not in traj.df.columns:
traj.add_direction(name=self.direction_col_name)
if self.column:
if self.column == self.speed_col_name and self.speed_col_missing:
tc.add_speed()
pts_gdf = traj.df

ids = None
if self.column is None and self.traj_id_col_name is not None:
ids = pts_gdf[self.traj_id_col_name].unique()
self.set_default_cmaps(ids)

pts_gdf["triangle_angle"] = pts_gdf[self.direction_col_name] * -1.0
pts_gdf["triangle_angle"] = pts_gdf["triangle_angle"].astype(float)
pts_gdf["dash_angle"] = ((pts_gdf[self.direction_col_name] * -1.0) + 90).astype(
float
)

hover_cols = self.kwargs.pop("hover_cols", None)

self.kwargs["hover_cols"] = ["triangle_angle", "dash_angle"]
if hover_cols:
self.kwargs["hover_cols"] = self.kwargs["hover_cols"] + hover_cols
if self.marker_color:
self.kwargs["color"] = self.marker_color
if self.column:
self.kwargs["hover_cols"] = self.kwargs["hover_cols"] + [self.column]

if (
self.hvplot_is_geo
and not self.data.is_latlon
and self.data.get_crs() is not None
):
pts_gdf = pts_gdf.to_crs(epsg=4326)

if self.column:
arrow_shaft = pts_gdf.hvplot(
geo=self.hvplot_is_geo,
tiles=None,
marker="dash",
angle=dim("dash_angle"),
size=self.marker_size * 1.7,
line_width=self.marker_size / 70.0,
clim=self.clim,
*self.args,
**self.kwargs
)
arrow_head = pts_gdf.hvplot(
geo=self.hvplot_is_geo,
tiles=None,
marker="triangle",
angle=dim("triangle_angle"),
size=self.marker_size,
clim=self.clim,
*self.args,
**self.kwargs
)
return arrow_shaft * arrow_head
else:
plots = []
for i in range(len(ids)):
tmp = pts_gdf[pts_gdf[self.traj_id_col_name] == ids[i]]
arrow_shaft = tmp.hvplot(
geo=self.hvplot_is_geo,
tiles=None,
marker="dash",
angle=dim("dash_angle"),
size=self.marker_size * 1.7,
line_width=self.marker_size / 70.0,
color=self.get_color(i),
*self.args,
**self.kwargs
)
arrow_head = tmp.hvplot(
geo=self.hvplot_is_geo,
tiles=None,
marker="triangle",
angle=dim("triangle_angle"),
size=self.marker_size,
color=self.get_color(i),
*self.args,
**self.kwargs
)
plots.append(arrow_shaft * arrow_head)
return Overlay(plots)

def set_default_cmaps(self, ids=None):
from holoviews import Cycle

Expand Down
21 changes: 20 additions & 1 deletion tutorials/0-debug.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,15 @@
"toy_traj.hvplot(c='speed', line_width=7, **hv_defaults)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"toy_traj.hvplot_pts(c='speed', line_width=7, **hv_defaults)"
]
},
{
"cell_type": "code",
"execution_count": null,
Expand Down Expand Up @@ -214,7 +223,17 @@
"metadata": {},
"outputs": [],
"source": [
"intersections.trajectories[0].df"
"#intersections.add_speed()\n",
"intersections.hvplot_pts(tiles=None, c='speed', clim=(0,20), **hv_defaults)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"intersections.hvplot_pts(tiles=None, **hv_defaults)"
]
},
{
Expand Down

0 comments on commit fbab0e0

Please sign in to comment.