/*
 * Copyright (c) 2009 The openGion Project.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
 * either express or implied. See the License for the specific language
 * governing permissions and limitations under the License.
 */
package org.opengion.plugin.view;

import org.opengion.hayabusa.common.HybsSystem;
import org.opengion.hayabusa.common.HybsSystemException;
import org.opengion.fukurou.util.StringUtil;

import java.util.Calendar;
import java.util.Map;
import java.util.TreeMap;
import java.util.Collection;
import java.util.Locale ;
import java.util.Date;
import java.text.DateFormat;
import java.text.SimpleDateFormat;

import java.awt.Graphics2D;
import java.awt.Color;
import java.awt.FontMetrics;
import java.awt.Stroke;
import java.awt.BasicStroke;
import java.awt.image.BufferedImage;

import javax.imageio.ImageIO;
import java.io.File;
import java.io.IOException;


/**
 * キー、日時、状況コードを持つ稼働状況の表示を行うクラスです。
 *
 * パラメータが必要な場合は、ViewTimeBarParamTag を使用してください。
 *
 * パラメータが設定されていない場合は、ViewForm_ImageTimeBar の初期値が使用されます。
 * (パラメータを使用するには、viewタグのuseParam 属性をtrueに設定する必要があります。)
 *
 * SELECT文は、キー、日時、状況コードが、必須項目で、カラムの並び順は、完全に固定です。
 * よって、カラム位置を指定する必要はありませんが、SELECT文を自由に設定することも
 * 出来ませんので、ご注意ください。<br/>
 * この固定化に伴い、WRITABLE 指定も使用できません。（そもそも書き込み不可です）
 * それ以降のカラムについては、内部処理としては、使用していません。
 * ただし、パラメータで、カラー色指定、ラベル表記部、イメージ重ね合わせ、
 * ポップアップ表記、リンク表記に使えます。
 *
 * データの並び順(ORDER BY)も、キー、日時順にしてください。
 * データは、キー単位に１レコード作成されます。（キーブレイク）その間、日時順に
 * データを処理します。
 *
 * データの表示は、今のレコードの日時から、次のレコードの日時までを一つの状態と
 * して表します。今のレコードを表示するには、次のレコードが必要になります。
 * 画面表示は、表示開始日時(minStartTime) から 表示期間(timeSpan)分を表示します。
 * 通常、開始時刻は、表示開始時刻より前より始まり、次のレコードで、終了時刻が決定
 * されます。最後のデータは、期間満了まで続いていると仮定されます。
 * データが存在しないのであれば、「存在しないデータ」を作成してください。
 * 
 * ImageTimeBar では、キーでまとめた値について、各状況コードをカラー化し、積み上げ
 * 帯グラフ形式でPNG画像化します。<br/>
 * この画像を、読み込む HTML を出力することで、画面上に、積み上げ帯グラフを表示します。
 * 状況コードに対応する色は、標準では自動作成ですが、外部から色文字列(colorClm)を与えることで
 * 自由に指定する事も可能です。
 *
 * ポップアップ表記(tipsClm)、リンク表記(linkClm)は、この画像に対するエリア指定タグを出力する事で実現します。
 * 画像ファイルは、全データに対して、１画像だけなので、サイズは大きくなりますが、１レコード
 * 単位に画像を作成しないため、レスポンスは向上します。
 * それぞれ、viewMarker , viewLink を利用することが可能です。特に、リンク表記(linkClm) については、
 * linkタグの hrefTarget 属性を true に設定することで適用できます。
 *
 * 画像ファイルは、java.io.File.createTempFile( File ) で作成するため、JavaVM(=Tomcat)が
 * 正常終了するときに、削除されます。異常終了時には残りますが、temp フォルダを定期的に
 * 整理すれば、それほど大量のファイルが残ることはないと思われます。
 *
 * データは、イベント発生時に作成されると仮定しています。つまり、書き込まれた日時から、
 * 状況コードに対応する状況が発生し、次の状況違いのレコードまで継続していると考えます。
 * よって、データを途中で切り出す場合、切り出す範囲の前の状態が必要になります。
 * 一番最初の状態は、"不明" として扱います。（空欄＝白色）
 *
 * @og.rev 5.5.5.6 (2012/08/31) 新規追加
 * @og.group 画面表示
 *
 * @version  4.0
 * @author	 Kazuhiko Hasegawa
 * @since    JDK5.0,
 */
public class ViewForm_ImageTimeBar extends ViewForm_HTMLTable {
	//* このプログラムのVERSION文字列を設定します。	{@value} */
	private static final String VERSION = "5.6.5.0 (2013/06/07)" ;

	private static final Color LABEL_COLOR = Color.BLACK;		// ラベル記述時の色
	private static final Color NULL_COLOR  = Color.WHITE;		// 5.6.1.1 (2013/02/08) 不明(空欄)時の色

	private long startDate		;	// タイムテーブルの表示開始日時から求めた long 値(1=分単位)
	private long timeSpan		;	// タイムテーブルの表示期間。元は時間指定であるが、分単位で持つ。(1=分単位)

	private boolean useLegend	;	// カラーの凡例を使用するかどうか[true/false]
	private int maxLabelWidth	;	// ラベル表記部の最大サイズをpxで指定。何もなければ、可変長サイズ
	private int maxTimeWidth	;	// タイム表記部の最大サイズをpxで指定。
	private int chartHeight		;	// １レコードのチャートの間隔をpxで指定。実際の幅は、CHART_HEIGHT＋MARGIN＊２
	private int chartPadding	;	// イメージ作成の 全体テーブルの隙間
	private int recodeMargin	;	// 各レコード、文字等の内部の間隔
	private boolean useLastData	;	// 5.6.1.1 (2013/02/08) 行の最後の情報が、継続しているとして使うかどうか[true/false]

	private String tempDir		;	// 画像ファイルを作成するテンポラリディレクトリ(相対パス)
	private String tempUrl		;	// 作成した画像ファイルを取得するときに使用するURL(コンテキスト/相対パス)

	// SELECT文は、キー、日時、状況コードが、必須項目
	private static final int	keyClmNo	= 0;	// ヘッダー1：キーカラムNo
	private static final int	dyClmNo		= 1;	// ヘッダー2：日時カラムNo
	private static final int	fgjClmNo	= 2;	// ヘッダー3：状況コードカラムNo

