Add `Axis.set_title()` for hipper labelling
Use our internal `Label` with much better dpi based sizing of text and placement below the y-axis ticks area for more minimalism and less clutter. Play around with `lru_cache` on axis label bounding rects and for now just hack sizing by subtracting half the text height (not sure why) from the width to avoid over-extension / overlap with any adjacent axis.vlm_plotz
							parent
							
								
									584be7dca9
								
							
						
					
					
						commit
						cad3fdc3b9
					
				| 
						 | 
					@ -18,7 +18,7 @@
 | 
				
			||||||
Chart axes graphics and behavior.
 | 
					Chart axes graphics and behavior.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
"""
 | 
					"""
 | 
				
			||||||
from functools import partial
 | 
					from functools import lru_cache
 | 
				
			||||||
from typing import List, Tuple, Optional, Callable
 | 
					from typing import List, Tuple, Optional, Callable
 | 
				
			||||||
from math import floor
 | 
					from math import floor
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -27,8 +27,10 @@ import pyqtgraph as pg
 | 
				
			||||||
from PyQt5 import QtCore, QtGui, QtWidgets
 | 
					from PyQt5 import QtCore, QtGui, QtWidgets
 | 
				
			||||||
from PyQt5.QtCore import QPointF
 | 
					from PyQt5.QtCore import QPointF
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from ._style import DpiAwareFont, hcolor, _font
 | 
					 | 
				
			||||||
from ..data._source import float_digits
 | 
					from ..data._source import float_digits
 | 
				
			||||||
 | 
					from ._label import Label
 | 
				
			||||||
 | 
					from ._style import DpiAwareFont, hcolor, _font
 | 
				
			||||||
 | 
					from ._interaction import ChartView
 | 
				
			||||||
 | 
					
 | 
				
			||||||
_axis_pen = pg.mkPen(hcolor('bracket'))
 | 
					_axis_pen = pg.mkPen(hcolor('bracket'))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -72,7 +74,10 @@ class Axis(pg.AxisItem):
 | 
				
			||||||
        })
 | 
					        })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self.setTickFont(_font.font)
 | 
					        self.setTickFont(_font.font)
 | 
				
			||||||
 | 
					        # NOTE: this is for surrounding "border"
 | 
				
			||||||
        self.setPen(_axis_pen)
 | 
					        self.setPen(_axis_pen)
 | 
				
			||||||
 | 
					        # this is the text color
 | 
				
			||||||
 | 
					        self.setTextPen(_axis_pen)
 | 
				
			||||||
        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"
 | 
				
			||||||
