/* * 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); } } }