diff --git a/examples/plot_vertical.py b/examples/plot_vertical.py
new file mode 100644
index 0000000..6709bc3
--- /dev/null
+++ b/examples/plot_vertical.py
@@ -0,0 +1,14 @@
+"""
+====================
+Vertical orientation
+====================
+
+This illustrates the effect of orientation='vertical'.
+"""
+
+from matplotlib import pyplot as plt
+from upsetplot import generate_data, plot
+
+example = generate_data(aggregated=True)
+plot(example, orientation='vertical')
+plt.show()
diff --git a/upsetplot/plotting.py b/upsetplot/plotting.py
index 5203bf3..1e635a9 100644
--- a/upsetplot/plotting.py
+++ b/upsetplot/plotting.py
@@ -52,6 +52,60 @@ def _process_data(data, sort_by, sort_sets_by):
     return data, totals
 
 
+class _Transposed:
+    """Wrap an object in order to transpose some plotting operations
+
+    Attributes of obj will be mapped.
+    Keyword arguments when calling obj will be mapped.
+
+    The mapping is not recursive: callable attributes need to be _Transposed
+    again.
+    """
+
+    def __init__(self, obj):
+        self.__obj = obj
+
+    def __getattr__(self, key):
+        return getattr(self.__obj, self._NAME_TRANSPOSE.get(key, key))
+
+    def __call__(self, *args, **kwargs):
+        return self.__obj(*args, **{self._NAME_TRANSPOSE.get(k, k): v
+                                    for k, v in kwargs.items()})
+
+    _NAME_TRANSPOSE = {
+        'width': 'height',
+        'height': 'width',
+        'hspace': 'wspace',
+        'wspace': 'hspace',
+        'hlines': 'vlines',
+        'vlines': 'hlines',
+        'bar': 'barh',
+        'barh': 'bar',
+        'xaxis': 'yaxis',
+        'yaxis': 'xaxis',
+        'left': 'bottom',
+        'right': 'top',
+        'top': 'right',
+        'bottom': 'left',
+        'sharex': 'sharey',
+        'sharey': 'sharex',
+        'get_figwidth': 'get_figheight',
+        'get_figheight': 'get_figwidth',
+        'set_figwidth': 'set_figheight',
+        'set_figheight': 'set_figwidth',
+    }
+
+
+def _transpose(obj):
+    if isinstance(obj, str):
+        return _Transposed._NAME_TRANSPOSE.get(obj, obj)
+    return _Transposed(obj)
+
+
+def _identity(obj):
+    return obj
+
+
 class UpSet:
     """Manage the data and drawing for a basic UpSet plot
 
@@ -63,9 +117,8 @@ class UpSet:
         Values for each set to plot.
         Should have multi-index where each level is binary,
         corresponding to set membership.
-    vert : bool
-        If True, the primary plot (bar chart of intersections) will
-        be vertical.
+    orientation : {'horizontal' (default), 'vertical'}
+        If horizontal, intersections are listed from left to right.
     sort_by : {'cardinality', 'degree'}
         If 'cardinality', set intersections are listed from largest to
         smallest value.
@@ -86,17 +139,16 @@ class UpSet:
         elements.
     totals_plot_elements : int
         The totals plot should be large enough to fit this many matrix
-        dots.
+        elements.
     """
 
-    def __init__(self, data, vert=True, sort_by='degree',
+    def __init__(self, data, orientation='horizontal', sort_by='degree',
                  sort_sets_by='cardinality', forecolor='black',
                  with_lines=True, element_size=32,
                  intersection_plot_elements=6, totals_plot_elements=2):
 
-        self._vert = vert
-        if not vert:
-            raise NotImplementedError()
+        self._horizontal = orientation == 'horizontal'
+        self._reorient = _identity if self._horizontal else _transpose
         self._forecolor = forecolor
         self._with_lines = with_lines
         self._element_size = element_size
@@ -107,6 +159,13 @@ class UpSet:
          self.totals) = _process_data(data,
                                       sort_by=sort_by,
                                       sort_sets_by=sort_sets_by)
