[Userscript] Wanikani Heatmap

:warning: This is a third-party script/app and is not created by the WaniKani team. By using this, you understand that it can stop working at any time or be discontinued indefinitely.

What is Wanikani Heatmap

This script adds a so called heatmap to your dashboard, which displays how many reviews and lessons you did on each day. The reviews heatmap additionally features a forecast for upcoming reviews. Along with the heat maps you also get a number of interesting stats, such as how many days you have studied, your daily average, and your streak.

The look and feel of the heatmaps is very customizable. See the settings section for more detail.

A lot of this info can be hovered over to display some additional or more detailed info. The time spent on reviews, for example, can also show you how much time you have spent in the last year, month, week, or day. If you hover over a day it will tell you the exact number of items you did on that date. It will also tell you if you burned any items on that day.

You may notice that some days in the above screenshots have white borders around them. These are the dates where you leveled up. If you hover on these days it will also tell you what level you reached that day.

If you click on a date, you will also be provided with specific information on what you did that day. This will tell you the levels of the items, the SRS, types, and how well your reviews went. The items you learned or did reviews for are also displayed, and if you hover over them you can see detailed information about that item.

The script will not show any review data from before August 2017 since Wanikani did not log reviews before that. All lessons will be shown, however, as long as you have not reset them. If you have reset before then the script can recover lost lesson data by looking at your review history and guessing when the lesson was done, but only for resets after August 2017.


There are plenty of settings available for this script


  • Position: This determines where the heatmap should be inserted into the page. Try out the different positions and see which you like best

  • Start Date: This allows you to set a manual start date. The script will ignore any data before this date. This is useful for users who went away for years then came back and want a fresh start

  • First Day Of The Week: Determines which day of the week should be on top. Each row of a year represents a different weekday. Default is Monday because Monday is obviously the correct choice upside_down_face

  • New Day Starts At: This setting allows you to determine when a new day starts. If you set it to 4, then the new day will start at 4 AM on the following date. This is useful if you stay up late and want your reviews to count towards the day before.

  • Session Time Limit: This determines how long you can go between review or lesson answers that it should still count to the same session. This has bearing on your total number of sessions as well as your time spent.

  • Theme: Mainly used to change the background between light and dark, but there is also a Breeze Dark theme which changes the color of the items in the pop-up.

  • Reverse Year Order: If you want the most recent year on the bottom instead of the top, this setting is for you.

  • Segment Year: Put spaces between the months to clearly show when one ends and another starts.

  • No Gap: Removes the gap between dates.

  • Day Of Week Labels: Whether you want the day of week labels displayed?!

  • Month Labels: Month labels offers three different settings. Only on top, which… Only displays the month labels on the topmost year. All, which… Displays them on all years. And none… Which displays them twice each year.

  • Current Day Indicator: Puts a border with a color of your choosing around the current day. Fancy.

  • Level-up Indicator: Puts a border with a color of your choosing around the dates of your level ups! Incredible!

Reviews & Lessons

  • Auto Range: Let the script determine what the bounds of your intervals should be using quantiles.

  • Use Gradients: Let days take on any color between your chosen colors depending on the actual number of items you did that specific day.

Let me explain the intervals. To the left you have your interval bounds. These determine at what number of items the color on the right should start to be used. In the screenshot above, you can see that days between 1 and 64 items should have the color \definecolor{c}{rgb}{0.68, 0.895, 1}\color{c}\text{#ADE4FF}. And then to the right there is a trash can for removing intervals.

  • Add Interval: This button adds an interval to your intervals! Amazing! Don’t forget to choose an interval bound and color.

  • Generate Colors: This button generates new colors for your intervals through interpolation between your first and last non-zero interval.

  • Reload Review Data: In case anything goes wrong you can choose to clear the review cache and fetch all your reviews anew.

  • Include Zeros In Streak: If you don’t want your lesson streak to be broken by not having any lessons to do, then tick this boxxy box. This setting makes days without any lessons available count towards your streak.

  • Recover Reset Lessons: If you have ever reset then your lessons heatmap will be missing some data. This is due to WK deleting lesson data for all items that you reset. Ticking this box allows the Heatmap to guess when you did those lessons based on your review history, and thus restore reset lessons to your heatmap.

For more heatmap screenshots, have a look at this thread

Available at

the local ice cream stand


Nice script. You should add a note that WKOF is a prerequisite (and what it is/link) and is not bundled in the heatmap script, and if possible add a notification to the heatmap script that the WKOF is missing (dialog or whatever).

It will be easier for first-time users.


Maybe follow WK colors (from apprentice, guru, master…)


I have been tricked (ಥ_ʖಥ)


I thought I did
You didn’t get an alert?

// Make sure WKOF is installed
		if (!wkof) {
				var response = confirm('WaniKani Heatmap requires WaniKani Open Framework.\n Click "OK" to be forwarded to installation instructions.');
				if (response) window.location.href = 'https://community.wanikani.com/t/instructions-installing-wanikani-open-framework/28549';

I did try that, but it got rather messy. I think having a spectrum is more intuitive. Thanks for the suggestion though. I don’t really like the current colors.


Nope, no window.

I am not exactly sure how Tampermonkey works in Firefox, but the script itself is executed in the strict mode. In strict mode, they did some changes with the strict mode and undeclared variables… not really sure what (I am not a JS developer), the code itself is run in the eval() (or apply() or something).

Anyway, when I debug the code, once I reach the if (!wkof), the ReferenceError is thrown, unless I enable the WKOF in the Tapermonkey dashboard.

I guess the reason is same as why this (fix quotes, not sure why are they changed in the post):
(function() {“use strict”; eval(“if (!wk) { console.log(1);} else {console.log(2);}”); }())

throw ReferenceError: wk is not defined when I run it in the console.

Huh. I didn’t know about that. @rfindley do you know if there’s a good reason why Firefox would throw a ReferenceError? Should we maybe use a try-catch instead of if (!wkof)?

It’s wkof, not wk. Does it work with wkof?

Thanks so much for this! It’s really interesting to see.


Could change it to:

if (!window.wkof) ...

Or don’t turn on strict in your script (though I don’t know whether there’s a setting to make it default to strict, in which case some people may still have problems.).


I don’t have 'use strict' in the script, so I would assume it’s something on their end. I’ll use just put var wkof = window.wkof in my scripts from now on

1 Like

Awesome!! Looks very nice, thank you!
I love my review heatmap in Anki. I was not sooo excited about getting it for WaniKani since I could imagine what it would look like anyway (who doesn’t painfully remember when you had those few weeks where you didn’t review) but now that I see it, I love it :slight_smile:

In case you are interested in extending this further, here’s a screenshot of the info that the Anki plugin shows below the heatmap. This is also super motivating, I look at it even more than at the actual heatmap:


Sounds like good info for an update at some point ukKr5


I couldn’t wait :smiley:

Added no styling, just quickly threw something together. In case you want to reuse it. It is called in the same way as create_heatmap:

    function create_streakinfo(data) {
        var streakinfoSection = document.createElement('section');
        $(streakinfoSection).attr("class", "streakinfo");

        var daysWithReviews = 0;
        var daysWithoutReviews = 0;
        var longestStreak = 0;
        var totalReviews = 0;

        //get date without time (00:00) in order to match date in data
        let firstDate = new Date(new Date(data.first_date).getFullYear(), new Date(data.first_date).getMonth(), new Date(data.first_date).getDate());
        let lastDate = new Date(new Date(data.last_date).getFullYear(), new Date(data.last_date).getMonth(), new Date(data.last_date).getDate());

        var currentDate = firstDate;
        var currentStreak = 0;
        var previousDayHadReviews = false;

        while (currentDate <= lastDate) {
            var dateInSeconds = currentDate.getTime() / 1000;
            var count = data.counts[dateInSeconds];
            if (count) {
                if (previousDayHadReviews) {
                totalReviews += count;
                previousDayHadReviews = true;
            } else {
                if (currentStreak > longestStreak) {
                    longestStreak = currentStreak;
                currentStreak = 0;
                previousDayHadReviews = false;
            currentDate.setDate(currentDate.getDate() + 1);
        if (currentStreak > longestStreak) longestStreak = currentStreak;
        let averageReviews = Math.round(totalReviews / (daysWithReviews + daysWithoutReviews));
        let percentageWithReviews = Math.round(daysWithReviews / ((daysWithReviews + daysWithoutReviews) / 100));

        streakinfoSection.append("Average daily reviews: " + averageReviews + " Studied on " + percentageWithReviews + "% of days (" + daysWithReviews + " out of " + (daysWithReviews + daysWithoutReviews) + ") Longest streak: " + longestStreak + " Current streak: " + currentStreak );

(I also didn’t double check super duper carefully so the calculation might be a bit off; it “felt right” and I decided I was done :wink: )


It looks super cool! Small idea, it might be fun to see when you had WaniKani on vacation mode. Is it possible to add that?


I pretty much never use strict mode, and I’d still expect it to throw an error if it doesn’t have “window.” in front and the variable hasn’t been declared or assigned yet. That’s just normal JavaScript. Maybe greasemonkey/tampermonkey handles that differently somehow?

1 Like

I like it! And the colors seem fine to me.

Minor thing: it time travels.


Seems like you wouldn’t want that to throw an error if the state of being undefined was one of the conditions you were trying to test. How would you get a false? or true for if-not, ykwim


Nice script Kumi! For colors, maybe yellow to dark red? It’s is a heat map after all.

This is really one of those things that @viet and @oldbonsai should implement as a proper feature though…


Hey, you made it! Thanks Kumi! You’re awesome.

1 Like


I definitely started on July 4th, is there no data prior to August 4th?