# Copyright (c) Streamlit Inc. (2018-2022) Snowflake Inc. (2022-2026)
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Collection of chart commands that are rendered via our vega-lite chart component."""

from __future__ import annotations

import json
import re
import threading
from contextlib import nullcontext
from dataclasses import dataclass
from typing import (
    TYPE_CHECKING,
    Any,
    Literal,
    TypeAlias,
    TypedDict,
    Union,
    cast,
    overload,
)

from typing_extensions import Required

from streamlit import dataframe_util, type_util
from streamlit.deprecation_util import (
    make_deprecated_name_warning,
    show_deprecation_warning,
)
from streamlit.elements.lib.built_in_chart_utils import (
    AddRowsMetadata,
    ChartStackType,
    ChartType,
    generate_chart,
    maybe_raise_stack_warning,
)
from streamlit.elements.lib.form_utils import current_form_id
from streamlit.elements.lib.layout_utils import (
    Height,
    LayoutConfig,
    Width,
    validate_height,
    validate_width,
)
from streamlit.elements.lib.policies import check_widget_policies
from streamlit.elements.lib.utils import Key, compute_and_register_element_id, to_key
from streamlit.errors import StreamlitAPIException
from streamlit.proto.VegaLiteChart_pb2 import (
    VegaLiteChart as VegaLiteChartProto,
)
from streamlit.runtime.metrics_util import gather_metrics
from streamlit.runtime.scriptrunner_utils.script_run_context import get_script_run_ctx
from streamlit.runtime.state import WidgetCallback, register_widget
from streamlit.util import AttributeDictionary, calc_hash

if TYPE_CHECKING:
    from collections.abc import Iterable, Sequence

    import altair as alt

    from streamlit.dataframe_util import Data
    from streamlit.delta_generator import DeltaGenerator
    from streamlit.elements.lib.color_util import Color

VegaLiteSpec: TypeAlias = dict[str, Any]
AltairChart: TypeAlias = Union[
    "alt.Chart",
    "alt.ConcatChart",
    "alt.FacetChart",
    "alt.HConcatChart",
    "alt.LayerChart",
    "alt.RepeatChart",
    "alt.VConcatChart",
]

_altair_globals_lock = threading.Lock()


class VegaLiteState(TypedDict, total=False):
    """
    The schema for the Vega-Lite event state.

    The event state is stored in a dictionary-like object that supports both
    key and attribute notation. Event states cannot be programmatically
    changed or set through Session State.

    Only selection events are supported at this time.

    Attributes
    ----------
    selection : dict
        The state of the ``on_select`` event. This attribute returns a
        dictionary-like object that supports both key and attribute notation.
        The name of each Vega-Lite selection parameter becomes an attribute in
        the ``selection`` dictionary. The format of the data within each
        attribute is determined by the selection parameter definition within
        Vega-Lite.

    Examples
    --------
    The following two examples have equivalent definitions. Each one has a
    point and interval selection parameter include in the chart definition.
    The point selection parameter is named ``"point_selection"``. The interval
    or box selection parameter is named ``"interval_selection"``.

    **Example 1: Chart selections with ``st.altair_chart``**

    >>> import altair as alt
    >>> import pandas as pd
    >>> import streamlit as st
    >>> from numpy.random import default_rng as rng
    >>>
    >>> df = pd.DataFrame(rng(0).standard_normal((20, 3)), columns=["a", "b", "c"])
    >>>
    >>> point_selector = alt.selection_point("point_selection")
    >>> interval_selector = alt.selection_interval("interval_selection")
    >>> chart = (
    ...     alt.Chart(df)
    ...     .mark_circle()
    ...     .encode(
    ...         x="a",
    ...         y="b",
    ...         size="c",
    ...         color="c",
    ...         tooltip=["a", "b", "c"],
    ...         fillOpacity=alt.condition(point_selector, alt.value(1), alt.value(0.3)),
    ...     )
    ...     .add_params(point_selector, interval_selector)
    ... )
    >>>
    >>> event = st.altair_chart(chart, key="alt_chart", on_select="rerun")
    >>>
    >>> event

    **Example 2: Chart selections with ``st.vega_lite_chart``**

    >>> import pandas as pd
    >>> import streamlit as st
    >>> from numpy.random import default_rng as rng
    >>>
    >>> df = pd.DataFrame(rng(0).standard_normal((20, 3)), columns=["a", "b", "c"])
    >>>
    >>> spec = {
    ...     "mark": {"type": "circle", "tooltip": True},
    ...     "params": [
    ...         {"name": "interval_selection", "select": "interval"},
    ...         {"name": "point_selection", "select": "point"},
    ...     ],
    ...     "encoding": {
    ...         "x": {"field": "a", "type": "quantitative"},
    ...         "y": {"field": "b", "type": "quantitative"},
    ...         "size": {"field": "c", "type": "quantitative"},
    ...         "color": {"field": "c", "type": "quantitative"},
    ...         "fillOpacity": {
    ...             "condition": {"param": "point_selection", "value": 1},
    ...             "value": 0.3,
    ...         },
    ...     },
    ... }
    >>>
    >>> event = st.vega_lite_chart(df, spec, key="vega_chart", on_select="rerun")
    >>>
    >>> event

    Try selecting points in this interactive example. When you click a point,
    the selection will appear under the attribute, ``"point_selection"``, which
    is the name given to the point selection parameter. Similarly, when you
    make an interval selection, it will appear under the attribute
    ``"interval_selection"``. You can give your selection parameters other
    names if desired.

    If you hold ``Shift`` while selecting points, existing point selections
    will be preserved. Interval selections are not preserved when making
    additional selections.

    .. output::
        https://doc-chart-events-vega-lite-state.streamlit.app
        height: 600px

    """

    selection: Required[AttributeDictionary]


@dataclass
class VegaLiteStateSerde:
    """VegaLiteStateSerde is used to serialize and deserialize the VegaLite Chart state."""

    selection_parameters: Sequence[str]

    def deserialize(self, ui_value: str | None) -> VegaLiteState:
        empty_selection_state: VegaLiteState = {
            "selection": AttributeDictionary(
                # Initialize the select state with empty dictionaries for each selection parameter.
                {param: {} for param in self.selection_parameters}
            ),
        }

        selection_state = (
            empty_selection_state
            if ui_value is None
            else cast("VegaLiteState", AttributeDictionary(json.loads(ui_value)))
        )

        if "selection" not in selection_state:
            selection_state = empty_selection_state  # type: ignore[unreachable]

        return cast("VegaLiteState", AttributeDictionary(selection_state))

    def serialize(self, selection_state: VegaLiteState) -> str:
        return json.dumps(selection_state, default=str)


def _patch_null_legend_titles(spec: VegaLiteSpec) -> None:
    """Patches null legend titles in the 'color' channel of the spec.
    This is a fix for the Vega-Lite bug where null legend titles
    cause a wrong formatting of the chart as shown on the issue #9339.
    """

    encoding = spec.get("encoding")
    if not isinstance(encoding, dict):
        return

    color_spec = encoding.get("color")
    if not isinstance(color_spec, dict):
        return

    if "title" in color_spec and color_spec.get("title") is None:
        # Patch legend title given null value directly in the encoding
        color_spec["title"] = " "

    legend = color_spec.get("legend")
    if isinstance(legend, dict) and "title" in legend and legend.get("title") is None:
        # Patch legend title given null value in the legend
        legend["title"] = " "


def _has_nested_composition(spec: VegaLiteSpec) -> bool:
    """Check if a vconcat spec contains nested multi-view compositions.

    This function checks if a vconcat chart contains nested hconcat, vconcat,
    concat, layer, facet, or repeat operators. Such nested compositions don't
    work well with fit-x autosize and forced child widths, and can cause
    infinite extent errors.

    In valid Vega-Lite specs, composition operators
    (hconcat, vconcat, concat, layer, facet, repeat) are always top-level keys
    of a view specification. They cannot be buried inside encoding, mark, or
    other nested properties. This allows us to check only the immediate children
    of vconcat for nested composition operators.

    Parameters
    ----------
    spec : VegaLiteSpec
        The Vega-Lite spec to check.

    Returns
    -------
    bool
        True if the spec contains nested composition operators, False otherwise.
    """
    # Check if vconcat contains nested compositions.
    # We only need to check top-level keys of each child spec since composition
    # operators are always top-level in valid Vega-Lite specs.
    if "vconcat" in spec and isinstance(spec["vconcat"], list):
        for item in spec["vconcat"]:
            # Check if this item is a dict containing any composition operator
            if isinstance(item, dict) and any(
                k in item
                for k in ["hconcat", "vconcat", "concat", "layer", "facet", "repeat"]
            ):
                return True
    return False


def _prepare_vega_lite_spec(
    spec: VegaLiteSpec,
    use_container_width: bool,
) -> VegaLiteSpec:
    # Clone the spec dict, since we may be mutating it.
    spec = dict(spec)

    if len(spec) == 0:
        raise StreamlitAPIException("Vega-Lite charts require a non-empty spec dict.")

    if "autosize" not in spec:
        # type fit does not work for many chart types. This change focuses
        # on vconcat with use_container_width=True as there are unintended
        # consequences of changing the default autosize for all charts.
        # fit-x fits the width and height can be adjusted.
        is_facet_chart = "facet" in spec or (
            "encoding" in spec
            and (any(x in spec["encoding"] for x in ["row", "column", "facet"]))
        )
        has_nested_comp = _has_nested_composition(spec)

        if "vconcat" in spec and use_container_width:
            # For vconcat charts with container width stretching:
            # - Simple vconcat: use fit-x (fits width only, better control)
            # - Nested compositions (vconcat+hconcat): use pad (no automatic fitting)
            #   fit-x causes "Infinite extent" errors with nested hconcat (issue #13410)
            #
            # Known limitation: Nested compositions may overflow the container because
            # Vega-Lite's width property only controls the plotting area (data marks),
            # not the total SVG width which includes axes, labels, legends, and padding.
            # The frontend sets spec.width to containerWidth, but with autosize: pad,
            # Vega adds decorations on top, causing total SVG to exceed container bounds.
            # This is a Vega-Lite architectural limitation similar to facet charts
            # (see https://github.com/vega/vega-lite/issues/5219).
            # Trade-off: Accept overflow to ensure charts render correctly rather than
            # appear as empty elements with "Infinite extent" errors.
            if has_nested_comp:
                # use pad = "no automatic fitting" - accurate description of what's happening
                # produces same overflow behavior as fit
                spec["autosize"] = {"type": "pad", "contains": "padding"}
            else:
                spec["autosize"] = {"type": "fit-x", "contains": "padding"}

        elif is_facet_chart or (has_nested_comp and not use_container_width):
            # Facet charts and nested compositions without stretching use pad
            # (no automatic sizing, uses natural/content size)
            spec["autosize"] = {"type": "pad", "contains": "padding"}

        else:
            spec["autosize"] = {"type": "fit", "contains": "padding"}

    _patch_null_legend_titles(spec)

    return spec


def _marshall_chart_data(
    proto: VegaLiteChartProto,
    spec: VegaLiteSpec,
    data: Data = None,
) -> None:
    """Adds the data to the proto and removes it from the spec dict.
    These operations will happen in-place.
    """

    # Pull data out of spec dict when it's in a 'datasets' key:
    #   datasets: {foo: df1_bytes, bar: df2_bytes}, ...}
    if "datasets" in spec:
        for dataset_name, dataset_data in spec["datasets"].items():
            dataset = proto.datasets.add()
            dataset.name = str(dataset_name)
            dataset.has_name = True
            # The ID transformer (_to_arrow_dataset function registered before conversion to dict)
            # already serializes the data into Arrow IPC format (bytes) when the Altair object
            # gets converted into the vega-lite spec dict.
            # If its already in bytes, we don't need to serialize it here again.
            # We just need to pass the data information into the correct proto fields.

            # TODO(lukasmasuch): Are there any other cases where we need to serialize the data
            # or can we remove the convert_anything_to_arrow_bytes here?
            dataset.data.data = (
                dataset_data
                if isinstance(dataset_data, bytes)
                else dataframe_util.convert_anything_to_arrow_bytes(dataset_data)
            )
        del spec["datasets"]

    # Pull data out of spec dict when it's in a top-level 'data' key:
    # > {data: df}
    # > {data: {values: df, ...}}
    # > {data: {url: 'url'}}
    # > {data: {name: 'foo'}}
    if "data" in spec:
        data_spec = spec["data"]

        if isinstance(data_spec, dict):
            if "values" in data_spec:
                data = data_spec["values"]
                del spec["data"]
        else:
            data = data_spec
            del spec["data"]

    if data is not None:
        proto.data.data = dataframe_util.convert_anything_to_arrow_bytes(data)


def _convert_altair_to_vega_lite_spec(
    altair_chart: AltairChart,
) -> VegaLiteSpec:
    """Convert an Altair chart object to a Vega-Lite chart spec."""
    import altair as alt

    # alt.themes was deprecated in Altair 5.5.0 in favor of alt.theme
    if type_util.is_altair_version_less_than("5.5.0"):
        alt_theme = alt.themes
    else:
        alt_theme = alt.theme

    # This is where we'll store Arrow-serialized versions of the chart data.
    # This happens in _to_arrow_dataset().
    datasets: dict[str, Any] = {}

    # Normally altair_chart.to_dict() would transform the dataframe used by the
    # chart into an array of dictionaries. To avoid that, we install a
    # transformer that replaces datasets with a reference by the object id of
    # the dataframe. We then fill in the dataset manually later on.
    #
    # Note: it's OK to re-register this every time we run this function since
    # transformers are stored in a dict. So there's no duplication.
    #
    # type: ignore[arg-type,attr-defined,unused-ignore]
    alt.data_transformers.register("to_arrow_dataset", _to_arrow_dataset)

    # Settings like alt.theme.enable and alt.data_transformers.enable are global to all
    # threads. So this lock makes sure that whatever we set those to only apply to the
    # current thread.
    with _altair_globals_lock:
        # The default altair theme has some width/height defaults defined
        # which are not useful for Streamlit. Therefore, we change the theme to
        # "none" to avoid those defaults.
        theme_context = (
            alt_theme.enable("none") if alt_theme.active == "default" else nullcontext()
        )

        data_transformer = alt.data_transformers.enable(
            "to_arrow_dataset", datasets=datasets
        )

        with theme_context:  # ty: ignore[invalid-context-manager]
            # type: ignore[attr-defined,unused-ignore]
            with data_transformer:  # ty: ignore[invalid-context-manager]
                chart_dict = altair_chart.to_dict()

    # Put datasets back into the chart dict:
    chart_dict["datasets"] = datasets
    return chart_dict


def _extract_selection_parameters(spec: VegaLiteSpec) -> set[str]:
    """Extract the names of all valid selection parameters from the spec.

    This function recursively traverses composite view specs (layer, hconcat,
    vconcat, concat, facet, repeat) to find all selection parameters, regardless
    of where they are defined in the spec hierarchy.

    Altair automatically hoists params to the top level, but raw Vega-Lite specs
    may have params defined at any level in the view hierarchy.
    """
    if not spec:
        return set()

    param_names: set[str] = set()

    # Extract from top-level params.
    # Non-list params or non-dict entries are silently skipped as they are malformed.
    if "params" in spec and isinstance(spec["params"], list):
        for param in spec["params"]:
            if not isinstance(param, dict):
                # This is unexpected and should not happen
                continue
            # Check if it looks like a valid selection parameter:
            # https://vega.github.io/vega-lite/docs/selection.html
            if param.get("name") and param.get("select"):
                param_names.add(param["name"])

    # Recursively check composite view specs (layer, hconcat, vconcat, concat).
    # Non-dict entries in the list are silently skipped as they are malformed.
    for key in ("layer", "hconcat", "vconcat", "concat"):
        if key in spec and isinstance(spec[key], list):
            for child_spec in spec[key]:
                if isinstance(child_spec, dict):
                    param_names.update(_extract_selection_parameters(child_spec))

    # Check facet/repeat spec (the inner view specification)
    if "spec" in spec and isinstance(spec["spec"], dict):
        param_names.update(_extract_selection_parameters(spec["spec"]))

    return param_names


def _parse_selection_mode(
    spec: VegaLiteSpec,
    selection_mode: str | Iterable[str] | None,
) -> list[str]:
    """Parse and check the user provided selection modes.

    This will raise an exception if no valid selection parameters are found in the spec
    or if the user provided selection modes are not defined in the spec.

    Parameters
    ----------
    spec : VegaLiteSpec
        The Vega-Lite chart specification.

    selection_mode : str, Iterable[str], or None
        The user provided selection mode(s).

    Returns
    -------
    list[str]
        The parsed selection mode(s) that should be activated.
    """

    # Extract all selection parameters from the spec:
    all_selection_params = _extract_selection_parameters(spec)

    if not all_selection_params:
        raise StreamlitAPIException(
            "Selections are activated, but the provided chart spec does not "
            "have any selections defined. To add selections to `st.altair_chart`, check out the documentation "
            "[here](https://altair-viz.github.io/user_guide/interactions.html#selections-capturing-chart-interactions)."
            " For adding selections to `st.vega_lite_chart`, take a look "
            "at the specification [here](https://vega.github.io/vega-lite/docs/selection.html)."
        )

    if selection_mode is None:
        # Activate all selection parameters:
        return sorted(all_selection_params)

    if isinstance(selection_mode, str):
        # Convert single string to list:
        selection_mode = [selection_mode]

    # Check that all provided selection parameters are defined in the spec:
    for selection_name in selection_mode:
        if selection_name not in all_selection_params:
            raise StreamlitAPIException(
                f"Selection parameter '{selection_name}' is not defined in the chart "
                f"spec. Available selection parameters are: {all_selection_params}."
            )
    return sorted(selection_mode)


def _reset_counter_pattern(prefix: str, vega_spec: str) -> str:
    """Altair uses a global counter for unnamed parameters and views.
    We need to reset these counters on a spec-level to make the
    spec stable across reruns and avoid changes to the element ID.
    """

    # Altair 6.0.0 introduced a new way to handle parameters,
    # by using hashes instead of pure counters:
    pattern = re.compile(rf'"{prefix}[0-9a-z]+"')
    # Get all matches without duplicates in order of appearance.
    # Using a set here would not guarantee the order of appearance,
    # which might lead to different replacements on each run.
    # The order of the spec from Altair is expected to stay stable
    # within the same session / Altair version.
    # The order might change with Altair updates, but that's not really
    # a case that is relevant for us since we mainly care about having
    # this stable within a session.
    if matches := list(dict.fromkeys(pattern.findall(vega_spec))):
        # Add a prefix to the replacement to avoid
        # replacing instances that already have been replaced before.
        # The prefix here is arbitrarily chosen with the main goal
        # that its extremely unlikely to already be part of the spec:
        replacement_prefix = "__replace_prefix_o9hd101n22e1__"

        # Replace all matches with a counter starting from 1
        # We start from 1 to imitate the altair behavior.
        for counter, match in enumerate(matches, start=1):
            vega_spec = vega_spec.replace(
                match, f'"{replacement_prefix}{prefix}{counter}"'
            )

        # Remove the prefix again from all replacements:
        vega_spec = vega_spec.replace(replacement_prefix, "")
    return vega_spec


def _stabilize_vega_json_spec(vega_spec: str) -> str:
    """Makes the chart spec stay stable across reruns and sessions.

    Altair auto creates names for unnamed parameters & views. It uses a global counter
    for the naming which will result in a different spec on every rerun.
    In Streamlit, we need the spec to be stable across reruns and sessions to prevent the chart
    from getting a new identity. So we need to replace the names with counter with a stable name.
    Having a stable chart spec is also important for features like forward message cache,
    where we don't want to have changing messages on every rerun.

    Parameter counter:
    https://github.com/vega/altair/blob/f345cd9368ae2bbc98628e9245c93fa9fb582621/altair/vegalite/v5/api.py#L196

    View counter:
    https://github.com/vega/altair/blob/f345cd9368ae2bbc98628e9245c93fa9fb582621/altair/vegalite/v5/api.py#L2885

    This is temporary solution waiting for a fix for this issue:
    https://github.com/vega/altair/issues/3416

    Other solutions we considered:
     - working on the dict object: this would require to iterate through the object and do the
       same kind of replacement; though we would need to know the structure and since we need
       the spec in String-format anyways, we deemed that executing the replacement on the
       String is the better alternative
     - resetting the counter: the counter is incremented already when the chart object is created
       (see this GitHub issue comment https://github.com/vega/altair/issues/3416#issuecomment-2098530464),
       so it would be too late here to reset the counter with a thread-lock to prevent interference
       between sessions
    """

    # We only want to apply these replacements if it is really necessary
    # since there is a risk that we replace names that where chosen by the user
    # and thereby introduce unwanted side effects.

    # We only need to apply the param_ fix if there are actually parameters defined
    # somewhere in the spec. We can check for this by looking for the '"params"' key.
    # This isn't a perfect check, but good enough to prevent unnecessary executions
    # for the majority of charts.
    if '"params"' in vega_spec:
        vega_spec = _reset_counter_pattern("param_", vega_spec)

    # Simple check if the spec contains a composite chart:
    # https://vega.github.io/vega-lite/docs/composition.html
    # Other charts will not contain the `view_` name,
    # so its better to not replace this pattern.
    if re.search(r'"(vconcat|hconcat|facet|layer|concat|repeat)"', vega_spec):
        vega_spec = _reset_counter_pattern("view_", vega_spec)
    return vega_spec


class VegaChartsMixin:
    """Mix-in class for all vega-related chart commands.

    Altair is a python wrapper on top of the vega-lite spec. And our
    built-in chart commands are just another layer on-top of Altair.
    All of these chart commands will be eventually converted to a vega-lite
    spec and rendered using the same vega-lite chart component.
    """

    @gather_metrics("line_chart")
    def line_chart(
        self,
        data: Data = None,
        *,
        x: str | None = None,
        y: str | Sequence[str] | None = None,
        x_label: str | None = None,
        y_label: str | None = None,
        color: str | Color | list[Color] | None = None,
        width: Width = "stretch",
        height: Height = "content",
        use_container_width: bool | None = None,
    ) -> DeltaGenerator:
        """Display a line chart.

        This is syntax-sugar around ``st.altair_chart``. The main difference
        is this command uses the data's own column and indices to figure out
        the chart's Altair spec. As a result this is easier to use for many
        "just plot this" scenarios, while being less customizable.

        Parameters
        ----------
        data : Anything supported by st.dataframe
            Data to be plotted.

        x : str or None
            Column name or key associated to the x-axis data. If ``x`` is
            ``None`` (default), Streamlit uses the data index for the x-axis
            values.

        y : str, Sequence of str, or None
            Column name(s) or key(s) associated to the y-axis data. If this is
            ``None`` (default), Streamlit draws the data of all remaining
            columns as data series. If this is a ``Sequence`` of strings,
            Streamlit draws several series on the same chart by melting your
            wide-format table into a long-format table behind the scenes.

        x_label : str or None
            The label for the x-axis. If this is ``None`` (default), Streamlit
            will use the column name specified in ``x`` if available, or else
            no label will be displayed.

        y_label : str or None
            The label for the y-axis. If this is ``None`` (default), Streamlit
            will use the column name(s) specified in ``y`` if available, or
            else no label will be displayed.

        color : str, tuple, Sequence of str, Sequence of tuple, or None
            The color to use for different lines in this chart.

            For a line chart with just one line, this can be:

            - None, to use the default color.
            - A hex string like "#ffaa00" or "#ffaa0088".
            - An RGB or RGBA tuple with the red, green, blue, and alpha
              components specified as ints from 0 to 255 or floats from 0.0 to
              1.0.
            - A built-in color name: "red", "orange", "yellow", "green",
              "blue", "violet", "gray"/"grey", or "primary". These map to
              theme colors that you can customize using ``theme.<color>Color``
              configuration options.

            For a line chart with multiple lines, where the dataframe is in
            long format (that is, y is None or just one column), this can be:

            - None, to use the default colors.
            - The name of a column in the dataset. Data points will be grouped
              into lines of the same color based on the value of this column.
              In addition, if the values in this column match one of the color
              formats above (hex string or color tuple), then that color will
              be used.

              For example: if the dataset has 1000 rows, but this column only
              contains the values "adult", "child", and "baby", then those 1000
              datapoints will be grouped into three lines whose colors will be
              automatically selected from the default palette.

              But, if for the same 1000-row dataset, this column contained
              the values "#ffaa00", "#f0f", "#0000ff", then then those 1000
              datapoints would still be grouped into three lines, but their
              colors would be "#ffaa00", "#f0f", "#0000ff" this time around.

            For a line chart with multiple lines, where the dataframe is in
            wide format (that is, y is a Sequence of columns), this can be:

            - None, to use the default colors.
            - A list of string colors or color tuples to be used for each of
              the lines in the chart. This list should have the same length
              as the number of y values (e.g. ``color=["#fd0", "#f0f", "#04f"]``
              for three lines). You can also use built-in color names in the
              list (e.g. ``color=["red", "blue", "green"]``).

            You can set the default colors in the ``theme.chartCategoryColors``
            configuration option.

        width : "stretch", "content", or int
            The width of the chart element. This can be one of the following:

            - ``"stretch"`` (default): The width of the element matches the
              width of the parent container.
            - ``"content"``: The width of the element matches the width of its
              content, but doesn't exceed the width of the parent container.
            - An integer specifying the width in pixels: The element has a
              fixed width. If the specified width is greater than the width of
              the parent container, the width of the element matches the width
              of the parent container.

        height : "content", "stretch", or int
            The height of the chart element. This can be one of the following:

            - ``"content"`` (default): The height of the element matches the
              height of its content.
            - ``"stretch"``: The height of the element matches the height of
              its content or the height of the parent container, whichever is
              larger. If the element is not in a parent container, the height
              of the element matches the height of its content.
            - An integer specifying the height in pixels: The element has a
              fixed height. If the content is larger than the specified
              height, scrolling is enabled.

        use_container_width : bool or None
            Whether to override the chart's native width with the width of
            the parent container. This can be one of the following:

            - ``None`` (default): Streamlit will use the chart's default behavior.
            - ``True``: Streamlit sets the width of the chart to match the
              width of the parent container.
            - ``False``: Streamlit sets the width of the chart to fit its
              contents according to the plotting library, up to the width of
              the parent container.

            .. deprecated::
                ``use_container_width`` is deprecated and will be removed in a
                future release. For ``use_container_width=True``, use
                ``width="stretch"``.

        Examples
        --------
        **Example 1: Basic line chart from a dataframe**

        If you don't use any of the optional parameters, Streamlit plots each
        column as a separate line, uses the index as the x values, and labels
        each series with the column name:

        >>> import pandas as pd
        >>> import streamlit as st
        >>> from numpy.random import default_rng as rng
        >>>
        >>> df = pd.DataFrame(rng(0).standard_normal((20, 3)), columns=["a", "b", "c"])
        >>>
        >>> st.line_chart(df)

        .. output::
           https://doc-line-chart.streamlit.app/
           height: 440px

        **Example 2: Line chart from specific dataframe columns**

        You can choose different columns to use for the x and y values. If your
        dataframe is in long format (all y-values in one column), you can set
        the line colors from another column.

        If the column contains color strings, the colors will be applied
        directly and the series will be unlabeled. If the column contains other
        values, those values will label each line, and the line colors will be
        selected from the default color palette. You can configure this color
        palette in the ``theme.chartCategoryColors`` configuration option.

        >>> import pandas as pd
        >>> import streamlit as st
        >>> from numpy.random import default_rng as rng
        >>>
        >>> df = pd.DataFrame(
        ...     {
        ...         "col1": list(range(20)) * 3,
        ...         "col2": rng(0).standard_normal(60),
        ...         "col3": ["a"] * 20 + ["b"] * 20 + ["c"] * 20,
        ...     }
        ... )
        >>>
        >>> st.line_chart(df, x="col1", y="col2", color="col3")

        .. output::
           https://doc-line-chart1.streamlit.app/
           height: 440px

        **Example 3: Line chart from wide-format dataframe**

        If your dataframe is in wide format (y-values are in multiple columns),
        you can pass a list of columns to the ``y`` parameter. Each column
        name becomes a series label. To override the default colors, pass a
        list of colors to the ``color`` parameter, one for each series:

        >>> import pandas as pd
        >>> import streamlit as st
        >>> from numpy.random import default_rng as rng
        >>>
        >>> df = pd.DataFrame(rng(0).standard_normal((20, 3)), columns=["a", "b", "c"])
        >>>
        >>> st.line_chart(
        ...     df,
        ...     x="a",
        ...     y=["b", "c"],
        ...     color=["#FF0000", "#0000FF"],
        ... )

        .. output::
           https://doc-line-chart2.streamlit.app/
           height: 440px

        """
        chart, add_rows_metadata = generate_chart(
            chart_type=ChartType.LINE,
            data=data,
            x_from_user=x,
            y_from_user=y,
            x_axis_label=x_label,
            y_axis_label=y_label,
            color_from_user=color,
            size_from_user=None,
            width=width,
            height=height,
            use_container_width=(width == "stretch"),
        )

        return cast(
            "DeltaGenerator",
            self._altair_chart(
                chart,
                use_container_width=use_container_width,
                theme="streamlit",
                add_rows_metadata=add_rows_metadata,
                width=width,
                height=height,
            ),
        )

    @gather_metrics("area_chart")
    def area_chart(
        self,
        data: Data = None,
        *,
        x: str | None = None,
        y: str | Sequence[str] | None = None,
        x_label: str | None = None,
        y_label: str | None = None,
        color: str | Color | list[Color] | None = None,
        stack: bool | ChartStackType | None = None,
        width: Width = "stretch",
        height: Height = "content",
        use_container_width: bool | None = None,
    ) -> DeltaGenerator:
        """Display an area chart.

        This is syntax-sugar around ``st.altair_chart``. The main difference
        is this command uses the data's own column and indices to figure out
        the chart's Altair spec. As a result this is easier to use for many
        "just plot this" scenarios, while being less customizable.

        Parameters
        ----------
        data : Anything supported by st.dataframe
            Data to be plotted.

        x : str or None
            Column name or key associated to the x-axis data. If ``x`` is
            ``None`` (default), Streamlit uses the data index for the x-axis
            values.

        y : str, Sequence of str, or None
            Column name(s) or key(s) associated to the y-axis data. If this is
            ``None`` (default), Streamlit draws the data of all remaining
            columns as data series. If this is a ``Sequence`` of strings,
            Streamlit draws several series on the same chart by melting your
            wide-format table into a long-format table behind the scenes.

        x_label : str or None
            The label for the x-axis. If this is ``None`` (default), Streamlit
            will use the column name specified in ``x`` if available, or else
            no label will be displayed.

        y_label : str or None
            The label for the y-axis. If this is ``None`` (default), Streamlit
            will use the column name(s) specified in ``y`` if available, or
            else no label will be displayed.

        color : str, tuple, Sequence of str, Sequence of tuple, or None
            The color to use for different series in this chart.

            For an area chart with just 1 series, this can be:

            - None, to use the default color.
            - A hex string like "#ffaa00" or "#ffaa0088".
            - An RGB or RGBA tuple with the red, green, blue, and alpha
              components specified as ints from 0 to 255 or floats from 0.0 to
              1.0.
            - A built-in color name: "red", "orange", "yellow", "green",
              "blue", "violet", "gray"/"grey", or "primary". These map to
              theme colors that you can customize using ``theme.<color>Color``
              configuration options.

            For an area chart with multiple series, where the dataframe is in
            long format (that is, y is None or just one column), this can be:

            - None, to use the default colors.
            - The name of a column in the dataset. Data points will be grouped
              into series of the same color based on the value of this column.
              In addition, if the values in this column match one of the color
              formats above (hex string or color tuple), then that color will
              be used.

              For example: if the dataset has 1000 rows, but this column only
              contains the values "adult", "child", and "baby", then those 1000
              datapoints will be grouped into three series whose colors will be
              automatically selected from the default palette.

              But, if for the same 1000-row dataset, this column contained
              the values "#ffaa00", "#f0f", "#0000ff", then then those 1000
              datapoints would still be grouped into 3 series, but their
              colors would be "#ffaa00", "#f0f", "#0000ff" this time around.

            For an area chart with multiple series, where the dataframe is in
            wide format (that is, y is a Sequence of columns), this can be:

            - None, to use the default colors.
            - A list of string colors or color tuples to be used for each of
              the series in the chart. This list should have the same length
              as the number of y values (e.g. ``color=["#fd0", "#f0f", "#04f"]``
              for three lines). You can also use built-in color names in the
              list (e.g. ``color=["red", "blue", "green"]``).

            You can set the default colors in the ``theme.chartCategoryColors``
            configuration option.

        stack : bool, "normalize", "center", or None
            Whether to stack the areas. If this is ``None`` (default),
            Streamlit uses Vega's default. Other values can be as follows:

            - ``True``: The areas form a non-overlapping, additive stack within
              the chart.
            - ``False``: The areas overlap each other without stacking.
            - ``"normalize"``: The areas are stacked and the total height is
              normalized to 100% of the height of the chart.
            - ``"center"``: The areas are stacked and shifted to center their
              baseline, which creates a steamgraph.

        width : "stretch", "content", or int
            The width of the chart element. This can be one of the following:

            - ``"stretch"`` (default): The width of the element matches the
              width of the parent container.
            - ``"content"``: The width of the element matches the width of its
              content, but doesn't exceed the width of the parent container.
            - An integer specifying the width in pixels: The element has a
              fixed width. If the specified width is greater than the width of
              the parent container, the width of the element matches the width
              of the parent container.

        height : "stretch", "content", or int
            The height of the chart element. This can be one of the following:

            - ``"content"`` (default): The height of the element matches the
              height of its content.
            - ``"stretch"``: The height of the element matches the height of
              its content or the height of the parent container, whichever is
              larger. If the element is not in a parent container, the height
              of the element matches the height of its content.
            - An integer specifying the height in pixels: The element has a
              fixed height. If the content is larger than the specified
              height, scrolling is enabled.

        use_container_width : bool or None
            Whether to override the chart's native width with the width of
            the parent container. This can be one of the following:

            - ``None`` (default): Streamlit will use the chart's default behavior.
            - ``True``: Streamlit sets the width of the chart to match the
              width of the parent container.
            - ``False``: Streamlit sets the width of the chart to fit its
              contents according to the plotting library, up to the width of
              the parent container.

            .. deprecated::
                ``use_container_width`` is deprecated and will be removed in a
                future release. For ``use_container_width=True``, use
                ``width="stretch"``.

        Examples
        --------
        **Example 1: Basic area chart from a dataframe**

        If you don't use any of the optional parameters, Streamlit plots each
        column as a separate area, uses the index as the x values, and labels
        each series with the column name:

        >>> import pandas as pd
        >>> import streamlit as st
        >>> from numpy.random import default_rng as rng
        >>>
        >>> df = pd.DataFrame(rng(0).standard_normal((20, 3)), columns=["a", "b", "c"])
        >>>
        >>> st.area_chart(df)

        .. output::
           https://doc-area-chart.streamlit.app/
           height: 440px

        **Example 2: Area chart from specific dataframe columns**

        You can choose different columns to use for the x and y values. If your
        dataframe is in long format (all y-values in one column), you can set
        the area colors from another column.

        If the column contains color strings, the colors will be applied
        directly and the series will be unlabeled. If the column contains other
        values, those values will label each area, and the area colors will be
        selected from the default color palette. You can configure this color
        palette in the ``theme.chartCategoryColors`` configuration option.

        >>> import pandas as pd
        >>> import streamlit as st
        >>> from numpy.random import default_rng as rng
        >>>
        >>> df = pd.DataFrame(
        ...     {
        ...         "col1": list(range(20)) * 3,
        ...         "col2": rng(0).standard_normal(60),
        ...         "col3": ["a"] * 20 + ["b"] * 20 + ["c"] * 20,
        ...     }
        ... )
        >>>
        >>> st.area_chart(df, x="col1", y="col2", color="col3")

        .. output::
           https://doc-area-chart1.streamlit.app/
           height: 440px

        **Example 3: Area chart from wide-format dataframe**

        If your dataframe is in wide format (y-values are in multiple columns),
        you can pass a list of columns to the ``y`` parameter. Each column
        name becomes a series label. To override the default colors, pass a
        list of colors to the ``color`` parameter, one for each series. If your
        areas are overlapping, use colors with some transparency (alpha
        channel) for the best results.

        >>> import pandas as pd
        >>> import streamlit as st
        >>> from numpy.random import default_rng as rng
        >>>
        >>> df = pd.DataFrame(
        ...     {
        ...         "col1": list(range(20)),
        ...         "col2": rng(0).standard_normal(20),
        ...         "col3": rng(1).standard_normal(20),
        ...     }
        ... )
        >>>
        >>> st.area_chart(
        ...     df,
        ...     x="col1",
        ...     y=["col2", "col3"],
        ...     color=["#FF000080", "#0000FF80"],
        ... )

        .. output::
           https://doc-area-chart2.streamlit.app/
           height: 440px

        **Example 4: Area chart with different stacking**

        You can adjust the stacking behavior by setting ``stack``. You can
        create a streamgraph by setting ``stack="center"``:

        >>> import streamlit as st
        >>> from vega_datasets import data
        >>>
        >>> df = data.unemployment_across_industries()
        >>>
        >>> st.area_chart(df, x="date", y="count", color="series", stack="center")

        .. output::
           https://doc-area-chart-steamgraph.streamlit.app/
           height: 440px

        """
        # Check that the stack parameter is valid, raise more informative error message if not
        maybe_raise_stack_warning(
            stack,
            "st.area_chart",
            "https://docs.streamlit.io/develop/api-reference/charts/st.area_chart",
        )

        # st.area_chart's stack=False option translates to a "layered" area chart for
        # vega. We reserve stack=False for
        # grouped/non-stacked bar charts, so we need to translate False to "layered"
        # here. The default stack type was changed in vega-lite 5.14.1:
        # https://github.com/vega/vega-lite/issues/9337
        # To get the old behavior, we also need to set stack to layered as the
        # default (if stack is None)
        if stack is False or stack is None:
            stack = "layered"

        chart, add_rows_metadata = generate_chart(
            chart_type=ChartType.AREA,
            data=data,
            x_from_user=x,
            y_from_user=y,
            x_axis_label=x_label,
            y_axis_label=y_label,
            color_from_user=color,
            size_from_user=None,
            width=width,
            height=height,
            stack=stack,
            use_container_width=(width == "stretch"),
        )
        return cast(
            "DeltaGenerator",
            self._altair_chart(
                chart,
                use_container_width=use_container_width,
                theme="streamlit",
                add_rows_metadata=add_rows_metadata,
                width=width,
                height=height,
            ),
        )

    @gather_metrics("bar_chart")
    def bar_chart(
        self,
        data: Data = None,
        *,
        x: str | None = None,
        y: str | Sequence[str] | None = None,
        x_label: str | None = None,
        y_label: str | None = None,
        color: str | Color | list[Color] | None = None,
        horizontal: bool = False,
        sort: bool | str = True,
        stack: bool | ChartStackType | None = None,
        width: Width = "stretch",
        height: Height = "content",
        use_container_width: bool | None = None,
    ) -> DeltaGenerator:
        """Display a bar chart.

        This is syntax-sugar around ``st.altair_chart``. The main difference
        is this command uses the data's own column and indices to figure out
        the chart's Altair spec. As a result this is easier to use for many
        "just plot this" scenarios, while being less customizable.

        Parameters
        ----------
        data : Anything supported by st.dataframe
            Data to be plotted.

        x : str or None
            Column name or key associated to the x-axis data. If ``x`` is
            ``None`` (default), Streamlit uses the data index for the x-axis
            values.

        y : str, Sequence of str, or None
            Column name(s) or key(s) associated to the y-axis data. If this is
            ``None`` (default), Streamlit draws the data of all remaining
            columns as data series. If this is a ``Sequence`` of strings,
            Streamlit draws several series on the same chart by melting your
            wide-format table into a long-format table behind the scenes.

        x_label : str or None
            The label for the x-axis. If this is ``None`` (default), Streamlit
            will use the column name specified in ``x`` if available, or else
            no label will be displayed.

        y_label : str or None
            The label for the y-axis. If this is ``None`` (default), Streamlit
            will use the column name(s) specified in ``y`` if available, or
            else no label will be displayed.

        color : str, tuple, Sequence of str, Sequence of tuple, or None
            The color to use for different series in this chart.

            For a bar chart with just one series, this can be:

            - None, to use the default color.
            - A hex string like "#ffaa00" or "#ffaa0088".
            - An RGB or RGBA tuple with the red, green, blue, and alpha
              components specified as ints from 0 to 255 or floats from 0.0 to
              1.0.
            - A built-in color name: "red", "orange", "yellow", "green",
              "blue", "violet", "gray"/"grey", or "primary". These map to
              theme colors that you can customize using ``theme.<color>Color``
              configuration options.

            For a bar chart with multiple series, where the dataframe is in
            long format (that is, y is None or just one column), this can be:

            - None, to use the default colors.
            - The name of a column in the dataset. Data points will be grouped
              into series of the same color based on the value of this column.
              In addition, if the values in this column match one of the color
              formats above (hex string or color tuple), then that color will
              be used.

              For example: if the dataset has 1000 rows, but this column only
              contains the values "adult", "child", and "baby", then those 1000
              datapoints will be grouped into three series whose colors will be
              automatically selected from the default palette.

              But, if for the same 1000-row dataset, this column contained
              the values "#ffaa00", "#f0f", "#0000ff", then then those 1000
              datapoints would still be grouped into 3 series, but their
              colors would be "#ffaa00", "#f0f", "#0000ff" this time around.

            For a bar chart with multiple series, where the dataframe is in
            wide format (that is, y is a Sequence of columns), this can be:

            - None, to use the default colors.
            - A list of string colors or color tuples to be used for each of
              the series in the chart. This list should have the same length
              as the number of y values (e.g. ``color=["#fd0", "#f0f", "#04f"]``
              for three lines). You can also use built-in color names in the
              list (e.g. ``color=["red", "blue", "green"]``).

            You can set the default colors in the ``theme.chartCategoryColors``
            configuration option.

        horizontal : bool
            Whether to make the bars horizontal. If this is ``False``
            (default), the bars display vertically. If this is ``True``,
            Streamlit swaps the x-axis and y-axis and the bars display
            horizontally.

        sort : bool or str
            How to sort the bars. This can be one of the following:

            - ``True`` (default): The bars are sorted automatically along the
              independent/categorical axis with Altair's default sorting. This
              also correctly sorts ordered categorical columns
              (``pd.Categorical``).
            - ``False``: The bars are shown in data order without sorting.
            - The name of a column (e.g. ``"col1"``): The bars are sorted by
              that column in ascending order.
            - The name of a column with a minus-sign prefix (e.g. ``"-col1"``):
              The bars are sorted by that column in descending order.

        stack : bool, "normalize", "center", "layered", or None
            Whether to stack the bars. If this is ``None`` (default),
            Streamlit uses Vega's default. Other values can be as follows:

            - ``True``: The bars form a non-overlapping, additive stack within
              the chart.
            - ``False``: The bars display side by side.
            - ``"layered"``: The bars overlap each other without stacking.
            - ``"normalize"``: The bars are stacked and the total height is
              normalized to 100% of the height of the chart.
            - ``"center"``: The bars are stacked and shifted to center the
              total height around an axis.

        width : "stretch", "content", or int
            The width of the chart element. This can be one of the following:

            - ``"stretch"`` (default): The width of the element matches the
              width of the parent container.
            - ``"content"``: The width of the element matches the width of its
              content, but doesn't exceed the width of the parent container.
            - An integer specifying the width in pixels: The element has a
              fixed width. If the specified width is greater than the width of
              the parent container, the width of the element matches the width
              of the parent container.

        height : "stretch", "content", or int
            The height of the chart element. This can be one of the following:

            - ``"content"`` (default): The height of the element matches the
              height of its content.
            - ``"stretch"``: The height of the element matches the height of
              its content or the height of the parent container, whichever is
              larger. If the element is not in a parent container, the height
              of the element matches the height of its content.
            - An integer specifying the height in pixels: The element has a
              fixed height. If the content is larger than the specified
              height, scrolling is enabled.

        use_container_width : bool or None
            Whether to override the chart's native width with the width of
            the parent container. This can be one of the following:

            - ``None`` (default): Streamlit will use the chart's default behavior.
            - ``True``: Streamlit sets the width of the chart to match the
              width of the parent container.
            - ``False``: Streamlit sets the width of the chart to fit its
              contents according to the plotting library, up to the width of
              the parent container.

            .. deprecated::
                ``use_container_width`` is deprecated and will be removed in a
                future release. For ``use_container_width=True``, use
                ``width="stretch"``.

        Examples
        --------
        **Example 1: Basic bar chart from a dataframe**

        If you don't use any of the optional parameters, Streamlit plots each
        column as a series of bars, uses the index as the x values, and labels
        each series with the column name:

        >>> import pandas as pd
        >>> import streamlit as st
        >>> from numpy.random import default_rng as rng
        >>>
        >>> df = pd.DataFrame(rng(0).standard_normal((20, 3)), columns=["a", "b", "c"])
        >>>
        >>> st.bar_chart(df)

        .. output::
           https://doc-bar-chart.streamlit.app/
           height: 440px

        **Example 2: Bar chart from specific dataframe columns**

        You can choose different columns to use for the x and y values. If your
        dataframe is in long format (all y-values in one column), you can set
        the bar colors from another column.

        If the column contains color strings, the colors will be applied
        directly and the series will be unlabeled. If the column contains other
        values, those values will label each series, and the bar colors will be
        selected from the default color palette. You can configure this color
        palette in the ``theme.chartCategoryColors`` configuration option.

        >>> import pandas as pd
        >>> import streamlit as st
        >>> from numpy.random import default_rng as rng
        >>>
        >>> df = pd.DataFrame(
        ...     {
        ...         "col1": list(range(20)) * 3,
        ...         "col2": rng(0).standard_normal(60),
        ...         "col3": ["a"] * 20 + ["b"] * 20 + ["c"] * 20,
        ...     }
        ... )
        >>>
        >>> st.bar_chart(df, x="col1", y="col2", color="col3")

        .. output::
           https://doc-bar-chart1.streamlit.app/
           height: 440px

        **Example 3: Bar chart from wide-format dataframe**

        If your dataframe is in wide format (y-values are in multiple columns),
        you can pass a list of columns to the ``y`` parameter. Each column
        name becomes a series label. To override the default colors, pass a
        list of colors to the ``color`` parameter, one for each series:

        >>> import pandas as pd
        >>> import streamlit as st
        >>> from numpy.random import default_rng as rng
        >>>
        >>> df = pd.DataFrame(
        ...     {
        ...         "col1": list(range(20)),
        ...         "col2": rng(0).standard_normal(20),
        ...         "col3": rng(1).standard_normal(20),
        ...     }
        ... )
        >>>
        >>> st.bar_chart(
        ...     df,
        ...     x="col1",
        ...     y=["col2", "col3"],
        ...     color=["#FF0000", "#0000FF"],
        ... )

        .. output::
           https://doc-bar-chart2.streamlit.app/
           height: 440px

        **Example 4: Horizontal bar chart**

        You can use the ``horizontal`` parameter to display horizontal bars
        instead of vertical bars. This is useful when you have long labels on
        the x-axis, or when you want to display a large number of categories.
        This example requires ``vega_datasets`` to be installed.

        >>> import streamlit as st
        >>> from vega_datasets import data
        >>>
        >>> source = data.barley()
        >>>
        >>> st.bar_chart(source, x="variety", y="yield", color="site", horizontal=True)

        .. output::
           https://doc-bar-chart-horizontal.streamlit.app/
           height: 440px

        **Example 5: Unstacked bar chart**

        You can configure the stacking behavior of the bars by setting the
        ``stack`` parameter. Set it to ``False`` to display bars side by side.
        This example requires ``vega_datasets`` to be installed.

        >>> import streamlit as st
        >>> from vega_datasets import data
        >>>
        >>> source = data.barley()
        >>>
        >>> st.bar_chart(source, x="year", y="yield", color="site", stack=False)

        .. output::
           https://doc-bar-chart-unstacked.streamlit.app/
           height: 440px

        """
        # Check that the stack parameter is valid, raise more informative error message if not
        maybe_raise_stack_warning(
            stack,
            "st.bar_chart",
            "https://docs.streamlit.io/develop/api-reference/charts/st.bar_chart",
        )

        # Offset encodings (used for non-stacked/grouped bar charts) are not supported in Altair < 5.0.0
        if type_util.is_altair_version_less_than("5.0.0") and stack is False:
            raise StreamlitAPIException(
                "Streamlit does not support non-stacked (grouped) bar charts with "
                "Altair 4.x. Please upgrade to Version 5."
            )

        bar_chart_type = (
            ChartType.HORIZONTAL_BAR if horizontal else ChartType.VERTICAL_BAR
        )

        chart, add_rows_metadata = generate_chart(
            chart_type=bar_chart_type,
            data=data,
            x_from_user=x,
            y_from_user=y,
            x_axis_label=x_label,
            y_axis_label=y_label,
            color_from_user=color,
            size_from_user=None,
            width=width,
            height=height,
            use_container_width=use_container_width,
            stack=stack,
            horizontal=horizontal,
            sort_from_user=sort,
        )
        return cast(
            "DeltaGenerator",
            self._altair_chart(
                chart,
                use_container_width=use_container_width,
                theme="streamlit",
                add_rows_metadata=add_rows_metadata,
                width=width,
                height=height,
            ),
        )

    @gather_metrics("scatter_chart")
    def scatter_chart(
        self,
        data: Data = None,
        *,
        x: str | None = None,
        y: str | Sequence[str] | None = None,
        x_label: str | None = None,
        y_label: str | None = None,
        color: str | Color | list[Color] | None = None,
        size: str | float | int | None = None,
        width: Width = "stretch",
        height: Height = "content",
        use_container_width: bool | None = None,
    ) -> DeltaGenerator:
        """Display a scatterplot chart.

        This is syntax-sugar around ``st.altair_chart``. The main difference
        is this command uses the data's own column and indices to figure out
        the chart's Altair spec. As a result this is easier to use for many
        "just plot this" scenarios, while being less customizable.

        Parameters
        ----------
        data : Anything supported by st.dataframe
            Data to be plotted.

        x : str or None
            Column name or key associated to the x-axis data. If ``x`` is
            ``None`` (default), Streamlit uses the data index for the x-axis
            values.

        y : str, Sequence of str, or None
            Column name(s) or key(s) associated to the y-axis data. If this is
            ``None`` (default), Streamlit draws the data of all remaining
            columns as data series. If this is a ``Sequence`` of strings,
            Streamlit draws several series on the same chart by melting your
            wide-format table into a long-format table behind the scenes.

        x_label : str or None
            The label for the x-axis. If this is ``None`` (default), Streamlit
            will use the column name specified in ``x`` if available, or else
            no label will be displayed.

        y_label : str or None
            The label for the y-axis. If this is ``None`` (default), Streamlit
            will use the column name(s) specified in ``y`` if available, or
            else no label will be displayed.

        color : str, tuple, Sequence of str, Sequence of tuple, or None
            The color of the circles representing each datapoint.

            This can be:

            - None, to use the default color.
            - A hex string like "#ffaa00" or "#ffaa0088".
            - An RGB or RGBA tuple with the red, green, blue, and alpha
              components specified as ints from 0 to 255 or floats from 0.0 to
              1.0.
            - A built-in color name: "red", "orange", "yellow", "green",
              "blue", "violet", "gray"/"grey", or "primary". These map to
              theme colors that you can customize using ``theme.<color>Color``
              configuration options.
            - The name of a column in the dataset where the color of that
              datapoint will come from.

              If the values in this column are in one of the color formats
              above (hex string or color tuple), then that color will be used.

              Otherwise, the color will be automatically picked from the
              default palette.

              For example: if the dataset has 1000 rows, but this column only
              contains the values "adult", "child", and "baby", then those 1000
              datapoints be shown using three colors from the default palette.

              But if this column only contains floats or ints, then those
              1000 datapoints will be shown using a colors from a continuous
              color gradient.

              Finally, if this column only contains the values "#ffaa00",
              "#f0f", "#0000ff", then then each of those 1000 datapoints will
              be assigned "#ffaa00", "#f0f", or "#0000ff" as appropriate.

            If the dataframe is in wide format (that is, y is a Sequence of
            columns), this can also be:

            - A list of string colors or color tuples to be used for each of
              the series in the chart. This list should have the same length
              as the number of y values (e.g. ``color=["#fd0", "#f0f", "#04f"]``
              for three series). You can also use built-in color names in the
              list (e.g. ``color=["red", "blue", "green"]``).

        size : str, float, int, or None
            The size of the circles representing each point.

            This can be:

            - A number like 100, to specify a single size to use for all
              datapoints.
            - The name of the column to use for the size. This allows each
              datapoint to be represented by a circle of a different size.

        width : "stretch", "content", or int
            The width of the chart element. This can be one of the following:

            - ``"stretch"`` (default): The width of the element matches the
              width of the parent container.
            - ``"content"``: The width of the element matches the width of its
              content, but doesn't exceed the width of the parent container.
            - An integer specifying the width in pixels: The element has a
              fixed width. If the specified width is greater than the width of
              the parent container, the width of the element matches the width
              of the parent container.

        height : "stretch", "content", or int
            The height of the chart element. This can be one of the following:

            - ``"content"`` (default): The height of the element matches the
              height of its content.
            - ``"stretch"``: The height of the element matches the height of
              its content or the height of the parent container, whichever is
              larger. If the element is not in a parent container, the height
              of the element matches the height of its content.
            - An integer specifying the height in pixels: The element has a
              fixed height. If the content is larger than the specified
              height, scrolling is enabled.

        use_container_width : bool or None
            Whether to override the chart's native width with the width of
            the parent container. This can be one of the following:

            - ``None`` (default): Streamlit will use the chart's default behavior.
            - ``True``: Streamlit sets the width of the chart to match the
              width of the parent container.
            - ``False``: Streamlit sets the width of the chart to fit its
              contents according to the plotting library, up to the width of
              the parent container.

            .. deprecated::
                ``use_container_width`` is deprecated and will be removed in a
                future release. For ``use_container_width=True``, use
                ``width="stretch"``.

        Examples
        --------
        **Example 1: Basic scatter chart from a dataframe**

        If you don't use any of the optional parameters, Streamlit plots each
        column as a color-coded group of points, uses the index as the x
        values, and labels each group with the column name:

        >>> import pandas as pd
        >>> import streamlit as st
        >>> from numpy.random import default_rng as rng
        >>>
        >>> df = pd.DataFrame(rng(0).standard_normal((20, 3)), columns=["a", "b", "c"])
        >>>
        >>> st.scatter_chart(df)

        .. output::
           https://doc-scatter-chart.streamlit.app/
           height: 440px

        **Example 2: Scatter chart from specific dataframe columns**

        You can choose different columns to use for the x and y values. If your
        dataframe is in long format (all y-values in one column), you can set
        the scatter point colors from another column.

        If the column contains color strings, the colors will be applied
        directly and each color group will be unlabeled. If the column contains
        other values, those values will label each group, and the scatter point
        colors will be selected from the default color palette. You can
        configure this color palette in the ``theme.chartCategoryColors``
        configuration option.

        >>> import pandas as pd
        >>> import streamlit as st
        >>> from numpy.random import default_rng as rng
        >>>
        >>> df = pd.DataFrame(
        ...     rng(0).standard_normal((20, 3)), columns=["col1", "col2", "col3"]
        ... )
        >>> df["col4"] = rng(0).choice(["a", "b", "c"], 20)
        >>>
        >>> st.scatter_chart(
        ...     df,
        ...     x="col1",
        ...     y="col2",
        ...     color="col4",
        ...     size="col3",
        ... )

        .. output::
           https://doc-scatter-chart1.streamlit.app/
           height: 440px

        **Example 3: Scatter chart from wide-format dataframe**

        If your dataframe is in wide format (y-values are in multiple columns),
        you can pass a list of columns to the ``y`` parameter. Each column
        name becomes a group label. To override the default colors, pass a
        list of colors to the ``color`` parameter, one for each group:

        >>> import pandas as pd
        >>> import streamlit as st
        >>> from numpy.random import default_rng as rng
        >>>
        >>> df = pd.DataFrame(
        ...     rng(0).standard_normal((20, 4)),
        ...     columns=["col1", "col2", "col3", "col4"],
        ... )
        >>>
        >>> st.scatter_chart(
        ...     df,
        ...     x="col1",
        ...     y=["col2", "col3"],
        ...     size="col4",
        ...     color=["#FF0000", "#0000FF"],
        ... )

        .. output::
           https://doc-scatter-chart2.streamlit.app/
           height: 440px

        """
        chart, add_rows_metadata = generate_chart(
            chart_type=ChartType.SCATTER,
            data=data,
            x_from_user=x,
            y_from_user=y,
            x_axis_label=x_label,
            y_axis_label=y_label,
            color_from_user=color,
            size_from_user=size,
            width=width,
            height=height,
            use_container_width=(width == "stretch"),
        )
        return cast(
            "DeltaGenerator",
            self._altair_chart(
                chart,
                use_container_width=use_container_width,
                theme="streamlit",
                add_rows_metadata=add_rows_metadata,
                width=width,
                height=height,
            ),
        )

    # When on_select=Ignore, return DeltaGenerator.
    @overload
    def altair_chart(
        self,
        altair_chart: AltairChart,
        *,
        width: Width | None = None,
        height: Height = "content",
        use_container_width: bool | None = None,
        theme: Literal["streamlit"] | None = "streamlit",
        key: Key | None = None,
        on_select: Literal["ignore"] = "ignore",
        selection_mode: str | Iterable[str] | None = None,
    ) -> DeltaGenerator: ...

    # When on_select=rerun, return VegaLiteState.
    @overload
    def altair_chart(
        self,
        altair_chart: AltairChart,
        *,
        width: Width | None = None,
        height: Height = "content",
        use_container_width: bool | None = None,
        theme: Literal["streamlit"] | None = "streamlit",
        key: Key | None = None,
        on_select: Literal["rerun"] | WidgetCallback,
        selection_mode: str | Iterable[str] | None = None,
    ) -> VegaLiteState: ...

    @gather_metrics("altair_chart")
    def altair_chart(
        self,
        altair_chart: AltairChart,
        *,
        width: Width | None = None,
        height: Height = "content",
        use_container_width: bool | None = None,
        theme: Literal["streamlit"] | None = "streamlit",
        key: Key | None = None,
        on_select: Literal["rerun", "ignore"] | WidgetCallback = "ignore",
        selection_mode: str | Iterable[str] | None = None,
    ) -> DeltaGenerator | VegaLiteState:
        """Display a chart using the Vega-Altair library.

        `Vega-Altair <https://altair-viz.github.io/>`_ is a declarative
        statistical visualization library for Python, based on Vega and
        Vega-Lite.

        Parameters
        ----------
        altair_chart : altair.Chart
            The Altair chart object to display. See
            https://altair-viz.github.io/gallery/ for examples of graph
            descriptions.

        width : "stretch", "content", int, or None
            The width of the chart element. This can be one of the following:

            - ``"stretch"``: The width of the element matches the width of the
              parent container.
            - ``"content"``: The width of the element matches the width of its
              content, but doesn't exceed the width of the parent container.
            - An integer specifying the width in pixels: The element has a
              fixed width. If the specified width is greater than the width of
              the parent container, the width of the element matches the width
              of the parent container.
            - ``None`` (default): Streamlit uses ``"stretch"`` for most charts,
              and uses ``"content"`` for the following multi-view charts:

                - Facet charts: the spec contains ``"facet"`` or encodings for
                  ``"row"``, ``"column"``, or ``"facet"``.
                - Horizontal concatenation charts: the spec contains
                  ``"hconcat"``.
                - Repeat charts: the spec contains ``"repeat"``.
                - Nested composition charts: the spec contains ``"vconcat"``
                  with nested ``"hconcat"``, ``"vconcat"``, ``"concat"``, or
                  ``"layer"`` operators (e.g., scatter plots with marginal
                  histograms).

        height : "content", "stretch", or int
            The height of the chart element. This can be one of the following:

            - ``"content"`` (default): The height of the element matches the
              height of its content.
            - ``"stretch"``: The height of the element matches the height of
              its content or the height of the parent container, whichever is
              larger. If the element is not in a parent container, the height
              of the element matches the height of its content.
            - An integer specifying the height in pixels: The element has a
              fixed height. If the content is larger than the specified
              height, scrolling is enabled.

        use_container_width : bool or None
            Whether to override the chart's native width with the width of
            the parent container. This can be one of the following:

            - ``None`` (default): Streamlit will use the parent container's
              width for all charts except those with known incompatibility
              (``altair.Facet``, ``altair.HConcatChart``, and
              ``altair.RepeatChart``).
            - ``True``: Streamlit sets the width of the chart to match the
              width of the parent container.
            - ``False``: Streamlit sets the width of the chart to fit its
              contents according to the plotting library, up to the width of
              the parent container.

            .. deprecated::
                ``use_container_width`` is deprecated and will be removed in a
                future release. For ``use_container_width=True``, use
                ``width="stretch"``.

        theme : "streamlit" or None
            The theme of the chart. If ``theme`` is ``"streamlit"`` (default),
            Streamlit uses its own design default. If ``theme`` is ``None``,
            Streamlit falls back to the default behavior of the library.

            The ``"streamlit"`` theme can be partially customized through the
            configuration options ``theme.chartCategoricalColors`` and
            ``theme.chartSequentialColors``. Font configuration options are
            also applied.

        key : str, int, or None
            An optional string to use for giving this element a stable
            identity. If this is ``None`` (default), the element's identity
            will be determined based on the values of the other parameters.

            Additionally, if selections are activated and ``key`` is provided,
            Streamlit will register the key in Session State to store the
            selection state. The selection state is read-only. For more
            details, see `Widget behavior
            <https://docs.streamlit.io/develop/concepts/architecture/widget-behavior>`_.

            Additionally, if ``key`` is provided, it will be used as a
            CSS class name prefixed with ``st-key-``.

        on_select : "ignore", "rerun", or callable
            How the figure should respond to user selection events. This
            controls whether or not the figure behaves like an input widget.
            ``on_select`` can be one of the following:

            - ``"ignore"`` (default): Streamlit will not react to any selection
              events in the chart. The figure will not behave like an input
              widget.

            - ``"rerun"``: Streamlit will rerun the app when the user selects
              data in the chart. In this case, ``st.altair_chart`` will return
              the selection data as a dictionary.

            - A ``callable``: Streamlit will rerun the app and execute the
              ``callable`` as a callback function before the rest of the app.
              In this case, ``st.altair_chart`` will return the selection data
              as a dictionary.

            To use selection events, the object passed to ``altair_chart`` must
            include selection parameters. To learn about defining interactions
            in Altair and how to declare selection-type parameters, see
            `Interactive Charts \
            <https://altair-viz.github.io/user_guide/interactions.html>`_
            in Altair's documentation.

            For consistent selection output, especially in multi-view charts
            (layer, hconcat, vconcat, facet, repeat), specify ``fields`` or
            ``encodings`` in your selection, like
            ``alt.selection_point(fields=["Origin"])`` or
            ``alt.selection_point(encodings=["x", "y"])``. Without explicit
            fields, Vega may add an internal row identifier field (``vgsid``)
            to your data, and selections can then return this identifier
            instead of your original data values.

        selection_mode : str or Iterable of str
            The selection parameters Streamlit should use. If
            ``selection_mode`` is ``None`` (default), Streamlit will use all
            selection parameters defined in the chart's Altair spec.

            When Streamlit uses a selection parameter, selections from that
            parameter will trigger a rerun and be included in the selection
            state. When Streamlit does not use a selection parameter,
            selections from that parameter will not trigger a rerun and not be
            included in the selection state.

            Selection parameters are identified by their ``name`` property.

        Returns
        -------
        element or dict
            If ``on_select`` is ``"ignore"`` (default), this command returns an
            internal placeholder for the chart element that can be used with
            the ``.add_rows()`` method. Otherwise, this command returns a
            dictionary-like object that supports both key and attribute
            notation. The attributes are described by the ``VegaLiteState``
            dictionary schema.

        Examples
        --------
        >>> import altair as alt
        >>> import pandas as pd
        >>> import streamlit as st
        >>> from numpy.random import default_rng as rng
        >>>
        >>> df = pd.DataFrame(rng(0).standard_normal((60, 3)), columns=["a", "b", "c"])
        >>>
        >>> chart = (
        ...     alt.Chart(df)
        ...     .mark_circle()
        ...     .encode(x="a", y="b", size="c", color="c", tooltip=["a", "b", "c"])
        ... )
        >>>
        >>> st.altair_chart(chart)

        .. output::
           https://doc-vega-lite-chart.streamlit.app/
           height: 450px

        """
        return self._altair_chart(
            altair_chart=altair_chart,
            width=width,
            height=height,
            use_container_width=use_container_width,
            theme=theme,
            key=key,
            on_select=on_select,
            selection_mode=selection_mode,
        )

    # When on_select=Ignore, return DeltaGenerator.
    @overload
    def vega_lite_chart(
        self,
        data: Data = None,
        spec: VegaLiteSpec | None = None,
        *,
        width: Width | None = None,
        height: Height = "content",
        use_container_width: bool | None = None,
        theme: Literal["streamlit"] | None = "streamlit",
        key: Key | None = None,
        on_select: Literal["ignore"] = "ignore",
        selection_mode: str | Iterable[str] | None = None,
    ) -> DeltaGenerator: ...

    # When on_select=rerun, return VegaLiteState.
    @overload
    def vega_lite_chart(
        self,
        data: Data = None,
        spec: VegaLiteSpec | None = None,
        *,
        width: Width | None = None,
        height: Height = "content",
        use_container_width: bool | None = None,
        theme: Literal["streamlit"] | None = "streamlit",
        key: Key | None = None,
        on_select: Literal["rerun"] | WidgetCallback,
        selection_mode: str | Iterable[str] | None = None,
    ) -> VegaLiteState: ...

    @gather_metrics("vega_lite_chart")
    def vega_lite_chart(
        self,
        data: Data = None,
        spec: VegaLiteSpec | None = None,
        *,
        width: Width | None = None,
        height: Height = "content",
        use_container_width: bool | None = None,
        theme: Literal["streamlit"] | None = "streamlit",
        key: Key | None = None,
        on_select: Literal["rerun", "ignore"] | WidgetCallback = "ignore",
        selection_mode: str | Iterable[str] | None = None,
    ) -> DeltaGenerator | VegaLiteState:
        """Display a chart using the Vega-Lite library.

        `Vega-Lite <https://vega.github.io/vega-lite/>`_ is a high-level
        grammar for defining interactive graphics.

        Parameters
        ----------
        data : Anything supported by st.dataframe
            Either the data to be plotted or a Vega-Lite spec containing the
            data (which more closely follows the Vega-Lite API).

        spec : dict or None
            The Vega-Lite spec for the chart. If ``spec`` is ``None`` (default),
            Streamlit uses the spec passed in ``data``. You cannot pass a spec
            to both ``data`` and ``spec``. See
            https://vega.github.io/vega-lite/docs/ for more info.

        width : "stretch", "content", int, or None
            The width of the chart element. This can be one of the following:

            - ``"stretch"``: The width of the element matches the width of the
              parent container.
            - ``"content"``: The width of the element matches the width of its
              content, but doesn't exceed the width of the parent container.
            - An integer specifying the width in pixels: The element has a
              fixed width. If the specified width is greater than the width of
              the parent container, the width of the element matches the width
              of the parent container.
            - ``None`` (default): Streamlit uses ``"stretch"`` for most charts,
              and uses ``"content"`` for the following multi-view charts:

                - Facet charts: the spec contains ``"facet"`` or encodings for
                  ``"row"``, ``"column"``, or ``"facet"``.
                - Horizontal concatenation charts: the spec contains
                  ``"hconcat"``.
                - Repeat charts: the spec contains ``"repeat"``.
                - Nested composition charts: the spec contains ``"vconcat"``
                  with nested ``"hconcat"``, ``"vconcat"``, ``"concat"``, or
                  ``"layer"`` operators (e.g., scatter plots with marginal
                  histograms).

        height : "content", "stretch", or int
            The height of the chart element. This can be one of the following:

            - ``"content"`` (default): The height of the element matches the
              height of its content.
            - ``"stretch"``: The height of the element matches the height of
              its content or the height of the parent container, whichever is
              larger. If the element is not in a parent container, the height
              of the element matches the height of its content.
            - An integer specifying the height in pixels: The element has a
              fixed height. If the content is larger than the specified
              height, scrolling is enabled.

        use_container_width : bool or None
            Whether to override the chart's native width with the width of
            the parent container. This can be one of the following:

            - ``None`` (default): Streamlit will use the parent container's
              width for all charts except those with known incompatibility
              (``altair.Facet``, ``altair.HConcatChart``, and
              ``altair.RepeatChart``).
            - ``True``: Streamlit sets the width of the chart to match the
              width of the parent container.
            - ``False``: Streamlit sets the width of the chart to fit its
              contents according to the plotting library, up to the width of
              the parent container.

            .. deprecated::
                ``use_container_width`` is deprecated and will be removed in a
                future release. For ``use_container_width=True``, use
                ``width="stretch"``.

        theme : "streamlit" or None
            The theme of the chart. If ``theme`` is ``"streamlit"`` (default),
            Streamlit uses its own design default. If ``theme`` is ``None``,
            Streamlit falls back to the default behavior of the library.

            The ``"streamlit"`` theme can be partially customized through the
            configuration options ``theme.chartCategoricalColors`` and
            ``theme.chartSequentialColors``. Font configuration options are
            also applied.

        key : str, int, or None
            An optional string to use for giving this element a stable
            identity. If this is ``None`` (default), the element's identity
            will be determined based on the values of the other parameters.

            If selections are activated and ``key`` is provided,
            Streamlit will register the key in Session State to store the
            selection state. The selection state is read-only. For more
            details, see `Widget behavior
            <https://docs.streamlit.io/develop/concepts/architecture/widget-behavior>`_.

            Additionally, if ``key`` is provided, it will be used as a
            CSS class name prefixed with ``st-key-``.

        on_select : "ignore", "rerun", or callable
            How the figure should respond to user selection events. This
            controls whether or not the figure behaves like an input widget.
            ``on_select`` can be one of the following:

            - ``"ignore"`` (default): Streamlit will not react to any selection
              events in the chart. The figure will not behave like an input
              widget.

            - ``"rerun"``: Streamlit will rerun the app when the user selects
              data in the chart. In this case, ``st.vega_lite_chart`` will
              return the selection data as a dictionary.

            - A ``callable``: Streamlit will rerun the app and execute the
              ``callable`` as a callback function before the rest of the app.
              In this case, ``st.vega_lite_chart`` will return the selection data
              as a dictionary.

            To use selection events, the Vega-Lite spec defined in ``data`` or
            ``spec`` must include selection parameters from the charting
            library. To learn about defining interactions in Vega-Lite, see
            `Dynamic Behaviors with Parameters \
            <https://vega.github.io/vega-lite/docs/parameter.html>`_
            in Vega-Lite's documentation.

            For consistent selection output, especially in multi-view charts
            (layer, hconcat, vconcat, facet, repeat), specify ``fields`` or
            ``encodings`` in your selection, like
            ``alt.selection_point(fields=["Origin"])`` or
            ``alt.selection_point(encodings=["x", "y"])``. Without explicit
            fields, Vega may add an internal row identifier field (``vgsid``)
            to your data, and selections can then return this identifier
            instead of your original data values.

        selection_mode : str or Iterable of str
            The selection parameters Streamlit should use. If
            ``selection_mode`` is ``None`` (default), Streamlit will use all
            selection parameters defined in the chart's Vega-Lite spec.

            When Streamlit uses a selection parameter, selections from that
            parameter will trigger a rerun and be included in the selection
            state. When Streamlit does not use a selection parameter,
            selections from that parameter will not trigger a rerun and not be
            included in the selection state.

            Selection parameters are identified by their ``name`` property.

        Returns
        -------
        element or dict
            If ``on_select`` is ``"ignore"`` (default), this command returns an
            internal placeholder for the chart element that can be used with
            the ``.add_rows()`` method. Otherwise, this command returns a
            dictionary-like object that supports both key and attribute
            notation. The attributes are described by the ``VegaLiteState``
            dictionary schema.

        Examples
        --------
        >>> import pandas as pd
        >>> import streamlit as st
        >>> from numpy.random import default_rng as rng
        >>>
        >>> df = pd.DataFrame(rng(0).standard_normal((60, 3)), columns=["a", "b", "c"])
        >>>
        >>> st.vega_lite_chart(
        ...     df,
        ...     {
        ...         "mark": {"type": "circle", "tooltip": True},
        ...         "encoding": {
        ...             "x": {"field": "a", "type": "quantitative"},
        ...             "y": {"field": "b", "type": "quantitative"},
        ...             "size": {"field": "c", "type": "quantitative"},
        ...             "color": {"field": "c", "type": "quantitative"},
        ...         },
        ...     },
        ... )

        .. output::
           https://doc-vega-lite-chart.streamlit.app/
           height: 450px

        Examples of Vega-Lite usage without Streamlit can be found at
        https://vega.github.io/vega-lite/examples/. Most of those can be easily
        translated to the syntax shown above.

        """
        return self._vega_lite_chart(
            data=data,
            spec=spec,
            use_container_width=use_container_width,
            theme=theme,
            key=key,
            on_select=on_select,
            selection_mode=selection_mode,
            width=width,
            height=height,
        )

    def _altair_chart(
        self,
        altair_chart: AltairChart,
        use_container_width: bool | None = None,
        theme: Literal["streamlit"] | None = "streamlit",
        key: Key | None = None,
        on_select: Literal["rerun", "ignore"] | WidgetCallback = "ignore",
        selection_mode: str | Iterable[str] | None = None,
        add_rows_metadata: AddRowsMetadata | None = None,
        width: Width | None = None,
        height: Height = "content",
    ) -> DeltaGenerator | VegaLiteState:
        """Internal method to enqueue a vega-lite chart element based on an Altair chart.

        See the `altair_chart` method docstring for more information.
        """

        if type_util.is_altair_version_less_than("5.0.0") and on_select != "ignore":
            raise StreamlitAPIException(
                "Streamlit does not support selections with Altair 4.x. Please upgrade "
                "to Version 5. "
                "If you would like to use Altair 4.x with selections, please upvote "
                "this [Github issue](https://github.com/streamlit/streamlit/issues/8516)."
            )

        vega_lite_spec = _convert_altair_to_vega_lite_spec(altair_chart)
        return self._vega_lite_chart(
            data=None,  # The data is already part of the spec
            spec=vega_lite_spec,
            use_container_width=use_container_width,
            theme=theme,
            key=key,
            on_select=on_select,
            selection_mode=selection_mode,
            add_rows_metadata=add_rows_metadata,
            width=width,
            height=height,
        )

    def _vega_lite_chart(
        self,
        data: Data = None,
        spec: VegaLiteSpec | None = None,
        use_container_width: bool | None = None,
        theme: Literal["streamlit"] | None = "streamlit",
        key: Key | None = None,
        on_select: Literal["rerun", "ignore"] | WidgetCallback = "ignore",
        selection_mode: str | Iterable[str] | None = None,
        add_rows_metadata: AddRowsMetadata | None = None,
        width: Width | None = None,
        height: Height = "content",
    ) -> DeltaGenerator | VegaLiteState:
        """Internal method to enqueue a vega-lite chart element based on a vega-lite spec.

        See the `vega_lite_chart` method docstring for more information.
        """
        if theme not in {"streamlit", None}:
            raise StreamlitAPIException(
                f'You set theme="{theme}" while Streamlit charts only support '
                "theme=”streamlit” or theme=None to fallback to the default "
                "library theme."
            )

        if on_select not in {"ignore", "rerun"} and not callable(on_select):
            raise StreamlitAPIException(
                f"You have passed {on_select} to `on_select`. But only 'ignore', "
                "'rerun', or a callable is supported."
            )

        key = to_key(key)
        is_selection_activated = on_select != "ignore"

        if is_selection_activated:
            # Run some checks that are only relevant when selections are activated

            is_callback = callable(on_select)
            check_widget_policies(
                self.dg,
                key,
                on_change=cast("WidgetCallback", on_select) if is_callback else None,
                default_value=None,
                writes_allowed=False,
                enable_check_callback_rules=is_callback,
            )

        # Support passing data inside spec['datasets'] and spec['data'].
        # (The data gets pulled out of the spec dict later on.)
        if isinstance(data, dict) and spec is None:
            spec = data
            data = None

        if spec is None:
            spec = {}

        # Set the default value for width. Altair and Vega charts have different defaults depending on the chart type,
        # so they don't default the value in the function signature and width could be None here.
        if use_container_width is None and width is None:
            # Some multi-view charts (facet, horizontal concatenation, and repeat;
            # see https://altair-viz.github.io/user_guide/compound_charts.html)
            # don't work well with `width=stretch`, so we disable it for
            # those charts (see https://github.com/streamlit/streamlit/issues/9091).
            # All other charts (including vertical concatenation) default to
            # `width=stretch` unless width is provided.
            # Nested vconcat+hconcat charts (issue #13410) also don't work well
            # with width=stretch, so we treat them like hconcat charts.
            is_facet_chart = "facet" in spec or (
                "encoding" in spec
                and (any(x in spec["encoding"] for x in ["row", "column", "facet"]))
            )
            has_nested_comp = _has_nested_composition(spec)
            width = (
                "stretch"
                if not (
                    is_facet_chart
                    or "hconcat" in spec
                    or "repeat" in spec
                    or has_nested_comp
                )
                else "content"
            )

        if use_container_width is not None:
            show_deprecation_warning(
                make_deprecated_name_warning(
                    "use_container_width",
                    "width",
                    "2025-12-31",
                    "For `use_container_width=True`, use `width='stretch'`. "
                    "For `use_container_width=False`, use `width='content'` or specify an integer width.",
                    include_st_prefix=False,
                ),
                show_in_browser=False,
            )
            if use_container_width:
                width = "stretch"
            elif not isinstance(width, int):
                # No specific width provided, use content width
                width = "content"
                # Otherwise keep the integer width - user explicitly set both use_container_width=False and width=int

        if width is not None:
            validate_width(width, allow_content=True)
        validate_height(height, allow_content=True)

        vega_lite_proto = VegaLiteChartProto()

        use_container_width_for_spec = (
            use_container_width
            if use_container_width is not None
            else width == "stretch"
        )

        spec = _prepare_vega_lite_spec(spec, use_container_width_for_spec)
        _marshall_chart_data(vega_lite_proto, spec, data)

        # Prevent the spec from changing across reruns:
        vega_lite_proto.spec = _stabilize_vega_json_spec(json.dumps(spec))

        if use_container_width is not None:
            vega_lite_proto.use_container_width = use_container_width
        vega_lite_proto.theme = theme or ""

        if is_selection_activated:
            # Load the stabilized spec again as a dict:
            final_spec = json.loads(vega_lite_proto.spec)

            # Parse and check the specified selection modes
            parsed_selection_modes = _parse_selection_mode(final_spec, selection_mode)
            vega_lite_proto.selection_mode.extend(parsed_selection_modes)

            vega_lite_proto.form_id = current_form_id(self.dg)

            ctx = get_script_run_ctx()
            vega_lite_proto.id = compute_and_register_element_id(
                "vega_lite_chart",
                user_key=key,
                # There are some edge cases where selections can become orphaned when the data changes.
                #  The frontend can handle this without errors, but it might be a nice enhancement
                # to automatically reset the backend & frontend selection state in this case.
                key_as_main_identity={"selection_mode"},
                dg=self.dg,
                vega_lite_spec=vega_lite_proto.spec,
                # The data is either in vega_lite_proto.data.data
                # or in a named dataset in vega_lite_proto.datasets
                vega_lite_data=vega_lite_proto.data.data,
                # Its enough to just use the names here since they are expected
                # to contain hashes based on the dataset data.
                named_datasets=[dataset.name for dataset in vega_lite_proto.datasets],
                theme=theme,
                use_container_width=use_container_width,
                selection_mode=parsed_selection_modes,
            )

            serde = VegaLiteStateSerde(parsed_selection_modes)

            widget_state = register_widget(
                vega_lite_proto.id,
                on_change_handler=on_select if callable(on_select) else None,
                deserializer=serde.deserialize,
                serializer=serde.serialize,
                ctx=ctx,
                value_type="string_value",
            )

            layout_config = LayoutConfig(width=width, height=height)
            self.dg._enqueue(
                "vega_lite_chart",
                vega_lite_proto,
                add_rows_metadata=add_rows_metadata,
                layout_config=layout_config,
            )
            return widget_state.value

        # If its not used with selections activated, just return
        # the delta generator related to this element.
        layout_config = LayoutConfig(width=width, height=height)
        return self.dg._enqueue(
            "vega_lite_chart",
            vega_lite_proto,
            add_rows_metadata=add_rows_metadata,
            layout_config=layout_config,
        )

    @property
    def dg(self) -> DeltaGenerator:
        """Get our DeltaGenerator."""
        return cast("DeltaGenerator", self)


def _to_arrow_dataset(data: Any, datasets: dict[str, Any]) -> dict[str, str]:
    """Altair data transformer that serializes the data,
    creates a stable name based on the hash of the data,
    stores the bytes into the datasets mapping and
    returns this name to have it be used in Altair.
    """
    # Already serialize the data to be able to create a stable
    # dataset name:
    data_bytes = dataframe_util.convert_anything_to_arrow_bytes(data)
    # Use the content hash of the data as the name:
    name = calc_hash(str(data_bytes))

    datasets[name] = data_bytes
    return {"name": name}
