Quetzall AI Lab ヒーローヘッダー

Quetzall AI Lab

【サイト拡張】学習時間ログのバーチャート作成
~積み上げ棒グラフの表示~

thumbnail_img

全体構想のとおり、学習時間を記録・表示するものを作成しました。この記事ではWebフォームから送信されデータベースに保存されている学習時間をバーチャートで表示するシステムを説明します。

目次

  1. 要件
  2. 全体の構成
  3. 全体フレームの作成
    1. show_bar_chart.php
  4. SQLデータベースのデータ処理
    1. calculate_study_log.php
  5. バーチャートの描画
    1. make_stack_bar_chart.js
  6. 成果物
  7. 参考文献

1. 要件

sqlite3に記録されたデータをPHPとJavaScriptを使って学習記録を表示するために、以下要件のクリアを目的としました。

  • 表示されるチャートは、月別、週別、日別の期間別に表示ができる
  • それぞれの期間はラジオボタン Month、Week、Date を選択することで表示が切り替わる
  • 学習教材別に学習時間を確認したいので、教材毎に色が異なる積み上げ棒グラフとする

2. 全体の構想

まず、本システムの階層図が以下のようになります。studylog フォルダ内のshow_bar_chart.phpに学習の記録を表示するような構造です。学習の記録である log.dbに記録されている学習時間データをcalculate_study_log.phpで集計し、集計したものをmake_stack_bar_chart.js へ渡してチャートで表示させます。

file_directory_chart

3. グラフ要素 全体フレームの作成

i. show_bar_chart.php

全体骨子であるshow_bar_chart.phpは大きく分けてHTMLとPHP、JavaScriptの3要素で構成しています。コードは以下の通りです。

                                
                                        <!DOCTYPE html>
                                        <html lang="ja">
                                        <body>
                                            <div class="pagewrap">
                                                <div class="graph_paper_chart">
                                                    <h2>Studylog</h2>
                                                    <fieldset id="period" style="margin-top: 5px;">
                                                    <input id="month" class="radio-inline__input" type="radio" name="accessible-radio" value="month"/>
                                                    <label class="radio-inline__label" for="month">
                                                        Month
                                                    </label>
                                                    <input id="week" class="radio-inline__input" type="radio" name="accessible-radio" value="week" checked/>
                                                    <label class="radio-inline__label" for="week">
                                                        Week
                                                    </label>
                                                    <input id="day" class="radio-inline__input" type="radio" name="accessible-radio" value="date"/>
                                                    <label class="radio-inline__label" for="day">
                                                        Date
                                                    </label>
                                                    </fieldset>
                                                    <canvas id="stackedBarChart"></canvas>
                                                </div>
                                            </div>
                                        </body>
                                        <?php include "local_php/calculate_study_log.php" /* 学習時間チャートに描画するためのデータを処理する */ ?>
                                        <!-- jQueryを読み込み -->
                                        <script src="local_scripts/jquery-3.4.1.min.js"></script>
                                        <!-- PHPで集計した値をmake_stack_bar_chart.js へ引き渡す処理。他の処理と異なり、外部ソース化ができないので注意。 -->
                                        <script>
                                            /* matrix_summaryとaxis_array_summaryの中身確認 */
                                            console.log("matrix_summary");
                                            let matrix_summary= JSON.parse('<?php echo $matrix_summary; ?>');
                                            console.dir(matrix_summary);
                                            console.log("axis_array_summary");
                                            const axis_array_summary=JSON.parse('<?php echo $axis_array_summary; ?>');
                                            console.dir(axis_array_summary);
                                            console.log("sum_array_summary");
                                            let sum_array_summary=JSON.parse('<?php echo $sum_array_summary; ?>');
                                            console.dir(sum_array_summary);
                                        </script>
                                        <!-- chart.jsを利用するための外部ファイルを参照 --><script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
                                        <!-- チャートを描画 --><script src="local_scripts/make_stack_bar_chart.js"></script>
                                        </html>
                                
                            

HTML箇所について

1行目〜24行目までがHTMLのコードであり、ブラウザで表示される部分となります。月別、週別、日別で学習記録が見られるよう、ラジオボタンで選択された期間を認識して、21行目のcanvasタグのチャートがそれに合わせて期間表示を変更する仕組みです。例えば、ラジオボタンMonthWeekへ切り替わった時にチャートの横軸も月単位から週単位へ変更されます。

PHP箇所について

25行目では,log.dbに記録されたデータを読み込み、処理するためのPHPプログラムcalculate_study_log.phpを読み込んでいます。プログラムの詳細は4.で説明します。

JavaScript箇所について

26行目~42行目までがJavaScriptの処理が記述された箇所です。
まず、本システムでは、ラジオボタン選択の変化を検知し、チャートを更新するのにjQueryを利用しています。そのため、jQueryを27行目で読み込んでいます。

次に、28行目から40行目までがPHPで処理されたデータ配列をJavaScriptで受け取るための処理となります。const x_js=JSON.parse('<?php echo $x_php; ?>');でPHPの変数$x_phpをJavascriptの変数x_jsで受け取ることができます[1]。なお、console.logとconsole.dirは変数の中身を確認するための処理となり、削除しても問題ありません。

そしてチャートをcanvasタグに描画するにあたり、chart.jsを事前に読み込んでおく必要があります。本システムでは41行目でネット上で公式公開されているchart.jsを読み込んでいます[2]

最後に、canvasタグに描画するにあたってPHPから受け取った変数をチャートへ反映する処理make_stack_bar_chart.jsを42行目で読み込んでいます。プログラムの詳細は5.で説明します。

4. SQLデータベースのデータ処理

i. calculate_study_log.php

このPHPファイルでは、学習時間のバーチャートを描画するための3つの連想配列を準備しています。

  • $axis_array_summary バーチャートの横軸要素(計測期間)を格納した配列
  • $matrix_summary バーチャートの積み上げ要素を格納した配列
  • $sum_array_summary バーチャートの各期間毎の合計学習時間(積み上げ要素の合計値)を格納した配列

それぞれがバーチャート上で、どの箇所に相当するのかを示したのが以下の図となります。

3つの連想配列では、月別、週別、日別の集計を格納しています。たとえば、$matrix_summary["month"]["MAY"]=[tA_MAY, tB_MAY, tC_MAY] では、月別集計した中で5月の学習時間を教材別に取得することとなります。* tA-MAY: 5月の教材A学習時間、 tB-MAY: 5月の教材B学習時間、 tC-MAY: 5月の教材C学習時間
以下が配列を表形式で表したものとなります。左端が配列の1次要素である計測期間の単位であり、上端が計測の期間periodxを指名しています。両方が交差する箇所がその計測期間に学習した時間となります。
$matrix_summaryは、バーチャートのバーの内訳であるため、1つの計測期間の内訳を配列形式で表現しており、$axis_array_summary$sum_array_summaryと異なり3次元連想配列なります。

$axis_array_summary
period1 period2 ... periodN
month JAN FEB ... DEC
week Week1 Week2 ... WeekN
date Day1 Day2 ... DayN
$matrix_summary
period1 period2 ... periodN
month {material A : tA-JAN, material B : tB-JAN, material C : tC-JAN} {material A : tA-FEB, material B : tB-FEB, material C : tC-FEB} ... {material A : tA-DEC, material B : tB-DEC, material C : tC-DEC}
week {material A : tA-Week1, material B : tB-Week1, material C : tC-Week1} {material A : tA-Week2, material B : tB-Week2, material C : tC-Week2} ... {material A : tA-WeekN, material B : tB-WeekN, material C : tC-WeekN}
date {material A : tA-Day1, material B : tB-Day1, material C : tC-Day1} {material A : tA-Day2, material B : tB-Day2, material C : tC-Day2} ... {material A : tA-DayN, material B : tB-DayN, material C : tC-DayN}
$sum_array_summary
period1 period2 ... periodN
month TJAN TFEB ... TDEC
week TWeek1 TWeek2 ... TWeekN
date TDay1 TDay2 ... TDayN

