Skip to content

issue 14: Multi dims horizontal labels #63

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
88 changes: 53 additions & 35 deletions larray_editor/arrayadapter.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,15 @@


class LArrayDataAdapter(object):
def __init__(self, axes_model, xlabels_model, ylabels_model, data_model,
data=None, changes=None, current_filter=None, bg_gradient=None, bg_value=None):
def __init__(self, axes_model, hlabels_model, vlabels_model, data_model, data=None,
changes=None, current_filter=None, nb_dims_hlabels=1, bg_gradient=None, bg_value=None):
# set models
self.axes_model = axes_model
self.xlabels_model = xlabels_model
self.ylabels_model = ylabels_model
self.hlabels_model = hlabels_model
self.vlabels_model = vlabels_model
self.data_model = data_model
# set number of dims of hlabels
self.nb_dims_hlabels = nb_dims_hlabels
# set current filter
if current_filter is None:
current_filter = {}
Expand All @@ -31,38 +33,49 @@ def set_changes(self, changes=None):
assert isinstance(changes, dict)
self.changes = changes

def update_nb_dims_hlabels(self, nb_dims_hlabels):
self.nb_dims_hlabels = nb_dims_hlabels
self.update_axes_and_labels()

def get_axes_names(self):
return self.filtered_data.axes.display_names

def get_axes(self):
axes = self.filtered_data.axes
axes_names = self.filtered_data.axes.display_names
# test self.filtered_data.size == 0 is required in case an instance built as LArray([]) is passed
# test len(axes) == 0 is required when a user filters until to get a scalar
if self.filtered_data.size == 0 or len(axes) == 0:
if self.filtered_data.size == 0 or len(axes_names) == 0:
return None
elif len(axes_names) == 1:
return [axes_names]
else:
axes_names = axes.display_names
if len(axes_names) >= 2:
axes_names = axes_names[:-2] + [axes_names[-2] + '\\' + axes_names[-1]]
return [[axis_name] for axis_name in axes_names]
nb_dims_vlabels = len(axes_names) - self.nb_dims_hlabels
# axes corresponding to horizontal labels are set to the last column
res = [['' for c in range(nb_dims_vlabels-1)] + [axis_name] for axis_name in axes_names[nb_dims_vlabels:]]
# axes corresponding to vertical labels are set to the last row
res = res + [[axis_name for axis_name in axes_names[:nb_dims_vlabels]]]
return res

def get_xlabels(self):
def get_labels(self):
axes = self.filtered_data.axes
if self.filtered_data.size == 0 or len(axes) == 0:
return None
vlabels = None
hlabels = None
else:
return [[label] for label in axes.labels[-1]]

def get_ylabels(self):
axes = self.filtered_data.axes
if self.filtered_data.size == 0 or len(axes) == 0:
return None
elif len(axes) == 1:
return [['']]
else:
labels = axes.labels[:-1]
prod = Product(labels)
return [_LazyDimLabels(prod, i) for i in range(len(labels))]
nb_dims_vlabels = len(axes) - self.nb_dims_hlabels
def get_labels_product(axes, extra_row=False):
if len(axes) == 0:
return [[' ']]
else:
# XXX: appends a fake axis instead of using _LazyNone because
# _LazyNone mess up with LabelsArrayModel.get_values (in which slices are used)
if extra_row:
axes.append(la.Axis([' ']))
prod = Product(axes.labels)
return [_LazyDimLabels(prod, i) for i in range(len(axes.labels))]
vlabels = get_labels_product(axes[:nb_dims_vlabels])
hlabels = get_labels_product(axes[nb_dims_vlabels:], nb_dims_vlabels > 0)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this seems odd. I would have thought vlabels would get the extra/fake axis instead of hlabels and that the condition would be nb_dims_vlabels == 0, not nb_dims_vlabels > 0???

return vlabels, hlabels

def get_2D_data(self):
"""Returns Numpy 2D ndarray"""
Expand Down Expand Up @@ -109,24 +122,29 @@ def set_data(self, data, bg_value=None, current_filter=None):
self.bg_value = la.aslarray(bg_value) if bg_value is not None else None
self.update_filtered_data(current_filter, reset_minmax=True)

def update_axes_and_labels(self):
axes = self.get_axes()
vlabels, hlabels = self.get_labels()
self.axes_model.set_data(axes)
self.hlabels_model.set_data(hlabels)
self.vlabels_model.set_data(vlabels)

def update_data_2D(self, reset_minmax=False):
data_2D = self.get_2D_data()
changes_2D = self.get_changes_2D()
bg_value_2D = self.get_bg_value_2D(data_2D.shape)
self.data_model.set_data(data_2D, changes_2D, reset_minmax=reset_minmax)
self.data_model.set_bg_value(bg_value_2D)

def update_filtered_data(self, current_filter=None, reset_minmax=False):
if current_filter is not None:
assert isinstance(current_filter, dict)
self.current_filter = current_filter
self.filtered_data = self.la_data[self.current_filter]
if np.isscalar(self.filtered_data):
self.filtered_data = la.aslarray(self.filtered_data)
axes = self.get_axes()
xlabels = self.get_xlabels()
ylabels = self.get_ylabels()
data_2D = self.get_2D_data()
changes_2D = self.get_changes_2D()
bg_value_2D = self.get_bg_value_2D(data_2D.shape)
self.axes_model.set_data(axes)
self.xlabels_model.set_data(xlabels)
self.ylabels_model.set_data(ylabels)
self.data_model.set_data(data_2D, changes_2D, reset_minmax=reset_minmax)
self.data_model.set_bg_value(bg_value_2D)
self.update_axes_and_labels()
self.update_data_2D(reset_minmax=reset_minmax)

def get_data(self):
return self.la_data
Expand Down
38 changes: 25 additions & 13 deletions larray_editor/arraymodel.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,8 @@ class LabelsArrayModel(AbstractArrayModel):
font : QFont, optional
Font. Default is `Calibri` with size 11.
"""
def __init__(self, parent=None, data=None, readonly=False, font=None):
def __init__(self, parent=None, data=None, readonly=False, font=None, orientation=Qt.Horizontal):
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If it turns out to be indeed necessary to differentiate on the orientation (I am unsure about this), then having two different subclasses of LabelsArrayModel depending on the orientation would make things cleaner.

self.orientation = orientation
AbstractArrayModel.__init__(self, parent, data, readonly, font)
self.font.setBold(True)

Expand All @@ -136,28 +137,39 @@ def _set_data(self, data, changes=None):
QMessageBox.critical(self.dialog, "Error", "Expected list or tuple.")
data = [[]]
self._data = data
self.total_rows = len(data[0])
self.total_cols = len(data) if self.total_rows > 0 else 0
if self.orientation == Qt.Horizontal:
self.total_rows = len(data) if self.total_cols > 0 else 0
self.total_cols = len(data[0])
else:
self.total_rows = len(data[0])
self.total_cols = len(data) if self.total_rows > 0 else 0
self._compute_rows_cols_loaded()

def flags(self, index):
"""Set editable flag"""
return Qt.ItemIsEnabled

def get_value(self, index):
i = index.row()
j = index.column()
# we need to inverse column and row because of the way ylabels are generated
return str(self._data[j][i])
if self.orientation == Qt.Horizontal:
i, j = index.row(), index.column()
else:
i, j = index.column(), index.row()
return str(self._data[i][j])

# XXX: I wonder if we shouldn't return a 2D Numpy array of strings?
def get_values(self, left=0, top=0, right=None, bottom=None):
if right is None:
right = self.total_rows
if bottom is None:
bottom = self.total_cols
values = [list(line[left:right]) for line in self._data[top:bottom]]
return values
if self.orientation == Qt.Horizontal:
if right is None:
right = self.total_cols
if bottom is None:
bottom = self.total_rows
return [list(line[left:right]) for line in self._data[top:bottom]]
else:
if right is None:
right = self.total_rows
if bottom is None:
bottom = self.total_cols
return [list(line[top:bottom]) for line in self._data[left:right]]

def data(self, index, role=Qt.DisplayRole):
# print('data', index.column(), index.row(), self.rowCount(), self.columnCount(), '\n', self._data)
Expand Down
Loading