Skip to content
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

Implementation details for dimension and factor aggregation weighting logic #642

Merged
merged 1 commit into from
Nov 22, 2024
Merged
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
8 changes: 8 additions & 0 deletions geest/core/json_tree_item.py
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,14 @@ def getStatus(self):
if not data.get("required", False):
if not float(data.get("dimension_weighting", 0.0)):
return "Excluded from analysis"
if self.isDimension():
# if the sum of the factor weightings is 0, return "Excluded from analysis"
weight_sum = 0
for child in self.childItems:
weight_sum += float(child.attribute("factor_weighting", 0.0))
if not weight_sum:
return "Excluded from analysis"

if "Error" in data.get("result", ""):
return "Workflow failed"
if "Failed" in data.get("result", ""):
Expand Down
48 changes: 34 additions & 14 deletions geest/gui/dialogs/dimension_aggregation_dialog.py
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ def __init__(
item = self.tree_item.getItemByGuid(guid)
attributes = item.attributes()
factor_id = attributes.get("name")
dimension_weighting = attributes.get("dimension_weighting", 0)
dimension_weighting = float(attributes.get("dimension_weighting", 0.0))
default_dimension_weighting = attributes.get(
"default_dimension_weighting", 0
)
Expand All @@ -160,14 +160,14 @@ def __init__(
weighting_item = QDoubleSpinBox(self)
weighting_item.setRange(0.0, 1.0)
weighting_item.setDecimals(4)
weighting_item.setValue(float(dimension_weighting))
weighting_item.setValue(dimension_weighting)
weighting_item.setSingleStep(0.01)
weighting_item.valueChanged.connect(self.validate_weightings)
self.table.setCellWidget(row, 1, weighting_item)
self.weightings[guid] = weighting_item

# Use checkboxes
checkbox_widget = self.create_checkbox_widget(row)
checkbox_widget = self.create_checkbox_widget(row, dimension_weighting)
self.table.setCellWidget(row, 2, checkbox_widget)
if factor_required == 1:
checkbox_widget.setEnabled(False)
Expand Down Expand Up @@ -235,12 +235,15 @@ def toggle_guid_column(self):
self.guid_column_visible = not self.guid_column_visible
self.table.setColumnHidden(4, not self.guid_column_visible)

def create_checkbox_widget(self, row: int) -> QWidget:
def create_checkbox_widget(self, row: int, dimension_weighting: float) -> QWidget:
"""
Create a QWidget containing a QCheckBox for a specific row and center it.
"""
checkbox = QCheckBox()
checkbox.setChecked(True) # Initially checked
if dimension_weighting > 0:
checkbox.setChecked(True) # Initially checked
else:
checkbox.setChecked(False)
checkbox.stateChanged.connect(
lambda state, r=row: self.toggle_row_widgets(r, state)
)
Expand Down Expand Up @@ -293,7 +296,12 @@ def auto_calculate_weightings(self):
log_message("No enabled rows found, skipping auto-calculation")
return # No enabled rows, avoid division by zero

equal_weighting = 1.0 / len(enabled_rows) # Divide equally among enabled rows
if len(enabled_rows) == 0:
equal_weighting = 0.0
else:
equal_weighting = 1.0 / len(
enabled_rows
) # Divide equally among enabled rows

# Set the weighting for each enabled row
for row in enabled_rows:
Expand All @@ -305,6 +313,7 @@ def auto_calculate_weightings(self):
log_message(f"Setting zero weighting for row: {row}")
widget = self.table.cellWidget(row, 1) # Assuming weight is in column 1
widget.setValue(0)
self.validate_weightings()

def is_checkbox_checked(self, row: int) -> bool:
"""
Expand Down Expand Up @@ -333,7 +342,7 @@ def get_checkbox_in_row(self, row: int) -> QCheckBox:
return checkbox
return None

def assignWeightings(self):
def saveWeightingsToModel(self):
"""Assign new weightings to the dimensions's factors."""
for factor_guid, spin_box in self.weightings.items():
try:
Expand All @@ -353,12 +362,23 @@ def update_preview(self):

def validate_weightings(self):
"""Validate weightings to ensure they sum to 1 and are within range."""
total_weighting = sum(
float(spin_box.value() or 0) for spin_box in self.weightings.values()
)
valid_sum = (
abs(total_weighting - 1.0) < 0.001
) # Allow slight floating-point tolerance
try:
total_weighting = sum(
float(spin_box.value() or 0) for spin_box in self.weightings.values()
)
valid_sum = (
abs(total_weighting - 1.0) < 0.001
) # Allow slight floating-point tolerance
except ValueError:
valid_sum = False

# In the case that all rows are disabled, the sum is valid
enabled_rows = [
row for row in range(self.table.rowCount()) if self.is_checkbox_checked(row)
]
enabled_rows_count = len(enabled_rows)
if enabled_rows_count == 0:
valid_sum = True

# Update button state and font color for validation
for spin_box in self.weightings.values():
Expand All @@ -372,7 +392,7 @@ def validate_weightings(self):

def accept_changes(self):
"""Handle the OK button by applying changes and closing the dialog."""
self.assignWeightings() # Assign weightings when changes are accepted
self.saveWeightingsToModel() # Assign weightings when changes are accepted
if self.editing:
updated_data = self.dimension_data
updated_data["description"] = self.text_edit_left.toPlainText()
Expand Down
41 changes: 23 additions & 18 deletions geest/gui/dialogs/factor_aggregation_dialog.py
Original file line number Diff line number Diff line change
Expand Up @@ -196,9 +196,12 @@ def switch_page(self):
current_index = self.stacked_widget.currentIndex()
self.stacked_widget.setCurrentIndex(1 - current_index) # Toggle between 0 and 1

def create_checkbox_widget(self, row: int) -> QWidget:
def create_checkbox_widget(self, row: int, weighting_value: float) -> QWidget:
checkbox = QCheckBox()
checkbox.setChecked(True)
if weighting_value > 0:
checkbox.setChecked(True)
else:
checkbox.setChecked(False)
checkbox.stateChanged.connect(
lambda state, r=row: self.toggle_row_widgets(r, state)
)
Expand Down Expand Up @@ -245,18 +248,18 @@ def populate_table(self):
self.table.setItem(row, 1, name_item)

# Weighting
weighting_value = attributes.get("factor_weighting", 0)
weighting_value = float(attributes.get("factor_weighting", 0.0))
weighting_item = QDoubleSpinBox()
weighting_item.setRange(0.0, 1.0)
weighting_item.setDecimals(4)
weighting_item.setSingleStep(0.01)
weighting_item.setValue(float(weighting_value))
weighting_item.setValue(weighting_value)
weighting_item.valueChanged.connect(self.validate_weightings)
self.table.setCellWidget(row, 2, weighting_item)
self.weightings[guid] = weighting_item

# Use (Checkbox)
checkbox_widget = self.create_checkbox_widget(row)
checkbox_widget = self.create_checkbox_widget(row, weighting_value)
self.table.setCellWidget(row, 3, checkbox_widget)

# GUID
Expand Down Expand Up @@ -292,15 +295,18 @@ def auto_calculate_weightings(self):
row for row in range(self.table.rowCount()) if self.is_checkbox_checked(row)
]
if not enabled_rows:
return
equal_weighting = 1.0 / len(enabled_rows)
equal_weighting = 0.0
else:
equal_weighting = 1.0 / len(enabled_rows)

for row in enabled_rows:
widget = self.table.cellWidget(row, 2) # Weight column
widget.setValue(equal_weighting)
for row in range(self.table.rowCount()):
if row not in enabled_rows:
widget = self.table.cellWidget(row, 2)
widget.setValue(0)
self.validate_weightings()

def is_checkbox_checked(self, row: int) -> bool:
checkbox = self.get_checkbox_in_row(row)
Expand All @@ -316,7 +322,7 @@ def get_checkbox_in_row(self, row: int) -> QCheckBox:
return checkbox
return None

def assignWeightings(self):
def saveWeightingsToModel(self):
"""Assign new weightings to the factor's indicators."""
for indicator_guid, spin_box in self.weightings.items():
try:
Expand All @@ -332,18 +338,9 @@ def assignWeightings(self):

log_message(traceback.format_exc(), tag="Geest", level=Qgis.Warning)

def validate_weightings(self):
total_weighting = sum(
float(spin_box.value()) for spin_box in self.weightings.values()
)
valid_sum = abs(total_weighting - 1.0) < 0.001
for spin_box in self.weightings.values():
spin_box.setStyleSheet("color: black;" if valid_sum else "color: red;")
self.button_box.button(QDialogButtonBox.Ok).setEnabled(valid_sum)

def accept_changes(self):
"""Handle the OK button by applying changes and closing the dialog."""
self.assignWeightings()
self.saveWeightingsToModel()
if self.editing:
updated_data = self.factor_data
updated_data["description"] = self.text_edit_left.toPlainText()
Expand All @@ -367,6 +364,14 @@ def validate_weightings(self):
except ValueError:
valid_sum = False

# In the case that all rows are disabled, the sum is valid
enabled_rows = [
row for row in range(self.table.rowCount()) if self.is_checkbox_checked(row)
]
enabled_rows_count = len(enabled_rows)
if enabled_rows_count == 0:
valid_sum = True

# Update button state and cell highlighting
for spin_box in self.weightings.values():
if valid_sum:
Expand Down
4 changes: 2 additions & 2 deletions geest/gui/panels/tree_panel.py
Original file line number Diff line number Diff line change
Expand Up @@ -816,7 +816,7 @@ def edit_dimension_aggregation(self, dimension_item):
dimension_name, dimension_data, dimension_item, editing=editing, parent=self
)
if dialog.exec_(): # If OK was clicked
dialog.assignWeightings()
dialog.saveWeightingsToModel()
self.save_json_to_working_directory() # Save changes to the JSON if necessary

def edit_factor_aggregation(self, factor_item):
Expand All @@ -830,7 +830,7 @@ def edit_factor_aggregation(self, factor_item):
factor_name, factor_data, factor_item, editing=editing, parent=self
)
if dialog.exec_(): # If OK was clicked
dialog.assignWeightings()
dialog.saveWeightingsToModel()
self.save_json_to_working_directory() # Save changes to the JSON if necessary

def show_layer_properties(self, item):
Expand Down
Loading