配列の1次要素は、show_bar_chart.phpのラジオボタン Month, Week, Date毎に対応するようになっています。つまり、選択されているラジオボタン名が配列の連想キー(=配列の第1次要素)となり、キー要素によって得られた値をグラフで表示する仕組みです。たとえば、ラジオボタンMonthが選択された時、上記の3つの連想配列にて連想キー"month"で参照することによって、3つの月別の要素を取得し、それをチャートで表示するようなイメージです。詳細は 4. で解説します。

                                
                                        <?php
                                          // 指定期間における勉強した教材種類を取り出す
                                          function matrix_initialization($db, $period_start, $period_end, $period_array) {
                                        
                                            // 学習した教材をPHP配列型へ変換・格納する
                                            $material_array = ["materialA", "materialB", "materialC"];
                                        
                                            // 取得した勉強種別$material_arrayと計測期間$period_arrayを使って勉強時間を集計する$matrixを初期化する
                                            $matrix = [];
                                            for ($i = 0; $i < count($material_array); $i++) {
                                                $matrix[$material_array[$i]] = [];
                                                for ($j = 0; $j < count($period_array); $j++) {
                                                    $matrix[$material_array[$i]][$period_array[$j]] = 0;
                                                }
                                            }
                                        
                                            return [$material_array, $matrix];
                                          }
                                        
                                          // 図の横軸に合計値を表示するため期間単位あたりの合計勉強時間を格納する配列を準備する[初期化する]
                                          function period_sum_initialization($period_array) {
                                            $period_sum_tmp_array = [];
                                            for ($i = 0; $i < count($period_array); $i++) {
                                              $period_sum_tmp_array[$i] = 0;
                                            }
                                        
                                            return $period_sum_tmp_array;
                                          }
                                        
                                          // period_sum_initializationで準備した配列にhourで入力された状態で渡された$period_sum_tmp_arrayを{hour}時間{min}分の形式に変形し直す
                                          function calculate_period_sum($period_sum_tmp_array) {
                                            $period_sum = [];
                                            for ($i = 0; $i < count($period_sum_tmp_array); $i++) {
                                              $hour = floor($period_sum_tmp_array[$i]);
                                              $min = round(($period_sum_tmp_array[$i] - $hour) * 60, 0);
                                              $period_sum[$i] = "{$hour}h {$min}m";
                                            }
                                        
                                            return $period_sum;
                                          }
                                        
                                          // 日曜日でない場合、直前の日曜日を取得する関数
                                          function get_just_before_sunday($day) {
                                            $day_of_week = (int)$day->format('w');// 今日の曜日を取得(0:日曜日、1:月曜日、... 6:土曜日)
                                            if ($day_of_week !== 0) {
                                              $day->modify('-' . $day_of_week . ' days');
                                            } // 今日が日曜日でない場合、直前の日曜日を取得
                                            $sunday = $day->format('Y-m-d');// 直前の日曜日の日付を取得
                                        
                                            return $sunday;
                                          }
                                        
                                          // 週別の勉強時間を集計するために、集計する週数分の日曜日(代表値)を取得する関数
                                          function make_week_array($day, $week_number) {
                                            $sunday = get_just_before_sunday($day);
                                            $week_array = [];
                                            for ($i = 0; $i < $week_number; $i++) {
                                              // 7週前の日曜日の日付を配列に格納
                                              $week_array[] = $day->format('Y-m-d');
                                              $day->modify('-1 week');
                                            }
                                        
                                            return $week_array;
                                          }
                                        
                                          function get_period_end_date ($period_end_sunday) {
                                            $end_date = new Datetime($period_end_sunday);
                                            $end_date->modify('+6 days');
                                            $period_end = $end_date->format('Y-m-d');
                                        
                                            return $period_end;
                                          }
                                        
                                          function convert_date_format ($str_date) {
                                            $date_tmp = new DateTime($str_date);
                                            $date_tmp = $date_tmp->format('m/d');
                                            return $date_tmp;
                                          }
                                        
                                          function make_date_array($day_number) {
                                            $today = new DateTime();
                                            $date_tmp = $today;
                                            $date_array = [];
                                            for ($i = 0; $i < $day_number; $i++) {
                                              $date = $date_tmp->format('Y-m-d');
                                              array_push(
                                                $date_array,
                                                $date
                                              );
                                              $date_tmp->modify('-1 days');
                                            }
                                            
                                            return $date_array;
                                          }
                                        
                                          date_default_timezone_set('Asia/Tokyo'); //日本のタイムゾーンに設定
                                          $today = new DateTime(); // 本日の日付を取得
                                          $year = date("Y");
                                          $month = date("m");
                                          
                                          // データベースに接続する
                                          $db_name = "log.db";
                                          $db = new SQLite3($db_name);
                                          
                                          // 月別に積み上げ棒グラフデータを集計する
                                            // 月別の配列を準備する
                                            $month_array = array("JAN", "FEB", "MAR", "APL", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC");
                                            // 教材種別ごとに当該月の勉強時間を格納する二次元配列(辞書型)の初期化
                                            $period_start = '{$year}-01-01';
                                            $period_end = '{$year}-12-31';
                                            list($month_material_array, $month_matrix) = matrix_initialization($db, $period_start, $period_end, $month_array);
                                        
                                            // 二次元配列に当該月の勉強時間の記録を格納していく
                                            $sum_month_tmp_array = period_sum_initialization($month_array); // 月あたり合計勉強時間を格納する配列を準備する
                                            $records = $db->query("SELECT date, hour, minute, material_id FROM study_log WHERE date BETWEEN '{$year}-01-01' AND '{$year}-12-31'");
                                            while($record = $records->fetchArray(SQLITE3_ASSOC)) {
                                              $rec_material = $record['material_id'];
                                              $rec_month = date('n',strtotime($record['date']));
                                              $rec_hour = intval($record['hour']);
                                              $rec_minute = intval($record['minute']);
                                              $month_matrix[$rec_material][$month_array[intval($rec_month) -1]] += $rec_hour + ($rec_minute / 60);
                                              $sum_month_tmp_array[intval($rec_month) -1] += $rec_hour + ($rec_minute / 60);
                                            }
                                            $sum_month_array = calculate_period_sum($sum_month_tmp_array);
                                        
                                          // 週別に積み上げ棒グラフデータを集計する(直近{$week_number}週間)
                                            $week_number = 8;// 本日から何週分遡った分の日曜日を取得するか
                                            // 直近{$week_number}週分の日曜日を取り出す
                                            $week_array_tmp = make_week_array($today, $week_number);
                                            $element_amount = count($week_array_tmp) - 1;// $week_array_tmpの要素数を取得しておく
                                            $period_start = $week_array_tmp[$element_amount]; 
                                            $period_end_sunday = $week_array_tmp[0];
                                            $period_end = get_period_end_date($period_end_sunday);
                                            for ($i = 0; $i <= $element_amount; $i++) {
                                              $week_array[$i] = convert_date_format($week_array_tmp[$element_amount - $i]);
                                            }
                                            list($week_material_array, $week_matrix) = matrix_initialization($db, $period_start, $period_end, $week_array);
                                        
                                            // 二次元配列に当該期間の勉強時間の記録を格納していく
                                            $sum_week_tmp_array = period_sum_initialization($week_array); // 週あたり合計勉強時間を格納する配列を準備する
                                            $records = $db->query("SELECT date, hour, minute, material_id FROM study_log WHERE date BETWEEN '{$period_start}' AND '{$period_end}'");
                                            while($record = $records->fetchArray(SQLITE3_ASSOC)) {
                                              $rec_material = $record['material_id'];
                                              if (in_array($rec_material, $week_material_array)) {
                                                $conv_to_date = New DateTime($record['date']);
                                                $sunday_tmp = get_just_before_sunday($conv_to_date);
                                                $sunday = convert_date_format($sunday_tmp);
                                                $rec_hour = intval($record['hour']);
                                                $rec_minute = intval($record['minute']);
                                                $week_matrix[$rec_material][$sunday] += $rec_hour + ($rec_minute / 60);
                                                $sum_week_tmp_array[array_search($sunday, $week_array)] += $rec_hour + ($rec_minute / 60);
                                              } 
                                            }
                                            $sum_week_array = calculate_period_sum($sum_week_tmp_array);
                                            
                                          // 日別に積み上げ棒グラフデータを集計する(直近8日間)
                                            $day_number = 8;// 本日から何日分遡って集計するか
                                            // 直近{$day_number}日分の日を取り出す
                                            $date_array_tmp = make_date_array($day_number);
                                            $element_amount = count($date_array_tmp) - 1;//date_array_tmpの要素数を取得しておく
                                            // 直近 {$day_number}日間に記録された勉強種別を取り出す
                                            $period_start = $date_array_tmp[count($date_array_tmp) - 1];//  $date_array_tmpは、要素番号が大きいほど古い日付となるので注意
                                            $period_end = $date_array_tmp[0];// $date_array_tmpは、要素番号が小さいほど新しい日付となる
                                            for ($i=0; $i<=$element_amount; $i++) {
                                              $date_array[$i] = convert_date_format($date_array_tmp[$element_amount - $i]);
                                            }
                                            
                                            list($date_material_array, $date_matrix) = matrix_initialization($db, $period_start, $period_end, $date_array_tmp);
                                        
                                            // 二次元配列に当該機関の勉強時間の記録を格納していく
                                            $sum_date_tmp_array = period_sum_initialization($date_array);
                                            $records = $db->query("SELECT date, hour, minute, material_id FROM study_log WHERE date BETWEEN '{$period_start}' AND '{$period_end}'");
                                            while($record = $records->fetchArray(SQLITE3_ASSOC)) {
                                              $rec_material = $record['material_id'];
                                              if (in_array($rec_material, $date_material_array)) {
                                                $date = convert_date_format($record['date']);
                                                $rec_hour = intval($record['hour']);
                                                $rec_minute = intval($record['minute']);
                                                $date_matrix[$rec_material][$date] += $rec_hour + ($rec_minute / 60);
                                                $sum_date_tmp_array[array_search($date, $date_array)] += $rec_hour + ($rec_minute / 60);
                                              }
                                            };
                                            $sum_date_array = calculate_period_sum($sum_date_tmp_array);
                                        
                                            $matrix_summary = array(
                                              "month" => $month_matrix,
                                              "week" => $week_matrix,
                                              "date" => $date_matrix
                                            );
                                            $matrix_summary = json_encode($matrix_summary);
                                        
                                            $axis_array_summary = array(
                                              "month" => $month_array,
                                              "week" => $week_array,
                                              "date" => $date_array
                                            );
                                            $axis_array_summary = json_encode($axis_array_summary);
                                        
                                            $sum_array_summary = array(
                                              "month" => $sum_month_array,
                                              "week" => $sum_week_array,
                                              "date" => $sum_date_array
                                            );
                                            $sum_array_summary = json_encode($sum_array_summary);
                                             
                                        ?>
                                
                            
  1. 指定期間に学習した教材種類を格納した配列と、学習時間を格納する二次元配列を用意する
  1. 教材を配列 $material_array に格納する
  1. 空の配列 $matrix を用意する
  2. line6の教材種類分だけループさせる
  1. $matrix の第1要素に教材種別を格納し、その値を空の配列にする ex) $matrix[教材A]=[空の配列]
  2. 指定期間の区分数をループさせる
  1. $matrix の第1要素に教材配列、第2要素に期間の区分を格納し、その値を0とする ex) $matrix[教材A][2月] = 0
  1. 教材を格納した配列 $material_array と学習時間を格納する二次元配列 $material を返す
  1. 全教材の学習時間を合算した値を格納するための配列を準備する
  1. 空の配列 $period_sum_tmp_array を準備する
  2. 指定期間の区分数をループさせる
  1. line22の $period_sum_tmp_array に0を格納する
  1. 指定期間の区分数分だけ0を格納した$period_sum_tmp_array を返す
  1. hour[時間]を格納した配列 $period_sum_tmp_array の値を hour[時間] -> hour[時間] 分[minute]へ変換する関数
  1. 空の配列 $period_sum を用意する
  2. $period_sum_tmp_array の要素をループさせる
  1. 空の配列 $period_sum を用意する
  2. $period_sum_tmp_array の要素をループさせる
  1. $period_sum_tmp_array の要素からfloor関数を使って小数点以下を切り捨てる。少数を切り捨てた値が合計hour[時間となるので、それを $hour へ代入する
  2. $period_sum_tmp_array の要素とline34の $hour の差をとり、60をかけることで 分[minute] を得る。その値を $min へ格納する。
  3. 配列$period_sum へ $hour と $min を文字列形式で格納する
  1. $period_sum を返す
  1. 引数 $day(曜日) の直前の日曜日を取得する関数
  1. 曜日を取得し、$day_of_week へ格納する
  2. 日曜日との日数差を算出し、$day_of_week が日曜日であるか確認する ex)月曜日の場合は日曜日との日数差が1となる
  1. 日曜日でないならば、$day_of_week分を差し引いて直前の日曜日を取得し、それを $day へ格納する
  1. $day の形式を'y-m-d'に変換し、$sundayへ格納する
  1. $sunday を返す
  1. 直近7週間の日曜日を取得し、それぞれを配列に格納する関数
  1. line42~51の関数から、直前の日曜日を取得する
  2. 空の配列 $week_array を準備する
  3. 週数 $week_number 分だけループさせる
  1. 日曜日 $day を'y-m-d'に変換し、配列$week_array に格納する
  2. 日曜日 $day から1週間分を引いて、その前の日曜日を取得する
  1. $day の形式を'y-m-d'に変換し、$sundayへ格納する
  1. 指定期間の最終日(土曜日)を取得する関数
  1. $end_date に指定期間の最終日曜日 $period_end_sunday を格納する
  2. $end_date(日曜日) に6日分足して土曜日にする。これが指定期間の最終日となる。
  3. $end_date を'Y-m-d'形式に変換し、$period_end に格納する
  1. $period_end を返す
  1. 日程(文字列型)を指定のフォーマットに変換する関数
  1. 引数 $str_date(文字列型) を日にち型に変換して、$date_tmp に格納する
  2. $date_tmpを指定のフォーマット'm/d'に変換する
  3. $date_tmp を返す
  1. 直近8日間を取得し、それぞれを配列に格納する関数
  1. 当日の日程を取得し、$today に格納する
  2. $today を $date_tmp に格納する
  3. 空の配列$date_array を準備する
  4. $day_number分(デフォルトで8日間)だけループさせる
  1. $date_tmp を指定のフォーマット 'Y-m-d' に変換し $date に格納する
  2. $date_array に $date を追加する
  1. $date_tmp から1日分差し引く
  1. $date_array を返す
  1. 日本のタイムゾーンに設定する
  2. 現在の日付と時刻を取得する
  3. 今年の年(YYYY)を取得
  4. 今月の月(MM)を取得
  1. 学習記録が保存されているデータベース名 log.db を取得する
  2. データベース log.db に接続する
  1. 月ごとの配列を定義する
  1. 集計期間の開始日を設定する
  2. 集計期間の終了日を設定する
  3. matrix_initialization 関数を使って、教材と勉強時間の集計用の配列を初期化する
  1. 月ごとの合計勉強時間を格納する配列を初期化する
  2. データベースから今年の勉強記録を取得
  1. $records から1行ずつデータを取得し、$record に連想配列(SQLITE3_ASSOC 形式)として格納していく
  1. $record 配列から material_id キーの値(教材種別)を取得し、$rec_material に格納する
  2. $record 配列から date キーの値を取得し、strtotime 関数でUNIXタイムスタンプに変換し、date 関数で月(1から12の整数)を取得して $rec_month に格納する
  3. $record 配列から hour キーの値を整数として取得し、$rec_hour に格納する
  4. $record 配列から minute キーの値を整数として取得し、$rec_minute に格納する
  5. $month_matrix 配列の $rec_material の $month_array に対応する月のキーの値に、$rec_hour と $rec_minute を時間単位に変換した値を加算する。これにより、特定の教材の月ごとの勉強時間が更新される。
  6. $sum_month_tmp_array 配列の $rec_month に対応するインデックスに、$rec_hour と $rec_minute を時間単位に変換した値を加算する。これにより、月ごとの合計勉強時間が更新される。
  1. 最後に、calculate_period_sum 関数を使って、$sum_month_tmp_array から $sum_month_array を計算します。
  1. $week_number を8に設定する。これは、現在の日から8週間前までの日曜日を取得するための設定となる。
  1. make_week_array 関数を使って、今日から過去8週間分の日曜日の日付を $week_array_tmp に格納した。
  2. $week_array_tmp の要素数から1を引いた値を $element_amount に格納した。これは配列の最後のインデックスを取得するため。
  3. $week_array_tmp の最後の要素を $period_start に格納した。これは期間の開始日を意味する。
  4. $week_array_tmp の最初の要素を $period_end_sunday に格納した。これは期間の終了日を意味する。
  5. get_period_end_date 関数を使って、$period_end_sunday から $period_end を取得した。これは期間の終了日を変換する。
  6. line130の要素数分だけループさせる
  7. ループを使って、$week_array_tmp の要素を逆順に $week_array に格納し、convert_date_format 関数で日付形式を変換した。
  1. matrix_initialization 関数を使って、$db、$period_start、$period_end、および $week_array を基に $week_material_array と $week_matrix を初期化した。
  1. period_sum_initialization 関数を使って、$week_array に基づいて $sum_week_tmp_array を初期化した。これは各週の合計勉強時間を格納するための配列。
  2. $db から study_log テーブルの date、hour、minute、および material_id 列を選択し、$period_start から $period_end の間のデータを $records に格納した。
  1. ループを使って、$records から1行ずつデータを取得し、$record に連想配列(SQLITE3_ASSOC 形式)として格納した。
  1. $record 配列から material_id キーの値(教材種別)を取得し、$rec_material に格納した。
  2. $rec_material が $week_material_array に含まれているかどうかを確認し、含まれていれば次の処理に進んだ。
  1. $record 配列から date キーの値を取得し、それを DateTime オブジェクトとして $conv_to_date に格納した。
  2. $conv_to_date の直前の日曜日を取得し、$sunday_tmp に格納した。
  3. $sunday_tmp のフォーマットを変換し、$sunday に格納した。
  4. $record 配列から hour キーの値を整数として取得し、$rec_hour に格納した。
  5. $record 配列から minute キーの値を整数として取得し、$rec_minute に格納した。
  6. $week_matrix 配列の $rec_material の $sunday キーの値に、$rec_hour と $rec_minute を時間単位に変換した値を加算した。これにより、特定の教材の週ごとの勉強時間を更新した。
  7. $sum_week_tmp_array 配列の $sunday に対応するインデックスに、$rec_hour と $rec_minute を時間単位に変換した値を加算した。これにより、週ごとの合計勉強時間を更新した。
  1. 最後に、calculate_period_sum 関数を使って、$sum_week_tmp_array から $sum_week_array を計算した。
  1. $day_number を8に設定した。これは、現在の日から8日前までの日数を集計するため。
  1. make_date_array 関数を使って、過去8日分の日付を $date_array_tmp に格納した。
  2. $date_array_tmp の要素数から1を引いた値を $element_amount に格納した。これは配列の最後のインデックスを取得するため。
  1. $date_array_tmp の最後の要素を $period_start に格納した。これは期間の開始日を意味する。
  2. $date_array_tmp の最初の要素を $period_end に格納した。これは期間の終了日を意味する。
  1. ループを使って、$date_array_tmp の要素を逆順に $date_array に格納する。
  1. $date_array に格納する要素を、convert_date_format 関数で日付形式を変換した。
  1. matrix_initialization 関数を使って、$db、$period_start、$period_end、および $date_array_tmp を基に $date_material_array と $date_matrix を初期化した。
  1. period_sum_initialization 関数を使って、$date_array に基づいて $sum_date_tmp_array を初期化した。これは各日の合計勉強時間を格納するための配列。
  2. $db から study_log テーブルの date、hour、minute、および material_id 列を選択し、$period_start から $period_end の間のデータを $records に格納した。
  1. ループを使って、$records から1行ずつデータを取得し、$record に連想配列(SQLITE3_ASSOC 形式)として格納した。
  1. $record 配列から material_id キーの値を取得し、$rec_material に格納した。これは教材種別。
  2. $rec_material が $date_material_array に含まれているかどうかを確認し、含まれていれば次の処理に進む。
  1. $record 配列から date キーの値を取得し、それを convert_date_format 関数で変換し、$date に格納した。
  2. $record 配列から hour キーの値を整数として取得し、$rec_hour に格納した。
  3. $record 配列から minute キーの値を整数として取得し、$rec_minute に格納した。
  4. $date_matrix 配列の $rec_material の $date キーの値に、$rec_hour と $rec_minute を時間単位に変換した値を加算した。これにより、特定の教材の日ごとの勉強時間を更新した。
  5. $sum_date_tmp_array 配列の $date に対応するインデックスに、$rec_hour と $rec_minute を時間単位に変換した値を加算した。これにより、日の合計勉強時間を更新した。
  1. 最後に、calculate_period_sum 関数を使って、$sum_date_tmp_array から $sum_date_array を計算した。
  1. $matrix_summary 配列を作成し、"month" キーに $month_matrix、"week" キーに $week_matrix、"date" キーに $date_matrix を格納した。
  1. $matrix_summary を json_encode 関数でJSON形式に変換し、同じ変数に再格納した。
  1. $axis_array_summary 配列を作成し、"month" キーに $month_array、"week" キーに $week_array、"date" キーに $date_array を格納した。
  1. $axis_array_summary を json_encode 関数でJSON形式に変換し、同じ変数に再格納した。
  1. $sum_array_summary 配列を作成し、"month" キーに $sum_month_array、"week" キーに $sum_week_array、"date" キーに $sum_date_array を格納した。
  1. $sum_array_summary を json_encode 関数でJSON形式に変換し、同じ変数に再格納した。
