diff --git a/larray_editor/arrayadapter.py b/larray_editor/arrayadapter.py
index 8cfbae9..d04e9a3 100644
--- a/larray_editor/arrayadapter.py
+++ b/larray_editor/arrayadapter.py
@@ -6,13 +6,15 @@
 
 
 class LArrayDataAdapter(object):
-    def __init__(self, axes_model, xlabels_model, ylabels_model, data_model,
-                 data=None, changes=None, current_filter=None, bg_gradient=None, bg_value=None):
+    def __init__(self, axes_model, hlabels_model, vlabels_model, data_model, data=None,
+                 changes=None, current_filter=None, nb_dims_hlabels=1, bg_gradient=None, bg_value=None):
         # set models
         self.axes_model = axes_model
-        self.xlabels_model = xlabels_model
-        self.ylabels_model = ylabels_model
+        self.hlabels_model = hlabels_model
+        self.vlabels_model = vlabels_model
         self.data_model = data_model
+        # set number of dims of hlabels
+        self.nb_dims_hlabels = nb_dims_hlabels
         # set current filter
         if current_filter is None:
             current_filter = {}
@@ -31,38 +33,49 @@ def set_changes(self, changes=None):
         assert isinstance(changes, dict)
         self.changes = changes
 
+    def update_nb_dims_hlabels(self, nb_dims_hlabels):
+        self.nb_dims_hlabels = nb_dims_hlabels
+        self.update_axes_and_labels()
+
     def get_axes_names(self):
         return self.filtered_data.axes.display_names
 
     def get_axes(self):
-        axes = self.filtered_data.axes
+        axes_names = self.filtered_data.axes.display_names
         # test self.filtered_data.size == 0 is required in case an instance built as LArray([]) is passed
         # test len(axes) == 0 is required when a user filters until to get a scalar
-        if self.filtered_data.size == 0 or len(axes) == 0:
+        if self.filtered_data.size == 0 or len(axes_names) == 0:
             return None
+        elif len(axes_names) == 1:
+            return [axes_names]
         else:
-            axes_names = axes.display_names
-            if len(axes_names) >= 2:
-                axes_names = axes_names[:-2] + [axes_names[-2] + '\\' + axes_names[-1]]
-            return [[axis_name] for axis_name in axes_names]
+            nb_dims_vlabels = len(axes_names) - self.nb_dims_hlabels
+            # axes corresponding to horizontal labels are set to the last column
+            res = [['' for c in range(nb_dims_vlabels-1)] + [axis_name] for axis_name in axes_names[nb_dims_vlabels:]]
+            # axes corresponding to vertical labels are set to the last row
+            res = res + [[axis_name for axis_name in axes_names[:nb_dims_vlabels]]]
+            return res
 
-    def get_xlabels(self):
+    def get_labels(self):
         axes = self.filtered_data.axes
         if self.filtered_data.size == 0 or len(axes) == 0:
-            return None
+            vlabels = None
+            hlabels = None
         else:
-            return [[label] for label in axes.labels[-1]]
-
-    def get_ylabels(self):
-        axes = self.filtered_data.axes
-        if self.filtered_data.size == 0 or len(axes) == 0:
-            return None
-        elif len(axes) == 1:
-            return [['']]
-        else:
-            labels = axes.labels[:-1]
-            prod = Product(labels)
-            return [_LazyDimLabels(prod, i) for i in range(len(labels))]
+            nb_dims_vlabels = len(axes) - self.nb_dims_hlabels
+            def get_labels_product(axes, extra_row=False):
+                if len(axes) == 0:
+                    return [[' ']]
+                else:
+                    # XXX: appends a fake axis instead of using _LazyNone because
+                    # _LazyNone mess up with LabelsArrayModel.get_values (in which slices are used)
+                    if extra_row:
+                        axes.append(la.Axis([' ']))
+                    prod = Product(axes.labels)
+                    return [_LazyDimLabels(prod, i) for i in range(len(axes.labels))]
+            vlabels = get_labels_product(axes[:nb_dims_vlabels])
+            hlabels = get_labels_product(axes[nb_dims_vlabels:], nb_dims_vlabels > 0)
+        return vlabels, hlabels
 
     def get_2D_data(self):
         """Returns Numpy 2D ndarray"""
