PHPでCOVID-19情報をグラフ表示

(1/1)
2019 年(平成 31 年)末頃より新型コロナウイルス感染症(COVID-19)が、中国の武漢市を中心に出現した。WHO の発表によると、2020 年(令和 2 年)4 月 16 日現在、世界の患者数は約 199 万人、死亡者数は約 13 万人に達した。
今回は、国内の COVID-19 感染の推移を、最新のデータを読み込んでグラフ表示する PHP プログラムを作ってみることにする。

本プログラムで参照するデータは、東洋経済オンライン編集部の荻原和樹さんが「新型コロナウイルス国内感染の状況」(MIT ライセンス)として公開しているもの、および、jig.jp 創業者&会長の福野泰介さんが「新型コロナウイルス対策ダッシュボード」として公開しているものをマッシュアップした。

(2021 年 4 月 10 日)キャッシュ・システム導入:pahooCache クラス.表示期間 PERIOD 追加.「実行」ボタン廃止.
(2021 年 2 月 23 日)データファイルの全面変更,対応関数変更
(2020 年 12 月 13 日)追加:入院治療を要する者、表示変更:現在患者数→入院治療を要する者の増減。
(2020 年 12 月 5 日)PHP8 対応。

サンプル・プログラム:実行例

PHPでCOVID-19情報をグラフ表示

目次

サンプル・プログラム

圧縮ファイルの内容
viewCOVID-19.phpサンプル・プログラム本体
pahooStat.php統計に関わるクラス pahooStat。
使い方は「PHPで太陽黒点相対数の周期変化を描く」を参照。
pahooCache.phpキャッシュ処理に関わるクラス pahooCache。
キャッシュ処理に関わるクラスの使い方は「PHPで天気予報を求める」を参照。include_path が通ったディレクトリに配置すること。

準備:初期値

0033: //プログラム・タイトル
0034: define('TITLE', 'COVID-19情報をグラフ表示');
0035: 
0036: //移動平均区間;日
0037: define('INTERVAL', 7);
0038: 
0039: //表示機関:日
0040: define('PERIOD', 365);
0041: 
0042: //傾きを求める区間;日
0043: define('SLOPE', 30);
0044: 
0045: //グラフの表示幅・高さ(単位:ピクセル)
0046: define('WIDTH',  600);
0047: define('HEIGHT', 400);
0048: 
0049: //グラフの色
0050: define('COLOR_COVID', '#88CCFF');
0051: define('COLOR_MEAN',  '#CC0000');
0052: 
0053: //jqPlotのあるフォルダ
0054: define('JQPLOT', '../../../../common/jqplot/');
0055: 
0056: //キャッシュ保持時間(分) 0:キャッシュしない
0057: //アクセス負荷軽減のため,60分以上のキャッシュ保持をお勧めします.
0058: define('LIFE_CACHE', 360);
0059: 
0060: //キャッシュ・ディレクトリ
0061: //書き込み可能で,外部からアクセスされないディレクトリを指定してください.
0062: define('DIR_CACHE', './covid19_pcache/');
0063: 
0064: //統計に関わるクラス:include_pathが通ったディレクトリに配置
0065: require_once('pahooStat.php');
0066: 
0067: //キャッシュ処理に関わるクラス:include_pathが通ったディレクトリに配置
0068: require_once('pahooCache.php');

上記の初期値は任意に変更が可能である。

グラフ描画のために、「PHP で NHK 政治意識月例調査をグラフ表示」で紹介したjQuery のプラグイン jqPlot を用いた。プラグインをダウンロードしたら解凍して、そのパスを定数 JQPLOT に定義する。

データ提供サイトへ負荷をかけないよう、キャッシュ・クラス pahooCache を導入した。使用方法については、「PHP で天気予報を求める - キャッシュ・システム」を参照いただきたい。
キャッシュ保持時間、キャッシュ・ディレクトリともに、自サイトの環境に応じて変更してほしい。

準備:データ・ファイル

参照データは、東洋経済オンライン編集部の荻原和樹さんが「新型コロナウイルス国内感染の状況」として公開しているもの、および、jig.jp 創業者&会長の福野泰介さんが「新型コロナウイルス対策ダッシュボード」として公開しているもの。

0070: //データファイル名(変更不可)
0071: define('DATA_FILE1', 'https://raw.githubusercontent.com/kaz-ogiwara/covid19/master/data/data.json');                     //グラフ描画情報【廃止】
0072: define('DATA_FILE_BEDS1', 'https://raw.githubusercontent.com/code4sabae/covid19/master/data/bedforinfection_current.json'); //感染症ベッド数
0073: define('DATA_FILE_BEDS2', 'https://raw.githubusercontent.com/code4sabae/covid19/master/data/bedforinfection_summary.json'); //感染症ベッド数
0074: define('DATA_FILE_NP1', 'https://raw.githubusercontent.com/code4sabae/covid19/master/data/covid19japan-fast.json');         //現在の入院患者数
0075: define('DATA_FILE_NP2', 'https://raw.githubusercontent.com/code4sabae/covid19/master/data/covid19japan-all.json');         //現在の入院患者数
0076: 
0077: //v.3.0にて追加(変更不可)
0078: //厚生労働省オープンデータ「陽性者数」
0079: define('FILE_PCR_POSITIVE_DAILY', 'https://toyokeizai.net/sp/visual/tko/covid19/csv/pcr_positive_daily.csv');
0080: //厚生労働省オープンデータ「PCR検査実施人数」
0081: define('FILE_PCR_TESTED_DAILY', 'https://toyokeizai.net/sp/visual/tko/covid19/csv/pcr_tested_daily.csv');
0082: //厚生労働省オープンデータ「入院治療等を要する者の数」
0083: define('FILE_CASES_TOTAL', 'https://toyokeizai.net/sp/visual/tko/covid19/csv/cases_total.csv');
0084: //厚生労働省オープンデータ「退院又は療養解除となった者の数」
0085: define('FILE_RECOVERY_TOTAL', 'https://toyokeizai.net/sp/visual/tko/covid19/csv/recovery_total.csv');
0086: //厚生労働省オープンデータ「死亡者数」
0087: define('FILE_DEATH_TOTAL', 'https://toyokeizai.net/sp/visual/tko/covid19/csv/death_total.csv');
0088: //厚生労働省オープンデータ「PCR検査の実施件数」
0089: define('FILE_PCR_CASE_DAILY', 'https://toyokeizai.net/sp/visual/tko/covid19/csv/pcr_case_daily.csv');
0090: //公表日ごとの全国の重症者数
0091: define('FILE_SEVERE_DAILY', 'https://toyokeizai.net/sp/visual/tko/covid19/csv/severe_daily.csv');
0092: //日別全国の実効再生産数
0093: define('FILE_EFFECTIVE_REPRODUCTION_NUMBER', 'https://toyokeizai.net/sp/visual/tko/covid19/csv/effective_reproduction_number.csv');
0094: //年代別の国内発生動向
0095: define('FILE_DEMOGRAPHY', 'https://toyokeizai.net/sp/visual/tko/covid19/csv/demography.csv');
0096: //都道府県別の発生動向
0097: define('FILE_PREFECTURE', 'https://toyokeizai.net/sp/visual/tko/covid19/csv/prefectures.csv');

解説:グラフにプロットするためのデータを配列に格納する

0395: /**
0396:  * グラフにプロットするためのデータを配列に格納する
0397:  * @param string $fname   CSVファイル名
0398:  * @param array  $cols    取得結果のvalに合算するカラム番号
0399:  * @param string $errmsg  エラーメッセージ格納用
0400:  * @param int    $method  データの積算方法(省略時:0)
0401:  *                          0:そのまま
0402:  *                          1:前日の値を加算
0403:  *                          2:前日の値を減算
0404:  *                          3:値から前日の値を減算
0405:  *                          4:前日までの積算値を加算
0406:  *                          5:前日までの積算値を減算
0407:  *                          6:値から前日までの積算値を減算
0408:  * @param string $pref    都道府県名(省略時:全国)
0409:  * @return array  取得結果/FALSE:取得失敗
0410:  *                  [連番][要素名]
0411:  *                  要素名と値
0412:  *                      year    年
0413:  *                      month    月
0414:  *                      day        日
0415:  *                      val        プロット値
0416: */
0417: function readData($fname$cols, &$errmsg$method=0, $pref='全国') {
0418:     $data = array();
0419:     $cnt = 0;
0420:     $errmsg = '';
0421: 
0422:     //ファイル・オープン
0423:     $infp = @fopen($fname, 'r');
0424:     if ($infp == FALSE) {
0425:         $errmsg = $fname . ' にアクセスできません.';
0426:         return FALSE;
0427:     }
0428: 
0429:     //データ読み込み
0430:     $ss = fgets($infp, 1000);        //1行目スキップ
0431:     while (! feof($infp)) {
0432:         $ss   = fgets($infp, 1000);
0433:         $arr  = preg_split('/\,/ui', $ss);
0434:         $flag = FALSE;
0435:         if (isset($arr[0])) {
0436:             if (preg_match('/([0-9]+)\/([0-9]+)\/([0-9]+)/ui', $arr[0]$arr2) > 0) {
0437:                 $data[$cnt]['year']  = (int)$arr2[1];
0438:                 $data[$cnt]['month'] = (int)$arr2[2];
0439:                 $data[$cnt]['day']   = (int)$arr2[3];
0440:                 $flag = TRUE;
0441:             } else if (isset($arr[3]) && ($pref == $arr[3])) {
0442:                 $data[$cnt]['year']  = (int)$arr[0];
0443:                 $data[$cnt]['month'] = (int)$arr[1];
0444:                 $data[$cnt]['day']   = (int)$arr[2];
0445:                 $flag = TRUE;
0446:             }
0447:         }
0448:         if ($flag) {
0449:             //データの積算
0450:             $val = 0.0;
0451:             foreach ($cols as $key) {
0452:                 $val += (float)$arr[$key];
0453:             }
0454:             if ($cnt == 0) {
0455:                 $data[$cnt]['val'] = $val;
0456:                 $old1 = $data[$cnt]['val'];
0457:                 $old2 = $data[$cnt]['val'];
0458:             } else {
0459:                 switch ($method) {
0460:                     case 0:
0461:                         $data[$cnt]['val'] = $val;
0462:                         break;
0463:                     case 1:
0464:                         $data[$cnt]['val'] = $val + $old1;
0465:                         break;
0466:                     case 2:
0467:                         $data[$cnt]['val'] = $val - $old1;
0468:                         break;
0469:                     case 3:
0470:                         $data[$cnt]['val'] = $old1 - $val;
0471:                         break;
0472:                     case 4:
0473:                         $data[$cnt]['val'] = $val + $old2;
0474:                         break;
0475:                     case 5:
0476:                         $data[$cnt]['val'] = $val - $old2;
0477:                         break;
0478:                     case 6:
0479:                         $data[$cnt]['val'] = $old2 - $val;
0480:                         break;
0481:                 }
0482:                 $old1 = $val;
0483:                 $old2 = $data[$cnt]['val'];
0484:             }
0485:             $cnt++;
0486:         }
0487:     }
0488:     fclose($infp);
0489: 
0490:     return $data;
0491: }

COVID-19 グラフ描画のためのデータを CSV ファイルから読み込み配列に格納するのが、ユーザー関数 readData である。
上述のように複数の CSV ファイルを読み込み対象としているが、データ構造は似ている――1 列目、または 1~3 列目に年月日、2 列目以降、または 4 列目以降に毎日のデータ――ので、この関数に適切なパラメータを与えることで処理を行う。

まず、 fopen  で CVS ファイルをオープンし、失敗したらエラーを返す。
冒頭行はラベルなので読み飛ばす。
2行目からがデータ行である。年月日が 1 列目だけか、1~3 列名にまたがっているかは正規表現パターンで判定する。

日々のデータ積算処理だが、データ構造及び描画したいグラフの種類によって、次の 7 つのケースが考えられる。
  1. そのまま
  2. 前日の値を加算
  3. 前日の値を減算
  4. 値から前日の値を減算
  5. 前日までの積算値を加算
  6. 前日までの積算値を減算
  7. 値から前日までの積算値を減算
積算方法は引数 $method で渡すことによって、配列 $data に格納していく。

0271: /**
0272:  * データ・ファイルを読み込み、現在の都道府県別ベッド数を取得
0273:  * @param array  $items 都道府県別ベッド数格納用
0274:  * @return int 全国ベッド数/FALSE
0275: */
0276: function readBeds(&$items) {
0277:     global $TablePref;
0278: 
0279:     //オブジェクト生成
0280:     $pcc = new pahooCache(LIFE_CACHEDIR_CACHE);
0281: 
0282:     //データファイル(その1)
0283: //  $contents = file_get_contents(DATA_FILE_BEDS1);
0284:     $contents = $pcc->load(DATA_FILE_BEDS1);
0285:     if ($contents == FALSE)     return FALSE;
0286:     $data = json_decode($contentsTRUE);
0287:     $cnt = 0;
0288:     foreach ($data as $key=>$arr) {
0289:         if (isset($arr['自治体名']) && isset($arr['新型コロナウイルス対策感染症病床数']) && isset($arr['発表日'])) {
0290:             $pref = $arr['自治体名'];
0291:             $yyyymmdd = $arr['発表日'];
0292:             //新規登録
0293:             if (! isset($items[$pref])) {
0294:                 $items[$pref]['date'] = $yyyymmdd;
0295:                 $items[$pref]['val']  = (int)$arr['新型コロナウイルス対策感染症病床数'];
0296:             } else if ($yyyymmdd > $items[$pref]['date']) {
0297:                 $items[$pref]['date'] = $yyyymmdd;
0298:                 $items[$pref]['val']  = (int)$arr['新型コロナウイルス対策感染症病床数'];
0299:             }
0300:         }
0301:     }
0302: 
0303:     //データファイル(その2)
0304:     $contents = file_get_contents(DATA_FILE_BEDS2);
0305:     if ($contents == FALSE)     return FALSE;
0306:     $data = json_decode($contentsTRUE);
0307:     $cnt = 0;
0308:     foreach ($TablePref as $code=>$arr1) {
0309:         if (! isset($items[$arr1])) {
0310:             foreach ($data['area'] as $key=>$arr2) {
0311:                 if ($arr2['name_ja'] == $arr1) {
0312:                     $items[$arr1]['val'] = $arr2['sum'];
0313:                     break;
0314:                 }
0315:             }
0316:         }
0317:     }
0318: 
0319:     //合計
0320:     $cnt = 0;
0321:     foreach ($items as $arr)    $cnt += $arr['val'];
0322: 
0323:     //オブジェクト解放
0324:     $pcc = NULL;
0325: 
0326:     return $cnt;
0327: }

現在の都道府県別ベッド数を GitHub の 2 つのファイルから読み込み、組み込み関数  json_decode  によって配列に展開する。

0329: /**
0330:  * データ・ファイルを読み込み、現在の都道府県別入院患者数を取得
0331:  * @param array  $items 都道府県別入院患者数格納用
0332:  * @return int 全国入院患者数/FALSE
0333: */
0334: function readNP(&$items) {
0335:     global $TablePref;
0336: 
0337:     //オブジェクト生成
0338:     $pcc = new pahooCache(LIFE_CACHEDIR_CACHE);
0339: 
0340:     //データファイル(その1)
0341: //  $contents = file_get_contents(DATA_FILE_NP1);
0342:     $contents = $pcc->load(DATA_FILE_NP1);
0343:     if ($contents == FALSE)     return FALSE;
0344:     $data = json_decode($contentsTRUE);
0345:     $cnt = 0;
0346:     foreach ($data as $key=>$arr) {
0347:         if (isset($arr['name']) && isset($arr['ncurrentpatients']) && isset($arr['lastUpdate'])) {
0348:             $pref = $arr['name'];
0349:             $yyyymmdd = $arr['lastUpdate'];
0350:             //新規登録
0351:             if (! isset($items[$pref])) {
0352:                 $items[$pref]['date'] = $yyyymmdd;
0353:                 $items[$pref]['val']  = (int)$arr['ncurrentpatients'];
0354:             } else if ($yyyymmdd > $items[$pref]['date']) {
0355:                 $items[$pref]['date'] = $yyyymmdd;
0356:                 $items[$pref]['val']  = (int)$arr['ncurrentpatients'];
0357:             }
0358:         }
0359:     }
0360: 
0361:     //データファイル(その2)
0362: //  $contents = file_get_contents(DATA_FILE_NP2);
0363:     $contents = $pcc->load(DATA_FILE_NP2);
0364:     if ($contents == FALSE)     return FALSE;
0365:     $data = json_decode($contentsTRUE);
0366:     $cnt = 0;
0367:     foreach ($TablePref as $code=>$arr1) {
0368:         if (! isset($items[$arr1])) {
0369:             foreach ($data as $key=>$arr2) {
0370:                 if (preg_match('/現在は入院/ui', $arr2['description']) > 0) {
0371:                     foreach ($arr2['area'] as $key=>$arr3) {
0372:                         if ($arr3['name_jp'] == $arr1) {
0373:                             if (! isset($items[$arr1]['val'])) {
0374:                                 $items[$arr1]['val'] = $arr3['ncurrentpatients'];
0375:                             }
0376:                             break;
0377:                         }
0378:                     }
0379:                     break;
0380:                 }
0381:             }
0382:         }
0383:     }
0384: 
0385:     //合計
0386:     $cnt = 0;
0387:     foreach ($items as $arr)    $cnt += $arr['val'];
0388: 
0389:     //オブジェクト解放
0390:     $pcc = NULL;
0391: 
0392:     return $cnt;
0393: }

現在の都道府県別入院患者数を GitHub の 2 つのファイルから読み込み、組み込み関数  json_decode  によって配列に展開する。

解説:検査陽性者数を取得

0493: /**
0494:  * 新規検査陽性者数を取得
0495:  * @param string $errmsg  エラーメッセージ格納用
0496:  * @param string $pref    都道府県名(省略時:全国)
0497:  * @return array データ配列
0498: */
0499: function pcr_tested_positive_daily(&$errmsg$pref=PREF_ALL) {
0500:     if ($pref == PREF_ALL) {
0501:         $fname = FILE_PCR_POSITIVE_DAILY;
0502:         $cols = array(1);
0503:         $method = 0;
0504:     } else {
0505:         $fname = FILE_PREFECTURE;
0506:         $cols = array(5);
0507:         $method = 2;
0508:     }
0509: 
0510:     return readData($fname$cols$errmsg$method$pref);
0511: }

日々の新規検査陽性者数を取得するユーザー関数は pcr_tested_positive_daily である。
対象が全国化都道府県かによって、読み込む CSV ファイル名が異なる。また、データが格納されている列 $col と、データ積算方式 $method も変わる。

0513: /**
0514:  * 累計検査陽性者数を取得
0515:  * @param string $errmsg  エラーメッセージ格納用
0516:  * @param string $pref    都道府県名(省略時:全国)
0517:  * @return array データ配列
0518: */
0519: function pcr_tested_positive_total(&$errmsg$pref=PREF_ALL) {
0520:     if ($pref == PREF_ALL) {
0521:         $fname = FILE_PCR_POSITIVE_DAILY;
0522:         $cols = array(1);
0523:         $method = 4;
0524:     } else {
0525:         $fname = FILE_PREFECTURE;
0526:         $cols = array(5);
0527:         $method = 1;
0528:     }
0529: 
0530:     return readData($fname$cols$errmsg$method$pref);
0531: }

累計の検査陽性者数を取得するユーザー関数は pcr_tested_positive_total である。
上述の pcr_tested_positive_daily との違いは、データ積算方式 $method である。
この他、検査人数、検査件数、入院治療等を要する者などを取得するユーザー関数は、すべて、pcr_tested_positive_dailypcr_tested_positive_total のバリエーションになる。

解説:jqPlot用のスクリプト

0735: /**
0736:  * jqPlot用のスクリプト
0737:  * @param array  $items データ配列
0738:  * @param string $pref  都道府県名
0739:  * @param string $title グラフのタイトル
0740:  * @param bool   $log   TRUE:縦軸は対数/FALSE:通常(省略時)
0741:  * @param bool   $int   TRUE:縦軸は整数(省略時)/FALSE:小数
0742:  * @param float  $ymax  Y軸の最大値(省略時:空文字=データの最大値)
0743:  * @return string スクリプト
0744: */
0745: function plot($items$pref='', $title='', $log=FALSE$int=TRUE$ymax='') {
0746:     //Y軸の最大値
0747:     if ($ymax != '') {
0748:         $ymax = sprintf('max: %f,', $ymax);
0749:     }
0750: 
0751:     //グラフの色
0752:     $color_covid = COLOR_COVID;
0753:     $color_mean  = COLOR_MEAN;
0754:     //移動平均を求める区間(日)
0755:     $interval = INTERVAL;
0756: 
0757:     //移動平均
0758:     $x = array();
0759:     $y = array();
0760:     foreach ($items as $key=>$val)  $x[$key] = $val['val'];
0761:     //統計オブジェクト
0762:     $pst = new pahooStat();
0763:     $pst->simple_moving_average(INTERVAL$x$y);        //移動平均
0764:     foreach ($y as $key=>$val)  $items[$key]['mean'] = $val;
0765: 
0766:     //直近の傾き(最小二乗法)
0767:     $x1 = array();
0768:     $y1 = array();
0769:     $n = count($items);
0770:     for ($i = 0; $i < SLOPE$i++) {
0771:         $x1[$i] = $n - SLOPE + $i;
0772:         $y1[$i] = $items[$n - SLOPE + $i]['val'];
0773:     }
0774:     list($a0$a1) = $pst->LSM($x1$y1SLOPE);      //最小二乗法
0775:     $pst = NULL;
0776:     $slope = sprintf('直近%d日間の傾き:%.2f', SLOPE$a1);
0777: 
0778:     $series = '';
0779:     $xmin = date('Y-m-d', (time() - PERIOD * 24 * 60 * 60));
0780:     $xmax = '';
0781:     $rendere_yaxis = $log ? 'renderer: $.jqplot.LogAxisRenderer' : 'min: 0';
0782:     $format_yaxis  = $int ? "%'d" : "%.1f";
0783: 
0784:     //系列の生成
0785:     $cnt = 0;
0786:     $s1 = $s2 = '';
0787:     foreach ($items as $key=>$val) {
0788:         $xmax = sprintf("%04d-%02d-%02d", $val['year'], $val['month'], $val['day']);
0789:         if ($xmax < $xmin)  continue;
0790:         if ($key == 0)  $xmin = $xmax;
0791:         $s1 .= isset($val['val']) ? sprintf("['%s', %.3f],", $xmax$val['val']) : '';
0792:         $s2 .= isset($val['mean']) ? sprintf("['%s', %.3f],", $xmax$val['mean']) : '';
0793:         $cnt++;
0794:     }
0795:     $barwidth = (int)(WIDTH / $cnt * 0.7);
0796: 
0797: $js =<<< EOT
0798: jQuery(function() {
0799:     jQuery.jqplot('jqPlot_polls',
0800:     [
0801:         [ {$s1} ],
0802:         [ {$s2} ]
0803:     ],
0804:     {
0805:         //タイトル
0806:         title: {
0807:             text: '{$pref}の{$title}',
0808:             show: true,
0809:             fontFamily: 'serif',
0810:             fontSize: '20px',
0811:             textAlign: 'center',
0812:             textColor: 'black',
0813:         },
0814:         //系列
0815:         series: [
0816:         {
0817:             label: '{$title}',
0818:             color: '{$color_covid}',
0819:             renderer: jQuery . jqplot . BarRenderer,
0820:             rendererOptions: {
0821:                 barWidth: {$barwidth},
0822:                 shadowOffset: 0
0823:             }
0824:         },
0825:         {
0826:             label: '{$interval}日移動平均',
0827:             color: '{$color_mean}'
0828:         },
0829:         ],
0830:         legend: {
0831:             show: true,
0832:             placement: 'inside',
0833:             location: 'nw',
0834:             renderer: $.jqplot.EnhancedLegendRenderer,
0835:             rendererOptions: { numberRows: 1 }
0836:         },
0837:         seriesDefaults: {
0838:             showLine: true,
0839:             rendererOptions: { smooth: false },
0840:             markerOptions: { size: 0 },
0841:         },
0842:         //軸
0843:         axes: {
0844:             xaxis: {
0845:                 renderer:$.jqplot.DateAxisRenderer,
0846:                 tickOptions: { formatString: '%m/%d' },
0847:                 label: '日付',
0848:                 min: '{$xmin}',
0849:                 max: '{$xmax}',
0850:             },
0851:             yaxis: {
0852:                 {$ymax}
0853:                 {$rendere_yaxis},
0854:                 labelRenderer: $.jqplot.CanvasAxisLabelRenderer,
0855:                 label: '{$title}',
0856:                 tickOptions: {
0857:                     formatString: "{$format_yaxis}",
0858:                     angle: -30,
0859:                 }
0860:             }
0861:         },
0862:         //ハイライター
0863:         highlighter: {
0864:             show: true,
0865:             showMarker: true,
0866:             tooltipLocation: 'sw',
0867:             fadeTooltip: false,
0868:             bringSeriesToFront: true,
0869:             tooltipAxes: 'xy',
0870:             formatString: '%s<br />%s'
0871:         }
0872:     }
0873:     );
0874:     $('#slope').html('{$slope}');
0875: });
0876: 
0877: 
0878: EOT;
0879: 
0880:     return $js;
0881: }

データ配列を与え、jqPlot 用のスクリプトを生成する。
感染者数(累計)を表示するときなどには、「指数関数的に増えている」という話を見やすくするために、縦軸を対数軸にできるようにしている。

実数を棒グラフで、移動平均を折れ線グラフで表示する。
移動平均については、「PHP で太陽黒点相対数の周期変化を描く」で紹介したとおりである。

また、直近 SLOPE 日間のグラフの傾きを、最小二乗法によって求めるようにした。

解説:データ読み込み~グラフ描画

0099: //処理選択肢
0100: $SelectFuncs = array(
0101:     //関数名,タイトル,ラジオボタンcheked,Y軸を対数軸とするか,
0102:     //目盛りを整数にするかどうか,Y軸の最大値,都道府県を選択可能とするか
0103:     'pcr_tested_positive_daily' => array('title'=>'新規検査陽性者(人)', 'checked'=>'checked', 'log'=>FALSE, 'int'=>TRUE, 'ymax'=>'', 'prefectures'=>TRUE),
0104:     'pcr_tested_positive_total' => array('title'=>'累計検査陽性者(人)', 'checked'=>'', 'log'=>FALSE, 'int'=>TRUE, 'ymax'=>'', 'prefectures'=>TRUE),
0105:     'pcr_tested_daily' => array('title'=>'新規検査人数(人)', 'checked'=>'', 'log'=>FALSE, 'int'=>TRUE, 'ymax'=>'', 'prefectures'=>TRUE),
0106:     'pcr_tested_total' => array('title'=>'累計検査人数(人)', 'checked'=>'', 'log'=>FALSE, 'int'=>TRUE, 'ymax'=>'', 'prefectures'=>TRUE),
0107:     'pcr_case_daily' => array('title'=>'新規検査件数(件)', 'checked'=>'', 'log'=>FALSE, 'int'=>TRUE, 'ymax'=>'', 'prefectures'=>FALSE),
0108:     'pcr_case_total' => array('title'=>'累計検査件数(件)', 'checked'=>'', 'log'=>FALSE, 'int'=>TRUE, 'ymax'=>'', 'prefectures'=>FALSE),
0109:     'case_total'     => array('title'=>'入院治療等を要する者(人)', 'checked'=>'', 'log'=>FALSE, 'int'=>TRUE, 'ymax'=>'', 'prefectures'=>FALSE),
0110:     'recovery_daily' => array('title'=>'新規退院・療養解除(人)', 'checked'=>'', 'log'=>FALSE, 'int'=>TRUE, 'ymax'=>'', 'prefectures'=>TRUE),
0111:     'recovery_total' => array('title'=>'累計退院・療養解除(人)', 'checked'=>'', 'log'=>FALSE, 'int'=>TRUE, 'ymax'=>'', 'prefectures'=>TRUE),
0112:     'severe_daily' => array('title'=>'重症者(人)', 'checked'=>'', 'log'=>FALSE, 'int'=>TRUE, 'ymax'=>'', 'prefectures'=>TRUE),
0113:     'death_daily' => array('title'=>'新規死亡者(人)', 'checked'=>'', 'log'=>FALSE, 'int'=>TRUE, 'ymax'=>'', 'prefectures'=>TRUE),
0114:     'death_total' => array('title'=>'累計死亡者(人)', 'checked'=>'', 'log'=>FALSE, 'int'=>TRUE, 'ymax'=>'', 'prefectures'=>TRUE),
0115:     'reproduction' => array('title'=>'実効再生産数', 'checked'=>'', 'log'=>FALSE, 'int'=>FALSE, 'ymax'=>'', 'prefectures'=>TRUE),
0116: );
0117: 
0118: //全国(変更不可)
0119: define('PREF_ALL', '全国');
0120: 
0121: //都道府県テーブル(変更不可)
0122: $TablePrefarray(PREF_ALL, '北海道', '青森県', '岩手県', '宮城県', '秋田県', '山形県', '福島県', '茨城県', '栃木県', '群馬県', '埼玉県', '千葉県', '東京都', '神奈川県', '新潟県', '富山県', '石川県', '福井県', '山梨県', '長野県', '岐阜県', '静岡県', '愛知県', '三重県', '滋賀県', '京都府', '大阪府', '兵庫県', '奈良県', '和歌山県', '鳥取県', '島根県', '岡山県', '広島県', '山口県', '徳島県', '香川県', '愛媛県', '高知県', '福岡県', '佐賀県', '長崎県', '熊本県', '大分県', '宮崎県', '鹿児島県', '沖縄県');

0911: /**
0912:  * HTML BODYを作成する
0913:  * @param string $func jqPlot関数
0914:  * @param string $pref 都道府県名または'全国'
0915:  * @param string $res  エラー・メッセージ
0916:  * @return string HTML BODY
0917: */
0918: function makeCommonBody($func$pref$res) {
0919:     global $SelectFuncs$TablePref;
0920: 
0921:     $myself   = MYSELF;
0922:     $refere   = REFERENCE;
0923:     $refdata  = REF_DATA;
0924:     $refdata2 = REF_DATA2;
0925:     $title    = TITLE;
0926:     $version  = '<span style="font-size:small;">' . date('Y/m/d版', filemtime(__FILE__)) . '</span>';
0927:     $width  = WIDTH;
0928:     $height = HEIGHT;
0929:     $errmsg = '';
0930:     $debug  = '';
0931: 
0932:     //処理選択ラジオボタン
0933:     $str_radio = '';
0934:     $i = 1;
0935:     foreach ($SelectFuncs as $key=>$val) {
0936:         $str_radio .= "<input type=\"radio\" name=\"func\" value=\"{$key}\" {$val['checked']} onChange=\"submit()\" />{$val['title']} ";
0937:         if ($i % 3 == 0)    $str_radio .= '<br />';
0938:         $i++;
0939:     }
0940: 
0941:     //デバッグ情報
0942:     if (! FLAG_RELEASE) {
0943:         $phpver = phpversion();
0944: $debug =<<< EOT
0945: <p>
0946: <span style="font-weight:bold;">★デバックモードで動作中...</span><br />
0947: PHPver : {$phpver}
0948: 
0949: EOT;
0950:     }
0951: 
0952:     //データ読み込み
0953:     $data  = array();
0954:     $items = array();
0955:     if ($SelectFuncs[$func]['prefectures'] == FALSE)   $pref = PREF_ALL;
0956: 
0957:     //感染症ベッド数
0958:     $beds = array();
0959:     $beds_total = readBeds($beds);
0960:     $bed = ($pref == PREF_ALL) ? $beds_total : $beds[$pref]['val'];
0961:     $bed = number_format($bed);
0962: 
0963:     //入院患者数
0964:     $nps = array();
0965:     $nps_total = readNP($nps);
0966:     $np = ($pref == PREF_ALL) ? $nps_total : $nps[$pref]['val'];
0967:     $np = number_format($np);
0968: 
0969:     //グラフ描画
0970:     if ($res == '') {
0971:         $selector = makeSelector($pref$SelectFuncs[$func]['prefectures']);
0972:         $items = $func($errmsg$pref);
0973:         if ($items != FALSE) {
0974:             //jqPlot
0975:             $js = plot($items$pref$SelectFuncs[$func]['title'], $SelectFuncs[$func]['log'], $SelectFuncs[$func]['int'], $SelectFuncs[$func]['ymax']);
0976: $res =<<< EOT
0977: <script>
0978: {$js}
0979: </script>
0980: <div id="jqPlot_polls" style="margin-top:20px; width:{$width}px; height:{$height}px;"></div>
0981: <p id="slope"></p>
0982: <p>現在の入院患者数:{$np}人 感染症ベッド数:{$bed}床</p>
0983: 
0984: EOT;
0985:         } else {
0986: $res =<<< EOT
0987: <p style="color:red;">エラー:{$errmsg}</p>
0988: 
0989: EOT;
0990:         }
0991:     } else {
0992: $res =<<< EOT
0993: <p style="color:red;">{$res}</p>
0994: 
0995: EOT;
0996:     }
0997: 
0998: $html =<<< EOT
0999: <body>
1000: <h2>{$title} {$version}</h2>
1001: <form name="myform" method="post" action="{$myself}">
1002: <p>都道府県を選択 {$selector}</p>
1003: <p>{$str_radio}</p>
1004: </form>
1005: 
1006: {$res}
1007: 
1008: <div style="border-style:solid; border-width:1px; margin:20px 0px 0px 0px; padding:5px; width:{$width}px; font-size:small;">
1009: <h3>使い方</h3>
1010: <ol>
1011: <li>都道府県を選択してください.</li>
1012: <li>見たいグラフを選択してください.</li>
1013: <li>グラフを表示します.</li>
1014: </ol>
1015: 参 考:<a href="{$refere}">{$refere}</a><br />
1016: データ:<a href="{$refdata}">東洋経済オンライン「新型コロナウイルス 国内感染の状況」</a>【MITライセンス】<br />
1017:     <a href="{$refdata2}">新型コロナウイルス対策ダッシュボード</a>
1018: {$debug}
1019: </div>
1020: </body>
1021: 
1022: EOT;
1023:     return $html;
1024: }

前述のユーザー関数は、ラジオボタンに応じて変数 $func によって呼び出すようにしている。

活用例

みんなの知識 ちょっと便利帳」では、「グラフで見る COVID-19」で本プログラムを利用し、見やすいページを提供している。ありがとうございます。

参考サイト

(この項おわり)
header