Repository: plotly/plotly.py
Pull request #1523 (2019-04-15 23:30:04)

Support calling orca through xvfb on Linux
## Overview
This PR adds support for calling orca through Xvfb, simplifying the process of using plotly.py's image export functions in a headless Linux environment.

This is accomplished through the addition of a new `use_xvfb` parameter in `plotly.io.orca.config`.  This parameter may be set to `False`, `True`, or `'auto'`, where:
  - `False`: Never use xvfb
  - `True`: Use Xvfb and raise an error if `xvfb-run` is not found on the system Path
  - `'auto'`:  Use Xvfb if running on Linux, no display serer is detected (determined by checking the `DISPLAY` environment variable), and `xvfb-run` is on the path.

`use_xvfb` will default to `False` for the remainder of version 3, but it will default to `'auto'` in version 4.  The new `orca_defaults` future flag may be used to enable this default behavior now.

## Additional Changes 
Add `LD_PRELOAD` to the list of environment variables to be excluded to be unset when calling orca to avoid breaking orca in Colab (See https://medium.com/@n0mad/how-i-trained-starcraft-2-ais-using-googles-free-gpus-44bc635b0418 for discussion of the Colab's use of tcmalloc).


jonmmease: Using plotly.py with orca in Colab
```
!pip install https://11956-14579099-gh.circle-artifacts.com/0/dist/plotly-3.8.0%2B2.g4a2ccc2e.tar.gz
!apt-get install xvfb libgtk2.0-0 libgconf-2-4

!wget -c https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh
!chmod +x Miniconda3-latest-Linux-x86_64.sh
!bash ./Miniconda3-latest-Linux-x86_64.sh -b -f -p /usr/local
!conda install -q -y --prefix /usr/local -c plotly plotly-orca

import sys
sys.path.append('/usr/local/lib/python3.6/site-packages/')
sys.path.append('/usr/local/bin')
```

```python
from _plotly_future_ import v4
import plotly.io as pio
import plotly.graph_objs as go

fig = go.Figure(data=[{'y': [3, 2, 4]}])
pio.to_image(fig)
```

jonmmease: orca setup for use in Kaggle kernels

```
!pip install https://11970-14579099-gh.circle-artifacts.com/0/dist/plotly-3.8.0%2B3.g27437f41.tar.gz
!conda install -y -c plotly plotly-orca
!apt-get -y install libxss1
```

If the Kaggle Python docker image (https://github.com/Kaggle/docker-python/blob/master/Dockerfile) was updated to include `conda install -y -c plotly plotly-orca` and `apt-get -y install libxss1` then orca would work out-of-the-box with the changes in this branch since Xvfb is already included.


jonmmease: Unfortunately, it appears this approach won't work with Azure notebooks because sudo is required to use `apt-get` to install Xvfb and sudo isn't supported right now https://github.com/Microsoft/AzureNotebooks/issues/303.

Patch:
diff --git a/_plotly_future_/orca_defaults.py b/_plotly_future_/orca_defaults.py
new file mode 100644
index 000000000..b63282fb6
--- /dev/null
+++ b/_plotly_future_/orca_defaults.py
@@ -0,0 +1,5 @@
+from __future__ import absolute_import
+from _plotly_future_ import _future_flags, _assert_plotly_not_imported
+
+_assert_plotly_not_imported()
+_future_flags.add('orca_defaults')
diff --git a/_plotly_future_/v4.py b/_plotly_future_/v4.py
index 25324f695..a4acb8cf1 100644
--- a/_plotly_future_/v4.py
+++ b/_plotly_future_/v4.py
@@ -1,4 +1,10 @@
 from __future__ import absolute_import
 from _plotly_future_ import (
-    renderer_defaults, template_defaults, extract_chart_studio,
-    remove_deprecations, v4_subplots)
+    renderer_defaults,
+    template_defaults,
+    extract_chart_studio,
+    remove_deprecations,
+    v4_subplots,
+    orca_defaults,
+)
+
diff --git a/_plotly_utils/basevalidators.py b/_plotly_utils/basevalidators.py
index 2e69546e5..b28f7d633 100644
--- a/_plotly_utils/basevalidators.py
+++ b/_plotly_utils/basevalidators.py
@@ -1454,14 +1454,9 @@ class SubplotidValidator(BaseValidator):
             self.base = dflt
         else:
             # e.g. regex == '/^y([2-9]|[1-9][0-9]+)?$/'
-            self.base = re.match('/\^(\w+)',
-                                 regex).group(1)
+            self.base = re.match('/\^(\w+)', regex).group(1)
 
-        if regex is not None:
-            # Remove leading/trailing '/' characters
-            self.regex = regex[1:-1]
-        else:
-            self.regex = dflt + "(\d*)"
+        self.regex = self.base + "(\d*)"
 
     def description(self):
 
diff --git a/codegen/datatypes.py b/codegen/datatypes.py
index 017770867..5b5416256 100644
--- a/codegen/datatypes.py
+++ b/codegen/datatypes.py
@@ -491,11 +491,16 @@ def add_docstring(buffer, node, header, prepend_extras=(), append_extras=()):
 
     # Write any append extras
     for p, v in append_extras:
-        v_wrapped = '\n'.join(textwrap.wrap(
-            v,
-            width=79-12,
-            initial_indent=' ' * 12,
-            subsequent_indent=' ' * 12))
+        if '\n' in v:
+            # If v contains newlines then assume it's already wrapped as
+            # desired
+            v_wrapped = v
+        else:
+            v_wrapped = '\n'.join(textwrap.wrap(
+                v,
+                width=79-12,
+                initial_indent=' ' * 12,
+                subsequent_indent=' ' * 12))
         buffer.write(f"""
         {p}
 {v_wrapped}""")
diff --git a/codegen/figure.py b/codegen/figure.py
index d60f2c7b7..e4cf108e8 100644
--- a/codegen/figure.py
+++ b/codegen/figure.py
@@ -107,28 +107,72 @@ class {fig_classname}({base_classname}):\n""")
     # ### add_trace methods for each trace type ###
     for trace_node in trace_nodes:
 
+        include_secondary_y = bool([
+            d for d in trace_node.child_datatypes
+            if d.name_property == 'yaxis'
+        ])
+        if include_secondary_y:
+            secondary_y_1 = ', secondary_y=None'
+            secondary_y_2 = ', secondary_y=secondary_y'
+            secondary_y_docstring = f"""
+        secondary_y: boolean or None (default None)
+            * If True, only select yaxis objects associated with the secondary
+              y-axis of the subplot.
+            * If False, only select yaxis objects associated with the primary
+              y-axis of the subplot.
+            * If None (the default), do not filter yaxis objects based on
+              a secondary y-axis condition. 
+
+            To select yaxis objects by secondary y-axis, the Figure must
+            have been created using plotly.subplots.make_subplots. See
+            the docstring for the specs argument to make_subplots for more
+            info on creating subplots with secondary y-axes."""
+        else:
+            secondary_y_1 = ''
+            secondary_y_2 = ''
+            secondary_y_docstring = ''
+
         # #### Function signature ####
         buffer.write(f"""
     def add_{trace_node.plotly_name}(self""")
 
         # #### Function params####
+        param_extras = ['row', 'col']
+        if include_secondary_y:
+            param_extras.append('secondary_y')
         add_constructor_params(buffer,
                                trace_node.child_datatypes,
-                               append_extras=['row', 'col'])
+                               append_extras=param_extras)
 
         # #### Docstring ####
         header = f"Add a new {trace_node.name_datatype_class} trace"
 
-        extras = (('row : 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)',
-                   'Subplot col index (starting from 1) for the trace to be '
-                   'added. Only valid if figure was created using '
-                   '`plotly.tools.make_subplots`'))
-
-        add_docstring(buffer, trace_node, header, append_extras=extras)
+        doc_extras = [(
+            'row : 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)',
+             'Subplot col index (starting from 1) for the trace to be '
+             'added. Only valid if figure was created using '
+             '`plotly.tools.make_subplots`')]
+
+        if include_secondary_y:
+            doc_extras.append(
+                ('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
+            following conditions are satisfied:
+              * The figure was created using `plotly.subplots.make_subplots`.
+              * The row and col arguments are not None
+              * The subplot at the specified row and col has type xy
+                (which is the default) and secondary_y True.  These
+                properties are specified in the specs argument to
+                make_subplots. See the make_subplots docstring for more info.\
+""")
+            )
+
+        add_docstring(buffer, trace_node, header, append_extras=doc_extras)
 
         # #### Function body ####
         buffer.write(f"""
@@ -143,8 +187,14 @@ class {fig_classname}({base_classname}):\n""")
         buffer.write(f"""
             **kwargs)""")
 
+        if include_secondary_y:
+            secondary_y_kwarg = ', secondary_y=secondary_y'
+        else:
+            secondary_y_kwarg = ''
+
         buffer.write(f"""
-        return self.add_trace(new_trace, row=row, col=col)""")
+        return self.add_trace(
+            new_trace, row=row, col=col{secondary_y_kwarg})""")
 
     # update layout subplots
     # ----------------------
@@ -152,9 +202,32 @@ class {fig_classname}({base_classname}):\n""")
     for subplot_node in subplot_nodes:
         singular_name = subplot_node.name_property
         plural_name = inflect_eng.plural_noun(singular_name)
+
+        if singular_name == 'yaxis':
+            secondary_y_1 = ', secondary_y=None'
+            secondary_y_2 = ', secondary_y=secondary_y'
+            secondary_y_docstring = f"""
+        secondary_y: boolean or None (default None)
+            * If True, only select yaxis objects associated with the secondary
+              y-axis of the subplot.
+            * If False, only select yaxis objects associated with the primary
+              y-axis of the subplot.
+            * If None (the default), do not filter yaxis objects based on
+              a secondary y-axis condition. 
+            
+            To select yaxis objects by secondary y-axis, the Figure must
+            have been created using plotly.subplots.make_subplots. See
+            the docstring for the specs argument to make_subplots for more
+            info on creating subplots with secondary y-axes."""
+        else:
+            secondary_y_1 = ''
+            secondary_y_2 = ''
+            secondary_y_docstring = ''
+
         buffer.write(f"""
 
-    def select_{plural_name}(self, selector=None, row=None, col=None):
+    def select_{plural_name}(
+            self, selector=None, row=None, col=None{secondary_y_1}):
         \"\"\"
         Select {singular_name} subplot objects from a particular subplot cell
         and/or {singular_name} subplot objects that satisfy custom selection
@@ -172,8 +245,8 @@ class {fig_classname}({base_classname}):\n""")
             Subplot row and column index of {singular_name} objects to select.
             To select {singular_name} objects by row and column, the Figure
             must have been created using plotly.subplots.make_subplots.
-            If None (the default), all {singular_name} objects are selected.
-
+            If None (the default), all {singular_name} objects are selected.\
+{secondary_y_docstring}
         Returns
         -------
         generator
@@ -184,9 +257,10 @@ class {fig_classname}({base_classname}):\n""")
             _validate_v4_subplots('select_{plural_name}')
 
         return self._select_layout_subplots_by_prefix(
-            '{singular_name}', selector, row, col)
+            '{singular_name}', selector, row, col{secondary_y_2})
 
-    def for_each_{singular_name}(self, fn, selector=None, row=None, col=None):
+    def for_each_{singular_name}(
+            self, fn, selector=None, row=None, col=None{secondary_y_1}):
         \"\"\"
         Apply a function to all {singular_name} objects that satisfy the
         specified selection criteria
@@ -205,21 +279,25 @@ class {fig_classname}({base_classname}):\n""")
             Subplot row and column index of {singular_name} objects to select.
             To select {singular_name} objects by row and column, the Figure
             must have been created using plotly.subplots.make_subplots.
-            If None (the default), all {singular_name} objects are selected.
-        
+            If None (the default), all {singular_name} objects are selected.\
+{secondary_y_docstring}
         Returns
         -------
         self
             Returns the Figure object that the method was called on
         \"\"\"
         for obj in self.select_{plural_name}(
-                selector=selector, row=row, col=col):
+                selector=selector, row=row, col=col{secondary_y_2}):
             fn(obj)
 
         return self
 
     def update_{plural_name}(
-            self, patch=None, selector=None, row=None, col=None, **kwargs):
+            self,
+            patch=None,
+            selector=None,
+            row=None, col=None{secondary_y_1},
+            **kwargs):
         \"\"\"
         Perform a property update operation on all {singular_name} objects
         that satisfy the specified selection criteria
@@ -239,7 +317,8 @@ class {fig_classname}({base_classname}):\n""")
             Subplot row and column index of {singular_name} objects to select.
             To select {singular_name} objects by row and column, the Figure
             must have been created using plotly.subplots.make_subplots.
-            If None (the default), all {singular_name} objects are selected.
+            If None (the default), all {singular_name} objects are selected.\
+{secondary_y_docstring}
         **kwargs
             Additional property updates to apply to each selected
             {singular_name} object. If a property is specified in
@@ -251,7 +330,7 @@ class {fig_classname}({base_classname}):\n""")
             Returns the Figure object that the method was called on
         \"\"\"
         for obj in self.select_{plural_name}(
-                selector=selector, row=row, col=col):
+                selector=selector, row=row, col=col{secondary_y_2}):
             obj.update(patch, **kwargs)
 
         return self""")
diff --git a/plotly/basedatatypes.py b/plotly/basedatatypes.py
index cd8cd7ea9..314468921 100644
--- a/plotly/basedatatypes.py
+++ b/plotly/basedatatypes.py
@@ -639,7 +639,8 @@ class BaseFigure(object):
         for trace_ind, trace in enumerate(self._data_objs):
             trace._trace_ind = trace_ind
 
-    def select_traces(self, selector=None, row=None, col=None):
+    def select_traces(
+            self, selector=None, row=None, col=None, secondary_y=None):
         """
         Select traces from a particular subplot cell and/or traces
         that satisfy custom selection criteria.
@@ -657,7 +658,18 @@ class BaseFigure(object):
             To select traces by row and column, the Figure must have been
             created using plotly.subplots.make_subplots.  If None
             (the default), all traces are selected.
-
+        secondary_y: boolean or None (default None)
+            * If True, only select traces associated with the secondary
+              y-axis of the subplot.
+            * If False, only select traces associated with the primary
+              y-axis of the subplot.
+            * If None (the default), do not filter traces based on secondary
+              y-axis.
+
+            To select traces by secondary y-axis, the Figure must have been
+            created using plotly.subplots.make_subplots. See the docstring
+            for the specs argument to make_subplots for more info on
+            creating subplots with secondary y-axes.
         Returns
         -------
         generator
@@ -667,20 +679,40 @@ class BaseFigure(object):
         if not selector:
             selector = {}
 
-        if row is not None or col is not None:
+        if row is not None or col is not None or secondary_y is not None:
             _validate_v4_subplots('select_traces')
             grid_ref = self._validate_get_grid_ref()
             filter_by_subplot = True
 
-            if row is None:
+            if row is None and col is not None:
                 # All rows for column
-                grid_subplot_refs = [ref_row[col-1] for ref_row in grid_ref]
-            elif col is None:
+                grid_subplot_ref_tuples = [
+                    ref_row[col-1] for ref_row in grid_ref
+                ]
+            elif col is None and row is not None:
                 # All columns for row
-                grid_subplot_refs = grid_ref[row-1]
-            else:
+                grid_subplot_ref_tuples = grid_ref[row-1]
+            elif col is not None and row is not None:
                 # Single grid cell
-                grid_subplot_refs = [grid_ref[row-1][col-1]]
+                grid_subplot_ref_tuples = [grid_ref[row-1][col-1]]
+            else:
+                # row and col are None, secondary_y not None
+                grid_subplot_ref_tuples = [
+                    refs
+                    for refs_row in grid_ref
+                    for refs in refs_row
+                ]
+
+            # Collect list of subplot refs, taking secondary_y into account
+            grid_subplot_refs = []
+            for refs in grid_subplot_ref_tuples:
+                if not refs:
+                    continue
+                if secondary_y is not True:
+                    grid_subplot_refs.append(refs[0])
+
+                if secondary_y is not False and len(refs) > 1:
+                    grid_subplot_refs.append(refs[1])
 
         else:
             filter_by_subplot = False
@@ -728,7 +760,8 @@ class BaseFigure(object):
 
         return True
 
-    def for_each_trace(self, fn, selector=None, row=None, col=None):
+    def for_each_trace(
+            self, fn, selector=None, row=None, col=None, secondary_y=None):
         """
         Apply a function to all traces that satisfy the specified selection
         criteria
@@ -748,19 +781,38 @@ class BaseFigure(object):
             To select traces by row and column, the Figure must have been
             created using plotly.subplots.make_subplots.  If None
             (the default), all traces are selected.
-
+        secondary_y: boolean or None (default None)
+            * If True, only select traces associated with the secondary
+              y-axis of the subplot.
+            * If False, only select traces associated with the primary
+              y-axis of the subplot.
+            * If None (the default), do not filter traces based on secondary
+              y-axis.
+
+            To select traces by secondary y-axis, the Figure must have been
+            created using plotly.subplots.make_subplots. See the docstring
+            for the specs argument to make_subplots for more info on
+            creating subplots with secondary y-axes.
         Returns
         -------
         self
             Returns the Figure object that the method was called on
         """
-        for trace in self.select_traces(selector=selector, row=row, col=col):
+        for trace in self.select_traces(
+                selector=selector, row=row, col=col, secondary_y=secondary_y):
             fn(trace)
 
         return self
 
     def update_traces(
-            self, patch=None, selector=None, row=None, col=None, **kwargs):
+            self,
+            patch=None,
+            selector=None,
+            row=None,
+            col=None,
+            secondary_y=None,
+            **kwargs
+    ):
         """
         Perform a property update operation on all traces that satisfy the
         specified selection criteria
@@ -781,6 +833,18 @@ class BaseFigure(object):
             To select traces by row and column, the Figure must have been
             created using plotly.subplots.make_subplots.  If None
             (the default), all traces are selected.
+        secondary_y: boolean or None (default None)
+            * If True, only select traces associated with the secondary
+              y-axis of the subplot.
+            * If False, only select traces associated with the primary
+              y-axis of the subplot.
+            * If None (the default), do not filter traces based on secondary
+              y-axis.
+
+            To select traces by secondary y-axis, the Figure must have been
+            created using plotly.subplots.make_subplots. See the docstring
+            for the specs argument to make_subplots for more info on
+            creating subplots with secondary y-axes.
         **kwargs
             Additional property updates to apply to each selected trace. If
             a property is specified in both patch and in **kwargs then the
@@ -791,28 +855,40 @@ class BaseFigure(object):
         self
             Returns the Figure object that the method was called on
         """
