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

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

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

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

サンプル・プログラム

解説:準備

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

0025: //エラー表示用フォント・ファイル名;各自の環境に合わせて
0026: define('FONT', '../../../../common/font/ipagp.ttf');
0027: 
0028: //JpGraph(各自の環境に合わせて)
0029: require_once('jpgraph/jpgraph.php');
0030: require_once('jpgraph/jpgraph_line.php');
0031: require_once('jpgraph/jpgraph_log.php');

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

解説:CPU情報

0033: //インテルCPUの情報
0034: //年  CPU                クロック(Hz)  プロセス(m)  トランジスタ数
0035: //      *印のCPUはグラフ上に名称表示
0036: $DataCPU =<<< EOD
0037: 1971    *4004           7.40E+05        1.00E-05        2.30E+03
0038: 1972    8008            5.00E+05        1.00E-05        3.50E+03
0039: 1974    *8080           2.00E+06        6.00E-06        4.50E+03
0040: 1979    *8086           5.00E+06        3.00E-06        2.90E+04
0041: 1982    *80286           8.00E+06        1.50E-06        1.34E+05
0042: 1985    *80386           1.60E+07        1.50E-06        2.75E+05
0043: 1989    *80486           1.60E+07        1.00E-06        1.19E+06
0044: 1993    *Pentium       6.00E+07        8.00E-07        3.10E+06
0045: 1995    PentiumPro        1.50E+08        6.00E-07        5.50E+06
0046: 1997    *PentiumII       2.33E+08        3.50E-07        7.50E+06
0047: 1999    PentiumIII        4.50E+08        2.50E-07        9.50E+06
0048: 2000    *Pentium4       1.30E+09        1.80E-07        4.20E+07
0049: 2005    *PentiumM       9.00E+08        1.30E-07        7.70E+07
0050: 2006    CoreDuo            1.07E+09        6.50E-08        1.51E+08
0051: 2006    Core2 Duo       1.60E+09        6.50E-08        1.67E+08
0052: 2009    *Core i7-860  2.80E+09        4.50E-08        7.74E+08
0053: 2011    Core i7-2700K   3.50E+09        3.20E-08        1.16E+09
0054: 2012    Core i7-3770K   3.50E+09        2.20E-08        1.40E+09
0055: 2014    *Core i7-5960X  3.00E+09        2.20E-08        2.60E+09
0056: 
0057: EOD;

0071: /**
0072:  * 文字列を行列(2次元配列)に分解
0073:  *  行区切り:改行,列区切り:タブ(複数可)
0074:  * @param string $sour  入力文字列
0075:  * @param array  $dest  結果を格納する2次元配列
0076:  * @return int 分解した行数
0077: */
0078: function str2matrix($sour, &$dest) {
0079:     $arr1 = preg_split("/\n/ui", $sour);
0080:     $cnt1 = 0;
0081:     foreach ($arr1 as $val1) {
0082:         $str = preg_replace("/\n/ui", '', $val1);
0083:         if (mb_strlen($str) < 1)    continue;
0084:         $arr2 = preg_split("/[\t]+/ui", $str);
0085:         $cnt2 = 0;
0086:         foreach ($arr2 as $val2) {
0087:             $dest[$cnt1][$cnt2] = $val2;
0088:             $cnt2++;
0089:         }
0090:         $cnt1++;
0091:     }
0092: 
0093:     return $cnt1;
0094: }

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

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

解説:描画用配列に変換

0096: /**
0097:  * データ配列を描画用配列に変換
0098:  * @param array  $matrix 2次元配列
0099:  * @return array(最小年,最大年)
0100: */
0101: function matrix2items($matrix, &$items) {
0102:     //データの分解
0103:     $year_min = 99999;
0104:     $year_max = 0;
0105:     foreach ($matrix as $val) {
0106:         $year = (int)$val[0];
0107:         if ($year < $year_min)  $year_min = $year;
0108:         if ($year > $year_max)  $year_max = $year;
0109:         $items[$year]['CPU']         = $val[1];
0110:         $items[$year]['clock']       = $val[2];
0111:         $items[$year]['process']     = $val[3];
0112:         $items[$year]['transistors'] = $val[4];
0113:     }
0114: 
0115:     return array($year_min$year_max);
0116: }

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

解説:グラフを描く

