reactive_mind_map 1.0.2 copy "reactive_mind_map: ^1.0.2" to clipboard
reactive_mind_map: ^1.0.2 copied to clipboard

A highly customizable and interactive mind map package for Flutter with multiple layouts, dynamic sizing, and rich styling options.

example/lib/main.dart

import 'package:flutter/material.dart';
import 'package:reactive_mind_map/reactive_mind_map.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Reactive Mind Map Example',
      theme: ThemeData(primarySwatch: Colors.blue, useMaterial3: true),
      home: const MindMapExamplePage(),
      debugShowCheckedModeBanner: false,
    );
  }
}

class MindMapExamplePage extends StatefulWidget {
  const MindMapExamplePage({super.key});

  @override
  State<MindMapExamplePage> createState() => _MindMapExamplePageState();
}

class _MindMapExamplePageState extends State<MindMapExamplePage> {
  MindMapData _currentData = DemoData.flutterDev;
  MindMapStyle _currentStyle = const MindMapStyle();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Reactive Mind Map Package'),
        backgroundColor: Colors.white,
        foregroundColor: Colors.black87,
        elevation: 0,
        actions: [
          IconButton(
            icon: const Icon(Icons.palette),
            onPressed: _showStyleDialog,
            tooltip: '스타일 변경',
          ),
          IconButton(
            icon: const Icon(Icons.data_array),
            onPressed: _showDataDialog,
            tooltip: '데이터 변경',
          ),
        ],
      ),
      body: MindMapWidget(
        data: _currentData,
        style: _currentStyle,
        onNodeTap: (node) {
          ScaffoldMessenger.of(
            context,
          ).showSnackBar(SnackBar(content: Text('탭: ${node.title}')));
        },
        onNodeLongPress: (node) {
          _showNodeDetails(node);
        },
        onNodeDoubleTap: (node) {
          ScaffoldMessenger.of(
            context,
          ).showSnackBar(SnackBar(content: Text('더블 탭: ${node.title}')));
        },
        onNodeExpandChanged: (node, isExpanded) {
          // Debug output removed for production
        },
      ),
    );
  }

  void _showStyleDialog() {
    showDialog(
      context: context,
      builder:
          (context) => AlertDialog(
            title: const Text('스타일 변경'),
            content: SingleChildScrollView(
              child: Column(
                mainAxisSize: MainAxisSize.min,
                children: [
                  _buildLayoutButtons(),
                  const SizedBox(height: 16),
                  _buildShapeButtons(),
                  const SizedBox(height: 16),
                  _buildThemeButtons(),
                ],
              ),
            ),
            actions: [
              TextButton(
                onPressed: () => Navigator.of(context).pop(),
                child: const Text('닫기'),
              ),
            ],
          ),
    );
  }

  Widget _buildLayoutButtons() {
    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        const Text('레이아웃:', style: TextStyle(fontWeight: FontWeight.bold)),
        const SizedBox(height: 8),
        Wrap(
          spacing: 8,
          children:
              MindMapLayout.values.map((layout) {
                return ElevatedButton(
                  onPressed: () {
                    setState(() {
                      _currentStyle = _currentStyle.copyWith(layout: layout);
                    });
                    Navigator.of(context).pop();
                  },
                  style: ElevatedButton.styleFrom(
                    backgroundColor:
                        _currentStyle.layout == layout
                            ? Colors.blue
                            : Colors.grey[300],
                    foregroundColor:
                        _currentStyle.layout == layout
                            ? Colors.white
                            : Colors.black,
                  ),
                  child: Text(_getLayoutName(layout)),
                );
              }).toList(),
        ),
      ],
    );
  }

  Widget _buildShapeButtons() {
    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        const Text('노드 모양:', style: TextStyle(fontWeight: FontWeight.bold)),
        const SizedBox(height: 8),
        Wrap(
          spacing: 8,
          children:
              NodeShape.values.map((shape) {
                return ElevatedButton(
                  onPressed: () {
                    setState(() {
                      _currentStyle = _currentStyle.copyWith(nodeShape: shape);
                    });
                    Navigator.of(context).pop();
                  },
                  style: ElevatedButton.styleFrom(
                    backgroundColor:
                        _currentStyle.nodeShape == shape
                            ? Colors.blue
                            : Colors.grey[300],
                    foregroundColor:
                        _currentStyle.nodeShape == shape
                            ? Colors.white
                            : Colors.black,
                  ),
                  child: Text(_getShapeName(shape)),
                );
              }).toList(),
        ),
      ],
    );
  }

  Widget _buildThemeButtons() {
    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        const Text('테마:', style: TextStyle(fontWeight: FontWeight.bold)),
        const SizedBox(height: 8),
        Wrap(
          spacing: 8,
          children: [
            ElevatedButton(
              onPressed: () {
                setState(() {
                  _currentStyle = const MindMapStyle(); // 기본 테마
                });
                Navigator.of(context).pop();
              },
              child: const Text('기본'),
            ),
            ElevatedButton(
              onPressed: () {
                setState(() {
                  _currentStyle = _currentStyle.copyWith(
                    backgroundColor: Colors.black87,
                    connectionColor: Colors.white54,
                    defaultTextStyle: const TextStyle(
                      color: Colors.white,
                      fontWeight: FontWeight.w600,
                    ),
                  );
                });
                Navigator.of(context).pop();
              },
              child: const Text('다크'),
            ),
            ElevatedButton(
              onPressed: () {
                setState(() {
                  _currentStyle = _currentStyle.copyWith(
                    backgroundColor: const Color(0xFFF0F8FF),
                    defaultNodeColors: [
                      Colors.pink[400]!,
                      Colors.purple[400]!,
                      Colors.indigo[400]!,
                      Colors.cyan[400]!,
                      Colors.teal[400]!,
                      Colors.green[400]!,
                      Colors.lime[400]!,
                      Colors.orange[400]!,
                    ],
                  );
                });
                Navigator.of(context).pop();
              },
              child: const Text('파스텔'),
            ),
          ],
        ),
      ],
    );
  }

  void _showDataDialog() {
    showModalBottomSheet(
      context: context,
      builder:
          (context) => Container(
            padding: const EdgeInsets.all(20),
            child: Column(
              mainAxisSize: MainAxisSize.min,
              children: [
                const Text(
                  '테스트 데이터 선택',
                  style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
                ),
                const SizedBox(height: 16),
                Wrap(
                  spacing: 8,
                  children: [
                    ElevatedButton(
                      onPressed: () {
                        setState(() {
                          _currentData = DemoData.flutterDev;
                        });
                        Navigator.pop(context);
                      },
                      child: const Text('Flutter 개발'),
                    ),
                    ElevatedButton(
                      onPressed: () {
                        setState(() {
                          _currentData = DemoData.businessPlan;
                        });
                        Navigator.pop(context);
                      },
                      child: const Text('사업 계획'),
                    ),
                    ElevatedButton(
                      onPressed: () {
                        setState(() {
                          _currentData = DemoData.generateLargeData(25);
                        });
                        Navigator.pop(context);
                      },
                      child: const Text('25개 노드'),
                    ),
                    ElevatedButton(
                      onPressed: () {
                        setState(() {
                          _currentData = DemoData.generateLargeData(50);
                        });
                        Navigator.pop(context);
                      },
                      child: const Text('50개 노드'),
                    ),
                  ],
                ),
              ],
            ),
          ),
    );
  }

  void _showNodeDetails(MindMapData node) {
    showDialog(
      context: context,
      builder:
          (context) => AlertDialog(
            title: Text(node.title),
            content: Column(
              mainAxisSize: MainAxisSize.min,
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Text('ID: ${node.id}'),
                const SizedBox(height: 8),
                Text('설명: ${node.description}'),
                const SizedBox(height: 8),
                Text('자식 수: ${node.children.length}'),
              ],
            ),
            actions: [
              TextButton(
                onPressed: () => Navigator.of(context).pop(),
                child: const Text('닫기'),
              ),
            ],
          ),
    );
  }

  String _getLayoutName(MindMapLayout layout) {
    switch (layout) {
      case MindMapLayout.right:
        return '오른쪽';
      case MindMapLayout.left:
        return '왼쪽';
      case MindMapLayout.top:
        return '위쪽';
      case MindMapLayout.bottom:
        return '아래쪽';
      case MindMapLayout.radial:
        return '원형';
      case MindMapLayout.horizontal:
        return '좌우';
      case MindMapLayout.vertical:
        return '상하';
    }
  }

  String _getShapeName(NodeShape shape) {
    switch (shape) {
      case NodeShape.roundedRectangle:
        return '둥근 사각형';
      case NodeShape.circle:
        return '원형';
      case NodeShape.rectangle:
        return '사각형';
      case NodeShape.diamond:
        return '다이아몬드';
      case NodeShape.hexagon:
        return '육각형';
      case NodeShape.ellipse:
        return '타원';
    }
  }
}

