Rename `Flow` -> `Viz`

The type is better described as a "data visualization":
https://en.wikipedia.org/wiki/Data_and_information_visualization

Add `ChartPlotWidget.get_viz()` to start working towards not accessing
the private table directly XD

We'll probably end up using the name `Flow` for a type that tracks
a collection of composed/cascaded `Flume`s:
https://en.wikipedia.org/wiki/Two-port_network#Cascade_connection
multichartz
Tyler Goodlet 2022-11-24 15:33:58 -05:00
parent d3a40678ff
commit 190c792515
11 changed files with 132 additions and 195 deletions

View File

@ -302,7 +302,7 @@ class DynamicDateAxis(Axis):
# XX: ARGGGGG AG:LKSKDJF:LKJSDFD # XX: ARGGGGG AG:LKSKDJF:LKJSDFD
chart = self.pi.chart_widget chart = self.pi.chart_widget
flow = chart._flows[chart.name] flow = chart._vizs[chart.name]
shm = flow.shm shm = flow.shm
bars = shm.array bars = shm.array
first = shm._first.value first = shm._first.value

View File

@ -72,7 +72,7 @@ from ._interaction import ChartView
from ._forms import FieldsForm from ._forms import FieldsForm
from .._profile import pg_profile_enabled, ms_slower_then from .._profile import pg_profile_enabled, ms_slower_then
from ._overlay import PlotItemOverlay from ._overlay import PlotItemOverlay
from ._flows import Flow from ._flows import Viz
from ._search import SearchWidget from ._search import SearchWidget
from . import _pg_overrides as pgo from . import _pg_overrides as pgo
from .._profile import Profiler from .._profile import Profiler
@ -711,7 +711,7 @@ class LinkedSplits(QWidget):
if style == 'ohlc_bar': if style == 'ohlc_bar':
# graphics, data_key = cpw.draw_ohlc( # graphics, data_key = cpw.draw_ohlc(
flow = cpw.draw_ohlc( viz = cpw.draw_ohlc(
name, name,
shm, shm,
flume=flume, flume=flume,
@ -727,7 +727,7 @@ class LinkedSplits(QWidget):
elif style == 'line': elif style == 'line':
add_label = True add_label = True
# graphics, data_key = cpw.draw_curve( # graphics, data_key = cpw.draw_curve(
flow = cpw.draw_curve( viz = cpw.draw_curve(
name, name,
shm, shm,
flume, flume,
@ -738,7 +738,7 @@ class LinkedSplits(QWidget):
elif style == 'step': elif style == 'step':
add_label = True add_label = True
# graphics, data_key = cpw.draw_curve( # graphics, data_key = cpw.draw_curve(
flow = cpw.draw_curve( viz = cpw.draw_curve(
name, name,
shm, shm,
flume, flume,
@ -751,8 +751,8 @@ class LinkedSplits(QWidget):
else: else:
raise ValueError(f"Chart style {style} is currently unsupported") raise ValueError(f"Chart style {style} is currently unsupported")
graphics = flow.graphics graphics = viz.graphics
data_key = flow.name data_key = viz.name
if _is_main: if _is_main:
assert style == 'ohlc_bar', 'main chart must be OHLC' assert style == 'ohlc_bar', 'main chart must be OHLC'
@ -908,7 +908,7 @@ class ChartPlotWidget(pg.PlotWidget):
# self.setViewportMargins(0, 0, 0, 0) # self.setViewportMargins(0, 0, 0, 0)
# registry of overlay curve names # registry of overlay curve names
self._flows: dict[str, Flow] = {} self._vizs: dict[str, Viz] = {}
self.feed: Feed | None = None self.feed: Feed | None = None
@ -974,7 +974,7 @@ class ChartPlotWidget(pg.PlotWidget):
Return a range tuple for the bars present in view. Return a range tuple for the bars present in view.
''' '''
main_flow = self._flows[self.name] main_flow = self._vizs[self.name]
ifirst, l, lbar, rbar, r, ilast = main_flow.datums_range() ifirst, l, lbar, rbar, r, ilast = main_flow.datums_range()
return l, lbar, rbar, r return l, lbar, rbar, r
@ -1038,9 +1038,9 @@ class ChartPlotWidget(pg.PlotWidget):
Set the view box to the "default" startup view of the scene. Set the view box to the "default" startup view of the scene.
''' '''
flow = self._flows.get(self.name) flow = self._vizs.get(self.name)
if not flow: if not flow:
log.warning(f'`Flow` for {self.name} not loaded yet?') log.warning(f'`Viz` for {self.name} not loaded yet?')
return return
arr = flow.shm.array arr = flow.shm.array
@ -1220,7 +1220,7 @@ class ChartPlotWidget(pg.PlotWidget):
**graphics_kwargs, **graphics_kwargs,
) -> Flow: ) -> Viz:
''' '''
Draw a "curve" (line plot graphics) for the provided data in Draw a "curve" (line plot graphics) for the provided data in
the input shm array ``shm``. the input shm array ``shm``.
@ -1254,7 +1254,7 @@ class ChartPlotWidget(pg.PlotWidget):
**graphics_kwargs, **graphics_kwargs,
) )
flow = self._flows[data_key] = Flow( flow = self._vizs[data_key] = Viz(
data_key, data_key,
pi, pi,
shm, shm,
@ -1332,7 +1332,7 @@ class ChartPlotWidget(pg.PlotWidget):
array_key: Optional[str] = None, array_key: Optional[str] = None,
**draw_curve_kwargs, **draw_curve_kwargs,
) -> Flow: ) -> Viz:
''' '''
Draw OHLC datums to chart. Draw OHLC datums to chart.
@ -1358,7 +1358,7 @@ class ChartPlotWidget(pg.PlotWidget):
Update the named internal graphics from ``array``. Update the named internal graphics from ``array``.
''' '''
flow = self._flows[array_key or graphics_name] flow = self._vizs[array_key or graphics_name]
return flow.update_graphics( return flow.update_graphics(
array_key=array_key, array_key=array_key,
**kwargs, **kwargs,
@ -1426,15 +1426,15 @@ class ChartPlotWidget(pg.PlotWidget):
delayed=True, delayed=True,
) )
# TODO: here we should instead look up the ``Flow.shm.array`` # TODO: here we should instead look up the ``Viz.shm.array``
# and read directly from shm to avoid copying to memory first # and read directly from shm to avoid copying to memory first
# and then reading it again here. # and then reading it again here.
flow_key = name or self.name flow_key = name or self.name
flow = self._flows.get(flow_key) viz = self._vizs.get(flow_key)
if ( if (
flow is None viz is None
): ):
log.error(f"flow {flow_key} doesn't exist in chart {self.name} !?") log.error(f"viz {flow_key} doesn't exist in chart {self.name} !?")
key = res = 0, 0 key = res = 0, 0
else: else:
@ -1445,11 +1445,11 @@ class ChartPlotWidget(pg.PlotWidget):
rbar, rbar,
r, r,
last, last,
) = bars_range or flow.datums_range() ) = bars_range or viz.datums_range()
profiler(f'{self.name} got bars range') profiler(f'{self.name} got bars range')
key = round(lbar), round(rbar) key = round(lbar), round(rbar)
res = flow.maxmin(*key) res = viz.maxmin(*key)
if ( if (
res is None res is None
@ -1465,3 +1465,9 @@ class ChartPlotWidget(pg.PlotWidget):
profiler(f'yrange mxmn: {key} -> {res}') profiler(f'yrange mxmn: {key} -> {res}')
# print(f'{flow_key} yrange mxmn: {key} -> {res}') # print(f'{flow_key} yrange mxmn: {key} -> {res}')
return res return res
def get_viz(
self,
key: str,
) -> Viz:
return self._vizs[key]

View File

@ -274,8 +274,8 @@ class ContentsLabels:
) -> None: ) -> None:
for chart, name, label, update in self._labels: for chart, name, label, update in self._labels:
flow = chart._flows[name] viz = chart.get_viz(name)
array = flow.shm.array array = viz.shm.array
if not ( if not (
index >= 0 index >= 0
@ -482,25 +482,32 @@ class Cursor(pg.GraphicsObject):
def add_curve_cursor( def add_curve_cursor(
self, self,
plot: ChartPlotWidget, # noqa chart: ChartPlotWidget, # noqa
curve: 'PlotCurveItem', # noqa curve: 'PlotCurveItem', # noqa
) -> LineDot: ) -> LineDot:
# if this plot contains curves add line dot "cursors" to denote # if this chart contains curves add line dot "cursors" to denote
# the current sample under the mouse # the current sample under the mouse
main_flow = plot._flows[plot.name] main_viz = chart.get_viz(chart.name)
# read out last index # read out last index
i = main_flow.shm.array[-1]['index'] i = main_viz.shm.array[-1]['index']
cursor = LineDot( cursor = LineDot(
curve, curve,
index=i, index=i,
plot=plot plot=chart
) )
plot.addItem(cursor) chart.addItem(cursor)
self.graphics[plot].setdefault('cursors', []).append(cursor) self.graphics[chart].setdefault('cursors', []).append(cursor)
return cursor return cursor
def mouseAction(self, action, plot): # noqa def mouseAction(
self,
action: str,
plot: ChartPlotWidget,
) -> None: # noqa
log.debug(f"{(action, plot.name)}") log.debug(f"{(action, plot.name)}")
if action == 'Enter': if action == 'Enter':
self.active_plot = plot self.active_plot = plot

View File

@ -76,7 +76,7 @@ from .._profile import Profiler
log = get_logger(__name__) log = get_logger(__name__)
# TODO: delegate this to each `Flow.maxmin()` which includes # TODO: delegate this to each `Viz.maxmin()` which includes
# caching and further we should implement the following stream based # caching and further we should implement the following stream based
# approach, likely with ``numba``: # approach, likely with ``numba``:
# https://arxiv.org/abs/cs/0610046 # https://arxiv.org/abs/cs/0610046
@ -111,7 +111,7 @@ def chart_maxmin(
# TODO: we need to NOT call this to avoid a manual # TODO: we need to NOT call this to avoid a manual
# np.max/min trigger and especially on the vlm_chart # np.max/min trigger and especially on the vlm_chart
# flows which aren't shown.. like vlm? # vizs which aren't shown.. like vlm?
if vlm_chart: if vlm_chart:
out = vlm_chart.maxmin() out = vlm_chart.maxmin()
if out: if out:
@ -218,7 +218,7 @@ class DisplayState(Struct):
_, _, _, r = chart.bars_range() _, _, _, r = chart.bars_range()
liv = r >= shm.index liv = r >= shm.index
# update the "last datum" (aka extending the flow graphic with # update the "last datum" (aka extending the vizs graphic with
# new data) only if the number of unit steps is >= the number of # new data) only if the number of unit steps is >= the number of
# such unit steps per pixel (aka uppx). Iow, if the zoom level # such unit steps per pixel (aka uppx). Iow, if the zoom level
# is such that a datum(s) update to graphics wouldn't span # is such that a datum(s) update to graphics wouldn't span
@ -299,14 +299,14 @@ async def graphics_update_loop(
fqsn = symbol.fqsn fqsn = symbol.fqsn
# update last price sticky # update last price sticky
fast_pi = fast_chart._flows[fqsn].plot fast_pi = fast_chart._vizs[fqsn].plot
last_price_sticky = fast_pi.getAxis('right')._stickies[fqsn] last_price_sticky = fast_pi.getAxis('right')._stickies[fqsn]
last_price_sticky.update_from_data( last_price_sticky.update_from_data(
*ohlcv.array[-1][['index', 'close']] *ohlcv.array[-1][['index', 'close']]
) )
last_price_sticky.show() last_price_sticky.show()
slow_pi = hist_chart._flows[fqsn].plot slow_pi = hist_chart._vizs[fqsn].plot
hist_last_price_sticky = slow_pi.getAxis('right')._stickies[fqsn] hist_last_price_sticky = slow_pi.getAxis('right')._stickies[fqsn]
hist_last_price_sticky.update_from_data( hist_last_price_sticky.update_from_data(
*hist_ohlcv.array[-1][['index', 'close']] *hist_ohlcv.array[-1][['index', 'close']]
@ -381,7 +381,7 @@ async def graphics_update_loop(
}) })
if vlm_chart: if vlm_chart:
vlm_pi = vlm_chart._flows['volume'].plot vlm_pi = vlm_chart._vizs['volume'].plot
vlm_sticky = vlm_pi.getAxis('right')._stickies['volume'] vlm_sticky = vlm_pi.getAxis('right')._stickies['volume']
ds.vlm_chart = vlm_chart ds.vlm_chart = vlm_chart
ds.vlm_sticky = vlm_sticky ds.vlm_sticky = vlm_sticky
@ -439,8 +439,8 @@ async def graphics_update_loop(
and liv and liv
): ):
# hist_chart.increment_view(steps=i_diff) # hist_chart.increment_view(steps=i_diff)
flow = hist_chart._flows[fqsn] viz = hist_chart._vizs[fqsn]
flow.plot.vb._set_yrange( viz.plot.vb._set_yrange(
# yrange=hist_chart.maxmin(name=fqsn) # yrange=hist_chart.maxmin(name=fqsn)
) )
# hist_chart.view._set_yrange(yrange=hist_chart.maxmin()) # hist_chart.view._set_yrange(yrange=hist_chart.maxmin())
@ -516,7 +516,7 @@ def graphics_update_cycle(
flume = ds.flume flume = ds.flume
sym = flume.symbol sym = flume.symbol
fqsn = sym.fqsn fqsn = sym.fqsn
main_flow = chart._flows[fqsn] main_viz = chart._vizs[fqsn]
profiler = Profiler( profiler = Profiler(
msg=f'Graphics loop cycle for: `{chart.name}`', msg=f'Graphics loop cycle for: `{chart.name}`',
@ -560,7 +560,7 @@ def graphics_update_cycle(
): ):
# print(f'INCREMENTING {fqsn}') # print(f'INCREMENTING {fqsn}')
chart.increment_view(steps=i_diff) chart.increment_view(steps=i_diff)
main_flow.plot.vb._set_yrange( main_viz.plot.vb._set_yrange(
# yrange=(mn, mx), # yrange=(mn, mx),
) )
@ -627,7 +627,7 @@ def graphics_update_cycle(
# chart.name, # chart.name,
# do_append=do_append, # do_append=do_append,
) )
main_flow.draw_last(array_key=fqsn) main_viz.draw_last(array_key=fqsn)
hist_chart.update_graphics_from_flow( hist_chart.update_graphics_from_flow(
fqsn, fqsn,
@ -746,7 +746,7 @@ def graphics_update_cycle(
and not chart._static_yrange == 'axis' and not chart._static_yrange == 'axis'
): ):
# main_vb = chart.view # main_vb = chart.view
main_vb = chart._flows[fqsn].plot.vb main_vb = chart._vizs[fqsn].plot.vb
if ( if (
main_vb._ic is None main_vb._ic is None
or not main_vb._ic.is_set() or not main_vb._ic.is_set()
@ -777,26 +777,26 @@ def graphics_update_cycle(
is_1m=True, is_1m=True,
) )
if hist_liv: if hist_liv:
flow = hist_chart._flows[fqsn] viz = hist_chart._vizs[fqsn]
flow.plot.vb._set_yrange( viz.plot.vb._set_yrange(
# yrange=hist_chart.maxmin(name=fqsn), # yrange=hist_chart.maxmin(name=fqsn),
) )
# XXX: update this every draw cycle to make L1-always-in-view work. # XXX: update this every draw cycle to make L1-always-in-view work.
vars['last_mx'], vars['last_mn'] = mx, mn vars['last_mx'], vars['last_mn'] = mx, mn
# run synchronous update on all linked flows # run synchronous update on all linked viz
# TODO: should the "main" (aka source) flow be special? # TODO: should the "main" (aka source) viz be special?
for curve_name, flow in chart._flows.items(): for curve_name, viz in chart._vizs.items():
# update any overlayed fsp flows # update any overlayed fsp flows
if ( if (
# curve_name != chart.data_key # curve_name != chart.data_key
curve_name != fqsn curve_name != fqsn
and not flow.is_ohlc and not viz.is_ohlc
): ):
update_fsp_chart( update_fsp_chart(
chart, chart,
flow, viz,
curve_name, curve_name,
array_key=curve_name, array_key=curve_name,
) )
@ -810,7 +810,7 @@ def graphics_update_cycle(
# and not do_append # and not do_append
# and not do_rt_update # and not do_rt_update
): ):
flow.draw_last( viz.draw_last(
array_key=curve_name, array_key=curve_name,
only_last_uppx=True, only_last_uppx=True,
) )
@ -819,7 +819,7 @@ def graphics_update_cycle(
# TODO: can we unify this with the above loop? # TODO: can we unify this with the above loop?
if vlm_chart: if vlm_chart:
# print(f"DOING VLM {fqsn}") # print(f"DOING VLM {fqsn}")
vlm_flows = vlm_chart._flows vlm_vizs = vlm_chart._vizs
# always update y-label # always update y-label
ds.vlm_sticky.update_from_data( ds.vlm_sticky.update_from_data(
@ -864,21 +864,21 @@ def graphics_update_cycle(
vars['last_mx_vlm'] = mx_vlm_in_view vars['last_mx_vlm'] = mx_vlm_in_view
# update all downstream FSPs # update all downstream FSPs
for curve_name, flow in vlm_flows.items(): for curve_name, viz in vlm_vizs.items():
if ( if (
curve_name not in {'volume', fqsn} curve_name not in {'volume', fqsn}
and flow.render and viz.render
and ( and (
liv and do_rt_update liv and do_rt_update
or do_append or do_append
) )
# and not flow.is_ohlc # and not viz.is_ohlc
# and curve_name != fqsn # and curve_name != fqsn
): ):
update_fsp_chart( update_fsp_chart(
vlm_chart, vlm_chart,
flow, viz,
curve_name, curve_name,
array_key=curve_name, array_key=curve_name,
# do_append=uppx < update_uppx, # do_append=uppx < update_uppx,
@ -887,7 +887,7 @@ def graphics_update_cycle(
# is this even doing anything? # is this even doing anything?
# (pretty sure it's the real-time # (pretty sure it's the real-time
# resizing from last quote?) # resizing from last quote?)
fvb = flow.plot.vb fvb = viz.plot.vb
fvb._set_yrange( fvb._set_yrange(
name=curve_name, name=curve_name,
) )
@ -903,9 +903,9 @@ def graphics_update_cycle(
# range of that set. # range of that set.
): ):
# always update the last datum-element # always update the last datum-element
# graphic for all flows # graphic for all vizs
# print(f'drawing last {flow.name}') # print(f'drawing last {viz.name}')
flow.draw_last(array_key=curve_name) viz.draw_last(array_key=curve_name)
async def link_views_with_region( async def link_views_with_region(
@ -935,12 +935,12 @@ async def link_views_with_region(
hist_pi.addItem(region, ignoreBounds=True) hist_pi.addItem(region, ignoreBounds=True)
region.setOpacity(6/16) region.setOpacity(6/16)
flow = rt_chart._flows[flume.symbol.fqsn] viz = rt_chart._vizs[flume.symbol.fqsn]
assert flow assert viz
# XXX: no idea why this doesn't work but it's causing # XXX: no idea why this doesn't work but it's causing
# a weird placement of the region on the way-far-left.. # a weird placement of the region on the way-far-left..
# region.setClipItem(flow.graphics) # region.setClipItem(viz.graphics)
# poll for datums load and timestep detection # poll for datums load and timestep detection
for _ in range(100): for _ in range(100):
@ -1050,11 +1050,11 @@ def multi_maxmin(
) -> tuple[float, float]: ) -> tuple[float, float]:
''' '''
Flows "group" maxmin loop; assumes all named flows Viz "group" maxmin loop; assumes all named vizs
are in the same co-domain and thus can be sorted are in the same co-domain and thus can be sorted
as one set. as one set.
Iterates all the named flows and calls the chart Iterates all the named vizs and calls the chart
api to find their range values and return. api to find their range values and return.
TODO: really we should probably have a more built-in API TODO: really we should probably have a more built-in API
@ -1277,7 +1277,7 @@ async def display_symbol_data(
hist_pi.hideAxis('left') hist_pi.hideAxis('left')
hist_pi.hideAxis('bottom') hist_pi.hideAxis('bottom')
flow = hist_chart.draw_curve( viz = hist_chart.draw_curve(
fqsn, fqsn,
hist_ohlcv, hist_ohlcv,
flume, flume,
@ -1298,8 +1298,8 @@ async def display_symbol_data(
# specially store ref to shm for lookup in display loop # specially store ref to shm for lookup in display loop
# since only a placeholder of `None` is entered in # since only a placeholder of `None` is entered in
# ``.draw_curve()``. # ``.draw_curve()``.
flow = hist_chart._flows[fqsn] viz = hist_chart._vizs[fqsn]
assert flow.plot is hist_pi assert viz.plot is hist_pi
pis.setdefault(fqsn, [None, None])[1] = hist_pi pis.setdefault(fqsn, [None, None])[1] = hist_pi
rt_pi = rt_chart.overlay_plotitem( rt_pi = rt_chart.overlay_plotitem(
@ -1310,7 +1310,7 @@ async def display_symbol_data(
rt_pi.hideAxis('left') rt_pi.hideAxis('left')
rt_pi.hideAxis('bottom') rt_pi.hideAxis('bottom')
flow = rt_chart.draw_curve( viz = rt_chart.draw_curve(
fqsn, fqsn,
ohlcv, ohlcv,
flume, flume,
@ -1331,8 +1331,8 @@ async def display_symbol_data(
# specially store ref to shm for lookup in display loop # specially store ref to shm for lookup in display loop
# since only a placeholder of `None` is entered in # since only a placeholder of `None` is entered in
# ``.draw_curve()``. # ``.draw_curve()``.
flow = rt_chart._flows[fqsn] viz = rt_chart._vizs[fqsn]
assert flow.plot is rt_pi assert viz.plot is rt_pi
pis.setdefault(fqsn, [None, None])[0] = rt_pi pis.setdefault(fqsn, [None, None])[0] = rt_pi
rt_chart.setFocus() rt_chart.setFocus()

View File

@ -377,7 +377,7 @@ class SelectRect(QtWidgets.QGraphicsRectItem):
nbars = ixmx - ixmn + 1 nbars = ixmx - ixmn + 1
chart = self._chart chart = self._chart
data = chart._flows[chart.name].shm.array[ixmn:ixmx] data = chart.get_viz(chart.name).shm.array[ixmn:ixmx]
if len(data): if len(data):
std = data['close'].std() std = data['close'].std()

View File

@ -65,7 +65,7 @@ log = get_logger(__name__)
def render_baritems( def render_baritems(
flow: Flow, viz: Viz,
graphics: BarItems, graphics: BarItems,
read: tuple[ read: tuple[
int, int, np.ndarray, int, int, np.ndarray,
@ -89,7 +89,7 @@ def render_baritems(
bars = graphics bars = graphics
# if no source data renderer exists create one. # if no source data renderer exists create one.
self = flow self = viz
show_bars: bool = False show_bars: bool = False
r = self._src_r r = self._src_r
@ -98,28 +98,28 @@ def render_baritems(
# OHLC bars path renderer # OHLC bars path renderer
r = self._src_r = Renderer( r = self._src_r = Renderer(
flow=self, viz=self,
fmtr=OHLCBarsFmtr( fmtr=OHLCBarsFmtr(
shm=flow.shm, shm=viz.shm,
flow=flow, viz=viz,
_last_read=read, _last_read=read,
), ),
) )
ds_curve_r = Renderer( ds_curve_r = Renderer(
flow=self, viz=self,
fmtr=OHLCBarsAsCurveFmtr( fmtr=OHLCBarsAsCurveFmtr(
shm=flow.shm, shm=viz.shm,
flow=flow, viz=viz,
_last_read=read, _last_read=read,
), ),
) )
curve = FlattenedOHLC( curve = FlattenedOHLC(
name=f'{flow.name}_ds_ohlc', name=f'{viz.name}_ds_ohlc',
color=bars._color, color=bars._color,
) )
flow.ds_graphics = curve viz.ds_graphics = curve
curve.hide() curve.hide()
self.plot.addItem(curve) self.plot.addItem(curve)
@ -142,7 +142,7 @@ def render_baritems(
): ):
# print('FLIPPING TO BARS') # print('FLIPPING TO BARS')
should_line = False should_line = False
flow._in_ds = False viz._in_ds = False
elif ( elif (
not in_line not in_line
@ -150,7 +150,7 @@ def render_baritems(
): ):
# print('FLIPPING TO LINE') # print('FLIPPING TO LINE')
should_line = True should_line = True
flow._in_ds = True viz._in_ds = True
profiler(f'ds logic complete line={should_line}') profiler(f'ds logic complete line={should_line}')
@ -196,9 +196,9 @@ def render_baritems(
) )
class Flow(msgspec.Struct): # , frozen=True): class Viz(msgspec.Struct): # , frozen=True):
''' '''
(Financial Signal-)Flow compound type which wraps a real-time (Data) "Visualization" compound type which wraps a real-time
shm array stream with displayed graphics (curves, charts) shm array stream with displayed graphics (curves, charts)
for high level access and control as well as efficient incremental for high level access and control as well as efficient incremental
update. update.
@ -216,7 +216,7 @@ class Flow(msgspec.Struct): # , frozen=True):
# for tracking y-mn/mx for y-axis auto-ranging # for tracking y-mn/mx for y-axis auto-ranging
yrange: tuple[float, float] = None yrange: tuple[float, float] = None
# in some cases a flow may want to change its # in some cases a viz may want to change its
# graphical "type" or, "form" when downsampling, to # graphical "type" or, "form" when downsampling, to
# start this is only ever an interpolation line. # start this is only ever an interpolation line.
ds_graphics: Optional[Curve] = None ds_graphics: Optional[Curve] = None
@ -251,12 +251,6 @@ class Flow(msgspec.Struct): # , frozen=True):
def shm(self) -> ShmArray: def shm(self) -> ShmArray:
return self._shm return self._shm
# TODO: remove this and only allow setting through
# private ``._shm`` attr?
# @shm.setter
# def shm(self, shm: ShmArray) -> ShmArray:
# self._shm = shm
def maxmin( def maxmin(
self, self,
lbar: int, lbar: int,
@ -318,7 +312,7 @@ class Flow(msgspec.Struct): # , frozen=True):
def view_range(self) -> tuple[int, int]: def view_range(self) -> tuple[int, int]:
''' '''
Return the indexes in view for the associated Return the indexes in view for the associated
plot displaying this flow's data. plot displaying this viz's data.
''' '''
vr = self.plot.viewRect() vr = self.plot.viewRect()
@ -344,7 +338,7 @@ class Flow(msgspec.Struct): # , frozen=True):
# TODO: avoid this and have shm passed # TODO: avoid this and have shm passed
# in earlier. # in earlier.
if self.shm is None: if self.shm is None:
# haven't initialized the flow yet # haven't initialized the viz yet
return (0, l, 0, 0, r, 0) return (0, l, 0, 0, r, 0)
array = self.shm.array array = self.shm.array
@ -420,7 +414,7 @@ class Flow(msgspec.Struct): # , frozen=True):
''' '''
profiler = Profiler( profiler = Profiler(
msg=f'Flow.update_graphics() for {self.name}', msg=f'Viz.update_graphics() for {self.name}',
disabled=not pg_profile_enabled(), disabled=not pg_profile_enabled(),
ms_threshold=4, ms_threshold=4,
# ms_threshold=ms_slower_then, # ms_threshold=ms_slower_then,
@ -475,10 +469,10 @@ class Flow(msgspec.Struct): # , frozen=True):
if isinstance(graphics, StepCurve): if isinstance(graphics, StepCurve):
r = self._src_r = Renderer( r = self._src_r = Renderer(
flow=self, viz=self,
fmtr=StepCurveFmtr( fmtr=StepCurveFmtr(
shm=self.shm, shm=self.shm,
flow=self, viz=self,
_last_read=read, _last_read=read,
), ),
) )
@ -493,10 +487,10 @@ class Flow(msgspec.Struct): # , frozen=True):
if not r: if not r:
# just using for ``.diff()`` atm.. # just using for ``.diff()`` atm..
r = self._src_r = Renderer( r = self._src_r = Renderer(
flow=self, viz=self,
fmtr=IncrementalFormatter( fmtr=IncrementalFormatter(
shm=self.shm, shm=self.shm,
flow=self, viz=self,
_last_read=read, _last_read=read,
), ),
) )
@ -581,7 +575,7 @@ class Flow(msgspec.Struct): # , frozen=True):
path, data, reset = out path, data, reset = out
# if self.yrange: # if self.yrange:
# print(f'flow {self.name} yrange from m4: {self.yrange}') # print(f'viz {self.name} yrange from m4: {self.yrange}')
# XXX: SUPER UGGGHHH... without this we get stale cache # XXX: SUPER UGGGHHH... without this we get stale cache
# graphics that don't update until you downsampler again.. # graphics that don't update until you downsampler again..
@ -691,7 +685,7 @@ class Flow(msgspec.Struct): # , frozen=True):
class Renderer(msgspec.Struct): class Renderer(msgspec.Struct):
flow: Flow viz: Viz
fmtr: IncrementalFormatter fmtr: IncrementalFormatter
# output graphics rendering, the main object # output graphics rendering, the main object
@ -794,7 +788,7 @@ class Renderer(msgspec.Struct):
- blah blah blah (from notes) - blah blah blah (from notes)
''' '''
# TODO: can the renderer just call ``Flow.read()`` directly? # TODO: can the renderer just call ``Viz.read()`` directly?
# unpack latest source data read # unpack latest source data read
fmtr = self.fmtr fmtr = self.fmtr
@ -858,7 +852,7 @@ class Renderer(msgspec.Struct):
path is None path is None
or should_redraw or should_redraw
): ):
# print(f"{self.flow.name} -> REDRAWING BRUH") # print(f"{self.viz.name} -> REDRAWING BRUH")
if new_sample_rate and showing_src_data: if new_sample_rate and showing_src_data:
log.info(f'DEDOWN -> {array_key}') log.info(f'DEDOWN -> {array_key}')
self._in_ds = False self._in_ds = False
@ -870,8 +864,8 @@ class Renderer(msgspec.Struct):
y_1d, y_1d,
uppx, uppx,
) )
self.flow.yrange = ymn, ymx self.viz.yrange = ymn, ymx
# print(f'{self.flow.name} post ds: ymn, ymx: {ymn},{ymx}') # print(f'{self.viz.name} post ds: ymn, ymx: {ymn},{ymx}')
reset = True reset = True
profiler(f'FULL PATH downsample redraw={should_ds}') profiler(f'FULL PATH downsample redraw={should_ds}')
@ -942,7 +936,7 @@ class Renderer(msgspec.Struct):
profiler('generated append qpath') profiler('generated append qpath')
if use_fpath: if use_fpath:
# print(f'{self.flow.name}: FAST PATH') # print(f'{self.viz.name}: FAST PATH')
# an attempt at trying to make append-updates faster.. # an attempt at trying to make append-updates faster..
if fast_path is None: if fast_path is None:
fast_path = append_path fast_path = append_path

View File

@ -289,7 +289,7 @@ async def run_fsp_ui(
# first UI update, usually from shm pushed history # first UI update, usually from shm pushed history
update_fsp_chart( update_fsp_chart(
chart, chart,
chart._flows[array_key], chart.get_viz(array_key),
name, name,
array_key=array_key, array_key=array_key,
) )
@ -357,7 +357,7 @@ async def run_fsp_ui(
# last = time.time() # last = time.time()
# TODO: maybe this should be our ``Flow`` type since it maps # TODO: maybe this should be our ``Viz`` type since it maps
# one flume to the next? The machinery for task/actor mgmt should # one flume to the next? The machinery for task/actor mgmt should
# be part of the instantiation API? # be part of the instantiation API?
class FspAdmin: class FspAdmin:
@ -386,7 +386,7 @@ class FspAdmin:
# TODO: make this a `.src_flume` and add # TODO: make this a `.src_flume` and add
# a `dst_flume`? # a `dst_flume`?
# (=> but then wouldn't this be the most basic `Flow`?) # (=> but then wouldn't this be the most basic `Viz`?)
self.flume = flume self.flume = flume
def rr_next_portal(self) -> tractor.Portal: def rr_next_portal(self) -> tractor.Portal:
@ -694,7 +694,7 @@ async def open_vlm_displays(
) -> tuple[float, float]: ) -> tuple[float, float]:
''' '''
Flows "group" maxmin loop; assumes all named flows Viz "group" maxmin loop; assumes all named flows
are in the same co-domain and thus can be sorted are in the same co-domain and thus can be sorted
as one set. as one set.
@ -865,7 +865,7 @@ async def open_vlm_displays(
# specially store ref to shm for lookup in display loop # specially store ref to shm for lookup in display loop
# since only a placeholder of `None` is entered in # since only a placeholder of `None` is entered in
# ``.draw_curve()``. # ``.draw_curve()``.
# flow = chart._flows[name] # viz = chart._vizs[name]
assert flow.plot is pi assert flow.plot is pi
chart_curves( chart_curves(
@ -901,7 +901,7 @@ async def open_vlm_displays(
# liquidity events (well at least on low OHLC periods - 1s). # liquidity events (well at least on low OHLC periods - 1s).
vlm_curve.hide() vlm_curve.hide()
chart.removeItem(vlm_curve) chart.removeItem(vlm_curve)
vflow = chart._flows['volume'] vflow = chart._vizs['volume']
vflow.render = False vflow.render = False
# avoid range sorting on volume once disabled # avoid range sorting on volume once disabled

View File

@ -504,7 +504,7 @@ class ChartView(ViewBox):
# if ( # if (
# ev.delta() < 0 # ev.delta() < 0
# and vl >= len(chart._flows[chart.name].shm.array) + 666 # and vl >= len(chart._vizs[chart.name].shm.array) + 666
# ): # ):
# log.debug("Min zoom bruh...") # log.debug("Min zoom bruh...")
# return # return
@ -821,7 +821,7 @@ class ChartView(ViewBox):
# XXX: only compute the mxmn range # XXX: only compute the mxmn range
# if none is provided as input! # if none is provided as input!
if not yrange: if not yrange:
# flow = chart._flows[name] # flow = chart._vizs[name]
yrange = self._maxmin() yrange = self._maxmin()
if yrange is None: if yrange is None:
@ -912,7 +912,7 @@ class ChartView(ViewBox):
graphics items which are our children. graphics items which are our children.
''' '''
graphics = [f.graphics for f in self._chart._flows.values()] graphics = [f.graphics for f in self._chart._vizs.values()]
if not graphics: if not graphics:
return 0 return 0
@ -948,7 +948,7 @@ class ChartView(ViewBox):
plots |= linked.subplots plots |= linked.subplots
for chart_name, chart in plots.items(): for chart_name, chart in plots.items():
for name, flow in chart._flows.items(): for name, flow in chart._vizs.items():
if ( if (
not flow.render not flow.render

View File

@ -42,7 +42,7 @@ from ._compression import (
if TYPE_CHECKING: if TYPE_CHECKING:
from ._flows import ( from ._flows import (
Renderer, Renderer,
Flow, Viz,
) )
from .._profile import Profiler from .._profile import Profiler
@ -72,7 +72,7 @@ class IncrementalFormatter(msgspec.Struct):
''' '''
shm: ShmArray shm: ShmArray
flow: Flow viz: Viz
# last read from shm (usually due to an update call) # last read from shm (usually due to an update call)
_last_read: tuple[ _last_read: tuple[
@ -89,7 +89,7 @@ class IncrementalFormatter(msgspec.Struct):
def __repr__(self) -> str: def __repr__(self) -> str:
msg = ( msg = (
f'{type(self)}: ->\n\n' f'{type(self)}: ->\n\n'
f'fqsn={self.flow.name}\n' f'fqsn={self.viz.name}\n'
f'shm_name={self.shm.token["shm_name"]}\n\n' f'shm_name={self.shm.token["shm_name"]}\n\n'
f'last_vr={self._last_vr}\n' f'last_vr={self._last_vr}\n'
@ -129,7 +129,7 @@ class IncrementalFormatter(msgspec.Struct):
last_in_view, last_in_view,
) = self.last_read ) = self.last_read
# TODO: can the renderer just call ``Flow.read()`` directly? # TODO: can the renderer just call ``Viz.read()`` directly?
# unpack latest source data read # unpack latest source data read
( (
xfirst, xfirst,
@ -336,7 +336,7 @@ class IncrementalFormatter(msgspec.Struct):
if slice_to_inview: if slice_to_inview:
view_changed = self._track_inview_range(view_range) view_changed = self._track_inview_range(view_range)
array = in_view array = in_view
profiler(f'{self.flow.name} view range slice {view_range}') profiler(f'{self.viz.name} view range slice {view_range}')
hist = array[:slice_to_head] hist = array[:slice_to_head]
@ -369,9 +369,9 @@ class IncrementalFormatter(msgspec.Struct):
# # assert (len(appended) - 1) == append_len # # assert (len(appended) - 1) == append_len
# # assert len(appended) == append_len # # assert len(appended) == append_len
# print( # print(
# f'{self.flow.name} APPEND LEN: {append_len}\n' # f'{self.viz.name} APPEND LEN: {append_len}\n'
# f'{self.flow.name} APPENDED: {appended}\n' # f'{self.viz.name} APPENDED: {appended}\n'
# f'{self.flow.name} app_tres: {app_tres}\n' # f'{self.viz.name} app_tres: {app_tres}\n'
# ) # )
# update the last "in view data range" # update the last "in view data range"

View File

@ -1,3 +0,0 @@
"""
Super hawt Qt UI components
"""

View File

@ -1,67 +0,0 @@
import sys
from PySide2.QtCharts import QtCharts
from PySide2.QtWidgets import QApplication, QMainWindow
from PySide2.QtCore import Qt, QPointF
from PySide2 import QtGui
import qdarkstyle
data = ((1, 7380, 7520, 7380, 7510, 7324),
(2, 7520, 7580, 7410, 7440, 7372),
(3, 7440, 7650, 7310, 7520, 7434),
(4, 7450, 7640, 7450, 7550, 7480),
(5, 7510, 7590, 7460, 7490, 7502),
(6, 7500, 7590, 7480, 7560, 7512),
(7, 7560, 7830, 7540, 7800, 7584))
app = QApplication([])
# set dark stylesheet
# import pdb; pdb.set_trace()
app.setStyleSheet(qdarkstyle.load_stylesheet_pyside())
series = QtCharts.QCandlestickSeries()
series.setDecreasingColor(Qt.darkRed)
series.setIncreasingColor(Qt.darkGreen)
ma5 = QtCharts.QLineSeries() # 5-days average data line
tm = [] # stores str type data
# in a loop, series and ma5 append corresponding data
for num, o, h, l, c, m in data:
candle = QtCharts.QCandlestickSet(o, h, l, c)
series.append(candle)
ma5.append(QPointF(num, m))
tm.append(str(num))
pen = candle.pen()
# import pdb; pdb.set_trace()
chart = QtCharts.QChart()
# import pdb; pdb.set_trace()
series.setBodyOutlineVisible(False)
series.setCapsVisible(False)
# brush = QtGui.QBrush()
# brush.setColor(Qt.green)
# series.setBrush(brush)
chart.addSeries(series) # candle
chart.addSeries(ma5) # ma5 line
chart.setAnimationOptions(QtCharts.QChart.SeriesAnimations)
chart.createDefaultAxes()
chart.legend().hide()
chart.axisX(series).setCategories(tm)
chart.axisX(ma5).setVisible(False)
view = QtCharts.QChartView(chart)
view.chart().setTheme(QtCharts.QChart.ChartTheme.ChartThemeDark)
view.setRubberBand(QtCharts.QChartView.HorizontalRubberBand)
# chartview.chart().setTheme(QtCharts.QChart.ChartTheme.ChartThemeBlueCerulean)
ui = QMainWindow()
# ui.setGeometry(50, 50, 500, 300)
ui.setCentralWidget(view)
ui.show()
sys.exit(app.exec_())