+        if not self._horizontal:
+            self.intersections = self.intersections[::-1]
+
+    def _swapaxes(self, x, y):
+        if self._horizontal:
+            return x, y
+        return y, x
 
     def make_grid(self, fig=None):
         """Get a SubplotSpec for each Axes, accounting for label text width
@@ -124,11 +183,12 @@ class UpSet:
         t.remove()
 
         MAGIC_MARGIN = 10  # FIXME
-        figw = fig.get_window_extent(renderer=r).width
+        figw = self._reorient(fig.get_window_extent(renderer=r)).width
         if self._element_size is None:
             colw = (figw - textw - MAGIC_MARGIN) / (len(self.intersections) +
                                                     self._totals_plot_elements)
         else:
+            fig = self._reorient(fig)
             render_ratio = figw / fig.get_figwidth()
             colw = self._element_size / 72 * render_ratio
             figw = (colw * (len(self.intersections) +
@@ -142,26 +202,35 @@ class UpSet:
         text_nelems = int(np.ceil(figw / colw - (len(self.intersections) +
                                                  self._totals_plot_elements)))
 
-        GS = matplotlib.gridspec.GridSpec
-        gridspec = GS(n_cats + self._intersection_plot_elements,
-                      n_inters + text_nelems + self._totals_plot_elements,
+        GS = self._reorient(matplotlib.gridspec.GridSpec)
+        gridspec = GS(*self._swapaxes(n_cats +
+                                      self._intersection_plot_elements,
+                                      n_inters + text_nelems +
+                                      self._totals_plot_elements),
                       hspace=1)
-        return {'intersections': gridspec[:-n_cats, -n_inters:],
-                'matrix': gridspec[-n_cats:, -n_inters:],
-                'totals': gridspec[-n_cats:, :self._totals_plot_elements],
-                'gs': gridspec}
+        if self._horizontal:
+            return {'intersections': gridspec[:-n_cats, -n_inters:],
+                    'matrix': gridspec[-n_cats:, -n_inters:],
+                    'totals': gridspec[-n_cats:, :self._totals_plot_elements],
+                    'gs': gridspec}
+        else:
+            return {'intersections': gridspec[-n_inters:, n_cats:],
+                    'matrix': gridspec[-n_inters:, :n_cats],
+                    'totals': gridspec[:self._totals_plot_elements, :n_cats],
+                    'gs': gridspec}
 
     def plot_matrix(self, ax):
         """Plot the matrix of intersection indicators onto ax
         """
+        ax = self._reorient(ax)
         data = self.intersections
         n_sets = data.index.nlevels
 
         # alternating row shading (XXX: use add_patch(Rectangle)?)
         alternating = np.arange(0, n_sets, 2)
         ax.barh(alternating, np.full(len(alternating), len(data) + 1),
-                left=-1, color='#f5f5f5', zorder=0, linewidth=0,
-                align='center')
+                color='#f5f5f5', zorder=0, linewidth=0, align='center',
+                **{'left' if self._horizontal else 'bottom': -1})
 
         idx = np.flatnonzero(data.index.to_frame()[data.index.names].values)
         c = np.array(['lightgrey'] * len(data) * n_sets, dtype='O')
@@ -173,7 +242,7 @@ class UpSet:
         else:
             # TODO: make s relative to colw
             s = 200
-        ax.scatter(x, y, c=c.tolist(), linewidth=0, s=s)
+        ax.scatter(*self._swapaxes(x, y), c=c.tolist(), linewidth=0, s=s)
 
         if self._with_lines:
             line_data = (pd.Series(y[idx], index=x[idx])
@@ -183,36 +252,45 @@ class UpSet:
                       line_data['min'], line_data['max'],
                       lw=2, colors=self._forecolor)
 
-        ax.set_yticks(np.arange(n_sets))
-        ax.set_yticklabels(data.index.names)
+        tick_axis = ax.yaxis
+        tick_axis.set_ticks(np.arange(n_sets))
+        tick_axis.set_ticklabels(data.index.names,
+                                 rotation=0 if self._horizontal else -90)
         ax.xaxis.set_visible(False)
         ax.tick_params(axis='both', which='both', length=0)
+        if not self._horizontal:
+            ax.yaxis.set_ticks_position('top')
         ax.set_frame_on(False)
 
     def plot_intersections(self, ax):
         """Plot bars indicating intersection size
         """
+        ax = self._reorient(ax)
         ax.bar(np.arange(len(self.intersections)), self.intersections,
-               width=.5, color=self._forecolor, zorder=10, align='center')
+               .5, color=self._forecolor, zorder=10, align='center')
         ax.xaxis.set_visible(False)
         for x in ['top', 'bottom', 'right']:
-            ax.spines[x].set_visible(False)
-        ax.yaxis.grid(True)
-        ax.set_ylabel('Intersection size')
-        # ax.get_yaxis().set_tick_params(direction='in')
+            ax.spines[self._reorient(x)].set_visible(False)
+
+        tick_axis = ax.yaxis
+        tick_axis.grid(True)
+        tick_axis.set_label('Intersection size')
+        # tick_axis.set_tick_params(direction='in')
 
     def plot_totals(self, ax):
         """Plot bars indicating total set size
         """
+        orig_ax = ax
+        ax = self._reorient(ax)
         ax.barh(np.arange(len(self.totals.index.values)), self.totals,
-                height=.5, color=self._forecolor, align='center')
+                .5, color=self._forecolor, align='center')
         max_total = self.totals.max()
-        ax.set_xlim(max_total, 0)
+        if self._horizontal:
+            orig_ax.set_xlim(max_total, 0)
         for x in ['top', 'left', 'right']:
-            ax.spines[x].set_visible(False)
+            ax.spines[self._reorient(x)].set_visible(False)
         ax.yaxis.set_visible(False)
         ax.xaxis.grid(True)
-        ax.ticklabel_format(axis='x')
 
     def plot(self, fig=None):
         """Draw all parts of the plot onto fig or a new figure
