    001/* ===========================================================
    002 * Orson Charts : a 3D chart library for the Java(tm) platform
    003 * ===========================================================
    004 *
    005 * (C)opyright 2013-2022, by David Gilbert.  All rights reserved.
    006 *
    007 * https://github.com/jfree/orson-charts
    008 *
    009 * This program is free software: you can redistribute it and/or modify
    010 * it under the terms of the GNU General Public License as published by
    011 * the Free Software Foundation, either version 3 of the License, or
    012 * (at your option) any later version.
    013 *
    014 * This program is distributed in the hope that it will be useful,
    015 * but WITHOUT ANY WARRANTY; without even the implied warranty of
    016 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    017 * GNU General Public License for more details.
    018 *
    019 * You should have received a copy of the GNU General Public License
    020 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
    021 *
    022 * [Oracle and Java are registered trademarks of Oracle and/or its affiliates.
    023 * Other names may be trademarks of their respective owners.]
    024 *
    025 * If you do not wish to be bound by the terms of the GPL, an alternative
    026 * commercial license can be purchased.  For details, please see visit the
    027 * Orson Charts home page:
    028 *
    029 * http://www.object-refinery.com/orsoncharts/index.html
    030 *
    031 */
    032
    033package org.jfree.chart3d.marker;
    034
    035import java.awt.Color;
    036import java.awt.Font;
    037import java.awt.Graphics2D;
    038import java.awt.Stroke;
    039import java.awt.geom.Line2D;
    040import java.awt.geom.Path2D;
    041import java.awt.geom.Point2D;
    042import java.io.IOException;
    043import java.io.ObjectInputStream;
    044import java.io.ObjectOutputStream;
    045import java.io.Serializable;
    046
    047import org.jfree.chart3d.axis.CategoryAxis3D;
    048import org.jfree.chart3d.graphics2d.Anchor2D;
    049import org.jfree.chart3d.internal.SerialUtils;
    050import org.jfree.chart3d.internal.Args;
    051import org.jfree.chart3d.internal.ObjectUtils;
    052
    053/**
    054 * A marker for a category on a {@link CategoryAxis3D}.  This marker could be
    055 * used to highlight one selected category.
    056 * <br><br>
    057 * For an example, please refer to the demo {@code CategoryMarkerDemo1.java}.
    058 * <br><br>
    059 * NOTE: This class is serializable, but the serialization format is subject
    060 * to change in future releases and should not be relied upon for persisting
    061 * instances of this class.
    062 *
    063 * @since 1.2
    064 */
    065@SuppressWarnings("serial")
    066public class CategoryMarker extends AbstractMarker implements Serializable {
    067
    068        /** The category to mark. */
    069    Comparable<?> category;
    070
    071    /**
    072     * The marker type (used to indicate whether the marker is represented by
    073     * a line or a band).
    074     */
    075    CategoryMarkerType type;
    076
    077    /** The label for the marker (optional). */
    078    private String label;
    079
    080    /** The font for the label. */
    081    private Font font;
    082
    083    /** The color for the label. */
    084    private Color labelColor;
    085
    086    /** The anchor for the label. */
    087    private Anchor2D labelAnchor;
    088
    089    /** The stroke for the marker line(s). */
    090    private transient Stroke lineStroke;
    091
    092    /** The color for the marker line. */
    093    private Color lineColor;
    094
    095    /** The fill color used when drawing a band for the marker. */
    096    private Color fillColor;
    097
    098    /**
    099     * Creates a marker for the specified category.
    100     *
    101     * @param category  the category key ({@code null} not permitted).
    102     */
    103    public CategoryMarker(Comparable<?> category) {
    104        super();
    105        Args.nullNotPermitted(category, "category");
    106        this.category = category;
    107        this.type = CategoryMarkerType.BAND;
    108        this.font = Marker.DEFAULT_MARKER_FONT;
    109        this.labelColor = Marker.DEFAULT_LABEL_COLOR;
    110        this.labelAnchor = Anchor2D.CENTER;
    111        this.lineStroke = DEFAULT_LINE_STROKE;
    112        this.lineColor = DEFAULT_LINE_COLOR;
    113        this.fillColor = DEFAULT_FILL_COLOR;
    114    }
    115
    116    /**
    117     * Returns the category.
    118     *
    119     * @return The category (never {@code null}).
    120     */
    121    public Comparable<?> getCategory() {
    122        return this.category;
    123    }
    124
    125    /**
    126     * Sets the category for the marker and sends a change event to all
    127     * registered listeners.
    128     *
    129     * @param category  the new category ({@code null} not permitted).
    130     */
    131    public void setCategory(Comparable<?> category) {
    132        Args.nullNotPermitted(category, "category");
    133        this.category = category;
    134        fireChangeEvent();
    135    }
    136
    137    /**
    138     * Returns the marker type which determines whether the marker is drawn
    139     * as a band (the default) or a line.
    140     *
    141     * @return The type (never {@code null}).
    142     */
    143    public CategoryMarkerType getType() {
    144        return this.type;
    145    }
    146
    147    /**
    148     * Sets the marker type and sends a change event to all registered
    149     * listeners.
    150     *
    151     * @param type  the type ({@code null} not permitted).
    152     */
    153    public void setType(CategoryMarkerType type) {
    154        Args.nullNotPermitted(type, "type");
    155        this.type = type;
    156        fireChangeEvent();
    157    }
    158
    159    /**
    160     * Returns the label for the marker (if this is {@code null} then no
    161     * label is displayed).
    162     *
    163     * @return The label (possibly {@code null}).
    164     */
    165    public String getLabel() {
    166        return this.label;
    167    }
    168
    169    /**
    170     * Sets the label and sends a change event to all registered listeners.
    171     *
    172     * @param label  the label ({@code null} permitted).
    173     */
    174    public void setLabel(String label) {
    175        this.label = label;
    176        fireChangeEvent();
    177    }
    178
    179    /**
    180     * Returns the font for the label.  The default value is
    181     * {@link Marker#DEFAULT_MARKER_FONT}.
    182     *
    183     * @return The font (never {@code null}).
    184     */
    185    public Font getFont() {
    186        return this.font;
    187    }
    188
    189    /**
    190     * Sets the font for the marker label and sends a change event to all
    191     * registered listeners.
    192     *
    193     * @param font  the font ({@code null} not permitted).
    194     */
    195    public void setFont(Font font) {
    196        Args.nullNotPermitted(font, "font");
    197        this.font = font;
    198        fireChangeEvent();
    199    }
    200
    201    /**
    202     * Returns the label color.  The default value is
    203     * {@link Marker#DEFAULT_LABEL_COLOR}.
    204     *
    205     * @return The label color (never {@code null}).
    206     */
    207    public Color getLabelColor() {
    208        return this.labelColor;
    209    }
    210
    211    /**
    212     * Sets the label color and sends a change event to all registered
    213     * listeners.
    214     *
    215     * @param color  the color ({@code null} not permitted).
    216     */
    217    public void setLabelColor(Color color) {
    218        Args.nullNotPermitted(color, "color");
    219        this.labelColor = color;
    220        fireChangeEvent();
    221    }
    222
    223    /**
    224     * Returns the anchor for the label.  The default value is
    225     * {@link Anchor2D#CENTER}.
    226     *
    227     * @return The anchor for the label.
    228     */
    229    public Anchor2D getLabelAnchor() {
    230        return this.labelAnchor;
    231    }
    232
    233    /**
    234     * Sets the anchor for the label and sends a change event to all registered
    235     * listeners.
    236     *
    237     * @param anchor  the anchor ({@code null} not permitted).
    238     */
    239    public void setLabelAnchor(Anchor2D anchor) {
    240        Args.nullNotPermitted(anchor, "anchor");
    241        this.labelAnchor = anchor;
    242        fireChangeEvent();
    243    }
    244
    245    /**
    246     * Returns the line color for the marker.
    247     *
    248     * @return The line color (never {@code null}).
    249     */
    250    public Color getLineColor() {
    251        return this.lineColor;
    252    }
    253
    254    /**
    255     * Sets the line color for the marker and sends a change event to all
    256     * registered listeners.
    257     *
    258     * @param color  the color ({@code null} not permitted).
    259     */
    260    public void setLineColor(Color color) {
    261        Args.nullNotPermitted(color, "color");
    262        this.lineColor = color;
    263        fireChangeEvent();
    264    }
    265
    266    /**
    267     * Returns the line stroke.  The default value is
    268     * {@link Marker#DEFAULT_LINE_STROKE}.
    269     *
    270     * @return The line stroke (never {@code null}).
    271     */
    272    public Stroke getLineStroke() {
    273        return this.lineStroke;
    274    }
    275
    276    /**
    277     * Sets the line stroke and sends a change event to all registered
    278     * listeners.
    279     *
    280     * @param stroke  the stroke ({@code null} not permitted).
    281     */
    282    public void setLineStroke(Stroke stroke) {
    283        Args.nullNotPermitted(stroke, "stroke");
    284        this.lineStroke = stroke;
    285        fireChangeEvent();
    286    }
    287
    288    /**
    289     * Returns the color used to fill the marker band.
    290     *
    291     * @return The color (never {@code null}).
    292     */
    293    public Color getFillColor() {
    294        return this.fillColor;
    295    }
    296
    297    /**
    298     * Sets the color used to fill the marker band and sends a change event
    299     * to all registered listeners.
    300     *
    301     * @param color  the color ({@code null} not permitted).
    302     */
    303    public void setFillColor(Color color) {
    304        Args.nullNotPermitted(color, "color");
    305        this.fillColor = color;
    306        fireChangeEvent();
    307    }
    308
    309    /**
    310     * Handles drawing of the marker.  This method is called by the library,
    311     * you won't normally call it directly.
    312     *
    313     * @param g2  the graphics device ({@code null} not permitted).
    314     * @param markerData   the marker data ({@code null} not permitted).
    315     * @param reverse  a flag to indicate reverse orientation.
    316     */
    317    @Override
    318    public void draw(Graphics2D g2, MarkerData markerData, boolean reverse) {
    319        if (markerData.getType().equals(MarkerDataType.VALUE)) {
    320            MarkerLine ml = markerData.getValueLine();
    321            g2.setPaint(this.lineColor);
    322            g2.setStroke(this.lineStroke);
    323            Line2D l = new Line2D.Double(ml.getStartPoint(), ml.getEndPoint());
    324            g2.draw(l);
    325            Point2D labelPoint = markerData.getLabelPoint();
    326            if (labelPoint != null) {
    327                g2.setFont(this.font);
    328                g2.setColor(this.labelColor);
    329                drawMarkerLabel(g2, this.label, labelPoint.getX(),
    330                        labelPoint.getY(), this.labelAnchor, l, reverse);
    331            }
    332        } else if (markerData.getType().equals(MarkerDataType.RANGE)) {
    333            MarkerLine sl = markerData.getStartLine();
    334            Line2D l1 = new Line2D.Double(sl.getStartPoint(), sl.getEndPoint());
    335            MarkerLine el = markerData.getEndLine();
    336            Line2D l2 = new Line2D.Double(el.getStartPoint(), el.getEndPoint());
    337
    338            Path2D path = new Path2D.Double();
    339            path.moveTo(l1.getX1(), l1.getY1());
    340            path.lineTo(l1.getX2(), l1.getY2());
    341            path.lineTo(l2.getX2(), l2.getY2());
    342            path.lineTo(l2.getX1(), l2.getY1());
    343            path.closePath();
    344            g2.setPaint(this.fillColor);
    345            g2.fill(path);
    346
    347            g2.setColor(this.lineColor);
    348            g2.setStroke(this.lineStroke);
    349            g2.draw(l1);
    350            g2.draw(l2);
    351
    352            Point2D labelPoint = markerData.getLabelPoint();
    353            if (labelPoint != null) {
    354                g2.setFont(this.font);
    355                g2.setColor(this.labelColor);
    356                drawMarkerLabel(g2, this.label, labelPoint.getX(),
    357                        labelPoint.getY(), markerData.getLabelAnchor(), l1, l2,
    358                        reverse);
    359            }
    360        }
    361    }
    362
    363    @Override
    364    public int hashCode() {
    365        int hash = 7;
    366        hash = 73 * hash + ObjectUtils.hashCode(this.category);
    367        return hash;
    368    }
    369
    370    @Override
    371    public boolean equals(Object obj) {
    372        if (obj == null) {
    373            return false;
    374        }
    375        if (getClass() != obj.getClass()) {
    376            return false;
    377        }
    378        final CategoryMarker other = (CategoryMarker) obj;
    379        if (!ObjectUtils.equals(this.category, other.category)) {
    380            return false;
    381        }
    382        if (this.type != other.type) {
    383            return false;
    384        }
    385        if (!ObjectUtils.equals(this.label, other.label)) {
    386            return false;
    387        }
    388        if (!ObjectUtils.equals(this.font, other.font)) {
    389            return false;
    390        }
    391        if (!ObjectUtils.equals(this.labelColor, other.labelColor)) {
    392            return false;
    393        }
    394        if (!ObjectUtils.equals(this.labelAnchor, other.labelAnchor)) {
    395            return false;
    396        }
    397        if (!ObjectUtils.equals(this.lineStroke, other.lineStroke)) {
    398            return false;
    399        }
    400        if (!ObjectUtils.equals(this.lineColor, other.lineColor)) {
    401            return false;
    402        }
    403        if (!ObjectUtils.equals(this.fillColor, other.fillColor)) {
    404            return false;
    405        }
    406        return true;
    407    }
    408
    409    /**
    410     * Provides serialization support.
    411     *
    412     * @param stream  the output stream.
    413     *
    414     * @throws IOException  if there is an I/O error.
    415     */
    416    private void writeObject(ObjectOutputStream stream) throws IOException {
    417        stream.defaultWriteObject();
    418        SerialUtils.writeStroke(this.lineStroke, stream);
    419    }
    420
    421    /**
    422     * Provides serialization support.
    423     *
    424     * @param stream  the input stream.
    425     *
    426     * @throws IOException  if there is an I/O error.
    427     * @throws ClassNotFoundException  if there is a classpath problem.
    428     */
    429    private void readObject(ObjectInputStream stream)
    430        throws IOException, ClassNotFoundException {
    431        stream.defaultReadObject();
    432        this.lineStroke = SerialUtils.readStroke(stream);
    433    }
    434
    435}