from .pyqtgraph.graphicsItems.LegendItem import ItemSample
from .pyqtgraph.graphicsItems.ScatterPlotItem import ScatterPlotItem, drawSymbol
from .pyqtgraph.graphicsItems.PlotDataItem import PlotDataItem
from .pyqtgraph import functions as fn
from optimeed.visualize.graphs import pyqtgraph as pg
from PyQt5 import QtCore, QtGui, QtWidgets, QtOpenGL
import os
isOnWindows = os.name == 'nt'
"""Other modified files (directly): ScatterPlotItem.py, to change point selection. Ctrl + clic: select area. Clic: only one single point:
class OnClicSelector:
def __init__(self):
self.p_list = []
def add_point(self, newp):
self.p_list.append(newp)
def draw(self, painter):
if len(self.p_list) > 2:
pen = fn.mkPen(1)
pen.setWidthF(2)
painter.setPen(pen)
painter.drawPolyline(QtGui.QPolygonF(self.p_list))
def reset(self):
self.p_list = []
def getPath(self):
return path.Path([(p.x(), p.y()) for p in self.p_list] + [(self.p_list[-1].x(), self.p_list[-1].y())])
------------------------------
def mouseDragEvent(self, ev):
if ev.modifiers() and QtCore.Qt.ControlModifier:
ev.accept()
self.clicSelector.add_point(ev.pos())
if ev.isFinish():
path = self.clicSelector.getPath()
points = self.points()
contains_points = path.contains_points([(p.pos().x(), p.pos().y()) for p in points])
indices = [i for i, cond in enumerate(contains_points) if cond]
points_clicked = [points[i] for i in indices]
self.ptsClicked = points_clicked
self.sigClicked.emit(self, self.ptsClicked)
self.clicSelector.reset()
self.update()
else:
ev.ignore()
"""
[docs]class myGraphicsLayout(pg.GraphicsLayout):
def __init__(self):
super().__init__()
self.layout.setHorizontalSpacing(-5)
self.layout.setVerticalSpacing(-5)
[docs] def addItem(self, item, row=None, col=None, rowspan=1, colspan=1):
"""
Add an item to the layout and place it in the next available cell (or in the cell specified).
The item must be an instance of a QGraphicsWidget subclass.
"""
if row is None:
row = self.currentRow
if col is None:
col = self.currentCol
self.items[item] = []
for i in range(rowspan):
for j in range(colspan):
row2 = row + i
col2 = col + j
if row2 not in self.rows:
self.rows[row2] = {}
self.rows[row2][col2] = item
self.items[item].append((row2, col2))
self.layout.addItem(item, row, col, rowspan, colspan)
self.nextRow()
[docs] def set_graph_disposition(self, item, row=1, col=1, rowspan=1, colspan=1):
"""
Function to modify the position of an item in the list
:param item: WidgetPlotItem to set
:param row: Row
:param col: Column
:param rowspan:
:param colspan:
:return:
"""
# Remove from existing
self.layout.removeItem(item)
for r, c in self.items[item]:
del self.rows[r][c]
# Add new configuration
for i in range(rowspan):
for j in range(colspan):
row2 = row + i
col2 = col + j
if row2 not in self.rows:
self.rows[row2] = {}
self.rows[row2][col2] = item
self.items[item].append((row2, col2))
self.layout.addItem(item, row, col, rowspan, colspan)
[docs]class myItemSample(ItemSample):
def __init__(self, item):
super().__init__(item)
self.offset = 13
self.widthCell = 40
[docs] def set_offset(self, offset):
self.offset = offset
[docs] def set_width_cell(self, width):
self.widthCell = width
# """Overwrites for text closer to the sample"""
# def boundingRect(self):
# return QtCore.QRectF(0, 0, 0, 0)
[docs] def paint(self, p, *args):
"""Overwrites to make matlab-like samples"""
# p.setRenderHint(p.Antialiasing) # only if the data is antialiased.
opts = self.item.opts
if opts.get('fillLevel', None) is not None and opts.get('fillBrush', None) is not None:
p.setBrush(fn.mkBrush(opts['fillBrush']))
p.setPen(fn.mkPen(None))
p.drawPolygon(QtGui.QPolygonF([QtCore.QPointF(2, 18), QtCore.QPointF(18, 2), QtCore.QPointF(18, 18)]))
p.save()
linewidth = self.widthCell
v_offset = self.offset
opts = self.item.opts
if not isinstance(self.item, ScatterPlotItem):
pen = fn.mkPen(opts['pen'])
pen.setWidthF(pen.widthF()*0.9)
p.setPen(pen)
p.drawLine(0, v_offset, linewidth, v_offset)
p.restore()
symbol = opts.get('symbol', None)
if symbol is not None:
if isinstance(symbol, list):
symbol = symbol[0]
if isinstance(self.item, PlotDataItem):
opts = self.item.scatter.opts
pen = fn.mkPen(opts['pen'])
brush = fn.mkBrush(opts['brush'])
size = opts['size'] * 1.0 # Scale factor for legend size
p.translate(linewidth / 2, v_offset)
drawSymbol(p, symbol, size, pen, brush)
[docs]class myLegend(pg.LegendItem):
"""Legend that fixes bugs (flush left + space) from pyqtgraph's legend"""
def __init__(self, size=None, offset=(30, 30), is_light=False):
super(myLegend, self).__init__(size, offset)
self.layout.setVerticalSpacing(-8)
self.is_light = is_light
self.width_cell_sample = 25
[docs] def set_space_sample_label(self, theSpace):
"""To set the gap between the sample and the label"""
self.layout.setHorizontalSpacing(theSpace)
[docs] def set_offset_sample(self, offset):
"""To tune the offset between the sample and the text"""
for sample, label in self.items:
sample.set_offset(offset)
[docs] def set_width_cell_sample(self, width):
"""Set width of sample"""
self.width_cell_sample = width
self.apply_width_sample()
[docs] def updateSize(self):
if self.size is not None:
return
height = 0
width = 0
for sample, label in self.items:
height += max(sample.boundingRect().height(), label.height())
width = max(width, sample.boundingRect().width() + label.width()) # + 13
self.setGeometry(0, 0, width, height)
[docs] def addItem(self, item, name):
"""Overwrites to flush left"""
# label = LabelItem(name)
label = myLabelItem(name, justify='left')
width_cell = self.width_cell_sample
if isinstance(item, ItemSample):
sample = item
else:
sample = myItemSample(item)
sample.set_width_cell(width_cell*1)
row = self.layout.rowCount()
self.items.append((sample, label))
self.layout.addItem(sample, row, 0)
self.layout.setColumnFixedWidth(0, width_cell)
self.layout.setColumnFixedWidth(1, 10)
self.layout.setHorizontalSpacing(0.0) # No spacing between sample and text
self.layout.addItem(label, row, 1)
self.layout.setRowFixedHeight(row, 15)
self.updateSize()
[docs] def apply_width_sample(self):
width_cell = self.width_cell_sample
for sample, label in self.items:
sample.set_width_cell(width_cell*1)
self.layout.setColumnFixedWidth(0, width_cell)
self.updateSize()
[docs] def set_font(self, font_size, font_color, fontname=None):
for item in self.items:
for single_item in item:
if isinstance(single_item, pg.LabelItem):
if fontname is not None:
single_item.setText(single_item.text, **{'color': font_color, 'size': str(font_size) + 'pt', 'font-family': fontname})
else:
single_item.setText(single_item.text, **{'color': font_color, 'size': str(font_size) + 'pt'})
[docs] def paint(self, p, *args):
"""Overwrited to select background color"""
if len(self.items):
if self.is_light:
p.setPen(pg.mkPen(100, 100, 100, 255))
p.setBrush(pg.mkBrush(255, 255, 255, 255))
else:
p.setPen(pg.mkPen(100, 100, 100, 255))
p.setBrush(pg.mkBrush(10, 10, 10, 255))
offset_x = 0
offset_y = 20
boundingRect = QtCore.QRectF(0, offset_y/2, self.width()-offset_x, self.height()-offset_y)
p.drawRect(boundingRect) # self.boundingRect())
[docs] def set_position(self, position, offset):
"""
Set the position of the legend, in a corner.
:param position: String (NW, NE, SW, SE), indicates which corner the legend is close
:param offset: Tuple (xoff, yoff), x and y offset from the edge
:return:
"""
offPosMult = 1
if position == "NW":
quartil_parent = (0, 0)
oxMult, oyMult = 1, 1
elif position == "NE":
quartil_parent = (1, 0)
oxMult, oyMult = -1, 1
elif position == "SW":
quartil_parent = (0, 1)
oxMult, oyMult = 1, -1
offPosMult = -1
elif position == "SE":
quartil_parent = (1, 1)
oxMult, oyMult = -1, -1
offPosMult = -1
else:
print("Could not place legend : position unknown (please use NW, NE, SW or SE)")
return
self.anchor(quartil_parent, quartil_parent, offset=(offset[0] * oxMult, offPosMult * (-10) + offset[1] * oyMult)) # * -10: corresponds to offset_y defined in paint
[docs]class myLabelItem(pg.LabelItem):
[docs] def setText(self, text, **args):
""" Overwrited to add font-family to options """
self.text = text
opts = self.opts
for k in args:
opts[k] = args[k]
optlist = []
color = self.opts['color']
if color is None:
color = pg.getConfigOption('foreground')
color = fn.mkColor(color)
optlist.append('color: #' + fn.colorStr(color)[:6])
if 'size' in opts:
optlist.append('font-size: ' + opts['size'])
if 'bold' in opts and opts['bold'] in [True, False]:
optlist.append('font-weight: ' + {True: 'bold', False: 'normal'}[opts['bold']])
if 'italic' in opts and opts['italic'] in [True, False]:
optlist.append('font-style: ' + {True: 'italic', False: 'normal'}[opts['italic']])
if 'font-family' in opts:
optlist.append('font-family: ' + opts['font-family'])
full = "<span style='%s'>%s</span>" % ('; '.join(optlist), text)
# print full
self.item.setHtml(full)
self.updateMin()
self.resizeEvent(None)
self.updateGeometry()
[docs]class myAxis(pg.AxisItem):
update_label = QtCore.pyqtSignal(str)
def __init__(self, orientation):
super().__init__(orientation)
self.nudges = {'left': (-10, 0), 'right': (5, 0), 'bottom': (0, -5), 'top': (0, 5)}
self.update_label.connect(self.label.setHtml)
# self.style.update({'textFillLimits': [(0, 0.8), ## never fill more than 80% of the axis
# (4, 0.4), ## If we already have 4 ticks with text, fill no more than 40% of the axis
# (6, 0.2)]})
[docs] def _updateLabel(self):
# self.label.setHtml(self.labelString())
self.update_label.emit(self.labelString()) # Use of a signal/slot instead of directly changing the value
self._adjustSize()
self.picture = None
self.update()
[docs] def get_label_pos(self):
"""Overwrited to place label closer to the axis"""
br = self.label.boundingRect()
x, y = 0, 0
if self.orientation == 'left':
y, x = int(self.size().height()/2 + br.width()/2), 0
elif self.orientation == 'right':
y, x = int(self.size().height()/2 + br.width()/2), int(self.size().width()-br.height())
elif self.orientation == 'top':
y, x = 0, int(self.size().width()/2. - br.width()/2.)
elif self.orientation == 'bottom':
x, y = int(self.size().width()/2. - br.width()/2.), int(self.size().height()-br.height())
try:
return int(x + self.nudges[self.orientation][0]), int(y - self.nudges[self.orientation][1])
except AttributeError:
return x, y
[docs] def resizeEvent(self, ev=None):
"""Overwrited to place label closer to the axis"""
p = QtCore.QPointF(0, 0)
x_pos, y_pos = self.get_label_pos()
p.setX(int(x_pos))
p.setY(int(y_pos))
self.label.setPos(p)
self.picture = None
[docs] def set_label_pos(self, orientation, x_offset=0, y_offset=0):
self.nudges[orientation] = (x_offset, y_offset)
[docs] def set_number_ticks(self, number):
self.style.update({'textFillLimits': [(number, 0)]})