webサイト上で動く円グラフのアニメーション

# 完成図
:::note info
**使い方**
1. Addボタンを押す
1. 要素の出力したい角度を入力
1. 要素を欲しい数だけ繰り返し追加する
1. Generationボタンを押す
1. Start Animationボタンを押す
:::

:::note warn
**注意点**
Addボタンで追加した要素を後から変更した場合は必ずGenerationボタンを押すこと
:::


<p class="codepen" data-height="500" data-default-tab="result" data-slug-hash="VwNJJPm"</p>
<script async src="https://static.codepen.io/assets/embed/ei.js"></script>

# code
こちらを基に応用させたものである

https://qiita.com/zono0013/items/f6d9657c043b5a52af3d

## HTML
```index.html
<div id="selects">
    <div>
      <button onclick="startAnimation()">Start Animation</button>
      <button onclick="all_reset()">Reset Animation</button>
    </div>

    <div id="list"></div>   
    
    <button id="addButton">Add</button> 
    <button id="generationButton">Generation</button>
</div>

<div id="circle" class="circle"></div>
```

## CSS
```index.css
  body {
    display: grid;
    place-items: center;
    height: calc(100vh - 16px);
    width: calc(100vw - 16px);
    grid-template-columns: repeat(5, 1fr);
    grid-template-rows: repeat(5, 1fr);
  }
  #selects {
    height: 100%;
    width: 100%;
    grid-column: 1/6;
    grid-row: 1/2;
    display: grid;
    align-items: center;
    justify-content: center;
    padding: 20px;
  }
  .circle {
    width: 200px;
    height: 200px;
    display: grid;
    grid-template-columns: 1fr;
    grid-template-rows: 1fr;
    grid-column: 2/5;
    grid-row: 2/5;
    background-color: gray; /* 円の背景色 */
    border-radius: 50%; /* 円形にする */
    position: relative;
    overflow: hidden;
  }
```

## JS
```index.js
// ランダムな色を生成する関数
function getRandomColor() {
  // 0 から 255 の間のランダムな整数を生成して、RGB値を作成する
  var r = Math.floor(Math.random() * 256);
  var g = Math.floor(Math.random() * 256);
  var b = Math.floor(Math.random() * 256);

  // RGB値をCSSの色の形式に変換して返す
  return 'rgb(' + r + ',' + g + ',' + b + ')';
}

// addボタンがクリックされたときの処理
document.getElementById('addButton').addEventListener('click', function() {
  var list = document.getElementById('list');
  var newItem = document.createElement('div');
  var newIndex = list.children.length + 1;

  newItem.innerHTML = `
  <label for="title${newIndex}_percent">角度を入力</label>
  <input type="number" class="percent" id="title${newIndex}_percent" name="title${newIndex}_percent">
  `;

  list.appendChild(newItem);

  // 追加された要素の数値入力フィールドを取得
  var newNumberInput = document.getElementById('title' + newIndex + '_percent');

  // 追加された要素の数値入力フィールドが空でない場合、ボタンを有効にする
  if (!newNumberInput.value.trim()) {
    document.getElementById('addButton').disabled = true;
  } else {
    document.getElementById('addButton').disabled = false;
  }

  // 追加された要素の数値入力フィールドの値が変更されるたびにチェックする
  newNumberInput.addEventListener('input', function() {
    if (!this.value.trim()) {
      document.getElementById('addButton').disabled = true;
    } else {
      document.getElementById('addButton').disabled = false;
    }
  });
});

document.getElementById('generationButton').addEventListener('click', function() {
  var circle = document.getElementById('circle');
  circle.innerHTML = ''; // 円を空にする

  var len = document.getElementById('list').children.length; 
  var rotates = 0;

  // 360度を超えるかどうかをチェック
  var check = 0;
  for (var k = 1; k <= len; k++) {
    var timeInput = document.getElementById('title' + k + '_percent');
    check += Number(timeInput.value);
    if (check > 360) {
      alert("360度を超えています. 値を再入力してください");
      return;
    }
  }

  // 各要素を生成
  for (var t = 1; t <= len; t++) {
    var timeInput = document.getElementById('title' + t + '_percent');
    var color = getRandomColor();
    var count = Math.ceil(Number(timeInput.value) / 60); // 60度ごとの要素の数

    for (var i = 0; i < count; i++) {
      var newIndex_c = circle.children.length + 1;
      var newTriangle = document.createElement('div');
      newTriangle.style.height = '0';
      newTriangle.style.width = '0';
      newTriangle.style.display = 'grid';
      newTriangle.style.gridColumn = '1/2';
      newTriangle.style.gridRow = '1/2';

      var angle = (i + 1) * 60; // 60度ごとの角度
      var myangle = Math.min(Number(timeInput.value) - i * 60, 60); // 残りの角度
      console.log("rotates: " + rotates + "  myangle: " + myangle);

      newTriangle.style.transform = 'translate(calc(200px / 2), calc(200px / 2 - 200px * 1.25 * cos(var(--angle) * 1deg))) rotate(' + rotates + 'deg)';
      newTriangle.style.transformOrigin = 'left bottom';
      newTriangle.style.borderRight = 'calc(200px * 1.25 * sin(calc(var(--angle) * 1deg))) solid transparent';
      newTriangle.style.borderTop = 'calc(200px * 1.25 * cos(calc(var(--angle) * 1deg))) solid ' + color;
      newTriangle.classList.add('triangle' + newIndex_c);
      newTriangle.id = 'triangle' + newIndex_c;
      newTriangle.setAttribute('data-myangle', myangle);

      circle.appendChild(newTriangle);

      rotates += myangle;
    }
  }
});
var reset_times = true; // アニメーションをリセットするかどうかのフラグ
var animatnow = false; // アニメーション中かどうかのフラグ

// アニメーション開始関数
function startAnimation() {
  smoothTransition(360, 10);
}

// アニメーション関数(滑らかに移動する関数) 
function smoothTransition() {
  animatnow = true;
  var currentAngle = 0; // 現在の角度
  var frames = 100; // アニメーションのフレーム数
  var duration = 1000; // アニメーションの期間(大まかな)
  var interval = duration / frames; // フレーム間の間隔（ミリ秒）
  var increment = (360 - currentAngle) / frames; // 各フレームでの角度の増加量
  var circle = document.getElementById('circle');
  var newIndex_c = circle.children.length + 1;

  var animation = setInterval(function() {
    // 現在の角度を更新
    currentAngle += increment;
    for(var i = 1; i < newIndex_c; i++){
      console.log('changeAngle:' + i)
      changeAngle(currentAngle, i); // changeAngle の実行結果を取得
    }

    // アニメーションが終了(リセット)したらクリアする
    if (currentAngle >= 360 || !reset_times) {
      if(!reset_times){
        reset_angle();
      }
      animatnow = false;
      reset_times = true;
      clearInterval(animation);
    }
  }, interval);
}

// 角度を変更する関数
function changeAngle(angle, i){
  var timeInput = document.getElementById('triangle' + i);
  console.log("changeAngle:" + i)
  var timeValue = timeInput.getAttribute('data-myangle');
  console.log("timeValue:" + timeValue + "  i:" + i)

  // 既に設定された角度の合計を計算
  var per = 0;
  if(i>1){
    for (var j = 1; j < i; j++) {
      var timeInput_j = document.getElementById('triangle' + j);
      var timeValue_j = timeInput_j.getAttribute('data-myangle');
      per += Number(timeValue_j);
    }
  } 
  angle = Number(angle);
  console.log("per:" + per + "  timeValue:" + timeValue + "  angle:" + angle + "  i:" + i)
  if(angle >= Number(timeValue) + per){
    document.getElementById('triangle' + i).style.setProperty('--angle', Number(timeValue) + 0.1);
  }else{
    if(0>angle - per){
      document.getElementById('triangle' + i).style.setProperty('--angle', 0);
    }else{
      document.getElementById('triangle' + i).style.setProperty('--angle', angle - per);
    }
  }

}

// アニメーション終了関数(角度をリセットする関数)
function reset_angle(){
  var circle = document.getElementById('circle');
  var newIndex_c = circle.children.length + 1;
  for(var i = 1; i < newIndex_c; i++){
    document.getElementById('triangle' + i).style.setProperty('--angle', 0);
  }
}

// 初期化
function all_reset(){
  if(animatnow){
    reset_times = false;
  }else{
    reset_angle();
  }
}
```