diff --git a/README.md b/README.md index 11f117aca59..6ee878a7dfe 100644 --- a/README.md +++ b/README.md @@ -149,5 +149,5 @@ Code and documentation copyright 2019 Plotly, Inc. Code released under the [MIT license](https://github.com/plotly/plotly.py/blob/main/LICENSE.txt). -Docs released under the [Creative Commons license](https://github.com/plotly/documentation/blob/source/LICENSE). +Docs released under the [Creative Commons license](https://creativecommons.org/licenses/by/4.0/). diff --git a/plotly/basedatatypes.py b/plotly/basedatatypes.py index 1384e08d543..1f7fbb085e5 100644 --- a/plotly/basedatatypes.py +++ b/plotly/basedatatypes.py @@ -1627,7 +1627,7 @@ def _add_domain(ax_letter, new_axref): xref, yref = map(lambda t: _add_domain(*t), zip(["x", "y"], [xref, yref])) new_obj.update(xref=xref, yref=yref) - self.layout[prop_plural] += (new_obj,) + self.layout._append_array_prop(prop_plural, new_obj) # The 'new_obj.xref' and 'new_obj.yref' parameters need to be reset otherwise it # will appear as if user supplied yref params when looping through subplots and # will force annotation to be on the axis of the last drawn annotation @@ -3290,13 +3290,20 @@ def _perform_batch_animate(self, animation_opts): # Exports # ------- - def to_dict(self): + def to_dict(self, encode_base64=True): """ Convert figure to a dictionary Note: the dictionary includes the properties explicitly set by the user, it does not include default values of unspecified properties + Parameters + ---------- + encode_base64: bool (default True) + If True, large numerical arrays will be converted to a binary + base64 encoding (bdata). If False, these arrays will remain + as numpy arrays or lists. + Returns ------- dict @@ -3319,22 +3326,30 @@ def to_dict(self): res["frames"] = frames # Add base64 conversion before sending to the front-end - convert_to_base64(res) + if encode_base64: + convert_to_base64(res) return res - def to_plotly_json(self): + def to_plotly_json(self, encode_base64=True): """ Convert figure to a JSON representation as a Python dict Note: May include some JSON-invalid data types, use the `PlotlyJSONEncoder` util or the `to_json` method to encode to a string. + Parameters + ---------- + encode_base64: bool (default True) + If True, large numerical arrays will be converted to a binary + base64 encoding (bdata). If False, these arrays will remain + as numpy arrays or lists. + Returns ------- dict """ - return self.to_dict() + return self.to_dict(encode_base64=encode_base64) @staticmethod def _to_ordered_dict(d, skip_uid=False): @@ -4124,21 +4139,13 @@ def _process_multiple_axis_spanning_shapes( self.layout[layout_obj][-1].update(xref="x") if self.layout[layout_obj][-1].yref is None: self.layout[layout_obj][-1].update(yref="y") - new_layout_objs = tuple( - filter( - lambda x: x is not None, - [ - self._make_axis_spanning_layout_object( - direction, - self.layout[layout_obj][n], - ) - for n in range(n_layout_objs_before, n_layout_objs_after) - ], + + # Update the new objects in-place to append " domain" to xref/yref + for n in range(n_layout_objs_before, n_layout_objs_after): + self._make_axis_spanning_layout_object( + direction, + self.layout[layout_obj][n], ) - ) - self.layout[layout_obj] = ( - self.layout[layout_obj][:n_layout_objs_before] + new_layout_objs - ) def add_vline( self, @@ -5380,6 +5387,54 @@ def _set_compound_prop(self, prop, val): self._compound_props[prop] = val return val + def _append_array_prop(self, prop, new_element): + """ + Append a single element to a compound array property without + re-validating all existing elements. + + This provides O(1) appends instead of O(N) re-validation that + occurs with _set_array_prop when called via ``+=``. + + Parameters + ---------- + prop : str + Name of a compound array property (e.g. 'annotations') + new_element : BasePlotlyType + The already-constructed element to append. Must be an instance + of the appropriate data class for this property. + """ + # Get or initialize the existing compound array + curr_val = self._compound_array_props.get(prop, ()) + if curr_val is None: + curr_val = () + elif isinstance(curr_val, list): + curr_val = tuple(curr_val) + + # Create a copy of the new element (same pattern as + # CompoundArrayValidator.validate_coerce) so the caller can + # safely modify/reset the original without affecting the stored copy. + element_copy = type(new_element)(new_element) + + # Make a deep copy of new element's props for _props storage + new_dict = deepcopy(element_copy._props) if element_copy._props else {} + + # Update _props dict + if not self._in_batch_mode: + self._init_props() + if prop not in self._props: + self._props[prop] = [] + self._props[prop].append(new_dict) + + # Send update notification + self._send_prop_set(prop, self._props.get(prop)) + + # Reparent the copy + element_copy._orphan_props.clear() + element_copy._parent = self + + # Append to _compound_array_props + self._compound_array_props[prop] = curr_val + (element_copy,) + def _set_array_prop(self, prop, val): """ Set the value of a compound property diff --git a/plotly/express/_chart_types.py b/plotly/express/_chart_types.py index 2b4b10184e8..25427520beb 100644 --- a/plotly/express/_chart_types.py +++ b/plotly/express/_chart_types.py @@ -3,6 +3,18 @@ from ._core import make_figure from ._doc import make_docstring import plotly.graph_objs as go +from typing import Any, Literal, Sequence, Mapping, Optional, Union, TYPE_CHECKING + +if TYPE_CHECKING: + import pandas as pd + +# Type aliases for common parameter types +DataFrameLike = Union["pd.DataFrame", Mapping[str, Any], None] +ColumnRef = Union[str, int, Sequence[str], Sequence[int], None] +ColorScale = Union[str, Sequence[str], Sequence[tuple[float, str]], None] +SequenceLike = Union[Sequence[Any], None] +MappingLike = Union[Mapping[Any, Any], None] +RangeLike = Union[Sequence[float], None] _wide_mode_xy_append = [ "Either `x` or `y` can optionally be a list of column references or array_likes, ", @@ -12,55 +24,55 @@ def scatter( - data_frame=None, - x=None, - y=None, - color=None, - symbol=None, - size=None, - hover_name=None, - hover_data=None, - custom_data=None, - text=None, - facet_row=None, - facet_col=None, - facet_col_wrap=0, - facet_row_spacing=None, - facet_col_spacing=None, - error_x=None, - error_x_minus=None, - error_y=None, - error_y_minus=None, - animation_frame=None, - animation_group=None, - category_orders=None, - labels=None, - orientation=None, - color_discrete_sequence=None, - color_discrete_map=None, - color_continuous_scale=None, - range_color=None, - color_continuous_midpoint=None, - symbol_sequence=None, - symbol_map=None, - opacity=None, - size_max=None, - marginal_x=None, - marginal_y=None, - trendline=None, - trendline_options=None, - trendline_color_override=None, - trendline_scope="trace", - log_x=False, - log_y=False, - range_x=None, - range_y=None, - render_mode="auto", - title=None, - subtitle=None, - template=None, - width=None, - height=None, + data_frame: DataFrameLike = None, + x: ColumnRef = None, + y: ColumnRef = None, + color: ColumnRef = None, + symbol: ColumnRef = None, + size: ColumnRef = None, + hover_name: ColumnRef = None, + hover_data: Union[SequenceLike, MappingLike] = None, + custom_data: SequenceLike = None, + text: ColumnRef = None, + facet_row: ColumnRef = None, + facet_col: ColumnRef = None, + facet_col_wrap: int = 0, + facet_row_spacing: Optional[float] = None, + facet_col_spacing: Optional[float] = None, + error_x: ColumnRef = None, + error_x_minus: ColumnRef = None, + error_y: ColumnRef = None, + error_y_minus: ColumnRef = None, + animation_frame: ColumnRef = None, + animation_group: ColumnRef = None, + category_orders: MappingLike = None, + labels: MappingLike = None, + orientation: Optional[Literal["v", "h"]] = None, + color_discrete_sequence: SequenceLike = None, + color_discrete_map: MappingLike = None, + color_continuous_scale: ColorScale = None, + range_color: RangeLike = None, + color_continuous_midpoint: Optional[float] = None, + symbol_sequence: SequenceLike = None, + symbol_map: MappingLike = None, + opacity: Optional[float] = None, + size_max: Optional[float] = None, + marginal_x: Optional[Literal["rug", "box", "violin", "histogram"]] = None, + marginal_y: Optional[Literal["rug", "box", "violin", "histogram"]] = None, + trendline: Optional[Literal["ols", "lowess", "rolling", "expanding", "ewm"]] = None, + trendline_options: MappingLike = None, + trendline_color_override: Optional[str] = None, + trendline_scope: Literal["trace", "overall"] = "trace", + log_x: bool = False, + log_y: bool = False, + range_x: RangeLike = None, + range_y: RangeLike = None, + render_mode: Literal["auto", "svg", "webgl"] = "auto", + title: Optional[str] = None, + subtitle: Optional[str] = None, + template: Union[str, go.layout.Template, None] = None, + width: Optional[int] = None, + height: Optional[int] = None, ) -> go.Figure: """ In a scatter plot, each row of `data_frame` is represented by a symbol @@ -73,45 +85,47 @@ def scatter( def density_contour( - data_frame=None, - x=None, - y=None, - z=None, - color=None, - facet_row=None, - facet_col=None, - facet_col_wrap=0, - facet_row_spacing=None, - facet_col_spacing=None, - hover_name=None, - hover_data=None, - animation_frame=None, - animation_group=None, - category_orders=None, - labels=None, - orientation=None, - color_discrete_sequence=None, - color_discrete_map=None, - marginal_x=None, - marginal_y=None, - trendline=None, - trendline_options=None, - trendline_color_override=None, - trendline_scope="trace", - log_x=False, - log_y=False, - range_x=None, - range_y=None, - histfunc=None, - histnorm=None, - nbinsx=None, - nbinsy=None, - text_auto=False, - title=None, - subtitle=None, - template=None, - width=None, - height=None, + data_frame: DataFrameLike = None, + x: ColumnRef = None, + y: ColumnRef = None, + z: ColumnRef = None, + color: ColumnRef = None, + facet_row: ColumnRef = None, + facet_col: ColumnRef = None, + facet_col_wrap: int = 0, + facet_row_spacing: Optional[float] = None, + facet_col_spacing: Optional[float] = None, + hover_name: ColumnRef = None, + hover_data: Union[SequenceLike, MappingLike] = None, + animation_frame: ColumnRef = None, + animation_group: ColumnRef = None, + category_orders: MappingLike = None, + labels: MappingLike = None, + orientation: Optional[Literal["v", "h"]] = None, + color_discrete_sequence: SequenceLike = None, + color_discrete_map: MappingLike = None, + marginal_x: Optional[Literal["rug", "box", "violin", "histogram"]] = None, + marginal_y: Optional[Literal["rug", "box", "violin", "histogram"]] = None, + trendline: Optional[Literal["ols", "lowess", "rolling", "expanding", "ewm"]] = None, + trendline_options: MappingLike = None, + trendline_color_override: Optional[str] = None, + trendline_scope: Literal["trace", "overall"] = "trace", + log_x: bool = False, + log_y: bool = False, + range_x: RangeLike = None, + range_y: RangeLike = None, + histfunc: Optional[Literal["count", "sum", "avg", "min", "max"]] = None, + histnorm: Optional[ + Literal["", "percent", "probability", "density", "probability density"] + ] = None, + nbinsx: Optional[int] = None, + nbinsy: Optional[int] = None, + text_auto: Union[bool, str] = False, + title: Optional[str] = None, + subtitle: Optional[str] = None, + template: Union[str, go.layout.Template, None] = None, + width: Optional[int] = None, + height: Optional[int] = None, ) -> go.Figure: """ In a density contour plot, rows of `data_frame` are grouped together @@ -147,42 +161,44 @@ def density_contour( def density_heatmap( - data_frame=None, - x=None, - y=None, - z=None, - facet_row=None, - facet_col=None, - facet_col_wrap=0, - facet_row_spacing=None, - facet_col_spacing=None, - hover_name=None, - hover_data=None, - animation_frame=None, - animation_group=None, - category_orders=None, - labels=None, - orientation=None, - color_continuous_scale=None, - range_color=None, - color_continuous_midpoint=None, - marginal_x=None, - marginal_y=None, - opacity=None, - log_x=False, - log_y=False, - range_x=None, - range_y=None, - histfunc=None, - histnorm=None, - nbinsx=None, - nbinsy=None, - text_auto=False, - title=None, - subtitle=None, - template=None, - width=None, - height=None, + data_frame: DataFrameLike = None, + x: ColumnRef = None, + y: ColumnRef = None, + z: ColumnRef = None, + facet_row: ColumnRef = None, + facet_col: ColumnRef = None, + facet_col_wrap: int = 0, + facet_row_spacing: Optional[float] = None, + facet_col_spacing: Optional[float] = None, + hover_name: ColumnRef = None, + hover_data: Union[SequenceLike, MappingLike] = None, + animation_frame: ColumnRef = None, + animation_group: ColumnRef = None, + category_orders: MappingLike = None, + labels: MappingLike = None, + orientation: Optional[Literal["v", "h"]] = None, + color_continuous_scale: ColorScale = None, + range_color: RangeLike = None, + color_continuous_midpoint: Optional[float] = None, + marginal_x: Optional[Literal["rug", "box", "violin", "histogram"]] = None, + marginal_y: Optional[Literal["rug", "box", "violin", "histogram"]] = None, + opacity: Optional[float] = None, + log_x: bool = False, + log_y: bool = False, + range_x: RangeLike = None, + range_y: RangeLike = None, + histfunc: Optional[Literal["count", "sum", "avg", "min", "max"]] = None, + histnorm: Optional[ + Literal["", "percent", "probability", "density", "probability density"] + ] = None, + nbinsx: Optional[int] = None, + nbinsy: Optional[int] = None, + text_auto: Union[bool, str] = False, + title: Optional[str] = None, + subtitle: Optional[str] = None, + template: Union[str, go.layout.Template, None] = None, + width: Optional[int] = None, + height: Optional[int] = None, ) -> go.Figure: """ In a density heatmap, rows of `data_frame` are grouped together into @@ -219,49 +235,49 @@ def density_heatmap( def line( - data_frame=None, - x=None, - y=None, - line_group=None, - color=None, - line_dash=None, - symbol=None, - hover_name=None, - hover_data=None, - custom_data=None, - text=None, - facet_row=None, - facet_col=None, - facet_col_wrap=0, - facet_row_spacing=None, - facet_col_spacing=None, - error_x=None, - error_x_minus=None, - error_y=None, - error_y_minus=None, - animation_frame=None, - animation_group=None, - category_orders=None, - labels=None, - orientation=None, - color_discrete_sequence=None, - color_discrete_map=None, - line_dash_sequence=None, - line_dash_map=None, - symbol_sequence=None, - symbol_map=None, - markers=False, - log_x=False, - log_y=False, - range_x=None, - range_y=None, - line_shape=None, - render_mode="auto", - title=None, - subtitle=None, - template=None, - width=None, - height=None, + data_frame: DataFrameLike = None, + x: ColumnRef = None, + y: ColumnRef = None, + line_group: ColumnRef = None, + color: ColumnRef = None, + line_dash: ColumnRef = None, + symbol: ColumnRef = None, + hover_name: ColumnRef = None, + hover_data: Union[SequenceLike, MappingLike] = None, + custom_data: SequenceLike = None, + text: ColumnRef = None, + facet_row: ColumnRef = None, + facet_col: ColumnRef = None, + facet_col_wrap: int = 0, + facet_row_spacing: Optional[float] = None, + facet_col_spacing: Optional[float] = None, + error_x: ColumnRef = None, + error_x_minus: ColumnRef = None, + error_y: ColumnRef = None, + error_y_minus: ColumnRef = None, + animation_frame: ColumnRef = None, + animation_group: ColumnRef = None, + category_orders: MappingLike = None, + labels: MappingLike = None, + orientation: Optional[Literal["v", "h"]] = None, + color_discrete_sequence: SequenceLike = None, + color_discrete_map: MappingLike = None, + line_dash_sequence: SequenceLike = None, + line_dash_map: MappingLike = None, + symbol_sequence: SequenceLike = None, + symbol_map: MappingLike = None, + markers: bool = False, + log_x: bool = False, + log_y: bool = False, + range_x: RangeLike = None, + range_y: RangeLike = None, + line_shape: Optional[Literal["linear", "spline", "hv", "vh", "hvh", "vhv"]] = None, + render_mode: Literal["auto", "svg", "webgl"] = "auto", + title: Optional[str] = None, + subtitle: Optional[str] = None, + template: Union[str, go.layout.Template, None] = None, + width: Optional[int] = None, + height: Optional[int] = None, ) -> go.Figure: """ In a 2D line plot, each row of `data_frame` is represented as a vertex of @@ -274,45 +290,45 @@ def line( def area( - data_frame=None, - x=None, - y=None, - line_group=None, - color=None, - pattern_shape=None, - symbol=None, - hover_name=None, - hover_data=None, - custom_data=None, - text=None, - facet_row=None, - facet_col=None, - facet_col_wrap=0, - facet_row_spacing=None, - facet_col_spacing=None, - animation_frame=None, - animation_group=None, - category_orders=None, - labels=None, - color_discrete_sequence=None, - color_discrete_map=None, - pattern_shape_sequence=None, - pattern_shape_map=None, - symbol_sequence=None, - symbol_map=None, - markers=False, - orientation=None, - groupnorm=None, - log_x=False, - log_y=False, - range_x=None, - range_y=None, - line_shape=None, - title=None, - subtitle=None, - template=None, - width=None, - height=None, + data_frame: DataFrameLike = None, + x: ColumnRef = None, + y: ColumnRef = None, + line_group: ColumnRef = None, + color: ColumnRef = None, + pattern_shape: ColumnRef = None, + symbol: ColumnRef = None, + hover_name: ColumnRef = None, + hover_data: Union[SequenceLike, MappingLike] = None, + custom_data: SequenceLike = None, + text: ColumnRef = None, + facet_row: ColumnRef = None, + facet_col: ColumnRef = None, + facet_col_wrap: int = 0, + facet_row_spacing: Optional[float] = None, + facet_col_spacing: Optional[float] = None, + animation_frame: ColumnRef = None, + animation_group: ColumnRef = None, + category_orders: MappingLike = None, + labels: MappingLike = None, + color_discrete_sequence: SequenceLike = None, + color_discrete_map: MappingLike = None, + pattern_shape_sequence: SequenceLike = None, + pattern_shape_map: MappingLike = None, + symbol_sequence: SequenceLike = None, + symbol_map: MappingLike = None, + markers: bool = False, + orientation: Optional[Literal["v", "h"]] = None, + groupnorm: Optional[Literal["", "fraction", "percent"]] = None, + log_x: bool = False, + log_y: bool = False, + range_x: RangeLike = None, + range_y: RangeLike = None, + line_shape: Optional[Literal["linear", "spline", "hv", "vh", "hvh", "vhv"]] = None, + title: Optional[str] = None, + subtitle: Optional[str] = None, + template: Union[str, go.layout.Template, None] = None, + width: Optional[int] = None, + height: Optional[int] = None, ) -> go.Figure: """ In a stacked area plot, each row of `data_frame` is represented as @@ -330,49 +346,49 @@ def area( def bar( - data_frame=None, - x=None, - y=None, - color=None, - pattern_shape=None, - facet_row=None, - facet_col=None, - facet_col_wrap=0, - facet_row_spacing=None, - facet_col_spacing=None, - hover_name=None, - hover_data=None, - custom_data=None, - text=None, - base=None, - error_x=None, - error_x_minus=None, - error_y=None, - error_y_minus=None, - animation_frame=None, - animation_group=None, - category_orders=None, - labels=None, - color_discrete_sequence=None, - color_discrete_map=None, - color_continuous_scale=None, - pattern_shape_sequence=None, - pattern_shape_map=None, - range_color=None, - color_continuous_midpoint=None, - opacity=None, - orientation=None, - barmode="relative", - log_x=False, - log_y=False, - range_x=None, - range_y=None, - text_auto=False, - title=None, - subtitle=None, - template=None, - width=None, - height=None, + data_frame: DataFrameLike = None, + x: ColumnRef = None, + y: ColumnRef = None, + color: ColumnRef = None, + pattern_shape: ColumnRef = None, + facet_row: ColumnRef = None, + facet_col: ColumnRef = None, + facet_col_wrap: int = 0, + facet_row_spacing: Optional[float] = None, + facet_col_spacing: Optional[float] = None, + hover_name: ColumnRef = None, + hover_data: Union[SequenceLike, MappingLike] = None, + custom_data: SequenceLike = None, + text: ColumnRef = None, + base: ColumnRef = None, + error_x: ColumnRef = None, + error_x_minus: ColumnRef = None, + error_y: ColumnRef = None, + error_y_minus: ColumnRef = None, + animation_frame: ColumnRef = None, + animation_group: ColumnRef = None, + category_orders: MappingLike = None, + labels: MappingLike = None, + color_discrete_sequence: SequenceLike = None, + color_discrete_map: MappingLike = None, + color_continuous_scale: ColorScale = None, + pattern_shape_sequence: SequenceLike = None, + pattern_shape_map: MappingLike = None, + range_color: RangeLike = None, + color_continuous_midpoint: Optional[float] = None, + opacity: Optional[float] = None, + orientation: Optional[Literal["v", "h"]] = None, + barmode: Literal["relative", "overlay", "group"] = "relative", + log_x: bool = False, + log_y: bool = False, + range_x: RangeLike = None, + range_y: RangeLike = None, + text_auto: Union[bool, str] = False, + title: Optional[str] = None, + subtitle: Optional[str] = None, + template: Union[str, go.layout.Template, None] = None, + width: Optional[int] = None, + height: Optional[int] = None, ) -> go.Figure: """ In a bar plot, each row of `data_frame` is represented as a rectangular @@ -390,40 +406,40 @@ def bar( def timeline( - data_frame=None, - x_start=None, - x_end=None, - y=None, - color=None, - pattern_shape=None, - facet_row=None, - facet_col=None, - facet_col_wrap=0, - facet_row_spacing=None, - facet_col_spacing=None, - hover_name=None, - hover_data=None, - custom_data=None, - text=None, - animation_frame=None, - animation_group=None, - category_orders=None, - labels=None, - color_discrete_sequence=None, - color_discrete_map=None, - pattern_shape_sequence=None, - pattern_shape_map=None, - color_continuous_scale=None, - range_color=None, - color_continuous_midpoint=None, - opacity=None, - range_x=None, - range_y=None, - title=None, - subtitle=None, - template=None, - width=None, - height=None, + data_frame: DataFrameLike = None, + x_start: ColumnRef = None, + x_end: ColumnRef = None, + y: ColumnRef = None, + color: ColumnRef = None, + pattern_shape: ColumnRef = None, + facet_row: ColumnRef = None, + facet_col: ColumnRef = None, + facet_col_wrap: int = 0, + facet_row_spacing: Optional[float] = None, + facet_col_spacing: Optional[float] = None, + hover_name: ColumnRef = None, + hover_data: Union[SequenceLike, MappingLike] = None, + custom_data: SequenceLike = None, + text: ColumnRef = None, + animation_frame: ColumnRef = None, + animation_group: ColumnRef = None, + category_orders: MappingLike = None, + labels: MappingLike = None, + color_discrete_sequence: SequenceLike = None, + color_discrete_map: MappingLike = None, + pattern_shape_sequence: SequenceLike = None, + pattern_shape_map: MappingLike = None, + color_continuous_scale: ColorScale = None, + range_color: RangeLike = None, + color_continuous_midpoint: Optional[float] = None, + opacity: Optional[float] = None, + range_x: RangeLike = None, + range_y: RangeLike = None, + title: Optional[str] = None, + subtitle: Optional[str] = None, + template: Union[str, go.layout.Template, None] = None, + width: Optional[int] = None, + height: Optional[int] = None, ) -> go.Figure: """ In a timeline plot, each row of `data_frame` is represented as a rectangular @@ -441,45 +457,47 @@ def timeline( def histogram( - data_frame=None, - x=None, - y=None, - color=None, - pattern_shape=None, - facet_row=None, - facet_col=None, - facet_col_wrap=0, - facet_row_spacing=None, - facet_col_spacing=None, - hover_name=None, - hover_data=None, - animation_frame=None, - animation_group=None, - category_orders=None, - labels=None, - color_discrete_sequence=None, - color_discrete_map=None, - pattern_shape_sequence=None, - pattern_shape_map=None, - marginal=None, - opacity=None, - orientation=None, - barmode="relative", - barnorm=None, - histnorm=None, - log_x=False, - log_y=False, - range_x=None, - range_y=None, - histfunc=None, - cumulative=None, - nbins=None, - text_auto=False, - title=None, - subtitle=None, - template=None, - width=None, - height=None, + data_frame: DataFrameLike = None, + x: ColumnRef = None, + y: ColumnRef = None, + color: ColumnRef = None, + pattern_shape: ColumnRef = None, + facet_row: ColumnRef = None, + facet_col: ColumnRef = None, + facet_col_wrap: int = 0, + facet_row_spacing: Optional[float] = None, + facet_col_spacing: Optional[float] = None, + hover_name: ColumnRef = None, + hover_data: Union[SequenceLike, MappingLike] = None, + animation_frame: ColumnRef = None, + animation_group: ColumnRef = None, + category_orders: MappingLike = None, + labels: MappingLike = None, + color_discrete_sequence: SequenceLike = None, + color_discrete_map: MappingLike = None, + pattern_shape_sequence: SequenceLike = None, + pattern_shape_map: MappingLike = None, + marginal: Optional[Literal["rug", "box", "violin", "histogram"]] = None, + opacity: Optional[float] = None, + orientation: Optional[Literal["v", "h"]] = None, + barmode: Literal["relative", "overlay", "group"] = "relative", + barnorm: Optional[Literal["", "fraction", "percent"]] = None, + histnorm: Optional[ + Literal["", "percent", "probability", "density", "probability density"] + ] = None, + log_x: bool = False, + log_y: bool = False, + range_x: RangeLike = None, + range_y: RangeLike = None, + histfunc: Optional[Literal["count", "sum", "avg", "min", "max"]] = None, + cumulative: Optional[bool] = None, + nbins: Optional[int] = None, + text_auto: Union[bool, str] = False, + title: Optional[str] = None, + subtitle: Optional[str] = None, + template: Union[str, go.layout.Template, None] = None, + width: Optional[int] = None, + height: Optional[int] = None, ) -> go.Figure: """ In a histogram, rows of `data_frame` are grouped together into a @@ -514,47 +532,47 @@ def histogram( def ecdf( - data_frame=None, - x=None, - y=None, - color=None, - text=None, - line_dash=None, - symbol=None, - facet_row=None, - facet_col=None, - facet_col_wrap=0, - facet_row_spacing=None, - facet_col_spacing=None, - hover_name=None, - hover_data=None, - animation_frame=None, - animation_group=None, - markers=False, - lines=True, - category_orders=None, - labels=None, - color_discrete_sequence=None, - color_discrete_map=None, - line_dash_sequence=None, - line_dash_map=None, - symbol_sequence=None, - symbol_map=None, - marginal=None, - opacity=None, - orientation=None, - ecdfnorm="probability", - ecdfmode="standard", - render_mode="auto", - log_x=False, - log_y=False, - range_x=None, - range_y=None, - title=None, - subtitle=None, - template=None, - width=None, - height=None, + data_frame: DataFrameLike = None, + x: ColumnRef = None, + y: ColumnRef = None, + color: ColumnRef = None, + text: ColumnRef = None, + line_dash: ColumnRef = None, + symbol: ColumnRef = None, + facet_row: ColumnRef = None, + facet_col: ColumnRef = None, + facet_col_wrap: int = 0, + facet_row_spacing: Optional[float] = None, + facet_col_spacing: Optional[float] = None, + hover_name: ColumnRef = None, + hover_data: Union[SequenceLike, MappingLike] = None, + animation_frame: ColumnRef = None, + animation_group: ColumnRef = None, + markers: bool = False, + lines: bool = True, + category_orders: MappingLike = None, + labels: MappingLike = None, + color_discrete_sequence: SequenceLike = None, + color_discrete_map: MappingLike = None, + line_dash_sequence: SequenceLike = None, + line_dash_map: MappingLike = None, + symbol_sequence: SequenceLike = None, + symbol_map: MappingLike = None, + marginal: Optional[Literal["rug", "box", "violin", "histogram"]] = None, + opacity: Optional[float] = None, + orientation: Optional[Literal["v", "h"]] = None, + ecdfnorm: Optional[Literal["probability", "percent"]] = "probability", + ecdfmode: Optional[Literal["standard", "complementary", "reversed"]] = "standard", + render_mode: Literal["auto", "svg", "webgl"] = "auto", + log_x: bool = False, + log_y: bool = False, + range_x: RangeLike = None, + range_y: RangeLike = None, + title: Optional[str] = None, + subtitle: Optional[str] = None, + template: Union[str, go.layout.Template, None] = None, + width: Optional[int] = None, + height: Optional[int] = None, ) -> go.Figure: """ In a Empirical Cumulative Distribution Function (ECDF) plot, rows of `data_frame` @@ -581,37 +599,37 @@ def ecdf( def violin( - data_frame=None, - x=None, - y=None, - color=None, - facet_row=None, - facet_col=None, - facet_col_wrap=0, - facet_row_spacing=None, - facet_col_spacing=None, - hover_name=None, - hover_data=None, - custom_data=None, - animation_frame=None, - animation_group=None, - category_orders=None, - labels=None, - color_discrete_sequence=None, - color_discrete_map=None, - orientation=None, - violinmode=None, - log_x=False, - log_y=False, - range_x=None, - range_y=None, - points=None, - box=False, - title=None, - subtitle=None, - template=None, - width=None, - height=None, + data_frame: DataFrameLike = None, + x: ColumnRef = None, + y: ColumnRef = None, + color: ColumnRef = None, + facet_row: ColumnRef = None, + facet_col: ColumnRef = None, + facet_col_wrap: int = 0, + facet_row_spacing: Optional[float] = None, + facet_col_spacing: Optional[float] = None, + hover_name: ColumnRef = None, + hover_data: Union[SequenceLike, MappingLike] = None, + custom_data: SequenceLike = None, + animation_frame: ColumnRef = None, + animation_group: ColumnRef = None, + category_orders: MappingLike = None, + labels: MappingLike = None, + color_discrete_sequence: SequenceLike = None, + color_discrete_map: MappingLike = None, + orientation: Optional[Literal["v", "h"]] = None, + violinmode: Optional[Literal["group", "overlay"]] = None, + log_x: bool = False, + log_y: bool = False, + range_x: RangeLike = None, + range_y: RangeLike = None, + points: Optional[Literal["outliers", "suspectedoutliers", "all", False]] = None, + box: bool = False, + title: Optional[str] = None, + subtitle: Optional[str] = None, + template: Union[str, go.layout.Template, None] = None, + width: Optional[int] = None, + height: Optional[int] = None, ) -> go.Figure: """ In a violin plot, rows of `data_frame` are grouped together into a @@ -635,37 +653,37 @@ def violin( def box( - data_frame=None, - x=None, - y=None, - color=None, - facet_row=None, - facet_col=None, - facet_col_wrap=0, - facet_row_spacing=None, - facet_col_spacing=None, - hover_name=None, - hover_data=None, - custom_data=None, - animation_frame=None, - animation_group=None, - category_orders=None, - labels=None, - color_discrete_sequence=None, - color_discrete_map=None, - orientation=None, - boxmode=None, - log_x=False, - log_y=False, - range_x=None, - range_y=None, - points=None, - notched=False, - title=None, - subtitle=None, - template=None, - width=None, - height=None, + data_frame: DataFrameLike = None, + x: ColumnRef = None, + y: ColumnRef = None, + color: ColumnRef = None, + facet_row: ColumnRef = None, + facet_col: ColumnRef = None, + facet_col_wrap: int = 0, + facet_row_spacing: Optional[float] = None, + facet_col_spacing: Optional[float] = None, + hover_name: ColumnRef = None, + hover_data: Union[SequenceLike, MappingLike] = None, + custom_data: SequenceLike = None, + animation_frame: ColumnRef = None, + animation_group: ColumnRef = None, + category_orders: MappingLike = None, + labels: MappingLike = None, + color_discrete_sequence: SequenceLike = None, + color_discrete_map: MappingLike = None, + orientation: Optional[Literal["v", "h"]] = None, + boxmode: Optional[Literal["group", "overlay"]] = None, + log_x: bool = False, + log_y: bool = False, + range_x: RangeLike = None, + range_y: RangeLike = None, + points: Optional[Literal["outliers", "suspectedoutliers", "all", False]] = None, + notched: bool = False, + title: Optional[str] = None, + subtitle: Optional[str] = None, + template: Union[str, go.layout.Template, None] = None, + width: Optional[int] = None, + height: Optional[int] = None, ) -> go.Figure: """ In a box plot, rows of `data_frame` are grouped together into a @@ -688,35 +706,35 @@ def box( def strip( - data_frame=None, - x=None, - y=None, - color=None, - facet_row=None, - facet_col=None, - facet_col_wrap=0, - facet_row_spacing=None, - facet_col_spacing=None, - hover_name=None, - hover_data=None, - custom_data=None, - animation_frame=None, - animation_group=None, - category_orders=None, - labels=None, - color_discrete_sequence=None, - color_discrete_map=None, - orientation=None, - stripmode=None, - log_x=False, - log_y=False, - range_x=None, - range_y=None, - title=None, - subtitle=None, - template=None, - width=None, - height=None, + data_frame: DataFrameLike = None, + x: ColumnRef = None, + y: ColumnRef = None, + color: ColumnRef = None, + facet_row: ColumnRef = None, + facet_col: ColumnRef = None, + facet_col_wrap: int = 0, + facet_row_spacing: Optional[float] = None, + facet_col_spacing: Optional[float] = None, + hover_name: ColumnRef = None, + hover_data: Union[SequenceLike, MappingLike] = None, + custom_data: SequenceLike = None, + animation_frame: ColumnRef = None, + animation_group: ColumnRef = None, + category_orders: MappingLike = None, + labels: MappingLike = None, + color_discrete_sequence: SequenceLike = None, + color_discrete_map: MappingLike = None, + orientation: Optional[Literal["v", "h"]] = None, + stripmode: Optional[Literal["group", "overlay"]] = None, + log_x: bool = False, + log_y: bool = False, + range_x: RangeLike = None, + range_y: RangeLike = None, + title: Optional[str] = None, + subtitle: Optional[str] = None, + template: Union[str, go.layout.Template, None] = None, + width: Optional[int] = None, + height: Optional[int] = None, ) -> go.Figure: """ In a strip plot each row of `data_frame` is represented as a jittered @@ -742,47 +760,47 @@ def strip( def scatter_3d( - data_frame=None, - x=None, - y=None, - z=None, - color=None, - symbol=None, - size=None, - text=None, - hover_name=None, - hover_data=None, - custom_data=None, - error_x=None, - error_x_minus=None, - error_y=None, - error_y_minus=None, - error_z=None, - error_z_minus=None, - animation_frame=None, - animation_group=None, - category_orders=None, - labels=None, - size_max=None, - color_discrete_sequence=None, - color_discrete_map=None, - color_continuous_scale=None, - range_color=None, - color_continuous_midpoint=None, - symbol_sequence=None, - symbol_map=None, - opacity=None, - log_x=False, - log_y=False, - log_z=False, - range_x=None, - range_y=None, - range_z=None, - title=None, - subtitle=None, - template=None, - width=None, - height=None, + data_frame: DataFrameLike = None, + x: ColumnRef = None, + y: ColumnRef = None, + z: ColumnRef = None, + color: ColumnRef = None, + symbol: ColumnRef = None, + size: ColumnRef = None, + text: ColumnRef = None, + hover_name: ColumnRef = None, + hover_data: Union[SequenceLike, MappingLike] = None, + custom_data: SequenceLike = None, + error_x: ColumnRef = None, + error_x_minus: ColumnRef = None, + error_y: ColumnRef = None, + error_y_minus: ColumnRef = None, + error_z: ColumnRef = None, + error_z_minus: ColumnRef = None, + animation_frame: ColumnRef = None, + animation_group: ColumnRef = None, + category_orders: MappingLike = None, + labels: MappingLike = None, + size_max: Optional[float] = None, + color_discrete_sequence: SequenceLike = None, + color_discrete_map: MappingLike = None, + color_continuous_scale: ColorScale = None, + range_color: RangeLike = None, + color_continuous_midpoint: Optional[float] = None, + symbol_sequence: SequenceLike = None, + symbol_map: MappingLike = None, + opacity: Optional[float] = None, + log_x: bool = False, + log_y: bool = False, + log_z: bool = False, + range_x: RangeLike = None, + range_y: RangeLike = None, + range_z: RangeLike = None, + title: Optional[str] = None, + subtitle: Optional[str] = None, + template: Union[str, go.layout.Template, None] = None, + width: Optional[int] = None, + height: Optional[int] = None, ) -> go.Figure: """ In a 3D scatter plot, each row of `data_frame` is represented by a @@ -795,46 +813,46 @@ def scatter_3d( def line_3d( - data_frame=None, - x=None, - y=None, - z=None, - color=None, - line_dash=None, - text=None, - line_group=None, - symbol=None, - hover_name=None, - hover_data=None, - custom_data=None, - error_x=None, - error_x_minus=None, - error_y=None, - error_y_minus=None, - error_z=None, - error_z_minus=None, - animation_frame=None, - animation_group=None, - category_orders=None, - labels=None, - color_discrete_sequence=None, - color_discrete_map=None, - line_dash_sequence=None, - line_dash_map=None, - symbol_sequence=None, - symbol_map=None, - markers=False, - log_x=False, - log_y=False, - log_z=False, - range_x=None, - range_y=None, - range_z=None, - title=None, - subtitle=None, - template=None, - width=None, - height=None, + data_frame: DataFrameLike = None, + x: ColumnRef = None, + y: ColumnRef = None, + z: ColumnRef = None, + color: ColumnRef = None, + line_dash: ColumnRef = None, + text: ColumnRef = None, + line_group: ColumnRef = None, + symbol: ColumnRef = None, + hover_name: ColumnRef = None, + hover_data: Union[SequenceLike, MappingLike] = None, + custom_data: SequenceLike = None, + error_x: ColumnRef = None, + error_x_minus: ColumnRef = None, + error_y: ColumnRef = None, + error_y_minus: ColumnRef = None, + error_z: ColumnRef = None, + error_z_minus: ColumnRef = None, + animation_frame: ColumnRef = None, + animation_group: ColumnRef = None, + category_orders: MappingLike = None, + labels: MappingLike = None, + color_discrete_sequence: SequenceLike = None, + color_discrete_map: MappingLike = None, + line_dash_sequence: SequenceLike = None, + line_dash_map: MappingLike = None, + symbol_sequence: SequenceLike = None, + symbol_map: MappingLike = None, + markers: bool = False, + log_x: bool = False, + log_y: bool = False, + log_z: bool = False, + range_x: RangeLike = None, + range_y: RangeLike = None, + range_z: RangeLike = None, + title: Optional[str] = None, + subtitle: Optional[str] = None, + template: Union[str, go.layout.Template, None] = None, + width: Optional[int] = None, + height: Optional[int] = None, ) -> go.Figure: """ In a 3D line plot, each row of `data_frame` is represented as a vertex of @@ -847,35 +865,35 @@ def line_3d( def scatter_ternary( - data_frame=None, - a=None, - b=None, - c=None, - color=None, - symbol=None, - size=None, - text=None, - hover_name=None, - hover_data=None, - custom_data=None, - animation_frame=None, - animation_group=None, - category_orders=None, - labels=None, - color_discrete_sequence=None, - color_discrete_map=None, - color_continuous_scale=None, - range_color=None, - color_continuous_midpoint=None, - symbol_sequence=None, - symbol_map=None, - opacity=None, - size_max=None, - title=None, - subtitle=None, - template=None, - width=None, - height=None, + data_frame: DataFrameLike = None, + a: ColumnRef = None, + b: ColumnRef = None, + c: ColumnRef = None, + color: ColumnRef = None, + symbol: ColumnRef = None, + size: ColumnRef = None, + text: ColumnRef = None, + hover_name: ColumnRef = None, + hover_data: Union[SequenceLike, MappingLike] = None, + custom_data: SequenceLike = None, + animation_frame: ColumnRef = None, + animation_group: ColumnRef = None, + category_orders: MappingLike = None, + labels: MappingLike = None, + color_discrete_sequence: SequenceLike = None, + color_discrete_map: MappingLike = None, + color_continuous_scale: ColorScale = None, + range_color: RangeLike = None, + color_continuous_midpoint: Optional[float] = None, + symbol_sequence: SequenceLike = None, + symbol_map: MappingLike = None, + opacity: Optional[float] = None, + size_max: Optional[float] = None, + title: Optional[str] = None, + subtitle: Optional[str] = None, + template: Union[str, go.layout.Template, None] = None, + width: Optional[int] = None, + height: Optional[int] = None, ) -> go.Figure: """ In a ternary scatter plot, each row of `data_frame` is represented by a @@ -888,35 +906,35 @@ def scatter_ternary( def line_ternary( - data_frame=None, - a=None, - b=None, - c=None, - color=None, - line_dash=None, - line_group=None, - symbol=None, - hover_name=None, - hover_data=None, - custom_data=None, - text=None, - animation_frame=None, - animation_group=None, - category_orders=None, - labels=None, - color_discrete_sequence=None, - color_discrete_map=None, - line_dash_sequence=None, - line_dash_map=None, - symbol_sequence=None, - symbol_map=None, - markers=False, - line_shape=None, - title=None, - subtitle=None, - template=None, - width=None, - height=None, + data_frame: DataFrameLike = None, + a: ColumnRef = None, + b: ColumnRef = None, + c: ColumnRef = None, + color: ColumnRef = None, + line_dash: ColumnRef = None, + line_group: ColumnRef = None, + symbol: ColumnRef = None, + hover_name: ColumnRef = None, + hover_data: Union[SequenceLike, MappingLike] = None, + custom_data: SequenceLike = None, + text: ColumnRef = None, + animation_frame: ColumnRef = None, + animation_group: ColumnRef = None, + category_orders: MappingLike = None, + labels: MappingLike = None, + color_discrete_sequence: SequenceLike = None, + color_discrete_map: MappingLike = None, + line_dash_sequence: SequenceLike = None, + line_dash_map: MappingLike = None, + symbol_sequence: SequenceLike = None, + symbol_map: MappingLike = None, + markers: bool = False, + line_shape: Optional[Literal["linear", "spline"]] = None, + title: Optional[str] = None, + subtitle: Optional[str] = None, + template: Union[str, go.layout.Template, None] = None, + width: Optional[int] = None, + height: Optional[int] = None, ) -> go.Figure: """ In a ternary line plot, each row of `data_frame` is represented as @@ -929,40 +947,40 @@ def line_ternary( def scatter_polar( - data_frame=None, - r=None, - theta=None, - color=None, - symbol=None, - size=None, - hover_name=None, - hover_data=None, - custom_data=None, - text=None, - animation_frame=None, - animation_group=None, - category_orders=None, - labels=None, - color_discrete_sequence=None, - color_discrete_map=None, - color_continuous_scale=None, - range_color=None, - color_continuous_midpoint=None, - symbol_sequence=None, - symbol_map=None, - opacity=None, - direction="clockwise", - start_angle=90, - size_max=None, - range_r=None, - range_theta=None, - log_r=False, - render_mode="auto", - title=None, - subtitle=None, - template=None, - width=None, - height=None, + data_frame: DataFrameLike = None, + r: ColumnRef = None, + theta: ColumnRef = None, + color: ColumnRef = None, + symbol: ColumnRef = None, + size: ColumnRef = None, + hover_name: ColumnRef = None, + hover_data: Union[SequenceLike, MappingLike] = None, + custom_data: SequenceLike = None, + text: ColumnRef = None, + animation_frame: ColumnRef = None, + animation_group: ColumnRef = None, + category_orders: MappingLike = None, + labels: MappingLike = None, + color_discrete_sequence: SequenceLike = None, + color_discrete_map: MappingLike = None, + color_continuous_scale: ColorScale = None, + range_color: RangeLike = None, + color_continuous_midpoint: Optional[float] = None, + symbol_sequence: SequenceLike = None, + symbol_map: MappingLike = None, + opacity: Optional[float] = None, + direction: Literal["clockwise", "counterclockwise"] = "clockwise", + start_angle: int = 90, + size_max: Optional[float] = None, + range_r: RangeLike = None, + range_theta: RangeLike = None, + log_r: bool = False, + render_mode: Literal["auto", "svg", "webgl"] = "auto", + title: Optional[str] = None, + subtitle: Optional[str] = None, + template: Union[str, go.layout.Template, None] = None, + width: Optional[int] = None, + height: Optional[int] = None, ) -> go.Figure: """ In a polar scatter plot, each row of `data_frame` is represented by a @@ -975,41 +993,41 @@ def scatter_polar( def line_polar( - data_frame=None, - r=None, - theta=None, - color=None, - line_dash=None, - hover_name=None, - hover_data=None, - custom_data=None, - line_group=None, - text=None, - symbol=None, - animation_frame=None, - animation_group=None, - category_orders=None, - labels=None, - color_discrete_sequence=None, - color_discrete_map=None, - line_dash_sequence=None, - line_dash_map=None, - symbol_sequence=None, - symbol_map=None, - markers=False, - direction="clockwise", - start_angle=90, - line_close=False, - line_shape=None, - render_mode="auto", - range_r=None, - range_theta=None, - log_r=False, - title=None, - subtitle=None, - template=None, - width=None, - height=None, + data_frame: DataFrameLike = None, + r: ColumnRef = None, + theta: ColumnRef = None, + color: ColumnRef = None, + line_dash: ColumnRef = None, + hover_name: ColumnRef = None, + hover_data: Union[SequenceLike, MappingLike] = None, + custom_data: SequenceLike = None, + line_group: ColumnRef = None, + text: ColumnRef = None, + symbol: ColumnRef = None, + animation_frame: ColumnRef = None, + animation_group: ColumnRef = None, + category_orders: MappingLike = None, + labels: MappingLike = None, + color_discrete_sequence: SequenceLike = None, + color_discrete_map: MappingLike = None, + line_dash_sequence: SequenceLike = None, + line_dash_map: MappingLike = None, + symbol_sequence: SequenceLike = None, + symbol_map: MappingLike = None, + markers: bool = False, + direction: Literal["clockwise", "counterclockwise"] = "clockwise", + start_angle: int = 90, + line_close: bool = False, + line_shape: Optional[Literal["linear", "spline"]] = None, + render_mode: Literal["auto", "svg", "webgl"] = "auto", + range_r: RangeLike = None, + range_theta: RangeLike = None, + log_r: bool = False, + title: Optional[str] = None, + subtitle: Optional[str] = None, + template: Union[str, go.layout.Template, None] = None, + width: Optional[int] = None, + height: Optional[int] = None, ) -> go.Figure: """ In a polar line plot, each row of `data_frame` is represented as a @@ -1022,38 +1040,38 @@ def line_polar( def bar_polar( - data_frame=None, - r=None, - theta=None, - color=None, - pattern_shape=None, - hover_name=None, - hover_data=None, - custom_data=None, - base=None, - animation_frame=None, - animation_group=None, - category_orders=None, - labels=None, - color_discrete_sequence=None, - color_discrete_map=None, - color_continuous_scale=None, - pattern_shape_sequence=None, - pattern_shape_map=None, - range_color=None, - color_continuous_midpoint=None, - barnorm=None, - barmode="relative", - direction="clockwise", - start_angle=90, - range_r=None, - range_theta=None, - log_r=False, - title=None, - subtitle=None, - template=None, - width=None, - height=None, + data_frame: DataFrameLike = None, + r: ColumnRef = None, + theta: ColumnRef = None, + color: ColumnRef = None, + pattern_shape: ColumnRef = None, + hover_name: ColumnRef = None, + hover_data: Union[SequenceLike, MappingLike] = None, + custom_data: SequenceLike = None, + base: ColumnRef = None, + animation_frame: ColumnRef = None, + animation_group: ColumnRef = None, + category_orders: MappingLike = None, + labels: MappingLike = None, + color_discrete_sequence: SequenceLike = None, + color_discrete_map: MappingLike = None, + color_continuous_scale: ColorScale = None, + pattern_shape_sequence: SequenceLike = None, + pattern_shape_map: MappingLike = None, + range_color: RangeLike = None, + color_continuous_midpoint: Optional[float] = None, + barnorm: Optional[Literal["", "fraction", "percent"]] = None, + barmode: Optional[Literal["relative", "overlay", "stack", "group"]] = "relative", + direction: Literal["clockwise", "counterclockwise"] = "clockwise", + start_angle: int = 90, + range_r: RangeLike = None, + range_theta: RangeLike = None, + log_r: bool = False, + title: Optional[str] = None, + subtitle: Optional[str] = None, + template: Union[str, go.layout.Template, None] = None, + width: Optional[int] = None, + height: Optional[int] = None, ) -> go.Figure: """ In a polar bar plot, each row of `data_frame` is represented as a wedge diff --git a/plotly/express/_core.py b/plotly/express/_core.py index 9fbf526cdeb..8682d24d5db 100644 --- a/plotly/express/_core.py +++ b/plotly/express/_core.py @@ -1,5 +1,8 @@ import plotly.graph_objs as go import plotly.io as pio +from typing import Optional, Any, TYPE_CHECKING +if TYPE_CHECKING: + import pandas as pd from collections import namedtuple, OrderedDict from ._special_inputs import IdentityMap, Constant, Range from .trendline_functions import ols, lowess, rolling, expanding, ewm @@ -98,7 +101,7 @@ def reset(self): MAPBOX_TOKEN = None -def set_mapbox_access_token(token): +def set_mapbox_access_token(token: str): """ Arguments: token: A Mapbox token to be used in `plotly.express.scatter_mapbox` and \ @@ -109,7 +112,7 @@ def set_mapbox_access_token(token): MAPBOX_TOKEN = token -def get_trendline_results(fig): +def get_trendline_results(fig: go.Figure) -> Optional["pd.DataFrame"]: """ Extracts fit statistics for trendlines (when applied to figures generated with the `trendline` argument set to `"ols"`). diff --git a/plotly/express/_special_inputs.py b/plotly/express/_special_inputs.py index c1b3d4d102f..a0b98458c8d 100644 --- a/plotly/express/_special_inputs.py +++ b/plotly/express/_special_inputs.py @@ -1,3 +1,5 @@ +from typing import Any, Optional + class IdentityMap(object): """ `dict`-like object which acts as if the value for any key is the key itself. Objects @@ -7,13 +9,13 @@ class IdentityMap(object): functions, such as `line_dash_map` and `symbol_map`. """ - def __getitem__(self, key): + def __getitem__(self, key: Any) -> Any: return key - def __contains__(self, key): + def __contains__(self, key: Any) -> bool: return True - def copy(self): + def copy(self) -> "IdentityMap": return self @@ -24,7 +26,7 @@ class Constant(object): constant value. An optional label can be provided. """ - def __init__(self, value, label=None): + def __init__(self, value: Any, label: Optional[str] = None) -> None: self.value = value self.label = label @@ -36,5 +38,5 @@ class Range(object): onto integers starting at 0. An optional label can be provided. """ - def __init__(self, label=None): + def __init__(self, label: Optional[str] = None) -> None: self.label = label diff --git a/plotly/io/_json.py b/plotly/io/_json.py index 0e741711f4f..8471d829a55 100644 --- a/plotly/io/_json.py +++ b/plotly/io/_json.py @@ -172,7 +172,7 @@ def to_json_plotly(plotly_object, pretty=False, engine=None): return _safe(orjson.dumps(cleaned, option=opts).decode("utf8"), _swap_orjson) -def to_json(fig, validate=True, pretty=False, remove_uids=True, engine=None): +def to_json(fig, validate=True, pretty=False, remove_uids=True, engine=None, base64=True): """ Convert a figure to a JSON string representation @@ -200,6 +200,10 @@ def to_json(fig, validate=True, pretty=False, remove_uids=True, engine=None): If not specified, the default engine is set to the current value of plotly.io.json.config.default_engine. + base64: bool (default True) + True if large numerical arrays should be converted to a binary + base64 encoding (bdata), False otherwise. + Returns ------- str @@ -211,7 +215,10 @@ def to_json(fig, validate=True, pretty=False, remove_uids=True, engine=None): """ # Validate figure # --------------- - fig_dict = validate_coerce_fig_to_dict(fig, validate) + if hasattr(fig, "to_dict"): + fig_dict = fig.to_dict(encode_base64=base64) + else: + fig_dict = validate_coerce_fig_to_dict(fig, validate) # Remove trace uid # ---------------- diff --git a/plotly/io/_kaleido.py b/plotly/io/_kaleido.py index 29fa845d762..d0bb0eec438 100644 --- a/plotly/io/_kaleido.py +++ b/plotly/io/_kaleido.py @@ -429,7 +429,7 @@ def write_image( height: Union[int, None] = None, validate: bool = True, # Deprecated - engine: Union[str, None] = "auto", + engine: Union[str, None] = None, ): """ Convert a figure to a static image and write it to a file or writeable diff --git a/plotly/shapeannotation.py b/plotly/shapeannotation.py index a2323ed02d4..41173054a11 100644 --- a/plotly/shapeannotation.py +++ b/plotly/shapeannotation.py @@ -1,10 +1,52 @@ # some functions defined here to avoid numpy import +import datetime + + +def _is_date_string(val): + """Check if a value is a date/datetime string.""" + if not isinstance(val, str): + return False + try: + datetime.datetime.fromisoformat(val.replace("Z", "+00:00")) + return True + except (ValueError, AttributeError): + return False + + +def _datetime_str_to_ms(val): + """Convert a datetime string to milliseconds since epoch.""" + dt = datetime.datetime.fromisoformat(val.replace("Z", "+00:00")) + if dt.tzinfo is None: + dt = dt.replace(tzinfo=datetime.timezone.utc) + return dt.timestamp() * 1000 + + +def _ms_to_datetime_str(ms): + """Convert milliseconds since epoch back to a datetime string.""" + dt = datetime.datetime.fromtimestamp(ms / 1000, tz=datetime.timezone.utc) + return dt.strftime("%Y-%m-%d %H:%M:%S") + def _mean(x): if len(x) == 0: raise ValueError("x must have positive length") - return float(sum(x)) / len(x) + try: + return float(sum(x)) / len(x) + except TypeError: + # Handle non-numeric types like datetime strings or datetime objects + if all(_is_date_string(v) for v in x): + ms_values = [_datetime_str_to_ms(v) for v in x] + mean_ms = sum(ms_values) / len(ms_values) + return _ms_to_datetime_str(mean_ms) + # Handle datetime.datetime, pd.Timestamp, or similar objects + if all(hasattr(v, "timestamp") for v in x): + ts_values = [v.timestamp() * 1000 for v in x] + mean_ms = sum(ts_values) / len(ts_values) + return datetime.datetime.fromtimestamp( + mean_ms / 1000, tz=datetime.timezone.utc + ).isoformat() + raise def _argmin(x): diff --git a/tests/test_optional/test_autoshapes/test_annotated_shapes.py b/tests/test_optional/test_autoshapes/test_annotated_shapes.py index a008e3bda12..14f7b31792a 100644 --- a/tests/test_optional/test_autoshapes/test_annotated_shapes.py +++ b/tests/test_optional/test_autoshapes/test_annotated_shapes.py @@ -425,5 +425,84 @@ def test_all_annotation_positions(): draw_all_annotation_positions(testing=True) + if __name__ == "__main__": draw_all_annotation_positions() + + +# Tests for datetime axis annotation support (issue #3065) +import datetime + + +def test_vline_datetime_string_annotation(): + """add_vline with annotation_text on datetime x-axis should not crash.""" + fig = go.Figure() + fig.add_trace( + go.Scatter(x=["2018-01-01", "2018-06-01", "2018-12-31"], y=[1, 2, 3]) + ) + fig.add_vline(x="2018-09-24", annotation_text="test") + assert len(fig.layout.annotations) == 1 + assert fig.layout.annotations[0].text == "test" + + +def test_hline_with_datetime_vline(): + """add_hline should still work alongside datetime vline usage.""" + fig = go.Figure() + fig.add_trace( + go.Scatter(x=["2018-01-01", "2018-06-01", "2018-12-31"], y=[1, 2, 3]) + ) + fig.add_hline(y=2, annotation_text="hline test") + assert len(fig.layout.annotations) == 1 + assert fig.layout.annotations[0].text == "hline test" + + +def test_vrect_datetime_string_annotation(): + """add_vrect with annotation_text on datetime x-axis should not crash.""" + fig = go.Figure() + fig.add_trace( + go.Scatter(x=["2018-01-01", "2018-06-01", "2018-12-31"], y=[1, 2, 3]) + ) + fig.add_vrect(x0="2018-03-01", x1="2018-09-01", annotation_text="rect test") + assert len(fig.layout.annotations) == 1 + assert fig.layout.annotations[0].text == "rect test" + + +def test_vline_datetime_object_annotation(): + """add_vline with datetime.datetime object should not crash.""" + fig = go.Figure() + fig.add_trace( + go.Scatter( + x=[ + datetime.datetime(2018, 1, 1), + datetime.datetime(2018, 6, 1), + datetime.datetime(2018, 12, 31), + ], + y=[1, 2, 3], + ) + ) + fig.add_vline(x=datetime.datetime(2018, 9, 24), annotation_text="dt test") + assert len(fig.layout.annotations) == 1 + assert fig.layout.annotations[0].text == "dt test" + + +def test_vrect_datetime_object_annotation(): + """add_vrect with datetime.datetime objects should compute correct mean.""" + fig = go.Figure() + fig.add_trace( + go.Scatter( + x=[ + datetime.datetime(2018, 1, 1), + datetime.datetime(2018, 6, 1), + datetime.datetime(2018, 12, 31), + ], + y=[1, 2, 3], + ) + ) + fig.add_vrect( + x0=datetime.datetime(2018, 3, 1), + x1=datetime.datetime(2018, 9, 1), + annotation_text="rect dt test", + ) + assert len(fig.layout.annotations) == 1 + assert fig.layout.annotations[0].text == "rect dt test" +