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

Allow Bars to be plotted on continuous axes #6145

Merged
merged 26 commits into from
May 17, 2024
Merged

Allow Bars to be plotted on continuous axes #6145

merged 26 commits into from
May 17, 2024

Conversation

philippjfr
Copy link
Member

About 10 years late, but better than never.

hv.Bars(range(1, 10))

bokeh_plot - 2024-03-07T004024 188

df.time.dt.date.value_counts().sort_index().iloc[:30].hvplot.bar(title='Daily Earthquakes')

bokeh_plot - 2024-03-07T004230 940

@MarcSkovMadsen
Copy link
Collaborator

MarcSkovMadsen commented Mar 9, 2024

I've tested it out.

  • bokeh backend: width of bars should be fixed. Right now they are dynamic. Zoom, Hover, Layouts etc. seem to work. Nice.
  • matplotlib backend: not showing/ working
  • plotly backend: not showing/ working

Getting Bokeh backend working is a big improvement for me and what I will be using most of the time. Getting Plotly backend working would be really nice and I see many users in business preferring that backend.

test-bars-with-axis-series-ezgif.com-video-speed.mp4
Code
import holoviews as hv
import panel as pn
import pandas as pd
import pandas as pd
import numpy as np
from datetime import date
import param
import math
import hvplot.pandas

pn.extension("plotly", sizing_mode="stretch_width")


min_price = 90
max_price = 110

ACCENT = "#3d5a80"
POS = "#98C1D9"
NEG = "#ee6c4d"

PERIODS = {
    "MS": (date(2022, 1, 1), date(2024, 12, 1)),
    "ME": (date(2022, 1, 1), date(2024, 12, 1)),
    "D": (date(2022, 1, 1), date(2022, 7, 1)),
    "B": (date(2022, 1, 1), date(2022, 7, 1)),
    "h": (date(2022, 1, 1), date(2022, 1, 8)),
}
OPTIONS={
    "bokeh": {
        "line_width": 5
    },
    "matplotlib": {
        "linewidth": 5
    },
    "plotly": {
        "line_width": 5
    }
}
def _get_label(text: str):
    return pn.pane.Markdown(text, margin=(-20, 5, -20, 5))

class PricePlotter(pn.viewable.Viewer):
    frequency = param.Selector(default="B", objects=list(PERIODS))
    start_date = param.CalendarDate(date(2022, 1, 1))
    end_date = param.CalendarDate(date(2022, 4, 1))
    lib = param.Selector(default="hvPlot", objects=["hvPlot", "HoloViews"])
    backend = param.Selector(default="bokeh", objects=["bokeh", "matplotlib", "plotly"])

    def __init__(self, **params):
        super().__init__(**params)

        self.settings = pn.Column(
            _get_label("Frequency"),
            pn.widgets.RadioButtonGroup.from_param(
                self.param.frequency, button_type="primary", button_style="outline"
            ),
            self.param.start_date,
            self.param.end_date,
            _get_label("Library"),
            pn.widgets.RadioButtonGroup.from_param(
                self.param.lib, button_type="primary", button_style="outline"
            ),
            _get_label("Backend"),
            pn.widgets.RadioButtonGroup.from_param(
                self.param.backend, button_type="primary", button_style="outline"
            )
        )
        self._layout = pn.Row(self.settings, self.get_plot)

    def __panel__(self):
        return self._layout

    @param.depends("frequency", watch=True, on_init=True)
    def _update_period(self):
        start, end = PERIODS[self.frequency]
        self.param.update(start_date=start, end_date=end)

    @param.depends("backend", watch=True, on_init=True)
    def _update_backend(self):
        hv.extension(self.backend)

    def get_prices(self):
        dates = pd.date_range(
            start=self.start_date, end=self.end_date, freq=self.frequency
        )
        num_values = len(dates)
        values = np.random.uniform(min_price, max_price, size=num_values)
        df = pd.DataFrame({"date": dates, "price": values})
        df["diff"] = df["price"].diff()
        df["color"] = (df["diff"] >= 0).map({True: "blue", False: "red"})
        df = df.dropna()
        return df

    def get_plot(self):

        df = self.get_prices()
        df_pos = df[df["diff"] >= 0]
        df_neg = df[df["diff"] <= 0]

        max_price = df["price"].max()
        max_y = math.ceil(max_price / 10) * 10 + 10
        options=OPTIONS[self.backend]
        if self.lib == "HoloViews":
            abs_plot = hv.Curve(df, kdims="date", vdims="price").opts(
                responsive=True,
                height=500,
                color=ACCENT,
                ylim=(0, max_y),
                tools=["hover"],
                **options
            )
            rel_plot_pos = hv.Bars(
                df_pos,
                kdims="date",
                vdims="diff",
            ).opts(responsive=True, height=200, color=POS, tools=["hover"])
            rel_plot_neg = hv.Bars(
                df_neg,
                kdims="date",
                vdims="diff",
            ).opts(responsive=True, height=200, color=NEG, tools=["hover"])
        else:
            abs_plot = df.hvplot.line(
                x="date",
                y="price",
                responsive=True,
                height=500,
                color=ACCENT,
                ylim=(0, max_y),
                tools=["hover"],
                xlabel="Date",
                ylabel="Price",
                **options
            )
            rel_plot_pos = df_pos.hvplot.bar(
                x="date",
                y="diff",
                responsive=True,
                height=200,
                color=POS,
                tools=["hover"],
                xlabel="Date",
                ylabel="Diff",
            )
            rel_plot_neg = df_neg.hvplot.bar(
                x="date",
                y="diff",
                responsive=True,
                height=200,
                color=NEG,
                tools=["hover"],
                xlabel="Date",
                ylabel="Diff",
            )
        return (abs_plot + rel_plot_pos * rel_plot_neg).cols(1)