-        for trace in self.select_traces(selector=selector, row=row, col=col):
+        for trace in self.select_traces(
+                selector=selector, row=row, col=col, secondary_y=secondary_y):
             trace.update(patch, **kwargs)
         return self
 
     def _select_layout_subplots_by_prefix(
-            self, prefix, selector=None, row=None, col=None):
+            self,
+            prefix,
+            selector=None,
+            row=None,
+            col=None,
+            secondary_y=None
+    ):
         """
         Helper called by code generated select_* methods
         """
 
-        if row is not None or col is not None:
+        if row is not None or col is not None or secondary_y is not None:
             # Build mapping from container keys ('xaxis2', 'scene4', etc.)
-            # to row/col pairs
+            # to (row, col, secondary_y) triplets
             grid_ref = self._validate_get_grid_ref()
             container_to_row_col = {}
             for r, subplot_row in enumerate(grid_ref):
-                for c, ref in enumerate(subplot_row):
-                    if ref is None:
+                for c, subplot_refs in enumerate(subplot_row):
+                    if not subplot_refs:
                         continue
-                    for layout_key in ref['layout_keys']:
-                        if layout_key.startswith(prefix):
-                            container_to_row_col[layout_key] = r + 1, c + 1
+
+                    # collect primary keys
+                    for i, subplot_ref in enumerate(subplot_refs):
+                        for layout_key in subplot_ref.layout_keys:
+                            if layout_key.startswith(prefix):
+                                is_secondary_y = i == 1
+                                container_to_row_col[layout_key] = (
+                                    r + 1, c + 1, is_secondary_y)
         else:
             container_to_row_col = None
 
@@ -824,13 +900,19 @@ class BaseFigure(object):
 
                 # Filter by row/col
                 if (row is not None and
-                        container_to_row_col.get(k, (None, None))[0] != row):
+                        container_to_row_col.get(
+                            k, (None, None, None))[0] != row):
                     # row specified and this is not a match
                     continue
                 elif (col is not None and
-                      container_to_row_col.get(k, (None, None))[1] != col):
+                      container_to_row_col.get(
+                          k, (None, None, None))[1] != col):
                     # col specified and this is not a match
                     continue
+                elif (secondary_y is not None and
+                      container_to_row_col.get(
+                          k, (None, None, None))[2] != secondary_y):
+                    continue
 
                 # Filter by selector
                 if not self._selector_matches(self.layout[k], selector):
@@ -1268,7 +1350,7 @@ Invalid property path '{key_path_str}' for trace class {trace_class}
         else:
             BaseFigure._raise_invalid_rows_cols(name=name, n=n, invalid=vals)
 
-    def add_trace(self, trace, row=None, col=None):
+    def add_trace(self, trace, row=None, col=None, secondary_y=None):
         """
         Add a trace to the figure
 
@@ -1286,15 +1368,26 @@ Invalid property path '{key_path_str}' for trace class {trace_class}
                   - All remaining properties are passed to the constructor
                     of the specified trace type.
 
-        row : int or None (default)
+        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.tools.make_subplots`
-        col : int or None (default)
+            `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.tools.make_subplots`
-
+            `plotly.subplots.make_subplots`
+        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
+            following conditions are satisfied:
+              * The figure was created using `plotly.subplots.make_subplots`.
+              * The row and col arguments are not None
+              * The subplot at the specified row and col has type xy
+                (which is the default) and secondary_y True.  These
+                properties are specified in the specs argument to
+                make_subplots. See the make_subplots docstring for more info.
+              * The trace argument is a 2D cartesian trace
+                (scatter, bar, etc.)
         Returns
         -------
         BaseTraceType
@@ -1337,12 +1430,14 @@ Invalid property path '{key_path_str}' for trace class {trace_class}
                 'Received col parameter but not row.\n'
                 'row and col must be specified together')
 
-        return self.add_traces(data=[trace],
-                               rows=[row] if row is not None else None,
-                               cols=[col] if col is not None else None
-                               )[0]
+        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
+        )[0]
 
-    def add_traces(self, data, rows=None, cols=None):
+    def add_traces(self, data, rows=None, cols=None, secondary_ys=None):
         """
         Add traces to the figure
 
@@ -1370,6 +1465,9 @@ Invalid property path '{key_path_str}' for trace class {trace_class}
             List of subplot column indexes (starting from 1) for the traces
             to be added. Only valid if figure was created using
             `plotly.tools.make_subplots`
+        secondary_ys: None or list[boolean] (default None)
+            List of secondary_y booleans for traces to be added. See the
+            docstring for `add_trace` for more info.
 
         Returns
         -------
@@ -1419,10 +1517,22 @@ Invalid property path '{key_path_str}' for trace class {trace_class}
                 'Received cols parameter but not rows.\n'
                 'rows and cols must be specified together')
 
+        # Process secondary_ys defaults
+        if secondary_ys is not None and rows is None:
+            # Default rows/cols to 1s if secondary_ys specified but not rows
+            # or cols
+            rows = [1] * len(secondary_ys)
+            cols = rows
+        elif secondary_ys is None and rows is not None:
+            # Default secondary_ys to Nones if secondary_ys is not specified
+            # but not rows and cols are specified
+            secondary_ys = [None] * len(rows)
+
         # Apply rows / cols
         if rows is not None:
-            for trace, row, col in zip(data, rows, cols):
-                self._set_trace_grid_position(trace, row, col)
+            for trace, row, col, secondary_y in \
+                    zip(data, rows, cols, secondary_ys):
+                self._set_trace_grid_position(trace, row, col, secondary_y)
 
         # Make deep copy of trace data (Optimize later if needed)
         new_traces_data = [deepcopy(trace._props) for trace in data]
@@ -1494,13 +1604,14 @@ Please use the add_trace method with the row and col parameters.
 
         self.add_trace(trace=trace, row=row, col=col)
 
-    def _set_trace_grid_position(self, trace, row, col):
+    def _set_trace_grid_position(
+            self, trace, row, col, secondary_y=False):
         grid_ref = self._validate_get_grid_ref()
 
         from _plotly_future_ import _future_flags
         if 'v4_subplots' in _future_flags:
             return _set_trace_grid_reference(
-                trace, self.layout, grid_ref, row, col)
+                trace, self.layout, grid_ref, row, col, secondary_y)
 
         if row <= 0:
             raise Exception("Row value is out of range. "
@@ -1544,7 +1655,7 @@ Please use the add_trace method with the row and col parameters.
                             "to create the figure with a subplot grid.")
         return grid_ref
 
-    def get_subplot(self, row, col):
+    def get_subplot(self, row, col, secondary_y=False):
         """
         Return an object representing the subplot at the specified row
         and column.  May only be used on Figures created using
@@ -1556,7 +1667,13 @@ Please use the add_trace method with the row and col parameters.
             1-based index of subplot row
         col: int
             1-based index of subplot column
-
+        secondary_y: bool
+            If True, select the subplot that consists of the x-axis and the
+            secondary y-axis at the specified row/col. Only valid if the
+            subplot at row/col is an 2D cartesian subplot that was created
+            with a secondary y-axis.  See the docstring for the specs argument
+            to make_subplots for more info on creating a subplot with a
+            secondary y-axis.
         Returns
         -------
         subplot
@@ -1574,7 +1691,7 @@ Please use the add_trace method with the row and col parameters.
                 - xaxis: plotly.graph_objs.layout.XAxis instance for subplot
                 - yaxis: plotly.graph_objs.layout.YAxis instance for subplot
         """
-        return _get_grid_subplot(self, row, col)
+        return _get_grid_subplot(self, row, col, secondary_y)
 
     # Child property operations
     # -------------------------
diff --git a/plotly/graph_objs/_figure.py b/plotly/graph_objs/_figure.py
index 8cc8ef262..bb1a7d6e4 100644
--- a/plotly/graph_objs/_figure.py
+++ b/plotly/graph_objs/_figure.py
@@ -721,6 +721,7 @@ class Figure(BaseFigure):
         ysrc=None,
         row=None,
         col=None,
+        secondary_y=None,
         **kwargs
     ):
         """
@@ -971,6 +972,17 @@ class Figure(BaseFigure):
             Subplot col index (starting from 1) for the trace to be
             added. Only valid if figure was created using
             `plotly.tools.make_subplots`
+        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
+            following conditions are satisfied:
+              * The figure was created using `plotly.subplots.make_subplots`.
+              * The row and col arguments are not None
+              * The subplot at the specified row and col has type xy
+                (which is the default) and secondary_y True.  These
+                properties are specified in the specs argument to
+                make_subplots. See the make_subplots docstring for more info.                
+
 
         Returns
         -------
@@ -1038,7 +1050,9 @@ class Figure(BaseFigure):
             ysrc=ysrc,
             **kwargs
         )
-        return self.add_trace(new_trace, row=row, col=col)
+        return self.add_trace(
+            new_trace, row=row, col=col, secondary_y=secondary_y
+        )
 
     def add_barpolar(
         self,
@@ -1365,6 +1379,7 @@ class Figure(BaseFigure):
         ysrc=None,
         row=None,
         col=None,
+        secondary_y=None,
         **kwargs
     ):
         """
@@ -1600,6 +1615,17 @@ class Figure(BaseFigure):
             Subplot col index (starting from 1) for the trace to be
             added. Only valid if figure was created using
             `plotly.tools.make_subplots`
+        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
+            following conditions are satisfied:
+              * The figure was created using `plotly.subplots.make_subplots`.
+              * The row and col arguments are not None
+              * The subplot at the specified row and col has type xy
+                (which is the default) and secondary_y True.  These
+                properties are specified in the specs argument to
+                make_subplots. See the make_subplots docstring for more info.                
+
 
         Returns
         -------
@@ -1657,7 +1683,9 @@ class Figure(BaseFigure):
             ysrc=ysrc,
             **kwargs
         )
-        return self.add_trace(new_trace, row=row, col=col)
+        return self.add_trace(
+            new_trace, row=row, col=col, secondary_y=secondary_y
+        )
 
     def add_candlestick(
         self,
@@ -1700,6 +1728,7 @@ class Figure(BaseFigure):
         yaxis=None,
         row=None,
         col=None,
+        secondary_y=None,
         **kwargs
     ):
         """
@@ -1853,6 +1882,17 @@ class Figure(BaseFigure):
             Subplot col index (starting from 1) for the trace to be
             added. Only valid if figure was created using
             `plotly.tools.make_subplots`
+        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
+            following conditions are satisfied:
+              * The figure was created using `plotly.subplots.make_subplots`.
+              * The row and col arguments are not None
+              * The subplot at the specified row and col has type xy
+                (which is the default) and secondary_y True.  These
+                properties are specified in the specs argument to
+                make_subplots. See the make_subplots docstring for more info.                
+
 
         Returns
         -------
@@ -1898,7 +1938,9 @@ class Figure(BaseFigure):
             yaxis=yaxis,
             **kwargs
         )
-        return self.add_trace(new_trace, row=row, col=col)
+        return self.add_trace(
+            new_trace, row=row, col=col, secondary_y=secondary_y
+        )
 
     def add_carpet(
         self,
@@ -1937,6 +1979,7 @@ class Figure(BaseFigure):
         ysrc=None,
         row=None,
         col=None,
+        secondary_y=None,
         **kwargs
     ):
         """
@@ -2081,6 +2124,17 @@ class Figure(BaseFigure):
             Subplot col index (starting from 1) for the trace to be
             added. Only valid if figure was created using
             `plotly.tools.make_subplots`
+        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
+            following conditions are satisfied:
+              * The figure was created using `plotly.subplots.make_subplots`.
+              * The row and col arguments are not None
+              * The subplot at the specified row and col has type xy
+                (which is the default) and secondary_y True.  These
+                properties are specified in the specs argument to
+                make_subplots. See the make_subplots docstring for more info.                
+
 
         Returns
         -------
@@ -2122,7 +2176,9 @@ class Figure(BaseFigure):
             ysrc=ysrc,
             **kwargs
         )
-        return self.add_trace(new_trace, row=row, col=col)
+        return self.add_trace(
+            new_trace, row=row, col=col, secondary_y=secondary_y
+        )
 
     def add_choropleth(
         self,
@@ -2785,6 +2841,7 @@ class Figure(BaseFigure):
         zsrc=None,
         row=None,
         col=None,
+        secondary_y=None,
         **kwargs
     ):
         """
@@ -3031,6 +3088,17 @@ class Figure(BaseFigure):
             Subplot col index (starting from 1) for the trace to be
             added. Only valid if figure was created using
             `plotly.tools.make_subplots`
+        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
+            following conditions are satisfied:
+              * The figure was created using `plotly.subplots.make_subplots`.
+              * The row and col arguments are not None
+              * The subplot at the specified row and col has type xy
+                (which is the default) and secondary_y True.  These
+                properties are specified in the specs argument to
+                make_subplots. See the make_subplots docstring for more info.                
+
 
         Returns
         -------
@@ -3093,7 +3161,9 @@ class Figure(BaseFigure):
             zsrc=zsrc,
             **kwargs
         )
-        return self.add_trace(new_trace, row=row, col=col)
+        return self.add_trace(
+            new_trace, row=row, col=col, secondary_y=secondary_y
+        )
 
     def add_contourcarpet(
         self,
@@ -3148,6 +3218,7 @@ class Figure(BaseFigure):
         zsrc=None,
         row=None,
         col=None,
+        secondary_y=None,
         **kwargs
     ):
         """
@@ -3360,6 +3431,17 @@ class Figure(BaseFigure):
             Subplot col index (starting from 1) for the trace to be
             added. Only valid if figure was created using
             `plotly.tools.make_subplots`
+        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
+            following conditions are satisfied:
+              * The figure was created using `plotly.subplots.make_subplots`.
+              * The row and col arguments are not None
+              * The subplot at the specified row and col has type xy
+                (which is the default) and secondary_y True.  These
+                properties are specified in the specs argument to
+                make_subplots. See the make_subplots docstring for more info.                
+
 
         Returns
         -------
@@ -3417,7 +3499,9 @@ class Figure(BaseFigure):
             zsrc=zsrc,
             **kwargs
         )
-        return self.add_trace(new_trace, row=row, col=col)
+        return self.add_trace(
+            new_trace, row=row, col=col, secondary_y=secondary_y
+        )
 
     def add_heatmap(
         self,
@@ -3473,6 +3557,7 @@ class Figure(BaseFigure):
         zsrc=None,
         row=None,
         col=None,
+        secondary_y=None,
         **kwargs
     ):
         """
@@ -3706,6 +3791,17 @@ class Figure(BaseFigure):
             Subplot col index (starting from 1) for the trace to be
             added. Only valid if figure was created using
             `plotly.tools.make_subplots`
+        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
+            following conditions are satisfied:
+              * The figure was created using `plotly.subplots.make_subplots`.
+              * The row and col arguments are not None
+              * The subplot at the specified row and col has type xy
+                (which is the default) and secondary_y True.  These
+                properties are specified in the specs argument to
+                make_subplots. See the make_subplots docstring for more info.                
+
 
         Returns
         -------
@@ -3764,7 +3860,9 @@ class Figure(BaseFigure):
             zsrc=zsrc,
             **kwargs
         )
-        return self.add_trace(new_trace, row=row, col=col)
+        return self.add_trace(
+            new_trace, row=row, col=col, secondary_y=secondary_y
+        )
 
     def add_heatmapgl(
         self,
@@ -3809,6 +3907,7 @@ class Figure(BaseFigure):
         zsrc=None,
         row=None,
         col=None,
+        secondary_y=None,
         **kwargs
     ):
         """
@@ -3983,6 +4082,17 @@ class Figure(BaseFigure):
             Subplot col index (starting from 1) for the trace to be
             added. Only valid if figure was created using
             `plotly.tools.make_subplots`
+        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
+            following conditions are satisfied:
+              * The figure was created using `plotly.subplots.make_subplots`.
+              * The row and col arguments are not None
+              * The subplot at the specified row and col has type xy
+                (which is the default) and secondary_y True.  These
+                properties are specified in the specs argument to
+                make_subplots. See the make_subplots docstring for more info.                
+
 
         Returns
         -------
@@ -4030,7 +4140,9 @@ class Figure(BaseFigure):
             zsrc=zsrc,
             **kwargs
         )
-        return self.add_trace(new_trace, row=row, col=col)
+        return self.add_trace(
+            new_trace, row=row, col=col, secondary_y=secondary_y
+        )
 
     def add_histogram(
         self,
@@ -4083,6 +4195,7 @@ class Figure(BaseFigure):
         ysrc=None,
         row=None,
         col=None,
+        secondary_y=None,
         **kwargs
     ):
         """
@@ -4317,6 +4430,17 @@ class Figure(BaseFigure):
             Subplot col index (starting from 1) for the trace to be
             added. Only valid if figure was created using
             `plotly.tools.make_subplots`
+        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
+            following conditions are satisfied:
+              * The figure was created using `plotly.subplots.make_subplots`.
+              * The row and col arguments are not None
+              * The subplot at the specified row and col has type xy
+                (which is the default) and secondary_y True.  These
+                properties are specified in the specs argument to
+                make_subplots. See the make_subplots docstring for more info.                
+
 
         Returns
         -------
@@ -4372,7 +4496,9 @@ class Figure(BaseFigure):
             ysrc=ysrc,
             **kwargs
         )
-        return self.add_trace(new_trace, row=row, col=col)
+        return self.add_trace(
+            new_trace, row=row, col=col, secondary_y=secondary_y
+        )
 
     def add_histogram2d(
         self,
@@ -4425,6 +4551,7 @@ class Figure(BaseFigure):
         zsrc=None,
         row=None,
         col=None,
+        secondary_y=None,
         **kwargs
     ):
         """
