From 6d0d85926df4274a031714c9afb3c7cdf730483f Mon Sep 17 00:00:00 2001 From: Andrew <15331990+ahuang11@users.noreply.github.com> Date: Fri, 6 Oct 2023 09:06:48 -0700 Subject: [PATCH] Allow string formatting for labels (#1140) Co-authored-by: Maxime Liquet <35924738+maximlt@users.noreply.github.com> --- examples/reference/pandas/labels.ipynb | 17 +++++++++++++++++ hvplot/converter.py | 9 ++++++++- hvplot/plotting/core.py | 4 +++- hvplot/tests/testcharts.py | 20 ++++++++++++++++++++ 4 files changed, 48 insertions(+), 2 deletions(-) diff --git a/examples/reference/pandas/labels.ipynb b/examples/reference/pandas/labels.ipynb index e6abb6c72..f5614955f 100644 --- a/examples/reference/pandas/labels.ipynb +++ b/examples/reference/pandas/labels.ipynb @@ -42,6 +42,23 @@ "df.hvplot.labels(x='Longitude', y='Latitude', text='City', text_baseline='bottom', hover=False) * \\\n", "df.hvplot.labels(x='Longitude', y='Latitude', text='Country', text_baseline='top', hover=False)" ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "It's also possible to provide a template string containing the names of the columns and reduce the font size." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "df.hvplot.points(x='Longitude', y='Latitude', padding=0.2, hover_cols='all', width=300) * \\\n", + "df.hvplot.labels(x='Longitude', y='Latitude', text='@{City}, {Country}', text_baseline='bottom', text_font_size='10px', hover=False)" + ] } ], "metadata": { diff --git a/hvplot/converter.py b/hvplot/converter.py index 57a672320..bf13c0689 100644 --- a/hvplot/converter.py +++ b/hvplot/converter.py @@ -2086,7 +2086,14 @@ def labels(self, x=None, y=None, data=None): self.use_index = False data, x, y = self._process_chart_args(data, x, y, single_y=True) - text = self.kwds.get('text', [c for c in data.columns if c not in (x, y)][0]) + text = self.kwds.get('text') + if not text: + text = [c for c in data.columns if c not in (x, y)][0] + elif text not in data.columns: + template_str = text # needed for dask lazy compute + data["label"] = data.apply(lambda row: template_str.format(**row), axis=1) + text = "label" + kdims, vdims = self._get_dimensions([x, y], [text]) cur_opts, compat_opts = self._get_compat_opts('Labels') element = self._get_element('labels') diff --git a/hvplot/plotting/core.py b/hvplot/plotting/core.py index 6315ff498..ab08b2bf3 100644 --- a/hvplot/plotting/core.py +++ b/hvplot/plotting/core.py @@ -1771,7 +1771,9 @@ def labels(self, x=None, y=None, text=None, **kwds): y : string, optional The coordinate variable along the y-axis text : string, optional - The column to draw the text labels from + The column to draw the text labels from; it's also possible to + provide a template string containing the column names to + automatically format the text, e.g. "{col1}, {col2}". **kwds : optional Additional keywords arguments are documented in `hvplot.help('labels')`. diff --git a/hvplot/tests/testcharts.py b/hvplot/tests/testcharts.py index 3adf2587c..0247e6a0d 100644 --- a/hvplot/tests/testcharts.py +++ b/hvplot/tests/testcharts.py @@ -3,6 +3,7 @@ from unittest import SkipTest, expectedFailure from parameterized import parameterized +from holoviews.core.dimension import Dimension from holoviews import NdOverlay, Store, dim, render from holoviews.element import Curve, Area, Scatter, Points, Path, HeatMap from holoviews.element.comparison import ComparisonTestCase @@ -128,6 +129,11 @@ def setUp(self): 'time': pd.date_range('1/1/2000', periods=10, tz='UTC'), 'A': [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], 'B': list('abcdefghij')}) + self.edge_df = pd.DataFrame({ + "Latitude": [-34.58, -15.78, -33.45], + "Longitude": [-58.66, -47.91, -70.66], + "Volume {m3}": ["1", "2", "3"], + }) @parameterized.expand([('line', Curve), ('area', Area), ('scatter', Scatter)]) def test_wide_chart(self, kind, element): @@ -347,6 +353,20 @@ def test_errorbars_no_hover(self): bkplot = Store.renderers['bokeh'].get_plot(plot) assert not bkplot.tools + def test_labels_format(self): + plot = self.df.hvplot("x", "y", text="({x}, {y})", kind="labels") + assert list(plot.dimensions()) == [Dimension('x'), Dimension('y'), Dimension('label')] + assert list(plot.data["label"]) == ['(1, 2)', '(3, 4)', '(5, 6)'] + + def test_labels_no_format_edge_case(self): + plot = self.edge_df.hvplot.labels("Longitude", "Latitude") + assert list(plot.dimensions()) == [Dimension('Longitude'), Dimension('Latitude'), Dimension('Volume {m3}')] + assert list(plot.data["Volume {m3}"]) == ['1', '2', '3'] + + def test_labels_format_float(self): + plot = self.edge_df.hvplot.labels("Longitude", "Latitude", text="{Longitude:.1f}E {Latitude:.2f}N") + assert list(plot.dimensions()) == [Dimension('Longitude'), Dimension('Latitude'), Dimension('label')] + assert list(plot.data["label"]) == ['-58.7E -34.58N', '-47.9E -15.78N', '-70.7E -33.45N'] class TestChart1DDask(TestChart1D):