Skip to content

Commit

Permalink
Stacking options - st.area_chart (streamlit#8992)
Browse files Browse the repository at this point in the history
PR extends flexibility of `st.area_chart` by adding a new parameter
`stack` to allow explicit control over multiple stacking options:
- True: Stacked areas (generally the default/current behavior)
- False: Layered area chart
-"normalize": Stacked areas with normalized domain
- "center": Steamgraph/Stacked areas with center baseline
  • Loading branch information
mayagbarnes authored Jul 10, 2024
1 parent 17876a2 commit 99b5676
Show file tree
Hide file tree
Showing 17 changed files with 69 additions and 13 deletions.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
10 changes: 10 additions & 0 deletions e2e_playwright/st_area_chart.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@

import numpy as np
import pandas as pd
from vega_datasets import data as vega_data

import streamlit as st

Expand Down Expand Up @@ -69,3 +70,12 @@
st.area_chart(utc_df)
st.area_chart(color_df, x="a", y="b", color="e")
st.area_chart(df, x_label="X Axis Label", y_label="Y Axis Label")

# Additional tests for stacking options
np.random.seed(5)
df = pd.DataFrame(np.random.randn(20, 3), columns=["a", "b", "c"])
st.area_chart(df, color=["#ffaa00", "#3399ff", "#009900"], stack=False)
source = vega_data.unemployment_across_industries()
st.area_chart(source, x="date", y="count", color="series", stack=True)
st.area_chart(source, x="date", y="count", color="series", stack="normalize")
st.area_chart(source, x="date", y="count", color="series", stack="center")
2 changes: 1 addition & 1 deletion e2e_playwright/st_area_chart_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

from e2e_playwright.conftest import ImageCompareFunction

TOTAL_AREA_CHARTS = 11
TOTAL_AREA_CHARTS = 15


def test_area_chart_rendering(app: Page, assert_snapshot: ImageCompareFunction):
Expand Down
16 changes: 14 additions & 2 deletions lib/streamlit/elements/lib/built_in_chart_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,17 @@ class ChartType(Enum):
_NON_EXISTENT_COLUMN_NAME: Final = "DOES_NOT_EXIST" + _PROTECTION_SUFFIX


def maybe_raise_stack_warning(
stack: bool | ChartStackType | None, command: str | None, docs_link: str
):
# Check that the stack parameter is valid, raise more informative error message if not
if stack not in (None, True, False, "normalize", "center", "layered"):
raise StreamlitAPIException(
f'Invalid value for stack parameter: {stack}. Stack must be one of True, False, "normalize", "center", "layered" or None. '
f"See documentation for `{command}` [here]({docs_link}) for more information."
)


def generate_chart(
chart_type: ChartType,
data: Data | None,
Expand All @@ -128,7 +139,7 @@ def generate_chart(
size_from_user: str | float | None = None,
width: int | None = None,
height: int | None = None,
# Bar charts only:
# Bar & Area charts only:
stack: bool | ChartStackType | None = None,
) -> tuple[alt.Chart, AddRowsMetadata]:
"""Function to use the chart's type, data columns and indices to figure out the chart's spec."""
Expand Down Expand Up @@ -675,6 +686,7 @@ def _get_opacity_encoding(
) -> alt.OpacityValue | None:
import altair as alt

# Opacity set to 0.7 for all area charts
if color_column and chart_type == ChartType.AREA:
return alt.OpacityValue(0.7)

Expand Down Expand Up @@ -761,7 +773,7 @@ def _get_axis_encodings(
)
stack_encoding = y_encoding

# Handle stacking - only relevant for bar charts
# Handle stacking - only relevant for bar & area charts
_update_encoding_with_stack(stack, stack_encoding)

return x_encoding, y_encoding
Expand Down
41 changes: 31 additions & 10 deletions lib/streamlit/elements/vega_charts.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
ChartStackType,
ChartType,
generate_chart,
maybe_raise_stack_warning,
)
from streamlit.elements.lib.event_utils import AttributeDictionary
from streamlit.elements.lib.policies import check_widget_policies
Expand Down Expand Up @@ -754,6 +755,7 @@ def area_chart(
x_label: str | None = None,
y_label: str | None = None,
color: str | Color | list[Color] | None = None,
stack: bool | ChartStackType | None = None,
width: int | None = None,
height: int | None = None,
use_container_width: bool = True,
Expand Down Expand Up @@ -837,6 +839,13 @@ def area_chart(
as the number of y values (e.g. ``color=["#fd0", "#f0f", "#04f"]``
for three lines).
stack : bool, "normalize", "center", or None
Whether to stack the areas. If this is ``None`` (default), uses
Vega's default. If ``True``, stacks the areas on top of one another.
If ``False``, overlays the areas without stacking. If "normalize",
the areas are stacked and normalized to 100%. If "center", the areas
are stacked and shifted to center their baseline (produces steamgraph).
width : int or None
Desired width of the chart expressed in pixels. If ``width`` is
``None`` (default), Streamlit sets the width of the chart to fit
Expand Down Expand Up @@ -920,6 +929,18 @@ def area_chart(
"""

# 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.
if stack is False:
stack = "layered"

chart, add_rows_metadata = generate_chart(
chart_type=ChartType.AREA,
data=data,
Expand All @@ -931,6 +952,7 @@ def area_chart(
size_from_user=None,
width=width,
height=height,
stack=stack,
)
return cast(
"DeltaGenerator",
Expand Down Expand Up @@ -1045,8 +1067,8 @@ def bar_chart(
stack : bool, "normalize", "center", "layered", or None
Whether to stack the bars. If this is ``None`` (default), uses Vega's
default. If this is ``True``, the bars are stacked on top of each other.
If this is ``False``, the bars are displayed side by side. If "normalize",
default. If ``True``, the bars are stacked on top of each other.
If ``False``, the bars are displayed side by side. If "normalize",
the bars are stacked and normalized to 100%. If "center", the bars are
stacked around a central axis. If "layered", the bars are stacked on top
of one another.
Expand Down Expand Up @@ -1151,20 +1173,19 @@ def bar_chart(
"""

# 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."
)

# Check that the stack parameter is valid, raise more informative error message if not
VALID_STACK_TYPES = (None, True, False, "normalize", "center", "layered")
if stack not in VALID_STACK_TYPES:
raise StreamlitAPIException(
f'Invalid value for stack parameter: {stack}. Stack must be one of True, False, "normalize", "center", "layered" or None. '
"See documentation for `st.bar_chart` [here](https://docs.streamlit.io/develop/api-reference/charts/st.bar_chart) for more information."
)

bar_chart_type = (
ChartType.HORIZONTAL_BAR if horizontal else ChartType.VERTICAL_BAR
)
Expand Down
13 changes: 13 additions & 0 deletions lib/tests/streamlit/elements/vega_charts_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -1372,6 +1372,19 @@ def assert_output_df_is_correct_and_input_is_untouched(

pd.testing.assert_frame_equal(output_df, expected_df)

@parameterized.expand([True, False, "normalize", "center"])
def test_area_chart_stack_param(self, stack: bool | str):
"""Test that the stack parameter is passed to the chart."""
df = pd.DataFrame([[20, 30, 50]], columns=["a", "b", "c"])

st.area_chart(df, x="a", y=["b", "c"], stack=stack)

proto = self.get_delta_from_queue().new_element.arrow_vega_lite_chart
chart_spec = json.loads(proto.spec)

self.assertIn(chart_spec["mark"], ["area", {"type": "area"}])
self.assertEqual(chart_spec["encoding"]["y"]["stack"], stack)


class VegaUtilitiesTest(unittest.TestCase):
"""Test vega chart utility methods."""
Expand Down

0 comments on commit 99b5676

Please sign in to comment.