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}