/* * TracePoint2D, a tuned Point2D.Double for use with ITrace2D- implementations. Copyright (c) 2004 - 2011 Achim Westermann, * Achim.Westermann@gmx.de * * This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. * * This library 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * * If you modify or optimize the code in a useful way please let me know. Achim.Westermann@gmx.de */ package info.monitorenter.gui.chart; import java.awt.geom.Point2D; import java.util.LinkedHashSet; import java.util.Set; /** * A specialized version of <code>java.awt.Point2D.Double </code> who carries two further values: <code> double scaledX</code> and * <code>double scaledY</code> which allow the <code>Chart2D</code> to cache the scaled values (between 0.0 and 1.0) without having to keep * a copy of the aggregators (<code>ITrace2D</code>) complete tracepoints. * <p> * This avoids the necessity to care for the correct order of a set of scaled tracepoints copied for caching purposes. Especially in the * case of new <code>TracePoint2D</code> instances added to a <code>ITrace2D</code> instance managed by a <code>Chart2D</code> there remains * no responsibility for sorting the cached copy. This allows that the managing <code>Chart2D</code> may just rescale the newly added * tracepoint instead of searching for the correct order of the new tracepoint by value - comparisons of x and y: The * <code>TracePoint2D</code> passed to the method <code>traceChanged(Chart2DDataChangeEvent e)</code> coded in the argument is the original. * <br> * <p> * Why caching of scaled values for the coordinates? <br> * This takes more RAM but else for every <code>repaint()</code> invocation of the <code>Chart2D</code> would force all tracepoints of all * traces to be rescaled again. * <p> * A TracePoint2D will inform it's listener of type <code>ITrace</code> on changes of the internal values. * <p> * * @author Achim Westermann <a href='mailto:Achim.Westermann@gmx.de'>Achim.Westermann@gmx.de </a> * @version $Revision: 1.34 $ */ public class TracePoint2D extends Point2D.Double implements ITracePoint2D { /** * Generated <code>serialVersionUID</code>. */ private static final long serialVersionUID = 3618980079204512309L; /** * The list of additional point painters. */ private Set<IPointPainter<?>> m_additionalPointPainters = new LinkedHashSet<IPointPainter<?>>(); /** * The reference to the listening <code>ITrace</code> who owns this point. * <p> * A trace point should be contained only in one trace! * <p> */ private ITrace2D m_listener; /** * Scaled x value. */ private double m_scaledX; /** * Scaled y value. */ private double m_scaledY; /** * The x coordinate, re-declared as the super class member will not be serialized. */ private double m_x; /** * The y coordinate, re-declared as the super class member will not be serialized. */ private double m_y; /** * Intended for <code>{@link TracePointProviderDefault}</code> only. * <p> */ protected TracePoint2D() { // nop } /** * Construct a TracePoint2D whose coords are initalized to (x,y). * <p> * * @param xValue * the x value to use. * @param yValue * the y value to use. */ public TracePoint2D(final double xValue, final double yValue) { this.m_x = xValue; this.m_y = yValue; } /** * @see info.monitorenter.gui.chart.ITracePoint2D#addAdditionalPointPainter(info.monitorenter.gui.chart.IPointPainter) */ public final boolean addAdditionalPointPainter(final IPointPainter<?> additionalPointPainter) { Boolean result; result = this.doSynchronized(new ICodeBlock<Boolean>() { @SuppressWarnings("synthetic-access") public Boolean execute() { final boolean res = TracePoint2D.this.m_additionalPointPainters.add(additionalPointPainter); // for interpolated points listener may be null: if (res && TracePoint2D.this.m_listener != null) { TracePoint2D.this.m_listener.firePointChanged(TracePoint2D.this, ITracePoint2D.STATE_CHANGED); } return Boolean.valueOf(res); } }); return result.booleanValue(); } /** * @see info.monitorenter.gui.chart.ITracePoint2D#clone() */ @Override public Object clone() { final TracePoint2D result = (TracePoint2D) super.clone(); result.m_x = this.m_x; result.m_y = this.m_y; result.m_scaledX = this.m_scaledX; result.m_scaledY = this.m_scaledY; result.m_additionalPointPainters = new LinkedHashSet<IPointPainter<?>>(this.m_additionalPointPainters); return result; } /** * @see info.monitorenter.gui.chart.ITracePoint2D#compareTo(info.monitorenter.gui.chart.ITracePoint2D) */ public int compareTo(final ITracePoint2D obj) { int result; final double othx = obj.getX(); if (this.m_x < othx) { result = -1; } else { if (this.m_x == othx) { result = 0; } else { result = 1; } } return result; } /** * @see java.awt.geom.Point2D#equals(java.lang.Object) */ @Override public boolean equals(final Object obj) { if (this == obj) { return true; } if (!super.equals(obj)) { return false; } if (this.getClass() != obj.getClass()) { return false; } final TracePoint2D other = (TracePoint2D) obj; if (java.lang.Double.doubleToLongBits(this.m_x) != java.lang.Double.doubleToLongBits(other.m_x)) { return false; } if (java.lang.Double.doubleToLongBits(this.m_y) != java.lang.Double.doubleToLongBits(other.m_y)) { return false; } return true; } /** * @see info.monitorenter.gui.chart.ITracePoint2D#getAdditionalPointPainters() */ public final Set<IPointPainter<?>> getAdditionalPointPainters() { return this.m_additionalPointPainters; } /** * @see info.monitorenter.gui.chart.ITracePoint2D#getEuclidDistance(double, double) */ public double getEuclidDistance(final double xNormalized, final double yNormalized) { double result; final double xdist = Math.abs(this.m_scaledX - xNormalized); final double ydist = Math.abs(this.m_scaledY - yNormalized); result = Math.sqrt(Math.pow(xdist, 2) + Math.pow(ydist, 2)); return result; } /** * @see info.monitorenter.gui.chart.ITracePoint2D#getListener() */ public ITrace2D getListener() { return this.m_listener; } /** * @see info.monitorenter.gui.chart.ITracePoint2D#getManhattanDistance(double, double) */ public double getManhattanDistance(final double xNormalized, final double yNormalized) { double result; result = Math.abs(this.m_scaledX - xNormalized) + Math.abs(this.m_scaledY - yNormalized); return result; } /** * @see info.monitorenter.gui.chart.ITracePoint2D#getManhattanDistance(info.monitorenter.gui.chart.ITracePoint2D) */ public double getManhattanDistance(final ITracePoint2D point) { return this.getManhattanDistance(point.getX(), point.getY()); } /** * @see info.monitorenter.gui.chart.ITracePoint2D#getScaledX() */ public final double getScaledX() { return this.m_scaledX; } /** * @see info.monitorenter.gui.chart.ITracePoint2D#getScaledY() */ public final double getScaledY() { return this.m_scaledY; } /** * @see info.monitorenter.gui.chart.ITracePoint2D#getX() */ @Override public double getX() { return this.m_x; } /** * @see info.monitorenter.gui.chart.ITracePoint2D#getY() */ @Override public double getY() { return this.m_y; } /** * @see java.awt.geom.Point2D#hashCode() */ @Override public int hashCode() { final int prime = 31; int result = super.hashCode(); long temp; temp = java.lang.Double.doubleToLongBits(this.m_x); result = prime * result + (int) (temp ^ (temp >>> 32)); temp = java.lang.Double.doubleToLongBits(this.m_y); result = prime * result + (int) (temp ^ (temp >>> 32)); return result; } /** * @see info.monitorenter.gui.chart.ITracePoint2D#setListener(info.monitorenter.gui.chart.ITrace2D) */ public void setListener(final ITrace2D listener) { this.m_listener = listener; } /** * @see info.monitorenter.gui.chart.ITracePoint2D#setLocation(double, double) */ @Override public void setLocation(final double xValue, final double yValue) { this.doSynchronized(new ICodeBlock<Object>() { @SuppressWarnings("synthetic-access") public Object execute() { TracePoint2D.this.m_x = xValue; TracePoint2D.this.m_y = yValue; if (TracePoint2D.this.m_listener != null) { TracePoint2D.this.m_listener.firePointChanged(TracePoint2D.this, ITracePoint2D.STATE_CHANGED); } return null; } }); } /** * Internal helper that invokes the given internal runnable if this trace point is already connected to a trace. This is needed to avoid * that changes on a trace point that are made within a paint iteration while the point already has been painted (but the iteration is * still ongoing and the modification of the point -> request of a repaint to the chart is accumulated) are not reflected in the UI (are * not drawn). * <p> * Synchronization is following the idiom also used in the painting <code>{@link Chart2D}</code>. * * @param runSynchronized * code to execute synchronized. */ private <T> T doSynchronized(final ICodeBlock<T> runSynchronized) { T result; if (this.m_listener != null) { Chart2D chart = this.m_listener.getRenderer(); if (chart != null) { // already connected to the chart: keep full locking order synchronized (chart) { synchronized (this.m_listener) { result = runSynchronized.execute(); } } } else { // not connected to a chart by now: synchronized (this.m_listener) { result = runSynchronized.execute(); } } } else { // not connected to any trace now: result = runSynchronized.execute(); } return result; } /** * @see info.monitorenter.gui.chart.ITracePoint2D#setScaledX(double) */ public final void setScaledX(final double scaledX) { this.m_scaledX = scaledX; } /** * @see info.monitorenter.gui.chart.ITracePoint2D#setScaledY(double) */ public final void setScaledY(final double scaledY) { this.m_scaledY = scaledY; } /** * @see info.monitorenter.gui.chart.ITracePoint2D#toString() */ @Override public String toString() { return "TracePoint2D[" + this.m_x + ", " + this.m_y + "]"; } /** * @see info.monitorenter.gui.chart.ITracePoint2D#removeAdditionalPointPainter(info.monitorenter.gui.chart.IPointPainter) */ public boolean removeAdditionalPointPainter(final IPointPainter<?> pointPainter) { Boolean result; result = this.doSynchronized(new ICodeBlock<Boolean>() { @SuppressWarnings("synthetic-access") public Boolean execute() { boolean res = TracePoint2D.this.m_additionalPointPainters.remove(pointPainter); if (res) { if (TracePoint2D.this != null) { TracePoint2D.this.m_listener.firePointChanged(TracePoint2D.this, ITracePoint2D.STATE_CHANGED); } } return Boolean.valueOf(res); } }); return result.booleanValue(); } /** * @see info.monitorenter.gui.chart.ITracePoint2D#removeAllAdditionalPointPainters() */ public Set<IPointPainter<?>> removeAllAdditionalPointPainters() { return this.doSynchronized(new ICodeBlock<Set<IPointPainter<?>>>() { @SuppressWarnings("synthetic-access") public Set<IPointPainter<?>> execute() { Set<IPointPainter<?>> result = TracePoint2D.this.m_additionalPointPainters; TracePoint2D.this.m_additionalPointPainters = new LinkedHashSet<IPointPainter<?>>(); if (TracePoint2D.this != null) { TracePoint2D.this.m_listener.firePointChanged(TracePoint2D.this, ITracePoint2D.STATE_CHANGED); } return result; } } ); } }