@@ -4669,6 +4796,17 @@ class Figure(BaseFigure):
             Subplot col index (starting from 1) for the trace to be
             added. Only valid if figure was created using
             `plotly.tools.make_subplots`
+        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
+            following conditions are satisfied:
+              * The figure was created using `plotly.subplots.make_subplots`.
+              * The row and col arguments are not None
+              * The subplot at the specified row and col has type xy
+                (which is the default) and secondary_y True.  These
+                properties are specified in the specs argument to
+                make_subplots. See the make_subplots docstring for more info.                
+
 
         Returns
         -------
@@ -4724,7 +4862,9 @@ class Figure(BaseFigure):
             zsrc=zsrc,
             **kwargs
         )
-        return self.add_trace(new_trace, row=row, col=col)
+        return self.add_trace(
+            new_trace, row=row, col=col, secondary_y=secondary_y
+        )
 
     def add_histogram2dcontour(
         self,
@@ -4780,6 +4920,7 @@ class Figure(BaseFigure):
         zsrc=None,
         row=None,
         col=None,
+        secondary_y=None,
         **kwargs
     ):
         """
@@ -5042,6 +5183,17 @@ class Figure(BaseFigure):
             Subplot col index (starting from 1) for the trace to be
             added. Only valid if figure was created using
             `plotly.tools.make_subplots`
+        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
+            following conditions are satisfied:
+              * The figure was created using `plotly.subplots.make_subplots`.
+              * The row and col arguments are not None
+              * The subplot at the specified row and col has type xy
+                (which is the default) and secondary_y True.  These
+                properties are specified in the specs argument to
+                make_subplots. See the make_subplots docstring for more info.                
+
 
         Returns
         -------
@@ -5100,7 +5252,9 @@ class Figure(BaseFigure):
             zsrc=zsrc,
             **kwargs
         )
-        return self.add_trace(new_trace, row=row, col=col)
+        return self.add_trace(
+            new_trace, row=row, col=col, secondary_y=secondary_y
+        )
 
     def add_isosurface(
         self,
@@ -5875,6 +6029,7 @@ class Figure(BaseFigure):
         yaxis=None,
         row=None,
         col=None,
+        secondary_y=None,
         **kwargs
     ):
         """
@@ -6027,6 +6182,17 @@ class Figure(BaseFigure):
             Subplot col index (starting from 1) for the trace to be
             added. Only valid if figure was created using
             `plotly.tools.make_subplots`
+        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
+            following conditions are satisfied:
+              * The figure was created using `plotly.subplots.make_subplots`.
+              * The row and col arguments are not None
+              * The subplot at the specified row and col has type xy
+                (which is the default) and secondary_y True.  These
+                properties are specified in the specs argument to
+                make_subplots. See the make_subplots docstring for more info.                
+
 
         Returns
         -------
@@ -6072,7 +6238,9 @@ class Figure(BaseFigure):
             yaxis=yaxis,
             **kwargs
         )
-        return self.add_trace(new_trace, row=row, col=col)
+        return self.add_trace(
+            new_trace, row=row, col=col, secondary_y=secondary_y
+        )
 
     def add_parcats(
         self,
@@ -6717,6 +6885,7 @@ class Figure(BaseFigure):
         ysrc=None,
         row=None,
         col=None,
+        secondary_y=None,
         **kwargs
     ):
         """
@@ -6863,6 +7032,17 @@ class Figure(BaseFigure):
             Subplot col index (starting from 1) for the trace to be
             added. Only valid if figure was created using
             `plotly.tools.make_subplots`
+        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
+            following conditions are satisfied:
+              * The figure was created using `plotly.subplots.make_subplots`.
+              * The row and col arguments are not None
+              * The subplot at the specified row and col has type xy
+                (which is the default) and secondary_y True.  These
+                properties are specified in the specs argument to
+                make_subplots. See the make_subplots docstring for more info.                
+
 
         Returns
         -------
@@ -6903,7 +7083,9 @@ class Figure(BaseFigure):
             ysrc=ysrc,
             **kwargs
         )
-        return self.add_trace(new_trace, row=row, col=col)
+        return self.add_trace(
+            new_trace, row=row, col=col, secondary_y=secondary_y
+        )
 
     def add_sankey(
         self,
@@ -7131,6 +7313,7 @@ class Figure(BaseFigure):
         ysrc=None,
         row=None,
         col=None,
+        secondary_y=None,
         **kwargs
     ):
         """
@@ -7428,6 +7611,17 @@ class Figure(BaseFigure):
             Subplot col index (starting from 1) for the trace to be
             added. Only valid if figure was created using
             `plotly.tools.make_subplots`
+        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
+            following conditions are satisfied:
+              * The figure was created using `plotly.subplots.make_subplots`.
+              * The row and col arguments are not None
+              * The subplot at the specified row and col has type xy
+                (which is the default) and secondary_y True.  These
+                properties are specified in the specs argument to
+                make_subplots. See the make_subplots docstring for more info.                
+
 
         Returns
         -------
@@ -7493,7 +7687,9 @@ class Figure(BaseFigure):
             ysrc=ysrc,
             **kwargs
         )
-        return self.add_trace(new_trace, row=row, col=col)
+        return self.add_trace(
+            new_trace, row=row, col=col, secondary_y=secondary_y
+        )
 
     def add_scatter3d(
         self,
@@ -7836,6 +8032,7 @@ class Figure(BaseFigure):
         yaxis=None,
         row=None,
         col=None,
+        secondary_y=None,
         **kwargs
     ):
         """
@@ -8038,6 +8235,17 @@ class Figure(BaseFigure):
             Subplot col index (starting from 1) for the trace to be
             added. Only valid if figure was created using
             `plotly.tools.make_subplots`
+        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
+            following conditions are satisfied:
+              * The figure was created using `plotly.subplots.make_subplots`.
+              * The row and col arguments are not None
+              * The subplot at the specified row and col has type xy
+                (which is the default) and secondary_y True.  These
+                properties are specified in the specs argument to
+                make_subplots. See the make_subplots docstring for more info.                
+
 
         Returns
         -------
@@ -8087,7 +8295,9 @@ class Figure(BaseFigure):
             yaxis=yaxis,
             **kwargs
         )
-        return self.add_trace(new_trace, row=row, col=col)
+        return self.add_trace(
+            new_trace, row=row, col=col, secondary_y=secondary_y
+        )
 
     def add_scattergeo(
         self,
@@ -8431,6 +8641,7 @@ class Figure(BaseFigure):
         ysrc=None,
         row=None,
         col=None,
+        secondary_y=None,
         **kwargs
     ):
         """
@@ -8655,6 +8866,17 @@ class Figure(BaseFigure):
             Subplot col index (starting from 1) for the trace to be
             added. Only valid if figure was created using
             `plotly.tools.make_subplots`
+        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
+            following conditions are satisfied:
+              * The figure was created using `plotly.subplots.make_subplots`.
+              * The row and col arguments are not None
+              * The subplot at the specified row and col has type xy
+                (which is the default) and secondary_y True.  These
+                properties are specified in the specs argument to
+                make_subplots. See the make_subplots docstring for more info.                
+
 
         Returns
         -------
@@ -8710,7 +8932,9 @@ class Figure(BaseFigure):
             ysrc=ysrc,
             **kwargs
         )
-        return self.add_trace(new_trace, row=row, col=col)
+        return self.add_trace(
+            new_trace, row=row, col=col, secondary_y=secondary_y
+        )
 
     def add_scattermapbox(
         self,
@@ -11289,6 +11513,7 @@ class Figure(BaseFigure):
         ysrc=None,
         row=None,
         col=None,
+        secondary_y=None,
         **kwargs
     ):
         """
@@ -11548,6 +11773,17 @@ class Figure(BaseFigure):
             Subplot col index (starting from 1) for the trace to be
             added. Only valid if figure was created using
             `plotly.tools.make_subplots`
+        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
+            following conditions are satisfied:
+              * The figure was created using `plotly.subplots.make_subplots`.
+              * The row and col arguments are not None
+              * The subplot at the specified row and col has type xy
+                (which is the default) and secondary_y True.  These
+                properties are specified in the specs argument to
+                make_subplots. See the make_subplots docstring for more info.                
+
 
         Returns
         -------
@@ -11607,7 +11843,9 @@ class Figure(BaseFigure):
             ysrc=ysrc,
             **kwargs
         )
-        return self.add_trace(new_trace, row=row, col=col)
+        return self.add_trace(
+            new_trace, row=row, col=col, secondary_y=secondary_y
+        )
 
     def add_volume(
         self,
@@ -12003,6 +12241,7 @@ class Figure(BaseFigure):
         ysrc=None,
         row=None,
         col=None,
+        secondary_y=None,
         **kwargs
     ):
         """
@@ -12239,6 +12478,17 @@ class Figure(BaseFigure):
             Subplot col index (starting from 1) for the trace to be
             added. Only valid if figure was created using
             `plotly.tools.make_subplots`
+        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
+            following conditions are satisfied:
+              * The figure was created using `plotly.subplots.make_subplots`.
+              * The row and col arguments are not None
+              * The subplot at the specified row and col has type xy
+                (which is the default) and secondary_y True.  These
+                properties are specified in the specs argument to
+                make_subplots. See the make_subplots docstring for more info.                
+
 
         Returns
         -------
@@ -12300,7 +12550,9 @@ class Figure(BaseFigure):
             ysrc=ysrc,
             **kwargs
         )
-        return self.add_trace(new_trace, row=row, col=col)
+        return self.add_trace(
+            new_trace, row=row, col=col, secondary_y=secondary_y
+        )
 
     def select_geos(self, selector=None, row=None, col=None):
         """
@@ -12321,7 +12573,6 @@ class Figure(BaseFigure):
             To select geo objects by row and column, the Figure
             must have been created using plotly.subplots.make_subplots.
             If None (the default), all geo objects are selected.
-
         Returns
         -------
         generator
@@ -12355,7 +12606,6 @@ class Figure(BaseFigure):
             To select geo objects by row and column, the Figure
             must have been created using plotly.subplots.make_subplots.
             If None (the default), all geo objects are selected.
-        
         Returns
         -------
         self
@@ -12423,7 +12673,6 @@ class Figure(BaseFigure):
             To select mapbox objects by row and column, the Figure
             must have been created using plotly.subplots.make_subplots.
             If None (the default), all mapbox objects are selected.
-
         Returns
         -------
         generator
@@ -12457,7 +12706,6 @@ class Figure(BaseFigure):
             To select mapbox objects by row and column, the Figure
             must have been created using plotly.subplots.make_subplots.
             If None (the default), all mapbox objects are selected.
-        
         Returns
         -------
         self
@@ -12525,7 +12773,6 @@ class Figure(BaseFigure):
             To select polar objects by row and column, the Figure
             must have been created using plotly.subplots.make_subplots.
             If None (the default), all polar objects are selected.
-
         Returns
         -------
         generator
@@ -12559,7 +12806,6 @@ class Figure(BaseFigure):
             To select polar objects by row and column, the Figure
             must have been created using plotly.subplots.make_subplots.
             If None (the default), all polar objects are selected.
-        
         Returns
         -------
         self
@@ -12627,7 +12873,6 @@ class Figure(BaseFigure):
             To select scene objects by row and column, the Figure
             must have been created using plotly.subplots.make_subplots.
             If None (the default), all scene objects are selected.
-
         Returns
         -------
         generator
@@ -12661,7 +12906,6 @@ class Figure(BaseFigure):
             To select scene objects by row and column, the Figure
             must have been created using plotly.subplots.make_subplots.
             If None (the default), all scene objects are selected.
-        
         Returns
         -------
         self
@@ -12729,7 +12973,6 @@ class Figure(BaseFigure):
             To select ternary objects by row and column, the Figure
             must have been created using plotly.subplots.make_subplots.
             If None (the default), all ternary objects are selected.
-
         Returns
         -------
         generator
@@ -12763,7 +13006,6 @@ class Figure(BaseFigure):
             To select ternary objects by row and column, the Figure
             must have been created using plotly.subplots.make_subplots.
             If None (the default), all ternary objects are selected.
-        
         Returns
         -------
         self
@@ -12831,7 +13073,6 @@ class Figure(BaseFigure):
             To select xaxis objects by row and column, the Figure
             must have been created using plotly.subplots.make_subplots.
             If None (the default), all xaxis objects are selected.
-
         Returns
         -------
         generator
@@ -12865,7 +13106,6 @@ class Figure(BaseFigure):
             To select xaxis objects by row and column, the Figure
             must have been created using plotly.subplots.make_subplots.
             If None (the default), all xaxis objects are selected.
-        
         Returns
         -------
         self
@@ -12914,7 +13154,9 @@ class Figure(BaseFigure):
 
         return self
 
-    def select_yaxes(self, selector=None, row=None, col=None):
+    def select_yaxes(
+        self, selector=None, row=None, col=None, secondary_y=None
+    ):
         """
         Select yaxis subplot objects from a particular subplot cell
         and/or yaxis subplot objects that satisfy custom selection
@@ -12933,7 +13175,18 @@ class Figure(BaseFigure):
             To select yaxis objects by row and column, the Figure
             must have been created using plotly.subplots.make_subplots.
             If None (the default), all yaxis objects are selected.
-
+        secondary_y: boolean or None (default None)
+            * If True, only select yaxis objects associated with the secondary
+              y-axis of the subplot.
+            * If False, only select yaxis objects associated with the primary
+              y-axis of the subplot.
+            * If None (the default), do not filter yaxis objects based on
+              a secondary y-axis condition. 
+            
+            To select yaxis objects by secondary y-axis, the Figure must
+            have been created using plotly.subplots.make_subplots. See
+            the docstring for the specs argument to make_subplots for more
+            info on creating subplots with secondary y-axes.
         Returns
         -------
         generator
@@ -12944,10 +13197,12 @@ class Figure(BaseFigure):
             _validate_v4_subplots('select_yaxes')
 
         return self._select_layout_subplots_by_prefix(
-            'yaxis', selector, row, col
+            'yaxis', selector, row, col, secondary_y=secondary_y
         )
 
-    def for_each_yaxis(self, fn, selector=None, row=None, col=None):
+    def for_each_yaxis(
+        self, fn, selector=None, row=None, col=None, secondary_y=None
+    ):
         """
         Apply a function to all yaxis objects that satisfy the
         specified selection criteria
@@ -12967,19 +13222,38 @@ class Figure(BaseFigure):
             To select yaxis objects by row and column, the Figure
             must have been created using plotly.subplots.make_subplots.
             If None (the default), all yaxis objects are selected.
-        
+        secondary_y: boolean or None (default None)
+            * If True, only select yaxis objects associated with the secondary
+              y-axis of the subplot.
+            * If False, only select yaxis objects associated with the primary
+              y-axis of the subplot.
+            * If None (the default), do not filter yaxis objects based on
+              a secondary y-axis condition. 
+            
+            To select yaxis objects by secondary y-axis, the Figure must
+            have been created using plotly.subplots.make_subplots. See
+            the docstring for the specs argument to make_subplots for more
+            info on creating subplots with secondary y-axes.
         Returns
         -------
         self
             Returns the Figure object that the method was called on
         """
-        for obj in self.select_yaxes(selector=selector, row=row, col=col):
+        for obj in self.select_yaxes(
+            selector=selector, row=row, col=col, secondary_y=secondary_y
+        ):
             fn(obj)
 
         return self
 
     def update_yaxes(
-        self, patch=None, selector=None, row=None, col=None, **kwargs
+        self,
+        patch=None,
+        selector=None,
+        row=None,
+        col=None,
+        secondary_y=None,
+        **kwargs
     ):
         """
         Perform a property update operation on all yaxis objects
@@ -13001,6 +13275,18 @@ class Figure(BaseFigure):
             To select yaxis objects by row and column, the Figure
             must have been created using plotly.subplots.make_subplots.
             If None (the default), all yaxis objects are selected.
+        secondary_y: boolean or None (default None)
+            * If True, only select yaxis objects associated with the secondary
+              y-axis of the subplot.
+            * If False, only select yaxis objects associated with the primary
+              y-axis of the subplot.
+            * If None (the default), do not filter yaxis objects based on
+              a secondary y-axis condition. 
+            
+            To select yaxis objects by secondary y-axis, the Figure must
+            have been created using plotly.subplots.make_subplots. See
+            the docstring for the specs argument to make_subplots for more
+            info on creating subplots with secondary y-axes.
         **kwargs
             Additional property updates to apply to each selected
             yaxis object. If a property is specified in
@@ -13011,7 +13297,9 @@ class Figure(BaseFigure):
         self
             Returns the Figure object that the method was called on
         """
-        for obj in self.select_yaxes(selector=selector, row=row, col=col):
+        for obj in self.select_yaxes(
+            selector=selector, row=row, col=col, secondary_y=secondary_y
+        ):
             obj.update(patch, **kwargs)
 
         return self
diff --git a/plotly/graph_objs/_figurewidget.py b/plotly/graph_objs/_figurewidget.py
index 71698ae07..66e20df3f 100644
--- a/plotly/graph_objs/_figurewidget.py
+++ b/plotly/graph_objs/_figurewidget.py
@@ -721,6 +721,7 @@ class FigureWidget(BaseFigureWidget):
         ysrc=None,
         row=None,
         col=None,
+        secondary_y=None,
         **kwargs
     ):
         """
@@ -971,6 +972,17 @@ class FigureWidget(BaseFigureWidget):
             Subplot col index (starting from 1) for the trace to be
             added. Only valid if figure was created using
             `plotly.tools.make_subplots`
+        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
+            following conditions are satisfied:
+              * The figure was created using `plotly.subplots.make_subplots`.
+              * The row and col arguments are not None
+              * The subplot at the specified row and col has type xy
+                (which is the default) and secondary_y True.  These
+                properties are specified in the specs argument to
+                make_subplots. See the make_subplots docstring for more info.                
+
 
         Returns
         -------
@@ -1038,7 +1050,9 @@ class FigureWidget(BaseFigureWidget):
             ysrc=ysrc,
             **kwargs
         )
-        return self.add_trace(new_trace, row=row, col=col)
+        return self.add_trace(
+            new_trace, row=row, col=col, secondary_y=secondary_y
+        )
 
     def add_barpolar(
         self,
@@ -1365,6 +1379,7 @@ class FigureWidget(BaseFigureWidget):
         ysrc=None,
         row=None,
         col=None,
+        secondary_y=None,
         **kwargs
     ):
         """
