Skip to content

Instantly share code, notes, and snippets.

@JaredEzz
Created January 20, 2026 20:11
Show Gist options
  • Select an option

  • Save JaredEzz/ad2678cc55cec7fbeee2169b5bdb568a to your computer and use it in GitHub Desktop.

Select an option

Save JaredEzz/ad2678cc55cec7fbeee2169b5bdb568a to your computer and use it in GitHub Desktop.
Bloc Refactor Evaluation Metrics
import 'package:flutter/material.dart';
import 'dart:math' as math;
import 'dart:ui' as ui;
// ==============================================================================
// 1. DATA MODELS & MOCK DATA
// ==============================================================================
class BlocMetrics {
final String name;
final String domain; // e.g., "Feature", "Core", "Legacy"
// Categorical Scores (0-100)
final double modularization;
final double dataIntegrity;
final double statePattern;
final double errorHandling;
final double reliability; // Testing coverage
final double decoupling;
// Complexity Magnitude (M)
final double magnitude;
const BlocMetrics({
required this.name,
required this.domain,
required this.modularization,
required this.dataIntegrity,
required this.statePattern,
required this.errorHandling,
required this.reliability,
required this.decoupling,
required this.magnitude,
});
double get avgHealth => (modularization + dataIntegrity + statePattern + errorHandling + reliability + decoupling) / 6;
// The "Misery Index" - How urgent is the refactor?
// High Magnitude + Low Health = High Misery
double get miseryIndex => magnitude * (1 - (avgHealth / 100));
Color get healthColor {
if (avgHealth >= 90) return const Color(0xFF00C853); // A - Perfect
if (avgHealth >= 75) return const Color(0xFF64DD17); // B - Good
if (avgHealth >= 60) return const Color(0xFFFFD600); // C - Warning
if (avgHealth >= 40) return const Color(0xFFFF6D00); // D - Danger
return const Color(0xFFD50000); // F - Critical
}
}
class Snapshot {
final String label;
final List<BlocMetrics> blocs;
Snapshot(this.label, this.blocs);
}
// --- MOCK REPOSITORY ---
class Repository {
static List<Snapshot> getHistory() {
return [
Snapshot("Q3 2025 (Legacy)", [
const BlocMetrics(name: "SimulatorBloc", domain: "Core", magnitude: 580, modularization: 20, dataIntegrity: 30, statePattern: 20, errorHandling: 10, reliability: 15, decoupling: 10),
const BlocMetrics(name: "AssessmentBloc", domain: "Core", magnitude: 420, modularization: 40, dataIntegrity: 40, statePattern: 50, errorHandling: 30, reliability: 20, decoupling: 30),
const BlocMetrics(name: "AuthBloc", domain: "Shared", magnitude: 150, modularization: 80, dataIntegrity: 70, statePattern: 60, errorHandling: 50, reliability: 70, decoupling: 60),
const BlocMetrics(name: "ReportingBloc", domain: "Feature", magnitude: 310, modularization: 30, dataIntegrity: 20, statePattern: 40, errorHandling: 20, reliability: 0, decoupling: 20),
const BlocMetrics(name: "UserProfileBloc", domain: "Feature", magnitude: 90, modularization: 90, dataIntegrity: 85, statePattern: 90, errorHandling: 80, reliability: 90, decoupling: 90),
const BlocMetrics(name: "WizardBloc", domain: "Feature", magnitude: 220, modularization: 50, dataIntegrity: 60, statePattern: 50, errorHandling: 50, reliability: 40, decoupling: 50),
]),
Snapshot("Q4 2025 (Current)", [
const BlocMetrics(name: "SimulatorBloc", domain: "Core", magnitude: 510, modularization: 35, dataIntegrity: 50, statePattern: 30, errorHandling: 20, reliability: 40, decoupling: 25), // Slight improvement
const BlocMetrics(name: "AssessmentBloc", domain: "Core", magnitude: 280, modularization: 75, dataIntegrity: 80, statePattern: 85, errorHandling: 70, reliability: 80, decoupling: 75), // Refactored!
const BlocMetrics(name: "AuthBloc", domain: "Shared", magnitude: 140, modularization: 90, dataIntegrity: 90, statePattern: 85, errorHandling: 90, reliability: 95, decoupling: 90),
const BlocMetrics(name: "ReportingBloc", domain: "Feature", magnitude: 320, modularization: 30, dataIntegrity: 20, statePattern: 40, errorHandling: 20, reliability: 0, decoupling: 20), // Stagnant
const BlocMetrics(name: "UserProfileBloc", domain: "Feature", magnitude: 95, modularization: 95, dataIntegrity: 90, statePattern: 95, errorHandling: 90, reliability: 100, decoupling: 95),
const BlocMetrics(name: "WizardBloc", domain: "Feature", magnitude: 230, modularization: 55, dataIntegrity: 65, statePattern: 55, errorHandling: 55, reliability: 45, decoupling: 55),
const BlocMetrics(name: "NewSearchBloc", domain: "Feature", magnitude: 80, modularization: 100, dataIntegrity: 100, statePattern: 100, errorHandling: 100, reliability: 100, decoupling: 100), // New perfect bloc
]),
];
}
}
// ==============================================================================
// 2. MAIN APP SCAFFOLDING
// ==============================================================================
void main() => runApp(const BlocAuditApp());
class BlocAuditApp extends StatelessWidget {
const BlocAuditApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
theme: ThemeData.dark().copyWith(
scaffoldBackgroundColor: const Color(0xFF0F172A), // Slate 900
cardColor: const Color(0xFF1E293B), // Slate 800
colorScheme: const ColorScheme.dark(
primary: Color(0xFF38BDF8), // Sky 400
secondary: Color(0xFF818CF8), // Indigo 400
surface: Color(0xFF1E293B),
),
),
home: const DashboardLayout(),
);
}
}
class DashboardLayout extends StatefulWidget {
const DashboardLayout({super.key});
@override
State<DashboardLayout> createState() => _DashboardLayoutState();
}
class _DashboardLayoutState extends State<DashboardLayout> {
final List<Snapshot> history = Repository.getHistory();
late Snapshot currentSnapshot;
late Snapshot? previousSnapshot;
int _selectedIndex = 0;
@override
void initState() {
super.initState();
currentSnapshot = history.last;
previousSnapshot = history.first;
}
void _switchSnapshot(Snapshot s) {
setState(() {
// If we pick the first one, prev is null. If we pick last, prev is first.
// Simplified for demo logic.
if (s == history.first) {
currentSnapshot = s;
previousSnapshot = null;
} else {
currentSnapshot = s;
previousSnapshot = history.first;
}
});
}
@override
Widget build(BuildContext context) {
// The View definitions
final views = [
_ViewDef("Priority Heatmap", Icons.dashboard, HeatmapView(currentSnapshot)),
_ViewDef("Quadrant Scatter", Icons.bubble_chart, ScatterView(currentSnapshot)),
_ViewDef("Radar Analysis", Icons.radar, RadarView(currentSnapshot)),
_ViewDef("Progress Delta", Icons.compare_arrows, DeltaView(currentSnapshot, previousSnapshot)),
_ViewDef("Awfulness Ranked", Icons.warning_amber, AwfulnessListView(currentSnapshot)),
_ViewDef("Modularization Tree", Icons.account_tree, TreeView(currentSnapshot)),
_ViewDef("Category Breakdown", Icons.bar_chart, CategoryBreakdownView(currentSnapshot)),
_ViewDef("Historic Trend", Icons.show_chart, TrendLineView(history)),
_ViewDef("Data Grid", Icons.table_chart, DataGridView(currentSnapshot)),
_ViewDef("Grade Distribution", Icons.pie_chart, DistributionView(currentSnapshot)),
];
return Scaffold(
appBar: AppBar(
title: const Text("Birdseye BLoC Auditor"),
elevation: 0,
backgroundColor: const Color(0xFF0F172A),
actions: [
PopupMenuButton<Snapshot>(
initialValue: currentSnapshot,
onSelected: _switchSnapshot,
itemBuilder: (context) => history.map((s) => PopupMenuItem(value: s, child: Text(s.label))).toList(),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: Row(children: [Text(currentSnapshot.label), const Icon(Icons.arrow_drop_down)]),
),
),
],
),
body: Row(
children: [
NavigationRail(
backgroundColor: const Color(0xFF1E293B),
selectedIndex: _selectedIndex,
onDestinationSelected: (int index) => setState(() => _selectedIndex = index),
labelType: NavigationRailLabelType.selected,
destinations: views.map((v) => NavigationRailDestination(
icon: Icon(v.icon),
label: Text(v.title, style: const TextStyle(fontSize: 10)),
)).toList(),
),
const VerticalDivider(thickness: 1, width: 1, color: Colors.white10),
Expanded(
child: Padding(
padding: const EdgeInsets.all(24.0),
child: views[_selectedIndex].widget,
),
),
],
),
);
}
}
class _ViewDef {
final String title;
final IconData icon;
final Widget widget;
_ViewDef(this.title, this.icon, this.widget);
}
// ==============================================================================
// 3. VISUALIZATION IMPLEMENTATIONS
// ==============================================================================
/// --- 1. PRIORITY HEATMAP (Treemap Style) ---
/// Concepts: Size = Magnitude, Color = Health
class HeatmapView extends StatelessWidget {
final Snapshot snapshot;
const HeatmapView(this.snapshot, {super.key});
@override
Widget build(BuildContext context) {
// Sort by magnitude desc
final sorted = List<BlocMetrics>.from(snapshot.blocs)..sort((a, b) => b.magnitude.compareTo(a.magnitude));
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const _Header("Priority Heatmap", "Size = Complexity (Magnitude) | Color = Health Score"),
Expanded(
child: LayoutBuilder(
builder: (context, constraints) {
// Simple "Squarified" Treemap approximation using Wrap for simplicity in pure Flutter
// In a real app, you'd use a proper treemap algo, but this visualized the concept well.
return SingleChildScrollView(
child: Wrap(
spacing: 4,
runSpacing: 4,
children: sorted.map((bloc) {
// Normalizing size to viewport
final areaPercent = bloc.magnitude / 2000; // rough total mag
final boxSize = math.sqrt(constraints.maxWidth * constraints.maxHeight * areaPercent).clamp(60.0, 300.0);
return Tooltip(
message: "${bloc.name}\nMag: ${bloc.magnitude}\nHealth: ${bloc.avgHealth.toInt()}%",
child: Container(
width: boxSize,
height: boxSize,
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: bloc.healthColor.withOpacity(0.8),
borderRadius: BorderRadius.circular(4),
border: Border.all(color: Colors.white24),
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(bloc.name, textAlign: TextAlign.center, style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 12, color: Colors.black87), maxLines: 2, overflow: TextOverflow.ellipsis),
const SizedBox(height: 4),
Text("${bloc.avgHealth.toInt()}%", style: const TextStyle(fontWeight: FontWeight.w900, color: Colors.black54)),
],
),
),
);
}).toList(),
),
);
},
),
),
],
);
}
}
/// --- 2. QUADRANT SCATTER (Bubble Chart) ---
/// X = Magnitude, Y = Health.
/// Top Right = Complex but Good (Gold Standard). Bottom Right = Complex & Bad (Critical).
class ScatterView extends StatelessWidget {
final Snapshot snapshot;
const ScatterView(this.snapshot, {super.key});
@override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const _Header("Compliance vs Complexity", "Y-Axis: Health (High is Good) | X-Axis: Magnitude (Right is Complex)"),
Expanded(
child: Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
border: Border.all(color: Colors.white10),
borderRadius: BorderRadius.circular(12),
color: Colors.black12,
),
child: CustomPaint(
size: Size.infinite,
painter: _ScatterPainter(snapshot.blocs),
),
),
),
],
);
}
}
class _ScatterPainter extends CustomPainter {
final List<BlocMetrics> blocs;
_ScatterPainter(this.blocs);
@override
void paint(Canvas canvas, Size size) {
final paint = Paint()..style = PaintingStyle.fill;
final textPainter = TextPainter(textDirection: TextDirection.ltr);
// Grid
paint.color = Colors.white10;
paint.strokeWidth = 1;
canvas.drawLine(Offset(0, size.height/2), Offset(size.width, size.height/2), paint); // Mid Health
canvas.drawLine(Offset(size.width/2, 0), Offset(size.width/2, size.height), paint); // Mid Mag
// Plot
const maxMag = 600.0;
for (var bloc in blocs) {
final x = (bloc.magnitude / maxMag).clamp(0.0, 1.0) * size.width;
final y = size.height - ((bloc.avgHealth / 100) * size.height);
paint.color = bloc.healthColor.withOpacity(0.6);
canvas.drawCircle(Offset(x, y), 12, paint);
paint.color = Colors.white; // border
paint.style = PaintingStyle.stroke;
canvas.drawCircle(Offset(x, y), 12, paint);
paint.style = PaintingStyle.fill;
// Label
textPainter.text = TextSpan(text: bloc.name, style: const TextStyle(color: Colors.white70, fontSize: 10));
textPainter.layout();
textPainter.paint(canvas, Offset(x + 15, y - 5));
}
}
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) => true;
}
/// --- 3. RADAR ANALYSIS (Spider Chart) ---
/// Compare the 6 axes for a selected Bloc
class RadarView extends StatefulWidget {
final Snapshot snapshot;
const RadarView(this.snapshot, {super.key});
@override
State<RadarView> createState() => _RadarViewState();
}
class _RadarViewState extends State<RadarView> {
BlocMetrics? selected;
@override
void initState() {
super.initState();
selected = widget.snapshot.blocs.first;
}
@override
Widget build(BuildContext context) {
return Column(
children: [
const _Header("Categorical Radar", "Select a BLoC to analyze its specific weaknesses."),
SizedBox(
height: 50,
child: ListView(
scrollDirection: Axis.horizontal,
children: widget.snapshot.blocs.map((b) => Padding(
padding: const EdgeInsets.only(right: 8.0),
child: FilterChip(
label: Text(b.name),
selected: selected == b,
onSelected: (v) => setState(() => selected = b),
),
)).toList(),
),
),
const SizedBox(height: 20),
Expanded(
child: Center(
child: SizedBox(
width: 400,
height: 400,
child: CustomPaint(
painter: _RadarPainter(selected!),
),
),
),
),
],
);
}
}
class _RadarPainter extends CustomPainter {
final BlocMetrics bloc;
_RadarPainter(this.bloc);
@override
void paint(Canvas canvas, Size size) {
final center = Offset(size.width / 2, size.height / 2);
final radius = size.width / 2 * 0.8;
final paint = Paint()..color = Colors.white24..style = PaintingStyle.stroke;
// 6 Axes
final axes = ["Modular", "Data", "State", "Error", "Tests", "Decouple"];
final scores = [bloc.modularization, bloc.dataIntegrity, bloc.statePattern, bloc.errorHandling, bloc.reliability, bloc.decoupling];
// Draw Web
for (int i = 1; i <= 4; i++) {
canvas.drawCircle(center, radius * (i / 4), paint);
}
final path = Path();
for (int i = 0; i < 6; i++) {
final angle = (i * 60 - 90) * (math.pi / 180);
final x = center.dx + radius * math.cos(angle);
final y = center.dy + radius * math.sin(angle);
canvas.drawLine(center, Offset(x, y), paint);
// Draw Label
final tp = TextPainter(text: TextSpan(text: axes[i], style: const TextStyle(color: Colors.white54, fontSize: 12)), textDirection: TextDirection.ltr);
tp.layout();
tp.paint(canvas, Offset(center.dx + (radius + 20) * math.cos(angle) - tp.width/2, center.dy + (radius + 20) * math.sin(angle) - tp.height/2));
// Path calc
final scoreRadius = radius * (scores[i] / 100);
final sx = center.dx + scoreRadius * math.cos(angle);
final sy = center.dy + scoreRadius * math.sin(angle);
if (i == 0) path.moveTo(sx, sy); else path.lineTo(sx, sy);
}
path.close();
// Fill Shape
paint.color = bloc.healthColor.withOpacity(0.3);
paint.style = PaintingStyle.fill;
canvas.drawPath(path, paint);
paint.color = bloc.healthColor;
paint.strokeWidth = 2;
paint.style = PaintingStyle.stroke;
canvas.drawPath(path, paint);
}
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) => true;
}
/// --- 4. PROGRESS DELTA (Comparison) ---
class DeltaView extends StatelessWidget {
final Snapshot current;
final Snapshot? previous;
const DeltaView(this.current, this.previous, {super.key});
@override
Widget build(BuildContext context) {
if (previous == null) return const Center(child: Text("Select a different snapshot to compare against first quarter."));
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_Header("Refactor Impact", "Comparing ${previous!.label} vs ${current.label}"),
Expanded(
child: ListView.builder(
itemCount: current.blocs.length,
itemBuilder: (context, index) {
final cur = current.blocs[index];
// Find matching prev
final prev = previous!.blocs.firstWhere((p) => p.name == cur.name, orElse: () => cur);
final delta = cur.avgHealth - prev.avgHealth;
return Card(
margin: const EdgeInsets.only(bottom: 8),
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Row(
children: [
SizedBox(width: 150, child: Text(cur.name, style: const TextStyle(fontWeight: FontWeight.bold))),
Expanded(
child: Column(
children: [
Stack(
children: [
Container(height: 8, color: Colors.white10),
FractionallySizedBox(widthFactor: prev.avgHealth / 100, child: Container(height: 8, color: Colors.grey)),
FractionallySizedBox(widthFactor: cur.avgHealth / 100, child: Container(height: 8, color: cur.healthColor.withOpacity(0.5))),
],
)
],
),
),
const SizedBox(width: 20),
Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4),
decoration: BoxDecoration(
color: delta > 0 ? Colors.green.withOpacity(0.2) : (delta < 0 ? Colors.red.withOpacity(0.2) : Colors.grey.withOpacity(0.2)),
borderRadius: BorderRadius.circular(12)
),
child: Text(
"${delta > 0 ? '+' : ''}${delta.toStringAsFixed(1)}%",
style: TextStyle(color: delta > 0 ? Colors.green : (delta < 0 ? Colors.red : Colors.grey), fontWeight: FontWeight.bold)
),
)
],
),
),
);
},
),
)
],
);
}
}
/// --- 5. AWFULNESS RANKING ---
class AwfulnessListView extends StatelessWidget {
final Snapshot snapshot;
const AwfulnessListView(this.snapshot, {super.key});
@override
Widget build(BuildContext context) {
final sorted = List<BlocMetrics>.from(snapshot.blocs)..sort((a, b) => b.miseryIndex.compareTo(a.miseryIndex));
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const _Header("Refactor Priority Queue", "Sorted by 'Misery Index' (Size * Inverted Health)"),
Expanded(
child: ListView.separated(
itemCount: sorted.length,
separatorBuilder: (_, __) => const Divider(height: 1),
itemBuilder: (context, index) {
final b = sorted[index];
return ListTile(
leading: CircleAvatar(
backgroundColor: index < 3 ? Colors.redAccent : Colors.grey[800],
child: Text("${index + 1}", style: const TextStyle(color: Colors.white)),
),
title: Text(b.name, style: const TextStyle(fontWeight: FontWeight.bold)),
subtitle: Text("Mag: ${b.magnitude.toInt()} | Health: ${b.avgHealth.toInt()}%"),
trailing: Text("Score: ${b.miseryIndex.toInt()}", style: const TextStyle(fontSize: 18, fontWeight: FontWeight.w900, color: Colors.white54)),
);
},
),
),
],
);
}
}
/// --- 6. MODULARIZATION TREE (Visual Hierarchy) ---
/// Simulates how blocs break down.
class TreeView extends StatelessWidget {
final Snapshot snapshot;
const TreeView(this.snapshot, {super.key});
@override
Widget build(BuildContext context) {
// Group by Domain
final domains = <String, List<BlocMetrics>>{};
for (var b in snapshot.blocs) {
domains.putIfAbsent(b.domain, () => []).add(b);
}
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const _Header("Architecture Tree", "Visualizing Domain clusters."),
Expanded(
child: SingleChildScrollView(
child: Column(
children: domains.entries.map((entry) {
return ExpansionTile(
initiallyExpanded: true,
leading: const Icon(Icons.folder_open, color: Colors.blueGrey),
title: Text(entry.key, style: const TextStyle(fontWeight: FontWeight.bold)),
children: entry.value.map((b) => ListTile(
contentPadding: const EdgeInsets.only(left: 40, right: 20),
leading: Icon(Icons.extension, color: b.healthColor),
title: Text(b.name),
trailing: SizedBox(
width: 60,
child: LinearProgressIndicator(value: b.avgHealth / 100, color: b.healthColor, backgroundColor: Colors.white10),
),
)).toList(),
);
}).toList(),
),
),
),
],
);
}
}
/// --- 7. CATEGORY BREAKDOWN (Aggregate) ---
class CategoryBreakdownView extends StatelessWidget {
final Snapshot snapshot;
const CategoryBreakdownView(this.snapshot, {super.key});
@override
Widget build(BuildContext context) {
// Calculate averages per category
double avg(double Function(BlocMetrics) selector) {
return snapshot.blocs.map(selector).reduce((a, b) => a + b) / snapshot.blocs.length;
}
final data = [
MapEntry("Modularization", avg((b) => b.modularization)),
MapEntry("Data Integrity", avg((b) => b.dataIntegrity)),
MapEntry("State Pattern", avg((b) => b.statePattern)),
MapEntry("Error Handling", avg((b) => b.errorHandling)),
MapEntry("Reliability", avg((b) => b.reliability)),
MapEntry("Decoupling", avg((b) => b.decoupling)),
];
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const _Header("Team Weaknesses", "Aggregate scores across the entire codebase."),
Expanded(
child: Padding(
padding: const EdgeInsets.only(top: 20.0),
child: ListView(
children: data.map((e) => Padding(
padding: const EdgeInsets.only(bottom: 16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(e.key, style: const TextStyle(fontSize: 16)),
Text("${e.value.toInt()}%", style: const TextStyle(fontWeight: FontWeight.bold)),
],
),
const SizedBox(height: 8),
ClipRRect(
borderRadius: BorderRadius.circular(4),
child: LinearProgressIndicator(
value: e.value / 100,
minHeight: 12,
backgroundColor: Colors.white10,
color: e.value > 80 ? Colors.green : (e.value > 60 ? Colors.yellow : Colors.red),
),
)
],
),
)).toList(),
),
),
),
],
);
}
}
/// --- 8. HISTORIC TREND ---
class TrendLineView extends StatelessWidget {
final List<Snapshot> history;
const TrendLineView(this.history, {super.key});
@override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const _Header("Quality Velocity", "Average Codebase Health over Time"),
Expanded(
child: Center(
child: Container(
height: 300,
width: double.infinity,
padding: const EdgeInsets.all(20),
child: CustomPaint(
painter: _TrendPainter(history),
),
),
),
),
],
);
}
}
class _TrendPainter extends CustomPainter {
final List<Snapshot> history;
_TrendPainter(this.history);
@override
void paint(Canvas canvas, Size size) {
final paint = Paint()..color = Colors.blue..strokeWidth = 4..style = PaintingStyle.stroke;
final dotPaint = Paint()..color = Colors.white;
final path = Path();
double getAvg(Snapshot s) => s.blocs.map((b) => b.avgHealth).reduce((a, b) => a + b) / s.blocs.length;
final points = <Offset>[];
for (int i = 0; i < history.length; i++) {
final x = (size.width / (history.length + 1)) * (i + 1);
final y = size.height - ((getAvg(history[i]) / 100) * size.height);
points.add(Offset(x, y));
if (i == 0) path.moveTo(x, y); else path.lineTo(x, y);
}
canvas.drawPath(path, paint);
for (var p in points) {
canvas.drawCircle(p, 6, dotPaint);
}
}
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) => true;
}
/// --- 9. DATA GRID ---
class DataGridView extends StatelessWidget {
final Snapshot snapshot;
const DataGridView(this.snapshot, {super.key});
@override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const _Header("Raw Metrics", "Deep dive audit view."),
Expanded(
child: SingleChildScrollView(
scrollDirection: Axis.vertical,
child: SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: DataTable(
headingRowColor: MaterialStateProperty.all(Colors.white10),
columns: const [
DataColumn(label: Text("Name")),
DataColumn(label: Text("Total Health")),
DataColumn(label: Text("Mag")),
DataColumn(label: Text("Modular")),
DataColumn(label: Text("Data")),
DataColumn(label: Text("State")),
DataColumn(label: Text("Error")),
DataColumn(label: Text("Reliability")),
],
rows: snapshot.blocs.map((b) => DataRow(cells: [
DataCell(Text(b.name, style: const TextStyle(fontWeight: FontWeight.bold))),
DataCell(Text("${b.avgHealth.toInt()}%", style: TextStyle(color: b.healthColor, fontWeight: FontWeight.bold))),
DataCell(Text(b.magnitude.toInt().toString())),
DataCell(Text(b.modularization.toInt().toString())),
DataCell(Text(b.dataIntegrity.toInt().toString())),
DataCell(Text(b.statePattern.toInt().toString())),
DataCell(Text(b.errorHandling.toInt().toString())),
DataCell(Text(b.reliability.toInt().toString())),
])).toList(),
),
),
),
),
],
);
}
}
/// --- 10. GRADE DISTRIBUTION ---
class DistributionView extends StatelessWidget {
final Snapshot snapshot;
const DistributionView(this.snapshot, {super.key});
@override
Widget build(BuildContext context) {
int a = 0, b = 0, c = 0, d = 0, f = 0;
for (var bloc in snapshot.blocs) {
if (bloc.avgHealth >= 90) a++;
else if (bloc.avgHealth >= 75) b++;
else if (bloc.avgHealth >= 60) c++;
else if (bloc.avgHealth >= 40) d++;
else f++;
}
final max = [a,b,c,d,f].reduce(math.max);
Widget bar(String label, int count, Color color) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 8.0),
child: Row(
children: [
SizedBox(width: 30, child: Text(label, style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 18))),
const SizedBox(width: 10),
Expanded(
child: Stack(
children: [
Container(height: 40, color: Colors.white10),
FractionallySizedBox(
widthFactor: count / (max == 0 ? 1 : max),
child: Container(height: 40, color: color),
),
Positioned.fill(child: Align(alignment: Alignment.centerLeft, child: Padding(
padding: const EdgeInsets.only(left: 10.0),
child: Text("$count Blocs", style: const TextStyle(color: Colors.white, fontWeight: FontWeight.bold)),
)))
],
),
)
],
),
);
}
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const _Header("Grade Distribution", "How many Blocs are passing?"),
const SizedBox(height: 20),
bar("A", a, const Color(0xFF00C853)),
bar("B", b, const Color(0xFF64DD17)),
bar("C", c, const Color(0xFFFFD600)),
bar("D", d, const Color(0xFFFF6D00)),
bar("F", f, const Color(0xFFD50000)),
],
);
}
}
class _Header extends StatelessWidget {
final String title;
final String subtitle;
const _Header(this.title, this.subtitle);
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.only(bottom: 20.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(title, style: const TextStyle(fontSize: 24, fontWeight: FontWeight.w300)),
Text(subtitle, style: const TextStyle(color: Colors.white54)),
const Divider(height: 30),
],
),
);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment