#323 Zooming ChartPanel w/Logarithmic Axes

closed-fixed
David Gilbert
General (896)
7
2007-03-02
2004-01-20
Corey Himes
No

Logarithmic axes are being used for both the domain and
range axes of an XYPlot in a ChartPanel. Zooming in in
any form causes the plot to zoom to an unidentifiable
location.

Discussion

  • Corey Himes
    Corey Himes
    2004-01-20

     
  • Corey Himes
    Corey Himes
    2004-01-20

    Logged In: YES
    user_id=951857

    I have traced the problem to LogarithmicAxis. zoomRange
    needs to be overridden in LogarithmicAxis. I have attached a
    corrected version of zoomRange.

     
  • David Gilbert
    David Gilbert
    2004-01-28

    • priority: 5 --> 7
    • assigned_to: nobody --> mungady
     
  • David Gilbert
    David Gilbert
    2004-01-28

    Logged In: YES
    user_id=112975

    Thanks for the report. I will look at your solution...

    Regards,

    Dave Gilbert
    JFreeChart Project Leader

     
  • deverhel
    deverhel
    2004-05-26

    Logged In: YES
    user_id=1050162

    I have try the patch ,
    it work well for positive values,
    but if the axis allow negative values the problems
    are still present.

    I suggest to insert a switchingPow10 method that invert the
    switchingLog10, and use it instead the Math.pow in the
    method zoomRange:
    (and also in the java2DToValue method):
    Below I wrote the methods proposed to be inserted/overwritten
    in the LogarithmicAxis
    ------------------------------------------------------------
    protected double switchedPow10(double val) {
    return (!this.allowNegativesFlag && val < 1.0 && val
    > 0.0) ?
    Math.pow(10.0,val) :
    adjustedPow10(val);
    }

    public double adjustedPow10(double val) {
    boolean negFlag = (val < 0.0);
    if (negFlag) {
    val = -val ; // if negative then set
    flag and make positive
    }
    double res=Math.pow(10,val);
    if (val < 1.0) {
    res=(Math.pow(10,val+1.0)-10.0)/9.0; //invert adjustLog10
    }
    return negFlag ? -res : res;
    }
    public double java2DToValue(double java2DValue, Rectangle2D
    plotArea, RectangleEdge edge) {

    Range range = getRange();
    double axisMin = switchedLog10(range.getLowerBound());
    double axisMax = switchedLog10(range.getUpperBound());

    double plotMin = 0.0;
    double plotMax = 0.0;
    if (RectangleEdge.isTopOrBottom(edge)) {
    plotMin = plotArea.getMinX();
    plotMax = plotArea.getMaxX();
    }
    else if (RectangleEdge.isLeftOrRight(edge)) {
    plotMin = plotArea.getMaxY();
    plotMax = plotArea.getMinY();
    }
    if (isInverted()) {
    return switchedPow10(axisMax - ((java2DValue -
    plotMin) /

    (plotMax - plotMin)) * (axisMax - axisMin));
    } else {
    return switchedPow10( axisMin + ((java2DValue -
    plotMin) /
    (plotMax -
    plotMin)) * (axisMax - axisMin));
    }
    }

    public void zoomRange(double lowerPercent, double
    upperPercent)
    {
    double startLog = switchedLog10(getRange().getLowerBound());
    double lengthLog = switchedLog10(getRange().getUpperBound())
    - startLog;
    Range adjusted = null;
    if(isInverted()){
    adjusted = new Range( switchedPow10( startLog +
    lengthLog * (1 -

    lowerPercent)),switchedPow10(startLog
    + lengthLog * (1 +
    upperPercent)));
    } else{
    adjusted = new Range(switchedPow10( startLog
    + lengthLog * lowerPercent),
    switchedPow10( startLog + lengthLog * upperPercent));
    }
    setRange(adjusted);
    }

     
  • Vincent
    Vincent
    2005-03-03

    Logged In: YES
    user_id=1231559

    Hi!

    The 2nd patch doesn't work if the axis is inverted.

    cheers
    Vincent

     
  • Logged In: NO

    Why is this bug still open? It was reported over a year ago
    and in my opinion it is a serious problem that you cannot do
    mouse zooming in an XYPlot with a logarithmic range axis. It
    seems quite easy to fix, so why not give it a little attention.
    Thanks.

     
  • Roger Shaw
    Roger Shaw
    2005-09-14

    Logged In: YES
    user_id=712400

    Problem still exists in RC1 - Please fix!

     
  • berth
    berth
    2006-03-15

    Logged In: YES
    user_id=1031412

    Here is a fix, unit tested for JFreechart 1.0.1. I have made
    a subclass of LogarithmicAxis that does zoom in correctly. I
    have used the patches suggested here, the main problem was
    that switchedLog10 was not really an inverse of
    switchedLog10, you have to use this.smallLogFlag for
    switching, just like switchedLog10 does.

    Unit test follows in next comment.

    Please note that you'll have to put this class into another
    package.

    ---------------

    package com.decodon.visualization.jrefinery.chart;

    import org.jfree.chart.axis.LogarithmicAxis;

    import org.jfree.data.Range;

    import org.jfree.ui.RectangleEdge;

    import java.awt.geom.Rectangle2D;

    /**
    * This is a patched version of
    org.jfree.chart.axis.LogarithmicAxis, according to
    *
    http://sourceforge.net/tracker/index.php?func=detail&aid=880597&group_id=15494&atid=115494
    * It fixes a problem with zooming when logarithmic axes are
    used. Tested with JFreechart 1.0.1.
    *
    * @author berth (at) decodon dot com
    */
    public class LogarithmicAxisZoomPatched extends
    LogarithmicAxis {
    /**
    * Creates a new instance of LogarithmicAxisZoomPatched
    * @param label the axis label.
    */
    public LogarithmicAxisZoomPatched(String label) {
    super(label);
    }

    public double adjustedPow10\(double val\) \{
        boolean negFlag = \(val &lt; 0.0\);
    
        if \(negFlag\) \{
            val = -val; // if negative then set flag and make positive
        \}
    
        double res = Math.pow\(10, val\);
    
        if \(val &lt; 1.0\) \{
            res = \(Math.pow\(10, val + 1.0\) - 10.0\) / 9.0; //invert
    

    adjustLog10
    }

        return negFlag ? \(-res\) : res;
    \}
    
    /\*\*
     \* Converts a coordinate in Java2D space to the
    

    corresponding data
    * value, assuming that the axis runs along one edge of the
    specified
    * plotArea.
    *
    * @param java2DValue the coordinate in Java2D space.
    * @param plotArea the area in which the data is plotted.
    * @param edge the axis location.
    *
    * @return The data value.
    */
    public double java2DToValue(double java2DValue, Rectangle2D
    plotArea,
    RectangleEdge edge) {
    Range range = getRange();
    double axisMin = switchedLog10(range.getLowerBound());
    double axisMax = switchedLog10(range.getUpperBound());

        double plotMin = 0.0;
        double plotMax = 0.0;
    
        if \(RectangleEdge.isTopOrBottom\(edge\)\) \{
            plotMin = plotArea.getMinX\(\);
            plotMax = plotArea.getMaxX\(\);
        \} else if \(RectangleEdge.isLeftOrRight\(edge\)\) \{
            plotMin = plotArea.getMaxY\(\);
            plotMax = plotArea.getMinY\(\);
        \}
    
        if \(isInverted\(\)\) \{
            return switchedPow10\(axisMax -
                \(\(\(java2DValue - plotMin\) / \(plotMax - plotMin\)\) \*
    

    (axisMax -
    axisMin)));
    } else {
    return switchedPow10(axisMin +
    (((java2DValue - plotMin) / (plotMax - plotMin)) *
    (axisMax -
    axisMin)));
    }
    }

    /\*\*
     \* Returns the log10 value, depending on if values between
    

    0 and
    * 1 are being plotted. If negative values are not allowed and
    * the lower bound is between 0 and 10 then a normal log is
    * returned; otherwise the returned value is adjusted if the
    * given value is less than 10.
    *
    * Note: I've made this a public method to be able to
    access it in a unit test
    *
    * @param val the value.
    * @return log<sub>10</sub>(val).
    */
    public double switchedLog10(double val) {
    double retValue;

        retValue = super.switchedLog10\(val\);
    
        return retValue;
    \}
    
    /\*\* Inverse function of switchedLog10 \*/
    public double switchedPow10\(double val\) \{
        return this.smallLogFlag ? Math.pow\(10.0, val\) :
    

    adjustedPow10(val);
    }

    /\*\*
     \* Zooms in on the current range.
     \*
     \* @param lowerPercent  the new lower bound.
     \* @param upperPercent  the new upper bound.
     \*/
    public void zoomRange\(double lowerPercent, double
    

    upperPercent) {
    double startLog = switchedLog10(getRange().getLowerBound());
    double lengthLog = switchedLog10(getRange().getUpperBound()) -
    startLog;
    Range adjusted = null;

        if \(isInverted\(\)\) \{
            adjusted = new Range\(switchedPow10\(startLog +
                        \(lengthLog \* \(1 - lowerPercent\)\)\),
                    switchedPow10\(startLog + \(lengthLog \* \(1 +
    

    upperPercent))));
    } else {
    adjusted = new Range(switchedPow10(startLog +
    (lengthLog * lowerPercent)),
    switchedPow10(startLog + (lengthLog * upperPercent)));
    }

        setRange\(adjusted\);
    \}
    

    }

     
  • berth
    berth
    2006-03-15

    Logged In: YES
    user_id=1031412

    Here is the unit test for the fix below.

    Please note that you'll have to put this class into another
    package.

    ---------------

    /*
    * LogarithmicAxisZoomPatchedTest.java
    * JUnit based test
    *
    * Created on 14. März 2006, 15:58
    */
    package com.decodon.visualization.jrefinery.chart;

    import junit.framework.*;

    import org.jfree.chart.axis.LogarithmicAxis;

    import org.jfree.data.Range;

    import org.jfree.ui.RectangleEdge;

    import java.awt.geom.Rectangle2D;

    /** Unit tests for LogarithmicAxisZoomPatched. This test
    covers the zoom in functionality.
    * TODO: tests with Margins > 0, tests with negative axis
    values (I guess that's possible in JFreeChart)
    * @author berth (at) decodon dot com
    */

    public class LogarithmicAxisZoomPatchedTest extends TestCase {
    /** Tolerance for floating point comparisons */
    public static double EPSILON = 0.000001;
    LogarithmicAxisZoomPatched axis = null;

    public LogarithmicAxisZoomPatchedTest\(String testName\) \{
        super\(testName\);
    \}
    
    protected void setUp\(\) throws Exception \{
        axis = new LogarithmicAxisZoomPatched\("Value \(log\)"\);
        axis.setAllowNegativesFlag\(false\);
        axis.setLog10TickLabelsFlag\(false\);
        axis.setLowerMargin\(0.0\);
        axis.setUpperMargin\(0.0\);
    
        axis.setLowerBound\(0.2\);
        axis.setUpperBound\(100.0\);
    \}
    
    public static Test suite\(\) \{
        TestSuite suite = new
    

    TestSuite(LogarithmicAxisZoomPatchedTest.class);

        return suite;
    \}
    
    /\*\* Test if adjustedLog10 and adjustedPow10 are inverses of
    

    each other */
    public void testAdjustedLog10() {
    checkLogPowRoundTrip(20);
    checkLogPowRoundTrip(10);
    checkLogPowRoundTrip(5);
    checkLogPowRoundTrip(2);
    checkLogPowRoundTrip(1);
    checkLogPowRoundTrip(0.5);
    checkLogPowRoundTrip(0.2);
    checkLogPowRoundTrip(0.0001);
    }

    private void checkLogPowRoundTrip\(double value\) \{
        assertEquals\("log\(pow\(x\)\) = x", value,
            axis.adjustedLog10\(axis.adjustedPow10\(value\)\), EPSILON\);
        assertEquals\("pow\(log\(x\)\) = x", value,
            axis.adjustedPow10\(axis.adjustedLog10\(value\)\), EPSILON\);
    \}
    
    /\*\* Test if switchedLog10 and switchedPow10 are inverses of
    

    each other */
    public void testSwitchedLog10() {
    assertFalse("Axis should not allow negative values",
    axis.getAllowNegativesFlag());

        assertEquals\(Math.log\(0.5\) / LogarithmicAxis.LOG10\_VALUE,
    

    // log10(0.5) = -0.30102999566398114
    axis.switchedLog10(0.5), EPSILON);

        checkSwitchedLogPowRoundTrip\(20\);
        checkSwitchedLogPowRoundTrip\(10\);
        checkSwitchedLogPowRoundTrip\(5\);
        checkSwitchedLogPowRoundTrip\(2\);
        checkSwitchedLogPowRoundTrip\(1\);
        checkSwitchedLogPowRoundTrip\(0.5\);
        checkSwitchedLogPowRoundTrip\(0.2\);
        checkSwitchedLogPowRoundTrip\(0.0001\);
    \}
    
    private void checkSwitchedLogPowRoundTrip\(double value\) \{
        assertEquals\("log\(pow\(x\)\) = x", value,
            axis.switchedLog10\(axis.switchedPow10\(value\)\), EPSILON\);
        assertEquals\("pow\(log\(x\)\) = x", value,
            axis.switchedPow10\(axis.switchedLog10\(value\)\), EPSILON\);
    \}
    
    /\*\*
     \* Test of java2DToValue method, of class
    

    com.decodon.visualization.jrefinery.chart.LogarithmicAxisZoomPatched.
    */
    public void testJava2DToValue() {
    Rectangle2D plotArea = new Rectangle2D.Double(22, 33, 500,
    500);
    RectangleEdge edge = RectangleEdge.BOTTOM;

        // set axis bounds to be both greater than 1
        axis.setRange\(10, 20\);
        checkPointsToValue\(edge, plotArea\);
    
        // check for bounds interval that includes 1
        axis.setRange\(0.5, 10\);
        checkPointsToValue\(edge, plotArea\);
    
        // check for bounds interval that includes 1
        axis.setRange\(0.2, 20\);
        checkPointsToValue\(edge, plotArea\);
    
        // check for both bounds smaller than 1
        axis.setRange\(0.2, 0.7\);
        checkPointsToValue\(edge, plotArea\);
    \}
    
    /\*\*
     \* Test of valueToJava2D method, of class
    

    com.decodon.visualization.jrefinery.chart.LogarithmicAxisZoomPatched.
    */
    public void testValueToJava2D() {
    Rectangle2D plotArea = new Rectangle2D.Double(22, 33, 500,
    500);
    RectangleEdge edge = RectangleEdge.BOTTOM;

        // set axis bounds to be both greater than 1
        axis.setRange\(10, 20\);
        checkPointsToJava2D\(edge, plotArea\);
    
        // check for bounds interval that includes 1
        axis.setRange\(0.5, 10\);
        checkPointsToJava2D\(edge, plotArea\);
    
        // check for bounds interval that includes 1
        axis.setRange\(0.2, 20\);
        checkPointsToJava2D\(edge, plotArea\);
    
        // check for both bounds smaller than 1
        axis.setRange\(0.2, 0.7\);
        checkPointsToJava2D\(edge, plotArea\);
    \}
    
    private void checkPointsToJava2D\(final RectangleEdge edge,
        final Rectangle2D plotArea\) \{
        assertEquals\("Left most point on the axis should be
    

    beginning of range.",
    plotArea.getX(),
    axis.valueToJava2D(axis.getLowerBound(), plotArea, edge),
    EPSILON);
    assertEquals("Right most point on the axis should be end
    of range.",
    plotArea.getX() + plotArea.getWidth(),
    axis.valueToJava2D(axis.getUpperBound(), plotArea, edge),
    EPSILON);
    assertEquals("Center point on the axis should geometric
    mean of the bounds.",
    plotArea.getX() + (plotArea.getWidth() / 2),
    axis.valueToJava2D(Math.sqrt(
    axis.getLowerBound() * axis.getUpperBound()), plotArea,
    edge),
    EPSILON);
    }

    /\*\* Check the translation java2D to value for left, right,
    

    and center point of the */
    private void checkPointsToValue(RectangleEdge edge,
    Rectangle2D plotArea) {
    assertEquals("Right most point on the axis should be end
    of range.",
    axis.getUpperBound(),
    axis.java2DToValue(plotArea.getX() + plotArea.getWidth(),
    plotArea,
    edge), EPSILON);

        assertEquals\("Left most point on the axis should be
    

    beginning of range.",
    axis.getLowerBound(),
    axis.java2DToValue(plotArea.getX(), plotArea, edge),
    EPSILON);

        assertEquals\("Center point on the axis should geometric
    

    mean of the bounds.",
    Math.sqrt(axis.getUpperBound() * axis.getLowerBound()),
    axis.java2DToValue(plotArea.getX() + (plotArea.getWidth()
    / 2),
    plotArea, edge), EPSILON);
    }

    public static void main\(String\[\] args\) \{
    

    junit.textui.TestRunner.run(LogarithmicAxisZoomPatchedTest.class);
    }
    }

     
  • Kurt
    Kurt
    2006-03-28

    Logged In: YES
    user_id=1448929

    Did over-riding zoomRange work for anyone else? It didn't
    work for me.
    Does LogarithmicAxisZoomPatched work? Has anyone tried
    it?
    Kurt

     
  • berth
    berth
    2006-03-31

    Logged In: YES
    user_id=1031412

    Over-riding zoomRange did not work for me, so I fixed it in
    LogarithmicAxisZoomPatched. LogarithmicAxisZoomPatched works
    for me, obviously :-) The problem is that jfreechart
    underwent some API (and implementation) changes in the
    pre-1.0 versions. So maybe the zoomRange fix worked in
    jfreechart 0.9.x.

     
  • Sergei Ivanov
    Sergei Ivanov
    2007-02-28

    Logged In: YES
    user_id=1606022
    Originator: NO

    I have just tested berth's modifications against 1.0.4 and they are working just fine. There was a problem with inverted axes, which is fixed in the new version of zoomRange(), submitted as patch #1671069.
    @David: could you please integrate the complete patch into the latest codebase?

    https://sourceforge.net/tracker/index.php?func=detail&aid=1671069&group_id=15494&atid=315494

     
  • David Gilbert
    David Gilbert
    2007-03-02

    • status: open --> closed-fixed
     
  • David Gilbert
    David Gilbert
    2007-03-02

    Logged In: YES
    user_id=112975
    Originator: NO

    Hi Sergei,

    Thanks for your work on this. I committed your patch from #1671069, and added the unit test. This will be included in the 1.0.5 release.

    Regards,

    Dave Gilbert
    JFreeChart Project Leader