Rework axes types, sizing stuff

Make our own ``Axis`` and have it call an impl specific ``.resize()``
such that different axes can size to their own spec. Allow passing in a
"typical maximum value string" which will be used by default for sizing
the axis' minor dimension; a common value should be passed to all axes
in a linked split charts widget. Add size hinting for axes labels such
that they can check their parent (axis) for desired dimensions if
needed.
bar_select
Tyler Goodlet 2020-10-27 10:50:28 -04:00
parent 89d48afb6c
commit 23672fc22b
1 changed files with 58 additions and 46 deletions

View File

@ -1,7 +1,7 @@
""" """
Chart axes graphics and behavior. Chart axes graphics and behavior.
""" """
from typing import List from typing import List, Tuple
import pandas as pd import pandas as pd
import pyqtgraph as pg import pyqtgraph as pg
@ -14,12 +14,19 @@ from ..data._source import float_digits
_axis_pen = pg.mkPen(hcolor('bracket')) _axis_pen = pg.mkPen(hcolor('bracket'))
class PriceAxis(pg.AxisItem): class Axis(pg.AxisItem):
def __init__( def __init__(
self, self,
linked_charts,
typical_max_str: str = '100 000.00',
**kwargs
) -> None: ) -> None:
super().__init__(orientation='right')
self.linked_charts = linked_charts
super().__init__(**kwargs)
self.setTickFont(_font) self.setTickFont(_font)
self.setStyle(**{ self.setStyle(**{
'textFillLimits': [(0, 0.666)], 'textFillLimits': [(0, 0.666)],
@ -29,13 +36,31 @@ class PriceAxis(pg.AxisItem):
# 'tickTextWidth': 40, # 'tickTextWidth': 40,
# 'autoExpandTextSpace': True, # 'autoExpandTextSpace': True,
# 'maxTickLength': -20, # 'maxTickLength': -20,
# 'stopAxisAtTick': (True, True), # doesn't work well on price
# doesn't work well on price?
# 'stopAxisAtTick': (True, True),
}) })
# self.setLabel(**{'font-size': '10pt'}) # self.setLabel(**{'font-size': '10pt'})
self.setTickFont(_font) self.setTickFont(_font)
self.setPen(_axis_pen) self.setPen(_axis_pen)
self.typical_br = _font._fm.boundingRect(typical_max_str)
self.setWidth(40) # size the pertinent axis dimension to a "typical value"
self.resize()
class PriceAxis(Axis):
def __init__(
self,
*args,
**kwargs,
) -> None:
super().__init__(*args, orientation='right', **kwargs)
def resize(self) -> None:
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
@ -50,7 +75,8 @@ class PriceAxis(pg.AxisItem):
] ]
class DynamicDateAxis(pg.AxisItem): class DynamicDateAxis(Axis):
# time formats mapped by seconds between bars # time formats mapped by seconds between bars
tick_tpl = { tick_tpl = {
60*60*24: '%Y-%b-%d', 60*60*24: '%Y-%b-%d',
@ -59,24 +85,8 @@ class DynamicDateAxis(pg.AxisItem):
5: '%H:%M:%S', 5: '%H:%M:%S',
} }
def __init__( def resize(self) -> None:
self, self.setHeight(self.typical_br.height() + 3)
linked_charts,
*args,
**kwargs
) -> None:
super().__init__(*args, **kwargs)
self.linked_charts = linked_charts
self.setTickFont(_font)
self.setPen(_axis_pen)
# default styling
self.setStyle(**{
# tickTextOffset=4,
'textFillLimits': [(0, 0.666)],
'tickFont': _font,
})
self.setHeight(11)
def _indexes_to_timestrs( def _indexes_to_timestrs(
self, self,
@ -107,7 +117,7 @@ class AxisLabel(pg.GraphicsObject):
def __init__( def __init__(
self, self,
parent: pg.GraphicsObject, parent: Axis,
digits: int = 2, digits: int = 2,
bg_color: str = 'bracket', bg_color: str = 'bracket',
fg_color: str = 'black', fg_color: str = 'black',
@ -133,19 +143,6 @@ class AxisLabel(pg.GraphicsObject):
self.setFlag(self.ItemIgnoresTransformations) self.setFlag(self.ItemIgnoresTransformations)
def _size_br_from_str(self, value: str) -> None:
"""Do our best to render the bounding rect to a set margin
around provided string contents.
"""
txt_br = self._font._fm.boundingRect(value)
h, w = txt_br.height(), txt_br.width()
self.rect = QtCore.QRectF(
0, 0,
w + self._w_margin,
h + self._h_margin
)
def paint(self, p, option, widget): def paint(self, p, option, widget):
p.drawPicture(0, 0, self.pic) p.drawPicture(0, 0, self.pic)
@ -167,15 +164,19 @@ class AxisLabel(pg.GraphicsObject):
def boundingRect(self): # noqa def boundingRect(self): # noqa
return self.rect or QtCore.QRectF() return self.rect or QtCore.QRectF()
# uggggghhhh def _size_br_from_str(self, value: str) -> None:
"""Do our best to render the bounding rect to a set margin
around provided string contents.
def tick_to_string(self, tick_pos): """
raise NotImplementedError() txt_br = self._font._fm.boundingRect(value)
txt_h, txt_w = txt_br.height(), txt_br.width()
def update_label(self, evt_post, point_view): h, w = self.size_hint()
raise NotImplementedError() self.rect = QtCore.QRectF(
0, 0,
# end uggggghhhh (w or txt_w) + self._w_margin,
(h or txt_h) + self._h_margin,
)
# _common_text_flags = ( # _common_text_flags = (
@ -190,6 +191,7 @@ class AxisLabel(pg.GraphicsObject):
class XAxisLabel(AxisLabel): class XAxisLabel(AxisLabel):
_w_margin = 8 _w_margin = 8
_h_margin = 0
text_flags = ( text_flags = (
QtCore.Qt.TextDontClip QtCore.Qt.TextDontClip
@ -199,6 +201,10 @@ class XAxisLabel(AxisLabel):
# | QtCore.Qt.AlignHCenter # | QtCore.Qt.AlignHCenter
) )
def size_hint(self) -> Tuple[float, float]:
# size to parent axis height
return self.parent.height(), None
def update_label( def update_label(
self, self,
abs_pos: QPointF, # scene coords abs_pos: QPointF, # scene coords
@ -206,8 +212,10 @@ class XAxisLabel(AxisLabel):
offset: int = 1 # if have margins, k? offset: int = 1 # if have margins, k?
) -> None: ) -> None:
timestrs = self.parent._indexes_to_timestrs([int(data)]) timestrs = self.parent._indexes_to_timestrs([int(data)])
if not timestrs.any(): if not timestrs.any():
return return
self.label_str = timestrs[0] self.label_str = timestrs[0]
width = self.boundingRect().width() width = self.boundingRect().width()
new_pos = QPointF(abs_pos.x() - width / 2 - offset, 0) new_pos = QPointF(abs_pos.x() - width / 2 - offset, 0)
@ -222,6 +230,10 @@ class YAxisLabel(AxisLabel):
| QtCore.Qt.AlignVCenter | QtCore.Qt.AlignVCenter
) )
def size_hint(self) -> Tuple[float, float]:
# size to parent axis width
return None, self.parent.width()
def tick_to_string(self, tick_pos): def tick_to_string(self, tick_pos):
# WTF IS THIS FORMAT? # WTF IS THIS FORMAT?
return ('{: ,.%df}' % self.digits).format(tick_pos).replace(',', ' ') return ('{: ,.%df}' % self.digits).format(tick_pos).replace(',', ' ')