Skip to content

Commit 189146b

Browse files
committed
Closes #52 | fix stacked dialogs and cancel wiring in KK layout and matrix functions [ci]
- graphMatrixDistanceGeodesicCreate: remove progressUpdate from matrix-fill phase — DistanceEngine dialog is already destroyed on return from graphDistancesGeodesic(); matrix fill is O(N²) and needs no dialog - layoutForceDirectedKamadaKawai: guard graphMatrixDistanceGeodesicCreate return value; use graphDiameterCached() instead of re-triggering graphDiameter(); guard D==0 to prevent division by zero - writeMatrix: add MATRIX_DISTANCES_CHEBYSHEV to compute-phase switch; add progressFinish() before return true; fix default cases to return false - writeClusteringHierarchical: remove spurious progressFinish() from graphClusteringHierarchical failure path; fix default switch case
1 parent f2441d8 commit 189146b

File tree

4 files changed

+107
-82
lines changed

4 files changed

+107
-82
lines changed

src/graph.h

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -971,9 +971,9 @@ public slots:
971971
void addToDistanceSum(qreal delta);
972972
void incGeodesicsCount();
973973
void setAverageDistanceCached(qreal v);
974-
975-
void graphMatrixDistanceGeodesicCreate(const bool &considerWeights = false,
976-
const bool &inverseWeights = true,
974+
975+
bool graphMatrixDistanceGeodesicCreate(const bool &considerWeights = false,
976+
const bool &inverseWeights = false,
977977
const bool &dropIsolates = false);
978978

979979
void graphMatrixShortestPathsCreate(const bool &considerWeights = false,

src/graph/distances/graph_distance_cache.cpp

Lines changed: 34 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -124,28 +124,40 @@ void Graph::graphMatrixShortestPathsCreate(const bool &considerWeights,
124124
}
125125

126126
/**
127-
* @brief Creates the matrix DM of geodesic distances between vertices
128-
* @param considerWeights
129-
* @param inverseWeights
130-
* @param dropIsolates
127+
* @brief Creates the matrix DM of geodesic distances between vertices.
128+
*
129+
* Phase 1: calls graphDistancesGeodesic() which runs the DistanceEngine.
130+
* The engine owns its own progress dialog — it creates it, updates it,
131+
* and destroys it before returning. The dialog stack is empty on return.
132+
*
133+
* Phase 2: fills the DM matrix from the cached per-vertex distances.
134+
* This is an O(N²) memory-write pass — fast enough to need no progress
135+
* dialog of its own. No progressUpdate or progressFinish is called here;
136+
* the caller owns the dialog lifecycle for any subsequent phase.
137+
*
138+
* @param considerWeights If true, edge weights are used in distance computations.
139+
* @param inverseWeights If true, edge weights are inverted before use.
140+
* @param dropIsolates If true, isolate nodes are excluded from the analysis.
141+
* @return true on success, false if the computation was cancelled.
131142
*/
132-
void Graph::graphMatrixDistanceGeodesicCreate(const bool &considerWeights,
143+
bool Graph::graphMatrixDistanceGeodesicCreate(const bool &considerWeights,
133144
const bool &inverseWeights,
134145
const bool &dropIsolates)
135146
{
136147
qDebug() << "Graph::graphMatrixDistanceGeodesicCreate()";
137148

149+
// Phase 1: compute all geodesic distances via DistanceEngine.
150+
// The engine owns its own progress dialog for this phase.
138151
graphDistancesGeodesic(false, considerWeights, inverseWeights, dropIsolates);
139152

140153
if (progressCanceled())
141154
{
142155
calculatedDistances = false;
143-
return;
156+
return false;
144157
}
145158

146159
VList::const_iterator it, jt;
147160
int N = vertices(dropIsolates, false, true);
148-
int progressCounter = 0;
149161
int source = 0, target = 0;
150162
int i = 0, j = 0;
151163

@@ -155,76 +167,49 @@ void Graph::graphMatrixDistanceGeodesicCreate(const bool &considerWeights,
155167

156168
DM.resize(N, N);
157169

158-
QString pMsg = tr("Creating geodesic distances matrix. \nPlease wait ");
159-
progressStatus(pMsg);
160-
progressCreate(N, pMsg);
161-
162-
qDebug() << "Graph: graphMatrixDistanceGeodesicCreate() - Writing distances matrix...";
170+
// Phase 2: fill DM from cached per-vertex distances.
171+
// No progressUpdate here — the DistanceEngine dialog is already destroyed
172+
// by this point. The matrix-fill is O(N²) memory writes and needs no
173+
// progress reporting of its own.
174+
progressStatus(tr("Creating geodesic distances matrix. \nPlease wait "));
163175

164176
for (it = m_graph.cbegin(); it != m_graph.cend(); ++it)
165177
{
166178

167-
progressUpdate(++progressCounter);
168-
if (progressCanceled())
169-
{
170-
calculatedDistances = false;
171-
progressFinish();
172-
return;
173-
}
174-
175179
source = (*it)->number();
176180

177-
if ((*it)->isIsolated() && dropIsolates)
178-
{
179-
qDebug() << "Graph: graphMatrixDistanceGeodesicCreate() - "
180-
<< source << "isolated. SKIP";
181-
181+
if ((*it)->isIsolated() && dropIsolates) {
182182
continue;
183183
}
184-
185-
if (!(*it)->isEnabled())
186-
{
187-
qDebug() << "Graph: graphMatrixDistanceGeodesicCreate() - "
188-
<< source << "disabled. SKIP";
184+
if (!(*it)->isEnabled()) {
189185
continue;
190186
}
191187

192-
qDebug() << "Graph: graphMatrixDistanceGeodesicCreate() - source"
193-
<< source << "i" << i;
194-
195188
for (jt = m_graph.cbegin(); jt != m_graph.cend(); ++jt)
196189
{
197-
198190
target = (*jt)->number();
199191

200-
if ((*jt)->isIsolated() && dropIsolates)
201-
{
202-
qDebug() << "Graph: graphMatrixDistanceGeodesicCreate() - "
203-
<< target << "isolated. SKIP";
192+
if ((*jt)->isIsolated() && dropIsolates) {
204193
continue;
205194
}
206-
207-
if (!(*jt)->isEnabled())
208-
{
209-
qDebug() << "Graph: graphMatrixDistanceGeodesicCreate() - "
210-
<< target << "disabled. SKIP";
195+
if (!(*jt)->isEnabled()) {
211196
continue;
212197
}
213198

214-
qDebug() << "Graph: graphMatrixDistanceGeodesicCreate() - "
215-
<< "target" << target << "j" << j;
199+
// qDebug() << "Graph: graphMatrixDistanceGeodesicCreate() - "
200+
// << "target" << target << "j" << j;
201+
// qDebug() << "Graph: graphMatrixDistanceGeodesicCreate() - setting DM ("
202+
// << i << "," << j << ") =" << (*it)->distance(target);
216203

217-
qDebug() << "Graph: graphMatrixDistanceGeodesicCreate() - setting DM ("
218-
<< i << "," << j << ") =" << (*it)->distance(target);
219204
DM.setItem(i, j, (*it)->distance(target));
220-
221205
j++;
222206
}
223207
j = 0;
224208
i++;
225209
}
226210

227-
progressFinish();
211+
// No progressFinish() here — the caller owns the outer dialog lifecycle.
212+
return true;
228213
}
229214

230215
/**

src/graph/layouts/graph_layouts_force.cpp

Lines changed: 23 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -322,7 +322,6 @@ void Graph::layoutForceDirectedFruchtermanReingold(const int maxIterations)
322322
* distances and real ones for all pairs of particles
323323
* Initially, the particles/actors are placed on the vertices of a regular n-polygon
324324
*/
325-
326325
void Graph::layoutForceDirectedKamadaKawai(const int maxIterations,
327326
const bool considerWeights,
328327
const bool inverseWeights,
@@ -378,31 +377,42 @@ void Graph::layoutForceDirectedKamadaKawai(const int maxIterations,
378377

379378
qDebug() << "Compute dij, where (i,j) in E";
380379

381-
graphMatrixDistanceGeodesicCreate(considerWeights, inverseWeights, dropIsolates);
380+
if (!graphMatrixDistanceGeodesicCreate(considerWeights, inverseWeights, dropIsolates))
381+
{
382+
return;
383+
}
382384

383385
if (progressCanceled())
384386
{
385387
return;
386388
}
389+
387390
// processEvents() ensures the geodesic progress dialog updates are
388391
// flushed before we start the KK iteration phase.
389392
QApplication::processEvents();
390393

391-
// Compute original spring length
392-
// lij for 1 <= i!=j <= n using the formula:
393-
// lij = L x dij
394-
// where L the desirable length of a single edge in the display pane
395-
// Since we are on a restricted display (a canvas), L depends on the
396-
// diameter D of the graph and the length L of a side of the display square:
397-
// L = L0 / D
394+
// Use the cached diameter — graphMatrixDistanceGeodesicCreate() already
395+
// ran the DistanceEngine so re-calling graphDiameter() would redundantly
396+
// re-trigger it if the cache were ever invalidated.
397+
D = graphDiameterCached();
398+
L0 = canvasMinDimension() - 100;
398399

399-
qDebug() << "Compute lij = L x dij. lij will be symmmetric.";
400+
// Guard against degenerate graphs (no edges, all isolates, single node)
401+
// where D == 0, which would cause division by zero.
402+
if (D <= 0)
403+
{
404+
qDebug() << "KK layout: graph diameter is 0 (degenerate graph). Aborting.";
405+
progressFinish();
406+
setModStatus(ModStatus::VertexPositions);
407+
return;
408+
}
400409

401-
D = graphDiameter(considerWeights, inverseWeights);
402-
L0 = canvasMinDimension() - 100;
410+
// L = L0 / D: the desirable length of a single edge in the display pane
411+
// scales with the graph diameter so the layout fills the canvas.
403412
L = L0 / D;
404413

405-
qDebug() << "L=" << L0 << "/" << D << "=" << L;
414+
qDebug() << "Compute lij = L x dij. lij will be symmetric."
415+
<< "L0=" << L0 << "D=" << D << "L=" << L;
406416

407417
l.zeroMatrix(DM.rows(), DM.cols());
408418
l = DM;

src/graph/reporting/graph_reports.cpp

Lines changed: 47 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -4450,14 +4450,24 @@ bool Graph::writeCliqueCensus(const QString &fileName,
44504450
}
44514451

44524452
/**
4453-
* @brief Writes Hierarchical Clustering Analysis to a given file
4454-
* @param fileName
4455-
* @param matrix
4456-
* @param similarityMeasure
4457-
* @param method
4458-
* @param considerWeights
4459-
* @param inverseWeights
4460-
* @param dropIsolates
4453+
* @brief Performs Hierarchical Cluster Analysis (HCA) and writes the results to an HTML report file.
4454+
*
4455+
* The analysis proceeds in three phases:
4456+
* 1. Build the structural equivalence matrix (adjacency or geodesic distances).
4457+
* 2. Run the hierarchical clustering algorithm on the equivalence matrix.
4458+
* 3. Write the HTML report including the equivalence matrix and dendrogram.
4459+
*
4460+
* @param fileName Path to the output HTML report file.
4461+
* @param varLocation Whether variables are in rows or columns ("rows"/"cols").
4462+
* @param matrix Input matrix type: "adjacency" or "distances".
4463+
* @param metric Distance/dissimilarity metric (e.g., "euclidean", "manhattan").
4464+
* @param method Clustering linkage method (e.g., "single", "complete", "average").
4465+
* @param diagonal If true, include the diagonal of the matrix in computations.
4466+
* @param dendrogram If true, include dendrogram output in the report.
4467+
* @param considerWeights If true, edge weights are used in distance computations.
4468+
* @param inverseWeights If true, edge weights are inverted before use.
4469+
* @param dropIsolates If true, isolate nodes are excluded from the analysis.
4470+
* @return true on success, false if the computation was cancelled or an error occurred.
44614471
*/
44624472
bool Graph::writeClusteringHierarchical(const QString &fileName,
44634473
const QString &varLocation,
@@ -4510,8 +4520,7 @@ bool Graph::writeClusteringHierarchical(const QString &fileName,
45104520
STR_EQUIV = AM;
45114521
break;
45124522
case MATRIX_DISTANCES:
4513-
graphMatrixDistanceGeodesicCreate(considerWeights, inverseWeights, dropIsolates);
4514-
if (progressCanceled())
4523+
if (!graphMatrixDistanceGeodesicCreate(considerWeights, inverseWeights, dropIsolates))
45154524
{
45164525
file.close();
45174526
progressStatus(tr("Computation canceled."));
@@ -4520,7 +4529,9 @@ bool Graph::writeClusteringHierarchical(const QString &fileName,
45204529
STR_EQUIV = DM;
45214530
break;
45224531
default:
4523-
break;
4532+
file.close();
4533+
progressStatus(tr("Error: unsupported matrix type."));
4534+
return false;
45244535
}
45254536

45264537
if (!graphClusteringHierarchical(STR_EQUIV,
@@ -4534,8 +4545,8 @@ bool Graph::writeClusteringHierarchical(const QString &fileName,
45344545
dropIsolates))
45354546
{
45364547
qDebug() << "Graph::writeClusteringHierarchical() - HCA failed. Returning...";
4548+
file.close();
45374549
progressStatus("Error completing HCA analysis");
4538-
progressFinish();
45394550
return false;
45404551
}
45414552

@@ -5691,9 +5702,25 @@ void Graph::writeDataSetToFile(const QString dir, const QString fileName)
56915702
}
56925703
}
56935704

5705+
56945706
/**
5695-
Writes the specified matrix of social network to file fn
5696-
*/
5707+
* @brief Computes and writes the specified matrix of the social network to an HTML report file.
5708+
*
5709+
* Supported matrix types: adjacency, laplacian, degree, geodesic distances,
5710+
* shortest paths (geodesics), inverse adjacency, reachability, transpose,
5711+
* cocitation, and tie-profile distance matrices (Euclidean, Hamming, Jaccard,
5712+
* Manhattan, Chebyshev).
5713+
*
5714+
* @param fn Path to the output HTML report file.
5715+
* @param matrix Matrix type constant (e.g., MATRIX_ADJACENCY, MATRIX_DISTANCES).
5716+
* @param considerWeights If true, edge weights are used in distance computations.
5717+
* @param inverseWeights If true, edge weights are inverted before use.
5718+
* @param dropIsolates If true, isolate nodes are excluded from the analysis.
5719+
* @param varLocation Whether variables are in rows or columns ("rows"/"cols"),
5720+
* used for tie-profile distance matrices.
5721+
* @param simpler Reserved for future use.
5722+
* @return true on success, false if the computation was cancelled or an error occurred.
5723+
*/
56975724
bool Graph::writeMatrix(const QString &fn,
56985725
const int &matrix,
56995726
const bool &considerWeights,
@@ -5756,8 +5783,7 @@ bool Graph::writeMatrix(const QString &fn,
57565783
progressStatus(tr("Adjacency recomputed. Writing Degree Matrix..."));
57575784
break;
57585785
case MATRIX_DISTANCES:
5759-
graphMatrixDistanceGeodesicCreate(considerWeights, inverseWeights, dropIsolates);
5760-
if (progressCanceled())
5786+
if (!graphMatrixDistanceGeodesicCreate(considerWeights, inverseWeights, dropIsolates))
57615787
{
57625788
file.close();
57635789
progressStatus(tr("Computation canceled."));
@@ -5823,6 +5849,7 @@ bool Graph::writeMatrix(const QString &fn,
58235849
case MATRIX_DISTANCES_JACCARD:
58245850
case MATRIX_DISTANCES_MANHATTAN:
58255851
case MATRIX_DISTANCES_EUCLIDEAN:
5852+
case MATRIX_DISTANCES_CHEBYSHEV:
58265853
progressStatus(tr("Need to recompute tie profile distances. Please wait..."));
58275854
createMatrixAdjacency();
58285855
if (progressCanceled())
@@ -5835,7 +5862,9 @@ bool Graph::writeMatrix(const QString &fn,
58355862
break;
58365863

58375864
default:
5838-
break;
5865+
file.close();
5866+
progressStatus(tr("Error: unsupported matrix type."));
5867+
return false;
58395868
}
58405869

58415870
QTextStream outText(&file);
@@ -6090,6 +6119,7 @@ bool Graph::writeMatrix(const QString &fn,
60906119
outText << htmlEnd;
60916120

60926121
file.close();
6122+
progressFinish();
60936123
return true;
60946124
}
60956125

0 commit comments

Comments
 (0)