Add arrowheads to labels

For labels that want it add nice arrow paths that point just over the
respective axis. Couple label text offset from the axis line based on
parent 'tickTextOffset' setting. Drop `YSticky` it was not enough
meat to bother with.
basic_orders
Tyler Goodlet 2021-02-08 06:40:11 -05:00
parent c3fa31e731
commit d7f806c57b
1 changed files with 117 additions and 50 deletions

View File

@ -53,10 +53,10 @@ class Axis(pg.AxisItem):
self.setTickFont(_font.font) self.setTickFont(_font.font)
self.setStyle(**{ self.setStyle(**{
'textFillLimits': [(0, 0.666)], 'textFillLimits': [(0, 0.616)],
'tickFont': _font.font, 'tickFont': _font.font,
# offset of text *away from* axis line in px # offset of text *away from* axis line in px
'tickTextOffset': 2, 'tickTextOffset': 6,
}) })
self.setTickFont(_font.font) self.setTickFont(_font.font)
@ -78,6 +78,10 @@ class PriceAxis(Axis):
**kwargs, **kwargs,
) -> None: ) -> None:
super().__init__(*args, orientation='right', **kwargs) super().__init__(*args, orientation='right', **kwargs)
self.setStyle(**{
# offset of text *away from* axis line in px
'tickTextOffset': 9,
})
def resize(self) -> None: def resize(self) -> None:
self.setWidth(self.typical_br.width()) self.setWidth(self.typical_br.width())
@ -151,8 +155,8 @@ class DynamicDateAxis(Axis):
class AxisLabel(pg.GraphicsObject): class AxisLabel(pg.GraphicsObject):
_w_margin = 0 _x_margin = 0
_h_margin = 0 _y_margin = 0
def __init__( def __init__(
self, self,
@ -161,6 +165,7 @@ class AxisLabel(pg.GraphicsObject):
bg_color: str = 'bracket', bg_color: str = 'bracket',
fg_color: str = 'black', fg_color: str = 'black',
opacity: int = 0, opacity: int = 0,
use_arrow: bool = True,
font_size_inches: Optional[float] = None, font_size_inches: Optional[float] = None,
) -> None: ) -> None:
@ -183,6 +188,10 @@ class AxisLabel(pg.GraphicsObject):
self.bg_color = pg.mkColor(hcolor(bg_color)) self.bg_color = pg.mkColor(hcolor(bg_color))
self.fg_color = pg.mkColor(hcolor(fg_color)) self.fg_color = pg.mkColor(hcolor(fg_color))
self._use_arrow = use_arrow
# create triangle path
self.path = None
self.rect = None self.rect = None
def paint( def paint(
@ -195,13 +204,12 @@ class AxisLabel(pg.GraphicsObject):
if self.label_str: if self.label_str:
if not self.rect: # if not self.rect:
self._size_br_from_str(self.label_str) self._size_br_from_str(self.label_str)
p.setFont(self._dpifont.font) p.setFont(self._dpifont.font)
p.setPen(self.fg_color) p.setPen(self.fg_color)
p.setOpacity(self.opacity) p.setOpacity(self.opacity)
p.fillRect(self.rect, self.bg_color)
# can be overrided in subtype # can be overrided in subtype
self.draw(p, self.rect) self.draw(p, self.rect)
@ -215,13 +223,33 @@ class AxisLabel(pg.GraphicsObject):
) -> None: ) -> None:
# this adds a nice black outline around the label for some odd # this adds a nice black outline around the label for some odd
# reason; ok by us # reason; ok by us
p.setOpacity(self.opacity) # p.setOpacity(self.opacity)
p.drawRect(self.rect) 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))
p.fillRect(self.rect, self.bg_color)
def boundingRect(self): # noqa def boundingRect(self): # noqa
if self.label_str: if self.label_str:
self._size_br_from_str(self.label_str) self._size_br_from_str(self.label_str)
return self.rect
# if self.path:
# self.tl = self.path.controlPointRect().topLeft()
if not self.path:
self.tl = self.rect.topLeft()
return QtCore.QRectF(
self.tl,
self.rect.bottomRight(),
)
return QtCore.QRectF() return QtCore.QRectF()
@ -237,16 +265,20 @@ class AxisLabel(pg.GraphicsObject):
# # XXX: this can't be c # # XXX: this can't be c
# self._txt_br = self._dpifont.boundingRect(value) # self._txt_br = self._dpifont.boundingRect(value)
br = self._txt_br = self._dpifont.boundingRect(value) txt_br = self._txt_br = self._dpifont.boundingRect(value)
txt_h, txt_w = br.height(), br.width() txt_h, txt_w = txt_br.height(), txt_br.width()
h, w = self.size_hint() h, w = self.size_hint()
self.rect = QtCore.QRectF( self.rect = QtCore.QRectF(
0, 0, 0, 0,
(w or txt_w) + self._w_margin, (w or txt_w) + self._x_margin/2,
(h or txt_h) + self._h_margin, (h or txt_h) + self._y_margin/2,
) )
# print(self.rect)
# hb = self.path.controlPointRect()
# hb_size = hb.size()
return self.rect
# _common_text_flags = ( # _common_text_flags = (
# QtCore.Qt.TextDontClip | # QtCore.Qt.TextDontClip |
@ -258,7 +290,7 @@ class AxisLabel(pg.GraphicsObject):
class XAxisLabel(AxisLabel): class XAxisLabel(AxisLabel):
_w_margin = 4 _x_margin = 8
text_flags = ( text_flags = (
QtCore.Qt.TextDontClip QtCore.Qt.TextDontClip
@ -273,7 +305,7 @@ class XAxisLabel(AxisLabel):
self, self,
abs_pos: QPointF, # scene coords abs_pos: QPointF, # scene coords
value: float, # data for text value: float, # data for text
offset: int = 1 # if have margins, k? offset: int = 0 # if have margins, k?
) -> None: ) -> None:
timestrs = self.parent._indexes_to_timestrs([int(value)]) timestrs = self.parent._indexes_to_timestrs([int(value)])
@ -281,18 +313,39 @@ class XAxisLabel(AxisLabel):
if not timestrs.any(): if not timestrs.any():
return return
self.label_str = timestrs[0] pad = 1*' '
self.label_str = pad + timestrs[0] + pad
y_offset = self.parent.style['tickTextOffset'][1]
w = self.boundingRect().width() w = self.boundingRect().width()
self.setPos(QPointF( self.setPos(QPointF(
abs_pos.x() - w / 2 - offset, abs_pos.x() - w/2,
1, y_offset/2,
)) ))
self.update() self.update()
def _draw_arrow_path(self):
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
path.moveTo(left, 0)
path.lineTo(middle, -y_offset)
path.lineTo(right, 0)
path.closeSubpath()
self.path = path
self.tl = QtCore.QPointF(0, -y_offset)
class YAxisLabel(AxisLabel): class YAxisLabel(AxisLabel):
_h_margin = 2 _y_margin = 4
text_flags = ( text_flags = (
QtCore.Qt.AlignLeft QtCore.Qt.AlignLeft
@ -301,33 +354,6 @@ class YAxisLabel(AxisLabel):
| QtCore.Qt.TextDontClip | QtCore.Qt.TextDontClip
) )
def size_hint(self) -> Tuple[float, float]:
# size to parent axis width
return None, self.parent.width()
def update_label(
self,
abs_pos: QPointF, # scene coords
value: float, # data for text
offset: int = 1 # on odd dimension and/or adds nice black line
) -> None:
# this is read inside ``.paint()``
self.label_str = ' {value:,.{digits}f}'.format(
digits=self.digits, value=value).replace(',', ' ')
br = self.boundingRect()
h = br.height()
self.setPos(QPointF(
1,
abs_pos.y() - h / 2 - offset
))
self.update()
class YSticky(YAxisLabel):
"""Y-axis label that sticks to where it's placed despite chart resizing.
"""
def __init__( def __init__(
self, self,
chart, chart,
@ -341,11 +367,41 @@ class YSticky(YAxisLabel):
chart.sigRangeChanged.connect(self.update_on_resize) chart.sigRangeChanged.connect(self.update_on_resize)
self._last_datum = (None, None) self._last_datum = (None, None)
def update_on_resize(self, vr, r): # pull text offset from axis from parent axis
# TODO: add an `.index` to the array data-buffer layer self.x_offset = self.parent.style['tickTextOffset'][0]
# and make this way less shitty...
# pretty sure we did that ^ ? def size_hint(self) -> Tuple[float, float]:
# size to parent axis width
return None, self.parent.width()
def update_label(
self,
abs_pos: QPointF, # scene coords
value: float, # data for text
# on odd dimension and/or adds nice black line
x_offset: Optional[int] = None
) -> None:
# this is read inside ``.paint()``
self.label_str = '{value:,.{digits}f}'.format(
digits=self.digits, value=value).replace(',', ' ')
# pull text offset from axis from parent axis
x_offset = x_offset or self.x_offset
br = self.boundingRect()
h = br.height()
self.setPos(QPointF(
x_offset,
abs_pos.y() - h / 2 - self._y_margin / 2
))
self.update()
def update_on_resize(self, vr, r):
"""Tiis is a ``.sigRangeChanged()`` handler.
"""
index, last = self._last_datum index, last = self._last_datum
if index is not None: if index is not None:
self.update_from_data(index, last) self.update_from_data(index, last)
@ -360,3 +416,14 @@ class YSticky(YAxisLabel):
self._chart.mapFromView(QPointF(index, value)), self._chart.mapFromView(QPointF(index, value)),
value value
) )
def _draw_arrow_path(self):
x_offset = self.parent.style['tickTextOffset'][0]
path = QtGui.QPainterPath()
h = self.rect.height()
path.moveTo(0, 0)
path.lineTo(-x_offset - 2, h/2.)
path.lineTo(0, h)
path.closeSubpath()
self.path = path
self.tl = path.controlPointRect().topLeft()