/*
 *  AxisScalePolicyAutomaticBestFit.java of project jchart2d, . 
 *  Copyright (C) 2002 - 2011, Achim Westermann, created on Apr 22, 2011
 *
 *  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
 *
 *
 * File   : $Source: /cvsroot/jchart2d/jchart2d/codetemplates.xml,v $
 * Date   : $Date: 2009/02/24 16:45:41 $
 * Version: $Revision: 1.2 $
 */

package info.monitorenter.gui.chart.axis.scalepolicy;

import info.monitorenter.gui.chart.IAxis;
import info.monitorenter.gui.chart.IAxisScalePolicy;
import info.monitorenter.gui.chart.LabeledValue;
import info.monitorenter.gui.chart.axis.AAxis;
import info.monitorenter.util.Range;
import info.monitorenter.util.math.MathUtil;

import java.awt.Graphics;
import java.util.LinkedList;
import java.util.List;

/**
 * Very basic and fast scale policy implementation that ensures the following:
 * 
    *
  • Every scale tick is a minor or major tick of the corresponding axis.
  • *
  • If a scale tick was found that matches a major and a minor tick it is judged as major tick.
  • *
  • Every major tick is a multiple of minor ticks: It is not possible for the sum minor ticks to "skip" a major tick.
  • *
  • There is no guarantee that the labels of ticks will overwrite each others.
  • *
  • There is no guarantee that the major and minor ticks of the axis are chosen in a reasonable manner: You could get no labels at all if the values are too high or thousands of labels with a weird output.
  • *
*

* * * * @author Achim Westermann * */ public class AxisScalePolicyAutomaticBestFit implements IAxisScalePolicy { /** * Internally used for rounding to ticks, calculated once per paint iteration. */ protected double m_power; public List getScaleValues(final Graphics g2d, final IAxis axis) { final double labelspacepx = axis.getAccessor().getMinimumValueDistanceForLabels(g2d); final double formattingspace = axis.getFormatter().getMinimumValueShiftForChange(); final double max = Math.max(labelspacepx, formattingspace); return this.getLabels(max, axis); } /** * @see info.monitorenter.gui.chart.IAxisScalePolicy#initPaintIteration(info.monitorenter.gui.chart.IAxis) */ public void initPaintIteration(IAxis axis) { // get the powers of ten of the range, a minor Tick of 1.0 has to be // able to be 100 times in a range of 100 (match 1,2,3,... instead of // 10,20,30,.... final double range = axis.getMax() - axis.getMin(); double computeRange = range; if ((range == 0) || !MathUtil.isDouble(range)) { computeRange = 1; } double tmpPower = 0; if (computeRange > 1) { while (computeRange > 50) { computeRange /= 10; tmpPower++; } tmpPower = Math.pow(10, tmpPower - 1); } else { while (computeRange < 5) { computeRange *= 10; tmpPower++; } tmpPower = 1 / Math.pow(10, tmpPower); } this.m_power = tmpPower; } /** * Returns the labels for this axis. *

* The labels will have at least the given argument resolution as * distance in the value domain of the chart. *

* * @param resolution * the distance in the value domain of the chart that has to be at * least between to labels. * * @return the labels for the axis. */ protected List getLabels(final double resolution, final IAxis axis) { final List collect = new LinkedList(); if (resolution > 0) { final Range domain = axis.getRange(); final double min = domain.getMin(); final double max = domain.getMax(); String oldLabelName = ""; LabeledValue label; final double range = max - min; final double tickResolution = this.roundToTicks(resolution, false, false, axis).getValue(); double value = Math.ceil(min / tickResolution) * tickResolution; // This was value before the patch that prevents the labels from jumping: // It's benefit was that the first label was not this // far from the start of data (in case startMajorTicks of axis is true): // double value = min; String labelName = "start"; int loopStop = 0; boolean firstMajorFound = false; // first tick, manual init while ((value <= max) && (loopStop < 100)) { if (loopStop == 99) { if (AAxis.DEBUG) { System.out.println(axis.getAccessor().toString() + " axis: loop to high"); } } if (oldLabelName.equals(labelName)) { if (AAxis.DEBUG) { System.out.println("constant Label " + labelName); } } label = this.roundToTicks(value, false, !firstMajorFound && axis.isStartMajorTick(), axis); oldLabelName = labelName; labelName = label.getLabel(); value = label.getValue(); loopStop++; if (firstMajorFound || !axis.isStartMajorTick() || label.isMajorTick()) { firstMajorFound = true; if ((value <= max) && (value >= min)) { collect.add(label); } else if (value > max) { if (AAxis.DEBUG) { System.out.println("Dropping label (too high) : (" + label + ")[max: " + max + "]"); } } else if (value < min) { if (AAxis.DEBUG) { System.out.println("Dropping label (too low) : (" + label + ")[min: " + min + "]"); } } } value += resolution; } final int stop = collect.size(); for (int i = 0; i < stop; i++) { label = collect.get(i); label.setValue((label.getValue() - min) / range); } } return collect; } /** * Internal rounding routine. *

* Arguments are not chosen to be "understandable" or "usable" but optimized * for performance. *

* The findMajorTick argument may be used e.g. to force labels * to start from a major tick. *

* * @param value * the value to round. * @param floor * if true, rounding goes to floor else to ceiling. * @param findMajorTick * if true the returned value will be a major tick (which might be * fare more away from the given value than the next major tick). * @return the value rounded to minor or major ticks. */ protected LabeledValue roundToTicks(final double value, final boolean floor, final boolean findMajorTick, final IAxis axis) { final LabeledValue ret = new LabeledValue(); final double minorTick = axis.getMinorTickSpacing() * this.m_power; final double majorTick = axis.getMajorTickSpacing() * this.m_power; double majorRound; if (floor) { majorRound = Math.floor(value / majorTick); } else { majorRound = Math.ceil(value / majorTick); } final boolean majorZeroHit = (majorRound == 0) && (value != 0); majorRound *= majorTick; double minorRound; if (floor) { minorRound = Math.floor(value / minorTick); } else { minorRound = Math.ceil(value / minorTick); } final boolean minorZeroHit = (minorRound == 0) && (value != 0); minorRound *= minorTick; if (majorZeroHit || minorZeroHit) { if (AAxis.DEBUG) { System.out.println("zeroHit"); } } final double minorDistance = Math.abs(value - minorRound); final double majorDistance = Math.abs(value - majorRound); double majorMinorRelation = minorDistance / majorDistance; if (Double.isNaN(majorMinorRelation)) { majorMinorRelation = 1.0; } if ((majorDistance <= minorDistance) || findMajorTick) { ret.setValue(majorRound); ret.setMajorTick(true); } else { ret.setValue(minorRound); ret.setMajorTick(false); } // format label string. ret.setLabel(axis.getFormatter().format(ret.getValue())); // as formatting rounds too, reparse value so that it is exactly at the // point the label string describes. ret.setValue(axis.getFormatter().parse(ret.getLabel()).doubleValue()); return ret; } }

Ads help maintain this website.