Dancing Syntax

Building a Calendar Heatmap with SVG and React.js

Supporting emotional self-awareness through Data Visualization

Joshua Mclean
JavaScript in Plain English
10 min readJul 30, 2020

--

A Calendar — Photo by Nick Hiller on Unsplash.

Introduction

In 2016, I had put my academic plans on hold after my first year and a half of college in order to take care of my younger sibling when our mother was unable to. Some really amazing friends of mine stepped in to help me out and eventually ended up adopting her (and effectively me too!).

After we stabilized, I felt the urge to return learning something other than dance, and I eventually ended up attending Flatiron School. It had been about five years since I had been in a school-like environment, so managing stress and self-care was difficult. My struggles with my awareness around my self-care inspired me to make an app to help people manage their care better.

While pre-planning for my app, I reached out to my friends on Facebook and asked them what they would like and dislike out of a self-care app. I got over 60 responses in minutes. These responses helped to form some user stories for my app. One particular response was:

It would be cool to have some sort of simple data visualization for how you feel every day so you could track it and learn when you need to take more time for yourself. Something as simple as a calendar you color code for feelings like yellow = anxious, etc.

I thought this would be super cool, at the time of reading this suggestion, I didn’t quite know how I would do it, and I dreaded working with dates in programming, but I set out to accomplish it, so let’s do it!

The Fun Part

MVP

In order to make this happen I know I needed to keep track of a few things:

  • A user can pick from a list of feelings
  • Each feeling is mapped to a display color for that feeling
  • A dynamic, functional calendar to display the colors of a feeling on the given day when the feeling was reported

I decided that the minimum viable product, or “skateboard”, version of this would group my feelings into categories of “Needs Satisfied” and “Needs Unsatisfied” → then categories of parent words → then highly specific feeling words. I did it this way to support a user deepening their emotional granularity (read: specific understanding). For example, it’s more useful and specific to report that you felt empowered than confident. I did some research and came away with 217 feeling words to start with.

A portion of the 217 words I collected

I also decided that since I had so many words, I would assign colors to only the two topmost level categories, satisfied and unsatisfied. This will keep things simple and I plan to do more research into better words and mapping colors to feelings in a future refactor.

Environment

I’m using Ruby on Rails as a backend API with PostgreSQL as my database. I’m using React.js for my frontend client.

Preparing the SVG

For my project, I had the great pleasure to work with two designers, Anna Wu and Kendra Jenel. They created this graphic for me:

Ex. 1 Concept Calendar with colored squares to indicate feelings reported

After exporting it from Figma as SVG I edited the SVG to make the cells the same blank fill color and resized the first cell that highlights the current day in Ex. 1. Now it looks like this:

Blank Concept Calendar

From there I went to SVGtoJSX in order to quickly turn this SVG into a React Component. You have to do this because not all SVG properties are valid JSX. For example: stroke-width = valid SVG, strokeWidth = valid JSX.

SVGtoJSX also allows you to toggle the presence of IDs and determine whether you want a functional or class component, as well as memorize and toggle single quotes or double quotes.

After all that, we have a React component to work with.

Currently, the displayed month is an SVG <path> which means that instead of text, it is drawn as a series of attributes telling the client where, how, and what to draw.

<path id="July" d="M160.477 32.18C159.673 32.18 158.929 32.024 158.245 31.712C157.573 31.388 157.021 30.938 156.589 30.362L157.921 28.76C158.617 29.72 159.451 30.2 160.423 30.2C161.731 30.2 162.385 29.426 162.385 27.878V21.362H157.903V19.4H164.725V27.752C164.725 29.228 164.365 30.338 163.645 31.082C162.925 31.814 161.869 32.18 160.477 32.18ZM177.097 22.388V32H174.955V30.776C174.595 31.208 174.145 31.544 173.605 31.784C173.065 32.012 172.483 32.126 171.859 32.126C170.575 32.126 169.561 31.772 168.817 31.064C168.085 30.344 167.719 29.282 167.719 27.878V22.388H169.969V27.572C169.969 28.436 170.161 29.084 170.545 29.516C170.941 29.936 171.499 30.146 172.219 30.146C173.023 30.146 173.659 29.9 174.127 29.408C174.607 28.904 174.847 28.184 174.847 27.248V22.388H177.097ZM180.043 18.644H182.293V32H180.043V18.644ZM194.247 22.388L189.747 32.774C189.327 33.818 188.817 34.55 188.217 34.97C187.617 35.402 186.891 35.618 186.039 35.618C185.559 35.618 185.085 35.54 184.617 35.384C184.149 35.228 183.765 35.012 183.465 34.736L184.365 33.08C184.581 33.284 184.833 33.446 185.121 33.566C185.421 33.686 185.721 33.746 186.021 33.746C186.417 33.746 186.741 33.644 186.993 33.44C187.257 33.236 187.497 32.894 187.713 32.414L187.875 32.036L183.681 22.388H186.021L189.045 29.498L192.087 22.388H194.247Z" fill="black"/>,

In order to control what month is displayed, I made an array of these paths and a function to render that month <path> based on state.

Github gist showing the SVG Calendar Month paths

Refactor point: I’ll likely come back later and make this an object instead with the current month as the key to grab the month path.

Finally, I give all of my calendars’ SVG rectangles a date attribute set to "blank", then I wrap the <rect></rect> nodes into row groups that represent weeks. Then give each row <g></g> an id, and wrap the entire calendar in a group for the month, giving it an id and onClick event listener.

Calendar Functionality

Setup

This first step I took was to define some state to keep track of the current day, the current month, and the current year.

Github gist showing some initial code setup

In each function, I am defining a constant date and setting it equal to new Date() . According to MDN:

JavaScript Date objects represent a single moment in time in a platform-independent format. Date objects contain a Number that represents milliseconds since 1 January 1970 UTC.

As I am writing this it is Wednesday, July 29th so calling new Date() with no parameters gives me a date object for today’s date and the time is the time at which I initiated the Date() call. It displays in a human-readable format showing the GMT offset and my local time zone.

new Date()
//=> Wed Jul 29 2020 13:07:23 GMT-0700 (Pacific Daylight Time)

I’m using the toLocaleString method in the interest of future-proofing this for different timezones, but I’ll likely refactor that down the road.

The next step is to get both the first day of the given month and the number of days in a given month.

I found this blog post on Medium by Nitin Patel which helped me figure out how to get this data (thanks!). Patel is generating their entire calendar using raw CSS and JavaScript, which is super cool so check that out for the extra challenge.

// 1. Get days in given month, for given year.       daysInMonth = (iMonth, iYear) => {
return 32 - new Date(iYear, iMonth, 32).getDate();
}
a. new Date(2020, 0, 32)
//=> Sat Feb 01 2020 00:00:00 GMT-0800 (Pacific Standard Time)
b. new Date(2020, 0, 32).getDate()
//=> 1
c. 32 - 1
//=> 31
//Jan 31st!// 2. Get first day in given month, for given year. let firstDay = (new Date(year, month)).getDay();a. new Date(2020, 0)
//=> Wed Jan 01 2020 00:00:00 GMT-0800 (Pacific Standard Time)
b. (new Date(2020, 0)).getDay();
//=> 3
//Wednesday!

In Function 1, new Date(iYear, iMonth, 32).getDate() returns the numerical day of the 32nd day after the first day of the passed in month. JavaScript counts months start from 0 and the method getDay() returns the numerical day of the week, also starting the count from 0. We subtract that number from 32 and arrive on the final day for the passed in month.

Note: all the functions for setting state and populating the calendar are called within componentDidMount .

componentDidMount(){this.setMonth()this.setYear()this.setCurrent()this.populateCalendar(new Date().getFullYear(), new Date().getMonth())}

Now that I have this initial setup completed, I can move on to populating the calendar with dates.

Loop-de-loop!

Now for the fun part: Loops!

First, I add a ref to my constructor and attach that ref to the <g></g> that represents the month. React refs give us a way of referencing and accessing DOM nodes. You can access the attached node by calling .current on this.namedRef .

constructor(props){
super(props)
this.monthRef = React.createRef()
}
...further down in file<g id="month" ref={this.monthRef} onClick={this.handleClick}>
...Week rows and Day Rectangles
</g>

Now, I’ll define my function that will take in a numerical year and month and define the variables I’ll need to start off my loopalooza. Here’s the code, and there’s a step by step following it.

Github gist showing the code to loop over the calendar nodes.

Step By Step

  1. Iterate with a for loop through the month, cal.current.children will return an HTMLCollection which is our rows representing weeks. Each step of the row iteration will bring us to a new week.
  2. Define a variable representing the current iteration week’s child day <rect></rect> .
  3. Iterate again through the week, define a variable to represent the current iteration day <rect></rect> .
  4. Now that we have iterated over the entire structure, we can start populating the date property on each <rect></rect>.
  5. Check if we are on the first iteration and the second iteration’s numerical value is less than the value of firstDay. If true, continue the iteration. This will leave the date property on each rectangle “blank” until we hit the proper day of the week to start assign values.
  6. Check if the numerical value of the date variable is greater than the returned numerical value of daysInMonth function. If true, we have completed assigning values and we can break from the loops. we will increment date outside our loops
  7. Access the attributes of the currentCell at the current step of the nested loop and update the date value with the value from the date variable.
  8. Check if the value of date ,month , and year are equivalent to today's date in state, if true, then adjust the style of the currentCell to highlight the current day of the month. Since the highlight is a black outline around the rectangle, I have to decrease the size of the rectangle and draw the outline, which will shift my rectangle up 2.5 pixels and left 3 pixels. The last two lines in this block will reposition the cell where it should be properly respective to the other cells.
  9. For our next trick, the part that sends us on to greater things, we will define two variables to store a count of satisfied and unsatisfied category feelings and map over the User’s reported feelings.
  10. Inside the map, define a variable that holds the feeling object that is the result of filtering the list of feelings for specific feeling ID that matches the UserFeeling’s “feeling_id”
  11. Define a variable that reads the UserFeeling’s “created_at” attribute, and get the numerical day number for the date the record was created with .getUTCDate() .

#11 is super important. In my development environment, PostgreSQL will store my “created_at” date as a representation of UTC (Coordinated Universal Time) in the format YYYY-MM-DD HH:MM:SS:MS, but on the JavaScript side, my record gets converted into an ISO8601 format string which looks like this: 2020–07–01T02:32:21.580Z

The “T” Separates the date from the time, the “Z” stands for “Zulu” and indicates the zone designation for the zero offset of UTC time. Check out this example.

Rails/PostGresQL:Records "created_at" stored as UTC:2020-07-01 02:32:21.580076Parse in JS (local time is the default):
new Date(“2020-07-01 02:32:21.580076”)
//=> Wed Jul 01 2020 02:32:21 GMT-0700 (Pacific Daylight Time)
The same Record's "created_at" on the JavaScript side in ISO8601 format:2020–07–01T02:32:21.580ZParse in JS:
new Date(“2020–07–01T02:32:21.580Z”)
//=> Tue Jun 30 2020 19:32:21 GMT-0700 (Pacific Daylight Time)Uh oh! We've lost the offset and it thinks it's the previous daynew Date("2020-07-01T02:32:21.580Z").getDate()//=> 30Fixed!new Date("2020-07-01T02:32:21.580Z").getUTCDate()//=> 1

We need to specify that instead of assuming local time, we want to calculate dates based on UTC so calling .getUTCDate gives us the correct day that is agnostic from timezones.

12. Now that our dates are properly parsed, we can check to see if the currentCell‘s freshly minted date value is equivalent to the “created_at” date for our User’s reported feelings.

13. Use a Ternary to increment either the satisfied or unsatisfied counts for that current day

14. Finally, we can run a check to choose the fill color of the currentCell based on comparing satisfiedCount and unsatisfiedCount. Let’s pop in a debugger and watch this baby work!

Stepping through the iterations to watch the calendar populate.

Conclusion

WE MADE IT! This was really fun and challenging to build. I am particularly happy that I feel armed with a better understanding of how to work with dates in JavaScript. I have some refactors in my future, but I am pleased that it is working for now.

What is something you are proud of building? Any questions? Let me know in the comments!

  1. Date Constructor— MDN
  2. ISO8601 — Wikipedia
  3. Coordinated Universal Time — Wikipedia
  4. GMT vs. UTC — Time and Date
  5. Challenge of building a Calendar with Pure JavaScript Nitin Patel
  6. SVGtoJSX

--

--