2022-05-23 00:16:32 +04:00

200 lines
6.1 KiB
Python

# Copyright 2018-2022 Streamlit Inc.
#
# 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.
"""A Python wrapper around Vega-Lite."""
import json
from typing import cast
import streamlit
import streamlit.elements.legacy_data_frame as data_frame
import streamlit.elements.lib.dicttools as dicttools
from streamlit.logger import get_logger
from streamlit.proto.VegaLiteChart_pb2 import VegaLiteChart as VegaLiteChartProto
LOGGER = get_logger(__name__)
class LegacyVegaLiteMixin:
def _legacy_vega_lite_chart(
self,
data=None,
spec=None,
use_container_width=False,
**kwargs,
):
"""Display a chart using the Vega-Lite library.
Parameters
----------
data : pandas.DataFrame, pandas.Styler, numpy.ndarray, Iterable, dict,
or None
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 the spec was already passed in
the previous argument, this must be set to None. See
https://vega.github.io/vega-lite/docs/ for more info.
use_container_width : bool
If True, set the chart width to the column width. This takes
precedence over Vega-Lite's native `width` value.
**kwargs : any
Same as spec, but as keywords.
Example
-------
>>> import pandas as pd
>>> import numpy as np
>>>
>>> df = pd.DataFrame(
... np.random.randn(200, 3),
... columns=['a', 'b', 'c'])
>>>
>>> st._legacy_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://static.streamlit.io/0.25.0-2JkNY/index.html?id=8jmmXR8iKoZGV4kXaKGYV5
height: 200px
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.
"""
vega_lite_chart_proto = VegaLiteChartProto()
marshall(
vega_lite_chart_proto,
data,
spec,
use_container_width=use_container_width,
**kwargs,
)
return self.dg._enqueue("vega_lite_chart", vega_lite_chart_proto)
@property
def dg(self) -> "streamlit.delta_generator.DeltaGenerator":
"""Get our DeltaGenerator."""
return cast("streamlit.delta_generator.DeltaGenerator", self)
def marshall(proto, data=None, spec=None, use_container_width=False, **kwargs):
"""Construct a Vega-Lite chart object.
See DeltaGenerator._legacy_vega_lite_chart for docs.
"""
# 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
# Support passing no spec arg, but filling it with kwargs.
# Example:
# marshall(proto, baz='boz')
if spec is None:
spec = dict()
else:
# Clone the spec dict, since we may be mutating it.
spec = dict(spec)
# Support passing in kwargs. Example:
# marshall(proto, {foo: 'bar'}, baz='boz')
if len(kwargs):
# Merge spec with unflattened kwargs, where kwargs take precedence.
# This only works for string keys, but kwarg keys are strings anyways.
spec = dict(spec, **dicttools.unflatten(kwargs, _CHANNELS))
if len(spec) == 0:
raise ValueError("Vega-Lite charts require a non-empty spec dict.")
if "autosize" not in spec:
spec["autosize"] = {"type": "fit", "contains": "padding"}
# Pull data out of spec dict when it's in a 'dataset' key:
# marshall(proto, {datasets: {foo: df1, bar: df2}, ...})
if "datasets" in spec:
for k, v in spec["datasets"].items():
dataset = proto.datasets.add()
dataset.name = str(k)
dataset.has_name = True
data_frame.marshall_data_frame(v, dataset.data)
del spec["datasets"]
# Pull data out of spec dict when it's in a top-level 'data' key:
# marshall(proto, {data: df})
# marshall(proto, {data: {values: df, ...}})
# marshall(proto, {data: {url: 'url'}})
# marshall(proto, {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"]
proto.spec = json.dumps(spec)
proto.use_container_width = use_container_width
if data is not None:
data_frame.marshall_data_frame(data, proto.data)
# See https://vega.github.io/vega-lite/docs/encoding.html
_CHANNELS = set(
[
"x",
"y",
"x2",
"y2",
"xError",
"yError2",
"xError",
"yError2",
"longitude",
"latitude",
"color",
"opacity",
"fillOpacity",
"strokeOpacity",
"strokeWidth",
"size",
"shape",
"text",
"tooltip",
"href",
"key",
"order",
"detail",
"facet",
"row",
"column",
]
)