D3.jsとdivタグとCSSを使って単色(透明度)ヒートマップカレンダーを作る。

#はじめに
自作ツールにヒートマップカレンダーが欲しくなったので作ってみた。
d3.jsを勉強していたのでそれを使いつつ、divタグで適当に積み上げてそれをcssで固定させるイメージ。
動作はChrome推奨。(Edgeとかで見れなかった。)
~~~テストデータはよくよく見ると全月３１日まであったりする。~~~
※ちなみに、CSSはflexを使用しているが勉強不足なのでもしかしたら変な書き方があるかもしれない。
##参考イメージ
![image](https://qiita-image-store.s3.amazonaws.com/0/53550/af031a44-88dd-67c4-e417-3909c57c9c6e.png)

#ソース
```html:Calendar.html
<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8">
    <title>D3-ヒートマップカレンダー</title>
<style>
/*最大範囲を決定。*/
#container{
  margin: 0;
  padding: 0;
  width:  1200px; /*可変長で範囲を指定できるが、少なすぎると文字が潰れる。*/
  display: flex;
}

#main{
  width:100%;
  height:100%;
  display: flex;
  flex-flow: row wrap;
  background:rgba(255, 252, 242,0.5)
}
/*1ヶ月分の範囲を決定*/
#main .flex{
  margin:10px 0px 0px 0px;
  display: flex;
  width:  8%; /*1年 = 1レコード*/
  flex-flow: row wrap; 
  /*border:#ff0000 solid 1px;*/ /*境界線はみ出し確認デバッグ用*/
}
/*1日分の範囲を決定。*/
#main .flex .flex-item{
  width : 14.2%; /* 7曜日をここで表現*/
  font-size:10%;
  border-radius: 0.4em;
}
/*---------------------------------------*/

#container .MonthText{
    width:100%;
    text-align:center;
    font-size:70%;
}
</style>
    <script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script>
</head>

<body>
<h1>D3-ヒートマップカレンダー</h1>

<!--  -->
<div id="container">
<div id="main"></div>
</div>
<script>
    const MAX_LENGTH = 10;//テストデータの最大行数。10なら10年分出力。
    //この辺はテストデータ。
    const dataset1 = [['2008/1/1',0.61],['2008/1/2',0.41],['2008/1/3',0.89],['2008/1/4',0.74],['2008/1/5',0.62],['2008/1/6',0.75],['2008/1/7',0.16],['2008/1/8',0.96],['2008/1/9',0.56],['2008/1/10',0.98],['2008/1/11',0.36],['2008/1/12',0.24],['2008/1/13',0.97],['2008/1/14',0.78],['2008/1/15',0.85],['2008/1/16',0.77],['2008/1/17',0.54],['2008/1/18',0.58],['2008/1/19',0.62],['2008/1/20',0.42],['2008/1/21',0.01],['2008/1/22',0.72],['2008/1/23',0.51],['2008/1/24',0.46],['2008/1/25',0.11],['2008/1/26',0.98],['2008/1/27',0.67],['2008/1/28',0.5 ],['2008/1/29',0.02],['2008/1/30',0.26],['2008/1/31',0.47]]
    const dataset2 = [['2008/2/1',0.61],['2008/2/2',0.41],['2008/2/3',0.89],['2008/2/4',0.74],['2008/2/5',0.62],['2008/2/6',0.75],['2008/2/7',0.16],['2008/2/8',0.96],['2008/2/9',0.56],['2008/2/10',0.98],['2008/2/11',0.36],['2008/2/12',0.24],['2008/2/13',0.97],['2008/2/14',0.78],['2008/2/15',0.85],['2008/2/16',0.77],['2008/2/17',0.54],['2008/2/18',0.58],['2008/2/19',0.62],['2008/2/20',0.42],['2008/2/21',0.01],['2008/2/22',0.72],['2008/2/23',0.51],['2008/2/24',0.46],['2008/2/25',0.11],['2008/2/26',0.98],['2008/2/27',0.67],['2008/2/28',0.5 ],['2008/2/29',0.02],['2008/2/30',0.26],['2008/2/31',0.47]]
    const dataset3 = [['2008/3/1',0.61],['2008/3/2',0.41],['2008/3/3',0.89],['2008/3/4',0.74],['2008/3/5',0.62],['2008/3/6',0.75],['2008/3/7',0.16],['2008/3/8',0.96],['2008/3/9',0.56],['2008/3/10',0.98],['2008/3/11',0.36],['2008/3/12',0.24],['2008/3/13',0.97],['2008/3/14',0.78],['2008/3/15',0.85],['2008/3/16',0.77],['2008/3/17',0.54],['2008/3/18',0.58],['2008/3/19',0.62],['2008/3/20',0.42],['2008/3/21',0.01],['2008/3/22',0.72],['2008/3/23',0.51],['2008/3/24',0.46],['2008/3/25',0.11],['2008/3/26',0.98],['2008/3/27',0.67],['2008/3/28',0.5 ],['2008/3/29',0.02],['2008/3/30',0.26],['2008/3/31',0.47]]
    const dataset4 = [['2008/4/1',0.61],['2008/4/2',0.41],['2008/4/3',0.89],['2008/4/4',0.74],['2008/4/5',0.62],['2008/4/6',0.75],['2008/4/7',0.16],['2008/4/8',0.96],['2008/4/9',0.56],['2008/4/10',0.98],['2008/4/11',0.36],['2008/4/12',0.24],['2008/4/13',0.97],['2008/4/14',0.78],['2008/4/15',0.85],['2008/4/16',0.77],['2008/4/17',0.54],['2008/4/18',0.58],['2008/4/19',0.62],['2008/4/20',0.42],['2008/4/21',0.01],['2008/4/22',0.72],['2008/4/23',0.51],['2008/4/24',0.46],['2008/4/25',0.11],['2008/4/26',0.98],['2008/4/27',0.67],['2008/4/28',0.5 ],['2008/4/29',0.02],['2008/4/30',0.26],['2008/4/31',0.47]]
    const dataset5 = [['2008/5/1',0.61],['2008/5/2',0.41],['2008/5/3',0.89],['2008/5/4',0.74],['2008/5/5',0.62],['2008/5/6',0.75],['2008/5/7',0.16],['2008/5/8',0.96],['2008/5/9',0.56],['2008/5/10',0.98],['2008/5/11',0.36],['2008/5/12',0.24],['2008/5/13',0.97],['2008/5/14',0.78],['2008/5/15',0.85],['2008/5/16',0.77],['2008/5/17',0.54],['2008/5/18',0.58],['2008/5/19',0.62],['2008/5/20',0.42],['2008/5/21',0.01],['2008/5/22',0.72],['2008/5/23',0.51],['2008/5/24',0.46],['2008/5/25',0.11],['2008/5/26',0.98],['2008/5/27',0.67],['2008/5/28',0.5 ],['2008/5/29',0.02],['2008/5/30',0.26],['2008/5/31',0.47]]
    const dataset6 = [['2008/6/1',0.61],['2008/6/2',0.41],['2008/6/3',0.89],['2008/6/4',0.74],['2008/6/5',0.62],['2008/6/6',0.75],['2008/6/7',0.16],['2008/6/8',0.96],['2008/6/9',0.56],['2008/6/10',0.98],['2008/6/11',0.36],['2008/6/12',0.24],['2008/6/13',0.97],['2008/6/14',0.78],['2008/6/15',0.85],['2008/6/16',0.77],['2008/6/17',0.54],['2008/6/18',0.58],['2008/6/19',0.62],['2008/6/20',0.42],['2008/6/21',0.01],['2008/6/22',0.72],['2008/6/23',0.51],['2008/6/24',0.46],['2008/6/25',0.11],['2008/6/26',0.98],['2008/6/27',0.67],['2008/6/28',0.5 ],['2008/6/29',0.02],['2008/6/30',0.26],['2008/6/31',0.47]]
    const dataset7 = [['2008/7/1',0.61],['2008/7/2',0.41],['2008/7/3',0.89],['2008/7/4',0.74],['2008/7/5',0.62],['2008/7/6',0.75],['2008/7/7',0.16],['2008/7/8',0.96],['2008/7/9',0.56],['2008/7/10',0.98],['2008/7/11',0.36],['2008/7/12',0.24],['2008/7/13',0.97],['2008/7/14',0.78],['2008/7/15',0.85],['2008/7/16',0.77],['2008/7/17',0.54],['2008/7/18',0.58],['2008/7/19',0.62],['2008/7/20',0.42],['2008/7/21',0.01],['2008/7/22',0.72],['2008/7/23',0.51],['2008/7/24',0.46],['2008/7/25',0.11],['2008/7/26',0.98],['2008/7/27',0.67],['2008/7/28',0.5 ],['2008/7/29',0.02],['2008/7/30',0.26],['2008/7/31',0.47]]
    const dataset8 = [['2008/8/1',0.61],['2008/8/2',0.41],['2008/8/3',0.89],['2008/8/4',0.74],['2008/8/5',0.62],['2008/8/6',0.75],['2008/8/7',0.16],['2008/8/8',0.96],['2008/8/9',0.56],['2008/8/10',0.98],['2008/8/11',0.36],['2008/8/12',0.24],['2008/8/13',0.97],['2008/8/14',0.78],['2008/8/15',0.85],['2008/8/16',0.77],['2008/8/17',0.54],['2008/8/18',0.58],['2008/8/19',0.62],['2008/8/20',0.42],['2008/8/21',0.01],['2008/8/22',0.72],['2008/8/23',0.51],['2008/8/24',0.46],['2008/8/25',0.11],['2008/8/26',0.98],['2008/8/27',0.67],['2008/8/28',0.5 ],['2008/8/29',0.02],['2008/8/30',0.26],['2008/8/31',0.47]]
    const dataset9 = [['2008/9/1',0.61],['2008/9/2',0.41],['2008/9/3',0.89],['2008/9/4',0.74],['2008/9/5',0.62],['2008/9/6',0.75],['2008/9/7',0.16],['2008/9/8',0.96],['2008/9/9',0.56],['2008/9/10',0.98],['2008/9/11',0.36],['2008/9/12',0.24],['2008/9/13',0.97],['2008/9/14',0.78],['2008/9/15',0.85],['2008/9/16',0.77],['2008/9/17',0.54],['2008/9/18',0.58],['2008/9/19',0.62],['2008/9/20',0.42],['2008/9/21',0.01],['2008/9/22',0.72],['2008/9/23',0.51],['2008/9/24',0.46],['2008/9/25',0.11],['2008/9/26',0.98],['2008/9/27',0.67],['2008/9/28',0.5 ],['2008/9/29',0.02],['2008/9/30',0.26],['2008/9/31',0.47]]
    const dataset10 = [['2008/10/1',0.61],['2008/10/2',0.41],['2008/10/3',0.89],['2008/10/4',0.74],['2008/10/5',0.62],['2008/10/6',0.75],['2008/10/7',0.16],['2008/10/8',0.96],['2008/10/9',0.56],['2008/10/10',0.98],['2008/10/11',0.36],['2008/10/12',0.24],['2008/10/13',0.97],['2008/10/14',0.78],['2008/10/15',0.85],['2008/10/16',0.77],['2008/10/17',0.54],['2008/10/18',0.58],['2008/10/19',0.62],['2008/10/20',0.42],['2008/10/21',0.01],['2008/10/22',0.72],['2008/10/23',0.51],['2008/10/24',0.46],['2008/10/25',0.11],['2008/10/26',0.98],['2008/10/27',0.67],['2008/10/28',0.5 ],['2008/10/29',0.02],['2008/10/30',0.26],['2008/10/31',0.47]]
    const dataset11 = [['2008/11/1',0.61],['2008/11/2',0.41],['2008/11/3',0.89],['2008/11/4',0.74],['2008/11/5',0.62],['2008/11/6',0.75],['2008/11/7',0.16],['2008/11/8',0.96],['2008/11/9',0.56],['2008/11/10',0.98],['2008/11/11',0.36],['2008/11/12',0.24],['2008/11/13',0.97],['2008/11/14',0.78],['2008/11/15',0.85],['2008/11/16',0.77],['2008/11/17',0.54],['2008/11/18',0.58],['2008/11/19',0.62],['2008/11/20',0.42],['2008/11/21',0.01],['2008/11/22',0.72],['2008/11/23',0.51],['2008/11/24',0.46],['2008/11/25',0.11],['2008/11/26',0.98],['2008/11/27',0.67]]
    const dataset12 = [['2008/12/1',0.61],['2008/12/2',0.41],['2008/12/3',0.89],['2008/12/4',0.74],['2008/12/5',0.62],['2008/12/6',0.75],['2008/12/7',0.16],['2008/12/8',0.96],['2008/12/9',0.56],['2008/12/10',0.98],['2008/12/11',0.36],['2008/12/12',0.24],['2008/12/13',0.97],['2008/12/14',0.78],['2008/12/15',0.85],['2008/12/16',0.77],['2008/12/17',0.54],['2008/12/18',0.58],['2008/12/19',0.62],['2008/12/20',0.42],['2008/12/21',0.01],['2008/12/22',0.72],['2008/12/23',0.51],['2008/12/24',0.46],['2008/12/25',0.11],['2008/12/26',0.98],['2008/12/27',0.67],['2008/12/28',0.5 ],['2008/12/29',0.02],['2008/12/30',0.26],['2008/12/31',0.47]]
    
    
    function Calendar(datasetOriginal){
       //引数をコピー
       let dataset = Object.assign([], datasetOriginal); 
       const firstDate = new Date(dataset[0][0]);
       //初回日だけ見て曜日位置決定。日=0なので先頭で、土=6なので土が末行にくる。
       (function () {
           for(let i=0;i < firstDate.getDay();i++){
               dataset.unshift([null,null]);
           }
       }());
       //headをセット
       (function () {
           //アンシフトなので、逆順に入れてあげる。
           const aryHead = new Array('土','金','木','水','火','月','日');
           for(let i=0;i < aryHead.length + 1;i++){
               dataset.unshift(['head',aryHead[i]]);
           }
       }());
       //メイン処理
       (function () {
          let flex = d3.select("#main")
                .append("div")
                .attr("class", "flex")
                
          flex.data([firstDate.getMonth()+1])
                .append("div")
                .attr("class","MonthText")
                .text(function(d){
                    return firstDate.getFullYear() + "年" + d + "月\r\n";
                });
          
          flex.selectAll("div")
              .data(dataset)
              .enter()
              .append("div")
              .attr("class", "flex-item")
              .attr("style",
                   function(d){
                      if(d[0] === 'head'){
                        return "background-color:rgba(255, 194, 193,0.5);";
                      }else if(d[0] === null){
                        return ""; 
                      }else{
                        
                        //d[1] = Math.random();//とりあえず濃度をランダムにする。
                        return "background-color:rgba(0, 0, 255," + d[1] + ");" + (d[1] >= 0.7 ? "color:white" : "");
                      }
                  })
              .text(function(d){
                  if(d[0] === 'head'){
                      return d[1];
                  }else if(d[0] === null){
                      return "";
                  }else{
                      return d[0].split("/")[2];
                  }
                  
              });
        }());
    }
    
    var MakeCalendar = function(list){
        Calendar(list.month01);
        Calendar(list.month02);
        Calendar(list.month03);
        Calendar(list.month04);
        Calendar(list.month05);
        Calendar(list.month06);
        Calendar(list.month07);
        Calendar(list.month08);
        Calendar(list.month09);
        Calendar(list.month10);
        Calendar(list.month11);
        Calendar(list.month12);
    }
    
    //テストデータを月単位にセットする。
    let yearList = {};
    yearList.month01 = dataset1;
    yearList.month02 = dataset2;
    yearList.month03 = dataset3;
    yearList.month04 = dataset4;
    yearList.month05 = dataset5;
    yearList.month06 = dataset6;
    yearList.month07 = dataset7;
    yearList.month08 = dataset8;
    yearList.month09 = dataset9;
    yearList.month10 = dataset10;
    yearList.month11 = dataset11;
    yearList.month12 = dataset12;
    //実際のデータ作成。MAX_LENGTHは最大年数に見立ててる。
    for(let k=0;k < MAX_LENGTH;k++){
    	//1年単位で作成
    	MakeCalendar(yearList);
    }
    
</script>
</body>

</html>
```
#最後に
- rgbaでの透明度の設定を行っている。（グラデーションとか使えないのか検討。）
- 濃度を細かく設定しているが、
  おそらく5段階ぐらいに分類するように作り変えたほうがいいかもしれない。(差別化が難しい。）
- テストデータ自体用意するのがめんどうなので、濃度を変えたい場合はランダム指定で。
- 見やすいかと思って年月と曜日を表示させて見たが安っぽさが否めない。（英語表記にすればマシかも。）
- 構造的に歯抜けの日付を考慮できない仕様。歯抜けが出ると曜日ズレが発生する。(初期曜日処理のように空白埋めれば一応できなくはない。）
- ~~~調べてないがヒートマップカレンダーライブラリのようなものを使えばかんたんかもしれない。~~~

実際に自分のツールに反映してみたが今日時点で３ヶ月分のデータしかないのであんまり役に立っている印象がないが、
サボりが見える化できたので常に確認するようにしていく。