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

TypeError: FacetedEncoding.init() got multiple values for argument 'self' #3634

Open
gaspardc-met opened this issue Oct 7, 2024 · 1 comment
Labels

Comments

@gaspardc-met
Copy link

gaspardc-met commented Oct 7, 2024

What happened?

Using a complex altair chart in streamlit, worked in 5.3.0, and broke with 5.4.0 and then 5.4.1.
It seems to be an altair issue: TypeError: FacetedEncoding.init() got multiple values for argument 'self'

I tried to reproduce the error with the plot code and anonymized example data.
The error didn't show up neither with altair alone nor streamlit+altair.

However, it might give you an idea of the plot I use. The error happens in the first call to .encode in the fonction .mark_bar.encode()
Image

Long Code Block

import functools

import altair as alt
import numpy as np
import pandas as pd
import streamlit as st

# Mocked dependencies and constants
CMAP_BINS = [0, 0.25, 0.5, 0.75, 1.0]
CMAP_COLORS = ["#f7fbff", "#c6dbef", "#6baed6", "#2171b5", "#08306b"]


def custom_blues():
    bins = [0, 0.25, 0.5, 0.75, 1.0]
    colors = ["#f7fbff", "#c6dbef", "#6baed6", "#2171b5", "#08306b"]
    return bins, colors


def chronogram_legend(target="load", pump_toggle=False):
    legend = "Load Legend"
    short_legend = "Load"
    extra = None
    return legend, short_legend, extra


def chronogram_processing(chronogram, timedelta="60min", filter_load=True):
    data = chronogram.copy()
    data["start_time"] = data.index
    data["end_time"] = data.index + pd.Timedelta(timedelta)
    return data.reset_index(drop=True)


def get_machines_starts_and_stops(chronogram, timedelta="60min", separator_dt=None):
    starts_and_stops = alt.Chart(pd.DataFrame({"x": [], "y": []}))
    starts_and_stops_texts = alt.Chart(pd.DataFrame({"x": [], "y": []}))
    return starts_and_stops, starts_and_stops_texts


def get_vertical_separator(separator_dt, labels_y="", y_field="machine"):
    separator = alt.Chart(pd.DataFrame({"x": [], "y": []}))
    separator_labels = alt.Chart(pd.DataFrame({"x": [], "y": []}))
    return separator, separator_labels


