PHPで対数グラフ(ムーアの法則)を描く

(1/1)
インフルエンザ患者数のグラフを作る」では単純な折れ線グラフを扱ったが、PHP と JpGraph を用いることで Y軸が対数になっている折れ線グラフも描くことができる。今回は、ムーアの法則を題材に、インテルマイクロプロセッサのトランジスタ数の増え方を対数グラフに描いてみることにする。

(2021 年 10 月 20 日)Apple M1 Pro/Max追加
(2021 年 4 月 25 日)PHP8 対応,リファラ・チェック改良

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

ムーアの法則
サンプル・プログラムを実行すると、インテル CPU のトランジスタ数の推移と、ムーアの法則(18 ヶ月ごとに 2 倍、24 ヶ月ごとに 2 倍の 2通り)の 3 つの折れ線グラフが描かれる。

目次

サンプル・プログラム

圧縮ファイルの内容
Moore.phpサンプル・プログラム本体。

解説:準備

グラフ描画には JpGraph を利用する。導入方法については、「PHP と JpGraph で人口ピラミッドを表示する」を参照のこと。

0039: //エラー表示用フォント・ファイル名;各自の環境に合わせて
0040: define('FONT', '../../../../common/font/ipagp.ttf');
0041: 
0042: //JpGraph(各自の環境に合わせて)
0043: require_once('jpgraph/jpgraph.php');
0044: require_once('jpgraph/jpgraph_line.php');
0045: require_once('jpgraph/jpgraph_log.php');

PHP と JpGraph で人口ピラミッドを表示する」を参考に、JpGraph と日本語フォントを準備する。"jpgraph_log.php" は、対数グラフを描くために必要なモジュールだ。

解説:マイクロプロセッサ情報

0047: //マイクロプロセッサの情報
0048: //年 マイクロプロセッサ   クロック(Hz)    プロセス(m) トランジスタ数
0049: //      *印のマイクロプロセッサはグラフ上に名称表示
0050: $DataCPU =<<< EOT
0051: 1971    *4004           7.40E+05        1.00E-05        2.30E+03
0052: 1972    8008            5.00E+05        1.00E-05        3.50E+03
0053: 1974    *8080           2.00E+06        6.00E-06        4.50E+03
0054: 1979    *8086           5.00E+06        3.00E-06        2.90E+04
0055: 1982    *80286          8.00E+06        1.50E-06        1.34E+05
0056: 1985    *80386          1.60E+07        1.50E-06        2.75E+05
0057: 1989    *80486          1.60E+07        1.00E-06        1.19E+06
0058: 1993    *Pentium        6.00E+07        8.00E-07        3.10E+06
0059: 1995    PentiumPro      1.50E+08        6.00E-07        5.50E+06
0060: 1997    *PentiumII      2.33E+08        3.50E-07        7.50E+06
0061: 1999    PentiumIII      4.50E+08        2.50E-07        9.50E+06
0062: 2000    *Pentium4       1.30E+09        1.80E-07        4.20E+07
0063: 2005    *PentiumM       9.00E+08        1.30E-07        7.70E+07
0064: 2006    CoreDuo         1.07E+09        6.50E-08        1.51E+08
0065: 2006    Core2 Duo       1.60E+09        6.50E-08        1.67E+08
0066: 2009    *Core i7-860    2.80E+09        4.50E-08        7.74E+08
0067: 2011    Core i7-2700K   3.50E+09        3.20E-08        1.16E+09
0068: 2012    Core i7-3770K   3.50E+09        2.20E-08        1.40E+09
0069: 2014    Core i7-5960X   3.00E+09        2.20E-08        2.60E+09
0070: 2016    *Core i7-6950X  3.00E+09        1.40E-08        3.20E+09
0071: 2018    A12 Bionic      2.49E+09        7.00E-09        6.90E+09
0072: 2020    *Apple M1       3.20E+09        5.00E-09        1.60E+10
0073: 2021    Apple M1 Pro    3.20E+09        5.00E-09        3.37E+10
0074: 2021    *Apple M1 Max   3.20E+09        5.00E-09        5.70E+10
0075: 
0076: EOT;

0117: /**
0118:  * 文字列を行列(2次元配列)に分解
0119:  *  行区切り:改行,列区切り:タブ(複数可)
0120:  * @param   string $sour  入力文字列
0121:  * @param   array  $dest  結果を格納する2次元配列
0122:  * @return  int 分解した行数
0123: */
0124: function str2matrix($sour, &$dest) {
0125:     $arr1 = preg_split("/\n/ui", $sour);
0126:     $cnt1 = 0;
0127:     foreach ($arr1 as $val1) {
0128:         $str = preg_replace("/\n/ui", '', $val1);
0129:         if (mb_strlen($str) < 1)    continue;
0130:         $arr2 = preg_split("/[\t]+/ui", $str);
0131:         $cnt2 = 0;
0132:         foreach ($arr2 as $val2) {
0133:             $dest[$cnt1][$cnt2] = $val2;
0134:             $cnt2++;
0135:         }
0136:         $cnt1++;
0137:     }
0138: 
0139:     return $cnt1;
0140: }

マイクロプロセッサの情報は、Excel で入力してあったのだが、ソースプログラムに展開することにした。行区切りを改行に、列区切りをタブ(複数可能)と定義し、まず文字列として変数 $DataCPU に代入しておく。

グラフ描画の前に、これを行×列の 2 次元配列に展開する目的で、ユーザー関数 str2matrix を用意した。他の用途でも使えるだろう。

解説:描画用配列に変換

0142: /**
0143:  * データ配列を描画用配列に変換
0144:  * @param   array  $matrix 2次元配列
0145:  * @return  array(最小年,最大年)
0146: */
0147: function matrix2items($matrix, &$items) {
0148:     //データの分解
0149:     $year_min = 99999;
0150:     $year_max = 0;
0151:     foreach ($matrix as $val) {
0152:         $year = (int)$val[0];
0153:         if ($year < $year_min)  $year_min = $year;
0154:         if ($year > $year_max)  $year_max = $year;
0155:         $items[$year]['CPU']         = $val[1];
0156:         $items[$year]['clock']       = $val[2];
0157:         $items[$year]['process']     = $val[3];
0158:         $items[$year]['transistors'] = $val[4];
0159:     }
0160: 
0161:     return array($year_min$year_max);
0162: }

目的のグラフは、X軸が西暦年で、Y軸がトランジスタ数(対数)という形である。
そこで、ユーザー関数 str2matrix で変換したデータを、さらに、西暦年×列名という 2 次元配列に変換するためのユーザー関数 matrix2items を用意した。

解説:グラフを描く

0184: /**
0185:  * グラフ描画
0186:  * @param   array  $items データ
0187:  * @param   int    $year_min 最小年
0188:  * @param   int    $year_max 最大年
0189:  * @return  object graphオブジェクト
0190: */
0191: function drawGraph($items$year_min$year_max) {
0192:     $plotdata = array();
0193: 
0194:     //横軸(西暦年)の作成
0195:     $x0 = floor($year_min / 5) * 5;
0196:     $x1 = ceil($year_max  / 5) * 5;
0197:     $i = 1;
0198:     for ($year = $x0$year <= $x1$year++) {
0199:         $data_x[$year - $x0] = $year;
0200:     }
0201: 
0202:     //グラフ描画処理開始
0203:     $graph = new Graph(600, 500, 'auto');
0204:     $graph->SetScale('linlog');      //X軸:整数,Y軸:対数
0205:     $graph->SetBackgroundGradient('#FFFFCC','#FFFFCC', GRAD_HORBGRAD_MARGIN);
0206: 
0207:     //タイトル、凡例などの設定
0208:     $graph->img->SetImgFormat('png');
0209:     $graph->img->SetMargin(60, 60, 20, 40);
0210:     $graph->title->SetFont(FF_PGOTHICFS_NORMAL, 16);
0211:     $graph->title->Set('ムーアの法則');
0212:     $graph->xaxis->title->SetFont(FF_PGOTHIC);
0213:     $graph->xaxis->title->Set('');
0214:     $graph->xaxis->SetTickLabels($data_x);
0215:     $graph->xaxis->SetTextTickInterval(5);
0216:     $graph->xaxis->SetTextLabelInterval(2);
0217:     $graph->yaxis->title->SetFont(FF_PGOTHIC);
0218:     $graph->yaxis->title->Set('トランジスタ数');
0219:     $graph->yaxis->SetTitleMargin(40);
0220: 
0221:     //プロット配列を作成
0222:     for ($year = $x0$year <= $x1$year++) {
0223:         $data_y1[$year - $x0] = isset($items[$year]) ? (double)$items[$year]['transistors'] : '-';
0224:         $data_y2[$year - $x0] = 2.30E+03 * pow(2, ($year - $year_min) / (double)(18 / 12));
0225:         $data_y3[$year - $x0] = 2.30E+03 * pow(2, ($year - $year_min) / (double)(24 / 12));
0226:     }
0227: 
0228:     //ムーアの法則:18ヶ月ごとに2倍
0229:     $plotdata = new LinePlot($data_y2);
0230:     $graph->Add($plotdata);
0231:     $plotdata->SetWeight(2);                  //太さ
0232:     $plotdata->SetColor('royalblue');                //色
0233:     $plotdata->SetLegend('18ヶ月ごとに2倍');   //凡例
0234: 
0235:     //ムーアの法則:24ヶ月ごとに2倍
0236:     $plotdata = new LinePlot($data_y3);
0237:     $graph->Add($plotdata);
0238:     $plotdata->SetWeight(2);                  //太さ
0239:     $plotdata->SetColor('green');                //色
0240:     $plotdata->SetLegend('24ヶ月ごとに2倍');   //凡例
0241: 
0242:     //マイクロプロセッサ
0243:     $plotdata = new LinePlot($data_y1);
0244:     $graph->Add($plotdata);
0245:     $plotdata->SetColor('deeppink');         //色
0246:     $plotdata->SetWeight(2);                  //太さ
0247:     $plotdata->mark->SetTYPE(MARK_DIAMOND);        //プロット記号
0248:     $plotdata->value->SetFormatCallback('CPUname');
0249:     $plotdata->value->show();
0250:     $plotdata->SetLegend('マイクロプロセッサ');   //凡例
0251: 
0252:     //凡例
0253:     $graph->legend->SetFont(FF_PGOTHIC);                   //フォント
0254:     $graph->legend->SetColumns(1);                         //表示列数
0255:     $graph->legend->SetPos(0.12, 0.9, 'right', 'bottom');  //位置
0256: 
0257:     return $graph;
0258: }