plotter = PricePlotter()

pn.template.FastListTemplate(
    title="Bars Test App", sidebar=[plotter.settings], main=[plotter.get_plot], main_layout=None, accent=ACCENT
).servable()

@MarcSkovMadsen
Copy link
Collaborator

MarcSkovMadsen commented Mar 20, 2024

Matplotlib Analysis

This is how Matplotlib does it.

App

matplotlib-bars.mp4
Code
import panel as pn
import pandas as pd
import pandas as pd
import numpy as np
from datetime import date
import param
import matplotlib.pyplot as plt

pn.extension("plotly", sizing_mode="stretch_width")


min_price = 90
max_price = 110

ACCENT = "#3d5a80"
POS = "#98C1D9"
NEG = "#ee6c4d"

PERIODS = {
    "MS": (date(2022, 1, 1), date(2024, 12, 1)),
    "ME": (date(2022, 1, 1), date(2024, 12, 1)),
    "D": (date(2022, 1, 1), date(2022, 7, 1)),
    "B": (date(2022, 1, 1), date(2022, 7, 1)),
    "h": (date(2022, 1, 1), date(2022, 1, 8)),
}
OPTIONS={
    "bokeh": {
        "line_width": 5
    },
    "matplotlib": {
        "linewidth": 5
    },
    "plotly": {
        "line_width": 5
    }
}
def _get_label(text: str):
    return pn.pane.Markdown(text, margin=(-20, 5, -20, 5))

class PricePlotter(pn.viewable.Viewer):
    frequency = param.Selector(default="B", objects=list(PERIODS))
    start_date = param.CalendarDate(date(2022, 1, 1))
    end_date = param.CalendarDate(date(2022, 4, 1))
    
    def __init__(self, **params):
        super().__init__(**params)

        self.settings = pn.Column(
            _get_label("Frequency"),
            pn.widgets.RadioButtonGroup.from_param(
                self.param.frequency, button_type="primary", button_style="outline"
            ),
            self.param.start_date,
            self.param.end_date,
        )
        self._layout = pn.Row(self.settings, self.get_plot)

    def __panel__(self):
        return self._layout

    @param.depends("frequency", watch=True, on_init=True)
    def _update_period(self):
        start, end = PERIODS[self.frequency]
        self.param.update(start_date=start, end_date=end)

    def get_prices(self):
        dates = pd.date_range(
            start=self.start_date, end=self.end_date, freq=self.frequency
        )
        num_values = len(dates)
        values = np.random.uniform(min_price, max_price, size=num_values)
        df = pd.DataFrame({"date": dates, "price": values})
        df["diff"] = df["price"].diff()
        df["color"] = (df["diff"] >= 0).map({True: "blue", False: "red"})
        df = df.dropna()
        return df

    def get_plot(self):

        df = self.get_prices()
        df_pos = df[df["diff"] >= 0]
        df_neg = df[df["diff"] <= 0]

        max_price = df["price"].max()

        fig1 = plt.figure(figsize=(10, 3))
        plt.plot(df["date"], df["price"], color=ACCENT, label="Price")
        plt.xlabel("Date")
        plt.ylabel("Price")
        plt.title("Price Over Time")
        plt.legend()
        plt.close(fig1)

        # Plotting the bar plots for positive and negative differences
        fig2 = plt.figure(figsize=(10, 3))

        # Positive differences
        plt.bar(df_pos["date"], df_pos["diff"], color=POS, label="Positive Diff")
        # Negative differences
        plt.bar(df_neg["date"], df_neg["diff"], color=NEG, label="Negative Diff")

        plt.xlabel("Date")
        plt.ylabel("Diff")
        plt.title("Relative Differences Over Time")
        plt.legend()
        plt.close(fig2) # CLOSE THE FIGURE!
        return pn.Column(
            pn.pane.Matplotlib(fig1, tight=True), 
            pn.pane.Matplotlib(fig2, tight=True),
        )


