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( "'","'" ); 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) 短縮ラベルなど、<span>タグが付与される値から、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) 短縮ラベルなど、<span>タグが付与される値から、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}