▽コード詳細

プログラムの内容が少々長いので、セクションに分けて説明します。

▽ matrix_initialization 関数

                                            
                                                function matrix_initialization($db, $period_start, $period_end, $period_array) {
                                                    // 取り出したレコードをPHP配列型へ変換・格納する
                                                    $material_array = ["materialA", "materialB", "materialC"];
                                                
                                                    // 取得した勉強種別$material_arrayと計測期間$period_arrayを使って勉強時間を集計する$matrixを初期化する
                                                    $matrix = [];
                                                    for ($i = 0; $i < count($material_array); $i++) {
                                                        $matrix[$material_array[$i]] = [];
                                                        for ($j = 0; $j < count($period_array); $j++) {
                                                            $matrix[$material_array[$i]][$period_array[$j]] = 0;
                                                        }
                                                    }
                                                
                                                    return [$material_array, $matrix];
                                                }
                                            
                                        

$matrixに学習時間記録を格納するため、配列要素を0埋めした$matrixを準備することを目的とした関数です。名目上、初期化(initialization)と呼んでいます。副次的な処理として、$material_arrayに勉強に使った教材の種類を設定しています。ここは、ベタ打ちで設定していて、データベースの記録にマニュアルで合わせるような悪仕様となっています。RDBMSで教材種別を管理し、そこを参照するようにすると汎用性があると思います。
$material_array$period_array(期間)を使って、各教材毎の勉強時間を記録するための配列$matrixを初期化します。各教材ごとに、各期間に対して0を初期値として設定しています。$matrix[教材種別][計測期間]で、学習時間を格納します。

▽ period_sum_initialization 関数 と calculate_period_sum 関数

                                            
                                                function period_sum_initialization($period_array) {
                                                    $period_sum_tmp_array = [];
                                                    for ($i = 0; $i < count($period_array); $i++) {
                                                    $period_sum_tmp_array[$i] = 0;
                                                    }
                                                
                                                    return $period_sum_tmp_array;
                                                }

                                                function calculate_period_sum($period_sum_tmp_array) {
                                                    $period_sum = [];
                                                    for ($i = 0; $i &lt; count($period_sum_tmp_array); $i++) {
                                                    $hour = floor($period_sum_tmp_array[$i]);
                                                    $min = round(($period_sum_tmp_array[$i] - $hour) * 60, 0);
                                                    $period_sum[$i] = &quot;{$hour}h {$min}m&quot;;
                                                    }
                                                
                                                    return $period_sum;
                                                }
                                            
                                        

period_sum_initialization 関数は、上記の matrix_initialization 関数と考え方は同じで、 ある測定期間の総学習時間を格納する配列$period_sum_tmp_arrayを初期化することを目的とした関数です。ただし、本配列がチャートへ反映される$period_sum_arrayと異なる点として、全てhour[時間]単位で合計された値が各配列要素として格納されます。対して$period_sum_arrayは、可読性のためにhour[時間]分[minute]形式とした値が格納されます。

calculate_period_sum関数では、hour[時間]単位で値が格納されている$period_sum_tmp_arrayの各値をチャートに分かりやすく表示されるように、hour[時間]min[分]形式となるように変換しています。

▽ get_just_before_sunday 関数 と make_week_array 関数

                                            
                                                function get_just_before_sunday($day) {
                                                    $day_of_week = (int)$day->format('w');// 今日の曜日を取得(0:日曜日、1:月曜日、... 6:土曜日)
                                                    if ($day_of_week !== 0) {
                                                    $day->modify('-' . $day_of_week . ' days');
                                                    } // 今日が日曜日でない場合、直前の日曜日を取得
                                                    $sunday = $day->format('Y-m-d');// 直前の日曜日の日付を取得
                                                
                                                    return $sunday;
                                                }

                                                function make_week_array($day, $week_number) {
                                                    $sunday = get_just_before_sunday($day);
                                                    $week_array = [];
                                                    for ($i = 0; $i < $week_number; $i++) {
                                                    // 7週前の日曜日の日付を配列に格納
                                                    $week_array[] = $day->format('Y-m-d');
                                                    $day->modify('-1 week');
                                                    }
                                                
                                                    return $week_array;
                                                }
                                            
                                        

週別の学習記録をバーチャートを表示する際に、横軸の単位を週の代表曜日として日曜日を設定します。

get_just_before_sunday関数では、その起点となる、直前の日曜日を取得しています。まず、$day_of_weekに現在の曜日を整数で格納します(例: 0:日曜日、1:月曜日、... 6:土曜日)。そして、現在と$day_of_week日分の差を求めると、直前の日曜日が取得できます。たとえば7/1(月)の場合は、月曜日に相当する$day_of_week=1を7/1から引くと直前の日曜日である6/30(日)が取得できます。

make_week_array関数では、get_just_before_sunday関数で取得した日曜日を起点に$week_number(デフォルトで7)週分前の日曜日を配列$week_arrayへ入れ込んでいくものです。$week_arrayがバーチャートの横軸になります。

▽ get_period_end_date 関数

                                            
                                                function get_period_end_date ($period_end_sunday) {
                                                    $end_date = new Datetime($period_end_sunday);
                                                    $end_date->modify('+6 days');
                                                    $period_end = $end_date->format('Y-m-d');
                                                
                                                    return $period_end;
                                                }
                                            
                                        

週別に学習時間をまとめると、日曜日~土曜日単位で時間を集計しますが、直前の日曜日に直近の学習時間をチャートに反映するため、直近の土曜を集計期間の最終日として設定する関数です。これが無いと、直前の日曜日が集計期間の最終日となってしまい、記録した時点ではチャートにすぐ反映されず、1週間後(次の日曜日が最終日となるため)に反映されてしまいます。要するに、オンタイムでチャートに記録を反映するための関数です。

▽ convert_data_format 関数

                                            
                                                function convert_date_format ($str_date) {
                                                    $date_tmp = new DateTime($str_date);
                                                    $date_tmp = $date_tmp->format('m/d');
                                                    return $date_tmp;
                                                }
                                            
                                        

週別、日別バーチャートの横軸に日付を表示するために、表示形式を'm/d'となるように変更する関数です。

▽ make_date_array 関数

                                            
                                                function make_date_array($day_number) {
                                                    $today = new DateTime();
                                                    $date_tmp = $today;
                                                    $date_array = [];
                                                    for ($i = 0; $i < $day_number; $i++) {
                                                    $date = $date_tmp->format('Y-m-d');
                                                    array_push(
                                                        $date_array,
                                                        $date
                                                    );
                                                    $date_tmp->modify('-1 days');
                                                    }
                                                
                                                    return $date_array;
                                                }
                                            
                                        

日別バーチャートの横軸に表示する日付を配列形式で用意します。関数を呼び出した日から7日前までの日付を配列 $date_array に追加していきます。