@@ -232,9 +310,11 @@ class UpSet:
         specs = self.make_grid(fig)
         matrix_ax = fig.add_subplot(specs['matrix'])
         self.plot_matrix(matrix_ax)
-        inters_ax = fig.add_subplot(specs['intersections'], sharex=matrix_ax)
+        inters_ax = self._reorient(fig.add_subplot)(specs['intersections'],
+                                                    sharex=matrix_ax)
         self.plot_intersections(inters_ax, )
-        totals_ax = fig.add_subplot(specs['totals'], sharey=matrix_ax)
+        totals_ax = self._reorient(fig.add_subplot)(specs['totals'],
+                                                    sharey=matrix_ax)
         self.plot_totals(totals_ax)
         return {'matrix': matrix_ax,
                 'intersections': inters_ax,

Support transposed plot
Really, according to the original UpSet work, the current display is transposed. We should support the original format of having the intersection size bars horizontal.
diff --git a/upsetplot/tests/test_upsetplot.py b/upsetplot/tests/test_upsetplot.py
index 1dcfcab..4419b06 100644
--- a/upsetplot/tests/test_upsetplot.py
+++ b/upsetplot/tests/test_upsetplot.py
@@ -72,7 +72,9 @@ def test_param_validation(kw):
         UpSet(X, **kw)
 
 
-@pytest.mark.parametrize('kw', [{}, {'element_size': None}])
+@pytest.mark.parametrize('kw', [{},
+                                {'element_size': None},
+                                {'orientation': 'vertical'}])
 def test_plot_smoke_test(kw):
     fig = matplotlib.figure.Figure()
     X = generate_data(n_samples=100)
@@ -86,6 +88,25 @@ def test_plot_smoke_test(kw):
     assert plt.gcf().axes
 
 
+def test_vertical():
+    X = generate_data(n_samples=100)
+
+    fig = matplotlib.figure.Figure()
+    UpSet(X, orientation='horizontal').make_grid(fig)
+    horz_height = fig.get_figheight()
+    horz_width = fig.get_figwidth()
+    assert horz_height < horz_width
+
+    fig = matplotlib.figure.Figure()
+    UpSet(X, orientation='vertical').make_grid(fig)
+    vert_height = fig.get_figheight()
+    vert_width = fig.get_figwidth()
+    assert horz_width / horz_height > vert_width / vert_height
+
+    # TODO: test axes positions, plot order, bar orientation
+    pass
+
+
 def test_element_size():
     X = generate_data(n_samples=100)
     figsizes = []

pip install -e .
parse_log_pytest
requirements.txt
pytest pytest-cov
apt-get update apt-get install -y gcc
3.6
doc/requirements.txt
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
alabaster==0.7.13
attrs==22.2.0
Babel==2.11.0
certifi==2021.5.30
charset-normalizer==2.0.12
coverage==6.2
cycler==0.11.0
docutils==0.18.1
idna==3.10
imagesize==1.4.1
importlib-metadata==4.8.3
iniconfig==1.1.1
Jinja2==3.0.3
kiwisolver==1.3.1
MarkupSafe==2.0.1
matplotlib==3.3.4
numpy==1.19.5
numpydoc==1.1.0
packaging==21.3
pandas==1.1.5
Pillow==8.4.0
pluggy==1.0.0
py==1.11.0
Pygments==2.14.0
pyparsing==3.1.4
pytest==7.0.1
pytest-cov==4.0.0
python-dateutil==2.9.0.post0
pytz==2025.2
requests==2.27.1
scipy==1.5.4
six==1.17.0
snowballstemmer==2.2.0
Sphinx==5.3.0
sphinx-gallery==0.10.0
sphinx-issues==3.0.1
sphinxcontrib-applehelp==1.0.2
sphinxcontrib-devhelp==1.0.2
sphinxcontrib-htmlhelp==2.0.0
sphinxcontrib-jsmath==1.0.1
sphinxcontrib-qthelp==1.0.3
sphinxcontrib-serializinghtml==1.1.5
tomli==1.2.3
typing_extensions==4.1.1
-e git+https://github.com/jnothman/UpSetPlot.git@f6b066fe0e1a9b7ae06bc770b9a2ca69ce6468d8#egg=UpSetPlot
urllib3==1.26.20
zipp==3.6.0

name: UpSetPlot
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - ca-certificates=2025.2.25=h06a4308_0
  - certifi=2021.5.30=py36h06a4308_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.3=he6710b0_2
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - ncurses=6.4=h6a678d5_0
  - openssl=1.1.1w=h7f8727e_0
  - pip=21.2.2=py36h06a4308_0
  - python=3.6.13=h12debd9_1
  - readline=8.2=h5eee18b_0
  - setuptools=58.0.4=py36h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - wheel=0.37.1=pyhd3eb1b0_0
  - xz=5.6.4=h5eee18b_1
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - alabaster==0.7.13
      - attrs==22.2.0
      - babel==2.11.0
      - charset-normalizer==2.0.12
      - coverage==6.2
      - cycler==0.11.0
      - docutils==0.18.1
      - idna==3.10
      - imagesize==1.4.1
      - importlib-metadata==4.8.3
      - iniconfig==1.1.1
      - jinja2==3.0.3
      - kiwisolver==1.3.1
      - markupsafe==2.0.1
      - matplotlib==3.3.4
      - numpy==1.19.5
      - numpydoc==1.1.0
      - packaging==21.3
      - pandas==1.1.5
      - pillow==8.4.0
      - pluggy==1.0.0
      - py==1.11.0
      - pygments==2.14.0
      - pyparsing==3.1.4
      - pytest==7.0.1
      - pytest-cov==4.0.0
      - python-dateutil==2.9.0.post0
      - pytz==2025.2
      - requests==2.27.1
      - scipy==1.5.4
      - six==1.17.0
      - snowballstemmer==2.2.0
      - sphinx==5.3.0
      - sphinx-gallery==0.10.0
      - sphinx-issues==3.0.1
      - sphinxcontrib-applehelp==1.0.2
      - sphinxcontrib-devhelp==1.0.2
      - sphinxcontrib-htmlhelp==2.0.0
      - sphinxcontrib-jsmath==1.0.1
      - sphinxcontrib-qthelp==1.0.3
      - sphinxcontrib-serializinghtml==1.1.5
      - tomli==1.2.3
      - typing-extensions==4.1.1
      - urllib3==1.26.20
      - zipp==3.6.0
prefix: /opt/conda/envs/UpSetPlot

upsetplot/tests/test_upsetplot.py::test_plot_smoke_test[kw2] upsetplot/tests/test_upsetplot.py::test_vertical
upsetplot/tests/test_upsetplot.py::test_process_data[None-degree-X1] upsetplot/tests/test_upsetplot.py::test_process_data[cardinality-degree-X1]
upsetplot/tests/test_upsetplot.py::test_process_data[None-cardinality-X0] upsetplot/tests/test_upsetplot.py::test_process_data[None-cardinality-X1] upsetplot/tests/test_upsetplot.py::test_process_data[None-degree-X0] upsetplot/tests/test_upsetplot.py::test_process_data[cardinality-cardinality-X0] upsetplot/tests/test_upsetplot.py::test_process_data[cardinality-cardinality-X1] upsetplot/tests/test_upsetplot.py::test_process_data[cardinality-degree-X0] upsetplot/tests/test_upsetplot.py::test_not_aggregated[None-cardinality] upsetplot/tests/test_upsetplot.py::test_not_aggregated[None-degree] upsetplot/tests/test_upsetplot.py::test_not_aggregated[cardinality-cardinality] upsetplot/tests/test_upsetplot.py::test_not_aggregated[cardinality-degree] upsetplot/tests/test_upsetplot.py::test_param_validation[kw0] upsetplot/tests/test_upsetplot.py::test_param_validation[kw1] upsetplot/tests/test_upsetplot.py::test_param_validation[kw2] upsetplot/tests/test_upsetplot.py::test_param_validation[kw3] upsetplot/tests/test_upsetplot.py::test_param_validation[kw4] upsetplot/tests/test_upsetplot.py::test_plot_smoke_test[kw0] upsetplot/tests/test_upsetplot.py::test_plot_smoke_test[kw1] upsetplot/tests/test_upsetplot.py::test_element_size
upsetplot/plotting.py examples/plot_vertical.py
upsetplot/plotting.py examples/plot_vertical.py