TPadにはuser coordinate,Normalized Coordinate(NDC),Pixel Coordinateの三種類の座標系が用意されており、座標を指定してオブジェクトを描くことができる。
まず、Pad上でのヒストグラムの位置をNDCでどうなっているかを調べる。
[ ] gStyle->GetPadTopMargin() (const Float_t)1.00000001490116119e-01 [ ] gStyle->GetPadRightMargin() (const Float_t)1.00000001490116119e-01 [ ] gStyle->GetPadBottomMargin() (const Float_t)1.00000001490116119e-01 [ ] gStyle->GetPadLeftMargin() (const Float_t)1.00000001490116119e-01
デフォルト値では例えばユーザ座標の原点はNDCでは(0.1,0.1)になる。
で、さらに軸のメモリの最大値と最小値の情報が必要なので以下で取得する。
[ ] hist->GetXaxis()->GetXmax(); [ ] hist->GetXaxis()->GetXmin(); [ ] hist->GetYaxis()->GetXmax(); [ ] hist->GetYaxis()->GetXmin();
これがわかれば比率でUser Coordinate to NDCができる。
[ ] double l = gStyle->GetPadLeftMargin(); [ ] double r = 1 - gStyle->GetPadRightMargin(); [ ] double xm = hist->GetXaxis()->GetXmin(); [ ] double xM = hist->GetXaxis()->GetXmax(); [ ] double p = l + (r - l) * (x - xm) / (xM - xm); //User CoordinateでxにあたるNDCでの値 [ ] [ ] double b = gStyle->GetPadBottomMargin(); [ ] double t = 1 - gStyle->GetPadTopMargin(); [ ] double ym = hist->GetYaxis()->GetXmin(); [ ] double yM = hist->GetYaxis()->GetXmax(); [ ] double q = b + (t - b) * (y - ym) / (yM - ym); //User CoordinateでyにあたるNDCでの値
gStyleはTStyleのグローバル変数。
以下のクラスの見た目(style)のデフォルト値を変更することができる。
- Canvas - Pad - Histogram axis - Lines - Fill areas - Text - Markers - Functions - Histogram Statistics and Titles
ROOTを使っていてこんな経験はないだろうか?
これらの疑問はたぶんここを読めば解決します。
まずgStyleの仕組みについて。
gStyleとはオブジェクトのデフォルト値の設定をするためのグローバル変数。
例えばヒストグラムなどのオブジェクトを生成すると、そのオブジェクトはgStyleの現在の設定をコピーする。
よって、オブジェクトの生成→gStyleの設定変更という順番で行っても、オブジェクトにその設定は反映されない。
この状態でgStyleの設定を反映させたい場合はオブジェクトのUseCurrentStyleメソッドを使う必要がある。
gStyle->SetHogehoge(); obj->UseCurrentStyle();
次にオブジェクトが画面に描画されるタイミングについて。
ROOTでDrawされたオブジェクトはすぐさま画面出力(Update)されるわけではなくて、以下のタイミングで出力されるようになっている。
ここで注意することは画面上の表示が更新されるためにはCanvasに含まれる各Padの少なくとも一つがModifiedのフラッグが立っていないといけないこと。
PadはオブジェクトがDrawされたときModifiedされ、この後にUpdateされると画面の表示が更新される。ひとたび、UpdateされるとCanvas中のModifiedされているPadに含まれた各オブジェクトはPaintをCallされ、PadはModifiedのフラッグがおろされる。
逆にオブジェクトに何らかの変化があっても、それが描かれているPadがModifiedのフラッグが立っていない場合、Updateされても画面の表示は変わらない。
画面表示を行っているのはobj→Paint()で、Paintメソッドはcanvas→Update()が実行された時にcallされる。このとき初めて画面表示が行われる。
ただし、コマンドラインではDrawした時にUpdateがされるため、Paintも同時にCallされる。
例えば、コマンドラインで以下の様な順番で、コマンドすると、SetFillColorの操作は画面には反映されない。
[ ] hist->Draw(); //canvasの動作としてはModified -> Update -> Modifiedのフラッグを下ろす [ ] hist->SetFillColor(kYellow); //ここではcanvasはModifiedされない [ ] canvas->Update(); //canvasはModifiedされたことを知らないので、変化なし
このような場合は、一度CanvasをModifiedしてから、Updateを再度行う必要がある。
canvas->Modified(); canvas->Update();
ややこしいところは、これはコマンドライン独特の現象であるということ。どういうことかというと、以下のようなマクロの場合、ヒストグラムの設定は反映される。
hist->Draw(); //マクロの実行中はModifiedされるだけで、Updateされない。 hist->SetFillColor(kYellow); //ここではcanvasはModifiedされない //マクロが終了したときにUpdateがCallされる。Modifiedされているため、SetFillColorがきちんと反映される
この挙動の差はかなり理解しにくいと思う。
rootには TAttFill, TAttLine, TAttMarker, TAttTextt といったグラフィックの性質を司るベースクラスがあり、様々なクラスがこれを継承している。
例えば、TH1は前3つを継承しているし、TLineはTAttLineを継承している。
(rootに限らずクラス継承そのもののメリットだけど)これによって、グラフィックの性質は統一的にその設定を変えることができる。
TColorWheel *w = new TColorWheel(); w->Draw();
上に書かれているkRedとかkBlueとかはrootで最初から定義されている色である。その正体は下のように
enum EColor { kWhite =0, kBlack =1, kGray=920, kRed =632, kGreen =416, kBlue=600, kYellow=400, kMagenta=616, kCyan=432, kOrange=800, kSpring=820, kTeal=840, kAzure =860, kViolet =880, kPink=900 };
$ROOTSYS/include/Rtypes.hで定義された、列挙型である。
例えば、
myObject.SetFillColor(kRed); myObject.SetFillColor(kYellow-10); myLine.SetLineColor(kMagenta+2);
のように明暗などを調整できる。
TCanvas *c = new TCanvas("c","Marker types",0,0,500,200); TMarker marker; marker.DisplayMarkerTypes();
8-20はなんなんだろう。
{ TCanvas *Lw = new TCanvas("Lw","test",500,200); TText t; t.SetTextAlign(32); t.SetTextSize(0.08); Int_t i=1; for (float s=0.1; s<1.0 ; s+=0.092) { TLine *lh = new TLine(0.15,s,.85,s); lh->SetLineWidth(i); t.DrawText(0.1,s,Form("%d",i++)); lh->Draw(); } return Lw; }
TAttFillで背景を透明にしたい場合。
obj->SetFillStyle(0)
オブジェクトをディスプレイに出したり、画像として保存するときはTCanvas型のオブジェクトを作るのは必須である。
しかし、TH1やTTreeのDrawを使うと勝手にc1という名前でTCanvasが生成されるので、自分で宣言する必要はない。これを確認しておく。
[ ] TH1F *h1 = new TH1F("h1","",100,0,10); [ ] h1->Draw(); Info in <TCanvas::MakeDefCanvas>: created default TCanvas with name c1
下のようにして、このc1にアクセスして設定を変えることもできる。
[ ] c1->SetLogy();
TCanvas *c1 = new TCanvas("name","title",form); TCanvas *c1 = new TCanvas("name","title",width,height);
form = 1 700x500 at 10,10 (set by TStyle::SetCanvasDefH,W,X,Y)
form = 2 500x500 at 20,20
form = 3 500x500 at 30,30
form = 4 500x500 at 40,40
form = 5 500x500 at 50,50
TCanvasの大きさはメニューなどを含んだ大きさに設定されるため、画像としては宣言した大きさよりも若干小さくなるという謎仕様がある。
具体的に言うと、width4,height28だけ小さくなる(環境によって変わるかもしれない)。
root [] TCanvas *c1 = new TCanvas("name","title"); //デフォルト値は700×500なのだが… root [] c1->GetWw() (const unsigned int)696 root [] c1->GetWh() (const unsigned int)472
TCanvasにもカレントディレクトリみたいにカレントキャンバスというべき概念がある。キャンバスを変えないで、ヒストグラムを描こうとすると前のオブジェクトは上書きされて消えてしまう(ただし、上書きして消去するのはヒストグラムの場合のみ)。
複数のオブジェクトを描く方法としては、TCanvasを分割するか、TCanvasを複数宣言すればいい。
canvas->Divide(n,m); //n行,m列にcanvasを分割する canvas->cd(i); //i番目の区画に移動する hist->Draw();
区画の番号はDivide(4,3)なら下のようになる。
1 | 2 | 3 | 4 |
5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 |
下のようにPad同士のマージンを調整することもできる(デフォルト値は0.01)。
canvas->Divide(n,m,0.02,0.02);
cd()またはcd(0)は親キャンバスを指す。
Divideせずに別々の画像として保存したい場合は下のようにして、複数宣言したCanvasを行き来することもできる。
TCanvas *c1 = new TCanvas("c1","c1"); TCanvas *c2 = new TCanvas("c2","c2"); c1->cd(); h1->Draw(); c2->cd(); h2->Draw();
上の例ではわざと手間のかかる方法をとったが、本当はTCanvasを宣言すると自動的にそのキャンバスに移動するので、下のようにすればできる。
TCanvas *c1 = new TCanvas("c1","c1"); h1->Draw(); TCanvas *c2 = new TCanvas("c2","c2"); h2->Draw();
Drawメソッドは正確には、TCanvasではなくて、TCanvasに含まれたTPadに描くというのが正しい(と思う)。 cdで内部的に行われているのはつまり、オブジェクトを描く対象のTPadを選択するということ。
Divideを使った場合は各区画の大きさは同じのものしか作れない。
もしも不均一な大きさで絵を描きたい場合はTPadを自分で適当な大きさで作る必要がある。
でも初心者向けにTPadの説明はされないと思うので、使い方を知らない場合も多いと思う。
TPad *pad = new TPad("name", "title",xlow,ylow,xup,yup,color, bordersize,bordermode); pad->Draw(); pad->SetNumber(n);
bordermode = -1 box looks as it is behind the screen
bordermode = 0 no special effects
bordermode = 1 box looks as it is in front of the screen
c1->SetLogy(); c1->SetLogy(0);// もとに戻す
canvasを分割している場合は
c1->GetPad(i)->SetLogy(); c1->cd(i)->SetLogy();//移動しつつ、せっても変える
で出来る。
c1->Print("title.ps"); //psじゃなくても、png,pdf,gifなども作れる c1->SaveAs("title.ps"); //psじゃなくても、png,pdf,gifなども作れる
c1->Print("title.pdf(") c2->Print("title.pdf") c3->Print("title.pdf)")
c1->SetGrid(); c1->SetGridx(); c1->SetGridy();
Gridのlineのtypesは下のように変えることができる。TAttLineのtypeが使える。
gStyle->SetGridStyle(1); //defaultは3
c1->Clear();
rootでは一度TCanvasにDrawしないと、画像出力できない。ところでGUIが使えないor使いたくない場合、TCanvasが画面に出て欲しくないときもある。
そういうときはマクロを読み込むときに
$ root -b macro.cxx
とするか
gROOT->SetBatch();
として、バッチモードで動かせばいい。なんとなく動作も軽い気がする。
TLine line=new TLine(0,0,3,5); line->SetLineColor(3); line->SetLineWidth(5); line->SetLineStyle(3);//1=line,2=broken,3=dotted,4=broken-dot,5=long-broken-dot line->Draw();