はじめに
データを加工してグラフ画像を作成する必要がありました。
棒グラフを積み重ねる必要があったので、当初はMATLABを使う予定でした。
しかし、色々調べるとPerlの「GD::Graph」モジュールも綺麗そうだと分かったので使ってみました。
フォントさえ用意すればLinux、Windows関係なく日本語も表示出来ます。
環境1
「Perl ver.5.12」では「Statistics::Lite」がインストール出来なかったので「ver.5.10」を使用した。
- WindowsXP Professional SP3 32bit
- Strawberry Perl ver.5.10.1.5
- GDGraph ver.1.44_01
環境2
- Debian squeeze 32bit
- Perl ver.5.10.1
- libgd-graph-perl(GDGraph ver.1.44-3)
データ
テキストデータだけでPerlのスクリプトは含まれません。
wp.zip
日本語フォント Migu 1P
「MigMix-1P-20110610」に含まれる「MigMix-1P-bold.ttf」を使いました。
http://mix-mplus-ipa.sourceforge.jp/migmix/
自分用のメモ
X軸は「control.txt」の目盛りを基準とするため、「control.txt」の平均値を使う。
このときにX軸データのMin、Maxが必要なので、予め、全てのデータを連結したファイルを作成して読み込む。
GDモジュールで画像サイズを小さくすると目盛り値が重なってしまうので、ある程度大きいサイズを設定する。
Y軸の値は0~Nの整数値なので、それぞれの値毎に配列を準備(二次元配列で対応)して、各々が別の棒グラフになるようにする。
それを「GD::Graph」を使って棒グラフを積み重ねることで色分けする。
「control.txt」の平均値を計算してX軸の中心とする。
X軸のデータの最小値、最大値を求めて、それらが含まれるようにX軸の目盛りの数を設定する。
一目盛り幅は、平均値の20%とする。
その目盛りの幅がBIN幅となり、その範囲に含まれるデータを加算して棒グラフで表示する。
BIN幅の左の目盛りは~以上、右の目盛りは~未満とする。
グラフ図
ソース
UTF8で保存。
#!/usr/bin/perl
use strict;
use warnings;
use utf8;
use Encode;
#use GD::Graph::mixed;
use GD::Graph::bars;
#use GD::Graph::hbars;
use Statistics::Lite qw( :all );
#use POSIX; # ceil
#use Math::Round; # 四捨五入
{
my $infile = "control.txt";
# my $infile = "data1.txt";
# my $infile = "data2.txt";
# control.txt、data1-2を合体させたもの。
my $allfile = "data_all.txt";
# GDで使用するフォント
my $font_name = "MigMix-1P-bold.ttf";
# 平均値の20%を一目盛の幅とする
my $ratio = 0.2;
# $allfileの読み込み
open my $h_inall, "<", $allfile or die "$!: $allfile";
my @data_all = <$h_inall>;
close( $h_inall );
my $ct_all = 0;
my @data_x_all = ();
my $tmp;
foreach my $i ( @data_all )
{
( $data_x_all[ $ct_all ], $tmp ) = split(/\t/, $i);
chomp( $data_x_all[ $ct_all ] );
$ct_all++;
}
# 個別のデータ読み込み
open my $h_in, "<", $infile or die "$!: $infile";
my @data = <$h_in>;
close( $h_in );
my $ct = 0;
my @data_x = ();
my @data_y = ();
foreach my $i ( @data )
{
( $data_x[ $ct ], $data_y[ $ct ] ) = split(/\t/, $i);
chomp( $data_x[ $ct ] );
chomp( $data_y[ $ct ] );
$ct++;
}
my $mean_x = mean @data_x;
# my $stddev_x = stddev @data_x;
# print "mean x org: $mean_x \n";
# print "width org: " . ($mean_x * $ratio) . "\n";
# print "min x: " . (min @data_x) . "\n";
# X軸の目盛りをcontrol.txtのデータに合わせる
$mean_x = 0.894063333333333 if ( $infile !~ /control/ );
# データの最小値・最大値
# my $min_x = min @data_x;
# my $max_x = max @data_x;
my $min_x = min @data_x_all;
my $max_x = max @data_x_all;
# print "min x: $min_x \n";
# print "max x: $max_x \n";
# X軸の目盛りの分割数(プラス側・マイナス側)
# 小数点以下を切り捨てる。
my $div_x_nega = abs( $mean_x - $min_x ) / ( $ratio * $mean_x );
my $div_x_plus = abs( $mean_x - $max_x ) / ( $ratio * $mean_x );
$div_x_nega = sprintf( "%d", $div_x_nega );
$div_x_plus = sprintf( "%d", $div_x_plus );
$div_x_nega += 1;
$div_x_plus += 1;
# X軸のマイナス側のBIN幅(二度手間だが、出力用に使う)
my $r1;
my $r2;
my @bin_width_nega = ();
my @bin_width_plus = ();
my @bin_width = ();
for ( my $i = 0; $i < $div_x_nega; $i++ )
{
$r1 = $mean_x * ( 1 - ( 1 + $i ) * $ratio ) ;
$r2 = $mean_x * ( 1 - $i * $ratio ) ;
$bin_width_nega[ $i ] = $r1 . "\t" . $r2;
# print "- $i $i: $data_x[ $i ] $range1 $r2 \n";
}
@bin_width_nega = reverse @bin_width_nega;
# X軸のプラス側のBIN幅(二度手間だが、出力用に使う)
for ( my $i = 0; $i < $div_x_plus; $i++ )
{
$r1 = $mean_x * ( 1 + $i * $ratio );
$r2 = $mean_x * ( 1 + ( 1 + $i ) * $ratio );
$bin_width_plus[ $i ] = $r1 . "\t" . $r2;
# print "+ $i $i: $data_x[ $i ] $range1 $r2 \n";
}
@bin_width = ( @bin_width_nega, @bin_width_plus );
# 実際の目盛(X軸のマイナス側)
my @scale_x_nega = ();
for ( my $i = 0; $i < $div_x_nega; $i++ )
{
$scale_x_nega[ $i ] = $mean_x - ( $ratio * $mean_x ) / 2 - $i * $ratio * $mean_x
}
@scale_x_nega = reverse @scale_x_nega;
# 実際の目盛(X軸のプラス側)
my @scale_x_plus = ();
for ( my $i = 0; $i < $div_x_plus; $i++ )
{
$scale_x_plus[ $i ] = $mean_x + ( $ratio * $mean_x ) / 2 + $i * $ratio * $mean_x
}
my @scale_x = ( @scale_x_nega, @scale_x_plus );
# X軸の目盛りの桁を統一する
foreach my $i ( @scale_x )
{
$i = sprintf( "%0.3f", $i );
}
# print "@scale_x \n\n";
# 結果を格納する配列の初期化
my @result = ();
for ( my $i = 0; $i <= @data_y; $i++ )
{
for ( my $j = 0; $j < $div_x_nega + $div_x_plus; $j++ )
{
$result[ $i ][ $j ] = 0;
}
}
# BIN幅に合わせたデータの仕分け
my $range1;
my $range2;
my $range_found;
for ( my $i = 0; $i < @data_x; $i++ )
{
# print "$i: $data_x[ $i ] $data_y[ $i ] \n";
$range_found = 0;
# X軸のマイナス側
for ( my $k = 0; $k < $div_x_nega; $k++ )
{
$range1 = $mean_x * ( 1 - ( 1 + $k ) * $ratio ) ;
$range2 = $mean_x * ( 1 - $k * $ratio ) ;
if ( $range1 <= $data_x[ $i ] &amp;&amp; $data_x[ $i ] < $range2 )
{
# print "- found $i $k: $data_x[ $i ] $data_y[ $i ] $range1 $range2 \n";
$result[ $data_y[ $i ] ][ $div_x_nega - 1 - $k ] += $data_y[ $i ]; # 加算する
$range_found = 1;
last;
}
}
next if ( $range_found == 1 );
# X軸のプラス側
for ( my $j = 0; $j < $div_x_plus; $j++ )
{
$range1 = $mean_x * ( 1 + $j * $ratio );
$range2 = $mean_x * ( 1 + ( 1 + $j ) * $ratio );
if ( $range1 <= $data_x[ $i ] &amp;&amp; $data_x[ $i ] < $range2 )
{
# print "+ found $i $j: $data_x[ $i ] $data_y[ $i ] $range1 $range2 \n";
$result[ $data_y[ $i ] ][ $j + $div_x_nega ] += $data_y[ $i ]; # 加算する
last;
}
}
}
# Y値の重複データを削除、数値の昇順でソートする
my %count = ();
my @data_y_uniq = grep( !$count{$_}++, @data_y );
@data_y_uniq = sort {$a <=> $b} @data_y_uniq;
# GDに渡す配列に、X軸の目盛りを格納した配列を代入する
my @plot_data = ();
push @plot_data, \@scale_x;
# データ整理
foreach my $i ( @data_y_uniq )
{
my @data = ();
for ( my $j = 0; $j < $div_x_nega + $div_x_plus; $j++ )
{
print "Error: $i $j \n" if ! defined $result[ $i ][ $j ];
push @data, $result[ $i ][ $j ];
}
push @plot_data, \@data;
}
# データ範囲毎に標準出力に出力する(確認用)
for ( my $j = 0; $j < $div_x_nega + $div_x_plus; $j++ )
{
my @data = ();
foreach my $i ( @data_y_uniq )
{
print "Error: $i $j \n" if ! defined $result[ $i ][ $j ];
push @data, $result[ $i ][ $j ];
}
my $sum = sum @data;
# print $scale_x[ $j ] . "\t" . $sum . "\n";
print $bin_width[ $j ] . "\t" . $sum . "\n";
}
# グラフ描画
# my $gd = GD::Graph::mixed->new( 1280, 1024 );
my $gd = GD::Graph::bars->new( 1280, 1024 );
# 全てのグラフを棒グラフにする
my @gd_types = ();
for ( my $i = 0; $i < @data_y_uniq; $i++ )
{
$gd_types[ $i ] = 'bars';
}
# 「GD::Graph::mixed」のオプション
$gd->set(
title => encode('utf-8', $infile),
t_margin => 10,
b_margin => 10,
l_margin => 10,
r_margin => 10,
x_label => encode('utf-8', "横軸"),
x_label_position => 0.5,
y_label => encode('utf-8', "縦軸"),
types => [ @gd_types ],
y_tick_number => 10,
y_label_skip => 2,
bar_width => 20,
bar_spacing => 3,
# overwrite => 1, # 「$gd = GD::Graph::mixed」の場合は不要。
cumulate => 1 # 棒グラフを縦に積み重ねる場合は「1」
);
# フォントの設定
GD::Text->font_path("./");
$gd->set_title_font( $font_name, 14 );
$gd->set_legend_font( $font_name, 8 );
$gd->set_x_axis_font( $font_name, 8 );
$gd->set_x_label_font( $font_name, 10 );
$gd->set_y_axis_font( $font_name, 8 );
$gd->set_y_label_font( $font_name, 8 );
# $gd->set_legend( encode(utf-8, "xxx"),
# encode(utf-8, "yyy"),
# encode(utf-8, "zzz");
my $img = $gd->plot( \@plot_data ) or die( "Cannot create image" );
# ファイルに保存する
my $imagefile = "gd-" . $infile . ".jpg";
open my $h_gd, ">", "$imagefile" or die "$!: $imagefile";
binmode $h_gd;
print $h_gd $img->jpeg();
close $h_gd;
exit;
}



Comments