CF解题数据可视化

显示某人Codeforces每个难度过了多少题,每个标签占比,喜欢用什么语言过题,有什么题试过了但是没有通过。

このスクリプトの質問や評価の投稿はこちら通報はこちらへお寄せください。
// ==UserScript==
// @name         CF解题数据可视化
// @name:en      codeforces analytics
// @namespace    https://codeforces.com/profile/tongwentao
// @version      1.5.4
// @description  显示某人Codeforces每个难度过了多少题,每个标签占比,喜欢用什么语言过题,有什么题试过了但是没有通过。
// @description:en Analyse Codeforces profiles
// @author       tongwentao
// @match        https://codeforces.com/profile/*
// @icon         data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==
// @grant        none
// @require https://cdn.jsdelivr.net/npm/[email protected]/dist/echarts.min.js
// @license MIT
// ==/UserScript==

(function() {
  'use strict';
  function drawChart(res){
    drawRatingChart(res);
    drawTagsChart(res);
    drawLangChart(res);
    drawUnsolvedChart(res);
  }

  function drawRatingChart(res){
      var div='<div class="roundbox userActivityRoundBox borderTopRound borderBottomRound" id="ratingChart" style="height:400px;padding:2em 1em 0 1em;margin-top:1em;"></div>';
      document.getElementById('pageContent').insertAdjacentHTML('beforeend',div);
      var chartDom = document.getElementById('ratingChart');
      var myChart = echarts.init(chartDom);
      var option;
      var key;
      window.addEventListener('resize', function() {
        myChart.resize();
      });
      var xData=[];
      var yData=[];
      xData=Object.keys(res.rating);
      for(key in xData){
          yData.push(res.rating[xData[key]]);
      }
      option = {
        title: {
          text: 'Problem Ratings',
          left: 'center'
        },
          tooltip: {
              trigger: 'axis',
              axisPointer: {
                type: 'shadow'
              }
            },
            grid: {
              left: '3%',
              right: '4%',
              bottom: '3%',
              containLabel: true
            },
            xAxis: [
              {
                type: 'category',
                data: xData,
                axisTick: {
                  alignWithLabel: true
                }
              }
            ],
            yAxis: [
              {
                type: 'value'
              }
            ],
            series: [
              {
                name: 'solved',
                type: 'bar',
                barWidth: '60%',
                data: yData.map((value, index) => ({
                    value: value,
                    itemStyle: {
                        color: `hsl(${(index * 35) % 360}, 70%, 50%)`
                    }
                }))
              }
            ]
      };

      option && myChart.setOption(option);

  }
  function drawTagsChart(res){

    var div='<div class="roundbox userActivityRoundBox borderTopRound borderBottomRound" id="tagsChart" style="height:400px;padding:2em 1em 0 1em;margin-top:1em;"></div>';
    document.getElementById('pageContent').insertAdjacentHTML('beforeend',div);
    var chartDom = document.getElementById('tagsChart');
    var myChart = echarts.init(chartDom);
    var option;
    var data1=[];
    var key;
    window.addEventListener('resize', function() {
      myChart.resize();
    });
    for(key in res.tags){
      var tag=res.tags[key];
      data1.push({value:tag,name:key});
    }
    data1.sort(function(nextValue,currentValue){
      if(nextValue.value<currentValue.value)
        return 1;
      else if(nextValue.value>currentValue.value)
        return -1;
      return 0;
    });
    var data2=[];
    for(key in data1){
      data2.push(data1[key].name);
    }
    option = {
      title: {
        text: 'Tags Solved',
        left: 'center'
      },
      tooltip: {
        trigger: 'item',
        formatter: '{a} <br/>{b} : {c} ({d}%)'
      },
      legend: {
        type: 'scroll',
        orient: 'vertical',
        right: 10,
        top: 20,
        bottom: 20,
        data: data2,
        formatter:function(name){
            let singleData = option.series[0].data.filter(function(item){
                return item.name == name
            })
            return  name + ' : ' + singleData[0].value;
        },
      },
      series: [
        {
          name: 'tag',
          type: 'pie',
          radius: '55%',
          center: ['40%', '50%'],
          data: data1,
          emphasis: {
            itemStyle: {
              shadowBlur: 10,
              shadowOffsetX: 0,
              shadowColor: 'rgba(0, 0, 0, 0.5)'
            }
          }
        }
      ]
    };

    option && myChart.setOption(option);
  }

  function drawLangChart(res){
    var div='<div class="roundbox userActivityRoundBox borderTopRound borderBottomRound" id="langChart" style="height:400px;padding:2em 1em 0 1em;margin-top:1em;"></div>';
    document.getElementById('pageContent').insertAdjacentHTML('beforeend',div);
    var chartDom = document.getElementById('langChart');
    var myChart = echarts.init(chartDom);
    var option;
    var data1=[];
    var key;
    window.addEventListener('resize', function() {
      myChart.resize();
    });
    for(key in res.lang){
      var lang=res.lang[key];
      data1.push({value:lang,name:key});
    }
    data1.sort(function(nextValue,currentValue){
      if(nextValue.value<currentValue.value)
        return 1;
      else if(nextValue.value>currentValue.value)
        return -1;
      return 0;
    });
    var data2=[];
    for(key in data1){
      data2.push(data1[key].name);
    }
    option = {
      title: {
        text: 'Programming Language',
        left: 'center'
      },
      tooltip: {
        trigger: 'item',
        formatter: '{a} <br/>{b} : {c} ({d}%)'
      },
      legend: {
        type: 'scroll',
        orient: 'vertical',
        right: 10,
        top: 20,
        bottom: 20,
        data: data2,
        formatter:function(name){
          let singleData = option.series[0].data.filter(function(item){
              return item.name == name
          })
          return  name + ' : ' + singleData[0].value;
        },
      },
      series: [
        {
          name: 'lang',
          type: 'pie',
          radius: '55%',
          center: ['40%', '50%'],
          data: data1,
          emphasis: {
            itemStyle: {
              shadowBlur: 10,
              shadowOffsetX: 0,
              shadowColor: 'rgba(0, 0, 0, 0.5)'
            }
          }
        }
      ]
    };

    option && myChart.setOption(option);
  }

  function drawUnsolvedChart(res) {
  // 创建容器 div
  var div = '<div class="roundbox userActivityRoundBox borderTopRound borderBottomRound" id="unsolvedChart" style="padding: 1em; margin-top: 1em;"></div>';
  document.getElementById('pageContent').insertAdjacentHTML('beforeend', div);

  // 设置标题样式
  var header = `<h4 style="font-size: 1.2em; color: #333; font-weight: bold; margin-bottom: 0.5em;">Unsolved Problems (total: ${Object.keys(res.unsolved).length})</h4>`;
  document.getElementById('unsolvedChart').insertAdjacentHTML('beforeend', header);

  // 设置列表容器样式
  var listContainer = '<div style="display: flex; flex-wrap: wrap; gap: 10px;"></div>';
  document.getElementById('unsolvedChart').insertAdjacentHTML('beforeend', listContainer);

  // 逐个添加未解决的问题链接
  var unsolvedDiv = document.getElementById('unsolvedChart').lastChild;
  var key, contestId, problemIndex, problemLink;
  for (key in res.unsolved) {
    contestId = res.unsolved[key].contestId;
    problemIndex = res.unsolved[key].problemIndex;

    // 根据 contestId 判断链接类型(正常题目 or gym 题目)
    if (contestId < 10000) {
      problemLink = `<a href="https://codeforces.com/problemset/problem/${contestId}/${problemIndex}" target="_blank" style="text-decoration: none; color: #0073e6; padding: 0.3em 0.6em; border-radius: 5px; border: 1px solid #0073e6; font-size: 0.9em;">${key}</a>`;
    } else {
      problemLink = `<a href="https://codeforces.com/problemset/gymProblem/${contestId}/${problemIndex}" target="_blank" style="text-decoration: none; color: #0073e6; padding: 0.3em 0.6em; border-radius: 5px; border: 1px solid #0073e6; font-size: 0.9em;">${key}</a>`;
    }
    unsolvedDiv.insertAdjacentHTML('beforeend', problemLink);
  }
}

  var pathname = window.location.pathname;
var handle = pathname.substring(pathname.lastIndexOf('/') + 1, pathname.length);
var httpRequest = new XMLHttpRequest();

// 添加加载动画
var loadingDiv = `<div id="loadingAnimation" style="position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%);
                  font-size: 1.5em; color: #0073e6; text-align: center;">
                    <div class="spinner" style="margin-bottom: 10px; border: 4px solid #f3f3f3; border-top: 4px solid #0073e6; border-radius: 50%; width: 40px; height: 40px; animation: spin 1s linear infinite;"></div>
                    Loading...
                 </div>
                 <style>
                    @keyframes spin {
                      0% { transform: rotate(0deg); }
                      100% { transform: rotate(360deg); }
                    }
                 </style>`;
document.body.insertAdjacentHTML('beforeend', loadingDiv);

httpRequest.open('GET', 'https://codeforces.com/api/user.status?handle=' + handle, true);
httpRequest.send();
httpRequest.onreadystatechange = function () {
  if (httpRequest.readyState == 4) {
    // 移除加载动画
    var loadingElem = document.getElementById('loadingAnimation');
    if (loadingElem) {
      loadingElem.remove();
    }

    if (httpRequest.status == 200) {
      var json = JSON.parse(httpRequest.responseText);
      var result = json.result;
      var res = { rating: {}, tags: {}, lang: {}, unsolved: {} };
      var solved = {};
      var key;
      var contestId;
      var problemIndex;
      var problemId;
      for (key in result) {
        if (result[key].verdict === "OK") {
          var rating = result[key].problem.rating;
          var tags = result[key].problem.tags;
          var lang = result[key].programmingLanguage;
          if (rating in res.rating) {
            res.rating[rating]++;
          } else {
            res.rating[rating] = 1;
          }
          for (var key2 in tags) {
            var tag = tags[key2];
            if (tag in res.tags) {
              res.tags[tag]++;
            } else {
              res.tags[tag] = 1;
            }
          }
          if (lang in res.lang) {
            res.lang[lang]++;
          } else {
            res.lang[lang] = 1;
          }
          contestId = result[key].problem.contestId;
          problemIndex = result[key].problem.index;
          problemId = contestId + problemIndex;
          if (!(problemId in solved)) {
            solved[problemId] = { contestId: contestId, problemIndex: problemIndex };
          }
        }
      }
      for (key in result) {
        if (result[key].verdict !== "OK") {
          contestId = result[key].problem.contestId;
          problemIndex = result[key].problem.index;
          problemId = contestId + problemIndex;
          if (!(problemId in solved)) {
            res.unsolved[problemId] = { contestId: contestId, problemIndex: problemIndex };
          }
        }
      }
      console.log(res);
      drawChart(res);
    }
  }
};


})();