plotter = PricePlotter()

pn.template.FastListTemplate(
    title="Bars Test App", sidebar=[plotter.settings], main=[plotter.get_plot], main_layout=None, accent=ACCENT
).servable()

Matplotlib Bars Use a Fixed Default Width

Matplotlib uses a fixed, default width of 0.8.

image

See https://github.com/matplotlib/matplotlib/blob/df148eea635d3c6855565681f3275db6dd2be436/lib/matplotlib/axes/_axes.py#L2326

@MarcSkovMadsen
Copy link
Collaborator

MarcSkovMadsen commented Mar 20, 2024

Pandas .plot analysis

This is how Pandas .plot does it.

App

pandas-plot-bar-ezgif.com-video-speed.mp4
Code
import panel as pn
import pandas as pd
import pandas as pd
import numpy as np
from datetime import date
import param
import matplotlib.pyplot as plt

pn.extension("plotly", sizing_mode="stretch_width")


min_price = 90
max_price = 110

ACCENT = "#3d5a80"
POS = "#98C1D9"
NEG = "#ee6c4d"

PERIODS = {
    "MS": (date(2022, 1, 1), date(2024, 12, 1)),
    "ME": (date(2022, 1, 1), date(2024, 12, 1)),
    "D": (date(2022, 1, 1), date(2022, 7, 1)),
    "B": (date(2022, 1, 1), date(2022, 7, 1)),
    "h": (date(2022, 1, 1), date(2022, 1, 8)),
}
OPTIONS={
    "bokeh": {
        "line_width": 5
    },
    "matplotlib": {
        "linewidth": 5
    },
    "plotly": {
        "line_width": 5
    }
}
def _get_label(text: str):
    return pn.pane.Markdown(text, margin=(-20, 5, -20, 5))

class PricePlotter(pn.viewable.Viewer):
    frequency = param.Selector(default="B", objects=list(PERIODS))
    start_date = param.CalendarDate(date(2022, 1, 1))
    end_date = param.CalendarDate(date(2022, 4, 1))
    
    def __init__(self, **params):
        super().__init__(**params)

        self.settings = pn.Column(
            _get_label("Frequency"),
            pn.widgets.RadioButtonGroup.from_param(
                self.param.frequency, button_type="primary", button_style="outline"
            ),
            self.param.start_date,
            self.param.end_date,
        )
        self._layout = pn.Row(self.settings, self.get_plot)

    def __panel__(self):
        return self._layout

    @param.depends("frequency", watch=True, on_init=True)
    def _update_period(self):
        start, end = PERIODS[self.frequency]
        self.param.update(start_date=start, end_date=end)

    def get_prices(self):
        dates = pd.date_range(
            start=self.start_date, end=self.end_date, freq=self.frequency
        )
        num_values = len(dates)
        values = np.random.uniform(min_price, max_price, size=num_values)
        df = pd.DataFrame({"date": dates, "price": values})
        df["diff"] = df["price"].diff()
        df["color"] = (df["diff"] >= 0).map({True: "blue", False: "red"})
        df = df.dropna()
        return df

    def get_plot(self):

        df = self.get_prices()
        df_pos = df[df["diff"] >= 0]
        df_neg = df[df["diff"] <= 0]

        max_price = df["price"].max()

        fig1 = plt.figure(figsize=(10, 3))
        plt.plot(df["date"], df["price"], color=ACCENT, label="Price")
        plt.xlabel("Date")
        plt.ylabel("Price")
        plt.title("Price Over Time")
        plt.legend()
        plt.close(fig1)

        # Plotting the bar plots for positive and negative differences
        df_pos['date'] = pd.to_datetime(df_pos['date'])
        df_neg['date'] = pd.to_datetime(df_neg['date'])

        # Set 'date' column as index for easy plotting
        df_pos.set_index('date', inplace=True)
        df_neg.set_index('date', inplace=True)

        # Concatenate positive and negative differences into a single DataFrame
        df_combined = pd.concat([df_pos['diff'], df_neg['diff']], axis=1)
        df_combined.columns = ['Positive Diff', 'Negative Diff']

        # Plot using pandas plot
        fig2 = plt.figure(figsize=(10, 3))
        ax = fig2.add_subplot(111)
        ax = df_combined.plot.bar(figsize=(10, 3), color=[POS, NEG], ax=ax, xlabel="Date", ylabel="Diff", title="Relative Differences Over Time")
        ax.legend(["Positive Diff", "Negative Diff"])
        plt.close(fig2) # CLOSE THE FIGURE!
        return pn.Column(
            pn.pane.Matplotlib(fig1, tight=True), 
            pn.pane.Matplotlib(fig2, tight=True),
        )


