Atcoder Perf Graph

レーティンググラフにパフォーマンスのグラフを重ねて表示します

נכון ליום 26-09-2023. ראה הגרסה האחרונה.

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name         Atcoder Perf Graph
// @namespace    http://atcoder.jp/
// @version      1.0.2
// @description  レーティンググラフにパフォーマンスのグラフを重ねて表示します
// @author       nzm_ort
// @include      *://atcoder.jp/users*
// @exclude      *://atcoder.jp/users/*?graph=rank
// @exclude      *://atcoder.jp/users/*?graph=dist
// @exclude      *://atcoder.jp/users/*/history*
// @grant        none
// @require      https://code.jquery.com/jquery-1.8.0.min.js
// @run-at       document-end
// @license MIT

// ==/UserScript==
"use strict";

let scriptsArray = $('script');
scriptsArray[14].remove();
let copyPage = $("html").clone().html();
$("html").remove();
document.write(copyPage);

// 各値設定
{
    const element = document.getElementsByClassName('btn-text-group')[document.getElementsByClassName('btn-text-group').length - 1];
    const insertButton = Object.assign(document.createElement('button'), {
        className: '',
        id: 'onoffButton',
        style: '\
        margin-left:100px;\
        appearance: none;\
        border: 0;\
        border-radius: 5px;\
        background: #616161;\
        color: #fff;\
        padding: 5px 10px;\
        font-size: 16px;\
        n_clicks: 0;\
        '
    }
    );
    insertButton.textContent = "パフォーマンス ON/OFF切り替え"
    element.appendChild(insertButton)
}

// const
const MARGIN_VAL_X = 86400 * 30;
const MARGIN_VAL_Y_LOW = 100;//
const MARGIN_VAL_Y_HIGH = 300;
const OFFSET_X = 50;
const OFFSET_Y = 5;
const DEFAULT_WIDTH = 640;
let canvas_status = document.getElementById("ratingStatus");
const STATUS_WIDTH = canvas_status.width - OFFSET_X - 10;
const STATUS_HEIGHT = canvas_status.height - OFFSET_Y - 5;
let canvas_graph = document.getElementById("ratingGraph");
const PANEL_WIDTH = canvas_graph.width - OFFSET_X - 10;
const PANEL_HEIGHT = canvas_graph.height - OFFSET_Y - 30;

// highest吹き出しサイズ
const HIGHEST_WIDTH = 115;
const HIGHEST_HEIGHT = 20;
const LABEL_FONT = "12px Lato";
const START_YEAR = 2010;
const MONTH_NAMES = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
const YEAR_SEC = 86400 * 365;
const STEP_SIZE = 400;  // y軸のステップ数
const COLORS = [
    [0, "#808080", 0.15],
    [400, "#804000", 0.15],
    [800, "#008000", 0.15],
    [1200, "#00C0C0", 0.2],
    [1600, "#0000FF", 0.1],
    [2000, "#C0C000", 0.25],
    [2400, "#FF8000", 0.2],
    [2800, "#FF0000", 0.1]
];

const STAR_MIN = 3200;
const PARTICLE_MIN = 3;
const PARTICLE_MAX = 20;
const LIFE_MAX = 30;
const EPS = 1e-9;

let cj = createjs;
let stage_graph, stage_status;
// graph
let panel_shape, border_shape;
let chart_container, line_shape, vertex_shapes, highest_shape;
let n, x_min, x_max, y_min, y_max;

//performance graph
let perf_panel_shape, perf_border_shape;
let perf_chart_container, perf_line_shape, perf_vertex_shapes, perf_highest_shape;
let perf_n, perf_x_min, perf_x_max, perf_y_min, perf_y_max;
let perf_rating_history = []

// status
let border_status_shape;
let rating_text, place_text, diff_text, date_text, contest_name_text, perf_text;
let particles;
let standings_url;
const username = document.getElementsByClassName("username")[0].textContent;

// キャンバスサイズなど設定
function initStage(stage, canvas) {
    let width = canvas.getAttribute('width');
    let height = canvas.getAttribute('height');
    console.log(height)
    
    if (window.devicePixelRatio) {
        //縦横の設定
        canvas.setAttribute('width', Math.round(width * 1));  // width*window.devicePixelRatioとすると検証で更新したときに2倍になってしまっていた(通常は問題ない?)
        canvas.setAttribute('height', Math.round(height * 1));  // width*window.devicePixelRatioとすると検証で更新したときに2倍になってしまっていた(通常は問題ない?)
        console.log(window.devicePixelRatio)
        stage.scaleX = stage.scaleY = 1;  // =window.devicePixelRatioとすると検証で更新したときに2倍になってしまっていた(通常は問題ない?)
    }
    
    //  minWidthも設定しないと検証で更新したときに小さくなってしまった(通常は問題なし?)
    canvas.style.maxWidth = width + "px";
    canvas.style.maxHeight = height + "px";
    canvas.style.minWidth = width + "px";
    canvas.style.minHeight = height + "px";
    canvas.style.width = canvas.style.height = "100%";
    stage.enableMouseOver();
}

// 図形の追加
function newShape(parent) {
    let s = new cj.Shape();
    parent.addChild(s);
    return s;
}
// テキストの追加
function newText(parent, x, y, font) {
    let t = new cj.Text("", font, "#000");
    t.x = x;
    t.y = y;
    t.textAlign = "center";
    t.textBaseline = "middle";
    parent.addChild(t);
    return t;
}

// 描画などもろもろ実行
function init(click_num) {
    // rating_history: ratingの変化情報
    // console.log(rating_history)で確認できる
    n = rating_history.length;
    perf_n = perf_rating_history.length;
    if (n == 0 ) return;

    // stage
    stage_graph = new cj.Stage("ratingGraph");  // Stage("canvasのID");
    stage_status = new cj.Stage("ratingStatus");
    initStage(stage_graph, canvas_graph);
    initStage(stage_status, canvas_status);

    //グラフサイズ
    x_min = 100000000000;
    x_max = 0;
    y_min = 10000;
    y_max = 0;
    for (let i = 0; i < n; i++) {
        x_min = Math.min(x_min, rating_history[i].EndTime);
        x_max = Math.max(x_max, rating_history[i].EndTime);
        y_min = Math.min(y_min, rating_history[i].NewRating);
        y_max = Math.max(y_max, rating_history[i].NewRating);
    }
    x_min -= MARGIN_VAL_X; //最初にコンテストに参加した日ー1ヶ月
    x_max += MARGIN_VAL_X; //最後にコンテストに参加した日+1ヶ月
    y_min = Math.min(1500, Math.max(0, y_min - MARGIN_VAL_Y_LOW));
    y_max += MARGIN_VAL_Y_HIGH;

    //  パフォーマンスグラフのサイズ
    //  x軸(日付)については、レーティンググラフと一緒なのでy軸のみ決定(パフォーマンスデータからmaxとminを取得)
    perf_y_min = 10000;
    perf_y_max = 0;
    for (let i = 0; i < perf_rating_history.length; i++) {
        perf_y_min = Math.min(perf_y_min, perf_rating_history[i].Performance);
        perf_y_max = Math.max(perf_y_max, perf_rating_history[i].Performance);
    }
    perf_y_min = Math.min(1500, Math.max(0, perf_y_min - MARGIN_VAL_Y_LOW));
    perf_y_max += MARGIN_VAL_Y_HIGH;

    // 偶数回クリックなら、パフォーマンスグラフが表示されている
    // レーティンググラフとパフォーマンスグラフから表示する高さを決定
    // 奇数回クリックの場合は、パフォグラフは表示されないのでレーティンググラフの高さをy軸に設定
    if (click_num%2===0){
        y_min = Math.min(y_min, perf_y_min);
        y_max = Math.max(y_max, perf_y_max);
    }

    initBackground();  // 背景の描画
    initChart(click_num);  // プロットと直線の描画
    initPerfChart(click_num)  // パフォーマンスグラフ描画
    stage_graph.update();

    initStatus(click_num);  // コンテスト情報描画
    stage_status.update();

    //マウスオーバー時のアニメーション
    cj.Ticker.setFPS(60);
    cj.Ticker.addEventListener("tick", handleTick);
    function handleTick(event) {
        updateParticles();
        stage_status.update();
    }
}

function getPer(x, l, r) {
    return (x - l) / (r - l);
}
function getColor(x) {
    for (let i = COLORS.length - 1; i >= 0; i--) {
        if (x >= COLORS[i][0]) return COLORS[i];
    }
    return [-1, "#000000", 0.1];
}
function initBackground() {
    panel_shape = newShape(stage_graph);
    panel_shape.x = OFFSET_X;
    panel_shape.y = OFFSET_Y;
    panel_shape.alpha = 0.3;

    border_shape = newShape(stage_graph);
    border_shape.x = OFFSET_X;
    border_shape.y = OFFSET_Y;

    // 左軸
    function newLabelY(s, y) {
        let t = new cj.Text(s, LABEL_FONT, "#000");
        t.x = OFFSET_X - 10;
        t.y = OFFSET_Y + y;
        t.textAlign = "right";
        t.textBaseline = "middle";
        stage_graph.addChild(t);
    }
    // x軸ラベル
    function newLabelX(s, x, y) {
        let t = new cj.Text(s, LABEL_FONT, "#000");
        t.x = OFFSET_X + x;
        t.y = OFFSET_Y + PANEL_HEIGHT + 2 + y;
        t.textAlign = "center";
        t.textBaseline = "top";
        stage_graph.addChild(t);
    }

    //https://createjs.com/docs/easeljs/classes/Graphics.html Graphics Classのドキュメント
    let y1 = 0;
    // グラフ中の(レートの)色設定
    for (let i = COLORS.length - 1; i >= 0; i--) {
        let y2 = PANEL_HEIGHT - PANEL_HEIGHT * getPer(COLORS[i][0], y_min, y_max);
        if (y2 > 0 && y1 < PANEL_HEIGHT) {
            y1 = Math.max(y1, 0);                           //rect ( x, y, w , h )
            panel_shape.graphics.beginFill(COLORS[i][1]).rect(0, y1, PANEL_WIDTH, Math.min(y2, PANEL_HEIGHT) - y1);
        }
        y1 = y2;
    }
    // y軸ラベル
    for (let i = 0; i <= y_max; i += STEP_SIZE) {
        if (i >= y_min) {
            let y = PANEL_HEIGHT - PANEL_HEIGHT * getPer(i, y_min, y_max);
            newLabelY(String(i), y);
            border_shape.graphics.beginStroke("#FFF").setStrokeStyle(0.5);
            if (i == 2000) border_shape.graphics.beginStroke("#000");
            border_shape.graphics.moveTo(0, y).lineTo(PANEL_WIDTH, y);
        }
    }
    border_shape.graphics.beginStroke("#FFF").setStrokeStyle(0.5);

    let month_step = 6;
    for (let i = 3; i >= 1; i--) {
        if (x_max - x_min <= YEAR_SEC * i + MARGIN_VAL_X * 2) month_step = i;//初めてすぐの人は短めに
    }

    // x軸ラベル
    let first_flag = true;
    for (let i = START_YEAR; i < 3000; i++) {
        let break_flag = false;
        for (let j = 0; j < 12; j += month_step) {
            let month = ('00' + (j + 1)).slice(-2);
            let unix = Date.parse(String(i) + "-" + month + "-01T00:00:00") / 1000;
            if (x_min < unix && unix < x_max) {
                let x = PANEL_WIDTH * getPer(unix, x_min, x_max);
                if (j == 0 || first_flag) {
                    newLabelX(MONTH_NAMES[j], x, 0);
                    newLabelX(String(i), x, 13);
                    first_flag = false;
                } else {
                    newLabelX(MONTH_NAMES[j], x, 0);
                }
                border_shape.graphics.mt(x, 0).lt(x, PANEL_HEIGHT)
            }
            if (unix > x_max) { break_flag = true; break; }
        }
        if (break_flag) break;
    }
    border_shape.graphics.s("#888").ss(1.5).rr(0, 0, PANEL_WIDTH, PANEL_HEIGHT, 2);
}