/// 데모 데이터 클래스
class DemoData {
  static const MindMapData flutterDev = MindMapData(
    id: 'root',
    title: 'Flutter\n개발',
    description: 'Flutter 앱 개발 완벽 가이드',
    color: Color(0xFF2563EB),
    children: [
      MindMapData(
        id: 'ui',
        title: 'UI & 위젯',
        description: 'Flutter의 UI 구성 요소들',
        color: Color(0xFF7C3AED),
        children: [
          MindMapData(
            id: 'stateless',
            title: 'StatelessWidget',
            description: '상태가 없는 위젯',
            color: Color(0xFF9333EA),
          ),
          MindMapData(
            id: 'stateful',
            title: 'StatefulWidget',
            description: '상태를 가지는 위젯',
            color: Color(0xFF9333EA),
          ),
          MindMapData(
            id: 'layout',
            title: 'Layout 위젯',
            description: '레이아웃 위젯들',
            color: Color(0xFF9333EA),
          ),
        ],
      ),
      MindMapData(
        id: 'navigation',
        title: '네비게이션',
        description: '화면 간 이동과 라우팅',
        color: Color(0xFF059669),
        children: [
          MindMapData(
            id: 'basic_nav',
            title: 'Basic Navigation',
            description: '기본 네비게이션',
            color: Color(0xFF10B981),
          ),
          MindMapData(
            id: 'go_router',
            title: 'GoRouter',
            description: '선언적 라우팅',
            color: Color(0xFF10B981),
          ),
        ],
      ),
      MindMapData(
        id: 'state',
        title: '상태 관리',
        description: '앱 상태 관리 패턴들',
        color: Color(0xFFDC2626),
        children: [
          MindMapData(
            id: 'provider',
            title: 'Provider',
            description: 'Provider 패턴',
            color: Color(0xFFEF4444),
          ),
          MindMapData(
            id: 'riverpod',
            title: 'Riverpod',
            description: '강력한 상태 관리',
            color: Color(0xFFEF4444),
          ),
          MindMapData(
            id: 'bloc',
            title: 'BLoC',
            description: 'BLoC 패턴',
            color: Color(0xFFEF4444),
          ),
        ],
      ),
    ],
  );