グラフの描画はユーザー関数 drawGraph で行う。

まず、X軸を用意する。西暦年を 5 年刻みで、最小年と最大年を設定する。
次に、グラフ描画処理の開始とタイトル、凡例などの設定を行う。このあたりは「インフルエンザ患者数のグラフを作る」とほぼ同じである。

続けて、プロット用の配列を 3 つ $data_y1, $data_y2, $data_y3 作る。
配列 $data_y1 には、マイクロプロセッサのトランジスタ数を代入する。最初に与えられたデータにない年は、'-' を代入することで、折れ線グラフを自動的に繋げてくれる。ここで空文字 '' を代入すると、グラフは途切れる。
1971 年(昭和 46 年)に発売された 4004 のトランジスタ数を基準に、18 ヶ月ごとに 2 倍にした値は配列 $data_y2 に、24 ヶ月ごとに 2 倍にした値は配列 $data_y3 に、それぞれ代入する。2 のべき乗を計算するために組み込み関数  pow  を用いている。
ちなみに、当初のムーアの法則では「18 ヶ月ごと」だったが、その後のトランジスタ数の増加が減速し、「24 ヶ月」に訂正したという経緯がある。

最後に、プロット用の配列を描画していく。
配列 $data_y1 を描く際に、$plotdata->value->SetFormatCallback を使ってデータポイントにマイクロプロセッサ名を表示させている。本来、データポイントには Y 値(ここではトランジスタ数)が表示されるのだが、コールバックされるユーザー関数 CPUname において受け取ったトランジスタとデータ表を照合し、マイクロプロセッサ名を返すようにしていることで、グラフ上にマイクロプロセッサ名が表示されるという仕組みである。
すべてのマイクロプロセッサ名が表示されると重なって見にくくなるので、冒頭にアスタリスクが付いているマイクロプロセッサ名だけを表示するようにしている。

解説:画面出力

0270: //グラフを表示
0271: $graph = drawGraph($Items$year_min$year_max);
0272: if ($mode == 1) {
0273:     header('Content-type: image/png');
0274:     header('Content-Disposition: filename="Moore.png"');
0275:     $graph->Stroke();
0276: else {

画面への出力も JpGraph の機能を利用している。

コマンドライン・オプション img が指定された場合は、出力データを img タグの中に画像データを埋め込むための文字列を生成する。詳しくは、「PHP で TrueType フォントを利用する(その2)」-「解説:画像を HTML コンテンツに埋め込む」を参照のこと。

参考サイト

(この項おわり)
header