	private int[] labelClmsNo	= null;				// 初期値は、キーカラム
	private int[] maxClmWidth	= null;				// labelClms 単位の最大文字列長
	private int   colClmNo		= -1;				// カラーコード直接指定する場合に色文字列を指定するカラムNo
	private int   tipsClmNo		= -1;				// マウスオーバー時のTips表示を行うカラムNo
	private int   linkClmNo		= -1;				// クリッカブルリンクを設定するカラムNo

	private int str2DateTime	;	// getStr2Date(String)処理をおこなった時の、引数の時刻情報(分単位)をセットするテンポラリ変数。
	private int startTime		;	// startDate の時刻情報。上記処理で、startDate を getStr2Date(String) 処理したときの値を持っておくための変数。

	private int MAX_X	;	// イメージの最大横幅(X)方向のサイズpx。chartPadding*2 + maxLabelWidth + maxTimeWidth
	private int MAX_Y	;	// イメージの最大縦幅(Y)方向のサイズpx。chartPadding*2 + (chartHeight+recodeMargin*2)*(レコード数＋ヘッダー数)

	/**
	 * 内容をクリア(初期化)します。
	 *
	 */
//	@Override
//	public void clear() {
//		super.clear();
//	}

	/**
	 * DBTableModel から HTML文字列を作成して返します。
	 * startNo(表示開始位置)から、pageSize(表示件数)までのView文字列を作成します。
	 * 表示残りデータが pageSize 以下の場合は,残りのデータをすべて出力します。
	 *
	 * @og.rev 5.6.1.0 (2013/02/01) tips や link は、ひとつ前のデータで作成する必要がる為、最初の1件目は、処理しないように修正
	 * @og.rev 5.6.1.1 (2013/02/08) 初期値の色を、直接の値ではなく、static final で定義された色を使用する。色自体は変えていません
	 * @og.rev 5.6.1.1 (2013/02/08) useLastData 追加
	 * @og.rev 5.6.3.0 (2013/04/01) tipsのシングルクォーテーション のエスケープ
	 * @og.rev 5.6.3.1 (2013/04/05) 短縮ラベルなど、<span>タグが付与される値から、spanタグを削除します。
	 *
	 * @param  startNo	  表示開始位置
	 * @param  pageSize   表示件数
	 *
	 * @return	DBTableModelから作成された HTML文字列
	 */
	@Override
	public String create( final int startNo, final int pageSize )  {
		if( getRowCount() == 0 ) { return ""; }	// 暫定処置

		// パラメータの情報を初期化＆取得します。
		paramInit();

		int lastNo = getLastNo( startNo, pageSize );

		// イメージの 最大Ｘ、Ｙサイズを求め、結果を、MAX_X,MAX_Y 変数に設定する。
		calcImageSize( startNo,lastNo );

		BufferedImage img = new BufferedImage( MAX_X, MAX_Y, BufferedImage.TYPE_INT_ARGB);
		Graphics2D g2 = img.createGraphics();

		// chartPadding を考慮した領域のクリップ（クリップ外にDrowされても無視されます。）
		g2.setClip( chartPadding , chartPadding , MAX_X-chartPadding*2+1 , MAX_Y-chartPadding*2+1 );	// （x 座標,y 座標,幅,高さ) ＋１は罫線の分

		StringBuilder out = new StringBuilder( HybsSystem.BUFFER_LARGE );

		String oldKeyVal = "";				// 初期値。１回目にキーブレイクさせる。
		long   oldTime   = 0L;				// 日付項目の一つ前の値

//		Color  oldColor  = Color.WHITE;		// 色の一つ前の値(初期値)
		Color  oldColor  = NULL_COLOR;		// 5.6.1.1 (2013/02/08) 不明(空欄)時の色(初期値)

		ColorMap colMap = new ColorMap();	// 状況コード、ラベル、カラーを管理するクラス

		int rowCnt = (useLegend) ? 2 : 1;		// 凡例(useLegend)を使う(true)場合は、２行分、使わない場合は、ヘッダーの１行が初期値
		int imgX   = 0;							// イメージ出力時の X軸の左端 px数
		int imgY   = 0;							// イメージ出力時の Y軸の上端 px数
		int imgW   = 0;							// イメージ出力時の 幅(fillRectで使用)
		int imgH   = chartHeight;				// イメージ出力時の 高さ(fillRectで使用)
		int rowH   = chartHeight+recodeMargin*2;	// 罫線出力時の高さ(drawRectで使用)

		double timeScale = (double)maxTimeWidth/(double)timeSpan;
		boolean useTipsLink = (tipsClmNo >= 0 || linkClmNo >= 0) ;		// tipsClm か linkClm かどちらかを使用する場合、true
		for( int row=startNo; row<lastNo; row++ ) {
			// キーブレイクの判定
			String keyVal  = getValue( row,keyClmNo );
			String dyVal   = getValue( row,dyClmNo );

			// キーブレイク判定。キーブレイクは、一番初めから来る。
			if( !oldKeyVal.equals( keyVal ) ) {
				// キーブレイクで最初に行うのは、前のレコードの未出力分の処理。１行目は、処理不要
				if( row > startNo ) {
					imgX = chartPadding + maxLabelWidth + (int)(oldTime*timeScale);
					imgW = (int)((timeSpan-oldTime)*timeScale) ;

					// 幅が0以上の場合のみ描画する。
					if( imgW > 0 ) {
						// 5.6.1.1 (2013/02/08) useLastData 追加
						if( useLastData ) { g2.setColor( oldColor ); }					// 色の設定は、旧色を使う
						else {				g2.setColor( NULL_COLOR ); }				// NULL色を使う
						g2.fillRect( imgX , imgY+recodeMargin , imgW, imgH );			// (実際の状態)左端x,上端y,幅w,高さh

						// tipsClm か linkClm を使用する。
						// 5.6.1.0 (2013/02/01) tips や link は、ひとつ前のデータで作成する必要がる為、最初の1件目は、処理しないように修正
//						if( useTipsLink ) {
//						if( useTipsLink && row > 0 ) {
						if( useLastData && useTipsLink && row > 0 ) {	// 5.6.1.1 (2013/02/08) useLastData 追加
							// tips や link は、ひとつ前のデータで作成する必要がある。(row-1)
							String tips = (tipsClmNo >= 0) ? getValueLabel( row-1,tipsClmNo ) : getValueLabel( row-1,fgjClmNo ) ;
							if( tips != null && tips.indexOf( "'" ) >= 0 ) {	// 5.6.3.0 (2013/04/01) シングルクォーテーション のエスケープ
								tips = tips.replaceAll( "'","&#39;" );
							}
							tips = StringUtil.spanCut( tips );					// 5.6.3.1 (2013/04/05) spanタグを削除

							out.append( "<area shape='rect' alt='" ).append( tips ).append( "'" );
							if( linkClmNo >= 0 ) {
								String hrefVal = getValueLabel( row-1,linkClmNo );
								if( hrefVal != null && hrefVal.startsWith( "href" ) ) {		// linkClmの値の先頭が、html の場合のみ追加する。
									out.append( hrefVal );
								}
							}
							out.append( " coords='" ).append( imgX ).append( "," ).append( imgY+recodeMargin ).append( "," );		// 左端x1,上端y1
							out.append( imgX+imgW ).append( "," ).append( imgY+recodeMargin+chartHeight ).append( "' />" );			// 右端x2,下段y2
						}
					}
				}

				// 次に、新しい行の出力（ラベルは、ブレイク時に出力しておきます。）
				oldKeyVal = keyVal;			// null は来ないはず
				imgY = chartPadding+(rowH)*rowCnt ;
				rowCnt++ ;

				// レコードのラベル分だけ、繰返し描画する。
				int clmSu = labelClmsNo.length;
				int posX  = chartPadding ;						// ラベル文字列の書き出し位置の初期値
				int posY2 = imgY+chartHeight+recodeMargin ;		// ラベルのY軸基準。
				g2.setColor( LABEL_COLOR );
				for( int col=0; col<clmSu; col++ ) {
					String lbl = getValueLabel( row,labelClmsNo[col] );		// その行の値のRenderer値
					lbl = StringUtil.spanCut( lbl );						// 5.6.3.1 (2013/04/05) spanタグを削除
					g2.drawString( lbl , posX + recodeMargin, posY2 );		// 文字列,ベースラインのx座標,y座標
					posX += recodeMargin*2 + maxClmWidth[col] ;				// 次の書き出し位置は、文字最大長＋マージン
				}

				// レコードの枠線
				g2.drawRect( chartPadding , imgY , MAX_X-chartPadding*2 , rowH );		// (レコード枠)左端x,上端y,幅w,高さh

				oldTime = 0L;				// キーブレイクによる初期化
//				oldColor= Color.WHITE;		// キーブレイクによる初期化
				oldColor= NULL_COLOR;		// 5.6.1.1 (2013/02/08) キーブレイクによる初期化=不明(空欄)時の色
			}

			long newTime = getStr2Date( dyVal ) - startDate;		// 次の時刻
			if( newTime < oldTime ) { newTime = oldTime; }			// 前の時刻より小さい場合は、前の時刻まで、進めておく。

			imgX = chartPadding + maxLabelWidth + (int)(oldTime*timeScale);		// old時刻から書き始める(左端x)
			imgW = (int)((newTime-oldTime)*timeScale) ;							// 差分幅だけ描画する。

			// 幅が0以上の場合のみ描画する。
			if( imgW > 0 ) {
				g2.setColor( oldColor );										// 色の設定
				g2.fillRect( imgX , imgY+recodeMargin , imgW, chartHeight );	// (実際の状態)左端x,上端y,幅w,高さh

				// tipsClm か linkClm を使用する。
				// 5.6.1.0 (2013/02/01) tips や link は、ひとつ前のデータで作成する必要がる為、最初の1件目は、処理しないように修正
//				if( useTipsLink ) {
				if( useTipsLink && row > 0 ) {
					// tips や link は、ひとつ前のデータで作成する必要がある。(row-1)
					String tips = (tipsClmNo >= 0) ? getValueLabel( row-1,tipsClmNo ) : getValueLabel( row-1,fgjClmNo ) ;
					if( tips != null && tips.indexOf( "'" ) >= 0 ) {	// 5.6.3.0 (2013/04/01) シングルクォーテーション のエスケープ
						tips = tips.replaceAll( "'","&#39;" );
					}
					tips = StringUtil.spanCut( tips );					// 5.6.3.1 (2013/04/05) spanタグを削除

					out.append( "<area shape='rect' alt='" ).append( tips ).append( "'" );
					if( linkClmNo >= 0 ) {
						String hrefVal = getValueLabel( row-1,linkClmNo );
						if( hrefVal != null && hrefVal.startsWith( "href" ) ) {		// linkClmの値の先頭が、html の場合のみ追加する。
							out.append( hrefVal );
						}
					}
					out.append( " coords='" ).append( imgX ).append( "," ).append( imgY+recodeMargin ).append( "," );		// 左端x1,上端y1
					out.append( imgX+imgW ).append( "," ).append( imgY+recodeMargin+chartHeight ).append( "' />" );			// 右端x2,下段y2
				}
			}

			oldTime   = newTime ;

			String fgjVal  = getValue( row,fgjClmNo );			// 状況コードのキー
			String fgjLbl  = getValueLabel( row,fgjClmNo );		// 状況コードのラベル
			if( colClmNo >= 0 ) {
				oldColor  = colMap.getColor( fgjVal,fgjLbl,getValue( row,colClmNo ) );		// 色文字列を指定する場合
			}
			else {
				oldColor  = colMap.getColor( fgjVal,fgjLbl );								// 色文字列を指定しない＝自動設定
			}
		}

		// レコードのいちばん最後の出力。一応、ジャストの場合(oldTime == maxEdTime)は、処理しない
		// 5.6.1.1 (2013/02/08) レコードのいちばん最後の出力は、NULL色を使うように変更
		if( oldTime < timeSpan ) {
			imgX = chartPadding + maxLabelWidth + (int)(oldTime*timeScale);
			imgW = (int)((timeSpan-oldTime)*timeScale) ;
			// 5.6.1.1 (2013/02/08) useLastData 追加
			if( useLastData ) { g2.setColor( oldColor ); }					// 色の設定は、旧色を使う
			else {				g2.setColor( NULL_COLOR ); }				// NULL色を使う
			g2.fillRect( imgX , imgY+recodeMargin , imgW, chartHeight );	// (実際の状態)左端,上端,幅,高さ

			// tipsClm か linkClm を使用する。
//			if( useTipsLink ) {
			if( useLastData && useTipsLink ) {			// 5.6.1.1 (2013/02/08) useLastData 追加
				// tips や link 最後の出力は、最後のデータなので、lastNo-1 となる。
				String tips = (tipsClmNo >= 0) ? getValueLabel( lastNo-1,tipsClmNo ) : getValueLabel( lastNo-1,fgjClmNo ) ;
				if( tips != null && tips.indexOf( "'" ) >= 0 ) {	// 5.6.3.0 (2013/04/01) シングルクォーテーション のエスケープ
					tips = tips.replaceAll( "'","&#39;" );
				}
				tips = StringUtil.spanCut( tips );					// 5.6.3.1 (2013/04/05) spanタグを削除

				out.append( "<area shape='rect' alt='" ).append( tips ).append( "'" );
				if( linkClmNo >= 0 ) {
					String hrefVal = getValueLabel( lastNo-1,linkClmNo );
					if( hrefVal != null && hrefVal.startsWith( "href" ) ) {		// linkClmの値の先頭が、html の場合のみ追加する。
						out.append( hrefVal );
					}
				}
				out.append( " coords='" ).append( imgX ).append( "," ).append( imgY+recodeMargin ).append( "," );		// 左端x1,上端y1
				out.append( imgX+imgW ).append( "," ).append( imgY+recodeMargin+chartHeight ).append( "' />" );			// 右端x2,下段y2
			}
		}

		// ヘッダー情報のイメージを作成します。
		imageHeaderPaint( g2 , timeScale , colMap );

		// イメージファイルを出力し、URLを返します。
		File file = null;
		try {
			File dir = new File( tempDir );		// 画像ファイル出力先のフォルダ
			dir.mkdirs();						// なければ作成する。

			file = File.createTempFile( "Img",".png", dir );	// テンポラリファイルの作成
			file.deleteOnExit();								// JavaVM 停止時にテンポラリファイルの削除

			ImageIO.write(img, "PNG", file );
			g2.dispose();
		}
		catch( IOException ex ) {
			System.out.println( "エラー:" + ex );
		}

		// imgタグを作成します。
		int width  = MAX_X;
		int height = MAX_Y;

		StringBuilder out2 = new StringBuilder( HybsSystem.BUFFER_LARGE );
		out2.append( "<img id='ImageTimeBar' alt='ImageTimeBar' border='0'" );
		out2.append( " width='" ).append( width ).append( "px' height='" ).append( height ).append( "px'" );
		out2.append( " src='" ).append( tempUrl ).append( file.getName() ).append( "'" );

		// area タグのデータが作成されていれば、出力します。
		if( out.length() > 0 ) {
			out2.append( " usemap='#TimeBarMap' />" );	// img タグにusemap 属性を追加して、終了部分を追記

			out2.append( "<map name='TimeBarMap'>" );
			out2.append( out );
			out2.append( "</map>" );
		}
		else {
			out2.append( " />" );						// img タグの終了部分を追記
		}

		return out2.toString();
	}