@@ -109,6 +122,20 @@ def set_data(self, data, bg_value=None, current_filter=None):
         self.bg_value = la.aslarray(bg_value) if bg_value is not None else None
         self.update_filtered_data(current_filter, reset_minmax=True)
 
+    def update_axes_and_labels(self):
+        axes = self.get_axes()
+        vlabels, hlabels = self.get_labels()
+        self.axes_model.set_data(axes)
+        self.hlabels_model.set_data(hlabels)
+        self.vlabels_model.set_data(vlabels)
+
+    def update_data_2D(self, reset_minmax=False):
+        data_2D = self.get_2D_data()
+        changes_2D = self.get_changes_2D()
+        bg_value_2D = self.get_bg_value_2D(data_2D.shape)
+        self.data_model.set_data(data_2D, changes_2D, reset_minmax=reset_minmax)
+        self.data_model.set_bg_value(bg_value_2D)
+
     def update_filtered_data(self, current_filter=None, reset_minmax=False):
         if current_filter is not None:
             assert isinstance(current_filter, dict)
@@ -116,17 +143,8 @@ def update_filtered_data(self, current_filter=None, reset_minmax=False):
         self.filtered_data = self.la_data[self.current_filter]
         if np.isscalar(self.filtered_data):
             self.filtered_data = la.aslarray(self.filtered_data)
-        axes = self.get_axes()
-        xlabels = self.get_xlabels()
-        ylabels = self.get_ylabels()
-        data_2D = self.get_2D_data()
-        changes_2D = self.get_changes_2D()
-        bg_value_2D = self.get_bg_value_2D(data_2D.shape)
-        self.axes_model.set_data(axes)
-        self.xlabels_model.set_data(xlabels)
-        self.ylabels_model.set_data(ylabels)
-        self.data_model.set_data(data_2D, changes_2D, reset_minmax=reset_minmax)
-        self.data_model.set_bg_value(bg_value_2D)
+        self.update_axes_and_labels()
+        self.update_data_2D(reset_minmax=reset_minmax)
 
     def get_data(self):
         return self.la_data
