Tidy axis code
parent
02edfdf846
commit
dd1aed627e
|
@ -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,6 +289,8 @@ 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(
|
||||||
|
@ -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)
|
||||||
|
|
Loading…
Reference in New Issue