/* * AAxisTransformation.java of project jchart2d, * base class for Axis implementations that transform the scale * for changed display. * 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.IAxisLabelFormatter; import info.monitorenter.gui.chart.IAxisScalePolicy; import info.monitorenter.gui.chart.ITrace2D; import info.monitorenter.gui.chart.ITracePoint2D; import info.monitorenter.gui.chart.axis.scalepolicy.AxisScalePolicyTransformation; import info.monitorenter.util.Range; import info.monitorenter.util.math.MathUtil; import java.awt.event.MouseEvent; import java.util.Iterator; /** * Base class for Axis implementations that transform the scale for changed * display. * <p> * * Note that instances of this implementations will only accept subtypes of * {@link AxisScalePolicyTransformation} for the method * {@link #setAxisScalePolicy(IAxisScalePolicy)}. * <p> * * * @param <T> * Used to enforce that this instance only accepts * {@link AxisScalePolicyTransformation} and subtypes. * * @author <a href="mailto:Achim.Westermann@gmx.de">Achim Westermann</a> * * @version $Revision: 1.32 $ */ public abstract class AAxisTransformation<T extends AxisScalePolicyTransformation> extends AAxis<T> { /** * An accessor for the x axis of a chart. * <p> * * @author <a href="mailto:Achim.Westermann@gmx.de>Achim Westermann </a> * @see Chart2D#getAxisX() */ protected final class XDataAccessor extends AAxis<T>.XDataAccessor { /** Generated <code>serialVersionUID</code>. */ private static final long serialVersionUID = 8775312615991487847L; /** * 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.XDataAccessor#scaleTrace(info.monitorenter.gui.chart.ITrace2D, * info.monitorenter.util.Range) */ @Override protected void scaleTrace(final ITrace2D trace, final Range range) { if (trace.isVisible()) { Iterator<ITracePoint2D> itPoints = trace.iterator(); ITracePoint2D point; double result; double scaler = range.getExtent(); itPoints = trace.iterator(); while (itPoints.hasNext()) { point = itPoints.next(); double absolute = point.getX(); try { result = (AAxisTransformation.this.transform(absolute) - range.getMin()); result = result / scaler; if (!MathUtil.isDouble(result)) { result = 0; } } catch (IllegalArgumentException e) { long tstamp = System.currentTimeMillis(); if (tstamp - AAxisTransformation.this.m_outputErrorTstamp > AAxisTransformation.OUTPUT_ERROR_THRESHHOLD) { System.out.println(e.getLocalizedMessage()); AAxisTransformation.this.m_outputErrorTstamp = tstamp; } result = 0; } point.setScaledX(result); } } } /** * @see info.monitorenter.gui.chart.axis.AAxis.XDataAccessor#translatePxToValue(int) */ @Override public double translatePxToValue(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 = new Range(AAxisTransformation.this.getMinTransformed(), AAxisTransformation.this.getMaxTransformed()); result = scaledX * valueRangeX.getExtent() + valueRangeX.getMin(); } return result; } /** * @see info.monitorenter.gui.chart.axis.AAxis.XDataAccessor#translateValueToPx(double) */ @Override public final int translateValueToPx(final double value) { /* * Note: This code (the math) is the combination of the scaleTrace code * above for normalization plus the deflate-into-pixelspace code in * Chart2D.paint. */ int result; double normalizedValue; Range range = new Range(AAxisTransformation.this.getMinTransformed(), AAxisTransformation.this.getMaxTransformed()); double scaler = range.getExtent(); double absolute = value; try { normalizedValue = (AAxisTransformation.this.transform(absolute) - range.getMin()); normalizedValue = normalizedValue / scaler; if (!MathUtil.isDouble(normalizedValue)) { normalizedValue = 0; } } catch (IllegalArgumentException e) { long tstamp = System.currentTimeMillis(); if (tstamp - AAxisTransformation.this.m_outputErrorTstamp > AAxisTransformation.OUTPUT_ERROR_THRESHHOLD) { System.out.println(e.getLocalizedMessage()); AAxisTransformation.this.m_outputErrorTstamp = tstamp; } normalizedValue = 0; } // Now the value is normalized: Chart2D chart = this.getChart(); int pixelRange = this.getPixelRange(); result = (int) Math.round(chart.getXChartStart() + normalizedValue * pixelRange); 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 final class YDataAccessor extends AAxis<T>.YDataAccessor { /** Generated <code>serialVersionUID</code>. */ private static final long serialVersionUID = 3043923189624836455L; /** * 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.YDataAccessor#scaleTrace(info.monitorenter.gui.chart.ITrace2D, * info.monitorenter.util.Range) */ @Override protected void scaleTrace(final ITrace2D trace, final Range range) { if (trace.isVisible()) { ITracePoint2D point; double scaler = range.getExtent(); double result; Iterator<ITracePoint2D> itPoints = trace.iterator(); while (itPoints.hasNext()) { point = itPoints.next(); double absolute = point.getY(); try { // range.getMin() is based upon the transformed minimum (see // getMin() of outer class)! result = (AAxisTransformation.this.transform(absolute) - range.getMin()); result = result / scaler; if (!MathUtil.isDouble(result)) { result = 0; } } catch (IllegalArgumentException e) { long tstamp = System.currentTimeMillis(); if (tstamp - AAxisTransformation.this.m_outputErrorTstamp > AAxisTransformation.OUTPUT_ERROR_THRESHHOLD) { System.out.println(e.getLocalizedMessage()); AAxisTransformation.this.m_outputErrorTstamp = tstamp; } result = 0; } point.setScaledY(result); } } } /** * @see info.monitorenter.gui.chart.axis.AAxis.YDataAccessor#translatePxToValue(int) */ @Override public double translatePxToValue(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 = new Range(AAxisTransformation.this.getMinTransformed(), AAxisTransformation.this.getMaxTransformed()); result = scaledY * valueRangeY.getExtent() + valueRangeY.getMin(); } return result; } /** * @see info.monitorenter.gui.chart.axis.AAxis.AChart2DDataAccessor#translateValueToPx(double) */ @Override public final int translateValueToPx(final double value) { /* * Note: This code (the math) is the combination of the scaleTrace code * above for normalization plus the deflate-into-pixelspace code in * Chart2D.paint. */ int result = 0; Range range = AAxisTransformation.this.getRange(); double scaler = range.getExtent(); double normalizedValue; double absolute = value; try { // range.getMin() is based upon the transformed minimum (see getMin() of // outer class)! normalizedValue = (AAxisTransformation.this.transform(absolute) - range.getMin()); normalizedValue = normalizedValue / scaler; if (!MathUtil.isDouble(normalizedValue)) { normalizedValue = 0; } } catch (IllegalArgumentException e) { long tstamp = System.currentTimeMillis(); if (tstamp - AAxisTransformation.this.m_outputErrorTstamp > AAxisTransformation.OUTPUT_ERROR_THRESHHOLD) { System.out.println(e.getLocalizedMessage()); AAxisTransformation.this.m_outputErrorTstamp = tstamp; } normalizedValue = 0; } // Now the value is normalized: Chart2D chart = this.getChart(); int pixelRange = this.getPixelRange(); result = (int) Math.round(chart.getYChartStart() - normalizedValue * pixelRange); return result; } } /** * Internal flag that defines that only every n milliseconds a transformation * error (untransformable value was used in chart: this axis implementation of * axis is not recommended for the data used) should be reported on system * output. */ private static final int OUTPUT_ERROR_THRESHHOLD = 30000; /** Generated <code>serialVersionUID</code>. **/ private static final long serialVersionUID = -4665444421196939779L; /** * Internal timestamp of the last transformation error reporting. */ protected long m_outputErrorTstamp = 0; /** * Creates a default instance that will use a * {@link info.monitorenter.gui.chart.labelformatters.LabelFormatterAutoUnits} * for formatting labels. * <p> */ public AAxisTransformation() { super(); } /** * Creates an instance that will 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 AAxisTransformation(final IAxisLabelFormatter formatter, final T scalePolicy) { super(formatter, scalePolicy); } /** * @see info.monitorenter.gui.chart.axis.AAxis#createAccessor(info.monitorenter.gui.chart.Chart2D, * int, int) */ @Override protected AAxis<T>.AChart2DDataAccessor createAccessor(final Chart2D chart, final int dimension, final int position) { AAxis<T>.AChart2DDataAccessor result; if (dimension == Chart2D.X) { // Don't allow a combination of dimension and position that is not usable: if ((position & (Chart2D.CHART_POSITION_BOTTOM | Chart2D.CHART_POSITION_TOP)) == 0) { throw new IllegalArgumentException("X axis only valid with top or bottom position."); } this.setAxisPosition(position); result = new XDataAccessor(chart); } else if (dimension == Chart2D.Y) { // Don't allow a combination of dimension and position that is not usable: if ((position & (Chart2D.CHART_POSITION_LEFT | Chart2D.CHART_POSITION_RIGHT)) == 0) { throw new IllegalArgumentException("Y axis only valid with left or right position."); } this.setAxisPosition(position); result = new YDataAccessor(chart); } else { throw new IllegalArgumentException("Dimension has to be Chart2D.X or Chart2D.Y!"); } return result; } /** * Returns the transformed max with additional error treatment in case of * empty traces. * <p> * * @return the transformed max with additional error treatment in case of * empty traces. * * @see info.monitorenter.gui.chart.axis.AAxis#getMax() */ public double getMaxTransformed() { double result = 1.0; try { result = this.transform(super.getMax()); } catch (IllegalArgumentException e) { // nop } return result; } /** * Returns the transformed min with additional error treatment in case of * empty traces. * <p> * * @return the transformed min with additional error treatment in case of * empty traces. * * @see info.monitorenter.gui.chart.axis.AAxis#getMin() */ public double getMinTransformed() { double result = 0.0; try { result = this.transform(super.getMin()); } catch (IllegalArgumentException e) { // nop } return result; } /** * @see info.monitorenter.gui.chart.IAxis#getScaledValue(double) */ public final double getScaledValue(final double absolute) { double result; Range range = new Range(this.getMinTransformed(), this.getMaxTransformed()); try { result = (AAxisTransformation.this.transform(absolute) - range.getMin()); double scaler = range.getExtent(); result = result / scaler; if (!MathUtil.isDouble(result)) { result = 0; } } catch (IllegalArgumentException e) { long tstamp = System.currentTimeMillis(); if (tstamp - this.m_outputErrorTstamp > AAxisTransformation.OUTPUT_ERROR_THRESHHOLD) { System.out.println(e.getLocalizedMessage()); this.m_outputErrorTstamp = tstamp; } result = 0; } return result; } /** * Overridden to incorporate transformation. * <p> * * @see info.monitorenter.gui.chart.IAxis#scaleTrace(info.monitorenter.gui.chart.ITrace2D) */ @Override public void scaleTrace(final ITrace2D trace) { final Range range = new Range(this.getMinTransformed(), this.getMaxTransformed()); this.m_accessor.scaleTrace(trace, range); } /** * Template method for performing the axis transformation. * <p> * The argument should not be negative, so only normalized values (no chart * values but their scaled values or pixel values) should be given here. * <p> * * @param in * the value to transform. * * @return the transformed value. * * @throws IllegalArgumentException * if scaling is impossible (due to some mathematical transformation * in implementations like * {@link info.monitorenter.gui.chart.axis.AxisLog10} */ public abstract double transform(final double in) throws IllegalArgumentException; /** * @see info.monitorenter.gui.chart.axis.AAxis#translateMousePosition(java.awt.event.MouseEvent) */ @Override public final double translateMousePosition(final MouseEvent mouseEvent) throws IllegalArgumentException { return this.untransform(this.getAccessor().translateMousePosition(mouseEvent)); } /** * @see info.monitorenter.gui.chart.axis.AAxis#translatePxToValue(int) */ @Override public double translatePxToValue(final int pixel) { return this.untransform(this.m_accessor.translatePxToValue(pixel)); } /** * Template method for performing the reverse axis transformation. * <p> * This is the counterpart to {@link #transform(double)}. * <p> * * @param in * the transformed value. * @return the normal value; */ public abstract double untransform(final double in); }