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