streamlit-nightly 1.33.1.dev20240501__py2.py3-none-any.whl → 1.34.1.dev20240503__py2.py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (23) hide show
  1. streamlit/components/v1/custom_component.py +3 -9
  2. streamlit/delta_generator.py +32 -208
  3. streamlit/elements/lib/built_in_chart_utils.py +920 -0
  4. streamlit/elements/utils.py +1 -14
  5. streamlit/elements/{arrow_altair.py → vega_charts.py} +301 -836
  6. streamlit/static/asset-manifest.json +5 -5
  7. streamlit/static/index.html +1 -1
  8. streamlit/static/static/js/5441.71804c26.chunk.js +1 -0
  9. streamlit/static/static/js/7483.64f23be7.chunk.js +2 -0
  10. streamlit/static/static/js/{main.af77b7ba.js → main.3b0201f6.js} +2 -2
  11. {streamlit_nightly-1.33.1.dev20240501.dist-info → streamlit_nightly-1.34.1.dev20240503.dist-info}/METADATA +1 -1
  12. {streamlit_nightly-1.33.1.dev20240501.dist-info → streamlit_nightly-1.34.1.dev20240503.dist-info}/RECORD +19 -20
  13. streamlit/elements/altair_utils.py +0 -40
  14. streamlit/elements/arrow_vega_lite.py +0 -229
  15. streamlit/static/static/js/43.c6749504.chunk.js +0 -1
  16. streamlit/static/static/js/656.7150a933.chunk.js +0 -2
  17. /streamlit/static/static/css/{43.e3b876c5.chunk.css → 5441.e3b876c5.chunk.css} +0 -0
  18. /streamlit/static/static/js/{656.7150a933.chunk.js.LICENSE.txt → 7483.64f23be7.chunk.js.LICENSE.txt} +0 -0
  19. /streamlit/static/static/js/{main.af77b7ba.js.LICENSE.txt → main.3b0201f6.js.LICENSE.txt} +0 -0
  20. {streamlit_nightly-1.33.1.dev20240501.data → streamlit_nightly-1.34.1.dev20240503.data}/scripts/streamlit.cmd +0 -0
  21. {streamlit_nightly-1.33.1.dev20240501.dist-info → streamlit_nightly-1.34.1.dev20240503.dist-info}/WHEEL +0 -0
  22. {streamlit_nightly-1.33.1.dev20240501.dist-info → streamlit_nightly-1.34.1.dev20240503.dist-info}/entry_points.txt +0 -0
  23. {streamlit_nightly-1.33.1.dev20240501.dist-info → streamlit_nightly-1.34.1.dev20240503.dist-info}/top_level.txt +0 -0
@@ -12,30 +12,22 @@
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
14
 
15
- """A Python wrapper around Altair.
16
- Altair is a Python visualization library based on Vega-Lite,
17
- a nice JSON schema for expressing graphs and charts.
18
- """
15
+ """Collection of chart commands that are rendered via our vega-lite chart component."""
16
+
19
17
  from __future__ import annotations
20
18
 
19
+ import json
21
20
  from contextlib import nullcontext
22
- from datetime import date
23
- from enum import Enum
24
- from typing import TYPE_CHECKING, Any, Collection, Literal, Sequence, cast
21
+ from typing import TYPE_CHECKING, Any, Final, Literal, Sequence, cast
25
22
 
26
- import streamlit.elements.arrow_vega_lite as arrow_vega_lite
23
+ import streamlit.elements.lib.dicttools as dicttools
27
24
  from streamlit import type_util