@@ -1600,6 +1615,17 @@ class FigureWidget(BaseFigureWidget):
             Subplot col index (starting from 1) for the trace to be
             added. Only valid if figure was created using
             `plotly.tools.make_subplots`
+        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
+            following conditions are satisfied:
+              * The figure was created using `plotly.subplots.make_subplots`.
+              * The row and col arguments are not None
+              * The subplot at the specified row and col has type xy
+                (which is the default) and secondary_y True.  These
+                properties are specified in the specs argument to
+                make_subplots. See the make_subplots docstring for more info.                
+
 
         Returns
         -------
@@ -1657,7 +1683,9 @@ class FigureWidget(BaseFigureWidget):
             ysrc=ysrc,
             **kwargs
         )
-        return self.add_trace(new_trace, row=row, col=col)
+        return self.add_trace(
+            new_trace, row=row, col=col, secondary_y=secondary_y
+        )
 
     def add_candlestick(
         self,
@@ -1700,6 +1728,7 @@ class FigureWidget(BaseFigureWidget):
         yaxis=None,
         row=None,
         col=None,
+        secondary_y=None,
         **kwargs
     ):
         """
@@ -1853,6 +1882,17 @@ class FigureWidget(BaseFigureWidget):
             Subplot col index (starting from 1) for the trace to be
             added. Only valid if figure was created using
             `plotly.tools.make_subplots`
+        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
+            following conditions are satisfied:
+              * The figure was created using `plotly.subplots.make_subplots`.
+              * The row and col arguments are not None
+              * The subplot at the specified row and col has type xy
+                (which is the default) and secondary_y True.  These
+                properties are specified in the specs argument to
+                make_subplots. See the make_subplots docstring for more info.                
+
 
         Returns
         -------
@@ -1898,7 +1938,9 @@ class FigureWidget(BaseFigureWidget):
             yaxis=yaxis,
             **kwargs
         )
-        return self.add_trace(new_trace, row=row, col=col)
+        return self.add_trace(
+            new_trace, row=row, col=col, secondary_y=secondary_y
+        )
 
     def add_carpet(
         self,
@@ -1937,6 +1979,7 @@ class FigureWidget(BaseFigureWidget):
         ysrc=None,
         row=None,
         col=None,
+        secondary_y=None,
         **kwargs
     ):
         """
@@ -2081,6 +2124,17 @@ class FigureWidget(BaseFigureWidget):
             Subplot col index (starting from 1) for the trace to be
             added. Only valid if figure was created using
             `plotly.tools.make_subplots`
+        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
+            following conditions are satisfied:
+              * The figure was created using `plotly.subplots.make_subplots`.
+              * The row and col arguments are not None
+              * The subplot at the specified row and col has type xy
+                (which is the default) and secondary_y True.  These
+                properties are specified in the specs argument to
+                make_subplots. See the make_subplots docstring for more info.                
+
 
         Returns
         -------
@@ -2122,7 +2176,9 @@ class FigureWidget(BaseFigureWidget):
             ysrc=ysrc,
             **kwargs
         )
-        return self.add_trace(new_trace, row=row, col=col)
+        return self.add_trace(
+            new_trace, row=row, col=col, secondary_y=secondary_y
+        )
 
     def add_choropleth(
         self,
@@ -2785,6 +2841,7 @@ class FigureWidget(BaseFigureWidget):
         zsrc=None,
         row=None,
         col=None,
+        secondary_y=None,
         **kwargs
     ):
         """
@@ -3031,6 +3088,17 @@ class FigureWidget(BaseFigureWidget):
             Subplot col index (starting from 1) for the trace to be
             added. Only valid if figure was created using
             `plotly.tools.make_subplots`
+        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
+            following conditions are satisfied:
+              * The figure was created using `plotly.subplots.make_subplots`.
+              * The row and col arguments are not None
+              * The subplot at the specified row and col has type xy
+                (which is the default) and secondary_y True.  These
+                properties are specified in the specs argument to
+                make_subplots. See the make_subplots docstring for more info.                
+
 
         Returns
         -------
@@ -3093,7 +3161,9 @@ class FigureWidget(BaseFigureWidget):
             zsrc=zsrc,
             **kwargs
         )
-        return self.add_trace(new_trace, row=row, col=col)
+        return self.add_trace(
+            new_trace, row=row, col=col, secondary_y=secondary_y
+        )
 
     def add_contourcarpet(
         self,
@@ -3148,6 +3218,7 @@ class FigureWidget(BaseFigureWidget):
         zsrc=None,
         row=None,
         col=None,
+        secondary_y=None,
         **kwargs
     ):
         """
@@ -3360,6 +3431,17 @@ class FigureWidget(BaseFigureWidget):
             Subplot col index (starting from 1) for the trace to be
             added. Only valid if figure was created using
             `plotly.tools.make_subplots`
+        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
+            following conditions are satisfied:
+              * The figure was created using `plotly.subplots.make_subplots`.
+              * The row and col arguments are not None
+              * The subplot at the specified row and col has type xy
+                (which is the default) and secondary_y True.  These
+                properties are specified in the specs argument to
+                make_subplots. See the make_subplots docstring for more info.                
+
 
         Returns
         -------
@@ -3417,7 +3499,9 @@ class FigureWidget(BaseFigureWidget):
             zsrc=zsrc,
             **kwargs
         )
-        return self.add_trace(new_trace, row=row, col=col)
+        return self.add_trace(
+            new_trace, row=row, col=col, secondary_y=secondary_y
+        )
 
     def add_heatmap(
         self,
@@ -3473,6 +3557,7 @@ class FigureWidget(BaseFigureWidget):
         zsrc=None,
         row=None,
         col=None,
+        secondary_y=None,
         **kwargs
     ):
         """
@@ -3706,6 +3791,17 @@ class FigureWidget(BaseFigureWidget):
             Subplot col index (starting from 1) for the trace to be
             added. Only valid if figure was created using
             `plotly.tools.make_subplots`
+        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
+            following conditions are satisfied:
+              * The figure was created using `plotly.subplots.make_subplots`.
+              * The row and col arguments are not None
+              * The subplot at the specified row and col has type xy
+                (which is the default) and secondary_y True.  These
+                properties are specified in the specs argument to
+                make_subplots. See the make_subplots docstring for more info.                
+
 
         Returns
         -------
@@ -3764,7 +3860,9 @@ class FigureWidget(BaseFigureWidget):
             zsrc=zsrc,
             **kwargs
         )
-        return self.add_trace(new_trace, row=row, col=col)
+        return self.add_trace(
+            new_trace, row=row, col=col, secondary_y=secondary_y
+        )
 
     def add_heatmapgl(
         self,
@@ -3809,6 +3907,7 @@ class FigureWidget(BaseFigureWidget):
         zsrc=None,
         row=None,
         col=None,
+        secondary_y=None,
         **kwargs
     ):
         """
@@ -3983,6 +4082,17 @@ class FigureWidget(BaseFigureWidget):
             Subplot col index (starting from 1) for the trace to be
             added. Only valid if figure was created using
             `plotly.tools.make_subplots`
+        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
+            following conditions are satisfied:
+              * The figure was created using `plotly.subplots.make_subplots`.
+              * The row and col arguments are not None
+              * The subplot at the specified row and col has type xy
+                (which is the default) and secondary_y True.  These
+                properties are specified in the specs argument to
+                make_subplots. See the make_subplots docstring for more info.                
+
 
         Returns
         -------
@@ -4030,7 +4140,9 @@ class FigureWidget(BaseFigureWidget):
             zsrc=zsrc,
             **kwargs
         )
-        return self.add_trace(new_trace, row=row, col=col)
+        return self.add_trace(
+            new_trace, row=row, col=col, secondary_y=secondary_y
+        )
 
     def add_histogram(
         self,
@@ -4083,6 +4195,7 @@ class FigureWidget(BaseFigureWidget):
         ysrc=None,
         row=None,
         col=None,
+        secondary_y=None,
         **kwargs
     ):
         """
@@ -4317,6 +4430,17 @@ class FigureWidget(BaseFigureWidget):
             Subplot col index (starting from 1) for the trace to be
             added. Only valid if figure was created using
             `plotly.tools.make_subplots`
+        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
+            following conditions are satisfied:
+              * The figure was created using `plotly.subplots.make_subplots`.
+              * The row and col arguments are not None
+              * The subplot at the specified row and col has type xy
+                (which is the default) and secondary_y True.  These
+                properties are specified in the specs argument to
+                make_subplots. See the make_subplots docstring for more info.                
+
 
         Returns
         -------
@@ -4372,7 +4496,9 @@ class FigureWidget(BaseFigureWidget):
             ysrc=ysrc,
             **kwargs
         )
-        return self.add_trace(new_trace, row=row, col=col)
+        return self.add_trace(
+            new_trace, row=row, col=col, secondary_y=secondary_y
+        )
 
     def add_histogram2d(
         self,
@@ -4425,6 +4551,7 @@ class FigureWidget(BaseFigureWidget):
         zsrc=None,
         row=None,
         col=None,
+        secondary_y=None,
         **kwargs
     ):
         """
@@ -4669,6 +4796,17 @@ class FigureWidget(BaseFigureWidget):
             Subplot col index (starting from 1) for the trace to be
             added. Only valid if figure was created using
             `plotly.tools.make_subplots`
+        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
+            following conditions are satisfied:
+              * The figure was created using `plotly.subplots.make_subplots`.
+              * The row and col arguments are not None
+              * The subplot at the specified row and col has type xy
+                (which is the default) and secondary_y True.  These
+                properties are specified in the specs argument to
+                make_subplots. See the make_subplots docstring for more info.                
+
 
         Returns
         -------
@@ -4724,7 +4862,9 @@ class FigureWidget(BaseFigureWidget):
             zsrc=zsrc,
             **kwargs
         )
-        return self.add_trace(new_trace, row=row, col=col)
+        return self.add_trace(
+            new_trace, row=row, col=col, secondary_y=secondary_y
+        )
 
     def add_histogram2dcontour(
         self,
@@ -4780,6 +4920,7 @@ class FigureWidget(BaseFigureWidget):
         zsrc=None,
         row=None,
         col=None,
+        secondary_y=None,
         **kwargs
     ):
         """
@@ -5042,6 +5183,17 @@ class FigureWidget(BaseFigureWidget):
             Subplot col index (starting from 1) for the trace to be
             added. Only valid if figure was created using
             `plotly.tools.make_subplots`
+        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
+            following conditions are satisfied:
+              * The figure was created using `plotly.subplots.make_subplots`.
+              * The row and col arguments are not None
+              * The subplot at the specified row and col has type xy
+                (which is the default) and secondary_y True.  These
+                properties are specified in the specs argument to
+                make_subplots. See the make_subplots docstring for more info.                
+
 
         Returns
         -------
@@ -5100,7 +5252,9 @@ class FigureWidget(BaseFigureWidget):
             zsrc=zsrc,
             **kwargs
         )
-        return self.add_trace(new_trace, row=row, col=col)
+        return self.add_trace(
+            new_trace, row=row, col=col, secondary_y=secondary_y
+        )
 
     def add_isosurface(
         self,
@@ -5875,6 +6029,7 @@ class FigureWidget(BaseFigureWidget):
         yaxis=None,
         row=None,
         col=None,
+        secondary_y=None,
         **kwargs
     ):
         """
@@ -6027,6 +6182,17 @@ class FigureWidget(BaseFigureWidget):
             Subplot col index (starting from 1) for the trace to be
             added. Only valid if figure was created using
             `plotly.tools.make_subplots`
+        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
+            following conditions are satisfied:
+              * The figure was created using `plotly.subplots.make_subplots`.
+              * The row and col arguments are not None
+              * The subplot at the specified row and col has type xy
+                (which is the default) and secondary_y True.  These
+                properties are specified in the specs argument to
+                make_subplots. See the make_subplots docstring for more info.                
+
 
         Returns
         -------
@@ -6072,7 +6238,9 @@ class FigureWidget(BaseFigureWidget):
             yaxis=yaxis,
             **kwargs
         )
-        return self.add_trace(new_trace, row=row, col=col)
+        return self.add_trace(
+            new_trace, row=row, col=col, secondary_y=secondary_y
+        )
 
     def add_parcats(
         self,
@@ -6717,6 +6885,7 @@ class FigureWidget(BaseFigureWidget):
         ysrc=None,
         row=None,
         col=None,
+        secondary_y=None,
         **kwargs
     ):
         """
@@ -6863,6 +7032,17 @@ class FigureWidget(BaseFigureWidget):
             Subplot col index (starting from 1) for the trace to be
             added. Only valid if figure was created using
             `plotly.tools.make_subplots`
+        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
+            following conditions are satisfied:
+              * The figure was created using `plotly.subplots.make_subplots`.
+              * The row and col arguments are not None
+              * The subplot at the specified row and col has type xy
+                (which is the default) and secondary_y True.  These
+                properties are specified in the specs argument to
+                make_subplots. See the make_subplots docstring for more info.                
+
 
         Returns
         -------
@@ -6903,7 +7083,9 @@ class FigureWidget(BaseFigureWidget):
             ysrc=ysrc,
             **kwargs
         )
-        return self.add_trace(new_trace, row=row, col=col)
+        return self.add_trace(
+            new_trace, row=row, col=col, secondary_y=secondary_y
+        )
 
     def add_sankey(
         self,
@@ -7131,6 +7313,7 @@ class FigureWidget(BaseFigureWidget):
         ysrc=None,
         row=None,
         col=None,
+        secondary_y=None,
         **kwargs
     ):
         """
@@ -7428,6 +7611,17 @@ class FigureWidget(BaseFigureWidget):
             Subplot col index (starting from 1) for the trace to be
             added. Only valid if figure was created using
             `plotly.tools.make_subplots`
+        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
+            following conditions are satisfied:
+              * The figure was created using `plotly.subplots.make_subplots`.
+              * The row and col arguments are not None
+              * The subplot at the specified row and col has type xy
+                (which is the default) and secondary_y True.  These
+                properties are specified in the specs argument to
+                make_subplots. See the make_subplots docstring for more info.                
+
 
         Returns
         -------
@@ -7493,7 +7687,9 @@ class FigureWidget(BaseFigureWidget):
             ysrc=ysrc,
             **kwargs
         )
-        return self.add_trace(new_trace, row=row, col=col)
+        return self.add_trace(
+            new_trace, row=row, col=col, secondary_y=secondary_y
+        )
 
     def add_scatter3d(
         self,
@@ -7836,6 +8032,7 @@ class FigureWidget(BaseFigureWidget):
         yaxis=None,
         row=None,
         col=None,
+        secondary_y=None,
         **kwargs
     ):
         """
@@ -8038,6 +8235,17 @@ class FigureWidget(BaseFigureWidget):
             Subplot col index (starting from 1) for the trace to be
             added. Only valid if figure was created using
             `plotly.tools.make_subplots`
+        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
+            following conditions are satisfied:
+              * The figure was created using `plotly.subplots.make_subplots`.
+              * The row and col arguments are not None
+              * The subplot at the specified row and col has type xy
+                (which is the default) and secondary_y True.  These
+                properties are specified in the specs argument to
+                make_subplots. See the make_subplots docstring for more info.                
+
 
         Returns
         -------
@@ -8087,7 +8295,9 @@ class FigureWidget(BaseFigureWidget):
             yaxis=yaxis,
             **kwargs
         )
-        return self.add_trace(new_trace, row=row, col=col)
+        return self.add_trace(
+            new_trace, row=row, col=col, secondary_y=secondary_y
+        )
 
     def add_scattergeo(
         self,
@@ -8431,6 +8641,7 @@ class FigureWidget(BaseFigureWidget):
         ysrc=None,
         row=None,
         col=None,
+        secondary_y=None,
         **kwargs
     ):
         """
@@ -8655,6 +8866,17 @@ class FigureWidget(BaseFigureWidget):
             Subplot col index (starting from 1) for the trace to be
             added. Only valid if figure was created using
             `plotly.tools.make_subplots`
+        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
+            following conditions are satisfied:
+              * The figure was created using `plotly.subplots.make_subplots`.
+              * The row and col arguments are not None
+              * The subplot at the specified row and col has type xy
+                (which is the default) and secondary_y True.  These
+                properties are specified in the specs argument to
+                make_subplots. See the make_subplots docstring for more info.                
+
 
         Returns
         -------
@@ -8710,7 +8932,9 @@ class FigureWidget(BaseFigureWidget):
             ysrc=ysrc,
             **kwargs
         )
-        return self.add_trace(new_trace, row=row, col=col)
+        return self.add_trace(
+            new_trace, row=row, col=col, secondary_y=secondary_y
+        )
 
     def add_scattermapbox(
         self,
@@ -11289,6 +11513,7 @@ class FigureWidget(BaseFigureWidget):
         ysrc=None,
         row=None,
         col=None,
+        secondary_y=None,
         **kwargs
     ):
         """
@@ -11548,6 +11773,17 @@ class FigureWidget(BaseFigureWidget):
             Subplot col index (starting from 1) for the trace to be
             added. Only valid if figure was created using
             `plotly.tools.make_subplots`
+        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
+            following conditions are satisfied:
+              * The figure was created using `plotly.subplots.make_subplots`.
+              * The row and col arguments are not None
+              * The subplot at the specified row and col has type xy
+                (which is the default) and secondary_y True.  These
+                properties are specified in the specs argument to
+                make_subplots. See the make_subplots docstring for more info.                
+
 
         Returns
         -------
@@ -11607,7 +11843,9 @@ class FigureWidget(BaseFigureWidget):
             ysrc=ysrc,
             **kwargs
         )
-        return self.add_trace(new_trace, row=row, col=col)
+        return self.add_trace(
+            new_trace, row=row, col=col, secondary_y=secondary_y
+        )
 
     def add_volume(
         self,
@@ -12003,6 +12241,7 @@ class FigureWidget(BaseFigureWidget):
         ysrc=None,
         row=None,
         col=None,
+        secondary_y=None,
         **kwargs
     ):
         """
