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 org.jfree.chart.renderer.category.LineAndShapeRenderer;
019import org.jfree.chart.renderer.category.CategoryItemRendererState;
020import org.jfree.ui.RectangleEdge;
021
022import java.awt.Graphics2D;
023import java.awt.Shape;
024import java.awt.Color;
025import java.awt.Paint;
026import java.awt.Stroke;
027import java.awt.geom.Line2D;
028import java.awt.geom.Rectangle2D;
029import java.io.IOException;
030import java.io.ObjectOutputStream;
031import java.io.ObjectInputStream;
032
033import org.jfree.chart.axis.CategoryAxis;
034import org.jfree.chart.axis.ValueAxis;
035import org.jfree.chart.entity.EntityCollection;
036import org.jfree.chart.plot.CategoryPlot;
037import org.jfree.chart.plot.PlotOrientation;
038import org.jfree.data.category.CategoryDataset;
039import org.jfree.data.general.DatasetUtilities;
040import org.jfree.data.Range;
041import org.jfree.util.ShapeUtilities;
042
043import java.awt.geom.AffineTransform;
044
045/**
046 * HybsLineRenderer は、org.jfree.chart.renderer.category.LineAndShapeRenderer を
047 * 拡張したカスタマイズクラスです。
048 * これは、描画に対して、予め制限を設けて、処理速度の向上を図っています。
049 *
050 * @og.rev 3.8.9.2 (2007/07/28) 新規作成
051 *
052 * @version  0.9.0  2001/05/05
053 * @author   Kazuhiko Hasegawa
054 * @since    JDK1.1,
055 */
056public class HybsLineRenderer extends LineAndShapeRenderer implements HybsDrawItem {
057        private static final long serialVersionUID = 519020100801L ;
058
059        private transient ValueMarkOverColors overColors = null;        // 4.0.3.0 (2008/01/07) マーカーラインでShapeを切り替える時の色指定
060
061        private Color[] shapeColors      = null;                // 4.0.3.0 (2008/01/07) データ毎にShapeを切り替える時の色指定
062        private double  visibleLimit = Double.NEGATIVE_INFINITY;
063        private int             dynamicOCNo  = -1;                      // 4.1.1.0 (2008/02/04) 動的なマーカーラインの基準シリーズ番号
064        private String  shapeScale       = null;                // 4.1.1.0 (2008/02/04) shapeの大きさの倍率
065        private boolean isItemLabelLastVisible = false; // 4.1.2.0 (2008/03/12)
066        private final int hsCode = Long.valueOf( System.nanoTime() ).hashCode() ;               // 4.3.1.1 (2008/08/23)
067
068        /**
069         * Creates a renderer with both lines and shapes visible by default.
070         */
071        public HybsLineRenderer() {
072                super(true, true);
073        }
074
075        /**
076         * Creates a new renderer with lines and/or shapes visible.
077         *
078         * @param lines  draw lines?
079         * @param shapes  draw shapes?
080         */
081        public HybsLineRenderer( final boolean lines, final boolean shapes ) {
082                super(lines,shapes);
083        }
084
085        /**
086         * データ毎にShapeを切り替える時の色の繰返しパターンを文字列配列で指定します。
087         *
088         * HybsLine でのみ使用可能です。
089         * これは、データそのものが、繰返し性のある場合に、その繰返し性に対応した
090         * 形状のShape を表示させる場合に使用します。
091         * 繰返しShapeの形状は、JFreeChart のシリーズ毎の繰返し標準形状を使用します。
092         * 現在のバージョンでは、10個までの繰返しに対応可能です。
093         * 繰返し色を、指定した分だけ、順に使用されていきます。
094         *
095         * 指定文字列は、java.awt.Color クラスのstatic フィールド名で指定します。
096         * BLACK , BLUE , CYAN , DARK_GRAY , GRAY , GREEN , LIGHT_GRAY ,
097         * MAGENTA , ORANGE , PINK , RED , WHITE , YELLOW , (PURPLE) が指定できます。
098         * また、#XXXXXX形式の16bitRGB表記 でも指定可能です。
099         *
100         * @og.rev 4.0.3.0 (2008/01/07) 新規追加
101         *
102         * @param       colors  データ毎の色の繰返しパターン(文字列配列)
103         * @see         java.awt.Color#BLACK
104         */
105        protected void setShapeColors( final Color[] colors ) {
106                shapeColors = colors;
107        }
108
109        /**
110         * shapeの大きさを倍率指定で変更します(初期値:null)。
111         *
112         * ラインチャートのShape(各グラフのポイントのマーカー)の大きさは、通常は、
113         * 自動設定されます。
114         * この大きさを、倍率指定で、変更可能です。
115         * 指定は、double 型です。
116         * 初期値は、null は、スケール変更しません(自動設定のままの大きさ)
117         *
118         * @og.rev 4.1.1.0 (2008/02/04) 新規追加
119         *
120         * @param       scale   shapeの大きさの倍率
121         */
122        protected void setShapeScale( final String scale ) {
123                shapeScale = scale;
124        }
125
126        /**
127         * マーカーラインの超過時のShape色管理クラスを設定します。
128         *
129         * 動的なマーカーラインを使用する場合は、引数のシリーズデータが
130         * マーカーラインの最下位閾値に相当します。これは、グラフ化されますが、
131         * Shape は自動的に削除されます。
132         * 逆に、最上位のデータ(シリーズ=0)のShape は必ず付けます。
133         *
134         * @og.rev 4.1.0.1(2008/01/19) 新規追加
135         * @og.rev 4.1.1.0 (2008/02/04) 動的なオーバーカラー
136         *
137         * @param       vmoc    マーカーラインの超過時のShape色管理クラス
138         * @param       dynamicOverColorNo      動的なマーカーラインの基準シリーズ番号
139         */
140        protected void setValueMarkOverColors( final ValueMarkOverColors vmoc,
141                                                                                final int dynamicOverColorNo ) {
142                overColors      = vmoc;
143                dynamicOCNo     = dynamicOverColorNo;
144        }
145
146        /**
147         * 表示下限値(これ以下のデータは未表示)の値(double)を指定します。
148         *
149         * HybsLine でのみ使用可能です。
150         * この設定値以下のデータは、存在しない扱いとします。
151         * Lineを引くとき、このデータと、存在しているデータ間にラインは引かれません。
152         * 何も指定しない場合は、設定しません。
153         *
154         * @og.rev 4.0.3.0 (2008/01/07) 新規追加
155         *
156         * @param       limit   表示下限値(これ以下のデータは未表示)
157         */
158        protected void setVisibleLimit( final double limit ) {
159                visibleLimit = limit;
160        }
161
162        /**
163         * itemLabelVisible 時に、最後の値のみ表示するかどうか[true:有効/false:無効]を指定します。
164         *
165         * これは、itemLabelVisible 属性に、"last" という設定値を指定した場合は、
166         * 最後のみラベル表示します。
167         * このメソッドでは、true が指定された場合は、"last" 属性が有効になったと
168         * 判断します。
169         *
170         * @og.rev 4.1.2.0 (2008/03/12) 新規追加
171         *
172         * @param       flag    最後の値のみ表示するかどうか[true:有効/false:無効]
173         */
174        public void setItemLabelLastVisible( final boolean flag ) {
175                isItemLabelLastVisible = flag;
176        }
177
178        /**
179         * drawItem と同等の機能を持った、高速版メソッドです。
180         *
181         * @og.rev 4.0.3.0 (2008/01/07) shapeColors 属性追加
182         * @og.rev 4.1.1.0 (2008/02/04) 繰返しshapeの開始番号(shapeStartNo)追加
183         * @og.rev 4.1.1.0 (2008/02/04) seriesColors 属性は、色(Paint)のみ切り替えるようにする。
184         * @og.rev 4.1.1.0 (2008/02/04) ラベルブレイク機能の追加(HybsCategoryAxis)
185         * @og.rev 4.1.1.0 (2008/02/04) 動的なマーカーライン
186         * @og.rev 4.1.1.0 (2008/02/22) Stroke を設定するロジックを追加
187         * @og.rev 4.1.2.0 (2008/03/12) ラベルのアンダーライン時にItemLavelを表示しない
188         *
189         * @param g2                    Graphics2Dオブジェクト
190         * @param state                 CategoryItemRendererStateオブジェクト
191         * @param dataArea              Rectangle2Dオブジェクト
192         * @param plot                  CategoryPlotオブジェクト
193         * @param domainAxis    CategoryAxisオブジェクト
194         * @param rangeAxis             ValueAxisオブジェクト
195         * @param dataset               CategoryDatasetオブジェクト
196         * @param serNo                 シリアル番号
197         */
198        public void drawItem2( final Graphics2D g2, final CategoryItemRendererState state,
199                        final Rectangle2D dataArea, final CategoryPlot plot, final CategoryAxis domainAxis,
200                        final ValueAxis rangeAxis, final CategoryDataset dataset, final int serNo ) {
201
202                int clmCount = dataset.getColumnCount();
203                int rowCount = dataset.getRowCount();
204                RectangleEdge domEdge   = plot.getDomainAxisEdge();
205                RectangleEdge rangeEdge = plot.getRangeAxisEdge();
206
207                boolean isShape = getBaseShapesVisible() ;
208
209                HybsCategoryAxis hybsAxis = null;
210                if( domainAxis instanceof HybsCategoryAxis ) {
211                        hybsAxis = (HybsCategoryAxis)domainAxis;
212                        hybsAxis.setItemLabelLastVisible( isItemLabelLastVisible );
213                }
214
215                // データ毎にShapeを切り替える時の色の繰返しパターン
216                int shpCnt = ( shapeColors == null ) ? 1 : shapeColors.length;
217
218                // Shape の形状を指定できる。任意ではなく、表示順の開始位置の指定
219                int shapeNo = 0 ;
220
221                // shapeの大きさの倍率
222                AffineTransform transform = null;
223                if( shapeScale != null ) {
224                        double scale = Double.parseDouble( shapeScale );
225                        transform = AffineTransform.getScaleInstance( scale, scale );
226                }
227
228                // 4.1.1.0 (2008/02/22) Stroke を設定するロジックを追加
229                Stroke baseStroke = getBaseStroke();
230                if( baseStroke != null ) { g2.setStroke( baseStroke ); }
231
232                // トリッキー:row == serNo を処理したいがために、1回余分にループをまわす。
233                for( int row=0; row<=rowCount; row++ ) {
234                        if( row == serNo ) { continue; }        // Mis Add 2007/07/23
235                        if( row >= rowCount ) {
236                                if( serNo >= 0 ) {
237                                        row = serNo;
238                                        rowCount = -1;          // 終了条件
239                                }
240                                else {
241                                        break;
242                                }
243                        }
244
245                        // 4.1.1.0 (2008/02/22) Stroke を設定するロジックを追加
246                        Stroke serStroke = getSeriesStroke( row );
247                        if( serStroke != null ) { g2.setStroke( serStroke ); }
248
249                        Paint rowPaint = lookupSeriesPaint( row );
250                        Shape rowShape = lookupSeriesShape( shapeNo );
251
252                        // shapeの大きさの倍率
253                        if( transform != null ) {
254                                rowShape = transform.createTransformedShape(rowShape);
255                        }
256
257                        Paint linePaint = rowPaint ;
258                        // 4.1.1.0 (2008/02/04) 動的なマーカーライン(row==dynamicOCNoのデータで判定)
259                        boolean shapeFlag = false;
260                        if( row == dynamicOCNo ) {      // 使わない場合は、-1 なので、マッチしない。
261                                if( overColors != null ) {
262                                        linePaint = overColors.getDynamicColor();
263                                }
264                        }
265                        else {
266                                // 動的なマーカーライン使用時(dynamicOCNo >= 0)は、row == 0 で、Shape を使います。
267                                if( isShape || row == serNo || ( dynamicOCNo >= 0 && row == 0 ) ) {
268                                        shapeFlag = true;
269                                        shapeNo++ ;             // Shape 形状の変更は、使用した場合のみ
270                                }
271                        }
272
273                        double v0 = 0;
274                        double x0 = 0;
275                        double y0 = 0;
276
277                        boolean isItemLabelsVisible = isSeriesItemLabelsVisible( row );
278
279                        int clmSeq = 0;         // カラムの繰返し制御(Shape色の順番表示用)
280                        for( int column=0; column<clmCount; column++ ) {
281                                // nothing is drawn for null...
282                                Number v1Num = dataset.getValue( row,column );
283                                if(v1Num == null) { continue; }
284                                double v1 = v1Num.doubleValue();
285                                double x1 = domainAxis.getCategoryMiddle( column,clmCount,dataArea,domEdge );
286                                double y1 = rangeAxis.valueToJava2D( v1,dataArea,rangeEdge );
287
288                                // Line の描画
289                                if( column > 0 && v0 >= visibleLimit && v1 >= visibleLimit ) {
290                                        Line2D line = new Line2D.Double( x0,y0,x1,y1 );
291                                        g2.setPaint( linePaint );
292                                        g2.draw( line );
293                                }
294
295                                // Shape の描画
296                                if( shapeFlag ) {
297                                        // ラベルブレイク処理
298                                        if( hybsAxis != null && hybsAxis.isLabelBreak( column ) ) {
299                                                clmSeq = 0;             // 初期化
300                                        }
301
302                                        int adrs = clmSeq%shpCnt;
303                                        clmSeq++ ;
304                                        Paint paint = ( shapeColors == null ) ? rowPaint : shapeColors[adrs];
305                                        // 4.1.1.0 (2008/02/04) 動的なマーカーライン(row==dynamicOCNoのデータで判定)
306                                        if( overColors != null ) {
307                                                if( dynamicOCNo >= 0 ) {
308                                                        paint = overColors.getColor( v1,dataset.getValue( dynamicOCNo,column ) );
309                                                }
310                                                else {
311                                                        paint = overColors.getColor( v1 );
312                                                }
313                                        }
314                                        g2.setPaint(paint);
315                                        Shape shape = ShapeUtilities.createTranslatedShape( rowShape,x1,y1 );
316                                        g2.fill(shape);
317                                        g2.setPaint(rowPaint);  // 色を戻す。
318                                        // 4.3.1.0 (2008/08/09) add an item entity, if this information is being collected
319                                        EntityCollection entities = state.getEntityCollection();
320                                        if( entities != null ) {
321                                                addItemEntity( entities, dataset, row, column, shape );
322                                        }
323                                }
324
325                                // ItemLabel の描画
326                                //   山形 0-1-0 nega=fale , x= 0 上中
327                                //   右坂 0-1-2 nega=true , x=10 下右
328                                //   谷形 1-0-1 nega=true , x= 0 下中
329                                //   左坂 2-1-0 nega=fale , x=10 上右
330                                if( isItemLabelsVisible ) {
331                                        // 4.1.2.0 (2008/03/12) アンダースコアの場合は、表示しない。
332                                        if( hybsAxis != null && hybsAxis.isViewItemLabel( column ) ) {
333                                                double v2 = v0 ;        // 仮設定(最後のcolumnとnull の場合)
334                                                if( column+1 < clmCount ) {
335                                                        Number v2Num = dataset.getValue( row,column+1 );
336                                                        if( v2Num != null ) {
337                                                                v2 = v2Num.doubleValue();
338                                                        }
339                                                }
340                                                boolean nega = ( v1<v2 ) ;
341                                                double  lblx = ( nega && v0<v1 || !nega && v0>v1 ) ? x1 + 10 : x1 ;
342
343                                                drawItemLabel( g2,PlotOrientation.VERTICAL,dataset,row,column,lblx,y1,nega );
344                                        }
345                                }
346
347                                v0 = v1;        // Lineを消す処理に、過去の値が必要
348                                x0 = x1;
349                                y0 = y1;
350                        }
351                }
352        }
353
354        /**
355         * この文字列と指定されたオブジェクトを比較します。
356         *
357         * 親クラスで、equals メソッドが実装されているため、警告がでます。
358         *
359         * @og.rev 5.1.8.0 (2010/07/01) findbug対応
360         *
361         * @param object Object
362         *
363         * @return      Objectが等しい場合は true、そうでない場合は false
364         */
365//      @Override
366//      public boolean equals( final Object object ) {
367//              return super.equals( object );
368//      }
369
370        /**
371         * このオブジェクトのハッシュコードを取得します。
372         *
373         * @og.rev 5.1.8.0 (2010/07/01) findbug対応
374         *
375         * @return      ハッシュコード
376         */
377//      public int hashCode() { return super.hashCode() ; }
378
379        /**
380         * このオブジェクトと指定されたオブジェクトを比較します。
381         *
382         * @og.rev 4.3.1.1 (2008/08/23) 新規追加
383         * @og.rev 5.1.9.0 (2010/08/01) findbug対応
384         *
385         * @param       anObject        比較されるオブジェクト
386         *
387         * @return      指定されたオブジェクトが等しい場合は true、そうでない場合は false
388         */
389        @Override
390        public boolean equals( final Object anObject ) {
391//              return this == anObject;
392                if( super.equals( anObject ) ) {
393                        return hsCode == ((HybsLineRenderer)anObject).hsCode;
394                }
395                return false;
396        }
397
398        /**
399         * このオブジェクトのハッシュコードを返します。
400         *
401         * @og.rev 4.3.1.1 (2008/08/23) 新規追加
402         * @og.rev 5.1.9.0 (2010/08/01) findbug対応
403         *
404         * @return      このオブジェクトのハッシュコード値
405         */
406        @Override
407        public int hashCode() { return hsCode ; }
408
409        /**
410         * 指定されたデータセットからのアイテムをすべて表示するために、要求する値の範囲を返します。
411         *
412         * @param       dataset カテゴリDataset
413         *
414         * @return      Rangeオブジェクト
415         */
416        @Override
417        public Range findRangeBounds( final CategoryDataset dataset ) {
418                if( dataset instanceof HybsJDBCCategoryDataset2 ) {
419                        return ((HybsJDBCCategoryDataset2)dataset).getRange();
420                }
421                else {
422                        return DatasetUtilities.findRangeBounds(dataset);
423                }
424        }
425
426        /**
427         * シリアライズ用のカスタムシリアライズ書き込みメソッド
428         *
429         * @og.rev 4.1.0.1(2008/01/19) 新規追加
430         * @serialData 一部のオブジェクト(ValueMarkOverColors)は、シリアライズされません。
431         *
432         * @param       strm    ObjectOutputStreamオブジェクト
433         * @throws IOException 入出力エラーが発生した場合
434         */
435        private void writeObject( final ObjectOutputStream strm ) throws IOException {
436                strm.defaultWriteObject();
437        }
438
439        /**
440         * シリアライズ用のカスタムシリアライズ読み込みメソッド
441         *
442         * ここでは、transient 宣言された内部変数の内、初期化が必要なフィールドのみ設定します。
443         *
444         * @og.rev 4.1.0.1(2008/01/19) 新規追加
445         * @serialData 一部のオブジェクト(ValueMarkOverColors)は、読み込まれません。
446         *
447         * @param       strm    ObjectInputStreamオブジェクト
448         * @throws IOException  シリアライズに関する入出力エラーが発生した場合
449         * @throws ClassNotFoundException       クラスを見つけることができなかった場合
450         */
451        private void readObject( final ObjectInputStream strm ) throws IOException , ClassNotFoundException {
452                strm.defaultReadObject();
453        }
454}