/* =========================================================== * Orson Charts : a 3D chart library for the Java(tm) platform * =========================================================== * * (C)opyright 2013-2016, by Object Refinery Limited. All rights reserved. * * http://www.object-refinery.com/orsoncharts/index.html * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. * * [Oracle and Java are registered trademarks of Oracle and/or its affiliates. * Other names may be trademarks of their respective owners.] * * If you do not wish to be bound by the terms of the GPL, an alternative * commercial license can be purchased. For details, please see visit the * Orson Charts home page: * * http://www.object-refinery.com/orsoncharts/index.html * */ package com.orsoncharts.marker; import java.awt.FontMetrics; import java.awt.Graphics2D; import java.awt.geom.Line2D; import java.awt.geom.Rectangle2D; import javax.swing.event.EventListenerList; import com.orsoncharts.Chart3DChangeListener; import com.orsoncharts.ChartElementVisitor; import com.orsoncharts.graphics3d.Utils2D; import com.orsoncharts.util.Anchor2D; import com.orsoncharts.util.ArgChecks; import com.orsoncharts.util.RefPt2D; import com.orsoncharts.util.TextAnchor; import com.orsoncharts.util.TextUtils; /** * A base class for implementing markers (includes the event notification * mechanism). * * @since 1.2 */ public abstract class AbstractMarker implements Marker { /** Storage for registered change listeners. */ private transient EventListenerList listenerList; /** * Default constructor. */ AbstractMarker() { this.listenerList = new EventListenerList(); } /** * Draws a marker label. * * @param g2 the graphics target ({@code null} not permitted). * @param label the label. * @param x the x-coordinate for the anchor point. * @param y the y-cpordinate for the anchor point. * @param anchor the label anchor ({@code null} not permitted). * @param refLine a reference line that is used to determine the rotation * angle for the label ({@code null} not permitted). * @param reverse a flag to indicate reverse orientation. */ protected void drawMarkerLabel(Graphics2D g2, String label, double x, double y, Anchor2D anchor, Line2D refLine, boolean reverse) { double angle = Utils2D.calculateTheta(refLine); boolean vflip = false; if (angle > Math.PI / 2) { angle -= Math.PI; vflip = true; } if (angle < -Math.PI / 2) { angle += Math.PI; vflip = true; } if (reverse) { vflip = !vflip; } double lineLength = Utils2D.length(refLine); FontMetrics fm = g2.getFontMetrics(); Rectangle2D bounds = fm.getStringBounds(label, g2); if (bounds.getWidth() < lineLength) { TextAnchor textAnchor = deriveTextAnchorForLine(anchor.getRefPt(), !vflip); TextUtils.drawRotatedString(label, g2, (float) x, (float) y, textAnchor, angle, textAnchor); } } /** * Draws a marker label. * * @param g2 the graphics target ({@code null} not permitted). * @param label the label. * @param x the x-coordinate for the anchor point. * @param y the y-cpordinate for the anchor point. * @param anchor the label anchor ({@code null} not permitted). * @param refLine1 a reference line that is used to determine the rotation * angle for the label ({@code null} not permitted). * @param refLine2 a reference line that is used to determine the rotation * angle for the label ({@code null} not permitted). * @param reverse a flag to indicate reverse orientation. */ protected void drawMarkerLabel(Graphics2D g2, String label, double x, double y, Anchor2D anchor, Line2D refLine1, Line2D refLine2, boolean reverse) { double angle; if (anchor.getRefPt().isTop()) { angle = Utils2D.calculateTheta(refLine2); } else if (anchor.getRefPt().isBottom()) { angle = Utils2D.calculateTheta(refLine1); } else { angle = (Utils2D.calculateTheta(refLine1) + Utils2D.calculateTheta(refLine2)) / 2.0; } boolean vflip = false; if (angle > Math.PI / 2) { angle -= Math.PI; vflip = true; } if (angle < -Math.PI / 2) { angle += Math.PI; vflip = true; } if (reverse) { vflip = !vflip; } double lineLength1 = Utils2D.length(refLine1); double lineLength2 = Utils2D.length(refLine2); Rectangle2D bounds = g2.getFontMetrics().getStringBounds(label, g2); if (bounds.getWidth() < Math.min(lineLength1, lineLength2)) { TextAnchor textAnchor = deriveTextAnchor(anchor.getRefPt(), !vflip); TextUtils.drawRotatedString(label, g2, (float) x, (float) y, textAnchor, angle, textAnchor); } } /** * Receives a visitor. * * @param visitor the visitor. * * @since 1.2 */ @Override public void receive(ChartElementVisitor visitor) { visitor.visit(this); } /** * Registers a listener to receive notification of changes to the marker. * * @param listener the listener ({@code null} not permitted). */ @Override public void addChangeListener(MarkerChangeListener listener) { this.listenerList.add(MarkerChangeListener.class, listener); } /** * Deregisters a listener so that it no longer receives notification of * changes to the marker. * * @param listener the listener ({@code null} not permitted). */ @Override public void removeChangeListener(MarkerChangeListener listener) { this.listenerList.remove(MarkerChangeListener.class, listener); } /** * Sends a {@link MarkerChangeEvent} to all registered listeners. */ protected void fireChangeEvent() { Object[] listeners = this.listenerList.getListenerList(); for (int i = listeners.length - 2; i >= 0; i -= 2) { if (listeners[i] == Chart3DChangeListener.class) { ((MarkerChangeListener) listeners[i + 1]).markerChanged( new MarkerChangeEvent(this, this)); } } } /** * A utility method that returns a suitable text anchor for a given * reference point. This is used for range marker label positioning. * * @param refPt the reference point ({@code null} not permitted). * @param vflip is the text flipped vertically? * * @return A text anchor (never {@code null}). */ protected static TextAnchor deriveTextAnchor(RefPt2D refPt, boolean vflip) { ArgChecks.nullNotPermitted(refPt, "refPt"); if (refPt.equals(RefPt2D.TOP_LEFT)) { return vflip ? TextAnchor.TOP_LEFT : TextAnchor.BOTTOM_RIGHT; } else if (refPt.equals(RefPt2D.TOP_CENTER)) { return vflip ? TextAnchor.TOP_CENTER : TextAnchor.BOTTOM_CENTER; } else if (refPt.equals(RefPt2D.TOP_RIGHT)) { return vflip ? TextAnchor.TOP_RIGHT :TextAnchor.BOTTOM_LEFT; } if (refPt.equals(RefPt2D.CENTER_LEFT)) { return vflip ? TextAnchor.CENTER_LEFT : TextAnchor.CENTER_RIGHT; } else if (refPt.equals(RefPt2D.CENTER)) { return TextAnchor.CENTER; } else if (refPt.equals(RefPt2D.CENTER_RIGHT)) { return vflip ? TextAnchor.CENTER_RIGHT : TextAnchor.CENTER_LEFT; } else if (refPt.equals(RefPt2D.BOTTOM_LEFT)) { return vflip ? TextAnchor.BOTTOM_LEFT : TextAnchor.TOP_RIGHT; } else if (refPt.equals(RefPt2D.BOTTOM_CENTER)) { return vflip ? TextAnchor.BOTTOM_CENTER : TextAnchor.TOP_CENTER; } else if (refPt.equals(RefPt2D.BOTTOM_RIGHT)) { return vflip ? TextAnchor.BOTTOM_RIGHT : TextAnchor.TOP_LEFT; } throw new RuntimeException("Unknown refPt " + refPt); } /** * A utility method that returns a suitable text anchor for a given * reference point relative to a line (rather than a rectangle which is * the normal case). This is used for value marker label positioning. * * @param refPt the reference point ({@code null} not permitted). * @param vflip is the text flipped vertically? * * @return A text anchor (never {@code null}). */ protected static TextAnchor deriveTextAnchorForLine(RefPt2D refPt, boolean vflip) { if (refPt.equals(RefPt2D.TOP_LEFT)) { return vflip ? TextAnchor.BOTTOM_LEFT : TextAnchor.TOP_RIGHT; } else if (refPt.equals(RefPt2D.TOP_CENTER)) { return vflip ? TextAnchor.BOTTOM_CENTER : TextAnchor.TOP_CENTER; } else if (refPt.equals(RefPt2D.TOP_RIGHT)) { return vflip ? TextAnchor.BOTTOM_RIGHT :TextAnchor.TOP_LEFT; } if (refPt.equals(RefPt2D.CENTER_LEFT)) { return vflip ? TextAnchor.CENTER_LEFT : TextAnchor.CENTER_RIGHT; } else if (refPt.equals(RefPt2D.CENTER)) { return TextAnchor.CENTER; } else if (refPt.equals(RefPt2D.CENTER_RIGHT)) { return vflip ? TextAnchor.CENTER_RIGHT : TextAnchor.CENTER_LEFT; } else if (refPt.equals(RefPt2D.BOTTOM_LEFT)) { return vflip ? TextAnchor.TOP_LEFT : TextAnchor.BOTTOM_RIGHT; } else if (refPt.equals(RefPt2D.BOTTOM_CENTER)) { return vflip ? TextAnchor.TOP_CENTER : TextAnchor.BOTTOM_CENTER; } else if (refPt.equals(RefPt2D.BOTTOM_RIGHT)) { return vflip ? TextAnchor.TOP_RIGHT : TextAnchor.BOTTOM_LEFT; } throw new RuntimeException("Unknown refPt " + refPt); } }