	/**
	 * パラメータ内容を初期化します。
	 *
	 * @og.rev 5.6.1.1 (2013/02/08) useLastData 追加
	 */
	private void paramInit() {
		String s_startDate	= getParam( "START_DATE"		);	// タイムテーブルの表示開始日時をセットします(初期値:データの最小日時)。
		int timeSpanHour	= getIntParam( "TIME_SPAN"		);	// タイムテーブルの表示期間を時間で指定します(初期値:{@og.value TIME_SPAN})。

		String[] s_labelClms	= StringUtil.csv2Array( getParam( "LABEL_CLMS" ) );	// 一覧表のラベル表示部に表示するカラム名をCSV形式で指定します。
		String s_colClm			= getParam( "COLOR_CLM"		);	// レコードに付ける色を色文字列で指定する場合のカラム名を指定します。
		String s_tipsClm		= getParam( "TIPS_CLM"		);	// レコード単位に、マウスオーバー時のTips表示を行うカラム名を指定します。
		String s_linkClm		= getParam( "LINK_CLM"		);	// レコード単位に、クリッカブルリンクを設定するカラム名を指定します。

		useLegend		= getBoolParam( "USE_LEGEND"		);	// カラーの凡例を使用するかどうか[true/false]
		maxLabelWidth	= getIntParam( "MAX_LABEL_WIDTH"	);	// ラベル表記部の最大サイズをpxで指定。何もなければ、可変長サイズ
		maxTimeWidth	= getIntParam( "MAX_TIME_WIDTH"		);	// タイム表記部の最大サイズをpxで指定。
		chartHeight		= getIntParam( "CHART_HEIGHT"		);	// １レコードのチャートの間隔をpxで指定。実際の幅は、CHART_HEIGHT＋MARGIN＊２
		chartPadding	= getIntParam( "CHART_PADDING"		);	// イメージ作成の 全体テーブルの隙間
		recodeMargin	= getIntParam( "RECODE_MARGIN"		);	// 各レコード、文字等の内部の間隔
		useLastData		= getBoolParam( "USE_LAST_DATA"		);	// 5.6.1.1 (2013/02/08) 行の最後の情報が、継続しているとして使うかどうか[true/false]

		tempDir			= getParam( "TEMP_DIR"				);	// 画像ファイルを作成するテンポラリディレクトリ(相対パス)
		tempUrl			= getParam( "TEMP_URL"				);	// 作成した画像ファイルを取得するときに使用するURL(コンテキスト/相対パス)

		startDate		= getStr2Date( s_startDate );			// 分単位に変換する。
		startTime		= str2DateTime ;						// 開始日時の時刻情報(分単位)の値。str2DateTime は、getStr2Date(String)メソッド実行後にセットされる。
		timeSpan		= timeSpanHour * 60L ;					// 分単位に変換する。

		int len = s_labelClms.length;
		if( len > 0 ) {
			labelClmsNo = new int[len];
			for( int i=0; i<len; i++ ) {
				labelClmsNo[i] = getColumnNo( s_labelClms[i] );
			}
		}
		else {
			labelClmsNo = new int[] { keyClmNo };		// 初期値は、キーカラム
		}

		// 指定のカラム名に対するカラム番号を取得。なければ、-1 を設定しておく。
		if( s_colClm  != null ) { colClmNo  = getColumnNo( s_colClm  ); }			// レコードに付ける色を色文字列で指定する場合のカラムNo
		if( s_tipsClm != null ) { tipsClmNo = getColumnNo( s_tipsClm ); }			// レコード単位に、マウスオーバー時のTips表示を行うカラムNo
		if( s_linkClm != null ) { linkClmNo = getColumnNo( s_linkClm ); }			// レコード単位に、クリッカブルリンクを設定するカラムNo
	}

	/**
	 * イメージの ＸＹサイズを求め、結果を、MAX_X,MAX_Y 変数に設定します。
	 *
	 * また、ラベル文字列の最大長が指定されていない場合（maxLabelWidth == -1）最大長を自動計算し、maxLabelWidth変数にセットします。
	 *
	 * maxLabelWidth : -1 の場合は、ラベル文字列の最大長を求めて、この値を計算する。= (recodeMargin*2 + ラベル文字列の最大長)
	 * MAX_X : イメージの最大横幅(X)方向のサイズpx。chartPadding*2 + maxLabelWidth + maxTimeWidth
	 * MAX_Y : イメージの最大縦幅(Y)方向のサイズpx。chartPadding*2 + (chartHeight+recodeMargin*2)*(レコード数＋ヘッダー数)
	 *
	 * @og.rev 5.6.3.1 (2013/04/05) 短縮ラベルなど、<span>タグが付与される値から、spanタグを削除します。
	 * 
	 * @param	startNo	計算の開始列番号
	 * @param	lastNo	計算の終了列番号
	 */
	private void calcImageSize( final int startNo , final int lastNo ) {
		String oldKeyVal = "";	// 初期値。１回目にキーブレイクさせる。

		int clmSu = labelClmsNo.length;
		maxClmWidth = new int[clmSu];

		int rowCnt = (useLegend) ? 2 : 1;		// 凡例(useLegend)を使う(true)場合は、２行分、使わない場合は、ヘッダーの１行が初期値

		// ラベル領域の長さを各ラベル長を積み上げて計算する。
		if( maxLabelWidth < 0 ) {
			// FontMetrics を取得する為だけの BufferedImage
			BufferedImage img = new BufferedImage( 1, 1, BufferedImage.TYPE_INT_ARGB);
			Graphics2D g2 = img.createGraphics();
			FontMetrics fontM = g2.getFontMetrics();

			// 初期値の計算は、ヘッダーのラベルの幅を使用する。
			for( int col=0; col<clmSu; col++ ) {
				String lbl = getColumnLabel( labelClmsNo[col] );	// ヘッダーのラベル
				lbl = StringUtil.spanCut( lbl );					// 5.6.3.1 (2013/04/05) spanタグを削除
				maxClmWidth[col] = fontM.stringWidth( lbl );		// 最大値の初期値として、ヘッダーラベルの幅をセットしておく。
			}

			for( int row=startNo; row<lastNo; row++ ) {
				// キーブレイク判定。キーブレイクは、一番初めから来る。
				String keyVal = getValue( row,keyClmNo );
				if( !oldKeyVal.equals( keyVal ) ) {
					oldKeyVal = keyVal;
					rowCnt++;				// レコード数

					// ラベルは、キーブレイク時の値のみ採用する。
					for( int col=0; col<clmSu; col++ ) {
						String lbl = getValueLabel( row,labelClmsNo[col] );	// その行の値のRenderer値
						lbl = StringUtil.spanCut( lbl );					// 5.6.3.1 (2013/04/05) spanタグを削除
						int fontW = fontM.stringWidth( lbl );
						if( maxClmWidth[col] < fontW ) { maxClmWidth[col] = fontW; }
					}
				}
			}
			g2.dispose();

			// 最大ラベル幅は、各ラベルの最大値＋（マージン＊２）＊カラム数
			maxLabelWidth = recodeMargin * 2 * clmSu ;
			for( int col=0; col<clmSu; col++ ) {
				maxLabelWidth += maxClmWidth[col];
			}
		}
		else  {
			for( int row=startNo; row<lastNo; row++ ) {
				// キーブレイク判定。キーブレイクは、一番初めから来る。
				String keyVal = getValue( row,keyClmNo );
				if( !oldKeyVal.equals( keyVal ) ) {
					oldKeyVal = keyVal;
					rowCnt++;				// レコード数
				}
			}

			// 最大ラベル幅は、均等割り付け。端数は無視（どうせ、ラベル部は、maxLabelWidth で計算するので。）
			int clmWidth = ( maxLabelWidth - recodeMargin * 2 * clmSu ) / clmSu ;
			for( int col=0; col<clmSu; col++ ) {
				maxClmWidth[col] = clmWidth;
			}
		}

		MAX_X = chartPadding*2 + maxLabelWidth + maxTimeWidth ;
		MAX_Y = chartPadding*2 + (chartHeight+recodeMargin*2)*rowCnt ;
	}