plotter = PricePlotter()

pn.template.FastListTemplate(
    title="Bars Test App", sidebar=[plotter.settings], main=[plotter.get_plot], main_layout=None, accent=ACCENT
).servable()

Implementation

The implemention is here. It looks to me as a default, fixed width of 0.5 is used ?? In practice I don't think that is what I see??

https://github.com/pandas-dev/pandas/blob/114a84d8a0eee8fb93a4d2d701a2a6e62ebcf6d2/pandas/plotting/_matplotlib/core.py#L1803

@MarcSkovMadsen
Copy link
Collaborator

MarcSkovMadsen commented Mar 20, 2024

Plotly Analysis

Plotly adjust the bar width based on the input. Clearly the best behaviour.

App

plotly-plot.mp4
Code
import panel as pn
import pandas as pd
import pandas as pd
import numpy as np
from datetime import date
import param
import plotly.express as px
from plotly.subplots import make_subplots

pn.extension("plotly", sizing_mode="stretch_width")


min_price = 90
max_price = 110

ACCENT = "#3d5a80"
POS = "#98C1D9"
NEG = "#ee6c4d"

PERIODS = {
    "MS": (date(2022, 1, 1), date(2024, 12, 1)),
    "ME": (date(2022, 1, 1), date(2024, 12, 1)),
    "D": (date(2022, 1, 1), date(2022, 7, 1)),
    "B": (date(2022, 1, 1), date(2022, 7, 1)),
    "h": (date(2022, 1, 1), date(2022, 1, 8)),
}
OPTIONS={
    "bokeh": {
        "line_width": 5
    },
    "matplotlib": {
        "linewidth": 5
    },
    "plotly": {
        "line_width": 5
    }
}
def _get_label(text: str):
    return pn.pane.Markdown(text, margin=(-20, 5, -20, 5))

