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

View File

@ -21,7 +21,6 @@ Higher level annotation editors.
from __future__ import annotations from __future__ import annotations
from collections import defaultdict from collections import defaultdict
from typing import ( from typing import (
Literal,
Sequence, Sequence,
TYPE_CHECKING, TYPE_CHECKING,
) )
@ -67,18 +66,9 @@ log = get_logger(__name__)
class ArrowEditor(Struct): 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 godw: GodWidget = None # type: ignore # noqa
_arrows: dict[ _arrows: dict[str, list[pg.ArrowItem]] = {}
str,
list[pg.ArrowItem]
] = {}
def add( def add(
self, self,
@ -86,14 +76,8 @@ class ArrowEditor(Struct):
uid: str, uid: str,
x: float, x: float,
y: float, y: float,
color: str|None = None, color: str = 'default',
pointing: Literal[ pointing: str | None = None,
'up',
'down',
None,
] = None,
alpha: int = 255,
zval: float = 1e9,
) -> pg.ArrowItem: ) -> pg.ArrowItem:
''' '''
@ -109,11 +93,6 @@ class ArrowEditor(Struct):
# scale arrow sizing to dpi-aware font # scale arrow sizing to dpi-aware font
size = _font.font.pixelSize() * 0.8 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( arrow = pg.ArrowItem(
angle=angle, angle=angle,
baseAngle=0, baseAngle=0,
@ -121,58 +100,22 @@ class ArrowEditor(Struct):
headWidth=size/2, headWidth=size/2,
tailLen=None, tailLen=None,
pxMode=True, pxMode=True,
# coloring
pen=pen,
brush=brush,
)
arrow.setZValue(zval)
arrow.setPos(x, y)
plot.addItem(arrow) # render to view
# register for removal # coloring
arrow._uid = uid pen=pg.mkPen(hcolor('papas_special')),
self._arrows.setdefault( brush=pg.mkBrush(hcolor(color)),
uid, [] )
).append(arrow) arrow.setPos(x, y)
self._arrows.setdefault(uid, []).append(arrow)
# render to view
plot.addItem(arrow)
return arrow return arrow
def remove( def remove(self, arrow) -> bool:
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'
)
for linked in self.godw.iter_linked(): for linked in self.godw.iter_linked():
linked.chart.plotItem.removeItem(arrow) 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): class LineEditor(Struct):
@ -318,9 +261,6 @@ class LineEditor(Struct):
return lines return lines
# compat with ArrowEditor
remove = remove_line
def as_point( def as_point(
pair: Sequence[float, float] | QPointF, pair: Sequence[float, float] | QPointF,
@ -669,6 +609,3 @@ class SelectRect(QtWidgets.QGraphicsRectItem):
): ):
scen.removeItem(self._label_proxy) scen.removeItem(self._label_proxy)
# compat with ArrowEditor
remove = delete

View File

@ -27,12 +27,10 @@ from contextlib import (
from functools import partial from functools import partial
from pprint import pformat from pprint import pformat
from typing import ( from typing import (
# Any,
AsyncContextManager, AsyncContextManager,
Literal,
) )
from uuid import uuid4
import pyqtgraph as pg
import tractor import tractor
import trio import trio
from tractor import trionics from tractor import trionics
@ -51,13 +49,11 @@ from piker.ui.qt import (
) )
from ._display import DisplayState from ._display import DisplayState
from ._interaction import ChartView from ._interaction import ChartView
from ._editors import ( from ._editors import SelectRect
SelectRect,
ArrowEditor,
)
from ._chart import ChartPlotWidget from ._chart import ChartPlotWidget
from ._dataviz import Viz from ._dataviz import Viz
log = get_logger(__name__) log = get_logger(__name__)
# NOTE: this is UPDATED by the `._display.graphics_update_loop()` # 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). # the "annotations server" which actually renders to a Qt canvas).
# type AnnotsTable = dict[int, QGraphicsItem] # type AnnotsTable = dict[int, QGraphicsItem]
AnnotsTable = dict[int, QGraphicsItem] AnnotsTable = dict[int, QGraphicsItem]
EditorsTable = dict[int, ArrowEditor]
_annots: AnnotsTable = {} _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( async def serve_rc_annots(
@ -125,12 +95,6 @@ async def serve_rc_annots(
annots: AnnotsTable, annots: AnnotsTable,
) -> None: ) -> None:
'''
A small viz(ualization) server for remote ctl of chart
annotations.
'''
global _editors
async for msg in annot_req_stream: async for msg in annot_req_stream:
match msg: match msg:
case { case {
@ -140,6 +104,7 @@ async def serve_rc_annots(
'meth': str(meth), 'meth': str(meth),
'kwargs': dict(kwargs), 'kwargs': dict(kwargs),
}: }:
ds: DisplayState = _dss[fqme] ds: DisplayState = _dss[fqme]
chart: ChartPlotWidget = { chart: ChartPlotWidget = {
60: ds.hist_chart, 60: ds.hist_chart,
@ -171,67 +136,15 @@ async def serve_rc_annots(
aids.add(aid) aids.add(aid)
await annot_req_stream.send(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 { case {
'cmd': 'remove', 'cmd': 'remove',
'aid': int(aid)|str(aid), 'aid': int(aid),
}: }:
# NOTE: this is normally entered on # NOTE: this is normally entered on
# a client's annotation de-alloc normally # a client's annotation de-alloc normally
# prior to detach or modify. # prior to detach or modify.
annot: QGraphicsItem = annots[aid] annot: QGraphicsItem = annots[aid]
assert rm_annot(annot) annot.delete()
# respond to client indicating annot # respond to client indicating annot
# was indeed deleted. # was indeed deleted.
@ -275,12 +188,6 @@ async def remote_annotate(
) -> None: ) -> None:
global _dss, _ctxs 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 assert _dss
_ctxs[ctx.cid] = (ctx, set()) _ctxs[ctx.cid] = (ctx, set())
@ -305,7 +212,7 @@ async def remote_annotate(
assert _ctx is ctx assert _ctx is ctx
for aid in aids: for aid in aids:
annot: QGraphicsItem = _annots[aid] annot: QGraphicsItem = _annots[aid]
assert rm_annot(annot) annot.delete()
class AnnotCtl(Struct): class AnnotCtl(Struct):
@ -427,55 +334,20 @@ class AnnotCtl(Struct):
'timeframe': timeframe, 'timeframe': timeframe,
}) })
async def add_arrow( # TODO: do we even need this?
self, # async def modify(
fqme: str, # self,
timeframe: float, # aid: int, # annotation id
x: float, # meth: str, # far end graphics object method to invoke
y: float, # params: dict[str, Any], # far end `meth(**kwargs)`
pointing: Literal[ # ) -> bool:
'up', # '''
'down', # Modify an existing (remote) annotation's graphics
], # paramters, thus changing it's appearance / state in real
# TODO: a `Literal['view', 'scene']` for this? # time.
# domain: str = 'view', # or 'scene'
color: str = 'dad_blue',
alpha: int = 116,
from_acm: bool = False, # '''
# raise NotImplementedError
) -> 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
@acm @acm
@ -502,9 +374,7 @@ async def open_annot_ctl(
# TODO: print the current discoverable actor UID set # TODO: print the current discoverable actor UID set
# here as well? # here as well?
if not maybe_portals: if not maybe_portals:
raise RuntimeError( raise RuntimeError('No chart UI actors found in service domain?')
'No chart actors found in service domain?'
)
for portal in maybe_portals: for portal in maybe_portals:
ctx_mngrs.append( ctx_mngrs.append(