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}