/*
* AAxis.java (bold as love), base class for an axis of the Chart2D.
* Copyright (C) 2007 -2011 Achim Westermann, created on 20:33:13.
*
* 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.axis;
import info.monitorenter.gui.chart.Chart2D;
import info.monitorenter.gui.chart.IAxis;
import info.monitorenter.gui.chart.IAxisLabelFormatter;
import info.monitorenter.gui.chart.IAxisScalePolicy;
import info.monitorenter.gui.chart.IAxisTickPainter;
import info.monitorenter.gui.chart.IAxisTitlePainter;
import info.monitorenter.gui.chart.IRangePolicy;
import info.monitorenter.gui.chart.ITrace2D;
import info.monitorenter.gui.chart.ITracePoint2D;
import info.monitorenter.gui.chart.LabeledValue;
import info.monitorenter.gui.chart.axis.scalepolicy.AxisScalePolicyAutomaticBestFit;
import info.monitorenter.gui.chart.labelformatters.LabelFormatterAutoUnits;
import info.monitorenter.gui.chart.labelformatters.LabelFormatterSimple;
import info.monitorenter.gui.chart.rangepolicies.RangePolicyUnbounded;
import info.monitorenter.util.ExceptionUtil;
import info.monitorenter.util.Range;
import info.monitorenter.util.StringUtil;
import info.monitorenter.util.math.MathUtil;
import java.awt.Dimension;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.event.MouseEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.io.Serializable;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.concurrent.CopyOnWriteArraySet;
/**
* The base class for an axis of the <code>{@link Chart2D}</code>.
* <p>
* Normally - as the design and interaction of an <code>Axis</code> with the
* <code>{@link Chart2D}D</code> is very fine-grained - it is not instantiated
* by users of jchart2d: It is automatically instantiated by the constructor of
* <code>Chart2D</code>. It then may be retrieved from the <code>Chart2D</code>
* by the methods {@link Chart2D#getAxisX()} and {@link Chart2D#getAxisY()} for
* further configuration.
* <p>
*
* @param <T>
* Subtypes may be more picky which scale policies the accept to
* disallow incorrect scales: This supports it (see
* {@link IAxis#setAxisScalePolicy(IAxisScalePolicy)}).
*
* @author <a href="mailto:Achim.Westermann@gmx.de">Achim Westermann </a>
* @version $Revision: 1.61 $
*/
public abstract class AAxis<T extends IAxisScalePolicy> implements IAxis<T>, PropertyChangeListener {
/**
* Used for finding ticks with labels and controlling their value / distance.
*/
private IAxisScalePolicy m_axisScalePolicy;
/**
* @see info.monitorenter.gui.chart.IAxis#getAxisScalePolicy()
*/
public IAxisScalePolicy getAxisScalePolicy() {
return this.m_axisScalePolicy;
}
/**
* The default used is <code>{@link AxisScalePolicyAutomaticBestFit}</code>.
* <p>
*
* @see info.monitorenter.gui.chart.IAxis#setAxisScalePolicy(info.monitorenter.gui.chart.IAxisScalePolicy)
*/
public IAxisScalePolicy setAxisScalePolicy(final T axisScalePolicy) {
// TODO Event management for update painting
IAxisScalePolicy result = this.m_axisScalePolicy;
this.m_axisScalePolicy = axisScalePolicy;
this.m_propertyChangeSupport.firePropertyChange(new PropertyChangeEvent(this,
IAxis.PROPERTY_AXIS_SCALE_POLICY_CHANGED, result, this.m_axisScalePolicy));
return result;
}
/**
* An internal connector class that will connect the axis to the a Chart2D.
* <p>
* It is aggregated to the {@link AAxis} in order to access either y values or
* x values of the Chart2D thus making the IAxis an Y Axis or X axis. This
* strategy reduces redundant code for label creation. It avoids complex
* inheritance / interface implements for different IAxis implementation that
* would be necessary for y-axis / x-axis implementations.
* <p>
*
* @author <a href="mailto:Achim.Westermann@gmx.de">Achim Westermann </a>
*/
public abstract class AChart2DDataAccessor implements Serializable {
/** Generated <code>serialVersionUID</code>. **/
private static final long serialVersionUID = 5023422232812082122L;
/** The chart that is accessed. */
protected Chart2D m_chart;
/**
* Constructor with the chart that is accessed.
* <p>
*
* @param chart
* the chart that is accessed.
*/
protected AChart2DDataAccessor(final Chart2D chart) {
AAxis.this.setAccessor(this);
this.m_chart = chart;
}
/**
* Returns the chart that is accessed.
* <p>
*
* @return the chart that is accessed.
*/
public final Chart2D getChart() {
return this.m_chart;
}
/**
* Returns the constant for the dimension that is accessed on the chart.
* <p>
*
* @return {@link Chart2D#X}, {@link Chart2D#Y} or -1 if this axis is not
* assigned to a chart.
*/
public abstract int getDimension();
/**
* Returns the height in pixel the corresponding axis needs to paint itself.
* <p>
* This includes the axis line, it's ticks and labels and it's title.
* <p>
*
* @param g2d
* needed for font metric information.
* @return the height in pixel the corresponding axis needs to paint itself.
*/
public abstract int getHeight(Graphics g2d);
/**
* Returns the maximum pixels that will be needed to paint a label.
* <p>
*
* @param g2d
* provides information about the graphics context to paint on
* (e.g. font size).
* @return the maximum pixels that will be needed to paint a label.
*/
protected abstract double getMaximumPixelForLabel(final Graphics g2d);
/**
* Returns the max value of the given trace according to the dimension the
* outer axis belongs to.
* <p>
* This is either an x or an y value depending on the dimension the outer
* axis is working in.
* <p>
*
* @param trace
* the trace to read the maximum of.
* @return the max value of the given trace according to the dimension the
* outer axis belongs to.
*/
protected abstract double getMaxValue(ITrace2D trace);
/**
* Returns the minimum amount of increase in the value that will be needed
* to display all labels without overwriting each others.
* <p>
* This procedure needs the amount of pixels needed by the largest possible
* label and relies on the implementation of
* {@link #getMaximumPixelForLabel(Graphics)}, whose result is multiplied
* with the "value per pixel" quantifier.
* <p>
*
* @param g2d
* the current graphic context to use in case further information
* is required.
* @return the minimum amount of increase in the value that will be needed
* to display all labels without overwriting each others.
*/
public abstract double getMinimumValueDistanceForLabels(final Graphics g2d);
/**
* Returns the min value of the given trace according to the dimension the
* outer axis belongs to.
* <p>
* This is either an x or an y value depending on the dimension the outer
* axis is working in.
* <p>
*
* @param trace
* the trace to read the maximum of.
* @return the min value of the given trace according to the dimension the
* outer axis belongs to.
*/
protected abstract double getMinValue(ITrace2D trace);
/**
* Returns the amount of pixel available for displaying the values on the
* chart in the dimension this accessor stands for.
* <p>
* This method must not be called within the first lines of a paint cycle
* (necessary underlying values then are computed new).
* <p>
*
* @return the amount of pixel available for displaying the values on the
* chart in the dimension this accessor stands for.
*/
protected abstract int getPixelRange();
/**
* Returns the value of the given point according to the dimension the outer
* axis belongs to.
* <p>
* This is either <code>{@link ITracePoint2D#getX()}</code> or
* <code>{@link ITracePoint2D#getY()}</code>.
* <p>
*
* @param point
* the point to read <code>{@link ITracePoint2D#getX()}</code> or
* <code>{@link ITracePoint2D#getY()}</code> from.
* @return the value of the given point according to the dimension the outer
* axis belongs to.
*/
protected abstract double getValue(ITracePoint2D point);
/**
* Returns the value distance on the current chart that exists for the given
* amount of pixel distance in the given direction of this
* <code>AAxis</code>.
* <p>
* Depending on the width of the actual Chart2D and the contained values,
* the relation between displayed distances (pixel) and value distances (the
* values of the added
* <code>{@link info.monitorenter.gui.chart.ITrace2D}</code> instances
* changes.
* <p>
* This method calculates depending on the actual painting area of the
* Chart2D, the shift in value between two points that have a screen
* distance of the given pixel. <br>
* This method is not used by the chart itself but a helper for outside use.
* <p>
*
* @param pixel
* The desired distance between to scale points of the x- axis in
* pixel.
* @return a scaled (from pixel to internal value-range) and normed (to the
* factor of the current unit of the axis) value usable to calculate
* the coordinates for the scale points of the axis.
*/
protected abstract double getValueDistanceForPixel(int pixel);
/**
* Returns the width in pixel the corresponding axis needs to paint itself.
* <p>
* This includes the axis line, it's ticks and labels and it's title.
* <p>
*
* @param g2d
* needed for font metric information.
* @return the width in pixel the corresponding axis needs to paint itself.
*/
public abstract int getWidth(Graphics g2d);
/**
* Scales the given trace in the dimension represented by this axis.
* <p>
* This method is not deadlock - safe and should be called by the
* <code>{@link Chart2D}</code> only!
* <p>
*
* @param trace
* the trace to scale.
* @param range
* the range to use as scaler.
*/
protected abstract void scaleTrace(ITrace2D trace, Range range);
/**
* Returns the translation of the mouse event coordinates of the given mouse
* event to the value within the chart for the dimension (x,y) covered by
* this axis.
* <p>
* Note that the mouse event has to be an event fired on this component!
* <p>
*
* @param mouseEvent
* a mouse event that has been fired on this component.
* @return the translation of the mouse event coordinates of the given mouse
* event to the value within the chart for the dimension covered by
* this axis (x or y) or null if no calculations could be performed
* as the chart was not painted before.
*/
public abstract double translateMousePosition(final MouseEvent mouseEvent);
/**
* Transforms the given pixel value (which has to be a awt value like
* {@link java.awt.event.MouseEvent#getX()} into the chart value.
* <p>
* Internal use only, the interface does not guarantee that the pixel
* corresponds to any valid awt pixel value within the chart component.
* <p>
* Warning: A value transformed to a pixel by
* {@link #translateValueToPx(double)} and then retransformed by
* {@link #translatePxToValue(int)} will most often have changed, as the
* transformation from value to px a) has to hit an exact int b) most often
* will map from a bigger domain (value range) to a smaller one (range of
* chart on the screen).
* <p>
*
* @param pixel
* a pixel value of the chart component as used by awt.
* @return the awt pixel value transformed to the chart value.
*/
public abstract double translatePxToValue(final int pixel);
/**
* Transforms the given chart data value into the corresponding awt pixel
* value for the chart.
* <p>
* The inverse transformation to {@link #translatePxToValue(int)}.
* <p>
*
* @param value
* a chart data value.
* @return the awt pixel value corresponding to the chart data value.
*/
public abstract int translateValueToPx(final double value);
}
/**
* Base implementation that delegates the call to a template method after
* synchronization on the chart.
* <p>
*
* @author Achim Westermann
* @version $Revision: 1.61 $
* @since 3.0.0
*/
private abstract static class APropertyChangeReactorSynced implements
AAxis.IPropertyChangeReactor {
/**
* Defcon.
* <p>
*/
protected APropertyChangeReactorSynced() {
super();
}
/**
* @see info.monitorenter.gui.chart.axis.AAxis.IPropertyChangeReactor#propertyChange(java.beans.PropertyChangeEvent,
* info.monitorenter.gui.chart.axis.AAxis)
*/
public final void propertyChange(final PropertyChangeEvent changeEvent, final AAxis<?> receiver) {
final Chart2D chart = receiver.getAccessor().getChart();
synchronized (chart) {
if (Chart2D.DEBUG_THREADING) {
System.out.println("AAxis.propertyChange (" + Thread.currentThread().getName()
+ "), 1 lock");
}
final boolean repaint = this.propertyChangeSynced(changeEvent, receiver);
if (repaint) {
chart.setRequestedRepaint(true);
}
}
}
/**
* Handle the property change of the given receiver.
* <p>
* This method is invoked with synchronization on the chart of the receiver.
* <p>
* Please note: You must not react upon the property name because the sole
* intention of this interface is to have a map with the property name as
* key and access implementations of this interface very fast which allows
* to avoid large else if - code for identification of the proper property
* name for the code to execute.
* <p>
* Implementations are highly proprietary as they have to trust that they
* are only invoked with the correct property change events!!!
* <p>
*
* @param changeEvent
* the change event the receiver received.
* @param receiver
* the original receiver of the change event.
* @return if true a repaint request will be marked for the chart.
*/
protected abstract boolean propertyChangeSynced(final PropertyChangeEvent changeEvent,
final AAxis<?> receiver);
}
/**
* Internal encapsulation of the code to react upon a property change.
* <p>
* Used on the value side of a map to have a fast dispatch to the code to
* react as axis listens to a lot of different properties.
* <p>
*
* @author Achim Westermann
* @version $Revision: 1.61 $
* @since 3.0.0
*/
private static interface IPropertyChangeReactor {
/**
* Handle the property change of the given receiver.
* <p>
* Please note: You must not react upon the property name because the sole
* intention of this interface is to have a map with the property name as
* key and access implementations of this interface very fast which allows
* to avoid large else if - code for identification of the proper property
* name for the code to execute.
* <p>
* Implementations are highly proprietary as they have to trust that they
* are only invoked with the correct property change events!!!
* <p>
*
* @param changeEvent
* the change event the receiver received.
* @param receiver
* the original receiver of the change event.
*/
public void propertyChange(PropertyChangeEvent changeEvent, final AAxis<?> receiver);
}
/**
* Reused property change listener that will signal the chart to repaint.
* TODO: Enter a comment that ends with a '.'
* <p>
*
* @author Achim Westermann
* @version $Revision: 1.61 $
* @since 3.0.0
*/
private static final class PropertyChangeRepainter implements AAxis.IPropertyChangeReactor {
/**
* Defcon, internal use only.
* <p>
*/
protected PropertyChangeRepainter() {
super();
}
/**
* @see info.monitorenter.gui.chart.axis.AAxis.IPropertyChangeReactor#propertyChange(java.beans.PropertyChangeEvent,
* info.monitorenter.gui.chart.axis.AAxis)
*/
public void propertyChange(final PropertyChangeEvent changeEvent, final AAxis<?> receiver) {
final AAxis<?>.AChart2DDataAccessor accessor = receiver.getAccessor();
// only if this axis is already connected to a chart:
if (accessor != null) {
final Chart2D parent = accessor.getChart();
if (parent != null) {
parent.setRequestedRepaint(true);
}
}
}
}
/**
* An accessor for the x axis of a chart.
* <p>
*
* @author <a href="mailto:Achim.Westermann@gmx.de>Achim Westermann </a>
* @see Chart2D#getAxisX()
*/
public class XDataAccessor extends AAxis<T>.AChart2DDataAccessor {
/** Generated <code>serialVersionUID</code>. */
private static final long serialVersionUID = 1185826702304621485L;
/**
* Creates an instance that accesses the given chart's x axis.
* <p>
*
* @param chart
* the chart to access.
*/
public XDataAccessor(final Chart2D chart) {
super(chart);
}
/**
* @see info.monitorenter.gui.chart.axis.AAxis.AChart2DDataAccessor#getDimension()
*/
@Override
public int getDimension() {
return Chart2D.X;
}
/**
* @see info.monitorenter.gui.chart.axis.AAxis.AChart2DDataAccessor#getHeight(java.awt.Graphics)
*/
@Override
public int getHeight(final Graphics g2d) {
final FontMetrics fontdim = g2d.getFontMetrics();
// the height of the font:
int height = fontdim.getHeight();
// and the height of a major tick mark:
height += this.getChart().getAxisTickPainter().getMajorTickLength();
// and the height of the axis title:
height += AAxis.this.getAxisTitle().getTitlePainter().getHeight(AAxis.this, g2d);
return height;
}
/**
* @see info.monitorenter.gui.chart.axis.AAxis.AChart2DDataAccessor#getMaximumPixelForLabel(Graphics)
*/
@Override
protected final double getMaximumPixelForLabel(final Graphics g2d) {
final FontMetrics fontdim = g2d.getFontMetrics();
final int fontwidth = fontdim.charWidth('0');
/*
* multiply with longest possible number. longest possible number is the
* non-fraction part of the highest number plus the maximum amount of
* fraction digits plus one for the fraction separator dot.
*/
final int len = AAxis.this.getFormatter().getMaxAmountChars();
return fontwidth * (len + 2);
}
/**
* @see info.monitorenter.gui.chart.axis.AAxis.AChart2DDataAccessor#getMaxValue(info.monitorenter.gui.chart.ITrace2D)
*/
@Override
protected double getMaxValue(final ITrace2D trace) {
return trace.getMaxX();
}
/**
* @see info.monitorenter.gui.chart.axis.AAxis.AChart2DDataAccessor#getMinimumValueDistanceForLabels(java.awt.Graphics)
*/
@Override
public final double getMinimumValueDistanceForLabels(final Graphics g2d) {
double result;
final Dimension d = this.m_chart.getSize();
final int pxrange = (int) d.getWidth() - 60;
if (pxrange <= 0) {
result = 1;
} else {
double valuerange = AAxis.this.getMax() - AAxis.this.getMin();
if (valuerange == 0) {
valuerange = 10;
}
final double pxToValue = valuerange / pxrange;
result = pxToValue * this.getMaximumPixelForLabel(g2d);
}
return result;
}
/**
* @see info.monitorenter.gui.chart.axis.AAxis.AChart2DDataAccessor#getMinValue(info.monitorenter.gui.chart.ITrace2D)
*/
@Override
protected double getMinValue(final ITrace2D trace) {
return trace.getMinX();
}
/**
* @see info.monitorenter.gui.chart.axis.AAxis.AChart2DDataAccessor#getPixelRange()
*/
@Override
protected int getPixelRange() {
return this.m_chart.getXChartEnd() - this.m_chart.getXChartStart();
}
/**
* @see info.monitorenter.gui.chart.axis.AAxis.AChart2DDataAccessor#getValue(info.monitorenter.gui.chart.ITracePoint2D)
*/
@Override
protected double getValue(final ITracePoint2D point) {
return point.getX();
}
/**
* @see info.monitorenter.gui.chart.axis.AAxis.AChart2DDataAccessor#getValueDistanceForPixel(int)
*/
@Override
protected final double getValueDistanceForPixel(final int pixel) {
double result;
final Dimension d = this.m_chart.getSize();
final int pxrangex = (int) d.getWidth() - 60;
if (pxrangex <= 0) {
result = -1d;
} else {
final double valuerangex = AAxis.this.getMax() - AAxis.this.getMin();
final double pxToValue = valuerangex / pxrangex;
result = pxToValue * pixel;
}
return result;
}
/**
* @see info.monitorenter.gui.chart.axis.AAxis.AChart2DDataAccessor#getWidth(java.awt.Graphics)
*/
@Override
public int getWidth(final Graphics g2d) {
final FontMetrics fontdim = g2d.getFontMetrics();
// only the space required for the right side label:
final int fontwidth = fontdim.charWidth('0');
final int rightSideOverhang = (AAxis.this.getFormatter().getMaxAmountChars()) * fontwidth;
return rightSideOverhang;
}
/**
* @see info.monitorenter.gui.chart.axis.AAxis.AChart2DDataAccessor#scaleTrace(info.monitorenter.gui.chart.ITrace2D,
* info.monitorenter.util.Range)
*/
@Override
protected void scaleTrace(final ITrace2D trace, final Range range) {
Iterator<ITracePoint2D> itPoints;
final double scaler = range.getExtent();
if (trace.isVisible()) {
itPoints = trace.iterator();
ITracePoint2D point;
while (itPoints.hasNext()) {
point = itPoints.next();
final double absolute = point.getX();
double result = (absolute - range.getMin()) / scaler;
if (!MathUtil.isDouble(result)) {
result = 0;
}
point.setScaledX(result);
}
}
}
/**
* Returns "X".
* <p>
*
* @return "X"
*/
@Override
public String toString() {
return "X";
}
/**
* @see info.monitorenter.gui.chart.axis.AAxis.AChart2DDataAccessor#translateMousePosition(java.awt.event.MouseEvent)
*/
@Override
public final double translateMousePosition(final MouseEvent mouseEvent) {
return this.translatePxToValue(mouseEvent.getX());
}
/**
* @see info.monitorenter.gui.chart.axis.AAxis.AChart2DDataAccessor#translatePxToValue(int)
*/
@Override
public double translatePxToValue(final int pixel) {
double result = 0;
// relate to the offset:
final double px = pixel - this.m_chart.getXChartStart();
final int rangeX = this.m_chart.getXChartEnd() - this.m_chart.getXChartStart();
if (rangeX != 0) {
final double scaledX = px / rangeX;
final Range valueRangeX = AAxis.this.getRange();
result = scaledX * valueRangeX.getExtent() + valueRangeX.getMin();
}
return result;
}
/**
* @see info.monitorenter.gui.chart.axis.AAxis.AChart2DDataAccessor#translateValueToPx(double)
*/
@Override
public int translateValueToPx(final double value) {
int result = 0;
// first normalize to [00.0..1.0]
double valueNormalized;
// cannot use AAxis.this.getMax() / getMin() because log axis will
// transform those values!
final Range valueRange = AAxis.this.getRange();
valueNormalized = (value - valueRange.getMin()) / valueRange.getExtent();
// now expand into the pixel space:
final int rangeX = this.getPixelRange();
if (rangeX == 0) {
// return null
} else {
final double tmpResult = (valueNormalized * rangeX + this.m_chart.getXChartStart());
result = (int) Math.round(tmpResult);
}
return result;
}
}
/**
* @see info.monitorenter.gui.chart.IAxis#getDimensionString()
*/
public String getDimensionString() {
String result = null;
if (this.m_accessor != null) {
result = this.m_accessor.toString();
}
return result;
}
/**
* Accesses the y axis of the {@link Chart2D}.
* <p>
*
* @see AAxis#setAccessor(info.monitorenter.gui.chart.axis.AAxis.AChart2DDataAccessor)
* @see Chart2D#getAxisY()
*/
protected class YDataAccessor extends AAxis<T>.AChart2DDataAccessor {
/** Generated <code>serialVersionUID</code>. */
private static final long serialVersionUID = -3665759247443586028L;
/**
* Creates an instance that accesses the y axis of the given chart.
* <p>
*
* @param chart
* the chart to access.
*/
public YDataAccessor(final Chart2D chart) {
super(chart);
}
/**
* @see info.monitorenter.gui.chart.axis.AAxis.AChart2DDataAccessor#getDimension()
*/
@Override
public final int getDimension() {
return Chart2D.Y;
}
/**
* @see info.monitorenter.gui.chart.axis.AAxis.AChart2DDataAccessor#getHeight(java.awt.Graphics)
*/
@Override
public final int getHeight(final Graphics g2d) {
final FontMetrics fontdim = g2d.getFontMetrics();
// only the space required for the right side label:
int fontHeight = fontdim.getHeight();
// -4 is for showing colons of x - labels that are below the baseline
fontHeight += 4;
return fontHeight;
}
/**
* @see info.monitorenter.gui.chart.axis.AAxis.AChart2DDataAccessor#getMaximumPixelForLabel(Graphics)
* @param g2d
* the current graphic context to use in case further information
* is required.
*/
@Override
protected final double getMaximumPixelForLabel(final Graphics g2d) {
final FontMetrics fontdim = g2d.getFontMetrics();
final int fontheight = fontdim.getHeight();
return fontheight + 10;
}
/**
* @see info.monitorenter.gui.chart.axis.AAxis.AChart2DDataAccessor#getMaxValue(info.monitorenter.gui.chart.ITrace2D)
*/
@Override
protected double getMaxValue(final ITrace2D trace) {
return trace.getMaxY();
}
/**
* @see info.monitorenter.gui.chart.axis.AAxis.AChart2DDataAccessor#getMinimumValueDistanceForLabels(Graphics)
*/
@Override
public final double getMinimumValueDistanceForLabels(final Graphics g2d) {
double result;
final Dimension d = this.m_chart.getSize();
final int pxrange = (int) d.getHeight() - 40;
if (pxrange <= 0) {
result = 1;
} else {
double valuerange = AAxis.this.getMax() - AAxis.this.getMin();
if (valuerange == 0) {
valuerange = 10;
}
final double pxToValue = valuerange / pxrange;
result = pxToValue * this.getMaximumPixelForLabel(g2d);
}
return result;
}
/**
* @see info.monitorenter.gui.chart.axis.AAxis.AChart2DDataAccessor#getMinValue(info.monitorenter.gui.chart.ITrace2D)
*/
@Override
protected double getMinValue(final ITrace2D trace) {
return trace.getMinY();
}
/**
* @see info.monitorenter.gui.chart.axis.AAxis.AChart2DDataAccessor#getPixelRange()
*/
@Override
protected final int getPixelRange() {
return this.m_chart.getYChartStart() - this.m_chart.getYChartEnd();
}
/**
* @see info.monitorenter.gui.chart.axis.AAxis.AChart2DDataAccessor#getValue(info.monitorenter.gui.chart.ITracePoint2D)
*/
@Override
protected double getValue(final ITracePoint2D point) {
return point.getY();
}
/**
* @see info.monitorenter.gui.chart.axis.AAxis.AChart2DDataAccessor#getValueDistanceForPixel(int)
*/
@Override
protected final double getValueDistanceForPixel(final int pixel) {
double result;
final Dimension d = this.m_chart.getSize();
final int pxrangey = (int) d.getHeight() - 40;
if (pxrangey <= 0) {
result = -1d;
} else {
final double valuerangey = AAxis.this.getMaxValue() - AAxis.this.getMinValue();
final double pxToValue = valuerangey / pxrangey;
result = pxToValue * pixel;
}
return result;
}
/**
* @see info.monitorenter.gui.chart.axis.AAxis.AChart2DDataAccessor#getWidth(java.awt.Graphics)
*/
@Override
public final int getWidth(final Graphics g2d) {
final FontMetrics fontdim = g2d.getFontMetrics();
// the width of the font:
final int fontWidth = fontdim.charWidth('0');
// times the maximum amount of chars:
int height = fontWidth * AAxis.this.getFormatter().getMaxAmountChars();
// and the height of a major tick mark:
height += this.getChart().getAxisTickPainter().getMajorTickLength();
// and the Width of the axis title:
height += AAxis.this.getAxisTitle().getTitlePainter().getWidth(AAxis.this, g2d);
return height;
}
/**
* @see info.monitorenter.gui.chart.axis.AAxis.AChart2DDataAccessor#scaleTrace(info.monitorenter.gui.chart.ITrace2D,
* info.monitorenter.util.Range)
*/
@Override
protected void scaleTrace(final ITrace2D trace, final Range range) {
if (trace.isVisible()) {
final double scaler = range.getExtent();
final Iterator<ITracePoint2D> itPoints = trace.iterator();
ITracePoint2D point;
while (itPoints.hasNext()) {
point = itPoints.next();
final double absolute = point.getY();
double result = (absolute - range.getMin()) / scaler;
if (!MathUtil.isDouble(result)) {
result = 0;
}
point.setScaledY(result);
}
}
}
/**
* Returns "Y".
* <p>
*
* @return "Y"
*/
@Override
public String toString() {
return "Y";
}
/**
* @see info.monitorenter.gui.chart.axis.AAxis.AChart2DDataAccessor#translateMousePosition(java.awt.event.MouseEvent)
*/
@Override
public double translateMousePosition(final MouseEvent mouseEvent) {
return this.translatePxToValue(mouseEvent.getY());
}
/**
* @see info.monitorenter.gui.chart.axis.AAxis.AChart2DDataAccessor#translatePxToValue(int)
*/
@Override
public double translatePxToValue(final int pixel) {
double result = 0;
// invert, as awt px are higher the lower the chart value is:
final double px = this.m_chart.getYChartStart() - pixel;
final int rangeY = this.m_chart.getYChartStart() - this.m_chart.getYChartEnd();
if (rangeY != 0) {
final double scaledY = px / rangeY;
final Range valueRangeY = AAxis.this.getRange();
result = scaledY * valueRangeY.getExtent() + valueRangeY.getMin();
}
return result;
}
/**
* @see info.monitorenter.gui.chart.axis.AAxis.AChart2DDataAccessor#translateValueToPx(double)
*/
@Override
public int translateValueToPx(final double value) {
int result = 0;
// first normalize to [00.0..1.0]
double valueNormalized;
// cannot use AAxis.this.getMax() / getMin() because log axis will
// transform those values!
final Range valueRange = AAxis.this.getRange();
valueNormalized = (value - valueRange.getMin()) / valueRange.getExtent();
// now expand into the pixel space:
final int rangeY = this.getPixelRange();
if (rangeY == 0) {
// return null
} else {
result = (int) Math.round(this.m_chart.getYChartStart() - valueNormalized * rangeY);
}
return result;
}
}
/** Debugging flag for sysouts. */
public static final boolean DEBUG = false;
/**
* Internal fast access to the right property change code encapsulation via
* the property name.
* <p>
* This is done for better performance - an old endless
* <code>..else if(propertyName.equals(..))..</code> has been replaced by
* this.
* <p>
*/
private static SortedMap<String, AAxis.IPropertyChangeReactor> propertyReactors;
/** Generated <code>serialVersionUID</code>. **/
private static final long serialVersionUID = -3615740476406530580L;
static {
AAxis.propertyReactors = new TreeMap<String, AAxis.IPropertyChangeReactor>();
// Don't waste the heap for stateless code:
final IPropertyChangeReactor repaintReactor = new PropertyChangeRepainter();
AAxis.propertyReactors.put(ITrace2D.PROPERTY_STROKE, repaintReactor);
AAxis.propertyReactors.put(ITrace2D.PROPERTY_STROKE, repaintReactor);
AAxis.propertyReactors.put(ITrace2D.PROPERTY_PAINTERS, repaintReactor);
AAxis.propertyReactors.put(ITrace2D.PROPERTY_COLOR, repaintReactor);
AAxis.propertyReactors.put(ITrace2D.PROPERTY_NAME, repaintReactor);
AAxis.propertyReactors.put(ITrace2D.PROPERTY_ERRORBARPOLICY, repaintReactor);
AAxis.propertyReactors.put(ITrace2D.PROPERTY_ERRORBARPOLICY_CONFIGURATION, repaintReactor);
AAxis.propertyReactors.put(ITrace2D.PROPERTY_ZINDEX, repaintReactor);
AAxis.propertyReactors.put(IAxis.PROPERTY_LABELFORMATTER, repaintReactor);
AAxis.propertyReactors.put(IAxisLabelFormatter.PROPERTY_FORMATCHANGE, repaintReactor);
AAxis.propertyReactors.put(AxisTitle.PROPERTY_TITLEFONT, repaintReactor);
AAxis.propertyReactors.put(AxisTitle.PROPERTY_TITLE, repaintReactor);
AAxis.propertyReactors.put(AxisTitle.PROPERTY_TITLEPAINTER, repaintReactor);
AAxis.propertyReactors.put(ITrace2D.PROPERTY_MAX_X, new APropertyChangeReactorSynced() {
/**
* @see info.monitorenter.gui.chart.axis.AAxis.APropertyChangeReactorSynced#propertyChangeSynced(java.beans.PropertyChangeEvent,
* info.monitorenter.gui.chart.axis.AAxis)
*/
@Override
protected boolean propertyChangeSynced(final PropertyChangeEvent changeEvent,
final AAxis<?> receiver) {
boolean result = false;
if (Chart2D.DEBUG_SCALING) {
System.out.println("pc-Xmax");
}
final AAxis<?>.AChart2DDataAccessor accessor = receiver.getAccessor();
// only care if axis works in x dimension:
if (accessor.getDimension() == Chart2D.X) {
final double value = ((Double) changeEvent.getNewValue()).doubleValue();
if (value > receiver.m_max) {
final ITrace2D trace = (ITrace2D) changeEvent.getSource();
if (trace.isVisible()) {
receiver.m_max = value;
receiver.m_needsFullRescale = true;
result = true;
}
} else if (value < receiver.m_max) {
receiver.m_max = receiver.findMax();
receiver.m_needsFullRescale = true;
result = true;
}
}
return result;
}
});
AAxis.propertyReactors.put(ITrace2D.PROPERTY_MIN_X, new APropertyChangeReactorSynced() {
/**
* @see info.monitorenter.gui.chart.axis.AAxis.APropertyChangeReactorSynced#propertyChangeSynced(java.beans.PropertyChangeEvent,
* info.monitorenter.gui.chart.axis.AAxis)
*/
@Override
protected boolean propertyChangeSynced(final PropertyChangeEvent changeEvent,
final AAxis<?> receiver) {
boolean result = false;
if (Chart2D.DEBUG_SCALING) {
System.out.println("pc-Xmin");
}
if (receiver.getAccessor().getDimension() == Chart2D.X) {
final double value = ((Double) changeEvent.getNewValue()).doubleValue();
if (value < receiver.m_min) {
final ITrace2D trace = (ITrace2D) changeEvent.getSource();
if (trace.isVisible()) {
receiver.m_min = value;
receiver.m_needsFullRescale = true;
result = true;
}
} else if (value > receiver.m_min) {
receiver.m_min = receiver.findMin();
receiver.m_needsFullRescale = true;
result = true;
}
}
return result;
}
});
AAxis.propertyReactors.put(ITrace2D.PROPERTY_MAX_Y, new APropertyChangeReactorSynced() {
/**
* @see info.monitorenter.gui.chart.axis.AAxis.APropertyChangeReactorSynced#propertyChangeSynced(java.beans.PropertyChangeEvent,
* info.monitorenter.gui.chart.axis.AAxis)
*/
@Override
protected boolean propertyChangeSynced(final PropertyChangeEvent changeEvent,
final AAxis<?> receiver) {
boolean result = false;
if (Chart2D.DEBUG_SCALING) {
System.out.println("pc-Ymax");
}
// only care if axis works in y dimension:
if (receiver.getAccessor().getDimension() == Chart2D.Y) {
final double value = ((Double) changeEvent.getNewValue()).doubleValue();
if (value > receiver.m_max) {
final ITrace2D trace = (ITrace2D) changeEvent.getSource();
if (trace.isVisible()) {
receiver.m_max = value;
receiver.m_needsFullRescale = true;
result = true;
}
} else if (value < receiver.m_max) {
receiver.m_max = receiver.findMax();
receiver.m_needsFullRescale = true;
result = true;
}
}
return result;
}
});
AAxis.propertyReactors.put(ITrace2D.PROPERTY_MIN_Y, new APropertyChangeReactorSynced() {
/**
* @see info.monitorenter.gui.chart.axis.AAxis.APropertyChangeReactorSynced#propertyChangeSynced(java.beans.PropertyChangeEvent,
* info.monitorenter.gui.chart.axis.AAxis)
*/
@Override
protected boolean propertyChangeSynced(final PropertyChangeEvent changeEvent,
final AAxis<?> receiver) {
boolean result = false;
if (Chart2D.DEBUG_SCALING) {
System.out.println("pc-Ymin");
}
if (receiver.getAccessor().getDimension() == Chart2D.Y) {
final double value = ((Double) changeEvent.getNewValue()).doubleValue();
if (value < receiver.m_min) {
final ITrace2D trace = (ITrace2D) changeEvent.getSource();
if (trace.isVisible()) {
receiver.m_min = value;
receiver.m_needsFullRescale = true;
result = true;
}
} else if (value > receiver.m_min) {
receiver.m_min = receiver.findMin();
receiver.m_needsFullRescale = true;
result = true;
}
}
return result;
}
});
AAxis.propertyReactors.put(ITrace2D.PROPERTY_TRACEPOINT, new APropertyChangeReactorSynced() {
/**
* @see info.monitorenter.gui.chart.axis.AAxis.APropertyChangeReactorSynced#propertyChangeSynced(java.beans.PropertyChangeEvent,
* info.monitorenter.gui.chart.axis.AAxis)
*/
@Override
protected boolean propertyChangeSynced(final PropertyChangeEvent changeEvent,
final AAxis<?> receiver) {
boolean result = false;
// now points added or removed -> rescale!
if (Chart2D.DEBUG_SCALING) {
System.out.println("pc-tp");
}
final ITracePoint2D oldPt = (ITracePoint2D) changeEvent.getOldValue();
final ITracePoint2D newPt = (ITracePoint2D) changeEvent.getNewValue();
// added or removed?
// we only care about added points (rescaling is our task)
if (oldPt == null) {
receiver.scalePoint(newPt);
result = true;
}
return result;
}
});
AAxis.propertyReactors.put(ITrace2D.PROPERTY_VISIBLE, new APropertyChangeReactorSynced() {
/**
* @see info.monitorenter.gui.chart.axis.AAxis.APropertyChangeReactorSynced#propertyChangeSynced(java.beans.PropertyChangeEvent,
* info.monitorenter.gui.chart.axis.AAxis)
*/
@Override
protected boolean propertyChangeSynced(final PropertyChangeEvent changeEvent,
final AAxis<?> receiver) {
boolean result = false;
// invisible traces don't count for max and min, so
// expensive search has to be started here:
// TODO: Do performance: Get the trace of the event and check only
// it's bounds here!!!
receiver.m_max = receiver.findMax();
receiver.m_min = receiver.findMin();
// if the trace that became visible does not exceed bounds
// it will not cause a "dirty Scaling" -> updateScaling and
// repainting (in Painter Thread).
final ITrace2D trace = (ITrace2D) changeEvent.getSource();
receiver.scaleTrace(trace);
result = true;
return result;
}
});
AAxis.propertyReactors.put(ITrace2D.PROPERTY_POINT_CHANGED, new APropertyChangeReactorSynced() {
/**
* @see info.monitorenter.gui.chart.axis.AAxis.APropertyChangeReactorSynced#propertyChangeSynced(java.beans.PropertyChangeEvent,
* info.monitorenter.gui.chart.axis.AAxis)
*/
@Override
protected boolean propertyChangeSynced(final PropertyChangeEvent changeEvent,
final AAxis<?> receiver) {
boolean result = false;
final ITracePoint2D changed = (ITracePoint2D) changeEvent.getNewValue();
receiver.scalePoint(changed);
result = true;
return result;
}
});
}
/**
* The accessor to the Chart2D.
* <p>
* It determines, which axis (x or y) this instance is representing.
* <p>
*/
protected AChart2DDataAccessor m_accessor;
/** The position of this axis on the chart. */
private int m_axisPosition = -1;
/**
* The title of this axis, by default an empty title with an <code>
* {@link info.monitorenter.gui.chart.axistitlepainters.AxisTitlePainterDefault}
* </code>.
*/
private IAxis.AxisTitle m_axisTitle;
/**
* Formatting of the labels.
*/
protected IAxisLabelFormatter m_formatter;
/**
* The major tick spacing for label generations.
* <p>
*
* @see #setMajorTickSpacing(double)
*/
protected double m_majorTickSpacing = 5;
/** The current maximum value for all points in all traces. */
protected double m_max;
/** The current minimum value for all points in all traces. */
protected double m_min;
/**
* The minor tick spacing for label generations.
* <p>
*
* @see #setMinorTickSpacing(double)
*/
protected double m_minorTickSpacing = 1;
/**
* Flag to detect if a re-scaling has to be done.
* <p>
* It is set to false in <code>{@link #scale()}</code> which is triggered from
* the painting Thread. Whenever a bound change is detected in
* <code>{@link #propertyChange(PropertyChangeEvent)}</code> this is set to
* true.
* <p>
* Please remind: In previous versions there was only a test if the bounds had
* changed since the last scaling. This was not always correct: If in between
* two paint cycles the bounds were changed and new points added but at the
* point in time when the 2nd paint cycle starts the bounds would be equal no
* full rescaling would be performed even if the added points would have been
* scaled in relation to the changed bounds at their adding time: Bounds
* checks are not sufficient!
* <p>
*/
protected boolean m_needsFullRescale = false;
/** Boolean switch for painting x grid lines. * */
private boolean m_paintGrid = false;
/** Boolean switch for painting the scale in this dimension. */
private boolean m_paintScale = true;
/** The left x pixel coordinate of this axis. */
private int m_pixelXLeft;
/** The right x pixel coordinate of this axis. */
private int m_pixelXRight;
/** The bottom y pixel coordinate of this axis. */
private int m_pixelYBottom;
/** The top y pixel coordinate of this axis. */
private int m_pixelYTop;
/** Support for acting as a property change event producer for listeners. */
private final PropertyChangeSupport m_propertyChangeSupport;
/**
* A plugable range policy.
*/
protected IRangePolicy m_rangePolicy;
/**
* The range used for scaling in the previous paint operation.
* <p>
* This is used for detection of dirty scaling.
* <p>
*/
private final Range m_rangePreviousScaling = new Range(0, 0);
/** Reused range for <code>{@link #getRange()}</code>. */
private final Range m_reusedRange = new Range(0, 0);
/**
* Controls whether scale values are started from major ticks.
* <p>
* Default is false.
* <p>
*/
private boolean m_startMajorTick = false;
/**
* The internal <code>Set</code> used to store the different
* <code>ITrace2d</code> instances to paint with z-index ordering based on
* <code>{@link ITrace2D#getZIndex()}</code>.
* <p>
* It is crucial to use a set implementation here that is not backed by a map. To be more precise:
* It is crucial to use an implementation that will use equals whenever operations like contains are invoked
* instead of searching by a computed key. In the latter case you could add traces here (at that point in time a key
* is computed from the trace state) then modify the traces (e.g. adding points) and later when trying to remove the
* trace the given traces's key would be computed but no key for it found.
*/
private final Set<ITrace2D> m_traces = new CopyOnWriteArraySet<ITrace2D>();
/**
* True if this axis is to be painted on a chart; false to hide.
*/
private boolean m_visible = true;
/**
* Default constructor that uses a {@link LabelFormatterAutoUnits} for
* formatting labels.
* <p>
*/
@SuppressWarnings("unchecked")
public AAxis() {
/*
* I consider this necessary cast to T as a bug (compare with declaration if T in class header):
*/
this(new LabelFormatterAutoUnits(new LabelFormatterSimple()),
(T)new AxisScalePolicyAutomaticBestFit());
}
/**
* Constructor that uses the given label formatter for formatting labels.
* <p>
*
* @param formatter
* needed for formatting labels of this axis.
*
* @param scalePolicy
* controls the ticks/labels and their distance.
*/
public AAxis(final IAxisLabelFormatter formatter, final T scalePolicy) {
this.m_propertyChangeSupport = new PropertyChangeSupport(this);
this.setAxisTitle(new AxisTitle(null));
this.m_rangePolicy = new RangePolicyUnbounded(Range.RANGE_UNBOUNDED);
this.setFormatter(formatter);
this.setAxisScalePolicy(scalePolicy);
}
// public AAxis(LabelFormatterAutoUnits labelFormatterAutoUnits,
// AxisScalePolicyAutomaticBestFit axisScalePolicyAutomaticBestFit) {
// this(labelFormatterAutoUnits, axisScalePolicyAutomaticBestFit);
// }
/**
* @see info.monitorenter.gui.chart.IAxis#addPropertyChangeListener(java.lang.String,
* java.beans.PropertyChangeListener)
*/
public void addPropertyChangeListener(final String propertyName,
final PropertyChangeListener listener) {
this.m_propertyChangeSupport.addPropertyChangeListener(propertyName, listener);
}
/**
* @see info.monitorenter.gui.chart.IAxis#addTrace(info.monitorenter.gui.chart.ITrace2D)
*/
public boolean addTrace(final ITrace2D trace) {
boolean result = false;
if (Chart2D.DEBUG_THREADING) {
System.out.println(Thread.currentThread().getName() + "Aaxis" + this.getDimensionString()
+ ".addTrace(), 0 locks");
}
synchronized (this.getAccessor().getChart()) {
if (Chart2D.DEBUG_THREADING) {
System.out.println(Thread.currentThread().getName() + ", AAxis" + this.getDimensionString()
+ ".addTrace(), 1 lock");
}
synchronized (trace) {
if (Chart2D.DEBUG_THREADING) {
System.out.println(Thread.currentThread().getName() + ", AAxis"
+ this.getDimensionString() + ".addTrace(), 2 locks");
}
if (this.m_traces.contains(trace)) {
throw new IllegalArgumentException("Trace " + trace.getName()
+ " is already contaied in this axis " + this.getAxisTitle() + ". Review your code. ");
}
// do it here:
result = this.m_traces.add(trace);
if (result) {
this.listen2Trace(trace);
// for static traces (all points added) we won't get events.
// so update here:
final double max = this.getAccessor().getMaxValue(trace);
if (max > this.m_max) {
this.m_max = max;
}
final double min = this.getAccessor().getMinValue(trace);
if (min < this.m_min) {
this.m_min = min;
}
// special case: first trace added:
if (this.getTraces().size() == 1) {
this.m_min = min;
this.m_max = max;
}
if (Chart2D.DEBUG_THREADING) {
System.out.println(Thread.currentThread().getName() + ", AAxis"
+ this.getDimensionString() + ".addTrace(), before installing chart to trace "
+ trace.getName());
ExceptionUtil.dumpThreadStack(System.out);
}
trace.setRenderer(this.m_accessor.getChart());
if (Chart2D.DEBUG_THREADING) {
System.out.println(Thread.currentThread().getName() + ", AAxis"
+ this.getDimensionString() + ".addTrace(), after installing chart to trace "
+ trace.getName());
}
// unconditionally scale the trace as we don't know which
// bounds it was related to before.
this.scaleTrace(trace);
}
}
if (Chart2D.DEBUG_THREADING) {
System.out.println(Thread.currentThread().getName() + ", AAxis" + this.getDimensionString()
+ ".addTrace(), left 1 lock: 1 remaining");
}
}
if (Chart2D.DEBUG_THREADING) {
System.out.println(Thread.currentThread().getName() + ", AAxis" + this.getDimensionString()
+ ".addTrace(), left 1 lock: 0 remaining");
}
// A deadlock occurs if a listener triggers paint.
// This was the case with ChartPanel.
this.m_propertyChangeSupport.firePropertyChange(IAxis.PROPERTY_ADD_REMOVE_TRACE, null, trace);
return result;
}
/**
* Template method to create the proper <code>
* {@link AAxis.AChart2DDataAccessor}</code>
* implementation.
* <p>
*
* @param chart
* the chart to access.
* @param dimension
* <code>{@link Chart2D#X}</code> or <code>{@link Chart2D#Y}</code>.
* @param position
* <code>{@link Chart2D#CHART_POSITION_BOTTOM}</code>, <code>
* {@link Chart2D#CHART_POSITION_LEFT}</code>, <code>
* {@link Chart2D#CHART_POSITION_RIGHT}</code> or <code>
* {@link Chart2D#CHART_POSITION_TOP}</code>.
* @return the proper <code>{@link AAxis.AChart2DDataAccessor}</code>
* implementation.
*/
protected abstract AAxis<T>.AChart2DDataAccessor createAccessor(Chart2D chart, int dimension, int position);
/**
* @see java.lang.Object#equals(java.lang.Object)
*/
@Override
public boolean equals(final Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (this.getClass() != obj.getClass()) {
return false;
}
final AAxis<?> other = (AAxis<?>) obj;
if (this.m_accessor == null) {
if (other.m_accessor != null) {
return false;
}
} else if (!this.m_accessor.equals(other.m_accessor)) {
return false;
}
if (this.m_axisPosition != other.m_axisPosition) {
return false;
}
if (this.m_axisTitle == null) {
if (other.m_axisTitle != null) {
return false;
}
} else if (!this.m_axisTitle.equals(other.m_axisTitle)) {
return false;
}
if (this.m_formatter == null) {
if (other.m_formatter != null) {
return false;
}
} else if (!this.m_formatter.equals(other.m_formatter)) {
return false;
}
if (Double.doubleToLongBits(this.m_majorTickSpacing) != Double
.doubleToLongBits(other.m_majorTickSpacing)) {
return false;
}
if (Double.doubleToLongBits(this.m_max) != Double.doubleToLongBits(other.m_max)) {
return false;
}
if (Double.doubleToLongBits(this.m_min) != Double.doubleToLongBits(other.m_min)) {
return false;
}
if (Double.doubleToLongBits(this.m_minorTickSpacing) != Double
.doubleToLongBits(other.m_minorTickSpacing)) {
return false;
}
if (this.m_needsFullRescale != other.m_needsFullRescale) {
return false;
}
if (this.m_paintGrid != other.m_paintGrid) {
return false;
}
if (this.m_paintScale != other.m_paintScale) {
return false;
}
if (this.m_pixelXLeft != other.m_pixelXLeft) {
return false;
}
if (this.m_pixelXRight != other.m_pixelXRight) {
return false;
}
if (this.m_pixelYBottom != other.m_pixelYBottom) {
return false;
}
if (this.m_pixelYTop != other.m_pixelYTop) {
return false;
}
if (this.m_propertyChangeSupport == null) {
if (other.m_propertyChangeSupport != null) {
return false;
}
} else if (!this.m_propertyChangeSupport.equals(other.m_propertyChangeSupport)) {
return false;
}
if (this.m_rangePolicy == null) {
if (other.m_rangePolicy != null) {
return false;
}
} else if (!this.m_rangePolicy.equals(other.m_rangePolicy)) {
return false;
}
if (this.m_rangePreviousScaling == null) {
if (other.m_rangePreviousScaling != null) {
return false;
}
} else if (!this.m_rangePreviousScaling.equals(other.m_rangePreviousScaling)) {
return false;
}
if (this.m_reusedRange == null) {
if (other.m_reusedRange != null) {
return false;
}
} else if (!this.m_reusedRange.equals(other.m_reusedRange)) {
return false;
}
if (this.m_startMajorTick != other.m_startMajorTick) {
return false;
}
if (this.m_traces == null) {
if (other.m_traces != null) {
return false;
}
} else if (!this.m_traces.equals(other.m_traces)) {
return false;
}
if (this.m_visible != other.m_visible) {
return false;
}
return true;
}
/**
* Searches for the maximum value of all contained ITraces in the dimension
* this axis stands for.
* <p>
* This method is triggered when a trace fired a property change for property
* <code>{@link ITrace2D#PROPERTY_MAX_X}</code> or
* <code>{@link ITrace2D#PROPERTY_MAX_Y}</code> with a value lower than the
* internal stored maximum.
* <p>
* Performance breakdown is avoided because all <code>ITrace2D</code>
* implementations cache their max and min values.
* <p>
*
* @return the maximum value of all traces for the dimension this axis works
* in.
*/
protected final double findMax() {
double max = -Double.MAX_VALUE;
double tmp;
final Iterator<ITrace2D> it = this.getTraces().iterator();
ITrace2D trace;
while (it.hasNext()) {
trace = it.next();
if (trace.isVisible()) {
tmp = this.getAccessor().getMaxValue(trace);
if (tmp > max) {
max = tmp;
}
}
}
if (max == -Double.MAX_VALUE) {
max = 10;
}
return max;
}
/**
* Searches for the minimum value of all contained ITraces in the dimension
* this axis stands for.
* <p>
* This method is triggered when a trace fired a property change for property
* <code>{@link ITrace2D#PROPERTY_MAX_X}</code> or
* <code>{@link ITrace2D#PROPERTY_MAX_Y}</code> with a value lower than the
* internal stored minimum.
* <p>
* Performance breakdown is avoided because all <code>ITrace2D</code>
* implementations cache their max and min values.
* <p>
*
* @return the minimum value of all traces for the dimension this axis works
* in.
*/
protected final double findMin() {
double min = Double.MAX_VALUE;
double tmp;
final Iterator<ITrace2D> it = this.getTraces().iterator();
ITrace2D trace;
while (it.hasNext()) {
trace = it.next();
if (trace.isVisible()) {
tmp = this.getAccessor().getMinValue(trace);
if (tmp < min) {
min = tmp;
}
}
}
if (min == Double.MAX_VALUE) {
min = 0;
}
return min;
}
/**
* Returns the accessor to the chart.
* <p>
*
* @return the accessor to the chart.
*/
public AChart2DDataAccessor getAccessor() {
return this.m_accessor;
}
/**
* @see info.monitorenter.gui.chart.IAxis#getAxisPosition()
*/
public int getAxisPosition() {
return this.m_axisPosition;
}
/**
* @see info.monitorenter.gui.chart.IAxis#getAxisTitle()
*/
public AxisTitle getAxisTitle() {
return this.m_axisTitle;
}
/**
* @see info.monitorenter.gui.chart.IAxis#getDimension()
*/
public int getDimension() {
int result = -1;
if (this.m_accessor != null) {
result = this.m_accessor.getDimension();
}
return result;
}
/**
* @return Returns the formatter.
*/
public final IAxisLabelFormatter getFormatter() {
return this.m_formatter;
}
/**
* @see info.monitorenter.gui.chart.IAxis#getHeight(java.awt.Graphics)
*/
public final int getHeight(final Graphics g2d) {
return this.getAccessor().getHeight(g2d);
}
/**
* @see info.monitorenter.gui.chart.IAxis#getMajorTickSpacing()
*/
public double getMajorTickSpacing() {
return this.m_majorTickSpacing;
}
/**
* @see info.monitorenter.gui.chart.IAxis#getMax()
*/
public double getMax() {
return this.getRangePolicy().getMax(this.m_min, this.m_max);
}
/**
* @see info.monitorenter.gui.chart.IAxis#getMaxValue()
*/
public double getMaxValue() {
return this.m_max;
}
/**
* @see info.monitorenter.gui.chart.IAxis#getMin()
*/
public double getMin() {
return this.getRangePolicy().getMin(this.m_min, this.m_max);
}
/**
* Get the minor tick spacing for label generation.
* <p>
*
* @return he minor tick spacing for label generation.
* @see #setMinorTickSpacing(double)
*/
public double getMinorTickSpacing() {
return this.m_minorTickSpacing;
}
/**
* @see info.monitorenter.gui.chart.IAxis#getMinValue()
*/
public double getMinValue() {
return this.m_min;
}
/**
* @see info.monitorenter.gui.chart.IAxis#getPixelXLeft()
*/
public final int getPixelXLeft() {
return this.m_pixelXLeft;
}
/**
* @see info.monitorenter.gui.chart.IAxis#getPixelXRight()
*/
public final int getPixelXRight() {
return this.m_pixelXRight;
}
/**
* @see info.monitorenter.gui.chart.IAxis#getPixelYBottom()
*/
public final int getPixelYBottom() {
return this.m_pixelYBottom;
}
/**
* @see info.monitorenter.gui.chart.IAxis#getPixelYTop()
*/
public final int getPixelYTop() {
return this.m_pixelYTop;
}
/**
* @see info.monitorenter.gui.chart.IAxis#getPropertyChangeListeners(java.lang.String)
*/
public PropertyChangeListener[] getPropertyChangeListeners(final String propertyName) {
return this.m_propertyChangeSupport.getPropertyChangeListeners(propertyName);
}
/**
* This method is used by the Chart2D to scale it's values during painting.
* <p>
* Caution: This method does not necessarily return the Range configured with
* {@link #setRange(Range)}. The internal {@link IRangePolicy} is taken into
* account.
* <p>
*
* @return the range corresponding to the upper and lower bound of the values
* that will be visible on this Axis of the Chart2D.
*
* @see #setRangePolicy(IRangePolicy)
*/
public final Range getRange() {
final double min = this.getMin();
double max = this.getMax();
if (min == max) {
max += 10;
}
this.m_reusedRange.setMax(max);
this.m_reusedRange.setMin(min);
return this.m_reusedRange;
}
/**
* Returns the range policy of this axis.
* <p>
*
* @return the range policy of this axis.
*/
public IRangePolicy getRangePolicy() {
return this.m_rangePolicy;
}
/**
* @deprecated use {@link #getAxisTitle()} and on the result
* {@link IAxis.AxisTitle#getTitle()}.
*/
@Deprecated
public final String getTitle() {
return this.getAxisTitle().getTitle();
}
/**
* @deprecated this method might be dropped because the painter should be of
* no concern.
* @see info.monitorenter.gui.chart.IAxis#getTitlePainter()
*/
@Deprecated
public final IAxisTitlePainter getTitlePainter() {
return this.getAxisTitle().getTitlePainter();
}
/**
* @see info.monitorenter.gui.chart.IAxis#getTraces()
*/
public Set<ITrace2D> getTraces() {
return this.m_traces;
}
/**
* Returns the value distance on the current chart that exists for the given
* amount of pixel distance in the given direction of this <code>Axis</code>.
* <p>
* Depending on the width of the actual Chart2D and the contained values, the
* relation between displayed distances (pixel) and value distances (the
* values of the addes
* <code>{@link info.monitorenter.gui.chart.ITrace2D}</code> instances
* changes.
* <p>
* This method calculates depending on the actual painting area of the
* Chart2D, the shift in value between two points that have a screen distance
* of the given pixel. <br>
* This method is not used by the chart itself but a helper for outside use.
* <p>
*
* @param pixel
* The desired distance between to scalepoints of the x- axis in
* pixel.
* @return a scaled (from pixel to internal value-range) and normed (to the
* factor of the current unit of the axis) value usable to calculate
* the coords for the scalepoints of the axis.
*/
protected final double getValueDistanceForPixel(final int pixel) {
return this.m_accessor.getValueDistanceForPixel(pixel);
}
/**
* @see info.monitorenter.gui.chart.IAxis#getWidth(java.awt.Graphics)
*/
public final int getWidth(final Graphics g2d) {
return this.getAccessor().getWidth(g2d);
}
/**
* @see java.lang.Object#hashCode()
*/
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((this.m_accessor == null) ? 0 : this.m_accessor.hashCode());
result = prime * result + this.m_axisPosition;
result = prime * result + ((this.m_axisTitle == null) ? 0 : this.m_axisTitle.hashCode());
result = prime * result + ((this.m_formatter == null) ? 0 : this.m_formatter.hashCode());
long temp;
temp = Double.doubleToLongBits(this.m_majorTickSpacing);
result = prime * result + (int) (temp ^ (temp >>> 32));
temp = Double.doubleToLongBits(this.m_max);
result = prime * result + (int) (temp ^ (temp >>> 32));
temp = Double.doubleToLongBits(this.m_min);
result = prime * result + (int) (temp ^ (temp >>> 32));
temp = Double.doubleToLongBits(this.m_minorTickSpacing);
result = prime * result + (int) (temp ^ (temp >>> 32));
result = prime * result + (this.m_needsFullRescale ? 1231 : 1237);
result = prime * result + (this.m_paintGrid ? 1231 : 1237);
result = prime * result + (this.m_paintScale ? 1231 : 1237);
result = prime * result + this.m_pixelXLeft;
result = prime * result + this.m_pixelXRight;
result = prime * result + this.m_pixelYBottom;
result = prime * result + this.m_pixelYTop;
result = prime * result
+ ((this.m_propertyChangeSupport == null) ? 0 : this.m_propertyChangeSupport.hashCode());
result = prime * result + ((this.m_rangePolicy == null) ? 0 : this.m_rangePolicy.hashCode());
result = prime * result
+ ((this.m_rangePreviousScaling == null) ? 0 : this.m_rangePreviousScaling.hashCode());
result = prime * result + ((this.m_reusedRange == null) ? 0 : this.m_reusedRange.hashCode());
result = prime * result + (this.m_startMajorTick ? 1231 : 1237);
result = prime * result + ((this.m_traces == null) ? 0 : this.m_traces.hashCode());
result = prime * result + (this.m_visible ? 1231 : 1237);
return result;
}
/**
* @see info.monitorenter.gui.chart.IAxis#hasTrace(info.monitorenter.gui.chart.ITrace2D)
*/
public final boolean hasTrace(final ITrace2D trace) {
// TODO: Null enters here for tooltips with arithmetic mean trace!!!
boolean result = false;
result = this.m_traces.contains(trace);
return result;
}
/**
* Performs expensive calculations for various values that are used by many
* calls throughout a paint iterations.
* <p>
* These values are constant throughout a paint iteration by the contract that
* no point is added removed or changed in this period. Because these values
* are used from many methods it is impossible to calculate them at a
* "transparent" method that may perform this caching over a paint period
* without knowledge from outside. The first method called in a paint
* iteration is called several further times in the iteration. So this is the
* common hook to invoke before painting a chart.
* <p>
*/
public void initPaintIteration() {
// This is needed e.g. for LabelFormatterAutoUnits to choose the unit
// according to the actual range of this paint iteration.
this.m_formatter.initPaintIteration();
this.m_axisScalePolicy.initPaintIteration(this);
}
/**
* @see info.monitorenter.gui.chart.IAxis#isDirtyScaling()
*/
public final boolean isDirtyScaling() {
boolean result = this.m_needsFullRescale;
if (!result) {
Range range;
range = this.getRange();
result |= !range.equals(this.m_rangePreviousScaling);
}
return result;
}
/**
* Returns whether the x grid is painted or not.
* <p>
*
* @return whether the x grid is painted or not.
*/
public final boolean isPaintGrid() {
return this.m_paintGrid;
}
/**
* Returns whether the scale for this axis should be painted or not.
* <p>
*
* @return whether the scale for this axis should be painted or not.
*/
public final boolean isPaintScale() {
return this.m_paintScale;
}
/**
* Check whether scale values are started from major ticks.
* <p>
*
* @return true if scale values start from major ticks.
* @see AAxis#setMajorTickSpacing(double)
*/
public boolean isStartMajorTick() {
return this.m_startMajorTick;
}
/**
* Check if this axis is visible, i.e. needs to be painted on the chart.
* <p>
*
* @return true if this axis has to be painted on the chart.
*/
public boolean isVisible() {
return this.m_visible;
}
/**
* Adds this axis as a listener to all property change events of the given
* trace that are needed here.
* <p>
*
* @param trace
* the trace to listen to.
*/
private void listen2Trace(final ITrace2D trace) {
// listen to bound changes and more
if (this.getAccessor().getDimension() == Chart2D.X) {
trace.addPropertyChangeListener(ITrace2D.PROPERTY_MAX_X, this);
trace.addPropertyChangeListener(ITrace2D.PROPERTY_MIN_X, this);
} else {
trace.addPropertyChangeListener(ITrace2D.PROPERTY_MAX_Y, this);
trace.addPropertyChangeListener(ITrace2D.PROPERTY_MIN_Y, this);
}
// These are repaint candidates:
trace.addPropertyChangeListener(ITrace2D.PROPERTY_COLOR, this);
trace.addPropertyChangeListener(ITrace2D.PROPERTY_STROKE, this);
trace.addPropertyChangeListener(ITrace2D.PROPERTY_VISIBLE, this);
trace.addPropertyChangeListener(ITrace2D.PROPERTY_ZINDEX, this);
trace.addPropertyChangeListener(ITrace2D.PROPERTY_PAINTERS, this);
trace.addPropertyChangeListener(ITrace2D.PROPERTY_ERRORBARPOLICY, this);
trace.addPropertyChangeListener(ITrace2D.PROPERTY_ERRORBARPOLICY_CONFIGURATION, this);
trace.addPropertyChangeListener(ITrace2D.PROPERTY_ZINDEX, this);
trace.addPropertyChangeListener(ITrace2D.PROPERTY_NAME, this);
// listen to newly added points
// this is needed for scaling at point level.
// else every bound change would force to rescale all traces!
trace.addPropertyChangeListener(ITrace2D.PROPERTY_TRACEPOINT, this);
// listen to changed points whose location was changed:
trace.addPropertyChangeListener(ITrace2D.PROPERTY_POINT_CHANGED, this);
}
/**
* Internal routine for an axis to listen to the new given axis title.
* <p>
* Factored out to have a better overview of event handling (vs. putting this
* code in setters).
* <p>
*
* @param axisTitle
* the axis title to listen to.
*/
private void listenToAxisTitle(final AxisTitle axisTitle) {
axisTitle.addPropertyChangeListener(AxisTitle.PROPERTY_TITLE, this);
axisTitle.addPropertyChangeListener(AxisTitle.PROPERTY_TITLEFONT, this);
axisTitle.addPropertyChangeListener(AxisTitle.PROPERTY_TITLEPAINTER, this);
}
/**
* @see info.monitorenter.gui.chart.IAxis#paint(java.awt.Graphics)
*/
public void paint(final Graphics g2d) {
if (!this.m_visible) {
return;
}
switch (this.getDimension() | this.getAxisPosition()) {
case (Chart2D.X | Chart2D.CHART_POSITION_BOTTOM): {
this.paintAxisXBottom(g2d);
break;
}
case (Chart2D.X | Chart2D.CHART_POSITION_TOP): {
this.paintAxisXTop(g2d);
break;
}
case (Chart2D.Y | Chart2D.CHART_POSITION_LEFT): {
this.paintAxisYLeft(g2d);
break;
}
case (Chart2D.Y | Chart2D.CHART_POSITION_RIGHT): {
this.paintAxisYRight(g2d);
break;
}
default: {
throw new IllegalStateException("No valid Chart position found for this axis: " + this);
}
}
}
/**
* Internally paints this axis in the case it is assigned as an x axis on
* bottom of the corresponding chart.
* <p>
*
* @param g2d
* the graphics context to use.
*/
private void paintAxisXBottom(final Graphics g2d) {
final Chart2D chart = this.getAccessor().getChart();
int tmp = 0;
final FontMetrics fontdim = g2d.getFontMetrics();
final int fontheight = fontdim.getHeight();
final int xAxisStart = chart.getXChartStart();
final int xAxisEnd = chart.getXChartEnd();
final int yAxisEnd = chart.getYChartEnd();
final int rangexPx = xAxisEnd - xAxisStart;
final int yAxisLine = this.getPixelYTop();
g2d.drawLine(xAxisStart, yAxisLine, xAxisEnd, yAxisLine);
// drawing the x title :
this.paintTitle(g2d);
// drawing tick - scale, corresponding values, grid and conditional unit
// label:
if (this.isPaintScale() || this.isPaintGrid()) {
final IAxisTickPainter tickPainter = chart.getAxisTickPainter();
tmp = 0;
final List<LabeledValue> labels = this.m_axisScalePolicy.getScaleValues(g2d, this);
for (final LabeledValue label : labels) {
tmp = xAxisStart + (int) (label.getValue() * rangexPx);
// true -> is bottom axis:
if (this.isPaintScale()) {
tickPainter.paintXTick(tmp, yAxisLine, label.isMajorTick(), true, g2d);
tickPainter.paintXLabel(tmp, yAxisLine + fontheight, label.getLabel(), g2d);
}
if (this.isPaintGrid()) {
// do not paint over the axis
if (tmp != xAxisStart) {
g2d.setColor(chart.getGridColor());
g2d.drawLine(tmp, yAxisLine - 1, tmp, yAxisEnd);
g2d.setColor(chart.getForeground());
}
}
}
}
// unit-labeling
g2d.drawString(this.getFormatter().getUnit().getUnitName(), xAxisEnd, yAxisLine + 4
+ fontdim.getHeight() * 2);
}
/**
* Internally paints this axis in the case it is assigned as an x axis on top
* of the corresponding chart.
* <p>
*
* @param g2d
* the graphics context to use.
*/
private void paintAxisXTop(final Graphics g2d) {
final Chart2D chart = this.getAccessor().getChart();
int tmp = 0;
final FontMetrics fontdim = g2d.getFontMetrics();
final int xAxisStart = chart.getXChartStart();
final int xAxisEnd = chart.getXChartEnd();
final int yAxisStart = chart.getYChartStart();
final int rangexPx = xAxisEnd - xAxisStart;
// 1.2) x axis top:
final int yAxisLine = this.getPixelYBottom();
g2d.drawLine(xAxisStart, yAxisLine, xAxisEnd, yAxisLine);
// drawing the x title :
this.paintTitle(g2d);
// drawing tick - scale, corresponding values, grid and conditional unit
// label:
if (this.isPaintScale()||this.isPaintGrid()) {
// first for x- angle.
tmp = 0;
final IAxisTickPainter tickPainter = chart.getAxisTickPainter();
final int majorTickLength = tickPainter.getMajorTickLength();
final List<LabeledValue> labels = this.m_axisScalePolicy.getScaleValues(g2d, this);
for (final LabeledValue label : labels) {
tmp = xAxisStart + (int) (label.getValue() * rangexPx);
if (this.isPaintScale()) {
// 2nd boolean false -> is not bottom axis (top):
tickPainter.paintXTick(tmp, yAxisLine, label.isMajorTick(), false, g2d);
tickPainter.paintXLabel(tmp, yAxisLine - majorTickLength, label.getLabel(), g2d);
}
if (this.isPaintGrid()) {
// do not paint over the axis:
if (tmp != xAxisStart) {
g2d.setColor(chart.getGridColor());
g2d.drawLine(tmp, yAxisLine + 1, tmp, yAxisStart);
g2d.setColor(chart.getForeground());
}
}
}
// unit-labeling
g2d.drawString(this.getFormatter().getUnit().getUnitName(), xAxisEnd, yAxisLine - 4
- fontdim.getHeight());
}
}
/**
* Internally paints this axis in the case it is assigned as a y axis on the
* left side of the corresponding chart.
* <p>
*
* @param g2d
* the graphics context to use.
*/
private void paintAxisYLeft(final Graphics g2d) {
final Chart2D chart = this.getAccessor().getChart();
int tmp = 0;
final FontMetrics fontdim = g2d.getFontMetrics();
final int xAxisStart = chart.getXChartStart();
final int xAxisEnd = chart.getXChartEnd();
final int yAxisStart = chart.getYChartStart();
final int yAxisEnd = chart.getYChartEnd();
final int rangeyPx = yAxisStart - yAxisEnd;
final int xAxisLine = this.getPixelXRight();
g2d.drawLine(xAxisLine, yAxisStart, xAxisLine, yAxisEnd);
// drawing the y title :
this.paintTitle(g2d);
// drawing tick - scale, corresponding values, grid and conditional unit
// label:
if (this.isPaintScale() || this.isPaintGrid()) {
final IAxisTickPainter tickPainter = chart.getAxisTickPainter();
final int majorTickLength = tickPainter.getMajorTickLength();
final List<LabeledValue> labels = this.m_axisScalePolicy.getScaleValues(g2d, this);
for (final LabeledValue label : labels) {
tmp = yAxisStart - (int) (label.getValue() * rangeyPx);
if (this.isPaintScale()) {
// true -> is left y axis:
tickPainter.paintYTick(xAxisLine, tmp, label.isMajorTick(), true, g2d);
tickPainter.paintYLabel(xAxisLine - majorTickLength
- fontdim.stringWidth(label.getLabel()), tmp, label.getLabel(), g2d);
}
if (this.isPaintGrid()) {
if (tmp != yAxisStart) {
g2d.setColor(chart.getGridColor());
g2d.drawLine(xAxisStart + 1, tmp, xAxisEnd, tmp);
g2d.setColor(chart.getForeground());
}
}
}
// unit-labeling
final String unitName = this.getFormatter().getUnit().getUnitName();
g2d.drawString(unitName, 4, yAxisEnd);
}
}
/**
* Internally paints this axis in the case it is assigned as a y axis on the
* right side of the corresponding chart.
* <p>
*
* @param g2d
* the graphics context to use.
*/
private void paintAxisYRight(final Graphics g2d) {
final Chart2D chart = this.getAccessor().getChart();
int tmp = 0;
final FontMetrics fontdim = g2d.getFontMetrics();
final int xAxisStart = chart.getXChartStart();
final int xAxisEnd = chart.getXChartEnd();
final int yAxisStart = chart.getYChartStart();
final int yAxisEnd = chart.getYChartEnd();
final int rangeyPx = yAxisStart - yAxisEnd;
final int xAxisLine = this.getPixelXLeft();
g2d.drawLine(xAxisLine, yAxisStart, xAxisLine, yAxisEnd);
// drawing the y title :
this.paintTitle(g2d);
// drawing tick - scale, corresponding values, grid and conditional unit
// label:
if (this.isPaintScale() || this.isPaintGrid()) {
// then for y- angle.
final IAxisTickPainter tickPainter = chart.getAxisTickPainter();
final List<LabeledValue> labels = this.m_axisScalePolicy.getScaleValues(g2d, this);
final int tickWidth = tickPainter.getMajorTickLength() + 4;
for (final LabeledValue label : labels) {
tmp = yAxisStart - (int) (label.getValue() * rangeyPx);
if (this.isPaintScale()) {
// false -> is right y axis:
tickPainter.paintYTick(xAxisLine, tmp, label.isMajorTick(), false, g2d);
tickPainter.paintYLabel(xAxisLine + tickWidth, tmp, label.getLabel(), g2d);
}
if (this.isPaintGrid()) {
// do not paint over the axis:
if (tmp != yAxisStart) {
g2d.setColor(chart.getGridColor());
g2d.drawLine(xAxisStart + 1, tmp, xAxisEnd, tmp);
g2d.setColor(chart.getForeground());
}
}
}
// unit-labeling
final String unitName = this.getFormatter().getUnit().getUnitName();
g2d.drawString(unitName, (int) chart.getSize().getWidth()
- fontdim.charsWidth(unitName.toCharArray(), 0, unitName.length()) - 4, yAxisEnd);
}
}
/**
* @see info.monitorenter.gui.chart.IAxis#paintTitle(java.awt.Graphics)
*/
public int paintTitle(final Graphics g2d) {
int result = 0;
// TODO: Add support for different axis locations: top, bottom, left,right!
// drawing the title :
final IAxis.AxisTitle axisTitle = this.getAxisTitle();
final String title = axisTitle.getTitle();
if (!StringUtil.isEmpty(title)) {
IAxisTitlePainter titlePainter;
titlePainter = axisTitle.getTitlePainter();
titlePainter.paintTitle(this, g2d);
final int dimension = this.getDimension();
switch (dimension) {
case Chart2D.X:
result = titlePainter.getHeight(this, g2d);
break;
case Chart2D.Y:
result = titlePainter.getWidth(this, g2d);
break;
default:
throw new IllegalArgumentException(
"Given axis.getDimension() is neither Chart2D.X nor Chart2D.Y!");
}
}
return result;
}
/**
* Receives all <code>{@link PropertyChangeEvent}</code> from all instances
* the chart registers itself as a <code>{@link PropertyChangeListener}</code>
* .
* <p>
*
* @param evt
* the property change event that was fired.
* @see java.beans.PropertyChangeListener#propertyChange(java.beans.PropertyChangeEvent)
*/
public void propertyChange(final PropertyChangeEvent evt) {
if (Chart2D.DEBUG_THREADING) {
System.out
.println("AAxis.propertyChange (" + Thread.currentThread().getName() + "), 0 locks");
}
final String property = evt.getPropertyName();
final IPropertyChangeReactor reactor = AAxis.propertyReactors.get(property);
if (reactor != null) {
reactor.propertyChange(evt, this);
}
}
/**
* @see info.monitorenter.gui.chart.IAxis#removeAllTraces()
*/
public final Set<ITrace2D> removeAllTraces() {
final Set<ITrace2D> result = new TreeSet<ITrace2D>();
result.addAll(this.m_traces);
/*
* cannot work on this.m_traces as remove will cause concurrent modification
* exception!
*/
for (final ITrace2D trace : result) {
this.removeTrace(trace);
}
return result;
}
/**
* @see info.monitorenter.gui.chart.IAxis#removeAxisTitle()
*/
public AxisTitle removeAxisTitle() {
final AxisTitle result = this.m_axisTitle;
this.unListenToAxisTitle(this.m_axisTitle);
this.m_axisTitle = null;
return result;
}
/**
* @see info.monitorenter.gui.chart.IAxis#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.IAxis#removeTrace(info.monitorenter.gui.chart.ITrace2D)
*/
public boolean removeTrace(final ITrace2D trace) {
final boolean result = this.m_traces.remove(trace);
if (result) {
this.unlisten2Trace(trace);
this.m_max = this.findMax();
this.m_min = this.findMin();
this.m_propertyChangeSupport.firePropertyChange(IAxis.PROPERTY_ADD_REMOVE_TRACE, trace, null);
}
return result;
}
/**
* Internally rescales the given <code>{@link ITracePoint2D}</code> in the
* dimension this axis works in.
* <p>
*
* @param point
* the point to scale (between 0.0 and 1.0) according to the internal
* bounds.
*/
protected final void scalePoint(final ITracePoint2D point) {
final int axis = this.getAccessor().getDimension();
if (axis == Chart2D.X) {
point.setScaledX(this.getScaledValue(point.getX()));
} else if (axis == Chart2D.Y) {
point.setScaledY(this.getScaledValue(point.getY()));
}
if (Chart2D.DEBUG_SCALING) {
// This is ok for fixed viewports that zoom!
if ((point.getScaledX() > 1.0) || (point.getScaledX() < 0.0) || (point.getScaledY() > 1.0)
|| (point.getScaledY() < 0.0)) {
System.out.println("Scaled Point " + point + " to [" + point.getScaledX() + ","
+ point.getScaledY() + "]");
}
}
}
/**
* @see info.monitorenter.gui.chart.IAxis#scale()
*/
public void scale() {
final Iterator<ITrace2D> it = this.m_traces.iterator();
ITrace2D trace;
while (it.hasNext()) {
trace = it.next();
this.scaleTrace(trace);
}
this.m_rangePreviousScaling.mimic(this.getRange());
this.m_needsFullRescale = false;
}
/**
* @see info.monitorenter.gui.chart.IAxis#scaleTrace(info.monitorenter.gui.chart.ITrace2D)
*/
public void scaleTrace(final ITrace2D trace) {
final Range range = this.getRange();
this.m_accessor.scaleTrace(trace, range);
}
/**
* Sets the accessor to the axis of the chart.
* <p>
*
* @param accessor
* the accessor to the axis of the chart.
*/
protected final void setAccessor(final AChart2DDataAccessor accessor) {
this.m_accessor = accessor;
}
/**
* Sets the axisPosition.
* <p>
*
* @param axisPosition
* {@link Chart2D#CHART_POSITION_LEFT},
* {@link Chart2D#CHART_POSITION_RIGHT},
* {@link Chart2D#CHART_POSITION_TOP},
* {@link Chart2D#CHART_POSITION_BOTTOM} or -1 if this axis is not
* assigned to a chart.
*/
protected final synchronized void setAxisPosition(final int axisPosition) {
this.m_axisPosition = axisPosition;
}
/**
* @see info.monitorenter.gui.chart.IAxis#setAxisTitle(info.monitorenter.gui.chart.IAxis.AxisTitle)
*/
public void setAxisTitle(final AxisTitle axisTitle) {
this.unListenToAxisTitle(this.m_axisTitle);
this.m_axisTitle = axisTitle;
this.listenToAxisTitle(this.m_axisTitle);
}
/**
* Callback that allows the chart to register itself with the axis when the axis is added to the chart.
* <p>
* <b>This is intended for <code>Chart2D</code> only!</b>.
* Please do not use this from anywhere in your code. It allows to
* <p>
*
* @param chart
* the chart to register itself with this axis.
* @param dimension
* <code>{@link Chart2D#X}</code> or <code>{@link Chart2D#Y}</code>.
* @param position
* <code>{@link Chart2D#CHART_POSITION_BOTTOM}</code>, <code>
* {@link Chart2D#CHART_POSITION_LEFT}</code>, <code>
* {@link Chart2D#CHART_POSITION_RIGHT}</code> or <code>
* {@link Chart2D#CHART_POSITION_TOP}</code>.
*/
public void setChart(final Chart2D chart, final int dimension, final int position) {
this.m_accessor = this.createAccessor(chart, dimension, position);
this.m_needsFullRescale = true;
}
/**
* Sets the formatter to use for labels.
* <p>
*
* @param formatter
* The formatter to set.
*/
public void setFormatter(final IAxisLabelFormatter formatter) {
if (this.getAccessor() != null) {
// remove listener for this:
this.removePropertyChangeListener(IAxis.PROPERTY_LABELFORMATTER, this.getAccessor()
.getChart());
// add listener for this:
this.addPropertyChangeListener(IAxis.PROPERTY_LABELFORMATTER, this.getAccessor().getChart());
// listener to subsequent format changes:
if (this.m_formatter != null) {
// remove listener on old formatter:
this.m_formatter.removePropertyChangeListener(IAxisLabelFormatter.PROPERTY_FORMATCHANGE,
this);
}
formatter.addPropertyChangeListener(IAxisLabelFormatter.PROPERTY_FORMATCHANGE, this);
}
final IAxisLabelFormatter old = this.m_formatter;
this.m_formatter = formatter;
this.m_formatter.setAxis(this);
this.m_propertyChangeSupport.firePropertyChange(IAxis.PROPERTY_LABELFORMATTER, old,
this.m_formatter);
}
/**
* This method sets the major tick spacing for label generation.
* <p>
* Only values between 0.0 and 100.0 are allowed.
* <p>
* The number that is passed-in represents the distance, measured in values,
* between each major tick mark. If you have a trace with a range from 0 to 50
* and the major tick spacing is set to 10, you will get major ticks next to
* the following values: 0, 10, 20, 30, 40, 50.
* <p>
* <b>Note: </b> <br>
* Ticks are free of any multiples of 1000. If the chart contains values
* between 0 an 1000 and configured a tick of 2 the values 0, 200, 400, 600,
* 800 and 1000 will highly probable to be displayed. This depends on the size
* (in pixels) of the <code>Chart2D<</code>. Of course there is a difference:
* ticks are used in divisions and multiplications: If the internal values are
* very low and the ticks are very high, huge rounding errors might occur
* (division by ticks results in very low values a double cannot hit exactly.
* So prefer setting ticks between 0 an 10 or - if you know your values are
* very small (e.g. in nano range [10 <sup>-9 </sup>]) use a small value (e.g.
* 2*10 <sup>-9 </sup>).
* <p>
*
* @param majorTickSpacing
* the major tick spacing for label generation.
*/
public void setMajorTickSpacing(final double majorTickSpacing) {
this.m_majorTickSpacing = majorTickSpacing;
}
/**
* This method sets the minor tick spacing for label generation.
* <p>
* The number that is passed-in represents the distance, measured in values,
* between each major tick mark. If you have a trace with a range from 0 to 50
* and the major tick spacing is set to 10, you will get major ticks next to
* the following values: 0, 10, 20, 30, 40, 50.
* <p>
* <b>Note: </b> <br>
* Ticks are free of any powers of 10. There is no difference between setting
* a tick to 2, 200 or 20000 because ticks cannot break the rule that every
* scale label has to be visible. If the chart contains values between 0 an
* 1000 and configured a tick of 2 the values 0, 200, 400, 600, 800 and 1000
* will highly probable to be displayed. This depends on the size (in pixels)
* of the <code>Chart2D<</code>. Of course there is a difference: ticks are
* used in divisions and multiplications: If the internal values are very low
* and the ticks are very high, huge rounding errors might occur (division by
* ticks results in very low values a double cannot hit exactly. So prefer
* setting ticks between 0 an 10 or - if you know your values are very small
* (e.g. in nano range [10 <sup>-9 </sup>]) use a small value (e.g. 2*10
* <sup>-9 </sup>).
* <p>
*
* @param minorTickSpacing
* the minor tick spacing to set.
*/
public void setMinorTickSpacing(final double minorTickSpacing) {
this.m_minorTickSpacing = minorTickSpacing;
}
/**
* Set whether the grid in this dimension should be painted or not.
* <p>
*
* @param grid
* true if the grid should be painted or false if not.
*/
public final void setPaintGrid(final boolean grid) {
final boolean oldValue = this.m_paintGrid;
this.m_paintGrid = grid;
if (oldValue != grid) {
this.m_propertyChangeSupport.firePropertyChange(new PropertyChangeEvent(this,
IAxis.PROPERTY_PAINTGRID, new Boolean(oldValue), Boolean.valueOf(this.m_paintGrid)));
}
}
/**
* Set if the scale for this axis should be shown.
* <p>
*
* @param show
* true if the scale on this axis should be shown, false else.
*/
public final void setPaintScale(final boolean show) {
boolean oldValue = this.m_paintScale;
this.m_paintScale = show;
if (oldValue != this.m_paintScale) {
this.m_propertyChangeSupport.firePropertyChange(new PropertyChangeEvent(this,
IAxis.PROPERTY_PAINTSCALE, new Boolean(oldValue), Boolean.valueOf(this.m_paintGrid)));
}
}
/**
* @see info.monitorenter.gui.chart.IAxis#setPixelXLeft(int)
*/
public final void setPixelXLeft(final int pixelXLeft) {
this.m_pixelXLeft = pixelXLeft;
}
/**
* @see info.monitorenter.gui.chart.IAxis#setPixelXRight(int)
*/
public final void setPixelXRight(final int pixelXRight) {
this.m_pixelXRight = pixelXRight;
}
/**
* @see info.monitorenter.gui.chart.IAxis#setPixelYBottom(int)
*/
public final void setPixelYBottom(final int pixelYBottom) {
this.m_pixelYBottom = pixelYBottom;
}
/**
* @see info.monitorenter.gui.chart.IAxis#setPixelYTop(int)
*/
public final void setPixelYTop(final int pixelYTop) {
this.m_pixelYTop = pixelYTop;
}
/**
* <p>
* Sets a Range to use for filtering the view to the the connected Axis. Note
* that it's effect will be affected by the internal {@link IRangePolicy}.
* </p>
* <p>
* To get full control use: <br>
* <code> setRangePolicy(new <AnARangePolicy>(range);</code>
* </p>
*
* @param range
* Range to use for filtering the view to the the connected Axis.
* @see #getRangePolicy()
* @see IRangePolicy#setRange(Range)
*/
public final void setRange(final Range range) {
this.getRangePolicy().setRange(range);
}
/**
* Ensures that no deadlock / NPE due to a missing internal chart reference may
* occur.
* <p>
*
* @throws IllegalStateException
* if this axis is not assigned to a chart.
*
*/
protected final void ensureInitialized() {
if (this.m_accessor == null) {
throw new IllegalStateException("Add this axis to a chart first before this operation (undebuggable deadlocks might occur else)");
}
}
/**
* <p>
* Sets the RangePolicy.
* </p>
* <p>
* If the given RangePolicy has an unconfigured internal Range (
* {@link Range#RANGE_UNBOUNDED}) the old internal RangePolicy is taken into
* account: <br>
* If the old RangePolicy has a configured Range this is transferred to the
* new RangePolicy.
* </p>
* A property change event for {@link IAxis#PROPERTY_RANGEPOLICY} is fired and
* receives listeners if a change took place.
* <p>
*
* @param rangePolicy
* The rangePolicy to set.
*/
public void setRangePolicy(final IRangePolicy rangePolicy) {
this.ensureInitialized();
final IRangePolicy old = this.getRangePolicy();
double max = 0;
double min = 0;
if (old != null) {
max = AAxis.this.getMax();
min = AAxis.this.getMin();
old.removePropertyChangeListener(this.m_accessor.m_chart, IRangePolicy.PROPERTY_RANGE);
old.removePropertyChangeListener(this.m_accessor.m_chart, IRangePolicy.PROPERTY_RANGE_MAX);
old.removePropertyChangeListener(this.m_accessor.m_chart, IRangePolicy.PROPERTY_RANGE_MIN);
}
this.m_rangePolicy = rangePolicy;
this.m_rangePolicy.addPropertyChangeListener(IRangePolicy.PROPERTY_RANGE,
this.m_accessor.m_chart);
this.m_rangePolicy.addPropertyChangeListener(IRangePolicy.PROPERTY_RANGE_MAX,
this.m_accessor.m_chart);
this.m_rangePolicy.addPropertyChangeListener(IRangePolicy.PROPERTY_RANGE_MIN,
this.m_accessor.m_chart);
// check for scaling changes:
if (((max != 0) && (min != 0))
&& ((max != AAxis.this.getMax()) || (min != AAxis.this.getMin()))) {
this.m_accessor.m_chart.propertyChange(new PropertyChangeEvent(rangePolicy,
IRangePolicy.PROPERTY_RANGE, new Range(min, max), this.m_rangePolicy.getRange()));
}
this.m_propertyChangeSupport.firePropertyChange(IAxis.PROPERTY_RANGEPOLICY, old, rangePolicy);
}
/**
* Set wether scale values are started from major ticks.
* <p>
*
* @param majorTick
* true if scale values shall start with a major tick.
* @see AAxis#setMajorTickSpacing(double)
*/
public void setStartMajorTick(final boolean majorTick) {
this.m_startMajorTick = majorTick;
}
/**
* @deprecated use {@link #getAxisTitle()} and on the result
* {@link IAxis.AxisTitle#setTitle(String)}
*/
@Deprecated
public final String setTitle(final String title) {
final String result = this.getAxisTitle().setTitle(title);
return result;
}
/**
* Sets the title painter of this axis which is by default <code>
* {@link info.monitorenter.gui.chart.axistitlepainters.AxisTitlePainterDefault}
* </code>.
* <p>
*
* @deprecated use {@link #getAxisTitle()} and on the result
* {@link IAxis.AxisTitle#setTitlePainter(IAxisTitlePainter)}
* instead.
*/
@Deprecated
public final IAxisTitlePainter setTitlePainter(final IAxisTitlePainter painter) {
final IAxisTitlePainter result = this.getAxisTitle().setTitlePainter(painter);
return result;
}
/**
* Set the visibility of this axis.
* <p>
*
* @param visible
* true to show, false to hide
*/
public void setVisible(final boolean visible) {
this.m_visible = visible;
}
/**
* Returns the translation of the mouse event coordinates of the given mouse
* event to the value within the chart for the dimension (x,y) covered by this
* axis.
* <p>
* Note that the mouse event has to be an event fired on the correspondinig
* chart component!
* <p>
*
* @param mouseEvent
* a mouse event that has been fired on this component.
*
* @return the translation of the mouse event coordinates of the given mouse
* event to the value within the chart for the dimension covered by
* this axis (x or y) or null if no calculations could be performed as
* the chart was not painted before.
*
* @throws IllegalArgumentException
* if the given mouse event is out of the current graphics context
* (not a mouse event of the chart component).
*/
public double translateMousePosition(final MouseEvent mouseEvent) throws IllegalArgumentException {
return this.getAccessor().translateMousePosition(mouseEvent);
}
/**
* @see info.monitorenter.gui.chart.IAxis#translatePxToValue(int)
*/
public double translatePxToValue(final int pixel) {
return this.m_accessor.translatePxToValue(pixel);
}
/**
* @see info.monitorenter.gui.chart.IAxis#translateValueToPx(double)
*/
public final int translateValueToPx(final double value) {
return this.m_accessor.translateValueToPx(value);
}
/**
* Removes this axis as a listener for all property change events of the given
* trace that are needed here.
* <p>
* TODO: stick to <code>{@link AAxis#listen2Trace(ITrace2D)}</code>.
* <p>
*
* @param trace
* the trace to not listen to any more.
*/
private void unlisten2Trace(final ITrace2D trace) {
if (this.getAccessor().getDimension() == Chart2D.X) {
trace.removePropertyChangeListener(ITrace2D.PROPERTY_MAX_X, this);
trace.removePropertyChangeListener(ITrace2D.PROPERTY_MIN_X, this);
} else {
trace.removePropertyChangeListener(ITrace2D.PROPERTY_MAX_Y, this);
trace.removePropertyChangeListener(ITrace2D.PROPERTY_MIN_Y, this);
}
trace.removePropertyChangeListener(ITrace2D.PROPERTY_COLOR, this);
trace.removePropertyChangeListener(ITrace2D.PROPERTY_STROKE, this);
trace.removePropertyChangeListener(ITrace2D.PROPERTY_VISIBLE, this);
trace.removePropertyChangeListener(ITrace2D.PROPERTY_ZINDEX, this);
trace.removePropertyChangeListener(ITrace2D.PROPERTY_PAINTERS, this);
trace.removePropertyChangeListener(ITrace2D.PROPERTY_ERRORBARPOLICY, this);
trace.removePropertyChangeListener(ITrace2D.PROPERTY_ERRORBARPOLICY_CONFIGURATION, this);
trace.removePropertyChangeListener(ITrace2D.PROPERTY_ZINDEX, this);
trace.removePropertyChangeListener(ITrace2D.PROPERTY_NAME, this);
trace.removePropertyChangeListener(ITrace2D.PROPERTY_TRACEPOINT, this);
trace.removePropertyChangeListener(ITrace2D.PROPERTY_POINT_CHANGED, this);
}
/**
* Internal routine for an axis to not listen to the new given axis title any
* more.
* <p>
* Factored out to have a better overview of event handling (vs. putting this
* code in setters).
* <p>
*
* @param axisTitle
* the axis title not to listen to any more.
*/
private void unListenToAxisTitle(final AxisTitle axisTitle) {
// This is the case when the axis is created: m_axisTitle is null then.
if (axisTitle != null) {
axisTitle.removePropertyChangeListener(AxisTitle.PROPERTY_TITLE, this);
axisTitle.removePropertyChangeListener(AxisTitle.PROPERTY_TITLEFONT, this);
axisTitle.removePropertyChangeListener(AxisTitle.PROPERTY_TITLEPAINTER, this);
}
}
}