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}