class PricePlotter(pn.viewable.Viewer):
    frequency = param.Selector(default="B", objects=list(PERIODS))
    start_date = param.CalendarDate(date(2022, 1, 1))
    end_date = param.CalendarDate(date(2022, 4, 1))
    
    def __init__(self, **params):
        super().__init__(**params)

        self.settings = pn.Column(
            _get_label("Frequency"),
            pn.widgets.RadioButtonGroup.from_param(
                self.param.frequency, button_type="primary", button_style="outline"
            ),
            self.param.start_date,
            self.param.end_date,
        )
        self._layout = pn.Row(self.settings, self.get_plot)

    def __panel__(self):
        return self._layout

    @param.depends("frequency", watch=True, on_init=True)
    def _update_period(self):
        start, end = PERIODS[self.frequency]
        self.param.update(start_date=start, end_date=end)

    def get_prices(self):
        dates = pd.date_range(
            start=self.start_date, end=self.end_date, freq=self.frequency
        )
        num_values = len(dates)
        values = np.random.uniform(min_price, max_price, size=num_values)
        df = pd.DataFrame({"date": dates, "price": values})
        df["diff"] = df["price"].diff()
        df["color"] = (df["diff"] >= 0).map({True: "pos", False: "neg"})
        df = df.dropna()
        return df

    def get_plot(self):

        df = self.get_prices()
        abs_plot = px.line(
            df,
            x="date",
            y="price",
            height=400,
            color_discrete_sequence=[ACCENT],
            labels={"date": "Date", "price": "Price"},
            title="Absolute Price Over Time",
        )

        rel_plot_pos = px.bar(
            df,
            x="date",
            y="diff",
            height=400,
            color="color",
            color_discrete_sequence=[POS, NEG],
            labels={"date": "Date", "diff": "Diff"},
            title="Positive Relative Differences Over Time",
        )

        fig = make_subplots(rows=3, cols=1, shared_xaxes=True, vertical_spacing=0.03)
        # Add plots to subplots
        fig.add_trace(abs_plot.data[0], row=1, col=1)
        fig.add_trace(rel_plot_pos.data[0], row=2, col=1)
        fig.add_trace(rel_plot_pos.data[1], row=2, col=1)
        
        # Update layout
        fig.update_layout(
            height=800,
            title_text="Combined Plots",
            showlegend=False  # Assuming you don't want individual legends
        )
        return fig


plotter = PricePlotter()

pn.template.FastListTemplate(
    title="Bars Test App", sidebar=[plotter.settings], main=[plotter.get_plot], main_layout=None, accent=ACCENT
).servable()

Implementation Details

I've not been able to find the calculation of the bar width. I believe its done on Javascript side

@hoxbro hoxbro reopened this Mar 20, 2024
@hoxbro
Copy link
Member

hoxbro commented Mar 20, 2024

I assume it was a mistake you closed this?

@MarcSkovMadsen
Copy link
Collaborator

MarcSkovMadsen commented Mar 20, 2024

Reflection on calculation of bar width.

Could the bar width be calculated by

  • sorting the timeseries
  • Calculating the length of all steps
  • Taking the minimum of all steps.
  • Adjusting with a factor 0.8

?

@MarcSkovMadsen
Copy link
Collaborator

MarcSkovMadsen commented Mar 20, 2024

Test of Bar Width formula

If you change the bar width calculation from

xdiff = np.diff(xvals).astype('timedelta64[ms]').astype(np.int32) * width * 0.5

to

xdiff = np.diff(xvals).astype('timedelta64[ms]').astype(np.int32) * width * 0.5
min_value = np.min(xdiff)
xdiff = np.full_like(xdiff, min_value)

It looks like below which looks correct to me.

bar-width-formula.mp4

I would recommend experimenting with the width factor. Probably using 0.8 instead of 0.5.

@TheoMathurin
Copy link
Contributor

This is a very welcome addition!

@droumis
Copy link
Member

droumis commented Mar 27, 2024

Similarly, it would be great to be able to use distribution models like BoxWhisker and Violin on a datetime or other continuous x axis. For instance, if one has a distribution of continuous values grouped per day

Copy link

codecov bot commented Apr 22, 2024

Codecov Report

Attention: Patch coverage is 99.15612% with 2 lines in your changes are missing coverage. Please review.

Project coverage is 88.38%. Comparing base (b1fe1cf) to head (d1a9ca7).
Report is 10 commits behind head on main.

Files Patch % Lines
holoviews/plotting/mpl/chart.py 94.44% 2 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #6145      +/-   ##
==========================================
+ Coverage   88.22%   88.38%   +0.16%     
==========================================
  Files         321      322       +1     
  Lines       67285    67559     +274     
==========================================
+ Hits        59361    59715     +354     
+ Misses       7924     7844      -80     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@ahuang11
Copy link
Collaborator

Okay, MPL partially fixed

import holoviews as hv
import pandas as pd
import matplotlib.pyplot as plt
# hv.extension("matplotlib")

df = pd.DataFrame({"x": [1, 2, 3, 4, 5], "y": [10, 20, 30, 40, 50]})

fig = plt.figure()
ax = fig.add_subplot(111)
ax.bar(df["x"], df["y"])

hv.extension()

display(hv.Bars(df, ["x"], ["y"]).opts(bar_padding=0.1, align="center"))
hv.Bars(df, ["x"], ["y"]).opts(bar_padding=0.1, align="edge")
image

Reference:
image

Categories:

import numpy as np
import holoviews as hv
hv.extension('matplotlib')

