検査部ホームページへ
香川医科大

ICG 血中消失率 アプレットについて

 これは Java の学習を始めて3週目に書き始め、4週目で完成したアプレットです。40(直前)の手習い、というわけですが、C++を知っている身には、Javaというのは非常に習い易い言語で、2週間もテキストを読めばコマンドライン版のアプリケーションならすぐ書けるようになりました。全国のC++プログラマーの皆さん、Java、やりましょう。C++に比べると、よほど精神衛生にいいですよ^^;

 参考書は『独習Java』ジョゼフ・オニール著 翔泳社。Javaの中級まで案内してくれる本で、JDK1.2の入ったCD-ROMつき。Javaの入門書には2系列あって、Javaを「WWWページを飾る楽しいアニメーション作成キット」と捉えたものと、強力なオブジェクト指向プログラミング言語と捉えたものです。もちろん、本書は後者です。Javaを使えるようになりたい人は、本の前半にAWTの解説が出てくるような本は避けましょう。

 3週目まではJDK1.2で学習し、本アプレットのプロトタイプを完成させました(これは後述のバグを含んでいます)。

 ただ、これはあまりにも飾り気がないのと、すべてテキストエディタ(秀丸 for Win32)で作るのは効率が悪いので、4週目から Borland JBuilder(INPRISE) を用いました。最初、JDKだけで学習したのは、JBuilder というあまりにも優れ過ぎた統合環境を初めから使ったら、決して Java で「独り立ち」はできないだろう、と思ったからです。これは C++Builder があるせいで、ある意味、Windowsプログラミングが「本当に」は身に付かないのと同じです。

 この JBuilder(ver.1)、一年以上前に、発売と同時くらいに買ったのですが、統合環境自体がJavaで書かれているためとても重い。当時、Pentium100MHz、48MBメモリのノートで仕事をしていた私の環境ではとても使えたものではなく、お蔵入りにしていたものです。幸いなことに現在使っているPentium 160MHz、64MBメモリのノート上なら、決して快適ではないもののなんとか使えるようになりました。

 JBuilderはすばらしい製品です。重いことを除けば (とある独占的ソフト会社のビジュアルなJ++とは違い)100% PureJavaで、実に容易にコーディングできます。ver.1 はJava1.1です。残念なことに環境が整わないので、このアプレットがMacやLinuxでも動作するのかどうか確認ができません。どなたか、Mac版のIEやLinux版のNetscapeなどでの動作を知らせていただけませんでしょうか?

 今後作成予定のアプレットは、回帰式(Passing-Bablok法、幾何平均回帰、Deming法、一次回帰)のアプレット、データベースとJDBCで接続して精度管理をするアプレット(あるいはアプリケーション)、似非科学的・占い的学説であるところのバイオリズム計算アプレット(相性診断機能付き)、姓名判断ソフトなど...

//タイトル:   ICG k-value calculator
//バージョン: 
//著作権:     Copyright (c) 1997
//作者:       Toshihiro Inage
//会社名:     KMS
//説明:       ICG 残存率のデータから 半減期、k値を計算する。これはヘルプダイアログ
//ファイル名  DlgHelp.java
//
//正しくはもっと例外処理を使うべきなのだが、慣れないもので...
//

package icg2;

import java.awt.*;
import java.awt.event.*;
import borland.jbcl.layout.*;
import borland.jbcl.control.*;

public class DlgHelp extends Dialog {
  static int count = 0;

  Panel panel1 = new Panel();
  //私としては、インスタンス変数をこんなところで初期化までするというのは
  //どうかと思うが、JBuilderはそうなっているので仕方が無い。
  //そのためにコンストラクタがあるんだけどねぇ...
  Button button1 = new Button();
  TextArea textArea1 = new TextArea();
  Panel bevelPanel1 = new Panel();
  BorderLayout borderLayout1 = new BorderLayout();
  FlowLayout flowLayout1 = new FlowLayout();

  public DlgHelp(Frame frame, String title, boolean modal) {
    super(frame, title, modal);
    count++;
    try {
      jbInit();
      add(panel1);
      pack();
    }
    catch (Exception e) {
      e.printStackTrace();
    }
  }
  //C++ならデフォルト引数で対応するところが、このようにいちいち別関数に
  //しているところをみると、Javaにはデフォルト引数は無いらしい。
  public DlgHelp(Frame frame) {
    this(frame, "", false);
  }

  public DlgHelp(Frame frame, boolean modal) {
    this(frame, "", modal);
  }

  public DlgHelp(Frame frame, String title) {
    this(frame, title, false);
  }

  public void jbInit() throws Exception{
    button1.setLabel("Close");
    textArea1.setBackground(Color.white);
    textArea1.setEditable(false);
    bevelPanel1.setLayout(flowLayout1);
    button1.addActionListener(new DlgHelp_button1_actionAdapter(this));
    panel1.setLayout(borderLayout1);
    panel1.add(textArea1, BorderLayout.CENTER);
    panel1.add(bevelPanel1, BorderLayout.SOUTH);
    bevelPanel1.add(button1, null);
    //もうちょっとスマートな定義ができないかと思うが、まあ、いいか...
    //C言語流の「行末の\」マークがうまく働かなかったのでこんなことになった^^;
    textArea1.append("ICG 血中消失率 k値計算アプレット\n\n");
    textArea1.append("ICG試験の際に血中消失率k値を計算するには片対数グラフを\n");
    textArea1.append("用いるなどし、煩雑な手順によらなければなりません。\n");
    textArea1.append(" このアプレットはこの手順を簡略化するためのものです。\n");
    textArea1.append("\n");
    textArea1.append(" 5、10、15分のICG残存率(%)を入力し、[Calculate]ボタンを\n");
    textArea1.append("クリックするだけで、必要な処理をすべてい、半減時間、k値\n");
    textArea1.append("を計算します。結果はグラフ上で確認できますので、ICGの静注\n");
    textArea1.append("量や採血が適切であったかどうかが一目瞭然です。\n");
    textArea1.append("\n");
    textArea1.append(" 10分値は省略可能です。その場合でも半減時間、k値に変わり\n");
    textArea1.append("はありません。\n\n");
    textArea1.append(" 15分値しかわからない場合は、[]15min Only をチェックし、15\n");
    textArea1.append("分値だけで計算することもできます。この場合、ICGの初期残存率\n");
    textArea1.append("は理論値の100%として計算しますので、静注量や採血が不適切だっ\n");
    textArea1.append("た場合のデータは信頼が置けませんので注意してください。\n");
  }

  void button1_actionPerformed(ActionEvent e) {
     setVisible(false);
     dispose();
  }
  int get_count(){return count;}
  public void dispose(){	
     count--;//Javaにはデストラクタは無いのだろうか? 
             //これはデストラクタに置くべきコードだ。
     super.dispose();
  }
}

class DlgHelp_button1_actionAdapter implements java.awt.event.ActionListener {
  DlgHelp adaptee;

  DlgHelp_button1_actionAdapter(DlgHelp adaptee) {
    this.adaptee = adaptee;
  }

  public void actionPerformed(ActionEvent e) {
    adaptee.button1_actionPerformed(e);
  }
}

//
//タイトル:   ICG k-value calculator
//バージョン: 
//著作権:     Copyright (c) 1997
//作者:       Toshihiro Inage
//会社名:     KMS
//説明:       ICG 残存率のデータから 半減期、k値を計算する。
//ファイル名  IcgCalc.java

package icg2;

import java.awt.*;
import java.awt.event.*;
import java.applet.*;
import borland.jbcl.layout.*;
import borland.jbcl.control.*;

/*
データは3通りの形で保持する。
icg[]とmin[]は元々のデータ。ICG残存率と対応する時間(分)
y[]とx[]は、これをグラフの座標に変換したもの。グラフ左下の原点を(0,0)とする。
gy[]とgx[]は、Canvas上の座標に変換したもの。Canvas左上が(0,0)になる。

回帰の計算を画面座標 y[], x[] で行うところがミソである。

最初はy[], x[]を用いず、gy[],gx[]で回帰の計算をしていた。それでも結果に変わりは
ないだろうと考えていたが、甘かった。これがバグとなり、プロットした点と回帰直線
とのズレが無視できなかった。そこで、y[],x[]を導入し、各形式間の変換関数である
icg2y(), y2gy(), icg2gy(), y2icg(), gy2icg()などと多数の関数を定義するハメに
なってしまった。C++ならこれらをインライン関数にするところだが、果たしてJavaの
場合、こういう細かい関数が乱立した状態の場合の効率はどうなのだろう?
*/

class IcgGraph extends Canvas{
  double intercept=0.0, slope=0.0;  //切片と傾き。これはグラフ座標をもとにして計算したもの
  double k_value=0.0, half_time=0.0;  //k値、半減時間
  static final double k_param = 0.693;  //k値計算の定数 k値=0.693/半減時間(分)
  static final double min[] = {5.0, 10.0, 15.0};
  double c0=0.0;  //ICG 初期値

  TextArea txaAns=new TextArea();  //計算結果の表示領域
  
  double icg[]=new double[3];    //ICGの生データ
  double x[]=new double[3], y[]=new double[3];  //座標変換したmin[]とicg[]

  int w=0, h=0;    //キャンバスの大きさ
  static final int margine=20;  //グラフの周囲のマージン

  boolean flag=false;  //計算ができていたらtrue

  //コンストラクタ
  IcgGraph(){}
  public void paint(Graphics g){
    g.setColor(Color.lightGray);
    for(int i=1;i<10;i++){
      g.drawLine(margine,        icg2gy((double)i),
                 this.w-margine, icg2gy((double)i)
                 );
    }
    for(int i=10;i<=100;i+=10){
      g.drawLine(margine,        icg2gy((double)i),
                 this.w-margine, icg2gy((double)i)
                 );
    }
    for(int i=100;i<=200;i+=100){
      g.drawLine(margine,        icg2gy((double)i),
                 this.w-margine, icg2gy((double)i)
                 );
    }
//    グラフのx軸グリッドの表示
    g.drawLine(min2gx(0.0), icg2gy(1.0), min2gx(0.0), icg2gy(200.0));
    for(int i=0;i0.0){
          g.drawOval(x2gx(this.x[i])-4, y2gy(this.y[i])-4, 8, 8);
        }
      }
      gx1 = min2gx(0.0);
      gy1 = y2gy(this.intercept);
      gx2 = min2gx(15.0);
      gy2 = y2gy(this.intercept + this.slope * min2x(15.0));

      g.setColor(Color.red);
      g.drawLine(gx1, gy1, gx2, gy2);
      g.setColor(Color.yellow);
      //半減時間の線
      g.drawString(round(c0/2.0,1)+"%", 1, icg2gy(c0/2.0)-2);
      g.drawLine(margine, icg2gy(c0/2.0),
             min2gx(this.half_time), icg2gy(c0/2.0));
      g.drawLine(min2gx(this.half_time), icg2gy(c0/2.0),
                 min2gx(this.half_time), this.h-margine);
      g.setColor(Color.blue);
      // C0の表示
      g.drawString(String.valueOf(round(this.c0,1)+"%"),1,y2gy(this.intercept));
      //半減時間の表示
      g.drawString(get_halftime(), min2gx(this.half_time), this.h-margine);
//      this.repaint();
      this.set_flag(false);
    }
  }
  public void set_textarea(TextArea ta){
    this.txaAns = ta;
  }
  public void set_size(int w, int h){
    this.w = w;
    this.h = h;
    this.setSize(w, h);
  }
  public void set_flag(boolean f){
    this.flag = f;
  }
  public void init_data(){
    for(int i=0;i"+min2gx(min[i])+"\n");
    }
  }
  public int set_data(double d[]){  //登録したデータの数を返す
    for(int i=0;i0.0 && this.icg[1]>0.0 && this.icg[1]>0.0){
      //3点ともに入力されているとき
      double avx=0.0;
      double avy=0.0;
      double sumx=0.0;
      double sumy=0.0;
      double sxx = 0.0;
      double sxy = 0.0;
      for(int i=0; i0.0){
      //flag15minOnly==trueのとき
      this.intercept = icg2y(100.0);
      this.slope = (y[2]-this.intercept) / x[2];
    }else
    if(icg[0]>0.0 && icg[1]==0.0 && icg[2]>0.0){
      // 5min, 15minが入力され、10minが省略されている場合
      this.slope = (this.y[2]-this.y[0])/(this.x[2]-this.x[0]);
      this.intercept = this.y[0] - this.slope * this.x[0];
    }else{
      this.set_flag(false);
    }
//    System.out.println(this.slope+","+this.intercept);
  }
  public void calc_halftime(){
    c0 = y2icg(this.intercept);  //0分の時のicg値
    this.half_time = x2min((icg2y(c0/2.0) - this.intercept)/this.slope);
//    System.out.println("半減期 "+this.half_time+"分");
  }
  public void calc_k_value(){
    this.k_value = k_param/this.half_time;
//    System.out.println("K値  "+this.k_value);
  }
  public void show_answer(){
    this.txaAns.append("半減時間 : "+get_halftime()+"\n");
    this.txaAns.append("   k値 : "+round(this.k_value,3));
  }
  private String get_halftime(){
    int m=(int)Math.floor(this.half_time);
    int s=(int)((this.half_time-(double)m)*60.0);
    return new String(m+"分"+s+"秒");
  }
  static private double round(double d, int k){
    double rnd = Math.pow(10, k);
    return Math.round(d*rnd)/rnd;
  }
}



