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.List;
019import java.util.ArrayList;
020import java.awt.Graphics2D;
021import java.awt.geom.Rectangle2D;
022import org.jfree.ui.RectangleEdge;
023import org.jfree.text.TextBlock;
024import org.jfree.chart.axis.AxisState;
025import org.jfree.chart.axis.CategoryAxis;
026import org.jfree.chart.axis.CategoryAnchor;
027
028import org.opengion.fukurou.util.StringUtil;                                            // 6.2.0.0 (2015/02/27)
029
030/**
031 * HybsCategoryAxis は、CategoryAxis クラスを継承した、横軸管理クラスです。
032 * 横軸ラベルの表示制御を、主に行っています。
033 * 横軸表示には、3つの制御機能がカスタマイズされています。
034 *
035 *  1."_" ラベルのスキップ(非表示)
036 *  2.cutNo 属性による、ラベル文字位置指定のキーブレイク
037 *  3.skip 属性による、ラベルをスキップする間隔の指定
038 *
039 * 上記、1,2,3 の順番で優先的に処理されます。
040 *
041 * @version  0.9.0      2007/06/21
042 * @author       Kazuhiko Hasegawa
043 * @since        JDK1.1,
044 */
045public class HybsCategoryAxis extends CategoryAxis {
046        private static final long serialVersionUID = 519020100801L ;
047
048        private static final TextBlock NULL_LABEL = new TextBlock() ;
049
050        /**
051         * LabelVisible の状態を表す、enum 定義です。
052         *
053         * TRUE(true) , FALSE(false) , UNDER(true) を持っています。
054         *
055         * @og.rev 4.1.2.0 (2008/03/12) 新規追加
056         */
057        private enum LabelVisible { TRUE(true) , FALSE(false) , UNDER(true) ;
058                private final boolean flag ;
059
060                /**
061                 * enumのコンストラクタ
062                 *
063                 * @param       flag    boolean型の値
064                 */
065                LabelVisible( final boolean flag ) { this.flag = flag; }
066
067                /**
068                 * enum内部の値を、boolean 型に変換します。
069                 *
070                 * @return      boolean 型の値
071                 */
072                public boolean booleanValue() { return flag; }
073        };
074
075        /** For serialization. */
076        private int skip  = 1;                          // skip数
077        private int count       ;                               // skip 時の現在位置のカウント
078
079        private transient List<LabelVisible> labelBreak ;
080        private int             cutNo                   = -1;           // 4.1.1.0 (2008/02/04) ラベルブレイクのsubstring 位置
081        private String  breakKey                ;                       // 4.1.1.0 (2008/02/04) ラベルブレイクの前回キー
082        private boolean isLastVisible   ;                       // 4.1.2.0 (2008/03/12) 6.0.2.5 (2014/10/31) refactoring
083        private final int hsCode = Long.valueOf( System.nanoTime() ).hashCode() ;       // 5.1.9.0 (2010/08/01) equals,hashCode
084
085        /**
086         * 引数を指定して作成する コンストラクター
087         *
088         * skip(ラベルの表示間隔) = 1 , cutNo(ラベルブレイクのsubstring 位置) = -1 で初期化します。
089         *
090         * @param       label   ラベル
091         */
092        public HybsCategoryAxis( final String label ) {
093                this( label,1,-1 );
094        }
095
096        /**
097         * 引数を指定して作成する コンストラクター
098         *
099         * @og.rev 4.1.1.0 (2008/02/04) cutNo 新規追加
100         *
101         * @param       label   ラベル
102         * @param       skip    ラベルの表示間隔
103         * @param       cutNo   ラベルブレイクのsubstring 位置
104         */
105        protected HybsCategoryAxis( final String label,final int skip,final int cutNo ) {
106                super( label );
107                this.skip       = skip ;
108                this.cutNo      = cutNo ;
109        }
110
111        /**
112         * itemLabelVisible 時に、最後の値のみ表示するかどうか[true/false]を指定します。
113         *
114         * これは、itemLabelVisible 属性に、"last" という設定値を指定した場合は、
115         * 最後のみラベル表示します。
116         * このメソッドでは、true が指定された場合は、"last" 属性が有効になったと
117         * 判断します。
118         * (独自メソッド)
119         *
120         * @og.rev 4.1.2.0 (2008/03/12) 新規追加
121         *
122         * @param       flag            最後の値のみ表示するかどうか[true/false]
123         */
124        protected void setItemLabelLastVisible( final boolean flag ) {
125                isLastVisible = flag;                                   // 6.0.2.5 (2014/10/31) refactoring
126        }
127
128        /**
129         * 軸を引く場合、使用することができるチックの一時的リストを作成します。
130         *
131         * @og.rev 4.1.1.0 (2008/02/04) labelBreak 新規追加
132         *
133         * @param       g2                      Graphics2Dオブジェクト(フォント測定に使用)
134         * @param       state           AxisStateオブジェクト
135         * @param       dataArea        インサイドエリアを示すRectangle2Dオブジェクト
136         * @param       edge            ロケーションを指定するRectangleEdgeオブジェクト
137         *
138         * @return      チックのリスト
139         */
140        @Override
141        public List<?> refreshTicks( final Graphics2D g2,
142                                                          final AxisState state,
143                                                          final Rectangle2D dataArea,
144                                                          final RectangleEdge edge) {
145                count = 0;
146                labelBreak = new ArrayList<>();
147
148                return super.refreshTicks( g2, state, dataArea, edge);
149        }
150
151        /**
152         * TextBlock オブジェクトを作成します。
153         *
154         * このメソッドでは、3つの拡張機能を実現しています。
155         *  1."_" ラベルのスキップ(非表示)
156         *  2.cutNo 属性による、ラベル文字位置指定のキーブレイク
157         *  3.skip 属性による、ラベルをスキップする間隔の指定
158         *  4.改行コード(chr(10)、¥n)または、改行コード文字列('¥n'文字列)による、ラベルの段組指定 (6.4.9.2 (2016/08/19))
159         * cutNo が指定された場合は、skip 処理は行われません。また、
160         * その場合のラベルは、cutNoで指定された先頭文字列のみ表示されます。
161         * 文字列が、cutNoで指定された数より小さい場合は、そのまま使用されます。
162         *
163         * @og.rev 4.1.1.0 (2008/02/04) cutNo,labelBreak 追加
164         * @og.rev 4.1.2.0 (2008/03/12) LabelVisible.UNDER 処理を追加
165         * @og.rev 4.3.1.1 (2008/08/23) lbl の null参照はずしの対応
166         * @og.rev 6.4.9.2 (2016/08/19) 横軸ラベルの折り返し対応
167         *
168         * @param       category        カテゴリ名
169         * @param       width           幅
170         * @param       edge            表示範囲を示すRectangleEdgeオブジェクト
171         * @param       g2                      Graphics2Dオブジェクト
172         *
173         * @return      TextBlockオブジェクト
174         */
175        @SuppressWarnings("rawtypes")
176        @Override
177        protected TextBlock createLabel( final Comparable category, final float width, final RectangleEdge edge, final Graphics2D g2 ) {
178                TextBlock label = null ;
179                String    lbl   = null;
180                if( category instanceof String ) {                                      // 4.3.1.1 (2008/08/23) instanceof チェックは、nullチェック不要
181                        lbl = (String)category;
182                        if( StringUtil.startsChar( lbl , '_' ) ) {              // 6.2.0.0 (2015/02/27) 1文字 String.startsWith
183                                label = NULL_LABEL;
184                        }
185                }
186
187                if( cutNo > 0 && lbl != null ) {
188                        if( lbl.length() >= cutNo ) {
189                                lbl = lbl.substring( 0,cutNo );
190                        }
191
192                        if( ! lbl.equals( breakKey ) ) {
193                                // 6.4.9.2 (2016/08/19) 横軸ラベルの折り返し対応
194                                label = multiLabel( lbl, width, edge,  g2 );
195                                breakKey = lbl ;
196                        }
197                }
198                else {
199                        if( count % skip == 0 ) {
200                                // 6.4.9.2 (2016/08/19) 横軸ラベルの折り返し対応
201                                label = multiLabel( category, width, edge,  g2 );
202                        }
203                        count++;
204                }
205
206                if( label == null ) {
207                        label = NULL_LABEL;
208                        labelBreak.add( LabelVisible.FALSE );
209                }
210                else if( label.equals( NULL_LABEL ) ) {
211                        labelBreak.add( LabelVisible.UNDER );
212                }
213                else {
214                        labelBreak.add( LabelVisible.TRUE );
215                }
216
217                return label;
218        }
219
220        /**
221         * 複数行の横軸に対応した、TextBlock オブジェクトを作成します。
222         *
223         * ラベルに改行コードを含む場合、ラベルを段組(改行)します。
224         * 改行コードとは、本当の改行コード(ORACLEで言うところの、chr(10)か、
225         * 改行コード文字列('¥n'文字列)を認識します。
226         *
227         * @og.rev 6.4.9.2 (2016/08/19) 横軸ラベルの折り返し対応
228         *
229         * @param       category        カテゴリ名(ラベル)
230         * @param       width           幅
231         * @param       edge            表示範囲を示すRectangleEdgeオブジェクト
232         * @param       g2                      Graphics2Dオブジェクト
233         *
234         * @return      TextBlockオブジェクト
235         */
236        @SuppressWarnings("rawtypes")
237        private TextBlock multiLabel( final Comparable category, final float width, final RectangleEdge edge, final Graphics2D g2 ) {
238                final TextBlock label ;
239                // splitの関係上、文字列のみ処理します。
240                if( category instanceof String ) {
241                        final String lbl = (String)category;
242                        final String[] sp = lbl.split( "\\n|\\\\n" );
243                        label = super.createLabel( sp[0], width, edge,  g2 );           // 一つ目のラベル
244                        for( int i=1; i<sp.length; i++ ) {                                                      // 改行があれば、ここでループされる。
245                                final TextBlock lbl2 = super.createLabel( sp[i], width, edge,  g2 );
246                                label.addLine( lbl2.getLastLine() );                                    // フォント情報などを引き継ぐ
247                        }
248                }
249                else {
250                        label = super.createLabel( category, width, edge,  g2 );        // 文字列でなければ、そのまま使用する。
251                }
252
253                return label;
254        }
255
256        /**
257         * ラベルブレイクするかどうかを返します。
258         *
259         * skip または、cutNo によるラベルの間引き処理で、指定のCategoryAxis
260         * に対するカラム番号を指定する事で、判定値を返します。
261         * 処理が、Label の作成済みかどうかに依存する為、その判定を先に行います。
262         *
263         * @og.rev 4.1.1.0 (2008/02/04) 新規追加
264         *
265         * @param column カラム番号
266         *
267         * @return      ラベルブレイクするかどうか(true:する)
268         */
269        protected boolean isLabelBreak( final int column ) {
270                return labelBreak == null ||
271                                labelBreak.size() <= column ||
272                                labelBreak.get( column ).booleanValue() ;
273        }
274
275        /**
276         * ITEM ラベル(各データの設定値の説明用の値)を表示するかどうかを返します。
277         *
278         * ラベルの先頭に、アンダースコアがついたラベルは、ラベルの表示と
279         * ItemLabel の表示を抑止します。(false)
280         * それ以外のラベルは、表示する(true) を返します。
281         * 処理が、Label の作成済みかどうかに依存する為、その判定を先に行います。
282         *
283         * @og.rev 4.1.2.0 (2008/03/12) 新規追加
284         *
285         * @param       column  カラム番号
286         *
287         * @return      ITEMラベルを表示するかどうか(true:する)
288         */
289        protected boolean isViewItemLabel( final int column ) {
290                boolean flag = labelBreak == null ||
291                                                labelBreak.size() <= column ||
292                                                labelBreak.get( column ) != LabelVisible.UNDER ;
293
294                if( flag && isLastVisible && labelBreak.size() -1 != column ) {                 // 6.0.2.5 (2014/10/31) refactoring
295                        flag = false;
296                }
297
298                return flag;
299        }
300
301        /**
302         * ドメイン(横軸)のカテゴリ単位のライン(縦線)の描画位置を返します。
303         *
304         * この位置は、labelBreak が存在しないか、または、ブレークするときのみ
305         * 値を返します。これにより、ライン(縦線)の位置を、グラフの中心から
306         * ずらす事が可能になります。
307         * また、labelBreak により、ラベルを描画しない場合は、線の位置を、0 に
308         * 設定する事で、画面から見えなくします。
309         *
310         * @param       anchor                  CategoryAnchorオブジェクト
311         * @param       category                カテゴリ番号
312         * @param       categoryCount   カテゴリ数
313         * @param       area                    範囲を表すRectangle2Dオブジェクト
314         * @param       edge                    ロケーションを指定するRectangleEdgeオブジェクト
315         *
316         * @return      ライン(縦線)の描画位置
317         */
318        @Override
319        public double getCategoryJava2DCoordinate( final CategoryAnchor anchor,
320                                                                                           final int category,
321                                                                                           final int categoryCount,
322                                                                                           final Rectangle2D area,
323                                                                                           final RectangleEdge edge) {
324
325                final double result ;
326
327                // labelBreak が存在しないか、または、ブレークするときのみ値を返す。
328                if( isLabelBreak( category ) ) {
329                        result = super.getCategoryJava2DCoordinate(
330                                                        anchor,category,categoryCount,area,edge
331                                        ) ;
332                }
333                else {
334                        result = 0;
335                }
336                return result ;
337        }
338
339        /**
340         * この文字列と指定されたオブジェクトを比較します。
341         *
342         * 親クラスで、equals メソッドが実装されているため、警告がでます。
343         *
344         * @og.rev 5.1.8.0 (2010/07/01) findbug対応
345         * @og.rev 5.1.9.0 (2010/08/01) findbug対応
346         *
347         * @param       object  比較するオブジェクト
348         *
349         * @return      Objectが等しい場合は true、そうでない場合は false
350         */
351        @Override
352        public boolean equals( final Object object ) {
353                // 6.4.1.1 (2016/01/16) PMD refactoring. A method should have only one exit point, and that should be the last statement in the method
354                return super.equals( object ) && hsCode == ((HybsCategoryAxis)object).hsCode;
355        }
356
357        /**
358         * このオブジェクトのハッシュコードを取得します。
359         *
360         * @og.rev 5.1.8.0 (2010/07/01) findbug対応
361         * @og.rev 5.1.9.0 (2010/08/01) findbug対応
362         *
363         * @return      ハッシュコード
364         */
365        @Override
366        public int hashCode() { return hsCode ; }
367}