	/**
	 * ヘッダー情報のイメージを作成します。
	 *
	 * 全体の枠もここで作成しておきます。
	 * イメージは、キーカラムのラベルと、時間軸になります。時間軸は縦方向にすべて線を引きます。
	 * 時間軸の間隔は、timeScale によって、切り替わります。
	 * 凡例を使う場合（useLegend=true）は、引数の ColorMap を利用して作成します。
	 *
	 * @og.rev 5.6.3.1 (2013/04/05) 短縮ラベルなど、<span>タグが付与される値から、spanタグを削除します。
	 * @og.rev 5.6.5.0 (2013/06/07) 年月日情報を表示します。なお、日単位の場合は、年月は省略します。
	 *
	 * @param	g2			描画するGraphics2Dオブジェクト
	 * @param	timeScale	時間(分)当たりのピクセル数
	 * @param	colMap		状況コードに対応したカラーマップ
	 */
	private void imageHeaderPaint( final Graphics2D g2 , final double timeScale , final ColorMap colMap ) {

		int posY1 = chartPadding  ;
		int posY2 = chartPadding+chartHeight+recodeMargin ;

		// 凡例を使う場合
		if( useLegend && colMap != null ) {
			// 凡例を並べる間隔を求める。
			FontMetrics fontM = g2.getFontMetrics();
			int maxSize = fontM.stringWidth( colMap.getMaxLengthLabel() ) ;		// 文字列の最大長ラベルの幅
			int imgW  = chartHeight ;				// 凡例■の幅(高さと同じにしているので真四角)
			int mgnW  = recodeMargin ;				// 凡例■から文字までの間
			int spanW = maxSize + recodeMargin ;	// 凡例■から凡例■までの間隔。文字最大長＋α

			int legX  = chartPadding ;
			for( Object[] obj : colMap.values() ) {
				String lbl = (String)obj[0];
				Color  col = (Color)obj[1];

				g2.setColor( col );												// 凡例■の色
				g2.fillRect( legX , posY1+recodeMargin , imgW , chartHeight );	// (実際の状態)左端x,上端y,幅w,高さh

				legX += imgW + mgnW ;
				g2.setColor( LABEL_COLOR );
				g2.drawString( lbl , legX , posY2 );		// 文字列,ベースラインのx座標,y座標

				legX += spanW ;
			}
			posY1 += chartHeight+recodeMargin*2 ;	// １レコード分の高さを加算しておく。
			posY2 += chartHeight+recodeMargin*2 ;	// １レコード分の高さを加算しておく。
		}

		// まずは、全体の枠線の描画
		g2.setColor( LABEL_COLOR );
		g2.drawRect( chartPadding, posY1, MAX_X-chartPadding*2, MAX_Y-posY1-chartPadding );				// 左端,上端,幅,高さ

		// ヘッダーのラベル分だけ、繰返し描画する。
		int clmSu = labelClmsNo.length;
		int posX  = chartPadding ;		// ラベル文字列の書き出し位置の初期値
		for( int col=0; col<clmSu; col++ ) {
			String lbl = getColumnLabel( labelClmsNo[col] );			// ヘッダーのラベル
			lbl = StringUtil.spanCut( lbl );							// 5.6.3.1 (2013/04/05) spanタグを削除
			g2.drawString( lbl , posX + recodeMargin, posY2 );			// 文字列,ベースラインのx座標,y座標
			posX += recodeMargin*2 + maxClmWidth[col] ;					// 次の書き出し位置は、文字最大長＋マージン*2
			g2.drawLine( posX, posY1, posX, MAX_Y-chartPadding );		// 始点(x 座標,y 座標),終点(x 座標,y 座標)
		}

		int step = TimeScaleStep.getStep( timeScale );			// 時間スケールに対応したSTEP数

		// 日付ヘッダー部の描画のためのカレンダー
		Calendar cal = Calendar.getInstance();
		cal.setTimeInMillis( startDate * 60000L );

		int week = cal.get( Calendar.DAY_OF_WEEK );	// 開始曜日

		String fmt = ( step < 1440 ) ? "yyyy/MM/dd(EE)" : "dd" ;		// 上段フォーマットは、時間ベースの場合は、"yyyy/MM/dd(EE)" 、日ベースの場合は、"dd"
		DateFormat format1 = new SimpleDateFormat( fmt,Locale.JAPAN );

		// グラフ領域の日付ヘッダー部の描画
		g2.setStroke( DSAH_STROK );				// 日付部は、破線
		posX = chartPadding+maxLabelWidth ;		// グラフ領域は、chartPadding+maxLabelWidth から。
		for( int tm = 0; tm < timeSpan; tm+=step ) {

			int offset = chartHeight / 2 + recodeMargin;	// ヘッダーの表示基準のオフセット（チャート幅の半分）

			// 上段：ヘッダー出力
			if( ( tm % 1440 ) == 0 ) {
				Date dt = new Date( (startDate + tm) * 60000L );
				g2.drawString( format1.format( dt ) , posX + recodeMargin , posY2-offset );			// 文字列,ベースラインのx座標,y座標
				offset = 0;		// ヘッダーを表示する場合のみ上まで線を引く。
			}

			g2.drawString( getTime2Str(startTime+tm,step,week) , posX + recodeMargin , posY2 );		// 文字列,ベースラインのx座標,y座標
			g2.drawLine( posX, posY1+offset, posX, MAX_Y-chartPadding );							// 始点(x 座標,y 座標),終点(x 座標,y 座標)

//			g2.drawString( getTime2Str(startTime+tm,step) , posX + recodeMargin , posY2 );	// 文字列,ベースラインのx座標,y座標
//			g2.drawLine( posX, posY1-10, posX, MAX_Y-chartPadding-10 );						// 始点(x 座標,y 座標),終点(x 座標,y 座標)
			posX += (int)(step*timeScale);
		}
	}

