Fix (really sidestep) flat bar rendering issue(s)
It seems a plethora of problems (including drawing performance) are due to trying to hack around the strange rendering bug in Qt with `QLineF` with y1 == y2. There was all sorts of weirdness that would show up with trying (a hack) to just set all 4 points to the same value including strange infinite diagonal ghost lines randomly on charts. Instead, just place hold these flat bar's 'body' line with a `None` and filter the null values out before calling `QPainter.drawLines()`. This results in simply no body lines drawn for these datums. We can probably `numba` the filtering too if it turns out to be a bottleneck.bar_select
parent
7be624de39
commit
8c25892521
|
@ -13,10 +13,8 @@ from PyQt5.QtCore import QLineF
|
||||||
from ._style import _xaxis_at, hcolor, _font
|
from ._style import _xaxis_at, hcolor, _font
|
||||||
from ._axes import YAxisLabel, XAxisLabel
|
from ._axes import YAxisLabel, XAxisLabel
|
||||||
|
|
||||||
# TODO:
|
|
||||||
# - checkout pyqtgraph.PlotCurveItem.setCompositionMode
|
|
||||||
|
|
||||||
_mouse_rate_limit = 60 # calc current screen refresh rate?
|
_mouse_rate_limit = 40 # calc current screen refresh rate?
|
||||||
_debounce_delay = 1/2e3
|
_debounce_delay = 1/2e3
|
||||||
_ch_label_opac = 1
|
_ch_label_opac = 1
|
||||||
|
|
||||||
|
@ -48,6 +46,7 @@ class LineDot(pg.CurvePoint):
|
||||||
dot = self.dot = QtGui.QGraphicsEllipseItem(
|
dot = self.dot = QtGui.QGraphicsEllipseItem(
|
||||||
QtCore.QRectF(-size/2, -size/2, size, size)
|
QtCore.QRectF(-size/2, -size/2, size, size)
|
||||||
)
|
)
|
||||||
|
# if we needed transformable dot?
|
||||||
# dot.translate(-size*0.5, -size*0.5)
|
# dot.translate(-size*0.5, -size*0.5)
|
||||||
dot.setPen(pen)
|
dot.setPen(pen)
|
||||||
dot.setBrush(brush)
|
dot.setBrush(brush)
|
||||||
|
@ -56,9 +55,6 @@ class LineDot(pg.CurvePoint):
|
||||||
# keep a static size
|
# keep a static size
|
||||||
self.setFlag(self.ItemIgnoresTransformations)
|
self.setFlag(self.ItemIgnoresTransformations)
|
||||||
|
|
||||||
def paint(self, p, opt, widget):
|
|
||||||
p.drawPicture(0, 0, self._pic)
|
|
||||||
|
|
||||||
|
|
||||||
class CrossHair(pg.GraphicsObject):
|
class CrossHair(pg.GraphicsObject):
|
||||||
|
|
||||||
|
@ -234,12 +230,14 @@ def _mk_lines_array(data: List, size: int) -> np.ndarray:
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# TODO: `numba` this?
|
||||||
def bars_from_ohlc(
|
def bars_from_ohlc(
|
||||||
data: np.ndarray,
|
data: np.ndarray,
|
||||||
w: float,
|
w: float,
|
||||||
start: int = 0,
|
start: int = 0,
|
||||||
) -> np.ndarray:
|
) -> np.ndarray:
|
||||||
"""Generate an array of lines objects from input ohlc data.
|
"""Generate an array of lines objects from input ohlc data.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
lines = _mk_lines_array(data, data.shape[0])
|
lines = _mk_lines_array(data, data.shape[0])
|
||||||
|
|
||||||
|
@ -247,25 +245,22 @@ def bars_from_ohlc(
|
||||||
open, high, low, close, index = q[
|
open, high, low, close, index = q[
|
||||||
['open', 'high', 'low', 'close', 'index']]
|
['open', 'high', 'low', 'close', 'index']]
|
||||||
|
|
||||||
# place the x-coord start as "middle" of the drawing range such
|
|
||||||
# that the open arm line-graphic is at the left-most-side of
|
|
||||||
# the indexe's range according to the view mapping.
|
|
||||||
index_start = index
|
|
||||||
|
|
||||||
# high - low line
|
# high - low line
|
||||||
if low != high:
|
if low != high:
|
||||||
# hl = QLineF(index, low, index, high)
|
hl = QLineF(index, low, index, high)
|
||||||
hl = QLineF(index_start, low, index_start, high)
|
|
||||||
else:
|
else:
|
||||||
# XXX: if we don't do it renders a weird rectangle?
|
# XXX: if we don't do it renders a weird rectangle?
|
||||||
# see below too for handling this later...
|
# see below too for handling this later...
|
||||||
hl = QLineF(low, low, low, low)
|
hl = None
|
||||||
hl._flat = True
|
|
||||||
|
# NOTE: place the x-coord start as "middle" of the drawing range such
|
||||||
|
# that the open arm line-graphic is at the left-most-side of
|
||||||
|
# the indexe's range according to the view mapping.
|
||||||
|
|
||||||
# open line
|
# open line
|
||||||
o = QLineF(index_start - w, open, index_start, open)
|
o = QLineF(index - w, open, index, open)
|
||||||
# close line
|
# close line
|
||||||
c = QLineF(index_start, close, index_start + w, close)
|
c = QLineF(index, close, index + w, close)
|
||||||
|
|
||||||
# indexing here is as per the below comments
|
# indexing here is as per the below comments
|
||||||
lines[i] = (hl, o, c)
|
lines[i] = (hl, o, c)
|
||||||
|
@ -274,6 +269,7 @@ def bars_from_ohlc(
|
||||||
# array and avoiding the call to `np.ravel()` below?
|
# array and avoiding the call to `np.ravel()` below?
|
||||||
# lines[3*i:3*i+3] = (hl, o, c)
|
# lines[3*i:3*i+3] = (hl, o, c)
|
||||||
|
|
||||||
|
# XXX: legacy code from candles custom graphics:
|
||||||
# if not _tina_mode:
|
# if not _tina_mode:
|
||||||
# else _tina_mode:
|
# else _tina_mode:
|
||||||
# self.lines = lines = np.concatenate(
|
# self.lines = lines = np.concatenate(
|
||||||
|
@ -379,7 +375,11 @@ class BarItems(pg.GraphicsObject):
|
||||||
|
|
||||||
# use 2d array of lines objects, see conlusion on speed:
|
# use 2d array of lines objects, see conlusion on speed:
|
||||||
# https://stackoverflow.com/a/60089929
|
# https://stackoverflow.com/a/60089929
|
||||||
to_draw = np.ravel(self.lines[istart:iend])
|
flat = np.ravel(self.lines[istart:iend])
|
||||||
|
|
||||||
|
# TODO: do this with numba for speed gain:
|
||||||
|
# https://stackoverflow.com/questions/58422690/filtering-a-numpy-array-what-is-the-best-approach
|
||||||
|
to_draw = flat[np.where(flat != None)] # noqa
|
||||||
|
|
||||||
# pre-computing a QPicture object allows paint() to run much
|
# pre-computing a QPicture object allows paint() to run much
|
||||||
# more quickly, rather than re-drawing the shapes every time.
|
# more quickly, rather than re-drawing the shapes every time.
|
||||||
|
@ -435,35 +435,27 @@ class BarItems(pg.GraphicsObject):
|
||||||
return
|
return
|
||||||
|
|
||||||
# current bar update
|
# current bar update
|
||||||
i, open, last, = array[-1][['index', 'open', 'close']]
|
i, high, low, last, = array[-1][['index', 'high', 'low', 'close']]
|
||||||
body, larm, rarm = self.lines[index-1]
|
assert i == self.index-1
|
||||||
|
body, larm, rarm = self.lines[i]
|
||||||
|
|
||||||
# XXX: is there a faster way to modify this?
|
# XXX: is there a faster way to modify this?
|
||||||
# update close line / right arm
|
# update close line / right arm
|
||||||
rarm.setLine(rarm.x1(), last, rarm.x2(), last)
|
rarm.setLine(rarm.x1(), last, rarm.x2(), last)
|
||||||
|
|
||||||
# update body
|
if low != high:
|
||||||
high = body.y2()
|
if body is None:
|
||||||
low = body.y1()
|
body = self.lines[index-1][0] = QLineF(i, low, i, high)
|
||||||
if last < low:
|
else:
|
||||||
low = last
|
# update body
|
||||||
|
body.setLine(i, low, i, high)
|
||||||
if last > high:
|
|
||||||
high = last
|
|
||||||
|
|
||||||
if getattr(body, '_flat', None) and low != high:
|
|
||||||
# if the bar was flat it likely does not have
|
|
||||||
# the index set correctly due to a rendering bug
|
|
||||||
# see above
|
|
||||||
body.setLine(i, low, i, high)
|
|
||||||
body._flat = False
|
|
||||||
else:
|
else:
|
||||||
body.setLine(body.x1(), low, body.x2(), high)
|
# XXX: high == low -> remove any HL line to avoid render bug
|
||||||
|
if body is not None:
|
||||||
|
body = self.lines[index-1][0] = None
|
||||||
|
|
||||||
self.draw_lines(just_history=False)
|
self.draw_lines(just_history=False)
|
||||||
|
|
||||||
# XXX: From the customGraphicsItem.py example:
|
|
||||||
# The only required methods are paint() and boundingRect()
|
|
||||||
# @timeit
|
# @timeit
|
||||||
def paint(self, p, opt, widget):
|
def paint(self, p, opt, widget):
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue