Skip to content
Closed
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
176 changes: 176 additions & 0 deletions Gvisual/src/gvisual/CommunityPanelController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
package gvisual;

import edu.uci.ics.jung.graph.Graph;

import javax.swing.*;
import javax.swing.border.EtchedBorder;
import javax.swing.border.TitledBorder;
import java.awt.*;
import java.util.*;
import java.util.List;
import java.util.function.Supplier;

/**
* Encapsulates the "Community Detection" analysis panel previously embedded
* in Main.java. Owns UI, analysis lifecycle, overlay state, and the
* community colour palette so that Main only needs to wire the panel in.
*/
public class CommunityPanelController {

private static final Color[] COMMUNITY_COLORS = {
new Color(0, 200, 120), // Emerald green
new Color(65, 135, 255), // Bright blue
new Color(255, 100, 100), // Coral red
new Color(255, 200, 50), // Golden yellow
new Color(200, 100, 255), // Purple
new Color(255, 150, 50), // Orange
new Color(100, 220, 220), // Teal
new Color(255, 100, 200), // Pink
new Color(180, 220, 80), // Lime
new Color(150, 130, 255), // Lavender
new Color(255, 180, 150), // Salmon
new Color(100, 180, 150), // Sea green
};

private final JPanel panel;
private final JLabel countLabel;
private final JLabel modularityLabel;
private final JLabel detailsLabel;
private final JButton detectButton;
private final JButton clearButton;

private final Supplier<Graph<String, edge>> graphSupplier;
private final Runnable onOverlayChanged;

private boolean overlayActive;
private Map<String, Integer> nodeCommunityMap;

/**
* @param graphSupplier supplies the current graph
* @param onOverlayChanged callback to refresh renderers/visualization after overlay changes
*/
public CommunityPanelController(Supplier<Graph<String, edge>> graphSupplier,
Runnable onOverlayChanged) {
this.graphSupplier = graphSupplier;
this.onOverlayChanged = onOverlayChanged;

Font labelFont = new Font("SansSerif", Font.PLAIN, 12);

countLabel = new JLabel("Communities: \u2014");
countLabel.setFont(labelFont);
countLabel.setAlignmentX(JLabel.LEFT_ALIGNMENT);

modularityLabel = new JLabel("Modularity: \u2014");
modularityLabel.setFont(labelFont);
modularityLabel.setAlignmentX(JLabel.LEFT_ALIGNMENT);

detailsLabel = new JLabel("<html>Click 'Detect' to find communities.</html>");
detailsLabel.setFont(labelFont);
detailsLabel.setAlignmentX(JLabel.LEFT_ALIGNMENT);

detectButton = new JButton("Detect");
detectButton.setAlignmentX(JButton.LEFT_ALIGNMENT);
detectButton.addActionListener(e -> runDetection());

clearButton = new JButton("Clear");
clearButton.setAlignmentX(JButton.LEFT_ALIGNMENT);
clearButton.addActionListener(e -> clearOverlay());

JPanel buttonPanel = new JPanel();
buttonPanel.setLayout(new BoxLayout(buttonPanel, BoxLayout.X_AXIS));
buttonPanel.setAlignmentX(JPanel.LEFT_ALIGNMENT);
buttonPanel.add(detectButton);
buttonPanel.add(Box.createHorizontalStrut(4));
buttonPanel.add(clearButton);

panel = new JPanel();
panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
panel.setBorder(BorderFactory.createTitledBorder(
BorderFactory.createEtchedBorder(EtchedBorder.LOWERED),
"Community Detection",
TitledBorder.CENTER,
TitledBorder.TOP));
panel.add(countLabel);
panel.add(modularityLabel);
panel.add(Box.createVerticalStrut(4));
panel.add(buttonPanel);
panel.add(Box.createVerticalStrut(4));
panel.add(detailsLabel);
}

public JPanel getPanel() { return panel; }
public boolean isOverlayActive() { return overlayActive; }
public Map<String, Integer> getNodeCommunityMap() { return nodeCommunityMap; }

/** Returns the shared community colour palette. */
public static Color[] getCommunityColors() { return COMMUNITY_COLORS; }

private void runDetection() {
Graph<String, edge> g = graphSupplier.get();
if (g == null || g.getVertexCount() == 0) {
detailsLabel.setText("<html>No graph loaded.</html>");
return;
}

CommunityDetector detector = new CommunityDetector(g);
CommunityDetector.DetectionResult result = detector.detect();

overlayActive = true;
nodeCommunityMap = new HashMap<>(result.getNodeToCommunity());

int total = result.getCommunityCount();
List<CommunityDetector.Community> significant = result.getSignificantCommunities(2);
double modularity = result.getModularity(g);

countLabel.setText("Communities: " + total
+ " (" + significant.size() + " with 2+ members)");
modularityLabel.setText(String.format("Modularity: %.4f", modularity));

StringBuilder details = new StringBuilder("<html>");
if (significant.isEmpty()) {
details.append("No communities with 2+ members found.");
} else {
int shown = Math.min(significant.size(), 8);
for (int i = 0; i < shown; i++) {
CommunityDetector.Community c = significant.get(i);
Color col = COMMUNITY_COLORS[c.getId() % COMMUNITY_COLORS.length];
String hex = String.format("#%02x%02x%02x", col.getRed(), col.getGreen(), col.getBlue());
details.append(String.format(
"<b style='color:%s'>\u25A0</b> C%d: %d nodes, %d edges, density=%.3f<br/>"
+ "&nbsp;&nbsp;dominant: %s, avg wt: %.1f<br/>",
hex, c.getId(), c.getSize(), c.getInternalEdges(),
c.getDensity(), getDominantLabel(c.getDominantType()),
c.getAverageWeight()));
}
if (significant.size() > shown) {
details.append("...and ").append(significant.size() - shown).append(" more<br/>");
}
}
int isolatedCount = total - significant.size();
if (isolatedCount > 0) {
details.append("<i>").append(isolatedCount).append(" isolated node(s)</i>");
}
details.append("</html>");
detailsLabel.setText(details.toString());

panel.revalidate();
panel.repaint();
onOverlayChanged.run();
}

private void clearOverlay() {
overlayActive = false;
nodeCommunityMap = null;
countLabel.setText("Communities: \u2014");
modularityLabel.setText("Modularity: \u2014");
detailsLabel.setText("<html>Click 'Detect' to find communities.</html>");
panel.revalidate();
panel.repaint();
onOverlayChanged.run();
}

private static String getDominantLabel(String typeCode) {
EdgeType type = EdgeType.fromCode(typeCode);
return type != null ? type.getDisplayLabel() : typeCode;
}
}
184 changes: 184 additions & 0 deletions Gvisual/src/gvisual/MSTPanelController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
package gvisual;