@st.cache_data(show_spinner=False, ttl=60 * 60)  # TTL in seconds
def plot_chronogram(
    chronogram: pd.DataFrame,
    formatted=".0f",
    target="load",
    timedelta="60min",
    filter_load=True,
    expand: bool = False,
    pump_toggle: bool = False,
    display_starts_and_stops: bool = False,
    separator_dt: pd.Timestamp = None,
):
    legend, short_legend, _ = chronogram_legend(target=target, pump_toggle=pump_toggle)
    data = chronogram_processing(chronogram=chronogram, timedelta=timedelta, filter_load=filter_load)
    if expand:
        data = data.set_index("start_time").sort_index().reset_index()
        data.loc[28:, "end_time"] = data.loc[28:, "end_time"] + pd.Timedelta("45T")

    if target == "pressure":
        bins, colors = custom_blues()
        scale = alt.Scale(domain=bins, range=colors, type="ordinal")
    elif target == "load":
        scale = alt.Scale(domain=CMAP_BINS, range=CMAP_COLORS, type="threshold")
    else:
        scale = alt.Scale(scheme="blues")

    sort_order = [""] + data["machine"].sort_values().unique().tolist()

    chart = (
        alt.Chart(data)
        .mark_bar()
        .encode(
            x=alt.X("start_time:T", title="Horizon Temporel"),
            x2=alt.X2("end_time:T"),
            y=alt.Y("machine:N", title="Utilisation: Groupes ou AFC", sort=sort_order),
            color=alt.Color(
                "load:Q",
                title=short_legend,
                scale=scale,
                legend=alt.Legend(title=legend),
            ),
            stroke=alt.value("white"),
            strokeWidth=alt.value(2),
            tooltip=[
                alt.Tooltip("start_time:T", format="%Y-%m-%d", title="Date"),
                alt.Tooltip("start_time:T", format="%H:%M", title="Heure"),
                alt.Tooltip("load:Q", format=formatted, title=legend),
            ],
        )
    ).properties(
        title=f"Chronogramme d'opération: {legend}",
        width=800,
        height=350,
    )

    text = (
        alt.Chart(data)
        .mark_text(dx=0, dy=0, color="white", fontSize=10)
        .encode(
            x=alt.X("mid_time:T"),
            y=alt.Y("machine:N", sort=None),
            text=alt.Text("load:Q", format=formatted),
            tooltip=[
                alt.Tooltip("start_time:T", format="%Y-%m-%d", title="Date"),
                alt.Tooltip("start_time:T", format="%H:%M", title="Heure"),
                alt.Tooltip("load:Q", format=formatted, title=legend),
            ],
        )
        .transform_calculate(mid_time="datum.start_time + (datum.end_time - datum.start_time)/2")
        .transform_filter((alt.datum.load >= 0.0) & (alt.datum.load != 0.01))
    )

    text_hot = (
        alt.Chart(data)
        .mark_text(dx=0, dy=0, color="white", fontSize=10)
        .encode(
            x=alt.X("mid_time:T"),
            y=alt.Y("machine:N", sort=None),
            text=alt.value("Chaud"),
            tooltip=alt.value(None),
        )
        .transform_calculate(mid_time="datum.start_time + (datum.end_time - datum.start_time)/2")
        .transform_filter(alt.datum.load == 0.01)
    )

    all_charts = [chart, text, text_hot]

    if display_starts_and_stops:
        starts_and_stops, starts_and_stops_texts = get_machines_starts_and_stops(
            chronogram=chronogram,
            timedelta=timedelta,
            separator_dt=separator_dt,
        )
        all_charts += [starts_and_stops, starts_and_stops_texts]

    display_separator = separator_dt is not None and separator_dt > chronogram.index.min()
    if display_separator:
        separator, separator_labels = get_vertical_separator(separator_dt=separator_dt, labels_y="", y_field="machine")
        all_charts += [separator, separator_labels]

    composed = (
        functools.reduce(lambda a, b: a + b, all_charts)
        .configure_legend(orient="right", titleOrient="right")
        .configure_axis(labelFontSize=12, titleFontSize=12)
    )

    return composed


# Main block to generate and display the chart
if __name__ == "__main__":
    # Generate fake data
    date_range = pd.date_range(start="2022-01-01", periods=48, freq="30min")
    data = pd.DataFrame(index=date_range)
    data["machine"] = np.random.choice(["A", "B", "C"], size=len(data))
    data["load"] = np.random.rand(len(data))

    # Call the plotting function
    chart = plot_chronogram(chronogram=data)

    st.altair_chart(chart)

What would you like to happen instead?

No response

Which version of Altair are you using?

altair: 5.4.1
python: 3.11

@dangotbanned
Copy link
Member

dangotbanned commented Oct 7, 2024

Using a complex altair chart in streamlit, worked in 5.3.0, and broke with 5.4.0 and then 5.4.1. It seems to be an altair issue: TypeError: FacetedEncoding.init() got multiple values for argument 'self'

I tried to reproduce the error with the plot code and anonymized example data. The error didn't show up neither with altair alone nor streamlit+altair.

@gaspardc-met could you provide the traceback please?

I'm unsure what FacetedEncoding.init() is referring to, as that isn't a method on alt.FacetedEncoding:

Repro

import altair as alt

>>> alt.FacetedEncoding().init()

Traceback

AttributeError                            Traceback (most recent call last)
Cell In[17], line 3
      1 import altair as alt
----> 3 alt.FacetedEncoding().init()

File c:/../altair/altair/utils/schemapi.py:1063, in SchemaBase.__getattr__(self, attr)
   1061 except AttributeError:
   1062     _getattr = super().__getattribute__
-> 1063 return _getattr(attr)

AttributeError: 'FacetedEncoding' object has no attribute 'init'

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants