I’m making a GUI using PyQt5 where I have hexagonal cells that have a button that is part of a circle at some vertex. However, I can’t find a way to make the clickable area of the button be the arc, and nothing but the arc. Even when I create a “slice” widget and paint it, there is either a clickable area outside of the slice (making a clickable area of a rectangle), or the clickable area is too small. How can I make a button of the correct shape?
Here is a stripped-down version of my code. The button position is determined by the channel_pos argument for the Hex class; 0 is the top vertex and they are numbered clockwise around the hexagon.
import sys, csv
import numpy as np
import pandas as pd
from PyQt5.QtWidgets import QApplication, QMainWindow, QPushButton, QLabel
from PyQt5.QtCore import Qt, QRectF, QRect, QPoint, QPointF
from PyQt5.QtGui import QPainter, QPen, QColor, QRegion, QPainterPath, QPolygonF
from PyQt5.QtWidgets import QWidget, QScrollArea, QVBoxLayout
class Hex(QWidget): #pads
def __init__(self, buttons, state, radius, cell_id, label_pos, channel_id, channel_pos, color, parent=None):
super().__init__(parent)
self.cell_id = cell_id
self.label_pos = label_pos
self.radius = radius
self.state = state
self.color = color
self.channel_id = channel_id
self.channel_pos = channel_pos
#make button, store it in button dict
self.button2 = WedgeButton(self.state, channel_id, self.channel_pos, ' ',
[self.radius/3*np.cos(channel_pos*np.pi/3 + np.pi/2),self.radius/3*np.sin(channel_pos*np.pi/3 + np.pi/2)], self.radius/1.5, self)
buttons[cell_id] = self.button2
def paintEvent(self, event):
#draw pad
painter = QPainter(self)
painter.setRenderHint(QPainter.Antialiasing)
vertices = [QPointF(self.radius * np.cos(x*np.pi/3 + np.pi/2) + self.radius, self.radius * np.sin(x*np.pi/3 + np.pi/2) + self.radius) for x in range (0,6)]
polygon = QPolygonF(vertices)
pen = QPen(QColor(self.color))
painter.setPen(pen)
painter.setBrush(QColor(self.color))
painter.drawPolygon(polygon)
# Draw label
font = painter.font()
font.setPointSize(9)
painter.setFont(font)
pen = QPen(Qt.black)
painter.setPen(pen)
#based on position number of channel, calculate position of button within pad
angle = 3*np.pi/2 + self.channel_pos*np.pi/3 #angle from center of cell to vertex identified by channel_pos
self.button2.setGeometry(int(self.radius - self.button2.radius + self.radius*np.cos(angle)),
int(self.radius - self.button2.radius + self.radius*np.sin(angle)),int(self.button2.radius*2),int(self.button2.radius*2))
self.button2.show()
class WedgeButton(QPushButton):
#whether or not front side buttons are active
active = 0
def __init__(self, state, channel_id, channel_pos, label, label_pos, radius, parent=None):
super().__init__(parent)
self.channel_id = channel_id
self.channel_pos = channel_pos
self.label_pos = label_pos
self.label = label
self.state = int(state)
self.radius = radius
self.clicked.connect(self.changeState)
def changeState(self):
old_state = self.state
self.state = (self.state + 1) % 4
self.update()
def paintEvent(self, event):
painter = QPainter(self)
painter.setRenderHint(QPainter.Antialiasing)
rect = QRectF(0, 0, self.width(), self.height())
pen = QPen(Qt.black)
painter.setPen(pen)
if self.state == 0:
painter.setBrush(QColor('#3498db'))
pen.setColor(QColor('#3498db'))
elif self.state == 1:
painter.setBrush(Qt.yellow)
pen.setColor(Qt.yellow)
elif self.state == 3:
painter.setBrush(Qt.red)
pen.setColor(Qt.red)
elif self.state == 2:
painter.setBrush(QColor('#ffbc36'))
pen.setColor(QColor('#ffbc36'))
painter.setPen(pen)
start_angle = ((210-self.channel_pos*60)*16)%(360*16)
span_angle = 120*16
painter.drawPie(0,0,int(2*self.radius),int(2*self.radius), start_angle, span_angle)
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setGeometry(0, 0, 1000, 900)
self.scroll = QScrollArea()
self.scroll.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn)
self.scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
self.scroll.setWidgetResizable(True) # Allow the widget inside the scroll area to resize
self.setCentralWidget(self.scroll)
self.widget = QWidget()
self.scroll.setWidget(self.widget)
self.vbox = QVBoxLayout() # Create a layout for the buttons
self.widget.setLayout(self.vbox) # Set the layout for the widget contained within the scroll area
self.widget.adjustSize()
self.buttons = {}
pad = Hex(self.buttons, 0,
30, 0, [0,18], 0,
0, '#d1dbe8',
parent = self.widget)
pad.setGeometry(500, 500, pad.radius*2, pad.radius*2)
if __name__ == "__main__":
app = QApplication(sys.argv)
mainWindow = MainWindow()
mainWindow.show()
sys.exit(app.exec_())
I tried using a setting a mask for the area, but that produced the same issues and was very pixellated.
Nedjma Kalliney is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.