StatKani - Data visualizations for WaniKani

Hello! :wave: I’m working on a statistics site and wanted to share what I’ve built so far.

Link → StatKani
Code → GitHub

:warning: The site does not work on mobile devices (yet).
:warning: It will take a few minutes to load your data the first time you log in.

I’ll be working on the site next weekend so if you encounter a bug, have some feedback, or want a specific feature, then please drop a reply with the details.

Thank you @rfindley, @Kumirei, and @rwesterhof for providing inspiration with your projects. :bowing_man:



Hey, your link is going to the graphs page, which is showing up like this.

Looks like unless you write in some code to direct, you need to be linking them here instead.


It looks really cool by the way!


Fixed, thank you!

1 Like

Looks cool so far. My level one was almost 2000days, my graph looks like this:

Would be cool to be able to set the start date. Or somehow scale that graph in a differnt way…


Wow, I did not account for a 5 year level ಠ_ಠ

I changed the scaling of the y-axis to use the median instead, so your graph should look much more reasonable now. Thanks for the feedback!


OH boy do I have a test case for you then.


btw it doesn't appear optimized for mobile display


OH boy do I have a test case for you then.

Slow and steady wins the race creates annoying edge cases :face_with_spiral_eyes:

btw it doesn’t appear optimized for mobile display

Yeah, I’ll have to figure out how to get the bar graph and heatmap to look good on smaller screens. That’s a bit more involved so I’ll see what I can do about it over the weekend :+1:


Guess I’m also a good test case, can’t believe it’s almost 8 years ago… and you can see it does not know what to do before level 15 below.

level graphs


WK did not record level progression until August of 2017, so that’s probably why. @nanyaLang If you want to account for this you would need to look at the subjects’ unlock dates and “guess” when the users leveled up.

How I did it in the Heatmap, if you are curious

It’s probably not the best way, but it works well enough

// Find indefinite level ups by looking at lesson history
let levels = {}
// Sort lessons by level then unlocked date
items.forEach((item) => {
    if (
        item.object !== 'kanji' ||
        !item.assignments ||
        !item.assignments.unlocked_at ||
        item.assignments.unlocked_at >= first_recorded_date
    let date = new Date(item.assignments.unlocked_at).toDateString()
    if (!levels[]) levels[] = {}
    if (!levels[][date]) levels[][date] = 1
    else levels[][date]++
// Discard dates with less than 10 unlocked
// then discard levels with no dates
// then keep earliest date for each level
for (let [level, data] of Object.entries(levels)) {
    for (let [date, count] of Object.entries(data)) {
        if (count < 10) delete data[date]
    if (Object.keys(levels[level]).length == 0) {
        delete levels[level]
    levels[level] = Object.keys(data).reduce((low, curr) => (low < curr ? low : curr),
// Map to array of [[level0, date0], [level1, date1], ...] Format
levels = Object.entries(levels).map(([level, date]) => [Number(level), date])
// Add definite level ups from API
Object.values(level_progressions).forEach((level) =>
    levels.push([, new Date(]),

It does capture resets looks like. I started in 2013 (kinda) but reset from level 11 to level 3 in 2018.

Although level 11 appears to be missing?

I can’t check it on a non-mobile device until later today though.

1 Like

Looks nice so far! But my graph is also a bit confusing. I reset from level 42 to 31, and I’m currently in level 40 again. Levels 31-39 show the duration after the reset, my current level doesn’t appear, and levels 41-42 show the duration before the reset.
Adding something that marks resets would also be nice.

1 Like

I didn’t have to do this in my script but my hunch is to watch when radicals are unlocked and that is the level up date. For levels with no radicals watch when kanji are unlocked. The logic is that radicals are always unlocked on level up. When there is no radicals kanji are always unlocked on level up.


Wow, the charts and overall design of the site is gorgeous, very well done!

Though I’d like to know what the “crack” in some bars of the bar graph means.


Site looks very slick, love it!

I noticed that nothing scales when I go fullscreen though… the graphs end up looking a bit small.

@Logograph the y-axis is scaled to the median time, so the breaks in the graph, I assume, are any times longer than the maximum the graph allows.


Screen Shot 2021-10-12 at 00.11.43

ahh… shouldn’t these numbers be the same?


Thank you, I like it, how clever. It’s very nice looking!


Wow, it’s a bug fiesta in here! :tada:

I’ll try to get your missing levels using the methods the others mentioned. It might take a few days though.

I’m going to need API keys to figure out the issues with resets. WK doesn’t have DMs so if you’re comfortable sharing your API key with me, you can send them over to (I’ll be sure to delete them once I’m done) @Voi @Cassini

Good catch, fixed!


Looks really nice! Will definitely be keeping an eye on this project! :slight_smile:


It adds one to my Enlightened and Burn items, though.

Also, I don’t quite understand the graph. Is it supposed to show all the levels including the repeats or did it just show levels 17-21 that I did before the reset just to take up space?

1 Like