| 
						 | 
					@ -91,6 +96,7 @@ class PriceAxis(Axis):
 | 
				
			||||||
        self,
 | 
					        self,
 | 
				
			||||||
        *args,
 | 
					        *args,
 | 
				
			||||||
        min_tick: int = 2,
 | 
					        min_tick: int = 2,
 | 
				
			||||||
 | 
					        title: str = '',
 | 
				
			||||||
        formatter: Optional[Callable[[float], str]] = None,
 | 
					        formatter: Optional[Callable[[float], str]] = None,
 | 
				
			||||||
        **kwargs
 | 
					        **kwargs
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -98,6 +104,43 @@ class PriceAxis(Axis):
 | 
				
			||||||
        super().__init__(*args, **kwargs)
 | 
					        super().__init__(*args, **kwargs)
 | 
				
			||||||
        self.formatter = formatter
 | 
					        self.formatter = formatter
 | 
				
			||||||
        self._min_tick: int = min_tick
 | 
					        self._min_tick: int = min_tick
 | 
				
			||||||
 | 
					        self.title = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def set_title(
 | 
				
			||||||
 | 
					        self,
 | 
				
			||||||
 | 
					        title: str,
 | 
				
			||||||
 | 
					        view: Optional[ChartView] = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ) -> Label:
 | 
				
			||||||
 | 
					        '''
 | 
				
			||||||
 | 
					        Set a sane UX label using our built-in ``Label``.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        '''
 | 
				
			||||||
 | 
					        # XXX: built-in labels but they're huge, and placed weird..
 | 
				
			||||||
 | 
					        # self.setLabel(title)
 | 
				
			||||||
 | 
					        # self.showLabel()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        label = self.title = Label(
 | 
				
			||||||
 | 
					            view=view or self.linkedView(),
 | 
				
			||||||
 | 
					            fmt_str=title,
 | 
				
			||||||
 | 
					            color='bracket',
 | 
				
			||||||
 | 
					            parent=self,
 | 
				
			||||||
 | 
					            # update_on_range_change=False,
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        def below_axis() -> QPointF:
 | 
				
			||||||
 | 
					            return QPointF(
 | 
				
			||||||
 | 
					                0,
 | 
				
			||||||
 | 
					                self.size().height(),
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # XXX: doesn't work? have to pass it above
 | 
				
			||||||
 | 
					        # label.txt.setParent(self)
 | 
				
			||||||
 | 
					        label.scene_anchor = below_axis
 | 
				
			||||||
 | 
					        label.render()
 | 
				
			||||||
 | 
					        label.show()
 | 
				
			||||||
 | 
					        label.update()
 | 
				
			||||||
 | 
					        return label
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def set_min_tick(
 | 
					    def set_min_tick(
 | 
				
			||||||
        self,
 | 
					        self,
 | 
				
			||||||
| 
						 | 
					@ -124,6 +167,8 @@ class PriceAxis(Axis):
 | 
				
			||||||
            float_digits(spacing * scale),
 | 
					            float_digits(spacing * scale),
 | 
				
			||||||
            self._min_tick,
 | 
					            self._min_tick,
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
 | 
					        if self.title:
 | 
				
			||||||
 | 
					            self.title.update()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # print(f'vals: {vals}\nscale: {scale}\nspacing: {spacing}')
 | 
					        # print(f'vals: {vals}\nscale: {scale}\nspacing: {spacing}')
 | 
				
			||||||
        # print(f'digits: {digits}')
 | 
					        # print(f'digits: {digits}')
 | 
				
			||||||
| 
						 | 
					@ -291,9 +336,10 @@ class AxisLabel(pg.GraphicsObject):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def boundingRect(self):  # noqa
 | 
					    def boundingRect(self):  # noqa
 | 
				
			||||||
        """Size the graphics space from the text contents.
 | 
					        '''
 | 
				
			||||||
 | 
					        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)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -309,23 +355,32 @@ class AxisLabel(pg.GraphicsObject):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return QtCore.QRectF()
 | 
					        return QtCore.QRectF()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # return self.rect or QtCore.QRectF()
 | 
					    # TODO: but the input probably needs to be the "len" of
 | 
				
			||||||
 | 
					    # the current text value:
 | 
				
			||||||
 | 
					    @lru_cache
 | 
				
			||||||
 | 
					    def _size_br_from_str(
 | 
				
			||||||
 | 
					        self,
 | 
				
			||||||
 | 
					        value: str
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def _size_br_from_str(self, value: str) -> None:
 | 
					    ) -> tuple[float, float]:
 | 
				
			||||||
        """Do our best to render the bounding rect to a set margin
 | 
					        '''
 | 
				
			||||||
 | 
					        Do our best to render the bounding rect to a set margin
 | 
				
			||||||
        around provided string contents.
 | 
					        around provided string contents.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        """
 | 
					        '''
 | 
				
			||||||
        # size the filled rect to text and/or parent axis
 | 
					        # size the filled rect to text and/or parent axis
 | 
				
			||||||
        # if not self._txt_br:
 | 
					        # if not self._txt_br:
 | 
				
			||||||
        #     # XXX: this can't be c
 | 
					        #     # XXX: this can't be called until stuff is rendered?
 | 
				
			||||||
        #     self._txt_br = self._dpifont.boundingRect(value)
 | 
					        #     self._txt_br = self._dpifont.boundingRect(value)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        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()
 | 
				
			||||||
 | 
					        # print(f'wsw: {self._dpifont.boundingRect(" ")}')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # allow subtypes to specify a static width and height
 | 
					        # allow subtypes to specify a static width and height
 | 
				
			||||||
        h, w = self.size_hint()
 | 
					        h, w = self.size_hint()
 | 
				
			||||||
 | 
					        # print(f'axis size: {self._parent.size()}')
 | 
				
			||||||
 | 
					        # print(f'axis geo: {self._parent.geometry()}')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self.rect = QtCore.QRectF(
 | 
					        self.rect = QtCore.QRectF(
 | 
				
			||||||
            0, 0,
 | 
					            0, 0,
 | 
				
			||||||
| 
						 | 
					@ -336,7 +391,7 @@ class AxisLabel(pg.GraphicsObject):
 | 
				
			||||||
        # hb = self.path.controlPointRect()
 | 
					        # hb = self.path.controlPointRect()
 | 
				
			||||||
        # hb_size = hb.size()
 | 
					        # hb_size = hb.size()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return self.rect
 | 
					        return (self.rect.width(), self.rect.height())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# _common_text_flags = (
 | 
					# _common_text_flags = (
 | 
				
			||||||
#     QtCore.Qt.TextDontClip |
 | 
					#     QtCore.Qt.TextDontClip |
 | 
				
			||||||
| 
						 | 
					@ -432,8 +487,12 @@ class YAxisLabel(AxisLabel):
 | 
				
			||||||
            self.x_offset, y_offset = self._parent.txt_offsets()
 | 
					            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(-ish)
 | 
				
			||||||
        return None, self._parent.width()
 | 
					        wsh = self._dpifont.boundingRect(' ').height() / 2
 | 
				
			||||||
 | 
					        return (
 | 
				
			||||||
 | 
					            None,
 | 
				
			||||||
 | 
					            self._parent.size().width() - wsh,
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def update_label(
 | 
					    def update_label(
 | 
				
			||||||
        self,
 | 
					        self,
 | 
				
			||||||
| 
						 | 
					@ -461,9 +520,10 @@ class YAxisLabel(AxisLabel):
 | 
				
			||||||
        self.update()
 | 
					        self.update()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def update_on_resize(self, vr, r):
 | 
					    def update_on_resize(self, vr, r):
 | 
				
			||||||
        """Tiis is a ``.sigRangeChanged()`` handler.
 | 
					        '''
 | 
				
			||||||
 | 
					        This 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)
 | 
				
			||||||
| 
						 | 
					@ -473,11 +533,13 @@ class YAxisLabel(AxisLabel):
 | 
				
			||||||
        index: int,
 | 
					        index: int,
 | 
				
			||||||
        value: float,
 | 
					        value: float,
 | 
				
			||||||
        _save_last: bool = True,
 | 
					        _save_last: bool = True,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    ) -> None:
 | 
					    ) -> None:
 | 
				
			||||||
        """Update the label's text contents **and** position from
 | 
					        '''
 | 
				
			||||||
 | 
					        Update the label's text contents **and** position from
 | 
				
			||||||
        a view box coordinate datum.
 | 
					        a view box coordinate datum.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        """
 | 
					        '''
 | 
				
			||||||
        if _save_last:
 | 
					        if _save_last:
 | 
				
			||||||
            self._last_datum = (index, value)
 | 
					            self._last_datum = (index, value)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue