Skip to content

Commit

Permalink
Merge pull request #321 from pfafflabatuiuc/slider_update
Browse files Browse the repository at this point in the history
XYSelector select element slider update.
  • Loading branch information
wpfff committed Sep 2, 2022
2 parents 527b7a5 + 56acc5c commit 52e6d48
Showing 1 changed file with 83 additions and 23 deletions.
106 changes: 83 additions & 23 deletions plottr/node/dim_reducer.py
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,9 @@ def setDimInfos(self, infos: Dict[str, str]) -> None:
for ax, info in infos.items():
self.setDimInfo(ax, info)

@Slot(dict)
def setShapes(self, shapes: Dict[str, Dict[int, int]]) -> None:
self._dataShapes = shapes

class DimensionReductionAssignmentWidget(DimensionAssignmentWidget):

Expand Down Expand Up @@ -294,32 +297,49 @@ def setRole(self, dim: str, role: Optional[str] = None, **kw: Any) -> None:
item = self.findItems(dim, QtCore.Qt.MatchExactly, 0)[0]

if role == ReductionMethod.elementSelection.value:
value = kw.get('index', 0)

# only create the slider widget if it doesn't exist yet
if (self.itemWidget(item, 2) is None) or \
(self.choices[dim]['optionsWidget'] is None):
assert self._dataStructure is not None
assert self._dataShapes is not None
# get the number of elements in this dimension
axidx = self._dataStructure.axes().index(dim)
naxvals = self._dataShapes[dim][axidx]

w = self.elementSelectionSlider(nvals=naxvals, value=value)
w.valueChanged.connect(
lambda x: self.elementSelectionSliderChange(dim))
self.setNewSlider(dim, item)

def setShapes(self, shapes: Dict[str, Dict[int, int]]) -> None:
oldShapes = self._dataShapes
super().setShapes(shapes)
for dim, values in shapes.items():
if dim in self.choices:
# Only element selection has a slider that needs to be updated.
if self.getRole(dim)[0] == ReductionMethod.elementSelection.value:
# If this shape did not change, don't update the slider.
if oldShapes is not None:
if values != oldShapes[dim]:
self.setNewSlider(dim, self.findItems(dim, QtCore.Qt.MatchExactly, 0)[0])

def setNewSlider(self, dim: str, item: QtWidgets.QTreeWidgetItem) -> None:
assert self._dataStructure is not None
assert self._dataShapes is not None
# get the number of elements in this dimension
axidx = self._dataStructure.axes().index(dim)
naxvals = self._dataShapes[dim][axidx]

scaling = int(np.rint(self.logicalDpiX() / 96.0))
width = 150 + 50*(scaling - 1)
height = 22*scaling
w.setMinimumSize(width, height)
w.setMaximumHeight(height)
previousSlider = self.itemWidget(item, 2)
sliderValue = 0
if isinstance(previousSlider, QtWidgets.QSlider):
sliderValue = previousSlider.value()

self.choices[dim]['optionsWidget'] = w
self.setItemWidget(item, 2, w)
self.updateSizes()
w = self.elementSelectionSlider(nvals=naxvals, value=sliderValue)
w.valueChanged.connect(lambda x: self.elementSelectionSliderChange(dim))

self._setElementSelectionInfo(dim)
scaling = int(np.rint(self.logicalDpiX() / 96.0))
width = 150 + 50 * (scaling - 1)
height = 22 * scaling
w.setMinimumSize(width, height)
w.setMaximumHeight(height)

self.choices[dim]['optionsWidget'] = w
self.setItemWidget(item, 2, w)
self.setElementSelectionInfo(dim, item.text(3).split(' (')[0])
self.updateSizes()

def elementSelectionSlider(self, nvals: int, value: int = 0) -> QtWidgets.QSlider:
w = QtWidgets.QSlider(QtCore.Qt.Horizontal)
Expand All @@ -333,19 +353,21 @@ def elementSelectionSlider(self, nvals: int, value: int = 0) -> QtWidgets.QSlide
return w

def elementSelectionSliderChange(self, dim: str) -> None:
self._setElementSelectionInfo(dim)
roles = self.getRoles()
self.rolesChanged.emit(roles)

def _setElementSelectionInfo(self, dim: str) -> None:
def setElementSelectionInfo(self, dim: str, value: Optional[str] = None) -> None:
# get the number of elements in this dimension
assert self._dataStructure is not None
assert self._dataShapes is not None
roles = self.getRoles()
axidx = self._dataStructure.axes().index(dim)
naxvals = self._dataShapes[dim][axidx]
idx = roles[dim]['options']['index']
self.setDimInfo(dim, f"({idx + 1}/{naxvals})")
text = f"({idx + 1}/{naxvals})"
if value is not None:
text = value + ' ' + text
self.setDimInfo(dim, text)


class XYSelectionWidget(DimensionReductionAssignmentWidget):
Expand Down Expand Up @@ -415,6 +437,9 @@ def setData(self,
assert self.widget is not None
self.widget.setData(structure, shapes, dtype)

def setShapes(self, shapes: Dict[str, Dict[int, int]]) -> None:
assert self.widget is not None
self.widget.setShapes(shapes)

class DimensionReducer(Node):
"""
Expand Down Expand Up @@ -442,6 +467,11 @@ class DimensionReducer(Node):
The function must accept an ``axis = <int>`` keyword, and must return
an array of dimensionality that is reduced by one compared to its
input.
:reductionValues: ``Dict[str, float]``
Holds the value of the currently selected axis index.
This only happen when the role is ReductionMethod.elementSelection.
Dictionary with the axis as keys and the selected values as values.
"""

nodeName = 'DimensionReducer'
Expand All @@ -451,8 +481,12 @@ class DimensionReducer(Node):
#: changed.
newDataStructure = Signal(object, object, object)

#: A signal that emits (shapes) when the shapes have changed.
dataShapesChanged = Signal(dict)

def __init__(self, name: str):
self._reductions: Dict[str, Optional[ReductionType]] = {}
self._reductionValues: Dict[str, float] = {}
self._targetNames: Optional[List[str]] = None
self._dataStructure = None

Expand All @@ -478,10 +512,21 @@ def targetNames(self) -> Optional[List[str]]:
def targetNames(self, val: Optional[List[str]]) -> None:
self._targetNames = val

@property
def reductionValues(self) -> Dict[str, float]:
return self._reductionValues

@reductionValues.setter # type: ignore[misc]
@updateOption('reductionValues')
def reductionValues(self, val: Dict[str, float]) -> None:
self._reductionValues = val

# Data processing

def _applyDimReductions(self, data: DataDictBase) -> Optional[DataDictBase]:
"""Apply the reductions"""
reductionValues: Dict[str, float] = {} # Holds the temporary reduction values before saving them.

if self._targetNames is not None:
dnames = self._targetNames
else:
Expand Down Expand Up @@ -542,11 +587,16 @@ def _applyDimReductions(self, data: DataDictBase) -> Optional[DataDictBase]:
if len(axdata.shape) > len(targetShape):
newaxvals = funCall(data[ax]['values'], *arg, **kw)
data[ax]['values'] = newaxvals
if ax in self._reductions:
reductionValues[ax] = newaxvals.flat[0]

del data[n]['axes'][idx]

data = data.sanitize()
data.validate()
if self.reductionValues != reductionValues:
self.reductionValues = reductionValues # type: ignore[misc]
# Open bug: https://github.com/python/mypy/issues/1465
return data

def validateOptions(self, data: DataDictBase) -> bool:
Expand Down Expand Up @@ -634,11 +684,11 @@ def process(

return dict(dataOut=data)

# FIXME: include connection to a method that helps updating sliders etc.
def setupUi(self) -> None:
super().setupUi()
assert self.ui is not None
self.newDataStructure.connect(self.ui.setData)
self.dataShapesChanged.connect(self.ui.setShapes)


class XYSelectorNodeWidget(NodeWidget[XYSelectionWidget]):
Expand All @@ -650,6 +700,7 @@ def __init__(self, node: Optional[Node] = None):

self.optSetters = {
'dimensionRoles': self.setRoles,
'reductionValues': self.setReductionValues
}
self.optGetters = {
'dimensionRoles': self.getRoles,
Expand Down Expand Up @@ -698,13 +749,22 @@ def setRoles(self, roles: Dict[str, str]) -> None:

self.widget.emitRoleChangeSignal = True

def setReductionValues(self, val: Dict[str, float]) -> None:
if self.widget is not None:
for dim, value in val.items():
self.widget.setElementSelectionInfo(dim, str(value))

def setData(self,
structure: DataDictBase,
shapes: Dict[str, Dict[int, int]],
dtype: Type[DataDictBase]) -> None:
assert self.widget is not None
self.widget.setData(structure, shapes, dtype)

def setShapes(self, shapes: Dict[str, Dict[int, int]]) -> None:
assert self.widget is not None
self.widget.setShapes(shapes)


class XYSelector(DimensionReducer):

Expand Down

0 comments on commit 52e6d48

Please sign in to comment.