	// imageHeaderPaintメソッドで使用する 破線の定義
	private static final Stroke DSAH_STROK = new BasicStroke(
				1.0f						, // BasicStroke の幅
				BasicStroke.CAP_BUTT		, // 両端の装飾
				BasicStroke.JOIN_MITER		, // 輪郭線セグメントの接合部の装飾
				1.0f						, // 接合トリミングの制限値
				new float[] {2.0f, 1.0f}	, // 破線パターンを表す配列
				0.0f						  // 破線パターン開始位置のオフセット
	);

	/**
	 * 時間スケールに対応したSTEP数を管理するための内部クラス
	 *
	 * 時間ヘッダーを表示する場合、ある程度意味のある間隔でラベル表示したいと思います。
	 * 全体の描画領域の長さと、時間当たりのスケールファクター（ピクセル数）から、
	 * ラベルの描画間隔を求めます。
	 * 意味のある間隔は、STEPS で定義し、10分,30分,60分,1/4日,1/2日,1日 まで定義しています。
	 * 一番大きな単位以上の場合は、その最大単位の整数倍を返します。
	 *
	 * 一時間当たりの表示幅を、MIN_PX としていますので、この値以下の間隔では描画されません。
	 * 初期値は、600px を 24時間表示できる 600px/24h = 25px にしています。
	 */
	private static final class TimeScaleStep {
													// 分   分   時   1/4   1/2  １日
		private static final int[] STEPS = new int[] { 10 , 30 , 60 , 360 , 720 , 1440 };
		private static final int MIN_PX = 25;		// スケールに対する最小値

		/**
		 * 時間を意味のある範囲の整数として返します。
		 *
		 * 全体の描画領域の長さと、時間当たりのスケールファクター（ピクセル数）から、
		 * 10分,30分,60分,1/4日,1/2日,1日 までの整数値で返します。
		 * 一番大きな単位以上の場合は、その最大単位の整数倍を返します。
		 *
		 * @param	timeScale	時間(分)当たりのピクセル数
		 * @return	時間スケールに対応した意味のある範囲の整数
		 */
		public static final int getStep( final double timeScale ) {
			int tmStep = (int)Math.ceil(MIN_PX/(timeScale));	// 整数切り上げ

			for( int i=0; i<STEPS.length; i++ ) {
				if( tmStep <= STEPS[i] ) { return STEPS[i]; }	// 配列の数字に切り上げ
			}

			// 未設定の場合は、最上位の値の整数倍に切り上げ
			return (int)Math.ceil( tmStep / STEPS[STEPS.length-1] ) * STEPS[STEPS.length-1];
		}
	}