@@ -12239,6 +12478,17 @@ class FigureWidget(BaseFigureWidget):
             Subplot col index (starting from 1) for the trace to be
             added. Only valid if figure was created using
             `plotly.tools.make_subplots`
+        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
+            following conditions are satisfied:
+              * The figure was created using `plotly.subplots.make_subplots`.
+              * The row and col arguments are not None
+              * The subplot at the specified row and col has type xy
+                (which is the default) and secondary_y True.  These
+                properties are specified in the specs argument to
+                make_subplots. See the make_subplots docstring for more info.                
+
 
         Returns
         -------
@@ -12300,7 +12550,9 @@ class FigureWidget(BaseFigureWidget):
             ysrc=ysrc,
             **kwargs
         )
-        return self.add_trace(new_trace, row=row, col=col)
+        return self.add_trace(
+            new_trace, row=row, col=col, secondary_y=secondary_y
+        )
 
     def select_geos(self, selector=None, row=None, col=None):
         """
@@ -12321,7 +12573,6 @@ class FigureWidget(BaseFigureWidget):
             To select geo objects by row and column, the Figure
             must have been created using plotly.subplots.make_subplots.
             If None (the default), all geo objects are selected.
-
         Returns
         -------
         generator
@@ -12355,7 +12606,6 @@ class FigureWidget(BaseFigureWidget):
             To select geo objects by row and column, the Figure
             must have been created using plotly.subplots.make_subplots.
             If None (the default), all geo objects are selected.
-        
         Returns
         -------
         self
@@ -12423,7 +12673,6 @@ class FigureWidget(BaseFigureWidget):
             To select mapbox objects by row and column, the Figure
             must have been created using plotly.subplots.make_subplots.
             If None (the default), all mapbox objects are selected.
-
         Returns
         -------
         generator
@@ -12457,7 +12706,6 @@ class FigureWidget(BaseFigureWidget):
             To select mapbox objects by row and column, the Figure
             must have been created using plotly.subplots.make_subplots.
             If None (the default), all mapbox objects are selected.
-        
         Returns
         -------
         self
@@ -12525,7 +12773,6 @@ class FigureWidget(BaseFigureWidget):
             To select polar objects by row and column, the Figure
             must have been created using plotly.subplots.make_subplots.
             If None (the default), all polar objects are selected.
-
         Returns
         -------
         generator
@@ -12559,7 +12806,6 @@ class FigureWidget(BaseFigureWidget):
             To select polar objects by row and column, the Figure
             must have been created using plotly.subplots.make_subplots.
             If None (the default), all polar objects are selected.
-        
         Returns
         -------
         self
@@ -12627,7 +12873,6 @@ class FigureWidget(BaseFigureWidget):
             To select scene objects by row and column, the Figure
             must have been created using plotly.subplots.make_subplots.
             If None (the default), all scene objects are selected.
-
         Returns
         -------
         generator
@@ -12661,7 +12906,6 @@ class FigureWidget(BaseFigureWidget):
             To select scene objects by row and column, the Figure
             must have been created using plotly.subplots.make_subplots.
             If None (the default), all scene objects are selected.
-        
         Returns
         -------
         self
@@ -12729,7 +12973,6 @@ class FigureWidget(BaseFigureWidget):
             To select ternary objects by row and column, the Figure
             must have been created using plotly.subplots.make_subplots.
             If None (the default), all ternary objects are selected.
-
         Returns
         -------
         generator
@@ -12763,7 +13006,6 @@ class FigureWidget(BaseFigureWidget):
             To select ternary objects by row and column, the Figure
             must have been created using plotly.subplots.make_subplots.
             If None (the default), all ternary objects are selected.
-        
         Returns
         -------
         self
@@ -12831,7 +13073,6 @@ class FigureWidget(BaseFigureWidget):
             To select xaxis objects by row and column, the Figure
             must have been created using plotly.subplots.make_subplots.
             If None (the default), all xaxis objects are selected.
-
         Returns
         -------
         generator
@@ -12865,7 +13106,6 @@ class FigureWidget(BaseFigureWidget):
             To select xaxis objects by row and column, the Figure
             must have been created using plotly.subplots.make_subplots.
             If None (the default), all xaxis objects are selected.
-        
         Returns
         -------
         self
@@ -12914,7 +13154,9 @@ class FigureWidget(BaseFigureWidget):
 
         return self
 
-    def select_yaxes(self, selector=None, row=None, col=None):
+    def select_yaxes(
+        self, selector=None, row=None, col=None, secondary_y=None
+    ):
         """
         Select yaxis subplot objects from a particular subplot cell
         and/or yaxis subplot objects that satisfy custom selection
@@ -12933,7 +13175,18 @@ class FigureWidget(BaseFigureWidget):
             To select yaxis objects by row and column, the Figure
             must have been created using plotly.subplots.make_subplots.
             If None (the default), all yaxis objects are selected.
-
+        secondary_y: boolean or None (default None)
+            * If True, only select yaxis objects associated with the secondary
+              y-axis of the subplot.
+            * If False, only select yaxis objects associated with the primary
+              y-axis of the subplot.
+            * If None (the default), do not filter yaxis objects based on
+              a secondary y-axis condition. 
+            
+            To select yaxis objects by secondary y-axis, the Figure must
+            have been created using plotly.subplots.make_subplots. See
+            the docstring for the specs argument to make_subplots for more
+            info on creating subplots with secondary y-axes.
         Returns
         -------
         generator
@@ -12944,10 +13197,12 @@ class FigureWidget(BaseFigureWidget):
             _validate_v4_subplots('select_yaxes')
 
         return self._select_layout_subplots_by_prefix(
-            'yaxis', selector, row, col
+            'yaxis', selector, row, col, secondary_y=secondary_y
         )
 
-    def for_each_yaxis(self, fn, selector=None, row=None, col=None):
+    def for_each_yaxis(
+        self, fn, selector=None, row=None, col=None, secondary_y=None
+    ):
         """
         Apply a function to all yaxis objects that satisfy the
         specified selection criteria
@@ -12967,19 +13222,38 @@ class FigureWidget(BaseFigureWidget):
             To select yaxis objects by row and column, the Figure
             must have been created using plotly.subplots.make_subplots.
             If None (the default), all yaxis objects are selected.
-        
+        secondary_y: boolean or None (default None)
+            * If True, only select yaxis objects associated with the secondary
+              y-axis of the subplot.
+            * If False, only select yaxis objects associated with the primary
+              y-axis of the subplot.
+            * If None (the default), do not filter yaxis objects based on
+              a secondary y-axis condition. 
+            
+            To select yaxis objects by secondary y-axis, the Figure must
+            have been created using plotly.subplots.make_subplots. See
+            the docstring for the specs argument to make_subplots for more
+            info on creating subplots with secondary y-axes.
         Returns
         -------
         self
             Returns the Figure object that the method was called on
         """
-        for obj in self.select_yaxes(selector=selector, row=row, col=col):
+        for obj in self.select_yaxes(
+            selector=selector, row=row, col=col, secondary_y=secondary_y
+        ):
             fn(obj)
 
         return self
 
     def update_yaxes(
-        self, patch=None, selector=None, row=None, col=None, **kwargs
+        self,
+        patch=None,
+        selector=None,
+        row=None,
+        col=None,
+        secondary_y=None,
+        **kwargs
     ):
         """
         Perform a property update operation on all yaxis objects
@@ -13001,6 +13275,18 @@ class FigureWidget(BaseFigureWidget):
             To select yaxis objects by row and column, the Figure
             must have been created using plotly.subplots.make_subplots.
             If None (the default), all yaxis objects are selected.
+        secondary_y: boolean or None (default None)
+            * If True, only select yaxis objects associated with the secondary
+              y-axis of the subplot.
+            * If False, only select yaxis objects associated with the primary
+              y-axis of the subplot.
+            * If None (the default), do not filter yaxis objects based on
+              a secondary y-axis condition. 
+            
+            To select yaxis objects by secondary y-axis, the Figure must
+            have been created using plotly.subplots.make_subplots. See
+            the docstring for the specs argument to make_subplots for more
+            info on creating subplots with secondary y-axes.
         **kwargs
             Additional property updates to apply to each selected
             yaxis object. If a property is specified in
@@ -13011,7 +13297,9 @@ class FigureWidget(BaseFigureWidget):
         self
             Returns the Figure object that the method was called on
         """
-        for obj in self.select_yaxes(selector=selector, row=row, col=col):
+        for obj in self.select_yaxes(
+            selector=selector, row=row, col=col, secondary_y=secondary_y
+        ):
             obj.update(patch, **kwargs)
 
         return self
diff --git a/plotly/io/_orca.py b/plotly/io/_orca.py
index e8642d8c1..19bb51b43 100644
--- a/plotly/io/_orca.py
+++ b/plotly/io/_orca.py
@@ -22,6 +22,8 @@ from plotly.optional_imports import get_module
 
 psutil = get_module('psutil')
 
+from _plotly_future_ import _future_flags
+
 # Valid image format constants
 # ----------------------------
 valid_formats = ('png', 'jpeg', 'webp', 'svg', 'pdf', 'eps')
@@ -417,7 +419,11 @@ The port property must be an integer, but received value of type {typ}.
         -------
         str
         """
-        return self._props.get('executable', 'orca')
+        executable_list = self._props.get('executable_list', ['orca'])
+        if executable_list is None:
+            return None
+        else:
+            return ' '.join(executable_list)
 
     @executable.setter
     def executable(self, val):
@@ -429,7 +435,9 @@ The port property must be an integer, but received value of type {typ}.
                 raise ValueError("""
 The executable property must be a string, but received value of type {typ}.
     Received value: {val}""".format(typ=type(val), val=val))
-            self._props['executable'] = val
+            if isinstance(val, string_types):
+                val = [val]
+            self._props['executable_list'] = val
 
         # Server and validation must restart before setting is active
         reset_status()
@@ -661,6 +669,28 @@ but received value of type {typ}.
         # Server must restart before setting is active
         shutdown_server()
 
+    @property
+    def use_xvfb(self):
+        dflt = 'auto' if 'orca_defaults' in _future_flags else False
+        return self._props.get('use_xvfb', dflt)
+
+    @use_xvfb.setter
+    def use_xvfb(self, val):
+        valid_vals = [True, False, 'auto']
+        if val is None:
+            self._props.pop('use_xvfb', None)
+        else:
+            if val not in valid_vals:
+                raise ValueError("""
+The use_xvfb property must be one of {valid_vals}
+    Received value of type {typ}: {val}""".format(
+                    valid_vals=valid_vals, typ=type(val), val=repr(val)))
+
+            self._props['use_xvfb'] = val
+
+        # Server and validation must restart before setting is active
+        reset_status()
+
     @property
     def plotlyjs(self):
         """
@@ -704,6 +734,7 @@ orca configuration
     mathjax: {mathjax}
     topojson: {topojson}
     mapbox_access_token: {mapbox_access_token}
+    use_xvfb: {use_xvfb}
 
 constants
 ---------
@@ -721,7 +752,8 @@ constants
            topojson=self.topojson,
            mapbox_access_token=self.mapbox_access_token,
            plotlyjs=self.plotlyjs,
-           config_file=self.config_file)
+           config_file=self.config_file,
+           use_xvfb=self.use_xvfb)
 
 
 # Make config a singleton object
@@ -738,7 +770,7 @@ class OrcaStatus(object):
     """
     _props = {
         'state': 'unvalidated',  # or 'validated' or 'running'
-        'executable': None,
+        'executable_list': None,
         'version': None,
         'pid': None,
         'port': None,
@@ -770,7 +802,11 @@ class OrcaStatus(object):
 
         This property will be None if the `state` is 'unvalidated'.
         """
-        return self._props['executable']
+        executable_list = self._props['executable_list']
+        if executable_list is None:
+            return None
+        else:
+            return ' '.join(executable_list)
 
     @property
     def version(self):
@@ -851,7 +887,11 @@ def orca_env():
     to orca is transformed into a call to nodejs.
     See https://github.com/plotly/orca/issues/149#issuecomment-443506732
     """
-    clear_env_vars = ['NODE_OPTIONS', 'ELECTRON_RUN_AS_NODE']
+    clear_env_vars = [
+        'NODE_OPTIONS',
+        'ELECTRON_RUN_AS_NODE',
+        'LD_PRELOAD'
+    ]
     orig_env_vars = {}
 
     try:
@@ -932,11 +972,10 @@ https://community.plot.ly/c/api/python
     # -------------------------
     # Search for executable name or path in config.executable
     executable = which(config.executable)
+    path = os.environ.get("PATH", os.defpath)
+    formatted_path = path.replace(os.pathsep, '\n    ')
 
     if executable is None:
-        path = os.environ.get("PATH", os.defpath)
-        formatted_path = path.replace(os.pathsep, '\n    ')
-
         raise ValueError("""
 The orca executable is required to export figures as static images,
 but it could not be found on the system path.
@@ -949,6 +988,37 @@ Searched for executable '{executable}' on the following path:
             formatted_path=formatted_path,
             instructions=install_location_instructions))
 
+    # Check if we should run with Xvfb
+    # --------------------------------
+    xvfb_args = ["--auto-servernum",
+                 "--server-args",
+                 "-screen 0 640x480x24 +extension RANDR +extension GLX",
+                 executable]
+
+    if config.use_xvfb == True:
+        # Use xvfb
+        xvfb_run_executable = which('xvfb-run')
+        if not xvfb_run_executable:
+            raise ValueError("""
+The plotly.io.orca.config.use_xvfb property is set to True, but the
+xvfb-run executable could not be found on the system path.
+
+Searched for the executable 'xvfb-run' on the following path:
+    {formatted_path}""".format(formatted_path=formatted_path))
+
+        executable_list = [xvfb_run_executable] + xvfb_args
+    elif (config.use_xvfb == 'auto' and
+          sys.platform.startswith('linux') and
+          not os.environ.get('DISPLAY') and
+          which('xvfb-run')):
+        # use_xvfb is 'auto', we're on linux without a display server,
+        # and xvfb-run is available. Use it.
+        xvfb_run_executable = which('xvfb-run')
+        executable_list = [xvfb_run_executable] + xvfb_args
+    else:
+        # Do not use xvfb
+        executable_list = [executable]
+
     # Run executable with --help and see if it's our orca
     # ---------------------------------------------------
     invalid_executable_msg = """
@@ -964,7 +1034,7 @@ this message for details on what went wrong.
     # ### Run with Popen so we get access to stdout and stderr
     with orca_env():
         p = subprocess.Popen(
-            [executable, '--help'],
+            executable_list + ['--help'],
             stdout=subprocess.PIPE,
             stderr=subprocess.PIPE)
 
@@ -977,7 +1047,7 @@ Here is the error that was returned by the command
 
 [Return code: {returncode}]
 {err_msg}
-""".format(executable=executable,
+""".format(executable=' '.join(executable_list),
            err_msg=help_error.decode('utf-8'),
            returncode=p.returncode)
 
@@ -987,9 +1057,17 @@ Here is the error that was returned by the command
 
             err_msg += """\
 Note: When used on Linux, orca requires an X11 display server, but none was
-detected. Please install X11, or configure your system with Xvfb. See
-the orca README (https://github.com/plotly/orca) for instructions on using
-orca with Xvfb.
+detected. Please install Xvfb and configure plotly.py to run orca using Xvfb
+as follows:
+
+    >>> import plotly.io as pio
+    >>> pio.orca.config.use_xvfb = True
+    
+You can save this configuration for use in future sessions as follows:
+    >>> pio.orca.config.save() 
+    
+See https://www.x.org/releases/X11R7.6/doc/man/man1/Xvfb.1.xhtml
+for more info on Xvfb
 """
         raise ValueError(err_msg)
 
@@ -997,7 +1075,7 @@ orca with Xvfb.
         raise ValueError(invalid_executable_msg + """
 The error encountered is that no output was returned by the command
     $ {executable} --help
-""".format(executable=executable))
+""".format(executable=' '.join(executable_list)))
 
     if ("Plotly's image-exporting utilities" not in
             help_result.decode('utf-8')):
@@ -1006,14 +1084,14 @@ The error encountered is that unexpected output was returned by the command
     $ {executable} --help
 
 {help_result}
-""".format(executable=executable, help_result=help_result))
+""".format(executable=' '.join(executable_list), help_result=help_result))
 
     # Get orca version
     # ----------------
     # ### Run with Popen so we get access to stdout and stderr
     with orca_env():
         p = subprocess.Popen(
-            [executable, '--version'],
+            executable_list + ['--version'],
             stdout=subprocess.PIPE,
             stderr=subprocess.PIPE)
 
@@ -1029,7 +1107,7 @@ This command returned the following error:
 
 [Return code: {returncode}]
 {err_msg}
-        """.format(executable=executable,
+        """.format(executable=' '.join(executable_list),
                    err_msg=version_error.decode('utf-8'),
                    returncode=p.returncode))
 
@@ -1039,11 +1117,11 @@ The error encountered is that no version was reported by the orca executable.
 Here is the command that plotly.py ran to request the version:
 
     $ {executable} --version
-""".format(executable=executable))
+""".format(executable=' '.join(executable_list)))
     else:
         version_result = version_result.decode()
 
-    status._props['executable'] = executable
+    status._props['executable_list'] = executable_list
     status._props['version'] = version_result.strip()
     status._props['state'] = 'validated'
 
@@ -1061,7 +1139,7 @@ def reset_status():
     None
     """
     shutdown_server()
-    status._props['executable'] = None
+    status._props['executable_list'] = None
     status._props['version'] = None
     status._props['state'] = 'unvalidated'
 
@@ -1179,10 +1257,11 @@ Install using conda:
                 orca_state['port'] = config.port
 
             # Build orca command list
-            cmd_list = [status.executable, 'serve',
-                        '-p', str(orca_state['port']),
-                        '--plotly', config.plotlyjs,
-                        '--graph-only']
+            cmd_list = status._props['executable_list'] + [
+                'serve',
+                '-p', str(orca_state['port']),
+                '--plotly', config.plotlyjs,
+                '--graph-only']
 
             if config.topojson:
                 cmd_list.extend(['--topojson', config.topojson])
@@ -1198,8 +1277,9 @@ Install using conda:
             # specified port.
             DEVNULL = open(os.devnull, 'wb')
             with orca_env():
-                orca_state['proc'] = subprocess.Popen(cmd_list,
-                                                      stdout=DEVNULL)
+                orca_state['proc'] = subprocess.Popen(
+                    cmd_list, stdout=DEVNULL
+                )
 
             # Update orca.status so the user has an accurate view
             # of the state of the orca server
diff --git a/plotly/io/_renderers.py b/plotly/io/_renderers.py
index 293f75e9b..821ae0c30 100644
--- a/plotly/io/_renderers.py
+++ b/plotly/io/_renderers.py
@@ -133,7 +133,7 @@ Renderer must be a subclass of MimetypeRenderer or ExternalRenderer.
         self._default_renderers = [self[name] for name in renderer_names]
 
         # Register renderers for activation before their next use
-        self._to_activate.extend(self._default_renderers)
+        self._to_activate = list(self._default_renderers)
 
     @property
     def render_on_display(self):
@@ -420,17 +420,10 @@ if 'renderer_defaults' in _future_flags:
             default_renderer = 'vscode'
 
         # Fallback to renderer combination that will work automatically
-        # in the classic notebook, jupyterlab, nteract, vscode, and
-        # nbconvert HTML export. We use 'notebook_connected' rather than
-        # 'notebook' to avoid bloating notebook size and slowing down
-        # plotly.py initial import. This comes at the cost of requiring
-        # internet connectivity to view, but that is a preferable
-        # trade-off to adding ~3MB to each saved notebook.
-        #
-        # Note that this doesn't cause any problem for offline
-        # JupyterLab users.
+        # in the classic notebook (offline), jupyterlab, nteract, vscode, and
+        # nbconvert HTML export.
         if not default_renderer:
-            default_renderer = 'notebook_connected+plotly_mimetype'
+            default_renderer = 'plotly_mimetype+notebook'
     else:
         # If ipython isn't available, try to display figures in the default
         # browser
diff --git a/plotly/offline/offline.py b/plotly/offline/offline.py
index 2e30dd91b..6cb8cefde 100644
--- a/plotly/offline/offline.py
+++ b/plotly/offline/offline.py
@@ -272,9 +272,9 @@ def init_notebook_mode(connected=False):
         raise ImportError('`iplot` can only run inside an IPython Notebook.')
 
     if connected:
-        pio.renderers.default = 'notebook_connected+plotly_mimetype'
+        pio.renderers.default = 'plotly_mimetype+notebook_connected'
     else:
-        pio.renderers.default = 'notebook+plotly_mimetype'
+        pio.renderers.default = 'plotly_mimetype+notebook'
 
     # Trigger immediate activation of notebook. This way the plotly.js
     # library reference is available to the notebook immediately
diff --git a/plotly/subplots.py b/plotly/subplots.py
index eb0718619..dcb94138b 100644
--- a/plotly/subplots.py
+++ b/plotly/subplots.py
@@ -27,9 +27,13 @@ _subplot_prop_named_subplot = {'polar', 'ternary', 'mapbox'}
 
 
 # Named tuple to hold an xaxis/yaxis pair that represent a single subplot
-SubplotXY = collections.namedtuple('SubplotXY', ('xaxis', 'yaxis'))
+SubplotXY = collections.namedtuple('SubplotXY',
+                                   ('xaxis', 'yaxis'))
 SubplotDomain = collections.namedtuple('SubplotDomain', ('x', 'y'))
 
+SubplotRef = collections.namedtuple(
+    'SubplotRef', ('subplot_type', 'layout_keys', 'trace_kwargs'))
+
 
 def _get_initial_max_subplot_ids():
     max_subplot_ids = {subplot_type: 0
@@ -155,6 +159,9 @@ def make_subplots(
                 - trace type: A trace type which will be used to determine
                               the appropriate subplot type for that trace
 
+            * secondary_y (bool, default False): If True, create a secondary
+                y-axis positioned on the right side of the subplot. Only valid
+                if type='xy'.
             * colspan (int, default 1): number of subplot columns
                 for this subplot to span.
             * rowspan (int, default 1): number of subplot rows
@@ -351,62 +358,6 @@ The 'start_cell` argument to make_subplots must be one of \
     Received value of type {typ}: {val}""".format(
             typ=type(start_cell), val=repr(start_cell)))
 
-    # ### horizontal_spacing ###
-    if horizontal_spacing is None:
-        horizontal_spacing = 0.2 / cols
-
-    # ### vertical_spacing ###
-    if vertical_spacing is None:
-        if subplot_titles:
-            vertical_spacing = 0.5 / rows
-        else:
-            vertical_spacing = 0.3 / rows
-
-    # ### subplot titles ###
-    if not subplot_titles:
-        subplot_titles = [""] * rows * cols
-
-    # ### column_widths ###
-    if row_titles:
-        # Add a little breathing room between row labels and legend
-        max_width = 0.98
-    else:
-        max_width = 1.0
-    if column_widths is None:
-        widths = [(max_width - horizontal_spacing * (cols - 1)) / cols] * cols
-    elif isinstance(column_widths, (list, tuple)) and len(column_widths) == cols:
-        cum_sum = float(sum(column_widths))
-        widths = []
-        for w in column_widths:
-            widths.append(
-                (max_width - horizontal_spacing * (cols - 1)) * (w / cum_sum)
-            )
-    else:
-        raise ValueError("""
-The 'column_widths' argument to make_suplots must be a list of numbers of \
-length {cols}.
-    Received value of type {typ}: {val}""".format(
-            cols=cols, typ=type(cols), val=repr(column_widths)))
-
-    # ### row_heights ###
-    if row_heights is None:
-        heights = [(1. - vertical_spacing * (rows - 1)) / rows] * rows
-    elif isinstance(row_heights, (list, tuple)) and len(row_heights) == rows:
-        cum_sum = float(sum(row_heights))
-        heights = []
-        for h in row_heights:
-            heights.append(
-                (1. - vertical_spacing * (rows - 1)) * (h / cum_sum)
-            )
-        if row_dir < 0 and not use_legacy_row_heights_order:
-            heights = list(reversed(heights))
-    else:
-        raise ValueError("""
-The 'row_heights' argument to make_suplots must be a list of numbers of \
-length {rows}.
-    Received value of type {typ}: {val}""".format(
-            rows=rows, typ=type(cols), val=repr(row_heights)))
-
     # ### Helper to validate coerce elements of lists of dictionaries ###
     def _check_keys_and_fill(name, arg, defaults):
         def _checks(item, defaults):
@@ -461,14 +412,16 @@ dimensions ({rows} x {cols}).
             rows=rows, cols=cols, typ=type(specs), val=repr(specs)
         ))
 
-    # For backward compatibility, convert is_3d flag to type='scene' kwarg
     for row in specs:
         for spec in row:
+            # For backward compatibility,
+            # convert is_3d flag to type='scene' kwarg
             if spec and spec.pop('is_3d', None):
                 spec['type'] = 'scene'
 
     spec_defaults = dict(
         type='xy',
+        secondary_y=False,
         colspan=1,
         rowspan=1,
         l=0.0,
@@ -478,6 +431,18 @@ dimensions ({rows} x {cols}).
     )
     _check_keys_and_fill('specs', specs, spec_defaults)
 
+    # Validate secondary_y
+    has_secondary_y = False
+    for row in specs:
+        for spec in row:
+            if spec is not None:
+                has_secondary_y = has_secondary_y or spec['secondary_y']
+            if spec and spec['type'] != 'xy' and spec['secondary_y']:
+                raise ValueError("""
+The 'secondary_y' spec property is not supported for subplot of type '{s_typ}'
+     'secondary_y' is only supported for subplots of type 'xy'
+""".format(s_typ=spec['type']))
+
     # ### insets ###
     if insets is None or insets is False:
         insets = []
@@ -528,6 +493,73 @@ The {arg} argument to make_subplots must be one of: {valid_vals}
             val=repr(val)
         ))
 
+    # ### horizontal_spacing ###
+    if horizontal_spacing is None:
+        if has_secondary_y:
+            horizontal_spacing = 0.4 / cols
+        else:
+            horizontal_spacing = 0.2 / cols
+
+    # ### vertical_spacing ###
+    if vertical_spacing is None:
+        if subplot_titles:
+            vertical_spacing = 0.5 / rows
+        else:
+            vertical_spacing = 0.3 / rows
+
+    # ### subplot titles ###
+    if not subplot_titles:
+        subplot_titles = [""] * rows * cols
+
+    # ### column_widths ###
+    if has_secondary_y:
+        # Add room for secondary y-axis title
+        max_width = 0.94
+    elif row_titles:
+        # Add a little breathing room between row labels and legend
+        max_width = 0.98
+    else:
+        max_width = 1.0
+
+    if column_widths is None:
+        widths = [(max_width - horizontal_spacing * (
+                    cols - 1)) / cols] * cols
+    elif isinstance(column_widths, (list, tuple)) and len(
+            column_widths) == cols:
+        cum_sum = float(sum(column_widths))
+        widths = []
+        for w in column_widths:
+            widths.append(
+                (max_width - horizontal_spacing * (cols - 1)) * (
+                            w / cum_sum)
+            )
+    else:
+        raise ValueError("""
+The 'column_widths' argument to make_suplots must be a list of numbers of \
+length {cols}.
+    Received value of type {typ}: {val}""".format(
+            cols=cols, typ=type(column_widths), val=repr(column_widths)))
+
+    # ### row_heights ###
+    if row_heights is None:
+        heights = [(1. - vertical_spacing * (rows - 1)) / rows] * rows
+    elif isinstance(row_heights, (list, tuple)) and len(
+            row_heights) == rows:
+        cum_sum = float(sum(row_heights))
+        heights = []
+        for h in row_heights:
+            heights.append(
+                (1. - vertical_spacing * (rows - 1)) * (h / cum_sum)
+            )
+        if row_dir < 0 and not use_legacy_row_heights_order:
+            heights = list(reversed(heights))
+    else:
+        raise ValueError("""
+The 'row_heights' argument to make_suplots must be a list of numbers of \
+length {rows}.
+    Received value of type {typ}: {val}""".format(
+            rows=rows, typ=type(row_heights), val=repr(row_heights)))
+
     # ### column_titles / row_titles ###
     if column_titles and not isinstance(column_titles, (list, tuple)):
         raise ValueError("""
@@ -612,9 +644,11 @@ The row_titles argument to make_subplots must be a list or tuple
 
             # ### construct subplot container ###
             subplot_type = spec['type']
-            grid_ref_element = _init_subplot(
-                layout, subplot_type, x_domain, y_domain, max_subplot_ids)
-            grid_ref[r][c] = grid_ref_element
+            secondary_y = spec['secondary_y']
+            subplot_refs = _init_subplot(
+                layout, subplot_type, secondary_y,
+                x_domain, y_domain, max_subplot_ids)
+            grid_ref[r][c] = subplot_refs
 
     _configure_shared_axes(layout, grid_ref, specs, 'x', shared_xaxes, row_dir)
     _configure_shared_axes(layout, grid_ref, specs, 'y', shared_yaxes, row_dir)
@@ -658,10 +692,11 @@ The row_titles argument to make_subplots must be a list or tuple
 
             subplot_type = inset['type']
 
-            inset_ref_element = _init_subplot(
-                layout, subplot_type, x_domain, y_domain, max_subplot_ids)
+            subplot_refs = _init_subplot(
+                layout, subplot_type, False,
+                x_domain, y_domain, max_subplot_ids)
 
-            insets_ref[i_inset] = inset_ref_element
+            insets_ref[i_inset] = subplot_refs
 
     # Build grid_str
     # This is the message printed when print_grid=True
@@ -783,8 +818,8 @@ def _configure_shared_axes(layout, grid_ref, specs, x_or_y, shared, row_dir):
     else:
         rows_iter = range(rows)
 
-    def update_axis_matches(first_axis_id, ref, spec, remove_label):
-        if ref is None:
+    def update_axis_matches(first_axis_id, subplot_ref, spec, remove_label):
+        if subplot_ref is None:
             return first_axis_id
 
         if x_or_y == 'x':
@@ -792,12 +827,12 @@ def _configure_shared_axes(layout, grid_ref, specs, x_or_y, shared, row_dir):
         else:
             span = spec['rowspan']
 
-        if ref['subplot_type'] == 'xy' and span == 1:
+        if subplot_ref.subplot_type == 'xy' and span == 1:
             if first_axis_id is None:
-                first_axis_name = ref['layout_keys'][layout_key_ind]
+                first_axis_name = subplot_ref.layout_keys[layout_key_ind]
                 first_axis_id = first_axis_name.replace('axis', '')
             else:
-                axis_name = ref['layout_keys'][layout_key_ind]
+                axis_name = subplot_ref.layout_keys[layout_key_ind]
                 axis_to_match = layout[axis_name]
                 axis_to_match.matches = first_axis_id
                 if remove_label:
@@ -810,26 +845,32 @@ def _configure_shared_axes(layout, grid_ref, specs, x_or_y, shared, row_dir):
             first_axis_id = None
             ok_to_remove_label = x_or_y == 'x'
             for r in rows_iter:
-                ref = grid_ref[r][c]
+                if not grid_ref[r][c]:
+                    continue
+                subplot_ref = grid_ref[r][c][0]
                 spec = specs[r][c]
                 first_axis_id = update_axis_matches(
-                    first_axis_id, ref, spec, ok_to_remove_label)
+                    first_axis_id, subplot_ref, spec, ok_to_remove_label)
 
     elif shared == 'rows' or (x_or_y == 'y' and shared is True):
         for r in rows_iter:
             first_axis_id = None
             ok_to_remove_label = x_or_y == 'y'
             for c in range(cols):
-                ref = grid_ref[r][c]
+                if not grid_ref[r][c]:
+                    continue
+                subplot_ref = grid_ref[r][c][0]
                 spec = specs[r][c]
                 first_axis_id = update_axis_matches(
-                    first_axis_id, ref, spec, ok_to_remove_label)
+                    first_axis_id, subplot_ref, spec, ok_to_remove_label)
 
     elif shared == 'all':
         first_axis_id = None
         for c in range(cols):
             for ri, r in enumerate(rows_iter):
-                ref = grid_ref[r][c]
+                if not grid_ref[r][c]:
+                    continue
+                subplot_ref = grid_ref[r][c][0]
                 spec = specs[r][c]
 
                 if x_or_y == 'y':
@@ -838,11 +879,11 @@ def _configure_shared_axes(layout, grid_ref, specs, x_or_y, shared, row_dir):
                     ok_to_remove_label = ri > 0 if row_dir > 0 else r < rows - 1
 
                 first_axis_id = update_axis_matches(
-                    first_axis_id, ref, spec, ok_to_remove_label)
+                    first_axis_id, subplot_ref, spec, ok_to_remove_label)
 
 
 def _init_subplot_xy(
-        layout, x_domain, y_domain, max_subplot_ids=None
+        layout, secondary_y, x_domain, y_domain, max_subplot_ids=None
 ):
     if max_subplot_ids is None:
         max_subplot_ids = _get_initial_max_subplot_ids()
@@ -867,17 +908,36 @@ def _init_subplot_xy(
     layout[xaxis_name] = x_axis
     layout[yaxis_name] = y_axis
 
-    ref_element = {
-        'subplot_type': 'xy',
-        'layout_keys': (xaxis_name, yaxis_name),
-        'trace_kwargs': {'xaxis': x_label, 'yaxis': y_label}
-    }
+    subplot_refs = [SubplotRef(
+        subplot_type='xy',
+        layout_keys=(xaxis_name, yaxis_name),
+        trace_kwargs={'xaxis': x_label, 'yaxis': y_label}
+    )]
+
+    if secondary_y:
+        y_cnt += 1
+        secondary_yaxis_name = 'yaxis{cnt}'.format(
+            cnt=y_cnt if y_cnt > 1 else '')
+        secondary_y_label = "y{cnt}".format(cnt=y_cnt)
+
+        # Add secondary y-axis to subplot reference
+        subplot_refs.append(SubplotRef(
+            subplot_type='xy',
+            layout_keys=(xaxis_name, secondary_yaxis_name),
+            trace_kwargs={'xaxis': x_label, 'yaxis': secondary_y_label}
+        ))
+
+        # Add secondary y axis to layout
+        secondary_y_axis = {
+            'anchor': y_anchor, 'overlaying': y_label, 'side': 'right'
+        }
+        layout[secondary_yaxis_name] = secondary_y_axis
 
     # increment max_subplot_ids
     max_subplot_ids['xaxis'] = x_cnt
     max_subplot_ids['yaxis'] = y_cnt
 
-    return ref_element
+    return tuple(subplot_refs)
 
 
 def _init_subplot_single(
@@ -898,26 +958,28 @@ def _init_subplot_single(
                  if subplot_type in _subplot_prop_named_subplot
                  else subplot_type)
 
-    ref_element = {
-        'subplot_type': subplot_type,
-        'layout_keys': (label,),
-        'trace_kwargs': {trace_key: label}}
+    subplot_ref = SubplotRef(
+        subplot_type=subplot_type,
+        layout_keys=(label,),
+        trace_kwargs={trace_key: label}
+    )
 
     # increment max_subplot_id
     max_subplot_ids[subplot_type] = cnt
 
-    return ref_element
+    return (subplot_ref,)
 
 
 def _init_subplot_domain(x_domain, y_domain):
     # No change to layout since domain traces are labeled individually
-    ref_element = {
-        'subplot_type': 'domain',
-        'layout_keys': (),
-        'trace_kwargs': {
-            'domain': {'x': tuple(x_domain), 'y': tuple(y_domain)}}}
+    subplot_ref = SubplotRef(
+        subplot_type='domain',
+        layout_keys=(),
+        trace_kwargs={
+            'domain': {'x': tuple(x_domain), 'y': tuple(y_domain)}}
+    )
 
-    return ref_element
+    return (subplot_ref,)
 
 
 def _subplot_type_for_trace_type(trace_type):
@@ -966,7 +1028,8 @@ def _validate_coerce_subplot_type(subplot_type):
 
 
 def _init_subplot(
-        layout, subplot_type, x_domain, y_domain, max_subplot_ids=None
+        layout, subplot_type, secondary_y,
+        x_domain, y_domain, max_subplot_ids=None
 ):
     # Normalize subplot type
     subplot_type = _validate_coerce_subplot_type(subplot_type)
@@ -981,20 +1044,20 @@ def _init_subplot(
     y_domain = [max(0.0, y_domain[0]), min(1.0, y_domain[1])]
 
     if subplot_type == 'xy':
-        ref_element = _init_subplot_xy(
-            layout, x_domain, y_domain, max_subplot_ids
+        subplot_refs = _init_subplot_xy(
+            layout, secondary_y, x_domain, y_domain, max_subplot_ids
         )
     elif subplot_type in _single_subplot_types:
-        ref_element = _init_subplot_single(
+        subplot_refs = _init_subplot_single(
             layout, subplot_type, x_domain, y_domain, max_subplot_ids
         )
     elif subplot_type == 'domain':
-        ref_element = _init_subplot_domain(x_domain, y_domain)
+        subplot_refs = _init_subplot_domain(x_domain, y_domain)
     else:
         raise ValueError('Unsupported subplot type: {}'
                          .format(repr(subplot_type)))
 
-    return ref_element
+    return subplot_refs
 
 
 def _get_cartesian_label(x_or_y, r, c, cnt):
@@ -1009,7 +1072,6 @@ def _build_subplot_title_annotations(
         title_edge='top',
         offset=0
 ):
-
     # If shared_axes is False (default) use list_of_domains
     # This is used for insets and irregular layouts
     # if not shared_xaxes and not shared_yaxes:
@@ -1129,8 +1191,17 @@ def _build_grid_str(specs, grid_ref, insets, insets_ref, row_seq):
     _tmp = [['' for c in range(cols)] for r in range(rows)]
 
     # Define cell string as function of (r, c) and grid_ref
-    def _get_cell_str(r, c, ref):
-        ref_str = ','.join(ref['layout_keys'])
+    def _get_cell_str(r, c, subplot_refs):
+        layout_keys = sorted({
+            k
+            for ref in subplot_refs
+            for k in ref.layout_keys
+         })
+
+        ref_str = ','.join(layout_keys)
+
+        # Replace yaxis2 -> y2
+        ref_str = ref_str.replace('axis', '')
         return '({r},{c}) {ref}'.format(
             r=r + 1,
             c=c + 1,
@@ -1209,15 +1280,22 @@ def _build_grid_str(specs, grid_ref, insets, insets_ref, row_seq):
             c = inset['cell'][1] - 1
             ref = grid_ref[r][c]
 
+            subplot_labels_str = ','.join(insets_ref[i_inset][0].layout_keys)
+
+            # Replace, e.g., yaxis2 -> y2
+            subplot_labels_str = subplot_labels_str.replace('axis', '')
+
             grid_str += (
-                    s_str + ','.join(insets_ref[i_inset]['layout_keys']) + e_str +
-                    ' over ' +
+                    s_str + subplot_labels_str
+                    + e_str + ' over ' +
                     s_str + _get_cell_str(r, c, ref) + e_str + '\n'
             )
     return grid_str
 
 
-def _set_trace_grid_reference(trace, layout, grid_ref, row, col):
+def _set_trace_grid_reference(
+        trace, layout, grid_ref, row, col, secondary_y=False):
+
     if row <= 0:
         raise Exception("Row value is out of range. "
                         "Note: the starting cell is (1, 1)")
@@ -1225,20 +1303,31 @@ def _set_trace_grid_reference(trace, layout, grid_ref, row, col):
         raise Exception("Col value is out of range. "
                         "Note: the starting cell is (1, 1)")
     try:
-        ref = grid_ref[row - 1][col - 1]
+        subplot_refs = grid_ref[row - 1][col - 1]
     except IndexError:
         raise Exception("The (row, col) pair sent is out of "
                         "range. Use Figure.print_grid to view the "
                         "subplot grid. ")
 
-    if ref is None:
+    if not subplot_refs:
         raise ValueError("""
 No subplot specified at grid position ({row}, {col})""".format(
             row=row,
             col=col
         ))
 
-    for k in ref['trace_kwargs']:
+    if secondary_y:
+        if len(subplot_refs) < 2:
+            raise ValueError("""
+Subplot with type '{subplot_type}' at grid position ({row}, {col}) was not
+created with the secondary_y spec property set to True. See the docstring
+for the specs argument to plotly.subplots.make_subplots for more information.
+""")
+        trace_kwargs = subplot_refs[1].trace_kwargs
+    else:
+        trace_kwargs = subplot_refs[0].trace_kwargs
+
+    for k in trace_kwargs:
         if k not in trace:
             raise ValueError("""\
 Trace type '{typ}' is not compatible with subplot type '{subplot_type}'
@@ -1247,26 +1336,26 @@ at grid position ({row}, {col})
 See the docstring for the specs argument to plotly.subplots.make_subplot 
 for more information on subplot types""".format(
                 typ=trace.type,
-                subplot_type=ref['subplot_type'],
+                subplot_type=subplot_refs[0].subplot_type,
                 row=row,
                 col=col
             ))
 
     # Update trace reference
-    trace.update(ref['trace_kwargs'])
+    trace.update(trace_kwargs)
 
 
-def _get_grid_subplot(fig, row, col):
+def _get_grid_subplot(fig, row, col, secondary_y=False):
     # Make sure we're in future subplots mode
     from _plotly_future_ import _future_flags
     if 'v4_subplots' not in _future_flags:
         raise ValueError("""
-    plotly.subplots.get_subplots may only be used in the
-    v4_subplots _plotly_future_ mode.  To try it out, run
+plotly.subplots.get_subplots may only be used in the
+v4_subplots _plotly_future_ mode.  To try it out, run
 
-    >>> from _plotly_future_ import v4_subplots
+>>> from _plotly_future_ import v4_subplots
 
-    before importing plotly.
+before importing plotly.
     """)
 
     try:
@@ -1299,13 +1388,20 @@ The col argument to get_subplot must be an integer where 1 <= row <= {cols}
             val=repr(col)
         ))
 
-    ref = fig._grid_ref[row - 1][col - 1]
-    if ref is None:
+    subplot_refs = fig._grid_ref[row - 1][col - 1]
+    if not subplot_refs:
         return None
 
-    layout_keys = ref['layout_keys']
+    if secondary_y:
+        if len(subplot_refs) > 1:
+            layout_keys = subplot_refs[1].layout_keys
+        else:
+            return None
+    else:
+        layout_keys = subplot_refs[0].layout_keys
+
     if len(layout_keys) == 0:
-        return SubplotDomain(**ref['trace_kwargs']['domain'])
+        return SubplotDomain(**subplot_refs[0].trace_kwargs['domain'])
     elif len(layout_keys) == 1:
         return fig.layout[layout_keys[0]]
     elif len(layout_keys) == 2:
@@ -1320,41 +1416,45 @@ Unexpected subplot type with layout_keys of {}""".format(layout_keys))
 def _get_subplot_ref_for_trace(trace):
 
     if 'domain' in trace:
