Compare commits

..

No commits in common. "e77bec203df81e4c3ad4c944564a4679d4f43ce0" and "ad299789db9ef964246a3f76510fd1dc87d2ed15" have entirely different histories.

3 changed files with 45 additions and 254 deletions

View File

@ -50,6 +50,7 @@ async def markup_gaps(
'''
aids: dict[int] = {}
for i in range(gaps.height):
row: pl.DataFrame = gaps[i]
# the gap's RIGHT-most bar's OPEN value
@ -112,6 +113,7 @@ async def markup_gaps(
await tractor.pause()
istart: int = prev_r['index'][0]
# TODO: implement px-col width measure
# and ensure at least as many px-cols
# shown per rect as configured by user.
@ -123,25 +125,25 @@ async def markup_gaps(
rect_gap: float = BGM*3/8
opn: float = row['open'][0]
cls: float = prev_r['close'][0]
ro: tuple[float, float] = (
# dt_end_t,
iend + rect_gap + 1,
opn,
)
cls: float = prev_r['close'][0]
lc: tuple[float, float] = (
# dt_start_t,
istart - rect_gap, # + 1 ,
cls,
)
color: str = 'dad_blue'
diff: float = cls - opn
sgn: float = copysign(1, diff)
color: str = 'dad_blue'
# TODO? mks more sense to have up/down coloring?
# color: str = {
# -1: 'lilypad_green', # up-gap
# 1: 'wine', # down-gap
# }[sgn]
color: str = {
-1: 'buy_green',
1: 'sell_red',
}[sgn]
rect_kwargs: dict[str, Any] = dict(
fqme=fqme,
@ -151,27 +153,9 @@ async def markup_gaps(
color=color,
)
# add up/down rects
aid: int = await actl.add_rect(**rect_kwargs)
assert aid
aids[aid] = rect_kwargs
direction: str = (
'down' if sgn == 1
else 'up'
)
arrow_kwargs: dict[str, Any] = dict(
fqme=fqme,
timeframe=timeframe,
x=iend,
y=cls,
color=color,
alpha=160,
pointing=direction,
)
aid: int = await actl.add_arrow(
**arrow_kwargs
)
# tell chart to redraw all its
# graphics view layers Bo

View File

@ -21,7 +21,6 @@ Higher level annotation editors.
from __future__ import annotations
from collections import defaultdict
from typing import (
Literal,
Sequence,
TYPE_CHECKING,
)
@ -67,18 +66,9 @@ log = get_logger(__name__)
class ArrowEditor(Struct):
'''
Annotate a chart-view with arrows most often used for indicating,
- order txns/clears,
- positions directions,
- general points-of-interest like nooz events.
'''
godw: GodWidget = None # type: ignore # noqa
_arrows: dict[
str,
list[pg.ArrowItem]
] = {}
_arrows: dict[str, list[pg.ArrowItem]] = {}
def add(
self,
@ -86,14 +76,8 @@ class ArrowEditor(Struct):
uid: str,
x: float,
y: float,
color: str|None = None,
pointing: Literal[
'up',
'down',
None,
] = None,
alpha: int = 255,
zval: float = 1e9,
color: str = 'default',
pointing: str | None = None,
) -> pg.ArrowItem:
'''
@ -109,11 +93,6 @@ class ArrowEditor(Struct):
# scale arrow sizing to dpi-aware font
size = _font.font.pixelSize() * 0.8
color = color or 'default'
color = QColor(hcolor(color))
color.setAlpha(alpha)
pen = fn.mkPen(color, width=1)
brush = fn.mkBrush(color)
arrow = pg.ArrowItem(
angle=angle,
baseAngle=0,
@ -121,58 +100,22 @@ class ArrowEditor(Struct):
headWidth=size/2,
tailLen=None,
pxMode=True,
# coloring
pen=pen,
brush=brush,
)
arrow.setZValue(zval)
arrow.setPos(x, y)
plot.addItem(arrow) # render to view
# register for removal
arrow._uid = uid
self._arrows.setdefault(
uid, []
).append(arrow)
# coloring
pen=pg.mkPen(hcolor('papas_special')),
brush=pg.mkBrush(hcolor(color)),
)
arrow.setPos(x, y)
self._arrows.setdefault(uid, []).append(arrow)
# render to view
plot.addItem(arrow)
return arrow
def remove(
self,
arrow: pg.ArrowItem,
) -> None:
'''
Remove a *single arrow* from all chart views to which it was
added.
'''
uid: str = arrow._uid
arrows: list[pg.ArrowItem] = self._arrows[uid]
log.info(
f'Removing arrow from views\n'
f'uid: {uid!r}\n'
f'{arrow!r}\n'
)
def remove(self, arrow) -> bool:
for linked in self.godw.iter_linked():
linked.chart.plotItem.removeItem(arrow)
try:
arrows.remove(arrow)
except ValueError:
log.warning(
f'Arrow was already removed?\n'
f'uid: {uid!r}\n'
f'{arrow!r}\n'
)
def remove_all(self) -> set[pg.ArrowItem]:
'''
Remove all arrows added by this editor from all
chart-views.
'''
for uid, arrows in self._arrows.items():
for arrow in arrows:
self.remove(arrow)
class LineEditor(Struct):
@ -318,9 +261,6 @@ class LineEditor(Struct):
return lines
# compat with ArrowEditor
remove = remove_line
def as_point(
pair: Sequence[float, float] | QPointF,
@ -669,6 +609,3 @@ class SelectRect(QtWidgets.QGraphicsRectItem):
):
scen.removeItem(self._label_proxy)
# compat with ArrowEditor
remove = delete