  static const MindMapData businessPlan = MindMapData(
    id: 'business',
    title: '사업 계획',
    description: '새로운 사업 아이디어 구상',
    color: Color(0xFF059669),
    children: [
      MindMapData(
        id: 'market',
        title: '시장 분석',
        description: '타겟 시장과 경쟁 분석',
        color: Color(0xFF0EA5E9),
        children: [
          MindMapData(id: 'target', title: '타겟 고객', description: '주요 고객층 정의'),
          MindMapData(
            id: 'competition',
            title: '경쟁사 분석',
            description: '주요 경쟁사와 차별점',
          ),
        ],
      ),
      MindMapData(
        id: 'product',
        title: '제품/서비스',
        description: '핵심 제품과 서비스',
        color: Color(0xFF7C3AED),
        children: [
          MindMapData(id: 'features', title: '핵심 기능', description: '주요 기능과 특징'),
          MindMapData(id: 'pricing', title: '가격 전략', description: '가격 모델과 전략'),
        ],
      ),
      MindMapData(
        id: 'marketing',
        title: '마케팅',
        description: '마케팅 전략과 채널',
        color: Color(0xFFDC2626),
        children: [
          MindMapData(
            id: 'channels',
            title: '마케팅 채널',
            description: '온라인/오프라인 채널',
          ),
          MindMapData(id: 'budget', title: '마케팅 예산', description: '예산 배분과 계획'),
        ],
      ),
    ],
  );

  static MindMapData generateLargeData(int nodeCount) {
    final colors = [
      const Color(0xFF7C3AED),
      const Color(0xFF059669),
      const Color(0xFFDC2626),
      const Color(0xFFF59E0B),
      const Color(0xFF7C2D12),
      const Color(0xFF6B21A8),
      const Color(0xFF0EA5E9),
      const Color(0xFF10B981),
      const Color(0xFFEF4444),
    ];

    List<MindMapData> mainCategories = [];
    int remainingNodes = nodeCount - 1;
    int mainCategoryCount = (remainingNodes / 8).ceil().clamp(1, 12);
    int nodesPerCategory = (remainingNodes / mainCategoryCount).floor();

    for (int i = 0; i < mainCategoryCount; i++) {
      int categoryNodes =
          nodesPerCategory + (i < remainingNodes % mainCategoryCount ? 1 : 0);

      List<MindMapData> subNodes = [];
      for (int j = 1; j < categoryNodes && j < 15; j++) {
        subNodes.add(
          MindMapData(
            id: 'node_${i}_$j',
            title: '노드 ${i + 1}-$j',
            description: '카테고리 ${i + 1}의 서브 노드 $j',
            color: colors[i % colors.length].withValues(alpha: 0.8),
          ),
        );
      }

      mainCategories.add(
        MindMapData(
          id: 'category_$i',
          title: '카테고리 ${i + 1}',
          description: '메인 카테고리 ${i + 1}에 대한 설명',
          color: colors[i % colors.length],
          children: subNodes,
        ),
      );
    }

    return MindMapData(
      id: 'root',
      title: '대용량\n데이터',
      description: '$nodeCount개 노드 테스트',
      color: const Color(0xFF2563EB),
      children: mainCategories,
    );
  }
}
17
likes
0
points
102
downloads

Publisher

unverified uploader

Weekly Downloads

A highly customizable and interactive mind map package for Flutter with multiple layouts, dynamic sizing, and rich styling options.

Repository (GitHub)
View/report issues

Topics

#mind-map #visualization #ui #flutter-widget #interactive

License

unknown (license)

Dependencies

cupertino_icons, flutter

More

Packages that depend on reactive_mind_map