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.
 | 
			
		||||
 | 
			
		||||
"""
 | 
			
		||||
from functools import partial
 | 
			
		||||
from functools import lru_cache
 | 
			
		||||
from typing import List, Tuple, Optional, Callable
 | 
			
		||||
from math import floor
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -27,8 +27,10 @@ import pyqtgraph as pg
 | 
			
		|||
from PyQt5 import QtCore, QtGui, QtWidgets
 | 
			
		||||
from PyQt5.QtCore import QPointF
 | 
			
		||||
 | 
			
		||||
from ._style import DpiAwareFont, hcolor, _font
 | 
			
		||||
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'))
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -72,7 +74,10 @@ class Axis(pg.AxisItem):
 | 
			
		|||
        })
 | 
			
		||||
 | 
			
		||||
        self.setTickFont(_font.font)
 | 
			
		||||
        # NOTE: this is for surrounding "border"
 | 
			
		||||
        self.setPen(_axis_pen)
 | 
			
		||||
        # this is the text color
 | 
			
		||||
        self.setTextPen(_axis_pen)
 | 
			
		||||
        self.typical_br = _font._qfm.boundingRect(typical_max_str)
 | 
			
		||||
 | 
			
		||||
        # size the pertinent axis dimension to a "typical value"
 | 
			
		||||
| 
						 | 
				
			
			@ -91,6 +96,7 @@ class PriceAxis(Axis):
 | 
			
		|||
        self,
 | 
			
		||||
        *args,
 | 
			
		||||
        min_tick: int = 2,
 | 
			
		||||
        title: str = '',
 | 
			
		||||
        formatter: Optional[Callable[[float], str]] = None,
 | 
			
		||||
        **kwargs
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -98,6 +104,43 @@ class PriceAxis(Axis):
 | 
			
		|||
        super().__init__(*args, **kwargs)
 | 
			
		||||
        self.formatter = formatter
 | 
			
		||||
        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(
 | 
			
		||||
        self,
 | 
			
		||||
| 
						 | 
				
			
			@ -124,6 +167,8 @@ class PriceAxis(Axis):
 | 
			
		|||
            float_digits(spacing * scale),
 | 
			
		||||
            self._min_tick,
 | 
			
		||||
        )
 | 
			
		||||
        if self.title:
 | 
			
		||||
            self.title.update()
 | 
			
		||||
 | 
			
		||||
        # print(f'vals: {vals}\nscale: {scale}\nspacing: {spacing}')
 | 
			
		||||
        # print(f'digits: {digits}')
 | 
			
		||||
| 
						 | 
				
			
			@ -291,9 +336,10 @@ class AxisLabel(pg.GraphicsObject):
 | 
			
		|||
 | 
			
		||||
 | 
			
		||||
    def boundingRect(self):  # noqa
 | 
			
		||||
        """Size the graphics space from the text contents.
 | 
			
		||||
        '''
 | 
			
		||||
        Size the graphics space from the text contents.
 | 
			
		||||
 | 
			
		||||
        """
 | 
			
		||||
        '''
 | 
			
		||||
        if self.label_str:
 | 
			
		||||
            self._size_br_from_str(self.label_str)
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -309,23 +355,32 @@ class AxisLabel(pg.GraphicsObject):
 | 
			
		|||
 | 
			
		||||
        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:
 | 
			
		||||
        """Do our best to render the bounding rect to a set margin
 | 
			
		||||
    ) -> tuple[float, float]:
 | 
			
		||||
        '''
 | 
			
		||||
        Do our best to render the bounding rect to a set margin
 | 
			
		||||
        around provided string contents.
 | 
			
		||||
 | 
			
		||||
        """
 | 
			
		||||
        '''
 | 
			
		||||
        # size the filled rect to text and/or parent axis
 | 
			
		||||
        # 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)
 | 
			
		||||
 | 
			
		||||
        txt_br = self._txt_br = self._dpifont.boundingRect(value)
 | 
			
		||||
        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
 | 
			
		||||
        h, w = self.size_hint()
 | 
			
		||||
        # print(f'axis size: {self._parent.size()}')
 | 
			
		||||
        # print(f'axis geo: {self._parent.geometry()}')
 | 
			
		||||
 | 
			
		||||
        self.rect = QtCore.QRectF(
 | 
			
		||||
            0, 0,
 | 
			
		||||
| 
						 | 
				
			
			@ -336,7 +391,7 @@ class AxisLabel(pg.GraphicsObject):
 | 
			
		|||
        # hb = self.path.controlPointRect()
 | 
			
		||||
        # hb_size = hb.size()
 | 
			
		||||
 | 
			
		||||
        return self.rect
 | 
			
		||||
        return (self.rect.width(), self.rect.height())
 | 
			
		||||
 | 
			
		||||
# _common_text_flags = (
 | 
			
		||||
#     QtCore.Qt.TextDontClip |
 | 
			
		||||
| 
						 | 
				
			
			@ -432,8 +487,12 @@ class YAxisLabel(AxisLabel):
 | 
			
		|||
            self.x_offset, y_offset = self._parent.txt_offsets()
 | 
			
		||||
 | 
			
		||||
    def size_hint(self) -> Tuple[float, float]:
 | 
			
		||||
        # size to parent axis width
 | 
			
		||||
        return None, self._parent.width()
 | 
			
		||||
        # size to parent axis width(-ish)
 | 
			
		||||
        wsh = self._dpifont.boundingRect(' ').height() / 2
 | 
			
		||||
        return (
 | 
			
		||||
            None,
 | 
			
		||||
            self._parent.size().width() - wsh,
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    def update_label(
 | 
			
		||||
        self,
 | 
			
		||||
| 
						 | 
				
			
			@ -461,9 +520,10 @@ class YAxisLabel(AxisLabel):
 | 
			
		|||
        self.update()
 | 
			
		||||
 | 
			
		||||
    def update_on_resize(self, vr, r):
 | 
			
		||||
        """Tiis is a ``.sigRangeChanged()`` handler.
 | 
			
		||||
        '''
 | 
			
		||||
        This is a ``.sigRangeChanged()`` handler.
 | 
			
		||||
 | 
			
		||||
        """
 | 
			
		||||
        '''
 | 
			
		||||
        index, last = self._last_datum
 | 
			
		||||
        if index is not None:
 | 
			
		||||
            self.update_from_data(index, last)
 | 
			
		||||
| 
						 | 
				
			
			@ -473,11 +533,13 @@ class YAxisLabel(AxisLabel):
 | 
			
		|||
        index: int,
 | 
			
		||||
        value: float,
 | 
			
		||||
        _save_last: bool = True,
 | 
			
		||||
 | 
			
		||||
    ) -> None:
 | 
			
		||||
        """Update the label's text contents **and** position from
 | 
			
		||||
        '''
 | 
			
		||||
        Update the label's text contents **and** position from
 | 
			
		||||
        a view box coordinate datum.
 | 
			
		||||
 | 
			
		||||
        """
 | 
			
		||||
        '''
 | 
			
		||||
        if _save_last:
 | 
			
		||||
            self._last_datum = (index, value)
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue