Compare commits
No commits in common. "e77bec203df81e4c3ad4c944564a4679d4f43ce0" and "ad299789db9ef964246a3f76510fd1dc87d2ed15" have entirely different histories.
e77bec203d
...
ad299789db
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
|
||||||
|
|
@ -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(
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue