Repository: spyder-ide/spyder

Issue:
Index column of Pandas DataFrame should be visible all the time
Hello,

Index of Pandas DataFrame should be in a frozen pane in variable explorer

![capture d ecran 2015-07-02 a 22 36 20](https://cloud.githubusercontent.com/assets/109167/8487256/20e02cec-210b-11e5-853e-b1cc924f66de.png)
![capture d ecran 2015-07-02 a 22 36 30](https://cloud.githubusercontent.com/assets/109167/8487259/226be470-210b-11e5-8ad5-8cedc575288a.png)

Kind regards



Discussion:
I can take a look at this one. Don't know if it's possible in Qt though :-)

also, why are the columns so unnecessarily wide? seems to waste an awful lot of space :-/

@andfoy, please work on this one. The idea is to make the first column of our DataFrame viewer fixed when people scroll to left and right.

The file you need to work on is `spyder/widgets/variableexplorer/dataframeeditor.py`.

And here you can find how to do it in Qt:

http://doc.qt.io/qt-5/qtwidgets-itemviews-frozencolumn-example.html

Resolving patch:
diff --git a/spyder/widgets/variableexplorer/dataframeeditor.py b/spyder/widgets/variableexplorer/dataframeeditor.py
--- a/spyder/widgets/variableexplorer/dataframeeditor.py
+++ b/spyder/widgets/variableexplorer/dataframeeditor.py
@@ -15,13 +15,14 @@
 
 # Third party imports
 from pandas import DataFrame, Series
-from qtpy import API
+from qtpy import API, PYQT5
 from qtpy.compat import from_qvariant, to_qvariant
 from qtpy.QtCore import QAbstractTableModel, QModelIndex, Qt, Signal, Slot
 from qtpy.QtGui import QColor, QCursor
 from qtpy.QtWidgets import (QApplication, QCheckBox, QDialogButtonBox, QDialog,
                             QGridLayout, QHBoxLayout, QInputDialog, QLineEdit,
-                            QMenu, QMessageBox, QPushButton, QTableView)
+                            QMenu, QMessageBox, QPushButton, QTableView,
+                            QHeaderView)
 import numpy as np
 
 # Local imports
@@ -399,12 +400,67 @@ def reset(self):
         self.endResetModel()
 
 
+class FrozenTableView(QTableView):
+    """
+    This class implements a table with its first column frozen
+    For more information please see:
+    http://doc.qt.io/qt-5/qtwidgets-itemviews-frozencolumn-example.html
+    """
+    def __init__(self, parent):
+        QTableView.__init__(self, parent)
+        self.parent = parent
+        self.setModel(parent.model())
+        self.setFocusPolicy(Qt.NoFocus)
+        self.verticalHeader().hide()
+        if PYQT5:
+            self.horizontalHeader().setSectionResizeMode(QHeaderView.Fixed)
+        else:
+            self.horizontalHeader().setResizeMode(QHeaderView.Fixed)
+
+        parent.viewport().stackUnder(self)
+
+        self.setSelectionModel(parent.selectionModel())
+        for col in range(1, parent.model().columnCount()):
+            self.setColumnHidden(col, True)
+
+        self.setColumnWidth(0, parent.columnWidth(0))
+        self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
+        self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
+        self.show()
+
+        self.setVerticalScrollMode(1)
+
+    def update_geometry(self):
+        """
+        Update the frozen column size when an update occurs 
+        in its parent table 
+        """
+        self.setGeometry(self.parent.verticalHeader().width() 
+                         + self.parent.frameWidth(),
+                         self.parent.frameWidth(), 
+                         self.parent.columnWidth(0),
+                         self.parent.viewport().height() + 
+                         self.parent.horizontalHeader().height())
+
+
 class DataFrameView(QTableView):
     """Data Frame view class"""
     def __init__(self, parent, model):
         QTableView.__init__(self, parent)
         self.setModel(model)
 
+        self.frozen_table_view = FrozenTableView(self)
+        self.frozen_table_view.update_geometry()
+
+        self.setHorizontalScrollMode(1)
+        self.setVerticalScrollMode(1)
+
+        self.horizontalHeader().sectionResized.connect(self.update_section_width)
+        self.verticalHeader().sectionResized.connect(self.update_section_height)
+
+        self.frozen_table_view.verticalScrollBar().valueChanged.connect(
+            self.verticalScrollBar().setValue)
+
         self.sort_old = [None]
         self.header_class = self.horizontalHeader()
         self.header_class.sectionClicked.connect(self.sortByColumn)
@@ -412,10 +468,66 @@ def __init__(self, parent, model):
         config_shortcut(self.copy, context='variable_explorer', name='copy',
                         parent=self)
         self.horizontalScrollBar().valueChanged.connect(
-                            lambda val: self.load_more_data(val, columns=True))
+                        lambda val: self.load_more_data(val, columns=True))
+        self.verticalScrollBar().valueChanged.connect(
+                        lambda val: self.load_more_data(val, rows=True))
         self.verticalScrollBar().valueChanged.connect(
-                               lambda val: self.load_more_data(val, rows=True))
+            self.frozen_table_view.verticalScrollBar().setValue)
     
+    def update_section_width(self, logical_index, old_size, new_size):
+        """
+        Update the horizontal width of the frozen column when a
+        change takes place in the first column of the table 
+        """
+        if logical_index == 0:
+            self.frozen_table_view.setColumnWidth(0, new_size)
+            self.frozen_table_view.update_geometry()
+
+    def update_section_height(logical_index, old_size, new_size):
+        """
+        Update the vertical width of the frozen column when a
+        change takes place on any of the rows 
+        """
+        self.frozen_table_view.setRowHeight(logical_index, new_size)
+
+    def resizeEvent(self, event):
+        """
+        Update the frozen column dimensions when the enclosing 
+        window of this table reports a dimension change 
+        """
+        QTableView.resizeEvent(self, event)
+        self.frozen_table_view.update_geometry()
+
+    def moveCursor(self, cursor_action, modifiers):
+        """
+        Update the table position, along with the frozen column
+        when the cursor (selector) changes its position  
+        """
+        current = QTableView.moveCursor(self, cursor_action, modifiers)
+        
+        col_width = (self.frozen_table_view.columnWidth(0) + 
+                     self.frozen_table_view.columnWidth(1))
+        topleft_x = self.visualRect(current).topLeft().x()
+
+        overflow = self.MoveLeft and current.column() > 1
+        overflow = overflow and topleft_x < col_width
+
+        if cursor_action == overflow:
+            new_value = (self.horizontalScrollBar().value() + 
+                         topleft_x - col_width)
+            self.horizontalScrollBar().setValue(new_value)
+        return current
+
+    def scrollTo(self, index, hint):
+        """
+        Scroll the table if necessary to ensure that the item at 
+        index is visible. The view will try to position the item 
+        according to the given hint. This method does not takes 
+        effect only if the frozen column is scrolled.
+        """
+        if index.column() > 1:
+            QTableView.scrollTo(self, index, hint)
+
     def load_more_data(self, value, rows=False, columns=False):
         if rows and value == self.verticalScrollBar().maximum():
             self.model().fetch_more(rows=rows)


Test patch: