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