Nicer legend (#958)
* More customizable and nicer legend. - Give kwargs for legend frame and background colors instead of hard-coded values. - Reduce spacing for more compact legend - Give separate kwarg `labelTextColor`. - New method to clear all legend items. - New methods to get and change `offset` relative to the legend's parent. - Horizontal instead of tilted lines for legend pictures.
This commit is contained in:
parent
c95ab570b1
commit
c0ae44bc2d
@ -8,6 +8,7 @@ from .PlotDataItem import PlotDataItem
|
|||||||
from .GraphicsWidgetAnchor import GraphicsWidgetAnchor
|
from .GraphicsWidgetAnchor import GraphicsWidgetAnchor
|
||||||
__all__ = ['LegendItem']
|
__all__ = ['LegendItem']
|
||||||
|
|
||||||
|
|
||||||
class LegendItem(GraphicsWidget, GraphicsWidgetAnchor):
|
class LegendItem(GraphicsWidget, GraphicsWidgetAnchor):
|
||||||
"""
|
"""
|
||||||
Displays a legend used for describing the contents of a plot.
|
Displays a legend used for describing the contents of a plot.
|
||||||
@ -19,18 +20,28 @@ class LegendItem(GraphicsWidget, GraphicsWidgetAnchor):
|
|||||||
legend.setParentItem(plotItem)
|
legend.setParentItem(plotItem)
|
||||||
|
|
||||||
"""
|
"""
|
||||||
def __init__(self, size=None, offset=None):
|
def __init__(self, size=None, offset=None, horSpacing=25, verSpacing=0, pen=None,
|
||||||
|
brush=None, labelTextColor=None, **kwargs):
|
||||||
"""
|
"""
|
||||||
============== ===============================================================
|
============== ===============================================================
|
||||||
**Arguments:**
|
**Arguments:**
|
||||||
size Specifies the fixed size (width, height) of the legend. If
|
size Specifies the fixed size (width, height) of the legend. If
|
||||||
this argument is omitted, the legend will autimatically resize
|
this argument is omitted, the legend will automatically resize
|
||||||
to fit its contents.
|
to fit its contents.
|
||||||
offset Specifies the offset position relative to the legend's parent.
|
offset Specifies the offset position relative to the legend's parent.
|
||||||
Positive values offset from the left or top; negative values
|
Positive values offset from the left or top; negative values
|
||||||
offset from the right or bottom. If offset is None, the
|
offset from the right or bottom. If offset is None, the
|
||||||
legend must be anchored manually by calling anchor() or
|
legend must be anchored manually by calling anchor() or
|
||||||
positioned by calling setPos().
|
positioned by calling setPos().
|
||||||
|
horSpacing Specifies the spacing between the line symbol and the label.
|
||||||
|
verSpacing Specifies the spacing between individual entries of the legend
|
||||||
|
vertically. (Can also be negative to have them really close)
|
||||||
|
pen Pen to use when drawing legend border. Any single argument
|
||||||
|
accepted by :func:`mkPen <pyqtgraph.mkPen>` is allowed.
|
||||||
|
brush QBrush to use as legend background filling. Any single argument
|
||||||
|
accepted by :func:`mkBrush <pyqtgraph.mkBrush>` is allowed.
|
||||||
|
labelTextColor Pen to use when drawing legend text. Any single argument
|
||||||
|
accepted by :func:`mkPen <pyqtgraph.mkPen>` is allowed.
|
||||||
============== ===============================================================
|
============== ===============================================================
|
||||||
|
|
||||||
"""
|
"""
|
||||||
@ -40,17 +51,80 @@ class LegendItem(GraphicsWidget, GraphicsWidgetAnchor):
|
|||||||
GraphicsWidgetAnchor.__init__(self)
|
GraphicsWidgetAnchor.__init__(self)
|
||||||
self.setFlag(self.ItemIgnoresTransformations)
|
self.setFlag(self.ItemIgnoresTransformations)
|
||||||
self.layout = QtGui.QGraphicsGridLayout()
|
self.layout = QtGui.QGraphicsGridLayout()
|
||||||
|
self.layout.setVerticalSpacing(verSpacing)
|
||||||
|
self.layout.setHorizontalSpacing(horSpacing)
|
||||||
|
|
||||||
self.setLayout(self.layout)
|
self.setLayout(self.layout)
|
||||||
self.items = []
|
self.items = []
|
||||||
self.size = size
|
self.size = size
|
||||||
self.offset = offset
|
|
||||||
if size is not None:
|
if size is not None:
|
||||||
self.setGeometry(QtCore.QRectF(0, 0, self.size[0], self.size[1]))
|
self.setGeometry(QtCore.QRectF(0, 0, self.size[0], self.size[1]))
|
||||||
|
|
||||||
|
self.opts = {
|
||||||
|
'pen': fn.mkPen(pen),
|
||||||
|
'brush': fn.mkBrush(brush),
|
||||||
|
'labelTextColor': labelTextColor,
|
||||||
|
'offset': offset,
|
||||||
|
}
|
||||||
|
|
||||||
|
self.opts.update(kwargs)
|
||||||
|
|
||||||
|
def offset(self):
|
||||||
|
return self.opts['offset']
|
||||||
|
|
||||||
|
def setOffset(self, offset):
|
||||||
|
self.opts['offset'] = offset
|
||||||
|
|
||||||
|
offset = Point(self.opts['offset'])
|
||||||
|
anchorx = 1 if offset[0] <= 0 else 0
|
||||||
|
anchory = 1 if offset[1] <= 0 else 0
|
||||||
|
anchor = (anchorx, anchory)
|
||||||
|
self.anchor(itemPos=anchor, parentPos=anchor, offset=offset)
|
||||||
|
|
||||||
|
def pen(self):
|
||||||
|
return self.opts['pen']
|
||||||
|
|
||||||
|
def setPen(self, *args, **kargs):
|
||||||
|
"""
|
||||||
|
Sets the pen used to draw lines between points.
|
||||||
|
*pen* can be a QPen or any argument accepted by
|
||||||
|
:func:`pyqtgraph.mkPen() <pyqtgraph.mkPen>`
|
||||||
|
"""
|
||||||
|
pen = fn.mkPen(*args, **kargs)
|
||||||
|
self.opts['pen'] = pen
|
||||||
|
|
||||||
|
self.paint()
|
||||||
|
|
||||||
|
def brush(self):
|
||||||
|
return self.opts['brush']
|
||||||
|
|
||||||
|
def setBrush(self, *args, **kargs):
|
||||||
|
brush = fn.mkBrush(*args, **kargs)
|
||||||
|
if self.opts['brush'] == brush:
|
||||||
|
return
|
||||||
|
self.opts['brush'] = brush
|
||||||
|
|
||||||
|
self.paint()
|
||||||
|
|
||||||
|
def labelTextColor(self):
|
||||||
|
return self.opts['labelTextColor']
|
||||||
|
|
||||||
|
def setLabelTextColor(self, *args, **kargs):
|
||||||
|
"""
|
||||||
|
Sets the color of the label text.
|
||||||
|
*pen* can be a QPen or any argument accepted by
|
||||||
|
:func:`pyqtgraph.mkColor() <pyqtgraph.mkPen>`
|
||||||
|
"""
|
||||||
|
self.opts['labelTextColor'] = fn.mkColor(*args, **kargs)
|
||||||
|
for sample, label in self.items:
|
||||||
|
label.setAttr('color', self.opts['labelTextColor'])
|
||||||
|
|
||||||
|
self.paint()
|
||||||
|
|
||||||
def setParentItem(self, p):
|
def setParentItem(self, p):
|
||||||
ret = GraphicsWidget.setParentItem(self, p)
|
ret = GraphicsWidget.setParentItem(self, p)
|
||||||
if self.offset is not None:
|
if self.offset is not None:
|
||||||
offset = Point(self.offset)
|
offset = Point(self.opts['offset'])
|
||||||
anchorx = 1 if offset[0] <= 0 else 0
|
anchorx = 1 if offset[0] <= 0 else 0
|
||||||
anchory = 1 if offset[1] <= 0 else 0
|
anchory = 1 if offset[1] <= 0 else 0
|
||||||
anchor = (anchorx, anchory)
|
anchor = (anchorx, anchory)
|
||||||
@ -70,11 +144,12 @@ class LegendItem(GraphicsWidget, GraphicsWidgetAnchor):
|
|||||||
title The title to display for this item. Simple HTML allowed.
|
title The title to display for this item. Simple HTML allowed.
|
||||||
============== ========================================================
|
============== ========================================================
|
||||||
"""
|
"""
|
||||||
label = LabelItem(name)
|
label = LabelItem(name, color=self.opts['labelTextColor'], justify='left')
|
||||||
if isinstance(item, ItemSample):
|
if isinstance(item, ItemSample):
|
||||||
sample = item
|
sample = item
|
||||||
else:
|
else:
|
||||||
sample = ItemSample(item)
|
sample = ItemSample(item)
|
||||||
|
|
||||||
row = self.layout.rowCount()
|
row = self.layout.rowCount()
|
||||||
self.items.append((sample, label))
|
self.items.append((sample, label))
|
||||||
self.layout.addItem(sample, row, 0)
|
self.layout.addItem(sample, row, 0)
|
||||||
@ -90,16 +165,24 @@ class LegendItem(GraphicsWidget, GraphicsWidgetAnchor):
|
|||||||
item The item to remove or its name.
|
item The item to remove or its name.
|
||||||
============== ========================================================
|
============== ========================================================
|
||||||
"""
|
"""
|
||||||
# Thanks, Ulrich!
|
|
||||||
# cycle for a match
|
|
||||||
for sample, label in self.items:
|
for sample, label in self.items:
|
||||||
if sample.item is item or label.text == item:
|
if sample.item is item or label.text == item:
|
||||||
self.items.remove( (sample, label) ) # remove from itemlist
|
self.items.remove((sample, label)) # remove from itemlist
|
||||||
self.layout.removeItem(sample) # remove from layout
|
self.layout.removeItem(sample) # remove from layout
|
||||||
sample.close() # remove from drawing
|
sample.close() # remove from drawing
|
||||||
self.layout.removeItem(label)
|
self.layout.removeItem(label)
|
||||||
label.close()
|
label.close()
|
||||||
self.updateSize() # redraq box
|
self.updateSize() # redraq box
|
||||||
|
return # return after first match
|
||||||
|
|
||||||
|
def clear(self):
|
||||||
|
"""Removes all items from legend."""
|
||||||
|
for sample, label in self.items:
|
||||||
|
self.layout.removeItem(sample)
|
||||||
|
self.layout.removeItem(label)
|
||||||
|
|
||||||
|
self.items = []
|
||||||
|
self.updateSize()
|
||||||
|
|
||||||
def clear(self):
|
def clear(self):
|
||||||
"""
|
"""
|
||||||
@ -114,23 +197,14 @@ class LegendItem(GraphicsWidget, GraphicsWidgetAnchor):
|
|||||||
if self.size is not None:
|
if self.size is not None:
|
||||||
return
|
return
|
||||||
|
|
||||||
height = 0
|
self.setGeometry(0, 0, 0, 0)
|
||||||
width = 0
|
|
||||||
#print("-------")
|
|
||||||
for sample, label in self.items:
|
|
||||||
height += max(sample.height(), label.height()) + 3
|
|
||||||
width = max(width, (sample.sizeHint(QtCore.Qt.MinimumSize, sample.size()).width() +
|
|
||||||
label.sizeHint(QtCore.Qt.MinimumSize, label.size()).width()))
|
|
||||||
#print(width, height)
|
|
||||||
#print width, height
|
|
||||||
self.setGeometry(0, 0, width+25, height)
|
|
||||||
|
|
||||||
def boundingRect(self):
|
def boundingRect(self):
|
||||||
return QtCore.QRectF(0, 0, self.width(), self.height())
|
return QtCore.QRectF(0, 0, self.width(), self.height())
|
||||||
|
|
||||||
def paint(self, p, *args):
|
def paint(self, p, *args):
|
||||||
p.setPen(fn.mkPen(255,255,255,100))
|
p.setPen(self.opts['pen'])
|
||||||
p.setBrush(fn.mkBrush(100,100,100,50))
|
p.setBrush(self.opts['brush'])
|
||||||
p.drawRect(self.boundingRect())
|
p.drawRect(self.boundingRect())
|
||||||
|
|
||||||
def hoverEvent(self, ev):
|
def hoverEvent(self, ev):
|
||||||
@ -157,17 +231,14 @@ class ItemSample(GraphicsWidget):
|
|||||||
return QtCore.QRectF(0, 0, 20, 20)
|
return QtCore.QRectF(0, 0, 20, 20)
|
||||||
|
|
||||||
def paint(self, p, *args):
|
def paint(self, p, *args):
|
||||||
#p.setRenderHint(p.Antialiasing) # only if the data is antialiased.
|
|
||||||
opts = self.item.opts
|
opts = self.item.opts
|
||||||
|
|
||||||
if opts.get('fillLevel',None) is not None and opts.get('fillBrush',None) is not None:
|
if opts['antialias']:
|
||||||
p.setBrush(fn.mkBrush(opts['fillBrush']))
|
p.setRenderHint(p.Antialiasing)
|
||||||
p.setPen(fn.mkPen(None))
|
|
||||||
p.drawPolygon(QtGui.QPolygonF([QtCore.QPointF(2,18), QtCore.QPointF(18,2), QtCore.QPointF(18,18)]))
|
|
||||||
|
|
||||||
if not isinstance(self.item, ScatterPlotItem):
|
if not isinstance(self.item, ScatterPlotItem):
|
||||||
p.setPen(fn.mkPen(opts['pen']))
|
p.setPen(fn.mkPen(opts['pen']))
|
||||||
p.drawLine(2, 18, 18, 2)
|
p.drawLine(0, 11, 20, 11)
|
||||||
|
|
||||||
symbol = opts.get('symbol', None)
|
symbol = opts.get('symbol', None)
|
||||||
if symbol is not None:
|
if symbol is not None:
|
||||||
@ -178,7 +249,7 @@ class ItemSample(GraphicsWidget):
|
|||||||
brush = fn.mkBrush(opts['brush'])
|
brush = fn.mkBrush(opts['brush'])
|
||||||
size = opts['size']
|
size = opts['size']
|
||||||
|
|
||||||
p.translate(10,10)
|
p.translate(10, 10)
|
||||||
path = drawSymbol(p, symbol, size, pen, brush)
|
path = drawSymbol(p, symbol, size, pen, brush)
|
||||||
|
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user