I try to get a GUI application to work but unfortunately the spacing of the single canvas objects is not as expected. I want to have the following layout:
For the Grid Layout I use the following code:
# Adapt Widget StyleSheet
self.button_classification.setStyleSheet("font: bold 10pt Consolas; color: #009682") # #009682 is the KIT-Green
self.button_start.setStyleSheet('font: bold 10pt Consolas; color: #009682; border-style: outset; border-width: 2px; border-radius: 10px; border-color: #646873; padding: 6px')
self.layout4_image_widget.setStyleSheet('background-image: url(Python_logo.png);')
# Define Layouts
self.window = QWidget()
self.layout = QGridLayout()
# Add Widgets to layouts
self.layout.addWidget(self.canvas_left, 0, 0, 5, 5)
self.layout.addWidget(self.canvas_right, 0, 5, 5, 5)
self.layout.addWidget(self.canvas_right_buttom, 8, 7, 3, 3)
self.layout.addWidget(self.canvas_right_top, 5, 7, 3, 3)
self.layout.addWidget(self.canvas_middle_buttom, 8, 3, 3, 4)
self.layout.addWidget(self.canvas_middle_top, 5, 3, 3, 4)
self.layout.addWidget(self.layout4_image_widget, 5, 0, 3, 3)
self.layout.addWidget(self.button_start, 8, 0, 1, 3)
self.layout.addWidget(self.button_classification, 9, 0, 1, 3)
self.layout.addWidget(self.LCD_Number, 10, 0, 1, 3)
self.window.setLayout(self.layout)
But unfortunately what I receive as result is:
So the rows in the middle 5-7 are to wide.
Using column and row spans for widgets doesn't work as you seem to believe. Each "cell" in a grid layout might have different sizes, and it all depends on each widget sizeHint and sizePolicy.
All widget have a default size policy, which is a property that a widget uses to tell its container how it behaves when it is resized (should it expand, have a fixed height/width, can it shrink, etc).
By default, buttons and checkboxes have a fixed vertical policy, which means that they can never grow or shrink.
Every layout has a stretch value that can be assigned for each of its "slots". The stretch always defaults to 0, which leaves to each widget the possibility to grow or shrink to fit its needs, but when that value is set the available spaces are computed by the sum of each stretch in the layout and then divided equally. For example, if you have a simple layout with 2 widgets and a stretch=1 for the first and 2 for the second, the available space will be divided by 3 (1 + 2), with the first occupying a third of it and the second 2 thirds.
For grid layouts you can then use setRowStretch() and setColumnStretch() to achieve something like you proposed, but this would work only for widget that have compatible policies (preferred or expanding), and since in your case those buttons have fixed height it wouldn't be enough.
There are two possibilities, but in both cases I'll use a simpler layout, since there's no need to use a high count of rows and columns for the reasons explained above and setRowStretch/setColumnStretch can be used instead.
The layout uses just 4 columns:
+----+---+---+----+
| | |
+----+---+---+----+
| | | |
+----+---+---+----+
| | | |
+----+---+---+----+
The first row has two "cells" with a 2 column span, and the following rows have the 2 central slots united as a single cell with a similar span.
1. Manually set the policy for those widgets
In this case there are actually 5 rows, the first has a rowStretch=5, the second 3, and the remaining 1. By manually setting the vertical size policy to the widgets, they can grow to the height required by the stretch of the row they are occupying.
# first row, notice the spans
self.layout.addWidget(self.canvas_left, 0, 0, 1, 2)
self.layout.addWidget(self.canvas_right, 0, 2, 1, 2)
# second row, if the span is not specified defaults to (1, 1)
self.layout.addWidget(self.layout4_image_widget, 1, 0)
self.layout.addWidget(self.canvas_middle_top, 1, 1, 1, 2)
self.layout.addWidget(self.canvas_right_top, 1, 3)
# left widgets for third, fourth and fifth rows
self.layout.addWidget(self.button_start, 2, 0)
self.layout.addWidget(self.button_classification, 3, 0)
self.layout.addWidget(self.LCD_Number, 4, 0)
# remaining widgets, notice the vertical and horizontal spans
self.layout.addWidget(self.canvas_right_buttom, 2, 1, 3, 2)
self.layout.addWidget(self.canvas_middle_buttom, 2, 3, 3, 1)
# the column stretches set based on the grid given by you
self.layout.setColumnStretch(0, 3)
self.layout.setColumnStretch(1, 2)
self.layout.setColumnStretch(2, 2)
self.layout.setColumnStretch(3, 3)
# the row stretches, with the final 3 rows set to 1
self.layout.setRowStretch(0, 5)
self.layout.setRowStretch(1, 3)
self.layout.setRowStretch(2, 1)
self.layout.setRowStretch(3, 1)
self.layout.setRowStretch(4, 1)
# the default policy for both buttons and checkboxes is QSizePolicy.Minimum
# horizontally, and QSizePolicy.Fixed vertically, let's just change the latter
self.button_start.setSizePolicy(
QSizePolicy.Minimum, QSizePolicy.Preferred)
self.button_classification.setSizePolicy(
QSizePolicy.Minimum, QSizePolicy.Preferred)
Unfortunately this isn't perfect: as you can see the button might become too big and the checkbox has a lot of unnecessary margins that could be used by the LCD widget instead.
2. Use a container widget
In this case there only 3 rows, and an extra QWidget is used as container for the widgets on the bottom left.
self.layout.addWidget(self.canvas_left, 0, 0, 1, 2)
self.layout.addWidget(self.canvas_right, 0, 2, 1, 2)
self.layout.addWidget(self.layout4_image_widget, 1, 0)
self.layout.addWidget(self.canvas_middle_top, 1, 1, 1, 2)
self.layout.addWidget(self.canvas_right_top, 1, 3)
# create a container widget and add it to the main layout
self.container = QWidget()
self.layout.addWidget(self.container, 2, 0)
containerLayout = QVBoxLayout(self.container)
# add the widgets to the container
containerLayout.addWidget(self.button_start)
containerLayout.addWidget(self.button_classification)
containerLayout.addWidget(self.LCD_Number)
# the remaining widgets on the third row
self.layout.addWidget(self.canvas_right_buttom, 2, 1, 1, 2)
self.layout.addWidget(self.canvas_middle_buttom, 2, 3)
self.layout.setColumnStretch(0, 3)
self.layout.setColumnStretch(1, 2)
self.layout.setColumnStretch(2, 2)
self.layout.setColumnStretch(3, 3)
# there are only 3 rows, the second and third have the same row span
self.layout.setRowStretch(0, 5)
self.layout.setRowStretch(1, 3)
self.layout.setRowStretch(2, 3)
In this case the space usage is more optimized, leaving more space to those widgets that need it (the LCD).
https://doc.qt.io/qtforpython/PySide2/QtWidgets/QGridLayout.html#PySide2.QtWidgets.PySide2.QtWidgets.QGridLayout.setRowStretch
maybe there is something to do with this function... as they say the stretch factor has something to do with the row's relative space occupation and its default value is 0. You could maybe try setting them at 1 for each row.
Related
I'm trying to create an app with a file browser that is 1/5 the width of a tab pane.
Why is the layout columnSpan backwards from what I set it?
class MainWindow(QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
self.setWindowTitle("My App")
layout = QGridLayout()
layout.addWidget(FileTree(), 0, 0, 1, 1)
layout.addWidget(Tabs(), 0, 1, 1, 5)
widget = QWidget()
widget.setLayout(layout)
self.setCentralWidget(widget)
The row and column span refer to the grid cells of the layout, not the ratio of each row or column.
Setting a span means that the widget (or layout) will occupy that amount of cells, not that it will be 5 times bigger, as the size of each cell depends on the size (and hint) of its content. Since you have nothing else in the other 4 columns, those cells are actually empty.
It works exactly like a table: when you span a cell to make it occupy 2 columns, but the second column has no width, the cell will keep the same size.
Note that even if you had widgets in another row and in any of those columns, this would still not mean that the tab widget would occupy 5 times the space of the first, especially if those widgets were small: QTreeView inherits QAbstractItemView, which by default has an expanding size policy in both directions.
To achieve the wanted ratio, you must use setColumnStretch(), and not set any span unless you actually need it for widgets in other rows:
layout.addWidget(FileTree())
layout.addWidget(Tabs(), 0, 1)
layout.setColumnStretch(0, 1)
layout.setColumnStretch(1, 5)
I know the heading sounds confusing so I shall explain what I am trying to achieve here:
EDIT: Code example, should add up to 24 but does not
I have 10 data sources which all add up to my 11th data source.
I want to display this data on a 24 RGB ring, so i convert each data source into a percentage of the total.
However if I now try to display this on my LED ring by multiplying each percentage by 24, e.g. 0.56x24, 0.14x24, etc I don't always use all LEDs sometimes going over or under due to rounding up or down.
So my question is, whether there is a function which can distribute by data evenly and completely across the 24 LEDs?
I hope this explains what I am trying to achieve but please ask if you need more information.
Thanks
You might create a Waffle chart and take the same counts. Whatever algorithm Waffle uses, is probably well tried-out.
The complete source is on github, and isn't very long. Taking the same approach we get:
import matplotlib.pyplot as plt
from pywaffle import Waffle
num_leds = 24
values = [15812, 4162, 3054, 0, 43, 1564, 3080, 10, 320, 9]
labels = ['Black Coal', 'Brown Coal', 'Gas', 'Liquid Fuel', 'Other', 'Hydro', 'Wind', 'Large Solar', 'Small Solar', 'Battery']
colors = ['black', 'maroon', 'magenta', 'lightblue', 'red', 'mediumblue', 'coral', 'goldenrod', 'gold', 'blueviolet']
block_number_per_cat = [round(v * num_leds / sum(values)) for v in values]
blocks_per_label = {lab: num for lab, num in zip(labels, block_number_per_cat)}
print(blocks_per_label)
fig = plt.figure(
FigureClass=Waffle,
rows=1,
columns=num_leds,
values=values,
labels=labels,
colors=colors,
icons='lightbulb',
font_size=16,
legend={'loc': 'upper right', 'bbox_to_anchor': (1, -0.1)}
)
plt.show()
Answer:
{'Black Coal': 14, 'Brown Coal': 4, 'Gas': 3, 'Liquid Fuel': 0, 'Other': 0, 'Hydro': 1, 'Wind': 3, 'Large Solar': 0, 'Small Solar': 0, 'Battery': 0}
A solution
This code will generate the "color index" for each of your LEDs.
The input (data) is a list of totals from your data source:
# generate fake some data
# this should be coming from you
import random
data = [random.randint(500, 1000) for x in range(10)]
# compute the commutative sum of the entries
cumsum = [0,]
for i in range(len(data)):
cumsum.append(cumsum[i]+data[i])
cumsum.pop(0)
total = cumsum[-1]
# now we are ready to set the LEDs' color index
led_count = 24
leds = [0] * led_count
item = 0
for i in range(len(leds)):
while (i+1)/led_count > cumsum[item]/total:
item += 1
leds[i] = item
For example, if your totals (in data) are
[938, 765, 611, 980, 807, 961, 564, 919, 548, 888]
Then the results in leds will be
[0, 0, 1, 1, 1, 2, 3, 3, 3, 4, 4, 4, 5, 5, 5, 6, 7, 7, 7, 8, 8, 9, 9, 9]
Meaning the first to LEDs should be set to color 0, whatever that is, then the next three to color 1, etc.
How does this work?
Instead of trying to figure out how many LEDs we need for each category, the code figures out what category to assign to each LED. This guarantees that we will have no more, no less than the number of LEDs we actually have.
The code uses a cumulative sum to keep track where the change needs to happen from one category to the next.
E.g. instead of saying, we have 10%, 20%, 60%, 10% of each category, we consider a running tally: 10%, 30%, 90%, 100%.
Each LED represents 1/24% (for 24 LEDs). When marching through the LEDs (1/24%, 2/24%, 3/24%, 4/24%, ...) the code checks, if we crossed the threshold from one category to the next, and if we did, increments the category assigned to the current LED.
It's possible that the percentage to a category is so low, that it will be skipped entirely, but the algorithm will give you a distribution as good as possible.
Bonus points
Since ultimately you will have RGB values, it is an option to have "partial" LEDs.
To this, you'd need to keep track where in a LEDs interval is exactly the category boundary, and blend the colors accordingly.
This is not included in the code.
I'm using Pyqt5 to build a very simple GUI.
In this window I want to place some text with info on top of a grid layout. The grid is made of 2 columns and I want the text to go full width (like HTML attribute colspan). I can't find a way to place the entirety of the text.
Text is: "Return a list of same-distance points couples from a file of 2D points"
I tried setting the Qlabel containing the text as the 1x1 element of the grid and give it a width of 2 columns, I tried place i manually with the move function; either of these solutions does not show the text properly.
class MatchDistance(QWidget):
def initUI(self):
super().initUI()
self.setWindowTitle('Match distance')
info_label = QLabel("Return a list of same-distance points couples from a file of 2D points", self)
info_label.move(10, 10)
# QPushButton and QLineEdit setup [...]
self.grid.addWidget(self.input_file_1, 1, 1)
self.grid.addWidget(self.output_file, 2, 1)
self.grid.addWidget(self.btn_input_1, 1, 2)
self.grid.addWidget(self.btn_output, 2, 2)
self.grid.addWidget(self.btn_run, 3, 2)
self.grid.addWidget(self.btn_mainwindow, 4, 2)
self.setWindowTitle("script#1: Match distance")
self.show()
Your description is confusing so I will not refer to your implementation, but to respond you must take into account the following:
The indices that set the row and column start at 0.
If you use the layouts then you can no longer use move since the position is handled by the layout.
Considering the above, the solution is:
self.grid.addWidget(info_label, 0, 0, 1, 2)
self.grid.addWidget(self.input_file_1, 1, 0)
self.grid.addWidget(self.output_file, 2, 0)
self.grid.addWidget(self.btn_input_1, 1, 1)
self.grid.addWidget(self.btn_output, 2, 1)
self.grid.addWidget(self.btn_run, 3, 1)
self.grid.addWidget(self.btn_mainwindow, 4, 1)
I wrote a Python script using matplotlib, to visualize and rotate polycubes together with planes or axes that reveal symmetry in the polycube. Up to a certain point I succeeded as you can see from the illustrations below. There you see one of seventy-seven possible 3D polycubes of six cubes. My script uses matplotlib and it seems to do a nice job: matplotlib allows me to rotate the polycube and view it at any wanted angle. There is a big problem with most polycubes, which is illustrated in the second image below. A little after I start rotating the polycube, matplotlib shows planes that are partly behind other planes and are therefore partly invisble and not to be drawn or only partly drawn.
I have been searching a lot on forums and with Google, but to no avail. There were hits suggesting that I should use mayavi instead of matplotlib. So I studied extensively on mayavi. I spent literally weeks trying to figure out how to get mayavi going. The hits on docs.enthought seemed promising at first, but although mayavi is clearly suited to the purpose and superb in visualizing objects, I cannot find understandable documentation. I could use a real programmers guide on vtk or tvtk. There is a lot of documentation, but mostly for designers, not programmers. If not available (?) I would also be happy with an example of a script with hexahedrons or irregular grids, that works in canopy's (1.6.2) implementation of python.
I've modified an example from the vtk documentation (http://www.vtk.org/gitweb?p=VTK.git;a=blob_plain;f=Examples/DataManipulation/Python/BuildUGrid.py)
This example can be turned into a function which can create the geometry from a point coordinate array and an element array.
I haven't used any guides for vtk, I usually refer to the python examples found here: http://www.vtk.org/Wiki/VTK/Examples/Python
import vtk
# Initialize the vtkPoints variable and set the number of points
points = vtk.vtkPoints()
points.SetNumberOfPoints(8)
# Add points to the variable, with the point number first, then the x, y, z coordinates.
# For demonstration purposes, I started numbering the ponts at 10 (normally they would start at 0).
points.InsertPoint(0, 0, 0, 0)
points.InsertPoint(1, 1, 0, 0)
points.InsertPoint(2, 1, 1, 0)
points.InsertPoint(3, 0, 1, 0)
points.InsertPoint(4, 0, 0, 1)
points.InsertPoint(5, 1, 0, 1)
points.InsertPoint(6, 1, 1, 1)
points.InsertPoint(7, 0, 1, 1)
points.InsertPoint(8, 0, 0, 1.1)
points.InsertPoint(9, 1, 0, 1.1)
points.InsertPoint(10, 1, 1, 1.1)
points.InsertPoint(11, 0, 1, 1.1)
points.InsertPoint(12, 0, 0, 2)
points.InsertPoint(13, 1, 0, 2)
points.InsertPoint(14, 1, 1, 2)
points.InsertPoint(15, 0, 1, 2.5)
# Define the hexahedron, then set the point Ids of the hexahedron cell/element.
# From the documentation: points (0,1,2,3) is the base of the hexahedron which, using the right hand rule, forms a
# quadrilaterial whose normal points in the direction of the opposite face (4,5,6,7)
aHexahedron1 = vtk.vtkHexahedron()
aHexahedron1.GetPointIds().SetId(0, 0) # Cell point 0 corresponds to point 0 which was defined above
aHexahedron1.GetPointIds().SetId(1, 1)
aHexahedron1.GetPointIds().SetId(2, 2)
aHexahedron1.GetPointIds().SetId(3, 3)
aHexahedron1.GetPointIds().SetId(4, 4)
aHexahedron1.GetPointIds().SetId(5, 5)
aHexahedron1.GetPointIds().SetId(6, 6)
aHexahedron1.GetPointIds().SetId(7, 7)
# Define a second hexahedron
aHexahedron2 = vtk.vtkHexahedron()
aHexahedron2.GetPointIds().SetId(0, 8) # Cell point 0 corresponds to point 8 which was defined above
aHexahedron2.GetPointIds().SetId(1, 9)
aHexahedron2.GetPointIds().SetId(2, 10)
aHexahedron2.GetPointIds().SetId(3, 11)
aHexahedron2.GetPointIds().SetId(4, 12)
aHexahedron2.GetPointIds().SetId(5, 13)
aHexahedron2.GetPointIds().SetId(6, 14)
aHexahedron2.GetPointIds().SetId(7, 15)
# Define an unstructured grid.
aHexahedronGrid = vtk.vtkUnstructuredGrid()
# Add the hexahedron to the unstructured grid
# Note: this operation defines the point ids, and not the actual point coordinates
aHexahedronGrid.InsertNextCell(aHexahedron1.GetCellType(), aHexahedron1.GetPointIds())
aHexahedronGrid.InsertNextCell(aHexahedron2.GetCellType(), aHexahedron2.GetPointIds())
# Set the points which includes the coordinates. The point ids defined in the line above correspond to the point ids
# that were defined earlier (i.e. points.InsertPoint(10, 0, 0, 0))
aHexahedronGrid.SetPoints(points)
# Now we have defined one hexahedron, and added it an unstructured grid.
# We could create more hexahedrons, and add them to the same unstructured grid.
# To view the unstructured grid, we need to define a mapper and set the unstructured grid as the input
aHexahedronMapper = vtk.vtkDataSetMapper()
aHexahedronMapper.SetInputData(aHexahedronGrid)
# Define an actor, and set the mapper as the input
aHexahedronActor = vtk.vtkActor()
aHexahedronActor.SetMapper(aHexahedronMapper)
# Create the usual rendering stuff.
ren = vtk.vtkRenderer()
renWin = vtk.vtkRenderWindow()
renWin.AddRenderer(ren)
iren = vtk.vtkRenderWindowInteractor()
iren.SetRenderWindow(renWin)
iren.SetInteractorStyle(vtk.vtkInteractorStyleTrackballCamera()) # Change the rotation type from the default to 'trackball'
ren.SetBackground(.1, .2, .4)
# Add the actor to the renderer to actually view the geometry
ren.AddActor(aHexahedronActor)
# Render the scene and start interaction.
iren.Initialize()
renWin.Render()
iren.Start()
I am having troubles expanding a StaticBox.
Here is the code i am using:
self.images_area = wx.StaticBox(self, -1, '')
self.sizerBox = wx.BoxSizer(wx.HORIZONTAL)
self.sizerBox.Add(self.images_area, 0, wx.EXPAND|wx.ALL, 10)
self.SetSizer(self.sizerBox)
It appears to be working vertically however it does not expand horizontally (which I would have thought the opposite since I used wx.HORIZONTAL in the BoxSizer)
use
self.sizerBox.Add(self.images_area, 1, wx.EXPAND|wx.ALL, 10)
You have some indications in the wxPyWiki (point 8) of how EXPAND and the proportion parameters work to give the behavior of your widget.