import 'package:flutter/material.dart'; import 'package:syncrow_web/pages/analytics/modules/occupancy/widgets/heat_map_tooltip.dart'; import 'package:syncrow_web/pages/analytics/modules/occupancy/widgets/occupancy_painter.dart'; class InteractiveHeatMap extends StatefulWidget { const InteractiveHeatMap({ required this.items, required this.maxValue, required this.cellSize, super.key, }); final List items; final int maxValue; final double cellSize; @override State createState() => _InteractiveHeatMapState(); } class _InteractiveHeatMapState extends State { OccupancyPaintItem? _hoveredItem; OverlayEntry? _overlayEntry; final LayerLink _layerLink = LayerLink(); @override void dispose() { _removeOverlay(); _overlayEntry?.dispose(); super.dispose(); } void _removeOverlay() { _overlayEntry?.remove(); _overlayEntry = null; } void _showTooltip(OccupancyPaintItem item, Offset localPosition) { _removeOverlay(); final column = item.index ~/ 7; final row = item.index % 7; final x = column * widget.cellSize; final y = row * widget.cellSize; _overlayEntry = OverlayEntry( builder: (context) => Positioned( child: CompositedTransformFollower( link: _layerLink, offset: Offset(x + widget.cellSize, y), child: Material( color: Colors.transparent, child: Transform.translate( offset: Offset(-(widget.cellSize * 2.5), -50), child: HeatMapTooltip(date: item.date, value: item.value), ), ), ), ), ); Overlay.of(context).insert(_overlayEntry!); } @override Widget build(BuildContext context) { return CompositedTransformTarget( link: _layerLink, child: MouseRegion( onHover: (event) { final column = event.localPosition.dx ~/ widget.cellSize; final row = event.localPosition.dy ~/ widget.cellSize; final index = column * 7 + row; if (index >= 0 && index < widget.items.length) { final item = widget.items[index]; if (_hoveredItem != item) { setState(() => _hoveredItem = item); _showTooltip(item, event.localPosition); } } else { _removeOverlay(); setState(() => _hoveredItem = null); } }, onExit: (_) { _removeOverlay(); setState(() => _hoveredItem = null); }, child: CustomPaint( isComplex: true, size: _painterSize, painter: OccupancyPainter( items: widget.items, maxValue: widget.maxValue, hoveredItem: _hoveredItem, ), ), ), ); } Size get _painterSize { final height = 7 * widget.cellSize; final width = widget.items.length ~/ 7 * widget.cellSize; return Size(width, height); } }