function initChart(click_num4) {
    chart_container = new cj.Container();
    stage_graph.addChild(chart_container);
    chart_container.shadow = new cj.Shadow("rgba(0,0,0,0.3)", 1, 2, 3);  // 影

    line_shape = newShape(chart_container);
    highest_shape = newShape(chart_container);
    vertex_shapes = new Array();

    //  マウスホバー時のアニメーション
    function mouseoverVertex(e) {
        vertex_shapes[e.target.i].scaleX = vertex_shapes[e.target.i].scaleY = 1.2;
        stage_graph.update();
        setStatus(rating_history[e.target.i], perf_rating_history[e.target.i], true, click_num4);
    };
    function mouseoutVertex(e) {
        vertex_shapes[e.target.i].scaleX = vertex_shapes[e.target.i].scaleY = 1;
        stage_graph.update();
    };

    // 最高レーティング取得
    let highest_i = 0;
    for (let i = 0; i < n; i++) {
        if (rating_history[highest_i].NewRating < rating_history[i].NewRating) {
            highest_i = i;
        }
    }

    // rating-graph-plot(点、線は下で別に描画)
    for (let i = 0; i < n; i++) {
        vertex_shapes.push(newShape(chart_container));
        vertex_shapes[i].graphics.beginStroke("#FFF");
        if (i == highest_i) vertex_shapes[i].graphics.s("#000");//Highestなら外枠を黒に
        vertex_shapes[i].graphics.setStrokeStyle(0.5).beginFill(getColor(rating_history[i].NewRating)[1]).dc(0, 0, 3.5);

        vertex_shapes[i].x = OFFSET_X + PANEL_WIDTH * getPer(rating_history[i].EndTime, x_min, x_max);
        vertex_shapes[i].y = OFFSET_Y + (PANEL_HEIGHT - PANEL_HEIGHT * getPer(rating_history[i].NewRating, y_min, y_max));
        //console.log(rating_history[i].EndTime)
        vertex_shapes[i].i = i;

        let hitArea = new cj.Shape();
        hitArea.graphics.f("#000").dc(1.5, 1.5, 6);
        vertex_shapes[i].hitArea = hitArea;
        vertex_shapes[i].addEventListener("mouseover", mouseoverVertex);
        vertex_shapes[i].addEventListener("mouseout", mouseoutVertex);
    }

    {//highest
        let dx = 80;
        if ((x_min + x_max) / 2 < rating_history[highest_i].EndTime) dx = -80;
        let x = vertex_shapes[highest_i].x + dx;
        let y = vertex_shapes[highest_i].y - 16;
        highest_shape.graphics.s("#FFF").mt(vertex_shapes[highest_i].x, vertex_shapes[highest_i].y).lt(x, y);
        highest_shape.graphics.s("#888").f("#FFF").rr(x - HIGHEST_WIDTH / 2, y - HIGHEST_HEIGHT / 2, HIGHEST_WIDTH, HIGHEST_HEIGHT, 2);
        highest_shape.i = highest_i;
        let highest_text = newText(stage_graph, x, y, "12px Lato");
        highest_text.text = "Highest(Rate): " + rating_history[highest_i].NewRating;
        highest_shape.addEventListener("mouseover", mouseoverVertex);
        highest_shape.addEventListener("mouseout", mouseoutVertex);
    }

    // 線を描画(点と点をつなぐ)
    for (let j = 0; j < 2; j++) {
        if (j == 0) line_shape.graphics.s("#AAA").ss(2);
        else line_shape.graphics.s("#000").ss(0.5);

        line_shape.graphics.mt(vertex_shapes[0].x, vertex_shapes[0].y);
        for (let i = 0; i < n; i++) {
            line_shape.graphics.lt(vertex_shapes[i].x, vertex_shapes[i].y);
        }
    }
}

// パフォーマンスグラフの描画(基本はレーティンググラフと同様)
function initPerfChart(click_num2) {
    perf_chart_container = new cj.Container();
    stage_graph.addChild(perf_chart_container);
    perf_chart_container.shadow = new cj.Shadow("rgba(0,0,0,0.3)", 1, 2, 3);

    perf_line_shape = newShape(perf_chart_container);
    perf_highest_shape = newShape(perf_chart_container);
    perf_vertex_shapes = new Array();

    function mouseoverVertex(e) {
        perf_vertex_shapes[e.target.i].scaleX = perf_vertex_shapes[e.target.i].scaleY = 1.2;
        stage_graph.update();
        setStatus(rating_history[e.target.i], perf_rating_history[e.target.i], true, click_num2);
    };
    function mouseoutVertex(e) {
        perf_vertex_shapes[e.target.i].scaleX = perf_vertex_shapes[e.target.i].scaleY = 1;
        stage_graph.update();
    };

    // 最高パフォーマンスの取得
    let highest_i_perf = 0;
    for (let i = 0; i < n; i++) {
        if (perf_rating_history[highest_i_perf].Performance < perf_rating_history[i].Performance) {
            highest_i_perf = i;
        }
    }

    // performance-graph-plot
    for (let i = 0; i < perf_n; i++) {
        perf_vertex_shapes.push(newShape(perf_chart_container));
        perf_vertex_shapes[i].graphics.beginStroke("#FFF");
        if (i == highest_i_perf) {
            perf_vertex_shapes[i].graphics.s("#000");
            perf_vertex_shapes[i].graphics.setStrokeStyle(1).beginFill(getColor(perf_rating_history[i].Performance)[1]).dc(0, 0, 2.5);
        }
        else {
            perf_vertex_shapes[i].graphics.setStrokeStyle(0.5).beginFill(getColor(perf_rating_history[i].Performance)[1]).dc(0, 0, 2.8);
        }
        perf_vertex_shapes[i].x = OFFSET_X + PANEL_WIDTH * getPer(rating_history[i].EndTime, x_min, x_max);
        perf_vertex_shapes[i].y = OFFSET_Y + (PANEL_HEIGHT - PANEL_HEIGHT * getPer(perf_rating_history[i].Performance, y_min, y_max));
        perf_vertex_shapes[i].i = i;
        let hitArea = new cj.Shape();
        hitArea.graphics.f("#000").dc(1.5, 1.5, 6);
        perf_vertex_shapes[i].hitArea = hitArea;
        perf_vertex_shapes[i].addEventListener("mouseover", mouseoverVertex);
        perf_vertex_shapes[i].addEventListener("mouseout", mouseoutVertex);

    }

    {//highest-perf
        let dx_perf = 80;
        if ((x_min + x_max) / 2 < rating_history[highest_i_perf].EndTime) dx_perf = -80;
        let x = perf_vertex_shapes[highest_i_perf].x + dx_perf;
        let y = perf_vertex_shapes[highest_i_perf].y - 16;
        perf_highest_shape.graphics.s("#FFF").mt(perf_vertex_shapes[highest_i_perf].x, perf_vertex_shapes[highest_i_perf].y).lt(x, y);
        perf_highest_shape.graphics.s("#888").f("#FFF").rr(x - HIGHEST_WIDTH / 2, y - HIGHEST_HEIGHT / 2, HIGHEST_WIDTH, HIGHEST_HEIGHT, 2);
        perf_highest_shape.i = highest_i_perf;
        var highest_perf_text = newText(stage_graph, x, y, "12px Lato");
        highest_perf_text.text = "Highest(Perf): " + perf_rating_history[highest_i_perf].Performance;
        perf_highest_shape.addEventListener("mouseover", mouseoverVertex);
        perf_highest_shape.addEventListener("mouseout", mouseoutVertex);
    }

    // 線を描画
    for (let index = 0; index < 2; index++) {
        if (index == 0) perf_line_shape.graphics.s("#AAA").ss(2);
        else perf_line_shape.graphics.s("#F00").ss(0.5);
        perf_line_shape.graphics.mt(perf_vertex_shapes[0].x, perf_vertex_shapes[0].y);
        for (let i = 0; i < perf_rating_history.length; i++) {
            perf_line_shape.graphics.lt(perf_vertex_shapes[i].x, perf_vertex_shapes[i].y);
        }
    }

    // 最高パフォの吹き出し描画
    if (click_num2%2===0){
        perf_chart_container.visible = true
        highest_perf_text.text = "Highest(Perf): " + perf_rating_history[highest_i_perf].Performance;;
        stage_graph.update();
    }
    else{
        perf_chart_container.visible = false
        highest_perf_text.text = "";
        stage_graph.update();
    }
}

// status情報初期化関数
function initStatus(click_num5) {
    border_status_shape = newShape(stage_status);
    rating_text = newText(stage_status, OFFSET_X + 75, OFFSET_Y + STATUS_HEIGHT / 2, "48px 'Squada One'");
    perf_text = newText(stage_status, OFFSET_X + 75, OFFSET_Y + STATUS_HEIGHT / 2+25, "16px 'Squada One'")
    place_text = newText(stage_status, OFFSET_X + 160, OFFSET_Y + STATUS_HEIGHT / 2.7, "16px Lato");
    diff_text = newText(stage_status, OFFSET_X + 160, OFFSET_Y + STATUS_HEIGHT / 1.5, "11px Lato");
    diff_text.color = '#888';
    date_text = newText(stage_status, OFFSET_X + 200, OFFSET_Y + STATUS_HEIGHT / 4, "14px Lato");
    contest_name_text = newText(stage_status, OFFSET_X + 200, OFFSET_Y + STATUS_HEIGHT / 1.6, "20px Lato");
    date_text.textAlign = contest_name_text.textAlign = "left";
    contest_name_text.maxWidth = STATUS_WIDTH - 200 - 10;
    {
        let hitArea = new cj.Shape(); hitArea.graphics.f("#000").r(0, -12, contest_name_text.maxWidth, 24);
        contest_name_text.hitArea = hitArea;
        contest_name_text.cursor = "pointer";
        contest_name_text.addEventListener("click", function () {
            location.href = standings_url;
        });
    }
    particles = new Array();
    for (let i = 0; i < PARTICLE_MAX; i++) {
        particles.push(newText(stage_status, 0, 0, "64px Lato"));
        particles[i].visible = false;
    }
    setStatus(rating_history[rating_history.length - 1], perf_rating_history[perf_rating_history.length-1], false, click_num5);
}

function getRatingPer(x) {
    let pre = COLORS[COLORS.length - 1][0] + STEP_SIZE;
    for (let i = COLORS.length - 1; i >= 0; i--) {
        if (x >= COLORS[i][0]) return (x - COLORS[i][0]) / (pre - COLORS[i][0]);
        pre = COLORS[i][0];
    }
    return 0;
}

function getOrdinal(x) {
    let s = ["th", "st", "nd", "rd"], v = x % 100;
    return x + (s[(v - 20) % 10] || s[v] || s[0]);
}
function getDiff(x) {
    let sign = x == 0 ? 'ツア' : (x < 0 ? '-' : '+');
    return sign + Math.abs(x);
}

// status更新
function setStatus(data, data2, particle_flag, click_num3) {
    let date = new Date(data.EndTime * 1000);
    let rating = data.NewRating, old_rating = data.OldRating;
    let place = data.Place;
    let contest_name = data.ContestName;
    let perf = data2.Performance;
    let tmp = getColor(rating); let color = tmp[1], alpha = tmp[2];
    border_status_shape.graphics.c().s(color).ss(1).rr(OFFSET_X, OFFSET_Y, STATUS_WIDTH, STATUS_HEIGHT, 2);
    rating_text.text = rating;
    rating_text.color = color;
    perf_text.text = "perf: " + perf;
    place_text.text = getOrdinal(place);
    diff_text.text = getDiff(rating - old_rating);
    date_text.text = date.toLocaleDateString();
    contest_name_text.text = contest_name;
    if (particle_flag) {
        let particle_num = parseInt(Math.pow(getRatingPer(rating), 2) * (PARTICLE_MAX - PARTICLE_MIN) + PARTICLE_MIN);
        setParticles(particle_num, color, alpha, rating);
    }
    standings_url = data.StandingsUrl;

    // 偶数回クリック(パフォグラフが表示されている)ならレートのしたにパフォを表示
    if (click_num3%2===0){
        perf_text.text = "perf: " + perf;
        stage_graph.update();
    }
    // 奇数回なら表示しない
    else{
        perf_text.text = ""
        stage_graph.update();
    }
}

// ホバー時のレート変化アニメーション
function setParticle(particle, x, y, color, alpha, star_flag) {
    particle.x = x;
    particle.y = y;
    let ang = Math.random() * Math.PI * 2;
    let speed = Math.random() * 4 + 4;
    particle.vx = Math.cos(ang) * speed;
    particle.vy = Math.sin(ang) * speed;
    particle.rot_speed = Math.random() * 20 + 10;
    particle.life = LIFE_MAX;
    particle.visible = true;
    particle.color = color;

    if (star_flag) {
        particle.text = "★";
    } else {
        particle.text = "@";
    }
    particle.alpha = alpha;
}
function setParticles(num, color, alpha, rating) {
    for (let i = 0; i < PARTICLE_MAX; i++) {
        if (i < num) {
            setParticle(particles[i], rating_text.x, rating_text.y, color, alpha, rating >= STAR_MIN);
        } else {
            particles[i].life = 0;
            particles[i].visible = false;
        }
    }
}
function updateParticle(particle) {
    if (particle.life <= 0) {
        particle.visible = false;
        return;
    }
    particle.x += particle.vx;
    particle.vx *= 0.9;
    particle.y += particle.vy;
    particle.vy *= 0.9;
    particle.life--;
    particle.scaleX = particle.scaleY = particle.life / LIFE_MAX;
    particle.rotation += particle.rot_speed;
}
function updateParticles() {
    for (let i = 0; i < PARTICLE_MAX; i++) {
        if (particles[i].life > 0) {
            updateParticle(particles[i]);
        }
    }
}


// main関数
async function main() {

    //get json
    let allJson;
    try {
        const url=`https://atcoder.jp/users/${username}/history/json`;  // 参加したコンテスト情報のjson
        const res = await fetch(url);
        allJson = await res.json()
        // console.log(allJson.length)
        // console.log(allJson)
    } catch (reaseon) { console.log('try失敗') }
    {
        // rated参加ならデータに追加
        for (let i = 0; i < allJson.length; i++) {
            if (allJson[i].IsRated===true){
                perf_rating_history.push({ ...allJson[i] });
            }
        // パフォが低いものはマイナスパフォになってることがあるのでマイナスの場合は0としておく
        for (let i = 0; i < perf_rating_history.length; i++) {
            if (perf_rating_history[i].Performance<0){
                perf_rating_history[i].Performance = 0;
            }
        }
        //console.log(perf_rating_history)
        }
        // console.log(perf_rating_history)
    }

    // 描画関数実行
    // onoffボタンがクリックされたら切り替え
    init(0);
    let clickCount1 = 0;
    let onoffButton = document.getElementById('onoffButton');
    onoffButton.addEventListener('click', function () {
        clickCount1 += 1
        init(clickCount1);
        // console.log(clickCount1)
        // console.log(perf_chart_container.visible)
    })
}

main();