I'd like to have a QTableWidget that has 2 'header' rows. Essentially I'd like to have the top 2 rows of the table not scroll vertically. For example:
Header 1 | Header 2
__________________
Header 3 | Header 4
__________________
Data | Data
__________________
Data | Data
__________________
Data | Data
__________________
Data | Data
__________________
I'd like to prevent any of the 4 headers (first two rows) not scroll off the screen as the user scrolls down.
I don't see anyway in Qt to add additional header rows or prevent scrolling of a single row. Maybe there is a tricky way to do this by having 2 actual tables and 1 of those tables having a single row which is a header?
I found a way to do this albeit in a somewhat obscure way. I did find a great example on how to do something similar, but with columns instead of header rows.
http://doc.qt.nokia.com/4.7-snapshot/itemviews-frozencolumn.html
I created two tables, one for the header rows and one for the data. I then hid the horizontal headers for the data table, set the margins/spacing for everything to 0. This squished the tables close enough together to look like a single table.
Make sure to hide the horizontal scroll bars for each individual table and then adding a new scrollbar that connected the two hidden scroll bars. So, when the user scrolls with the stand-alone scrollbar it triggers events on the 'real' hidden scroll bars. This way users can only have a single scrollbar to interact with.
I also had to catch all the signals from the QHeaderView class
and make sure to apply the changes requested by the signals to both tables at the same time.
The trickest part was making sure the width of the vertical header items were the same length in both tables. The width of a vertical header item gets set on an event called resizeEvent, http://www.riverbankcomputing.co.uk/static/Docs/PyQt4/html/qwidget.html#resizeEvent. So I had to override this method in my class to set the headers on both tables to the same width.
Code:
def resize(self):
"""
Called when we know the data table has been setup by Qt so we are
guaranteed that the headers now have a width, etc.
There is no other way to guarantee that your elements have been sized,
etc. by Qt other than this event.
"""
# Make the width of the vertical headers on the header table the same
# size as the initialized width of the data table (data table widths
# are setup automatically to fit the content)
width = self._data_table.verticalHeader().width()
self._header_table.verticalHeader().setFixedWidth(width)
Override the selectAll() method so when a user clicks the corner button, if enabled, all data in both tables will be selected:
Code:
def selectAll(self):
"""Select all data in both tables"""
for table in [self._header_table, self._data_table]:
for row in xrange(table.rowCount()):
for col in xrange(table.columnCount()):
item = table.item(row, col)
table.setItemSelected(item, True)
Related
To more accurately extract table-like data embedded within table cells, I would like to be able to identify table cell boundaries in PDFs like this:
I have tried extracting such tables using Camelot, pdfplumber, and PyMuPDF, with varying degrees of success. But due to the inconsistency of the PDFs we receive, I'm not able to reliably get accurate results, even when specifying the table bounds.
I find that the results are better if I extract each table cell individually, by specifying the cell boundaries explicitly. I have tested this by manually entering the boundaries, which I get using Camelot's visual debugging tool.
My challenge is how to identify table cell boundaries programmatically, since the table may start anywhere on the page, and the cells are of variable vertical height.
It seems to me that one could do this by finding the coordinates of the row separator lines, which are so obvious visually to a human. But I have not figured out how to find these lines using python tools. Is this possible, or are there other/better ways to solve this problem?
I recently had a similar use case where I needed to figure out the boundaries via code itself. For your use case, there are two options:
If you want to identify the boundary of the entire table, you can do the following:
import pdfplumber
pdf = pdfplumber.open('file_name.pdf')
p0 = pdf.pages[req_page] # go to the required page
tables = p0.debug_tablefinder() # list of tables which pdfplumber identifies
req_table = tables.tables[i] # Suppose you want to use ith table
req_table.bbox # gives you the bounding box of the table (coordinates)
You want to visit each cell in the table and extract, say words, from them:
import pdfplumber
pdf = pdfplumber.open('file_name.pdf')
p0 = pdf.pages[req_page] # go to the required page
tables = p0.debug_tablefinder() # list of tables which pdfplumber identifies
req_table = tables.tables[i] # Suppose you want to use ith table
cells = req_table.cells # gives list of all cells in that table
for cell in cells[i:j]: # iterating through the required cells
p0.crop(cell).extract_words() # extract the words
I'm developing a small interface in pyqt5 from QT Designer, this includes a QTableWidget but I want to assign different widths to the columns, I have found topics that talk about the same but I don't know exactly where to insert the code they provide, I don't know if it's because of the version, I'm quite new in QT Designer.
I leave the questions I mention for what it's worth.
PyQt:How do i set different header sizes for individual headers?
PyQt Set column widths
The structure of my files is as follows:
app.py: Stores the functionalities of the application
SGS.py: The code that is generated after converting the .ui file to .py
SGS.ui
I leave the SGS.py part where the table headers are generated for what it's worth.
item = self.TableDocs.horizontalHeaderItem(0)
item.setText(_translate("MainWindow", "IDsystem"))
item = self.TableDocs.horizontalHeaderItem(2)
item.setText(_translate("MainWindow", "IDpeople"))
item = self.TableDocs.horizontalHeaderItem(3)
item.setText(_translate("MainWindow", "Work"))
item = self.TableDocs.horizontalHeaderItem(4)
item.setText(_translate("MainWindow", "Hours"))
I also leave the code with which I fill the table
result = Cur.execute("SELECT idsystem,IDpeople,work,hours FROM workers")
self.TableDocs.setRowCount(0)
for row_number, row_data in enumerate(result):
self.TableDocs.insertRow(row_number)
for column_number, data in enumerate(row_data):
self.TableDocs.setItem(row_number, column_number, QtWidgets.QTableWidgetItem(str(data)))
The python file generated from the ui should never be edited. Consider it as a resource file (like an image or a json file) that is used to "create" the interface. Everything that you can't do from Designer you will have to implement in your application code files.
You can set the size (or resize mode, for example automatic resizing, or the "stretching" to the available width) whenever a model is applied to the item view. Since you're using a QTableWidget (which has its internal private model), you can do that as soon as the widget is created in the interface, which is right after setupUi (or loadUi) if using a designer file.
In order to set the size and behavior of the sections, you need to access the table headers: the horizontal one for the columns, the vertical for the rows.
class MyWindow(QtWidgets.QMainWindow, Ui_MainWindow):
def __init__(self):
super(MyWindow, self).__init__()
self.setupUi(self)
horizontalHeader = self.TableDocs.horizontalHeader()
# resize the first column to 100 pixels
horizontalHeader.resizeSection(0, 100)
# adjust the second column to its contents
horizontalHeader.setSectionResizeMode(
1, QtWidgets.QHeaderView.ResizeToContents)
# adapt the third column to fill all available space
horizontalHeader.setSectionResizeMode(
2, QtWidgets.QHeaderView.Stretch)
Note that if you remove a column and insert another one in the same place, you'll need to set its section size or mode once again.
Bokeh has the ability to display data in a dataframe as shown here:
http://docs.bokeh.org/en/latest/docs/user_guide/interaction/widgets.html#data-table
The Setup:
I have a dataframe of the following format:
Index|Location|Value
-----|--------|-----
1 |1 | 10
2 |1 | 20
3 |1 | 30
4 |2 | 20
5 |2 | 30
6 |2 | 40
This dataframe can be displayed in a data table like so:
source = ColumnDataSource(data={
LOCATION_NAME: [],
VALUE_NAME: []
})
columns = [
TableColumn(field=LOCATION_NAME, title=LOCATION_NAME),
TableColumn(field=VALUE_NAME, title=VALUE_NAME)
]
data_table = DataTable(source=source, columns=columns, width=400, height=800)
def update_dt(df):
"""Update the data table. This function is called upon some trigger"""
source.data = {
LOCATION_NAME: mt_val_df[LOCATION_NAME],
VALUE_NAME: mt_val_df[VALUE]}
Ideally, I want this datatable to drive a heatmap where selections made for each location will lead to a changed value in the heatmap. But a heatmap cannot have several values for one location. I also do not know how to pre-select items in a datatable.
Assume that I have a second dataframe:
Index|Location|Value
-----|--------|-----
2 |1 | 20
6 |2 | 40
This dataframe represents a subset of the above table - perhaps some custom selection of the above.
The Problem:
At the most basic level: I have the index of my selection of rows. How can I highlight/pre-select rows in the data table above based on the rows of the second dataframe?
Update (2017-07-14): So far I tried setting the selected index on the data source python side. Although source['selected']['1d'].indices = [LIST OF MY SELECTION] does correctly set the indices, I am not seeing a corresponding update on the front-end DataTable in Bokeh 0.12.5.
I have also tried setting the indices on the front-end. My problem there is I don't know how to pass in parameters via CustomJS that are not related to Bokeh.
At a more complete level: How can selections in the datatable drive the heatmap?
Update (2017-07-17): I have not gotten the proposed solution to work within the context of a Bokeh app! I am currently trying to find the cause but it's a bit tricky to follow why nothing gets selected in the end. My suspicion is that the code string gets instantiated in the beginning when the page loads. My coordinates, however, are not calculated until later. Therefore, hitting a button with the callback leads to the selection of nothing - even if later the row selection has been calculated. Continued help would be appreciated!
I have found a partial answer to the above questions thanks to the helpful comments of Claire Tang and Bryan Van de ven here.
Concerning Pre-selection not showing up on the DataTable
This turns out to be caused be two issues as far as I am aware.
1.) If I updated the selected index list in a CustomJS, I was missing to register the changes in the DataTable.
button.callback = CustomJS(args=dict(source2=source2), code="""
source2.selected['1d'].indices = [1,2,3];
//I did not "emit" my changed selection in the line below.
source2.properties.selected.change.emit();
console.log(source2)
""")
2.) The other important aspect to note is that I was on Bokeh version 0.12.5. In this version "source2.properties.selected" is an unknown property (perhaps because this function is located somehwhere else or not implemented yet). Therefore, selection also failed for me as long as I remained on Bokeh 0.12.5. An update to Bokeh 0.12.6 and the above line enabled selections to appear on the DataTable.
Concerning dynamic input from a Jupyter Notebook
The above example shows how I can use a button and a linked CustomJS callback to trigger selection of hard-coded lists. The question is how to feed in a list of index values based on some more dynamic calculations because CustomJS does not allow for external parameters that are not Bokeh related. On this topic, since CustomJS "code" attribute just takes a string. I tried the following:
dynamically_calculated_index = [1,2,3]
button.callback = CustomJS(args=dict(source1=source1, source2=source2), code="""
source2.selected['1d'].indices = {0};
source2.properties.selected.change.emit();
console.log(source2)
""".format(dynamically_calculated_index))
I am not sure if this is best practice, and so I welcome feedback in this regard, but this works for me for now. As pointed out by #DuCorey, once these changes are in the main branch of bokeh, some permutations of my issue could be more easily solved as described by him/her.
Also: This approach only works for a Jupyter Notebook where the entire cell gets recomputed again, and any pre-computed selected indices get bound at cell execution time. I can add a static list and it works for that, but if I want to dynamically calculate the above list, it will not work. I need to find a workaround still.
Solving the above issues now allows me to concentrate on propagating changes in what is selected to a heatmap.
Final answer using Bokeh server
The answer here was rather simple: It is possible to change the selected items, but it has to be done in the following way:
ds.selected = {'0d': {'glyph': None, 'indicices': []},
'1d': {indices': selected_index_list},
'2d': {}}
Previously, I had only tried to replace the 1d indices, but for some unknown reason, I have to actually replace the entire selected dictionary for the change in selected index to be registered by the bokeh app. So don't just do:
ds.selected['1d']['indices'] = selected_index_list
This now works for me. An explanation from someone more knowledgable would be appreciated though.
I managed to pre-select using this
resultPath = "path/to/some/file.tsv"
resultsTable = pandas.read_csv(resultPath,sep="\t")
source = ColumnDataSource(data=resultsTable)
source.selected.indices = [0,1,2,3,4] # KEY LINE
table = DataTable(source=source)
Spotfire 5.5
Hi, I am looking for a way to color code or group columns together in a Spotfire cross-table. I have three categories (nearest, any, all) and three columns associated with each category. Is there a way I can visually group these columns with their corresponding category.
Is there a way to change column heading color?
Is there a way to put a border around the three column groups?
Can I display their category above the three corresponding columns?
Thanks
One can make a color according to category by using Properties->Color->Add Rule , where you can see many conditions to apply your visualization.
I'm running 7.0.1.9 and there is still no customization on table headers - besides choosing the font. Using rules to color table values would be your best bet, but it does not apply to the headers themselves, so you're pretty much stuck.
I have seen this example of custom css styling for creating an export tool, but that requires a much complex approach (basically you'll reconstruct the whole analysis by html coding) and I'm not quite sure would allow for header customization... anyways here it is: http://stn.spotfire.com/stn/Tutorials/HowToCreateExportTool.aspx
1.Is there a way to change column heading color?
Ans : You can change the color for column headers. Please find the script below
if($('#my-style').length){ // do nothing style sheet already exists } else { // add CSS to the body $('body').append($('
.sf-element-table-cell.sfc-column-header { background-color:#566573;color:white;text-align:right;}
.sf-element-table-cell.sfc-row { background-color:#F4F6F6;color:Black;text-align:right;}
', { id: 'my-style' })); }
Is there a way to put a border around the three column groups?
Ans : You can do this if you have this column as rows in one column. if not you can perform Unpivot and bring them into one column. then build a cross tab by taking column reference as the category (nearest, any, all) column.
3. Can I display their category above the three corresponding columns?
Ans : you will get category once you perform above operation.
Please let me know if you need any inputs.
I need to display data using the PyQt class QTableWidget. But there could be dozens of thousand lines. Displaying the whole sheet makes the appli to hang...
So I found a solution consisting on loading data (meaning that the sheet is created) but hidding most of the rows and, when moving the scroll bar, show new rows and hidding previous ones.
In the bellow code, I setted self.rowsShown = 50 and at the init the lines from 1 to 50 are shown. And I previously did self.verticalScrollBar().actionTriggered.connect(self.refreshDisplay).
So the code is:
def refreshDisplay(self):
"""
This is to refresh the display when the scrollbar is moved
"""
# Minimum is reached and a previous row exists
if self.verticalScrollBar().value() <= self.verticalScrollBar().minimum() and self.isRowHidden(self.firstRowShown-1):# Minimum is reached and a previous row exists
for row in xrange(self.lastRowShown-1, self.lastRowShown):
self.hideRow(row)
self.lastRowShown -= 1
for row in xrange(self.firstRowShown-1, self.firstRowShown):
self.showRow(row)
self.firstRowShown -= 1
# Maximum is reached and a next row exists
if self.verticalScrollBar().value() >= self.verticalScrollBar().maximum() and self.isRowHidden(self.lastRowShown+1):
for row in xrange(self.firstRowShown, self.firstRowShown+1):
self.hideRow(row)
self.firstRowShown += 1
for row in xrange(self.lastRowShown, self.lastRowShown+1):
self.showRow(row)
self.lastRowShown += 1
This is working very well when I use the roll of the mouse or click on buttons of the scrollbar. But when I grab the slider or use the keypad to move the case selected, I'm blocked in the area shown.
Moreover, the size of the scrolling area correspond to the rows shown only. I would like to redifine it to set my own bar (for example with a size based on the real number of lines), so that I can move fastly to another part of the sheet. But the size of the scroll bar is always updated with the number of lines shown.
So, what I expect from you is to help me to resolve this two issues, OR give me another idea for displaying such huge sheet. Maybe I missed a module or class that already bear this functionnality of loading/showing lines whilemoving the cursor.
Thanks
I have written a tool that displays 10^6+ lines in an Excel-like table without resource problems. The solution is to use QTableView and QAbstractTableModel. You have to derive from QAbstractTableModel and implement the necessary functions it requires you to implement (I remember headerData and data, but I think there are more). You then plug the model into your view by doing view.setModel(model). More information how this is done can be found here.