Tidy axis code

basic_orders
Tyler Goodlet 2021-02-11 11:48:19 -05:00
parent 02edfdf846
commit dd1aed627e
1 changed files with 63 additions and 31 deletions

View File

@ -16,6 +16,7 @@
""" """
Chart axes graphics and behavior. Chart axes graphics and behavior.
""" """
from typing import List, Tuple, Optional from typing import List, Tuple, Optional
@ -32,7 +33,7 @@ _axis_pen = pg.mkPen(hcolor('bracket'))
class Axis(pg.AxisItem): 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__( def __init__(
@ -64,11 +65,17 @@ class Axis(pg.AxisItem):
self.typical_br = _font._qfm.boundingRect(typical_max_str) self.typical_br = _font._qfm.boundingRect(typical_max_str)
# size the pertinent axis dimension to a "typical value" # 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: def set_min_tick(self, size: int) -> None:
self._min_tick = size self._min_tick = size
def txt_offsets(self) -> Tuple[int, int]:
return tuple(self.style['tickTextOffset'])
class PriceAxis(Axis): class PriceAxis(Axis):
@ -77,13 +84,13 @@ class PriceAxis(Axis):
*args, *args,
**kwargs, **kwargs,
) -> None: ) -> None:
super().__init__(*args, orientation='right', **kwargs) super().__init__(*args, **kwargs)
self.setStyle(**{ self.setStyle(**{
# offset of text *away from* axis line in px # offset of text *away from* axis line in px
'tickTextOffset': 9, 'tickTextOffset': 9,
}) })
def resize(self) -> None: def size_to_values(self) -> None:
self.setWidth(self.typical_br.width()) self.setWidth(self.typical_br.width())
# XXX: drop for now since it just eats up h space # XXX: drop for now since it just eats up h space
@ -116,7 +123,7 @@ class DynamicDateAxis(Axis):
1: '%H:%M:%S', 1: '%H:%M:%S',
} }
def resize(self) -> None: def size_to_values(self) -> None:
self.setHeight(self.typical_br.height() + 1) self.setHeight(self.typical_br.height() + 1)
def _indexes_to_timestrs( def _indexes_to_timestrs(
@ -160,22 +167,28 @@ class AxisLabel(pg.GraphicsObject):
def __init__( def __init__(
self, self,
parent: Axis, parent: pg.GraphicsItem,
digits: int = 2, digits: int = 2,
font_size_inches: Optional[float] = None,
bg_color: str = 'bracket', bg_color: str = 'bracket',
fg_color: str = 'black', fg_color: str = 'black',
opacity: int = 0, opacity: int = 1, # XXX: seriously don't set this to 0
use_arrow: bool = True, use_arrow: bool = True,
font_size_inches: Optional[float] = None,
) -> None: ) -> None:
super().__init__(parent) super().__init__()
self.setParentItem(parent)
self.setFlag(self.ItemIgnoresTransformations) self.setFlag(self.ItemIgnoresTransformations)
# XXX: pretty sure this is faster # XXX: pretty sure this is faster
self.setCacheMode(QtGui.QGraphicsItem.DeviceCoordinateCache) self.setCacheMode(QtGui.QGraphicsItem.DeviceCoordinateCache)
self.parent = parent self._parent = parent
self.opacity = opacity self.opacity = opacity
self.label_str = '' self.label_str = ''
self.digits = digits self.digits = digits
@ -200,6 +213,11 @@ class AxisLabel(pg.GraphicsObject):
opt: QtWidgets.QStyleOptionGraphicsItem, opt: QtWidgets.QStyleOptionGraphicsItem,
w: QtWidgets.QWidget w: QtWidgets.QWidget
) -> None: ) -> 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) # p.setCompositionMode(QtGui.QPainter.CompositionMode_SourceOver)
if self.label_str: if self.label_str:
@ -207,37 +225,41 @@ class AxisLabel(pg.GraphicsObject):
# 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.setPen(self.fg_color)
p.setOpacity(self.opacity)
# can be overrided in subtype # can be overrided in subtype
self.draw(p, self.rect) 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) p.drawText(self.rect, self.text_flags, self.label_str)
def draw( def draw(
self, self,
p: QtGui.QPainter, p: QtGui.QPainter,
rect: QtCore.QRectF rect: QtCore.QRectF
) -> None: ) -> 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 self._use_arrow:
if not self.path: if not self.path:
self._draw_arrow_path() self._draw_arrow_path()
p.drawPath(self.path) p.drawPath(self.path)
p.fillPath(self.path, pg.mkBrush(self.bg_color)) 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) p.fillRect(self.rect, self.bg_color)
def boundingRect(self): # noqa def boundingRect(self): # noqa
"""Size the graphics space from the text contents.
"""
if self.label_str: if self.label_str:
self._size_br_from_str(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_br = self._txt_br = self._dpifont.boundingRect(value)
txt_h, txt_w = txt_br.height(), txt_br.width() txt_h, txt_w = txt_br.height(), txt_br.width()
# allow subtypes to specify a static width and height
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._x_margin/2, (w or txt_w) + self._x_margin /2,
(h or txt_h) + self._y_margin/2, (h or txt_h) + self._y_margin /2,
) )
# print(self.rect) # print(self.rect)
# hb = self.path.controlPointRect() # hb = self.path.controlPointRect()
@ -299,7 +323,7 @@ class XAxisLabel(AxisLabel):
def size_hint(self) -> Tuple[float, float]: def size_hint(self) -> Tuple[float, float]:
# size to parent axis height # size to parent axis height
return self.parent.height(), None return self._parent.height(), None
def update_label( def update_label(
self, self,
@ -308,7 +332,7 @@ class XAxisLabel(AxisLabel):
offset: int = 0 # 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)])
if not timestrs.any(): if not timestrs.any():
return return
@ -316,9 +340,10 @@ class XAxisLabel(AxisLabel):
pad = 1*' ' pad = 1*' '
self.label_str = pad + timestrs[0] + pad self.label_str = pad + timestrs[0] + pad
y_offset = self.parent.style['tickTextOffset'][1] _, y_offset = self._parent.txt_offsets()
w = self.boundingRect().width() w = self.boundingRect().width()
self.setPos(QPointF( self.setPos(QPointF(
abs_pos.x() - w/2, abs_pos.x() - w/2,
y_offset/2, y_offset/2,
@ -326,13 +351,10 @@ class XAxisLabel(AxisLabel):
self.update() self.update()
def _draw_arrow_path(self): def _draw_arrow_path(self):
y_offset = self.parent.style['tickTextOffset'][1] y_offset = self._parent.style['tickTextOffset'][1]
path = QtGui.QPainterPath() path = QtGui.QPainterPath()
h, w = self.rect.height(), self.rect.width() 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 middle = w/2 - 0.5
# aw = (h + self._x_margin)/2
aw = h/2 aw = h/2
left = middle - aw left = middle - aw
right = middle + aw right = middle + aw
@ -341,6 +363,8 @@ class XAxisLabel(AxisLabel):
path.lineTo(right, 0) path.lineTo(right, 0)
path.closeSubpath() path.closeSubpath()
self.path = path self.path = path
# top left point is local origin and tip of the arrow path
self.tl = QtCore.QPointF(0, -y_offset) self.tl = QtCore.QPointF(0, -y_offset)
@ -364,15 +388,18 @@ class YAxisLabel(AxisLabel):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self._chart = chart self._chart = chart
chart.sigRangeChanged.connect(self.update_on_resize) chart.sigRangeChanged.connect(self.update_on_resize)
self._last_datum = (None, None) self._last_datum = (None, None)
# pull text offset from axis from parent axis # 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]: def size_hint(self) -> Tuple[float, float]:
# size to parent axis width # size to parent axis width
return None, self.parent.width() return None, self._parent.width()
def update_label( def update_label(
self, self,
@ -392,6 +419,7 @@ class YAxisLabel(AxisLabel):
br = self.boundingRect() br = self.boundingRect()
h = br.height() h = br.height()
self.setPos(QPointF( self.setPos(QPointF(
x_offset, x_offset,
abs_pos.y() - h / 2 - self._y_margin / 2 abs_pos.y() - h / 2 - self._y_margin / 2
@ -411,6 +439,10 @@ class YAxisLabel(AxisLabel):
index: int, index: int,
value: float, value: float,
) -> None: ) -> None:
"""Update the label's text contents **and** position from
a view box coordinate datum.
"""
self._last_datum = (index, value) self._last_datum = (index, value)
self.update_label( self.update_label(
self._chart.mapFromView(QPointF(index, value)), self._chart.mapFromView(QPointF(index, value)),
@ -418,7 +450,7 @@ class YAxisLabel(AxisLabel):
) )
def _draw_arrow_path(self): def _draw_arrow_path(self):
x_offset = self.parent.style['tickTextOffset'][0] x_offset = self._parent.style['tickTextOffset'][0]
path = QtGui.QPainterPath() path = QtGui.QPainterPath()
h = self.rect.height() h = self.rect.height()
path.moveTo(0, 0) path.moveTo(0, 0)