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 java.util.concurrent.ConcurrentMap; // 7.0.1.2 (2018/11/04) 019import java.util.concurrent.ConcurrentHashMap; // 7.0.1.2 (2018/11/04) 020 021// import org.opengion.fukurou.system.HybsConst ; 022import static org.opengion.fukurou.system.HybsConst.BUFFER_MIDDLE ; // 8.0.0.0 (2021/08/31) 023import static org.opengion.fukurou.system.HybsConst.CR ; // 8.0.0.0 (2021/08/31) 024 025/** 026 * JsChartDataV3 は、JsChartDataV3 の個別属性を管理しているデータ管理クラスです。 027 * 028 * 内部には、data:datasets: の 要素の属性と、options:scales:[x/y]: の 要素の属性を管理します。 029 * chartColumn 、useAxis 属性は別管理で、ticks と、grid:属性 は、関連する属性を無効化します。 030 * datasetOptions と、yAxesOptions は、直接追加されますので、既存の属性をセットしている場合は、 031 * 動作保障できません。 032 * 033 * 8.0.0.0 (2021/08/31) V2 → V3 対応 034 * scaleLabel → title 035 * gridLines → grid 036 * options 追加 037 * plugins 追加(options:plugins:) 038 * annotations 追加(plugins:annotation:annotations:) 039 * 040 * @og.rev 5.9.17.2 (2017/02/08) 新規作成 041 * @og.rev 7.0.1.1 (2018/10/22) 大幅見直し 042 * @og.rev 8.0.0.0 (2021/08/31) Ver3対応 大幅見直し 043 * 044 * @version 8.0.0.0 045 * @author T.OTA 046 * @since JDK11.0 047 * 048 */ 049public class JsChartDataV3 { 050 /** チャート属性 {@value} */ public static final String DATASET = "dataset"; 051 /** チャート属性 {@value} */ public static final String AXIS = "axis"; 052 /** チャート属性 {@value} */ public static final String TICKS = "ticks"; 053 /** チャート属性 {@value} */ public static final String TIME = "time"; // X軸用 axis属性 054// /** チャート属性 {@value} */ public static final String SCALE_LABEL = "scaleLabel"; 055 /** チャート属性 {@value} */ public static final String TITLE = "title"; // 8.0.0.0 (2021/08/31) V2 → V3 対応 056// /** チャート属性 {@value} */ public static final String GRID_LINES = "gridLines"; 057 /** チャート属性 {@value} */ public static final String GRID = "grid"; // 8.0.0.0 (2021/08/31) V2 → V3 対応 058 059 /** チャート属性 {@value} */ public static final String OPTIONS = "options"; // 8.0.0.0 (2021/08/31) V2 → V3 対応 060 /** チャート属性 {@value} */ public static final String PLUGINS = "plugins"; // 8.0.0.0 (2021/08/31) V2 → V3 対応 061 /** チャート属性 {@value} */ public static final String ANNOTATIONS = "annotations"; // 8.0.0.0 (2021/08/31) V2 → V3 対応 062 063 private static final String CR_TAB = CR + "\t\t"; // 8.0.0.0 (2021/08/31) 064 065// final int MAX_LEN = SCALE_LABEL.length(); // 暫定的に最も長い文字列 066 067// private final String[] AXIS_OPTS = new String[] { TICKS,TIME,SCALE_LABEL,GRID_LINES } ; // 7.2.9.4 (2020/11/20) private 追加 068 private final String[] AXIS_OPTS = new String[] { TICKS,TIME,TITLE,GRID } ; // 8.0.0.0 (2021/08/31) V2 → V3 対応 069 070 private final ConcurrentMap<String,StringBuilder> charts = new ConcurrentHashMap<>(); // 7.0.1.2 (2018/11/04) チャート本体のバッファのMap (not null保障) 071// private final ConcurrentMap<String,StringBuilder> options = new ConcurrentHashMap<>(); // 7.0.1.2 (2018/11/04) オプションバッファのMap (not null保障) 072 073 private String chartColumn ; // チャートカラム 074 private String yid ; // yAxesIDに使用するキーとなるid ( yAxesID=yid+'Ax' ) 075 private boolean useAxis ; // y軸表示を行うかどうか(true/false) 076 private boolean useTime ; // x軸の時間表示を使用するかどうか。 077 078 private final StringBuilder errBuf = new StringBuilder(); // 8.0.0.0 (2021/08/31) 079 080// private final StringBuilder dataset = new StringBuilder( BUFFER_MIDDLE ); 081// private final StringBuilder axis = new StringBuilder( BUFFER_MIDDLE ); 082// private final StringBuilder ticks = new StringBuilder( BUFFER_MIDDLE ); // axis の属性 083// private final StringBuilder scLbl = new StringBuilder( BUFFER_MIDDLE ); // axis の属性 084// private final StringBuilder grdLine = new StringBuilder( BUFFER_MIDDLE ); // axis の属性 085// private final StringBuilder time = new StringBuilder( BUFFER_MIDDLE ); // axis の属性 086 087 /** 088 * デフォルトコンストラクター 089 * 090 * @og.rev 6.9.7.0 (2018/05/14) PMD Each class should declare at least one constructor 091 */ 092 public JsChartDataV3() { super(); } // これも、自動的に呼ばれるが、空のメソッドを作成すると警告されるので、明示的にしておきます。 093 094 /** 095 * チャートカラムを設定します。 096 * 097 * @param chartColumn チャートカラム 098 */ 099 public void setChartColumn( final String chartColumn ) { 100 this.chartColumn = chartColumn; 101// addDataset( "data" , chartColumn , true ); // オブジェクトなので、クオート処理しません。 102 } 103 104 /** 105 * JsChartDataV3 オブジェクトを作成する時のチャートカラムを取得します。 106 * 107 * @return チャートカラム 108 */ 109 public String getChartColumn() { 110 return chartColumn; 111 } 112 113 /** 114 * データチャートのIDを指定します。 115 * 116 * yAxisIDに使用するキーとなるid ( yAxisID=yid+'Ax' ) 117 * 118 * @og.rev 7.0.1.1 (2018/10/22) 属性の追加。 119 * 120 * @param id 固有の名前 121 */ 122 public void setId( final String id ) { 123 yid = id; 124 125 addAxis( "id" , yid + "Ax" , false ); 126 } 127 128 /** 129 * y軸表示を使用するかどうか(true/false)を設定します。 130 * 131 * 使用するとは、yAxisID属性を、内部的に登録します。 132 * 133 * @param flag true:使用する/false:使用しない 134 */ 135 public void setUseAxis( final boolean flag ) { 136 useAxis = flag; 137 } 138 139 /** 140 * y軸表示を使用するかどうか(true/false)を設定します。 141 * 142 * @return true:使用する/false:使用しない 143 */ 144 public boolean isUseAxis() { 145 return useAxis; 146 } 147 148 /** 149 * x軸の時間表示を使用するかどうか(true/false)を設定します。 150 * 151 * 使用しない場合は、time バッファーを axis 属性に追加しません。 152 * 153 * @param flag true:使用する/false:使用しない 154 */ 155 public void setUseTime( final boolean flag ) { 156 useTime = flag; 157 } 158 159 /** 160 * キーと設定値をdatasetに追加します。 161 * 162 * @param key キー 163 * @param val 設定値 164 * @param isNum 数値項目/boolean項目かどうか(true:数値要素/false:文字または配列要素) 165 */ 166 public void addDataset( final String key , final String val , final boolean isNum ) { 167 addBuffer( DATASET,key,val,isNum ); 168 } 169 170 /** 171 * キーと設定値をaxisに追加します。 172 * 173 * ※ chartJS上は、Axes(axisの複数形)と、Axis を使い分けていますが、属性は、axis で統一します。 174 * 175 * @param key キー 176 * @param val 設定値 177 * @param isNum 数値項目かどうか(true:数値要素/false:文字または配列要素) 178 */ 179 public void addAxis( final String key , final String val , final boolean isNum ) { 180 addBuffer( AXIS,key,val,isNum ); 181 } 182 183 /** 184 * 設定値をaxisに追加します。 185 * 186 * これは、chartsバッファに、bufKey 毎のバッファに、引数をそのまま追加します。 187 * 188 * @og.rev 8.0.0.0 (2021/08/31) 新規作成 189 * 190 * @param bufKey 追加するバッファのキー 191 * @param val 設定値 192 */ 193 public void addAxis( final String bufKey , final String val ) { 194 if( val != null && val.length() > 0 ) { 195 // チャート本体のバッファに追加していきます。 196 final StringBuilder buf = charts.computeIfAbsent( bufKey , k -> new StringBuilder( BUFFER_MIDDLE ) ); 197 // 登録時の同一キーワードチェック 198 final int st = val.indexOf( ':' ); 199 if( st > 0 && buf.indexOf( val.substring( 0,st+1 ) ) >= 0 ) { // キーチェックは、':' も含めて確認する 200 errBuf.append( "addAxisで登録された" ).append( bufKey ) 201 .append( "属性は、chartsに設定済みです。" ).append( CR ) 202 .append( "val=" ).append( val ).append( CR ); 203 } 204 else { 205 buf.append( val ).append( ',' ); 206 } 207 } 208 } 209 210 /** 211 * キーと設定値をaxisのticks に追加します。 212 * 213 * @param key キー 214 * @param val 設定値 215 * @param isNum 数値項目かどうか(true:数値要素/false:文字または配列要素) 216 */ 217 public void addTicks( final String key , final String val , final boolean isNum ) { 218 addBuffer( TICKS,key,val,isNum ); 219 } 220 221 /** 222 * キーと設定値をaxisのtime に追加します。 223 * 224 * @param key キー 225 * @param val 設定値 226 * @param isNum 数値項目かどうか(true:数値要素/false:文字または配列要素) 227 */ 228 public void addTime( final String key , final String val , final boolean isNum ) { 229 addBuffer( TIME,key,val,isNum ); 230 } 231 232 /** 233 * キーと設定値をplugins に追加します。 234 * 235 * @og.rev 8.0.0.0 (2021/08/31) 新規作成 236 * 237 * @param key キー 238 * @param val 設定値 239 * @param isNum 数値項目かどうか(true:数値要素/false:文字または配列要素) 240 */ 241 public void addPlugins( final String key , final String val , final boolean isNum ) { 242 addBuffer( PLUGINS,key,val,isNum ); 243 } 244 245 /** 246 * キーと設定値をannotations に追加します。 247 * 248 * @og.rev 8.0.0.0 (2021/08/31) 新規作成 249 * 250 * @param key キー 251 * @param val 設定値 252 * @param isNum 数値項目かどうか(true:数値要素/false:文字または配列要素) 253 */ 254 public void addAnnotations( final String key , final String val , final boolean isNum ) { 255 addBuffer( ANNOTATIONS,key,val,isNum ); 256 } 257 258 /** 259 * キーと設定値を指定のバッファーに追加します。 260 * 261 * isNum=true か、内部で、先頭文字が、'[' か '{' の場合は、クオーテーションを付けません。 262 * また、引数が、nullか、空文字列の場合は、追加しません。 263 * 264 * @param bufKey 追加するバッファのキー 265 * @param key キー 266 * @param val 設定値 267 * @param isNum 数値項目かどうか(true:数値要素/false:文字または配列、オブジェクト要素) 268 */ 269 private void addBuffer( final String bufKey , final String key , final String val , final boolean isNum ) { 270 if( val != null && !val.trim().isEmpty() ) { 271 final String val2 = val.trim(); 272 273 // チャート本体のバッファに追加していきます。 274 final StringBuilder buf = charts.computeIfAbsent( bufKey , k -> new StringBuilder( BUFFER_MIDDLE ) ); 275 276 // 登録時の同一キーワードチェック 277 if( buf.indexOf( key+':' ) >= 0 ) { // キーチェックは、':' も含めて確認する 278 errBuf.append( "addBufferで登録された" ).append( bufKey ).append( ':' ).append( key ) 279 .append( "属性は、chartsに設定済みです。" ).append( CR ) 280 .append( "key:val=" ).append( key ).append( ':' ).append( val2 ).append( CR ); 281 } 282 else { 283 // bufKey が DATASET とAXIS の場合は、40文字単位で改行する。 284 if( DATASET.equals( bufKey ) || AXIS.equals( bufKey ) ) { 285 if( buf.length() - buf.lastIndexOf( CR ) > 40 ) { 286 buf.append( CR_TAB ); 287 } 288 } 289 290 if( isNum || '[' == val2.charAt(0) || '{' == val2.charAt(0) ) { 291 buf.append( key ).append( ':' ).append( val2 ).append( ',' ); 292 } 293 else { 294 buf.append( key ).append( ":'" ).append( val2 ).append( "'," ); 295 } 296 } 297 } 298 } 299 300// /** 301// * 指定のバッファーに、オプション属性を追加します。 302// * 303// * オプション属性は、各バッファーの一番最後にまとめて追加します。 304// * key:val の関係ではなく、val だけをそのまま追加していきます。 305// * オプションの追加は、まとめて最後に行いますので、このメソッド上では 306// * 最後にカンマは付けません。必要であれば、追加する設定値にカンマをつけてください。 307// * 308// * @og.rev 8.0.0.0 (2021/08/31) コメント修正(scaleLabel→title , gridLines→grid) 309// * 310// * @param bufKey キー [dataset,axis,ticks,time,scaleLabel,gridLines] が指定可能 311// * @param val 設定値 312// */ 313// public void addOptions( final String bufKey , final String val ) { 314// if( val != null && val.length() > 0 ) { 315// // オプション専用のバッファに追加していきます。 316// // これは、チャート本体のバッファに対して、最後に追加する必要があるためです。 317//// options.computeIfAbsent( bufKey , k -> new StringBuilder( BUFFER_MIDDLE ) ) 318//// .append( val ).append( ',' ); 319// 320// // オプション専用のバッファに追加していきます。 321// final StringBuilder buf = options.computeIfAbsent( bufKey , k -> new StringBuilder( BUFFER_MIDDLE ) ); 322// 323// // 登録時の同一キーワードチェック 324// final int st = val.indexOf( ':' ); 325// if( st > 0 && buf.indexOf( val.substring( 0,st+1 ) ) >= 0 ) { // キーチェックは、':' も含めて確認する 326// errBuf.append( "addOptionsで登録された" ).append( bufKey ) 327// .append( "属性は、options に設定済みです。" ).append( CR ) 328// .append( "val=" ).append( val ).append( CR ); 329// } 330// else { 331// buf.append( val ).append( ',' ).append( CR_TAB ); 332// } 333// } 334// } 335 336// /** 337// * キーと設定値をoptions に追加します。 338// * 339// * @og.rev 8.0.0.0 (2021/08/31) 新規作成 340// * 341// * @param key キー 342// * @param val 設定値 343// * @param isNum 数値項目かどうか(true:数値要素/false:文字または配列要素) 344// */ 345// public void addOptions( final String key , final String val , final boolean isNum ) { 346// if( val != null && !val.trim().isEmpty() ) { 347// final String val2 = val.trim(); 348// 349// // チャート本体のバッファに追加していきます。 350// final StringBuilder buf = options.computeIfAbsent( key , k -> new StringBuilder( BUFFER_MIDDLE ) ); 351// 352// // 登録時の同一キーワードチェック 353// if( buf.indexOf( key+':' ) >= 0 ) { // キーチェックは、':' も含めて確認する 354// errBuf.append( "addOptionsで登録された" ).append( key ) 355// .append( "属性は、optionsに設定済みです。" ).append( CR ) 356// .append( "key:val=" ).append( key ).append( ':' ).append( val2 ).append( CR ); 357// } 358// else { 359// if( isNum || '[' == val2.charAt(0) || '{' == val2.charAt(0) ) { 360// buf.append( key ).append( ':' ).append( val2 ).append( ',' ) ; 361// } 362// else { 363// buf.append( key ).append( ":'" ).append( val2 ).append( "'," ) ; 364// } 365// } 366// } 367// } 368 369 /** 370 * バッファキー内に、設定キーの値がすでに登録済みかどうか(あればtrue)を判定します。 371 * 372 * 一般とオプションの両方を検索します。 373 * 374 * @og.rev 7.0.1.3 (2018/11/12) バッファキー検索処理追加 375 * 376 * @param bufKey チェックするバッファのキー 377 * @param key キー 378 * @return すでに登録済みかどうか [true:登録済み/false:未登録] 379 */ 380 public boolean contains( final String bufKey , final String key ) { 381 boolean isContains = false; 382 383 final StringBuilder chBuf = charts.get( bufKey ); 384 if( chBuf != null && chBuf.indexOf( key ) >= 0 ) { isContains = true; } 385// else { 386// final StringBuilder optBuf = options.get( bufKey ); 387// if( optBuf != null && optBuf.indexOf( key ) >= 0 ) { isContains = true; } 388// } 389 390 return isContains ; 391 } 392 393 /** 394 * JsChartDataV3 オブジェクトのdata:datasets: パラメータ情報を取得します。 395 * 396 * ここで返す値は、yidが、'y0' とすると、 397 * const y0Ds = { dataset.toString() } ; という文字列を返します。 398 * 引数は、'x' か 'y' を指定します。 399 * 通常、Y軸表示を行う場合は、'y' を指定しまが、horizontalBar 使用時は、 400 * 'x' を指定することになります。 401 * ただし、useAxis=false の場合は、(x,y)AxisID は出力されません。 402 * 403 * @og.rev 7.0.1.1 (2018/10/22) data:datasets: パラメータ情報 404 * 405 * @param xy idのキーワード [x,y] 406 * @return パラメータ文字列 407 */ 408 public String getDataset( final char xy ) { 409 // チャート本体のバッファから取得します。 410 final StringBuilder dataset = charts.computeIfAbsent( DATASET , k -> new StringBuilder( BUFFER_MIDDLE ) ); 411 412 // chartColumn は linear の場合、名前が変更されるので、出力の直前にセッティングします。 413 dataset.append( "data:" ).append( chartColumn ).append( ',' ) ; 414 415 // 8.0.0.0 (2021/08/31) yidの初期値(1つ目)は、AxisID を出さない。 416// if( useAxis && dataset.indexOf( "AxisID:" ) < 0 ) { 417 if( useAxis && dataset.indexOf( "AxisID:" ) < 0 && !"y0".equals( yid ) ) { 418 dataset.append( xy ).append( "AxisID:'" ).append( getAxisKey() ).append( "'," ); 419 } 420 421 return new StringBuilder( BUFFER_MIDDLE ) 422// .append( "var " ).append( getDatasetKey() ).append( "={" ) 423 .append( "const " ).append( getDatasetKey() ).append( "={" ) 424 .append( dataset ) 425// .append( mapGet( options , DATASET ) ) // オプション専用のバッファ 426 .append( CR ).append( "\t};" ).toString(); 427 } 428 429 /** 430 * JsChartDataV3 オブジェクトのdata:datasets: パラメータ情報の変数名を取得します。 431 * 432 * ここで返す値は、yidが、'y0' とすると、 433 * "y0Ds" という文字列を返します。 434 * 435 * @og.rev 7.0.1.1 (2018/10/22) data:datasets: パラメータ変数名 436 * 437 * @return パラメータ文字列 438 */ 439 public String getDatasetKey() { 440 return yid + "Ds" ; 441 } 442 443 /** 444 * JsChartDataV3 オブジェクトのoptions:scales:yAxes: パラメータ情報を取得します。 445 * 446 * ここで返す値は、yidが、'y0' とすると、 447 * const y0Ax = { addAxis.toString() } ; という文字列を返します。 448 * ただし、useAxis=false の場合は、ゼロ文字列を返します。 449 * 450 * @og.rev 7.0.1.1 (2018/10/22) options:scales:yAxes: パラメータ情報 451 * 452 * @return パラメータ文字列 453 */ 454 public String getAxis() { 455 // チャート本体のバッファから取得します。 456 final StringBuilder axis = charts.computeIfAbsent( AXIS , k -> new StringBuilder( BUFFER_MIDDLE ) ); 457 458 // AXISのオプションである、TICKS,TIME,TITLE,GRID を追加します。 459 // これらは、チャート本体とオプション専用のバッファから取得しますが、オプション専用バッファは最後に追加します。 460 for( final String opt : AXIS_OPTS ) { 461 // 超特殊処理:useTime=false のときは、TIME は、処理しません。 462 if( !useTime && TIME.equals( opt ) ) { continue; } 463 464 final String key = opt + ":{" ; 465// if( axis.indexOf( key ) < 0 && ( charts.containsKey( opt ) || options.containsKey( opt ) ) ) { 466 if( axis.indexOf( key ) < 0 && charts.containsKey( opt ) ) { 467 axis.append( CR_TAB ).append( key ) 468 .append( mapGet( charts , opt ) ) // チャート本体のバッファ 469// .append( mapGet( options , opt ) ) // オプション専用のバッファ 470 .append( "}," ); 471 } 472 } 473 474 return new StringBuilder( BUFFER_MIDDLE ) 475// .append( "var " ).append( getAxisKey() ).append( "={" ) 476 .append( "const " ).append( getAxisKey() ).append( "={" ) 477 .append( axis ) 478// .append( mapGet( options , AXIS ) ) // オプション専用のバッファ 479 .append( CR ).append( "\t};" ).toString(); 480 } 481 482 /** 483 * JsChartDataV3 オブジェクトのoptions:scales:yAxes: パラメータ情報の変数名を取得します。 484 * 485 * ここで返す値は、yidが、'y0' とすると、 486 * "y0Ax ," という文字列を返します。便宜上、後ろのコロンも追加しています。 487 * その際、useAxis=false の場合は、空文字列を返します。 488 * ※ chartJS上は、Axes(axisの複数形)と、Axis を使い分けていますが、属性は、axis で統一します。 489 * 490 * @og.rev 7.0.1.1 (2018/10/22) options:scales:yAxes:パラメータ情報の変数名 491 * 492 * @return パラメータ文字列 493 */ 494 public String getAxisKey() { 495 return yid + "Ax" ; 496 } 497 498 /** 499 * MapのStringBuilderがnullなら、ゼロ文字列を、そうでなければ、StringBuilder#toString() 500 * の値を返します。 501 * 502 * map.getOrDefault( KEY , new StringBuilder() ) ).toString() 503 * という処理の簡易版です。 504 * 505 * final StringBuilder buf = map.get( KEY ); 506 * return buf == null || buf.length() == 0 ? "" : buf.toString(); 507 * 508 * @og.rev 7.0.1.2 (2018/11/04) 新規登録 509 * 510 * @param map 判定するMap 511 * @param key Mapから取り出すキー 512 * @return MapにStringBuilderがあれば、#toString()を、無ければ、ゼロ文字列を返します。 513 */ 514// private String nval( final ConcurrentMap<String,StringBuilder> map , final String key ) { 515 private String mapGet( final ConcurrentMap<String,StringBuilder> map , final String key ) { 516 final StringBuilder buf = map.get( key ); 517 return buf == null || buf.length() == 0 ? "" : buf.toString(); 518 } 519 520 /** 521 * エラーメッセージを返します。 522 * 523 * エラーが存在しなかった場合は、長さゼロの文字列になります。 524 * 525 * @og.rev 8.0.0.0 (2021/08/31) 新規作成 526 * 527 * @return エラーメッセージの内部バッファを文字列にして返します。 528 * @og.rtnNotNull 529 */ 530 public String getErrorMessage() { 531 return errBuf.toString(); 532 } 533 534 /** 535 * 内部バッファを文字列にして返します。 536 * 537 * @return 内部バッファを文字列にして返します。 538 * @og.rtnNotNull 539 */ 540 @Override 541 public String toString() { 542 final StringBuilder buf = new StringBuilder( BUFFER_MIDDLE ) 543 .append( "chartColumn=" ).append( chartColumn ).append( CR ) 544 .append( "datasetKey =" ).append( getDatasetKey() ).append( CR ) 545 .append( "axisKey =" ).append( getAxisKey() ).append( CR ); 546 547 charts.forEach( (k,v) -> buf.append( k ).append( " = " ).append( v ).append( CR ) ); 548// options.forEach( (k,v) -> buf.append( k ).append( " opt = " ).append( v ).append( CR ) ); 549 550 return buf.toString(); 551 } 552}