	/**
	 * 状況コード、ラベル、色を管理するための内部クラス
	 *
	 * 状況に応じたコード、ラベル、色を管理します。
	 * これは、getColor(状況コード,ラベル) または、getColor(状況コード,ラベル,色文字列) で
	 * 要求された情報を内部で管理し、同じコードの場合に同じ色を返します。
	 * また、凡例作成用に、最も文字数が長いラベルを管理します。
	 * 色文字列を指定した場合でも、最初に要求された状況コードに対応する色を返します。
	 * これは、同一状況コードで色違いを作成することができないことを意味します。
	 * 色文字列を指定しない場合は、内部の色配列から、順番に色を割り当てます。
	 * 色を割り当てる順番は、状況コードの発生順です。よって、検索条件によって、
	 * 状況コードの現れる順番が異なると、色も毎回異なることになります。
	 *
	 * 自動割り当ての色は、BLUE,CYAN,GRAY,GREEN,LIGHT_GRAY,MAGENTA,DARK_GRAY,ORANGE,PINK,RED,YELLOW 
	 * となっており、それを超えると、RGBをランダムに発生させて色を作成します。
	 * よって、どのような色になるかは全くわかりません。
	 */
	private static final class ColorMap {
		private static final Color[] CLR_ARY = new Color[] {
				Color.BLUE      ,Color.CYAN   ,Color.GRAY ,Color.GREEN ,Color.LIGHT_GRAY ,Color.MAGENTA ,
				Color.DARK_GRAY ,Color.ORANGE ,Color.PINK ,Color.RED   ,Color.YELLOW
		};
		private int lastCnt = 0;
		private final Map<String,Object[]> colMap = new TreeMap<String,Object[]>();
		private String maxLabel = "" ;		// 最大長のラベル
		private int    maxlen   = -1 ;		// 最大長のラベルのlength()

		/**
		 * 状況コードに対応した色オブジェクトを返します。
		 *
		 * 状況コードが初めて指定された場合は、順番に内部の色を割り当てます。
		 * また、その時のラベルも管理します。ラベルと色のセットは、凡例作成時に利用されます。
		 *
		 * 自動割り当ての色は、BLUE,CYAN,GRAY,GREEN,LIGHT_GRAY,MAGENTA,DARK_GRAY,ORANGE,PINK,RED,YELLOW 
		 * となっており、それを超えると、RGBをランダムに発生させて色を作成します。
		 * よって、どのような色になるかは全くわかりません。
		 *
		 * @param	fgj	状況コード
		 * @param	lbl	状況コードのラベル
		 * @return	状況コードに対応した色オブジェクト
		 */
		public Color getColor( final String fgj,final String lbl ) {
			return getColor( fgj,lbl,null );
		}

		/**
		 * 状況コードに対応した色オブジェクトを返します。
		 *
		 * 状況コードが初めて指定された場合は、引数の色文字列の色を割り当てます。
		 * また、その時のラベルも管理します。ラベルと色のセットは、凡例作成時に利用されます。
		 *
		 * 色文字列を指定した場合でも、最初に要求された状況コードに対応する色を返します。
		 * これは、同一状況コードで色違いを作成することができないことを意味します。
		 * 色文字列 が null の場合は、自動割り当てのメソッドと同じです。
		 * よって、色文字列の指定と、自動割り当てが同時に発生すると、異なる状況コードで
		 * 同じ色が指定される可能性がありますので、混在して使用しないでください。
		 *
		 * @param	fgj		状況コード
		 * @param	lbl		状況コードのラベル
		 * @param	colStr	状況コードに対応した色文字列(nullの場合は、自動割り当て)
		 * @return	状況コードに対応した色オブジェクト
		 */
		public Color getColor( final String fgj,final String lbl,final String colStr ) {
			if( fgj == null ) { return LABEL_COLOR; }
			if( lbl != null ) {
				int len = lbl.length();
				if( len > maxlen ) { maxLabel = lbl; maxlen = len; }
			}

			Object[] obj = colMap.get( fgj );
			if( obj == null ) {
				obj = new Object[2];
				obj[0] = lbl;
				obj[1] = (colStr != null) ? StringUtil.getColorInstance( colStr ) : uniqColor();

				colMap.put( fgj,obj );
			}

			return (Color)obj[1] ;
		}

		/**
		 * 内部のシーケンスに対応した、ユニークな色オブジェクトを返します。
		 *
		 * 内部カウンターを＋１ しながら、内部の色オブジェクトを返します。
		 *
		 * 自動割り当ての色は、BLUE,CYAN,GRAY,GREEN,LIGHT_GRAY,MAGENTA,DARK_GRAY,ORANGE,PINK,RED,YELLOW 
		 * となっており、それを超えると、RGBをランダムに発生させて色を作成します。
		 * よって、どのような色になるかは全くわかりません。
		 *
		 * @return	ユニークな色オブジェクト
		 */
		public Color uniqColor() {
			Color col = null;
			if( lastCnt < CLR_ARY.length ) {
				col = CLR_ARY[lastCnt++];
			}
			else {
				int R=(int)(Math.random()*256);
				int G=(int)(Math.random()*256);
				int B=(int)(Math.random()*256);
				col = new Color(R,G,B);
			}

			return col;
		}

		/**
		 * 内部で管理している、ラベル(String)と色オブジェクト(Color)の コレクションを返します。
		 *
		 * 内部で管理しているコレクションです。
		 * このコレクションは、状況コードでソートされています。
		 * コレクションの中身は、オブジェクト配列となっており、[0]は、String型のラベル、[1]は、
		 * Color型の色です。
		 *
		 * @return	ラベル(String)と色オブジェクト(Color)の コレクション
		 */
		public Collection<Object[]> values() {
			return colMap.values();
		}

		/**
		 * 登録されたラベル文字列で、最も文字数が長いラベルを返します。
		 *
		 * 凡例で、ラベルの最大長を求めるのに利用できます。
		 * ただし、簡易的に、length() 計算しているだけなので、英語、日本語混在や、
		 * プロポーショナルフォント使用時の厳密な最大長の文字列ではありません。
		 *
		 * @return	最も文字数が長いラベル
		 */
		public String getMaxLengthLabel() { return maxLabel; }
	}