data = [('one',8),('two', 10), ('three', 16), ('four', 8), ('five', 4), ('six', 1)]

bars = hv.Bars(data, hv.Dimension('Car occupants'), 'Count')

occupants = hv.Dimension('Car occupants', values=['three', 'two', 'four', 'one', 'five', 'six'])

# or using .redim.values(**{'Car Occupants': ['three', 'two', 'four', 'one', 'five', 'six']})

hv.Bars(data, occupants, 'Count') 
image

Groups:

samples = 100

pets = ['Cat', 'Dog', 'Hamster', 'Rabbit']
genders = ['Female', 'Male', "A", "B"]

np.random.seed(100)
pets_sample = np.random.choice(pets, samples)
gender_sample = np.random.choice(genders, samples)

bars = hv.Bars((pets_sample, gender_sample, np.ones(samples)), ['Pets', 'Gender']).aggregate(function=np.sum)

bars.opts(fig_size=300, aspect=2, show_legend=True)
image

Stacked:

image

Note for self; things to test:

  • align="center", "edge"
  • bar_padding
  • groups
  • stacked
  • categorical
  • numeric

@ahuang11 ahuang11 added the type: enhancement Minor feature or improvement to an existing feature label Apr 23, 2024
@ahuang11
Copy link
Collaborator

ahuang11 commented Apr 23, 2024

hv.Bars matplotlib backend is in a much better state now, with tests added!

Also, made continuous axes actually continuous, matching matplotlib behavior

image
import numpy as np
import pandas as pd
import holoviews as hv
import matplotlib.pyplot as plt
from holoviews.element import Bars
from holoviews.plotting.mpl import Store
import matplotlib.dates as mdates

hv.extension("matplotlib")

mpl_renderer = Store.renderers["matplotlib"]

hv.Bars(([0, 1, 2, 10], [10, 20, 30, 40]))

@ahuang11
Copy link
Collaborator

ahuang11 commented May 2, 2024

Make plotly continuous

import matplotlib.pyplot as plt
import holoviews as hv
import xarray as xr

hv.extension("plotly")

ds = xr.tutorial.open_dataset("air_temperature")
df = ds.resample(time="1MS").mean().mean(["lat", "lon"]).to_dataframe()
df

bars = hv.Bars(df, "time", "air")
bars
image image

@ahuang11 ahuang11 marked this pull request as ready for review May 2, 2024 14:44
@ahuang11 ahuang11 requested a review from hoxbro May 2, 2024 14:45
Copy link
Member

@hoxbro hoxbro left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have reviewed the code and left some comments.

I haven't yet tried out the code itself.

examples/reference/elements/bokeh/Bars.ipynb Outdated Show resolved Hide resolved
examples/reference/elements/bokeh/Bars.ipynb Outdated Show resolved Hide resolved
holoviews/plotting/bokeh/chart.py Outdated Show resolved Hide resolved
holoviews/plotting/bokeh/chart.py Outdated Show resolved Hide resolved
holoviews/plotting/bokeh/chart.py Outdated Show resolved Hide resolved
holoviews/plotting/plotly/chart.py Show resolved Hide resolved
holoviews/tests/plotting/matplotlib/test.ipynb Outdated Show resolved Hide resolved
holoviews/tests/plotting/matplotlib/test_barplot.py Outdated Show resolved Hide resolved
holoviews/tests/plotting/plotly/test_barplot.py Outdated Show resolved Hide resolved
ahuang11 and others added 3 commits May 6, 2024 06:43
@ahuang11 ahuang11 requested a review from hoxbro May 6, 2024 14:23
Copy link
Member

@hoxbro hoxbro left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Enumerate is not needed anymore, so I removed it.

@ahuang11
Copy link
Collaborator

Strange, but glad it works!

@hoxbro hoxbro added this to the 1.19.0 milestone May 17, 2024
@philippjfr
Copy link
Member Author

Okay, I reviewed again and it looks good to me.

@philippjfr philippjfr merged commit ee20d23 into main May 17, 2024
13 checks passed
@philippjfr philippjfr deleted the continuous_bars branch May 17, 2024 11:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
type: enhancement Minor feature or improvement to an existing feature
Projects
None yet
Development

Successfully merging this pull request may close these issues.

hv.Bars with more than one kdim randomly generates incorrect x-ticks in some cases (matplotlib backend)
6 participants