Create Dynamically Sized Squares

108

I would add a comment but I do not have enough reputation so sorry if this is not the answer you are looking for

You could use something like this

double width = MediaQuery.of(context).size.width; // gives width of device screen
double height = MediaQuery.of(context).size.height; // gives height of device screen

// if the card has padding 
double cardLeftPadding = a double;
double cardRightPadding = a double;


width -= (cardLeftPadding + cardRightPadding);
Container(
// ???HOW DO I AVOID THIS EXPLICIT NUMERIC CONTAINER SIZE???
          height: 3,
          width: width,
          decoration: const BoxDecoration(
            color: Colors.black12,
          ),),

I believe something like this will allow you to fit your heat map to the full length of your card

Share:
108
Corey Stewart
Author by

Corey Stewart

Updated on January 05, 2023

Comments

  • Corey Stewart
    Corey Stewart over 1 year

    I'm attempting to create a GitHub style heat map inside a Card and am struggling with the UI. The challenge is making the heat map dynamically expand to fit the Card it sits in based on the device's screen size.

    Here is an example screenshot.

    enter image description here

    The code to create the screenshot is below.

    Essentially the code,

    • creates a column that starts with two lines of text
    • then inserts a Row of Columns that consist of squares

    I'm not sure if I should focus on making the individual boxes expand, the columns that the individual boxes sit in, or both. All my experiments end in unbound errors. I'm not sure where/how to add the constraints.

    I also assume I'll need the boxes to be wrapped in AspectRatio() to keep the 1:1 ratio and be a square.

    (I've removed some of the the more verbose business logic in my actual code for simplicity.)

    class ProfileView extends StatelessWidget {
      const ProfileView({Key? key}) : super(key: key);
    
      List<Widget> _heatMapColumnList() {
        final _columns = <Widget>[];
        final _startDate = DateTime.now().subtract(const Duration(days: 365));
        final _endDate = DateTime.now();
        final _dateDifference = _endDate.difference(_startDate).inDays;
    for (var index = 0 - (_startDate.weekday % 7);
        index <= _endDate.difference(_startDate).inDays;
        index += 7) {
      //helper to change date by index
      final _firstDay = DateUtility.changeDay(_startDate, index);
    
      _columns.add(
        HeatMapColumn(
          startDate: _firstDay,
          endDate: index <= _dateDifference - 7
              ? DateUtility.changeDay(_startDate, index + 6)
              : _endDate,
          numDays: min(_endDate.difference(_firstDay).inDays + 1, 7),
        ),
      );
    }
    return _columns;
      }
    
      @override
      Widget build(BuildContext context) {
        return Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Padding(
              padding: const EdgeInsets.symmetric(horizontal: 12),
              child: Card(
                elevation: 1,
                child: Padding(
                  padding: const EdgeInsets.all(12),
                  child: Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: <Widget>[
                      const Text('Some Title Text'),
                      const Text('More SubTitle Text'),
                      const SizedBox(height: 10),
                      Row(
                        children: <Widget>[
                          ..._heatMapColumnList(),
                        ],
    ...
    

    ...

     class HeatMapColumn extends StatelessWidget {
          HeatMapColumn({
            super.key,
            required this.startDate,
            required this.endDate,
            required this.numDays,
          })  : dayContainers = List.generate(
                  numDays,
                  (i) => HeatMapBox(
                    date: DateUtility.changeDay(startDate, 1),
                  ),
                ),
                emptySpace = (numDays != 7)
                    ? List.generate(
                        7 - numDays,
                        (i) => const HeatMapBox(
                          date: null,
                        ),
                      )
                    : [];
        
          final List<Widget> dayContainers;
          final List<Widget> emptySpace;
        
          final DateTime startDate;
          final DateTime endDate;
        
          final int numDays;
        
          @override
          Widget build(BuildContext context) {
            return Padding(
              padding: const EdgeInsets.symmetric(vertical: 8),
              child: Column(
                children: <Widget>[
                  ...dayContainers,
                  ...emptySpace,
                ],
    

    ...

    // !!!THIS IS THE BOX I WANT TO DYNAMICALLY RESIZE!!!
    class HeatMapBox extends StatelessWidget {
      const HeatMapBox({
        required this.date,
        this.color,
        super.key,
      });
    
      final DateTime? date;
      final Color? color;
    
      @override
      Widget build(BuildContext context) {
        return Padding(
          padding: const EdgeInsets.all(1),
          child: SizedBox(
            child: Container(
    // ???HOW DO I AVOID THIS EXPLICIT NUMERIC CONTAINER SIZE???
              height: 3,
              width: 3,
              decoration: const BoxDecoration(
                color: Colors.black12,
              ),
            ),
          ),
        );
      }
    }
    
    • pskink
      pskink almost 2 years
      why not just to draw those small rects with CustomPainter?
    • Corey Stewart
      Corey Stewart almost 2 years
      I watched a video on it but do not see it's relevance. Care to tell me why I should? And perhaps answer your own question about why I wouldn't?
  • Corey Stewart
    Corey Stewart almost 2 years
    Here I am trying to find a way to get widgets to dynamically resize and the simple answer was to follow your lead with simple math. Solution was (MediaQuery's width - (horizontal Card padding * 2)) / 52 being assigned to the the width AND height to create squares. 52 corresponds with the number of squares in each row (weeks in a year). Thanks @Kyle
  • Kyle Lynch
    Kyle Lynch almost 2 years
    Thats awesome to hear man! I am glad I could help.