	/**
	 * 時間文字列を数字に変換します。
	 *
	 * "0800" は、480に、"2100" は、1260 に変換します。
	 * つまり、分単位の値に変換します。秒の単位は、切り捨てます。
	 * ここでは、時間文字列が、08:00 の様に、":" を含む場合は、削除し、
	 * ４ケタ以上の場合は、前から４ケタ分を使います。
	 * ４ケタ以下（１，２，３ケタ）でも、先頭に 0 埋めで、時間文字列にします。
	 * ０ケタ、および、null の場合は、defTm 初期値 を返します。
	 *
	 * @param	val  	時間文字列の値(0800 など)
	 * @param	defTm	引数の時間文字列が null の場合の初期値
	 *
	 * @return	時間文字列を数字に変換した結果( 80 など)
	 */
	private int getStr2Time( final String val , final int defTm ) {
		if( val == null || val.isEmpty() ) { return defTm; }

		// 桁数によって、処理の仕方が変わるため。
		int rtnInt = 0;
		switch( val.length() ) {
			case	1:
			case	2:	rtnInt = Integer.parseInt( val ); break;
			case	3:	rtnInt = Integer.parseInt( val.substring( 0,1 ) )*60 + Integer.parseInt( val.substring( 1,3 ) ); break;
			default	 :	rtnInt = Integer.parseInt( val.substring( 0,2 ) )*60 + Integer.parseInt( val.substring( 2,4 ) ); break;
		}

		return rtnInt ;
	}

	/**
	 * 日時文字列を数字に変換します。
	 *
	 * 日時文字列は、yyyyMMdd または、yyyyMMddhhmmss 形式とします。
	 * これを、エポックタイムからの経過時間の 分単位の値を求めます。
	 * 具体的には、Calendar オブジェクトの getTimeInMillis() の結果を、
	 * 60000 で割り算した値を作成します。
	 * よって、Calendar オブジェクト作成時も、秒の単位は切り捨てます。
	 * 引数が null の場合は、現在時刻より、作成します。
	 *
	 * @param	val  	日時文字列の値(yyyyMMdd または、yyyyMMddhhmmss 形式 など)
	 *
	 * @return	日時文字列を分換算した数字
	 */
	private long getStr2Date( final String val ) {
		Calendar cal = Calendar.getInstance();
		str2DateTime = 0;
		if( val == null ) {
			cal.set( Calendar.HOUR_OF_DAY, 0 );		// 5.3.5.0 (2011/05/01) 時間の解決規則が適用されるため、｢時｣だけは、setメソッドで 0 にセットする。
			cal.clear( Calendar.MINUTE );
			cal.clear( Calendar.SECOND );
			cal.clear( Calendar.MILLISECOND );
		}
		else if( val.length() == 8 ) {
			cal.clear();
			cal.set( Integer.parseInt( val.substring( 0,4 ) ) ,			// 年
					 Integer.parseInt( val.substring( 4,6 ) ) - 1,		// 月(0から始まる)
					 Integer.parseInt( val.substring( 6,8 ) )  			// 日
			);
		}
		else {
			cal.clear();
			cal.set( Integer.parseInt( val.substring( 0,4 ) ) ,			// 年
					 Integer.parseInt( val.substring( 4,6 ) ) - 1,		// 月(0から始まる)
					 Integer.parseInt( val.substring( 6,8 ) ) ,			// 日
					 Integer.parseInt( val.substring( 8,10 ) ) ,		// 時
					 Integer.parseInt( val.substring( 10,12 ) ) 		// 分
			);
			str2DateTime = Integer.parseInt( val.substring( 8,10 ) ) * 60 + Integer.parseInt( val.substring( 10,12 ) ) ;
		}
		return (cal.getTimeInMillis() / 60000L);		// 分単位に変換する。
	}

	// 5.6.5.0 (2013/06/07) 曜日データを配列で持っておきます。
	private static final String[] DAY_OF_WEEK_ja = new String[] { "土","日","月","火","水","木","金","土" };	// [0]="土" は、1～7 の余計算で、 7=0 になる為。

	/**
	 * 数字(分)を時間文字列に変換します。
	 *
	 * 480 は、"08" に、1260 は、"21" に変換します。
	 * 引数の時間は、分を表す整数です。２４時間表記であれば、0 ～ 1440 の範囲で収まりますが、
	 * 期間が長い場合は、その値を超えます。また、２４時間を超える場合は、0 に戻ります。
	 * 文字列にする場合の最小単位は、(時)なので、60(分)で割り算して、余は、切り捨てます。
	 * step は、60(分)単位の表示時に飛ばす数です。step=1 なら、60(分)単位、step=2 なら、120(分)
	 * 単位となります。stepが、24 以下の場合は、そのまま、24時間表記で構いませんが、
	 * それを超えると時間ではなく、日数表記に切り替わります。
	 *
	 * @og.rev 5.6.5.0 (2013/06/07) 月単位の場合は、曜日を表示します。
	 *
	 * @param	timeVal	引数の時間整数(分)
	 * @param	step	60分単位のステップ数( 10,30,60,720,1440 単位となるように調整)
	 * @param	week	カレンダクラスの曜日フィールド(DAY_OF_WEEK)
	 *
	 * @return	数字を時間文字列に変換した結果( "08" など)
	 */
//	private String getTime2Str( final int timeVal, final int step ) {
	private String getTime2Str( final int timeVal, final int step, final int week ) {

		int dtm = (timeVal / 60 / 24 );			// 日
		int htm = ((timeVal / 60) % 24 );		// 時(２４時間制)
		int mtm = (timeVal % 60 );				// 分(６０分制)

		StringBuilder rtn = new StringBuilder();

		if( step >= 1440 ) {
//			rtn.append( dtm ).append( "d" );							// 5.6.5.0 (2013/06/07) 相対日付廃止
			rtn.append( DAY_OF_WEEK_ja[ ( dtm + week ) % 7 ] );			// 曜日を表示
		}
		else {
			if( htm < 10 ) { rtn.append( "0" ); }
			rtn.append( htm );

			if( step < 60 ) { 
				rtn.append( ":" );
				if( mtm < 10 ) { rtn.append( "0" ); }
				rtn.append( mtm );
			}
		}

		return rtn.toString();
	}

	// 引数に、 を追加する。

	/**
	 * 表示項目の編集(並び替え)が可能かどうかを返します
	 *
	 * @return	表示項目の編集(並び替え)が可能かどうか(false:不可能)
	 */
	@Override
	public boolean isEditable() {
		return false;
	}
}