-        return {
-            'subplot_type': 'domain',
-            'layout_keys': (),
-            'trace_kwargs': {
+        return SubplotRef(
+            subplot_type='domain',
+            layout_keys=(),
+            trace_kwargs={
                 'domain': {'x': trace.domain.x,
-                           'y': trace.domain.y}}}
+                           'y': trace.domain.y}}
+        )
 
     elif 'xaxis' in trace and 'yaxis' in trace:
         xaxis_name = 'xaxis' + trace.xaxis[1:] if trace.xaxis else 'xaxis'
         yaxis_name = 'yaxis' + trace.yaxis[1:] if trace.yaxis else 'yaxis'
 
-        return {
-            'subplot_type': 'xy',
-            'layout_keys': (xaxis_name, yaxis_name),
-            'trace_kwargs': {'xaxis': trace.xaxis, 'yaxis': trace.yaxis}
-        }
+        return SubplotRef(
+            subplot_type='xy',
+            layout_keys=(xaxis_name, yaxis_name),
+            trace_kwargs={'xaxis': trace.xaxis, 'yaxis': trace.yaxis}
+        )
     elif 'geo' in trace:
-        return {
-            'subplot_type': 'geo',
-            'layout_keys': (trace.geo,),
-            'trace_kwargs': {'geo': trace.geo}}
+        return SubplotRef(
+            subplot_type='geo',
+            layout_keys=(trace.geo,),
+            trace_kwargs={'geo': trace.geo}
+        )
     elif 'scene' in trace:
-        return {
-            'subplot_type': 'scene',
-            'layout_keys': (trace.scene,),
-            'trace_kwargs': {'scene': trace.scene}}
+        return SubplotRef(
+            subplot_type='scene',
+            layout_keys=(trace.scene,),
+            trace_kwargs={'scene': trace.scene}
+        )
     elif 'subplot' in trace:
         for t in _subplot_prop_named_subplot:
             try:
                 validator = trace._get_prop_validator('subplot')
                 validator.validate_coerce(t)
-                return {
-                    'subplot_type': t,
-                    'layout_keys': (trace.subplot,),
-                    'trace_kwargs': {'subplot': trace.subplot}}
+                return SubplotRef(
+                    subplot_type=t,
+                    layout_keys=(trace.subplot,),
+                    trace_kwargs={'subplot': trace.subplot}
+                )
             except ValueError:
                 pass
 


Test patch:
diff --git a/plotly/tests/test_core/test_subplots/test_get_subplot.py b/plotly/tests/test_core/test_subplots/test_get_subplot.py
new file mode 100644
index 000000000..412657e40
--- /dev/null
+++ b/plotly/tests/test_core/test_subplots/test_get_subplot.py
@@ -0,0 +1,150 @@
+from __future__ import absolute_import
+from _plotly_future_ import _future_flags
+
+from unittest import TestCase
+from plotly.graph_objs import Figure
+from plotly import subplots
+import plotly.graph_objs as go
+from plotly.subplots import SubplotXY, SubplotDomain
+
+
+class TestGetSubplot(TestCase):
+    def setUp(self):
+        # Use v4_subplots mode
+        _future_flags.add('v4_subplots')
+
+    def tearDown(self):
+        _future_flags.remove('v4_subplots')
+
+    def test_get_subplot(self):
+        # Make Figure with subplot types
+        fig = subplots.make_subplots(
+            rows=4,
+            cols=2,
+            specs=[[{}, {'secondary_y': True}],
+                   [{'type': 'polar'}, {'type': 'ternary'}],
+                   [{'type': 'scene'}, {'type': 'geo'}],
+                   [{'type': 'domain', 'colspan': 2}, None]]
+        )
+
+        fig.add_scatter(y=[2, 1, 3], row=1, col=1)
+        fig.add_scatter(y=[2, 1, 3], row=1, col=2)
+        fig.add_scatter(y=[1, 3, 2], row=1, col=2, secondary_y=True)
+        fig.add_trace(go.Scatterpolar(
+            r=[2, 1, 3], theta=[20, 50, 125]), row=2, col=1)
+        fig.add_traces([go.Scatterternary(a=[.2, .1, .3], b=[.4, .6, .5])],
+                       rows=[2], cols=[2])
+        fig.add_scatter3d(x=[2, 0, 1], y=[0, 1, 0], z=[0, 1, 2], mode='lines',
+                          row=3, col=1)
+        fig.add_scattergeo(lat=[0, 40], lon=[10, 5], mode='lines', row=3,
+                           col=2)
+        fig.add_parcats(
+            dimensions=[
+                {'values': ['A', 'A', 'B', 'A', 'B']},
+                {'values': ['a', 'a', 'a', 'b', 'b']},
+            ], row=4, col=1)
+
+        fig.update_traces(uid=None)
+        fig.update(layout_height=1200)
+
+        # Check
+        expected = Figure({
+            'data': [
+                {'type': 'scatter', 'xaxis': 'x',
+                 'y': [2, 1, 3], 'yaxis': 'y'},
+                {'type': 'scatter', 'xaxis': 'x2',
+                 'y': [2, 1, 3], 'yaxis': 'y2'},
+                {'type': 'scatter', 'xaxis': 'x2',
+                 'y': [1, 3, 2], 'yaxis': 'y3'},
+                {'r': [2, 1, 3], 'subplot': 'polar',
+                 'theta': [20, 50, 125], 'type': 'scatterpolar'},
+                {'a': [0.2, 0.1, 0.3], 'b': [0.4, 0.6, 0.5],
+                 'subplot': 'ternary', 'type': 'scatterternary'},
+                {'mode': 'lines', 'scene': 'scene', 'type': 'scatter3d',
+                 'x': [2, 0, 1], 'y': [0, 1, 0], 'z': [0, 1, 2]},
+                {'geo': 'geo', 'lat': [0, 40], 'lon': [10, 5],
+                 'mode': 'lines', 'type': 'scattergeo'},
+                {'dimensions': [{'values': ['A', 'A', 'B', 'A', 'B']},
+                                {'values': ['a', 'a', 'a', 'b', 'b']}],
+                 'domain': {'x': [0.0, 0.9400000000000001],
+                            'y': [0.0, 0.19375]},
+                 'type': 'parcats'}],
+            'layout': {
+                'geo': {'domain': {'x': [0.5700000000000001,
+                                         0.9400000000000001],
+                                   'y': [0.26875, 0.4625]}},
+                'height': 1200,
+                'polar': {'domain': {'x': [0.0, 0.37],
+                                     'y': [0.5375, 0.73125]}},
+                'scene': {'domain': {'x': [0.0, 0.37],
+                                     'y': [0.26875, 0.4625]}},
+                'ternary': {'domain': {'x': [0.5700000000000001,
+                                             0.9400000000000001],
+                                       'y': [0.5375, 0.73125]}},
+                'xaxis': {'anchor': 'y', 'domain': [0.0, 0.37]},
+                'xaxis2': {'anchor': 'y2',
+                           'domain': [0.5700000000000001,
+                                      0.9400000000000001]},
+                'yaxis': {'anchor': 'x', 'domain': [0.80625, 1.0]},
+                'yaxis2': {'anchor': 'x2', 'domain': [0.80625, 1.0]},
+                'yaxis3': {'anchor': 'x2',
+                           'overlaying': 'y2',
+                           'side': 'right'}}
+        })
+
+        expected.update_traces(uid=None)
+
+        # Make sure we have expected starting figure
+        self.assertEqual(fig, expected)
+
+        # (1, 1)
+        subplot = fig.get_subplot(1, 1)
+        self.assertEqual(
+            subplot, SubplotXY(
+                xaxis=fig.layout.xaxis, yaxis=fig.layout.yaxis))
+
+        # (1, 2) Primary
+        subplot = fig.get_subplot(1, 2)
+        self.assertEqual(
+            subplot, SubplotXY(
+                xaxis=fig.layout.xaxis2, yaxis=fig.layout.yaxis2))
+
+        # (1, 2) Primary
+        subplot = fig.get_subplot(1, 2, secondary_y=True)
+        self.assertEqual(
+            subplot, SubplotXY(
+                xaxis=fig.layout.xaxis2, yaxis=fig.layout.yaxis3))
+
+        # (2, 1)
+        subplot = fig.get_subplot(2, 1)
+        self.assertEqual(
+            subplot, fig.layout.polar)
+
+        # (2, 2)
+        subplot = fig.get_subplot(2, 2)
+        self.assertEqual(
+            subplot, fig.layout.ternary)
+
+        # (3, 1)
+        subplot = fig.get_subplot(3, 1)
+        self.assertEqual(
+            subplot, fig.layout.scene)
+
+        # (3, 2)
+        subplot = fig.get_subplot(3, 2)
+        self.assertEqual(
+            subplot, fig.layout.geo)
+
+        # (4, 1)
+        subplot = fig.get_subplot(4, 1)
+        domain = fig.data[-1].domain
+        self.assertEqual(
+            subplot, SubplotDomain(x=domain.x, y=domain.y))
+
+    def test_get_subplot_out_of_bounds(self):
+        fig = subplots.make_subplots(rows=4, cols=2)
+
+        self.assertRaises(ValueError, lambda: fig.get_subplot(0, 1))
+        self.assertRaises(ValueError, lambda: fig.get_subplot(5, 1))
+        self.assertRaises(ValueError, lambda: fig.get_subplot(1, 0))
+        self.assertRaises(ValueError, lambda: fig.get_subplot(1, 3))
diff --git a/plotly/tests/test_core/test_subplots/test_make_subplots.py b/plotly/tests/test_core/test_subplots/test_make_subplots.py
index a6f70239b..9d97184cd 100644
--- a/plotly/tests/test_core/test_subplots/test_make_subplots.py
+++ b/plotly/tests/test_core/test_subplots/test_make_subplots.py
@@ -5,6 +5,7 @@ from unittest import TestCase
 from plotly.graph_objs import (Annotation, Annotations, Data, Figure, Font,
                                Layout, layout, Scene, XAxis, YAxis)
 import plotly.tools as tls
+from plotly import subplots
 
 
 class TestMakeSubplots(TestCase):
@@ -2170,4 +2171,119 @@ class TestMakeSubplots(TestCase):
 
         self.assertEqual(fig.to_plotly_json(), expected.to_plotly_json())
 
