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}