diff --git a/larray_editor/arraymodel.py b/larray_editor/arraymodel.py
index 96a49f8..393720f 100644
--- a/larray_editor/arraymodel.py
+++ b/larray_editor/arraymodel.py
@@ -125,7 +125,8 @@ class LabelsArrayModel(AbstractArrayModel):
     font : QFont, optional
         Font. Default is `Calibri` with size 11.
     """
-    def __init__(self, parent=None, data=None, readonly=False, font=None):
+    def __init__(self, parent=None, data=None, readonly=False, font=None, orientation=Qt.Horizontal):
+        self.orientation = orientation
         AbstractArrayModel.__init__(self, parent, data, readonly, font)
         self.font.setBold(True)
 
@@ -136,8 +137,12 @@ def _set_data(self, data, changes=None):
             QMessageBox.critical(self.dialog, "Error", "Expected list or tuple.")
             data = [[]]
         self._data = data
-        self.total_rows = len(data[0])
-        self.total_cols = len(data) if self.total_rows > 0 else 0
+        if self.orientation == Qt.Horizontal:
+            self.total_rows = len(data) if self.total_cols > 0 else 0
+            self.total_cols = len(data[0])
+        else:
+            self.total_rows = len(data[0])
+            self.total_cols = len(data) if self.total_rows > 0 else 0
         self._compute_rows_cols_loaded()
 
     def flags(self, index):
@@ -145,19 +150,26 @@ def flags(self, index):
         return Qt.ItemIsEnabled
 
     def get_value(self, index):
-        i = index.row()
-        j = index.column()
-        # we need to inverse column and row because of the way ylabels are generated
-        return str(self._data[j][i])
+        if self.orientation == Qt.Horizontal:
+            i, j = index.row(), index.column()
+        else:
+            i, j = index.column(), index.row()
+        return str(self._data[i][j])
 
     # XXX: I wonder if we shouldn't return a 2D Numpy array of strings?
     def get_values(self, left=0, top=0, right=None, bottom=None):
-        if right is None:
-            right = self.total_rows
-        if bottom is None:
-            bottom = self.total_cols
-        values = [list(line[left:right]) for line in self._data[top:bottom]]
-        return values
+        if self.orientation == Qt.Horizontal:
+            if right is None:
+                right = self.total_cols
+            if bottom is None:
+                bottom = self.total_rows
+            return [list(line[left:right]) for line in self._data[top:bottom]]
+        else:
+            if right is None:
+                right = self.total_rows
+            if bottom is None:
+                bottom = self.total_cols
+            return [list(line[top:bottom]) for line in self._data[left:right]]
 
     def data(self, index, role=Qt.DisplayRole):
         # print('data', index.column(), index.row(), self.rowCount(), self.columnCount(), '\n', self._data)
diff --git a/larray_editor/arraywidget.py b/larray_editor/arraywidget.py
index 5b8d7d1..291bd70 100644
--- a/larray_editor/arraywidget.py
+++ b/larray_editor/arraywidget.py
@@ -536,17 +536,17 @@ def __init__(self, parent, data=None, readonly=False, bg_value=None, bg_gradient
         self.model_axes = LabelsArrayModel(parent=self, readonly=readonly)
         self.view_axes = LabelsView(parent=self, model=self.model_axes, position=(TOP, LEFT))
 
-        self.model_xlabels = LabelsArrayModel(parent=self, readonly=readonly)
-        self.view_xlabels = LabelsView(parent=self, model=self.model_xlabels, position=(TOP, RIGHT))
+        self.model_hlabels = LabelsArrayModel(parent=self, readonly=readonly)
+        self.view_hlabels = LabelsView(parent=self, model=self.model_hlabels, position=(TOP, RIGHT))
 
-        self.model_ylabels = LabelsArrayModel(parent=self, readonly=readonly)
-        self.view_ylabels = LabelsView(parent=self, model=self.model_ylabels, position=(BOTTOM, LEFT))
+        self.model_vlabels = LabelsArrayModel(parent=self, readonly=readonly, orientation=Qt.Vertical)
+        self.view_vlabels = LabelsView(parent=self, model=self.model_vlabels, position=(BOTTOM, LEFT))
 
         self.model_data = DataArrayModel(parent=self, readonly=readonly, minvalue=minvalue, maxvalue=maxvalue)
         self.view_data = DataView(parent=self, model=self.model_data, dtype=data.dtype, shape=data.shape)
 
-        self.data_adapter = LArrayDataAdapter(axes_model=self.model_axes, xlabels_model=self.model_xlabels,
-                                              ylabels_model=self.model_ylabels, data_model=self.model_data, data=data,
+        self.data_adapter = LArrayDataAdapter(axes_model=self.model_axes, hlabels_model=self.model_hlabels,
+                                              vlabels_model=self.model_vlabels, data_model=self.model_data, data=data,
                                               bg_value=bg_value, bg_gradient=bg_gradient)
 
         # Create vertical and horizontal scrollbars
@@ -554,15 +554,15 @@ def __init__(self, parent, data=None, readonly=False, bg_value=None, bg_gradient
         self.hscrollbar = ScrollBar(self, self.view_data.horizontalScrollBar())
 
         # Synchronize resizing
-        self.view_axes.horizontalHeader().sectionResized.connect(self.view_ylabels.updateSectionWidth)
-        self.view_axes.verticalHeader().sectionResized.connect(self.view_xlabels.updateSectionHeight)
-        self.view_xlabels.horizontalHeader().sectionResized.connect(self.view_data.updateSectionWidth)
-        self.view_ylabels.verticalHeader().sectionResized.connect(self.view_data.updateSectionHeight)
+        self.view_axes.horizontalHeader().sectionResized.connect(self.view_vlabels.updateSectionWidth)
+        self.view_axes.verticalHeader().sectionResized.connect(self.view_hlabels.updateSectionHeight)
+        self.view_hlabels.horizontalHeader().sectionResized.connect(self.view_data.updateSectionWidth)
+        self.view_vlabels.verticalHeader().sectionResized.connect(self.view_data.updateSectionHeight)
         # Synchronize auto-resizing
         self.view_axes.horizontalHeader().sectionHandleDoubleClicked.connect(self.resize_axes_column_to_contents)
-        self.view_xlabels.horizontalHeader().sectionHandleDoubleClicked.connect(self.resize_xlabels_column_to_contents)
+        self.view_hlabels.horizontalHeader().sectionHandleDoubleClicked.connect(self.resize_hlabels_column_to_contents)
         self.view_axes.verticalHeader().sectionHandleDoubleClicked.connect(self.resize_axes_row_to_contents)
-        self.view_ylabels.verticalHeader().sectionHandleDoubleClicked.connect(self.resize_ylabels_row_to_contents)
+        self.view_vlabels.verticalHeader().sectionHandleDoubleClicked.connect(self.resize_vlabels_row_to_contents)
 
         # synchronize specific methods
         self.view_axes.allSelected.connect(self.view_data.selectAll)
@@ -572,18 +572,18 @@ def __init__(self, parent, data=None, readonly=False, bg_value=None, bg_gradient
         self.view_data.signal_plot.connect(self.plot)
 
         # Synchronize scrolling
-        # data <--> xlabels
-        self.view_data.horizontalScrollBar().valueChanged.connect(self.view_xlabels.horizontalScrollBar().setValue)
-        self.view_xlabels.horizontalScrollBar().valueChanged.connect(self.view_data.horizontalScrollBar().setValue)
-        # data <--> ylabels
-        self.view_data.verticalScrollBar().valueChanged.connect(self.view_ylabels.verticalScrollBar().setValue)
-        self.view_ylabels.verticalScrollBar().valueChanged.connect(self.view_data.verticalScrollBar().setValue)
+        # data <--> hlabels
+        self.view_data.horizontalScrollBar().valueChanged.connect(self.view_hlabels.horizontalScrollBar().setValue)
+        self.view_hlabels.horizontalScrollBar().valueChanged.connect(self.view_data.horizontalScrollBar().setValue)
+        # data <--> vlabels
+        self.view_data.verticalScrollBar().valueChanged.connect(self.view_vlabels.verticalScrollBar().setValue)
+        self.view_vlabels.verticalScrollBar().valueChanged.connect(self.view_data.verticalScrollBar().setValue)
 
         # Synchronize selecting columns(rows) via hor.(vert.) header of x(y)labels view
-        self.view_xlabels.horizontalHeader().sectionPressed.connect(self.view_data.selectColumn)
-        self.view_xlabels.horizontalHeader().sectionEntered.connect(self.view_data.selectNewColumn)
-        self.view_ylabels.verticalHeader().sectionPressed.connect(self.view_data.selectRow)
-        self.view_ylabels.verticalHeader().sectionEntered.connect(self.view_data.selectNewRow)
+        self.view_hlabels.horizontalHeader().sectionPressed.connect(self.view_data.selectColumn)
+        self.view_hlabels.horizontalHeader().sectionEntered.connect(self.view_data.selectNewColumn)
+        self.view_vlabels.verticalHeader().sectionPressed.connect(self.view_data.selectRow)
+        self.view_vlabels.verticalHeader().sectionEntered.connect(self.view_data.selectNewRow)
 
         # following lines are required to keep usual selection color
         # when selecting rows/columns via headers of label views.
@@ -598,17 +598,17 @@ def __init__(self, parent, data=None, readonly=False, bg_value=None, bg_gradient
         array_frame.setFrameStyle(QFrame.StyledPanel)
         # remove borders of internal tables
         self.view_axes.setFrameStyle(QFrame.NoFrame)
-        self.view_xlabels.setFrameStyle(QFrame.NoFrame)
-        self.view_ylabels.setFrameStyle(QFrame.NoFrame)
+        self.view_hlabels.setFrameStyle(QFrame.NoFrame)
+        self.view_vlabels.setFrameStyle(QFrame.NoFrame)
         self.view_data.setFrameStyle(QFrame.NoFrame)
         # Set layout of table views:
-        # [ axes  ][xlabels]|V|
-        # [ylabels][ data  ]|s|
+        # [ axes  ][vlabels]|V|
+        # [hlabels][ data  ]|s|
         # |  H. scrollbar  |
         array_layout = QGridLayout()
         array_layout.addWidget(self.view_axes, 0, 0)
-        array_layout.addWidget(self.view_xlabels, 0, 1)
-        array_layout.addWidget(self.view_ylabels, 1, 0)
+        array_layout.addWidget(self.view_hlabels, 0, 1)
+        array_layout.addWidget(self.view_vlabels, 1, 0)
         self.view_data.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
         array_layout.addWidget(self.view_data, 1, 1)
         array_layout.addWidget(self.vscrollbar, 0, 2, 2, 1)
@@ -662,6 +662,13 @@ def __init__(self, parent, data=None, readonly=False, bg_value=None, bg_gradient
         btn_layout.addWidget(gradient_chooser)
         self.gradient_chooser = gradient_chooser
 
+        label = QLabel("Horizontal Dimensions")
+        btn_layout.addWidget(label)
+        spin = QSpinBox(self)
+        spin.valueChanged.connect(self.nb_horizontal_dims_changed)
+        self.nb_horizontal_dims_spinbox = spin
+        btn_layout.addWidget(spin)
+
         # Set widget layout
         layout = QVBoxLayout()
         layout.addLayout(self.filters_layout)
@@ -675,6 +682,8 @@ def __init__(self, parent, data=None, readonly=False, bg_value=None, bg_gradient
         # See http://doc.qt.io/qt-4.8/qt-draganddrop-fridgemagnets-dragwidget-cpp.html for an example
         self.setAcceptDrops(True)
 
+        self._raw_data_selection = None
+
     def gradient_changed(self, index):
         gradient = self.gradient_chooser.itemData(index) if index > 0 else None
         self.model_data.set_bg_gradient(gradient)
@@ -760,8 +769,9 @@ def set_data(self, data=None, bg_value=None):
         axes = la_data.axes
         display_names = axes.display_names
 
-        # update data format and bgcolor
-        self._update_digits_scientific(la_data)
+        # update data format and bgcolor + dim spinbox
+        self._update_digits_scientific_dims(la_data)
+        self.nb_horizontal_dims_spinbox.setValue(1)
 
         # update filters
         filters_layout = self.filters_layout
@@ -777,11 +787,11 @@ def set_data(self, data=None, bg_value=None):
 
         # reset default size
         self.view_axes.set_default_size()
-        self.view_ylabels.set_default_size()
-        self.view_xlabels.set_default_size()
+        self.view_hlabels.set_default_size()
+        self.view_vlabels.set_default_size()
         self.view_data.set_default_size()
 
-    def _update_digits_scientific(self, data):
+    def _update_digits_scientific_dims(self, data):
         """
         data : LArray
         """
@@ -810,6 +820,9 @@ def _update_digits_scientific(self, data):
 
         self.gradient_chooser.setEnabled(self.model_data.bgcolor_possible)
 
+        self.nb_horizontal_dims_spinbox.setMinimum(1)
+        self.nb_horizontal_dims_spinbox.setMaximum(max(1, self.data_adapter.ndim - 1))
+
     def choose_scientific(self, data):
         # max_digits = self.get_max_digits()
         # default width can fit 8 chars
@@ -902,37 +915,37 @@ def autofit_columns(self):
         self.view_axes.autofit_columns()
         for column in range(self.model_axes.columnCount()):
             self.resize_axes_column_to_contents(column)
-        self.view_xlabels.autofit_columns()
-        for column in range(self.model_xlabels.columnCount()):
-            self.resize_xlabels_column_to_contents(column)
+        self.view_hlabels.autofit_columns()
+        for column in range(self.model_hlabels.columnCount()):
+            self.resize_hlabels_column_to_contents(column)
 
     def resize_axes_column_to_contents(self, column):
         # must be connected to view_axes.horizontalHeader().sectionHandleDoubleClicked signal
         width = max(self.view_axes.horizontalHeader().sectionSize(column),
-                    self.view_ylabels.sizeHintForColumn(column))
-        # no need to call resizeSection on view_ylabels (see synchronization lines in init)
+                    self.view_vlabels.sizeHintForColumn(column))
+        # no need to call resizeSection on view_vlabels (see synchronization lines in init)
         self.view_axes.horizontalHeader().resizeSection(column, width)
 
-    def resize_xlabels_column_to_contents(self, column):
+    def resize_hlabels_column_to_contents(self, column):
         # must be connected to view_labels.horizontalHeader().sectionHandleDoubleClicked signal
-        width = max(self.view_xlabels.horizontalHeader().sectionSize(column),
+        width = max(self.view_hlabels.horizontalHeader().sectionSize(column),
                     self.view_data.sizeHintForColumn(column))
         # no need to call resizeSection on view_data (see synchronization lines in init)
-        self.view_xlabels.horizontalHeader().resizeSection(column, width)
+        self.view_hlabels.horizontalHeader().resizeSection(column, width)
 
     def resize_axes_row_to_contents(self, row):
         # must be connected to view_axes.verticalHeader().sectionHandleDoubleClicked
         height = max(self.view_axes.verticalHeader().sectionSize(row),
-                     self.view_xlabels.sizeHintForRow(row))
-        # no need to call resizeSection on view_xlabels (see synchronization lines in init)
+                     self.view_hlabels.sizeHintForRow(row))
+        # no need to call resizeSection on view_hlabels (see synchronization lines in init)
         self.view_axes.verticalHeader().resizeSection(row, height)
 
-    def resize_ylabels_row_to_contents(self, row):
+    def resize_vlabels_row_to_contents(self, row):
         # must be connected to view_labels.verticalHeader().sectionHandleDoubleClicked
-        height = max(self.view_ylabels.verticalHeader().sectionSize(row),
+        height = max(self.view_vlabels.verticalHeader().sectionSize(row),
                      self.view_data.sizeHintForRow(row))
         # no need to call resizeSection on view_data (see synchronization lines in init)
-        self.view_ylabels.verticalHeader().resizeSection(row, height)
+        self.view_vlabels.verticalHeader().resizeSection(row, height)
 
     @property
     def dirty(self):
@@ -942,7 +955,7 @@ def dirty(self):
     def accept_changes(self):
         """Accept changes"""
         la_data = self.data_adapter.accept_changes()
-        self._update_digits_scientific(la_data)
+        self._update_digits_scientific_dims(la_data)
 
     def reject_changes(self):
         """Reject changes"""
@@ -967,6 +980,9 @@ def digits_changed(self, value):
         self.digits = value
         self.model_data.set_format(self.cell_format)
 
+    def nb_horizontal_dims_changed(self, value):
+        self.data_adapter.update_nb_dims_hlabels(value)
+
     def create_filter_combo(self, axis):
         def filter_changed(checked_items):
             self.data_adapter.change_filter(axis, checked_items)
@@ -1001,17 +1017,17 @@ def _selection_data(self, headers=True, none_selects_all=True):
             if not self.data_adapter.ndim:
                 return raw_data
             # FIXME: this is extremely ad-hoc.
-            # TODO: in the future (pandas-based branch) we should use to_string(data[self._selection_filter()])
+            # TODO: in the future (multi_index supported) we should use to_string(data[self._selection_filter()])
             dim_headers = self.model_axes.get_values()
-            xlabels = self.model_xlabels.get_values(top=col_min, bottom=col_max)
-            topheaders = [[dim_header[0] for dim_header in dim_headers] + [label[0] for label in xlabels]]
+            hlabels = self.model_hlabels.get_values(left=col_min, right=col_max)
+            topheaders = [dims + labels for dims, labels in zip(dim_headers, hlabels)]
             if self.data_adapter.ndim == 1:
                 return chain(topheaders, [chain([''], row) for row in raw_data])
             else:
                 assert self.data_adapter.ndim > 1
-                ylabels = self.model_ylabels.get_values(left=row_min, right=row_max)
+                vlabels = self.model_vlabels.get_values(top=row_min, bottom=row_max)
                 return chain(topheaders,
-                             [chain([ylabels[j][r] for j in range(len(ylabels))], row)
+                             [chain([vlabels[j][r] for j in range(len(vlabels))], row)
                               for r, row in enumerate(raw_data)])
         else:
             return raw_data
@@ -1033,6 +1049,8 @@ def vrepr(v):
         clipboard = QApplication.clipboard()
         clipboard.setText(text)
 
+        self._raw_data_selection = self._selection_data(headers=False)
+
     def to_excel(self):
         """View selection in Excel"""
         if xw is None:
@@ -1052,22 +1070,25 @@ def paste(self):
         if bounds is None:
             return
         row_min, row_max, col_min, col_max = bounds
-        clipboard = QApplication.clipboard()
-        text = str(clipboard.text())
-        list_data = [line.split('\t') for line in text.splitlines()]
-        try:
-            # take the first cell which contains '\'
-            pos_last = next(i for i, v in enumerate(list_data[0]) if '\\' in v)
-        except StopIteration:
-            # if there isn't any, assume 1d array
-            pos_last = 0
-        if pos_last:
-            # ndim > 1
-            list_data = [line[pos_last + 1:] for line in list_data[1:]]
-        elif len(list_data) == 2 and list_data[1][0] == '':
-            # ndim == 1
-            list_data = [list_data[1][1:]]
-        new_data = np.array(list_data)
+        # clipboard = QApplication.clipboard()
+        # text = str(clipboard.text())
+        # list_data = [line.split('\t') for line in text.splitlines()]
+        # try:
+        #     # take the first cell which contains '\'
+        #     pos_last = next(i for i, v in enumerate(list_data[0]) if '\\' in v)
+        # except StopIteration:
+        #     # if there isn't any, assume 1d array
+        #     pos_last = 0
+        # if pos_last:
+        #     # ndim > 1
+        #     list_data = [line[pos_last + 1:] for line in list_data[1:]]
+        # elif len(list_data) == 2 and list_data[1][0] == '':
+        #     # ndim == 1
+        #     list_data = [list_data[1][1:]]
+        # new_data = np.array(list_data)
+        if self._raw_data_selection is None:
+            return
+        new_data = np.array(self._raw_data_selection)
         if new_data.shape[0] > 1:
             row_max = row_min + new_data.shape[0]
         if new_data.shape[1] > 1:
@@ -1093,12 +1114,13 @@ def plot(self):
         row_min, row_max, col_min, col_max = self.view_data._selection_bounds()
         dim_names = self.data_adapter.get_axes_names()
         # labels
-        xlabels = [label[0] for label in self.model_xlabels.get_values(top=col_min, bottom=col_max)]
-        ylabels = self.model_ylabels.get_values(left=row_min, right=row_max)
-        # transpose ylabels
-        ylabels = [[str(ylabels[i][j]) for i in range(len(ylabels))] for j in range(len(ylabels[0]))]
-        # if there is only one dimension, ylabels is empty
-        if not ylabels:
+        xlabels = self.model_hlabels.get_values(left=col_min, right=col_max, bottom=self.data_adapter.nb_dims_hlabels)
+        xlabels = [[str(xlabels[i][j]) for i in range(len(xlabels))] for j in range(len(xlabels[0]))]
+        if self.data_adapter.ndim > 1:
+            ylabels = self.model_vlabels.get_values(top=row_min, bottom=row_max)
+            # transpose ylabels
+            ylabels = [[str(ylabels[i][j]) for i in range(len(ylabels))] for j in range(len(ylabels[0]))]
+        else:
             ylabels = [[]]
 
         assert data.ndim == 2
@@ -1118,7 +1140,7 @@ def plot(self):
         else:
             # plot each row as a line
             xlabel = dim_names[-1]
-            xticklabels = [str(label) for label in xlabels]
+            xticklabels = ['\n'.join(row) for row in xlabels]
             xdata = np.arange(col_max - col_min)
             for row in range(len(data)):
                 ax.plot(xdata, data[row], label=' '.join(ylabels[row]))