AtCoder Performace Graph

AtCoder のユーザーページでパフォーマンスをグラフ化します

// ==UserScript==
// @name         AtCoder Performace Graph
// @namespace    wiiiiam104
// @version      0.1.0
// @description  AtCoder のユーザーページでパフォーマンスをグラフ化します
// @author       @Wiiiiam_104
// @match        https://atcoder.jp/users/*
// ==/UserScript==

(function() {
  'use strict';

  function getJson(username){
    const xhr=new XMLHttpRequest();
    const url=`https://atcoder.jp/users/${username}/history/json`;
    xhr.open("GET",url);
    xhr.send();
    xhr.onload=()=>drawGraph(JSON.parse(xhr.responseText));
  }getJson(location.pathname.split("/")[2]);

  function drawGraph(data){
    function getColor(p){
      return p<0?"black":p<400?"gray":p<800?"brown":p<1200?"green":p<1600?"cyan":p<2000?"blue":p<2400?"yellow":p<2800?"orange":"red";
    }

    const h=200,w=400;
    function insertCanvas(){
      let div=document.createElement("div");
      div.innerHTML=`
        <canvas id="graph" height="${h}" width="${w}" style="background-color:#ccc;"></canvas>
        <p style="line-height:20px">2本の横線の内 下が真のRating 上がRated参加回数による補正前の値です。参加回数が多いほどこれらは近づきます。</p>
      `
      div.style="padding:10px;"
      document.querySelector("div.mt-2").appendChild(div);
    }insertCanvas();

    let perfs=[];
    for(let i=0;i<data.length;i++) if(data[i].IsRated) perfs.push(data[i].InnerPerformance);

    let ctx=document.querySelector("#graph").getContext("2d");
    ctx.clearRect(0,0,w,h);

    let maxPerf=-Infinity;
    for(let i=0;i<perfs.length;i++){
      if(maxPerf<perfs[i]) maxPerf=perfs[i];
    }

    function toYPosition(perf){
      return h-h*Math.pow(2,perf/400-maxPerf/400);
    }

    // draw color border
    for(let i=0;i<8&&i*400<maxPerf;i++){
      ctx.fillStyle="black";
      ctx.fillRect(0,toYPosition(i*400),w,1);
    }
    // draw data
    for(let i=0;i<perfs.length;i++){
      ctx.fillStyle=getColor(perfs[i]);
      let y=toYPosition(perfs[i]);
      ctx.fillRect(w*(1-Math.pow(0.9,perfs.length-i-1)),y,w/10*Math.pow(0.9,perfs.length-i-1),h-y);
    }

    let rating=data[data.length-1].NewRating;
    ctx.fillStyle=getColor(rating);
    ctx.fillRect(0,toYPosition(rating)-1,w,2);

    let PerfsAve=rating+1200/(Math.sqrt(19)-1)*(Math.sqrt(1-Math.pow(0.81,perfs.length))/(1-Math.pow(0.9,perfs.length))-1);
    ctx.fillStyle=getColor(PerfsAve);
    ctx.fillRect(0,toYPosition(PerfsAve)-1,w,2);
  }
})();