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