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

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

(2023年6月6日)Apple M2 Ultra 追加
(2023年5月5日)グラフにフッタを追加した。
(2023年1月20日)同じ年のCPUがあったら,トランジスタ数が多い方を採用する.Z80, MOS 6502, MC68000 など追加.
(2023年1月18日)Ryzen 9 7950X, Apple M2 Max 追加

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

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

目次

サンプル・プログラム

圧縮ファイルの内容
Moore.phpサンプル・プログラム本体。
Moore.php 更新履歴
バージョン 更新日 内容
1.4.1 2023/06/06 Apple M2 Ultra追加
1.4.0 2023/05/05 グラフにフッタを追加
1.3.1 2023/01/20 Z80, MOS 6502, MC68000 など追加
1.3.0 2023/01/20 同じ年のCPUがあったら,トランジスタ数が多い方を採用する.
1.2.3 2023/01/18 Ryzen 9 7950X, Apple M2 Max 追加

解説:準備

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

  36: //表示幅(ピクセル)
  37: define('WIDTH', 500);
  38: 
  39: //エラー表示用フォント・ファイル名;各自の環境に合わせて
  40: define('FONT', '../../../../common/font/ipagp.ttf');
  41: 
  42: //JpGraph(各自の環境に合わせて)
  43: require_once('jpgraph/jpgraph.php');
  44: require_once('jpgraph/jpgraph_line.php');
  45: require_once('jpgraph/jpgraph_log.php');

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

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

  47: //マイクロプロセッサの情報
  48: //年    マイクロプロセッサ  クロック(Hz)    プロセス(m) トランジスタ数
  49: //      *印のマイクロプロセッサはグラフ上に名称表示
  50: $DataCPU =<<< EOT
  51: 1971    *4004           7.40E+05        1.00E-05        2.30E+03
  52: 1972    8008            5.00E+05        1.00E-05        3.50E+03
  53: 1974    *8080           2.00E+06        6.00E-06        4.50E+03
  54: 1975    MOS 6502        3.00E+06        8.00E-06        3.51E+03
  55: 1976    *Z80            6.00E+06        4.00E-06        8.50E+03
  56: 1979    *8086           5.00E+06        3.00E-06        2.90E+04
  57: 1980    MC68000         2.00E+07        3.50E-06        6.80E+04
  58: 1982    *80286          1.20E+07        1.50E-06        1.34E+05
  59: 1985    *80386          1.60E+07        1.50E-06        2.75E+05
  60: 1986    R2000           1.50E+07        2.00E-06        1.10E+05
  61: 1987    MC68030         5.00E+07        8.00E-07        3.00E+05
  62: 1989    *80486          1.60E+08        1.00E-06        1.19E+06
  63: 1991    R4000           1.50E+08        1.00E-06        1.20E+06
  64: 1993    *Pentium        6.00E+07        8.00E-07        3.10E+06
  65: 1994    PowerPC 603     3.00E+08        2.90E-07        2.60E+06
  66: 1995    PentiumPro      1.50E+08        6.00E-07        5.50E+06
  67: 1997    *PentiumII      2.33E+08        3.50E-07        7.50E+06
  68: 1999    PentiumIII      4.50E+08        2.50E-07        9.50E+06
  69: 2000    *Pentium4       1.30E+09        1.80E-07        4.20E+07
  70: 2005    *PentiumM       9.00E+08        1.30E-07        7.70E+07
  71: 2006    CoreDuo         1.07E+09        6.50E-08        1.51E+08
  72: 2006    Core2 Duo       1.60E+09        6.50E-08        1.67E+08
  73: 2009    *Core i7-860    2.80E+09        4.50E-08        7.74E+08
  74: 2011    Core i7-2700K   3.50E+09        3.20E-08        1.16E+09
  75: 2012    Core i7-3770K   3.50E+09        2.20E-08        1.40E+09
  76: 2014    Core i7-5960X   3.00E+09        2.20E-08        2.60E+09
  77: 2016    *Core i7-6950X  3.00E+09        1.40E-08        3.20E+09
  78: 2017    Ryzen 7 1800X   4.00E+09        1.40E-08        4.80E+09
  79: 2018    A12 Bionic      2.49E+09        7.00E-09        6.90E+09
  80: 2020    *Apple M1       3.20E+09        5.00E-09        1.60E+10
  81: 2021    Apple M1 Max    3.20E+09        5.00E-09        5.70E+10
  82: 2022    Ryzen 9 7950X   5.70E+09        5.00E-09        1.31E+10
  83: 2022    Apple M1 Ultra  3.20E+09        5.00E-09        1.14E+11
  84: 2022    Apple M2        3.49E+09        5.00E-09        2.00E+10
  85: 2023    Apple M2 Max    3.49E+09        5.00E-09        6.70E+10
  86: 2023    *Apple M2 Ultra 3.49E+09        5.00E-09        13.40E+10
  87: 
  88: EOT;

 129: /**
 130:  * 指定したCPUデータ文字列を分解し,配列に格納する.
 131:  * 文字列の行区切りは改行,列区切りはタブ(複数可).
 132:  * @param   string $sour  入力文字列
 133:  * @param   array  $dest  結果を格納する2次元配列
 134:  * @return  int 分解した行数
 135: */
 136: function str2matrix($sour, &$dest) {
 137:     $arr1 = preg_split("/\n/ui", $sour);
 138:     $cnt1 = 0;
 139:     foreach ($arr1 as $val1) {
 140:         $str = preg_replace("/\n/ui", '', $val1);
 141:         if (mb_strlen($str< 1continue;
 142:         $arr2 = preg_split("/[\t]+/ui", $str);
 143:         $cnt2 = 0;
 144:         foreach ($arr2 as $val2) {
 145:             $dest[$cnt1][$cnt2] = $val2;
 146:             $cnt2++;
 147:         }
 148:         $cnt1++;
 149:     }
 150: 
 151:     return $cnt1;
 152: }

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

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

解説:描画用配列に変換

 154: /**
 155:  * CPUデータ配列をJpGraph描画用配列に変換する.
 156:  * 同じ年のCPUがあったら,トランジスタ数が多い方を採用する.
 157:  * @param   array  $matrix CPUデータ配列
 158:  * @param   array  $items  描画用配列
 159:  * @return  array(最小年,最大年) 描画用配列
 160: */
 161: function matrix2items($matrix, &$items) {
 162:     //データの分解
 163:     $year_min = 99999;
 164:     $year_max = 0;
 165:     foreach ($matrix as $val) {
 166:         $year = (int)$val[0];
 167:         if ($year < $year_min)   $year_min = $year;
 168:         if ($year > $year_max)  $year_max = $year;
 169:         //同じ年のCPUがあったら,トランジスタ数が多い方を採用する.
 170:         if (isset($items[$year])) {
 171:             if ((float)$val[4> $items[$year]['transistors']) {
 172:                 $items[$year]['CPU']         = (string)$val[1];
 173:                 $items[$year]['clock']       = (float)$val[2];
 174:                 $items[$year]['process']     = (float)$val[3];
 175:                 $items[$year]['transistors'] = (float)$val[4];
 176:             }
 177:         //同じ年のCPUがなければ,そのまま採用する.
 178:         } else {
 179:             $items[$year]['CPU']         = (string)$val[1];
 180:             $items[$year]['clock']       = (float)$val[2];
 181:             $items[$year]['process']     = (float)$val[3];
 182:             $items[$year]['transistors'] = (float)$val[4];
 183:         }
 184:     }
 185: 
 186:     return array($year_min, $year_max);
 187: }

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

解説:グラフを描く

 209: /**
 210:  * 指定した描画用データ配列によりJpGraphに対数グラフを描画する.
 211:  * @param   array  $items 描画用データ配列
 212:  * @param   int    $year_min 最小年
 213:  * @param   int    $year_max 最大年
 214:  * @return  object graphオブジェクト
 215: */
 216: function drawGraph($items, $year_min, $year_max) {
 217:     $plotdata = array();
 218: 
 219:     //横軸(西暦年)の作成
 220:     $x0 = floor($year_min / 5* 5;
 221:     $x1 = ceil($year_max  / 5* 5;
 222:     $i = 1;
 223:     for ($year = $x0$year <$x1$year++) {
 224:         $data_x[$year - $x0] = $year;
 225:     }
 226: 
 227:     //グラフ描画処理開始
 228:     $graph = new Graph(600, 500, 'auto');
 229:     $graph->SetScale('linlog');     //X軸:整数,Y軸:対数
 230:     $graph->SetBackgroundGradient('#FFFFCC','#FFFFCC', GRAD_HOR, BGRAD_MARGIN);
 231: 
 232:     //タイトル、凡例などの設定
 233:     $graph->img->SetImgFormat('png');
 234:     $graph->img->SetMargin(60, 60, 20, 40);
 235:     $graph->title->SetFont(FF_PGOTHIC, FS_NORMAL, 16);
 236:     $graph->title->Set('ムーアの法則');
 237:     $graph->xaxis->title->SetFont(FF_PGOTHIC);
 238:     $graph->xaxis->SetTitle('年','center');
 239:     $graph->xaxis->SetTickLabels($data_x);
 240:     $graph->xaxis->SetTextTickInterval(5);
 241:     $graph->xaxis->SetTextLabelInterval(2);
 242:     $graph->yaxis->title->SetFont(FF_PGOTHIC);
 243:     $graph->yaxis->title->Set('トランジスタ数');
 244:     $graph->yaxis->SetTitleMargin(40);
 245:     //フッタを設定する.
 246:     $graph->footer->right->SetFont(FF_PGOTHIC, FS_NORMAL, 10);
 247:     $graph->footer->right->Set('produced by JpGraph');
 248: 
 249:     //プロット配列を作成
 250:     for ($year = $x0$year <$x1$year++) {
 251:         $data_y1[$year - $x0] = isset($items[$year]) ? (double)$items[$year]['transistors': '-';
 252:         $data_y2[$year - $x0] = 2.30E+03 * pow(2, ($year - $year_min) / (double)(18 / 12));
 253:         $data_y3[$year - $x0] = 2.30E+03 * pow(2, ($year - $year_min) / (double)(24 / 12));
 254:     }
 255: 
 256:     //ムーアの法則:18ヶ月ごとに2倍
 257:     $plotdata = new LinePlot($data_y2);
 258:     $graph->Add($plotdata);
 259:     $plotdata->SetWeight(2);                    //太さ
 260:     $plotdata->SetColor('royalblue');               //色
 261:     $plotdata->SetLegend('18ヶ月ごとに2倍');    //凡例
 262: 
 263:     //ムーアの法則:24ヶ月ごとに2倍
 264:     $plotdata = new LinePlot($data_y3);
 265:     $graph->Add($plotdata);
 266:     $plotdata->SetWeight(2);                    //太さ
 267:     $plotdata->SetColor('green');               //色
 268:     $plotdata->SetLegend('24ヶ月ごとに2倍');    //凡例
 269: 
 270:     //マイクロプロセッサ
 271:     $plotdata = new LinePlot($data_y1);
 272:     $graph->Add($plotdata);
 273:     $plotdata->SetColor('deeppink');            //色
 274:     $plotdata->SetWeight(2);                    //太さ
 275:     $plotdata->mark->SetTYPE(MARK_DIAMOND);     //プロット記号
 276:     $plotdata->value->SetFormatCallback('CPUname');
 277:     $plotdata->value->show();
 278:     $plotdata->SetLegend('マイクロプロセッサ'); //凡例
 279: 
 280:     //凡例
 281:     $graph->legend->SetFont(FF_PGOTHIC);                    //フォント
 282:     $graph->legend->SetColumns(1);                          //表示列数
 283:     $graph->legend->SetPos(0.12, 0.9, 'right', 'bottom');   //位置
 284: 
 285:     return $graph;
 286: }

グラフの描画はユーザー関数 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 において受け取ったトランジスタとデータ表を照合し、マイクロプロセッサ名を返すようにしていることで、グラフ上にマイクロプロセッサ名が表示されるという仕組みである。
すべてのマイクロプロセッサ名が表示されると重なって見にくくなるので、冒頭にアスタリスクが付いているマイクロプロセッサ名だけを表示するようにしている。

解説:画面出力

 298: //JpGraphを使ってグラフ表示する.
 299: $graph = drawGraph($Items, $year_min, $year_max);
 300: //画面に表示する.
 301: if ($mode == 1) {
 302:     header('Content-type: image/png');
 303:     header('Content-Disposition: filename="Moore.png"');
 304:     $graph->Stroke();
 305: //imgタグ用の文字列を生成する.
 306: else {

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

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

参考サイト

(この項おわり)
header