diff --git a/hvplot/fugue.py b/hvplot/fugue.py new file mode 100644 index 000000000..65a18ee69 --- /dev/null +++ b/hvplot/fugue.py @@ -0,0 +1,62 @@ +""" +Experimental support for fugue. +""" +from typing import Any, Dict, Tuple + +import panel as _pn + +from . import hvPlotTabular, post_patch +from .util import _fugue_ipython + +def patch(name="hvplot", extension="bokeh", logo=False): + try: + from fugue import DataFrames, Outputter + from fugue.extensions import namespace_candidate, parse_outputter + except: + raise ImportError( + 'Could not add fugue support as it could not be imported. ' + 'Please make sure you have installed fugue in your environment.' + ) + + import hvplot.pandas # noqa: F401 + + class _Visualize(Outputter): + def __init__(self, func: str) -> None: + super().__init__() + self._func = func + getattr(hvPlotTabular, func) # ensure the func exists + + def process(self, dfs: DataFrames) -> None: + """ + Process the dataframes and output the result as + a pn.Column. + + Parameters: + ----------- + dfs: fugue.DataFrames + """ + charts = [] + for df in dfs.values(): + params = dict(self.params) + opts: Dict[str, Any] = params.pop("opts", {}) + chart = getattr(df.as_pandas().hvplot, self._func)(**params).opts(**opts) + charts.append(chart) + col = _pn.Column(*charts) + try: + if not _fugue_ipython: + get_ipython() + except NameError: + col.show() # in script + else: + from IPython.display import display + display(col) # in notebook + + + @parse_outputter.candidate(namespace_candidate(name, lambda x: isinstance(x, str))) + def _parse_hvplot(obj: Tuple[str, str]) -> Outputter: + return _Visualize(obj[1]) + + post_patch(extension, logo) + + +patch() diff --git a/hvplot/tests/testfugue.py b/hvplot/tests/testfugue.py new file mode 100644 index 000000000..7d4fe014d --- /dev/null +++ b/hvplot/tests/testfugue.py @@ -0,0 +1,46 @@ +"""Fugue test suite""" + +import hvplot +import pandas as pd +import pytest + +# Patch required before importing hvplot.fugue +hvplot.util._fugue_ipython = True + +try: + import fugue.api as fa + import hvplot.fugue # noqa: F401 +except: + pytest.skip(allow_module_level=True) + + +@pytest.fixture +def table(): + df = pd.DataFrame( + { + "g": ["a", "b", "a", "b", "a", "b"], + "x": [1, 2, 3, 4, 5, 6], + "y": [1, 2, 3, 4, 5, 6], + } + ) + return df + + +def test_fugure_ipython_line(table, capsys): + """hvplot works with Fugue""" + fa.fugue_sql( + """ + OUTPUT table USING hvplot:line( + x="x", + y="y", + by="g", + size=100, + opts={"width": 500, "height": 500} + ) + """ + ) + # Check that the output contains the following: + # Column + # [0] HoloViews(NdOverlay) + output = capsys.readouterr().out + assert output == 'Column\n [0] HoloViews(NdOverlay)\n' diff --git a/hvplot/util.py b/hvplot/util.py index 297b7cea7..a862d348e 100644 --- a/hvplot/util.py +++ b/hvplot/util.py @@ -25,7 +25,7 @@ bokeh_version = Version(bokeh.__version__) bokeh3 = bokeh_version >= Version("3.0") param2 = Version(param.__version__) >= Version("2.0rc4") - +_fugue_ipython = None # To be set to True in tests to mock ipython def with_hv_extension(func, extension='bokeh', logo=False): """If hv.extension is not loaded, load before calling function""" diff --git a/setup.py b/setup.py index 63f1d95b1..06cd312ca 100644 --- a/setup.py +++ b/setup.py @@ -62,6 +62,7 @@ def get_setup_version(reponame): 'ipywidgets', 'dask', 'polars', + 'fugue', ] # Dependencies required to run the notebooks