Add humanized duration labels to gap annotations
Introduce `humanize_duration()` helper in `.tsp._annotate` to convert seconds to short human-readable format (d/h/m/s). Extend annot-ctl API with `add_text()` method for placing `pg.TextItem` labels on charts. Also, - add duration labels on RHS of gap arrows in `markup_gaps()` - handle text item removal in `rm_annot()` match block - expose `TextItem` cmd in `serve_rc_annots()` IPC handler - use `hcolor()` for named-to-hex color conversion - set anchor positioning for up vs down gaps (this patch was generated in some part by [`claude-code`][claude-code-gh]) [claude-code-gh]: https://github.com/anthropics/claude-codemultiaddrs
parent
1776242413
commit
de5b1737b4
|
|
@ -35,6 +35,51 @@ if TYPE_CHECKING:
|
|||
from piker.ui._remote_ctl import AnnotCtl
|
||||
|
||||
|
||||
def humanize_duration(
|
||||
seconds: float,
|
||||
) -> str:
|
||||
'''
|
||||
Convert duration in seconds to short human-readable form.
|
||||
|
||||
Uses smallest appropriate time unit:
|
||||
- d: days
|
||||
- h: hours
|
||||
- m: minutes
|
||||
- s: seconds
|
||||
|
||||
Examples:
|
||||
- 86400 -> "1d"
|
||||
- 28800 -> "8h"
|
||||
- 180 -> "3m"
|
||||
- 45 -> "45s"
|
||||
|
||||
'''
|
||||
abs_secs: float = abs(seconds)
|
||||
|
||||
if abs_secs >= 86400:
|
||||
days: float = abs_secs / 86400
|
||||
if days >= 10:
|
||||
return f'{int(days)}d'
|
||||
return f'{days:.1f}d'
|
||||
|
||||
elif abs_secs >= 3600:
|
||||
hours: float = abs_secs / 3600
|
||||
if hours >= 10:
|
||||
return f'{int(hours)}h'
|
||||
return f'{hours:.1f}h'
|
||||
|
||||
elif abs_secs >= 60:
|
||||
mins: float = abs_secs / 60
|
||||
if mins >= 10:
|
||||
return f'{int(mins)}m'
|
||||
return f'{mins:.1f}m'
|
||||
|
||||
else:
|
||||
if abs_secs >= 10:
|
||||
return f'{int(abs_secs)}s'
|
||||
return f'{abs_secs:.1f}s'
|
||||
|
||||
|
||||
async def markup_gaps(
|
||||
fqme: str,
|
||||
timeframe: float,
|
||||
|
|
@ -124,6 +169,10 @@ async def markup_gaps(
|
|||
opn: float = row['open'][0]
|
||||
cls: float = prev_r['close'][0]
|
||||
|
||||
# get gap duration for humanized label
|
||||
gap_dur_s: float = row['s_diff'][0]
|
||||
gap_label: str = humanize_duration(gap_dur_s)
|
||||
|
||||
# BGM=0.16 is the normal diff from overlap between bars, SO
|
||||
# just go slightly "in" from that "between them".
|
||||
from_idx: int = BGM - .06 # = .10
|
||||
|
|
@ -181,6 +230,23 @@ async def markup_gaps(
|
|||
**arrow_kwargs
|
||||
)
|
||||
|
||||
# add duration label to RHS of arrow
|
||||
if sgn == -1: # up-gap
|
||||
anchor = (0, 0) # XXX, i dun get dese dims.. XD
|
||||
else: # down-gap
|
||||
anchor = (0, 1) # XXX y, x?
|
||||
|
||||
text_aid: int = await actl.add_text(
|
||||
fqme=fqme,
|
||||
timeframe=timeframe,
|
||||
text=gap_label,
|
||||
x=iend + 1,
|
||||
y=cls,
|
||||
color=color,
|
||||
anchor=anchor,
|
||||
)
|
||||
aids[text_aid] = {'text': gap_label}
|
||||
|
||||
# tell chart to redraw all its
|
||||
# graphics view layers Bo
|
||||
await actl.redraw(
|
||||
|
|
|
|||
|
|
@ -48,6 +48,7 @@ from piker.service import find_service
|
|||
from piker.brokers import SymbolNotFound
|
||||
from piker.ui.qt import (
|
||||
QGraphicsItem,
|
||||
QColor,
|
||||
)
|
||||
from ._display import DisplayState
|
||||
from ._interaction import ChartView
|
||||
|
|
@ -57,6 +58,7 @@ from ._editors import (
|
|||
)
|
||||
from ._chart import ChartPlotWidget
|
||||
from ._dataviz import Viz
|
||||
from ._style import hcolor
|
||||
|
||||
log = get_logger(__name__)
|
||||
|
||||
|
|
@ -93,7 +95,7 @@ _annots: AnnotsTable = {}
|
|||
_editors: EditorsTable = {}
|
||||
|
||||
def rm_annot(
|
||||
annot: ArrowEditor|SelectRect
|
||||
annot: ArrowEditor|SelectRect|pg.TextItem
|
||||
) -> bool:
|
||||
global _editors
|
||||
match annot:
|
||||
|
|
@ -114,6 +116,12 @@ def rm_annot(
|
|||
annot.delete()
|
||||
return True
|
||||
|
||||
case pg.TextItem():
|
||||
scene = annot.scene()
|
||||
if scene:
|
||||
scene.removeItem(annot)
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
|
|
@ -230,8 +238,41 @@ async def serve_rc_annots(
|
|||
aids.add(aid)
|
||||
await annot_req_stream.send(aid)
|
||||
|
||||
# TODO, use `pg.TextItem` to put a humaized
|
||||
# time label beside the arrows
|
||||
case {
|
||||
'cmd': 'TextItem',
|
||||
'fqme': fqme,
|
||||
'timeframe': timeframe,
|
||||
'kwargs': {
|
||||
'text': str(text),
|
||||
'x': int()|float() as x,
|
||||
'y': int()|float() as y,
|
||||
'color': color,
|
||||
'anchor': list(anchor),
|
||||
},
|
||||
}:
|
||||
ds: DisplayState = _dss[fqme]
|
||||
chart: ChartPlotWidget = {
|
||||
60: ds.hist_chart,
|
||||
1: ds.chart,
|
||||
}[timeframe]
|
||||
|
||||
# convert named color to hex
|
||||
color_hex: str = hcolor(color)
|
||||
|
||||
# create text item
|
||||
text_item: pg.TextItem = pg.TextItem(
|
||||
text=text,
|
||||
color=color_hex,
|
||||
anchor=anchor,
|
||||
)
|
||||
text_item.setPos(x, y)
|
||||
chart.plotItem.addItem(text_item)
|
||||
|
||||
aid: str = str(uuid4())
|
||||
annots[aid] = text_item
|
||||
aids: set[int] = ctxs[ipc_key][1]
|
||||
aids.add(aid)
|
||||
await annot_req_stream.send(aid)
|
||||
|
||||
case {
|
||||
'cmd': 'remove',
|
||||
|
|
@ -497,6 +538,49 @@ class AnnotCtl(Struct):
|
|||
)
|
||||
return aid
|
||||
|
||||
async def add_text(
|
||||
self,
|
||||
fqme: str,
|
||||
timeframe: float,
|
||||
text: str,
|
||||
x: float,
|
||||
y: float,
|
||||
color: str|tuple = 'dad_blue',
|
||||
anchor: tuple[float, float] = (0, 1),
|
||||
|
||||
from_acm: bool = False,
|
||||
|
||||
) -> int:
|
||||
'''
|
||||
Add a `pg.TextItem` annotation to the target view.
|
||||
|
||||
anchor: (x, y) where (0,0) is upper-left, (1,1) is lower-right
|
||||
|
||||
'''
|
||||
ipc: MsgStream = self._get_ipc(fqme)
|
||||
await ipc.send({
|
||||
'fqme': fqme,
|
||||
'cmd': 'TextItem',
|
||||
'timeframe': timeframe,
|
||||
'kwargs': {
|
||||
'text': text,
|
||||
'x': float(x),
|
||||
'y': float(y),
|
||||
'color': color,
|
||||
'anchor': tuple(anchor),
|
||||
},
|
||||
})
|
||||
aid: int = await ipc.receive()
|
||||
self._ipcs[aid] = ipc
|
||||
if not from_acm:
|
||||
self._annot_stack.push_async_callback(
|
||||
partial(
|
||||
self.remove,
|
||||
aid,
|
||||
)
|
||||
)
|
||||
return aid
|
||||
|
||||
|
||||
@acm
|
||||
async def open_annot_ctl(
|
||||
|
|
|
|||
Loading…
Reference in New Issue