001/*
002 * Copyright (c) 2009 The openGion Project.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *     http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
013 * either express or implied. See the License for the specific language
014 * governing permissions and limitations under the License.
015 */
016package org.opengion.plugin.view;
017
018import java.io.File;
019import java.io.IOException;
020import java.util.Calendar;
021import java.util.Map;
022import java.util.TreeMap;
023import java.util.Collection;
024import java.util.Locale;
025// import java.util.Date;                                                               // 7.0.5.1 (2019/09/27) Date → Calendar 変更
026import java.text.DateFormat;
027import java.text.SimpleDateFormat;
028import java.awt.Graphics2D;
029import java.awt.Color;
030import java.awt.FontMetrics;
031import java.awt.Stroke;
032import java.awt.BasicStroke;
033import java.awt.image.BufferedImage;
034import javax.imageio.ImageIO;
035
036import org.opengion.hayabusa.common.HybsSystemException;
037import org.opengion.fukurou.util.StringUtil;
038import org.opengion.fukurou.util.ColorMap;                              // 6.0.2.1 (2014/09/26)
039import org.opengion.fukurou.system.OgBuilder ;                  // 6.4.4.1 (2016/03/18)
040
041/**
042 * キー、日時、状況コードを持つ稼働状況の表示を行うクラスです。
043 *
044 * パラメータが必要な場合は、ViewTimeBarParamTag を使用してください。
045 *
046 * パラメータが設定されていない場合は、ViewForm_ImageTimeBar の初期値が使用されます。
047 * (パラメータを使用するには、viewタグのuseParam 属性をtrueに設定する必要があります。)
048 *
049 * SELECT文は、キー、日時、状況コードが、必須項目で、カラムの並び順は、完全に固定です。
050 * よって、カラム位置を指定する必要はありませんが、SELECT文を自由に設定することも
051 * 出来ませんので、ご注意ください。
052 * この固定化に伴い、WRITABLE 指定も使用できません。(そもそも書き込み不可です)
053 * それ以降のカラムについては、内部処理としては、使用していません。
054 * ただし、パラメータで、カラー色指定、ラベル表記部、イメージ重ね合わせ、
055 * ポップアップ表記、リンク表記に使えます。
056 *
057 * データの並び順(ORDER BY)も、キー、日時順にしてください。
058 * データは、キー単位に1レコード作成されます。(キーブレイク)その間、日時順に
059 * データを処理します。
060 *
061 * データの表示は、今のレコードの日時から、次のレコードの日時までを一つの状態と
062 * して表します。今のレコードを表示するには、次のレコードが必要になります。
063 * 画面表示は、表示開始日時(minStartTime) から 表示期間(timeSpan)分を表示します。
064 * 通常、開始時刻は、表示開始時刻より前より始まり、次のレコードで、終了時刻が決定
065 * されます。最後のデータは、期間満了まで続いていると仮定されます。
066 * データが存在しないのであれば、「存在しないデータ」を作成してください。
067 *
068 * ImageTimeBar では、キーでまとめた値について、各状況コードをカラー化し、積み上げ
069 * 帯グラフ形式でPNG画像化します。
070 * この画像を、読み込む HTML を出力することで、画面上に、積み上げ帯グラフを表示します。
071 * 状況コードに対応する色は、標準では自動作成ですが、外部から色文字列(colorClm)を与えることで
072 * 自由に指定する事も可能です。
073 *
074 * ポップアップ表記(tipsClm)、リンク表記(linkClm)は、この画像に対するエリア指定タグを出力する事で実現します。
075 * 画像ファイルは、全データに対して、1画像だけなので、サイズは大きくなりますが、1レコード
076 * 単位に画像を作成しないため、レスポンスは向上します。
077 * それぞれ、viewMarker , viewLink を利用することが可能です。特に、リンク表記(linkClm) については、
078 * linkタグの hrefTarget 属性を true に設定することで適用できます。
079 *
080 * 画像ファイルは、java.io.File.createTempFile( File ) で作成するため、JavaVM(=Tomcat)が
081 * 正常終了するときに、削除されます。異常終了時には残りますが、temp フォルダを定期的に
082 * 整理すれば、それほど大量のファイルが残ることはないと思われます。
083 *
084 * データは、イベント発生時に作成されると仮定しています。つまり、書き込まれた日時から、
085 * 状況コードに対応する状況が発生し、次の状況違いのレコードまで継続していると考えます。
086 * よって、データを途中で切り出す場合、切り出す範囲の前の状態が必要になります。
087 * 一番最初の状態は、"不明" として扱います。(空欄=白色)
088 *
089 * @og.rev 5.5.5.6 (2012/08/31) 新規追加
090 * @og.group 画面表示
091 *
092 * @version  4.0
093 * @author   Kazuhiko Hasegawa
094 * @since    JDK5.0,
095 */
096public class ViewForm_ImageTimeBar extends ViewForm_HTMLTable {
097        /** このプログラムのVERSION文字列を設定します。   {@value} */
098        private static final String VERSION = "8.5.4.2 (2024/01/12)" ;
099
100        /** ラベル記述時の色 */
101        private static final Color LABEL_COLOR  = Color.BLACK;
102        /** 5.6.1.1 (2013/02/08) 不明(空欄)時の色 */
103        private static final Color NULL_COLOR   = Color.WHITE;
104
105        private static final int        M_60            = 60;                           // 6.1.0.0 (2014/12/26) refactoring
106        private static final int        H_24            = 24;                           // 6.1.0.0 (2014/12/26) refactoring
107//      private static final int        W_7                     = 7;                            // 6.1.0.0 (2014/12/26) refactoring 7.0.5.1 (2019/09/27) 削除
108//      private static final int        DEC                     = 10;                           // 6.1.0.0 (2014/12/26) refactoring 7.2.9.4 (2020/11/20) 削除
109
110        /** 6.0.2.0 (2014/09/19) 1日が何分 なのか(=1440) */
111        private static final int        MINUTE_OF_DAY = 24 * 60 ;
112        /** 6.0.2.0 (2014/09/19) ミリ秒に換算した分。(=60000L) */
113        private static final long       MILLI_MINUTE  = 60 * 1000L;
114
115//      // 5.6.5.0 (2013/06/07) 曜日データを配列で持っておきます。
116//      // 6.4.1.1 (2016/01/16) DAY_OF_WEEK_ja → DAY_OF_WEEK_JA refactoring
117//      // 7.0.5.1 (2019/09/27) 削除
118//      private static final String[] DAY_OF_WEEK_JA = { "土","日","月","火","水","木","金","土" };     // [0]="土" は、1~7 の余計算で、7=0 になる為。
119
120        /** タイムテーブルの表示開始日時から求めた long 値(1=分単位) */
121        private long startDate          ;
122        /** タイムテーブルの表示期間。元は時間指定であるが、分単位で持つ。(1=分単位) */
123        private long timeSpan           ;
124
125        /** カラーの凡例を使用するかどうか[true/false] */
126        private boolean useLegend       ;
127        /** ラベル表記部の最大サイズをpxで指定。何もなければ、可変長サイズ */
128        private int maxLabelWidth       ;
129        /** タイム表記部の最大サイズをpxで指定。 */
130        private int maxTimeWidth        ;
131        /** 1レコードのチャートの間隔をpxで指定。実際の幅は、CHART_HEIGHT+MARGIN*2 */
132        private int chartHeight         ;
133        /** 6.4.6.1 (2016/06/03) チャートのヘッダー部の高さ(初期値は、チャートの間隔:chartHeight) */
134        private int headerHeight        ;
135        /** イメージ作成の 全体テーブルの隙間 */
136        private int chartPadding        ;
137        /** 各レコード、文字等の内部の間隔 */
138        private int recodeMargin        ;
139        /** 5.6.1.1 (2013/02/08) 行の最後の情報が、継続しているとして使うかどうか[true/false] */
140        private boolean useLastData     ;
141        /** 6.4.8.2 (2016/07/08) debug 属性を、パラメータにセットします[true/false]。 */
142        private boolean isDebug         ;
143
144        /** 画像ファイルを作成するテンポラリディレクトリ(相対パス) */
145        private String tempDir          ;
146        /** 作成した画像ファイルを取得するときに使用するURL(コンテキスト/相対パス) */
147        private String tempUrl          ;
148
149        /** 7.1.0.0 (2020/01/20) ヘッダー部分上段の日付フォーマット */
150        private String headerUpFmt      ;
151        /** 7.1.0.0 (2020/01/20) ヘッダー部分下段の日付フォーマット */
152        private String headerDwFmt      ;
153
154        // SELECT文は、キー、日時、状況コードが、必須項目
155        // 6.4.1.1 (2016/01/16) keyClmNo → KEY_CLMNO , dyClmNo → DY_CLMNO , fgjClmNo → FGJ_CLMNO refactoring
156        /** ヘッダー1:キーカラムNo */
157        private static final int        KEY_CLMNO       = 0;
158        /** ヘッダー2:日時カラムNo */
159        private static final int        DY_CLMNO        = 1;
160        /** ヘッダー3:状況コードカラムNo */
161        private static final int        FGJ_CLMNO       = 2;
162
163        /** 初期値は、キーカラム */
164        private int[] labelClmsNo       ;
165        /** labelClms 単位の最大文字列長 */
166        private int[] maxClmWidth       ;
167        /** カラーコード直接指定する場合に色文字列を指定するカラムNo */
168        private int   colClmNo          = -1;
169        /** マウスオーバー時のTips表示を行うカラムNo */
170        private int   tipsClmNo         = -1;
171        /** クリッカブルリンクを設定するカラムNo */
172        private int   linkClmNo         = -1;
173
174        /** getStr2Date(String)処理をおこなった時の、引数の時刻情報(分単位)をセットするテンポラリ変数。 */
175        private int str2DateTime        ;
176        /** startDate の時刻情報。上記処理で、startDate を getStr2Date(String) 処理したときの値を持っておくための変数。 */
177        private int startTime           ;
178        /** 6.0.2.0 (2014/09/19) データがない最後の処理で使用する現在時刻 */
179        private long nowTime            ;
180
181        // 6.3.9.0 (2015/11/06) Variables should start with a lowercase character(PMD)
182        /** イメージの最大横幅(X)方向のサイズpx。chartPadding*2 + maxLabelWidth + maxTimeWidth ; */
183        private int maxX        ;
184        /** イメージの最大縦幅(Y)方向のサイズpx。chartPadding*2 + (headerHeight+recodeMargin*2)*2 + (chartHeight+recodeMargin*2)*(rowCnt-2); */
185        private int maxY        ;
186
187        /** 6.9.9.1 (2018/08/27) 必要なのかどうか不明。「ラベル領域の長さ求めで、改行が含まれる場合の計算対応」 が必要なのかを判断するフラグ。処理速度向上用 */
188        private boolean useCR   ;
189
190        /** imageHeaderPaintメソッドで使用する 破線の定義 */
191        private static final Stroke DSAH_STROK = new BasicStroke(
192                                1.0f                                            , // BasicStroke の幅
193                                BasicStroke.CAP_BUTT            , // 両端の装飾
194                                BasicStroke.JOIN_MITER          , // 輪郭線セグメントの接合部の装飾
195                                1.0f                                            , // 接合トリミングの制限値
196                                new float[] {2.0f, 1.0f}        , // 破線パターンを表す配列
197                                0.0f                                              // 破線パターン開始位置のオフセット
198        );
199
200        /**
201         * デフォルトコンストラクター
202         *
203         * @og.rev 8.5.3.2 (2023/10/13) JDK21対応。警告: デフォルトのコンストラクタの使用で、コメントが指定されていません
204         */
205        public ViewForm_ImageTimeBar() { super(); }             // これも、自動的に呼ばれるが、空のメソッドを作成すると警告されるので、明示的にしておきます。
206
207        /**
208         * DBTableModel から HTML文字列を作成して返します。
209         * startNo(表示開始位置)から、pageSize(表示件数)までのView文字列を作成します。
210         * 表示残りデータが pageSize 以下の場合は、残りのデータをすべて出力します。
211         *
212         * @og.rev 5.6.1.0 (2013/02/01) tips や link は、ひとつ前のデータで作成する必要がる為、最初の1件目は、処理しないように修正
213         * @og.rev 5.6.1.1 (2013/02/08) 初期値の色を、直接の値ではなく、static final で定義された色を使用する。色自体は変えていません
214         * @og.rev 5.6.1.1 (2013/02/08) useLastData 追加
215         * @og.rev 5.6.3.0 (2013/04/01) tipsのシングルクォーテーション のエスケープ
216         * @og.rev 5.6.3.1 (2013/04/05) 短縮ラベルなど、<span>タグが付与される値から、spanタグを削除します。
217         * @og.rev 6.0.2.0 (2014/09/19) データが存在しない最後の処理で、現在時刻まで、継続しているとします。
218         * @og.rev 6.4.2.0 (2016/01/29) alt属性にtitle属性を追記。
219         * @og.rev 6.4.4.1 (2016/03/18) StringBuilderの代わりに、OgBuilderを使用する。
220         * @og.rev 6.4.4.2 (2016/04/01) 引数を、Supplierクラスに変更して、結果を複数指定できるようにします。
221         * @og.rev 6.4.6.1 (2016/06/03) ヘッダー部の高さを指定できるようにする。
222         * @og.rev 6.4.7.1 (2016/06/17) useLastData属性は、最後の処理のみに適用する。
223         * @og.rev 6.4.8.2 (2016/07/08) 未来時刻の場合に、現在時刻まで巻き戻って塗りつぶしているので、未来時刻の判定を追加。
224         * @og.rev 6.7.5.0 (2017/03/10) COLOR_CLM の値を、getValueLabel で取得するように変更。
225         * @og.rev 6.9.7.0 (2018/05/14) imgタグに付与する、高さ、幅を、AbstractViewForm から、取得します。
226         * @og.rev 6.9.9.1 (2018/08/27) ラベル領域の長さ求めで、改行が含まれる場合の計算対応。
227         * @og.rev 7.0.1.0 (2018/10/15) XHTML → HTML5 対応(空要素の、"/>" 止めを、">" に変更します)。
228         * @og.rev 7.0.5.1 (2019/09/27) getTime2Str 修正に伴う変更。
229         * @og.rev 8.5.4.0 (2023/12/01) HTML5廃止対応
230         *
231         * @param       startNo         表示開始位置
232         * @param       pageSize        表示件数
233         *
234         * @return      DBTableModelから作成された HTML文字列
235         * @og.rtnNotNull
236         */
237        @Override
238        public String create( final int startNo, final int pageSize ) {
239                if( getRowCount() == 0 ) { return ""; } // 暫定処置
240
241                // パラメータの情報を初期化&取得します。
242                paramInit();
243
244                final int lastNo = getLastNo( startNo, pageSize );
245
246                // イメージの 最大X、Yサイズを求め、結果を、maxX,maxY 変数に設定する。
247                calcImageSize( startNo,lastNo );
248
249                final BufferedImage img = new BufferedImage( maxX, maxY, BufferedImage.TYPE_INT_ARGB);
250                final Graphics2D g2 = img.createGraphics();
251
252                // chartPadding を考慮した領域のクリップ(クリップ外にDrowされても無視されます。)
253                g2.setClip( chartPadding , chartPadding , maxX-chartPadding*2+1 , maxY-chartPadding*2+1 );      // (x 座標,y 座標,幅,高さ) +1は罫線の分
254
255                final StringBuilder out = new StringBuilder( BUFFER_LARGE );
256
257                String oldKeyVal = "";                          // 初期値。1回目にキーブレイクさせる。
258                long   oldTime   = 0L;                          // 日付項目の一つ前の値
259
260                Color  oldColor  = NULL_COLOR;          // 5.6.1.1 (2013/02/08) 不明(空欄)時の色(初期値)
261
262                // 6.0.2.1 (2014/09/26) fukurou.util.ColorMap とクラス名がかぶるので、こちらを変更します。
263                final FlgColorMap colMap = new FlgColorMap();   // 状況コード、ラベル、カラーを管理するクラス
264
265                // 6.4.6.1 (2016/06/03) ヘッダー部の高さを指定できるようにする。
266                // ヘッダー文の考慮。rowCnt は、使わず、imgY は、加算していきます。
267
268                int imgY = chartPadding + (headerHeight + recodeMargin * 2) * (useLegend ? 2 : 1);              // 6.4.6.1 (2016/06/03)
269
270                final int rowH = chartHeight+recodeMargin*2;    // 罫線出力時の高さ(drawRectで使用)
271
272                // 8.5.4.2 (2024/01/12) PMD 7.0.0 UnnecessaryCast 対応
273//              final double timeScale = (double)maxTimeWidth/(double)timeSpan;
274//              final double timeScale = maxTimeWidth/timeSpan;
275                final double timeScale = maxTimeWidth/(double)timeSpan;                         // 8.5.5.0 (2024/02/02) UnnecessaryCast 対応 ミスのため復活(どちらかはキャストしておく)
276
277                final boolean useTipsLink = tipsClmNo >= 0 || linkClmNo >= 0 ;          // tipsClm か linkClm かどちらかを使用する場合、true
278
279                for( int row=startNo; row<lastNo; row++ ) {
280                        // キーブレイクの判定
281                        final String keyVal = getValue( row,KEY_CLMNO );
282                        final String dyVal  = getValue( row,DY_CLMNO );
283
284                        // キーブレイク判定。キーブレイクは、一番初めから来る。
285                        if( !oldKeyVal.equals( keyVal ) ) {
286                                // キーブレイクで最初に行うのは、前のレコードの未出力分の処理。1行目は、処理不要
287                                if( row > startNo ) {
288                                        // 6.0.2.0 (2014/09/19) データが存在しない最後の処理で、現在時刻まで、継続しているとします。
289                                        long lastTime = nowTime ;               // 現在時刻(分単位に変換する)
290                                        // 5.9.10.5 (2016/07/08) 現在時刻がtimeSpanより大きいor最終値より小さい場合は、lastTimeをtimeSpanにする。
291                                        if( lastTime > timeSpan || lastTime < oldTime ) { lastTime = timeSpan; }        // 5.9.10.5 (2016/07/08)
292
293                                        if( !useLastData ) { oldColor = NULL_COLOR; }                   // 6.4.7.1 (2016/06/17)
294                                        while( oldTime < timeSpan ) {
295                                                // 6.3.9.0 (2015/11/06) Found 'DD'-anomaly for variable(PMD)
296                                                final int imgX = chartPadding + maxLabelWidth + (int)(oldTime*timeScale);               // イメージ出力時の X軸の左端 px数
297                                                final int imgW = (int)((lastTime-oldTime)*timeScale) ;                                                  // イメージ出力時の 幅(fillRectで使用)
298
299                                                if( isDebug ) {
300//                                                      final int step = TimeScaleStep.getStep( timeScale );
301//                                                      final String stTM = getTime2Str(startTime+(int)oldTime ,step,0,0) ;                     // 7.0.5.1 (2019/09/27)
302//                                                      final String edTM = getTime2Str(startTime+(int)lastTime,step,0,0) ;                     // 7.0.5.1 (2019/09/27)
303                                                        final String stTM = getTime2Str(startTime+(int)oldTime ) ;                                      // 7.0.5.1 (2019/09/27)
304                                                        final String edTM = getTime2Str(startTime+(int)lastTime) ;                                      // 7.0.5.1 (2019/09/27)
305                                                        System.out.println( "② ROW[" + row + "]=" + stTM + " - " + edTM + " imgX=" + imgX + " imgW=" + imgW );
306                                                }
307                                                imageMeker(g2, oldColor, imgX, imgY, imgW, useTipsLink, row, out);      // 6.0.2.0 (2014/09/19)
308
309                                                oldTime  = lastTime ;
310                                                lastTime = timeSpan ;
311                                                oldColor = NULL_COLOR;          // 5.6.1.1 (2013/02/08) キーブレイクによる初期化=不明(空欄)時の色
312                                        }
313                                        imgY += rowH ;                                  // 初期値にヘッダー高さを加算しているため、2回目以降に加算していく。 6.4.6.1 (2016/06/03)
314                                }
315
316                                // 次に、新しい行の出力(ラベルは、ブレイク時に出力しておきます。)
317                                oldKeyVal = keyVal;                     // null は来ないはず
318                                // 6.4.6.1 (2016/06/03) ヘッダー部の高さを指定できるようにする。
319                                // ヘッダー文の考慮。rowCnt は、使わず、imgY は、加算していきます。
320                                // レコードのラベル分だけ、繰返し描画する。
321                                final int clmSu = labelClmsNo.length;
322                                int posX = chartPadding ;                                                               // ラベル文字列の書き出し位置の初期値
323                                final int posY2 = imgY+chartHeight+recodeMargin ;               // ラベルのY軸基準。
324                                g2.setColor( LABEL_COLOR );
325                                for( int col=0; col<clmSu; col++ ) {
326                                        final String lbl = StringUtil.spanCut( getValueLabel( row,labelClmsNo[col] ) );         // 5.6.3.1 (2013/04/05) spanタグを削除
327                                        // 6.9.9.1 (2018/08/27) ラベル領域の長さ求めで、改行が含まれる場合の計算対応。
328//                                      g2.drawString( lbl , posX + recodeMargin, posY2 );                                                                      // 文字列,ベースラインのx座標,y座標
329                                        if( useCR ) {
330                                                final String[] xlbls = lbl.split( "\\n|\\\\n" );                                                                        // 本物の改行と、"\n" 文字列
331                                                for( int i=0; i<xlbls.length; i++ ) {
332                                                        final int posY3 = posY2 - (xlbls.length-1-i)*rowH/xlbls.length;                                 // 改行ラベルを、均等に枠内に分割します。
333                                                        g2.drawString( xlbls[i] , posX + recodeMargin, posY3 );                                                 // 文字列,ベースラインのx座標,y座標
334                                                }
335                                        }
336                                        else {
337                                                g2.drawString( lbl , posX + recodeMargin, posY2 );                                                                      // 文字列,ベースラインのx座標,y座標
338                                        }
339
340                                        posX += recodeMargin*2 + maxClmWidth[col] ;                             // 次の書き出し位置は、文字最大長+マージン
341                                }
342
343                                // レコードの枠線
344                                g2.drawRect( chartPadding , imgY , maxX-chartPadding*2 , rowH );                // (レコード枠)左端x,上端y,幅w,高さh
345
346                                oldTime = 0L;                           // キーブレイクによる初期化
347                                oldColor= NULL_COLOR;           // 5.6.1.1 (2013/02/08) キーブレイクによる初期化=不明(空欄)時の色
348                        }
349
350                        long newTime = getStr2Date( dyVal ) - startDate;                // 次の時刻
351                        if( newTime < oldTime ) { newTime = oldTime; }                  // 前の時刻より小さい場合は、前の時刻まで、進めておく。
352
353                        // 6.3.9.0 (2015/11/06) Found 'DD'-anomaly for variable(PMD)
354                        final int imgX = chartPadding + maxLabelWidth + (int)(oldTime*timeScale);               // old時刻から書き始める(左端x)
355                        final int imgW = (int)((newTime-oldTime)*timeScale) ;                                                   // 差分幅だけ描画する。
356
357                        if( isDebug ) {
358//                              final int step = TimeScaleStep.getStep( timeScale );
359//                              final String stTM = getTime2Str(startTime+(int)oldTime,step,0,0) ;                      // 7.0.5.1 (2019/09/27)
360//                              final String edTM = getTime2Str(startTime+(int)newTime,step,0,0) ;                      // 7.0.5.1 (2019/09/27)
361                                final String stTM = getTime2Str(startTime+(int)oldTime) ;                                       // 7.0.5.1 (2019/09/27)
362                                final String edTM = getTime2Str(startTime+(int)newTime) ;                                       // 7.0.5.1 (2019/09/27)
363                                System.out.println( "① ROW[" + row + "]=" + stTM + " - " + edTM + " imgX=" + imgX + " imgW=" + imgW );
364                        }
365                        imageMeker(g2, oldColor, imgX, imgY, imgW, useTipsLink, row, out);      // 6.0.2.0 (2014/09/19)
366
367                        oldTime = newTime ;
368
369                        final String fgjVal = getValue( row,FGJ_CLMNO );                        // 状況コードのキー
370                        final String fgjLbl = getValueLabel( row,FGJ_CLMNO );           // 状況コードのラベル
371                        if( colClmNo >= 0 ) {
372                                // 6.7.5.0 (2017/03/10) COLOR_CLM の値を、getValueLabel で取得するように変更。
373                                oldColor = colMap.getColor( fgjVal,fgjLbl,getValueLabel( row,colClmNo ) );      // 色文字列を指定する場合
374                        }
375                        else {
376                                oldColor = colMap.getColor( fgjVal,fgjLbl );                                                            // 色文字列を指定しない=自動設定
377                        }
378                }
379
380                // レコードのいちばん最後の出力。一応、ジャストの場合(oldTime == maxEdTime)は、処理しない
381                // 5.6.1.1 (2013/02/08) レコードのいちばん最後の出力は、NULL色を使うように変更
382                long lastTime = nowTime ;               // 現在時刻(分単位に変換する)
383                // 6.4.8.2 (2016/07/08) 未来日付の場合に、現在時刻まで巻き戻って塗りつぶしている。
384                if( lastTime > timeSpan || lastTime < oldTime ) { lastTime = timeSpan; }                // 現在時刻が、timeSpanより大きい場合 OR 未来時刻の場合は、同じにする。
385
386                if( !useLastData ) { oldColor = NULL_COLOR; }                   // 6.4.7.1 (2016/06/17)
387                while( oldTime < timeSpan ) {
388                        // 6.3.9.0 (2015/11/06) Found 'DD'-anomaly for variable(PMD)
389                        final int imgX = chartPadding + maxLabelWidth + (int)(oldTime*timeScale);               // イメージ出力時の X軸の左端 px数
390                        final int imgW = (int)((lastTime-oldTime)*timeScale) ;                                                  // イメージ出力時の 幅(fillRectで使用)
391
392                        if( isDebug ) {
393//                              final int step = TimeScaleStep.getStep( timeScale );
394//                              final String stTM = getTime2Str(startTime+(int)oldTime ,step,0) ;                       // 7.0.5.1 (2019/09/27)
395//                              final String edTM = getTime2Str(startTime+(int)lastTime,step,0) ;                       // 7.0.5.1 (2019/09/27)
396                                final String stTM = getTime2Str(startTime+(int)oldTime ) ;                                      // 7.0.5.1 (2019/09/27)
397                                final String edTM = getTime2Str(startTime+(int)lastTime) ;                                      // 7.0.5.1 (2019/09/27)
398                                System.out.println( "③ ROW[" + lastNo + "]=" + stTM + " - " + edTM + " imgX=" + imgX + " imgW=" + imgW );
399                        }
400                        imageMeker(g2, oldColor, imgX, imgY, imgW, useTipsLink, lastNo, out);   // 6.0.2.0 (2014/09/19)
401
402                        oldTime  = lastTime ;
403                        lastTime = timeSpan ;
404                        oldColor = NULL_COLOR;          // 5.6.1.1 (2013/02/08) キーブレイクによる初期化=不明(空欄)時の色
405                }
406
407                // ヘッダー情報のイメージを作成します。
408                imageHeaderPaint( g2 , timeScale , colMap );
409
410                // 8.5.4.2 (2024/01/12) PMD 7.0.0 ExceptionAsFlowControl 対応
411//              try {
412                        final File dir = new File( tempDir );           // 画像ファイル出力先のフォルダ
413                        // 6.0.2.4 (2014/10/17) RV  java.io.File.mkdirs() の例外的戻り値を無視しています。
414                        if( ! dir.exists() && ! dir.mkdirs() ) {
415                                final String errMsg = "画像ファイル出力先のフォルダの作成に失敗しました。tempDir=[" + dir +"]" ;
416                                throw new HybsSystemException( errMsg );
417                        }
418
419                // イメージファイルを出力し、URLを返します。
420                final File file ;                               // 6.3.9.0 (2015/11/06) Found 'DU'-anomaly for variable(PMD)
421                try {
422                        file = File.createTempFile( "Img",".png", dir );        // テンポラリファイルの作成
423                        file.deleteOnExit();                                                            // JavaVM 停止時にテンポラリファイルの削除
424
425                        ImageIO.write(img, "PNG", file );
426                        g2.dispose();
427                }
428                catch( final IOException ex ) {
429                        final String errMsg = "画像ファイルの作成に失敗しました。tempDir=[" + tempDir +"]" ;
430                        throw new HybsSystemException( errMsg,ex );
431                }
432
433                // 6.9.7.0 (2018/05/14) imgタグに付与する、高さ、幅を、AbstractViewForm から、取得します。
434                // 高さ、幅は、一つでも指定されている場合は、AbstractViewForm の設定値を使用し、どちらも未設定の場合は、
435                // 元の画像サイズ(maxX,maxY)を記述します。(imgタグのサイズ指定で、ブラウザの画像読み込み時の体感速度が向上します。)
436                String width  = getWidth();                     // viewタグで設定した、幅
437                String height = getHeight();            // viewタグで設定した、高さ
438
439                if( StringUtil.isEmpty( width ) && StringUtil.isEmpty( height ) ) {             // 両方とも、empty の場合のみ、オリジナルの値をセットします。
440                        width  = maxX + "px" ;
441                        height = maxY + "px" ;
442                }
443
444                // imgタグを作成します。
445                // 6.3.9.0 (2015/11/06) width,height にセットしているが、値を変更していないので、直接設定する。
446                // 6.4.4.1 (2016/03/18) StringBuilderの代わりに、OgBuilderを使用する。
447                return new OgBuilder()
448//                      .append( "<img id='ImageTimeBar' alt='ImageTimeBar' title='ImageTimeBar' border='0'" )
449                        .append( "<img id='ImageTimeBar' alt='ImageTimeBar' title='ImageTimeBar'" )             // 8.5.4.0 (2023/12/01) Modify
450                        .appendNN( " width='"   , width                                         , "'" )                 // width が null の場合は、この行すべて登録しません。
451                        .appendNN( " height='"  , height                                        , "'" )                 // heightが null の場合は、この行すべて登録しません。
452                        .append(   " src='"             , tempUrl , file.getName()      , "'" )
453//              return new OgBuilder()
454//                      .append( "<img id='ImageTimeBar' alt='ImageTimeBar' title='ImageTimeBar' border='0'"
455//                                      , " width='"            , String.valueOf( maxX )
456//                                      , "px' height='"        , String.valueOf( maxY )
457//                                      , "px' src='"           , tempUrl
458//                                      , file.getName() , "'" )
459                        // 6.4.4.2 (2016/04/01) 引数がSupplierに変更。
460                        // area タグのデータが作成されていれば、出力します。
461                        // 7.0.1.0 (2018/10/15)
462                        .appendCase( out.length() > 0
463//                                                      , () -> new String[] {  " usemap='#TimeBarMap' /><map name='TimeBarMap'>"
464                                                        , () -> new String[] {  " usemap='#TimeBarMap' ><map name='TimeBarMap'>"
465                                                                                                        , out.toString()
466                                                                                                        , "</map>" }                            // true
467//                                                      , () -> new String[] {  " />" }                                 // false
468                                                        , () -> new String[] {  " >" }                                  // false
469                        ).toString();
470        }
471
472        /**
473         * データが存在しない最後の処理で、現在時刻まで、継続している処理をまとめます。
474         *
475         * このメソッドは、キーブレイク、通常の出力処理、データの最後の3か所に同じ処理が出てくるため
476         * 一つに整理しました。
477         *
478         * @og.rev 6.0.2.0 (2014/09/19) 新規作成
479         * @og.rev 6.4.2.0 (2016/01/29) alt属性にtitle属性を追記。
480         * @og.rev 6.4.7.1 (2016/06/17) useLastData属性は、最後の処理のみに適用する。
481         * @og.rev 6.4.8.2 (2016/07/08) クリッカブルマップの設定で、普通のリンクタグや、hrefOnly、hrefTarget が使えるようにする。
482         * @og.rev 6.4.9.5 (2016/09/09) title属性を廃止したが、一旦戻します。(ネイティブモードでツールチップが表示しない)
483         * @og.rev 6.7.9.1 (2017/05/19) 幅が0の場合は、1ピクセルだけ一つ前の領域に描画する。
484         * @og.rev 7.0.1.0 (2018/10/15) XHTML → HTML5 対応(空要素の、"/>" 止めを、">" に変更します)。
485         * @og.rev 7.0.2.1 (2019/03/04) linkClmNoのhrefVal で、空文字列もリンクの使用可否の判定条件に入れる
486         *
487         * @param       g2                      描画オブジェクト(Graphics2D)
488         * @param       oldColor        旧色
489         * @param       imgX            イメージ出力時の X軸の左端 px数
490         * @param       imgY            イメージ出力時の Y軸の上端 px数
491         * @param       imgW            イメージ出力時の 幅(fillRectで使用)
492         * @param       useTipsLink     Tipsのリンクを作成するかどうか(true:作成する)
493         * @param       row                     行番号
494         * @param       outBuf          出力用 StringBuilder
495         */
496        private void imageMeker( final Graphics2D g2,final Color oldColor,
497                                                         final int imgX,final int imgY,final int imgW,
498                                                         final boolean useTipsLink,final int row,final StringBuilder outBuf ) {
499                // 幅が0以上の場合のみ描画する。
500                if( imgW > 0 ) {
501                        // 6.4.7.1 (2016/06/17)
502                        g2.setColor( oldColor );
503                        g2.fillRect( imgX , imgY+recodeMargin , imgW, chartHeight );    // (実際の状態)左端x,上端y,幅w,高さh
504
505                        // 6.0.2.0 (2014/09/19) Tips表示を出さない条件に、oldColor == NULL_COLOR を加える。
506                        // equals ではなく、!= でオブジェクトの直接比較で判定する。
507                        // 6.4.7.1 (2016/06/17)
508//                      final boolean useTips = oldColor != NULL_COLOR && useTipsLink && row > 0 ;
509                        final boolean useTips = !NULL_COLOR.equals(oldColor) && useTipsLink && row > 0 ;                        // 7.2.9.4 (2020/11/20) PMD:Use equals() to compare object references.
510
511                        // tipsClm か linkClm を使用する。
512                        // 5.6.1.0 (2013/02/01) tips や link は、ひとつ前のデータで作成する必要がる為、最初の1件目は、処理しないように修正
513                        if( useTips ) {
514                                // tips や link は、ひとつ前のデータで作成する必要がある。(row-1)
515                                String tips = tipsClmNo >= 0 ? getValueLabel( row-1,tipsClmNo ) : getValueLabel( row-1,FGJ_CLMNO ) ;
516                                if( tips != null && tips.indexOf( '\'' ) >= 0 ) {               // 5.6.3.0 (2013/04/01) シングルクォーテーション のエスケープ
517                                        tips = tips.replaceAll( "'","&#39;" );
518                                }
519                                tips = StringUtil.spanCut( tips );                                              // 5.6.3.1 (2013/04/05) spanタグを削除
520
521                                outBuf.append( "<area shape='rect' alt='" ).append( tips ).append( "' title='" ).append( tips ).append( '\'' );                 // 6.4.9.5 (2016/09/09) 一旦戻す。
522                                if( linkClmNo >= 0 ) {
523                                        final String hrefVal = getValueLabel( row-1,linkClmNo );
524                                        // 7.0.2.1 (2019/03/04) 空文字列も判定条件に入れる
525//                                      if( hrefVal != null ) {
526                                        // 8.5.4.2 (2024/01/12) PMD 7.0.0 InefficientEmptyStringCheck 対応
527//                                      if( hrefVal != null && hrefVal.trim().length() > 0 ) {
528                                        if( StringUtil.isNotNull( hrefVal ) ) {                         // 8.5.4.2 (2024/01/12) PMD 7.0.0 InefficientEmptyStringCheck 対応
529                                                // 6.4.8.2 (2016/07/08) クリッカブルマップの設定で、普通のリンクタグや、hrefOnly、hrefTarget が使えるようにする。
530                                                outBuf.append( ' ' );
531                                                final int stAd = hrefVal.indexOf( "href" );
532                                                final int edAd = hrefVal.indexOf( '>' );
533                                                if( stAd >= 0 ) {                               // href が存在する。
534                                                        if( edAd >= 0 ) { outBuf.append( hrefVal.substring( stAd,edAd ) ); }    // a href ・・・ の場合の処置
535                                                        else            { outBuf.append( hrefVal.substring( stAd ) ); }                 // 従来の互換設定
536                                                }
537                                                else {
538                                                        outBuf.append( " href='" ).append( hrefVal ).append( '\'' );                    // リンクタグの hrefOnlyや、hrefTarget が使えるようにする。
539                                                }
540                                        }
541                                }
542
543                                // 6.4.8.2 (2016/07/08) title を、tips からキーの値に変更。ただし、未設定の場合のみ。
544                                if( outBuf.indexOf( "title=" ) < 0 ) {
545                                        final String title = getValueLabel( row-1,KEY_CLMNO );  // 6.4.8.2 (2016/07/08) title を、tips からキーの値に変更。
546                                        outBuf.append( "' title='" ).append( title ).append( '\'' );
547                                }
548
549                                // 6.0.2.5 (2014/10/31) char を append する。
550                                outBuf.append( " coords='" ).append( imgX )
551                                        .append( ',' ).append( imgY+recodeMargin )                              // 左端x1,上端y1
552                                        .append( ',' ).append( imgX+imgW )
553                                        .append( ',' ).append( imgY+recodeMargin+chartHeight )  // 右端x2,下段y2
554//                                      .append( "' />\n" );
555                                        .append( "' >\n" );                                                                             // 7.0.1.0 (2018/10/15)
556                        }
557                }
558                // 6.7.9.1 (2017/05/19) 幅が0の場合は、1ピクセルだけ一つ前の領域に描画する。
559                else {
560                        g2.setColor( oldColor );
561                        g2.fillRect( imgX -1 , imgY+recodeMargin , 1, chartHeight );    // (実際の状態)左端x,上端y,幅w,高さh
562                }
563        }
564
565        /**
566         * パラメータ内容を初期化します。
567         *
568         * @og.rev 5.6.1.1 (2013/02/08) useLastData 追加
569         * @og.rev 6.0.2.0 (2014/09/19) nowTime データがない最後の処理で使用する現在時刻
570         * @og.rev 6.4.6.1 (2016/06/03) ヘッダー部の高さを指定できるようにする。
571         * @og.rev 6.4.8.2 (2016/07/08) debug 属性を、パラメータにセットします。
572         * @og.rev 7.1.0.0 (2020/01/20) ヘッダー部分の日付フォーマット指定の追加
573         * @og.rev 8.5.4.2 (2024/01/12) PMD 7.0.0 LocalVariableNamingConventions 対応
574         */
575        private void paramInit() {
576//              final String s_startDate        = getParam( "START_DATE"                );      // タイムテーブルの表示開始日時をセットします(初期値:データの最小日時)。
577                final String tmpStartDate       = getParam( "START_DATE"                );      // タイムテーブルの表示開始日時をセットします(初期値:データの最小日時)。
578                final int timeSpanHour          = getIntParam( "TIME_SPAN"              );      // タイムテーブルの表示期間を時間で指定します(初期値:{@og.value TIME_SPAN})。
579
580//              final String[] s_labelClms      = StringUtil.csv2Array( getParam( "LABEL_CLMS" ) );     // 一覧表のラベル表示部に表示するカラム名をCSV形式で指定します。
581                final String[] labelClms        = StringUtil.csv2Array( getParam( "LABEL_CLMS" ) );     // 一覧表のラベル表示部に表示するカラム名をCSV形式で指定します。
582//              final String s_colClm           = getParam( "COLOR_CLM"         );      // レコードに付ける色を色文字列で指定する場合のカラム名を指定します。
583//              final String s_tipsClm          = getParam( "TIPS_CLM"          );      // レコード単位に、マウスオーバー時のTips表示を行うカラム名を指定します。
584//              final String s_linkClm          = getParam( "LINK_CLM"          );      // レコード単位に、クリッカブルリンクを設定するカラム名を指定します。
585                final String colClm                     = getParam( "COLOR_CLM"         );      // レコードに付ける色を色文字列で指定する場合のカラム名を指定します。
586                final String tipsClm            = getParam( "TIPS_CLM"          );      // レコード単位に、マウスオーバー時のTips表示を行うカラム名を指定します。
587                final String linkClm            = getParam( "LINK_CLM"          );      // レコード単位に、クリッカブルリンクを設定するカラム名を指定します。
588
589                useLegend               = getBoolParam( "USE_LEGEND"            );      // カラーの凡例を使用するかどうか[true/false]
590                maxLabelWidth   = getIntParam( "MAX_LABEL_WIDTH"        );      // ラベル表記部の最大サイズをpxで指定。何もなければ、可変長サイズ
591                maxTimeWidth    = getIntParam( "MAX_TIME_WIDTH"         );      // タイム表記部の最大サイズをpxで指定。
592                chartHeight             = getIntParam( "CHART_HEIGHT"           );      // 1レコードのチャートの間隔をpxで指定。実際の幅は、CHART_HEIGHT+MARGIN*2
593                headerHeight    = getIntParam( "HEADER_HEIGHT"          );      // 6.4.6.1 (2016/06/03) ヘッダー部の高さをpxで指定。凡例とヘッダーの高さになります。
594                if( headerHeight < 0 ) { headerHeight = chartHeight; }  // 指定が無い場合は、チャート幅と同じにします。
595                chartPadding    = getIntParam( "CHART_PADDING"          );      // イメージ作成の 全体テーブルの隙間
596                recodeMargin    = getIntParam( "RECODE_MARGIN"          );      // 各レコード、文字等の内部の間隔
597                useLastData             = getBoolParam( "USE_LAST_DATA"         );      // 5.6.1.1 (2013/02/08) 行の最後の情報が、継続しているとして使うかどうか[true/false]
598                isDebug                 = getBoolParam( "debug"                         );      // 6.4.8.2 (2016/07/08) debug 属性を、パラメータにセットします[true/false]。
599
600                tempDir                 = getParam( "TEMP_DIR"                          );      // 画像ファイルを作成するテンポラリディレクトリ(相対パス)
601                tempUrl                 = getParam( "TEMP_URL"                          );      // 作成した画像ファイルを取得するときに使用するURL(コンテキスト/相対パス)
602
603                headerUpFmt             = getParam( "HEADER_UP_FMT"                     );      // 7.1.0.0 (2020/01/20) ヘッダー部分上段の日付フォーマット
604                headerDwFmt             = getParam( "HEADER_DW_FMT"                     );      // 7.1.0.0 (2020/01/20) ヘッダー部分下段の日付フォーマット
605
606//              startDate               = getStr2Date( s_startDate );                   // 分単位に変換する。
607                startDate               = getStr2Date( tmpStartDate );                  // 分単位に変換する。
608                startTime               = str2DateTime ;                                                // 開始日時の時刻情報(分単位)の値。str2DateTime は、getStr2Date(String)メソッド実行後にセットされる。
609                timeSpan                = timeSpanHour * 60L ;                                  // 分単位に変換する。
610
611//              final int len = s_labelClms.length;
612                final int len = labelClms.length;
613                if( len > 0 ) {
614                        labelClmsNo = new int[len];
615                        for( int i=0; i<len; i++ ) {
616//                              labelClmsNo[i] = getColumnNo( s_labelClms[i] );
617                                labelClmsNo[i] = getColumnNo( labelClms[i] );
618                        }
619                }
620                else {
621                        labelClmsNo = new int[] { KEY_CLMNO };          // 初期値は、キーカラム
622                }
623
624                // 指定のカラム名に対するカラム番号を取得。なければ、-1 を設定しておく。
625//              if( s_colClm  != null ) { colClmNo  = getColumnNo( s_colClm  ); }                       // レコードに付ける色を色文字列で指定する場合のカラムNo
626//              if( s_tipsClm != null ) { tipsClmNo = getColumnNo( s_tipsClm ); }                       // レコード単位に、マウスオーバー時のTips表示を行うカラムNo
627//              if( s_linkClm != null ) { linkClmNo = getColumnNo( s_linkClm ); }                       // レコード単位に、クリッカブルリンクを設定するカラムNo
628                if( colClm  != null ) { colClmNo  = getColumnNo( colClm  ); }                           // レコードに付ける色を色文字列で指定する場合のカラムNo
629                if( tipsClm != null ) { tipsClmNo = getColumnNo( tipsClm ); }                           // レコード単位に、マウスオーバー時のTips表示を行うカラムNo
630                if( linkClm != null ) { linkClmNo = getColumnNo( linkClm ); }                           // レコード単位に、クリッカブルリンクを設定するカラムNo
631
632//              // 6.0.2.0 (2014/09/19) データがない最後の処理で使用する現在時刻
633//              final Calendar cal = Calendar.getInstance();
634//              nowTime = cal.getTimeInMillis() / MILLI_MINUTE - startDate ;            // 6.0.2.0 (2014/09/19) ミリ秒を分に換算、現在時刻(分単位)
635                nowTime = System.currentTimeMillis() / MILLI_MINUTE - startDate ;       // 8.5.4.2 (2024/01/12) PMD 7.0.0 AvoidCalendarDateCreation 対応
636        }
637
638        /**
639         * イメージの XYサイズを求め、結果を、maxX,maxY 変数に設定します。
640         *
641         * また、ラベル文字列の最大長が指定されていない場合(maxLabelWidth == -1)最大長を自動計算し、maxLabelWidth変数にセットします。
642         *
643         * maxLabelWidth : -1 の場合は、ラベル文字列の最大長を求めて、この値を計算する。= (recodeMargin*2 + ラベル文字列の最大長)
644         * maxX : イメージの最大横幅(X)方向のサイズpx。chartPadding*2 + maxLabelWidth + maxTimeWidth
645         * maxY : イメージの最大縦幅(Y)方向のサイズpx。chartPadding*2 + (chartHeight+recodeMargin*2)*(レコード数+ヘッダー数)
646         *
647         * @og.rev 5.6.3.1 (2013/04/05) 短縮ラベルなど、&lt;span&gt;タグが付与される値から、spanタグを削除します。
648         * @og.rev 6.4.6.1 (2016/06/03) ヘッダー部の高さを指定できるようにする。
649         * @og.rev 6.9.9.1 (2018/08/27) ラベル領域の長さ求めで、改行が含まれる場合の計算対応。
650         *
651         * @param       startNo 計算の開始列番号
652         * @param       lastNo  計算の終了列番号
653         */
654        private void calcImageSize( final int startNo , final int lastNo ) {
655                String oldKeyVal = "";  // 初期値。1回目にキーブレイクさせる。
656
657                final int clmSu = labelClmsNo.length;
658                maxClmWidth = new int[clmSu];
659
660                // 6.4.6.1 (2016/06/03) ヘッダー部の高さは、凡例とヘッダー部が対象。
661                int rowCnt = 0;                                         // 6.4.6.1 (2016/06/03) レコード数
662
663                // ラベル領域の長さを各ラベル長を積み上げて計算する。
664                if( maxLabelWidth < 0 ) {
665                        // FontMetrics を取得する為だけの BufferedImage
666                        final BufferedImage img = new BufferedImage( 1, 1, BufferedImage.TYPE_INT_ARGB);
667                        final Graphics2D g2 = img.createGraphics();
668                        final FontMetrics fontM = g2.getFontMetrics();
669
670                        // 初期値の計算は、ヘッダーのラベルの幅を使用する。
671                        for( int col=0; col<clmSu; col++ ) {
672                                final String lbl = StringUtil.spanCut( getColumnLabel( labelClmsNo[col] ) );            // 5.6.3.1 (2013/04/05) spanタグを削除
673//                              maxClmWidth[col] = fontM.stringWidth( lbl );                                                                            // 最大値の初期値として、ヘッダーラベルの幅をセットしておく。
674                                // 6.9.9.1 (2018/08/27) ラベル領域の長さ求めで、改行が含まれる場合の計算対応。
675                                final String[] xlbls = lbl.split( "\\n|\\\\n" );                                                                        // 本物の改行と、"\n" 文字列
676                                if( xlbls.length > 1 ) {
677                                        useCR = true;
678                                        for( final String xlbl : xlbls ) {
679                                                final int fontW = fontM.stringWidth( xlbl );
680                                                if( maxClmWidth[col] < fontW ) { maxClmWidth[col] = fontW; }                            // 分割したラベルで、最大値を求める。
681                                        }
682                                }
683                                else {
684                                        maxClmWidth[col] = fontM.stringWidth( lbl );                                                                    // 最大値の初期値として、ヘッダーラベルの幅をセットしておく。
685                                }
686                        }
687
688                        for( int row=startNo; row<lastNo; row++ ) {
689                                // キーブレイク判定。キーブレイクは、一番初めから来る。
690                                final String keyVal = getValue( row,KEY_CLMNO );
691                                if( !oldKeyVal.equals( keyVal ) ) {
692                                        oldKeyVal = keyVal;
693                                        rowCnt++;                               // レコード数
694
695                                        // ラベルは、キーブレイク時の値のみ採用する。
696                                        for( int col=0; col<clmSu; col++ ) {
697                                                final String lbl = StringUtil.spanCut( getValueLabel( row,labelClmsNo[col] ) );         // 5.6.3.1 (2013/04/05) spanタグを削除
698//                                              final int fontW = fontM.stringWidth( lbl );
699//                                              if( maxClmWidth[col] < fontW ) { maxClmWidth[col] = fontW; }
700
701                                                // 6.9.9.1 (2018/08/27) ラベル領域の長さ求めで、改行が含まれる場合の計算対応。
702                                                final String[] xlbls = lbl.split( "\\n|\\\\n" );                                                                        // 本物の改行と、"\n" 文字列
703                                                if( xlbls.length > 1 ) {
704                                                        useCR = true;
705                                                        for( final String xlbl : xlbls ) {
706                                                                final int fontW = fontM.stringWidth( xlbl );
707                                                                if( maxClmWidth[col] < fontW ) { maxClmWidth[col] = fontW; }                                    // 分割したラベルで、最大値を求める。
708                                                        }
709                                                }
710                                                else {
711                                                        final int fontW = fontM.stringWidth( lbl );
712                                                        if( maxClmWidth[col] < fontW ) { maxClmWidth[col] = fontW; }
713                                                }
714                                        }
715                                }
716                        }
717                        g2.dispose();
718
719                        // 最大ラベル幅は、各ラベルの最大値+(マージン*2)*カラム数
720                        maxLabelWidth = recodeMargin * 2 * clmSu ;
721                        for( int col=0; col<clmSu; col++ ) {
722                                maxLabelWidth += maxClmWidth[col];
723                        }
724                }
725                else {
726                        for( int row=startNo; row<lastNo; row++ ) {
727                                // キーブレイク判定。キーブレイクは、一番初めから来る。
728                                final String keyVal = getValue( row,KEY_CLMNO );
729                                if( !oldKeyVal.equals( keyVal ) ) {
730                                        oldKeyVal = keyVal;
731                                        rowCnt++;                               // レコード数
732                                }
733                        }
734
735                        // 最大ラベル幅は、均等割り付け。端数は無視(どうせ、ラベル部は、maxLabelWidth で計算するので。)
736                        final int clmWidth = ( maxLabelWidth - recodeMargin * 2 * clmSu ) / clmSu ;
737                        for( int col=0; col<clmSu; col++ ) {
738                                maxClmWidth[col] = clmWidth;
739                        }
740                }
741
742                maxX = chartPadding*2 + maxLabelWidth + maxTimeWidth ;
743                // 6.4.6.1 (2016/06/03) ヘッダー部の高さは、凡例とヘッダー部を考慮して、加算する。
744                maxY = chartPadding * 2 + (headerHeight + recodeMargin * 2) * (useLegend ? 2 : 1) + (chartHeight + recodeMargin * 2) * rowCnt ;
745        }
746
747        /**
748         * ヘッダー情報のイメージを作成します。
749         *
750         * 全体の枠もここで作成しておきます。
751         * イメージは、キーカラムのラベルと、時間軸になります。時間軸は縦方向にすべて線を引きます。
752         * 時間軸の間隔は、timeScale によって、切り替わります。
753         * 凡例を使う場合(useLegend=true)は、引数の FlgColorMap を利用して作成します。
754         *
755         * @og.rev 5.6.3.1 (2013/04/05) 短縮ラベルなど、&lt;span&gt;タグが付与される値から、spanタグを削除します。
756         * @og.rev 5.6.5.0 (2013/06/07) 年月日情報を表示します。なお、日単位の場合は、年月は省略します。
757         * @og.rev 6.3.9.0 (2015/11/06) FlgColorMapで管理している内部クラスを新規作成します。
758         * @og.rev 6.4.6.1 (2016/06/03) ヘッダー部の高さを指定できるようにする。
759         * @og.rev 6.9.9.1 (2018/08/27) ラベル領域の長さ求めで、改行が含まれる場合の計算対応。
760         * @og.rev 7.0.5.1 (2019/09/27) Date → Calendar 変更、日付表示方法見直し。
761         * @og.rev 7.0.7.1 (2019/12/24) 日付の箇所が、時間になっていた。
762         * @og.rev 7.1.0.0 (2020/01/20) ヘッダー部分の日付フォーマット指定の追加
763         *
764         * @param       g2                      描画するGraphics2Dオブジェクト
765         * @param       timeScale       時間(分)当たりのピクセル数
766         * @param       colMap          状況コードに対応したカラーマップ
767         */
768        private void imageHeaderPaint( final Graphics2D g2 , final double timeScale , final FlgColorMap colMap ) {
769
770                int posY1 = chartPadding ;
771                int posY2 = chartPadding+headerHeight+recodeMargin ;            // 6.4.6.1 (2016/06/03) ヘッダー部の高さ
772
773                // 凡例を使う場合
774                if( useLegend && colMap != null ) {
775                        // 凡例を並べる間隔を求める。
776                        final FontMetrics fontM = g2.getFontMetrics();
777                        final int maxSize = fontM.stringWidth( colMap.getMaxLengthLabel() ) ;           // 文字列の最大長ラベルの幅
778                        final int imgW  = headerHeight ;                        // 凡例■の幅(高さと同じにしているので真四角)、6.4.6.1 (2016/06/03)
779                        final int mgnW  = recodeMargin ;                        // 凡例■から文字までの間
780                        final int spanW = maxSize + recodeMargin ;      // 凡例■から凡例■までの間隔。文字最大長+α
781
782                        int legX = chartPadding ;
783
784                        // 6.3.9.0 (2015/11/06) FlgColorMapで管理している内部クラスを新規作成します。
785                        for( final FlgColorObj obj : colMap.values() ) {
786                                g2.setColor( obj.COL );                                                                                 // 凡例■の色
787                                g2.fillRect( legX , posY1+recodeMargin , imgW , headerHeight ); // (実際の状態)左端x,上端y,幅w,高さh、6.4.6.1 (2016/06/03)
788
789                                legX += imgW + mgnW ;
790                                g2.setColor( LABEL_COLOR );
791                                g2.drawString( obj.LBL , legX , posY2 );        // 文字列,ベースラインのx座標,y座標
792
793                                legX += spanW ;
794                        }
795                        posY1 += headerHeight+recodeMargin*2 ;  // 1レコード分の高さを加算しておく。、6.4.6.1 (2016/06/03)
796                        posY2 += headerHeight+recodeMargin*2 ;  // 1レコード分の高さを加算しておく。、6.4.6.1 (2016/06/03)
797                }
798
799                // まずは、全体の枠線の描画
800                g2.setColor( LABEL_COLOR );
801                g2.drawRect( chartPadding, posY1, maxX-chartPadding*2, maxY-posY1-chartPadding );               // 左端,上端,幅,高さ
802
803                // ヘッダーのラベル分だけ、繰返し描画する。
804                final int clmSu = labelClmsNo.length;
805                int posX = chartPadding ;               // ラベル文字列の書き出し位置の初期値
806                for( int col=0; col<clmSu; col++ ) {
807                        // 6.3.9.1 (2015/11/27) Found 'DD'-anomaly for variable(PMD)
808                        final String lbl = StringUtil.spanCut( getColumnLabel( labelClmsNo[col] ) );            // 5.6.3.1 (2013/04/05) spanタグを削除
809//                      g2.drawString( lbl , posX + recodeMargin, posY2 );                      // 文字列,ベースラインのx座標,y座標
810                        // 6.9.9.1 (2018/08/27) ラベル領域の長さ求めで、改行が含まれる場合の計算対応。
811                        if( useCR ) {
812                                final String[] xlbls = lbl.split( "\\n|\\\\n" );                                                                // 本物の改行と、"\n" 文字列
813                                for( int i=0; i<xlbls.length; i++ ) {
814                                        final int posY3 = posY2 - (xlbls.length-1-i)*headerHeight/xlbls.length;         // 改行ラベルを、均等に枠内に分割します。
815                                        g2.drawString( xlbls[i] , posX + recodeMargin, posY3 );                                         // 文字列,ベースラインのx座標,y座標
816                                }
817                        }
818                        else {
819                                g2.drawString( lbl , posX + recodeMargin, posY2 );              // 文字列,ベースラインのx座標,y座標
820                        }
821
822                        posX += recodeMargin*2 + maxClmWidth[col] ;                                     // 次の書き出し位置は、文字最大長+マージン*2
823                        g2.drawLine( posX, posY1, posX, maxY-chartPadding );            // 始点(x 座標,y 座標),終点(x 座標,y 座標)
824                }
825
826                final int step = TimeScaleStep.getStep( timeScale );                    // 時間スケールに対応したSTEP数
827
828                // 日付ヘッダー部の描画のためのカレンダー
829                final Calendar cal = Calendar.getInstance();
830                cal.setTimeInMillis( startDate * MILLI_MINUTE );                                // 6.0.2.0 (2014/09/19) ミリ秒に換算した分
831
832//              final int week = cal.get( Calendar.DAY_OF_WEEK );                               // 開始曜日 7.0.5.1 (2019/09/27) 削除
833
834//              final String fmt = step < MINUTE_OF_DAY ? "yyyy/MM/dd(EE)" : "dd" ;             // 上段フォーマットは、時間ベースの場合は、"yyyy/MM/dd(EE)"、日ベースの場合は、"dd"
835//              final String fmt1 = step >= MINUTE_OF_DAY ? "M月" : "H/d(EE)" ;                  // 7.0.5.1 (2019/09/27) 判定の反転と上段フォーマット変更
836
837                // 7.1.0.0 (2020/01/20) ヘッダー部分の日付フォーマット指定の追加
838//              final String fmt1 = step >= MINUTE_OF_DAY ? "M月" : "M/d(EE)" ;                  // 7.0.7.1 (2019/12/24) 日付の箇所が、時間になっていた。
839//              final String fmt2 = step >= MINUTE_OF_DAY ? "dEE" : "H:mm" ;                    // 7.0.5.1 (2019/09/27) 下段フォーマット追加
840                final String fmt1 = headerUpFmt != null ? headerUpFmt : (step >= MINUTE_OF_DAY ? "M月" : "M/d(EE)") ;    // 7.1.0.0 (2020/01/20)
841                final String fmt2 = headerDwFmt != null ? headerDwFmt : (step >= MINUTE_OF_DAY ? "dEE" : "H:mm") ;              // 7.1.0.0 (2020/01/20)
842
843                final DateFormat format1 = new SimpleDateFormat( fmt1,Locale.JAPAN );
844                final DateFormat format2 = new SimpleDateFormat( fmt2,Locale.JAPAN );   // 7.0.5.1 (2019/09/27) 下段フォーマット追加
845
846                // グラフ領域の日付ヘッダー部の描画
847                g2.setStroke( DSAH_STROK );                             // 日付部は、破線
848                posX = chartPadding+maxLabelWidth ;             // グラフ領域は、chartPadding+maxLabelWidth から。
849                int oldKey = -1;                                                // 7.0.5.1 (2019/09/27) ブレイクさせるキー(日や月)の旧の値
850                for( int tm=0; tm<timeSpan; tm+=step ) {
851                        int offset = headerHeight / 2 + recodeMargin;   // ヘッダーの表示基準のオフセット(チャート幅の半分)6.4.6.1 (2016/06/03)
852                        // 上段:ヘッダー出力 7.0.5.1 (2019/09/27) Date → Calendar 変更
853                        final Calendar calH = Calendar.getInstance();
854                        calH.setTimeInMillis( (startDate + tm) * MILLI_MINUTE );
855                        // 7.0.5.1 (2019/09/27) ステップが日単位より大きい場合は、月のブレイクでヘッダー出力します。
856                        final int newKey = step >= MINUTE_OF_DAY ? calH.get( Calendar.MONTH ) : calH.get( Calendar.DATE );
857                        if( oldKey != newKey ) {
858                                oldKey = newKey;
859                                g2.drawString( format1.format( calH.getTime() ) , posX + recodeMargin , posY2-offset ); // 文字列,ベースラインのx座標,y座標
860                                offset = 0;             // ヘッダーを表示する場合のみ上まで線を引く。
861                        }
862//                      if( tm % MINUTE_OF_DAY == 0 ) {                 // 6.0.2.0 (2014/09/19) 1日が何分
863//                              final Date dt = new Date( (startDate + tm) * MILLI_MINUTE );                                            // 6.0.2.0 (2014/09/19) ミリ秒に換算した分
864//                              g2.drawString( format1.format( dt ) , posX + recodeMargin , posY2-offset );                     // 文字列,ベースラインのx座標,y座標
865//                              offset = 0;             // ヘッダーを表示する場合のみ上まで線を引く。
866//                      }
867
868                        // getTime2Str に 7.0.5.1 (2019/09/27) 日付情報を渡します。
869//                      g2.drawString( getTime2Str(startTime+tm,step,week) , posX + recodeMargin , posY2 );             // 文字列,ベースラインのx座標,y座標
870                        g2.drawString( format2.format( calH.getTime() ) , posX + recodeMargin , posY2 );                // 文字列,ベースラインのx座標,y座標
871                        g2.drawLine( posX, posY1+offset, posX, maxY-chartPadding );                                                             // 始点(x 座標,y 座標),終点(x 座標,y 座標)
872
873                        posX += (int)(step*timeScale);
874                }
875        }
876
877        /**
878         * 時間スケールに対応したSTEP数を管理するための内部クラス
879         *
880         * 時間ヘッダーを表示する場合、ある程度意味のある間隔でラベル表示したいと思います。
881         * 全体の描画領域の長さと、時間当たりのスケールファクター(ピクセル数)から、
882         * ラベルの描画間隔を求めます。
883         * 意味のある間隔は、STEPS で定義し、10分,30分,60分,1/4日,1/2日,1日 まで定義しています。
884         * 一番大きな単位以上の場合は、その最大単位の整数倍を返します。
885         *
886         * 一時間当たりの表示幅を、MIN_PX としていますので、この値以下の間隔では描画されません。
887         * 初期値は、600px を 24時間表示できる 600px/24h = 25px にしています。
888         */
889        private static final class TimeScaleStep {
890                                                                                                        // 分   分   時   1/4   1/2  1日
891                // 8.5.4.2 (2024/01/12) PMD 7.0.0 UseShortArrayInitializer
892//              private static final int[] STEPS = new int[] { 10 , 30 , 60 , 360 , 720 , 1440 };
893                private static final int[] STEPS = { 10 , 30 , 60 , 360 , 720 , 1440 };
894                private static final int MAX_STEP = STEPS[STEPS.length-1] ;                                     // 8.5.5.1 (2024/02/29)
895                private static final int MIN_PX = 25;           // スケールに対する最小値
896
897                /**
898                 * デフォルトコンストラクターをprivateにして、
899                 * オブジェクトの生成をさせないようにする。
900                 */
901                private TimeScaleStep() {}
902
903                /**
904                 * 時間を意味のある範囲の整数として返します。
905                 *
906                 * 全体の描画領域の長さと、時間当たりのスケールファクター(ピクセル数)から、
907                 * 10分,30分,60分,1/4日,1/2日,1日 までの整数値で返します。
908                 * 一番大きな単位以上の場合は、その最大単位の整数倍を返します。
909                 *
910                 * @param       timeScale       時間(分)当たりのピクセル数
911                 * @return      時間スケールに対応した意味のある範囲の整数
912                 */
913                public static int getStep( final double timeScale ) {
914//                      final int tmStep = (int)Math.ceil(MIN_PX/(timeScale));  // 整数切り上げ
915                        final int tmStep = (int)Math.ceil(MIN_PX/timeScale);    // 整数切り上げ // 8.5.4.2 (2024/01/12) PMD 7.0.0 UselessParentheses
916
917                        // 7.2.9.4 (2020/11/20) PMD:This for loop can be replaced by a foreach loop
918                        for( final int step : STEPS ) {
919                                if( tmStep <= step ) { return step; }                           // 配列の数字に切り上げ
920                        }
921
922//                      for( int i=0; i<STEPS.length; i++ ) {
923//                              if( tmStep <= STEPS[i] ) { return STEPS[i]; }           // 配列の数字に切り上げ
924//                      }
925
926                        // 8.5.5.1 (2024/02/29) MAX_STEP 追加
927//                      // 未設定の場合は、最上位の値の整数倍に切り上げ
928//                      return (int)Math.ceil( (double)tmStep / STEPS[STEPS.length-1] ) * STEPS[STEPS.length-1];
929                        return (int)Math.ceil( (double)tmStep / MAX_STEP ) * MAX_STEP;
930                }
931        }
932
933        /**
934         * 状況コード、ラベル、色を管理するための内部クラス
935         *
936         * 状況に応じたコード、ラベル、色を管理します。
937         * これは、getColor(状況コード,ラベル) または、getColor(状況コード,ラベル,色文字列) で
938         * 要求された情報を内部で管理し、同じコードの場合に同じ色を返します。
939         * また、凡例作成用に、最も文字数が長いラベルを管理します。
940         * 色文字列を指定した場合でも、最初に要求された状況コードに対応する色を返します。
941         * これは、同一状況コードで色違いを作成することができないことを意味します。
942         * 色文字列を指定しない場合は、内部の色配列から、順番に色を割り当てます。
943         * 色を割り当てる順番は、状況コードの発生順です。よって、検索条件によって、
944         * 状況コードの現れる順番が異なると、色も毎回異なることになります。
945         *
946         * 自動割り当ての色は、BLUE,CYAN,GRAY,GREEN,LIGHT_GRAY,MAGENTA,DARK_GRAY,ORANGE,PINK,RED,YELLOW
947         * となっており、それを超えると、RGBをランダムに発生させて色を作成します。
948         * よって、どのような色になるかは全くわかりません。
949         *
950         * @og.rev 6.4.3.3 (2016/03/04) CLR_ARY等、カラー関連処理を、ColorMapクラスへ移動します。
951         */
952        private static final class FlgColorMap {
953                // 6.4.3.3 (2016/03/04) ColorMapクラスへ移動
954                private final Map<String,FlgColorObj> colMap = new TreeMap<>();                                                                         // 6.3.9.0 (2015/11/06)
955
956                // 8.5.5.1 (2024/02/29) PMD 7.0.0 FieldDeclarationsShouldBeAtStartOfClass
957                private int             lastCnt         ;
958                private String  maxLabel        = "" ;          // 最大長のラベル
959                private int             maxlen          = -1 ;          // 最大長のラベルのlength()
960
961                /**
962                 * デフォルトのコンストラクタ
963                 *
964                 * @og.rev 8.5.5.0 (2024/02/02) デフォルトのコンストラクタは必ず用意しておく。
965                 */
966                public FlgColorMap() {
967                        super();
968                }
969
970                /**
971                 * 状況コードに対応した色オブジェクトを返します。
972                 *
973                 * 状況コードが初めて指定された場合は、順番に内部の色を割り当てます。
974                 * また、その時のラベルも管理します。ラベルと色のセットは、凡例作成時に利用されます。
975                 *
976                 * 自動割り当ての色は、BLUE,CYAN,GRAY,GREEN,LIGHT_GRAY,MAGENTA,DARK_GRAY,ORANGE,PINK,RED,YELLOW
977                 * となっており、それを超えると、RGBをランダムに発生させて色を作成します。
978                 * よって、どのような色になるかは全くわかりません。
979                 *
980                 * @param       fgj     状況コード
981                 * @param       lbl     状況コードのラベル
982                 * @return      状況コードに対応した色オブジェクト
983                 */
984                public Color getColor( final String fgj,final String lbl ) {
985                        return getColor( fgj,lbl,null );
986                }
987
988                /**
989                 * 状況コードに対応した色オブジェクトを返します。
990                 *
991                 * 状況コードが初めて指定された場合は、引数の色文字列の色を割り当てます。
992                 * また、その時のラベルも管理します。ラベルと色のセットは、凡例作成時に利用されます。
993                 *
994                 * 色文字列を指定した場合でも、最初に要求された状況コードに対応する色を返します。
995                 * これは、同一状況コードで色違いを作成することができないことを意味します。
996                 * 色文字列 が null の場合は、自動割り当てのメソッドと同じです。
997                 * よって、色文字列の指定と、自動割り当てが同時に発生すると、異なる状況コードで
998                 * 同じ色が指定される可能性がありますので、混在して使用しないでください。
999                 *
1000                 * @og.rev 6.0.2.1 (2014/09/26) StringUtil → ColorMap
1001                 * @og.rev 6.3.9.0 (2015/11/06) FlgColorMapで管理している内部クラスを新規作成します。
1002                 * @og.rev 6.4.3.3 (2016/03/04) ColorMapクラスへ移動。それに関連する修正。
1003                 *
1004                 * @param       fgj             状況コード
1005                 * @param       lbl             状況コードのラベル
1006                 * @param       colStr  状況コードに対応した色文字列(nullの場合は、自動割り当て)
1007                 * @return      状況コードに対応した色オブジェクト
1008                 * @og.rtnNotNull
1009                 */
1010                public Color getColor( final String fgj,final String lbl,final String colStr ) {
1011                        // 6.3.9.1 (2015/11/27) A method should have only one exit point, and that should be the last statement in the method.(PMD)
1012                        final Color rtn ;
1013                        if( fgj == null ) { rtn = LABEL_COLOR; }
1014                        else {
1015                                if( lbl != null ) {
1016                                        final int len = lbl.length();
1017                                        if( len > maxlen ) { maxLabel = lbl; maxlen = len; }
1018                                }
1019
1020                                // Map#computeIfAbsent : 戻り値は、既存の、または計算された値。追加有り、置換なし、削除なし
1021                                // colMap からは、FlgColorObj が戻されるので、COL 属性を取得する。
1022                                final String cloCnt = colStr == null ? String.valueOf( lastCnt++ ) : colStr ;
1023                                rtn = colMap.computeIfAbsent( fgj , k -> new FlgColorObj( lbl , cloCnt ) ).COL;
1024                        }
1025
1026                        return rtn ;
1027                }
1028
1029                /**
1030                 * 内部で管理している、ラベル(String)と色オブジェクト(Color)の コレクションを返します。
1031                 *
1032                 * 内部で管理しているコレクションです。
1033                 * このコレクションは、状況コードでソートされています。
1034                 * コレクションの中身は、オブジェクト配列となっており、[0]は、String型のラベル、[1]は、
1035                 * Color型の色です。
1036                 *
1037                 * @og.rev 6.3.9.0 (2015/11/06) FlgColorMapで管理している内部クラスを新規作成します。
1038                 *
1039                 * @return      ラベル(String)と色オブジェクト(Color)の コレクション
1040                 */
1041                public Collection<FlgColorObj> values() {
1042                        return colMap.values();
1043                }
1044
1045                /**
1046                 * 登録されたラベル文字列で、最も文字数が長いラベルを返します。
1047                 *
1048                 * 凡例で、ラベルの最大長を求めるのに利用できます。
1049                 * ただし、簡易的に、length() 計算しているだけなので、英語、日本語混在や、
1050                 * プロポーショナルフォント使用時の厳密な最大長の文字列ではありません。
1051                 *
1052                 * @return      最も文字数が長いラベル
1053                 */
1054                public String getMaxLengthLabel() { return maxLabel; }
1055        }
1056
1057        /**
1058         * FlgColorMapで管理している内部クラス
1059         *
1060         * フラグに対して、Object[] 配列の [0]:ラベル、[1]:カラー で管理してましたが、
1061         * きちんとString と Color で管理します。そのための内部クラスです。
1062         *
1063         * @og.rev 6.3.9.0 (2015/11/06) FlgColorMapで管理している内部クラスを新規作成します。
1064         */
1065        private static final class FlgColorObj {
1066                private final String LBL ;
1067                private final Color  COL ;
1068
1069                /**
1070                 * ラベルとカラーを指定したインストラクター
1071                 *
1072                 * @param       lbl     ラベル
1073                 * @param       col     カラー
1074                 */
1075                /* default */ FlgColorObj( final String lbl , final Color col ) {
1076                        LBL = lbl ;
1077                        COL = col ;
1078                }
1079
1080                /**
1081                 * ラベルとカラー文字列を指定したインストラクター
1082                 *
1083                 * カラー文字列は、ColorMap で定義されている文字列です。
1084                 *
1085                 * @og.rev 6.4.3.3 (2016/03/04) 新規追加。
1086                 *
1087                 * @param       lbl             ラベル
1088                 * @param       colStr  カラー文字列
1089                 */
1090                /* default */ FlgColorObj( final String lbl , final String colStr ) {
1091                        LBL = lbl ;
1092                        COL = ColorMap.getColorInstance( colStr );
1093                }
1094
1095                /**
1096                 * ラベルとカラー番号を指定したインストラクター
1097                 *
1098                 * カラー番号は、ColorMap で定義されている番号です。
1099                 *
1100                 * @og.rev 6.4.3.3 (2016/03/04) 新規追加。
1101                 *
1102                 * @param       lbl     ラベル
1103                 * @param       no      カラー番号
1104                 */
1105                /* default */ FlgColorObj( final String lbl , final int no ) {
1106                        LBL = lbl ;
1107                        COL = ColorMap.getColorInstance( no );
1108                }
1109
1110                /**
1111                 * ラベルを返します。
1112                 *
1113                 * @og.rev 6.4.3.3 (2016/03/04) 新規追加。
1114                 *
1115                 * @return      ラベル
1116                 */
1117                /* default */ String getLabel() { return LBL; }
1118
1119                /**
1120                 * カラーを返します。
1121                 *
1122                 * @return      カラー
1123                 */
1124                /* default */ Color getColor() { return COL; }
1125        }
1126
1127        /**
1128         * 日時文字列を数字に変換します。
1129         *
1130         * 日時文字列は、yyyyMMdd または、yyyyMMddHHmmss 形式とします。
1131         * これを、エポックタイムからの経過時間の 分単位の値を求めます。
1132         * 具体的には、Calendar オブジェクトの getTimeInMillis() の結果を、
1133         * 60000 で割り算した値を作成します。
1134         * よって、Calendar オブジェクト作成時も、秒の単位は切り捨てます。
1135         * 引数が null の場合は、現在時刻より、作成します。
1136         *
1137         * @param       val     日時文字列の値(yyyyMMdd または、yyyyMMddHHmmss 形式 など)
1138         *
1139         * @return      日時文字列を分換算した数字
1140         */
1141        private long getStr2Date( final String val ) {
1142                final Calendar cal = Calendar.getInstance();
1143                str2DateTime = 0;
1144                if( val == null ) {
1145                        cal.set( Calendar.HOUR_OF_DAY, 0 );             // 5.3.5.0 (2011/05/01) 時間の解決規則が適用されるため、「時」だけは、setメソッドで 0 にセットする。
1146                        cal.clear( Calendar.MINUTE );
1147                        cal.clear( Calendar.SECOND );
1148                        cal.clear( Calendar.MILLISECOND );
1149                }
1150                else if( val.length() == 8 ) {
1151                        cal.clear();
1152                        cal.set( Integer.parseInt( val.substring( 0,4 ) ) ,                     // 年
1153                                         Integer.parseInt( val.substring( 4,6 ) ) - 1,          // 月(0から始まる)
1154                                         Integer.parseInt( val.substring( 6,8 ) )                       // 日
1155                        );
1156                }
1157                else {
1158                        cal.clear();
1159                        cal.set( Integer.parseInt( val.substring( 0,4 ) ) ,                     // 年
1160                                         Integer.parseInt( val.substring( 4,6 ) ) - 1,          // 月(0から始まる)
1161                                         Integer.parseInt( val.substring( 6,8 ) ) ,                     // 日
1162                                         Integer.parseInt( val.substring( 8,10 ) ) ,            // 時
1163                                         Integer.parseInt( val.substring( 10,12 ) )                     // 分
1164                        );
1165                        str2DateTime = Integer.parseInt( val.substring( 8,10 ) ) * 60 + Integer.parseInt( val.substring( 10,12 ) ) ;
1166                }
1167                return cal.getTimeInMillis() / MILLI_MINUTE ;           // 6.0.2.0 (2014/09/19) ミリ秒に換算した分
1168        }
1169
1170        /**
1171         * 数字(分)を時間文字列に変換します。
1172         *
1173         * 480 は、"08" に、1260 は、"21" に変換します。
1174         * 引数の時間は、分を表す整数です。24時間表記であれば、0 ~ 1440 の範囲で収まりますが、
1175         * 期間が長い場合は、その値を超えます。また、24時間を超える場合は、0 に戻ります。
1176         * 文字列にする場合の最小単位は、(時)なので、60(分)で割り算して、余は、切り捨てます。
1177         * step は、60(分)単位の表示時に飛ばす数です。step=60 なら、60(分)単位、step=120 なら、120(分)
1178         * 単位となります。stepが、1440 以下の場合は、そのまま、24時間表記で構いませんが、
1179         * それを超えると時間ではなく、日数表記に切り替わります。
1180         *
1181         * @og.rev 5.6.5.0 (2013/06/07) 月単位の場合は、曜日を表示します。
1182         * @og.rev 7.0.5.1 (2019/09/27) Date → Calendar 変更、日付表示方法見直し。
1183         * @og.rev 7.2.9.4 (2020/11/20) String.format でゼロ埋め対応
1184         *
1185         * @param       timeVal 引数の時間整数(分)
1186//       * @param       step    60分単位のステップ数( 10,30,60,720,1440 単位となるように調整)
1187//       * @param       week    カレンダクラスの曜日フィールド(DAY_OF_WEEK)
1188         *
1189         * @return      数字を時間文字列に変換した結果( "08" など)
1190         * @og.rtnNotNull
1191         */
1192//      private String getTime2Str( final int timeVal, final int step, final int week ) {       // 7.0.5.1 (2019/09/27)
1193        private String getTime2Str( final int timeVal ) {
1194                final int htm = (timeVal / M_60) % H_24 ;               // 時(24時間制) 7.0.5.1 (2019/09/27) 先頭の 0 は外す。
1195                final int mtm = timeVal % M_60 ;                                // 分(60分制) 7.0.5.1 (2019/09/27) 時間表示は、常に分まで出す。 先頭に0を付ける
1196
1197                return String.format( "%d:%02d", htm,mtm );
1198
1199//              final StringBuilder rtn = new StringBuilder( BUFFER_MIDDLE );
1200//
1201////            if( step >= MINUTE_OF_DAY ) {                                                           // 6.0.2.0 (2014/09/19) 1日が何分
1202////                    final int dtm = timeVal / MINUTE_OF_DAY ;                               // 日の整数値
1203////                    rtn.append( DAY_OF_WEEK_JA[ ( dtm + week ) % W_7 ] );   // 曜日を表示
1204////            }
1205////            else {
1206//                      final int htm = (timeVal / M_60) % H_24 ;               // 時(24時間制)
1207//      //              if( htm < DEC ) { rtn.append( '0' ); }                  // 6.0.2.5 (2014/10/31) 1桁の場合は、先頭に0を付ける 7.0.5.1 (2019/09/27) 先頭の 0 は外す。
1208//                      rtn.append( htm );
1209//
1210////                    if( step < M_60 ) {                                                             // 7.0.5.1 (2019/09/27) 時間表示は、常に分まで出す。
1211//                              final int mtm = timeVal % M_60 ;                        // 分(60分制)
1212//                              rtn.append( ':' )                                                       // 6.0.2.5 (2014/10/31) char を append する。
1213//                              if( mtm < DEC ) { rtn.append( '0' ); }          // 6.0.2.5 (2014/10/31) char を append する。
1214//                              rtn.append( mtm );
1215////                    }
1216////            }
1217//
1218//              return rtn.toString();
1219        }
1220
1221        /**
1222         * 表示項目の編集(並び替え)が可能かどうかを返します。
1223         *
1224         * @return      表示項目の編集(並び替え)が可能かどうか(false:不可能)
1225         */
1226        @Override
1227        public boolean isEditable() {
1228                return false;
1229        }
1230}