-    # def test_row_width_and_shared_yaxes(self):
+    def test_secondary_y(self):
+        fig = subplots.make_subplots(
+            rows=1, cols=1, specs=[[{'secondary_y': True}]])
+
+        expected = Figure({
+            'data': [],
+            'layout': {'xaxis': {'anchor': 'y', 'domain': [0.0, 0.94]},
+                       'yaxis': {'anchor': 'x', 'domain': [0.0, 1.0]},
+                       'yaxis2': {'anchor': 'x',
+                                  'overlaying': 'y',
+                                  'side': 'right'}}
+        })
+
+        self.assertEqual(fig.to_plotly_json(), expected.to_plotly_json())
+
+    def test_secondary_y_traces(self):
+        fig = subplots.make_subplots(
+            rows=1, cols=1, specs=[[{'secondary_y': True}]])
+
+        fig.add_scatter(y=[1, 3, 2], name='First', row=1, col=1)
+        fig.add_scatter(
+            y=[2, 1, 3], name='second', row=1, col=1, secondary_y=True)
+        fig.update_traces(uid=None)
+
+        expected = Figure({
+            'data': [{'name': 'First',
+                      'type': 'scatter',
+                      'y': [1, 3, 2],
+                      'xaxis': 'x',
+                      'yaxis': 'y'},
+                     {'name': 'second',
+                      'type': 'scatter',
+                      'y': [2, 1, 3],
+                      'xaxis': 'x',
+                      'yaxis': 'y2'}],
+            'layout': {'xaxis': {'anchor': 'y', 'domain': [0.0, 0.94]},
+                       'yaxis': {'anchor': 'x', 'domain': [0.0, 1.0]},
+                       'yaxis2': {'anchor': 'x',
+                                  'overlaying': 'y',
+                                  'side': 'right'}}
+        })
+        expected.update_traces(uid=None)
+
+        self.assertEqual(fig.to_plotly_json(), expected.to_plotly_json())
+
+    def test_secondary_y_subplots(self):
+        fig = subplots.make_subplots(
+            rows=2,
+            cols=2,
+            specs=[[{'secondary_y': True}, {'secondary_y': True}],
+                   [{'secondary_y': True}, {'secondary_y': True}]]
+        )
+
+        fig.add_scatter(y=[1, 3, 2], name='First', row=1, col=1)
+        fig.add_scatter(
+            y=[2, 1, 3], name='Second', row=1, col=1, secondary_y=True)
+
+        fig.add_scatter(y=[4, 3, 2], name='Third', row=1, col=2)
+        fig.add_scatter(
+            y=[8, 1, 3], name='Forth', row=1, col=2, secondary_y=True)
+
+        fig.add_scatter(y=[0, 2, 4], name='Fifth', row=2, col=1)
+        fig.add_scatter(
+            y=[2, 1, 3], name='Sixth', row=2, col=1, secondary_y=True)
+
+        fig.add_scatter(y=[2, 4, 0], name='Fifth', row=2, col=2)
+        fig.add_scatter(
+            y=[2, 3, 6], name='Sixth', row=2, col=2, secondary_y=True)
+
+        fig.update_traces(uid=None)
+
+        expected = Figure({
+            'data': [
+                {'name': 'First', 'type': 'scatter',
+                 'xaxis': 'x', 'y': [1, 3, 2], 'yaxis': 'y'},
+                {'name': 'Second', 'type': 'scatter',
+                 'xaxis': 'x', 'y': [2, 1, 3], 'yaxis': 'y2'},
+                {'name': 'Third', 'type': 'scatter',
+                 'xaxis': 'x2', 'y': [4, 3, 2], 'yaxis': 'y3'},
+                {'name': 'Forth', 'type': 'scatter',
+                 'xaxis': 'x2', 'y': [8, 1, 3], 'yaxis': 'y4'},
+                {'name': 'Fifth', 'type': 'scatter',
+                 'xaxis': 'x3', 'y': [0, 2, 4], 'yaxis': 'y5'},
+                {'name': 'Sixth', 'type': 'scatter',
+                 'xaxis': 'x3', 'y': [2, 1, 3], 'yaxis': 'y6'},
+                {'name': 'Fifth', 'type': 'scatter',
+                 'xaxis': 'x4', 'y': [2, 4, 0], 'yaxis': 'y7'},
+                {'name': 'Sixth', 'type': 'scatter',
+                 'xaxis': 'x4', 'y': [2, 3, 6], 'yaxis': 'y8'}],
+            'layout': {
+                'xaxis': {'anchor': 'y', 'domain': [0.0, 0.37]},
+                'xaxis2': {'anchor': 'y3',
+                           'domain': [0.5700000000000001, 0.9400000000000001]},
+                'xaxis3': {'anchor': 'y5', 'domain': [0.0, 0.37]},
+                'xaxis4': {'anchor': 'y7',
+                           'domain': [0.5700000000000001, 0.9400000000000001]},
+                'yaxis': {'anchor': 'x', 'domain': [0.575, 1.0]},
+                'yaxis2': {'anchor': 'x',
+                           'overlaying': 'y', 'side': 'right'},
+                'yaxis3': {'anchor': 'x2',
+                           'domain': [0.575, 1.0]},
+                'yaxis4': {'anchor': 'x2',
+                           'overlaying': 'y3', 'side': 'right'},
+                'yaxis5': {'anchor': 'x3',
+                           'domain': [0.0, 0.425]},
+                'yaxis6': {'anchor': 'x3',
+                           'overlaying': 'y5', 'side': 'right'},
+                'yaxis7': {'anchor': 'x4',
+                           'domain': [0.0, 0.425]},
+                'yaxis8': {'anchor': 'x4',
+                           'overlaying': 'y7', 'side': 'right'}}
+        })
+
+        expected.update_traces(uid=None)
+
+        self.assertEqual(fig.to_plotly_json(), expected.to_plotly_json())
diff --git a/plotly/tests/test_core/test_update_objects/test_update_subplots.py b/plotly/tests/test_core/test_update_objects/test_update_subplots.py
index 7ad874021..bea73c2d3 100644
--- a/plotly/tests/test_core/test_update_objects/test_update_subplots.py
+++ b/plotly/tests/test_core/test_update_objects/test_update_subplots.py
@@ -18,7 +18,9 @@ class TestSelectForEachUpdateSubplots(TestCase):
             rows=3,
             cols=3,
             specs=[[{}, {'type': 'scene'}, {}],
-                   [{}, {'type': 'polar'}, {'type': 'polar'}],
+                   [{'secondary_y': True},
+                    {'type': 'polar'},
+                    {'type': 'polar'}],
                    [{'type': 'xy', 'colspan': 2}, None, {'type': 'ternary'}]]
         ).update(layout={'height': 800})
 
@@ -50,15 +52,22 @@ class TestSelectForEachUpdateSubplots(TestCase):
 
     def assert_select_subplots(
             self, subplot_type, subplots_name, expected_nums,
-            selector=None, row=None, col=None, test_no_grid=False):
+            selector=None, row=None, col=None, secondary_y=None,
+            test_no_grid=False):
 
         select_fn = getattr(Figure, 'select_' + subplots_name)
         for_each_fn = getattr(Figure, 'for_each_' + subplot_type)
 
+        if secondary_y is not None:
+            sec_y_args = dict(secondary_y=secondary_y)
+        else:
+            sec_y_args = {}
+
         def check_select(fig):
             # Check select_*
             subplots = list(
-                select_fn(fig, selector=selector, row=row, col=col))
+                select_fn(fig, selector=selector,
+                          row=row, col=col, **sec_y_args))
             expected_keys = [
                 subplot_type + (str(cnt) if cnt > 1 else '')
                 for cnt in expected_nums
@@ -73,8 +82,7 @@ class TestSelectForEachUpdateSubplots(TestCase):
             subplots = []
             res = for_each_fn(
                 fig, lambda obj: subplots.append(obj),
-                selector=selector, row=row, col=col
-            )
+                selector=selector, row=row, col=col, **sec_y_args)
 
             self.assertIs(res, fig)
 
@@ -92,7 +100,7 @@ class TestSelectForEachUpdateSubplots(TestCase):
             'xaxis', 'xaxes', [1, 2, 3, 4], test_no_grid=True)
 
         self.assert_select_subplots(
-            'yaxis', 'yaxes', [1, 2, 3, 4], test_no_grid=True)
+            'yaxis', 'yaxes', [1, 2, 3, 4, 5], test_no_grid=True)
 
         self.assert_select_subplots(
             'scene', 'scenes', [1], test_no_grid=True)
@@ -127,6 +135,19 @@ class TestSelectForEachUpdateSubplots(TestCase):
         self.assert_select_subplots(
             'xaxis', 'xaxes', [], row=2, col=2)
 
+    def test_select_by_secondary_y(self):
+        self.assert_select_subplots(
+            'yaxis', 'yaxes', [4], secondary_y=True)
+
+        self.assert_select_subplots(
+            'yaxis', 'yaxes', [1, 2, 3, 5], secondary_y=False)
+
+        self.assert_select_subplots(
+            'yaxis', 'yaxes', [4], col=1, secondary_y=True)
+
+        self.assert_select_subplots(
+            'yaxis', 'yaxes', [], col=3, secondary_y=True)
+
     def test_select_by_type_and_selector(self):
         # xaxis
         self.assert_select_subplots(
@@ -288,10 +309,16 @@ class TestSelectForEachUpdateSubplots(TestCase):
 
     def assert_update_subplots(
             self, subplot_type, subplots_name, expected_nums, patch=None,
-            selector=None, row=None, col=None, test_no_grid=False, **kwargs):
+            selector=None, row=None, col=None, secondary_y=None,
+            test_no_grid=False, **kwargs):
 
         update_fn = getattr(Figure, 'update_' + subplots_name)
 
+        if secondary_y is not None:
+            secy_kwargs = dict(secondary_y=secondary_y)
+        else:
+            secy_kwargs = {}
+
         def check_update(fig):
 
             # Copy input figure so that we don't modify it
@@ -300,7 +327,9 @@ class TestSelectForEachUpdateSubplots(TestCase):
 
             # perform update_*
             update_res = update_fn(
-                fig, patch, selector=selector, row=row, col=col, **kwargs)
+                fig, patch, selector=selector, row=row, col=col,
+                **dict(kwargs, **secy_kwargs))
+
             self.assertIs(update_res, fig)
 
             # Build expected layout keys
@@ -331,7 +360,7 @@ class TestSelectForEachUpdateSubplots(TestCase):
             test_no_grid=True)
 
         self.assert_update_subplots(
-            'yaxis', 'yaxes', [1, 2, 3, 4],
+            'yaxis', 'yaxes', [1, 2, 3, 4, 5],
             {'range': [5, 10]},
             test_no_grid=True)
 
@@ -395,6 +424,17 @@ class TestSelectForEachUpdateSubplots(TestCase):
             row=2,
             col=3)
 
+    def test_update_by_secondary_y(self):
+        self.assert_update_subplots(
+            'yaxis', 'yaxes', [4],
+            {'range': [5, 10]},
+            secondary_y=True)
+
+        self.assert_update_subplots(
+            'yaxis', 'yaxes', [1, 2, 3, 5],
+            {'range': [5, 10]},
+            secondary_y=False)
+
     def test_update_by_type_and_grid_and_selector(self):
         # xaxis
         self.assert_update_subplots(
diff --git a/plotly/tests/test_core/test_update_objects/test_update_traces.py b/plotly/tests/test_core/test_update_objects/test_update_traces.py
index 70e7a5280..fc4816878 100644
--- a/plotly/tests/test_core/test_update_objects/test_update_traces.py
+++ b/plotly/tests/test_core/test_update_objects/test_update_traces.py
@@ -16,7 +16,7 @@ class TestSelectForEachUpdateTraces(TestCase):
             rows=3,
             cols=2,
             specs=[[{}, {'type': 'scene'}],
-                   [{}, {'type': 'polar'}],
+                   [{'secondary_y': True}, {'type': 'polar'}],
                    [{'type': 'domain', 'colspan': 2}, None]]
         ).update(layout={'height': 800})
 
@@ -104,6 +104,17 @@ class TestSelectForEachUpdateTraces(TestCase):
             col=1
         )
 
+        # data[9], (2, 1) with secondary_y
+        fig.add_scatter(
+            mode='lines',
+            y=[1, 2, 0],
+            line={'color': 'purple'},
+            name='C',
+            row=2,
+            col=1,
+            secondary_y=True
+        )
+
         self.fig = fig
         self.fig_no_grid = go.Figure(self.fig.to_dict())
 
@@ -112,10 +123,13 @@ class TestSelectForEachUpdateTraces(TestCase):
 
     # select_traces and for_each_trace
     # --------------------------------
-    def assert_select_traces(self, expected_inds, selector=None, row=None, col=None, test_no_grid=False):
+    def assert_select_traces(
+            self, expected_inds, selector=None,
+            row=None, col=None, secondary_y=None, test_no_grid=False):
+
         # Select traces on figure initialized with make_subplots
         trace_generator = self.fig.select_traces(
-            selector=selector, row=row, col=col)
+            selector=selector, row=row, col=col, secondary_y=secondary_y)
         self.assertTrue(inspect.isgenerator(trace_generator))
 
         trace_list = list(trace_generator)
@@ -124,7 +138,7 @@ class TestSelectForEachUpdateTraces(TestCase):
         # Select traces on figure not containing subplot info
         if test_no_grid:
             trace_generator = self.fig_no_grid.select_traces(
-                selector=selector, row=row, col=col)
+                selector=selector, row=row, col=col, secondary_y=secondary_y)
             trace_list = list(trace_generator)
             self.assertEqual(trace_list, [self.fig_no_grid.data[i] for i in expected_inds])
 
@@ -135,6 +149,7 @@ class TestSelectForEachUpdateTraces(TestCase):
             selector=selector,
             row=row,
             col=col,
+            secondary_y=secondary_y
         )
         self.assertIs(for_each_res, self.fig)
 
@@ -143,7 +158,7 @@ class TestSelectForEachUpdateTraces(TestCase):
 
     def test_select_by_type(self):
         self.assert_select_traces(
-            [0, 2], selector={'type': 'scatter'}, test_no_grid=True)
+            [0, 2, 9], selector={'type': 'scatter'}, test_no_grid=True)
         self.assert_select_traces(
             [1], selector={'type': 'bar'}, test_no_grid=True)
         self.assert_select_traces(
@@ -160,25 +175,30 @@ class TestSelectForEachUpdateTraces(TestCase):
     def test_select_by_grid(self):
         # Row and column
         self.assert_select_traces([0, 1], row=1, col=1)
-        self.assert_select_traces([2, 3], row=2, col=1)
+        self.assert_select_traces([2, 3, 9], row=2, col=1)
         self.assert_select_traces([4, 5], row=1, col=2)
         self.assert_select_traces([6, 7], row=2, col=2)
         self.assert_select_traces([8], row=3, col=1)
 
         # Row only
         self.assert_select_traces([0, 1, 4, 5], row=1)
-        self.assert_select_traces([2, 3, 6, 7], row=2)
+        self.assert_select_traces([2, 3, 6, 7, 9], row=2)
         self.assert_select_traces([8], row=3)
 
         # Col only
-        self.assert_select_traces([0, 1, 2, 3, 8], col=1)
+        self.assert_select_traces([0, 1, 2, 3, 8, 9], col=1)
         self.assert_select_traces([4, 5, 6, 7], col=2)
 
+    def test_select_by_secondary_y(self):
+        self.assert_select_traces([2, 3, 9], row=2, col=1)
+        self.assert_select_traces([2, 3], row=2, col=1, secondary_y=False)
+        self.assert_select_traces([9], row=2, col=1, secondary_y=True)
+
     def test_select_by_property_across_trace_types(self):
         self.assert_select_traces(
             [0, 4, 6], selector={'mode': 'markers'}, test_no_grid=True)
         self.assert_select_traces(
-            [2, 5, 7], selector={'mode': 'lines'}, test_no_grid=True)
+            [2, 5, 7, 9], selector={'mode': 'lines'}, test_no_grid=True)
         self.assert_select_traces(
             [0, 4],
             selector={'marker': {'color': 'green', 'size': 10}},
@@ -191,7 +211,7 @@ class TestSelectForEachUpdateTraces(TestCase):
         self.assert_select_traces(
             [0, 4, 6], selector={'marker.color': 'green'}, test_no_grid=True)
         self.assert_select_traces(
-            [2, 5, 8], selector={'line.color': 'purple'}, test_no_grid=True)
+            [2, 5, 8, 9], selector={'line.color': 'purple'}, test_no_grid=True)
 
     def test_select_property_and_grid(self):
         # (1, 1)
@@ -202,7 +222,7 @@ class TestSelectForEachUpdateTraces(TestCase):
 
         # (2, 1)
         self.assert_select_traces(
-            [2], selector={'mode': 'lines'}, row=2, col=1)
+            [2, 9], selector={'mode': 'lines'}, row=2, col=1)
 
         # (1, 2)
         self.assert_select_traces(
@@ -233,7 +253,7 @@ class TestSelectForEachUpdateTraces(TestCase):
     # test update_traces
     # ------------------
     def assert_update_traces(self, expected_inds, patch=None, selector=None,
-                             row=None, col=None, **kwargs):
+                             row=None, col=None, secondary_y=None, **kwargs):
         # Save off original figure
         fig_orig = copy.deepcopy(self.fig)
         for trace1, trace2 in zip(fig_orig.data, self.fig.data):
@@ -241,7 +261,8 @@ class TestSelectForEachUpdateTraces(TestCase):
 
         # Perform update
         update_res = self.fig.update_traces(
-            patch, selector=selector, row=row, col=col, **kwargs
+            patch, selector=selector, row=row, col=col,
+            secondary_y=secondary_y, **kwargs
         )
 
         # Check chaining support
@@ -260,10 +281,10 @@ class TestSelectForEachUpdateTraces(TestCase):
             self.assertEqual(t_orig, t)
 
     def test_update_traces_by_type(self):
-        self.assert_update_traces([0, 2], {'visible': 'legendonly'},
+        self.assert_update_traces([0, 2, 9], {'visible': 'legendonly'},
                                   selector={'type': 'scatter'})
 
-        self.assert_update_traces([0, 2],
+        self.assert_update_traces([0, 2, 9],
                                   selector={'type': 'scatter'},
                                   visible=False)
 
@@ -321,3 +342,6 @@ class TestSelectForEachUpdateTraces(TestCase):
         self.assert_update_traces([6], {'marker.size': 6},
                                   selector={'marker.color': 'green'}, row=2,
                                   col=2)
+
+        self.assert_update_traces([9], {'marker.size': 6},
+                                  col=1, secondary_y=True)