Fix static yrange and last bar double draw issues
parent
fc23b2180d
commit
c7d5ea6e15
|
@ -19,6 +19,7 @@ from ._style import (
|
||||||
_xaxis_at, _min_points_to_show, hcolor,
|
_xaxis_at, _min_points_to_show, hcolor,
|
||||||
CHART_MARGINS,
|
CHART_MARGINS,
|
||||||
_bars_from_right_in_follow_mode,
|
_bars_from_right_in_follow_mode,
|
||||||
|
_bars_to_left_in_follow_mode,
|
||||||
)
|
)
|
||||||
from ..data._source import Symbol
|
from ..data._source import Symbol
|
||||||
from .. import brokers
|
from .. import brokers
|
||||||
|
@ -206,6 +207,7 @@ class LinkedSplitCharts(QtGui.QWidget):
|
||||||
xaxis: DynamicDateAxis = None,
|
xaxis: DynamicDateAxis = None,
|
||||||
ohlc: bool = False,
|
ohlc: bool = False,
|
||||||
_is_main: bool = False,
|
_is_main: bool = False,
|
||||||
|
**cpw_kwargs,
|
||||||
) -> 'ChartPlotWidget':
|
) -> 'ChartPlotWidget':
|
||||||
"""Add (sub)plots to chart widget by name.
|
"""Add (sub)plots to chart widget by name.
|
||||||
|
|
||||||
|
@ -226,6 +228,7 @@ class LinkedSplitCharts(QtGui.QWidget):
|
||||||
parent=self.splitter,
|
parent=self.splitter,
|
||||||
axisItems={'bottom': xaxis, 'right': PriceAxis()},
|
axisItems={'bottom': xaxis, 'right': PriceAxis()},
|
||||||
viewBox=cv,
|
viewBox=cv,
|
||||||
|
**cpw_kwargs,
|
||||||
)
|
)
|
||||||
# this name will be used to register the primary
|
# this name will be used to register the primary
|
||||||
# graphics curve managed by the subchart
|
# graphics curve managed by the subchart
|
||||||
|
@ -283,7 +286,7 @@ class ChartPlotWidget(pg.PlotWidget):
|
||||||
self,
|
self,
|
||||||
# the data view we generate graphics from
|
# the data view we generate graphics from
|
||||||
array: np.ndarray,
|
array: np.ndarray,
|
||||||
yrange: Optional[Tuple[float, float]] = None,
|
static_yrange: Optional[Tuple[float, float]] = None,
|
||||||
**kwargs,
|
**kwargs,
|
||||||
):
|
):
|
||||||
"""Configure chart display settings.
|
"""Configure chart display settings.
|
||||||
|
@ -299,9 +302,8 @@ class ChartPlotWidget(pg.PlotWidget):
|
||||||
self._overlays = {} # registry of overlay curves
|
self._overlays = {} # registry of overlay curves
|
||||||
self._labels = {} # registry of underlying graphics
|
self._labels = {} # registry of underlying graphics
|
||||||
self._ysticks = {} # registry of underlying graphics
|
self._ysticks = {} # registry of underlying graphics
|
||||||
self._yrange = yrange
|
|
||||||
self._vb = self.plotItem.vb
|
self._vb = self.plotItem.vb
|
||||||
self._static_yrange = None
|
self._static_yrange = static_yrange # for "known y-range style"
|
||||||
|
|
||||||
# show only right side axes
|
# show only right side axes
|
||||||
self.hideAxis('left')
|
self.hideAxis('left')
|
||||||
|
@ -310,20 +312,21 @@ class ChartPlotWidget(pg.PlotWidget):
|
||||||
# show background grid
|
# show background grid
|
||||||
self.showGrid(x=True, y=True, alpha=0.4)
|
self.showGrid(x=True, y=True, alpha=0.4)
|
||||||
|
|
||||||
self.plotItem.vb.setXRange(0, 0)
|
# don't need right?
|
||||||
|
# self._vb.setXRange(0, 0)
|
||||||
|
|
||||||
# use cross-hair for cursor
|
# use cross-hair for cursor?
|
||||||
self.setCursor(QtCore.Qt.CrossCursor)
|
# self.setCursor(QtCore.Qt.CrossCursor)
|
||||||
|
|
||||||
# Assign callback for rescaling y-axis automatically
|
# Assign callback for rescaling y-axis automatically
|
||||||
# based on data contents and ``ViewBox`` state.
|
# based on data contents and ``ViewBox`` state.
|
||||||
self.sigXRangeChanged.connect(self._set_yrange)
|
self.sigXRangeChanged.connect(self._set_yrange)
|
||||||
|
|
||||||
vb = self._vb
|
|
||||||
# for mouse wheel which doesn't seem to emit XRangeChanged
|
# for mouse wheel which doesn't seem to emit XRangeChanged
|
||||||
vb.sigRangeChangedManually.connect(self._set_yrange)
|
self._vb.sigRangeChangedManually.connect(self._set_yrange)
|
||||||
|
|
||||||
# for when the splitter(s) are resized
|
# for when the splitter(s) are resized
|
||||||
vb.sigResized.connect(self._set_yrange)
|
self._vb.sigResized.connect(self._set_yrange)
|
||||||
|
|
||||||
def _update_contents_label(self, index: int) -> None:
|
def _update_contents_label(self, index: int) -> None:
|
||||||
if index >= 0 and index < len(self._array):
|
if index >= 0 and index < len(self._array):
|
||||||
|
@ -369,6 +372,7 @@ class ChartPlotWidget(pg.PlotWidget):
|
||||||
# adds all bar/candle graphics objects for each data point in
|
# adds all bar/candle graphics objects for each data point in
|
||||||
# the np array buffer to be drawn on next render cycle
|
# the np array buffer to be drawn on next render cycle
|
||||||
self.addItem(graphics)
|
self.addItem(graphics)
|
||||||
|
|
||||||
# draw after to allow self.scene() to work...
|
# draw after to allow self.scene() to work...
|
||||||
graphics.draw_from_data(data)
|
graphics.draw_from_data(data)
|
||||||
|
|
||||||
|
@ -392,7 +396,7 @@ class ChartPlotWidget(pg.PlotWidget):
|
||||||
)
|
)
|
||||||
|
|
||||||
self._labels[name] = (label, update)
|
self._labels[name] = (label, update)
|
||||||
self._update_contents_label(index=-1)
|
self._update_contents_label(len(data) - 1)
|
||||||
label.show()
|
label.show()
|
||||||
|
|
||||||
# set xrange limits
|
# set xrange limits
|
||||||
|
@ -400,7 +404,7 @@ class ChartPlotWidget(pg.PlotWidget):
|
||||||
|
|
||||||
# show last 50 points on startup
|
# show last 50 points on startup
|
||||||
self.plotItem.vb.setXRange(
|
self.plotItem.vb.setXRange(
|
||||||
xlast - 50,
|
xlast - _bars_to_left_in_follow_mode,
|
||||||
xlast + _bars_from_right_in_follow_mode
|
xlast + _bars_from_right_in_follow_mode
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -437,7 +441,7 @@ class ChartPlotWidget(pg.PlotWidget):
|
||||||
# XXX: How to stack labels vertically?
|
# XXX: How to stack labels vertically?
|
||||||
label = pg.LabelItem(
|
label = pg.LabelItem(
|
||||||
justify='left',
|
justify='left',
|
||||||
size='4pt',
|
size='6px',
|
||||||
)
|
)
|
||||||
label.setParentItem(self._vb)
|
label.setParentItem(self._vb)
|
||||||
# label.setParentItem(self.getPlotItem())
|
# label.setParentItem(self.getPlotItem())
|
||||||
|
@ -455,14 +459,14 @@ class ChartPlotWidget(pg.PlotWidget):
|
||||||
label.setText(f"{name} -> {data}")
|
label.setText(f"{name} -> {data}")
|
||||||
|
|
||||||
self._labels[name] = (label, update)
|
self._labels[name] = (label, update)
|
||||||
self._update_contents_label(index=-1)
|
self._update_contents_label(len(data) - 1)
|
||||||
|
|
||||||
# set a "startup view"
|
# set a "startup view"
|
||||||
xlast = len(data) - 1
|
xlast = len(data) - 1
|
||||||
|
|
||||||
# show last 50 points on startup
|
# configure "follow mode" view on startup
|
||||||
self.plotItem.vb.setXRange(
|
self.plotItem.vb.setXRange(
|
||||||
xlast - 50,
|
xlast - _bars_to_left_in_follow_mode,
|
||||||
xlast + _bars_from_right_in_follow_mode
|
xlast + _bars_from_right_in_follow_mode
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -514,46 +518,47 @@ class ChartPlotWidget(pg.PlotWidget):
|
||||||
that data always fits nicely inside the current view of the
|
that data always fits nicely inside the current view of the
|
||||||
data set.
|
data set.
|
||||||
"""
|
"""
|
||||||
l, lbar, rbar, r = self.bars_range()
|
|
||||||
|
|
||||||
# figure out x-range in view such that user can scroll "off" the data
|
|
||||||
# set up to the point where ``_min_points_to_show`` are left.
|
|
||||||
# if l < lbar or r > rbar:
|
|
||||||
view_len = r - l
|
|
||||||
# TODO: logic to check if end of bars in view
|
|
||||||
extra = view_len - _min_points_to_show
|
|
||||||
begin = 0 - extra
|
|
||||||
end = len(self._array) - 1 + extra
|
|
||||||
|
|
||||||
# XXX: test code for only rendering lines for the bars in view.
|
|
||||||
# This turns out to be very very poor perf when scaling out to
|
|
||||||
# many bars (think > 1k) on screen.
|
|
||||||
# name = self.name
|
|
||||||
# bars = self._graphics[self.name]
|
|
||||||
# bars.draw_lines(
|
|
||||||
# istart=max(lbar, l), iend=min(rbar, r), just_history=True)
|
|
||||||
|
|
||||||
# bars_len = rbar - lbar
|
|
||||||
# log.trace(
|
|
||||||
# f"\nl: {l}, lbar: {lbar}, rbar: {rbar}, r: {r}\n"
|
|
||||||
# f"view_len: {view_len}, bars_len: {bars_len}\n"
|
|
||||||
# f"begin: {begin}, end: {end}, extra: {extra}"
|
|
||||||
# )
|
|
||||||
self._set_xlimits(begin, end)
|
|
||||||
|
|
||||||
# yrange
|
# yrange
|
||||||
if self._static_yrange is not None:
|
# if self._static_yrange is not None:
|
||||||
yrange = self._static_yrange
|
# yrange = self._static_yrange
|
||||||
|
|
||||||
if yrange is not None:
|
if self._static_yrange is not None:
|
||||||
ylow, yhigh = yrange
|
ylow, yhigh = self._static_yrange
|
||||||
self._static_yrange = yrange
|
|
||||||
else:
|
else: # determine max, min y values in viewable x-range
|
||||||
|
|
||||||
|
l, lbar, rbar, r = self.bars_range()
|
||||||
|
|
||||||
|
# figure out x-range in view such that user can scroll "off"
|
||||||
|
# the data set up to the point where ``_min_points_to_show``
|
||||||
|
# are left.
|
||||||
|
view_len = r - l
|
||||||
|
# TODO: logic to check if end of bars in view
|
||||||
|
extra = view_len - _min_points_to_show
|
||||||
|
begin = 0 - extra
|
||||||
|
end = len(self._array) - 1 + extra
|
||||||
|
|
||||||
|
# XXX: test code for only rendering lines for the bars in view.
|
||||||
|
# This turns out to be very very poor perf when scaling out to
|
||||||
|
# many bars (think > 1k) on screen.
|
||||||
|
# name = self.name
|
||||||
|
# bars = self._graphics[self.name]
|
||||||
|
# bars.draw_lines(
|
||||||
|
# istart=max(lbar, l), iend=min(rbar, r), just_history=True)
|
||||||
|
|
||||||
|
# bars_len = rbar - lbar
|
||||||
|
# log.trace(
|
||||||
|
# f"\nl: {l}, lbar: {lbar}, rbar: {rbar}, r: {r}\n"
|
||||||
|
# f"view_len: {view_len}, bars_len: {bars_len}\n"
|
||||||
|
# f"begin: {begin}, end: {end}, extra: {extra}"
|
||||||
|
# )
|
||||||
|
self._set_xlimits(begin, end)
|
||||||
|
|
||||||
# TODO: this should be some kind of numpy view api
|
# TODO: this should be some kind of numpy view api
|
||||||
bars = self._array[lbar:rbar]
|
bars = self._array[lbar:rbar]
|
||||||
if not len(bars):
|
if not len(bars):
|
||||||
# likely no data loaded yet
|
# likely no data loaded yet or extreme scrolling?
|
||||||
log.error(f"WTF bars_range = {lbar}:{rbar}")
|
log.error(f"WTF bars_range = {lbar}:{rbar}")
|
||||||
return
|
return
|
||||||
|
|
||||||
|
@ -562,19 +567,18 @@ class ChartPlotWidget(pg.PlotWidget):
|
||||||
try:
|
try:
|
||||||
ylow = np.nanmin(bars['low'])
|
ylow = np.nanmin(bars['low'])
|
||||||
yhigh = np.nanmax(bars['high'])
|
yhigh = np.nanmax(bars['high'])
|
||||||
# std = np.std(bars['close'])
|
|
||||||
except (IndexError, ValueError):
|
except (IndexError, ValueError):
|
||||||
# must be non-ohlc array?
|
# must be non-ohlc array?
|
||||||
ylow = np.nanmin(bars)
|
ylow = np.nanmin(bars)
|
||||||
yhigh = np.nanmax(bars)
|
yhigh = np.nanmax(bars)
|
||||||
# std = np.std(bars)
|
|
||||||
|
|
||||||
# view margins: stay within 10% of the "true range"
|
# view margins: stay within a % of the "true range"
|
||||||
diff = yhigh - ylow
|
diff = yhigh - ylow
|
||||||
ylow = ylow - (diff * 0.04)
|
ylow = ylow - (diff * 0.08)
|
||||||
yhigh = yhigh + (diff * 0.01)
|
yhigh = yhigh + (diff * 0.01)
|
||||||
|
|
||||||
# compute contents label "height" in view terms
|
# compute contents label "height" in view terms
|
||||||
|
# to avoid having data "contents" overlap with them
|
||||||
if self._labels:
|
if self._labels:
|
||||||
label = self._labels[self.name][0]
|
label = self._labels[self.name][0]
|
||||||
rect = label.itemRect()
|
rect = label.itemRect()
|
||||||
|
@ -590,13 +594,14 @@ class ChartPlotWidget(pg.PlotWidget):
|
||||||
else:
|
else:
|
||||||
label_h = 0
|
label_h = 0
|
||||||
|
|
||||||
chart = self
|
if label_h > yhigh - ylow:
|
||||||
chart.setLimits(
|
label_h = 0
|
||||||
|
|
||||||
|
self.setLimits(
|
||||||
yMin=ylow,
|
yMin=ylow,
|
||||||
yMax=yhigh + label_h,
|
yMax=yhigh + label_h,
|
||||||
# minYRange=std
|
|
||||||
)
|
)
|
||||||
chart.setYRange(ylow, yhigh + label_h)
|
self.setYRange(ylow, yhigh + label_h)
|
||||||
|
|
||||||
def enterEvent(self, ev): # noqa
|
def enterEvent(self, ev): # noqa
|
||||||
# pg.PlotWidget.enterEvent(self, ev)
|
# pg.PlotWidget.enterEvent(self, ev)
|
||||||
|
@ -809,20 +814,23 @@ async def chart_from_fsp(
|
||||||
|
|
||||||
chart = linked_charts.add_plot(
|
chart = linked_charts.add_plot(
|
||||||
name=func_name,
|
name=func_name,
|
||||||
|
|
||||||
# TODO: enforce type checking here?
|
|
||||||
array=shm.array,
|
array=shm.array,
|
||||||
|
|
||||||
|
# settings passed down to ``ChartPlotWidget``
|
||||||
|
static_yrange=(0, 100),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# display contents labels asap
|
||||||
|
chart._update_contents_label(len(shm.array) - 1)
|
||||||
|
|
||||||
array = shm.array[func_name]
|
array = shm.array[func_name]
|
||||||
value = array[-1]
|
value = array[-1]
|
||||||
last_val_sticky = chart._ysticks[chart.name]
|
last_val_sticky = chart._ysticks[chart.name]
|
||||||
last_val_sticky.update_from_data(-1, value)
|
last_val_sticky.update_from_data(-1, value)
|
||||||
chart.update_from_array(chart.name, array)
|
chart.update_from_array(chart.name, array)
|
||||||
|
|
||||||
chart._set_yrange(yrange=(0, 100))
|
|
||||||
|
|
||||||
chart._shm = shm
|
chart._shm = shm
|
||||||
|
chart._set_yrange()
|
||||||
|
|
||||||
# update chart graphics
|
# update chart graphics
|
||||||
async for value in stream:
|
async for value in stream:
|
||||||
|
@ -830,7 +838,6 @@ async def chart_from_fsp(
|
||||||
value = array[-1]
|
value = array[-1]
|
||||||
last_val_sticky.update_from_data(-1, value)
|
last_val_sticky.update_from_data(-1, value)
|
||||||
chart.update_from_array(chart.name, array)
|
chart.update_from_array(chart.name, array)
|
||||||
# chart._set_yrange()
|
|
||||||
|
|
||||||
|
|
||||||
async def check_for_new_bars(feed, ohlcv, linked_charts):
|
async def check_for_new_bars(feed, ohlcv, linked_charts):
|
||||||
|
|
|
@ -285,7 +285,10 @@ class BarItems(pg.GraphicsObject):
|
||||||
index = len(lines)
|
index = len(lines)
|
||||||
self.lines[:index] = lines
|
self.lines[:index] = lines
|
||||||
self.index = index
|
self.index = index
|
||||||
self.draw_lines(just_history=True, iend=self.index)
|
|
||||||
|
# up to last to avoid double draw of last bar
|
||||||
|
self.draw_lines(just_history=True, iend=self.index - 1)
|
||||||
|
self.draw_lines(iend=self.index)
|
||||||
|
|
||||||
# @timeit
|
# @timeit
|
||||||
def draw_lines(
|
def draw_lines(
|
||||||
|
|
|
@ -17,10 +17,10 @@ _i3_rgba = QtGui.QColor.fromRgbF(*[0.14]*3 + [1])
|
||||||
_xaxis_at = 'bottom'
|
_xaxis_at = 'bottom'
|
||||||
|
|
||||||
# charting config
|
# charting config
|
||||||
|
CHART_MARGINS = (0, 0, 2, 2)
|
||||||
_min_points_to_show = 3
|
_min_points_to_show = 3
|
||||||
_bars_from_right_in_follow_mode = 5
|
_bars_from_right_in_follow_mode = 5
|
||||||
CHART_MARGINS = (0, 0, 2, 2)
|
_bars_to_left_in_follow_mode = 100
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
_tina_mode = False
|
_tina_mode = False
|
||||||
|
|
Loading…
Reference in New Issue