28
- from streamlit.color_util import (
29
- Color,
30
- is_color_like,
31
- is_color_tuple_like,
32
- is_hex_color_like,
33
- to_css_color,
25
+ from streamlit.elements.lib.built_in_chart_utils import (
26
+ AddRowsMetadata,
27
+ ChartType,
28
+ generate_chart,
34
29
  )
35
- from streamlit.elements.altair_utils import AddRowsMetadata
36
- from streamlit.elements.arrow import Data
37
- from streamlit.elements.utils import last_index_for_melted_dataframes
38
- from streamlit.errors import Error, StreamlitAPIException
30
+ from streamlit.errors import StreamlitAPIException
39
31
  from streamlit.proto.ArrowVegaLiteChart_pb2 import (
40
32
  ArrowVegaLiteChart as ArrowVegaLiteChartProto,
41
33
  )
@@ -43,54 +35,171 @@ from streamlit.runtime.metrics_util import gather_metrics
43
35
 
44
36
  if TYPE_CHECKING:
45
37
  import altair as alt
46
- import pandas as pd
47
38
 
39
+ from streamlit.color_util import Color
48
40
  from streamlit.delta_generator import DeltaGenerator
41
+ from streamlit.elements.arrow import Data
42
+
43
+ # See https://vega.github.io/vega-lite/docs/encoding.html
44
+ _CHANNELS: Final = {
45
+ "x",
46
+ "y",
47
+ "x2",
48
+ "y2",
49
+ "xError",
50
+ "xError2",
51
+ "yError",
52
+ "yError2",
53
+ "longitude",
54
+ "latitude",
55
+ "color",
56
+ "opacity",
57
+ "fillOpacity",
58
+ "strokeOpacity",
59
+ "strokeWidth",
60
+ "size",
61
+ "shape",
62
+ "text",
63
+ "tooltip",
64
+ "href",
65
+ "key",
66
+ "order",
67
+ "detail",
68
+ "facet",
69
+ "row",
70
+ "column",
71
+ }
72
+
73
+
74
+ def _prepare_vega_lite_spec(
75
+ spec: dict[str, Any] | None = None,
76
+ use_container_width: bool = False,
77
+ **kwargs,
78
+ ) -> dict[str, Any]:
79
+ # Support passing no spec arg, but filling it with kwargs.
80
+ # Example:
81
+ # marshall(proto, baz='boz')
82
+ if spec is None:
83
+ spec = dict()
84
+
85
+ if len(kwargs):
86
+ # Support passing in kwargs. Example:
87
+ # marshall(proto, {foo: 'bar'}, baz='boz')
88
+ # Merge spec with unflattened kwargs, where kwargs take precedence.
89
+ # This only works for string keys, but kwarg keys are strings anyways.
90
+ spec = dict(spec, **dicttools.unflatten(kwargs, _CHANNELS))
91
+ else:
92
+ # Clone the spec dict, since we may be mutating it.
93
+ spec = dict(spec)
94
+
95
+ if len(spec) == 0:
96
+ raise StreamlitAPIException("Vega-Lite charts require a non-empty spec dict.")
97
+
98
+ if "autosize" not in spec:
99
+ # type fit does not work for many chart types. This change focuses
100
+ # on vconcat with use_container_width=True as there are unintended
101
+ # consequences of changing the default autosize for all charts.
102
+ # fit-x fits the width and height can be adjusted.
103
+ if "vconcat" in spec and use_container_width:
104
+ spec["autosize"] = {"type": "fit-x", "contains": "padding"}
105
+ else:
106
+ spec["autosize"] = {"type": "fit", "contains": "padding"}
49
107
 
108
+ return spec
50
109
 
51
- class ChartType(Enum):
52
- AREA = {"mark_type": "area"}
53
- BAR = {"mark_type": "bar"}
54
- LINE = {"mark_type": "line"}
55
- SCATTER = {"mark_type": "circle"}
56
110
 
111
+ def _serialize_data(data: Any) -> bytes:
112
+ """Serialize the any type of data structure to Arrow IPC format (bytes)."""
113
+ import pyarrow as pa
114
+
115
+ if isinstance(data, pa.Table):
116
+ return type_util.pyarrow_table_to_bytes(data)
117
+
118
+ df = type_util.convert_anything_to_df(data)
119
+ return type_util.data_frame_to_bytes(df)
120
+
121
+
122
+ def _marshall_chart_data(
123
+ proto: ArrowVegaLiteChartProto,
124
+ spec: dict[str, Any],
125
+ data: Data = None,
126
+ ) -> None:
127
+ """Adds the data to the proto and removes it from the spec dict.
128
+ These operations will happen in-place."""
129
+
130
+ # Pull data out of spec dict when it's in a 'datasets' key:
131
+ # datasets: {foo: df1, bar: df2}, ...}
132
+ if "datasets" in spec:
133
+ for dataset_name, dataset_data in spec["datasets"].items():
134
+ dataset = proto.datasets.add()
135
+ dataset.name = str(dataset_name)
136
+ dataset.has_name = True
137
+ dataset.data.data = _serialize_data(dataset_data)
138
+ del spec["datasets"]
139
+
140
+ # Pull data out of spec dict when it's in a top-level 'data' key:
141
+ # {data: df}
142
+ # {data: {values: df, ...}}
143
+ # {data: {url: 'url'}}
144
+ # {data: {name: 'foo'}}
145
+ if "data" in spec:
146
+ data_spec = spec["data"]
147
+
148
+ if isinstance(data_spec, dict):
149
+ if "values" in data_spec:
150
+ data = data_spec["values"]
151
+ del spec["data"]
152
+ else:
153
+ data = data_spec
154
+ del spec["data"]
155
+
156
+ if data is not None:
157
+ proto.data.data = _serialize_data(data)
158
+
159
+
160
+ def _convert_altair_to_vega_lite_spec(altair_chart: alt.Chart) -> dict[str, Any]:
161
+ """Convert an Altair chart object to a Vega-Lite chart spec."""
162
+ import altair as alt
163
+
164
+ # Normally altair_chart.to_dict() would transform the dataframe used by the
165
+ # chart into an array of dictionaries. To avoid that, we install a
166
+ # transformer that replaces datasets with a reference by the object id of
167
+ # the dataframe. We then fill in the dataset manually later on.
168
+
169
+ datasets = {}
170
+
171
+ def id_transform(data) -> dict[str, str]:
172
+ """Altair data transformer that returns a fake named dataset with the
173
+ object id.
174
+ """
175
+ name = str(id(data))
176
+ datasets[name] = data
177
+ return {"name": name}
178
+
179
+ alt.data_transformers.register("id", id_transform) # type: ignore[attr-defined,unused-ignore]
180
+
181
+ # The default altair theme has some width/height defaults defined
182
+ # which are not useful for Streamlit. Therefore, we change the theme to
183
+ # "none" to avoid those defaults.
184
+ with alt.themes.enable("none") if alt.themes.active == "default" else nullcontext(): # type: ignore[attr-defined,unused-ignore]
185
+ with alt.data_transformers.enable("id"): # type: ignore[attr-defined,unused-ignore]
186
+ chart_dict = altair_chart.to_dict()
187
+
188
+ # Put datasets back into the chart dict but note how they weren't
189
+ # transformed.
190
+ chart_dict["datasets"] = datasets
191
+ return chart_dict
192
+
193
+
194
+ class VegaChartsMixin:
195
+ """Mix-in class for all vega-related chart commands.
196
+
197
+ Altair is a python wrapper on top of the vega-lite spec. And our
198
+ built-in chart commands are just another layer on-top of Altair.
199
+ All of these chart commands will be eventually converted to a vega-lite
200
+ spec and rendered using the same vega-lite chart component.
201
+ """
57
202
 
58
- # Color and size legends need different title paddings in order for them
59
- # to be vertically aligned.
60
- #
61
- # NOTE: I don't think it's possible to *perfectly* align the size and
62
- # color legends in all instances, since the "size" circles vary in size based
63
- # on the data, and their container is top-aligned with the color container. But
64
- # through trial-and-error I found this value to be a good enough middle ground.
65
- # See e2e/scripts/st_arrow_scatter_chart.py for some alignment tests.
66
- #
67
- # NOTE #2: In theory, we could move COLOR_LEGEND_SETTINGS into
68
- # ArrowVegaLiteChart/CustomTheme.tsx, but this would impact existing behavior.
69
- # (See https://github.com/streamlit/streamlit/pull/7164#discussion_r1307707345)
70
- COLOR_LEGEND_SETTINGS = dict(titlePadding=5, offset=5, orient="bottom")
71
- SIZE_LEGEND_SETTINGS = dict(titlePadding=0.5, offset=5, orient="bottom")
72
-
73
- # User-readable names to give the index and melted columns.
74
- SEPARATED_INDEX_COLUMN_TITLE = "index"
75
- MELTED_Y_COLUMN_TITLE = "value"
76
- MELTED_COLOR_COLUMN_TITLE = "color"
77
-
78
- # Crazy internal (non-user-visible) names for the index and melted columns, in order to
79
- # avoid collision with existing column names. The suffix below was generated with an
80
- # online random number generator. Rationale: because it makes it even less likely to
81
- # lead to a conflict than something that's human-readable (like "--streamlit-fake-field"
82
- # or something).
83
- PROTECTION_SUFFIX = "--p5bJXXpQgvPz6yvQMFiy"
84
- SEPARATED_INDEX_COLUMN_NAME = SEPARATED_INDEX_COLUMN_TITLE + PROTECTION_SUFFIX
85
- MELTED_Y_COLUMN_NAME = MELTED_Y_COLUMN_TITLE + PROTECTION_SUFFIX
86
- MELTED_COLOR_COLUMN_NAME = MELTED_COLOR_COLUMN_TITLE + PROTECTION_SUFFIX
87
-
88
- # Name we use for a column we know doesn't exist in the data, to address a Vega-Lite rendering bug
89
- # where empty charts need x, y encodings set in order to take up space.
90
- NON_EXISTENT_COLUMN_NAME = "DOES_NOT_EXIST" + PROTECTION_SUFFIX
91
-
92
-
93
- class ArrowAltairMixin:
94
203
  @gather_metrics("line_chart")
95
204
  def line_chart(
96
205
  self,
@@ -232,8 +341,8 @@ class ArrowAltairMixin:
232
341
  height: 440px
233
342
 
234
343
  """
235
- proto = ArrowVegaLiteChartProto()
236
- chart, add_rows_metadata = _generate_chart(
344
+
345
+ chart, add_rows_metadata = generate_chart(
237
346
  chart_type=ChartType.LINE,
238
347
  data=data,
239
348
  x_from_user=x,
@@ -243,10 +352,11 @@ class ArrowAltairMixin:
243
352
  width=width,
244
353
  height=height,
245
354
  )
246
- marshall(proto, chart, use_container_width, theme="streamlit")
247
-
248
- return self.dg._enqueue(
249
- "arrow_line_chart", proto, add_rows_metadata=add_rows_metadata
355
+ return self._altair_chart(
356
+ chart,
357
+ use_container_width=use_container_width,
358
+ theme="streamlit",
359
+ add_rows_metadata=add_rows_metadata,
250
360
  )
251
361
 
252
362
  @gather_metrics("area_chart")
@@ -391,8 +501,7 @@ class ArrowAltairMixin:
391
501
 
392
502
  """
393
503
 
394
- proto = ArrowVegaLiteChartProto()
395
- chart, add_rows_metadata = _generate_chart(
504
+ chart, add_rows_metadata = generate_chart(
396
505
  chart_type=ChartType.AREA,
397
506
  data=data,
398
507
  x_from_user=x,
@@ -402,10 +511,11 @@ class ArrowAltairMixin:
402
511
  width=width,
403
512
  height=height,
404
513
  )
405
- marshall(proto, chart, use_container_width, theme="streamlit")
406
-
407
- return self.dg._enqueue(
408
- "arrow_area_chart", proto, add_rows_metadata=add_rows_metadata
514
+ return self._altair_chart(
515
+ chart,
516
+ use_container_width=use_container_width,
517
+ theme="streamlit",
518
+ add_rows_metadata=add_rows_metadata,
409
519
  )
410
520
 
411
521
  @gather_metrics("bar_chart")
@@ -552,8 +662,7 @@ class ArrowAltairMixin:
552
662
 
553
663
  """
554
664
 
555
- proto = ArrowVegaLiteChartProto()
556
- chart, add_rows_metadata = _generate_chart(
665
+ chart, add_rows_metadata = generate_chart(
557
666
  chart_type=ChartType.BAR,
558
667
  data=data,
559
668
  x_from_user=x,
@@ -563,10 +672,11 @@ class ArrowAltairMixin:
563
672
  width=width,
564
673
  height=height,
565
674
  )
566
- marshall(proto, chart, use_container_width, theme="streamlit")
567
-
568
- return self.dg._enqueue(
569
- "arrow_bar_chart", proto, add_rows_metadata=add_rows_metadata
675
+ return self._altair_chart(
676
+ chart,
677
+ use_container_width=use_container_width,
678
+ theme="streamlit",
679
+ add_rows_metadata=add_rows_metadata,
570
680
  )
571
681
 
572
682
  @gather_metrics("scatter_chart")
@@ -725,8 +835,8 @@ class ArrowAltairMixin:
725
835
  height: 440px
726
836
 
727
837
  """
728
- proto = ArrowVegaLiteChartProto()
729
- chart, add_rows_metadata = _generate_chart(
838
+
839
+ chart, add_rows_metadata = generate_chart(
730
840
  chart_type=ChartType.SCATTER,
731
841
  data=data,
732
842
  x_from_user=x,
@@ -736,10 +846,11 @@ class ArrowAltairMixin:
736
846
  width=width,
737
847
  height=height,
738
848
  )
739
- marshall(proto, chart, use_container_width, theme="streamlit")
740
-
741
- return self.dg._enqueue(
742
- "arrow_scatter_chart", proto, add_rows_metadata=add_rows_metadata
849
+ return self._altair_chart(
850
+ chart,
851
+ use_container_width=use_container_width,
852
+ theme="streamlit",
853
+ add_rows_metadata=add_rows_metadata,
743
854
  )
744
855
 
745
856
  @gather_metrics("altair_chart")
@@ -790,780 +901,134 @@ class ArrowAltairMixin:
790
901
  https://altair-viz.github.io/gallery/.
791
902
 
792
903
  """
793
- if theme != "streamlit" and theme != None:
794
- raise StreamlitAPIException(
795
- f'You set theme="{theme}" while Streamlit charts only support theme=”streamlit” or theme=None to fallback to the default library theme.'
796
- )
797
- proto = ArrowVegaLiteChartProto()
798
- marshall(
799
- proto,
800
- altair_chart,
801
- use_container_width=use_container_width,
802
- theme=theme,
803
- )
804
-
805
- return self.dg._enqueue("arrow_vega_lite_chart", proto)
806
-
807
- @property
808
- def dg(self) -> DeltaGenerator:
809
- """Get our DeltaGenerator."""
810
- return cast("DeltaGenerator", self)
811
-
812
-
813
- def _is_date_column(df: pd.DataFrame, name: str | None) -> bool:
814
- """True if the column with the given name stores datetime.date values.
815
-
816
- This function just checks the first value in the given column, so
817
- it's meaningful only for columns whose values all share the same type.
818
-
819
- Parameters
820
- ----------
821
- df : pd.DataFrame
822
- name : str
823
- The column name
824
-
825
- Returns
826
- -------
827
- bool
828
-
829
- """
830
- if name is None:
831
- return False
832
-
833
- column = df[name]
834
- if column.size == 0:
835
- return False
836
-
837
- return isinstance(column.iloc[0], date)
838
-
839
-
840
- def _melt_data(
841
- df: pd.DataFrame,
842
- columns_to_leave_alone: list[str],
843
- columns_to_melt: list[str] | None,
844
- new_y_column_name: str,
845
- new_color_column_name: str,
846
- ) -> pd.DataFrame:
847
- """Converts a wide-format dataframe to a long-format dataframe."""
848
- import pandas as pd
849
- from pandas.api.types import infer_dtype
850
-
851
- melted_df = pd.melt(
852
- df,
853
- id_vars=columns_to_leave_alone,
854
- value_vars=columns_to_melt,
855
- var_name=new_color_column_name,
856
- value_name=new_y_column_name,
857
- )
858
-
859
- y_series = melted_df[new_y_column_name]
860
- if (
861
- y_series.dtype == "object"
862
- and "mixed" in infer_dtype(y_series)
863
- and len(y_series.unique()) > 100
864
- ):
865
- raise StreamlitAPIException(
866
- "The columns used for rendering the chart contain too many values with mixed types. Please select the columns manually via the y parameter."
867
- )
868
-
869
- # Arrow has problems with object types after melting two different dtypes
870
- # pyarrow.lib.ArrowTypeError: "Expected a <TYPE> object, got a object"
871
- fixed_df = type_util.fix_arrow_incompatible_column_types(
872
- melted_df,
873
- selected_columns=[
874
- *columns_to_leave_alone,
875
- new_color_column_name,
876
- new_y_column_name,
877
- ],
878
- )
879
-
880
- return fixed_df
881
-
882
-
883
- def prep_data(
884
- df: pd.DataFrame,
885
- x_column: str | None,
886
- y_column_list: list[str],
887
- color_column: str | None,
888
- size_column: str | None,
889
- ) -> tuple[pd.DataFrame, str | None, str | None, str | None, str | None]:
890
- """Prepares the data for charting. This is also used in add_rows.
891
-
892
- Returns the prepared dataframe and the new names of the x column (taking the index reset into
893
- consideration) and y, color, and size columns.
894
- """
895
-
896
- # If y is provided, but x is not, we'll use the index as x.
897
- # So we need to pull the index into its own column.
898
- x_column = _maybe_reset_index_in_place(df, x_column, y_column_list)
899
-
900
- # Drop columns we're not using.
901
- selected_data = _drop_unused_columns(
902
- df, x_column, color_column, size_column, *y_column_list
903
- )
904
-
905
- # Maybe convert color to Vega colors.
906
- _maybe_convert_color_column_in_place(selected_data, color_column)
907
-
908
- # Make sure all columns have string names.
909
- (
910
- x_column,
911
- y_column_list,
912
- color_column,
913
- size_column,
914
- ) = _convert_col_names_to_str_in_place(
915
- selected_data, x_column, y_column_list, color_column, size_column
916
- )
917
-
918
- # Maybe melt data from wide format into long format.
919
- melted_data, y_column, color_column = _maybe_melt(
920
- selected_data, x_column, y_column_list, color_column, size_column
921
- )
922
-
923
- # Return the data, but also the new names to use for x, y, and color.
924
- return melted_data, x_column, y_column, color_column, size_column
925
-
926
-
927
- def _generate_chart(
928
- chart_type: ChartType,
929
- data: Data | None,
930
- x_from_user: str | None = None,
931
- y_from_user: str | Sequence[str] | None = None,
932
- color_from_user: str | Color | list[Color] | None = None,
933
- size_from_user: str | float | None = None,
934
- width: int = 0,
935
- height: int = 0,
936
- ) -> tuple[alt.Chart, AddRowsMetadata]:
937
- """Function to use the chart's type, data columns and indices to figure out the chart's spec."""
938
- import altair as alt
939
-
940
- df = type_util.convert_anything_to_df(data, ensure_copy=True)
941
-
942
- # From now on, use "df" instead of "data". Deleting "data" to guarantee we follow this.
943
- del data
944
-
945
- # Convert arguments received from the user to things Vega-Lite understands.
946
- # Get name of column to use for x.
947
- x_column = _parse_x_column(df, x_from_user)
948
- # Get name of columns to use for y.
949
- y_column_list = _parse_y_columns(df, y_from_user, x_column)
950
- # Get name of column to use for color, or constant value to use. Any/both could be None.
951
- color_column, color_value = _parse_generic_column(df, color_from_user)
952
- # Get name of column to use for size, or constant value to use. Any/both could be None.
953
- size_column, size_value = _parse_generic_column(df, size_from_user)
954
-
955
- # Store some info so we can use it in add_rows.
956
- add_rows_metadata = AddRowsMetadata(
957
- # The last index of df so we can adjust the input df in add_rows:
958
- last_index=last_index_for_melted_dataframes(df),
959
- # This is the input to prep_data (except for the df):
960
- columns=dict(
961
- x_column=x_column,
962
- y_column_list=y_column_list,
963
- color_column=color_column,
964
- size_column=size_column,
965
- ),
966
- )
967
-
968
- # At this point, all foo_column variables are either None/empty or contain actual
969
- # columns that are guaranteed to exist.
970
-
971
- df, x_column, y_column, color_column, size_column = prep_data(
972
- df, x_column, y_column_list, color_column, size_column
973
- )
974
-
975
- # At this point, x_column is only None if user did not provide one AND df is empty.
976
-
977
- # Create a Chart with x and y encodings.
978
- chart = alt.Chart(
979
- data=df,
980
- mark=chart_type.value["mark_type"],
981
- width=width,
982
- height=height,
983
- ).encode(
984
- x=_get_x_encoding(df, x_column, x_from_user, chart_type),
985
- y=_get_y_encoding(df, y_column, y_from_user),
986
- )
987
-
988
- # Set up opacity encoding.
989
- opacity_enc = _get_opacity_encoding(chart_type, color_column)
990
- if opacity_enc is not None:
991
- chart = chart.encode(opacity=opacity_enc)
992
-
993
- # Set up color encoding.
994
- color_enc = _get_color_encoding(
995
- df, color_value, color_column, y_column_list, color_from_user
996
- )
997
- if color_enc is not None:
998
- chart = chart.encode(color=color_enc)
999
-
1000
- # Set up size encoding.
1001
- size_enc = _get_size_encoding(chart_type, size_column, size_value)
1002
- if size_enc is not None:
1003
- chart = chart.encode(size=size_enc)
1004
-
1005
- # Set up tooltip encoding.
1006
- if x_column is not None and y_column is not None:
1007
- chart = chart.encode(
1008
- tooltip=_get_tooltip_encoding(
1009
- x_column,
1010
- y_column,
1011
- size_column,
1012
- color_column,
1013
- color_enc,
1014
- )
1015
- )
1016
-
1017
- return chart.interactive(), add_rows_metadata
1018
-
1019
-
1020
- def _maybe_reset_index_in_place(
1021
- df: pd.DataFrame, x_column: str | None, y_column_list: list[str]
1022
- ) -> str | None:
1023
- if x_column is None and len(y_column_list) > 0:
1024
- if df.index.name is None:
1025
- # Pick column name that is unlikely to collide with user-given names.
1026
- x_column = SEPARATED_INDEX_COLUMN_NAME
1027
- else:
1028
- # Reuse index's name for the new column.
1029
- x_column = df.index.name
1030
-
1031
- df.index.name = x_column
1032
- df.reset_index(inplace=True)
1033
-
1034
- return x_column
1035
-
1036
-
1037
- def _drop_unused_columns(df: pd.DataFrame, *column_names: str | None) -> pd.DataFrame:
1038
- """Returns a subset of df, selecting only column_names that aren't None."""
1039
-
1040
- # We can't just call set(col_names) because sets don't have stable ordering,
1041
- # which means tests that depend on ordering will fail.
1042
- # Performance-wise, it's not a problem, though, since this function is only ever
1043
- # used on very small lists.
1044
- seen = set()
1045
- keep = []
1046
-
1047
- for x in column_names:
1048
- if x is None:
1049
- continue
1050
- if x in seen:
1051
- continue
1052
- seen.add(x)
1053
- keep.append(x)
1054
-
1055
- return df[keep]
1056
-
1057
-
1058
- def _maybe_convert_color_column_in_place(df: pd.DataFrame, color_column: str | None):
1059
- """If needed, convert color column to a format Vega understands."""
1060
- if color_column is None or len(df[color_column]) == 0:
1061
- return
1062
-
1063
- first_color_datum = df[color_column].iat[0]
1064
-
1065
- if is_hex_color_like(first_color_datum):
1066
- # Hex is already CSS-valid.
1067
- pass
1068
- elif is_color_tuple_like(first_color_datum):
1069
- # Tuples need to be converted to CSS-valid.
1070
- df[color_column] = df[color_column].map(to_css_color)
1071
- else:
1072
- # Other kinds of colors columns (i.e. pure numbers or nominal strings) shouldn't
1073
- # be converted since they are treated by Vega-Lite as sequential or categorical colors.
1074
- pass
1075
-
1076
-
1077
- def _convert_col_names_to_str_in_place(
1078
- df: pd.DataFrame,
1079
- x_column: str | None,
1080
- y_column_list: list[str],
1081
- color_column: str | None,
1082
- size_column: str | None,
1083
- ) -> tuple[str | None, list[str], str | None, str | None]:
1084
- """Converts column names to strings, since Vega-Lite does not accept ints, etc."""
1085
- import pandas as pd
1086
-
1087
- column_names = list(df.columns) # list() converts RangeIndex, etc, to regular list.
1088
- str_column_names = [str(c) for c in column_names]
1089
- df.columns = pd.Index(str_column_names)
1090
-
1091
- return (
1092
- None if x_column is None else str(x_column),
1093
- [str(c) for c in y_column_list],
1094
- None if color_column is None else str(color_column),
1095
- None if size_column is None else str(size_column),
1096
- )
1097
-
1098
-
1099
- def _parse_generic_column(
1100
- df: pd.DataFrame, column_or_value: Any
1101
- ) -> tuple[str | None, Any]:
1102
- if isinstance(column_or_value, str) and column_or_value in df.columns:
1103
- column_name = column_or_value
1104
- value = None
1105
- else:
1106
- column_name = None
1107
- value = column_or_value
1108
-
1109
- return column_name, value
1110
-
1111
-
1112
- def _parse_x_column(df: pd.DataFrame, x_from_user: str | None) -> str | None:
1113
- if x_from_user is None:
1114
- return None
1115
-
1116
- elif isinstance(x_from_user, str):
1117
- if x_from_user not in df.columns:
1118
- raise StreamlitColumnNotFoundError(df, x_from_user)
1119
-
1120
- return x_from_user
1121
-
1122
- else:
1123
- raise StreamlitAPIException(
1124
- "x parameter should be a column name (str) or None to use the "
1125
- f" dataframe's index. Value given: {x_from_user} "
1126
- f"(type {type(x_from_user)})"
904
+ return self._altair_chart(
905
+ altair_chart, use_container_width=use_container_width, theme=theme
1127
906
  )
1128
907
 
908
+ @gather_metrics("vega_lite_chart")
909
+ def vega_lite_chart(
910
+ self,
911
+ data: Data = None,
912
+ spec: dict[str, Any] | None = None,
913
+ use_container_width: bool = False,
914
+ theme: Literal["streamlit"] | None = "streamlit",
915
+ **kwargs: Any,
916
+ ) -> DeltaGenerator:
917
+ """Display a chart using the Vega-Lite library.
1129
918
 
1130
- def _parse_y_columns(
1131
- df: pd.DataFrame,
1132
- y_from_user: str | Sequence[str] | None,
1133
- x_column: str | None,
1134
- ) -> list[str]:
1135
- y_column_list: list[str] = []
1136
-
1137
- if y_from_user is None:
1138
- y_column_list = list(df.columns)
1139
-
1140
- elif isinstance(y_from_user, str):
1141
- y_column_list = [y_from_user]
1142
-
1143
- elif type_util.is_sequence(y_from_user):
1144
- y_column_list = list(str(col) for col in y_from_user)
1145
-
1146
- else:
1147
- raise StreamlitAPIException(
1148
- "y parameter should be a column name (str) or list thereof. "
1149
- f"Value given: {y_from_user} (type {type(y_from_user)})"
1150
- )
1151
-
1152
- for col in y_column_list:
1153
- if col not in df.columns:
1154
- raise StreamlitColumnNotFoundError(df, col)
919
+ Parameters
920
+ ----------
921
+ data : pandas.DataFrame, pandas.Styler, pyarrow.Table, numpy.ndarray, Iterable, dict, or None
922
+ Either the data to be plotted or a Vega-Lite spec containing the
923
+ data (which more closely follows the Vega-Lite API).
1155
924
 
1156
- # y_column_list should only include x_column when user explicitly asked for it.
1157
- if x_column in y_column_list and (not y_from_user or x_column not in y_from_user):
1158
- y_column_list.remove(x_column)
925
+ spec : dict or None
926
+ The Vega-Lite spec for the chart. If the spec was already passed in
927
+ the previous argument, this must be set to None. See
928
+ https://vega.github.io/vega-lite/docs/ for more info.
1159
929
 
1160
- return y_column_list
930
+ use_container_width : bool
931
+ If True, set the chart width to the column width. This takes
932
+ precedence over Vega-Lite's native `width` value.
1161
933
 
934
+ theme : "streamlit" or None
935
+ The theme of the chart. Currently, we only support "streamlit" for the Streamlit
936
+ defined design or None to fallback to the default behavior of the library.
1162
937
 
1163
- def _get_opacity_encoding(
1164
- chart_type: ChartType, color_column: str | None
1165
- ) -> alt.OpacityValue | None:
1166
- import altair as alt
938
+ **kwargs : any
939
+ Same as spec, but as keywords.
1167
940
 
1168
- if color_column and chart_type == ChartType.AREA:
1169
- return alt.OpacityValue(0.7)
941
+ Example
942
+ -------
943
+ >>> import streamlit as st
944
+ >>> import pandas as pd
945
+ >>> import numpy as np
946
+ >>>
947
+ >>> chart_data = pd.DataFrame(np.random.randn(200, 3), columns=["a", "b", "c"])
948
+ >>>
949
+ >>> st.vega_lite_chart(
950
+ ... chart_data,
951
+ ... {
952
+ ... "mark": {"type": "circle", "tooltip": True},
953
+ ... "encoding": {
954
+ ... "x": {"field": "a", "type": "quantitative"},
955
+ ... "y": {"field": "b", "type": "quantitative"},
956
+ ... "size": {"field": "c", "type": "quantitative"},
957
+ ... "color": {"field": "c", "type": "quantitative"},
958
+ ... },
959
+ ... },
960
+ ... )
1170
961
 
1171
- return None
962
+ .. output::
963
+ https://doc-vega-lite-chart.streamlit.app/
964
+ height: 300px
1172
965
 
966
+ Examples of Vega-Lite usage without Streamlit can be found at
967
+ https://vega.github.io/vega-lite/examples/. Most of those can be easily
968
+ translated to the syntax shown above.
1173
969
 
1174
- def _get_axis_config(df: pd.DataFrame, column_name: str | None, grid: bool) -> alt.Axis:
1175
- import altair as alt
1176
- from pandas.api.types import is_integer_dtype
1177
-
1178
- if column_name is not None and is_integer_dtype(df[column_name]):
1179
- # Use a max tick size of 1 for integer columns (prevents zoom into float numbers)
1180
- # and deactivate grid lines for x-axis
1181
- return alt.Axis(tickMinStep=1, grid=grid)
1182
-
1183
- return alt.Axis(grid=grid)
1184
-
1185
-
1186
- def _maybe_melt(
1187
- df: pd.DataFrame,
1188
- x_column: str | None,
1189
- y_column_list: list[str],
1190
- color_column: str | None,
1191
- size_column: str | None,
1192
- ) -> tuple[pd.DataFrame, str | None, str | None]:
1193
- """If multiple columns are set for y, melt the dataframe into long format."""
1194
- y_column: str | None
1195
-
1196
- if len(y_column_list) == 0:
1197
- y_column = None
1198
- elif len(y_column_list) == 1:
1199
- y_column = y_column_list[0]
1200
- elif x_column is not None:
1201
- # Pick column names that are unlikely to collide with user-given names.
1202
- y_column = MELTED_Y_COLUMN_NAME
1203
- color_column = MELTED_COLOR_COLUMN_NAME
1204
-
1205
- columns_to_leave_alone = [x_column]
1206
- if size_column:
1207
- columns_to_leave_alone.append(size_column)
1208
-
1209
- df = _melt_data(
1210
- df=df,
1211
- columns_to_leave_alone=columns_to_leave_alone,
1212
- columns_to_melt=y_column_list,
1213
- new_y_column_name=y_column,
1214
- new_color_column_name=color_column,
970
+ """
971
+ return self._vega_lite_chart(
972
+ data=data,
973
+ spec=spec,
974
+ use_container_width=use_container_width,
975
+ theme=theme,
976
+ **kwargs,
1215
977
  )
1216
978
 
1217
- return df, y_column, color_column
1218
-
1219
-
1220
- def _get_x_encoding(
1221
- df: pd.DataFrame,
1222
- x_column: str | None,
1223
- x_from_user: str | None,
1224
- chart_type: ChartType,
1225
- ) -> alt.X:
1226
- import altair as alt
1227
-
1228
- if x_column is None:
1229
- # If no field is specified, the full axis disappears when no data is present.
1230
- # Maybe a bug in vega-lite? So we pass a field that doesn't exist.
1231
- x_field = NON_EXISTENT_COLUMN_NAME
1232
- x_title = ""
1233
- elif x_column == SEPARATED_INDEX_COLUMN_NAME:
1234
- # If the x column name is the crazy anti-collision name we gave it, then need to set
1235
- # up a title so we never show the crazy name to the user.
1236
- x_field = x_column
1237
- # Don't show a label in the x axis (not even a nice label like
1238
- # SEPARATED_INDEX_COLUMN_TITLE) when we pull the x axis from the index.
1239
- x_title = ""
1240
- else:
1241
- x_field = x_column
1242
-
1243
- # Only show a label in the x axis if the user passed a column explicitly. We
1244
- # could go either way here, but I'm keeping this to avoid breaking the existing
1245
- # behavior.
1246
- if x_from_user is None:
1247
- x_title = ""
1248
- else:
1249
- x_title = x_column
1250
-
1251
- return alt.X(
1252
- x_field,
1253
- title=x_title,
1254
- type=_get_x_encoding_type(df, chart_type, x_column),
1255
- scale=alt.Scale(),
1256
- axis=_get_axis_config(df, x_column, grid=False),
1257
- )
1258
-
1259
-
1260
- def _get_y_encoding(
1261
- df: pd.DataFrame,
1262
- y_column: str | None,
1263
- y_from_user: str | Sequence[str] | None,
1264
- ) -> alt.Y:
1265
- import altair as alt
1266
-
1267
- if y_column is None:
1268
- # If no field is specified, the full axis disappears when no data is present.
1269
- # Maybe a bug in vega-lite? So we pass a field that doesn't exist.
1270
- y_field = NON_EXISTENT_COLUMN_NAME
1271
- y_title = ""
1272
- elif y_column == MELTED_Y_COLUMN_NAME:
1273
- # If the y column name is the crazy anti-collision name we gave it, then need to set
1274
- # up a title so we never show the crazy name to the user.
1275
- y_field = y_column
1276
- # Don't show a label in the y axis (not even a nice label like
1277
- # MELTED_Y_COLUMN_TITLE) when we pull the x axis from the index.
1278
- y_title = ""
1279
- else:
1280
- y_field = y_column
1281
-
1282
- # Only show a label in the y axis if the user passed a column explicitly. We
1283
- # could go either way here, but I'm keeping this to avoid breaking the existing
1284
- # behavior.
1285
- if y_from_user is None:
1286
- y_title = ""
1287
- else:
1288
- y_title = y_column
1289
-
1290
- return alt.Y(
1291
- field=y_field,
1292
- title=y_title,
1293
- type=_get_y_encoding_type(df, y_column),
1294
- scale=alt.Scale(),
1295
- axis=_get_axis_config(df, y_column, grid=True),
1296
- )
1297
-
1298
-
1299
- def _get_color_encoding(
1300
- df: pd.DataFrame,
1301
- color_value: Color | None,
1302
- color_column: str | None,
1303
- y_column_list: list[str],
1304
- color_from_user: str | Color | list[Color] | None,
1305
- ) -> alt.Color | alt.ColorValue | None:
1306
- import altair as alt
1307
-
1308
- has_color_value = color_value not in [None, [], tuple()]
1309
-
1310
- # If user passed a color value, that should win over colors coming from the
1311
- # color column (be they manual or auto-assigned due to melting)
1312
- if has_color_value:
1313
- # If the color value is color-like, return that.
1314
- if is_color_like(cast(Any, color_value)):
1315
- if len(y_column_list) != 1:
1316
- raise StreamlitColorLengthError([color_value], y_column_list)
1317
-
1318
- return alt.ColorValue(to_css_color(cast(Any, color_value)))
1319
-
1320
- # If the color value is a list of colors of approriate length, return that.
1321
- elif isinstance(color_value, (list, tuple)):
1322
- color_values = cast(Collection[Color], color_value)
1323
-
1324
- if len(color_values) != len(y_column_list):
1325
- raise StreamlitColorLengthError(color_values, y_column_list)
1326
-
1327
- if len(color_value) == 1:
1328
- return alt.ColorValue(to_css_color(cast(Any, color_value[0])))
1329
- else:
1330
- return alt.Color(
1331
- field=color_column,
1332
- scale=alt.Scale(range=[to_css_color(c) for c in color_values]),
1333
- legend=COLOR_LEGEND_SETTINGS,
1334
- type="nominal",
1335
- title=" ",
1336
- )
1337
-
1338
- raise StreamlitInvalidColorError(df, color_from_user)
1339
-
1340
- elif color_column is not None:
1341
- column_type: str | tuple[str, list[Any]]
1342
-
1343
- if color_column == MELTED_COLOR_COLUMN_NAME:
1344
- column_type = "nominal"
1345
- else:
1346
- column_type = type_util.infer_vegalite_type(df[color_column])
1347
-
1348
- color_enc = alt.Color(
1349
- field=color_column, legend=COLOR_LEGEND_SETTINGS, type=column_type
979
+ def _altair_chart(
980
+ self,
981
+ altair_chart: alt.Chart,
982
+ use_container_width: bool = False,
983
+ theme: Literal["streamlit"] | None = "streamlit",
984
+ add_rows_metadata: AddRowsMetadata | None = None,
985
+ ) -> DeltaGenerator:
986
+ """Internal method to enqueue a vega-lite chart element based on an Altair chart."""
987
+ vega_lite_spec = _convert_altair_to_vega_lite_spec(altair_chart)
988
+ return self._vega_lite_chart(
989
+ data=None, # The data is already part of the spec
990
+ spec=vega_lite_spec,
991
+ use_container_width=use_container_width,
992
+ theme=theme,
993
+ add_rows_metadata=add_rows_metadata,
1350
994
  )
1351
995
 
1352
- # Fix title if DF was melted
1353
- if color_column == MELTED_COLOR_COLUMN_NAME:
1354
- # This has to contain an empty space, otherwise the
1355
- # full y-axis disappears (maybe a bug in vega-lite)?
1356
- color_enc["title"] = " "
1357
-
1358
- # If the 0th element in the color column looks like a color, we'll use the color column's
1359
- # values as the colors in our chart.
1360
- elif len(df[color_column]) and is_color_like(df[color_column].iat[0]):
1361
- color_range = [to_css_color(c) for c in df[color_column].unique()]
1362
- color_enc["scale"] = alt.Scale(range=color_range)
1363
- # Don't show the color legend, because it will just show text with the color values,
1364
- # like #f00, #00f, etc, which are not user-readable.
1365
- color_enc["legend"] = None
1366
-
1367
- # Otherwise, let Vega-Lite auto-assign colors.
1368
- # This codepath is typically reached when the color column contains numbers (in which case
1369
- # Vega-Lite uses a color gradient to represent them) or strings (in which case Vega-Lite
1370
- # assigns one color for each unique value).
1371
- else:
1372
- pass
1373
-
1374
- return color_enc
1375
-
1376
- return None
1377
-
1378
-
1379
- def _get_size_encoding(
1380
- chart_type: ChartType,
1381
- size_column: str | None,
1382
- size_value: str | float | None,
1383
- ) -> alt.Size | alt.SizeValue | None:
1384
- import altair as alt
1385
-
1386
- if chart_type == ChartType.SCATTER:
1387
- if size_column is not None:
1388
- return alt.Size(
1389
- size_column,
1390
- legend=SIZE_LEGEND_SETTINGS,
1391
- )
996
+ def _vega_lite_chart(
997
+ self,
998
+ data: Data = None,
999
+ spec: dict[str, Any] | None = None,
1000
+ use_container_width: bool = False,
1001
+ theme: Literal["streamlit"] | None = "streamlit",
1002
+ add_rows_metadata: AddRowsMetadata | None = None,
1003
+ **kwargs: Any,
1004
+ ) -> DeltaGenerator:
1005
+ """Internal method to enqueue a vega-lite chart element based on a vega-lite spec."""
1392
1006
 
1393
- elif isinstance(size_value, (float, int)):
1394
- return alt.SizeValue(size_value)
1395
- elif size_value is None:
1396
- return alt.SizeValue(100)
1397
- else:
1007
+ if theme not in ["streamlit", None]:
1398
1008
  raise StreamlitAPIException(
1399
- f"This does not look like a valid size: {repr(size_value)}"
1400
- )
1401
-
1402
- elif size_column is not None or size_value is not None:
1403
- raise Error(
1404
- f"Chart type {chart_type.name} does not support size argument. "
1405
- "This should never happen!"
1406
- )
1407
-
1408
- return None
1409
-
1410
-
1411
- def _get_tooltip_encoding(
1412
- x_column: str,
1413
- y_column: str,
1414
- size_column: str | None,
1415
- color_column: str | None,
1416
- color_enc: alt.Color | alt.ColorValue | None,
1417
- ) -> list[alt.Tooltip]:
1418
- import altair as alt
1419
-
1420
- tooltip = []
1421
-
1422
- # If the x column name is the crazy anti-collision name we gave it, then need to set
1423
- # up a tooltip title so we never show the crazy name to the user.
1424
- if x_column == SEPARATED_INDEX_COLUMN_NAME:
1425
- tooltip.append(alt.Tooltip(x_column, title=SEPARATED_INDEX_COLUMN_TITLE))
1426
- else:
1427
- tooltip.append(alt.Tooltip(x_column))
1428
-
1429
- # If the y column name is the crazy anti-collision name we gave it, then need to set
1430
- # up a tooltip title so we never show the crazy name to the user.
1431
- if y_column == MELTED_Y_COLUMN_NAME:
1432
- tooltip.append(
1433
- alt.Tooltip(
1434
- y_column,
1435
- title=MELTED_Y_COLUMN_TITLE,
1436
- type="quantitative", # Just picked something random. Doesn't really matter!
1437
- )
1438
- )
1439
- else:
1440
- tooltip.append(alt.Tooltip(y_column))
1441
-
1442
- # If we earlier decided that there should be no color legend, that's because the
1443
- # user passed a color column with actual color values (like "#ff0"), so we should
1444
- # not show the color values in the tooltip.
1445
- if color_column and getattr(color_enc, "legend", True) is not None:
1446
- # Use a human-readable title for the color.
1447
- if color_column == MELTED_COLOR_COLUMN_NAME:
1448
- tooltip.append(
1449
- alt.Tooltip(
1450
- color_column,
1451
- title=MELTED_COLOR_COLUMN_TITLE,
1452
- type="nominal",
1453
- )
1009
+ f'You set theme="{theme}" while Streamlit charts only support theme=”streamlit” or theme=None to fallback to the default library theme.'
1454
1010
  )
1455
- else:
1456
- tooltip.append(alt.Tooltip(color_column))
1457
-
1458
- if size_column:
1459
- tooltip.append(alt.Tooltip(size_column))
1460
-
1461
- return tooltip
1462
-
1463
1011
 
1464
- def _get_x_encoding_type(
1465
- df: pd.DataFrame, chart_type: ChartType, x_column: str | None
1466
- ) -> type_util.VegaLiteType:
1467
- if x_column is None:
1468
- return "quantitative" # Anything. If None, Vega-Lite may hide the axis.
1012
+ # Support passing data inside spec['datasets'] and spec['data'].
1013
+ # (The data gets pulled out of the spec dict later on.)
1014
+ if isinstance(data, dict) and spec is None:
1015
+ spec = data
1016
+ data = None
1469
1017
 
1470
- # Bar charts should have a discrete (ordinal) x-axis, UNLESS type is date/time
1471
- # https://github.com/streamlit/streamlit/pull/2097#issuecomment-714802475
1472
- if chart_type == ChartType.BAR and not _is_date_column(df, x_column):
1473
- return "ordinal"
1474
-
1475
- return type_util.infer_vegalite_type(df[x_column])
1476
-
1477
-
1478
- def _get_y_encoding_type(
1479
- df: pd.DataFrame, y_column: str | None
1480
- ) -> type_util.VegaLiteType:
1481
- if y_column:
1482
- return type_util.infer_vegalite_type(df[y_column])
1483
-
1484
- return "quantitative" # Pick anything. If undefined, Vega-Lite may hide the axis.
1485
-
1486
-
1487
- def marshall(
1488
- vega_lite_chart: ArrowVegaLiteChartProto,
1489
- altair_chart: alt.Chart,
1490
- use_container_width: bool = False,
1491
- theme: None | Literal["streamlit"] = "streamlit",
1492
- **kwargs: Any,
1493
- ) -> None:
1494
- """Marshall chart's data into proto."""
1495
- import altair as alt
1496
-
1497
- # Normally altair_chart.to_dict() would transform the dataframe used by the
1498
- # chart into an array of dictionaries. To avoid that, we install a
1499
- # transformer that replaces datasets with a reference by the object id of
1500
- # the dataframe. We then fill in the dataset manually later on.
1501
-
1502
- datasets = {}
1503
-
1504
- def id_transform(data) -> dict[str, str]:
1505
- """Altair data transformer that returns a fake named dataset with the
1506
- object id.
1507
- """
1508
- name = str(id(data))
1509
- datasets[name] = data
1510
- return {"name": name}
1511
-
1512
- alt.data_transformers.register("id", id_transform) # type: ignore[attr-defined,unused-ignore]
1513
-
1514
- # The default altair theme has some width/height defaults defined
1515
- # which are not useful for Streamlit. Therefore, we change the theme to
1516
- # "none" to avoid those defaults.
1517
- with alt.themes.enable("none") if alt.themes.active == "default" else nullcontext(): # type: ignore[attr-defined,unused-ignore]
1518
- with alt.data_transformers.enable("id"): # type: ignore[attr-defined,unused-ignore]
1519
- chart_dict = altair_chart.to_dict()
1520
-
1521
- # Put datasets back into the chart dict but note how they weren't
1522
- # transformed.
1523
- chart_dict["datasets"] = datasets
1018
+ proto = ArrowVegaLiteChartProto()
1524
1019
 
1525
- arrow_vega_lite.marshall(
1526
- vega_lite_chart,
1527
- chart_dict,
1528
- use_container_width=use_container_width,
1529
- theme=theme,
1530
- **kwargs,
1531
- )
1020
+ spec = _prepare_vega_lite_spec(spec, use_container_width, **kwargs)
1021
+ _marshall_chart_data(proto, spec, data)
1532
1022
 
1023
+ proto.spec = json.dumps(spec)
1024
+ proto.use_container_width = use_container_width
1025
+ proto.theme = theme or ""
1533
1026
 
1534
- class StreamlitColumnNotFoundError(StreamlitAPIException):
1535
- def __init__(self, df, col_name, *args):
1536
- available_columns = ", ".join(str(c) for c in list(df.columns))
1537
- message = (
1538
- f'Data does not have a column named `"{col_name}"`. '
1539
- f"Available columns are `{available_columns}`"
1027
+ return self.dg._enqueue(
1028
+ "arrow_vega_lite_chart", proto, add_rows_metadata=add_rows_metadata
1540
1029
  )
1541
- super().__init__(message, *args)
1542
-
1543
-
1544
- class StreamlitInvalidColorError(StreamlitAPIException):
1545
- def __init__(self, df, color_from_user, *args):
1546
- ", ".join(str(c) for c in list(df.columns))
1547
- message = f"""
1548
- This does not look like a valid color argument: `{color_from_user}`.
1549
-
1550
- The color argument can be:
1551
1030
 
1552
- * A hex string like "#ffaa00" or "#ffaa0088".
1553
- * An RGB or RGBA tuple with the red, green, blue, and alpha
1554
- components specified as ints from 0 to 255 or floats from 0.0 to
1555
- 1.0.
1556
- * The name of a column.
1557
- * Or a list of colors, matching the number of y columns to draw.
1558
- """
1559
- super().__init__(message, *args)
1560
-
1561
-
1562
- class StreamlitColorLengthError(StreamlitAPIException):
1563
- def __init__(self, color_values, y_column_list, *args):
1564
- message = (
1565
- f"The list of colors `{color_values}` must have the same "
1566
- "length as the list of columns to be colored "
1567
- f"`{y_column_list}`."
1568
- )
1569
- super().__init__(message, *args)
1031
+ @property
1032
+ def dg(self) -> DeltaGenerator:
1033
+ """Get our DeltaGenerator."""
1034
+ return cast("DeltaGenerator", self)