05_02_06_diff_visualization - Visualización de Diferencias Temporales¶
🔀 Propósito¶
El Diff Visualization compara grafos entre commits/versions mostrando qué cambió: módulos añadidos, removidos, re-cableados. Responde "¿Cómo evolucionó la arquitectura?" y detecta cambios arquitectónicos significativos.
🏗️ Arquitectura¶
05_02_06_diff_visualization/
├── graph_diff.hpp # Motor de diff
├── test_diff.cpp # Tests
└── README.md # Documentación
Clases Implementadas¶
- GraphDiffCalculator - Detección de cambios
- DiffVisualizer - Renderización visual
- GitDiffIntegration - Integración con git
📊 Tipos de Cambios Detectables¶
Node Changes¶
- ADDED ➕ - Módulo nuevo
- REMOVED ➖ - Módulo eliminado
- MODIFIED 📝 - Propiedades cambiadas
- UNCHANGED - Sin cambios
Edge Changes¶
- ADDED ➕ - Nueva dependencia
- REMOVED ➖ - Dependencia eliminada
- MODIFIED 📝 - Tipo/versión cambiada
Property Changes (MODIFIED)¶
label- Nombre del módulolevel- Cambio de jerarquía (⚠️ grave)category- Cambio de categoríastatus- Estado de desarrollocpu_cycles- Cambio significativo (>20%)latency_samples- Latencia modificada
🔍 Uso Básico¶
Comparar Dos Grafos¶
#include "graph_diff.hpp"
using namespace audio_lab::dependency_graph::diff;
// Load graphs
Graph old_graph = load_from_catalog("catalog_v1.json");
Graph new_graph = load_from_catalog("catalog_v2.json");
// Calculate diff
auto diff = GraphDiffCalculator::calculate(
old_graph,
new_graph,
"v1.5.0", // old version
"v1.6.0" // new version
);
// Summary report
std::cout << diff.summary();
Output:
Graph Diff: v1.5.0 → v1.6.0
Node Changes:
➕ Added: 5
➖ Removed: 2
📝 Modified: 8
Edge Changes:
➕ Added: 12
➖ Removed: 3
📝 Modified: 1
Total changes: 31
Detailed Report¶
Output:
Graph Diff: v1.5.0 → v1.6.0
Node Changes:
➕ Added: 5
➖ Removed: 2
📝 Modified: 8
Nodes Added:
➕ ADDED: new_filter_module (L1_ATOM)
➕ ADDED: experimental_osc (L1_ATOM)
➕ ADDED: advanced_reverb (L2_CELL)
...
Nodes Removed:
➖ REMOVED: legacy_filter (L1_ATOM)
➖ REMOVED: old_delay (L1_ATOM)
Nodes Modified:
📝 MODIFIED: svf_filter
cpu_cycles: 120 → 95
status: STABLE → STABLE
📝 MODIFIED: poly_synth
level: L2_CELL → L3_ENGINE
cpu_cycles: 1800 → 2000
Edge Changes:
➕ EDGE ADDED: new_filter → add_kernel
➕ EDGE ADDED: advanced_reverb → svf_filter
➖ EDGE REMOVED: legacy_filter → mul_kernel
📝 EDGE MODIFIED: poly_synth → reverb_cell
📈 Métricas de Cambio¶
Churn Rate¶
Definición: Tasa de inestabilidad arquitectónica
Fórmula:
Uso:
size_t total = old_graph.get_nodes().size();
double churn = diff.calculate_churn_rate(total);
std::cout << "Churn rate: " << (churn * 100) << "%\n";
Interpretación: - < 5% = Estable, cambios incrementales - 5-15% = Evolución normal - 15-30% = Refactoring significativo - > 30% = Arquitectura inestable
Dependency Volatility¶
Definición: Cambios en el cableado del grafo
Fórmula:
Interpretación: - < 10% = Cableado estable - 10-25% = Re-cableado moderado - > 25% = Reorganización arquitectónica
Impact Radius¶
Definición: Módulos afectados indirectamente
size_t impact_radius = 0;
for (const auto& change : diff.node_changes) {
if (change.type == ChangeType::MODIFIED) {
// Count dependents affected
auto dependents = old_graph.get_dependents(change.node_id);
impact_radius += dependents.size();
}
}
🎨 Modos de Visualización¶
1. Side-by-Side¶
Propósito: Comparar estados antes/después
auto dot = DiffVisualizer::generate_side_by_side_dot(
old_graph,
new_graph,
diff
);
std::ofstream file("diff_side_by_side.dot");
file << dot;
file.close();
// Generate image
system("dot -Tsvg diff_side_by_side.dot -o diff.svg");
Output visual:
┌──────────────┐ ┌──────────────┐
│ v1.5.0 │ │ v1.6.0 │
├──────────────┤ ├──────────────┤
│ [A] ─→ [B] │ │ [A] ─→ [B] │
│ [C] (red) │ │ [D] (green) │
│ [E] ─→ [F] │ │ [E] ─→ [F] │
└──────────────┘ └──────────────┘
Legend:
Red = Removed
Green = Added
Orange = Modified
Gray = Unchanged
2. Overlay¶
Propósito: Ver cambios en contexto del grafo nuevo
auto dot = DiffVisualizer::generate_overlay_dot(new_graph, diff);
std::ofstream file("diff_overlay.dot");
file << dot;
file.close();
Colorización: - 🟢 Verde = Nodos/aristas añadidos - 🔴 Rojo = Removidos (no visible, solo en old) - 🟠 Naranja = Modificados - ⚪ Gris = Sin cambios
Uso: Focus en cambios recientes, contexto del grafo actual
3. Animation (Concepto)¶
Propósito: Transición animada A → B
// D3.js animation concept
nodes.transition()
.duration(1000)
.attr("cx", d => d.new_x)
.attr("cy", d => d.new_y)
.style("fill", d => {
if (d.change === "ADDED") return "green";
if (d.change === "REMOVED") return "red";
if (d.change === "MODIFIED") return "orange";
return "gray";
});
🔗 Git Integration¶
Compare Commits¶
#include "graph_diff.hpp"
auto diff = GitDiffIntegration::compare_commits(
"/path/to/repo",
"abc123f", // old commit
"def456a", // new commit
"catalog.json" // catalog path in repo
);
std::cout << diff.summary();
Internamente ejecuta:
git show abc123f:catalog.json > /tmp/old.json
git show def456a:catalog.json > /tmp/new.json
# Compare graphs
Compare with HEAD¶
auto diff = GitDiffIntegration::compare_with_head(
"/path/to/repo",
"catalog.json"
);
if (diff.has_changes()) {
std::cout << "⚠️ Uncommitted architectural changes:\n";
std::cout << diff.detailed_report();
}
Uso: Pre-commit hook para detectar cambios arquitectónicos
Compare Branches¶
# Via git tags
git show v1.5.0:catalog.json > old.json
git show v1.6.0:catalog.json > new.json
# Compare programmatically
./graph-diff old.json new.json
📊 Casos de Uso Prácticos¶
Caso 1: Release Notes Automation¶
// Generate architectural changes for release notes
auto diff = GitDiffIntegration::compare_commits(
repo_path,
"v1.5.0",
"v1.6.0",
"catalog.json"
);
std::cout << "# Architecture Changes v1.6.0\n\n";
if (diff.nodes_added > 0) {
std::cout << "## New Modules\n";
for (const auto& change : diff.node_changes) {
if (change.type == ChangeType::ADDED) {
std::cout << "- **" << change.new_node->label << "** ("
<< to_string(change.new_node->level) << ")\n";
}
}
std::cout << "\n";
}
if (diff.nodes_removed > 0) {
std::cout << "## Deprecated Modules\n";
for (const auto& change : diff.node_changes) {
if (change.type == ChangeType::REMOVED) {
std::cout << "- ~~" << change.old_node->label << "~~\n";
}
}
std::cout << "\n";
}
std::cout << "## Performance Improvements\n";
for (const auto& change : diff.node_changes) {
if (change.type == ChangeType::MODIFIED) {
for (const auto& prop : change.property_changes) {
if (prop.property_name == "cpu_cycles") {
int old_cpu = std::stoi(prop.old_value);
int new_cpu = std::stoi(prop.new_value);
if (new_cpu < old_cpu) {
double improvement = ((old_cpu - new_cpu) / (double)old_cpu) * 100;
std::cout << "- " << change.new_node->label
<< ": " << improvement << "% faster\n";
}
}
}
}
}
Caso 2: Breaking Change Detection¶
// Detect breaking architectural changes
auto diff = GitDiffIntegration::compare_with_head(repo_path);
std::vector<std::string> breaking_changes;
for (const auto& change : diff.node_changes) {
// Level change = breaking
if (change.type == ChangeType::MODIFIED) {
for (const auto& prop : change.property_changes) {
if (prop.property_name == "level") {
breaking_changes.push_back(
"⚠️ BREAKING: " + change.node_id +
" changed level from " + prop.old_value +
" to " + prop.new_value
);
}
}
}
// Node removed = potentially breaking
if (change.type == ChangeType::REMOVED) {
auto dependents = old_graph.get_dependents(change.node_id);
if (!dependents.empty()) {
breaking_changes.push_back(
"⚠️ BREAKING: Removed " + change.node_id +
" used by " + std::to_string(dependents.size()) + " modules"
);
}
}
}
if (!breaking_changes.empty()) {
std::cout << "🔴 BREAKING CHANGES DETECTED:\n";
for (const auto& bc : breaking_changes) {
std::cout << bc << "\n";
}
exit(1); // Fail CI
}
Caso 3: Architecture Evolution Report¶
// Monthly architecture review
struct ArchEvolution {
std::string version;
double churn_rate;
double volatility;
size_t complexity_delta;
};
std::vector<ArchEvolution> evolution;
// Compare sequential versions
std::vector<std::string> versions = {"v1.3.0", "v1.4.0", "v1.5.0", "v1.6.0"};
for (size_t i = 1; i < versions.size(); ++i) {
auto diff = GitDiffIntegration::compare_commits(
repo_path,
versions[i-1],
versions[i]
);
Graph old_g = load_graph(versions[i-1]);
auto old_stats = old_g.compute_statistics();
ArchEvolution ev;
ev.version = versions[i];
ev.churn_rate = diff.calculate_churn_rate(old_stats.node_count);
ev.volatility = diff.calculate_dependency_volatility(old_stats.edge_count);
ev.complexity_delta = diff.nodes_added - diff.nodes_removed;
evolution.push_back(ev);
}
// Report
std::cout << "Architecture Evolution Trend:\n\n";
std::cout << "Version | Churn | Volatility | Complexity Δ\n";
std::cout << "---------|-------|------------|-------------\n";
for (const auto& ev : evolution) {
std::cout << ev.version << " | "
<< std::fixed << std::setprecision(2)
<< (ev.churn_rate * 100) << "% | "
<< (ev.volatility * 100) << "% | "
<< (ev.complexity_delta > 0 ? "+" : "")
<< ev.complexity_delta << "\n";
}
Caso 4: Refactoring Impact Visualization¶
// Before refactoring: save baseline
Graph baseline = current_graph;
// ... do refactoring ...
// After refactoring: compare
Graph refactored = current_graph;
auto diff = GraphDiffCalculator::calculate(
baseline,
refactored,
"Before Refactoring",
"After Refactoring"
);
std::cout << "Refactoring Impact:\n";
std::cout << diff.summary();
// Visualize changes
auto overlay_dot = DiffVisualizer::generate_overlay_dot(refactored, diff);
render_to_svg(overlay_dot, "refactoring_impact.svg");
// Metrics
double churn = diff.calculate_churn_rate(baseline.get_nodes().size());
if (churn > 0.15) {
std::cout << "⚠️ High churn rate: " << (churn * 100) << "%\n";
std::cout << " Consider smaller refactoring steps\n";
} else {
std::cout << "✅ Refactoring impact acceptable: " << (churn * 100) << "%\n";
}
🧪 Testing¶
TEST_CASE("Detect node addition") {
Graph old_g, new_g;
// Old: A, B
add_nodes(old_g, {"A", "B"});
// New: A, B, C
add_nodes(new_g, {"A", "B", "C"});
auto diff = GraphDiffCalculator::calculate(old_g, new_g);
REQUIRE(diff.nodes_added == 1);
REQUIRE(diff.node_changes[0].node_id == "C");
REQUIRE(diff.node_changes[0].type == ChangeType::ADDED);
}
TEST_CASE("Detect property modification") {
Graph old_g, new_g;
auto old_node = std::make_shared<Node>("A", "Module A", L1_ATOM);
old_node->cpu_cycles = 100;
old_g.add_node(old_node);
auto new_node = std::make_shared<Node>("A", "Module A", L1_ATOM);
new_node->cpu_cycles = 150; // 50% increase
new_g.add_node(new_node);
auto diff = GraphDiffCalculator::calculate(old_g, new_g);
REQUIRE(diff.nodes_modified == 1);
REQUIRE(diff.node_changes[0].has_property_changes());
}
TEST_CASE("Churn rate calculation") {
Graph old_g, new_g;
// Old: 100 nodes
for (int i = 0; i < 100; ++i) {
add_node(old_g, "node_" + std::to_string(i));
}
// New: 95 old + 10 new = 105 total
// Removed: 5, Added: 10
for (int i = 0; i < 95; ++i) {
add_node(new_g, "node_" + std::to_string(i));
}
for (int i = 100; i < 110; ++i) {
add_node(new_g, "node_" + std::to_string(i));
}
auto diff = GraphDiffCalculator::calculate(old_g, new_g);
// Churn = (10 added + 5 removed) / 100 old = 0.15 = 15%
REQUIRE(diff.calculate_churn_rate(100) == Approx(0.15));
}
📈 Complejidad¶
| Operación | Complejidad | Notas |
|---|---|---|
| Node diff | O(V) | Scan ambos grafos |
| Edge diff | O(E) | Build edge maps |
| Property comparison | O(1) | Por nodo |
| Visualization | O(V+E) | Render completo |
| Git integration | O(V+E) + I/O | Load + compare |
🚀 Próximos Pasos¶
✅ Implementado: - Change detection (nodes/edges/properties) - Diff algorithms - 3 modos de visualización - Métricas de cambio - Git integration
⏳ Futuras Mejoras: - Semantic diff (understanding meaningful changes) - Animation support (web UI) - Diff optimization (incremental) - Historical trend analysis - AI-powered change suggestions
Status: ✅ Diff Visualization Completo Change types: 3 (ADDED/REMOVED/MODIFIED) Visualizations: 3 modos (side-by-side/overlay/animation concept) Metrics: Churn rate, volatility, impact radius Git: Full integration