月別バーチャート用のデータ集計

                                        
                                              // 月別の配列を準備する
                                                $month_array = array("JAN", "FEB", "MAR", "APL", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC");
                                                // 教材種別ごとに当該月の勉強時間を格納する二次元配列(辞書型)の初期化
                                                $period_start = '{$year}-01-01';
                                                $period_end = '{$year}-12-31';
                                                list($month_material_array, $month_matrix) = matrix_initialization($db, $period_start, $period_end, $month_array);

                                                // 二次元配列に当該月の勉強時間の記録を格納していく
                                                $sum_month_tmp_array = period_sum_initialization($month_array); // 月あたり合計勉強時間を格納する配列を準備する
                                                $records = $db->query("SELECT date, hour, minute, material_id FROM study_log WHERE date BETWEEN '{$year}-01-01' AND '{$year}-12-31'");
                                                while($record = $records->fetchArray(SQLITE3_ASSOC)) {
                                                    $rec_material = $record['material_id'];
                                                    $rec_month = date('n',strtotime($record['date']));
                                                    $rec_hour = intval($record['hour']);
                                                    $rec_minute = intval($record['minute']);
                                                    $month_matrix[$rec_material][$month_array[intval($rec_month) -1]] += $rec_hour + ($rec_minute / 60);
                                                    $sum_month_tmp_array[intval($rec_month) -1] += $rec_hour + ($rec_minute / 60);
                                                }
                                                $sum_month_array = calculate_period_sum($sum_month_tmp_array);
                                        
                                    

データ集計の流れは以下となります。

  1. チャート横軸単位を作成する。
  2. matrix_initialization 関数により教材毎の学習時間を、period_sum_initialization 関数より月あたりの合計学習時間を記録する配列を0埋めで用意する。利用した教材をまとめた$month_material_arrayと、当該月での学習記録を格納するための$month_matrixを得られる。
  3. データベースlog.dbより教材、学習月、学習時間[hour]、学習時間[min]のデータを取り出す。
  4. 上記c.で取り出したデータを$month_matrix["教材"]["学習月"] += 学習時間で教材・月毎に集計する。
  5. 上記c.で取り出したデータを$sum_month_tmp_array["学習月"] += 学習時間の形で月毎に集計する。
  6. calculate_period_sum 関数で$sum_month_tmp_arrayの表示形式を変換し、それを$sum_month_arrayに格納する。

月別のバーチャートではチャート横軸の単位は1~12月で固定されるので$month_arrayにそのまま、各月の略称を格納しています。

$month_matrix$sum_month_tmp_arrayにて学習時間を集計するにあたって$rec_minute/60としていますが、集計の単位をhour[時間]に変換している処理です。

配列の要素にて、$rec_month-1としているのは、$month_arrayのインデックスが0始まりであるのに対し、date('n', 学習日)の値である月が1月から始まっているので、そのズレを修正する目的です。たとえばdate('n', '2024-07-01')= 7 より $month_array[7] とすると得られる値が"AUG"であるため、−1を加えて$month_array[7-1]として"JUL"を得ています

上記より、このセクションでは月別の学習時間をチャートで表示するのに必要な以下の配列を取得しました。

$month_array <- 月別チャートの横軸単位
$month_matrix <- 月別チャートの学習時間内訳
$sum_month_array <- 月別チャートの月毎の学習時間総計

週別バーチャート用のデータ集計

                                        
                                            // 週別に積み上げ棒グラフデータを集計する(直近{$week_number}週間)
                                                $week_number = 8;// 本日から何週分遡った分の日曜日を取得するか
                                                // 直近{$week_number}週分の日曜日を取り出す
                                                $week_array_tmp = make_week_array($today, $week_number);
                                                $element_amount = count($week_array_tmp) - 1;// $week_array_tmpの要素数を取得しておく
                                                $period_start = $week_array_tmp[$element_amount];
                                                $period_end_sunday = $week_array_tmp[0];
                                                $period_end = get_period_end_date($period_end_sunday);
                                                for ($i = 0; $i <= $element_amount; $i++) {
                                                    $week_array[$i] = convert_date_format($week_array_tmp[$element_amount - $i]);
                                                }
                                                list($week_material_array, $week_matrix) = matrix_initialization($db, $period_start, $period_end, $week_array);
                                            
                                                // 二次元配列に当該期間の勉強時間の記録を格納していく
                                                $sum_week_tmp_array = period_sum_initialization($week_array); // 週あたり合計勉強時間を格納する配列を準備する
                                                $records = $db->query("SELECT date, hour, minute, material_id FROM study_log WHERE date BETWEEN '{$period_start}' AND '{$period_end}'");
                                                while($record = $records->fetchArray(SQLITE3_ASSOC)) {
                                                    $rec_material = $record['material_id'];
                                                    if (in_array($rec_material, $week_material_array)) {
                                                        $conv_to_date = New DateTime($record['date']);
                                                        $sunday_tmp = get_just_before_sunday($conv_to_date);
                                                        $sunday = convert_date_format($sunday_tmp);
                                                        $rec_hour = intval($record['hour']);
                                                        $rec_minute = intval($record['minute']);
                                                        $week_matrix[$rec_material][$sunday] += $rec_hour + ($rec_minute / 60);
                                                        $sum_week_tmp_array[array_search($sunday, $week_array)] += $rec_hour + ($rec_minute / 60);
                                                    }
                                                }
                                                $sum_week_array = calculate_period_sum($sum_week_tmp_array);
                                        
                                    

データ集計の流れは以下となります。

  1. make_week_array 関数と convert_date_format 関数 でチャート横軸単位を作成する。
  2. matrix_initialization 関数により教材毎の学習時間を、period_sum_initialization 関数より月あたりの合計学習時間を記録する配列を0埋めで用意する。利用した教材をまとめた$week_material_arrayと、当該週での学習記録を格納するための$week_matrixを得られる。
  3. データベースlog.dbより教材、学習月、学習時間[hour]、学習時間[min]のデータを取り出す。
  4. 上記c.で取り出したデータを$week_matrix["教材"]["学習月"] += 学習時間で教材・月毎に集計する。
  5. 上記c.で取り出したデータを$sum_week_tmp_array["学習月"] += 学習時間の形で月毎に集計する。
  6. calculate_period_sum 関数で$sum_week_tmp_arrayの表示形式を変換し、それを$sum_week_arrayに格納する。

make_week_array 関数では、直前の日曜日を起点に $week_nubmer=8 週前までの日曜日の日付を取得しています。作成された$week_array_tmpは、配列の順番が 直前の日曜日、直前の日曜日 - 1週間、直前の日曜日-2、… 直前の日曜日-7 となってしまいます。チャートにも配列の順番と同じ並びで横軸単位が表示されてしまうので、たとえば 2024/6/30、2024/6/23、… 2024/5/19 となります。一般的な感覚として図の左から右にかけて時間が流れるので、並びを逆にする必要があります。また、日付の表示形式も m/d形式 へ変更するために convert_date_format 関数で変換したものを $week_array としています。処理のイメージが以下のとなります。

diagram_shows_sorting_and_formatting_week_array

$week_matrixおよび$sum_week_tmp_arrayの集計は、$month_matrixおよび$sum_month_tmp_arrayと同様に$rec_minute/60としています。

上記より、このセクションでは週別の学習時間をチャートで表示するのに必要な以下の配列を取得しました。

$week_array <- 週別チャートの横軸単位
$week_matrix <- 週別チャートの学習時間内訳
$sum_week_array <- 週別チャートの月毎の学習時間総計

日別バーチャート用のデータ集計

                                        
                                            // 日別に積み上げ棒グラフデータを集計する(直近8日間)
                                                $day_number = 8;// 本日から何日分遡って集計するか
                                                // 直近{$day_number}日分の日を取り出す
                                                $date_array_tmp = make_date_array($day_number);
                                                $element_amount = count($date_array_tmp) - 1;//date_array_tmpの要素数を取得しておく
                                                // 直近 {$day_number}日間に記録された勉強種別を取り出す
                                                $period_start = $date_array_tmp[count($date_array_tmp) - 1];//  $date_array_tmpは、要素番号が大きいほど古い日付となるので注意
                                                $period_end = $date_array_tmp[0];// $date_array_tmpは、要素番号が小さいほど新しい日付となる
                                                for ($i=0; $i<=$element_amount; $i++) {
                                                    $date_array[$i] = convert_date_format($date_array_tmp[$element_amount - $i]);
                                                }
                                            
                                                list($date_material_array, $date_matrix) = matrix_initialization($db, $period_start, $period_end, $date_array_tmp);
                                            
                                                // 二次元配列に当該機関の勉強時間の記録を格納していく
                                                $sum_date_tmp_array = period_sum_initialization($date_array);
                                                $records = $db->query("SELECT date, hour, minute, material_id FROM study_log WHERE date BETWEEN '{$period_start}' AND '{$period_end}'");
                                                while($record = $records->fetchArray(SQLITE3_ASSOC)) {
                                                $rec_material = $record['material_id'];
                                                if (in_array($rec_material, $date_material_array)) {
                                                    $date = convert_date_format($record['date']);
                                                    $rec_hour = intval($record['hour']);
                                                    $rec_minute = intval($record['minute']);
                                                    $date_matrix[$rec_material][$date] += $rec_hour + ($rec_minute / 60);
                                                    $sum_date_tmp_array[array_search($date, $date_array)] += $rec_hour + ($rec_minute / 60);
                                                }
                                                };
                                                $sum_date_array = calculate_period_sum($sum_date_tmp_array);
                                        
                                    

データ集計の流れは以下となります。週別と同じですので、週別バーチャート用のデータ集計 セクションを確認されている場合は飛ばしても問題ありません。

  1. make_date_array 関数と convert_date_format 関数 でチャート横軸単位を作成する。
  2. matrix_initialization 関数により教材毎の学習時間を、period_sum_initialization 関数より月あたりの合計学習時間を記録する配列を0埋めで用意する。利用した教材をまとめた$date_material_arrayと、当該日での学習記録を格納するための$date_matrixを得られる。
  3. データベースlog.dbより教材、学習月、学習時間[hour]、学習時間[min]のデータを取り出す。
  4. 上記c.で取り出したデータを$date_matrix["教材"]["学習月"] += 学習時間で教材・月毎に集計する。
  5. 上記c.で取り出したデータを$sum_date_tmp_array["学習月"] += 学習時間の形で月毎に集計する。
  6. calculate_period_sum 関数で$sum_date_tmp_arrayの表示形式を変換し、それを$sum_date_arrayに格納する。

make_date_array 関数では、直前の日曜日を起点に $date_nubmer=8 日前までの日曜日の日付を取得しています。作成された$date_array_tmpは、配列の順番が 当日、昨日、一昨日 … 7日前 となってしまいます。チャートにも配列の順番と同じ並びで横軸単位が表示されてしまうので、たとえば 2024/6/30、2024/6/29、… 2024/6/23 となります。一般的な感覚として図の左から右にかけて時間が流れるので、並びを逆にする必要があります。また、日付の表示形式も m/d形式 へ変更するために convert_date_format 関数で変換したものを $date_array としています。

diagram_shows_sorting_and_formatting_date_array

$date_matrixおよび$sum_date_tmp_arrayの集計は、$month_matrixおよび$sum_month_tmp_arrayと同様に$rec_minute/60としています。

上記より、このセクションでは月別の学習時間をチャートで表示するのに必要な以下の配列を取得しました。

$date_array <- 日別チャートの横軸単位
$date_matrix <- 日別チャートの学習時間内訳
$sum_date_array <- 日別チャートの月毎の学習時間総計

連想配列への集約

                                        
                                            $matrix_summary = array(
                                                "month" => $month_matrix,
                                                "week" => $week_matrix,
                                                "date" => $date_matrix
                                              );
                                              $matrix_summary = json_encode($matrix_summary);
                                          
                                              $axis_array_summary = array(
                                                "month" => $month_array,
                                                "week" => $week_array,
                                                "date" => $date_array
                                              );
                                              $axis_array_summary = json_encode($axis_array_summary);
                                          
                                              $sum_array_summary = array(
                                                "month" => $sum_month_array,
                                                "week" => $sum_week_array,
                                                "date" => $sum_date_array
                                              );
                                              $sum_array_summary = json_encode($sum_array_summary);
                                        
                                    

最後に月別・週別・日別用のチャート要素を、それぞれ1つの連想配列にまとめました。PHP -> JavaScript の値渡しにて、calculate_study_log.php で json_encode() を宣言し、また ファイル呼出し元のshow_bar_chart.phpにてJson.parse('変数') とする必要があり、値渡しである変数が多いほど、同じ処理を行う必要があります。その部分を簡素化するために、各集計期間別の3つの連想配列に、集計期間毎の配列を格納しました。出来上がった3つの連想配列は、上部の表の通りとなります。

5. バーチャートの描画

i. make_stack_bar_chart.js

まず make_stack_bar_chart.js のコードを確認する前に、3.show_bar_chart.php の30~39行目を見てみます。JavaScript箇所についてにて触れているように外部phpファイルである calculate_study_log.php で集計した3つの連想配列を外部Javascriptファイル make_stack_bar_chart.js で利用するために、show_bar_chart.php にて、両ファイルにおける変数の橋渡しをしています。以下がcalculate_study_log.phpmake_stack_bar_chart.js の変数の対応です。phpでの変数を表す$記号を除いた変数名をJavaScriptでそのまま使っています。

calculate_study_log.php make_stack_bar_chart.js
バーの内訳 $matrix_summary matrix_summary
チャート横軸単位 $axis_array_summary axis_array_summary
バーの合計値 $sum_array_summary sum_array_summary

次に、 バーチャートを描画するプログラム make_stack_bar_chart.js を見ていきます。以下がプログラムの全体像です。

                                
                                    "use strict";

                                    // 棒グラフの配色を事前に設定しておく*SQLのDBでは決めず、Javascriptで設定しているので注意すること
                                    let color_array = ['#AA4499', '#88CCEE', '#332288', '#44AA99', '#117733', '#999933', '#DDCC77', '#CC6677', '#882255', '#DDDDDD'] // [purple, cyan, indigo, teal, green, olive, sand, rose, wine, pale gray]
                                    
                                    // ページを最初に読み込まれた際に図に表示する、週単位のデータを設定する
                                    let default_type = "week";
                                    let matrix = matrix_summary[default_type];
                                    let period_array = axis_array_summary[default_type];
                                    let sum_array = sum_array_summary[default_type];
                                    period_array = re_arrange_period_array(period_array, sum_array);
                                    let options = setting_options(); // チャートのオプション
                                    let data = arrange_dataset(matrix, period_array);
                                    draw_chart(data, options); // グラフ描画処理を呼び出す
                                    
                                    $('input[name="accessible-radio"]').change(function() {
                                      period_array = [];
                                      if (myChart) {
                                        myChart.destroy();
                                      }
                                      var period_type = $('input[name="accessible-radio"]:checked').val();
                                      matrix = matrix_summary[period_type];
                                      period_array = axis_array_summary[period_type];
                                      sum_array = sum_array_summary[period_type];
                                      period_array = re_arrange_period_array(period_array, sum_array);
                                      data = arrange_dataset(matrix, period_array);
                                      draw_chart(data, options);
                                    })
                                    
                                    // 横軸の期間要素を再編集する
                                    function re_arrange_period_array(period_array, sum_array) {
                                      let period_array_tmp = [];
                                      for (let i = 0; i < period_array.length; i++) {
                                        period_array_tmp[i] = [period_array[i],sum_array[i]];
                                      }
                                      return period_array_tmp;
                                    }
                                    
                                    // チャートの作成
                                    function draw_chart(data, options) {
                                      var ctx = document.getElementById('stackedBarChart').getContext('2d');
                                      window.myChart = new Chart(ctx, {
                                        type: 'bar',
                                        data: data,
                                        options: options
                                      });
                                    }
                                    
                                    function setting_options() {
                                      var opt = {
                                        scales: {
                                          x: {
                                            stacked: true
                                          },
                                          y: {
                                            stacked: true,
                                            ticks: {
                                              stepSize: 10
                                            },
                                            title: {
                                              display: true,
                                              text: "hour"
                                            }
                                          }
                                        }
                                      };
                                      console.log("opt:"+opt);
                                      return opt;
                                    }
                                    
                                    function arrange_dataset (matrix, period_array) {
                                      // チャートのデータ
                                      // 各要素を整形する
                                      let datasets = [];
                                      Object.keys(matrix).forEach(function(key, i) {
                                        console.log(i, key);
                                        let element = {};
                                        element["label"] = key;
                                        element["backgroundColor"] = color_array[i];
                                        let material_property = [];
                                        for (let period_value of period_array) {
                                            material_property.push(matrix[key][period_value[0]]);// period_valueは第0要素が期間名称、第1要素が単位合計時間なので、第0要素を選択しておく
                                        }
                                        element["data"] = material_property;
                                        datasets.push(element);
                                      });
                                      // 整形した要素をまとめる
                                      var data = {
                                        labels: period_array,
                                        datasets: datasets
                                      };
                                      return data;
                                    }
                                
                            
  1. 厳格モードを使用することで、エラーを早期に検出しやすくします。
  1. 棒グラフの各バーの色を配列で指定
  1. チャートの初期設定として「週別」を選択
  2. データのマトリックスを取得
  3. 横軸の期間データを取得
  4. 合計値の配列を取得
  5. 期間データを再編集
  6. チャートのオプションを設定
  7. データセットを整形
  8. チャートを描画
  1. 集計期間を表すラジオボタン(Month, Week, Date)の選択が変わった時に実行する関数
  1. 期間データをリセット
  2. すでにチャートが描かれているか確認
  1. 既存のチャートを破棄
  1. ラジオボタンで選択された期間タイプを取得
  2. 新しいデータのマトリックスを取得
  3. 新しい横軸の期間データを取得
  4. 新しい合計値の配列を取得
  5. re_arrange_period_array 関数にて期間データを再編集
  6. arrange_dataset 関数にてデータセットを整形
  7. チャートを描画
  1. 横軸の期間単位(期間要素)を再構築する関数
  1. 一時的な期間データの配列を初期化
  2. 集計期間の数だけ以下の処理を繰り返す
  1. 各期間とその合計値をペアにして配列に格納
  1. 再編集した期間データの配列を返す
  1. チャートを描画する関数
  1. キャンバスのコンテキストを取得
  2. 新しいチャートを作成し、グローバル変数として保存
  1. チャートの種類を棒グラフに設定
  2. チャートに使用するデータ
  3. チャートのオプション
  1. チャートの詳細事項を設定する関数
  1. 変数optにチャートの設定をjson形式で格納していく
  1. チャートのプロットエリアの設定
  1. 横軸の設定
  1. 横軸を積み上げ棒グラフに設定
  1. 縦軸の設定
  1. 縦軸を積み上げ棒グラフに設定
  2. 軸のスケール間隔について
  1. スケール間隔を10に設定
  1. 軸タイトルについて
  1. タイトルを表示
  2. タイトルテキストを設定
  1. オプション情報をコンソールに出力
  2. オプション情報を返す
  1. データセットを整形する関数
  1. データセットを初期化
  2. 引数matrix(辞書型配列)のキーとインデックスを1つずつ取り出す
  1. インデックスとキーをコンソールに出力
  2. 新しいデータセット(積み上げ棒グラフのバーの値)の要素を初期化
  3. バーが示す学習教材を設定
  4. バーチャートのバー色を設定
  5. データを格納する配列 material_property を初期化
  6. period_arrayの値を一つずつ取り出して、period_valueに格納し以下の処理を行う
  1. データを期間ごとに、material_property に追加していく
  1. データを設定
  2. データセットに追加
  1. chart.js が処理できる形に値を整形する
  1. 横軸のラベルを設定
  2. バーチャートのバーの値 datasets を設定
  1. 整形したデータを返す
▽コード詳細

少し長いので、分割して見ていきます。

▽ draw_chart 関数

                                    
                                        function draw_chart(data, options) {
                                            var ctx = document.getElementById('stackedBarChart').getContext('2d');
                                            window.myChart = new Chart(ctx, {
                                            type: 'bar',
                                            data: data,
                                            options: options
                                            });
                                        }
                                    
                                

チャートをページ上に描画する関数です。show_bar_chart.php の canvas要素(id="stackedBarChart")にてバーチャートを描画します。チャートを作成するにあたって必要な要素は以下のとおりです。

  • チャートの種類
  • チャートに落とし込むデータ
  • チャートの設定事項

チャートの種類は type: の箇所が相当し、'bar'(バーチャート)を指定しています。
チャートに反映させる元データは data: の箇所が相当しています。下記にて別途説明しますが、arrange_dataset 関数にて、バーチャートに表示できる形式にしたデータを格納している辞書型のデータ data です。
最後にチャートの細かな設定事項を options: の部分で決定しています。以下のsetting_options 関数で設定事項を options に格納し、それを利用しています。

▽ setting_options 関数

                                    
                                        function setting_options() {
                                            var opt = {
                                            scales: {
                                                x: {
                                                stacked: true
                                                },
                                                y: {
                                                stacked: true,
                                                ticks: {
                                                    stepSize: 10
                                                },
                                                title: {
                                                    display: true,
                                                    text: "hour"
                                                }
                                                }
                                            }
                                            };
                                            console.log("opt:"+opt);
                                            return opt;
                                        }
                                    
                                

この関数では、チャートの細かな設定事項を決定しています。前提として、draw_chart 関数より、今回は以下のことが決まっています。

グラフ種類: バーチャート
チャートに落とし込むデータ: 教材毎に集計した学習時間

重要なポイントとして、種別が 'bar' であり、データも1つのバーに対して複数の積み上げ要素があることから、縦軸・横軸で積み上げ棒グラフとして処理できるよう stacked: true としています。

▽ re_arrange_period_array 関数

                                    
                                        function re_arrange_period_array(period_array, sum_array) {
                                            let period_array_tmp = [];
                                            for (let i = 0; i < period_array.length; i++) {
                                            period_array_tmp[i] = [period_array[i],sum_array[i]];
                                            }
                                            return period_array_tmp;
                                        }
                                    
                                

この関数では、チャート横軸にて表示する計測期間 period_array とその期間の学習時間総計 sum_array を2次元配列 period_array_tmp としてまとめています。たとえば、 週別のチャートを描画する場合、引数 period_array=axis_array_summary["week"]=[…, weekx, …]sum_array=sum_array_summary["week"]=[…, Tweekx, …] とすると、計測した週毎に["weekx", Tweekx]のように計測週と学習時間総計をペアとなるように period_array_tmp に追加していきます。このようなデータ形式とすることで、集計期間とその期間の総計を併せてチャート横軸に表示することができます。

▽ arrange_dataset 関数

                                    
                                        function arrange_dataset (matrix, period_array) {
                                            // チャートのデータ
                                            // 各要素を整形する
                                            let datasets = [];
                                            Object.keys(matrix).forEach(function(key, i) {
                                            console.log(i, key);
                                            let element = {};
                                            element["label"] = key;
                                            element["backgroundColor"] = color_array[i];
                                            let material_property = [];
                                            for (let period_value of period_array) {
                                                material_property.push(matrix[key][period_value[0]]);// period_valueは第0要素が期間名称、第1要素が単位合計時間なので、第0要素を選択しておく
                                            }
                                            element["data"] = material_property;
                                            datasets.push(element);
                                            });
                                            // 整形した要素をまとめる
                                            var data = {
                                            labels: period_array,
                                            datasets: datasets
                                            };
                                            return data;
                                        }
                                    
                                

re_arrange_period_array 関数で作成した period_array と 教材毎に学習時間を格納している matrix を使って、バーチャートの積み上げ要素を作成していきます。1つの積み上げ要素に必要なデータとして、学習時間、積み上げ要素の色、学習教材種別を設定していきます。1つの教材の積み上げ要素を辞書型配列 element とすると、辞書のキー要素と引数が以下の対応となるよう、データを element へ格納していきます。

element ["label"] <- peroid_array
["backgroundColor"] <- color_array
["data"] <- matrix

element の各要素の格納が完了すると、それを配列 datasets へ入れていきます。これで教材1つの学習時間の準備が完了です。あとはそれを教材分ループさせることで、チャートへ反映するデータの整理が完了です。element から datasets への格納は以下のようなイメージです。ループ回数を重ねる毎に教材の学習時間も積み上がっていきます。

movie_of_making_datasets

上記の関数を使ってデータを積み上げバーチャートへ落とし込んでいきます。

まず、棒グラフの積み上げ要素ですが、事前に color_array として定義しておきます。データベース上で、教材毎に色を事前に決定しておくのも1つの方法ですが、バーチャートの要素を積み上げた時の配色が悪く見づらくなる可能性を考え、10色をここで定義しています。

つぎに、ページが読み込まれた際の初期のバーチャートを週別にしました。default_type = "week"、とし matrixperiod_arraysum_array にて週別の集計データを取り出しておき、それを re_arrange_period_array()arrange_dataset()draw_chart() 関数を経ることでページ上に週別の学習時間を表示する仕組みとなっています。

最後に、ラジオボタンで集計期間を変更した時の処理となります。$('input[ラジオボタン]').change(function(){ ~ })でラジオボタンの切り替わりタイミングでチャートで表示するデータを変更します。まず、period_array = [] で既に表示している横軸データを削除します。次にmyChart.destory()canvasタグの表示をリセットします。$('input[ラジオボタン]:checked).val() で選択されているラジオボタンの集計単位を取得して、読み込まれた際のチャート表示のように同様に各関数を経てチャートを更新します。

6. 成果物

以下のように出来上がりました。

7. 参考

  1. PHPからJavaScriptへの値渡し
  2. chart.jsの読み込み

コメント・お問合せ

以下のツイートの『返信』にてお願いいたします