diff --git a/.gitignore b/.gitignore index 64c3de539e..9331d2b9c2 100644 --- a/.gitignore +++ b/.gitignore @@ -31,7 +31,7 @@ node_modules/ # virtual envs vv -venv +venv* # dist files build @@ -46,4 +46,5 @@ temp-plot.html doc/python/.ipynb_checkpoints doc/python/.mapbox_token doc/.ipynb_checkpoints +tags doc/check-or-enforce-order.py diff --git a/doc/python/figure-structure.md b/doc/python/figure-structure.md index faa73fb4cd..f7cafe29af 100644 --- a/doc/python/figure-structure.md +++ b/doc/python/figure-structure.md @@ -95,18 +95,31 @@ The third of the three top-level attributes of a figure is `frames`, whose value ### The `config` Object -At [render-time](/python/renderers/), it is also possible to control certain figure behaviors which are not considered part of the figure proper i.e. the behaviour of the "modebar" and how the figure relates to mouse actions like scrolling etc. The object that contains these options is called the [`config`, and has its own documentation page](/python/configuration-options/). It is exposed in Python as the `config` keyword argument of the `.show()` method on `plotly.graph_objects.Figure` objects. +At [render-time](/python/renderers/), it is also possible to control certain figure behaviors which are not considered part of the figure proper i.e. the behavior of the "modebar" and how the figure relates to mouse actions like scrolling etc. The object that contains these options is called the [`config`, and has its own documentation page](/python/configuration-options/). It is exposed in Python as the `config` keyword argument of the `.show()` method on `plotly.graph_objects.Figure` objects. -### Positioning With Paper or Container Coordinates +### Positioning With Paper, Container Coordinates, or Axis Domain Coordinates Various figure components configured within the layout of the figure support positioning attributes named `x` or `y`, whose values may be specified in "paper coordinates" (sometimes referred to as "plot fractions" or "normalized coordinates"). Examples include `layout.xaxis.domain` or `layout.legend.x` or `layout.annotation[].x`. Positioning in paper coordinates is *not* done in absolute pixel terms, but rather in terms relative to a coordinate system defined with an origin `(0,0)` at `(layout.margin.l, layout.margin.b)` and a point `(1,1)` at `(layout.width-layout.margin.r, layout.height-layout.margin.t)` (note: `layout.margin` values are pixel values, as are `layout.width` and `layout.height`). Paper coordinate values less than 0 or greater than 1 are permitted, and refer to areas within the plot margins. +To position an object in "paper" coordinates, the corresponding axis reference +is set to `"paper"`. For instance a shape's `xref` attribute would be set to +`"paper"` so that the `x` value of the shape refers to its position in paper +coordinates. + Note that the contents of the `layout.margin` attribute are by default computed based on the position and dimensions of certain items like the title or legend, and may be made dependent on the position and dimensions of tick labels as well when setting the `layout.xaxis.automargin` attribute to `True`. This has the effect of automatically increasing the margin values and therefore shrinking the physical area defined between the `(0,0)` and `(1,1)` points. Positioning certain items at paper coordinates less than 0 or greater than 1 will also trigger this behavior. The `layout.width` and `layout.height`, however, are taken as givens, so a figure will never grow or shrink based on its contents. The figure title may be positioned using "container coordinates" which have `(0,0)` and `(1,1)` anchored at the bottom-left and top-right of the figure, respectively, and therefore are independent of the values of layout.margin. +Furthermore, shapes, annotations, and images can be placed relative to an axis's +domain so that, for instance, an `x` value of `0.5` would place the object +halfway along the x-axis, regardless of the domain as specified in the +`layout.xaxis.domain` attribute. This behavior can be specified by adding +`' domain'` to the axis reference in the axis referencing attribute of the object. +For example, setting `yref = 'y2 domain'` for a shape will refer to the length +and position of the axis named `y2`. + ### 2D Cartesian Trace Types and Subplots The most commonly-used kind of subplot is a [two-dimensional Cartesian subplot](/python/axes/). Traces compatible with these subplots support `xaxis` and `yaxis` attributes whose values must refer to corresponding objects in the layout portion of the figure. For example, if `xaxis="x"`, and `yaxis="y"` (which is the default) then this trace is drawn on the subplot at the intersection of the axes configured under `layout.xaxis` and `layout.yaxis`, but if `xaxis="x2"` and `yaxis="y3"` then the trace is drawn at the intersection of the axes configured under `layout.xaxis2` and `layout.yaxis3`. Note that attributes such as `layout.xaxis` and `layout.xaxis2` etc do not have to be explicitly defined, in which case default values will be inferred. Multiple traces of different types can be drawn on the same subplot. diff --git a/doc/python/horizontal-vertical-shapes.md b/doc/python/horizontal-vertical-shapes.md new file mode 100644 index 0000000000..65ad54f954 --- /dev/null +++ b/doc/python/horizontal-vertical-shapes.md @@ -0,0 +1,121 @@ +--- +jupyter: + jupytext: + notebook_metadata_filter: all + text_representation: + extension: .md + format_name: markdown + format_version: '1.2' + jupytext_version: 1.3.0 + kernelspec: + display_name: Python 3 + language: python + name: python3 + language_info: + codemirror_mode: + name: ipython + version: 3 + file_extension: .py + mimetype: text/x-python + name: python + nbconvert_exporter: python + pygments_lexer: ipython3 + version: 3.7.3 + plotly: + description: How to add annotated horizontal and vertical lines in Python. + display_as: file_settings + language: python + layout: base + name: Shapes + order: 37 + permalink: python/horizontal-vertical-shapes/ + thumbnail: thumbnail/horizontal-vertical-shapes.jpg +--- + +### Horizontal and Vertical Lines and Boxes (Autoshapes) in Plotly.py + +*introduced in plotly 4.12* + +Horizontal and vertical lines and rectangles (autoshapes) that span an entire +plot can be added via the `add_hline`, `add_vline`, `add_hrect`, and `add_vrect` +methods of `plotly.graph_objects.Figure`. Shapes added with these methods are +added as [layout shapes](/python/shapes) (as shown when doing `print(fig)`, for +example). These shapes are fixed to the endpoints of one axis, regardless of the +range of the plot, and fixed to data coordinates on the other axis. The +following shows some possibilities, try panning and zooming the resulting figure +to see how the shapes stick to some axes: + + +```python +import plotly.express as px + +df = px.data.iris() +fig = px.scatter(df, x="sepal_length", y="sepal_width") + +# Add a vertical line that spans the entire y axis +# intersecting the x axis at x=5 +fig.add_vline(x=5, line_color="red") +# Add a horizontal line that spans the entire x axis +# intersecting the y axis at y=3 +fig.add_hline(y=3, line_color="blue") +# Add a vertical rectangle that spans the entire y axis +# intersecting the x axis at 5.5 and 6.5 +fig.add_vrect(x0=5.5, x1=6.5, line_color="purple") +# Add a horizontal rectangle that spans the entire x axis +# intersecting the y axis at 2.5 and 4 +fig.add_hrect(y0=2.5, y1=4, line_color="orange") +# (try panning and zooming the plot) +fig.show() +``` + +#### Adding Autoshapes to Multiple Facets / Subplots + +The same line or box can be added to multiple facets by using the `'all'` +keyword in the `row` and `col` arguments like with `Figure.add_shape`. For +example +```python +import plotly.express as px + +df = px.data.tips() +fig = px.scatter(df, x="total_bill", y="tip", facet_row="smoker", facet_col="sex") +# Adds a vertical line to all facets +fig.add_vline(x=30, row="all", col="all", line_color="purple") +# Adds a horizontal line to all the rows of the second column +fig.add_hline(y=6, row="all", col=2, line_color="yellow") +# Adds a vertical rectangle to all the columns of the first row +fig.add_vrect(x0=20, x1=40, row=1, col="all", line_color="green") +fig.show() +``` +The default `row` and `col` values are `"all"` so +`fig.add_vline(x=30, line_color="purple")` is equivalent +to `fig.add_vline(x=30, row="all", col="all", line_color="purple")` in the above +example. + +#### Adding Text Annotations to Autoshapes + +Text [annotations](/python/text-and-annotations) can be added to an autoshape +using the `annotation` keyword. Using the above example: +```python +import plotly.express as px +import plotly.graph_objects as go + +df = px.data.tips() +fig = px.scatter(df, x="total_bill", y="tip", facet_row="smoker", facet_col="sex") +# Add annotations anchored to the top right corner of the resulting lines +fig.add_vline(x=30, line_color="purple", annotation=go.layout.Annotation(text="A")) +# Another way to add annotations when we are only interested in specifying text +fig.add_hline(y=6, row="all", col=2, line_color="yellow", annotation_text="B") +# Specify the position of the resulting annotations +fig.add_vrect( + x0=20, + x1=40, + row=1, + col="all", + line_color="green", + annotation_text="C", + annotation_position="bottom inside left", +) +fig.show() +``` +Call `help` on any of the autoshape functions in the Python interpreter to learn +more (e.g., `help(fig.add_vline)`). diff --git a/doc/python/images.md b/doc/python/images.md index a89ae248fd..d69aa91c88 100644 --- a/doc/python/images.md +++ b/doc/python/images.md @@ -309,7 +309,7 @@ fig.show(config={'doubleClick': 'reset'}) _introduced in plotly 4.7_ -It can be useful to add shapes to a layout image, for highlighting an object, drawing bounding boxes as part of a machine learning training set, or identifying seeds for a segmentation algorithm. +It can be useful to add shapes to a layout image, for highlighting an object, drawing bounding boxes as part of a machine learning training set, or identifying seeds for a segmentation algorithm. In order to enable shape drawing, you need to - define a dragmode corresponding to a drawing tool (`'drawline'`,`'drawopenpath'`, `'drawclosedpath'`, `'drawcircle'`, or `'drawrect'`) @@ -317,7 +317,7 @@ In order to enable shape drawing, you need to The style of new shapes is specified by the `newshape` layout attribute. Shapes can be selected and modified after they have been drawn. More details and examples are given in the [tutorial on shapes](/python/shapes#drawing-shapes-on-cartesian-plots). -Drawing or modifying a shape triggers a `relayout` event, which [can be captured by a callback inside a Dash application](https://dash.plotly.com/interactive-graphing). +Drawing or modifying a shape triggers a `relayout` event, which [can be captured by a callback inside a Dash application](https://dash.plotly.com/interactive-graphing). ```python import plotly.graph_objects as go @@ -339,7 +339,7 @@ fig.add_layout_image( ) fig.update_xaxes(showgrid=False, range=(0, img_width)) fig.update_yaxes(showgrid=False, scaleanchor='x', range=(img_height, 0)) -# Line shape added programatically +# Line shape added programatically fig.add_shape( type='line', xref='x', yref='y', x0=650, x1=1080, y0=380, y1=180, line_color='cyan' @@ -359,5 +359,42 @@ fig.show(config={'modeBarButtonsToAdd':['drawline', ]}) ``` + +### Images Placed Relative to Axes + +Using `xref='x domain'` or `yref='y domain'`, images can be placed relative to +axes. As an example, the following shows how to put an image in the top corner +of a subplot (try panning and zooming the resulting figure): + +```python +import plotly.express as px + +df = px.data.iris() +fig = px.scatter(df, x="sepal_length", y="sepal_width", facet_col="species") +# sources of images +sources = [ + "https://upload.wikimedia.org/wikipedia/commons/thumb/f/fe/Iris_setosa_var._setosa_%282595031014%29.jpg/360px-Iris_setosa_var._setosa_%282595031014%29.jpg", + "https://upload.wikimedia.org/wikipedia/commons/thumb/3/38/Iris_versicolor_quebec_1.jpg/320px-Iris_versicolor_quebec_1.jpg", + "https://upload.wikimedia.org/wikipedia/commons/thumb/f/f8/Iris_virginica_2.jpg/480px-Iris_virginica_2.jpg", +] +# add images +for col, src in enumerate(sources): + fig.add_layout_image( + row=1, + col=col + 1, + source=src, + xref="x domain", + yref="y domain", + x=1, + y=1, + xanchor="right", + yanchor="top", + sizex=0.2, + sizey=0.2, + ) + +fig.show() +``` + #### Reference -See https://plotly.com/python/reference/layout/images/ for more information and chart attribute options! \ No newline at end of file +See https://plotly.com/python/reference/layout/images/ for more information and chart attribute options! diff --git a/doc/python/shapes.md b/doc/python/shapes.md index ee7479b22d..90e89dc36b 100644 --- a/doc/python/shapes.md +++ b/doc/python/shapes.md @@ -35,7 +35,7 @@ jupyter: ### Filled Area Chart -There are two ways to draw filled shapes: scatter traces and [layout.shapes](https://plotly.com/python/reference/layout/shapes/#layout-shapes-items-shape-type) which is mostly useful for the 2d subplots, and defines the shape type to be drawn, and can be rectangle, circle, line, or path (a custom SVG path). You also can use [scatterpolar](https://plotly.com/python/polar-chart/#categorical-polar-chart), scattergeo, [scattermapbox](https://plotly.com/python/filled-area-on-mapbox/#filled-scattermapbox-trace) to draw filled shapes on any kind of subplots. To set an area to be filled with a solid color, you need to define [Scatter.fill="toself"](https://plotly.com/python/reference/scatter/#scatter-fill) that connects the endpoints of the trace into a closed shape. If `mode=line` (default value), then you need to repeat the initial point of a shape at the of the sequence to have a closed shape. +There are two ways to draw filled shapes: scatter traces and [layout.shapes](https://plotly.com/python/reference/layout/shapes/#layout-shapes-items-shape-type) which is mostly useful for the 2d subplots, and defines the shape type to be drawn, and can be rectangle, circle, line, or path (a custom SVG path). You also can use [scatterpolar](https://plotly.com/python/polar-chart/#categorical-polar-chart), scattergeo, [scattermapbox](https://plotly.com/python/filled-area-on-mapbox/#filled-scattermapbox-trace) to draw filled shapes on any kind of subplots. To set an area to be filled with a solid color, you need to define [Scatter.fill="toself"](https://plotly.com/python/reference/scatter/#scatter-fill) that connects the endpoints of the trace into a closed shape. If `mode=line` (default value), then you need to repeat the initial point of a shape at the of the sequence to have a closed shape. ```python import plotly.graph_objects as go @@ -53,7 +53,7 @@ fig = go.Figure(go.Scatter(x=[0,1,2,0,None,3,3,5,5,3], y=[0,2,0,0,None,0.5,1.5,1 fig.show() ``` -#### Vertical and Horizontal Lines Positioned Relative to the Axes +#### Vertical and Horizontal Lines Positioned Relative to the Axis Data ```python import plotly.graph_objects as go @@ -118,7 +118,7 @@ fig.update_shapes(dict(xref='x', yref='y')) fig.show() ``` -#### Lines Positioned Relative to the Plot & to the Axes +#### Lines Positioned Relative to the Plot & to the Axis Data ```python import plotly.graph_objects as go @@ -227,7 +227,7 @@ fig.update_shapes(dict( fig.show() ``` -#### Rectangles Positioned Relative to the Axes +#### Rectangles Positioned Relative to the Axis Data ```python import plotly.graph_objects as go @@ -274,7 +274,7 @@ fig.update_shapes(dict(xref='x', yref='y')) fig.show() ``` -#### Rectangle Positioned Relative to the Plot & to the Axes +#### Rectangle Positioned Relative to the Plot & to the Axis Data ```python import plotly.graph_objects as go @@ -329,6 +329,38 @@ fig.add_shape( fig.show() ``` +#### A Rectangle Placed Relative to the Axis Position and Length + +A shape can be placed relative to an axis's position on the plot by adding the +string `' domain'` to the axis reference in the `xref` or `yref` attributes for +shapes. +The following code places a rectangle that starts at 60% and ends at 70% along +the x-axis, starting from the left, and starts at 80% and ends at 90% along the +y-axis, starting from the bottom. + +```python +import plotly.graph_objects as go +import plotly.express as px + +df = px.data.wind() +fig = px.scatter(df, y="frequency") + +fig.update_layout(xaxis=dict(domain=[0, 0.5]), yaxis=dict(domain=[0.25, 0.75])) + +# Add a shape whose x and y coordinates refer to the domains of the x and y axes +fig.add_shape( + type="rect", + xref="x domain", + yref="y domain", + x0=0.6, + x1=0.7, + y0=0.8, + y1=0.9, +) + +fig.show() +``` + #### Highlighting Time Series Regions with Rectangle Shapes ```python @@ -389,7 +421,7 @@ fig.update_layout( fig.show() ``` -#### Circles Positioned Relative to the Axes +#### Circles Positioned Relative to the Axis Data ```python import plotly.graph_objects as go @@ -641,7 +673,7 @@ fig.add_trace(go.Bar(x=[11,13,15], y=[8,11,20]), row=2, col=2) # Add shapes fig.update_layout( shapes=[ - dict(type="line", xref="x1", yref="y1", + dict(type="line", xref="x", yref="y", x0=3, y0=0.5, x1=5, y1=0.8, line_width=3), dict(type="rect", xref="x2", yref='y2', x0=4, y0=2, x1=5, y1=6), @@ -652,6 +684,33 @@ fig.update_layout( fig.show() ``` +#### Adding Shapes to Subplots +The same shape can be added to mulitple facets by using the `'all'` +keyword in the `row` and `col` arguments. For example +```python +import plotly.express as px + +df = px.data.tips() +fig = px.scatter(df, x="total_bill", y="tip", facet_row="smoker", facet_col="sex") +# Adds a rectangle to all facets +fig.add_shape( + dict(type="rect", x0=25, x1=35, y0=4, y1=6, line_color="purple"), + row="all", + col="all", +) +# Adds a line to all the rows of the second column +fig.add_shape( + dict(type="line", x0=20, x1=25, y0=5, y1=6, line_color="yellow"), row="all", col=2 +) + +# Adds a circle to all the columns of the first row +fig.add_shape( + dict(type="circle", x0=10, y0=2, x1=20, y1=7), row=1, col="all", line_color="green" +) +fig.show() +``` + + #### SVG Paths ```python @@ -724,14 +783,14 @@ You can create layout shapes programatically, but you can also draw shapes manua This shape-drawing feature is particularly interesting for annotating graphs, in particular [image traces](/python/imshow) or [layout images](/python/images). -Once you have drawn shapes, you can select and modify an existing shape by clicking on its boundary (note the arrow pointer). Its fillcolor turns to pink to highlight the activated shape and then you can +Once you have drawn shapes, you can select and modify an existing shape by clicking on its boundary (note the arrow pointer). Its fillcolor turns to pink to highlight the activated shape and then you can - drag and resize it for lines, rectangles and circles/ellipses - drag and move individual vertices for closed paths - move individual vertices for open paths. -An activated shape is deleted by cliking on the `eraseshape` button. +An activated shape is deleted by clicking on the `eraseshape` button. -Drawing or modifying a shape triggers a `relayout` event, which [can be captured by a callback inside a Dash application](https://dash.plotly.com/interactive-graphing). +Drawing or modifying a shape triggers a `relayout` event, which [can be captured by a callback inside a Dash application](https://dash.plotly.com/interactive-graphing). ```python import plotly.graph_objects as go @@ -749,7 +808,7 @@ fig.add_annotation( # shape defined programatically fig.add_shape(editable=True, x0=-1, x1=0, y0=2, y1=3, - xref='x1', yref='y1') + xref='x', yref='y') # define dragmode and add modebar buttons fig.update_layout(dragmode='drawrect') fig.show(config={'modeBarButtonsToAdd':['drawline', @@ -786,7 +845,7 @@ fig.add_shape(line_color='yellow', opacity=0.4, editable=True, x0=0, x1=1, y0=2, y1=3, - xref='x1', yref='y1' + xref='x', yref='y' ) fig.update_layout(dragmode='drawrect', # style of new shapes @@ -803,4 +862,4 @@ fig.show(config={'modeBarButtonsToAdd':['drawline', ``` ### Reference -See https://plotly.com/python/reference/layout/shapes/ for more information and chart attribute options! \ No newline at end of file +See https://plotly.com/python/reference/layout/shapes/ for more information and chart attribute options! diff --git a/doc/python/text-and-annotations.md b/doc/python/text-and-annotations.md index e0d49676ea..6777c0114e 100644 --- a/doc/python/text-and-annotations.md +++ b/doc/python/text-and-annotations.md @@ -534,6 +534,74 @@ fig.update_layout( fig.show() ``` +### Adding Annotations Referenced to an Axis + +To place annotations relative to the length or height of an axis, the string +`' domain'` can be added after the axis reference in the `xref` or `yref` fields. +For example: + +```python +import plotly.express as px +import plotly.graph_objects as go + +df = px.data.wind() +fig = px.scatter(df, y="frequency") + +# Set a custom domain to see how the ' domain' string changes the behaviour +fig.update_layout(xaxis=dict(domain=[0, 0.5]), yaxis=dict(domain=[0.25, 0.75])) + +fig.add_annotation( + xref="x domain", + yref="y domain", + # The arrow head will be 25% along the x axis, starting from the left + x=0.25, + # The arrow head will be 40% along the y axis, starting from the bottom + y=0.4, + text="An annotation referencing the axes", + # By default, the text coordinates are specified in pixels and are relative + # to the arrow head + ax=100, + ay=100, + arrowhead=2, +) + +fig.show() +``` + +### Specifying the Text's Position Absolutely + +The text coordinates / dimensions of the arrow can be specified absolutely, as +long as they use exactly the same coordinate system as the arrowhead. For +example: + +```python +import plotly.express as px +import plotly.graph_objects as go + +df = px.data.wind() +fig = px.scatter(df, y="frequency") + +fig.update_layout(xaxis=dict(domain=[0, 0.5]), yaxis=dict(domain=[0.25, 0.75])) +fig.add_annotation( + xref="x domain", + yref="y", + x=0.75, + y=1, + text="An annotation whose text and arrowhead reference the axes and the data", + # If axref is exactly the same as xref, then the text's position is + # absolute and specified in the same coordinates as xref. + axref="x domain", + # The same is the case for yref and ayref, but here the coordinates are data + # coordinates + ayref="y", + ax=0.5, + ay=2, + arrowhead=2, +) + +fig.show() +``` + ### Customize Displayed Text with a Text Template To show an arbitrary text in your chart you can use [texttemplate](https://plotly.com/python/reference/pie/#pie-texttemplate), which is a template string used for rendering the information, and will override [textinfo](https://plotly.com/python/reference/treemap/#treemap-textinfo). diff --git a/packages/python/plotly/_plotly_utils/basevalidators.py b/packages/python/plotly/_plotly_utils/basevalidators.py index 099c26598c..190bc8fad6 100644 --- a/packages/python/plotly/_plotly_utils/basevalidators.py +++ b/packages/python/plotly/_plotly_utils/basevalidators.py @@ -354,14 +354,14 @@ def present(self, v): class DataArrayValidator(BaseValidator): """ - "data_array": { - "description": "An {array} of data. The value MUST be an - {array}, or we ignore it.", - "requiredOpts": [], - "otherOpts": [ - "dflt" - ] - }, + "data_array": { + "description": "An {array} of data. The value MUST be an + {array}, or we ignore it.", + "requiredOpts": [], + "otherOpts": [ + "dflt" + ] + }, """ def __init__(self, plotly_name, parent_name, **kwargs): @@ -394,18 +394,18 @@ def validate_coerce(self, v): class EnumeratedValidator(BaseValidator): """ - "enumerated": { - "description": "Enumerated value type. The available values are - listed in `values`.", - "requiredOpts": [ - "values" - ], - "otherOpts": [ - "dflt", - "coerceNumber", - "arrayOk" - ] - }, + "enumerated": { + "description": "Enumerated value type. The available values are + listed in `values`.", + "requiredOpts": [ + "values" + ], + "otherOpts": [ + "dflt", + "coerceNumber", + "arrayOk" + ] + }, """ def __init__( @@ -601,13 +601,13 @@ def validate_coerce(self, v): class BooleanValidator(BaseValidator): """ - "boolean": { - "description": "A boolean (true/false) value.", - "requiredOpts": [], - "otherOpts": [ - "dflt" - ] - }, + "boolean": { + "description": "A boolean (true/false) value.", + "requiredOpts": [], + "otherOpts": [ + "dflt" + ] + }, """ def __init__(self, plotly_name, parent_name, **kwargs): @@ -664,19 +664,19 @@ def validate_coerce(self, v): class NumberValidator(BaseValidator): """ - "number": { - "description": "A number or a numeric value (e.g. a number - inside a string). When applicable, values - greater (less) than `max` (`min`) are coerced to - the `dflt`.", - "requiredOpts": [], - "otherOpts": [ - "dflt", - "min", - "max", - "arrayOk" - ] - }, + "number": { + "description": "A number or a numeric value (e.g. a number + inside a string). When applicable, values + greater (less) than `max` (`min`) are coerced to + the `dflt`.", + "requiredOpts": [], + "otherOpts": [ + "dflt", + "min", + "max", + "arrayOk" + ] + }, """ def __init__( @@ -794,18 +794,18 @@ def validate_coerce(self, v): class IntegerValidator(BaseValidator): """ - "integer": { - "description": "An integer or an integer inside a string. When - applicable, values greater (less) than `max` - (`min`) are coerced to the `dflt`.", - "requiredOpts": [], - "otherOpts": [ - "dflt", - "min", - "max", - "arrayOk" - ] - }, + "integer": { + "description": "An integer or an integer inside a string. When + applicable, values greater (less) than `max` + (`min`) are coerced to the `dflt`.", + "requiredOpts": [], + "otherOpts": [ + "dflt", + "min", + "max", + "arrayOk" + ] + }, """ def __init__( @@ -925,18 +925,18 @@ def validate_coerce(self, v): class StringValidator(BaseValidator): """ - "string": { - "description": "A string value. Numbers are converted to strings - except for attributes with `strict` set to true.", - "requiredOpts": [], - "otherOpts": [ - "dflt", - "noBlank", - "strict", - "arrayOk", - "values" - ] - }, + "string": { + "description": "A string value. Numbers are converted to strings + except for attributes with `strict` set to true.", + "requiredOpts": [], + "otherOpts": [ + "dflt", + "noBlank", + "strict", + "arrayOk", + "values" + ] + }, """ def __init__( @@ -1094,21 +1094,21 @@ def validate_coerce(self, v): class ColorValidator(BaseValidator): """ - "color": { - "description": "A string describing color. Supported formats: - - hex (e.g. '#d3d3d3') - - rgb (e.g. 'rgb(255, 0, 0)') - - rgba (e.g. 'rgb(255, 0, 0, 0.5)') - - hsl (e.g. 'hsl(0, 100%, 50%)') - - hsv (e.g. 'hsv(0, 100%, 100%)') - - named colors(full list: - http://www.w3.org/TR/css3-color/#svg-color)", - "requiredOpts": [], - "otherOpts": [ - "dflt", - "arrayOk" - ] - }, + "color": { + "description": "A string describing color. Supported formats: + - hex (e.g. '#d3d3d3') + - rgb (e.g. 'rgb(255, 0, 0)') + - rgba (e.g. 'rgb(255, 0, 0, 0.5)') + - hsl (e.g. 'hsl(0, 100%, 50%)') + - hsv (e.g. 'hsv(0, 100%, 100%)') + - named colors(full list: + http://www.w3.org/TR/css3-color/#svg-color)", + "requiredOpts": [], + "otherOpts": [ + "dflt", + "arrayOk" + ] + }, """ re_hex = re.compile(r"#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})") @@ -1445,14 +1445,14 @@ def perform_validate_coerce(v, allow_number=None): class ColorlistValidator(BaseValidator): """ - "colorlist": { - "description": "A list of colors. Must be an {array} containing - valid colors.", - "requiredOpts": [], - "otherOpts": [ - "dflt" - ] - } + "colorlist": { + "description": "A list of colors. Must be an {array} containing + valid colors.", + "requiredOpts": [], + "otherOpts": [ + "dflt" + ] + } """ def __init__(self, plotly_name, parent_name, **kwargs): @@ -1492,20 +1492,20 @@ def validate_coerce(self, v): class ColorscaleValidator(BaseValidator): """ - "colorscale": { - "description": "A Plotly colorscale either picked by a name: - (any of Greys, YlGnBu, Greens, YlOrRd, Bluered, - RdBu, Reds, Blues, Picnic, Rainbow, Portland, - Jet, Hot, Blackbody, Earth, Electric, Viridis) - customized as an {array} of 2-element {arrays} - where the first element is the normalized color - level value (starting at *0* and ending at *1*), - and the second item is a valid color string.", - "requiredOpts": [], - "otherOpts": [ - "dflt" - ] - }, + "colorscale": { + "description": "A Plotly colorscale either picked by a name: + (any of Greys, YlGnBu, Greens, YlOrRd, Bluered, + RdBu, Reds, Blues, Picnic, Rainbow, Portland, + Jet, Hot, Blackbody, Earth, Electric, Viridis) + customized as an {array} of 2-element {arrays} + where the first element is the normalized color + level value (starting at *0* and ending at *1*), + and the second item is a valid color string.", + "requiredOpts": [], + "otherOpts": [ + "dflt" + ] + }, """ def __init__(self, plotly_name, parent_name, **kwargs): @@ -1560,7 +1560,7 @@ def description(self): Many predefined colorscale lists are included in the sequential, diverging, and cyclical modules in the plotly.colors package. - A list of 2-element lists where the first element is the - normalized color level value (starting at 0 and ending at 1), + normalized color level value (starting at 0 and ending at 1), and the second item is a valid color string. (e.g. [[0, 'green'], [0.5, 'red'], [1.0, 'rgb(0, 0, 255)']]) - One of the following named colorscales: @@ -1644,13 +1644,13 @@ def present(self, v): class AngleValidator(BaseValidator): """ - "angle": { - "description": "A number (in degree) between -180 and 180.", - "requiredOpts": [], - "otherOpts": [ - "dflt" - ] - }, + "angle": { + "description": "A number (in degree) between -180 and 180.", + "requiredOpts": [], + "otherOpts": [ + "dflt" + ] + }, """ def __init__(self, plotly_name, parent_name, **kwargs): @@ -1685,18 +1685,18 @@ def validate_coerce(self, v): class SubplotidValidator(BaseValidator): """ - "subplotid": { - "description": "An id string of a subplot type (given by dflt), - optionally followed by an integer >1. e.g. if - dflt='geo', we can have 'geo', 'geo2', 'geo3', - ...", - "requiredOpts": [ - "dflt" - ], - "otherOpts": [ - "regex" - ] - } + "subplotid": { + "description": "An id string of a subplot type (given by dflt), + optionally followed by an integer >1. e.g. if + dflt='geo', we can have 'geo', 'geo2', 'geo3', + ...", + "requiredOpts": [ + "dflt" + ], + "otherOpts": [ + "regex" + ] + } """ def __init__(self, plotly_name, parent_name, dflt=None, regex=None, **kwargs): @@ -1756,21 +1756,21 @@ def validate_coerce(self, v): class FlaglistValidator(BaseValidator): """ - "flaglist": { - "description": "A string representing a combination of flags - (order does not matter here). Combine any of the - available `flags` with *+*. - (e.g. ('lines+markers')). Values in `extras` - cannot be combined.", - "requiredOpts": [ - "flags" - ], - "otherOpts": [ - "dflt", - "extras", - "arrayOk" - ] - }, + "flaglist": { + "description": "A string representing a combination of flags + (order does not matter here). Combine any of the + available `flags` with *+*. + (e.g. ('lines+markers')). Values in `extras` + cannot be combined.", + "requiredOpts": [ + "flags" + ], + "otherOpts": [ + "dflt", + "extras", + "arrayOk" + ] + }, """ def __init__( @@ -1877,15 +1877,15 @@ def validate_coerce(self, v): class AnyValidator(BaseValidator): """ - "any": { - "description": "Any type.", - "requiredOpts": [], - "otherOpts": [ - "dflt", - "values", - "arrayOk" - ] - }, + "any": { + "description": "Any type.", + "requiredOpts": [], + "otherOpts": [ + "dflt", + "values", + "arrayOk" + ] + }, """ def __init__(self, plotly_name, parent_name, values=None, array_ok=False, **kwargs): @@ -1917,17 +1917,17 @@ def validate_coerce(self, v): class InfoArrayValidator(BaseValidator): """ - "info_array": { - "description": "An {array} of plot information.", - "requiredOpts": [ - "items" - ], - "otherOpts": [ - "dflt", - "freeLength", - "dimensions" - ] - } + "info_array": { + "description": "An {array} of plot information.", + "requiredOpts": [ + "items" + ], + "otherOpts": [ + "dflt", + "freeLength", + "dimensions" + ] + } """ def __init__( @@ -2707,7 +2707,7 @@ def description(self): - A string containing multiple registered template names, joined on '+' characters (e.g. 'template1+template2'). In this case the resulting - template is computed by merging together the collection of registered + template is computed by merging together the collection of registered templates""" return compound_description diff --git a/packages/python/plotly/codegen/figure.py b/packages/python/plotly/codegen/figure.py index bf466d06d3..9b579093fa 100644 --- a/packages/python/plotly/codegen/figure.py +++ b/packages/python/plotly/codegen/figure.py @@ -144,16 +144,18 @@ def add_{trace_node.plotly_name}(self""" doc_extras = [ ( - "row : int or None (default)", + "row : 'all', int or None (default)", "Subplot row index (starting from 1) for the trace to be " "added. Only valid if figure was created using " - "`plotly.tools.make_subplots`", + "`plotly.tools.make_subplots`." + "If 'all', addresses all rows in the specified column(s).", ), ( - "col : int or None (default)", + "col : 'all', int or None (default)", "Subplot col index (starting from 1) for the trace to be " "added. Only valid if figure was created using " - "`plotly.tools.make_subplots`", + "`plotly.tools.make_subplots`." + "If 'all', addresses all columns in the specified row(s).", ), ] @@ -539,7 +541,7 @@ def add_{method_prefix}{singular_name}(self""" buffer, node.child_datatypes, prepend_extras=["arg"], - append_extras=["row", "col", "secondary_y"], + append_extras=["row", "col", "secondary_y", "exclude_empty_subplots"], ) prepend_extras = [ @@ -550,9 +552,19 @@ def add_{method_prefix}{singular_name}(self""" ) ] append_extras = [ - ("row", f"Subplot row for {singular_name}"), - ("col", f"Subplot column for {singular_name}"), + ( + "row", + f"Subplot row for {singular_name}. If 'all', addresses all rows in the specified column(s).", + ), + ( + "col", + f"Subplot column for {singular_name}. If 'all', addresses all columns in the specified row(s).", + ), ("secondary_y", f"Whether to add {singular_name} to secondary y-axis"), + ( + "exclude_empty_subplots", + f"If True, {singular_name} will not be added to subplots without traces.", + ), ] add_docstring( buffer, @@ -589,6 +601,7 @@ def add_{method_prefix}{singular_name}(self""" row=row, col=col, secondary_y=secondary_y, + exclude_empty_subplots=exclude_empty_subplots, )""" ) diff --git a/packages/python/plotly/optional-requirements.txt b/packages/python/plotly/optional-requirements.txt index 8ac97789fc..450c32d334 100644 --- a/packages/python/plotly/optional-requirements.txt +++ b/packages/python/plotly/optional-requirements.txt @@ -25,7 +25,7 @@ psutil ## code formatting pre-commit -black==19.10b0 +black ## codegen dependencies ## inflect diff --git a/packages/python/plotly/plotly/basedatatypes.py b/packages/python/plotly/plotly/basedatatypes.py index 9733d5b324..8cb0b07a05 100644 --- a/packages/python/plotly/plotly/basedatatypes.py +++ b/packages/python/plotly/plotly/basedatatypes.py @@ -8,16 +8,150 @@ import warnings from contextlib import contextmanager from copy import deepcopy, copy +import itertools from _plotly_utils.utils import _natural_sort_strings, _get_int_type from .optional_imports import get_module +from . import shapeannotation + # Create Undefined sentinel value # - Setting a property to None removes any existing value # - Setting a property to Undefined leaves existing value unmodified Undefined = object() +def _combine_dicts(dicts): + all_args = dict() + for d in dicts: + for k in d: + all_args[k] = d[k] + return all_args + + +def _indexing_combinations(dims, alls, product=False): + """ + Gives indexing tuples specified by the coordinates in dims. + If a member of dims is 'all' then it is replaced by the corresponding member + in alls. + If product is True, then the cartesian product of all the indices is + returned, otherwise the zip (that means index lists of mis-matched length + will yield a list of tuples whose length is the length of the shortest + list). + """ + if len(dims) == 0: + # this is because list(itertools.product(*[])) returns [()] which has non-zero + # length! + return [] + if len(dims) != len(alls): + raise ValueError( + "Must have corresponding values in alls for each value of dims. Got dims=%s and alls=%s." + % (str(dims), str(alls)) + ) + r = [] + for d, a in zip(dims, alls): + if d == "all": + d = a + elif type(d) != type(list()): + d = [d] + r.append(d) + if product: + return itertools.product(*r) + else: + return zip(*r) + + +def _is_select_subplot_coordinates_arg(*args): + """ Returns true if any args are lists or the string 'all' """ + return any((a == "all") or (type(a) == type(list())) for a in args) + + +def _axis_spanning_shapes_docstr(shape_type): + docstr = "" + if shape_type == "hline": + docstr = """ +Add a horizontal line to a plot or subplot that extends infinitely in the +x-dimension. + +Parameters +---------- +y: float or int + A number representing the y coordinate of the horizontal line.""" + elif shape_type == "vline": + docstr = """ +Add a vertical line to a plot or subplot that extends infinitely in the +y-dimension. + +Parameters +---------- +x: float or int + A number representing the x coordinate of the vertical line.""" + elif shape_type == "hrect": + docstr = """ +Add a rectangle to a plot or subplot that extends infinitely in the +x-dimension. + +Parameters +---------- +y0: float or int + A number representing the y coordinate of one side of the rectangle. +y1: float or int + A number representing the y coordinate of the other side of the rectangle.""" + elif shape_type == "vrect": + docstr = """ +Add a rectangle to a plot or subplot that extends infinitely in the +y-dimension. + +Parameters +---------- +x0: float or int + A number representing the x coordinate of one side of the rectangle. +x1: float or int + A number representing the x coordinate of the other side of the rectangle.""" + docstr += """ +exclude_empty_subplots: Boolean + If True (default) do not place the shape on subplots that have no data + plotted on them. +row: None, int or 'all' + Subplot row for shape indexed starting at 1. If 'all', addresses all rows in + the specified column(s). If both row and col are None, addresses the + first subplot if subplots exist, or the only plot. By default is "all". +col: None, int or 'all' + Subplot column for shape indexed starting at 1. If 'all', addresses all rows in + the specified column(s). If both row and col are None, addresses the + first subplot if subplots exist, or the only plot. By default is "all". +annotation: dict or plotly.graph_objects.layout.Annotation. If dict(), + it is interpreted as describing an annotation. The annotation is + placed relative to the shape based on annotation_position (see + below) unless its x or y value has been specified for the annotation + passed here. xref and yref are always the same as for the added + shape and cannot be overridden.""" + if shape_type in ["hline", "vline"]: + docstr += """ +annotation_position: a string containing optionally ["top", "bottom"] + and ["left", "right"] specifying where the text should be anchored + to on the line. Example positions are "bottom left", "right top", + "right", "bottom". If an annotation is added but annotation_position is + not specified, this defaults to "top right".""" + elif shape_type in ["hrect", "vrect"]: + docstr += """ +annotation_position: a string containing optionally ["inside", "outside"], ["top", "bottom"] + and ["left", "right"] specifying where the text should be anchored + to on the rectangle. Example positions are "outside top left", "inside + bottom", "right", "inside left", "inside" ("outside" is not supported). If + an annotation is added but annotation_position is not specified this + defaults to "inside top right".""" + docstr += """ +annotation_*: any parameters to go.layout.Annotation can be passed as + keywords by prefixing them with "annotation_". For example, to specify the + annotation text "example" you can pass annotation_text="example" as a + keyword argument. +**kwargs: + Any named function parameters that can be passed to 'add_shape', + except for x0, x1, y0, y1 or type.""" + return docstr + + class BaseFigure(object): """ Base class for all figure types (both widget and non-widget) @@ -1086,7 +1220,14 @@ def _select_annotations_like( yield obj def _add_annotation_like( - self, prop_singular, prop_plural, new_obj, row=None, col=None, secondary_y=None + self, + prop_singular, + prop_plural, + new_obj, + row=None, + col=None, + secondary_y=None, + exclude_empty_subplots=False, ): # Make sure we have both row and col or neither if row is not None and col is None: @@ -1100,11 +1241,37 @@ def _add_annotation_like( "row and col must be specified together" ) + # Address multiple subplots + if row is not None and _is_select_subplot_coordinates_arg(row, col): + # TODO product argument could be added + rows_cols = self._select_subplot_coordinates(row, col) + for r, c in rows_cols: + self._add_annotation_like( + prop_singular, + prop_plural, + new_obj, + row=r, + col=c, + secondary_y=secondary_y, + exclude_empty_subplots=exclude_empty_subplots, + ) + return self + # Get grid_ref if specific row or column requested if row is not None: grid_ref = self._validate_get_grid_ref() + if row > len(grid_ref): + raise IndexError( + "row index %d out-of-bounds, row index must be between 1 and %d, inclusive." + % (row, len(grid_ref)) + ) + if col > len(grid_ref[row - 1]): + raise IndexError( + "column index %d out-of-bounds, " + "column index must be between 1 and %d, inclusive." + % (row, len(grid_ref[row - 1])) + ) refs = grid_ref[row - 1][col - 1] - if not refs: raise ValueError( "No subplot found at position ({r}, {c})".format(r=row, c=col) @@ -1132,6 +1299,22 @@ def _add_annotation_like( else: xaxis, yaxis = refs[0].layout_keys xref, yref = xaxis.replace("axis", ""), yaxis.replace("axis", "") + # if exclude_empty_subplots is True, check to see if subplot is + # empty and return if it is + if exclude_empty_subplots and ( + not self._subplot_contains_trace(xref, yref) + ): + return self + # in case the user specified they wanted an axis to refer to the + # domain of that axis and not the data, append ' domain' to the + # computed axis accordingly + def _add_domain(ax_letter, new_axref): + axref = ax_letter + "ref" + if axref in new_obj._props.keys() and "domain" in new_obj[axref]: + new_axref += " domain" + return 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,) @@ -1576,7 +1759,9 @@ def _validate_rows_cols(name, n, vals): else: BaseFigure._raise_invalid_rows_cols(name=name, n=n, invalid=vals) - def add_trace(self, trace, row=None, col=None, secondary_y=None): + def add_trace( + self, trace, row=None, col=None, secondary_y=None, exclude_empty_subplots=False + ): """ Add a trace to the figure @@ -1594,14 +1779,16 @@ def add_trace(self, trace, row=None, col=None, secondary_y=None): - All remaining properties are passed to the constructor of the specified trace type. - row : int or None (default None) - Subplot row index (starting from 1) for the trace to be added. - Only valid if figure was created using - `plotly.subplots.make_subplots` - col : int or None (default None) - Subplot col index (starting from 1) for the trace to be added. - Only valid if figure was created using - `plotly.subplots.make_subplots` + row : 'all', int or None (default) + Subplot row index (starting from 1) for the trace to be + added. Only valid if figure was created using + `plotly.tools.make_subplots`. + If 'all', addresses all rows in the specified column(s). + col : 'all', int or None (default) + Subplot col index (starting from 1) for the trace to be + added. Only valid if figure was created using + `plotly.tools.make_subplots`. + If 'all', addresses all columns in the specified row(s). secondary_y: boolean or None (default None) If True, associate this trace with the secondary y-axis of the subplot at the specified row and col. Only valid if all of the @@ -1614,6 +1801,9 @@ def add_trace(self, trace, row=None, col=None, secondary_y=None): make_subplots. See the make_subplots docstring for more info. * The trace argument is a 2D cartesian trace (scatter, bar, etc.) + exclude_empty_subplots: boolean + If True, the trace will not be added to subplots that don't already + have traces. Returns ------- BaseFigure @@ -1654,14 +1844,36 @@ def add_trace(self, trace, row=None, col=None, secondary_y=None): "row and col must be specified together" ) + # Address multiple subplots + if row is not None and _is_select_subplot_coordinates_arg(row, col): + # TODO add product argument + rows_cols = self._select_subplot_coordinates(row, col) + for r, c in rows_cols: + self.add_trace( + trace, + row=r, + col=c, + secondary_y=secondary_y, + exclude_empty_subplots=exclude_empty_subplots, + ) + return self + return self.add_traces( data=[trace], rows=[row] if row is not None else None, cols=[col] if col is not None else None, secondary_ys=[secondary_y] if secondary_y is not None else None, + exclude_empty_subplots=exclude_empty_subplots, ) - def add_traces(self, data, rows=None, cols=None, secondary_ys=None): + def add_traces( + self, + data, + rows=None, + cols=None, + secondary_ys=None, + exclude_empty_subplots=False, + ): """ Add traces to the figure @@ -1698,6 +1910,10 @@ def add_traces(self, data, rows=None, cols=None, secondary_ys=None): List of secondary_y booleans for traces to be added. See the docstring for `add_trace` for more info. + exclude_empty_subplots: boolean + If True, the trace will not be added to subplots that don't already + have traces. + Returns ------- BaseFigure @@ -1774,6 +1990,16 @@ def add_traces(self, data, rows=None, cols=None, secondary_ys=None): for trace, row, col, secondary_y in zip(data, rows, cols, secondary_ys): self._set_trace_grid_position(trace, row, col, secondary_y) + if exclude_empty_subplots: + data = list( + filter( + lambda trace: self._subplot_contains_trace( + trace["xaxis"], trace["yaxis"] + ), + data, + ) + ) + # Make deep copy of trace data (Optimize later if needed) new_traces_data = [deepcopy(trace._props) for trace in data] @@ -1872,6 +2098,47 @@ def _validate_get_grid_ref(self): ) return grid_ref + def _get_subplot_rows_columns(self): + """ + Returns a pair of lists, the first containing all the row indices and + the second all the column indices. + """ + # currently, this just iterates over all the rows and columns (because + # self._grid_ref is currently always rectangular) + grid_ref = self._validate_get_grid_ref() + nrows = len(grid_ref) + ncols = len(grid_ref[0]) + return (range(1, nrows + 1), range(1, ncols + 1)) + + def _get_subplot_coordinates(self): + """ + Returns an iterator over (row,col) pairs representing all the possible + subplot coordinates. + """ + return itertools.product(*self._get_subplot_rows_columns()) + + def _select_subplot_coordinates(self, rows, cols, product=False): + """ + Allows selecting all or a subset of the subplots. + If any of rows or columns is 'all', product is set to True. This is + probably the expected behaviour, so that rows=1,cols='all' selects all + the columns in row 1 (otherwise it would just select the subplot in the + first row and first column). + """ + product |= any([s == "all" for s in [rows, cols]]) + # TODO: If grid_ref ever becomes non-rectangular, then t should be the + # set-intersection of the result of _indexing_combinations and + # _get_subplot_coordinates, because some coordinates given by + # the _indexing_combinations function might be invalid. + t = _indexing_combinations( + [rows, cols], list(self._get_subplot_rows_columns()), product=product, + ) + t = list(t) + # remove rows and cols where the subplot is "None" + grid_ref = self._validate_get_grid_ref() + t = list(filter(lambda u: grid_ref[u[0] - 1][u[1] - 1] is not None, t)) + return t + def get_subplot(self, row, col, secondary_y=False): """ Return an object representing the subplot at the specified row @@ -3420,6 +3687,206 @@ def _index_is(iterable, val): return index_list[0] + def _make_axis_spanning_layout_object(self, direction, shape): + """ + Convert a shape drawn on a plot or a subplot into one whose yref or xref + ends with " domain" and has coordinates so that the shape will seem to + extend infinitely in that dimension. This is useful for drawing lines or + boxes on a plot where one dimension of the shape will not move out of + bounds when moving the plot's view. + Note that the shape already added to the (sub)plot must have the + corresponding axis reference referring to an actual axis (e.g., 'x', + 'y2' etc. are accepted, but not 'paper'). This will be the case if the + shape was added with "add_shape". + Shape must have the x0, x1, y0, y1 fields already initialized. + """ + if direction == "vertical": + # fix y points to top and bottom of subplot + axis = "y" + ref = "yref" + axis_layout_key_template = "yaxis%s" + elif direction == "horizontal": + # fix x points to left and right of subplot + axis = "x" + ref = "xref" + axis_layout_key_template = "xaxis%s" + else: + raise ValueError( + "Bad direction: %s. Permissible values are 'vertical' and 'horizontal'." + % (direction,) + ) + # set the ref to " domain" so that its size is based on the + # axis's size + shape[ref] += " domain" + return shape + + def _process_multiple_axis_spanning_shapes( + self, + shape_args, + row, + col, + shape_type, + exclude_empty_subplots=True, + annotation=None, + **kwargs + ): + """ + Add a shape or multiple shapes and call _make_axis_spanning_layout_object on + all the new shapes. + """ + if shape_type in ["vline", "vrect"]: + direction = "vertical" + elif shape_type in ["hline", "hrect"]: + direction = "horizontal" + else: + raise ValueError( + "Bad shape_type %s, needs to be one of 'vline', 'hline', 'vrect', 'hrect'" + % (shape_type,) + ) + if (row is not None or col is not None) and (not self._has_subplots()): + # this has no subplots to address, so we force row and col to be None + row = None + col = None + n_shapes_before = len(self.layout["shapes"]) + n_annotations_before = len(self.layout["annotations"]) + # shapes are always added at the end of the tuple of shapes, so we see + # how long the tuple is before the call and after the call, and adjust + # the new shapes that were added at the end + # extract annotation prefixed kwargs + # annotation with extra parameters based on the annotation_position + # argument and other annotation_ prefixed kwargs + shape_kwargs, annotation_kwargs = shapeannotation.split_dict_by_key_prefix( + kwargs, "annotation_" + ) + augmented_annotation = shapeannotation.axis_spanning_shape_annotation( + annotation, shape_type, shape_args, annotation_kwargs + ) + self.add_shape( + row=row, + col=col, + exclude_empty_subplots=exclude_empty_subplots, + **_combine_dicts([shape_args, shape_kwargs]) + ) + if augmented_annotation is not None: + self.add_annotation( + augmented_annotation, + row=row, + col=col, + exclude_empty_subplots=exclude_empty_subplots, + ) + # update xref and yref for the new shapes and annotations + for layout_obj, n_layout_objs_before in zip( + ["shapes", "annotations"], [n_shapes_before, n_annotations_before] + ): + n_layout_objs_after = len(self.layout[layout_obj]) + if (n_layout_objs_after > n_layout_objs_before) and ( + row == None and col == None + ): + # this was called intending to add to a single plot (and + # self.add_{layout_obj} succeeded) + # however, in the case of a single plot, xref and yref are not + # specified, so we specify them here so the following routines can work + # (they need to append " domain" to xref or yref) + self.layout[layout_obj][-1].update(xref="x", 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) + ], + ) + ) + self.layout[layout_obj] = ( + self.layout[layout_obj][:n_layout_objs_before] + new_layout_objs + ) + + def add_vline( + self, + x, + row="all", + col="all", + exclude_empty_subplots=True, + annotation=None, + **kwargs + ): + self._process_multiple_axis_spanning_shapes( + dict(type="line", x0=x, x1=x, y0=0, y1=1), + row, + col, + "vline", + exclude_empty_subplots=exclude_empty_subplots, + annotation=annotation, + **kwargs + ) + return self + + add_vline.__doc__ = _axis_spanning_shapes_docstr("vline") + + def add_hline(self, y, row="all", col="all", exclude_empty_subplots=True, **kwargs): + self._process_multiple_axis_spanning_shapes( + dict(type="line", x0=0, x1=1, y0=y, y1=y,), + row, + col, + "hline", + exclude_empty_subplots=exclude_empty_subplots, + **kwargs + ) + return self + + add_hline.__doc__ = _axis_spanning_shapes_docstr("hline") + + def add_vrect( + self, x0, x1, row="all", col="all", exclude_empty_subplots=True, **kwargs + ): + self._process_multiple_axis_spanning_shapes( + dict(type="rect", x0=x0, x1=x1, y0=0, y1=1), + row, + col, + "vrect", + exclude_empty_subplots=exclude_empty_subplots, + **kwargs + ) + return self + + add_vrect.__doc__ = _axis_spanning_shapes_docstr("vrect") + + def add_hrect( + self, y0, y1, row="all", col="all", exclude_empty_subplots=True, **kwargs + ): + self._process_multiple_axis_spanning_shapes( + dict(type="rect", x0=0, x1=1, y0=y0, y1=y1), + row, + col, + "hrect", + exclude_empty_subplots=exclude_empty_subplots, + **kwargs + ) + return self + + add_hrect.__doc__ = _axis_spanning_shapes_docstr("hrect") + + def _has_subplots(self): + """ Returns True if figure contains subplots, otherwise it contains a + single plot and so this returns False. """ + return self._grid_ref is not None + + def _subplot_contains_trace(self, xref, yref): + return any( + t == (xref, yref) + for t in [ + # if a trace exists but has no xaxis or yaxis keys, then it + # is plotted with xaxis 'x' and yaxis 'y' + ( + "x" if d["xaxis"] is None else d["xaxis"], + "y" if d["yaxis"] is None else d["yaxis"], + ) + for d in self.data + ] + ) + class BasePlotlyType(object): """ diff --git a/packages/python/plotly/plotly/graph_objs/_figure.py b/packages/python/plotly/plotly/graph_objs/_figure.py index 05e48d370f..0c2b481965 100644 --- a/packages/python/plotly/plotly/graph_objs/_figure.py +++ b/packages/python/plotly/plotly/graph_objs/_figure.py @@ -721,14 +721,16 @@ def add_area( "legendonly", the trace is not drawn, but can appear as a legend item (provided that the legend itself is visible). - row : int or None (default) + row : 'all', int or None (default) Subplot row index (starting from 1) for the trace to be added. Only valid if figure was created using - `plotly.tools.make_subplots` - col : int or None (default) + `plotly.tools.make_subplots`.If 'all', addresses all + rows in the specified column(s). + col : 'all', int or None (default) Subplot col index (starting from 1) for the trace to be added. Only valid if figure was created using - `plotly.tools.make_subplots` + `plotly.tools.make_subplots`.If 'all', addresses all + columns in the specified row(s). Returns ------- @@ -1174,14 +1176,16 @@ def add_bar( ysrc Sets the source reference on Chart Studio Cloud for y . - row : int or None (default) + row : 'all', int or None (default) Subplot row index (starting from 1) for the trace to be added. Only valid if figure was created using - `plotly.tools.make_subplots` - col : int or None (default) + `plotly.tools.make_subplots`.If 'all', addresses all + rows in the specified column(s). + col : 'all', int or None (default) Subplot col index (starting from 1) for the trace to be added. Only valid if figure was created using - `plotly.tools.make_subplots` + `plotly.tools.make_subplots`.If 'all', addresses all + columns in the specified row(s). secondary_y: boolean or None (default None) If True, associate this trace with the secondary y-axis of the subplot at the specified row and col. Only valid if all of the @@ -1520,14 +1524,16 @@ def add_barpolar( widthsrc Sets the source reference on Chart Studio Cloud for width . - row : int or None (default) + row : 'all', int or None (default) Subplot row index (starting from 1) for the trace to be added. Only valid if figure was created using - `plotly.tools.make_subplots` - col : int or None (default) + `plotly.tools.make_subplots`.If 'all', addresses all + rows in the specified column(s). + col : 'all', int or None (default) Subplot col index (starting from 1) for the trace to be added. Only valid if figure was created using - `plotly.tools.make_subplots` + `plotly.tools.make_subplots`.If 'all', addresses all + columns in the specified row(s). Returns ------- @@ -2067,14 +2073,16 @@ def add_box( ysrc Sets the source reference on Chart Studio Cloud for y . - row : int or None (default) + row : 'all', int or None (default) Subplot row index (starting from 1) for the trace to be added. Only valid if figure was created using - `plotly.tools.make_subplots` - col : int or None (default) + `plotly.tools.make_subplots`.If 'all', addresses all + rows in the specified column(s). + col : 'all', int or None (default) Subplot col index (starting from 1) for the trace to be added. Only valid if figure was created using - `plotly.tools.make_subplots` + `plotly.tools.make_subplots`.If 'all', addresses all + columns in the specified row(s). secondary_y: boolean or None (default None) If True, associate this trace with the secondary y-axis of the subplot at the specified row and col. Only valid if all of the @@ -2407,14 +2415,16 @@ def add_candlestick( a 2D cartesian y axis. If "y" (the default value), the y coordinates refer to `layout.yaxis`. If "y2", the y coordinates refer to `layout.yaxis2`, and so on. - row : int or None (default) + row : 'all', int or None (default) Subplot row index (starting from 1) for the trace to be added. Only valid if figure was created using - `plotly.tools.make_subplots` - col : int or None (default) + `plotly.tools.make_subplots`.If 'all', addresses all + rows in the specified column(s). + col : 'all', int or None (default) Subplot col index (starting from 1) for the trace to be added. Only valid if figure was created using - `plotly.tools.make_subplots` + `plotly.tools.make_subplots`.If 'all', addresses all + columns in the specified row(s). secondary_y: boolean or None (default None) If True, associate this trace with the secondary y-axis of the subplot at the specified row and col. Only valid if all of the @@ -2664,14 +2674,16 @@ def add_carpet( ysrc Sets the source reference on Chart Studio Cloud for y . - row : int or None (default) + row : 'all', int or None (default) Subplot row index (starting from 1) for the trace to be added. Only valid if figure was created using - `plotly.tools.make_subplots` - col : int or None (default) + `plotly.tools.make_subplots`.If 'all', addresses all + rows in the specified column(s). + col : 'all', int or None (default) Subplot col index (starting from 1) for the trace to be added. Only valid if figure was created using - `plotly.tools.make_subplots` + `plotly.tools.make_subplots`.If 'all', addresses all + columns in the specified row(s). secondary_y: boolean or None (default None) If True, associate this trace with the secondary y-axis of the subplot at the specified row and col. Only valid if all of the @@ -3008,14 +3020,16 @@ def add_choropleth( zsrc Sets the source reference on Chart Studio Cloud for z . - row : int or None (default) + row : 'all', int or None (default) Subplot row index (starting from 1) for the trace to be added. Only valid if figure was created using - `plotly.tools.make_subplots` - col : int or None (default) + `plotly.tools.make_subplots`.If 'all', addresses all + rows in the specified column(s). + col : 'all', int or None (default) Subplot col index (starting from 1) for the trace to be added. Only valid if figure was created using - `plotly.tools.make_subplots` + `plotly.tools.make_subplots`.If 'all', addresses all + columns in the specified row(s). Returns ------- @@ -3351,14 +3365,16 @@ def add_choroplethmapbox( zsrc Sets the source reference on Chart Studio Cloud for z . - row : int or None (default) + row : 'all', int or None (default) Subplot row index (starting from 1) for the trace to be added. Only valid if figure was created using - `plotly.tools.make_subplots` - col : int or None (default) + `plotly.tools.make_subplots`.If 'all', addresses all + rows in the specified column(s). + col : 'all', int or None (default) Subplot col index (starting from 1) for the trace to be added. Only valid if figure was created using - `plotly.tools.make_subplots` + `plotly.tools.make_subplots`.If 'all', addresses all + columns in the specified row(s). Returns ------- @@ -3731,14 +3747,16 @@ def add_cone( zsrc Sets the source reference on Chart Studio Cloud for z . - row : int or None (default) + row : 'all', int or None (default) Subplot row index (starting from 1) for the trace to be added. Only valid if figure was created using - `plotly.tools.make_subplots` - col : int or None (default) + `plotly.tools.make_subplots`.If 'all', addresses all + rows in the specified column(s). + col : 'all', int or None (default) Subplot col index (starting from 1) for the trace to be added. Only valid if figure was created using - `plotly.tools.make_subplots` + `plotly.tools.make_subplots`.If 'all', addresses all + columns in the specified row(s). Returns ------- @@ -4182,14 +4200,16 @@ def add_contour( zsrc Sets the source reference on Chart Studio Cloud for z . - row : int or None (default) + row : 'all', int or None (default) Subplot row index (starting from 1) for the trace to be added. Only valid if figure was created using - `plotly.tools.make_subplots` - col : int or None (default) + `plotly.tools.make_subplots`.If 'all', addresses all + rows in the specified column(s). + col : 'all', int or None (default) Subplot col index (starting from 1) for the trace to be added. Only valid if figure was created using - `plotly.tools.make_subplots` + `plotly.tools.make_subplots`.If 'all', addresses all + columns in the specified row(s). secondary_y: boolean or None (default None) If True, associate this trace with the secondary y-axis of the subplot at the specified row and col. Only valid if all of the @@ -4554,14 +4574,16 @@ def add_contourcarpet( zsrc Sets the source reference on Chart Studio Cloud for z . - row : int or None (default) + row : 'all', int or None (default) Subplot row index (starting from 1) for the trace to be added. Only valid if figure was created using - `plotly.tools.make_subplots` - col : int or None (default) + `plotly.tools.make_subplots`.If 'all', addresses all + rows in the specified column(s). + col : 'all', int or None (default) Subplot col index (starting from 1) for the trace to be added. Only valid if figure was created using - `plotly.tools.make_subplots` + `plotly.tools.make_subplots`.If 'all', addresses all + columns in the specified row(s). secondary_y: boolean or None (default None) If True, associate this trace with the secondary y-axis of the subplot at the specified row and col. Only valid if all of the @@ -4910,14 +4932,16 @@ def add_densitymapbox( zsrc Sets the source reference on Chart Studio Cloud for z . - row : int or None (default) + row : 'all', int or None (default) Subplot row index (starting from 1) for the trace to be added. Only valid if figure was created using - `plotly.tools.make_subplots` - col : int or None (default) + `plotly.tools.make_subplots`.If 'all', addresses all + rows in the specified column(s). + col : 'all', int or None (default) Subplot col index (starting from 1) for the trace to be added. Only valid if figure was created using - `plotly.tools.make_subplots` + `plotly.tools.make_subplots`.If 'all', addresses all + columns in the specified row(s). Returns ------- @@ -5343,14 +5367,16 @@ def add_funnel( ysrc Sets the source reference on Chart Studio Cloud for y . - row : int or None (default) + row : 'all', int or None (default) Subplot row index (starting from 1) for the trace to be added. Only valid if figure was created using - `plotly.tools.make_subplots` - col : int or None (default) + `plotly.tools.make_subplots`.If 'all', addresses all + rows in the specified column(s). + col : 'all', int or None (default) Subplot col index (starting from 1) for the trace to be added. Only valid if figure was created using - `plotly.tools.make_subplots` + `plotly.tools.make_subplots`.If 'all', addresses all + columns in the specified row(s). secondary_y: boolean or None (default None) If True, associate this trace with the secondary y-axis of the subplot at the specified row and col. Only valid if all of the @@ -5691,14 +5717,16 @@ def add_funnelarea( "legendonly", the trace is not drawn, but can appear as a legend item (provided that the legend itself is visible). - row : int or None (default) + row : 'all', int or None (default) Subplot row index (starting from 1) for the trace to be added. Only valid if figure was created using - `plotly.tools.make_subplots` - col : int or None (default) + `plotly.tools.make_subplots`.If 'all', addresses all + rows in the specified column(s). + col : 'all', int or None (default) Subplot col index (starting from 1) for the trace to be added. Only valid if figure was created using - `plotly.tools.make_subplots` + `plotly.tools.make_subplots`.If 'all', addresses all + columns in the specified row(s). Returns ------- @@ -6125,14 +6153,16 @@ def add_heatmap( zsrc Sets the source reference on Chart Studio Cloud for z . - row : int or None (default) + row : 'all', int or None (default) Subplot row index (starting from 1) for the trace to be added. Only valid if figure was created using - `plotly.tools.make_subplots` - col : int or None (default) + `plotly.tools.make_subplots`.If 'all', addresses all + rows in the specified column(s). + col : 'all', int or None (default) Subplot col index (starting from 1) for the trace to be added. Only valid if figure was created using - `plotly.tools.make_subplots` + `plotly.tools.make_subplots`.If 'all', addresses all + columns in the specified row(s). secondary_y: boolean or None (default None) If True, associate this trace with the secondary y-axis of the subplot at the specified row and col. Only valid if all of the @@ -6463,14 +6493,16 @@ def add_heatmapgl( zsrc Sets the source reference on Chart Studio Cloud for z . - row : int or None (default) + row : 'all', int or None (default) Subplot row index (starting from 1) for the trace to be added. Only valid if figure was created using - `plotly.tools.make_subplots` - col : int or None (default) + `plotly.tools.make_subplots`.If 'all', addresses all + rows in the specified column(s). + col : 'all', int or None (default) Subplot col index (starting from 1) for the trace to be added. Only valid if figure was created using - `plotly.tools.make_subplots` + `plotly.tools.make_subplots`.If 'all', addresses all + columns in the specified row(s). secondary_y: boolean or None (default None) If True, associate this trace with the secondary y-axis of the subplot at the specified row and col. Only valid if all of the @@ -6853,14 +6885,16 @@ def add_histogram( ysrc Sets the source reference on Chart Studio Cloud for y . - row : int or None (default) + row : 'all', int or None (default) Subplot row index (starting from 1) for the trace to be added. Only valid if figure was created using - `plotly.tools.make_subplots` - col : int or None (default) + `plotly.tools.make_subplots`.If 'all', addresses all + rows in the specified column(s). + col : 'all', int or None (default) Subplot col index (starting from 1) for the trace to be added. Only valid if figure was created using - `plotly.tools.make_subplots` + `plotly.tools.make_subplots`.If 'all', addresses all + columns in the specified row(s). secondary_y: boolean or None (default None) If True, associate this trace with the secondary y-axis of the subplot at the specified row and col. Only valid if all of the @@ -7288,14 +7322,16 @@ def add_histogram2d( zsrc Sets the source reference on Chart Studio Cloud for z . - row : int or None (default) + row : 'all', int or None (default) Subplot row index (starting from 1) for the trace to be added. Only valid if figure was created using - `plotly.tools.make_subplots` - col : int or None (default) + `plotly.tools.make_subplots`.If 'all', addresses all + rows in the specified column(s). + col : 'all', int or None (default) Subplot col index (starting from 1) for the trace to be added. Only valid if figure was created using - `plotly.tools.make_subplots` + `plotly.tools.make_subplots`.If 'all', addresses all + columns in the specified row(s). secondary_y: boolean or None (default None) If True, associate this trace with the secondary y-axis of the subplot at the specified row and col. Only valid if all of the @@ -7740,14 +7776,16 @@ def add_histogram2dcontour( zsrc Sets the source reference on Chart Studio Cloud for z . - row : int or None (default) + row : 'all', int or None (default) Subplot row index (starting from 1) for the trace to be added. Only valid if figure was created using - `plotly.tools.make_subplots` - col : int or None (default) + `plotly.tools.make_subplots`.If 'all', addresses all + rows in the specified column(s). + col : 'all', int or None (default) Subplot col index (starting from 1) for the trace to be added. Only valid if figure was created using - `plotly.tools.make_subplots` + `plotly.tools.make_subplots`.If 'all', addresses all + columns in the specified row(s). secondary_y: boolean or None (default None) If True, associate this trace with the secondary y-axis of the subplot at the specified row and col. Only valid if all of the @@ -8041,14 +8079,16 @@ def add_image( zsrc Sets the source reference on Chart Studio Cloud for z . - row : int or None (default) + row : 'all', int or None (default) Subplot row index (starting from 1) for the trace to be added. Only valid if figure was created using - `plotly.tools.make_subplots` - col : int or None (default) + `plotly.tools.make_subplots`.If 'all', addresses all + rows in the specified column(s). + col : 'all', int or None (default) Subplot col index (starting from 1) for the trace to be added. Only valid if figure was created using - `plotly.tools.make_subplots` + `plotly.tools.make_subplots`.If 'all', addresses all + columns in the specified row(s). secondary_y: boolean or None (default None) If True, associate this trace with the secondary y-axis of the subplot at the specified row and col. Only valid if all of the @@ -8232,14 +8272,16 @@ def add_indicator( "legendonly", the trace is not drawn, but can appear as a legend item (provided that the legend itself is visible). - row : int or None (default) + row : 'all', int or None (default) Subplot row index (starting from 1) for the trace to be added. Only valid if figure was created using - `plotly.tools.make_subplots` - col : int or None (default) + `plotly.tools.make_subplots`.If 'all', addresses all + rows in the specified column(s). + col : 'all', int or None (default) Subplot col index (starting from 1) for the trace to be added. Only valid if figure was created using - `plotly.tools.make_subplots` + `plotly.tools.make_subplots`.If 'all', addresses all + columns in the specified row(s). Returns ------- @@ -8578,14 +8620,16 @@ def add_isosurface( zsrc Sets the source reference on Chart Studio Cloud for z . - row : int or None (default) + row : 'all', int or None (default) Subplot row index (starting from 1) for the trace to be added. Only valid if figure was created using - `plotly.tools.make_subplots` - col : int or None (default) + `plotly.tools.make_subplots`.If 'all', addresses all + rows in the specified column(s). + col : 'all', int or None (default) Subplot col index (starting from 1) for the trace to be added. Only valid if figure was created using - `plotly.tools.make_subplots` + `plotly.tools.make_subplots`.If 'all', addresses all + columns in the specified row(s). Returns ------- @@ -9044,14 +9088,16 @@ def add_mesh3d( zsrc Sets the source reference on Chart Studio Cloud for z . - row : int or None (default) + row : 'all', int or None (default) Subplot row index (starting from 1) for the trace to be added. Only valid if figure was created using - `plotly.tools.make_subplots` - col : int or None (default) + `plotly.tools.make_subplots`.If 'all', addresses all + rows in the specified column(s). + col : 'all', int or None (default) Subplot col index (starting from 1) for the trace to be added. Only valid if figure was created using - `plotly.tools.make_subplots` + `plotly.tools.make_subplots`.If 'all', addresses all + columns in the specified row(s). Returns ------- @@ -9360,14 +9406,16 @@ def add_ohlc( a 2D cartesian y axis. If "y" (the default value), the y coordinates refer to `layout.yaxis`. If "y2", the y coordinates refer to `layout.yaxis2`, and so on. - row : int or None (default) + row : 'all', int or None (default) Subplot row index (starting from 1) for the trace to be added. Only valid if figure was created using - `plotly.tools.make_subplots` - col : int or None (default) + `plotly.tools.make_subplots`.If 'all', addresses all + rows in the specified column(s). + col : 'all', int or None (default) Subplot col index (starting from 1) for the trace to be added. Only valid if figure was created using - `plotly.tools.make_subplots` + `plotly.tools.make_subplots`.If 'all', addresses all + columns in the specified row(s). secondary_y: boolean or None (default None) If True, associate this trace with the secondary y-axis of the subplot at the specified row and col. Only valid if all of the @@ -9592,14 +9640,16 @@ def add_parcats( "legendonly", the trace is not drawn, but can appear as a legend item (provided that the legend itself is visible). - row : int or None (default) + row : 'all', int or None (default) Subplot row index (starting from 1) for the trace to be added. Only valid if figure was created using - `plotly.tools.make_subplots` - col : int or None (default) + `plotly.tools.make_subplots`.If 'all', addresses all + rows in the specified column(s). + col : 'all', int or None (default) Subplot col index (starting from 1) for the trace to be added. Only valid if figure was created using - `plotly.tools.make_subplots` + `plotly.tools.make_subplots`.If 'all', addresses all + columns in the specified row(s). Returns ------- @@ -9764,14 +9814,16 @@ def add_parcoords( "legendonly", the trace is not drawn, but can appear as a legend item (provided that the legend itself is visible). - row : int or None (default) + row : 'all', int or None (default) Subplot row index (starting from 1) for the trace to be added. Only valid if figure was created using - `plotly.tools.make_subplots` - col : int or None (default) + `plotly.tools.make_subplots`.If 'all', addresses all + rows in the specified column(s). + col : 'all', int or None (default) Subplot col index (starting from 1) for the trace to be added. Only valid if figure was created using - `plotly.tools.make_subplots` + `plotly.tools.make_subplots`.If 'all', addresses all + columns in the specified row(s). Returns ------- @@ -10110,14 +10162,16 @@ def add_pie( "legendonly", the trace is not drawn, but can appear as a legend item (provided that the legend itself is visible). - row : int or None (default) + row : 'all', int or None (default) Subplot row index (starting from 1) for the trace to be added. Only valid if figure was created using - `plotly.tools.make_subplots` - col : int or None (default) + `plotly.tools.make_subplots`.If 'all', addresses all + rows in the specified column(s). + col : 'all', int or None (default) Subplot col index (starting from 1) for the trace to be added. Only valid if figure was created using - `plotly.tools.make_subplots` + `plotly.tools.make_subplots`.If 'all', addresses all + columns in the specified row(s). Returns ------- @@ -10384,14 +10438,16 @@ def add_pointcloud( ysrc Sets the source reference on Chart Studio Cloud for y . - row : int or None (default) + row : 'all', int or None (default) Subplot row index (starting from 1) for the trace to be added. Only valid if figure was created using - `plotly.tools.make_subplots` - col : int or None (default) + `plotly.tools.make_subplots`.If 'all', addresses all + rows in the specified column(s). + col : 'all', int or None (default) Subplot col index (starting from 1) for the trace to be added. Only valid if figure was created using - `plotly.tools.make_subplots` + `plotly.tools.make_subplots`.If 'all', addresses all + columns in the specified row(s). secondary_y: boolean or None (default None) If True, associate this trace with the secondary y-axis of the subplot at the specified row and col. Only valid if all of the @@ -10595,14 +10651,16 @@ def add_sankey( "legendonly", the trace is not drawn, but can appear as a legend item (provided that the legend itself is visible). - row : int or None (default) + row : 'all', int or None (default) Subplot row index (starting from 1) for the trace to be added. Only valid if figure was created using - `plotly.tools.make_subplots` - col : int or None (default) + `plotly.tools.make_subplots`.If 'all', addresses all + rows in the specified column(s). + col : 'all', int or None (default) Subplot col index (starting from 1) for the trace to be added. Only valid if figure was created using - `plotly.tools.make_subplots` + `plotly.tools.make_subplots`.If 'all', addresses all + columns in the specified row(s). Returns ------- @@ -11077,14 +11135,16 @@ def add_scatter( ysrc Sets the source reference on Chart Studio Cloud for y . - row : int or None (default) + row : 'all', int or None (default) Subplot row index (starting from 1) for the trace to be added. Only valid if figure was created using - `plotly.tools.make_subplots` - col : int or None (default) + `plotly.tools.make_subplots`.If 'all', addresses all + rows in the specified column(s). + col : 'all', int or None (default) Subplot col index (starting from 1) for the trace to be added. Only valid if figure was created using - `plotly.tools.make_subplots` + `plotly.tools.make_subplots`.If 'all', addresses all + columns in the specified row(s). secondary_y: boolean or None (default None) If True, associate this trace with the secondary y-axis of the subplot at the specified row and col. Only valid if all of the @@ -11458,14 +11518,16 @@ def add_scatter3d( zsrc Sets the source reference on Chart Studio Cloud for z . - row : int or None (default) + row : 'all', int or None (default) Subplot row index (starting from 1) for the trace to be added. Only valid if figure was created using - `plotly.tools.make_subplots` - col : int or None (default) + `plotly.tools.make_subplots`.If 'all', addresses all + rows in the specified column(s). + col : 'all', int or None (default) Subplot col index (starting from 1) for the trace to be added. Only valid if figure was created using - `plotly.tools.make_subplots` + `plotly.tools.make_subplots`.If 'all', addresses all + columns in the specified row(s). Returns ------- @@ -11816,14 +11878,16 @@ def add_scattercarpet( a 2D cartesian y axis. If "y" (the default value), the y coordinates refer to `layout.yaxis`. If "y2", the y coordinates refer to `layout.yaxis2`, and so on. - row : int or None (default) + row : 'all', int or None (default) Subplot row index (starting from 1) for the trace to be added. Only valid if figure was created using - `plotly.tools.make_subplots` - col : int or None (default) + `plotly.tools.make_subplots`.If 'all', addresses all + rows in the specified column(s). + col : 'all', int or None (default) Subplot col index (starting from 1) for the trace to be added. Only valid if figure was created using - `plotly.tools.make_subplots` + `plotly.tools.make_subplots`.If 'all', addresses all + columns in the specified row(s). secondary_y: boolean or None (default None) If True, associate this trace with the secondary y-axis of the subplot at the specified row and col. Only valid if all of the @@ -12193,14 +12257,16 @@ def add_scattergeo( "legendonly", the trace is not drawn, but can appear as a legend item (provided that the legend itself is visible). - row : int or None (default) + row : 'all', int or None (default) Subplot row index (starting from 1) for the trace to be added. Only valid if figure was created using - `plotly.tools.make_subplots` - col : int or None (default) + `plotly.tools.make_subplots`.If 'all', addresses all + rows in the specified column(s). + col : 'all', int or None (default) Subplot col index (starting from 1) for the trace to be added. Only valid if figure was created using - `plotly.tools.make_subplots` + `plotly.tools.make_subplots`.If 'all', addresses all + columns in the specified row(s). Returns ------- @@ -12615,14 +12681,16 @@ def add_scattergl( ysrc Sets the source reference on Chart Studio Cloud for y . - row : int or None (default) + row : 'all', int or None (default) Subplot row index (starting from 1) for the trace to be added. Only valid if figure was created using - `plotly.tools.make_subplots` - col : int or None (default) + `plotly.tools.make_subplots`.If 'all', addresses all + rows in the specified column(s). + col : 'all', int or None (default) Subplot col index (starting from 1) for the trace to be added. Only valid if figure was created using - `plotly.tools.make_subplots` + `plotly.tools.make_subplots`.If 'all', addresses all + columns in the specified row(s). secondary_y: boolean or None (default None) If True, associate this trace with the secondary y-axis of the subplot at the specified row and col. Only valid if all of the @@ -12972,14 +13040,16 @@ def add_scattermapbox( "legendonly", the trace is not drawn, but can appear as a legend item (provided that the legend itself is visible). - row : int or None (default) + row : 'all', int or None (default) Subplot row index (starting from 1) for the trace to be added. Only valid if figure was created using - `plotly.tools.make_subplots` - col : int or None (default) + `plotly.tools.make_subplots`.If 'all', addresses all + rows in the specified column(s). + col : 'all', int or None (default) Subplot col index (starting from 1) for the trace to be added. Only valid if figure was created using - `plotly.tools.make_subplots` + `plotly.tools.make_subplots`.If 'all', addresses all + columns in the specified row(s). Returns ------- @@ -13347,14 +13417,16 @@ def add_scatterpolar( "legendonly", the trace is not drawn, but can appear as a legend item (provided that the legend itself is visible). - row : int or None (default) + row : 'all', int or None (default) Subplot row index (starting from 1) for the trace to be added. Only valid if figure was created using - `plotly.tools.make_subplots` - col : int or None (default) + `plotly.tools.make_subplots`.If 'all', addresses all + rows in the specified column(s). + col : 'all', int or None (default) Subplot col index (starting from 1) for the trace to be added. Only valid if figure was created using - `plotly.tools.make_subplots` + `plotly.tools.make_subplots`.If 'all', addresses all + columns in the specified row(s). Returns ------- @@ -13726,14 +13798,16 @@ def add_scatterpolargl( "legendonly", the trace is not drawn, but can appear as a legend item (provided that the legend itself is visible). - row : int or None (default) + row : 'all', int or None (default) Subplot row index (starting from 1) for the trace to be added. Only valid if figure was created using - `plotly.tools.make_subplots` - col : int or None (default) + `plotly.tools.make_subplots`.If 'all', addresses all + rows in the specified column(s). + col : 'all', int or None (default) Subplot col index (starting from 1) for the trace to be added. Only valid if figure was created using - `plotly.tools.make_subplots` + `plotly.tools.make_subplots`.If 'all', addresses all + columns in the specified row(s). Returns ------- @@ -14107,14 +14181,16 @@ def add_scatterternary( "legendonly", the trace is not drawn, but can appear as a legend item (provided that the legend itself is visible). - row : int or None (default) + row : 'all', int or None (default) Subplot row index (starting from 1) for the trace to be added. Only valid if figure was created using - `plotly.tools.make_subplots` - col : int or None (default) + `plotly.tools.make_subplots`.If 'all', addresses all + rows in the specified column(s). + col : 'all', int or None (default) Subplot col index (starting from 1) for the trace to be added. Only valid if figure was created using - `plotly.tools.make_subplots` + `plotly.tools.make_subplots`.If 'all', addresses all + columns in the specified row(s). Returns ------- @@ -14400,14 +14476,16 @@ def add_splom( is false and `showupperhalf` or `showlowerhalf` is false, this splom trace will generate one less x-axis and one less y-axis. - row : int or None (default) + row : 'all', int or None (default) Subplot row index (starting from 1) for the trace to be added. Only valid if figure was created using - `plotly.tools.make_subplots` - col : int or None (default) + `plotly.tools.make_subplots`.If 'all', addresses all + rows in the specified column(s). + col : 'all', int or None (default) Subplot col index (starting from 1) for the trace to be added. Only valid if figure was created using - `plotly.tools.make_subplots` + `plotly.tools.make_subplots`.If 'all', addresses all + columns in the specified row(s). Returns ------- @@ -14754,14 +14832,16 @@ def add_streamtube( zsrc Sets the source reference on Chart Studio Cloud for z . - row : int or None (default) + row : 'all', int or None (default) Subplot row index (starting from 1) for the trace to be added. Only valid if figure was created using - `plotly.tools.make_subplots` - col : int or None (default) + `plotly.tools.make_subplots`.If 'all', addresses all + rows in the specified column(s). + col : 'all', int or None (default) Subplot col index (starting from 1) for the trace to be added. Only valid if figure was created using - `plotly.tools.make_subplots` + `plotly.tools.make_subplots`.If 'all', addresses all + columns in the specified row(s). Returns ------- @@ -15105,14 +15185,16 @@ def add_sunburst( "legendonly", the trace is not drawn, but can appear as a legend item (provided that the legend itself is visible). - row : int or None (default) + row : 'all', int or None (default) Subplot row index (starting from 1) for the trace to be added. Only valid if figure was created using - `plotly.tools.make_subplots` - col : int or None (default) + `plotly.tools.make_subplots`.If 'all', addresses all + rows in the specified column(s). + col : 'all', int or None (default) Subplot col index (starting from 1) for the trace to be added. Only valid if figure was created using - `plotly.tools.make_subplots` + `plotly.tools.make_subplots`.If 'all', addresses all + columns in the specified row(s). Returns ------- @@ -15483,14 +15565,16 @@ def add_surface( zsrc Sets the source reference on Chart Studio Cloud for z . - row : int or None (default) + row : 'all', int or None (default) Subplot row index (starting from 1) for the trace to be added. Only valid if figure was created using - `plotly.tools.make_subplots` - col : int or None (default) + `plotly.tools.make_subplots`.If 'all', addresses all + rows in the specified column(s). + col : 'all', int or None (default) Subplot col index (starting from 1) for the trace to be added. Only valid if figure was created using - `plotly.tools.make_subplots` + `plotly.tools.make_subplots`.If 'all', addresses all + columns in the specified row(s). Returns ------- @@ -15691,14 +15775,16 @@ def add_table( "legendonly", the trace is not drawn, but can appear as a legend item (provided that the legend itself is visible). - row : int or None (default) + row : 'all', int or None (default) Subplot row index (starting from 1) for the trace to be added. Only valid if figure was created using - `plotly.tools.make_subplots` - col : int or None (default) + `plotly.tools.make_subplots`.If 'all', addresses all + rows in the specified column(s). + col : 'all', int or None (default) Subplot col index (starting from 1) for the trace to be added. Only valid if figure was created using - `plotly.tools.make_subplots` + `plotly.tools.make_subplots`.If 'all', addresses all + columns in the specified row(s). Returns ------- @@ -16007,14 +16093,16 @@ def add_treemap( "legendonly", the trace is not drawn, but can appear as a legend item (provided that the legend itself is visible). - row : int or None (default) + row : 'all', int or None (default) Subplot row index (starting from 1) for the trace to be added. Only valid if figure was created using - `plotly.tools.make_subplots` - col : int or None (default) + `plotly.tools.make_subplots`.If 'all', addresses all + rows in the specified column(s). + col : 'all', int or None (default) Subplot col index (starting from 1) for the trace to be added. Only valid if figure was created using - `plotly.tools.make_subplots` + `plotly.tools.make_subplots`.If 'all', addresses all + columns in the specified row(s). Returns ------- @@ -16410,14 +16498,16 @@ def add_violin( ysrc Sets the source reference on Chart Studio Cloud for y . - row : int or None (default) + row : 'all', int or None (default) Subplot row index (starting from 1) for the trace to be added. Only valid if figure was created using - `plotly.tools.make_subplots` - col : int or None (default) + `plotly.tools.make_subplots`.If 'all', addresses all + rows in the specified column(s). + col : 'all', int or None (default) Subplot col index (starting from 1) for the trace to be added. Only valid if figure was created using - `plotly.tools.make_subplots` + `plotly.tools.make_subplots`.If 'all', addresses all + columns in the specified row(s). secondary_y: boolean or None (default None) If True, associate this trace with the secondary y-axis of the subplot at the specified row and col. Only valid if all of the @@ -16812,14 +16902,16 @@ def add_volume( zsrc Sets the source reference on Chart Studio Cloud for z . - row : int or None (default) + row : 'all', int or None (default) Subplot row index (starting from 1) for the trace to be added. Only valid if figure was created using - `plotly.tools.make_subplots` - col : int or None (default) + `plotly.tools.make_subplots`.If 'all', addresses all + rows in the specified column(s). + col : 'all', int or None (default) Subplot col index (starting from 1) for the trace to be added. Only valid if figure was created using - `plotly.tools.make_subplots` + `plotly.tools.make_subplots`.If 'all', addresses all + columns in the specified row(s). Returns ------- @@ -17280,14 +17372,16 @@ def add_waterfall( ysrc Sets the source reference on Chart Studio Cloud for y . - row : int or None (default) + row : 'all', int or None (default) Subplot row index (starting from 1) for the trace to be added. Only valid if figure was created using - `plotly.tools.make_subplots` - col : int or None (default) + `plotly.tools.make_subplots`.If 'all', addresses all + rows in the specified column(s). + col : 'all', int or None (default) Subplot col index (starting from 1) for the trace to be added. Only valid if figure was created using - `plotly.tools.make_subplots` + `plotly.tools.make_subplots`.If 'all', addresses all + columns in the specified row(s). secondary_y: boolean or None (default None) If True, associate this trace with the secondary y-axis of the subplot at the specified row and col. Only valid if all of the @@ -18423,6 +18517,7 @@ def add_annotation( row=None, col=None, secondary_y=None, + exclude_empty_subplots=None, **kwargs ): """ @@ -18712,11 +18807,16 @@ def add_annotation( Shifts the position of the whole annotation and arrow up (positive) or down (negative) by this many pixels. row - Subplot row for annotation + Subplot row for annotation. If 'all', addresses all + rows in the specified column(s). col - Subplot column for annotation + Subplot column for annotation. If 'all', addresses all + columns in the specified row(s). secondary_y Whether to add annotation to secondary y-axis + exclude_empty_subplots + If True, annotation will not be added to subplots + without traces. Returns ------- @@ -18778,6 +18878,7 @@ def add_annotation( row=row, col=col, secondary_y=secondary_y, + exclude_empty_subplots=exclude_empty_subplots, ) def select_layout_images(self, selector=None, row=None, col=None, secondary_y=None): @@ -18942,6 +19043,7 @@ def add_layout_image( row=None, col=None, secondary_y=None, + exclude_empty_subplots=None, **kwargs ): """ @@ -19037,11 +19139,16 @@ def add_layout_image( refers to the point between the bottom and the top of the domain of the second y axis. row - Subplot row for image + Subplot row for image. If 'all', addresses all rows in + the specified column(s). col - Subplot column for image + Subplot column for image. If 'all', addresses all + columns in the specified row(s). secondary_y Whether to add image to secondary y-axis + exclude_empty_subplots + If True, image will not be added to subplots without + traces. Returns ------- @@ -19069,7 +19176,13 @@ def add_layout_image( **kwargs ) return self._add_annotation_like( - "image", "images", new_obj, row=row, col=col, secondary_y=secondary_y, + "image", + "images", + new_obj, + row=row, + col=col, + secondary_y=secondary_y, + exclude_empty_subplots=exclude_empty_subplots, ) def select_shapes(self, selector=None, row=None, col=None, secondary_y=None): @@ -19238,6 +19351,7 @@ def add_shape( row=None, col=None, secondary_y=None, + exclude_empty_subplots=None, **kwargs ): """ @@ -19403,11 +19517,16 @@ def add_shape( maintaining a position relative to data or plot fraction. row - Subplot row for shape + Subplot row for shape. If 'all', addresses all rows in + the specified column(s). col - Subplot column for shape + Subplot column for shape. If 'all', addresses all + columns in the specified row(s). secondary_y Whether to add shape to secondary y-axis + exclude_empty_subplots + If True, shape will not be added to subplots without + traces. Returns ------- @@ -19441,5 +19560,11 @@ def add_shape( **kwargs ) return self._add_annotation_like( - "shape", "shapes", new_obj, row=row, col=col, secondary_y=secondary_y, + "shape", + "shapes", + new_obj, + row=row, + col=col, + secondary_y=secondary_y, + exclude_empty_subplots=exclude_empty_subplots, ) diff --git a/packages/python/plotly/plotly/graph_objs/_figurewidget.py b/packages/python/plotly/plotly/graph_objs/_figurewidget.py index 6df993e928..25d453f37c 100644 --- a/packages/python/plotly/plotly/graph_objs/_figurewidget.py +++ b/packages/python/plotly/plotly/graph_objs/_figurewidget.py @@ -721,14 +721,16 @@ def add_area( "legendonly", the trace is not drawn, but can appear as a legend item (provided that the legend itself is visible). - row : int or None (default) + row : 'all', int or None (default) Subplot row index (starting from 1) for the trace to be added. Only valid if figure was created using - `plotly.tools.make_subplots` - col : int or None (default) + `plotly.tools.make_subplots`.If 'all', addresses all + rows in the specified column(s). + col : 'all', int or None (default) Subplot col index (starting from 1) for the trace to be added. Only valid if figure was created using - `plotly.tools.make_subplots` + `plotly.tools.make_subplots`.If 'all', addresses all + columns in the specified row(s). Returns ------- @@ -1174,14 +1176,16 @@ def add_bar( ysrc Sets the source reference on Chart Studio Cloud for y . - row : int or None (default) + row : 'all', int or None (default) Subplot row index (starting from 1) for the trace to be added. Only valid if figure was created using - `plotly.tools.make_subplots` - col : int or None (default) + `plotly.tools.make_subplots`.If 'all', addresses all + rows in the specified column(s). + col : 'all', int or None (default) Subplot col index (starting from 1) for the trace to be added. Only valid if figure was created using - `plotly.tools.make_subplots` + `plotly.tools.make_subplots`.If 'all', addresses all + columns in the specified row(s). secondary_y: boolean or None (default None) If True, associate this trace with the secondary y-axis of the subplot at the specified row and col. Only valid if all of the @@ -1520,14 +1524,16 @@ def add_barpolar( widthsrc Sets the source reference on Chart Studio Cloud for width . - row : int or None (default) + row : 'all', int or None (default) Subplot row index (starting from 1) for the trace to be added. Only valid if figure was created using - `plotly.tools.make_subplots` - col : int or None (default) + `plotly.tools.make_subplots`.If 'all', addresses all + rows in the specified column(s). + col : 'all', int or None (default) Subplot col index (starting from 1) for the trace to be added. Only valid if figure was created using - `plotly.tools.make_subplots` + `plotly.tools.make_subplots`.If 'all', addresses all + columns in the specified row(s). Returns ------- @@ -2067,14 +2073,16 @@ def add_box( ysrc Sets the source reference on Chart Studio Cloud for y . - row : int or None (default) + row : 'all', int or None (default) Subplot row index (starting from 1) for the trace to be added. Only valid if figure was created using - `plotly.tools.make_subplots` - col : int or None (default) + `plotly.tools.make_subplots`.If 'all', addresses all + rows in the specified column(s). + col : 'all', int or None (default) Subplot col index (starting from 1) for the trace to be added. Only valid if figure was created using - `plotly.tools.make_subplots` + `plotly.tools.make_subplots`.If 'all', addresses all + columns in the specified row(s). secondary_y: boolean or None (default None) If True, associate this trace with the secondary y-axis of the subplot at the specified row and col. Only valid if all of the @@ -2407,14 +2415,16 @@ def add_candlestick( a 2D cartesian y axis. If "y" (the default value), the y coordinates refer to `layout.yaxis`. If "y2", the y coordinates refer to `layout.yaxis2`, and so on. - row : int or None (default) + row : 'all', int or None (default) Subplot row index (starting from 1) for the trace to be added. Only valid if figure was created using - `plotly.tools.make_subplots` - col : int or None (default) + `plotly.tools.make_subplots`.If 'all', addresses all + rows in the specified column(s). + col : 'all', int or None (default) Subplot col index (starting from 1) for the trace to be added. Only valid if figure was created using - `plotly.tools.make_subplots` + `plotly.tools.make_subplots`.If 'all', addresses all + columns in the specified row(s). secondary_y: boolean or None (default None) If True, associate this trace with the secondary y-axis of the subplot at the specified row and col. Only valid if all of the @@ -2664,14 +2674,16 @@ def add_carpet( ysrc Sets the source reference on Chart Studio Cloud for y . - row : int or None (default) + row : 'all', int or None (default) Subplot row index (starting from 1) for the trace to be added. Only valid if figure was created using - `plotly.tools.make_subplots` - col : int or None (default) + `plotly.tools.make_subplots`.If 'all', addresses all + rows in the specified column(s). + col : 'all', int or None (default) Subplot col index (starting from 1) for the trace to be added. Only valid if figure was created using - `plotly.tools.make_subplots` + `plotly.tools.make_subplots`.If 'all', addresses all + columns in the specified row(s). secondary_y: boolean or None (default None) If True, associate this trace with the secondary y-axis of the subplot at the specified row and col. Only valid if all of the @@ -3008,14 +3020,16 @@ def add_choropleth( zsrc Sets the source reference on Chart Studio Cloud for z . - row : int or None (default) + row : 'all', int or None (default) Subplot row index (starting from 1) for the trace to be added. Only valid if figure was created using - `plotly.tools.make_subplots` - col : int or None (default) + `plotly.tools.make_subplots`.If 'all', addresses all + rows in the specified column(s). + col : 'all', int or None (default) Subplot col index (starting from 1) for the trace to be added. Only valid if figure was created using - `plotly.tools.make_subplots` + `plotly.tools.make_subplots`.If 'all', addresses all + columns in the specified row(s). Returns ------- @@ -3351,14 +3365,16 @@ def add_choroplethmapbox( zsrc Sets the source reference on Chart Studio Cloud for z . - row : int or None (default) + row : 'all', int or None (default) Subplot row index (starting from 1) for the trace to be added. Only valid if figure was created using - `plotly.tools.make_subplots` - col : int or None (default) + `plotly.tools.make_subplots`.If 'all', addresses all + rows in the specified column(s). + col : 'all', int or None (default) Subplot col index (starting from 1) for the trace to be added. Only valid if figure was created using - `plotly.tools.make_subplots` + `plotly.tools.make_subplots`.If 'all', addresses all + columns in the specified row(s). Returns ------- @@ -3731,14 +3747,16 @@ def add_cone( zsrc Sets the source reference on Chart Studio Cloud for z . - row : int or None (default) + row : 'all', int or None (default) Subplot row index (starting from 1) for the trace to be added. Only valid if figure was created using - `plotly.tools.make_subplots` - col : int or None (default) + `plotly.tools.make_subplots`.If 'all', addresses all + rows in the specified column(s). + col : 'all', int or None (default) Subplot col index (starting from 1) for the trace to be added. Only valid if figure was created using - `plotly.tools.make_subplots` + `plotly.tools.make_subplots`.If 'all', addresses all + columns in the specified row(s). Returns ------- @@ -4182,14 +4200,16 @@ def add_contour( zsrc Sets the source reference on Chart Studio Cloud for z . - row : int or None (default) + row : 'all', int or None (default) Subplot row index (starting from 1) for the trace to be added. Only valid if figure was created using - `plotly.tools.make_subplots` - col : int or None (default) + `plotly.tools.make_subplots`.If 'all', addresses all + rows in the specified column(s). + col : 'all', int or None (default) Subplot col index (starting from 1) for the trace to be added. Only valid if figure was created using - `plotly.tools.make_subplots` + `plotly.tools.make_subplots`.If 'all', addresses all + columns in the specified row(s). secondary_y: boolean or None (default None) If True, associate this trace with the secondary y-axis of the subplot at the specified row and col. Only valid if all of the @@ -4554,14 +4574,16 @@ def add_contourcarpet( zsrc Sets the source reference on Chart Studio Cloud for z . - row : int or None (default) + row : 'all', int or None (default) Subplot row index (starting from 1) for the trace to be added. Only valid if figure was created using - `plotly.tools.make_subplots` - col : int or None (default) + `plotly.tools.make_subplots`.If 'all', addresses all + rows in the specified column(s). + col : 'all', int or None (default) Subplot col index (starting from 1) for the trace to be added. Only valid if figure was created using - `plotly.tools.make_subplots` + `plotly.tools.make_subplots`.If 'all', addresses all + columns in the specified row(s). secondary_y: boolean or None (default None) If True, associate this trace with the secondary y-axis of the subplot at the specified row and col. Only valid if all of the @@ -4910,14 +4932,16 @@ def add_densitymapbox( zsrc Sets the source reference on Chart Studio Cloud for z . - row : int or None (default) + row : 'all', int or None (default) Subplot row index (starting from 1) for the trace to be added. Only valid if figure was created using - `plotly.tools.make_subplots` - col : int or None (default) + `plotly.tools.make_subplots`.If 'all', addresses all + rows in the specified column(s). + col : 'all', int or None (default) Subplot col index (starting from 1) for the trace to be added. Only valid if figure was created using - `plotly.tools.make_subplots` + `plotly.tools.make_subplots`.If 'all', addresses all + columns in the specified row(s). Returns ------- @@ -5343,14 +5367,16 @@ def add_funnel( ysrc Sets the source reference on Chart Studio Cloud for y . - row : int or None (default) + row : 'all', int or None (default) Subplot row index (starting from 1) for the trace to be added. Only valid if figure was created using - `plotly.tools.make_subplots` - col : int or None (default) + `plotly.tools.make_subplots`.If 'all', addresses all + rows in the specified column(s). + col : 'all', int or None (default) Subplot col index (starting from 1) for the trace to be added. Only valid if figure was created using - `plotly.tools.make_subplots` + `plotly.tools.make_subplots`.If 'all', addresses all + columns in the specified row(s). secondary_y: boolean or None (default None) If True, associate this trace with the secondary y-axis of the subplot at the specified row and col. Only valid if all of the @@ -5691,14 +5717,16 @@ def add_funnelarea( "legendonly", the trace is not drawn, but can appear as a legend item (provided that the legend itself is visible). - row : int or None (default) + row : 'all', int or None (default) Subplot row index (starting from 1) for the trace to be added. Only valid if figure was created using - `plotly.tools.make_subplots` - col : int or None (default) + `plotly.tools.make_subplots`.If 'all', addresses all + rows in the specified column(s). + col : 'all', int or None (default) Subplot col index (starting from 1) for the trace to be added. Only valid if figure was created using - `plotly.tools.make_subplots` + `plotly.tools.make_subplots`.If 'all', addresses all + columns in the specified row(s). Returns ------- @@ -6125,14 +6153,16 @@ def add_heatmap( zsrc Sets the source reference on Chart Studio Cloud for z . - row : int or None (default) + row : 'all', int or None (default) Subplot row index (starting from 1) for the trace to be added. Only valid if figure was created using - `plotly.tools.make_subplots` - col : int or None (default) + `plotly.tools.make_subplots`.If 'all', addresses all + rows in the specified column(s). + col : 'all', int or None (default) Subplot col index (starting from 1) for the trace to be added. Only valid if figure was created using - `plotly.tools.make_subplots` + `plotly.tools.make_subplots`.If 'all', addresses all + columns in the specified row(s). secondary_y: boolean or None (default None) If True, associate this trace with the secondary y-axis of the subplot at the specified row and col. Only valid if all of the @@ -6463,14 +6493,16 @@ def add_heatmapgl( zsrc Sets the source reference on Chart Studio Cloud for z . - row : int or None (default) + row : 'all', int or None (default) Subplot row index (starting from 1) for the trace to be added. Only valid if figure was created using - `plotly.tools.make_subplots` - col : int or None (default) + `plotly.tools.make_subplots`.If 'all', addresses all + rows in the specified column(s). + col : 'all', int or None (default) Subplot col index (starting from 1) for the trace to be added. Only valid if figure was created using - `plotly.tools.make_subplots` + `plotly.tools.make_subplots`.If 'all', addresses all + columns in the specified row(s). secondary_y: boolean or None (default None) If True, associate this trace with the secondary y-axis of the subplot at the specified row and col. Only valid if all of the @@ -6853,14 +6885,16 @@ def add_histogram( ysrc Sets the source reference on Chart Studio Cloud for y . - row : int or None (default) + row : 'all', int or None (default) Subplot row index (starting from 1) for the trace to be added. Only valid if figure was created using - `plotly.tools.make_subplots` - col : int or None (default) + `plotly.tools.make_subplots`.If 'all', addresses all + rows in the specified column(s). + col : 'all', int or None (default) Subplot col index (starting from 1) for the trace to be added. Only valid if figure was created using - `plotly.tools.make_subplots` + `plotly.tools.make_subplots`.If 'all', addresses all + columns in the specified row(s). secondary_y: boolean or None (default None) If True, associate this trace with the secondary y-axis of the subplot at the specified row and col. Only valid if all of the @@ -7288,14 +7322,16 @@ def add_histogram2d( zsrc Sets the source reference on Chart Studio Cloud for z . - row : int or None (default) + row : 'all', int or None (default) Subplot row index (starting from 1) for the trace to be added. Only valid if figure was created using - `plotly.tools.make_subplots` - col : int or None (default) + `plotly.tools.make_subplots`.If 'all', addresses all + rows in the specified column(s). + col : 'all', int or None (default) Subplot col index (starting from 1) for the trace to be added. Only valid if figure was created using - `plotly.tools.make_subplots` + `plotly.tools.make_subplots`.If 'all', addresses all + columns in the specified row(s). secondary_y: boolean or None (default None) If True, associate this trace with the secondary y-axis of the subplot at the specified row and col. Only valid if all of the @@ -7740,14 +7776,16 @@ def add_histogram2dcontour( zsrc Sets the source reference on Chart Studio Cloud for z . - row : int or None (default) + row : 'all', int or None (default) Subplot row index (starting from 1) for the trace to be added. Only valid if figure was created using - `plotly.tools.make_subplots` - col : int or None (default) + `plotly.tools.make_subplots`.If 'all', addresses all + rows in the specified column(s). + col : 'all', int or None (default) Subplot col index (starting from 1) for the trace to be added. Only valid if figure was created using - `plotly.tools.make_subplots` + `plotly.tools.make_subplots`.If 'all', addresses all + columns in the specified row(s). secondary_y: boolean or None (default None) If True, associate this trace with the secondary y-axis of the subplot at the specified row and col. Only valid if all of the @@ -8041,14 +8079,16 @@ def add_image( zsrc Sets the source reference on Chart Studio Cloud for z . - row : int or None (default) + row : 'all', int or None (default) Subplot row index (starting from 1) for the trace to be added. Only valid if figure was created using - `plotly.tools.make_subplots` - col : int or None (default) + `plotly.tools.make_subplots`.If 'all', addresses all + rows in the specified column(s). + col : 'all', int or None (default) Subplot col index (starting from 1) for the trace to be added. Only valid if figure was created using - `plotly.tools.make_subplots` + `plotly.tools.make_subplots`.If 'all', addresses all + columns in the specified row(s). secondary_y: boolean or None (default None) If True, associate this trace with the secondary y-axis of the subplot at the specified row and col. Only valid if all of the @@ -8232,14 +8272,16 @@ def add_indicator( "legendonly", the trace is not drawn, but can appear as a legend item (provided that the legend itself is visible). - row : int or None (default) + row : 'all', int or None (default) Subplot row index (starting from 1) for the trace to be added. Only valid if figure was created using - `plotly.tools.make_subplots` - col : int or None (default) + `plotly.tools.make_subplots`.If 'all', addresses all + rows in the specified column(s). + col : 'all', int or None (default) Subplot col index (starting from 1) for the trace to be added. Only valid if figure was created using - `plotly.tools.make_subplots` + `plotly.tools.make_subplots`.If 'all', addresses all + columns in the specified row(s). Returns ------- @@ -8578,14 +8620,16 @@ def add_isosurface( zsrc Sets the source reference on Chart Studio Cloud for z . - row : int or None (default) + row : 'all', int or None (default) Subplot row index (starting from 1) for the trace to be added. Only valid if figure was created using - `plotly.tools.make_subplots` - col : int or None (default) + `plotly.tools.make_subplots`.If 'all', addresses all + rows in the specified column(s). + col : 'all', int or None (default) Subplot col index (starting from 1) for the trace to be added. Only valid if figure was created using - `plotly.tools.make_subplots` + `plotly.tools.make_subplots`.If 'all', addresses all + columns in the specified row(s). Returns ------- @@ -9044,14 +9088,16 @@ def add_mesh3d( zsrc Sets the source reference on Chart Studio Cloud for z . - row : int or None (default) + row : 'all', int or None (default) Subplot row index (starting from 1) for the trace to be added. Only valid if figure was created using - `plotly.tools.make_subplots` - col : int or None (default) + `plotly.tools.make_subplots`.If 'all', addresses all + rows in the specified column(s). + col : 'all', int or None (default) Subplot col index (starting from 1) for the trace to be added. Only valid if figure was created using - `plotly.tools.make_subplots` + `plotly.tools.make_subplots`.If 'all', addresses all + columns in the specified row(s). Returns ------- @@ -9360,14 +9406,16 @@ def add_ohlc( a 2D cartesian y axis. If "y" (the default value), the y coordinates refer to `layout.yaxis`. If "y2", the y coordinates refer to `layout.yaxis2`, and so on. - row : int or None (default) + row : 'all', int or None (default) Subplot row index (starting from 1) for the trace to be added. Only valid if figure was created using - `plotly.tools.make_subplots` - col : int or None (default) + `plotly.tools.make_subplots`.If 'all', addresses all + rows in the specified column(s). + col : 'all', int or None (default) Subplot col index (starting from 1) for the trace to be added. Only valid if figure was created using - `plotly.tools.make_subplots` + `plotly.tools.make_subplots`.If 'all', addresses all + columns in the specified row(s). secondary_y: boolean or None (default None) If True, associate this trace with the secondary y-axis of the subplot at the specified row and col. Only valid if all of the @@ -9592,14 +9640,16 @@ def add_parcats( "legendonly", the trace is not drawn, but can appear as a legend item (provided that the legend itself is visible). - row : int or None (default) + row : 'all', int or None (default) Subplot row index (starting from 1) for the trace to be added. Only valid if figure was created using - `plotly.tools.make_subplots` - col : int or None (default) + `plotly.tools.make_subplots`.If 'all', addresses all + rows in the specified column(s). + col : 'all', int or None (default) Subplot col index (starting from 1) for the trace to be added. Only valid if figure was created using - `plotly.tools.make_subplots` + `plotly.tools.make_subplots`.If 'all', addresses all + columns in the specified row(s). Returns ------- @@ -9764,14 +9814,16 @@ def add_parcoords( "legendonly", the trace is not drawn, but can appear as a legend item (provided that the legend itself is visible). - row : int or None (default) + row : 'all', int or None (default) Subplot row index (starting from 1) for the trace to be added. Only valid if figure was created using - `plotly.tools.make_subplots` - col : int or None (default) + `plotly.tools.make_subplots`.If 'all', addresses all + rows in the specified column(s). + col : 'all', int or None (default) Subplot col index (starting from 1) for the trace to be added. Only valid if figure was created using - `plotly.tools.make_subplots` + `plotly.tools.make_subplots`.If 'all', addresses all + columns in the specified row(s). Returns ------- @@ -10110,14 +10162,16 @@ def add_pie( "legendonly", the trace is not drawn, but can appear as a legend item (provided that the legend itself is visible). - row : int or None (default) + row : 'all', int or None (default) Subplot row index (starting from 1) for the trace to be added. Only valid if figure was created using - `plotly.tools.make_subplots` - col : int or None (default) + `plotly.tools.make_subplots`.If 'all', addresses all + rows in the specified column(s). + col : 'all', int or None (default) Subplot col index (starting from 1) for the trace to be added. Only valid if figure was created using - `plotly.tools.make_subplots` + `plotly.tools.make_subplots`.If 'all', addresses all + columns in the specified row(s). Returns ------- @@ -10384,14 +10438,16 @@ def add_pointcloud( ysrc Sets the source reference on Chart Studio Cloud for y . - row : int or None (default) + row : 'all', int or None (default) Subplot row index (starting from 1) for the trace to be added. Only valid if figure was created using - `plotly.tools.make_subplots` - col : int or None (default) + `plotly.tools.make_subplots`.If 'all', addresses all + rows in the specified column(s). + col : 'all', int or None (default) Subplot col index (starting from 1) for the trace to be added. Only valid if figure was created using - `plotly.tools.make_subplots` + `plotly.tools.make_subplots`.If 'all', addresses all + columns in the specified row(s). secondary_y: boolean or None (default None) If True, associate this trace with the secondary y-axis of the subplot at the specified row and col. Only valid if all of the @@ -10595,14 +10651,16 @@ def add_sankey( "legendonly", the trace is not drawn, but can appear as a legend item (provided that the legend itself is visible). - row : int or None (default) + row : 'all', int or None (default) Subplot row index (starting from 1) for the trace to be added. Only valid if figure was created using - `plotly.tools.make_subplots` - col : int or None (default) + `plotly.tools.make_subplots`.If 'all', addresses all + rows in the specified column(s). + col : 'all', int or None (default) Subplot col index (starting from 1) for the trace to be added. Only valid if figure was created using - `plotly.tools.make_subplots` + `plotly.tools.make_subplots`.If 'all', addresses all + columns in the specified row(s). Returns ------- @@ -11077,14 +11135,16 @@ def add_scatter( ysrc Sets the source reference on Chart Studio Cloud for y . - row : int or None (default) + row : 'all', int or None (default) Subplot row index (starting from 1) for the trace to be added. Only valid if figure was created using - `plotly.tools.make_subplots` - col : int or None (default) + `plotly.tools.make_subplots`.If 'all', addresses all + rows in the specified column(s). + col : 'all', int or None (default) Subplot col index (starting from 1) for the trace to be added. Only valid if figure was created using - `plotly.tools.make_subplots` + `plotly.tools.make_subplots`.If 'all', addresses all + columns in the specified row(s). secondary_y: boolean or None (default None) If True, associate this trace with the secondary y-axis of the subplot at the specified row and col. Only valid if all of the @@ -11458,14 +11518,16 @@ def add_scatter3d( zsrc Sets the source reference on Chart Studio Cloud for z . - row : int or None (default) + row : 'all', int or None (default) Subplot row index (starting from 1) for the trace to be added. Only valid if figure was created using - `plotly.tools.make_subplots` - col : int or None (default) + `plotly.tools.make_subplots`.If 'all', addresses all + rows in the specified column(s). + col : 'all', int or None (default) Subplot col index (starting from 1) for the trace to be added. Only valid if figure was created using - `plotly.tools.make_subplots` + `plotly.tools.make_subplots`.If 'all', addresses all + columns in the specified row(s). Returns ------- @@ -11816,14 +11878,16 @@ def add_scattercarpet( a 2D cartesian y axis. If "y" (the default value), the y coordinates refer to `layout.yaxis`. If "y2", the y coordinates refer to `layout.yaxis2`, and so on. - row : int or None (default) + row : 'all', int or None (default) Subplot row index (starting from 1) for the trace to be added. Only valid if figure was created using - `plotly.tools.make_subplots` - col : int or None (default) + `plotly.tools.make_subplots`.If 'all', addresses all + rows in the specified column(s). + col : 'all', int or None (default) Subplot col index (starting from 1) for the trace to be added. Only valid if figure was created using - `plotly.tools.make_subplots` + `plotly.tools.make_subplots`.If 'all', addresses all + columns in the specified row(s). secondary_y: boolean or None (default None) If True, associate this trace with the secondary y-axis of the subplot at the specified row and col. Only valid if all of the @@ -12193,14 +12257,16 @@ def add_scattergeo( "legendonly", the trace is not drawn, but can appear as a legend item (provided that the legend itself is visible). - row : int or None (default) + row : 'all', int or None (default) Subplot row index (starting from 1) for the trace to be added. Only valid if figure was created using - `plotly.tools.make_subplots` - col : int or None (default) + `plotly.tools.make_subplots`.If 'all', addresses all + rows in the specified column(s). + col : 'all', int or None (default) Subplot col index (starting from 1) for the trace to be added. Only valid if figure was created using - `plotly.tools.make_subplots` + `plotly.tools.make_subplots`.If 'all', addresses all + columns in the specified row(s). Returns ------- @@ -12615,14 +12681,16 @@ def add_scattergl( ysrc Sets the source reference on Chart Studio Cloud for y . - row : int or None (default) + row : 'all', int or None (default) Subplot row index (starting from 1) for the trace to be added. Only valid if figure was created using - `plotly.tools.make_subplots` - col : int or None (default) + `plotly.tools.make_subplots`.If 'all', addresses all + rows in the specified column(s). + col : 'all', int or None (default) Subplot col index (starting from 1) for the trace to be added. Only valid if figure was created using - `plotly.tools.make_subplots` + `plotly.tools.make_subplots`.If 'all', addresses all + columns in the specified row(s). secondary_y: boolean or None (default None) If True, associate this trace with the secondary y-axis of the subplot at the specified row and col. Only valid if all of the @@ -12972,14 +13040,16 @@ def add_scattermapbox( "legendonly", the trace is not drawn, but can appear as a legend item (provided that the legend itself is visible). - row : int or None (default) + row : 'all', int or None (default) Subplot row index (starting from 1) for the trace to be added. Only valid if figure was created using - `plotly.tools.make_subplots` - col : int or None (default) + `plotly.tools.make_subplots`.If 'all', addresses all + rows in the specified column(s). + col : 'all', int or None (default) Subplot col index (starting from 1) for the trace to be added. Only valid if figure was created using - `plotly.tools.make_subplots` + `plotly.tools.make_subplots`.If 'all', addresses all + columns in the specified row(s). Returns ------- @@ -13347,14 +13417,16 @@ def add_scatterpolar( "legendonly", the trace is not drawn, but can appear as a legend item (provided that the legend itself is visible). - row : int or None (default) + row : 'all', int or None (default) Subplot row index (starting from 1) for the trace to be added. Only valid if figure was created using - `plotly.tools.make_subplots` - col : int or None (default) + `plotly.tools.make_subplots`.If 'all', addresses all + rows in the specified column(s). + col : 'all', int or None (default) Subplot col index (starting from 1) for the trace to be added. Only valid if figure was created using - `plotly.tools.make_subplots` + `plotly.tools.make_subplots`.If 'all', addresses all + columns in the specified row(s). Returns ------- @@ -13726,14 +13798,16 @@ def add_scatterpolargl( "legendonly", the trace is not drawn, but can appear as a legend item (provided that the legend itself is visible). - row : int or None (default) + row : 'all', int or None (default) Subplot row index (starting from 1) for the trace to be added. Only valid if figure was created using - `plotly.tools.make_subplots` - col : int or None (default) + `plotly.tools.make_subplots`.If 'all', addresses all + rows in the specified column(s). + col : 'all', int or None (default) Subplot col index (starting from 1) for the trace to be added. Only valid if figure was created using - `plotly.tools.make_subplots` + `plotly.tools.make_subplots`.If 'all', addresses all + columns in the specified row(s). Returns ------- @@ -14107,14 +14181,16 @@ def add_scatterternary( "legendonly", the trace is not drawn, but can appear as a legend item (provided that the legend itself is visible). - row : int or None (default) + row : 'all', int or None (default) Subplot row index (starting from 1) for the trace to be added. Only valid if figure was created using - `plotly.tools.make_subplots` - col : int or None (default) + `plotly.tools.make_subplots`.If 'all', addresses all + rows in the specified column(s). + col : 'all', int or None (default) Subplot col index (starting from 1) for the trace to be added. Only valid if figure was created using - `plotly.tools.make_subplots` + `plotly.tools.make_subplots`.If 'all', addresses all + columns in the specified row(s). Returns ------- @@ -14400,14 +14476,16 @@ def add_splom( is false and `showupperhalf` or `showlowerhalf` is false, this splom trace will generate one less x-axis and one less y-axis. - row : int or None (default) + row : 'all', int or None (default) Subplot row index (starting from 1) for the trace to be added. Only valid if figure was created using - `plotly.tools.make_subplots` - col : int or None (default) + `plotly.tools.make_subplots`.If 'all', addresses all + rows in the specified column(s). + col : 'all', int or None (default) Subplot col index (starting from 1) for the trace to be added. Only valid if figure was created using - `plotly.tools.make_subplots` + `plotly.tools.make_subplots`.If 'all', addresses all + columns in the specified row(s). Returns ------- @@ -14754,14 +14832,16 @@ def add_streamtube( zsrc Sets the source reference on Chart Studio Cloud for z . - row : int or None (default) + row : 'all', int or None (default) Subplot row index (starting from 1) for the trace to be added. Only valid if figure was created using - `plotly.tools.make_subplots` - col : int or None (default) + `plotly.tools.make_subplots`.If 'all', addresses all + rows in the specified column(s). + col : 'all', int or None (default) Subplot col index (starting from 1) for the trace to be added. Only valid if figure was created using - `plotly.tools.make_subplots` + `plotly.tools.make_subplots`.If 'all', addresses all + columns in the specified row(s). Returns ------- @@ -15105,14 +15185,16 @@ def add_sunburst( "legendonly", the trace is not drawn, but can appear as a legend item (provided that the legend itself is visible). - row : int or None (default) + row : 'all', int or None (default) Subplot row index (starting from 1) for the trace to be added. Only valid if figure was created using - `plotly.tools.make_subplots` - col : int or None (default) + `plotly.tools.make_subplots`.If 'all', addresses all + rows in the specified column(s). + col : 'all', int or None (default) Subplot col index (starting from 1) for the trace to be added. Only valid if figure was created using - `plotly.tools.make_subplots` + `plotly.tools.make_subplots`.If 'all', addresses all + columns in the specified row(s). Returns ------- @@ -15483,14 +15565,16 @@ def add_surface( zsrc Sets the source reference on Chart Studio Cloud for z . - row : int or None (default) + row : 'all', int or None (default) Subplot row index (starting from 1) for the trace to be added. Only valid if figure was created using - `plotly.tools.make_subplots` - col : int or None (default) + `plotly.tools.make_subplots`.If 'all', addresses all + rows in the specified column(s). + col : 'all', int or None (default) Subplot col index (starting from 1) for the trace to be added. Only valid if figure was created using - `plotly.tools.make_subplots` + `plotly.tools.make_subplots`.If 'all', addresses all + columns in the specified row(s). Returns ------- @@ -15691,14 +15775,16 @@ def add_table( "legendonly", the trace is not drawn, but can appear as a legend item (provided that the legend itself is visible). - row : int or None (default) + row : 'all', int or None (default) Subplot row index (starting from 1) for the trace to be added. Only valid if figure was created using - `plotly.tools.make_subplots` - col : int or None (default) + `plotly.tools.make_subplots`.If 'all', addresses all + rows in the specified column(s). + col : 'all', int or None (default) Subplot col index (starting from 1) for the trace to be added. Only valid if figure was created using - `plotly.tools.make_subplots` + `plotly.tools.make_subplots`.If 'all', addresses all + columns in the specified row(s). Returns ------- @@ -16007,14 +16093,16 @@ def add_treemap( "legendonly", the trace is not drawn, but can appear as a legend item (provided that the legend itself is visible). - row : int or None (default) + row : 'all', int or None (default) Subplot row index (starting from 1) for the trace to be added. Only valid if figure was created using - `plotly.tools.make_subplots` - col : int or None (default) + `plotly.tools.make_subplots`.If 'all', addresses all + rows in the specified column(s). + col : 'all', int or None (default) Subplot col index (starting from 1) for the trace to be added. Only valid if figure was created using - `plotly.tools.make_subplots` + `plotly.tools.make_subplots`.If 'all', addresses all + columns in the specified row(s). Returns ------- @@ -16410,14 +16498,16 @@ def add_violin( ysrc Sets the source reference on Chart Studio Cloud for y . - row : int or None (default) + row : 'all', int or None (default) Subplot row index (starting from 1) for the trace to be added. Only valid if figure was created using - `plotly.tools.make_subplots` - col : int or None (default) + `plotly.tools.make_subplots`.If 'all', addresses all + rows in the specified column(s). + col : 'all', int or None (default) Subplot col index (starting from 1) for the trace to be added. Only valid if figure was created using - `plotly.tools.make_subplots` + `plotly.tools.make_subplots`.If 'all', addresses all + columns in the specified row(s). secondary_y: boolean or None (default None) If True, associate this trace with the secondary y-axis of the subplot at the specified row and col. Only valid if all of the @@ -16812,14 +16902,16 @@ def add_volume( zsrc Sets the source reference on Chart Studio Cloud for z . - row : int or None (default) + row : 'all', int or None (default) Subplot row index (starting from 1) for the trace to be added. Only valid if figure was created using - `plotly.tools.make_subplots` - col : int or None (default) + `plotly.tools.make_subplots`.If 'all', addresses all + rows in the specified column(s). + col : 'all', int or None (default) Subplot col index (starting from 1) for the trace to be added. Only valid if figure was created using - `plotly.tools.make_subplots` + `plotly.tools.make_subplots`.If 'all', addresses all + columns in the specified row(s). Returns ------- @@ -17280,14 +17372,16 @@ def add_waterfall( ysrc Sets the source reference on Chart Studio Cloud for y . - row : int or None (default) + row : 'all', int or None (default) Subplot row index (starting from 1) for the trace to be added. Only valid if figure was created using - `plotly.tools.make_subplots` - col : int or None (default) + `plotly.tools.make_subplots`.If 'all', addresses all + rows in the specified column(s). + col : 'all', int or None (default) Subplot col index (starting from 1) for the trace to be added. Only valid if figure was created using - `plotly.tools.make_subplots` + `plotly.tools.make_subplots`.If 'all', addresses all + columns in the specified row(s). secondary_y: boolean or None (default None) If True, associate this trace with the secondary y-axis of the subplot at the specified row and col. Only valid if all of the @@ -18423,6 +18517,7 @@ def add_annotation( row=None, col=None, secondary_y=None, + exclude_empty_subplots=None, **kwargs ): """ @@ -18712,11 +18807,16 @@ def add_annotation( Shifts the position of the whole annotation and arrow up (positive) or down (negative) by this many pixels. row - Subplot row for annotation + Subplot row for annotation. If 'all', addresses all + rows in the specified column(s). col - Subplot column for annotation + Subplot column for annotation. If 'all', addresses all + columns in the specified row(s). secondary_y Whether to add annotation to secondary y-axis + exclude_empty_subplots + If True, annotation will not be added to subplots + without traces. Returns ------- @@ -18778,6 +18878,7 @@ def add_annotation( row=row, col=col, secondary_y=secondary_y, + exclude_empty_subplots=exclude_empty_subplots, ) def select_layout_images(self, selector=None, row=None, col=None, secondary_y=None): @@ -18942,6 +19043,7 @@ def add_layout_image( row=None, col=None, secondary_y=None, + exclude_empty_subplots=None, **kwargs ): """ @@ -19037,11 +19139,16 @@ def add_layout_image( refers to the point between the bottom and the top of the domain of the second y axis. row - Subplot row for image + Subplot row for image. If 'all', addresses all rows in + the specified column(s). col - Subplot column for image + Subplot column for image. If 'all', addresses all + columns in the specified row(s). secondary_y Whether to add image to secondary y-axis + exclude_empty_subplots + If True, image will not be added to subplots without + traces. Returns ------- @@ -19069,7 +19176,13 @@ def add_layout_image( **kwargs ) return self._add_annotation_like( - "image", "images", new_obj, row=row, col=col, secondary_y=secondary_y, + "image", + "images", + new_obj, + row=row, + col=col, + secondary_y=secondary_y, + exclude_empty_subplots=exclude_empty_subplots, ) def select_shapes(self, selector=None, row=None, col=None, secondary_y=None): @@ -19238,6 +19351,7 @@ def add_shape( row=None, col=None, secondary_y=None, + exclude_empty_subplots=None, **kwargs ): """ @@ -19403,11 +19517,16 @@ def add_shape( maintaining a position relative to data or plot fraction. row - Subplot row for shape + Subplot row for shape. If 'all', addresses all rows in + the specified column(s). col - Subplot column for shape + Subplot column for shape. If 'all', addresses all + columns in the specified row(s). secondary_y Whether to add shape to secondary y-axis + exclude_empty_subplots + If True, shape will not be added to subplots without + traces. Returns ------- @@ -19441,5 +19560,11 @@ def add_shape( **kwargs ) return self._add_annotation_like( - "shape", "shapes", new_obj, row=row, col=col, secondary_y=secondary_y, + "shape", + "shapes", + new_obj, + row=row, + col=col, + secondary_y=secondary_y, + exclude_empty_subplots=exclude_empty_subplots, ) diff --git a/packages/python/plotly/plotly/shapeannotation.py b/packages/python/plotly/plotly/shapeannotation.py new file mode 100644 index 0000000000..05d50cd6d1 --- /dev/null +++ b/packages/python/plotly/plotly/shapeannotation.py @@ -0,0 +1,246 @@ +# some functions defined here to avoid numpy import + + +def _mean(x): + if len(x) == 0: + raise ValueError("x must have positive length") + return float(sum(x)) / len(x) + + +def _argmin(x): + return sorted(enumerate(x), key=lambda t: t[1])[0][0] + + +def _argmax(x): + return sorted(enumerate(x), key=lambda t: t[1], reverse=True)[0][0] + + +def _df_anno(xanchor, yanchor, x, y): + """ Default annotation parameters """ + return dict(xanchor=xanchor, yanchor=yanchor, x=x, y=y, showarrow=False) + + +def _add_inside_to_position(pos): + if not ("inside" in pos or "outside" in pos): + pos.add("inside") + return pos + + +def _prepare_position(position, prepend_inside=False): + if position is None: + position = "top right" + pos_str = position + position = set(position.split(" ")) + if prepend_inside: + position = _add_inside_to_position(position) + return position, pos_str + + +def annotation_params_for_line(shape_type, shape_args, position): + # all x0, x1, y0, y1 are used to place the annotation, that way it could + # work with a slanted line + # even with a slanted line, there are the horizontal and vertical + # conventions of placing a shape + x0 = shape_args["x0"] + x1 = shape_args["x1"] + y0 = shape_args["y0"] + y1 = shape_args["y1"] + X = [x0, x1] + Y = [y0, y1] + R = "right" + T = "top" + L = "left" + C = "center" + B = "bottom" + M = "middle" + aY = max(Y) + iY = min(Y) + eY = _mean(Y) + aaY = _argmax(Y) + aiY = _argmin(Y) + aX = max(X) + iX = min(X) + eX = _mean(X) + aaX = _argmax(X) + aiX = _argmin(X) + position, pos_str = _prepare_position(position) + if shape_type == "vline": + if position == set(["top", "left"]): + return _df_anno(R, T, X[aaY], aY) + if position == set(["top", "right"]): + return _df_anno(L, T, X[aaY], aY) + if position == set(["top"]): + return _df_anno(C, B, X[aaY], aY) + if position == set(["bottom", "left"]): + return _df_anno(R, B, X[aiY], iY) + if position == set(["bottom", "right"]): + return _df_anno(L, B, X[aiY], iY) + if position == set(["bottom"]): + return _df_anno(C, T, X[aiY], iY) + if position == set(["left"]): + return _df_anno(R, M, eX, eY) + if position == set(["right"]): + return _df_anno(L, M, eX, eY) + elif shape_type == "hline": + if position == set(["top", "left"]): + return _df_anno(L, B, iX, Y[aiX]) + if position == set(["top", "right"]): + return _df_anno(R, B, aX, Y[aaX]) + if position == set(["top"]): + return _df_anno(C, B, eX, eY) + if position == set(["bottom", "left"]): + return _df_anno(L, T, iX, Y[aiX]) + if position == set(["bottom", "right"]): + return _df_anno(R, T, aX, Y[aaX]) + if position == set(["bottom"]): + return _df_anno(C, T, eX, eY) + if position == set(["left"]): + return _df_anno(R, M, iX, Y[aiX]) + if position == set(["right"]): + return _df_anno(L, M, aX, Y[aaX]) + raise ValueError('Invalid annotation position "%s"' % (pos_str,)) + + +def annotation_params_for_rect(shape_type, shape_args, position): + x0 = shape_args["x0"] + x1 = shape_args["x1"] + y0 = shape_args["y0"] + y1 = shape_args["y1"] + + position, pos_str = _prepare_position(position, prepend_inside=True) + if position == set(["inside", "top", "left"]): + return _df_anno("left", "top", min([x0, x1]), max([y0, y1])) + if position == set(["inside", "top", "right"]): + return _df_anno("right", "top", max([x0, x1]), max([y0, y1])) + if position == set(["inside", "top"]): + return _df_anno("center", "top", _mean([x0, x1]), max([y0, y1])) + if position == set(["inside", "bottom", "left"]): + return _df_anno("left", "bottom", min([x0, x1]), min([y0, y1])) + if position == set(["inside", "bottom", "right"]): + return _df_anno("right", "bottom", max([x0, x1]), min([y0, y1])) + if position == set(["inside", "bottom"]): + return _df_anno("center", "bottom", _mean([x0, x1]), min([y0, y1])) + if position == set(["inside", "left"]): + return _df_anno("left", "middle", min([x0, x1]), _mean([y0, y1])) + if position == set(["inside", "right"]): + return _df_anno("right", "middle", max([x0, x1]), _mean([y0, y1])) + if position == set(["inside"]): + # TODO: Do we want this? + return _df_anno("center", "middle", _mean([x0, x1]), _mean([y0, y1])) + if position == set(["outside", "top", "left"]): + return _df_anno( + "right" if shape_type == "vrect" else "left", + "bottom" if shape_type == "hrect" else "top", + min([x0, x1]), + max([y0, y1]), + ) + if position == set(["outside", "top", "right"]): + return _df_anno( + "left" if shape_type == "vrect" else "right", + "bottom" if shape_type == "hrect" else "top", + max([x0, x1]), + max([y0, y1]), + ) + if position == set(["outside", "top"]): + return _df_anno("center", "bottom", _mean([x0, x1]), max([y0, y1])) + if position == set(["outside", "bottom", "left"]): + return _df_anno( + "right" if shape_type == "vrect" else "left", + "top" if shape_type == "hrect" else "bottom", + min([x0, x1]), + min([y0, y1]), + ) + if position == set(["outside", "bottom", "right"]): + return _df_anno( + "left" if shape_type == "vrect" else "right", + "top" if shape_type == "hrect" else "bottom", + max([x0, x1]), + min([y0, y1]), + ) + if position == set(["outside", "bottom"]): + return _df_anno("center", "top", _mean([x0, x1]), min([y0, y1])) + if position == set(["outside", "left"]): + return _df_anno("right", "middle", min([x0, x1]), _mean([y0, y1])) + if position == set(["outside", "right"]): + return _df_anno("left", "middle", max([x0, x1]), _mean([y0, y1])) + raise ValueError("Invalid annotation position %s" % (pos_str,)) + + +def axis_spanning_shape_annotation(annotation, shape_type, shape_args, kwargs): + """ + annotation: a go.layout.Annotation object, a dict describing an annotation, or None + shape_type: one of 'vline', 'hline', 'vrect', 'hrect' and determines how the + x, y, xanchor, and yanchor values are set. + shape_args: the parameters used to draw the shape, which are used to place the annotation + kwargs: a dictionary that was the kwargs of a + _process_multiple_axis_spanning_shapes spanning shapes call. Items in this + dict whose keys start with 'annotation_' will be extracted and the keys with + the 'annotation_' part stripped off will be used to assign properties of the + new annotation. + + Property precedence: + The annotation's x, y, xanchor, and yanchor properties are set based on the + shape_type argument. Each property already specified in the annotation or + through kwargs will be left as is (not replaced by the value computed using + shape_type). Note that the xref and yref properties will in general get + overwritten if the result of this function is passed to an add_annotation + called with the row and col parameters specified. + + Returns an annotation populated with fields based on the + annotation_position, annotation_ prefixed kwargs or the original annotation + passed in to this function. + """ + # set properties based on annotation_ prefixed kwargs + prefix = "annotation_" + len_prefix = len(prefix) + annotation_keys = list(filter(lambda k: k.startswith(prefix), kwargs.keys())) + # If no annotation or annotation-key is specified, return None as we don't + # want an annotation in this case + if annotation is None and len(annotation_keys) == 0: + return None + # TODO: Would it be better if annotation were initialized to an instance of + # go.layout.Annotation ? + if annotation is None: + annotation = dict() + for k in annotation_keys: + if k == "annotation_position": + # don't set so that Annotation constructor doesn't complain + continue + subk = k[len_prefix:] + annotation[subk] = kwargs[k] + # set x, y, xanchor, yanchor based on shape_type and position + annotation_position = None + if "annotation_position" in kwargs.keys(): + annotation_position = kwargs["annotation_position"] + if shape_type.endswith("line"): + shape_dict = annotation_params_for_line( + shape_type, shape_args, annotation_position + ) + elif shape_type.endswith("rect"): + shape_dict = annotation_params_for_rect( + shape_type, shape_args, annotation_position + ) + for k in shape_dict.keys(): + # only set property derived from annotation_position if it hasn't already been set + # see above: this would be better as a go.layout.Annotation then the key + # would be checked for validity here (otherwise it is checked later, + # which I guess is ok too) + if (k not in annotation) or (annotation[k] is None): + annotation[k] = shape_dict[k] + return annotation + + +def split_dict_by_key_prefix(d, prefix): + """ + Returns two dictionaries, one containing all the items whose keys do not + start with a prefix and another containing all the items whose keys do start + with the prefix. Note that the prefix is not removed from the keys. + """ + no_prefix = dict() + with_prefix = dict() + for k in d.keys(): + if k.startswith(prefix): + with_prefix[k] = d[k] + else: + no_prefix[k] = d[k] + return (no_prefix, with_prefix) diff --git a/packages/python/plotly/plotly/tests/test_core/test_autoshapes/common.py b/packages/python/plotly/plotly/tests/test_core/test_autoshapes/common.py new file mode 100644 index 0000000000..af6d8c250f --- /dev/null +++ b/packages/python/plotly/plotly/tests/test_core/test_autoshapes/common.py @@ -0,0 +1,22 @@ +def _cmp_partial_dict(a, b): + ret = True + if len(list(b.keys())) == 0: + return False + for k in b.keys(): + try: + v = a[k] + ret &= v == b[k] + except KeyError: + return False + return ret + + +def _check_figure_layout_objects(test_input, expected, fig, layout_key="shapes"): + f, kwargs = test_input + f(fig, **kwargs) + ret = True + if len(fig.layout[layout_key]) != len(expected): + assert False + for s, d in zip(fig.layout[layout_key], expected): + ret &= _cmp_partial_dict(s, d) + assert ret diff --git a/packages/python/plotly/plotly/tests/test_core/test_autoshapes/test_annotated_shapes.py b/packages/python/plotly/plotly/tests/test_core/test_autoshapes/test_annotated_shapes.py new file mode 100644 index 0000000000..9c29282c94 --- /dev/null +++ b/packages/python/plotly/plotly/tests/test_core/test_autoshapes/test_annotated_shapes.py @@ -0,0 +1,430 @@ +# Test annotations added by calling hline, vline, hrect, vrect with the annotation* keywords +# +# How to use this test: +# Normally the test is run as part of the test suite for plotly.py run using +# pytest. To run this test, simply run pytest path/to/this/file.py +# Some tests compare a figure generated by this test with an expected figure +# stored in JSON format (somewhat like the plotly.js image tests). Actually only +# the annotations part of this figure is stored and compared. It could be that +# this figure needs to be updated from time to time. To update the figure, run +# (from an appropriate development environment) +# +# (plotly.py/venv)% WRITE_JSON=1 python path/to/this/file.py +# +# This will generate a figure and write it to a file. See below in the code for +# where the file is written. +# To see what the generated figure looks like, you can run +# +# (plotly.py/venv)% VISUALIZE=1 python path/to/this/file.py +# +# and the figure will be shown in your default browser. +# Note for the above commands python was used and not pytest. pytest should only +# be used when running the tests. + +import plotly.graph_objects as go +from plotly.subplots import make_subplots +from itertools import product +import os +import sys +import pytest +import json +from common import _cmp_partial_dict, _check_figure_layout_objects + + +@pytest.fixture +def single_plot_fixture(): + fig = go.Figure() + fig.update_xaxes(range=[0, 10]) + fig.update_yaxes(range=[0, 10]) + fig.add_trace(go.Scatter(x=[], y=[])) + return fig + + +@pytest.fixture +def multi_plot_fixture(): + fig = make_subplots(2, 2) + for r, c in product(range(2), range(2)): + r += 1 + c += 1 + fig.update_xaxes(row=r, col=c, range=[0, 10]) + fig.update_yaxes(row=r, col=c, range=[0, 10]) + fig.add_trace(go.Scatter(x=[], y=[]), row=r, col=c) + return fig + + +# Make sure adding a shape without specifying an annotation doesn't add any annotations +def test_add_shape_no_annotation(multi_plot_fixture): + multi_plot_fixture.add_hline(y=2, row="all", col="all") + assert len(multi_plot_fixture.layout.annotations) == 0 + assert len(multi_plot_fixture.layout.shapes) == 4 + + +# Adding without row and column on single plot works. +def test_add_annotated_shape_single_plot(single_plot_fixture): + single_plot_fixture.add_hline(y=1, annotation_text="A") + single_plot_fixture.add_vline(x=2, annotation_text="B") + single_plot_fixture.add_hrect(y0=3, y1=4, annotation_text="C") + single_plot_fixture.add_vrect(x0=5, x1=6, annotation_text="D") + ret = len(single_plot_fixture.layout.annotations) == 4 + for sh, d in zip( + single_plot_fixture.layout.annotations, + [{"text": "A"}, {"text": "B"}, {"text": "C"}, {"text": "D"}], + ): + ret &= _cmp_partial_dict(sh, d) + assert ret + + +# Adding without row and column on multi-facted plot works. +def test_add_annotated_shape_multi_plot(multi_plot_fixture): + multi_plot_fixture.add_hline(y=1, annotation_text="A") + multi_plot_fixture.add_vline(x=2, annotation_text="B") + multi_plot_fixture.add_hrect(y0=3, y1=4, annotation_text="C") + multi_plot_fixture.add_vrect(x0=5, x1=6, annotation_text="D") + ax_nums = ["", "2", "3", "4"] + ret = len(multi_plot_fixture.layout.annotations) == 16 + for sh, d in zip( + multi_plot_fixture.layout.annotations, + [ + {"text": "A", "xref": "x%s domain" % (n,), "yref": "y%s" % (n,)} + for n in ax_nums + ] + + [ + {"text": "B", "xref": "x%s" % (n,), "yref": "y%s domain" % (n,)} + for n in ax_nums + ] + + [ + {"text": "C", "xref": "x%s domain" % (n,), "yref": "y%s" % (n,)} + for n in ax_nums + ] + + [ + {"text": "D", "xref": "x%s" % (n,), "yref": "y%s domain" % (n,)} + for n in ax_nums + ], + ): + ret &= _cmp_partial_dict(sh, d) + assert ret + + +# Test that supplying a bad annotation position throws an error +def test_bad_annotation_position(multi_plot_fixture): + bad_pos = "russula delica" + with pytest.raises( + ValueError, match='Invalid annotation position "%s"' % (bad_pos,) + ): + multi_plot_fixture.add_vline( + x=3, annotation_text="Bad position", annotation_position=bad_pos + ) + + +# Test that position descriptions can be given in arbitrary order +def test_position_order(multi_plot_fixture): + multi_plot_fixture.add_hrect( + y0=3, + y1=6, + row=1, + col=2, + annotation_text="Position order", + annotation_position="left bottom outside", + ) + ret = len(multi_plot_fixture.layout.annotations) == 1 + for sh, d in zip( + multi_plot_fixture.layout.annotations, + [ + dict( + text="Position order", + x=0, + y=3, + xanchor="left", + yanchor="top", + xref="x2 domain", + yref="y2", + ) + ], + ): + ret &= _cmp_partial_dict(sh, d) + assert ret + + +# Test that you can override values computed from the annotation position. +def test_annotation_position_override(multi_plot_fixture): + multi_plot_fixture.add_hline( + row=2, + col=2, + y=1, + annotation_text="A", + annotation_position="top left", + annotation_xanchor="center", + ) + multi_plot_fixture.add_vline( + row=1, + col=2, + x=2, + annotation_text="B", + annotation_position="bottom left", + annotation_yanchor="middle", + ) + multi_plot_fixture.add_hrect( + row=2, + col=1, + y0=3, + y1=5, + annotation_text="C", + annotation_position="outside left", + annotation_xanchor="center", + ) + multi_plot_fixture.add_vrect( + row=1, + col=1, + x0=4, + x1=6, + annotation_text="D", + annotation_position="inside bottom right", + annotation_yanchor="middle", + annotation_xanchor="center", + ) + ret = len(multi_plot_fixture.layout.annotations) == 4 + for sh, d in zip( + multi_plot_fixture.layout.annotations, + [ + { + "xanchor": "center", + "xref": "x4 domain", + "yref": "y4", + "x": 0, + "y": 1, + "text": "A", + }, + { + "yanchor": "middle", + "xref": "x2", + "yref": "y2 domain", + "x": 2, + "y": 0, + "text": "B", + }, + { + "xanchor": "center", + "xref": "x3 domain", + "yref": "y3", + "x": 0, + "y": 4, + "text": "C", + }, + { + "xanchor": "center", + "yanchor": "middle", + "xref": "x", + "yref": "y domain", + "x": 6, + "y": 0, + "text": "D", + }, + ], + ): + ret &= _cmp_partial_dict(sh, d) + assert ret + + +# Test that you can add an annotation using annotation=go.layout.Annotation(...) +def test_specify_annotation_as_Annotation(multi_plot_fixture): + multi_plot_fixture.add_vrect( + row=2, + col=2, + x0=2, + x1=9, + annotation=go.layout.Annotation(text="A", x=5.5, xanchor="center"), + annotation_position="outside right", + ) + ret = len(multi_plot_fixture.layout.annotations) == 1 + for sh, d in zip( + multi_plot_fixture.layout.annotations, + [ + { + "text": "A", + "x": 5.5, + "xanchor": "center", + "y": 0.5, + "yanchor": "middle", + "xref": "x4", + "yref": "y4 domain", + } + ], + ): + ret &= _cmp_partial_dict(sh, d) + assert ret + + +# Test that you can add an annotation using annotation=dict(...) +def test_specify_annotation_as_dict(multi_plot_fixture): + multi_plot_fixture.add_vrect( + row=2, + col=2, + x0=2, + x1=9, + annotation=dict(text="A", x=5.5, xanchor="center"), + annotation_position="outside right", + ) + ret = len(multi_plot_fixture.layout.annotations) == 1 + for sh, d in zip( + multi_plot_fixture.layout.annotations, + [ + { + "text": "A", + "x": 5.5, + "xanchor": "center", + "y": 0.5, + "yanchor": "middle", + "xref": "x4", + "yref": "y4 domain", + } + ], + ): + ret &= _cmp_partial_dict(sh, d) + assert ret + + +# Test the default and coerced positions work +def test_default_annotation_positions(multi_plot_fixture): + # default position is (inside) top right + multi_plot_fixture.add_hrect(row=2, col=2, y0=1, y1=8, annotation_text="A") + multi_plot_fixture.add_vline(row=2, col=1, x=4, annotation_text="B") + # if position on {h,v}rect lacks inside/outside specifier it defaults to inside + multi_plot_fixture.add_vrect( + row=1, col=2, x0=3, x1=6, annotation_text="C", annotation_position="bottom left" + ) + ret = len(multi_plot_fixture.layout.annotations) == 3 + for sh, d in zip( + multi_plot_fixture.layout.annotations, + [ + { + "text": "A", + "x": 1, + "y": 8, + "xanchor": "right", + "yanchor": "top", + "xref": "x4 domain", + "yref": "y4", + }, + { + "text": "B", + "x": 4, + "y": 1, + "xanchor": "left", + "yanchor": "top", + "xref": "x3", + "yref": "y3 domain", + }, + { + "text": "C", + "x": 3, + "y": 0, + "xanchor": "left", + "yanchor": "bottom", + "xref": "x2", + "yref": "y2 domain", + }, + ], + ): + ret &= _cmp_partial_dict(sh, d) + assert ret + + +def draw_all_annotation_positions(testing=False): + visualize = os.environ.get("VISUALIZE", 0) + write_json = os.environ.get("WRITE_JSON", 0) + + line_positions = [ + "top left", + "top right", + "top", + "bottom left", + "bottom right", + "bottom", + "left", + "right", + ] + rect_positions = [ + "inside top left", + "inside top right", + "inside top", + "inside bottom left", + "inside bottom right", + "inside bottom", + "inside left", + "inside right", + "inside", + "outside top left", + "outside top right", + "outside top", + "outside bottom left", + "outside bottom right", + "outside bottom", + "outside left", + "outside right", + ] + fig = make_subplots( + 2, 2, column_widths=[3, 1], row_heights=[1, 3], vertical_spacing=0.07 + ) + for rc, pos, ax, sh in zip( + product(range(2), range(2)), + [line_positions, line_positions, rect_positions, rect_positions], + ["x", "y", "x", "y"], + ["vline", "hline", "vrect", "hrect"], + ): + r, c = rc + r += 1 + c = ((c + 1) % 2 if r == 1 else c) + 1 + fig.update_xaxes(row=r, col=c, range=[0, len(pos) if sh[0] == "v" else 1]) + fig.update_yaxes(row=r, col=c, range=[0, len(pos) if sh[0] == "h" else 1]) + fig.add_trace(go.Scatter(x=[], y=[]), row=r, col=c) + for n, p in enumerate(pos): + f = eval("fig.add_%s" % (sh,)) + args = ( + {ax: n + 0.5} + if sh.endswith("line") + else {ax + "0": n + 0.1, ax + "1": n + 0.9} + ) + args["annotation_text"] = p + args["annotation_position"] = p + args["annotation_font_size"] = 8 + args["annotation_font_color"] = "white" + args["row"] = r + args["col"] = c + args["annotation_bgcolor"] = "grey" + if sh[0] == "v": + args["annotation_textangle"] = 90 + f(**args) + fig.update_layout(title="Annotated hline, vline, hrect, vrect") + + # Get JSON representation of annotations + annotations_json = json.dumps( + json.loads(fig.to_json())["layout"]["annotations"], sort_keys=True + ) + + # compute path to where to write JSON annotations (this computes a path to a + # file in the same directory as this test script) + dirname0 = os.path.dirname(os.path.realpath(__file__)) + json_path = os.path.join(dirname0, "test_annotated_shapes_annotations.json") + + if (not testing) and write_json: + # write the annotations + with open(json_path, "w") as fd: + fd.write(annotations_json) + + if (not testing) and visualize: + fig.show() + + if testing: + # check the generated json matches the loaded json + with open(json_path, "r") as fd: + loaded_annotations_json = fd.read() + assert annotations_json == loaded_annotations_json + + +# Check all the annotations are in the expected positions +def test_all_annotation_positions(): + draw_all_annotation_positions(testing=True) + + +if __name__ == "__main__": + draw_all_annotation_positions() diff --git a/packages/python/plotly/plotly/tests/test_core/test_autoshapes/test_annotated_shapes_annotations.json b/packages/python/plotly/plotly/tests/test_core/test_autoshapes/test_annotated_shapes_annotations.json new file mode 100644 index 0000000000..8d38936b3d --- /dev/null +++ b/packages/python/plotly/plotly/tests/test_core/test_autoshapes/test_annotated_shapes_annotations.json @@ -0,0 +1 @@ +[{"bgcolor": "grey", "font": {"color": "white", "size": 8}, "showarrow": false, "text": "top left", "textangle": 90, "x": 0.5, "xanchor": "right", "xref": "x2", "y": 1, "yanchor": "top", "yref": "y2 domain"}, {"bgcolor": "grey", "font": {"color": "white", "size": 8}, "showarrow": false, "text": "top right", "textangle": 90, "x": 1.5, "xanchor": "left", "xref": "x2", "y": 1, "yanchor": "top", "yref": "y2 domain"}, {"bgcolor": "grey", "font": {"color": "white", "size": 8}, "showarrow": false, "text": "top", "textangle": 90, "x": 2.5, "xanchor": "center", "xref": "x2", "y": 1, "yanchor": "bottom", "yref": "y2 domain"}, {"bgcolor": "grey", "font": {"color": "white", "size": 8}, "showarrow": false, "text": "bottom left", "textangle": 90, "x": 3.5, "xanchor": "right", "xref": "x2", "y": 0, "yanchor": "bottom", "yref": "y2 domain"}, {"bgcolor": "grey", "font": {"color": "white", "size": 8}, "showarrow": false, "text": "bottom right", "textangle": 90, "x": 4.5, "xanchor": "left", "xref": "x2", "y": 0, "yanchor": "bottom", "yref": "y2 domain"}, {"bgcolor": "grey", "font": {"color": "white", "size": 8}, "showarrow": false, "text": "bottom", "textangle": 90, "x": 5.5, "xanchor": "center", "xref": "x2", "y": 0, "yanchor": "top", "yref": "y2 domain"}, {"bgcolor": "grey", "font": {"color": "white", "size": 8}, "showarrow": false, "text": "left", "textangle": 90, "x": 6.5, "xanchor": "right", "xref": "x2", "y": 0.5, "yanchor": "middle", "yref": "y2 domain"}, {"bgcolor": "grey", "font": {"color": "white", "size": 8}, "showarrow": false, "text": "right", "textangle": 90, "x": 7.5, "xanchor": "left", "xref": "x2", "y": 0.5, "yanchor": "middle", "yref": "y2 domain"}, {"bgcolor": "grey", "font": {"color": "white", "size": 8}, "showarrow": false, "text": "top left", "x": 0, "xanchor": "left", "xref": "x domain", "y": 0.5, "yanchor": "bottom", "yref": "y"}, {"bgcolor": "grey", "font": {"color": "white", "size": 8}, "showarrow": false, "text": "top right", "x": 1, "xanchor": "right", "xref": "x domain", "y": 1.5, "yanchor": "bottom", "yref": "y"}, {"bgcolor": "grey", "font": {"color": "white", "size": 8}, "showarrow": false, "text": "top", "x": 0.5, "xanchor": "center", "xref": "x domain", "y": 2.5, "yanchor": "bottom", "yref": "y"}, {"bgcolor": "grey", "font": {"color": "white", "size": 8}, "showarrow": false, "text": "bottom left", "x": 0, "xanchor": "left", "xref": "x domain", "y": 3.5, "yanchor": "top", "yref": "y"}, {"bgcolor": "grey", "font": {"color": "white", "size": 8}, "showarrow": false, "text": "bottom right", "x": 1, "xanchor": "right", "xref": "x domain", "y": 4.5, "yanchor": "top", "yref": "y"}, {"bgcolor": "grey", "font": {"color": "white", "size": 8}, "showarrow": false, "text": "bottom", "x": 0.5, "xanchor": "center", "xref": "x domain", "y": 5.5, "yanchor": "top", "yref": "y"}, {"bgcolor": "grey", "font": {"color": "white", "size": 8}, "showarrow": false, "text": "left", "x": 0, "xanchor": "right", "xref": "x domain", "y": 6.5, "yanchor": "middle", "yref": "y"}, {"bgcolor": "grey", "font": {"color": "white", "size": 8}, "showarrow": false, "text": "right", "x": 1, "xanchor": "left", "xref": "x domain", "y": 7.5, "yanchor": "middle", "yref": "y"}, {"bgcolor": "grey", "font": {"color": "white", "size": 8}, "showarrow": false, "text": "inside top left", "textangle": 90, "x": 0.1, "xanchor": "left", "xref": "x3", "y": 1, "yanchor": "top", "yref": "y3 domain"}, {"bgcolor": "grey", "font": {"color": "white", "size": 8}, "showarrow": false, "text": "inside top right", "textangle": 90, "x": 1.9, "xanchor": "right", "xref": "x3", "y": 1, "yanchor": "top", "yref": "y3 domain"}, {"bgcolor": "grey", "font": {"color": "white", "size": 8}, "showarrow": false, "text": "inside top", "textangle": 90, "x": 2.5, "xanchor": "center", "xref": "x3", "y": 1, "yanchor": "top", "yref": "y3 domain"}, {"bgcolor": "grey", "font": {"color": "white", "size": 8}, "showarrow": false, "text": "inside bottom left", "textangle": 90, "x": 3.1, "xanchor": "left", "xref": "x3", "y": 0, "yanchor": "bottom", "yref": "y3 domain"}, {"bgcolor": "grey", "font": {"color": "white", "size": 8}, "showarrow": false, "text": "inside bottom right", "textangle": 90, "x": 4.9, "xanchor": "right", "xref": "x3", "y": 0, "yanchor": "bottom", "yref": "y3 domain"}, {"bgcolor": "grey", "font": {"color": "white", "size": 8}, "showarrow": false, "text": "inside bottom", "textangle": 90, "x": 5.5, "xanchor": "center", "xref": "x3", "y": 0, "yanchor": "bottom", "yref": "y3 domain"}, {"bgcolor": "grey", "font": {"color": "white", "size": 8}, "showarrow": false, "text": "inside left", "textangle": 90, "x": 6.1, "xanchor": "left", "xref": "x3", "y": 0.5, "yanchor": "middle", "yref": "y3 domain"}, {"bgcolor": "grey", "font": {"color": "white", "size": 8}, "showarrow": false, "text": "inside right", "textangle": 90, "x": 7.9, "xanchor": "right", "xref": "x3", "y": 0.5, "yanchor": "middle", "yref": "y3 domain"}, {"bgcolor": "grey", "font": {"color": "white", "size": 8}, "showarrow": false, "text": "inside", "textangle": 90, "x": 8.5, "xanchor": "center", "xref": "x3", "y": 0.5, "yanchor": "middle", "yref": "y3 domain"}, {"bgcolor": "grey", "font": {"color": "white", "size": 8}, "showarrow": false, "text": "outside top left", "textangle": 90, "x": 9.1, "xanchor": "right", "xref": "x3", "y": 1, "yanchor": "top", "yref": "y3 domain"}, {"bgcolor": "grey", "font": {"color": "white", "size": 8}, "showarrow": false, "text": "outside top right", "textangle": 90, "x": 10.9, "xanchor": "left", "xref": "x3", "y": 1, "yanchor": "top", "yref": "y3 domain"}, {"bgcolor": "grey", "font": {"color": "white", "size": 8}, "showarrow": false, "text": "outside top", "textangle": 90, "x": 11.5, "xanchor": "center", "xref": "x3", "y": 1, "yanchor": "bottom", "yref": "y3 domain"}, {"bgcolor": "grey", "font": {"color": "white", "size": 8}, "showarrow": false, "text": "outside bottom left", "textangle": 90, "x": 12.1, "xanchor": "right", "xref": "x3", "y": 0, "yanchor": "bottom", "yref": "y3 domain"}, {"bgcolor": "grey", "font": {"color": "white", "size": 8}, "showarrow": false, "text": "outside bottom right", "textangle": 90, "x": 13.9, "xanchor": "left", "xref": "x3", "y": 0, "yanchor": "bottom", "yref": "y3 domain"}, {"bgcolor": "grey", "font": {"color": "white", "size": 8}, "showarrow": false, "text": "outside bottom", "textangle": 90, "x": 14.5, "xanchor": "center", "xref": "x3", "y": 0, "yanchor": "top", "yref": "y3 domain"}, {"bgcolor": "grey", "font": {"color": "white", "size": 8}, "showarrow": false, "text": "outside left", "textangle": 90, "x": 15.1, "xanchor": "right", "xref": "x3", "y": 0.5, "yanchor": "middle", "yref": "y3 domain"}, {"bgcolor": "grey", "font": {"color": "white", "size": 8}, "showarrow": false, "text": "outside right", "textangle": 90, "x": 16.9, "xanchor": "left", "xref": "x3", "y": 0.5, "yanchor": "middle", "yref": "y3 domain"}, {"bgcolor": "grey", "font": {"color": "white", "size": 8}, "showarrow": false, "text": "inside top left", "x": 0, "xanchor": "left", "xref": "x4 domain", "y": 0.9, "yanchor": "top", "yref": "y4"}, {"bgcolor": "grey", "font": {"color": "white", "size": 8}, "showarrow": false, "text": "inside top right", "x": 1, "xanchor": "right", "xref": "x4 domain", "y": 1.9, "yanchor": "top", "yref": "y4"}, {"bgcolor": "grey", "font": {"color": "white", "size": 8}, "showarrow": false, "text": "inside top", "x": 0.5, "xanchor": "center", "xref": "x4 domain", "y": 2.9, "yanchor": "top", "yref": "y4"}, {"bgcolor": "grey", "font": {"color": "white", "size": 8}, "showarrow": false, "text": "inside bottom left", "x": 0, "xanchor": "left", "xref": "x4 domain", "y": 3.1, "yanchor": "bottom", "yref": "y4"}, {"bgcolor": "grey", "font": {"color": "white", "size": 8}, "showarrow": false, "text": "inside bottom right", "x": 1, "xanchor": "right", "xref": "x4 domain", "y": 4.1, "yanchor": "bottom", "yref": "y4"}, {"bgcolor": "grey", "font": {"color": "white", "size": 8}, "showarrow": false, "text": "inside bottom", "x": 0.5, "xanchor": "center", "xref": "x4 domain", "y": 5.1, "yanchor": "bottom", "yref": "y4"}, {"bgcolor": "grey", "font": {"color": "white", "size": 8}, "showarrow": false, "text": "inside left", "x": 0, "xanchor": "left", "xref": "x4 domain", "y": 6.5, "yanchor": "middle", "yref": "y4"}, {"bgcolor": "grey", "font": {"color": "white", "size": 8}, "showarrow": false, "text": "inside right", "x": 1, "xanchor": "right", "xref": "x4 domain", "y": 7.5, "yanchor": "middle", "yref": "y4"}, {"bgcolor": "grey", "font": {"color": "white", "size": 8}, "showarrow": false, "text": "inside", "x": 0.5, "xanchor": "center", "xref": "x4 domain", "y": 8.5, "yanchor": "middle", "yref": "y4"}, {"bgcolor": "grey", "font": {"color": "white", "size": 8}, "showarrow": false, "text": "outside top left", "x": 0, "xanchor": "left", "xref": "x4 domain", "y": 9.9, "yanchor": "bottom", "yref": "y4"}, {"bgcolor": "grey", "font": {"color": "white", "size": 8}, "showarrow": false, "text": "outside top right", "x": 1, "xanchor": "right", "xref": "x4 domain", "y": 10.9, "yanchor": "bottom", "yref": "y4"}, {"bgcolor": "grey", "font": {"color": "white", "size": 8}, "showarrow": false, "text": "outside top", "x": 0.5, "xanchor": "center", "xref": "x4 domain", "y": 11.9, "yanchor": "bottom", "yref": "y4"}, {"bgcolor": "grey", "font": {"color": "white", "size": 8}, "showarrow": false, "text": "outside bottom left", "x": 0, "xanchor": "left", "xref": "x4 domain", "y": 12.1, "yanchor": "top", "yref": "y4"}, {"bgcolor": "grey", "font": {"color": "white", "size": 8}, "showarrow": false, "text": "outside bottom right", "x": 1, "xanchor": "right", "xref": "x4 domain", "y": 13.1, "yanchor": "top", "yref": "y4"}, {"bgcolor": "grey", "font": {"color": "white", "size": 8}, "showarrow": false, "text": "outside bottom", "x": 0.5, "xanchor": "center", "xref": "x4 domain", "y": 14.1, "yanchor": "top", "yref": "y4"}, {"bgcolor": "grey", "font": {"color": "white", "size": 8}, "showarrow": false, "text": "outside left", "x": 0, "xanchor": "right", "xref": "x4 domain", "y": 15.5, "yanchor": "middle", "yref": "y4"}, {"bgcolor": "grey", "font": {"color": "white", "size": 8}, "showarrow": false, "text": "outside right", "x": 1, "xanchor": "left", "xref": "x4 domain", "y": 16.5, "yanchor": "middle", "yref": "y4"}] \ No newline at end of file diff --git a/packages/python/plotly/plotly/tests/test_core/test_autoshapes/test_axis_span_shapes.py b/packages/python/plotly/plotly/tests/test_core/test_autoshapes/test_axis_span_shapes.py new file mode 100644 index 0000000000..4fef85fe33 --- /dev/null +++ b/packages/python/plotly/plotly/tests/test_core/test_autoshapes/test_axis_span_shapes.py @@ -0,0 +1,476 @@ +import plotly.graph_objs as go +from plotly.subplots import make_subplots +from plotly.basedatatypes import _indexing_combinations +import plotly.express as px +import pytest +from common import _cmp_partial_dict, _check_figure_layout_objects + + +@pytest.fixture +def subplot_fig_fixture(): + fig = px.scatter( + px.data.tips(), x="total_bill", y="tip", facet_row="smoker", facet_col="sex" + ) + # explicitly set domains so that we know what they will be + # these could be anything but we make them plausible + for ax, dom in zip( + [("x", ""), ("x", "2"), ("x", "3"), ("x", "4")], + [(0, 0.4), (0.5, 0.9), (0, 0.4), (0.5, 0.9)], + ): + axname = ax[0] + "axis" + ax[1] + fig["layout"][axname]["domain"] = dom + for ax, dom in zip( + [("y", ""), ("y", "2"), ("y", "3"), ("y", "4")], + [(0, 0.4), (0, 0.4), (0.5, 0.9), (0.5, 0.9)], + ): + axname = ax[0] + "axis" + ax[1] + fig["layout"][axname]["domain"] = dom + return fig + + +@pytest.fixture +def subplot_empty_traces_fig_fixture(): + fig = px.scatter( + px.data.tips(), x="total_bill", y="tip", facet_row="day", facet_col="time" + ) + # explicitly set domains so that we know what they will be + # these could be anything but we make them plausible + for ax, dom in zip( + [("x", "")] + [("x", str(n)) for n in range(2, 9)], + [((n % 2) * 0.5, (n % 2) * 0.5 + 0.4) for n in range(8)], + ): + axname = ax[0] + "axis" + ax[1] + fig["layout"][axname]["domain"] = dom + for ax, dom in zip( + [("y", "")] + [("y", str(n)) for n in range(2, 9)], + [((n // 2) * 0.25, (n // 2) * 0.25 + 0.2) for n in range(8)], + ): + axname = ax[0] + "axis" + ax[1] + fig["layout"][axname]["domain"] = dom + return fig + + +@pytest.fixture +def non_subplot_fig_fixture(): + fig = go.Figure(go.Scatter(x=[1, 2, 3], y=[4, 3, 2])) + return fig + + +# Fixture is here for testing custom-sized subplots +@pytest.fixture +def custom_sized_subplots(): + fig = make_subplots( + rows=5, + cols=2, + specs=[ + [{}, {"rowspan": 2}], + [{}, None], + [{"rowspan": 2, "colspan": 2}, None], + [None, None], + [{}, {}], + ], + print_grid=True, + ) + + fig.add_trace(go.Scatter(x=[1, 2], y=[1, 2], name="(1,1)"), row=1, col=1) + fig.add_trace(go.Scatter(x=[1, 2], y=[1, 2], name="(1,2)"), row=1, col=2) + fig.add_trace(go.Scatter(x=[1, 2], y=[1, 2], name="(2,1)"), row=2, col=1) + fig.add_trace(go.Scatter(x=[1, 2], y=[1, 2], name="(3,1)"), row=3, col=1) + fig.add_trace(go.Scatter(x=[1, 2], y=[1, 2], name="(5,1)"), row=5, col=1) + fig.add_trace(go.Scatter(x=[1, 2], y=[1, 2], name="(5,2)"), row=5, col=2) + return fig + + +# stuff to test: +# add_vline, hline etc. add the intended shape +# - then WLOG maybe we can just test 1 of them, e.g., add_vline? +# test that the addressing works correctly? this is already tested for in add_shape... +# make sure all the methods work for subplots and single plot +# test edge-cases of _make_paper_spanning_shape: bad direction, bad shape (e.g., a path) + + +@pytest.mark.parametrize( + "test_input,expected", + # test_input: (function,kwargs) + # expected: list of dictionaries with key:value pairs we expect in the added shapes + [ + ( + (go.Figure.add_vline, dict(x=20, row=1, col=1)), + [ + { + "type": "line", + "x0": 20, + "x1": 20, + "xref": "x", + "y0": 0, + "y1": 1, + "yref": "y domain", + } + ], + ), + ( + (go.Figure.add_vline, dict(x=20, row=2, col=2)), + [ + { + "type": "line", + "x0": 20, + "x1": 20, + "xref": "x4", + "y0": 0, + "y1": 1, + "yref": "y4 domain", + } + ], + ), + ( + (go.Figure.add_hline, dict(y=6, row=1, col=1)), + [ + { + "type": "line", + "x0": 0, + "x1": 1, + "xref": "x domain", + "y0": 6, + "y1": 6, + "yref": "y", + } + ], + ), + ( + (go.Figure.add_hline, dict(y=6, row=2, col=2)), + [ + { + "type": "line", + "x0": 0, + "x1": 1, + "xref": "x4 domain", + "y0": 6, + "y1": 6, + "yref": "y4", + } + ], + ), + ( + (go.Figure.add_vrect, dict(x0=20, x1=30, row=1, col=1)), + [ + { + "type": "rect", + "x0": 20, + "x1": 30, + "xref": "x", + "y0": 0, + "y1": 1, + "yref": "y domain", + } + ], + ), + ( + (go.Figure.add_vrect, dict(x0=20, x1=30, row=2, col=2)), + [ + { + "type": "rect", + "x0": 20, + "x1": 30, + "xref": "x4", + "y0": 0, + "y1": 1, + "yref": "y4 domain", + } + ], + ), + ( + (go.Figure.add_hrect, dict(y0=6, y1=8, row=1, col=1)), + [ + { + "type": "rect", + "x0": 0, + "x1": 1, + "xref": "x domain", + "y0": 6, + "y1": 8, + "yref": "y", + } + ], + ), + ( + (go.Figure.add_hrect, dict(y0=6, y1=8, row=2, col=2)), + [ + { + "type": "rect", + "x0": 0, + "x1": 1, + "xref": "x4 domain", + "y0": 6, + "y1": 8, + "yref": "y4", + } + ], + ), + ( + (go.Figure.add_vline, dict(x=20, row=2, col="all")), + [ + { + "type": "line", + "x0": 20, + "x1": 20, + "xref": "x3", + "y0": 0, + "y1": 1, + "yref": "y3 domain", + }, + { + "type": "line", + "x0": 20, + "x1": 20, + "xref": "x4", + "y0": 0, + "y1": 1, + "yref": "y4 domain", + }, + ], + ), + ( + (go.Figure.add_vline, dict(x=20, row="all", col=2)), + [ + { + "type": "line", + "x0": 20, + "x1": 20, + "xref": "x2", + "y0": 0, + "y1": 1, + "yref": "y2 domain", + }, + { + "type": "line", + "x0": 20, + "x1": 20, + "xref": "x4", + "y0": 0, + "y1": 1, + "yref": "y4 domain", + }, + ], + ), + ], +) +def test_add_span_shape(test_input, expected, subplot_fig_fixture): + _check_figure_layout_objects(test_input, expected, subplot_fig_fixture) + + +@pytest.mark.parametrize( + "test_input,expected", + # test_input: (function,kwargs) + # expected: list of dictionaries with key:value pairs we expect in the added shapes + [ + ( + (go.Figure.add_vline, dict(x=20, row=[3, 4], col="all")), + [ + { + "type": "line", + "x0": 20, + "x1": 20, + "xref": "x5", + "y0": 0, + "y1": 1, + "yref": "y5 domain", + }, + { + "type": "line", + "x0": 20, + "x1": 20, + "xref": "x7", + "y0": 0, + "y1": 1, + "yref": "y7 domain", + }, + ], + ), + ( + ( + go.Figure.add_vline, + dict(x=20, row="all", col=2, exclude_empty_subplots=False), + ), + [ + { + "type": "line", + "x0": 20, + "x1": 20, + "xref": "x2", + "y0": 0, + "y1": 1, + "yref": "y2 domain", + }, + { + "type": "line", + "x0": 20, + "x1": 20, + "xref": "x4", + "y0": 0, + "y1": 1, + "yref": "y4 domain", + }, + { + "type": "line", + "x0": 20, + "x1": 20, + "xref": "x6", + "y0": 0, + "y1": 1, + "yref": "y6 domain", + }, + { + "type": "line", + "x0": 20, + "x1": 20, + "xref": "x8", + "y0": 0, + "y1": 1, + "yref": "y8 domain", + }, + ], + ), + ], +) +def test_add_span_shape_no_empty_plot( + test_input, expected, subplot_empty_traces_fig_fixture +): + _check_figure_layout_objects(test_input, expected, subplot_empty_traces_fig_fixture) + + +@pytest.mark.parametrize( + "test_input,expected", + # test_input: (function,kwargs) + # expected: list of dictionaries with key:value pairs we expect in the added shapes + [ + ( + (go.Figure.add_hline, dict(y=6)), + [ + { + "type": "line", + "x0": 0, + "x1": 1, + "xref": "x domain", + "y0": 6, + "y1": 6, + "yref": "y", + } + ], + ), + ( + (go.Figure.add_vline, dict(x=6)), + [ + { + "type": "line", + "y0": 0, + "y1": 1, + "xref": "x", + "x0": 6, + "x1": 6, + "yref": "y domain", + } + ], + ), + ], +) +def test_non_subplot_add_span_shape(test_input, expected, non_subplot_fig_fixture): + _check_figure_layout_objects(test_input, expected, non_subplot_fig_fixture) + + +@pytest.mark.parametrize( + "test_input", + [ + (go.Figure.add_hline, dict(y=10, row=4, col=5)), + # valid row, invalid column + (go.Figure.add_hline, dict(y=10, row=1, col=5)), + ], +) +def test_invalid_subplot_address(test_input, subplot_fig_fixture): + f, kwargs = test_input + with pytest.raises(IndexError): + f(subplot_fig_fixture, **kwargs) + + +def _check_figure_shapes_custom_sized(test_input, expected, fig): + # look up domains in fig + corrects = [] + for d, ax in expected: + dom = [0, 1] + if ax[: len("xaxis")] == "xaxis": + d["x0"], d["x1"] = dom + elif ax[: len("yaxis")] == "yaxis": + d["y0"], d["y1"] = dom + else: + raise ValueError("bad axis") + corrects.append(d) + f, kwargs = test_input + f(fig, **kwargs) + if len(fig.layout.shapes) == 0: + assert False + if len(fig.layout.shapes) != len(corrects): + assert False + ret = True + for s, d in zip(fig.layout.shapes, corrects): + ret &= _cmp_partial_dict(s, d) + assert ret + + +@pytest.mark.parametrize( + "test_input,expected", + # test_input: (function,kwargs) + # expected: list of dictionaries with key:value pairs we expect in the added shapes + [ + ( + (go.Figure.add_vline, dict(x=1.5, row="all", col=2)), + [ + ( + { + "type": "line", + "x0": 1.5, + "x1": 1.5, + "xref": "x2", + "yref": "y2 domain", + }, + "yaxis2", + ), + ( + { + "type": "line", + "x0": 1.5, + "x1": 1.5, + "xref": "x6", + "yref": "y6 domain", + }, + "yaxis6", + ), + ], + ), + ( + (go.Figure.add_hline, dict(y=1.5, row=5, col="all")), + [ + ( + { + "type": "line", + "yref": "y5", + "y0": 1.5, + "y1": 1.5, + "xref": "x5 domain", + }, + "xaxis5", + ), + ( + { + "type": "line", + "yref": "y6", + "y0": 1.5, + "y1": 1.5, + "xref": "x6 domain", + }, + "xaxis6", + ), + ], + ), + ], +) +def test_custom_sized_subplots(test_input, expected, custom_sized_subplots): + _check_figure_shapes_custom_sized(test_input, expected, custom_sized_subplots) diff --git a/packages/python/plotly/plotly/tests/test_core/test_figure_messages/test_add_traces.py b/packages/python/plotly/plotly/tests/test_core/test_figure_messages/test_add_traces.py index aac4cfa972..5563a41973 100644 --- a/packages/python/plotly/plotly/tests/test_core/test_figure_messages/test_add_traces.py +++ b/packages/python/plotly/plotly/tests/test_core/test_figure_messages/test_add_traces.py @@ -2,6 +2,7 @@ from unittest import TestCase import plotly.graph_objs as go +from plotly.subplots import make_subplots if sys.version_info >= (3, 3): from unittest.mock import MagicMock @@ -93,3 +94,36 @@ def test_add_traces_with_integers(self): expected_data_length = 4 self.assertEqual(expected_data_length, len(fig2.data)) + + +def test_add_trace_exclude_empty_subplots(): + # Add traces + fig = make_subplots(2, 2) + fig.add_trace(go.Scatter(x=[1, 2, 3], y=[5, 1, 2]), row=1, col=1) + fig.add_trace(go.Scatter(x=[1, 2, 3], y=[2, 1, -7]), row=2, col=2) + # Add traces with exclude_empty_subplots set to true and make sure this + # doesn't add to traces that don't already have data + fig.add_trace( + go.Scatter(x=[1, 2, 3], y=[0, 1, -1]), + row="all", + col="all", + exclude_empty_subplots=True, + ) + assert len(fig.data) == 4 + assert fig.data[2]["xaxis"] == "x" and fig.data[2]["yaxis"] == "y" + assert fig.data[3]["xaxis"] == "x4" and fig.data[3]["yaxis"] == "y4" + + +def test_add_trace_no_exclude_empty_subplots(): + # Add traces + fig = make_subplots(2, 2) + fig.add_trace(go.Scatter(x=[1, 2, 3], y=[5, 1, 2]), row=1, col=1) + fig.add_trace(go.Scatter(x=[1, 2, 3], y=[2, 1, -7]), row=2, col=2) + # Add traces with exclude_empty_subplots set to true and make sure this + # doesn't add to traces that don't already have data + fig.add_trace(go.Scatter(x=[1, 2, 3], y=[0, 1, -1]), row="all", col="all") + assert len(fig.data) == 6 + assert fig.data[2]["xaxis"] == "x" and fig.data[2]["yaxis"] == "y" + assert fig.data[3]["xaxis"] == "x2" and fig.data[3]["yaxis"] == "y2" + assert fig.data[4]["xaxis"] == "x3" and fig.data[4]["yaxis"] == "y3" + assert fig.data[5]["xaxis"] == "x4" and fig.data[5]["yaxis"] == "y4" diff --git a/packages/python/plotly/plotly/tests/test_core/test_update_objects/test_row_col_subplot_addressing.py b/packages/python/plotly/plotly/tests/test_core/test_update_objects/test_row_col_subplot_addressing.py new file mode 100644 index 0000000000..7208a6faa6 --- /dev/null +++ b/packages/python/plotly/plotly/tests/test_core/test_update_objects/test_row_col_subplot_addressing.py @@ -0,0 +1,171 @@ +import plotly.graph_objs as go +from plotly.subplots import make_subplots +from plotly.basedatatypes import _indexing_combinations +import pytest +from itertools import product + +NROWS = 4 +NCOLS = 5 + + +@pytest.fixture +def subplot_fig_fixture(): + fig = make_subplots(NROWS, NCOLS) + return fig + + +@pytest.fixture +def non_subplot_fig_fixture(): + fig = go.Figure(go.Scatter(x=[1, 2, 3], y=[4, 3, 2])) + return fig + + +def test_invalid_validate_get_grid_ref(non_subplot_fig_fixture): + with pytest.raises(Exception): + _ = non_subplot_fig_fixture._validate_get_grid_ref() + + +def test_get_subplot_coordinates(subplot_fig_fixture): + assert set(subplot_fig_fixture._get_subplot_coordinates()) == set( + [(r, c) for r in range(1, NROWS + 1) for c in range(1, NCOLS + 1)] + ) + + +def test_indexing_combinations_edge_cases(): + # Although in theory _indexing_combinations works for any number of + # dimensions, we're just interested in 2D for subplots so that's what we + # test here. + assert _indexing_combinations([], []) == [] + with pytest.raises(ValueError): + _ = _indexing_combinations([[1, 2], [3, 4, 5]], [[1, 2]]) + + +# 18 combinations of input possible: +# ('all', 'all', 'product=True'), +# ('all', 'all', 'product=False'), +# ('all', '', 'product=True'), +# ('all', '', 'product=False'), +# ('all', '', 'product=True'), +# ('all', '', 'product=False'), +# ('', 'all', 'product=True'), +# ('', 'all', 'product=False'), +# ('', '', 'product=True'), +# ('', '', 'product=False'), +# ('', '', 'product=True'), +# ('', '', 'product=False'), +# ('', 'all', 'product=True'), +# ('', 'all', 'product=False'), +# ('', '', 'product=True'), +# ('', '', 'product=False'), +# ('', '', 'product=True'), +# ('', '', 'product=False') +# For we choose int because that's what the subplot indexing routines +# will work with. +all_rows = [1, 2, 3, 4] +all_cols = [1, 2, 3, 4, 5] + + +@pytest.mark.parametrize( + "test_input,expected", + [ + ( + dict(dims=["all", "all"], alls=[all_rows, all_cols], product=False), + set(zip(all_rows, all_cols)), + ), + ( + dict(dims=["all", "all"], alls=[all_rows, all_cols], product=True), + set([(r, c) for r in all_rows for c in all_cols]), + ), + ( + dict(dims=["all", [2, 4, 5]], alls=[all_rows, all_cols], product=False), + set(zip(all_rows, [2, 4, 5])), + ), + ( + dict(dims=["all", [2, 4, 5]], alls=[all_rows, all_cols], product=True), + set([(r, c) for r in all_rows for c in [2, 4, 5]]), + ), + ( + dict(dims=["all", 3], alls=[all_rows, all_cols], product=False), + set([(all_rows[0], 3)]), + ), + ( + dict(dims=["all", 3], alls=[all_rows, all_cols], product=True), + set([(r, c) for r in all_rows for c in [3]]), + ), + ( + dict(dims=[[1, 3], "all"], alls=[all_rows, all_cols], product=False), + set(zip([1, 3], all_cols)), + ), + ( + dict(dims=[[1, 3], "all"], alls=[all_rows, all_cols], product=True), + set([(r, c) for r in [1, 3] for c in all_cols]), + ), + ( + dict(dims=[[1, 3], [2, 4, 5]], alls=[all_rows, all_cols], product=False), + set(zip([1, 3], [2, 4, 5])), + ), + ( + dict(dims=[[1, 3], [2, 4, 5]], alls=[all_rows, all_cols], product=True), + set([(r, c) for r in [1, 3] for c in [2, 4, 5]]), + ), + ( + dict(dims=[[1, 3], 3], alls=[all_rows, all_cols], product=False), + set([(1, 3)]), + ), + ( + dict(dims=[[1, 3], 3], alls=[all_rows, all_cols], product=True), + set([(r, c) for r in [1, 3] for c in [3]]), + ), + ( + dict(dims=[2, "all"], alls=[all_rows, all_cols], product=False), + set([(2, all_cols[0])]), + ), + ( + dict(dims=[2, "all"], alls=[all_rows, all_cols], product=True), + set([(r, c) for r in [2] for c in all_cols]), + ), + ( + dict(dims=[2, [2, 4, 5]], alls=[all_rows, all_cols], product=False), + set([(2, 2)]), + ), + ( + dict(dims=[2, [2, 4, 5]], alls=[all_rows, all_cols], product=True), + set([(r, c) for r in [2] for c in [2, 4, 5]]), + ), + (dict(dims=[2, 3], alls=[all_rows, all_cols], product=False), set([(2, 3)])), + (dict(dims=[2, 3], alls=[all_rows, all_cols], product=True), set([(2, 3)])), + ], +) +def test_indexing_combinations(test_input, expected): + assert set(_indexing_combinations(**test_input)) == expected + + +def _sort_row_col_lists(rows, cols): + # makes sure that row and column lists are compared in the same order + # sorted on rows + si = sorted(range(len(rows)), key=lambda i: rows[i]) + rows = [rows[i] for i in si] + cols = [cols[i] for i in si] + return (rows, cols) + + +# _indexing_combinations tests most cases of the following function +# we just need to test that setting rows or cols to 'all' makes product True, +# and if not, we can still set product to True. +@pytest.mark.parametrize( + "test_input,expected", + [ + (("all", [2, 4, 5], False), zip(*product(range(1, NROWS + 1), [2, 4, 5])),), + (([1, 3], "all", False), zip(*product([1, 3], range(1, NCOLS + 1))),), + (([1, 3], "all", True), zip(*product([1, 3], range(1, NCOLS + 1))),), + (([1, 3], [2, 4, 5], False), [(1, 3), (2, 4)]), + (([1, 3], [2, 4, 5], True), zip(*product([1, 3], [2, 4, 5])),), + ], +) +def test_select_subplot_coordinates(subplot_fig_fixture, test_input, expected): + rows, cols, product = test_input + er, ec = _sort_row_col_lists(*expected) + t = subplot_fig_fixture._select_subplot_coordinates(rows, cols, product=product) + r, c = zip(*t) + r, c = _sort_row_col_lists(r, c) + assert (r == er) and (c == ec) diff --git a/packages/python/plotly/plotly/tests/test_core/test_update_objects/test_update_annotations.py b/packages/python/plotly/plotly/tests/test_core/test_update_objects/test_update_annotations.py index 0edd3cb644..c08129bb31 100644 --- a/packages/python/plotly/plotly/tests/test_core/test_update_objects/test_update_annotations.py +++ b/packages/python/plotly/plotly/tests/test_core/test_update_objects/test_update_annotations.py @@ -267,3 +267,57 @@ def test_update_images(self): def test_image_attributes(self): self.fig.add_layout_image(name="my name", x=1, y=2) self.fig.update_layout_images(opacity=0.1) + + +def test_exclude_empty_subplots(): + for k, fun, d in [ + ( + "shapes", + go.Figure.add_shape, + dict(type="rect", x0=1.5, x1=2.5, y0=3.5, y1=4.5), + ), + ("annotations", go.Figure.add_annotation, dict(x=1, y=2, text="A")), + ( + "images", + go.Figure.add_layout_image, + dict(x=3, y=4, sizex=2, sizey=3, source="test"), + ), + ]: + # make a figure where not all the subplots are populated + fig = make_subplots(2, 2) + fig.add_trace(go.Scatter(x=[1, 2, 3], y=[5, 1, 2]), row=1, col=1) + fig.add_trace(go.Scatter(x=[1, 2, 3], y=[2, 1, -7]), row=2, col=2) + # add a thing to all subplots but make sure it only goes on the + # plots without data + fun(fig, d, row="all", col="all", exclude_empty_subplots=True) + assert len(fig.layout[k]) == 2 + assert fig.layout[k][0]["xref"] == "x" and fig.layout[k][0]["yref"] == "y" + assert fig.layout[k][1]["xref"] == "x4" and fig.layout[k][1]["yref"] == "y4" + + +def test_no_exclude_empty_subplots(): + for k, fun, d in [ + ( + "shapes", + go.Figure.add_shape, + dict(type="rect", x0=1.5, x1=2.5, y0=3.5, y1=4.5), + ), + ("annotations", go.Figure.add_annotation, dict(x=1, y=2, text="A")), + ( + "images", + go.Figure.add_layout_image, + dict(x=3, y=4, sizex=2, sizey=3, source="test"), + ), + ]: + # make a figure where not all the subplots are populated + fig = make_subplots(2, 2) + fig.add_trace(go.Scatter(x=[1, 2, 3], y=[5, 1, 2]), row=1, col=1) + fig.add_trace(go.Scatter(x=[1, 2, 3], y=[2, 1, -7]), row=2, col=2) + # add a thing to all subplots and make sure it even goes on the + # plots without data + fun(fig, d, row="all", col="all", exclude_empty_subplots=False) + assert len(fig.layout[k]) == 4 + assert fig.layout[k][0]["xref"] == "x" and fig.layout[k][0]["yref"] == "y" + assert fig.layout[k][1]["xref"] == "x2" and fig.layout[k][1]["yref"] == "y2" + assert fig.layout[k][2]["xref"] == "x3" and fig.layout[k][2]["yref"] == "y3" + assert fig.layout[k][3]["xref"] == "x4" and fig.layout[k][3]["yref"] == "y4"