001    /* ===========================================================
002     * JFreeChart : a free chart library for the Java(tm) platform
003     * ===========================================================
004     *
005     * (C) Copyright 2000-2005, by Object Refinery Limited and Contributors.
006     *
007     * Project Info:  http://www.jfree.org/jfreechart/index.html
008     *
009     * This library is free software; you can redistribute it and/or modify it 
010     * under the terms of the GNU Lesser General Public License as published by 
011     * the Free Software Foundation; either version 2.1 of the License, or 
012     * (at your option) any later version.
013     *
014     * This library is distributed in the hope that it will be useful, but 
015     * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 
016     * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public 
017     * License for more details.
018     *
019     * You should have received a copy of the GNU Lesser General Public
020     * License along with this library; if not, write to the Free Software
021     * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, 
022     * USA.  
023     *
024     * [Java is a trademark or registered trademark of Sun Microsystems, Inc. 
025     * in the United States and other countries.]
026     *
027     * -------------------
028     * LineRenderer3D.java
029     * -------------------
030     * (C) Copyright 2004, 2005, by Tobias Selb and Contributors.
031     *
032     * Original Author:  Tobias Selb;
033     * Contributor(s):   David Gilbert (for Object Refinery Limited);
034     *
035     * $Id: LineRenderer3D.java,v 1.10.2.4 2005/10/25 20:54:16 mungady Exp $
036     *
037     * Changes
038     * -------
039     * 15-Oct-2004 : Version 1 (TS);
040     * 05-Nov-2004 : Modified drawItem() signature (DG);
041     * 11-Nov-2004 : Now uses ShapeUtilities class to translate shapes (DG);
042     * 26-Jan-2005 : Update for changes in super class (DG);
043     * 13-Apr-2005 : Check item visibility in drawItem() method (DG);
044     * 09-Jun-2005 : Use addItemEntity() in drawItem() method (DG);
045     * 10-Jun-2005 : Fixed capitalisation of setXOffset() and setYOffset() (DG);
046     * 
047     */
048    
049    package org.jfree.chart.renderer.category;
050    
051    import java.awt.AlphaComposite;
052    import java.awt.Color;
053    import java.awt.Composite;
054    import java.awt.Graphics2D;
055    import java.awt.Image;
056    import java.awt.Paint;
057    import java.awt.Shape;
058    import java.awt.Stroke;
059    import java.awt.geom.GeneralPath;
060    import java.awt.geom.Line2D;
061    import java.awt.geom.Rectangle2D;
062    import java.io.Serializable;
063    
064    import org.jfree.chart.Effect3D;
065    import org.jfree.chart.axis.CategoryAxis;
066    import org.jfree.chart.axis.ValueAxis;
067    import org.jfree.chart.entity.EntityCollection;
068    import org.jfree.chart.event.RendererChangeEvent;
069    import org.jfree.chart.plot.CategoryPlot;
070    import org.jfree.chart.plot.Marker;
071    import org.jfree.chart.plot.Plot;
072    import org.jfree.chart.plot.PlotOrientation;
073    import org.jfree.chart.plot.ValueMarker;
074    import org.jfree.data.Range;
075    import org.jfree.data.category.CategoryDataset;
076    import org.jfree.util.ShapeUtilities;
077    
078    /**
079     * A line renderer with a 3D effect.
080     * 
081     * @author Tobias Selb (http://www.uepselon.com) 
082     */
083    public class LineRenderer3D extends LineAndShapeRenderer 
084                                implements Effect3D, Serializable {
085       
086        /** For serialization. */
087        private static final long serialVersionUID = 5467931468380928736L;
088        
089        /** The default x-offset for the 3D effect. */
090        public static final double DEFAULT_X_OFFSET = 12.0;
091    
092        /** The default y-offset for the 3D effect. */
093        public static final double DEFAULT_Y_OFFSET = 8.0;
094       
095        /** The default wall paint. */
096        public static final Paint DEFAULT_WALL_PAINT = new Color(0xDD, 0xDD, 0xDD);
097       
098        /** The size of x-offset for the 3D effect. */
099        private double xOffset;
100    
101        /** The size of y-offset for the 3D effect. */
102        private double yOffset;
103       
104        /** The paint used to shade the left and lower 3D wall. */
105        private transient Paint wallPaint;
106       
107        /**
108         * Creates a new renderer.
109         */
110        public LineRenderer3D() {
111            super(true, false);  //Create a line renderer only
112            this.xOffset = DEFAULT_X_OFFSET;
113            this.yOffset = DEFAULT_Y_OFFSET;
114            this.wallPaint = DEFAULT_WALL_PAINT;
115        }
116       
117        /**
118         * Returns the x-offset for the 3D effect.
119         *
120         * @return The x-offset.
121         */
122        public double getXOffset() {
123            return this.xOffset;
124        }
125    
126        /**
127         * Returns the y-offset for the 3D effect.
128         *
129         * @return The y-offset.
130         */
131        public double getYOffset() {
132            return this.yOffset;
133        }
134       
135        /**
136         * Sets the x-offset.
137         * 
138         * @param xOffset  the x-offset.
139         */
140        public void setXOffset(double xOffset) {
141            this.xOffset = xOffset;
142            notifyListeners(new RendererChangeEvent(this));
143        }
144    
145        /**
146         * Sets the y-offset.
147         * 
148         * @param yOffset  the y-offset.
149         */
150        public void setYOffset(double yOffset) {
151            this.yOffset = yOffset;
152            notifyListeners(new RendererChangeEvent(this));
153        }
154    
155        /**
156         * Returns the paint used to highlight the left and bottom wall in the plot
157         * background.
158         *
159         * @return The paint.
160         */
161        public Paint getWallPaint() {
162            return this.wallPaint;
163        }
164    
165        /**
166         * Sets the paint used to hightlight the left and bottom walls in the plot
167         * background.
168         *
169         * @param paint  the paint.
170         */
171        public void setWallPaint(Paint paint) {
172            this.wallPaint = paint;
173            notifyListeners(new RendererChangeEvent(this));
174        }
175       
176        /**
177         * Draws the background for the plot.
178         *
179         * @param g2  the graphics device.
180         * @param plot  the plot.
181         * @param dataArea  the area inside the axes.
182         */
183        public void drawBackground(Graphics2D g2, CategoryPlot plot, 
184                                   Rectangle2D dataArea) {
185    
186            float x0 = (float) dataArea.getX();
187            float x1 = x0 + (float) Math.abs(this.xOffset);
188            float x3 = (float) dataArea.getMaxX();
189            float x2 = x3 - (float) Math.abs(this.xOffset);
190    
191            float y0 = (float) dataArea.getMaxY();
192            float y1 = y0 - (float) Math.abs(this.yOffset);
193            float y3 = (float) dataArea.getMinY();
194            float y2 = y3 + (float) Math.abs(this.yOffset);
195    
196            GeneralPath clip = new GeneralPath();
197            clip.moveTo(x0, y0);
198            clip.lineTo(x0, y2);
199            clip.lineTo(x1, y3);
200            clip.lineTo(x3, y3);
201            clip.lineTo(x3, y1);
202            clip.lineTo(x2, y0);
203            clip.closePath();
204    
205            // fill background...
206            Paint backgroundPaint = plot.getBackgroundPaint();
207            if (backgroundPaint != null) {
208                g2.setPaint(backgroundPaint);
209                g2.fill(clip);
210            }
211    
212            GeneralPath leftWall = new GeneralPath();
213            leftWall.moveTo(x0, y0);
214            leftWall.lineTo(x0, y2);
215            leftWall.lineTo(x1, y3);
216            leftWall.lineTo(x1, y1);
217            leftWall.closePath();
218            g2.setPaint(getWallPaint());
219            g2.fill(leftWall);
220    
221            GeneralPath bottomWall = new GeneralPath();
222            bottomWall.moveTo(x0, y0);
223            bottomWall.lineTo(x1, y1);
224            bottomWall.lineTo(x3, y1);
225            bottomWall.lineTo(x2, y0);
226            bottomWall.closePath();
227            g2.setPaint(getWallPaint());
228            g2.fill(bottomWall);
229    
230            // higlight the background corners...
231            g2.setPaint(Color.lightGray);
232            Line2D corner = new Line2D.Double(x0, y0, x1, y1);
233            g2.draw(corner);
234            corner.setLine(x1, y1, x1, y3);
235            g2.draw(corner);
236            corner.setLine(x1, y1, x3, y1);
237            g2.draw(corner);
238    
239            // draw background image, if there is one...
240            Image backgroundImage = plot.getBackgroundImage();
241            if (backgroundImage != null) {
242                Composite originalComposite = g2.getComposite();
243                g2.setComposite(AlphaComposite.getInstance(
244                    AlphaComposite.SRC, plot.getBackgroundAlpha())
245                );
246                g2.drawImage(
247                    backgroundImage,
248                    (int) x1, (int) y3,
249                    (int) (x3 - x1 + 1), (int) (y1 - y3 + 1),
250                    null
251                );
252                g2.setComposite(originalComposite);
253            }
254    
255        }
256    
257        /**
258         * Draws the outline for the plot.
259         *
260         * @param g2  the graphics device.
261         * @param plot  the plot.
262         * @param dataArea  the area inside the axes.
263         */
264        public void drawOutline(Graphics2D g2, CategoryPlot plot, 
265                                Rectangle2D dataArea) {
266    
267            float x0 = (float) dataArea.getX();
268            float x1 = x0 + (float) Math.abs(this.xOffset);
269            float x3 = (float) dataArea.getMaxX();
270            float x2 = x3 - (float) Math.abs(this.xOffset);
271    
272            float y0 = (float) dataArea.getMaxY();
273            float y1 = y0 - (float) Math.abs(this.yOffset);
274            float y3 = (float) dataArea.getMinY();
275            float y2 = y3 + (float) Math.abs(this.yOffset);
276    
277            GeneralPath clip = new GeneralPath();
278            clip.moveTo(x0, y0);
279            clip.lineTo(x0, y2);
280            clip.lineTo(x1, y3);
281            clip.lineTo(x3, y3);
282            clip.lineTo(x3, y1);
283            clip.lineTo(x2, y0);
284            clip.closePath();
285    
286            // put an outline around the data area...
287            Stroke outlineStroke = plot.getOutlineStroke();
288            Paint outlinePaint = plot.getOutlinePaint();
289            if ((outlineStroke != null) && (outlinePaint != null)) {
290                g2.setStroke(outlineStroke);
291                g2.setPaint(outlinePaint);
292                g2.draw(clip);
293            }
294    
295        }
296    
297        /**
298         * Draws a grid line against the domain axis.
299         *
300         * @param g2  the graphics device.
301         * @param plot  the plot.
302         * @param dataArea  the area for plotting data (not yet adjusted for any 
303         *                  3D effect).
304         * @param value  the Java2D value at which the grid line should be drawn.
305         *
306         */
307        public void drawDomainGridline(Graphics2D g2,
308                                       CategoryPlot plot,
309                                       Rectangle2D dataArea,
310                                       double value) {
311    
312            Line2D line1 = null;
313            Line2D line2 = null;
314            PlotOrientation orientation = plot.getOrientation();
315            if (orientation == PlotOrientation.HORIZONTAL) {
316                double y0 = value;
317                double y1 = value - getYOffset();
318                double x0 = dataArea.getMinX();
319                double x1 = x0 + getXOffset();
320                double x2 = dataArea.getMaxY();
321                line1 = new Line2D.Double(x0, y0, x1, y1);
322                line2 = new Line2D.Double(x1, y1, x2, y1);
323            }
324            else if (orientation == PlotOrientation.VERTICAL) {
325                double x0 = value;
326                double x1 = value + getXOffset();
327                double y0 = dataArea.getMaxY();
328                double y1 = y0 - getYOffset();
329                double y2 = dataArea.getMinY();
330                line1 = new Line2D.Double(x0, y0, x1, y1);
331                line2 = new Line2D.Double(x1, y1, x1, y2);
332            }
333            g2.setPaint(plot.getDomainGridlinePaint());
334            g2.setStroke(plot.getDomainGridlineStroke());
335            g2.draw(line1);
336            g2.draw(line2);
337    
338        }
339    
340        /**
341         * Draws a grid line against the range axis.
342         *
343         * @param g2  the graphics device.
344         * @param plot  the plot.
345         * @param axis  the value axis.
346         * @param dataArea  the area for plotting data (not yet adjusted for any 
347         *                  3D effect).
348         * @param value  the value at which the grid line should be drawn.
349         *
350         */
351        public void drawRangeGridline(Graphics2D g2,
352                                      CategoryPlot plot,
353                                      ValueAxis axis,
354                                      Rectangle2D dataArea,
355                                      double value) {
356    
357            Range range = axis.getRange();
358    
359            if (!range.contains(value)) {
360                return;
361            }
362    
363            Rectangle2D adjusted = new Rectangle2D.Double(
364                dataArea.getX(),
365                dataArea.getY() + getYOffset(),
366                dataArea.getWidth() - getXOffset(),
367                dataArea.getHeight() - getYOffset()
368            );
369    
370            Line2D line1 = null;
371            Line2D line2 = null;
372            PlotOrientation orientation = plot.getOrientation();
373            if (orientation == PlotOrientation.HORIZONTAL) {
374                double x0 = axis.valueToJava2D(value, adjusted, 
375                        plot.getRangeAxisEdge());
376                double x1 = x0 + getXOffset();
377                double y0 = dataArea.getMaxY();
378                double y1 = y0 - getYOffset();
379                double y2 = dataArea.getMinY();
380                line1 = new Line2D.Double(x0, y0, x1, y1);
381                line2 = new Line2D.Double(x1, y1, x1, y2);
382            }
383            else if (orientation == PlotOrientation.VERTICAL) {
384                double y0 = axis.valueToJava2D(value, adjusted,
385                        plot.getRangeAxisEdge());
386                double y1 = y0 - getYOffset();
387                double x0 = dataArea.getMinX();
388                double x1 = x0 + getXOffset();
389                double x2 = dataArea.getMaxX();
390                line1 = new Line2D.Double(x0, y0, x1, y1);
391                line2 = new Line2D.Double(x1, y1, x2, y1);
392            }
393            g2.setPaint(plot.getRangeGridlinePaint());
394            g2.setStroke(plot.getRangeGridlineStroke());
395            g2.draw(line1);
396            g2.draw(line2);
397    
398        }
399    
400        /**
401         * Draws a range marker.
402         *
403         * @param g2  the graphics device.
404         * @param plot  the plot.
405         * @param axis  the value axis.
406         * @param marker  the marker.
407         * @param dataArea  the area for plotting data (not including 3D effect).
408         */
409        public void drawRangeMarker(Graphics2D g2,
410                                    CategoryPlot plot,
411                                    ValueAxis axis,
412                                    Marker marker,
413                                    Rectangle2D dataArea) {
414    
415            if (marker instanceof ValueMarker) {
416                ValueMarker vm = (ValueMarker) marker;
417                double value = vm.getValue();
418                Range range = axis.getRange();
419                if (!range.contains(value)) {
420                    return;
421                }
422    
423                Rectangle2D adjusted = new Rectangle2D.Double(
424                    dataArea.getX(), dataArea.getY() + getYOffset(),
425                    dataArea.getWidth() - getXOffset(), 
426                    dataArea.getHeight() - getYOffset()
427                );
428    
429                GeneralPath path = null;
430                PlotOrientation orientation = plot.getOrientation();
431                if (orientation == PlotOrientation.HORIZONTAL) {
432                    float x = (float) axis.valueToJava2D(
433                        value, adjusted, plot.getRangeAxisEdge()
434                    );
435                    float y = (float) adjusted.getMaxY();
436                    path = new GeneralPath();
437                    path.moveTo(x, y);
438                    path.lineTo((float) (x + getXOffset()), 
439                            y - (float) getYOffset());
440                    path.lineTo(
441                        (float) (x + getXOffset()), 
442                        (float) (adjusted.getMinY() - getYOffset())
443                    );
444                    path.lineTo(x, (float) adjusted.getMinY());
445                    path.closePath();
446                }
447                else if (orientation == PlotOrientation.VERTICAL) {
448                    float y = (float) axis.valueToJava2D(
449                        value, adjusted, plot.getRangeAxisEdge()
450                    );
451                    float x = (float) dataArea.getX();
452                    path = new GeneralPath();
453                    path.moveTo(x, y);
454                    path.lineTo(x + (float) this.xOffset, y - (float) this.yOffset);
455                    path.lineTo((float) (adjusted.getMaxX() + this.xOffset), 
456                            y - (float) this.yOffset);
457                    path.lineTo((float) (adjusted.getMaxX()), y);
458                    path.closePath();
459                }
460                g2.setPaint(marker.getPaint());
461                g2.fill(path);
462                g2.setPaint(marker.getOutlinePaint());
463                g2.draw(path);
464            }
465        }
466       
467       /**
468         * Draw a single data item.
469         *
470         * @param g2  the graphics device.
471         * @param state  the renderer state.
472         * @param dataArea  the area in which the data is drawn.
473         * @param plot  the plot.
474         * @param domainAxis  the domain axis.
475         * @param rangeAxis  the range axis.
476         * @param dataset  the dataset.
477         * @param row  the row index (zero-based).
478         * @param column  the column index (zero-based).
479         * @param pass  the pass index.
480         */
481        public void drawItem(Graphics2D g2,
482                             CategoryItemRendererState state,
483                             Rectangle2D dataArea,
484                             CategoryPlot plot,
485                             CategoryAxis domainAxis,
486                             ValueAxis rangeAxis,
487                             CategoryDataset dataset,
488                             int row,
489                             int column,
490                             int pass) {
491    
492            if (!getItemVisible(row, column)) {
493                return;   
494            }
495            
496            // nothing is drawn for null...
497            Number v = dataset.getValue(row, column);
498            if (v == null) {
499                return;
500            }
501           
502            Rectangle2D adjusted = new Rectangle2D.Double(
503                dataArea.getX(),
504                dataArea.getY() + getYOffset(),
505                dataArea.getWidth() - getXOffset(),
506                dataArea.getHeight() - getYOffset()
507            );
508           
509            PlotOrientation orientation = plot.getOrientation();
510    
511            // current data point...
512            double x1 = domainAxis.getCategoryMiddle(
513                column, getColumnCount(), adjusted, plot.getDomainAxisEdge()
514            );
515            double value = v.doubleValue();
516            double y1 = rangeAxis.valueToJava2D(value, adjusted, 
517                    plot.getRangeAxisEdge());
518    
519            Shape shape = getItemShape(row, column);
520            if (orientation == PlotOrientation.HORIZONTAL) {
521                shape = ShapeUtilities.createTranslatedShape(shape, y1, x1);
522            }
523            else if (orientation == PlotOrientation.VERTICAL) {
524                shape = ShapeUtilities.createTranslatedShape(shape, x1, y1);
525            }
526           
527            if (getItemLineVisible(row, column)) {
528                if (column != 0) {
529    
530                    Number previousValue = dataset.getValue(row, column - 1);
531                    if (previousValue != null) {
532    
533                        // previous data point...
534                        double previous = previousValue.doubleValue();
535                        double x0 = domainAxis.getCategoryMiddle(
536                            column - 1, getColumnCount(), adjusted, 
537                            plot.getDomainAxisEdge()
538                        );
539                        double y0 = rangeAxis.valueToJava2D(
540                            previous, adjusted, plot.getRangeAxisEdge()
541                        );
542    
543                        double x2 = x0 + getXOffset();
544                        double y2 = y0 - getYOffset();
545                        double x3 = x1 + getXOffset();
546                        double y3 = y1 - getYOffset();
547                       
548                        GeneralPath clip = new GeneralPath();
549                       
550                        if (orientation == PlotOrientation.HORIZONTAL) {
551                            clip.moveTo((float) y0, (float) x0);
552                            clip.lineTo((float) y1, (float) x1);
553                            clip.lineTo((float) y3, (float) x3);
554                            clip.lineTo((float) y2, (float) x2);
555                            clip.lineTo((float) y0, (float) x0);
556                            clip.closePath();
557                        }
558                        else if (orientation == PlotOrientation.VERTICAL) {
559                            clip.moveTo((float) x0, (float) y0);
560                            clip.lineTo((float) x1, (float) y1);
561                            clip.lineTo((float) x3, (float) y3);
562                            clip.lineTo((float) x2, (float) y2);
563                            clip.lineTo((float) x0, (float) y0);
564                            clip.closePath();
565                        }
566                       
567                        g2.setPaint(getItemPaint(row, column));
568                        g2.fill(clip);
569                        g2.setStroke(getItemOutlineStroke(row, column));
570                        g2.setPaint(getItemOutlinePaint(row, column));
571                        g2.draw(clip);
572                    }
573                }
574            }
575    
576            // draw the item label if there is one...
577            if (isItemLabelVisible(row, column)) {
578                drawItemLabel(
579                    g2, orientation, dataset, row, column, x1, y1, (value < 0.0)
580                );
581            }
582    
583            // add an item entity, if this information is being collected
584            EntityCollection entities = state.getEntityCollection();
585            if (entities != null) {
586                addItemEntity(entities, dataset, row, column, shape);
587            }
588    
589        }
590    
591    }