Atcoder Perf Graph

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

As of 26/09/2023. See the latest version.

  1. // ==UserScript==
  2. // @name Atcoder Perf Graph
  3. // @namespace http://atcoder.jp/
  4. // @version 1.0.2
  5. // @description レーティンググラフにパフォーマンスのグラフを重ねて表示します
  6. // @author nzm_ort
  7. // @include *://atcoder.jp/users*
  8. // @exclude *://atcoder.jp/users/*?graph=rank
  9. // @exclude *://atcoder.jp/users/*?graph=dist
  10. // @exclude *://atcoder.jp/users/*/history*
  11. // @grant none
  12. // @require https://code.jquery.com/jquery-1.8.0.min.js
  13. // @run-at document-end
  14. // @license MIT
  15.  
  16. // ==/UserScript==
  17. "use strict";
  18.  
  19. let scriptsArray = $('script');
  20. scriptsArray[14].remove();
  21. let copyPage = $("html").clone().html();
  22. $("html").remove();
  23. document.write(copyPage);
  24.  
  25. // 各値設定
  26. {
  27. const element = document.getElementsByClassName('btn-text-group')[document.getElementsByClassName('btn-text-group').length - 1];
  28. const insertButton = Object.assign(document.createElement('button'), {
  29. className: '',
  30. id: 'onoffButton',
  31. style: '\
  32. margin-left:100px;\
  33. appearance: none;\
  34. border: 0;\
  35. border-radius: 5px;\
  36. background: #616161;\
  37. color: #fff;\
  38. padding: 5px 10px;\
  39. font-size: 16px;\
  40. n_clicks: 0;\
  41. '
  42. }
  43. );
  44. insertButton.textContent = "パフォーマンス ON/OFF切り替え"
  45. element.appendChild(insertButton)
  46. }
  47.  
  48. // const
  49. const MARGIN_VAL_X = 86400 * 30;
  50. const MARGIN_VAL_Y_LOW = 100;//
  51. const MARGIN_VAL_Y_HIGH = 300;
  52. const OFFSET_X = 50;
  53. const OFFSET_Y = 5;
  54. const DEFAULT_WIDTH = 640;
  55. let canvas_status = document.getElementById("ratingStatus");
  56. const STATUS_WIDTH = canvas_status.width - OFFSET_X - 10;
  57. const STATUS_HEIGHT = canvas_status.height - OFFSET_Y - 5;
  58. let canvas_graph = document.getElementById("ratingGraph");
  59. const PANEL_WIDTH = canvas_graph.width - OFFSET_X - 10;
  60. const PANEL_HEIGHT = canvas_graph.height - OFFSET_Y - 30;
  61.  
  62. // highest吹き出しサイズ
  63. const HIGHEST_WIDTH = 115;
  64. const HIGHEST_HEIGHT = 20;
  65. const LABEL_FONT = "12px Lato";
  66. const START_YEAR = 2010;
  67. const MONTH_NAMES = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
  68. const YEAR_SEC = 86400 * 365;
  69. const STEP_SIZE = 400; // y軸のステップ数
  70. const COLORS = [
  71. [0, "#808080", 0.15],
  72. [400, "#804000", 0.15],
  73. [800, "#008000", 0.15],
  74. [1200, "#00C0C0", 0.2],
  75. [1600, "#0000FF", 0.1],
  76. [2000, "#C0C000", 0.25],
  77. [2400, "#FF8000", 0.2],
  78. [2800, "#FF0000", 0.1]
  79. ];
  80.  
  81. const STAR_MIN = 3200;
  82. const PARTICLE_MIN = 3;
  83. const PARTICLE_MAX = 20;
  84. const LIFE_MAX = 30;
  85. const EPS = 1e-9;
  86.  
  87. let cj = createjs;
  88. let stage_graph, stage_status;
  89. // graph
  90. let panel_shape, border_shape;
  91. let chart_container, line_shape, vertex_shapes, highest_shape;
  92. let n, x_min, x_max, y_min, y_max;
  93.  
  94. //performance graph
  95. let perf_panel_shape, perf_border_shape;
  96. let perf_chart_container, perf_line_shape, perf_vertex_shapes, perf_highest_shape;
  97. let perf_n, perf_x_min, perf_x_max, perf_y_min, perf_y_max;
  98. let perf_rating_history = []
  99.  
  100. // status
  101. let border_status_shape;
  102. let rating_text, place_text, diff_text, date_text, contest_name_text, perf_text;
  103. let particles;
  104. let standings_url;
  105. const username = document.getElementsByClassName("username")[0].textContent;
  106.  
  107. // キャンバスサイズなど設定
  108. function initStage(stage, canvas) {
  109. let width = canvas.getAttribute('width');
  110. let height = canvas.getAttribute('height');
  111. console.log(height)
  112. if (window.devicePixelRatio) {
  113. //縦横の設定
  114. canvas.setAttribute('width', Math.round(width * 1)); // width*window.devicePixelRatioとすると検証で更新したときに2倍になってしまっていた(通常は問題ない?)
  115. canvas.setAttribute('height', Math.round(height * 1)); // width*window.devicePixelRatioとすると検証で更新したときに2倍になってしまっていた(通常は問題ない?)
  116. console.log(window.devicePixelRatio)
  117. stage.scaleX = stage.scaleY = 1; // =window.devicePixelRatioとすると検証で更新したときに2倍になってしまっていた(通常は問題ない?)
  118. }
  119. // minWidthも設定しないと検証で更新したときに小さくなってしまった(通常は問題なし?)
  120. canvas.style.maxWidth = width + "px";
  121. canvas.style.maxHeight = height + "px";
  122. canvas.style.minWidth = width + "px";
  123. canvas.style.minHeight = height + "px";
  124. canvas.style.width = canvas.style.height = "100%";
  125. stage.enableMouseOver();
  126. }
  127.  
  128. // 図形の追加
  129. function newShape(parent) {
  130. let s = new cj.Shape();
  131. parent.addChild(s);
  132. return s;
  133. }
  134. // テキストの追加
  135. function newText(parent, x, y, font) {
  136. let t = new cj.Text("", font, "#000");
  137. t.x = x;
  138. t.y = y;
  139. t.textAlign = "center";
  140. t.textBaseline = "middle";
  141. parent.addChild(t);
  142. return t;
  143. }
  144.  
  145. // 描画などもろもろ実行
  146. function init(click_num) {
  147. // rating_history: ratingの変化情報
  148. // console.log(rating_history)で確認できる
  149. n = rating_history.length;
  150. perf_n = perf_rating_history.length;
  151. if (n == 0 ) return;
  152.  
  153. // stage
  154. stage_graph = new cj.Stage("ratingGraph"); // Stage("canvasのID");
  155. stage_status = new cj.Stage("ratingStatus");
  156. initStage(stage_graph, canvas_graph);
  157. initStage(stage_status, canvas_status);
  158.  
  159. //グラフサイズ
  160. x_min = 100000000000;
  161. x_max = 0;
  162. y_min = 10000;
  163. y_max = 0;
  164. for (let i = 0; i < n; i++) {
  165. x_min = Math.min(x_min, rating_history[i].EndTime);
  166. x_max = Math.max(x_max, rating_history[i].EndTime);
  167. y_min = Math.min(y_min, rating_history[i].NewRating);
  168. y_max = Math.max(y_max, rating_history[i].NewRating);
  169. }
  170. x_min -= MARGIN_VAL_X; //最初にコンテストに参加した日ー1ヶ月
  171. x_max += MARGIN_VAL_X; //最後にコンテストに参加した日+1ヶ月
  172. y_min = Math.min(1500, Math.max(0, y_min - MARGIN_VAL_Y_LOW));
  173. y_max += MARGIN_VAL_Y_HIGH;
  174.  
  175. // パフォーマンスグラフのサイズ
  176. // x軸(日付)については、レーティンググラフと一緒なのでy軸のみ決定(パフォーマンスデータからmaxとminを取得)
  177. perf_y_min = 10000;
  178. perf_y_max = 0;
  179. for (let i = 0; i < perf_rating_history.length; i++) {
  180. perf_y_min = Math.min(perf_y_min, perf_rating_history[i].Performance);
  181. perf_y_max = Math.max(perf_y_max, perf_rating_history[i].Performance);
  182. }
  183. perf_y_min = Math.min(1500, Math.max(0, perf_y_min - MARGIN_VAL_Y_LOW));
  184. perf_y_max += MARGIN_VAL_Y_HIGH;
  185.  
  186. // 偶数回クリックなら、パフォーマンスグラフが表示されている
  187. // レーティンググラフとパフォーマンスグラフから表示する高さを決定
  188. // 奇数回クリックの場合は、パフォグラフは表示されないのでレーティンググラフの高さをy軸に設定
  189. if (click_num%2===0){
  190. y_min = Math.min(y_min, perf_y_min);
  191. y_max = Math.max(y_max, perf_y_max);
  192. }
  193.  
  194. initBackground(); // 背景の描画
  195. initChart(click_num); // プロットと直線の描画
  196. initPerfChart(click_num) // パフォーマンスグラフ描画
  197. stage_graph.update();
  198.  
  199. initStatus(click_num); // コンテスト情報描画
  200. stage_status.update();
  201.  
  202. //マウスオーバー時のアニメーション
  203. cj.Ticker.setFPS(60);
  204. cj.Ticker.addEventListener("tick", handleTick);
  205. function handleTick(event) {
  206. updateParticles();
  207. stage_status.update();
  208. }
  209. }
  210.  
  211. function getPer(x, l, r) {
  212. return (x - l) / (r - l);
  213. }
  214. function getColor(x) {
  215. for (let i = COLORS.length - 1; i >= 0; i--) {
  216. if (x >= COLORS[i][0]) return COLORS[i];
  217. }
  218. return [-1, "#000000", 0.1];
  219. }
  220. function initBackground() {
  221. panel_shape = newShape(stage_graph);
  222. panel_shape.x = OFFSET_X;
  223. panel_shape.y = OFFSET_Y;
  224. panel_shape.alpha = 0.3;
  225.  
  226. border_shape = newShape(stage_graph);
  227. border_shape.x = OFFSET_X;
  228. border_shape.y = OFFSET_Y;
  229.  
  230. // 左軸
  231. function newLabelY(s, y) {
  232. let t = new cj.Text(s, LABEL_FONT, "#000");
  233. t.x = OFFSET_X - 10;
  234. t.y = OFFSET_Y + y;
  235. t.textAlign = "right";
  236. t.textBaseline = "middle";
  237. stage_graph.addChild(t);
  238. }
  239. // x軸ラベル
  240. function newLabelX(s, x, y) {
  241. let t = new cj.Text(s, LABEL_FONT, "#000");
  242. t.x = OFFSET_X + x;
  243. t.y = OFFSET_Y + PANEL_HEIGHT + 2 + y;
  244. t.textAlign = "center";
  245. t.textBaseline = "top";
  246. stage_graph.addChild(t);
  247. }
  248.  
  249. //https://createjs.com/docs/easeljs/classes/Graphics.html Graphics Classのドキュメント
  250. let y1 = 0;
  251. // グラフ中の(レートの)色設定
  252. for (let i = COLORS.length - 1; i >= 0; i--) {
  253. let y2 = PANEL_HEIGHT - PANEL_HEIGHT * getPer(COLORS[i][0], y_min, y_max);
  254. if (y2 > 0 && y1 < PANEL_HEIGHT) {
  255. y1 = Math.max(y1, 0); //rect ( x, y, w , h )
  256. panel_shape.graphics.beginFill(COLORS[i][1]).rect(0, y1, PANEL_WIDTH, Math.min(y2, PANEL_HEIGHT) - y1);
  257. }
  258. y1 = y2;
  259. }
  260. // y軸ラベル
  261. for (let i = 0; i <= y_max; i += STEP_SIZE) {
  262. if (i >= y_min) {
  263. let y = PANEL_HEIGHT - PANEL_HEIGHT * getPer(i, y_min, y_max);
  264. newLabelY(String(i), y);
  265. border_shape.graphics.beginStroke("#FFF").setStrokeStyle(0.5);
  266. if (i == 2000) border_shape.graphics.beginStroke("#000");
  267. border_shape.graphics.moveTo(0, y).lineTo(PANEL_WIDTH, y);
  268. }
  269. }
  270. border_shape.graphics.beginStroke("#FFF").setStrokeStyle(0.5);
  271.  
  272. let month_step = 6;
  273. for (let i = 3; i >= 1; i--) {
  274. if (x_max - x_min <= YEAR_SEC * i + MARGIN_VAL_X * 2) month_step = i;//初めてすぐの人は短めに
  275. }
  276.  
  277. // x軸ラベル
  278. let first_flag = true;
  279. for (let i = START_YEAR; i < 3000; i++) {
  280. let break_flag = false;
  281. for (let j = 0; j < 12; j += month_step) {
  282. let month = ('00' + (j + 1)).slice(-2);
  283. let unix = Date.parse(String(i) + "-" + month + "-01T00:00:00") / 1000;
  284. if (x_min < unix && unix < x_max) {
  285. let x = PANEL_WIDTH * getPer(unix, x_min, x_max);
  286. if (j == 0 || first_flag) {
  287. newLabelX(MONTH_NAMES[j], x, 0);
  288. newLabelX(String(i), x, 13);
  289. first_flag = false;
  290. } else {
  291. newLabelX(MONTH_NAMES[j], x, 0);
  292. }
  293. border_shape.graphics.mt(x, 0).lt(x, PANEL_HEIGHT)
  294. }
  295. if (unix > x_max) { break_flag = true; break; }
  296. }
  297. if (break_flag) break;
  298. }
  299. border_shape.graphics.s("#888").ss(1.5).rr(0, 0, PANEL_WIDTH, PANEL_HEIGHT, 2);
  300. }
  301.  
  302. function initChart(click_num4) {
  303. chart_container = new cj.Container();
  304. stage_graph.addChild(chart_container);
  305. chart_container.shadow = new cj.Shadow("rgba(0,0,0,0.3)", 1, 2, 3); // 影
  306.  
  307. line_shape = newShape(chart_container);
  308. highest_shape = newShape(chart_container);
  309. vertex_shapes = new Array();
  310.  
  311. // マウスホバー時のアニメーション
  312. function mouseoverVertex(e) {
  313. vertex_shapes[e.target.i].scaleX = vertex_shapes[e.target.i].scaleY = 1.2;
  314. stage_graph.update();
  315. setStatus(rating_history[e.target.i], perf_rating_history[e.target.i], true, click_num4);
  316. };
  317. function mouseoutVertex(e) {
  318. vertex_shapes[e.target.i].scaleX = vertex_shapes[e.target.i].scaleY = 1;
  319. stage_graph.update();
  320. };
  321.  
  322. // 最高レーティング取得
  323. let highest_i = 0;
  324. for (let i = 0; i < n; i++) {
  325. if (rating_history[highest_i].NewRating < rating_history[i].NewRating) {
  326. highest_i = i;
  327. }
  328. }
  329.  
  330. // rating-graph-plot(点、線は下で別に描画)
  331. for (let i = 0; i < n; i++) {
  332. vertex_shapes.push(newShape(chart_container));
  333. vertex_shapes[i].graphics.beginStroke("#FFF");
  334. if (i == highest_i) vertex_shapes[i].graphics.s("#000");//Highestなら外枠を黒に
  335. vertex_shapes[i].graphics.setStrokeStyle(0.5).beginFill(getColor(rating_history[i].NewRating)[1]).dc(0, 0, 3.5);
  336.  
  337. vertex_shapes[i].x = OFFSET_X + PANEL_WIDTH * getPer(rating_history[i].EndTime, x_min, x_max);
  338. vertex_shapes[i].y = OFFSET_Y + (PANEL_HEIGHT - PANEL_HEIGHT * getPer(rating_history[i].NewRating, y_min, y_max));
  339. //console.log(rating_history[i].EndTime)
  340. vertex_shapes[i].i = i;
  341.  
  342. let hitArea = new cj.Shape();
  343. hitArea.graphics.f("#000").dc(1.5, 1.5, 6);
  344. vertex_shapes[i].hitArea = hitArea;
  345. vertex_shapes[i].addEventListener("mouseover", mouseoverVertex);
  346. vertex_shapes[i].addEventListener("mouseout", mouseoutVertex);
  347. }
  348.  
  349. {//highest
  350. let dx = 80;
  351. if ((x_min + x_max) / 2 < rating_history[highest_i].EndTime) dx = -80;
  352. let x = vertex_shapes[highest_i].x + dx;
  353. let y = vertex_shapes[highest_i].y - 16;
  354. highest_shape.graphics.s("#FFF").mt(vertex_shapes[highest_i].x, vertex_shapes[highest_i].y).lt(x, y);
  355. highest_shape.graphics.s("#888").f("#FFF").rr(x - HIGHEST_WIDTH / 2, y - HIGHEST_HEIGHT / 2, HIGHEST_WIDTH, HIGHEST_HEIGHT, 2);
  356. highest_shape.i = highest_i;
  357. let highest_text = newText(stage_graph, x, y, "12px Lato");
  358. highest_text.text = "Highest(Rate): " + rating_history[highest_i].NewRating;
  359. highest_shape.addEventListener("mouseover", mouseoverVertex);
  360. highest_shape.addEventListener("mouseout", mouseoutVertex);
  361. }
  362.  
  363. // 線を描画(点と点をつなぐ)
  364. for (let j = 0; j < 2; j++) {
  365. if (j == 0) line_shape.graphics.s("#AAA").ss(2);
  366. else line_shape.graphics.s("#000").ss(0.5);
  367.  
  368. line_shape.graphics.mt(vertex_shapes[0].x, vertex_shapes[0].y);
  369. for (let i = 0; i < n; i++) {
  370. line_shape.graphics.lt(vertex_shapes[i].x, vertex_shapes[i].y);
  371. }
  372. }
  373. }
  374.  
  375. // パフォーマンスグラフの描画(基本はレーティンググラフと同様)
  376. function initPerfChart(click_num2) {
  377. perf_chart_container = new cj.Container();
  378. stage_graph.addChild(perf_chart_container);
  379. perf_chart_container.shadow = new cj.Shadow("rgba(0,0,0,0.3)", 1, 2, 3);
  380.  
  381. perf_line_shape = newShape(perf_chart_container);
  382. perf_highest_shape = newShape(perf_chart_container);
  383. perf_vertex_shapes = new Array();
  384.  
  385. function mouseoverVertex(e) {
  386. perf_vertex_shapes[e.target.i].scaleX = perf_vertex_shapes[e.target.i].scaleY = 1.2;
  387. stage_graph.update();
  388. setStatus(rating_history[e.target.i], perf_rating_history[e.target.i], true, click_num2);
  389. };
  390. function mouseoutVertex(e) {
  391. perf_vertex_shapes[e.target.i].scaleX = perf_vertex_shapes[e.target.i].scaleY = 1;
  392. stage_graph.update();
  393. };
  394.  
  395. // 最高パフォーマンスの取得
  396. let highest_i_perf = 0;
  397. for (let i = 0; i < n; i++) {
  398. if (perf_rating_history[highest_i_perf].Performance < perf_rating_history[i].Performance) {
  399. highest_i_perf = i;
  400. }
  401. }
  402.  
  403. // performance-graph-plot
  404. for (let i = 0; i < perf_n; i++) {
  405. perf_vertex_shapes.push(newShape(perf_chart_container));
  406. perf_vertex_shapes[i].graphics.beginStroke("#FFF");
  407. if (i == highest_i_perf) {
  408. perf_vertex_shapes[i].graphics.s("#000");
  409. perf_vertex_shapes[i].graphics.setStrokeStyle(1).beginFill(getColor(perf_rating_history[i].Performance)[1]).dc(0, 0, 2.5);
  410. }
  411. else {
  412. perf_vertex_shapes[i].graphics.setStrokeStyle(0.5).beginFill(getColor(perf_rating_history[i].Performance)[1]).dc(0, 0, 2.8);
  413. }
  414. perf_vertex_shapes[i].x = OFFSET_X + PANEL_WIDTH * getPer(rating_history[i].EndTime, x_min, x_max);
  415. perf_vertex_shapes[i].y = OFFSET_Y + (PANEL_HEIGHT - PANEL_HEIGHT * getPer(perf_rating_history[i].Performance, y_min, y_max));
  416. perf_vertex_shapes[i].i = i;
  417. let hitArea = new cj.Shape();
  418. hitArea.graphics.f("#000").dc(1.5, 1.5, 6);
  419. perf_vertex_shapes[i].hitArea = hitArea;
  420. perf_vertex_shapes[i].addEventListener("mouseover", mouseoverVertex);
  421. perf_vertex_shapes[i].addEventListener("mouseout", mouseoutVertex);
  422.  
  423. }
  424.  
  425. {//highest-perf
  426. let dx_perf = 80;
  427. if ((x_min + x_max) / 2 < rating_history[highest_i_perf].EndTime) dx_perf = -80;
  428. let x = perf_vertex_shapes[highest_i_perf].x + dx_perf;
  429. let y = perf_vertex_shapes[highest_i_perf].y - 16;
  430. perf_highest_shape.graphics.s("#FFF").mt(perf_vertex_shapes[highest_i_perf].x, perf_vertex_shapes[highest_i_perf].y).lt(x, y);
  431. perf_highest_shape.graphics.s("#888").f("#FFF").rr(x - HIGHEST_WIDTH / 2, y - HIGHEST_HEIGHT / 2, HIGHEST_WIDTH, HIGHEST_HEIGHT, 2);
  432. perf_highest_shape.i = highest_i_perf;
  433. var highest_perf_text = newText(stage_graph, x, y, "12px Lato");
  434. highest_perf_text.text = "Highest(Perf): " + perf_rating_history[highest_i_perf].Performance;
  435. perf_highest_shape.addEventListener("mouseover", mouseoverVertex);
  436. perf_highest_shape.addEventListener("mouseout", mouseoutVertex);
  437. }
  438.  
  439. // 線を描画
  440. for (let index = 0; index < 2; index++) {
  441. if (index == 0) perf_line_shape.graphics.s("#AAA").ss(2);
  442. else perf_line_shape.graphics.s("#F00").ss(0.5);
  443. perf_line_shape.graphics.mt(perf_vertex_shapes[0].x, perf_vertex_shapes[0].y);
  444. for (let i = 0; i < perf_rating_history.length; i++) {
  445. perf_line_shape.graphics.lt(perf_vertex_shapes[i].x, perf_vertex_shapes[i].y);
  446. }
  447. }
  448.  
  449. // 最高パフォの吹き出し描画
  450. if (click_num2%2===0){
  451. perf_chart_container.visible = true
  452. highest_perf_text.text = "Highest(Perf): " + perf_rating_history[highest_i_perf].Performance;;
  453. stage_graph.update();
  454. }
  455. else{
  456. perf_chart_container.visible = false
  457. highest_perf_text.text = "";
  458. stage_graph.update();
  459. }
  460. }
  461.  
  462. // status情報初期化関数
  463. function initStatus(click_num5) {
  464. border_status_shape = newShape(stage_status);
  465. rating_text = newText(stage_status, OFFSET_X + 75, OFFSET_Y + STATUS_HEIGHT / 2, "48px 'Squada One'");
  466. perf_text = newText(stage_status, OFFSET_X + 75, OFFSET_Y + STATUS_HEIGHT / 2+25, "16px 'Squada One'")
  467. place_text = newText(stage_status, OFFSET_X + 160, OFFSET_Y + STATUS_HEIGHT / 2.7, "16px Lato");
  468. diff_text = newText(stage_status, OFFSET_X + 160, OFFSET_Y + STATUS_HEIGHT / 1.5, "11px Lato");
  469. diff_text.color = '#888';
  470. date_text = newText(stage_status, OFFSET_X + 200, OFFSET_Y + STATUS_HEIGHT / 4, "14px Lato");
  471. contest_name_text = newText(stage_status, OFFSET_X + 200, OFFSET_Y + STATUS_HEIGHT / 1.6, "20px Lato");
  472. date_text.textAlign = contest_name_text.textAlign = "left";
  473. contest_name_text.maxWidth = STATUS_WIDTH - 200 - 10;
  474. {
  475. let hitArea = new cj.Shape(); hitArea.graphics.f("#000").r(0, -12, contest_name_text.maxWidth, 24);
  476. contest_name_text.hitArea = hitArea;
  477. contest_name_text.cursor = "pointer";
  478. contest_name_text.addEventListener("click", function () {
  479. location.href = standings_url;
  480. });
  481. }
  482. particles = new Array();
  483. for (let i = 0; i < PARTICLE_MAX; i++) {
  484. particles.push(newText(stage_status, 0, 0, "64px Lato"));
  485. particles[i].visible = false;
  486. }
  487. setStatus(rating_history[rating_history.length - 1], perf_rating_history[perf_rating_history.length-1], false, click_num5);
  488. }
  489.  
  490. function getRatingPer(x) {
  491. let pre = COLORS[COLORS.length - 1][0] + STEP_SIZE;
  492. for (let i = COLORS.length - 1; i >= 0; i--) {
  493. if (x >= COLORS[i][0]) return (x - COLORS[i][0]) / (pre - COLORS[i][0]);
  494. pre = COLORS[i][0];
  495. }
  496. return 0;
  497. }
  498.  
  499. function getOrdinal(x) {
  500. let s = ["th", "st", "nd", "rd"], v = x % 100;
  501. return x + (s[(v - 20) % 10] || s[v] || s[0]);
  502. }
  503. function getDiff(x) {
  504. let sign = x == 0 ? 'ツア' : (x < 0 ? '-' : '+');
  505. return sign + Math.abs(x);
  506. }
  507.  
  508. // status更新
  509. function setStatus(data, data2, particle_flag, click_num3) {
  510. let date = new Date(data.EndTime * 1000);
  511. let rating = data.NewRating, old_rating = data.OldRating;
  512. let place = data.Place;
  513. let contest_name = data.ContestName;
  514. let perf = data2.Performance;
  515. let tmp = getColor(rating); let color = tmp[1], alpha = tmp[2];
  516. border_status_shape.graphics.c().s(color).ss(1).rr(OFFSET_X, OFFSET_Y, STATUS_WIDTH, STATUS_HEIGHT, 2);
  517. rating_text.text = rating;
  518. rating_text.color = color;
  519. perf_text.text = "perf: " + perf;
  520. place_text.text = getOrdinal(place);
  521. diff_text.text = getDiff(rating - old_rating);
  522. date_text.text = date.toLocaleDateString();
  523. contest_name_text.text = contest_name;
  524. if (particle_flag) {
  525. let particle_num = parseInt(Math.pow(getRatingPer(rating), 2) * (PARTICLE_MAX - PARTICLE_MIN) + PARTICLE_MIN);
  526. setParticles(particle_num, color, alpha, rating);
  527. }
  528. standings_url = data.StandingsUrl;
  529.  
  530. // 偶数回クリック(パフォグラフが表示されている)ならレートのしたにパフォを表示
  531. if (click_num3%2===0){
  532. perf_text.text = "perf: " + perf;
  533. stage_graph.update();
  534. }
  535. // 奇数回なら表示しない
  536. else{
  537. perf_text.text = ""
  538. stage_graph.update();
  539. }
  540. }
  541.  
  542. // ホバー時のレート変化アニメーション
  543. function setParticle(particle, x, y, color, alpha, star_flag) {
  544. particle.x = x;
  545. particle.y = y;
  546. let ang = Math.random() * Math.PI * 2;
  547. let speed = Math.random() * 4 + 4;
  548. particle.vx = Math.cos(ang) * speed;
  549. particle.vy = Math.sin(ang) * speed;
  550. particle.rot_speed = Math.random() * 20 + 10;
  551. particle.life = LIFE_MAX;
  552. particle.visible = true;
  553. particle.color = color;
  554.  
  555. if (star_flag) {
  556. particle.text = "★";
  557. } else {
  558. particle.text = "@";
  559. }
  560. particle.alpha = alpha;
  561. }
  562. function setParticles(num, color, alpha, rating) {
  563. for (let i = 0; i < PARTICLE_MAX; i++) {
  564. if (i < num) {
  565. setParticle(particles[i], rating_text.x, rating_text.y, color, alpha, rating >= STAR_MIN);
  566. } else {
  567. particles[i].life = 0;
  568. particles[i].visible = false;
  569. }
  570. }
  571. }
  572. function updateParticle(particle) {
  573. if (particle.life <= 0) {
  574. particle.visible = false;
  575. return;
  576. }
  577. particle.x += particle.vx;
  578. particle.vx *= 0.9;
  579. particle.y += particle.vy;
  580. particle.vy *= 0.9;
  581. particle.life--;
  582. particle.scaleX = particle.scaleY = particle.life / LIFE_MAX;
  583. particle.rotation += particle.rot_speed;
  584. }
  585. function updateParticles() {
  586. for (let i = 0; i < PARTICLE_MAX; i++) {
  587. if (particles[i].life > 0) {
  588. updateParticle(particles[i]);
  589. }
  590. }
  591. }
  592.  
  593.  
  594. // main関数
  595. async function main() {
  596.  
  597. //get json
  598. let allJson;
  599. try {
  600. const url=`https://atcoder.jp/users/${username}/history/json`; // 参加したコンテスト情報のjson
  601. const res = await fetch(url);
  602. allJson = await res.json()
  603. // console.log(allJson.length)
  604. // console.log(allJson)
  605. } catch (reaseon) { console.log('try失敗') }
  606. {
  607. // rated参加ならデータに追加
  608. for (let i = 0; i < allJson.length; i++) {
  609. if (allJson[i].IsRated===true){
  610. perf_rating_history.push({ ...allJson[i] });
  611. }
  612. // パフォが低いものはマイナスパフォになってることがあるのでマイナスの場合は0としておく
  613. for (let i = 0; i < perf_rating_history.length; i++) {
  614. if (perf_rating_history[i].Performance<0){
  615. perf_rating_history[i].Performance = 0;
  616. }
  617. }
  618. //console.log(perf_rating_history)
  619. }
  620. // console.log(perf_rating_history)
  621. }
  622.  
  623. // 描画関数実行
  624. // onoffボタンがクリックされたら切り替え
  625. init(0);
  626. let clickCount1 = 0;
  627. let onoffButton = document.getElementById('onoffButton');
  628. onoffButton.addEventListener('click', function () {
  629. clickCount1 += 1
  630. init(clickCount1);
  631. // console.log(clickCount1)
  632. // console.log(perf_chart_container.visible)
  633. })
  634. }
  635.  
  636. main();