/*
* ATrace2D.java , base class for ITrace2D implementations of jchart2d.
* Copyright (C) 2004 - 2011 Achim Westermann.
*
* 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.traces;
import info.monitorenter.gui.chart.Chart2D;
import info.monitorenter.gui.chart.IErrorBarPolicy;
import info.monitorenter.gui.chart.IPointPainter;
import info.monitorenter.gui.chart.ITrace2D;
import info.monitorenter.gui.chart.ITracePainter;
import info.monitorenter.gui.chart.ITracePoint2D;
import info.monitorenter.gui.chart.ITracePointProvider;
import info.monitorenter.gui.chart.traces.painters.TracePainterPolyline;
import info.monitorenter.util.SerializationUtility;
import info.monitorenter.util.StringUtil;
import info.monitorenter.util.math.MathUtil;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Stroke;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
import javax.swing.event.ChangeListener;
import javax.swing.event.SwingPropertyChangeSupport;
/**
* The abstract basic implementation of
* <code>{@link info.monitorenter.gui.chart.ITrace2D}</code> that provides the
* major amount of aspects needed in order to work correctly together with
* <code>{@link info.monitorenter.gui.chart.Chart2D}</code>.
* <p>
* Caching of minimum and maximum bounds, property change support, the complex
* z-Index handling (incorporates calls to internals of <code>Chart2D</code>,
* default naming, bound management and event handling are covered here.
* <p>
*
* @author <a href="mailto:Achim.Westermann@gmx.de">Achim Westermann </a>
* @version $Revision: 1.75 $
*/
public abstract class ATrace2D implements ITrace2D, Comparable<ITrace2D> {
/**
* Instance counter for read-access in subclasses.
*/
private static int instanceCount = 0;
/** Generated <code>serialVersionUID</code>. * */
private static final long serialVersionUID = -3955095612824507919L;
/**
* Returns the instanceCount for all <code>ATrace2D</code> subclasses.
*
* @return Returns the instanceCount for all <code>ATrace2D</code> subclasses.
*/
public static int getInstanceCount() {
return ATrace2D.instanceCount;
}
/**
* {@link javax.swing.event.ChangeListener} instances (mainly
* <code>Char2D</code> instances that are interested in changes of internal
* <code>ITracePoint2D</code> instances.
*/
private final List<ChangeListener> m_changeListeners = new LinkedList<ChangeListener>();
/** The color property. */
private Color m_color = Color.black;
/** The list of traces that compute their values from this trace. */
protected List<ITrace2D> m_computingTraces = new LinkedList<ITrace2D>();
/** The internal set of the error bar policies to use. */
private Set<IErrorBarPolicy< ? >> m_errorBarPolicies = new TreeSet<IErrorBarPolicy< ? >>();
/**
* Needed for special treatment of cached values in case of empty state (no
* points).
*/
private boolean m_firsttime = true;
/**
* Cached maximum x value for performance improvement.
*/
protected double m_maxX;
/**
* Cached maximum x value with error bar extension for performance
* improvement.
*/
protected double m_maxXErrorBar = -Double.MAX_VALUE;
/**
* Cached maximum y value for performance improvement.
*/
protected double m_maxY;
/**
* Cached maximum y value with error bar extension for performance
* improvement.
*/
protected double m_maxYErrorBar = -Double.MAX_VALUE;
/**
* Cached minimum x value for performance improvement.
*/
protected double m_minX;
/**
* Cached minimum x value with error bar extension for performance
* improvement.
*/
protected double m_minXErrorBar = Double.MAX_VALUE;
/**
* Cached minimum y value for performance improvement.
*/
protected double m_minY;
/**
* Cached minimum y value with error bar extension for performance
* improvement.
*/
protected double m_minYErrorBar = Double.MAX_VALUE;
/**
* The name property.
*/
protected String m_name = "";
/**
* The physical unit property for x dimension.
*/
protected String m_physicalUnitsX = "";
/**
* The physical unit property for x dimension.
*/
protected String m_physicalUnitsY = "";
/** The internal set of point highlighters to use. */
private Set<IPointPainter< ? >> m_pointHighlighters;
/**
* The instance that add support for firing <code>PropertyChangeEvents</code>
* and maintaining <code>PropertyChangeListeners</code>.
* <p>
*/
protected PropertyChangeSupport m_propertyChangeSupport = new SwingPropertyChangeSupport(this);
/**
* The <code>Chart2D</code> this trace is added to. Needed for
* synchronization.
*/
protected Object m_renderer = Boolean.FALSE;
/**
* The stroke property.
*/
private transient Stroke m_stroke;
/** The internal set of trace painters to use. */
private Set<ITracePainter< ? >> m_tracePainters;
/**
* The visible property.
*/
private boolean m_visible = true;
/**
* The zIndex property.
*/
private Integer m_zIndex = Integer.valueOf(ITrace2D.Z_INDEX_MIN + ATrace2D.instanceCount);
/**
* Defcon.
* <p>
*/
public ATrace2D() {
super();
ATrace2D.instanceCount++;
this.m_tracePainters = new LinkedHashSet<ITracePainter< ? >>();
this.m_tracePainters.add(new TracePainterPolyline());
this.m_pointHighlighters = new LinkedHashSet<IPointPainter< ? >>();
this.m_stroke = new BasicStroke(1f);
}
/**
* @see info.monitorenter.gui.chart.ITrace2D#addComputingTrace(info.monitorenter.gui.chart.ITrace2D)
*/
public void addComputingTrace(final ITrace2D trace) {
this.m_computingTraces.add(trace);
}
/**
* Ensures that no deadlock due to a missing internal chart reference may
* occur.
*
* @throws IllegalStateException
* if this trace is not assigned to a chart.
*
*/
protected final void ensureInitialized() {
if (this.m_renderer == Boolean.FALSE) {
throw new IllegalStateException("Connect this trace (" + this.getName()
+ ") to a chart first before this operation (undebuggable deadlocks might occur else)");
} else {
if (Chart2D.DEBUG_THREADING) {
System.out.println(this.getClass() + "(" + Thread.currentThread().getName()
+ ") this.m_renderer: " + this.m_renderer);
}
}
}
/**
* @see info.monitorenter.gui.chart.ITrace2D#addErrorBarPolicy(info.monitorenter.gui.chart.IErrorBarPolicy)
*/
public final boolean addErrorBarPolicy(final IErrorBarPolicy< ? > errorBarPolicy) {
boolean result = false;
if (Chart2D.DEBUG_THREADING) {
System.out.println("addErrorBarPolicy, 0 locks");
}
this.ensureInitialized();
synchronized (this.m_renderer) {
if (Chart2D.DEBUG_THREADING) {
System.out.println("addErrorBarPolicy, 1 lock");
}
synchronized (this) {
if (Chart2D.DEBUG_THREADING) {
System.out.println("addErrorBarPolicy, 2 locks");
}
result = this.m_errorBarPolicies.add(errorBarPolicy);
if (result) {
errorBarPolicy.setTrace(this);
errorBarPolicy.addPropertyChangeListener(IErrorBarPolicy.PROPERTY_CONFIGURATION, this);
this.expandErrorBarBounds();
this.firePropertyChange(ITrace2D.PROPERTY_ERRORBARPOLICY, null, errorBarPolicy);
}
}
}
return result;
}
/**
* @see info.monitorenter.gui.chart.ITrace2D#addPoint(double, double)
*/
public final boolean addPoint(final double x, final double y) {
boolean result = false;
ITracePoint2D p = null;
final Chart2D chart = this.getRenderer();
if (chart != null) {
final ITracePointProvider pointProvider = chart.getTracePointProvider();
if (pointProvider != null) {
p = pointProvider.createTracePoint(x, y);
}
}
if (p != null) {
result = this.addPoint(p);
} else {
// TODO: write a logging here?
final RuntimeException ex = new IllegalStateException(
"Unable to addPoint because trace is not assigned to chart yet. Call chart.addTrace(trace) before!!!");
throw ex;
}
return result;
}
/**
* Add the given point to this <code>ITrace2D</code>.
* <p>
* This implementation performs caching of minimum and maximum values for x
* and y and the delegates to {@link #addPointInternal(ITracePoint2D)} that
* has to perform the "real" add operation.
* <p>
* Property change events are fired as described in method
* <code>{@link #firePointAdded(ITracePoint2D)}</code>.
* <p>
*
* @see #firePointChanged(ITracePoint2D, int)
* @param p
* the <code>TracePoint2D</code> to add.
* @return true if the operation was successful, false else.
*/
public final boolean addPoint(final ITracePoint2D p) {
if (Chart2D.DEBUG_THREADING) {
System.out.println(Thread.currentThread().getName() + ", ATrace2D.addPoint, 0 locks");
}
boolean accepted = false;
this.ensureInitialized();
synchronized (this.m_renderer) {
if (Chart2D.DEBUG_THREADING) {
if (!(this.m_renderer instanceof Chart2D)) {
throw new RuntimeException(
"Call chart.setTrace(trace) first before adding points or you might run into deadlocks!");
}
System.out.println(Thread.currentThread().getName() + ", ATrace2D.addPoint, 1 lock");
}
synchronized (this) {
if (Chart2D.DEBUG_THREADING) {
System.out.println(Thread.currentThread().getName() + ", ATrace2D.addPoint, 2 locks");
}
accepted = this.addPointInternal(p);
if (accepted) {
// fires property changes for max/min x/y:
this.expandErrorBarBounds();
// min max bounds exceeded?
this.firePointAdded(p);
p.setListener(this);
// inform computing traces:
if (this.m_computingTraces.size() > 0) {
final Iterator<ITrace2D> it = this.m_computingTraces.iterator();
ITrace2D trace;
while (it.hasNext()) {
trace = it.next();
trace.addPoint(p);
}
}
}
if (this.m_firsttime) {
// MAX events / members are done already from the
// firePointAdded()->firePointChanged() method,
// this is only the special case that a new point also marks the
// minimum.
// Don't move this code block before the firePointAdded or
// the minimum of the chart will be higher than the maximum
// which causes an infinite loop in AxisAutoUnit!
this.m_minX = p.getX();
this.m_minY = p.getY();
this.m_maxX = p.getX();
this.m_maxY = p.getY();
final Double zero = new Double(0);
this.firePropertyChange(ITrace2D.PROPERTY_MIN_X, zero, new Double(this.m_minX));
this.firePropertyChange(ITrace2D.PROPERTY_MIN_Y, zero, new Double(this.m_minY));
this.m_firsttime = false;
}
}
if (Chart2D.DEBUG_THREADING) {
System.out.println(Thread.currentThread().getName()
+ ", ATrace2D.addPoint, freed 1 lock, 1 lock remaining.");
}
}
if (Chart2D.DEBUG_THREADING) {
System.out.println(Thread.currentThread().getName()
+ ", ATrace2D.addPoint, freed 1 lock, 0 locks remaining.");
}
return accepted;
}
/**
* @see info.monitorenter.gui.chart.ITrace2D#addPointHighlighter(info.monitorenter.gui.chart.IPointPainter)
*/
public boolean addPointHighlighter(final IPointPainter< ? > highlighter) {
boolean result = false;
this.ensureInitialized();
synchronized (this.m_renderer) {
synchronized (this) {
result = this.m_pointHighlighters.add(highlighter);
if (result) {
this.firePropertyChange(ITrace2D.PROPERTY_POINT_HIGHLIGHTERS_CHANGED, null, highlighter);
}
}
}
return result;
}
/**
* <p>
* Override this template method for the custom add operation that depends on
* the policies of the implementation.
* </p>
* <p>
* No property change events have to be fired by default. If this method
* returns <code>true</code> the outer logic of the calling method
* <code>{@link #addPoint(ITracePoint2D)}</code> will perform bound checks for
* the new point and fire property changes as described in method
* <code>{@link #firePointChanged(ITracePoint2D, int)}</code>.
* </p>
* <p>
* In special cases - when additional modifications to the internal set of
* points take place (e.g. a further point gets removed) this method should
* return false (regardless whether the new point was accepted or not) and
* perform bound checks and fire the property changes as mentioned above
* "manually".
* </p>
*
* @param p
* the point to add.
* @return true if the given point was accepted or false if not.
*/
protected abstract boolean addPointInternal(ITracePoint2D p);
/**
* @see info.monitorenter.gui.chart.ITrace2D#addPropertyChangeListener(java.lang.String,
* java.beans.PropertyChangeListener)
*/
public final void addPropertyChangeListener(final String propertyName,
final PropertyChangeListener listener) {
this.m_propertyChangeSupport.addPropertyChangeListener(propertyName, listener);
}
/**
* @see info.monitorenter.gui.chart.ITrace2D#addTracePainter(info.monitorenter.gui.chart.ITracePainter)
*/
public boolean addTracePainter(final ITracePainter< ? > painter) {
boolean result = false;
this.ensureInitialized();
synchronized (this.m_renderer) {
synchronized (this) {
result = this.m_tracePainters.add(painter);
if (result) {
this.firePropertyChange(ITrace2D.PROPERTY_PAINTERS, null, painter);
}
}
}
return result;
}
/**
* @param o
* the trace to compare to.
* @return see interface.
* @see java.lang.Comparable#compareTo(java.lang.Object)
*/
public final int compareTo(final ITrace2D o) {
// compare by z-index
int result = this.getZIndex().compareTo(o.getZIndex());
// for equal z-indices be more fine grained or else within a set traces
// would get lost!
if (result == 0) {
final int me = this.hashCode();
final int it = o.hashCode();
result = me - it;
}
return result;
}
/**
* @see info.monitorenter.gui.chart.ITrace2D#containsTracePainter(info.monitorenter.gui.chart.ITracePainter)
*/
public boolean containsTracePainter(final ITracePainter< ? > painter) {
return this.m_tracePainters.contains(painter);
}
/**
* Internally expands all bounds according to potential error bars.
*/
private void expandErrorBarBounds() {
final boolean requiresErrorBarCalculation = !this.isEmpty();
if (requiresErrorBarCalculation) {
boolean change;
this.ensureInitialized();
synchronized (this.m_renderer) {
synchronized (this) {
if (this.showsPositiveXErrorBars()) {
change = this.expandMaxXErrorBarBounds();
if (change) {
this.firePropertyChange(ITrace2D.PROPERTY_MAX_X, this, new Double(this.getMaxX()));
}
} else {
if (this.m_maxXErrorBar != -Double.MAX_VALUE) {
this.m_maxXErrorBar = -Double.MAX_VALUE;
this.firePropertyChange(ITrace2D.PROPERTY_MAX_X, this, new Double(this.getMaxX()));
}
}
if (this.showsPositiveYErrorBars()) {
change = this.expandMaxYErrorBarBounds();
if (change) {
this.firePropertyChange(ITrace2D.PROPERTY_MAX_Y, this, new Double(this.getMaxY()));
}
} else {
if (this.m_maxYErrorBar != -Double.MAX_VALUE) {
this.m_maxYErrorBar = -Double.MAX_VALUE;
this.firePropertyChange(ITrace2D.PROPERTY_MAX_Y, this, new Double(this.getMaxY()));
}
}
if (this.showsNegativeXErrorBars()) {
change = this.expandMinXErrorBarBounds();
if (change) {
this.firePropertyChange(ITrace2D.PROPERTY_MIN_X, this, new Double(this.getMinX()));
}
} else {
if (this.m_minXErrorBar != Double.MAX_VALUE) {
this.m_minXErrorBar = Double.MAX_VALUE;
this.firePropertyChange(ITrace2D.PROPERTY_MIN_X, this, new Double(this.getMinX()));
}
}
if (this.showsNegativeYErrorBars()) {
change = this.expandMinYErrorBarBounds();
if (change) {
this.firePropertyChange(ITrace2D.PROPERTY_MIN_Y, this, new Double(this.getMinY()));
}
} else {
if (this.m_minYErrorBar != Double.MAX_VALUE) {
this.m_minYErrorBar = Double.MAX_VALUE;
this.firePropertyChange(ITrace2D.PROPERTY_MIN_Y, this, new Double(this.getMinY()));
}
}
}
}
}
}
/**
* Internally takes into account that in case of error bars to render the
* maximum x value will be different.
* <p>
* Returns true if a change to <code>{@link #getMaxX()}</code> was done.
* <p>
*
* @return true if a change to <code>{@link #getMaxX()}</code> was done.
*/
private boolean expandMaxXErrorBarBounds() {
final Chart2D chart = this.getRenderer();
boolean change = false;
double errorBarMaxXCollect = -Double.MAX_VALUE;
if (chart != null) {
double errorBarMaxX = -Double.MAX_VALUE;
for (final IErrorBarPolicy< ? > errorBarPolicy : this.m_errorBarPolicies) {
if (errorBarPolicy.isShowPositiveXErrors()) {
// was it turned on?
errorBarMaxX = errorBarPolicy.getXError(this.m_maxX);
if (errorBarMaxX > errorBarMaxXCollect) {
errorBarMaxXCollect = errorBarMaxX;
}
}
}
}
final double absoluteMax = errorBarMaxXCollect + this.m_maxX;
if (!MathUtil.assertEqual(this.m_maxXErrorBar, absoluteMax, 0.00000001)) {
this.m_maxXErrorBar = absoluteMax;
change = true;
}
return change;
}
/**
* Internally takes into account that in case of error bars to render the
* maximum y value will be different.
* <p>
* Returns true if a change to <code>{@link #getMaxY()}</code> was done.
* <p>
*
* @return true if a change to <code>{@link #getMaxY()}</code> was done.
*/
private boolean expandMaxYErrorBarBounds() {
final Chart2D chart = this.getRenderer();
boolean change = false;
double errorBarMaxYCollect = -Double.MAX_VALUE;
if (chart != null) {
double errorBarMaxY = -Double.MAX_VALUE;
for (final IErrorBarPolicy< ? > errorBarPolicy : this.m_errorBarPolicies) {
if (errorBarPolicy.isShowPositiveYErrors()) {
errorBarMaxY = errorBarPolicy.getYError(this.m_maxY);
if (errorBarMaxY > errorBarMaxYCollect) {
errorBarMaxYCollect = errorBarMaxY;
}
}
}
}
final double absoluteMax = errorBarMaxYCollect + this.m_maxY;
if (!MathUtil.assertEqual(this.m_maxYErrorBar, absoluteMax, 0.00000001)) {
this.m_maxYErrorBar = absoluteMax;
change = true;
}
return change;
}
/**
* Internally takes into account that in case of error bars to render the
* minimum x value will be different.
* <p>
* Returns true if a change to <code>{@link #getMinX()}</code> was done.
* <p>
*
* @return true if a change to <code>{@link #getMinX()}</code> was done.
*/
private boolean expandMinXErrorBarBounds() {
final Chart2D chart = this.getRenderer();
boolean change = false;
double errorBarMinXCollect = -Double.MAX_VALUE;
if (chart != null) {
double errorBarMinX = -Double.MAX_VALUE;
for (final IErrorBarPolicy< ? > errorBarPolicy : this.m_errorBarPolicies) {
if (errorBarPolicy.isShowNegativeXErrors()) {
errorBarMinX = errorBarPolicy.getXError(this.m_minX);
if (errorBarMinX > errorBarMinXCollect) {
errorBarMinXCollect = errorBarMinX;
}
}
}
}
final double absoluteMin = this.m_minX - errorBarMinXCollect;
if (!MathUtil.assertEqual(this.m_minXErrorBar, absoluteMin, 0.00000001)) {
this.m_minXErrorBar = absoluteMin;
change = true;
}
return change;
}
/**
* Internally takes into account that in case of error bars to render the
* minimum y value will be different.
* <p>
* Returns true if a change to <code>{@link #getMinY()}</code> was done.
* <p>
*
* @return true if a change to <code>{@link #getMinY()}</code> was done.
*/
private boolean expandMinYErrorBarBounds() {
final Chart2D chart = this.getRenderer();
boolean change = false;
double errorBarMinYCollect = -Double.MAX_VALUE;
if (chart != null) {
double errorBarMinY = -Double.MAX_VALUE;
for (final IErrorBarPolicy< ? > errorBarPolicy : this.getErrorBarPolicies()) {
if (errorBarPolicy.isShowNegativeYErrors()) {
// calculate the error
errorBarMinY = errorBarPolicy.getYError(this.m_minY);
if (errorBarMinY > errorBarMinYCollect) {
errorBarMinYCollect = errorBarMinY;
}
}
}
}
final double absoluteMin = this.m_minY - errorBarMinYCollect;
if (!MathUtil.assertEqual(this.m_minYErrorBar, absoluteMin, 0.00000001)) {
this.m_minYErrorBar = absoluteMin;
change = true;
}
return change;
}
/**
* Decreases internal instance count by one.
* <p>
*
* @throws Throwable
* if something goes wrong.
*/
@Override
protected void finalize() throws Throwable {
super.finalize();
ATrace2D.instanceCount--;
}
/**
* Fire property change events related to an added point.
* <p>
* A property change event for property
* <code>{@link ITrace2D#PROPERTY_TRACEPOINT}</code> with null as the old
* value and the new point as the new value is fired. This allows e.g.
* rescaling of those instances (instead of having to rescale a whole trace).
* <p>
* Additionally before this property change, property change events for bounds
* are fired as described in method
* <code>{@link #firePointChanged(ITracePoint2D, int)}</code>.
* <p>
*
* @param added
* the point that was added.
*/
protected void firePointAdded(final ITracePoint2D added) {
this.firePointChanged(added, ITracePoint2D.STATE_ADDED);
this.firePropertyChange(ITrace2D.PROPERTY_TRACEPOINT, null, added);
}
/**
* Method triggered by <code>{@link ITracePoint2D#setLocation(double, double)}
* </code>, <code>{@link #addPoint(ITracePoint2D)}</code>
* or <code>
* {@link #removePoint(ITracePoint2D)}</code>.
* <p>
* Bound checks are performed and property change events for the properties
* <code>{@link ITrace2D#PROPERTY_MAX_X}</code>,
* <code>{@link ITrace2D#PROPERTY_MIN_X}</code>,
* <code>{@link ITrace2D#PROPERTY_MAX_Y}</code> and
* <code>{@link ITrace2D#PROPERTY_MIN_Y}</code> are fired if the add bounds
* have changed due to the modification of the point.
* <p>
* If <code>state</code> is <code>{@link ITracePoint2D#STATE_CHANGED}</code> a
* property change event with
* <code>{@link ITrace2D#PROPERTY_POINT_CHANGED}</code> will be fired to all
* listeners.
* <p>
*
* @param changed
* the point that has been changed which may be a newly added point
* (from <code>{@link #addPoint(ITracePoint2D)}</code>, a removed one
* or a modified one.
* @param state
* one of {<code>{@link ITracePoint2D#STATE_ADDED},
* {@link ITracePoint2D#STATE_CHANGED},
* {@link ITracePoint2D#STATE_REMOVED}</code> to inform about the
* type of change.
*/
public void firePointChanged(final ITracePoint2D changed, final int state) {
double tmpx = changed.getX();
double tmpy = changed.getY();
this.ensureInitialized();
synchronized (this.m_renderer) {
synchronized (this) {
// for a changed point all cases (new extremum as for added case, other
// point becomes extremum as the change point was one like in removed
// case) have
// to be tested. Additionally we have to fire a changd point event.
if (ITracePoint2D.STATE_ADDED == state) {
// add
if (tmpx > this.m_maxX) {
this.m_maxX = tmpx;
this.expandMaxXErrorBarBounds();
this.firePropertyChange(ITrace2D.PROPERTY_MAX_X, null, new Double(this.m_maxX));
} else if (tmpx < this.m_minX) {
this.m_minX = tmpx;
this.expandMinXErrorBarBounds();
this.firePropertyChange(ITrace2D.PROPERTY_MIN_X, null, new Double(this.m_minX));
}
if (tmpy > this.m_maxY) {
this.m_maxY = tmpy;
this.expandMaxYErrorBarBounds();
this.firePropertyChange(ITrace2D.PROPERTY_MAX_Y, null, new Double(this.m_maxY));
} else if (tmpy < this.m_minY) {
this.m_minY = tmpy;
this.expandMinYErrorBarBounds();
this.firePropertyChange(ITrace2D.PROPERTY_MIN_Y, null, new Double(this.m_minY));
}
}
if (ITracePoint2D.STATE_REMOVED == state) {
// removal: care for extrema (<=, >=)
if (tmpx >= this.m_maxX) {
tmpx = this.m_maxX;
this.maxXSearch();
this.firePropertyChange(ITrace2D.PROPERTY_MAX_X, new Double(tmpx), new Double(
this.m_maxX));
} else if (tmpx <= this.m_minX) {
tmpx = this.m_minX;
this.minXSearch();
this.firePropertyChange(ITrace2D.PROPERTY_MIN_X, new Double(tmpx), new Double(
this.m_minX));
}
if (tmpy >= this.m_maxY) {
tmpy = this.m_maxY;
this.maxYSearch();
this.firePropertyChange(ITrace2D.PROPERTY_MAX_Y, new Double(tmpy), new Double(
this.m_maxY));
} else if (tmpy <= this.m_minY) {
tmpy = this.m_minY;
this.minYSearch();
this.firePropertyChange(ITrace2D.PROPERTY_MIN_Y, new Double(tmpy), new Double(
this.m_minY));
}
if (this.getSize() == 0) {
this.m_firsttime = true;
}
}
if (state == ITracePoint2D.STATE_CHANGED) {
if (tmpx < this.m_maxX) {
final double oldMaxX = this.m_maxX;
this.maxXSearch();
this.firePropertyChange(ITrace2D.PROPERTY_MAX_X, new Double(oldMaxX), new Double(
this.m_maxX));
} else if (tmpx > this.m_maxX) {
final double oldMaxX = this.m_maxX;
this.m_maxX = tmpx;
this.expandMaxXErrorBarBounds();
this.firePropertyChange(ITrace2D.PROPERTY_MAX_X, new Double(oldMaxX), new Double(
this.m_maxX));
}
if (tmpx > this.m_minX) {
final double oldMinX = this.m_minX;
this.minXSearch();
this.firePropertyChange(ITrace2D.PROPERTY_MIN_X, new Double(oldMinX), new Double(
this.m_minX));
} else if (tmpx < this.m_minX) {
final double oldMinX = this.m_minX;
this.m_minX = tmpx;
this.expandMinXErrorBarBounds();
this.firePropertyChange(ITrace2D.PROPERTY_MIN_X, new Double(oldMinX), new Double(
this.m_minX));
}
if (tmpy < this.m_maxY) {
final double oldMaxY = this.m_maxY;
this.maxYSearch();
this.firePropertyChange(ITrace2D.PROPERTY_MAX_Y, new Double(oldMaxY), new Double(
this.m_maxY));
} else if (tmpy > this.m_maxY) {
final double oldMaxY = this.m_maxY;
this.m_maxY = tmpy;
this.expandMaxYErrorBarBounds();
this.firePropertyChange(ITrace2D.PROPERTY_MAX_Y, new Double(oldMaxY), new Double(
this.m_maxY));
}
if (tmpy > this.m_minY) {
final double oldMinY = this.m_minY;
this.minYSearch();
this.firePropertyChange(ITrace2D.PROPERTY_MIN_Y, new Double(oldMinY), new Double(
this.m_minY));
} else if (tmpy < this.m_minY) {
final double oldMinY = this.m_minY;
this.m_minY = tmpy;
this.expandMinYErrorBarBounds();
this.firePropertyChange(ITrace2D.PROPERTY_MIN_Y, new Double(oldMinY), new Double(
this.m_minY));
}
this.firePropertyChange(ITrace2D.PROPERTY_POINT_CHANGED, null, changed);
}
}
}
}
/**
* Fire property change events related to a removed point.
* <p>
* A property change event for property
* <code>{@link ITrace2D#PROPERTY_TRACEPOINT}</code> with a point as the old
* value and null as the new value is fired. This allows e.g. rescaling of
* those instances (instead of having to rescale a whole trace).
* <p>
* Additionally before this property change, property change events for bounds
* are fired as described in method
* <code>{@link #firePointChanged(ITracePoint2D, int)}</code>.
* <p>
*
* @param removed
* the point that was removed.
*/
protected void firePointRemoved(final ITracePoint2D removed) {
this.firePointChanged(removed, ITracePoint2D.STATE_REMOVED);
this.firePropertyChange(ITrace2D.PROPERTY_TRACEPOINT, removed, null);
}
/**
* Fires a property change event to the registered listeners.
* <p>
*
* @param property
* one of the <code>PROPERTY_XXX</code> constants defined in <code>
* {@link ITrace2D}</code>.
* @param oldvalue
* the old value of the property.
* @param newvalue
* the new value of the property.
*/
protected final void firePropertyChange(final String property, final Object oldvalue,
final Object newvalue) {
if (property.equals(ITrace2D.PROPERTY_MAX_X) || property.equals(ITrace2D.PROPERTY_MAX_Y)
|| property.equals(ITrace2D.PROPERTY_MIN_X) || property.equals(ITrace2D.PROPERTY_MIN_Y)
|| property.equals(ITrace2D.PROPERTY_TRACEPOINT)
|| property.equals(ITrace2D.PROPERTY_POINT_CHANGED)) {
if (!Thread.holdsLock(this.m_renderer)) {
throw new RuntimeException("Acquire a lock on the corresponding chart first!");
}
if (!Thread.holdsLock(this)) {
throw new RuntimeException("Acquire a lock on this trace first!");
}
if (Chart2D.DEBUG_THREADING) {
System.out.println("trace.firePropertyChange (" + property + "), 2 locks, renderer is: "
+ this.m_renderer);
}
}
this.m_propertyChangeSupport.firePropertyChange(property, oldvalue, newvalue);
}
/**
* Returns a shallow copied list of the change listeners of this instance.
* <p>
*
* @return a shallow copied list of the change listeners of this instance.
*/
public List<ChangeListener> getChangeListeners() {
return new LinkedList<ChangeListener>(this.m_changeListeners);
}
/**
* Get the <code>Color</code> this trace will be painted with.
* <p>
*
* @return the <code>Color</code> of this instance
*/
public final Color getColor() {
return this.m_color;
}
/**
* @see info.monitorenter.gui.chart.ITrace2D#getErrorBarPolicies()
*/
public final Set<IErrorBarPolicy< ? >> getErrorBarPolicies() {
return this.m_errorBarPolicies;
}
/**
* @see info.monitorenter.gui.chart.ITrace2D#getHasErrorBars()
*/
public final boolean getHasErrorBars() {
boolean result = false;
if (this.m_errorBarPolicies.size() > 0) {
for (final IErrorBarPolicy< ? > errorBarPolicy : this.m_errorBarPolicies) {
if (errorBarPolicy.getErrorBarPainters().size() > 0) {
result |= errorBarPolicy.isShowNegativeXErrors();
if (result) {
break;
}
result |= errorBarPolicy.isShowPositiveXErrors();
if (result) {
break;
}
result |= errorBarPolicy.isShowNegativeYErrors();
if (result) {
break;
}
result |= errorBarPolicy.isShowPositiveYErrors();
if (result) {
break;
}
}
}
}
return result;
}
/**
* Returns a label for this trace.
* <p>
* The label is constructed of
* <ul>
* <li>The name of this trace ({@link #getName()}).</li>
* <li>The physical unit of this trace ({@link #getPhysicalUnits()}).</li>
* </ul>
* <p>
*
* @return a label for this trace.
* @see ITrace2D#getLabel()
* @see #getName()
* @see #getPhysicalUnits()
*/
public final String getLabel() {
String name = this.getName();
final String physunit = this.getPhysicalUnits();
if (!(StringUtil.isEmpty(name)) && (!StringUtil.isEmpty(physunit))) {
name = new StringBuffer(name).append(" ").append(physunit).toString();
} else if (!StringUtil.isEmpty(physunit)) {
name = new StringBuffer("unnamed").append(" ").append(physunit).toString();
}
return name;
}
/**
* Returns the original maximum x- value ignoring the offsetX.
* <p>
*
* @return the original maximum x- value ignoring the offsetX.
*/
public final double getMaxX() {
synchronized (this.m_renderer) {
synchronized (this) {
double result = this.m_maxX;
if (this.m_maxXErrorBar != -Double.MAX_VALUE) {
result = this.m_maxXErrorBar;
}
return result;
}
}
}
/**
* Returns the original maximum y- value ignoring the offsetY.
* <p>
*
* @return the original maximum y- value ignoring the offsetY.
*/
public final double getMaxY() {
this.ensureInitialized();
synchronized (this.m_renderer) {
synchronized (this) {
double result = this.m_maxY;
if (this.m_maxYErrorBar != -Double.MAX_VALUE) {
result = this.m_maxYErrorBar;
}
return result;
}
}
}
/**
* Returns the original minimum x- value ignoring the offsetX.
* <p>
*
* @return the original minimum x- value ignoring the offsetX.
*/
public final double getMinX() {
synchronized (this.m_renderer) {
synchronized (this) {
double result = this.m_minX;
if (this.m_minXErrorBar != Double.MAX_VALUE) {
result = this.m_minXErrorBar;
}
return result;
}
}
}
/**
* Returns the original minimum y- value ignoring the offsetY.
* <p>
*
* @return the original minimum y- value ignoring the offsetY.
*/
public final double getMinY() {
this.ensureInitialized();
synchronized (this.m_renderer) {
synchronized (this) {
double result = this.m_minY;
if (this.m_minYErrorBar != Double.MAX_VALUE) {
result = this.m_minYErrorBar;
}
return result;
}
}
}
/**
* Returns the name of this trace.
* <p>
*
* @return the name of this trace.
* @see ITrace2D#getName()
* @see #setName(String s)
*/
public final String getName() {
return this.m_name;
}
/**
* Naive implementation that iterates over every point.
* <p>
* Subclasses that have more insight about their internal data storage could
* override this with a faster implementation (e.g. if the points are kept in
* a sorted order a skip - strategy) could find the minimum faster.
* <p>
*
* @see info.monitorenter.gui.chart.ITrace2D#getNearestPointEuclid(double,
* double)
*/
public DistancePoint getNearestPointEuclid(final double x, final double y) {
final DistancePoint result = new DistancePoint();
final Iterator<ITracePoint2D> it = this.iterator();
ITracePoint2D point;
double distance;
double shortestDistance = Double.MAX_VALUE;
while (it.hasNext()) {
point = it.next();
distance = point.getEuclidDistance(x, y);
if (distance < shortestDistance) {
shortestDistance = distance;
result.setPoint(point);
result.setDistance(shortestDistance);
}
}
return result;
}
/**
* Naive implementation that iterates over every point.
* <p>
* Subclasses that have more insight about their internal data storage could
* override this with a faster implementation (e.g. if the points are kept in
* a sorted order a skip - strategy could find the minimum faster.
* <p>
*
* @see info.monitorenter.gui.chart.ITrace2D#getNearestPointManhattan(double,
* double)
*/
public DistancePoint getNearestPointManhattan(final double x, final double y) {
final DistancePoint result = new DistancePoint();
final Iterator<ITracePoint2D> it = this.iterator();
ITracePoint2D point;
double manhattanDistance;
double shortestManhattanDistance = Double.MAX_VALUE;
while (it.hasNext()) {
point = it.next();
manhattanDistance = point.getManhattanDistance(x, y);
if (manhattanDistance < shortestManhattanDistance) {
shortestManhattanDistance = manhattanDistance;
result.setPoint(point);
result.setDistance(shortestManhattanDistance);
}
}
return result;
}
/**
* @see info.monitorenter.gui.chart.ITrace2D#getPhysicalUnits()
*/
public final String getPhysicalUnits() {
String result;
if (StringUtil.isEmpty(this.m_physicalUnitsX) && StringUtil.isEmpty(this.m_physicalUnitsY)) {
result = "";
} else {
result = new StringBuffer("[x: ").append(this.getPhysicalUnitsX()).append(", y: ").append(
this.getPhysicalUnitsY()).append("]").toString();
}
return result;
}
/**
* @see info.monitorenter.gui.chart.ITrace2D#getPhysicalUnitsX()
*/
public final String getPhysicalUnitsX() {
return this.m_physicalUnitsX;
}
/**
* @see info.monitorenter.gui.chart.ITrace2D#getPhysicalUnitsY()
*/
public final String getPhysicalUnitsY() {
return this.m_physicalUnitsY;
}
/**
* @see info.monitorenter.gui.chart.ITrace2D#getPointHighlighters()
*/
public final Set<IPointPainter< ? >> getPointHighlighters() {
return this.m_pointHighlighters;
}
/**
* @see info.monitorenter.gui.chart.ITrace2D#getPropertyChangeListeners(String)
*/
public PropertyChangeListener[] getPropertyChangeListeners(final String property) {
return this.m_propertyChangeSupport.getPropertyChangeListeners(property);
}
/**
* Returns the chart that renders this instance or null, if this trace is not
* added to a chart.
* <p>
* The chart that renders this trace registers itself with this trace in
* {@link Chart2D#addTrace(ITrace2D)}.
* <p>
*
* @return Returns the renderer.
* @see Chart2D#addTrace(ITrace2D)
*/
public final Chart2D getRenderer() {
Chart2D result = null;
if (this.m_renderer instanceof Chart2D) {
result = (Chart2D) this.m_renderer;
}
return result;
}
/**
* Get the <code>Stroke</code> object this instance will be painted with.
* <p>
*
* @return the <code>Stroke</code> object this <code>ITrace2D</code> will be
* painted with.
* @see info.monitorenter.gui.chart.ITrace2D#getStroke()
*/
public final Stroke getStroke() {
return this.m_stroke;
}
/**
* @see info.monitorenter.gui.chart.ITrace2D#getTracePainters()
*/
public final Set<ITracePainter< ? >> getTracePainters() {
return this.m_tracePainters;
}
/**
* @see info.monitorenter.gui.chart.ITrace2D#getZIndex()
*/
public final Integer getZIndex() {
// More expensive but the contract of the order of values described in the
// interface is inverted to the contract of TreeSetGreedy.
// This is done here instead of get/set ComparableProperty
// as those are invoked several times for each iteration
// (and paint contains several iterations).
if (Chart2D.DEBUG_THREADING) {
System.out.println(Thread.currentThread().getName() + ", " + this.getClass().getName()
+ ".getZindex, 0 locks");
}
synchronized (this.m_renderer) {
if (Chart2D.DEBUG_THREADING) {
System.out.println(Thread.currentThread().getName() + ", " + this.getClass().getName()
+ ".getZindex, 1 locks");
}
synchronized (this) {
if (Chart2D.DEBUG_THREADING) {
System.out.println(Thread.currentThread().getName() + ", " + this.getClass().getName()
+ ".getZindex, 2 locks");
}
}
if (Chart2D.DEBUG_THREADING) {
System.out.println(Thread.currentThread().getName() + ", " + this.getClass().getName()
+ ".getZindex, freed 1 lock: 1 lock remaining");
}
}
if (Chart2D.DEBUG_THREADING) {
System.out.println(Thread.currentThread().getName() + ", " + this.getClass().getName()
+ ".getZindex, freed 1 lock: 0 locks remaining");
}
return this.m_zIndex;
}
/**
* @see info.monitorenter.gui.chart.ITrace2D#isVisible()
*/
public final boolean isVisible() {
return this.m_visible;
}
/**
* Internal search for the maximum x value that is only invoked if no cached
* value is at hand or bounds have changed by adding new points.
* <p>
* The result is assigned to the property maxX.
* <p>
*
* @see #getMaxX()
*/
protected void maxXSearch() {
if (Chart2D.DEBUG_THREADING) {
System.out.println("trace.maxXSearch, 0 locks");
}
synchronized (this) {
if (Chart2D.DEBUG_THREADING) {
System.out.println("trace.maxXSearch, 1 locks");
}
double ret = -Double.MAX_VALUE;
ITracePoint2D tmpoint = null;
double tmp;
final Iterator<ITracePoint2D> it = this.iterator();
while (it.hasNext()) {
tmpoint = it.next();
tmp = tmpoint.getX();
if (tmp > ret) {
ret = tmp;
}
}
this.m_maxX = ret;
}
// compute the extra amount in case of error bar painters:
this.expandMaxXErrorBarBounds();
}
/**
* Internal search for the maximum y value that is only invoked if no cached
* value is at hand or bounds have changed by adding new points.
* <p>
* The result is assigned to the property maxY.
* <p>
*
* @see #getMaxY()
*/
protected void maxYSearch() {
if (Chart2D.DEBUG_THREADING) {
System.out.println("trace.maxYSearch, 0 locks");
}
synchronized (this) {
if (Chart2D.DEBUG_THREADING) {
System.out.println("trace.maxYSearch, 1 lock");
}
double ret = -Double.MAX_VALUE;
ITracePoint2D tmpoint = null;
double tmp;
final Iterator<ITracePoint2D> it = this.iterator();
while (it.hasNext()) {
tmpoint = it.next();
tmp = tmpoint.getY();
if (tmp > ret) {
ret = tmp;
}
}
this.m_maxY = ret;
}
// compute the extra amount in case of error bar painters:
this.expandMaxYErrorBarBounds();
}
/**
* Internal search for the minimum x value that is only invoked if no cached
* value is at hand or bounds have changed by adding new points.
* <p>
* The result is assigned to the property minX.
* <p>
*
* @see #getMinX()
*/
protected void minXSearch() {
if (Chart2D.DEBUG_THREADING) {
System.out.println("trace.minXSearch, 0 locks");
}
synchronized (this) {
if (Chart2D.DEBUG_THREADING) {
System.out.println("trace.minXSearch, 1 locks");
}
double ret = Double.MAX_VALUE;
ITracePoint2D tmpoint = null;
double tmp;
final Iterator<ITracePoint2D> it = this.iterator();
while (it.hasNext()) {
tmpoint = it.next();
tmp = tmpoint.getX();
if (tmp < ret) {
ret = tmp;
}
}
this.m_minX = ret;
}
// compute the extra amount in case of error bar painters:
this.expandMinXErrorBarBounds();
}
/**
* Internal search for the minimum y value that is only invoked if no cached
* value is at hand or bounds have changed by adding new points.
* <p>
* The result is assigned to the property minY.
* <p>
*
* @see #getMinY()
*/
protected void minYSearch() {
if (Chart2D.DEBUG_THREADING) {
System.out.println("trace.minYSearch, 0 locks");
}
synchronized (this) {
if (Chart2D.DEBUG_THREADING) {
System.out.println("trace.minYSearch, 1 locks");
}
double ret = Double.MAX_VALUE;
ITracePoint2D tmpoint = null;
double tmp;
final Iterator<ITracePoint2D> it = this.iterator();
while (it.hasNext()) {
tmpoint = it.next();
tmp = tmpoint.getY();
if (tmp < ret) {
ret = tmp;
}
}
this.m_minY = ret;
// compute the extra amount in case of error bar painters:
this.expandMinYErrorBarBounds();
}
}
/**
* @see java.beans.PropertyChangeListener#propertyChange(java.beans.PropertyChangeEvent)
*/
public void propertyChange(final PropertyChangeEvent evt) {
if (IErrorBarPolicy.PROPERTY_CONFIGURATION.equals(evt.getPropertyName())) {
this.expandErrorBarBounds();
this
.firePropertyChange(ITrace2D.PROPERTY_ERRORBARPOLICY_CONFIGURATION, null, evt.getSource());
}
}
/**
* Provides serialization support.
*
* @param stream
* the input stream.
* @throws IOException
* if there is an I/O error.
* @throws ClassNotFoundException
* if there is a classpath problem.
*/
private void readObject(final ObjectInputStream stream) throws IOException,
ClassNotFoundException {
stream.defaultReadObject();
this.m_stroke = SerializationUtility.readStroke(stream);
}
/**
* @see info.monitorenter.gui.chart.ITrace2D#removeAllPointHighlighters()
*/
public Set<IPointPainter< ? >> removeAllPointHighlighters() {
final Set<IPointPainter< ? >> result = new LinkedHashSet<IPointPainter< ? >>();
for (final IPointPainter< ? > highlighter : this.getPointHighlighters()) {
this.removePointHighlighter(highlighter);
result.add(highlighter);
}
return result;
}
/**
* Changes the internal state to empty to allow that the caching of bounds is
* cleared and delegates the call to {@link #removeAllPointsInternal()}.
* <p>
*
* @see info.monitorenter.gui.chart.ITrace2D#removeAllPoints()
*/
public final void removeAllPoints() {
this.ensureInitialized();
synchronized (this.m_renderer) {
synchronized (this) {
this.m_firsttime = true;
this.removeAllPointsInternal();
// property changes:
double oldValue = this.m_maxX;
this.m_maxX = 0;
this.firePropertyChange(ITrace2D.PROPERTY_MAX_X, new Double(oldValue), new Double(
this.m_maxX));
oldValue = this.m_maxY;
this.m_maxY = 0;
this.firePropertyChange(ITrace2D.PROPERTY_MAX_Y, new Double(oldValue), new Double(
this.m_maxY));
oldValue = this.m_minX;
this.m_minX = 0;
this.firePropertyChange(ITrace2D.PROPERTY_MIN_X, new Double(oldValue), new Double(
this.m_minX));
oldValue = this.m_minY;
this.m_minY = 0;
this.firePropertyChange(ITrace2D.PROPERTY_MIN_Y, new Double(oldValue), new Double(
this.m_minY));
// inform computing traces:
for (final ITrace2D trace : this.m_computingTraces) {
trace.removeAllPoints();
}
}
}
}
/**
* Override this template method for the custom remove operation that depends
* on the <code>Collection</code> used in the implementation.
* <p>
* No change events have to be fired, this is done by {@link ATrace2D}.
* <p>
*/
protected abstract void removeAllPointsInternal();
/**
* @see info.monitorenter.gui.chart.ITrace2D#removeComputingTrace(info.monitorenter.gui.chart.ITrace2D)
*/
public boolean removeComputingTrace(final ITrace2D trace) {
final boolean result = this.m_computingTraces.remove(trace);
return result;
}
/**
* @see info.monitorenter.gui.chart.ITrace2D#removeErrorBarPolicy(info.monitorenter.gui.chart.IErrorBarPolicy)
*/
public boolean removeErrorBarPolicy(final IErrorBarPolicy< ? > errorBarPolicy) {
boolean result = false;
if (Chart2D.DEBUG_THREADING) {
System.out.println("addErrorBarPolicy, 0 locks");
}
this.ensureInitialized();
synchronized (this.m_renderer) {
if (Chart2D.DEBUG_THREADING) {
System.out.println("addErrorBarPolicy, 1 lock");
}
synchronized (this) {
if (Chart2D.DEBUG_THREADING) {
System.out.println("addErrorBarPolicy, 2 locks");
}
result = this.m_errorBarPolicies.remove(errorBarPolicy);
if (result) {
errorBarPolicy.setTrace(null);
errorBarPolicy.removePropertyChangeListener(IErrorBarPolicy.PROPERTY_CONFIGURATION, this);
this.expandErrorBarBounds();
this.firePropertyChange(ITrace2D.PROPERTY_ERRORBARPOLICY, errorBarPolicy, null);
}
}
}
return result;
}
/**
* Remove the given point from this <code>ITrace2D</code>.
* <p>
* This implementation performs caching of minimum and maximum values for x
* and y and the delegates to
* <code>{@link #removePointInternal(ITracePoint2D)}</code> that has to
* perform the "real" add remove operation.
* <p>
* Property change events are fired as described in method
* <code>{@link #firePointRemoved(ITracePoint2D)}</code>.
* <p>
*
* @param point
* the <code>TracePoint2D</code> to remove.
* @return true if the removal succeeded, false else: this could be that the
* given point was not contained.
* @see #firePointChanged(ITracePoint2D, int)
*/
public boolean removePoint(final ITracePoint2D point) {
this.ensureInitialized();
synchronized (this.m_renderer) {
if (Chart2D.DEBUG_THREADING) {
System.out.println("removePoint, 0 locks");
}
synchronized (this) {
if (Chart2D.DEBUG_THREADING) {
System.out.println("removePoint, 1 lock");
}
final ITracePoint2D removed = this.removePointInternal(point);
if (removed != null) {
double tmpx = removed.getX();
double tmpy = removed.getY();
// System.out.println("Trace2DLtd.addPoint() removed point!");
if (tmpx >= this.m_maxX) {
tmpx = this.m_maxX;
this.maxXSearch();
this.firePropertyChange(ITrace2D.PROPERTY_MAX_X, new Double(tmpx), new Double(
this.m_maxX));
} else if (tmpx <= this.m_minX) {
tmpx = this.m_minX;
this.minXSearch();
this.firePropertyChange(ITrace2D.PROPERTY_MIN_X, new Double(tmpx), new Double(
this.m_minX));
}
if (tmpy >= this.m_maxY) {
tmpy = this.m_maxY;
this.maxYSearch();
this.firePropertyChange(ITrace2D.PROPERTY_MAX_Y, new Double(tmpy), new Double(
this.m_maxY));
} else if (tmpy <= this.m_minY) {
tmpy = this.m_minY;
this.minYSearch();
this.firePropertyChange(ITrace2D.PROPERTY_MIN_Y, new Double(tmpy), new Double(
this.m_minY));
}
this.firePointRemoved(removed);
removed.setListener(null);
// inform computing traces:
for (final ITrace2D trace : this.m_computingTraces) {
trace.removePoint(removed);
}
}
return removed != null;
}
}
}
/**
* @see info.monitorenter.gui.chart.ITrace2D#removePointHighlighter(info.monitorenter.gui.chart.IPointPainter)
*/
public boolean removePointHighlighter(final IPointPainter< ? > higlighter) {
this.ensureInitialized();
synchronized (this.m_renderer) {
synchronized (this) {
boolean result = false;
result = this.m_pointHighlighters.remove(higlighter);
if (result) {
this.firePropertyChange(ITrace2D.PROPERTY_POINT_HIGHLIGHTERS_CHANGED, higlighter, null);
}
return result;
}
}
}
/**
* Override this template method for the custom remove operation that depends
* on the internal storage the implementation.
* <p>
* The returned point may be the same as the given. But some "computing"
* traces like
* <code>{@link info.monitorenter.gui.chart.traces.computing.Trace2DArithmeticMean}</code>
* will internally delete a different point and return that one.
* <p>
* No property change events have to be fired by default. If this method
* returns <code>null</code> the outer logic of the calling method
* <code>{@link #removePoint(ITracePoint2D)}</code> will perform bound checks
* for the returned point and fire property changes for the properties
* <code>{@link ITrace2D#PROPERTY_MAX_X}</code>,
* <code>{@link ITrace2D#PROPERTY_MIN_X}</code>,
* <code>{@link ITrace2D#PROPERTY_MAX_Y}</code> and
* <code>{@link ITrace2D#PROPERTY_MIN_Y}</code>.
* <p>
* In special cases - when additional modifications to the internal set of
* points take place (e.g. a further point get added) this method should
* return false (regardless whether the old point was really removed or not)
* and perform bound checks and fire the property changes as mentioned above
* "manually".
* <p>
*
* @param point
* the point to remove.
* @return null if unsuccessful (and no events should be fired) or the point
* that actually was removed (in case different than the given one it
* should be somehow related to the given one).
*/
protected abstract ITracePoint2D removePointInternal(final ITracePoint2D point);
/**
* @see info.monitorenter.gui.chart.ITrace2D#removePropertyChangeListener(java.beans.PropertyChangeListener)
*/
public void removePropertyChangeListener(final PropertyChangeListener listener) {
this.m_propertyChangeSupport.removePropertyChangeListener(listener);
}
/**
* @see info.monitorenter.gui.chart.ITrace2D#removePropertyChangeListener(java.lang.String,
* java.beans.PropertyChangeListener)
*/
public void removePropertyChangeListener(final String property,
final PropertyChangeListener listener) {
this.m_propertyChangeSupport.removePropertyChangeListener(property, listener);
}
/**
* @see info.monitorenter.gui.chart.ITrace2D#removeTracePainter(info.monitorenter.gui.chart.ITracePainter)
*/
public boolean removeTracePainter(final ITracePainter< ? > painter) {
boolean result = false;
if (this.m_tracePainters.size() > 1) {
result = this.m_tracePainters.remove(painter);
if (result) {
this.firePropertyChange(ITrace2D.PROPERTY_PAINTERS, painter, null);
}
} else {
// nop: if not contained operation will not be successful, if contained
// not allowed.
}
return result;
}
/**
* <p>
* Set the <code>Color</code> this trace will be painted with.
* </p>
*
* @param color
* the <code>Color</code> this trace will be painted with.
*/
public final void setColor(final Color color) {
final Color oldValue = this.m_color;
this.m_color = color;
if (!this.m_color.equals(oldValue)) {
this.firePropertyChange(ITrace2D.PROPERTY_COLOR, oldValue, this.m_color);
}
}
/**
* @see info.monitorenter.gui.chart.ITrace2D#setErrorBarPolicy(info.monitorenter.gui.chart.IErrorBarPolicy)
*/
public final Set<IErrorBarPolicy< ? >> setErrorBarPolicy(final IErrorBarPolicy< ? > errorBarPolicy) {
final Set<IErrorBarPolicy< ? >> result = this.m_errorBarPolicies;
if (Chart2D.DEBUG_THREADING) {
System.out.println("setErrorBarPolicy, 0 locks");
}
this.ensureInitialized();
synchronized (this.m_renderer) {
if (Chart2D.DEBUG_THREADING) {
System.out.println("setErrorBarPolicy, 1 lock");
}
synchronized (this) {
if (Chart2D.DEBUG_THREADING) {
System.out.println("setErrorBarPolicy, 2 locks");
}
this.m_errorBarPolicies = new TreeSet<IErrorBarPolicy< ? >>();
final boolean added = this.m_errorBarPolicies.add(errorBarPolicy);
if (added) {
errorBarPolicy.setTrace(this);
this.expandErrorBarBounds();
errorBarPolicy.addPropertyChangeListener(IErrorBarPolicy.PROPERTY_CONFIGURATION, this);
this.firePropertyChange(ITrace2D.PROPERTY_ERRORBARPOLICY, null, errorBarPolicy);
}
// now remove this from the previous instances:
for (final IErrorBarPolicy< ? > oldPolicy : result) {
oldPolicy.setTrace(null);
errorBarPolicy.removePropertyChangeListener(IErrorBarPolicy.PROPERTY_CONFIGURATION, this);
this.firePropertyChange(ITrace2D.PROPERTY_ERRORBARPOLICY, oldPolicy, null);
}
}
}
return result;
}
/**
* Sets the descriptive name for this trace.
* <p>
* If the given argument is null or consists of whitespaces only the lable for
* this trace might become invisible (depending on physical units set).
* <p>
*
* @param name
* the descriptive name for this trace.
* @see info.monitorenter.gui.chart.ITrace2D#setName(java.lang.String)
*/
public final void setName(final String name) {
final String oldValue = this.m_name;
final String oldLabel = this.getLabel();
this.m_name = name;
final String newLabel = this.getLabel();
this.firePropertyChange(ITrace2D.PROPERTY_LABEL, oldLabel, newLabel);
this.firePropertyChange(ITrace2D.PROPERTY_NAME, oldValue, this.m_name);
}
/**
* @see ITrace2D#setPhysicalUnits(String, String)
*/
public final void setPhysicalUnits(final String xunit, final String yunit) {
final String oldValue = this.getPhysicalUnits();
final String oldLabel = this.getLabel();
this.m_physicalUnitsX = xunit;
this.m_physicalUnitsY = yunit;
final String newValue = this.getPhysicalUnits();
final String newLabel = this.getLabel();
this.firePropertyChange(ITrace2D.PROPERTY_LABEL, oldLabel, newLabel);
this.firePropertyChange(ITrace2D.PROPERTY_PHYSICALUNITS, oldValue, newValue);
}
/**
*
* @see info.monitorenter.gui.chart.ITrace2D#setPointHighlighter(info.monitorenter.gui.chart.IPointPainter)
*/
public final Set<IPointPainter< ? >> setPointHighlighter(final IPointPainter< ? > highlighter) {
this.ensureInitialized();
synchronized (this.m_renderer) {
synchronized (this) {
Set<IPointPainter< ? >> result = this.m_pointHighlighters;
this.m_pointHighlighters = new LinkedHashSet<IPointPainter< ? >>();
final boolean added = this.m_pointHighlighters.add(highlighter);
if (added) {
for (final IPointPainter< ? > rem : result) {
this.firePropertyChange(ITrace2D.PROPERTY_POINT_HIGHLIGHTERS_CHANGED, rem, null);
}
this.firePropertyChange(ITrace2D.PROPERTY_POINT_HIGHLIGHTERS_CHANGED, null, highlighter);
} else {
// roll back: will never happen, but here for formal reason
this.m_pointHighlighters = result;
result = null;
}
return result;
}
}
}
/**
* Allows the chart this instance is painted by to register itself.
* <p>
* This is internally required for synchronization and re-ordering due to
* z-Index changes.
* <p>
*
* @param renderer
* the chart that paints this instance.
*/
public final void setRenderer(final Chart2D renderer) {
final boolean requiresErrorBarCalculation = this.m_renderer != renderer;
this.m_renderer = renderer;
if (requiresErrorBarCalculation) {
this.expandErrorBarBounds();
}
}
/**
* @see info.monitorenter.gui.chart.ITrace2D#setStroke(java.awt.Stroke)
*/
public final void setStroke(final Stroke stroke) {
if (stroke == null) {
throw new IllegalArgumentException("Argument must not be null.");
}
final Stroke oldValue = this.m_stroke;
this.m_stroke = stroke;
if (!this.m_stroke.equals(oldValue)) {
this.firePropertyChange(ITrace2D.PROPERTY_STROKE, oldValue, this.m_stroke);
}
}
/**
* @see info.monitorenter.gui.chart.ITrace2D#setTracePainter(info.monitorenter.gui.chart.ITracePainter)
*/
public final Set<ITracePainter< ? >> setTracePainter(final ITracePainter< ? > painter) {
Set<ITracePainter< ? >> result = this.m_tracePainters;
this.m_tracePainters = new TreeSet<ITracePainter< ? >>();
final boolean added = this.m_tracePainters.add(painter);
if (added) {
for (final ITracePainter< ? > rem : result) {
this.firePropertyChange(ITrace2D.PROPERTY_PAINTERS, rem, null);
}
this.firePropertyChange(ITrace2D.PROPERTY_PAINTERS, null, painter);
} else {
// roll back: will never happen, but here for formal reason
this.m_tracePainters = result;
result = null;
}
return result;
}
/**
* <p>
* Set the visible property of this instance.
* </p>
* <p>
* Invisible <code>ITrace2D</code> instances (visible == false) will not be
* painted.
* </p>
*
* @param visible
* the visible property of this instance to set.
* @see info.monitorenter.gui.chart.ITrace2D#setVisible(boolean)
*/
public final void setVisible(final boolean visible) {
final boolean oldValue = this.m_visible;
this.m_visible = visible;
if (oldValue != this.m_visible) {
this.firePropertyChange(ITrace2D.PROPERTY_VISIBLE, Boolean.valueOf(oldValue), Boolean
.valueOf(this.m_visible));
}
}
/**
* @see info.monitorenter.gui.chart.ITrace2D#setZIndex(java.lang.Integer)
*/
public final void setZIndex(final Integer zIndex) {
if (Chart2D.DEBUG_THREADING) {
System.out.println("trace.setZIndex, 0 locks");
}
if (!zIndex.equals(this.m_zIndex)) {
this.ensureInitialized();
synchronized (this.m_renderer) {
if (Chart2D.DEBUG_THREADING) {
System.out.println("trace.setZIndex, 1 lock");
}
final Integer oldValue = this.m_zIndex;
synchronized (this) {
if (Chart2D.DEBUG_THREADING) {
System.out.println("trace.setZIndex, 2 locks");
}
this.m_zIndex = Integer.valueOf(zIndex.intValue());
this.firePropertyChange(ITrace2D.PROPERTY_ZINDEX, oldValue, this.m_zIndex);
}
}
}
}
/**
* @see info.monitorenter.gui.chart.ITrace2D#showsErrorBars()
*/
public boolean showsErrorBars() {
boolean result = false;
for (final IErrorBarPolicy< ? > errorBarPolicy : this.m_errorBarPolicies) {
if (errorBarPolicy.getErrorBarPainters().size() > 0) {
if (errorBarPolicy.isShowNegativeXErrors() || errorBarPolicy.isShowNegativeYErrors()
|| errorBarPolicy.isShowPositiveXErrors() || errorBarPolicy.isShowPositiveYErrors()) {
result = true;
break;
}
}
}
return result;
}
/**
* @see info.monitorenter.gui.chart.ITrace2D#showsNegativeXErrorBars()
*/
public boolean showsNegativeXErrorBars() {
boolean result = false;
for (final IErrorBarPolicy< ? > errorBarPolicy : this.m_errorBarPolicies) {
if (errorBarPolicy.getErrorBarPainters().size() > 0) {
if (errorBarPolicy.isShowNegativeXErrors()) {
result = true;
break;
}
}
}
return result;
}
/**
* @see info.monitorenter.gui.chart.ITrace2D#showsNegativeYErrorBars()
*/
public boolean showsNegativeYErrorBars() {
boolean result = false;
for (final IErrorBarPolicy< ? > errorBarPolicy : this.m_errorBarPolicies) {
if (errorBarPolicy.getErrorBarPainters().size() > 0) {
if (errorBarPolicy.isShowNegativeYErrors()) {
result = true;
break;
}
}
}
return result;
}
/**
* @see info.monitorenter.gui.chart.ITrace2D#showsPositiveXErrorBars()
*/
public boolean showsPositiveXErrorBars() {
boolean result = false;
for (final IErrorBarPolicy< ? > errorBarPolicy : this.m_errorBarPolicies) {
if (errorBarPolicy.getErrorBarPainters().size() > 0) {
if (errorBarPolicy.isShowPositiveXErrors()) {
result = true;
break;
}
}
}
return result;
}
/**
* @see info.monitorenter.gui.chart.ITrace2D#showsPositiveYErrorBars()
*/
public boolean showsPositiveYErrorBars() {
boolean result = false;
for (final IErrorBarPolicy< ? > errorBarPolicy : this.m_errorBarPolicies) {
if (errorBarPolicy.getErrorBarPainters().size() > 0) {
if (errorBarPolicy.isShowPositiveYErrors()) {
result = true;
break;
}
}
}
return result;
}
/**
* Returns <code>{@link #getName()}.</code>
* <p>
*
* @return <code>{@link #getName()}</code>.
*/
@Override
public String toString() {
return this.getName();
}
/**
* Provides serialization support.
*
* @param stream
* the output stream.
* @throws IOException
* if there is an I/O error.
*/
private void writeObject(final ObjectOutputStream stream) throws IOException {
stream.defaultWriteObject();
SerializationUtility.writeStroke(this.m_stroke, stream);
}
}