View File

@ -27,12 +27,10 @@ from contextlib import (
from functools import partial
from pprint import pformat
from typing import (
# Any,
AsyncContextManager,
Literal,
)
from uuid import uuid4
import pyqtgraph as pg
import tractor
import trio
from tractor import trionics
@ -51,13 +49,11 @@ from piker.ui.qt import (
)
from ._display import DisplayState
from ._interaction import ChartView
from ._editors import (
SelectRect,
ArrowEditor,
)
from ._editors import SelectRect
from ._chart import ChartPlotWidget
from ._dataviz import Viz
log = get_logger(__name__)
# NOTE: this is UPDATED by the `._display.graphics_update_loop()`
@ -87,34 +83,8 @@ _ctxs: IpcCtxTable = {}
# the "annotations server" which actually renders to a Qt canvas).
# type AnnotsTable = dict[int, QGraphicsItem]
AnnotsTable = dict[int, QGraphicsItem]
EditorsTable = dict[int, ArrowEditor]
_annots: AnnotsTable = {}
_editors: EditorsTable = {}
def rm_annot(
annot: ArrowEditor|SelectRect
) -> bool:
global _editors
match annot:
case pg.ArrowItem():
editor = _editors[annot._uid]
editor.remove(annot)
# ^TODO? only remove each arrow or all?
# if editor._arrows:
# editor.remove_all()
# else:
# log.warning(
# f'Annot already removed!\n'
# f'{annot!r}\n'
# )
return True
case SelectRect():
annot.delete()
return True
return False
async def serve_rc_annots(
@ -125,12 +95,6 @@ async def serve_rc_annots(
annots: AnnotsTable,
) -> None:
'''
A small viz(ualization) server for remote ctl of chart
annotations.
'''
global _editors
async for msg in annot_req_stream:
match msg:
case {
@ -140,6 +104,7 @@ async def serve_rc_annots(
'meth': str(meth),
'kwargs': dict(kwargs),
}:
ds: DisplayState = _dss[fqme]
chart: ChartPlotWidget = {
60: ds.hist_chart,
@ -171,67 +136,15 @@ async def serve_rc_annots(
aids.add(aid)
await annot_req_stream.send(aid)
case {
'cmd': 'ArrowEditor',
'fqme': fqme,
'timeframe': timeframe,
'meth': 'add'|'remove' as meth,
'kwargs': {
'x': float(x),
'y': float(y),
'pointing': pointing,
'color': color,
'aid': str()|None as aid,
'alpha': int(alpha),
},
# ?TODO? split based on method fn-sigs?
# 'pointing',
}:
ds: DisplayState = _dss[fqme]
chart: ChartPlotWidget = {
60: ds.hist_chart,
1: ds.chart,
}[timeframe]
cv: ChartView = chart.cv
godw = chart.linked.godwidget
arrows = ArrowEditor(godw=godw)
# `.add/.remove()` API
if meth != 'add':
# await tractor.pause()
raise ValueError(
f'Invalid arrow-edit request ?\n'
f'{msg!r}\n'
)
aid: str = str(uuid4())
arrow: pg.ArrowItem = arrows.add(
plot=chart.plotItem,
uid=aid,
x=x,
y=y,
pointing=pointing,
color=color,
alpha=alpha,
)
annots[aid] = arrow
_editors[aid] = arrows
aids: set[int] = ctxs[ipc_key][1]
aids.add(aid)
await annot_req_stream.send(aid)
# TODO, use `pg.TextItem` to put a humaized
# time label beside the arrows
case {
'cmd': 'remove',
'aid': int(aid)|str(aid),
'aid': int(aid),
}:
# NOTE: this is normally entered on
# a client's annotation de-alloc normally
# prior to detach or modify.
annot: QGraphicsItem = annots[aid]
assert rm_annot(annot)
annot.delete()
# respond to client indicating annot
# was indeed deleted.
@ -275,12 +188,6 @@ async def remote_annotate(
) -> None:
global _dss, _ctxs
if not _dss:
raise RuntimeError(
'Race condition on chart-init state ??\n'
'Anoter actor is trying to annoate this chart '
'before it has fully spawned.\n'
)
assert _dss
_ctxs[ctx.cid] = (ctx, set())
@ -305,7 +212,7 @@ async def remote_annotate(
assert _ctx is ctx
for aid in aids:
annot: QGraphicsItem = _annots[aid]
assert rm_annot(annot)
annot.delete()
class AnnotCtl(Struct):
@ -427,55 +334,20 @@ class AnnotCtl(Struct):
'timeframe': timeframe,
})
async def add_arrow(
self,
fqme: str,
timeframe: float,
x: float,
y: float,
pointing: Literal[
'up',
'down',
],
# TODO: a `Literal['view', 'scene']` for this?
# domain: str = 'view', # or 'scene'
color: str = 'dad_blue',
alpha: int = 116,
# TODO: do we even need this?
# async def modify(
# self,
# aid: int, # annotation id
# meth: str, # far end graphics object method to invoke
# params: dict[str, Any], # far end `meth(**kwargs)`
# ) -> bool:
# '''
# Modify an existing (remote) annotation's graphics
# paramters, thus changing it's appearance / state in real
# time.
from_acm: bool = False,
) -> int:
'''
Add a `SelectRect` annotation to the target view, return
the instances `id(obj)` from the remote UI actor.
'''
ipc: MsgStream = self._get_ipc(fqme)
await ipc.send({
'fqme': fqme,
'cmd': 'ArrowEditor',
'timeframe': timeframe,
# 'meth': str(meth),
'meth': 'add',
'kwargs': {
'x': float(x),
'y': float(y),
'color': color,
'pointing': pointing, # up|down
'alpha': alpha,
'aid': None,
},
})
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
# '''
# raise NotImplementedError
@acm
@ -502,9 +374,7 @@ async def open_annot_ctl(
# TODO: print the current discoverable actor UID set
# here as well?
if not maybe_portals:
raise RuntimeError(
'No chart actors found in service domain?'
)
raise RuntimeError('No chart UI actors found in service domain?')
for portal in maybe_portals:
ctx_mngrs.append(