def _button( self, label: str, key: Optional[str], help: Optional[str], is_form_submitter: bool, ) -> bool: button_proto = ButtonProto() # It doesn't make sense to create a button inside a form (except # for the "Form Submitter" button that's automatically created in # every form). We throw an error to warn the user about this. # We omit this check for scripts running outside streamlit, because # they will have no report_ctx. if streamlit._is_running_with_streamlit: if is_in_form(self.dg) and not is_form_submitter: raise StreamlitAPIException( f"`st.button()` can't be used in an `st.form()`.{FORM_DOCS_INFO}" ) elif not is_in_form(self.dg) and is_form_submitter: raise StreamlitAPIException( f"`st.form_submit_button()` must be used inside an `st.form()`.{FORM_DOCS_INFO}" ) button_proto.label = label button_proto.default = False button_proto.is_form_submitter = is_form_submitter button_proto.form_id = current_form_id(self.dg) if help is not None: button_proto.help = help ui_value = register_widget("button", button_proto, user_key=key) current_value = ui_value if ui_value is not None else False return self.dg._enqueue("button", button_proto, current_value) # type: ignore
def time_input(self, label, value=None, key=None, help=None): """Display a time input widget. Parameters ---------- label : str A short label explaining to the user what this time input is for. value : datetime.time/datetime.datetime The value of this widget when it first renders. This will be cast to str internally. Defaults to the current time. key : str An optional string to use as the unique key for the widget. If this is omitted, a key will be generated for the widget based on its content. Multiple widgets of the same type may not share the same key. help : str A tooltip that gets displayed next to the input. Returns ------- datetime.time The current value of the time input widget. Example ------- >>> t = st.time_input('Set an alarm for', datetime.time(8, 45)) >>> st.write('Alarm is set for', t) """ # Set value default. if value is None: value = datetime.now().time() # Ensure that the value is either datetime/time if not isinstance(value, datetime) and not isinstance(value, time): raise StreamlitAPIException( "The type of the value should be either datetime or time." ) # Convert datetime to time if isinstance(value, datetime): value = value.time() time_input_proto = TimeInputProto() time_input_proto.label = label time_input_proto.default = time.strftime(value, "%H:%M") if help is not None: time_input_proto.help = help ui_value = register_widget("time_input", time_input_proto, user_key=key) current_value = ( datetime.strptime(ui_value, "%H:%M").time() if ui_value is not None else value ) return self.dg._enqueue("time_input", time_input_proto, current_value)
def test_ids_are_diff_when_types_are_diff(self): text_input1 = TextInput() text_input1.label = "Label #1" text_input1.default = "Value #1" text_area2 = TextArea() text_area2.label = "Label #1" text_area2.default = "Value #1" element1 = Element() element1.text_input.CopyFrom(text_input1) element2 = Element() element2.text_area.CopyFrom(text_area2) register_widget("text_input", element1.text_input) register_widget("text_input", element2.text_input) self.assertNotEqual(element1.text_input.id, element2.text_area.id)
def test_ids_are_diff_when_values_are_diff(self): text_input1 = TextInput() text_input1.label = "Label #1" text_input1.default = "Value #1" text_input2 = TextInput() text_input2.label = "Label #1" text_input2.default = "Value #2" element1 = Element() element1.text_input.CopyFrom(text_input1) element2 = Element() element2.text_input.CopyFrom(text_input2) register_widget("text_input", element1.text_input, user_key="some_key1") register_widget("text_input", element2.text_input, user_key="some_key1") self.assertNotEqual(element1.text_input.id, element2.text_input.id)
def test_ids_are_equal_when_keys_are_equal(self): text_input1 = TextInput() text_input1.label = "Label #1" text_input1.default = "Value #1" text_input2 = TextInput() text_input2.label = "Label #1" text_input2.default = "Value #1" element1 = Element() element1.text_input.CopyFrom(text_input1) element2 = Element() element2.text_input.CopyFrom(text_input2) register_widget("text_input", element1.text_input, user_key="some_key") with self.assertRaises(DuplicateWidgetID): register_widget("text_input", element2.text_input, user_key="some_key")
def marshall_component(dg, element: Element) -> Union[Any, Type[NoValue]]: element.component_instance.component_name = self.name element.component_instance.form_id = current_form_id(dg) if self.url is not None: element.component_instance.url = self.url # Normally, a widget's element_hash (which determines # its identity across multiple runs of an app) is computed # by hashing the entirety of its protobuf. This means that, # if any of the arguments to the widget are changed, Streamlit # considers it a new widget instance and it loses its previous # state. # # However! If a *component* has a `key` argument, then the # component's hash identity is determined by entirely by # `component_name + url + key`. This means that, when `key` # exists, the component will maintain its identity even when its # other arguments change, and the component's iframe won't be # remounted on the frontend. # # So: if `key` is None, we marshall the element's arguments # *before* computing its widget_ui_value (which creates its hash). # If `key` is not None, we marshall the arguments *after*. def marshall_element_args(): element.component_instance.json_args = serialized_json_args element.component_instance.special_args.extend(special_args) if key is None: marshall_element_args() widget_value = register_widget( element_type="component_instance", element_proto=element.component_instance, user_key=key, widget_func_name=self.name, ) if key is not None: marshall_element_args() if widget_value is None: widget_value = default elif isinstance(widget_value, ArrowTableProto): widget_value = arrow_table.arrow_proto_to_dataframe( widget_value) # widget_value will be either None or whatever the component's most # recent setWidgetValue value is. We coerce None -> NoValue, # because that's what DeltaGenerator._enqueue expects. return widget_value if widget_value is not None else NoValue
def checkbox(self, label, value=False, key=None, help=None): """Display a checkbox widget. Parameters ---------- label : str A short label explaining to the user what this checkbox is for. value : bool Preselect the checkbox when it first renders. This will be cast to bool internally. key : str An optional string to use as the unique key for the widget. If this is omitted, a key will be generated for the widget based on its content. Multiple widgets of the same type may not share the same key. help : str A tooltip that gets displayed next to the checkbox. Returns ------- bool Whether or not the checkbox is checked. Example ------- >>> agree = st.checkbox('I agree') >>> >>> if agree: ... st.write('Great!') """ checkbox_proto = CheckboxProto() checkbox_proto.label = label checkbox_proto.default = bool(value) checkbox_proto.form_id = current_form_id(self.dg) if help is not None: checkbox_proto.help = help ui_value = register_widget("checkbox", checkbox_proto, user_key=key) current_value = ui_value if ui_value is not None else value return self.dg._enqueue("checkbox", checkbox_proto, bool(current_value))
def button(self, label, key=None, help=None): """Display a button widget. Parameters ---------- label : str A short label explaining to the user what this button is for. key : str An optional string to use as the unique key for the widget. If this is omitted, a key will be generated for the widget based on its content. Multiple widgets of the same type may not share the same key. help : str A tooltip that gets displayed when the button is hovered over. Returns ------- bool If the button was clicked on the last run of the app. Example ------- >>> if st.button('Say hello'): ... st.write('Why hello there') ... else: ... st.write('Goodbye') """ button_proto = ButtonProto() button_proto.label = label button_proto.default = False if help is not None: button_proto.help = help ui_value = register_widget("button", button_proto, user_key=key) current_value = ui_value if ui_value is not None else False return self.dg._enqueue("button", button_proto, current_value)
def number_input( self, label, min_value=None, max_value=None, value=NoValue(), step=None, format=None, key=None, help=None, ): """Display a numeric input widget. Parameters ---------- label : str A short label explaining to the user what this input is for. min_value : int or float or None The minimum permitted value. If None, there will be no minimum. max_value : int or float or None The maximum permitted value. If None, there will be no maximum. value : int or float or None The value of this widget when it first renders. Defaults to min_value, or 0.0 if min_value is None step : int or float or None The stepping interval. Defaults to 1 if the value is an int, 0.01 otherwise. If the value is not specified, the format parameter will be used. format : str or None A printf-style format string controlling how the interface should display numbers. Output must be purely numeric. This does not impact the return value. Valid formatters: %d %e %f %g %i %u key : str An optional string to use as the unique key for the widget. If this is omitted, a key will be generated for the widget based on its content. Multiple widgets of the same type may not share the same key. help : str A tooltip that gets displayed next to the input. Returns ------- int or float The current value of the numeric input widget. The return type will match the data type of the value parameter. Example ------- >>> number = st.number_input('Insert a number') >>> st.write('The current number is ', number) """ # Ensure that all arguments are of the same type. args = [min_value, max_value, value, step] int_args = all( isinstance(a, (numbers.Integral, type(None), NoValue)) for a in args) float_args = all( isinstance(a, (float, type(None), NoValue)) for a in args) if not int_args and not float_args: raise StreamlitAPIException( "All numerical arguments must be of the same type." f"\n`value` has {type(value).__name__} type." f"\n`min_value` has {type(min_value).__name__} type." f"\n`max_value` has {type(max_value).__name__} type." f"\n`step` has {type(step).__name__} type.") if isinstance(value, NoValue): if min_value is not None: value = min_value elif int_args and float_args: value = 0.0 # if no values are provided, defaults to float elif int_args: value = 0 else: value = 0.0 int_value = isinstance(value, numbers.Integral) float_value = isinstance(value, float) if value is None: raise StreamlitAPIException( "Default value for number_input should be an int or a float.") else: if format is None: format = "%d" if int_value else "%0.2f" # Warn user if they format an int type as a float or vice versa. if format in ["%d", "%u", "%i"] and float_value: import streamlit as st st.warning("Warning: NumberInput value below has type float," f" but format {format} displays as integer.") elif format[-1] == "f" and int_value: import streamlit as st st.warning( "Warning: NumberInput value below has type int so is" f" displayed as int despite format string {format}.") if step is None: step = 1 if int_value else 0.01 try: float(format % 2) except (TypeError, ValueError): raise StreamlitAPIException( "Format string for st.number_input contains invalid characters: %s" % format) # Ensure that the value matches arguments' types. all_ints = int_value and int_args if (min_value and min_value > value) or (max_value and max_value < value): raise StreamlitAPIException( "The default `value` of %(value)s " "must lie between the `min_value` of %(min)s " "and the `max_value` of %(max)s, inclusively." % { "value": value, "min": min_value, "max": max_value }) # Bounds checks. JSNumber produces human-readable exceptions that # we simply re-package as StreamlitAPIExceptions. try: if all_ints: if min_value is not None: JSNumber.validate_int_bounds(min_value, "`min_value`") if max_value is not None: JSNumber.validate_int_bounds(max_value, "`max_value`") if step is not None: JSNumber.validate_int_bounds(step, "`step`") JSNumber.validate_int_bounds(value, "`value`") else: if min_value is not None: JSNumber.validate_float_bounds(min_value, "`min_value`") if max_value is not None: JSNumber.validate_float_bounds(max_value, "`max_value`") if step is not None: JSNumber.validate_float_bounds(step, "`step`") JSNumber.validate_float_bounds(value, "`value`") except JSNumberBoundsException as e: raise StreamlitAPIException(str(e)) number_input_proto = NumberInputProto() number_input_proto.data_type = (NumberInputProto.INT if all_ints else NumberInputProto.FLOAT) number_input_proto.label = label number_input_proto.default = value number_input_proto.form_id = current_form_id(self.dg) if help is not None: number_input_proto.help = help if min_value is not None: number_input_proto.min = min_value number_input_proto.has_min = True if max_value is not None: number_input_proto.max = max_value number_input_proto.has_max = True if step is not None: number_input_proto.step = step if format is not None: number_input_proto.format = format ui_value = register_widget("number_input", number_input_proto, user_key=key) return_value = ui_value if ui_value is not None else value return self.dg._enqueue("number_input", number_input_proto, return_value)
def radio(self, label, options, index=0, format_func=str, key=None, help=None): """Display a radio button widget. Parameters ---------- label : str A short label explaining to the user what this radio group is for. options : list, tuple, numpy.ndarray, pandas.Series, or pandas.DataFrame Labels for the radio options. This will be cast to str internally by default. For pandas.DataFrame, the first column is selected. index : int The index of the preselected option on first render. format_func : function Function to modify the display of radio options. It receives the raw option as an argument and should output the label to be shown for that option. This has no impact on the return value of the radio. key : str An optional string to use as the unique key for the widget. If this is omitted, a key will be generated for the widget based on its content. Multiple widgets of the same type may not share the same key. help : str A tooltip that gets displayed next to the radio. Returns ------- any The selected option. Example ------- >>> genre = st.radio( ... "What\'s your favorite movie genre", ... ('Comedy', 'Drama', 'Documentary')) >>> >>> if genre == 'Comedy': ... st.write('You selected comedy.') ... else: ... st.write("You didn\'t select comedy.") """ options = ensure_iterable(options) if not isinstance(index, int): raise StreamlitAPIException("Radio Value has invalid type: %s" % type(index).__name__) if len(options) > 0 and not 0 <= index < len(options): raise StreamlitAPIException( "Radio index must be between 0 and length of options") radio_proto = RadioProto() radio_proto.label = label radio_proto.default = index radio_proto.options[:] = [ str(format_func(option)) for option in options ] radio_proto.form_id = current_form_id(self.dg) if help is not None: radio_proto.help = help ui_value = register_widget("radio", radio_proto, user_key=key) current_value = ui_value if ui_value is not None else index return_value = (options[current_value] if len(options) > 0 and options[current_value] is not None else NoValue) return self.dg._enqueue("radio", radio_proto, return_value)
def date_input( self, label, value=None, min_value=None, max_value=None, key=None, help=None, ): """Display a date input widget. Parameters ---------- label : str A short label explaining to the user what this date input is for. value : datetime.date or datetime.datetime or list/tuple of datetime.date or datetime.datetime or None The value of this widget when it first renders. If a list/tuple with 0 to 2 date/datetime values is provided, the datepicker will allow users to provide a range. Defaults to today as a single-date picker. min_value : datetime.date or datetime.datetime The minimum selectable date. Defaults to today-10y. max_value : datetime.date or datetime.datetime The maximum selectable date. Defaults to today+10y. key : str An optional string to use as the unique key for the widget. If this is omitted, a key will be generated for the widget based on its content. Multiple widgets of the same type may not share the same key. help : str A tooltip that gets displayed next to the input. Returns ------- datetime.date The current value of the date input widget. Example ------- >>> d = st.date_input( ... "When\'s your birthday", ... datetime.date(2019, 7, 6)) >>> st.write('Your birthday is:', d) """ # Set value default. if value is None: value = datetime.now().date() single_value = isinstance(value, (date, datetime)) range_value = isinstance(value, (list, tuple)) and len(value) in (0, 1, 2) if not single_value and not range_value: raise StreamlitAPIException( "DateInput value should either be an date/datetime or a list/tuple of " "0 - 2 date/datetime values") if single_value: value = [value] date_input_proto = DateInputProto() date_input_proto.is_range = range_value if help is not None: date_input_proto.help = help value = [v.date() if isinstance(v, datetime) else v for v in value] date_input_proto.label = label date_input_proto.default[:] = [ date.strftime(v, "%Y/%m/%d") for v in value ] if isinstance(min_value, datetime): min_value = min_value.date() elif min_value is None: today = date.today() min_value = date(today.year - 10, today.month, today.day) date_input_proto.min = date.strftime(min_value, "%Y/%m/%d") if max_value is None: today = date.today() max_value = date(today.year + 10, today.month, today.day) if isinstance(max_value, datetime): max_value = max_value.date() date_input_proto.max = date.strftime(max_value, "%Y/%m/%d") date_input_proto.form_id = current_form_id(self.dg) ui_value = register_widget("date_input", date_input_proto, user_key=key) if ui_value is not None: value = getattr(ui_value, "data") value = [datetime.strptime(v, "%Y/%m/%d").date() for v in value] return_value = value[0] if single_value else tuple(value) return self.dg._enqueue("date_input", date_input_proto, return_value)
def color_picker(self, label, value=None, key=None, help=None): """Display a color picker widget. Parameters ---------- label : str A short label explaining to the user what this input is for. value : str or None The hex value of this widget when it first renders. If None, defaults to black. key : str An optional string to use as the unique key for the widget. If this is omitted, a key will be generated for the widget based on its content. Multiple widgets of the same type may not share the same key. help : str A tooltip that gets displayed next to the color picker. Returns ------- str The selected color as a hex string. Example ------- >>> color = st.color_picker('Pick A Color', '#00f900') >>> st.write('The current color is', color) """ # set value default if value is None: value = "#000000" # make sure the value is a string if not isinstance(value, str): raise StreamlitAPIException(""" Color Picker Value has invalid type: %s. Expects a hex string like '#00FFAA' or '#000'. """ % type(value).__name__) # validate the value and expects a hex string match = re.match(r"^#(?:[0-9a-fA-F]{3}){1,2}$", value) if not match: raise StreamlitAPIException(""" '%s' is not a valid hex code for colors. Valid ones are like '#00FFAA' or '#000'. """ % value) color_picker_proto = ColorPickerProto() color_picker_proto.label = label color_picker_proto.default = str(value) if help is not None: color_picker_proto.help = help ui_value = register_widget("color_picker", color_picker_proto, user_key=key) current_value = ui_value if ui_value is not None else value return self.dg._enqueue("color_picker", color_picker_proto, str(current_value))
def slider( self, label, min_value=None, max_value=None, value=None, step=None, format=None, key=None, help=None, ): """Display a slider widget. This supports int, float, date, time, and datetime types. This also allows you to render a range slider by passing a two-element tuple or list as the `value`. The difference between `st.slider` and `st.select_slider` is that `slider` only accepts numerical or date/time data and takes a range as input, while `select_slider` accepts any datatype and takes an iterable set of options. Parameters ---------- label : str A short label explaining to the user what this slider is for. min_value : a supported type or None The minimum permitted value. Defaults to 0 if the value is an int, 0.0 if a float, value - timedelta(days=14) if a date/datetime, time.min if a time max_value : a supported type or None The maximum permitted value. Defaults to 100 if the value is an int, 1.0 if a float, value + timedelta(days=14) if a date/datetime, time.max if a time value : a supported type or a tuple/list of supported types or None The value of the slider when it first renders. If a tuple/list of two values is passed here, then a range slider with those lower and upper bounds is rendered. For example, if set to `(1, 10)` the slider will have a selectable range between 1 and 10. Defaults to min_value. step : int/float/timedelta or None The stepping interval. Defaults to 1 if the value is an int, 0.01 if a float, timedelta(days=1) if a date/datetime, timedelta(minutes=15) if a time (or if max_value - min_value < 1 day) format : str or None A printf-style format string controlling how the interface should display numbers. This does not impact the return value. Formatter for int/float supports: %d %e %f %g %i Formatter for date/time/datetime uses Moment.js notation: https://momentjs.com/docs/#/displaying/format/ key : str An optional string to use as the unique key for the widget. If this is omitted, a key will be generated for the widget based on its content. Multiple widgets of the same type may not share the same key. help : str A tooltip that gets displayed next to the slider. Returns ------- int/float/date/time/datetime or tuple of int/float/date/time/datetime The current value of the slider widget. The return type will match the data type of the value parameter. Examples -------- >>> age = st.slider('How old are you?', 0, 130, 25) >>> st.write("I'm ", age, 'years old') And here's an example of a range slider: >>> values = st.slider( ... 'Select a range of values', ... 0.0, 100.0, (25.0, 75.0)) >>> st.write('Values:', values) This is a range time slider: >>> from datetime import time >>> appointment = st.slider( ... "Schedule your appointment:", ... value=(time(11, 30), time(12, 45))) >>> st.write("You're scheduled for:", appointment) Finally, a datetime slider: >>> from datetime import datetime >>> start_time = st.slider( ... "When do you start?", ... value=datetime(2020, 1, 1, 9, 30), ... format="MM/DD/YY - hh:mm") >>> st.write("Start time:", start_time) """ # Set value default. if value is None: value = min_value if min_value is not None else 0 SUPPORTED_TYPES = { int: SliderProto.INT, float: SliderProto.FLOAT, datetime: SliderProto.DATETIME, date: SliderProto.DATE, time: SliderProto.TIME, } TIMELIKE_TYPES = (SliderProto.DATETIME, SliderProto.TIME, SliderProto.DATE) # Ensure that the value is either a single value or a range of values. single_value = isinstance(value, tuple(SUPPORTED_TYPES.keys())) range_value = isinstance(value, (list, tuple)) and len(value) in (0, 1, 2) if not single_value and not range_value: raise StreamlitAPIException( "Slider value should either be an int/float/datetime or a list/tuple of " "0 to 2 ints/floats/datetimes") # Simplify future logic by always making value a list if single_value: value = [value] def all_same_type(items): return len(set(map(type, items))) < 2 if not all_same_type(value): raise StreamlitAPIException( "Slider tuple/list components must be of the same type.\n" f"But were: {list(map(type, value))}") if len(value) == 0: data_type = SliderProto.INT else: data_type = SUPPORTED_TYPES[type(value[0])] datetime_min = time.min datetime_max = time.max if data_type == SliderProto.TIME: datetime_min = time.min.replace(tzinfo=value[0].tzinfo) datetime_max = time.max.replace(tzinfo=value[0].tzinfo) if data_type in (SliderProto.DATETIME, SliderProto.DATE): datetime_min = value[0] - timedelta(days=14) datetime_max = value[0] + timedelta(days=14) DEFAULTS = { SliderProto.INT: { "min_value": 0, "max_value": 100, "step": 1, "format": "%d", }, SliderProto.FLOAT: { "min_value": 0.0, "max_value": 1.0, "step": 0.01, "format": "%0.2f", }, SliderProto.DATETIME: { "min_value": datetime_min, "max_value": datetime_max, "step": timedelta(days=1), "format": "YYYY-MM-DD", }, SliderProto.DATE: { "min_value": datetime_min, "max_value": datetime_max, "step": timedelta(days=1), "format": "YYYY-MM-DD", }, SliderProto.TIME: { "min_value": datetime_min, "max_value": datetime_max, "step": timedelta(minutes=15), "format": "HH:mm", }, } if min_value is None: min_value = DEFAULTS[data_type]["min_value"] if max_value is None: max_value = DEFAULTS[data_type]["max_value"] if step is None: step = DEFAULTS[data_type]["step"] if (data_type in ( SliderProto.DATETIME, SliderProto.DATE, ) and max_value - min_value < timedelta(days=1)): step = timedelta(minutes=15) if format is None: format = DEFAULTS[data_type]["format"] if step == 0: raise StreamlitAPIException( "Slider components cannot be passed a `step` of 0.") # Ensure that all arguments are of the same type. args = [min_value, max_value, step] int_args = all(map(lambda a: isinstance(a, int), args)) float_args = all(map(lambda a: isinstance(a, float), args)) # When min and max_value are the same timelike, step should be a timedelta timelike_args = (data_type in TIMELIKE_TYPES and isinstance(step, timedelta) and type(min_value) == type(max_value)) if not int_args and not float_args and not timelike_args: raise StreamlitAPIException( "Slider value arguments must be of matching types." "\n`min_value` has %(min_type)s type." "\n`max_value` has %(max_type)s type." "\n`step` has %(step)s type." % { "min_type": type(min_value).__name__, "max_type": type(max_value).__name__, "step": type(step).__name__, }) # Ensure that the value matches arguments' types. all_ints = data_type == SliderProto.INT and int_args all_floats = data_type == SliderProto.FLOAT and float_args all_timelikes = data_type in TIMELIKE_TYPES and timelike_args if not all_ints and not all_floats and not all_timelikes: raise StreamlitAPIException( "Both value and arguments must be of the same type." "\n`value` has %(value_type)s type." "\n`min_value` has %(min_type)s type." "\n`max_value` has %(max_type)s type." % { "value_type": type(value).__name__, "min_type": type(min_value).__name__, "max_type": type(max_value).__name__, }) # Ensure that min <= value(s) <= max, adjusting the bounds as necessary. min_value = min(min_value, max_value) max_value = max(min_value, max_value) if len(value) == 1: min_value = min(value[0], min_value) max_value = max(value[0], max_value) elif len(value) == 2: start, end = value if start > end: # Swap start and end, since they seem reversed start, end = end, start value = start, end min_value = min(start, min_value) max_value = max(end, max_value) else: # Empty list, so let's just use the outer bounds value = [min_value, max_value] # Bounds checks. JSNumber produces human-readable exceptions that # we simply re-package as StreamlitAPIExceptions. # (We check `min_value` and `max_value` here; `value` and `step` are # already known to be in the [min_value, max_value] range.) try: if all_ints: JSNumber.validate_int_bounds(min_value, "`min_value`") JSNumber.validate_int_bounds(max_value, "`max_value`") elif all_floats: JSNumber.validate_float_bounds(min_value, "`min_value`") JSNumber.validate_float_bounds(max_value, "`max_value`") elif all_timelikes: # No validation yet. TODO: check between 0001-01-01 to 9999-12-31 pass except JSNumberBoundsException as e: raise StreamlitAPIException(str(e)) # Convert dates or times into datetimes if data_type == SliderProto.TIME: def _time_to_datetime(time): # Note, here we pick an arbitrary date well after Unix epoch. # This prevents pre-epoch timezone issues (https://bugs.python.org/issue36759) # We're dropping the date from datetime laters, anyways. return datetime.combine(date(2000, 1, 1), time) value = list(map(_time_to_datetime, value)) min_value = _time_to_datetime(min_value) max_value = _time_to_datetime(max_value) if data_type == SliderProto.DATE: def _date_to_datetime(date): return datetime.combine(date, time()) value = list(map(_date_to_datetime, value)) min_value = _date_to_datetime(min_value) max_value = _date_to_datetime(max_value) # Now, convert to microseconds (so we can serialize datetime to a long) if data_type in TIMELIKE_TYPES: SECONDS_TO_MICROS = 1000 * 1000 DAYS_TO_MICROS = 24 * 60 * 60 * SECONDS_TO_MICROS def _delta_to_micros(delta): return (delta.microseconds + delta.seconds * SECONDS_TO_MICROS + delta.days * DAYS_TO_MICROS) UTC_EPOCH = datetime(1970, 1, 1, tzinfo=timezone.utc) def _datetime_to_micros(dt): # If dt is naive, Python converts from local time utc_dt = dt.astimezone(timezone.utc) return _delta_to_micros(utc_dt - UTC_EPOCH) # Restore times/datetimes to original timezone (dates are always naive) orig_tz = (value[0].tzinfo if data_type in (SliderProto.TIME, SliderProto.DATETIME) else None) def _micros_to_datetime(micros): utc_dt = UTC_EPOCH + timedelta(microseconds=micros) # Convert from utc back to original time (local time if naive) return utc_dt.astimezone(orig_tz).replace(tzinfo=orig_tz) value = list(map(_datetime_to_micros, value)) min_value = _datetime_to_micros(min_value) max_value = _datetime_to_micros(max_value) step = _delta_to_micros(step) # It would be great if we could guess the number of decimal places from # the `step` argument, but this would only be meaningful if step were a # decimal. As a possible improvement we could make this function accept # decimals and/or use some heuristics for floats. slider_proto = SliderProto() slider_proto.label = label slider_proto.format = format slider_proto.default[:] = value slider_proto.min = min_value slider_proto.max = max_value slider_proto.step = step slider_proto.data_type = data_type slider_proto.options[:] = [] slider_proto.form_id = current_form_id(self.dg) if help is not None: slider_proto.help = help ui_value = register_widget("slider", slider_proto, user_key=key) if ui_value: current_value = getattr(ui_value, "data") else: # Widget has not been used; fallback to the original value, current_value = value # The widget always returns a float array, so fix the return type if necessary if data_type == SliderProto.INT: current_value = list(map(int, current_value)) if data_type == SliderProto.DATETIME: current_value = [ _micros_to_datetime(int(v)) for v in current_value ] if data_type == SliderProto.DATE: current_value = [ _micros_to_datetime(int(v)).date() for v in current_value ] if data_type == SliderProto.TIME: current_value = [ _micros_to_datetime(int(v)).time().replace(tzinfo=orig_tz) for v in current_value ] # If the original value was a list/tuple, so will be the output (and vice versa) return_value = current_value[0] if single_value else tuple( current_value) return self.dg._enqueue("slider", slider_proto, return_value)
def select_slider( self, label, options=[], value=None, format_func=str, key=None, help=None, ): """ Display a slider widget to select items from a list. This also allows you to render a range slider by passing a two-element tuple or list as the `value`. The difference between `st.select_slider` and `st.slider` is that `select_slider` accepts any datatype and takes an iterable set of options, while `slider` only accepts numerical or date/time data and takes a range as input. Parameters ---------- label : str A short label explaining to the user what this slider is for. options : list, tuple, numpy.ndarray, pandas.Series, or pandas.DataFrame Labels for the slider options. All options will be cast to str internally by default. For pandas.DataFrame, the first column is selected. value : a supported type or a tuple/list of supported types or None The value of the slider when it first renders. If a tuple/list of two values is passed here, then a range slider with those lower and upper bounds is rendered. For example, if set to `(1, 10)` the slider will have a selectable range between 1 and 10. Defaults to first option. format_func : function Function to modify the display of the labels from the options. argument. It receives the option as an argument and its output will be cast to str. key : str An optional string to use as the unique key for the widget. If this is omitted, a key will be generated for the widget based on its content. Multiple widgets of the same type may not share the same key. help : str A tooltip that gets displayed next to the select slider. Returns ------- any value or tuple of any value The current value of the slider widget. The return type will match the data type of the value parameter. Examples -------- >>> color = st.select_slider( ... 'Select a color of the rainbow', ... options=['red', 'orange', 'yellow', 'green', 'blue', 'indigo', 'violet']) >>> st.write('My favorite color is', color) And here's an example of a range select slider: >>> start_color, end_color = st.select_slider( ... 'Select a range of color wavelength', ... options=['red', 'orange', 'yellow', 'green', 'blue', 'indigo', 'violet'], ... value=('red', 'blue')) >>> st.write('You selected wavelengths between', start_color, 'and', end_color) """ options = ensure_iterable(options) if len(options) == 0: raise StreamlitAPIException( "The `options` argument needs to be non-empty") is_range_value = isinstance(value, (list, tuple)) slider_value = value # Convert element to index of the elements if is_range_value: slider_value = list(map(lambda v: options.index(v), value)) # type: ignore[no-any-return] start, end = slider_value if start > end: slider_value = [end, start] else: # Simplify future logic by always making value a list try: slider_value = [options.index(value)] except ValueError: if value is not None: raise slider_value = [0] slider_proto = SliderProto() slider_proto.label = label slider_proto.format = "%s" slider_proto.default[:] = slider_value slider_proto.min = 0 slider_proto.max = len(options) - 1 slider_proto.step = 1 # default for index changes slider_proto.data_type = SliderProto.INT slider_proto.options[:] = [ str(format_func(option)) for option in options ] slider_proto.form_id = current_form_id(self.dg) if help is not None: slider_proto.help = help ui_value = register_widget("slider", slider_proto, user_key=key) if ui_value: current_value = getattr(ui_value, "data") else: # Widget has not been used; fallback to the original value, current_value = slider_value # The widget always returns floats, so convert to ints before indexing current_value = list(map(lambda x: options[int(x)], current_value)) # type: ignore[no-any-return] # If the original value was a list/tuple, so will be the output (and vice versa) return_value = tuple( current_value) if is_range_value else current_value[0] return self.dg._enqueue("slider", slider_proto, return_value)
def text_input(self, label, value="", max_chars=None, key=None, type="default", help=None): """Display a single-line text input widget. Parameters ---------- label : str A short label explaining to the user what this input is for. value : any The text value of this widget when it first renders. This will be cast to str internally. max_chars : int or None Max number of characters allowed in text input. key : str An optional string to use as the unique key for the widget. If this is omitted, a key will be generated for the widget based on its content. Multiple widgets of the same type may not share the same key. type : str The type of the text input. This can be either "default" (for a regular text input), or "password" (for a text input that masks the user's typed value). Defaults to "default". help : str A tooltip that gets displayed next to the input. Returns ------- str The current value of the text input widget. Example ------- >>> title = st.text_input('Movie title', 'Life of Brian') >>> st.write('The current movie title is', title) """ text_input_proto = TextInputProto() text_input_proto.label = label text_input_proto.default = str(value) text_input_proto.form_id = current_form_id(self.dg) if help is not None: text_input_proto.help = help if max_chars is not None: text_input_proto.max_chars = max_chars if type == "default": text_input_proto.type = TextInputProto.DEFAULT elif type == "password": text_input_proto.type = TextInputProto.PASSWORD else: raise StreamlitAPIException( "'%s' is not a valid text_input type. Valid types are 'default' and 'password'." % type) ui_value = register_widget("text_input", text_input_proto, user_key=key) current_value = ui_value if ui_value is not None else value return self.dg._enqueue("text_input", text_input_proto, str(current_value))
def selectbox(self, label, options, index=0, format_func=str, key=None, help=None): """Display a select widget. Parameters ---------- label : str A short label explaining to the user what this select widget is for. options : list, tuple, numpy.ndarray, pandas.Series, or pandas.DataFrame Labels for the select options. This will be cast to str internally by default. For pandas.DataFrame, the first column is selected. index : int The index of the preselected option on first render. format_func : function Function to modify the display of the labels. It receives the option as an argument and its output will be cast to str. key : str An optional string to use as the unique key for the widget. If this is omitted, a key will be generated for the widget based on its content. Multiple widgets of the same type may not share the same key. help : str A tooltip that gets displayed next to the selectbox. Returns ------- any The selected option Example ------- >>> option = st.selectbox( ... 'How would you like to be contacted?', ... ('Email', 'Home phone', 'Mobile phone')) >>> >>> st.write('You selected:', option) """ options = ensure_iterable(options) if not isinstance(index, int): raise StreamlitAPIException( "Selectbox Value has invalid type: %s" % type(index).__name__) if len(options) > 0 and not 0 <= index < len(options): raise StreamlitAPIException( "Selectbox index must be between 0 and length of options") selectbox_proto = SelectboxProto() selectbox_proto.label = label selectbox_proto.default = index selectbox_proto.options[:] = [ str(format_func(option)) for option in options ] if help is not None: selectbox_proto.help = help ui_value = register_widget("selectbox", selectbox_proto, user_key=key) current_value = ui_value if ui_value is not None else index return_value = (options[current_value] if len(options) > 0 and options[current_value] is not None else NoValue) return self.dg._enqueue("selectbox", selectbox_proto, return_value)
def text_area(self, label, value="", height=None, max_chars=None, key=None, help=None): """Display a multi-line text input widget. Parameters ---------- label : str A short label explaining to the user what this input is for. value : any The text value of this widget when it first renders. This will be cast to str internally. height : int or None Desired height of the UI element expressed in pixels. If None, a default height is used. max_chars : int or None Maximum number of characters allowed in text area. key : str An optional string to use as the unique key for the widget. If this is omitted, a key will be generated for the widget based on its content. Multiple widgets of the same type may not share the same key. help : str A tooltip that gets displayed next to the textarea. Returns ------- str The current value of the text input widget. Example ------- >>> txt = st.text_area('Text to analyze', ''' ... It was the best of times, it was the worst of times, it was ... the age of wisdom, it was the age of foolishness, it was ... the epoch of belief, it was the epoch of incredulity, it ... was the season of Light, it was the season of Darkness, it ... was the spring of hope, it was the winter of despair, (...) ... ''') >>> st.write('Sentiment:', run_sentiment_analysis(txt)) """ text_area_proto = TextAreaProto() text_area_proto.label = label text_area_proto.default = str(value) text_area_proto.form_id = current_form_id(self.dg) if help is not None: text_area_proto.help = help if height is not None: text_area_proto.height = height if max_chars is not None: text_area_proto.max_chars = max_chars ui_value = register_widget("text_area", text_area_proto, user_key=key) current_value = ui_value if ui_value is not None else value return self.dg._enqueue("text_area", text_area_proto, str(current_value))
def multiselect( self, label, options, default=None, format_func=str, key=None, help=None ): """Display a multiselect widget. The multiselect widget starts as empty. Parameters ---------- label : str A short label explaining to the user what this select widget is for. options : list, tuple, numpy.ndarray, pandas.Series, or pandas.DataFrame Labels for the select options. This will be cast to str internally by default. For pandas.DataFrame, the first column is selected. default: [str] or None List of default values. format_func : function Function to modify the display of selectbox options. It receives the raw option as an argument and should output the label to be shown for that option. This has no impact on the return value of the selectbox. key : str An optional string to use as the unique key for the widget. If this is omitted, a key will be generated for the widget based on its content. Multiple widgets of the same type may not share the same key. help : str A tooltip that gets displayed next to the multiselect. Returns ------- [str] A list with the selected options Example ------- >>> options = st.multiselect( ... 'What are your favorite colors', ... ['Green', 'Yellow', 'Red', 'Blue'], ... ['Yellow', 'Red']) >>> >>> st.write('You selected:', options) .. note:: User experience can be degraded for large lists of `options` (100+), as this widget is not designed to handle arbitrary text search efficiently. See this `thread <https://discuss.streamlit.io/t/streamlit-loading-column-data-takes-too-much-time/1791>`_ on the Streamlit community forum for more information and `GitHub issue #1059 <https://github.com/streamlit/streamlit/issues/1059>`_ for updates on the issue. """ options = ensure_iterable(options) # Perform validation checks and return indices base on the default values. def _check_and_convert_to_indices(options, default_values): if default_values is None and None not in options: return None if not isinstance(default_values, list): # This if is done before others because calling if not x (done # right below) when x is of type pd.Series() or np.array() throws a # ValueError exception. if is_type(default_values, "numpy.ndarray") or is_type( default_values, "pandas.core.series.Series" ): default_values = list(default_values) elif not default_values or default_values in options: default_values = [default_values] else: default_values = list(default_values) for value in default_values: if value not in options: raise StreamlitAPIException( "Every Multiselect default value must exist in options" ) return [options.index(value) for value in default_values] indices = _check_and_convert_to_indices(options, default) multiselect_proto = MultiSelectProto() multiselect_proto.label = label default_value = [] if indices is None else indices multiselect_proto.default[:] = default_value multiselect_proto.options[:] = [str(format_func(option)) for option in options] multiselect_proto.form_id = current_form_id(self.dg) if help is not None: multiselect_proto.help = help ui_value = register_widget("multiselect", multiselect_proto, user_key=key) current_value = ui_value.data if ui_value is not None else default_value return_value = [options[i] for i in current_value] return self.dg._enqueue("multiselect", multiselect_proto, return_value)