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.hayabusa.io; 017 018import static org.opengion.fukurou.util.HybsConst.CR ; // 6.1.0.0 (2014/12/26) 019import org.opengion.fukurou.util.Closer ; 020import org.opengion.fukurou.util.LogWriter; 021import org.opengion.fukurou.util.ColorMap; // 6.0.2.2 (2014/10/03) 022// import org.opengion.fukurou.db.DBUtil; // 6.0.4.0 (2014/11/28) 023import org.opengion.fukurou.db.ResultSetValue; // 6.0.4.0 (2014/11/28) 024// import org.opengion.hayabusa.common.HybsSystem; 025import org.opengion.hayabusa.db.DBTableModel; 026 027import java.sql.Connection; 028import java.sql.ResultSet; 029// import java.sql.ResultSetMetaData; // 6.0.4.0 (2014/11/28) 030import java.sql.SQLException; 031import java.sql.Statement; 032 033import java.util.List; 034import java.util.ArrayList; 035import java.util.Arrays; 036// import java.util.Locale; 037import java.util.Set; 038import java.util.HashSet; 039 040import java.awt.Color; // 6.0.2.2 (2014/10/03) 041 042import org.jfree.data.Range; 043import org.jfree.data.category.DefaultCategoryDataset; 044 045/** 046 * HybsCategoryDataset は、org.jfree.data.category.DefaultCategoryDataset を継承したサブクラスで、 047 * HybsDataset インターフェースの実装クラスになっています。 048 * これは、JDBCCategoryDatasetの データベース機能と、DBTableModel から Dataset を作成する機能を 049 * 兼ね備えています。 050 * HybsDataset インターフェースは、シリーズのラベル指定、カテゴリカラーバー、パレート図用積上げ 051 * 計算などの処理を行うための、インターフェースで、それらの処理も、HybsCategoryDataset に実装します。 052 * 053 * このクラスでは、検索結果を内部で持っておき、getValue(int row, int column) 054 * メソッドで直接値を返します。 055 * 056 * select category,series1,series2,series3,・・・ from ・・・ 057 * series の横持ち(標準と同じ) 対応です。 058 * category カラムの値は、カテゴリのラベルになり、series1,2,3 のラベルがシリーズラベル、値が 059 * seriesの値になります。 060 * 061 * カテゴリのカラー名の指定を行う場合、最後のカラムが、カラー名の文字列になります。 062 * select category,series1,series2,series3,・・・,color from ・・・ 063 * color文字列の検索結果は、Dataset には含まれません。 064 * 065 * その場合、color カラムがシリーズとして認識されない様に、ChartDatasetTag で、useCategoryColor="true" 066 * を指定しておく必要があります。このフラグは、HybsCategoryDataset を使う処理以外では効果が 067 * ありません(シリーズとして使用されてしまう)のでご注意ください。 068 * このフラグは、カテゴリカラーバーを使う場合には必要ですが、カテゴリカラーバーと(例えばパレート図) 069 * を合成する場合に、パレート図側にも useCategoryColor="true" を設定しておけば、同じSQL または、 070 * DBTableModel を使う事ができるというためのフラグです。 071 * 072 * なお、Colorコードは、このクラスで作成しますが、Renderer に与える必要があります。 073 * 通常のRenderer には、categoryにカラーを指定する機能がありませんので、HybsBarRenderer に 074 * setCategoryColor( Color[] ) メソッドを用意します。(正確には、HybsDrawItem インターフェース) 075 * このRenderer で、getItemPaint( int , int )メソッドをオーバーライドすることで、カテゴリごとの 076 * 色を返します。 077 * 078 * @og.rev 5.8.5.0 (2015/02/06) 6.0.2.2 (2014/10/03) からの逆移植 079 * 080 * @version 5.8.5.0 (2015/02/06) 081 * @author Kazuhiko Hasegawa 082 * @since JDK1.6, 083 */ 084public class HybsCategoryDataset extends DefaultCategoryDataset implements HybsDataset { 085 private static final long serialVersionUID = 602220141003L ; 086 087 private final Set<String> cateCheck = new HashSet<String>(); // category の重複チェック 088 private final int hsCode = Long.valueOf( System.nanoTime() ).hashCode() ; // 5.1.9.0 (2010/08/01) equals,hashCode 089 090 private String[] seriesLabels ; 091 private boolean isColorCategory ; // 6.0.2.2 (2014/10/03) 092 private boolean isParetoData ; // 6.0.2.2 (2014/10/03) 093 094 private Number[][] numdata ; 095 private Color[] categoryColor ; 096 private Range range ; 097 098 /** 099 * CategoryDataset を構築するに当たり、初期パラメータを設定します。 100 * 101 * @og.rev 6.0.2.2 (2014/10/03) 新規追加 102 * 103 * @param lbls シリーズのラベル名配列 104 * @param isColCate カテゴリのカラー名の指定有無(true:使用する) 105 * @param isPareto パレート図用のDatasetとして処理するかどうか(true:処理する) 106 */ 107 public void initParam( final String[] lbls , final boolean isColCate , final boolean isPareto ) { 108 // 6.0.2.5 (2014/10/31) refactoring 109// seriesLabels = lbls; 110 if( lbls != null ) { seriesLabels = lbls.clone(); } 111 isColorCategory = isColCate; 112 isParetoData = isPareto; 113 } 114 115 /** 116 * コネクションと、SQL文字列から、CategoryDataset のデータを作成します。 117 * 元となる処理は、org.jfree.data.jdbc.JDBCCategoryDataset#executeQuery( Connection,String ) です。 118 * 119 * このメソッドでは、先に #initParam(String[],boolean,isPareto) のパラメータを使用して 120 * 検索した結果のデータを加工、処理します。 121 * また、内部的に、データをキャッシュする事と、データ範囲を示す レンジオブジェクト を作成します。 122 * 123 * @og.rev 6.0.2.2 (2014/10/03) 新規追加 124 * @og.rev 6.0.2.3 (2014/10/19) パレート図は、100分率にする。 125 * @og.rev 6.0.4.0 (2014/11/28) ResultSetValue を使用するように変更。 126 * @og.rev 6.9.7.0 (2018/05/14) データ0件のときは、処理を中断します。 5.10.15.0(2019/08/30)で追加 127 * 128 * @param con コネクション 129 * @param query SQL文字列 130 * 131 * @throws SQLException データベースアクセス時のエラー 132 * @see org.jfree.data.jdbc.JDBCCategoryDataset#executeQuery( Connection,String ) 133 * @see org.opengion.fukurou.db.ResultSetValue 134 */ 135 public void execute( final Connection con, final String query ) throws SQLException { 136 137 // Range を予め求めておきます。 138 double minimum = Double.POSITIVE_INFINITY; 139 double maximum = Double.NEGATIVE_INFINITY; 140 double sum = 0.0d; // 6.0.2.3 (2014/10/19) パレート図用合計 141 142 List<Color> colorList = null; // 6.0.2.2 (2014/10/03) カテゴリカラー 143 144 Statement statement = null; 145 ResultSet resultSet = null; 146 try { 147 statement = con.createStatement(); 148 resultSet = statement.executeQuery(query); 149 150 // 6.0.4.0 (2014/11/28) ResultSetValue を使用するように変更。 151// ResultSetMetaData metaData = resultSet.getMetaData(); 152 final ResultSetValue rsv = new ResultSetValue( resultSet ); 153 154// int dataSize = metaData.getColumnCount() -1; // series の個数は、category 分を引いた数。 155 int dataSize = rsv.getColumnCount() -1; // series の個数は、category 分を引いた数。 156 if( isColorCategory ) { // ColorCategory使用時 157 colorList = new ArrayList<Color>(); // カテゴリカラー 158 dataSize--; // 最終カラムが Colorコードなので、マイナスする。 159 } 160 161 if( dataSize<1 ) { 162 final String errMsg = "JDBCCategoryDataset.executeQuery() : insufficient columns " 163 + "returned from the database. \n" 164 + " SQL=" + query ; 165 throw new SQLException( errMsg ); 166 } 167 168 // 6.0.2.0 (2014/09/19) シリーズのラベル名配列を使うときは、シリーズ数必要。 169 if( seriesLabels != null && seriesLabels.length < dataSize ) { 170 final String errMsg = "seriesLabels を使用する場合は、必ずシリーズ数以上指定してください。" 171 + CR 172 + " seriesLabels=" + Arrays.toString( seriesLabels ) 173 + CR 174 + " seriesLabels.length=" + seriesLabels.length 175 + " dataSize=" + dataSize 176 + CR ; 177 throw new IllegalArgumentException( errMsg ); 178 } 179 180 String[] series = new String[dataSize]; 181 // 6.0.4.0 (2014/11/28) ResultSetValue を使用するように変更。 182 final String[] names = rsv.getNames(); 183// int[] columnType = new int[dataSize]; 184 // ORACLEの引数は、配列+1から始まるので、metaDataはi+2から取得。series と、seriesLabels は0から始まる。 185 for( int i=0; i<dataSize; i++ ) { 186 series[i] = seriesLabels != null && seriesLabels[i] != null 187 ? seriesLabels[i] 188// : metaData.getColumnLabel(i+2).toUpperCase( Locale.JAPAN ); 189 : names[i+1] ; 190// columnType[i] = metaData.getColumnType(i+2); 191 } 192 193 final List<Number[]> rowList = new ArrayList<Number[]>(); 194 // 6.0.4.0 (2014/11/28) ResultSetValue を使用するように変更。 195// while (resultSet.next()) { 196 while (rsv.next()) { 197 Number[] clmList = new Number[dataSize]; 198 // first column contains the row key... 199 // 6.0.2.0 (2014/09/19) columnKeyは、series , rowKey は、category に変更する。 200// String category = resultSet.getString(1); // 4.3.3.6 (2008/11/15) Generics警告対応 201 final String category = uniqCategory( resultSet.getString(1) ); // 6.0.2.3 (2014/10/10) categoryの重複回避 202 203 for( int i=0; i<dataSize; i++ ) { // 6.0.2.2 (2014/10/03) dataSize 分回す。 204 Number value = null; 205 // 6.0.2.1 (2014/09/26) org.opengion.fukurou.db.DBUtil に、移動 206 try { 207 // JDBCのアドレス指定は、+2 する。(category 分と、アドレスが1から始まる為。) 208// value = DBUtil.getNumber( columnType[i],resultSet.getObject(i+2) ); 209 // ResultSetValueのカラム番号は、+1 する。(category 分があるため) 210 value = rsv.getNumber( i+1 ); 211 } 212 catch( SQLException ex ) { // 6.0.4.0 (2014/11/28) ResultSetValue を使用するので。 213 LogWriter.log( ex ); 214 } 215 catch( RuntimeException ex ) { 216 LogWriter.log( ex ); 217 } 218 219 clmList[i] = value; 220 addValue(value, series[i], category); // 6.0.2.0 (2014/09/19) columnKeyは、series , rowKey は、category に変更する。 221 // Range 求め 222 if( value != null ) { 223 final double dbl = value.doubleValue(); 224 if( isParetoData ) { // 6.0.2.3 (2014/10/19) パレート図用合計 225 sum += dbl ; 226 } else { 227 if( dbl < minimum ) { minimum = dbl; } 228 if( maximum < dbl ) { maximum = dbl; } 229 } 230 } 231 } 232 rowList.add( clmList ); 233 // 6.0.2.2 (2014/10/03) ColorCategory は、最後のカラム 234 if( isColorCategory ) { 235 // 6.0.4.0 (2014/11/28) ResultSetValue を使用するように変更。 236// String colStr = resultSet.getString(dataSize+2); // 最後のカラム 237 final String colStr = rsv.getValue(dataSize+1); // 最後のカラム 238 final Color color = ColorMap.getColorInstance( colStr ); // 6.0.2.1 (2014/09/26) StringUtil → ColorMap 239 colorList.add( color ); 240 } 241 } 242 // 6.9.7.0 (2018/05/14) データ0件のときは、処理を中断します。 243 if( rowList.isEmpty() ) { return; } 244 245 numdata = rowList.toArray( new Number[dataSize][rowList.size()] ); 246 } 247 finally { 248 Closer.resultClose( resultSet ); 249 Closer.stmtClose( statement ); 250 } 251 252 // colorList が null でないかどうかで判定。 253 if( isColorCategory && colorList != null ) { 254 categoryColor = colorList.toArray( new Color[colorList.size()] ); 255 } 256 257 // 6.0.2.3 (2014/10/19) パレート図は、100分率にする。 258// if( isParetoData ) { maximum = changeParetoData(); } 259 if( isParetoData ) { 260 changeParetoData( sum ); 261 minimum = 0.0; 262 maximum = 100.0; 263 } 264 265 range = new Range( minimum, maximum ); 266 } 267 268 /** 269 * DBTableModelオブジェクトから、CategoryDataset のデータを作成します。 270 * openGionの独自処理メソッドです。 271 * 272 * このメソッドでは、先に #initParam(String[],boolean,isPareto) のパラメータを使用して 273 * 検索した結果のデータを加工、処理します。 274 * また、内部的に、データをキャッシュする事と、データ範囲を示す レンジオブジェクト を作成します。 275 * 276 * @og.rev 6.0.2.2 (2014/10/03) 新規追加 277 * @og.rev 6.0.2.3 (2014/10/19) パレート図は、100分率にする。 278 * @og.rev 6.9.7.0 (2018/05/14) データ0件のときは、処理を中断します。 5.10.15.0(2019/08/30)追加 279 * 280 * @param table DBTableModelオブジェクト 281 * @see #execute( Connection,String ) 282 */ 283 public void execute( final DBTableModel table ) { 284 // 6.9.7.0 (2018/05/14) データ0件のときは、処理を中断します。 285 if( table == null || table.getRowCount() == 0 ) { return; } 286 287 final int clmNo = table.getColumnCount(); 288 final int rowNo = table.getRowCount(); 289 290 // Range を予め求めておきます。 291 double minimum = Double.POSITIVE_INFINITY; 292 double maximum = Double.NEGATIVE_INFINITY; 293 double sum = 0.0d; // 6.0.2.3 (2014/10/19) パレート図用合計 294 295 int dataSize = clmNo -1; // series の個数は、category 分を引いた数。 296 List<Color> colorList = null; // 6.0.2.2 (2014/10/03) カテゴリカラー 297 if( isColorCategory ) { // ColorCategory使用時 298 colorList = new ArrayList<Color>(); // カテゴリカラー 299 dataSize--; // 最終カラムが Colorコードなので、マイナスする。 300 } 301 302 numdata = new Number[rowNo][clmNo]; 303 304 // ※ DBTableModel の row,col と、Dataset の row,col は、逆になっています。 305 for( int row=0; row<rowNo; row++ ) { 306// String category = table.getValue( row,0 ); // 1番目(アドレス=0)はカラムの設定値 307 final String category = uniqCategory( table.getValue( row,0 ) ); // 6.0.2.3 (2014/10/10) categoryの重複回避 308 final String[] vals = table.getValues( row ); 309 for( int clm=0; clm<dataSize; clm++ ) { 310 final String sval = vals[clm+1]; // 2番目(アドレス=1)からカラムデータを取得 311 final double val = ( sval == null || sval.isEmpty() ) ? 0.0d : Double.parseDouble( sval ) ; 312 313 addValue( val , seriesLabels[clm] , category ); // val,row,clm 314// numdata[row][clm] = new Double( val ); 315 numdata[row][clm] = Double.valueOf( val ); // 6.0.2.4 (2014/10/17) 効率の悪いメソッド 316 // Range 求め 317 if( isParetoData ) { // 6.0.2.3 (2014/10/19) パレート図用合計 318 sum += val ; 319 } else { 320 if( val < minimum ) { minimum = val; } 321 if( maximum < val ) { maximum = val; } 322 } 323 } 324 325 // 6.0.2.2 (2014/10/03) ColorCategory は、最後のカラム 326 if( isColorCategory ) { 327 final String colStr = vals[dataSize+1]; // 最後のカラム 328 final Color color = ColorMap.getColorInstance( colStr ); // 6.0.2.1 (2014/09/26) StringUtil → ColorMap 329 colorList.add( color ); 330 } 331 } 332 333 // colorList が null でないかどうかで判定。 334 if( isColorCategory && colorList != null ) { 335 categoryColor = colorList.toArray( new Color[colorList.size()] ); 336 } 337 338 // 6.0.2.3 (2014/10/19) パレート図は、100分率にする。 339// if( isParetoData ) { maximum = changeParetoData(); } 340 if( isParetoData ) { 341 changeParetoData( sum ); 342 minimum = 0.0; 343 maximum = 100.0; 344 } 345 346 range = new Range( minimum, maximum ); 347 } 348 349 /** 350 * 指定された行列から、数字オブジェクトを取得します。 351 * 352 * @param row 行番号(シリーズ:横持=clm相当) 353 * @param column カラム番号(カテゴリ:縦持ち=row相当) 354 * 355 * @return 指定の行列の値 356 */ 357 @Override 358 public Number getValue( final int row, final int column ) { 359 // 注意:行列の順序が逆です。 360 return numdata[column][row]; 361 } 362 363 /** 364 * レンジオブジェクトを取得します。(独自メソッド) 365 * 366 * @return レンジオブジェクト 367 */ 368 public Range getRange() { 369 return range; 370 } 371 372 /** 373 * パレート図用のDatasetに値を書き換えます。(独自メソッド) 374 * 375 * 色々と方法はあると思いますが、簡易的に、内部の Number配列を 376 * 積上げ計算して、パレート図用のデータを作成します。 377 * レンジオブジェクト も変更します。 378 * 379 * ※ 注意:親クラスの内部に持っている実データは変更されていないので、 380 * 場合によっては、おかしな動きをするかもしれません。 381 * その場合は、上位にもデータをセットするように変更する必要があります。 382 * 383 * なお、行列の順序が、イメージと異なりますので、注意願います。 384 * (columnは、series , row は、category で、シリーズを積み上げます) 385 * 386 * @og.rev 6.0.2.1 (2014/09/26) 新規追加 387 * @og.rev 6.0.2.2 (2014/10/03) HybsDataset i/f 388 * @og.rev 6.0.2.3 (2014/10/19) パレート図は、100分率にする。 389 * 390 * @param sum データの合計 391 */ 392// private double changeParetoData() { 393 private void changeParetoData( final double sum ) { 394// if( numdata == null || numdata.length == 0 || numdata[0].length == 0 ) { return 0.0d; } 395 if( numdata == null || numdata.length == 0 || numdata[0].length == 0 || sum == 0.0 ) { return ; } 396 397 final int rowCnt = numdata[0].length ; 398 final int clmCnt = numdata.length ; 399 400// double maximum = Double.NEGATIVE_INFINITY; 401 for( int rowNo=0; rowNo<rowCnt; rowNo++ ) { // 行列が逆。 402 double val = 0.0; // 初期値 403 for( int clmNo=0; clmNo<clmCnt; clmNo++ ) { // 積上げ計算するカラムでループを回す。 404 final Number v1Num = numdata[clmNo][rowNo]; 405 if(v1Num != null) { 406 val += v1Num.doubleValue(); // 積上げ計算は、元の値のままにしておきます。 407 } 408 // データをセットするときに、100分率にします。 409// numdata[clmNo][rowNo] = new Double(val); 410 numdata[clmNo][rowNo] = Double.valueOf( Math.round( val * 1000.0 / sum ) / 10.0 ); 411 // きちんと計算するなら、BigDecimal で、スケールを指定して四捨五入すべき・・・かも 412 // java.math.BigDecimal bd = new BigDecimal( val * 100.0 / sum ); 413 // numdata[clmNo][rowNo] = bd.setScale( 1, java.math.RoundingMode.HALF_UP ); 414 } 415// if( maximum < val ) { maximum = val; } // パレート図用の積上げなので、最大値は、最後のデータ 416 } 417 418// return maximum; 419 } 420 421 /** 422 * categoryカラー配列を取得します。(独自メソッド) 423 * 424 * このクラスは、一番最後のカラムを、色文字列として処理し、categoryにColorを指定できます。 425 * select文で指定されていなかった場合は、null を返します。 426 * 427 * select category,series1,series2,series3,・・・,color from ・・・ 428 * 429 * @og.rev 6.0.2.2 (2014/10/03) 新規追加 430 * 431 * なお、Colorコードは、このクラスで作成しますが、Renderer に与える必要があります。 432 * 通常のRenderer には、categoryにカラーを指定する機能がありませんので、HybsBarRenderer に 433 * setCategoryColor( Color[] ) メソッドを用意します。(正確には、HybsDrawItem インターフェース) 434 * このRenderer で、getItemPaint( int , int )メソッドをオーバーライドすることで、カテゴリごとの 435 * 色を返します。 436 * この設定を行うと、シリーズは、カテゴリと同一色になります。 437 * 438 * @return categoryカラー配列(なければ null) 439 */ 440 public Color[] getCategoryColor() { 441 // 6.0.2.5 (2014/10/31) refactoring 442// return categoryColor; 443 return ( categoryColor == null ) ? null : categoryColor.clone(); 444 } 445 446 /** 447 * category の重複をさけて、必要であれば、新しいカテゴリ名を作成します。 448 * 449 * カテゴリが同じ場合、JFreeChartでは、表示されません。これは、同じカテゴリと認識され 450 * 値が上書きされるためです。 451 * この問題は、なかなか気づきにくく、デバッグ等に時間がかかってしまいます。 452 * 重複チェックを行い、警告してもよいのですが、ここでは、新しいカテゴリ名を作成することで 453 * エラーを回避しつつ、とりあえずグラフ表示をするようにします。 454 * 455 * @og.rev 6.0.2.3 (2014/10/10) 新規追加 456 * 457 * @param category 元のカテゴリ名 458 * @return 新しい元のカテゴリ名 459 */ 460 private String uniqCategory( final String category ) { 461 String newCate = category ; 462 int i = 0; 463 while( !cateCheck.add( newCate ) ) { // すでに存在している場合。 464 newCate = category + "(" + (i++) + ")" ; 465 } 466 467 return newCate ; 468 } 469 470 /** 471 * この文字列と指定されたオブジェクトを比較します。 472 * 473 * 親クラスで、equals メソッドが実装されているため、警告がでます。 474 * 475 * @og.rev 5.1.8.0 (2010/07/01) findbug対応 476 * @og.rev 5.1.9.0 (2010/08/01) findbug対応 477 * 478 * @param object 比較するオブジェクト 479 * 480 * @return Objectが等しい場合は true、そうでない場合は false 481 */ 482 @Override 483 public boolean equals( final Object object ) { 484 if( super.equals( object ) ) { 485 return hsCode == ((HybsCategoryDataset)object).hsCode; 486 } 487 return false; 488 } 489 490 /** 491 * このオブジェクトのハッシュコードを取得します。 492 * 493 * @og.rev 5.1.8.0 (2010/07/01) findbug対応 494 * @og.rev 5.1.9.0 (2010/08/01) findbug対応 495 * 496 * @return ハッシュコード 497 */ 498 @Override 499 public int hashCode() { return hsCode ; } 500}