弧形中的不同颜色
Different colours in an arc
考虑以下玩具示例:
from PyQt5 import QtWidgets, QtGui, QtCore
class MainWindow(QtWidgets.QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
w = QtWidgets.QWidget()
layout = QtWidgets.QVBoxLayout()
w.setLayout(layout)
self.setCentralWidget(w)
label = QtWidgets.QLabel()
canvas = QtGui.QPixmap(400, 300)
label.setPixmap(canvas)
layout.addWidget(label)
def paintEvent():
painter = QtGui.QPainter(label.pixmap())
painter.setRenderHint(QtGui.QPainter.Antialiasing)
painter.setPen(QtCore.Qt.red)
painter.drawArc(0, 0, 100, 100, 1440, -2880)
painter.end()
paintEvent()
self.show()
app = QtWidgets.QApplication([])
window = MainWindow()
app.exec_()
如何使用任意数量的颜色绘制弧线(理想情况下长度不同)?
我试着用渐变(线性和圆锥形)来做,但我一直无法获得准确的结果。
我想更广泛的问题是在绘制弧线时我可以使用不同的笔颜色吗?请注意,圆弧可以是半圆、整圆或介于两者之间的任何形状。
颜色将使用百分比分布。每种颜色都是弧长的一部分。但我对所有颜色均等间距的解决方案感到满意。
一个可能的解决方案是分段绘制圆弧:
from PyQt5 import QtCore, QtGui, QtWidgets
class MainWindow(QtWidgets.QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
w = QtWidgets.QWidget()
layout = QtWidgets.QVBoxLayout()
w.setLayout(layout)
self.setCentralWidget(w)
label = QtWidgets.QLabel()
canvas = QtGui.QPixmap(400, 300)
canvas.fill(QtGui.QColor("white"))
label.setPixmap(canvas)
layout.addWidget(label)
def paint_label():
painter = QtGui.QPainter(label.pixmap())
painter.setRenderHint(QtGui.QPainter.Antialiasing)
r = QtCore.QRect(0, 0, 100, 100)
delta_angle = -180 * 16
start_angle = 90 * 16
values = (1, 2, 3, 4)
colors = (
QtGui.QColor("red"),
QtGui.QColor("blue"),
QtGui.QColor("green"),
QtGui.QColor("yellow"),
)
sum_of_values = sum(values)
for value, color in zip(values, colors):
end_angle = start_angle + int((value/sum_of_values) * delta_angle)
painter.setPen(color)
painter.drawArc(r, start_angle, end_angle - start_angle)
start_angle = end_angle
painter.end()
paint_label()
self.show()
def main():
app = QtWidgets.QApplication([])
window = MainWindow()
app.exec_()
if __name__ == "__main__":
main()
解决方案 非常好,但我想展示使用圆锥形渐变而不是绘制单个圆弧实现相同结果的可能性。
由于我们想要绘制实际的弧线,诀窍是使用 "ranges" 具有非常窄边距的颜色。
例如,要获得半红半蓝的锥形渐变,我们将使用如下内容:
gradient.setColorAt(.5, QtCore.Qt.red)
# set the next color with a stop very close to the previous
gradient.setColorAt(.500001, QtCore.Qt.blue)
我准备了一个带有小界面的例子来测试它的可能性。
class MainWindow(QtWidgets.QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
w = QtWidgets.QWidget()
layout = QtWidgets.QVBoxLayout()
w.setLayout(layout)
self.setCentralWidget(w)
panelLayout = QtWidgets.QHBoxLayout()
layout.addLayout(panelLayout)
panelLayout.addWidget(QtWidgets.QLabel('Start'))
self.startSpin = QtWidgets.QSpinBox(maximum=360, suffix='°')
self.startSpin.setValue(90)
panelLayout.addWidget(self.startSpin)
panelLayout.addWidget(QtWidgets.QLabel('Extent'))
self.extentSpin = QtWidgets.QSpinBox(maximum=360, suffix='°')
self.extentSpin.setValue(180)
panelLayout.addWidget(self.extentSpin)
panelLayout.addWidget(QtWidgets.QLabel('Width'))
self.penSpin = QtWidgets.QSpinBox(minimum=1, maximum=20, suffix='px')
self.penSpin.setValue(3)
panelLayout.addWidget(self.penSpin)
self.startSpin.valueChanged.connect(self.updateCanvas)
self.extentSpin.valueChanged.connect(self.updateCanvas)
self.penSpin.valueChanged.connect(self.updateCanvas)
self.colors = []
self.colorSpins = []
colorLayout = QtWidgets.QHBoxLayout()
layout.addLayout(colorLayout)
for color in ('red', 'green', 'blue', 'yellow'):
colorLayout.addWidget(QtWidgets.QLabel(color))
self.colors.append(QtGui.QColor(color))
colorSpin = QtWidgets.QSpinBox(minimum=1, maximum=50, value=25)
colorLayout.addWidget(colorSpin)
colorSpin.valueChanged.connect(self.updateCanvas)
self.colorSpins.append(colorSpin)
self.label = QtWidgets.QLabel()
canvas = QtGui.QPixmap(400, 300)
self.label.setPixmap(canvas)
layout.addWidget(self.label)
self.updateCanvas()
self.show()
def updateCanvas(self):
pm = QtGui.QPixmap(self.label.pixmap().size())
pm.fill(QtCore.Qt.transparent)
painter = QtGui.QPainter(pm)
painter.setRenderHint(QtGui.QPainter.Antialiasing)
painter.translate(.5, .5)
sizes = [spin.value() for spin in self.colorSpins]
total = sum(sizes)
extent = self.extentSpin.value() / 360
grad = QtGui.QConicalGradient(50, 50, self.startSpin.value())
gradPos = 1
# set colors starting from stop 1.0 to (1.0 - extent), since
# conical gradients are always counter-clockwise and the actual arc
# is negative, so it is drawn clockwise
for i, (size, color) in enumerate(zip(sizes, self.colors)):
grad.setColorAt(gradPos, color)
gradPos -= size / total * extent
if i < len(self.colors) - 1:
# extend the color right next to the next value
grad.setColorAt(gradPos + .000001, color)
if extent != 1:
# ensure that the first color is not painted at the edget of the
# last due to antialiasing
grad.setColorAt(0, self.colors[0])
grad.setColorAt(1 - extent, self.colors[-1])
offset = self.penSpin.maximum()
pen = QtGui.QPen(grad, self.penSpin.value(), cap=QtCore.Qt.FlatCap)
painter.setPen(pen)
# move the brush origin so that the conical gradient correctly centered
# in the middle of the ellipse
painter.setBrushOrigin(offset, offset)
painter.drawArc(offset, offset, 100, 100, self.startSpin.value() * 16, -self.extentSpin.value() * 16)
painter.end()
self.label.setPixmap(pm)
考虑以下玩具示例:
from PyQt5 import QtWidgets, QtGui, QtCore
class MainWindow(QtWidgets.QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
w = QtWidgets.QWidget()
layout = QtWidgets.QVBoxLayout()
w.setLayout(layout)
self.setCentralWidget(w)
label = QtWidgets.QLabel()
canvas = QtGui.QPixmap(400, 300)
label.setPixmap(canvas)
layout.addWidget(label)
def paintEvent():
painter = QtGui.QPainter(label.pixmap())
painter.setRenderHint(QtGui.QPainter.Antialiasing)
painter.setPen(QtCore.Qt.red)
painter.drawArc(0, 0, 100, 100, 1440, -2880)
painter.end()
paintEvent()
self.show()
app = QtWidgets.QApplication([])
window = MainWindow()
app.exec_()
如何使用任意数量的颜色绘制弧线(理想情况下长度不同)?
我试着用渐变(线性和圆锥形)来做,但我一直无法获得准确的结果。
我想更广泛的问题是在绘制弧线时我可以使用不同的笔颜色吗?请注意,圆弧可以是半圆、整圆或介于两者之间的任何形状。
颜色将使用百分比分布。每种颜色都是弧长的一部分。但我对所有颜色均等间距的解决方案感到满意。
一个可能的解决方案是分段绘制圆弧:
from PyQt5 import QtCore, QtGui, QtWidgets
class MainWindow(QtWidgets.QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
w = QtWidgets.QWidget()
layout = QtWidgets.QVBoxLayout()
w.setLayout(layout)
self.setCentralWidget(w)
label = QtWidgets.QLabel()
canvas = QtGui.QPixmap(400, 300)
canvas.fill(QtGui.QColor("white"))
label.setPixmap(canvas)
layout.addWidget(label)
def paint_label():
painter = QtGui.QPainter(label.pixmap())
painter.setRenderHint(QtGui.QPainter.Antialiasing)
r = QtCore.QRect(0, 0, 100, 100)
delta_angle = -180 * 16
start_angle = 90 * 16
values = (1, 2, 3, 4)
colors = (
QtGui.QColor("red"),
QtGui.QColor("blue"),
QtGui.QColor("green"),
QtGui.QColor("yellow"),
)
sum_of_values = sum(values)
for value, color in zip(values, colors):
end_angle = start_angle + int((value/sum_of_values) * delta_angle)
painter.setPen(color)
painter.drawArc(r, start_angle, end_angle - start_angle)
start_angle = end_angle
painter.end()
paint_label()
self.show()
def main():
app = QtWidgets.QApplication([])
window = MainWindow()
app.exec_()
if __name__ == "__main__":
main()
解决方案
由于我们想要绘制实际的弧线,诀窍是使用 "ranges" 具有非常窄边距的颜色。
例如,要获得半红半蓝的锥形渐变,我们将使用如下内容:
gradient.setColorAt(.5, QtCore.Qt.red)
# set the next color with a stop very close to the previous
gradient.setColorAt(.500001, QtCore.Qt.blue)
我准备了一个带有小界面的例子来测试它的可能性。
class MainWindow(QtWidgets.QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
w = QtWidgets.QWidget()
layout = QtWidgets.QVBoxLayout()
w.setLayout(layout)
self.setCentralWidget(w)
panelLayout = QtWidgets.QHBoxLayout()
layout.addLayout(panelLayout)
panelLayout.addWidget(QtWidgets.QLabel('Start'))
self.startSpin = QtWidgets.QSpinBox(maximum=360, suffix='°')
self.startSpin.setValue(90)
panelLayout.addWidget(self.startSpin)
panelLayout.addWidget(QtWidgets.QLabel('Extent'))
self.extentSpin = QtWidgets.QSpinBox(maximum=360, suffix='°')
self.extentSpin.setValue(180)
panelLayout.addWidget(self.extentSpin)
panelLayout.addWidget(QtWidgets.QLabel('Width'))
self.penSpin = QtWidgets.QSpinBox(minimum=1, maximum=20, suffix='px')
self.penSpin.setValue(3)
panelLayout.addWidget(self.penSpin)
self.startSpin.valueChanged.connect(self.updateCanvas)
self.extentSpin.valueChanged.connect(self.updateCanvas)
self.penSpin.valueChanged.connect(self.updateCanvas)
self.colors = []
self.colorSpins = []
colorLayout = QtWidgets.QHBoxLayout()
layout.addLayout(colorLayout)
for color in ('red', 'green', 'blue', 'yellow'):
colorLayout.addWidget(QtWidgets.QLabel(color))
self.colors.append(QtGui.QColor(color))
colorSpin = QtWidgets.QSpinBox(minimum=1, maximum=50, value=25)
colorLayout.addWidget(colorSpin)
colorSpin.valueChanged.connect(self.updateCanvas)
self.colorSpins.append(colorSpin)
self.label = QtWidgets.QLabel()
canvas = QtGui.QPixmap(400, 300)
self.label.setPixmap(canvas)
layout.addWidget(self.label)
self.updateCanvas()
self.show()
def updateCanvas(self):
pm = QtGui.QPixmap(self.label.pixmap().size())
pm.fill(QtCore.Qt.transparent)
painter = QtGui.QPainter(pm)
painter.setRenderHint(QtGui.QPainter.Antialiasing)
painter.translate(.5, .5)
sizes = [spin.value() for spin in self.colorSpins]
total = sum(sizes)
extent = self.extentSpin.value() / 360
grad = QtGui.QConicalGradient(50, 50, self.startSpin.value())
gradPos = 1
# set colors starting from stop 1.0 to (1.0 - extent), since
# conical gradients are always counter-clockwise and the actual arc
# is negative, so it is drawn clockwise
for i, (size, color) in enumerate(zip(sizes, self.colors)):
grad.setColorAt(gradPos, color)
gradPos -= size / total * extent
if i < len(self.colors) - 1:
# extend the color right next to the next value
grad.setColorAt(gradPos + .000001, color)
if extent != 1:
# ensure that the first color is not painted at the edget of the
# last due to antialiasing
grad.setColorAt(0, self.colors[0])
grad.setColorAt(1 - extent, self.colors[-1])
offset = self.penSpin.maximum()
pen = QtGui.QPen(grad, self.penSpin.value(), cap=QtCore.Qt.FlatCap)
painter.setPen(pen)
# move the brush origin so that the conical gradient correctly centered
# in the middle of the ellipse
painter.setBrushOrigin(offset, offset)
painter.drawArc(offset, offset, 100, 100, self.startSpin.value() * 16, -self.extentSpin.value() * 16)
painter.end()
self.label.setPixmap(pm)