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