From dd1aed627ecbd1c29a7f275021b087e1a78c36bc Mon Sep 17 00:00:00 2001 From: Tyler Goodlet Date: Thu, 11 Feb 2021 11:48:19 -0500 Subject: [PATCH] Tidy axis code --- piker/ui/_axes.py | 94 +++++++++++++++++++++++++++++++---------------- 1 file changed, 63 insertions(+), 31 deletions(-) diff --git a/piker/ui/_axes.py b/piker/ui/_axes.py index 25c14f34..fac0c934 100644 --- a/piker/ui/_axes.py +++ b/piker/ui/_axes.py @@ -16,6 +16,7 @@ """ Chart axes graphics and behavior. + """ from typing import List, Tuple, Optional @@ -32,7 +33,7 @@ _axis_pen = pg.mkPen(hcolor('bracket')) class Axis(pg.AxisItem): - """A better axis that sizes to typical tick contents considering font size. + """A better axis that sizes tick contents considering font size. """ def __init__( @@ -64,11 +65,17 @@ class Axis(pg.AxisItem): self.typical_br = _font._qfm.boundingRect(typical_max_str) # size the pertinent axis dimension to a "typical value" - self.resize() + self.size_to_values() + + def size_to_values(self) -> None: + pass def set_min_tick(self, size: int) -> None: self._min_tick = size + def txt_offsets(self) -> Tuple[int, int]: + return tuple(self.style['tickTextOffset']) + class PriceAxis(Axis): @@ -77,13 +84,13 @@ class PriceAxis(Axis): *args, **kwargs, ) -> None: - super().__init__(*args, orientation='right', **kwargs) + super().__init__(*args, **kwargs) self.setStyle(**{ # offset of text *away from* axis line in px 'tickTextOffset': 9, }) - def resize(self) -> None: + def size_to_values(self) -> None: self.setWidth(self.typical_br.width()) # XXX: drop for now since it just eats up h space @@ -116,7 +123,7 @@ class DynamicDateAxis(Axis): 1: '%H:%M:%S', } - def resize(self) -> None: + def size_to_values(self) -> None: self.setHeight(self.typical_br.height() + 1) def _indexes_to_timestrs( @@ -160,22 +167,28 @@ class AxisLabel(pg.GraphicsObject): def __init__( self, - parent: Axis, + parent: pg.GraphicsItem, digits: int = 2, + + font_size_inches: Optional[float] = None, bg_color: str = 'bracket', fg_color: str = 'black', - opacity: int = 0, + opacity: int = 1, # XXX: seriously don't set this to 0 + use_arrow: bool = True, - font_size_inches: Optional[float] = None, + ) -> None: - super().__init__(parent) + super().__init__() + self.setParentItem(parent) + self.setFlag(self.ItemIgnoresTransformations) # XXX: pretty sure this is faster self.setCacheMode(QtGui.QGraphicsItem.DeviceCoordinateCache) - self.parent = parent + self._parent = parent + self.opacity = opacity self.label_str = '' self.digits = digits @@ -200,6 +213,11 @@ class AxisLabel(pg.GraphicsObject): opt: QtWidgets.QStyleOptionGraphicsItem, w: QtWidgets.QWidget ) -> None: + """Draw a filled rectangle based on the size of ``.label_str`` text. + + Subtypes can customize further by overloading ``.draw()``. + + """ # p.setCompositionMode(QtGui.QPainter.CompositionMode_SourceOver) if self.label_str: @@ -207,37 +225,41 @@ class AxisLabel(pg.GraphicsObject): # if not self.rect: self._size_br_from_str(self.label_str) - p.setFont(self._dpifont.font) - p.setPen(self.fg_color) - p.setOpacity(self.opacity) - # can be overrided in subtype self.draw(p, self.rect) + p.setFont(self._dpifont.font) + p.setPen(self.fg_color) p.drawText(self.rect, self.text_flags, self.label_str) + def draw( self, p: QtGui.QPainter, rect: QtCore.QRectF ) -> None: - # this adds a nice black outline around the label for some odd - # reason; ok by us - # p.setOpacity(self.opacity) - p.drawRect(self.rect) if self._use_arrow: - if not self.path: self._draw_arrow_path() p.drawPath(self.path) p.fillPath(self.path, pg.mkBrush(self.bg_color)) + # this adds a nice black outline around the label for some odd + # reason; ok by us + p.setOpacity(self.opacity) + + # this cause the L1 labels to glitch out if used + # in the subtype and it will leave a small black strip + # with the arrow path if done before the above p.fillRect(self.rect, self.bg_color) def boundingRect(self): # noqa + """Size the graphics space from the text contents. + + """ if self.label_str: self._size_br_from_str(self.label_str) @@ -267,12 +289,14 @@ class AxisLabel(pg.GraphicsObject): txt_br = self._txt_br = self._dpifont.boundingRect(value) txt_h, txt_w = txt_br.height(), txt_br.width() + + # allow subtypes to specify a static width and height h, w = self.size_hint() self.rect = QtCore.QRectF( 0, 0, - (w or txt_w) + self._x_margin/2, - (h or txt_h) + self._y_margin/2, + (w or txt_w) + self._x_margin /2, + (h or txt_h) + self._y_margin /2, ) # print(self.rect) # hb = self.path.controlPointRect() @@ -299,7 +323,7 @@ class XAxisLabel(AxisLabel): def size_hint(self) -> Tuple[float, float]: # size to parent axis height - return self.parent.height(), None + return self._parent.height(), None def update_label( self, @@ -308,7 +332,7 @@ class XAxisLabel(AxisLabel): offset: int = 0 # if have margins, k? ) -> None: - timestrs = self.parent._indexes_to_timestrs([int(value)]) + timestrs = self._parent._indexes_to_timestrs([int(value)]) if not timestrs.any(): return @@ -316,9 +340,10 @@ class XAxisLabel(AxisLabel): pad = 1*' ' self.label_str = pad + timestrs[0] + pad - y_offset = self.parent.style['tickTextOffset'][1] + _, y_offset = self._parent.txt_offsets() w = self.boundingRect().width() + self.setPos(QPointF( abs_pos.x() - w/2, y_offset/2, @@ -326,13 +351,10 @@ class XAxisLabel(AxisLabel): self.update() def _draw_arrow_path(self): - y_offset = self.parent.style['tickTextOffset'][1] + y_offset = self._parent.style['tickTextOffset'][1] path = QtGui.QPainterPath() h, w = self.rect.height(), self.rect.width() - # middle = (w + self._y_margin)/2 - # middle = (w + self._x_margin)/2 middle = w/2 - 0.5 - # aw = (h + self._x_margin)/2 aw = h/2 left = middle - aw right = middle + aw @@ -341,6 +363,8 @@ class XAxisLabel(AxisLabel): path.lineTo(right, 0) path.closeSubpath() self.path = path + + # top left point is local origin and tip of the arrow path self.tl = QtCore.QPointF(0, -y_offset) @@ -364,15 +388,18 @@ class YAxisLabel(AxisLabel): super().__init__(*args, **kwargs) self._chart = chart + chart.sigRangeChanged.connect(self.update_on_resize) + self._last_datum = (None, None) # pull text offset from axis from parent axis - self.x_offset = self.parent.style['tickTextOffset'][0] + if getattr(self._parent, 'txt_offsets', False): + self.x_offset, y_offset = self._parent.txt_offsets() def size_hint(self) -> Tuple[float, float]: # size to parent axis width - return None, self.parent.width() + return None, self._parent.width() def update_label( self, @@ -392,6 +419,7 @@ class YAxisLabel(AxisLabel): br = self.boundingRect() h = br.height() + self.setPos(QPointF( x_offset, abs_pos.y() - h / 2 - self._y_margin / 2 @@ -411,6 +439,10 @@ class YAxisLabel(AxisLabel): index: int, value: float, ) -> None: + """Update the label's text contents **and** position from + a view box coordinate datum. + + """ self._last_datum = (index, value) self.update_label( self._chart.mapFromView(QPointF(index, value)), @@ -418,7 +450,7 @@ class YAxisLabel(AxisLabel): ) def _draw_arrow_path(self): - x_offset = self.parent.style['tickTextOffset'][0] + x_offset = self._parent.style['tickTextOffset'][0] path = QtGui.QPainterPath() h = self.rect.height() path.moveTo(0, 0)