Skip to content

Commit 7f3be77

Browse files
committed
Merge branch 'geopandas_district_layer'
2 parents 14b4de0 + febbfc9 commit 7f3be77

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

62 files changed

+4039
-3139
lines changed

LICENSE

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
QGIS Redistricting Plugin
22

3-
Copyright (C) 2022 Stuart C. Naifeh
3+
Copyright (C) 2022-2024 Stuart C. Naifeh
44
55

66
This program is free software; you can redistribute it and/or modify it under the terms

pylintrc

+2-2
Original file line numberDiff line numberDiff line change
@@ -441,10 +441,10 @@ exclude-too-few-public-methods=
441441
ignored-parents=
442442

443443
# Maximum number of arguments for function / method.
444-
max-args=5
444+
max-args=10
445445

446446
# Maximum number of attributes for a class (see R0902).
447-
max-attributes=7
447+
max-attributes=15
448448

449449
# Maximum number of boolean expressions in an if statement (see R0916).
450450
max-bool-expr=5

pyproject.toml

+1
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@ max_line_length = 120
44
[tool.pytest.ini_options]
55
filterwarnings = "ignore:distutils:DeprecationWarning"
66
qt_api = "pyqt5"
7+
# addopts = "-nauto"

redistricting/core/BasePlanBuilder.py

+9-2
Original file line numberDiff line numberDiff line change
@@ -183,7 +183,7 @@ def setPopLayer(self, value: QgsVectorLayer):
183183

184184
return self
185185

186-
def setJoinField(self, value: str):
186+
def setPopJoinField(self, value: str):
187187
if value is not None and not isinstance(value, str):
188188
raise ValueError(tr('Population join field must be a string'))
189189

@@ -305,7 +305,14 @@ def setDataFields(self, dataFields: Union[List[DataField], FieldList]):
305305
return self
306306

307307
@overload
308-
def appendDataField(self, field: str, isExpression: bool = False, caption: str = None, sumfield=None, pctbase=None) -> Self:
308+
def appendDataField(
309+
self,
310+
field: str,
311+
isExpression: bool = False,
312+
caption: str = None,
313+
sumfield=None,
314+
pctbase=None
315+
) -> Self:
309316
...
310317

311318
@overload

redistricting/core/DeltaList.py

+2-7
Original file line numberDiff line numberDiff line change
@@ -164,10 +164,6 @@ def setAssignLayer(self, value: QgsVectorLayer):
164164
self._undoStack = self._assignLayer.undoStack()
165165
self._undoStack.indexChanged.connect(self.update)
166166

167-
def detachSignals(self):
168-
self._undoStack.indexChanged.disconnect(self.update)
169-
self._undoStack = None
170-
171167
def isUpdatingPending(self):
172168
return self._pendingTask is not None and self._pendingTask.status() < self._pendingTask.TaskStatus.Complete
173169

@@ -192,9 +188,8 @@ def taskTerminated():
192188
if self._pendingTask and self._pendingTask.status() < self._pendingTask.TaskStatus.Complete:
193189
return self._pendingTask
194190

195-
if not self._assignLayer or not self._assignLayer.editBuffer() or \
196-
len(self._assignLayer.editBuffer().changedAttributeValues()) == 0:
197-
# self.clear()
191+
if not self._assignLayer or not self._assignLayer.editBuffer() or self._undoStack.index() == 0:
192+
self.clear()
198193
return None
199194

200195
self.updateStarted.emit(self._plan)

redistricting/core/DeltaListModel.py

+4-2
Original file line numberDiff line numberDiff line change
@@ -137,8 +137,10 @@ def updateFields(self):
137137
'format': '{:.2%}'
138138
})
139139

140-
def planChanged(self, plan, field, new, old): # pylint: disable=unused-argument
141-
if field in {'pop-field', 'pop-fields', 'data-fields'}:
140+
def planChanged(self, plan, fields):
141+
assert plan == self._plan
142+
143+
if fields & {'pop-field', 'pop-fields', 'data-fields'}:
142144
self.beginResetModel()
143145
self.updateFields()
144146
self.endResetModel()

redistricting/core/District.py

+12-6
Original file line numberDiff line numberDiff line change
@@ -44,11 +44,11 @@
4444

4545

4646
class District:
47-
def __init__(self, district: int, owner: DistrictList):
47+
def __init__(self, district: int, lst: DistrictList):
4848
self._district = district
49-
self._data = owner._data
49+
self._data = lst._data
5050
self._index = self._data.index.get_loc(district)
51-
self._list = owner
51+
self._list = lst
5252

5353
@overload
5454
def __getitem__(self, index: str | int) -> Any:
@@ -153,13 +153,19 @@ def isValid(self):
153153
lower, upper = self._list.idealRange(self["members"])
154154
return lower <= self.population <= upper
155155

156+
@property
157+
def assignments(self):
158+
return self._list.getAssignments(self._district)
159+
156160
@property
157161
def color(self):
158162
renderer = self._list.layer.renderer()
159163
if isinstance(renderer, QgsCategorizedSymbolRenderer):
160164
idx = renderer.categoryIndexForValue(self._district)
161-
if idx != -1:
162-
cat = renderer.categories()[idx]
163-
return QColor(cat.symbol().color())
165+
if idx == -1:
166+
idx = 0
167+
168+
cat = renderer.categories()[idx]
169+
return QColor(cat.symbol().color())
164170

165171
return QColor(QPalette().color(QPalette.Normal, QPalette.Window))

redistricting/core/DistrictDataModel.py

+24-10
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,10 @@
2222
* *
2323
***************************************************************************/
2424
"""
25-
from typing import Any
25+
from typing import (
26+
Any,
27+
Union
28+
)
2629

2730
import numpy as np
2831
import pandas as pd
@@ -63,29 +66,27 @@ def plan(self, value: RedistrictingPlan):
6366
self.beginResetModel()
6467

6568
if self._districts is not None:
66-
self._districts.updating.disconnect(self.beginResetModel)
67-
self._districts.updateComplete.disconnect(self.endResetModel)
68-
self._districts.updateTerminated.disconnect(self.endResetModel)
6969
self._districts.districtChanged.disconnect(self.districtChanged)
70+
self._plan.districtsUpdated.disconnect(self.districtsUpdated)
7071
self._plan.planChanged.disconnect(self.planChanged)
7172

7273
self._plan = value
7374
self._districts = self._plan.districts if self._plan else None
7475

7576
if self._districts is not None:
76-
self._districts.updating.connect(self.beginResetModel)
77-
self._districts.updateComplete.connect(self.endResetModel)
78-
self._districts.updateTerminated.connect(self.endResetModel)
7977
self._districts.districtChanged.connect(self.districtChanged)
78+
self._plan.districtsUpdated.connect(self.districtsUpdated)
8079
self._plan.planChanged.connect(self.planChanged)
8180

8281
self.endResetModel()
8382

84-
def planChanged(self, plan, prop, value, oldValue): # pylint: disable=unused-argument
85-
if prop in ('districts', 'data-fields', 'pop-field', 'pop-fields'):
83+
def planChanged(self, plan, props):
84+
assert plan == self.plan
85+
86+
if props & {'districts', 'data-fields', 'pop-field', 'pop-fields'}:
8687
self.beginResetModel()
8788
self.endResetModel()
88-
elif prop == 'deviation':
89+
elif 'deviation' in props:
8990
self.dataChanged.emit(self.createIndex(1, 1), self.createIndex(self.rowCount() - 1, 4), [Qt.BackgroundRole])
9091

9192
def districtChanged(self, district: District):
@@ -184,3 +185,16 @@ def flags(self, index):
184185
f |= Qt.ItemIsEditable
185186

186187
return f
188+
189+
def districtsUpdated(self, districts: Union[list[int], None]):
190+
if districts:
191+
for d in districts:
192+
self.dataChanged.emit(
193+
self.createIndex(d, 3),
194+
self.createIndex(d, self.columnCount()),
195+
[Qt.DisplayRole, Qt.EditRole]
196+
)
197+
self.dataChanged.emit(self.createIndex(d, 4), self.createIndex(d, 5), [Qt.FontRole])
198+
else:
199+
self.beginResetModel()
200+
self.endResetModel()

0 commit comments

Comments
 (0)