0118: /**
0119:  * CPU名取得
0120:  * @param double $y トランジスタ数
0121:  * @return string CPU名
0122: */
0123: function CPUname($y) { 
0124:     global $Items;
0125: 
0126:     $name = '';
0127:     foreach ($Items as $val) {
0128:         if ($val['transistors'] == $y) {
0129:             if (preg_match('/^\*/ui', $val['CPU']) > 0) {
0130:                 $name = preg_replace('/^\*/ui', '', $val['CPU']);
0131:             }
0132:         }
0133:     }
0134: 
0135:     return $name;
0136: }
0137: 
0138: /**
0139:  * グラフ描画
0140:  * @param array  $items データ
0141:  * @param int    $year_min 最小年
0142:  * @param int    $year_max 最大年
0143:  * @return object graphオブジェクト
0144: */
0145: function drawGraph($items$year_min$year_max) {
0146:     $plotdata = array();
0147: 
0148:     //横軸(西暦年)の作成
0149:     $x0 = floor($year_min / 5) * 5;
0150:     $x1 = ceil($year_max  / 5) * 5;
0151:     $i = 1;
0152:     for ($year = $x0$year <= $x1$year++) {
0153:         $data_x[$year - $x0] = $year;
0154:     }
0155: 
0156:     //グラフ描画処理開始
0157:     $graph = new Graph(600, 500, 'auto');
0158:     $graph->SetScale('linlog');        //X軸:整数,Y軸:対数
0159:     $graph->SetBackgroundGradient('#FFFFCC','#FFFFCC', GRAD_HOR, BGRAD_MARGIN);
0160: 
0161:     //タイトル、凡例などの設定
0162:     $graph->img->SetImgFormat('png');
0163:     $graph->img->SetMargin(60, 60, 20, 40);
0164:     $graph->title->SetFont(FF_PGOTHIC, FS_NORMAL, 16);
0165:     $graph->title->Set('ムーアの法則');
0166:     $graph->xaxis->title->SetFont(FF_PGOTHIC);
0167:     $graph->xaxis->title->Set('');
0168:     $graph->xaxis->SetTickLabels($data_x);
0169:     $graph->xaxis->SetTextTickInterval(5);
0170:     $graph->xaxis->SetTextLabelInterval(2);
0171:     $graph->yaxis->title->SetFont(FF_PGOTHIC);
0172:     $graph->yaxis->title->Set('トランジスタ数');
0173:     $graph->yaxis->SetTitleMargin(40);
0174: 
0175:     //プロット配列を作成
0176:     for ($year = $x0$year <= $x1$year++) {
0177:         $data_y1[$year - $x0] = isset($items[$year]) ? (double)$items[$year]['transistors'] : '-';
0178:         $data_y2[$year - $x0] = 2.30E+03 * pow(2, ($year - $year_min) / (double)(18 / 12));
0179:         $data_y3[$year - $x0] = 2.30E+03 * pow(2, ($year - $year_min) / (double)(24 / 12));
0180:     }
0181: 
0182:     //ムーアの法則:18ヶ月ごとに2倍
0183:     $plotdata = new LinePlot($data_y2);
0184:     $graph->Add($plotdata);
0185:     $plotdata->SetWeight(2);                  //太さ
0186:     $plotdata->SetColor('royalblue');             //色
0187:     $plotdata->SetLegend('18ヶ月ごとに2倍');    //凡例
0188: 
0189:     //ムーアの法則:24ヶ月ごとに2倍
0190:     $plotdata = new LinePlot($data_y3);
0191:     $graph->Add($plotdata);
0192:     $plotdata->SetWeight(2);                  //太さ
0193:     $plotdata->SetColor('green');             //色
0194:     $plotdata->SetLegend('24ヶ月ごとに2倍');    //凡例
0195: 
0196:     //インテルCPU
0197:     $plotdata = new LinePlot($data_y1);
0198:     $graph->Add($plotdata);
0199:     $plotdata->SetColor('deeppink');         //色
0200:     $plotdata->SetWeight(2);                  //太さ
0201:     $plotdata->mark->SetTYPE(MARK_DIAMOND);    //プロット記号
0202:     $plotdata->value->SetFormatCallback('CPUname');
0203:     $plotdata->value->show();
0204:     $plotdata->SetLegend('インテルCPU');        //凡例
0205: 
0206:     //凡例
0207:     $graph->legend->SetFont(FF_PGOTHIC);                   //フォント
0208:     $graph->legend->SetColumns(1);                         //表示列数
0209:     $graph->legend->SetPos(0.12, 0.9, 'right', 'bottom'); //位置
0210: 
0211:     return $graph;
0212: }

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

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

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

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

解説:画面出力

0224: //グラフを表示
0225: $graph = drawGraph($Items$year_min$year_max);
0226: if ($mode == 1) {
0227:     header('Content-type: image/png');
0228:     $graph->Stroke();
0229: else {
0230:     $im = $graph->Stroke(_IMG_HANDLER); 
0231:     ob_start();
0232:     ImagePNG($im); 
0233:     imagedestroy($im);
0234:     $res = base64_encode(ob_get_clean());
0235:     echo $res;
0236: }

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

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

参考サイト

(この項おわり)
header