public class IcgCalc extends Applet {
  XYLayout xYLayout1 = new XYLayout();
  boolean isStandalone = false;
  BevelPanel bevelPanel1 = new BevelPanel();
  BevelPanel pnlGraph = new BevelPanel();
  BevelPanel bevelPanel3 = new BevelPanel();
  BevelPanel bevelPanel4 = new BevelPanel();
  Label label1 = new Label();
  Label label2 = new Label();
  Label label3 = new Label();
  Label label4 = new Label();
  Label label5 = new Label();
  TextField tf05 = new TextField();
  TextField tf10 = new TextField();
  TextField tf15 = new TextField();
  TextArea taAns = new TextArea();
  Button btnCalc = new Button();
  Button btnClear = new Button();
  FlowLayout flowLayout1 = new FlowLayout();
  XYLayout xYLayout2 = new XYLayout();

  IcgGraph ig = new IcgGraph();
  double icg[] = new double[3];

  Checkbox ch15minOnly = new Checkbox();
  boolean flag15minOnly = false;
  Button button1 = new Button();
  FlowLayout flowLayout2 = new FlowLayout();  //5min, 10minを表示する時はtrue

  //引数値の取得
  public String getParameter(String key, String def) {
    return isStandalone ? System.getProperty(key, def) :
      (getParameter(key) != null ? getParameter(key) : def);
  }

  //アプレットの構築
  public IcgCalc() {
  }

  //アプレットの初期化
  public void init() {
    try { jbInit(); } catch (Exception e) { e.printStackTrace(); }
  }

  //コンポーネントの初期化
  public void jbInit() throws Exception{
    xYLayout1.setWidth(500);
    xYLayout1.setHeight(400);
    pnlGraph.setLayout(flowLayout2);
    bevelPanel1.setLayout(xYLayout2);
    bevelPanel3.setLayout(flowLayout1);
    label1.setFont(new Font("Serif", 1, 14));
    label1.setText("ICG k-value calculator");
    label2.setFont(new Font("Dialog", 1, 12));
    label2.setText("INPUT: ICG(%)");
    label3.setText(" 5min");
    label4.setText("10min");
    label5.setText("15min");
    btnCalc.setLabel("Calculate");
    btnCalc.addActionListener(new IcgCalc_btnCalc_actionAdapter(this));
    btnClear.setLabel("Clear All");
    btnClear.addActionListener(new IcgCalc_btnClear_actionAdapter(this));
    this.setLayout(xYLayout1);
    this.add(bevelPanel1, new XYConstraints(4, 5, 490, 31));
    bevelPanel1.add(label1, new XYConstraints(167, 0, -1, -1));
    this.add(pnlGraph, new XYConstraints(11, 43, 305, 303));
    pnlGraph.add(ig);
    this.add(bevelPanel3, new XYConstraints(5, 356, 490, 39));
    bevelPanel3.add(btnCalc, null);
    bevelPanel3.add(btnClear, null);
    bevelPanel3.add(button1, null);
    this.add(bevelPanel4, new XYConstraints(331, 39, 163, 313));
    bevelPanel4.add(label2, new XYConstraints(11, 6, 88, 20));
    bevelPanel4.add(label3, new XYConstraints(23, 34, 32, 19));
    bevelPanel4.add(label4, new XYConstraints(19, 58, 37, 20));
    bevelPanel4.add(label5, new XYConstraints(17, 85, 38, 18));
    bevelPanel4.add(tf05, new XYConstraints(56, 31, 89, 24));
    bevelPanel4.add(tf10, new XYConstraints(56, 56, 89, 24));
    bevelPanel4.add(tf15, new XYConstraints(56, 82, 89, 24));
    bevelPanel4.add(taAns, new XYConstraints(6, 159, 149, 145));
    bevelPanel4.add(ch15minOnly, new XYConstraints(22, 119, 124, 23));
    tf05.setVisible(true);
    tf10.setVisible(true);
    tf15.setVisible(true);
    //自家製のクラスであるところの IcgGraph
    ig.set_textarea(taAns);
    ch15minOnly.setFont(new Font("Dialog", 2, 12));
    ch15minOnly.setLabel("15min Only");
    button1.setLabel("Help");
    button1.addActionListener(new IcgCalc_button1_actionAdapter(this));
    ch15minOnly.addItemListener(new IcgCalc_ch15minOnly_itemAdapter(this));
    ch15minOnly.addMouseListener(new IcgCalc_ch15minOnly_mouseAdapter(this));
    ig.set_size(300, 300);
//  Dimension d=pnlGraph.getSize();  //これではダメ。パネルのサイズはどうすれば得られる?
//    ig.set_size(d.width, d.height);
//    taAns.append("w="+d.width+" h="+d.height);
    ig.init_data();
    ig.setBackground(Color.cyan);

}
  //getAppletInfo()やgetParameterInfo()はJBuilderが自動的に作成するメソッド。
  //特に役立ってはいないので削除してもいいだろう。
  //アプレット情報の取得
  public String getAppletInfo() {
    return "Applet Information";
  }