import edu.uci.ics.jung.graph.Graph;

import javax.swing.*;
import javax.swing.border.EtchedBorder;
import javax.swing.border.TitledBorder;
import java.awt.*;
import java.util.*;
import java.util.function.Supplier;

/**
* Encapsulates the "Minimum Spanning Tree" analysis panel previously embedded
* in Main.java. Owns UI, computation lifecycle, overlay state, and result
* formatting so that Main only needs to wire the panel into the layout.
*/
public class MSTPanelController {

private final JPanel panel;
private final JLabel summaryLabel;
private final JLabel statsLabel;
private final JLabel componentsLabel;
private final JButton computeButton;
private final JButton clearButton;

private final Supplier<Graph<String, edge>> graphSupplier;
private final Runnable onOverlayChanged;

private boolean overlayActive;
private final Set<edge> mstEdges = new HashSet<>();

/**
* @param graphSupplier supplies the current graph
* @param onOverlayChanged callback to refresh renderers/visualization after overlay changes
*/
public MSTPanelController(Supplier<Graph<String, edge>> graphSupplier,
Runnable onOverlayChanged) {
this.graphSupplier = graphSupplier;
this.onOverlayChanged = onOverlayChanged;

Font labelFont = new Font("SansSerif", Font.PLAIN, 12);

summaryLabel = new JLabel("<html>Click 'Compute' to find the MST.</html>");
summaryLabel.setFont(labelFont);
summaryLabel.setAlignmentX(JLabel.LEFT_ALIGNMENT);

statsLabel = new JLabel("");
statsLabel.setFont(labelFont);
statsLabel.setAlignmentX(JLabel.LEFT_ALIGNMENT);

componentsLabel = new JLabel("");
componentsLabel.setFont(labelFont);
componentsLabel.setAlignmentX(JLabel.LEFT_ALIGNMENT);

computeButton = new JButton("Compute");
computeButton.setAlignmentX(JButton.LEFT_ALIGNMENT);
computeButton.addActionListener(e -> runComputation());

clearButton = new JButton("Clear");
clearButton.setAlignmentX(JButton.LEFT_ALIGNMENT);
clearButton.addActionListener(e -> clearOverlay());

JPanel buttonPanel = new JPanel();
buttonPanel.setLayout(new BoxLayout(buttonPanel, BoxLayout.X_AXIS));
buttonPanel.setAlignmentX(JPanel.LEFT_ALIGNMENT);
buttonPanel.add(computeButton);
buttonPanel.add(Box.createHorizontalStrut(4));
buttonPanel.add(clearButton);

panel = new JPanel();
panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
panel.setBorder(BorderFactory.createTitledBorder(
BorderFactory.createEtchedBorder(EtchedBorder.LOWERED),
"Minimum Spanning Tree",
TitledBorder.CENTER,
TitledBorder.TOP));
panel.add(summaryLabel);
panel.add(Box.createVerticalStrut(4));
panel.add(buttonPanel);
panel.add(Box.createVerticalStrut(4));
panel.add(statsLabel);
panel.add(Box.createVerticalStrut(4));
panel.add(componentsLabel);
}

public JPanel getPanel() { return panel; }
public boolean isOverlayActive() { return overlayActive; }
public Set<edge> getMstEdges() { return Collections.unmodifiableSet(mstEdges); }

private void runComputation() {
Graph<String, edge> g = graphSupplier.get();
if (g == null || g.getVertexCount() == 0) {
summaryLabel.setText("<html>No graph loaded.</html>");
return;
}

MinimumSpanningTree mstComputer = new MinimumSpanningTree(g);
MinimumSpanningTree.MSTResult result = mstComputer.compute();

overlayActive = true;
mstEdges.clear();
mstEdges.addAll(result.getEdges());

// Summary
summaryLabel.setText("<html><b style='color:#00FF00'>"
+ result.getSummary() + "</b></html>");

// Stats
StringBuilder stats = new StringBuilder("<html>");
stats.append(String.format("<b>Vertices:</b> %d<br/>", result.getVertexCount()));
stats.append(String.format("<b>MST Edges:</b> %d<br/>", result.getEdgeCount()));
stats.append(String.format("<b>Total Weight:</b> %.1f<br/>", result.getTotalWeight()));
stats.append(String.format("<b>Avg Weight:</b> %.1f<br/>", result.getAverageWeight()));

if (result.getHeaviestEdge() != null) {
edge heavy = result.getHeaviestEdge();
stats.append(String.format("<b>Bottleneck:</b> %s\u2194%s (%.1f)<br/>",
heavy.getVertex1(), heavy.getVertex2(), heavy.getWeight()));
}
if (result.getLightestEdge() != null) {
edge light = result.getLightestEdge();
stats.append(String.format("<b>Lightest:</b> %s\u2194%s (%.1f)<br/>",
light.getVertex1(), light.getVertex2(), light.getWeight()));
}

// Edge type distribution
Map<String, Integer> dist = result.getEdgeTypeDistribution();
if (!dist.isEmpty()) {
stats.append("<b>Types:</b> ");
boolean first = true;
for (Map.Entry<String, Integer> entry : dist.entrySet()) {
if (!first) stats.append(", ");
stats.append(getDominantLabel(entry.getKey())).append("=").append(entry.getValue());
first = false;
}
stats.append("<br/>");
}
stats.append("</html>");
statsLabel.setText(stats.toString());

// Component breakdown (for forests)
if (result.getComponentCount() > 1) {
StringBuilder comps = new StringBuilder("<html><b>Components:</b><br/>");
int shown = Math.min(result.getComponents().size(), 6);
for (int i = 0; i < shown; i++) {
MinimumSpanningTree.MSTComponent comp = result.getComponents().get(i);
comps.append(String.format("&nbsp;C%d: %d nodes, %d edges, wt=%.1f",
comp.getId(), comp.getSize(), comp.getEdges().size(), comp.getTotalWeight()));
String dominant = comp.getDominantType();
if (dominant != null) {
comps.append(" (").append(getDominantLabel(dominant)).append(")");
}
comps.append("<br/>");
}
if (result.getComponents().size() > shown) {
comps.append("...and ").append(result.getComponents().size() - shown).append(" more<br/>");
}
comps.append("</html>");
componentsLabel.setText(comps.toString());
} else {
componentsLabel.setText("");
}

panel.revalidate();
panel.repaint();
onOverlayChanged.run();
}

private void clearOverlay() {
overlayActive = false;
mstEdges.clear();
summaryLabel.setText("<html>Click 'Compute' to find the MST.</html>");
statsLabel.setText("");
componentsLabel.setText("");
panel.revalidate();
panel.repaint();
onOverlayChanged.run();
}

private static String getDominantLabel(String typeCode) {
EdgeType type = EdgeType.fromCode(typeCode);
return type != null ? type.getDisplayLabel() : typeCode;
}
}
Loading
Loading