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