  //引数情報の取得
  public String[][] getParameterInfo() {
    return null;
  }

  void btnCalc_actionPerformed(ActionEvent e) {
     if(parse_data()){
       //JDK1.2でやってた頃は、インスタンス変数にはいちいちthisをつけないと
       //static変数じゃないよ、とエラーが出ていたので、クセになってしまった。
       //JBuilderでは出ないので、必要ないのかも知れない。
       this.ig.set_data(icg);
       this.ig.regress();
       this.ig.calc_halftime();
       this.ig.calc_k_value();
       this.ig.set_flag(true);
       this.ig.repaint();
       this.ig.show_answer();
      }

  }
  private boolean parse_data(){
    taAns.setText("半減期・k値計算\n");
    String s[] = {
      this.tf05.getText(),
      this.tf10.getText(),
      this.tf15.getText()
    };
    if(flag15minOnly){   //15分値のみ入力。C0=100%として計算
       icg[0] = icg[1] = 0.0;
       icg[2] = Double.valueOf(s[2]).doubleValue();
       return true;
    }

    for(int i=0;i1){	//同時に開くヘルプは一つだけ
       dh.dispose();
       return;
     }else{
       dh.setSize(400,350);
       dh.show();
     }
  }
  private Frame getBaseFrame(){	//ダイアログを開く元になるフレームをゲットする
    Component co = this;
    while(!((co = co.getParent()) instanceof Frame)){}
    return (Frame)co;
  }
}
//JBuilderのイベントハンドラは、このようにActionListenerを実装する別クラスができる。
//私の好みとしては 
//pulic class IcgGraph extends Applet implements ActionListener{...
//と、別クラスができるのを避けたいところだ。JBuilderの場合、そういう実装を(手動で)することもできる。
class IcgCalc_btnCalc_actionAdapter implements java.awt.event.ActionListener {
  IcgCalc adaptee;

  IcgCalc_btnCalc_actionAdapter(IcgCalc adaptee) {
    this.adaptee = adaptee;
  }

  public void actionPerformed(ActionEvent e) {
    adaptee.btnCalc_actionPerformed(e);
  }
}

class IcgCalc_btnClear_actionAdapter implements java.awt.event.ActionListener {
  IcgCalc adaptee;

  IcgCalc_btnClear_actionAdapter(IcgCalc adaptee) {
    this.adaptee = adaptee;
  }

  public void actionPerformed(ActionEvent e) {
    adaptee.btnClear_actionPerformed(e);
  }
}

class IcgCalc_ch15minOnly_mouseAdapter extends java.awt.event.MouseAdapter {
  IcgCalc adaptee;

  IcgCalc_ch15minOnly_mouseAdapter(IcgCalc adaptee) {
    this.adaptee = adaptee;
  }
//最初はチェックボックスをクリックするたびに処理するようにしていたが、
//内容が変更された時にそれに応じた処理をするように変更した。その方が自然。
//  public void mouseClicked(MouseEvent e) {
//    adaptee.ch15minOnly_mouseClicked(e);
//  }
}

class IcgCalc_ch15minOnly_itemAdapter implements java.awt.event.ItemListener {
  IcgCalc adaptee;

  IcgCalc_ch15minOnly_itemAdapter(IcgCalc adaptee) {
    this.adaptee = adaptee;
  }

  public void itemStateChanged(ItemEvent e) {
    adaptee.ch15minOnly_itemStateChanged(e);
  }
}

class IcgCalc_button1_actionAdapter implements java.awt.event.ActionListener {
  IcgCalc adaptee;

  IcgCalc_button1_actionAdapter(IcgCalc adaptee) {
    this.adaptee = adaptee;
  }

  public void actionPerformed(ActionEvent e) {
    adaptee.button1_actionPerformed(e);
  }
}



このページは香川医科大学検査部の紹介・情報提供が目的であり、
営利目的のものではありません。
香川医科大学
香川医科大学 検査部
検査部 clinilab@kms.ac.jp
Web管理者 tinage@kms.ac.jp
5