Annotations on logarithmic scaled plot

A discussion forum for JFreeChart (a 2D chart library for the Java platform).
Locked
pl477150
Posts: 12
Joined: Tue Oct 04, 2016 6:20 pm
antibot: No, of course not.

Annotations on logarithmic scaled plot

Post by pl477150 » Fri Feb 03, 2017 4:27 pm

Hello all,

I am trying to add some annotations to a plot I have that is using a logarithmic scale for the X and Y axis.

The problem is the X and Y coordinates I am trying to use for the annotations seem to also be on a logarithmic scale. If I add annotations 1-3 with coordinates (0,1), (0,2), and 0,3) it ends up looking like this:

Code: Select all

annotation3


annotation2

annotation1
instead of what I need, which would be more like:

Code: Select all

annotation3
annotation2
annotation1
Is there a way to have the annotation use normal XY coordinates instead of the logarithmic scale the plot is using? Base on the searches I have already tried I am guessing this will not be possible. But I am hoping for a miracle here.

paradoxoff
Posts: 1634
Joined: Sat Feb 17, 2007 1:51 pm

Re: Annotations on logarithmic scaled plot

Post by paradoxoff » Sun Feb 05, 2017 7:21 pm

You will most likely have to write some new annotation classes that give to the option for relative positioning of the annotation.
I once started to write a class "AreaTitleAnnotation" that allows to place a Title onto an XYPLot and a CategoryPlot. This class does not use absolute x, y, data coordinates, but uses either an instance of RectangleAnchor or absolute or relative svreen coordinates, relative to the top left corner of the data area.
If you want to use this class, you will also have to convert your annotations to some kind of Title. XYTextAnnotations could for example be replaced by TextTitles.
Please let me know whether you want to go in that direction.

pl477150
Posts: 12
Joined: Tue Oct 04, 2016 6:20 pm
antibot: No, of course not.

Re: Annotations on logarithmic scaled plot

Post by pl477150 » Mon Feb 06, 2017 4:16 pm

I would ultimately like to add some text to the plot, hopefully in a small box, and place it in one of the corners of the plot. I am also fairly new to coding, so the Rectangle stuff I tried before with Baseline and top, bottom, left, right, was somewhat confusing to me.

I did find some examples using TextTitle but it seems like these can only be places at the Top, bottom, right, or left of the chart area and not into the plot itself.

I would like to use your idea but I am will probably need everything explained, which will be too much work for you I think.

pl477150
Posts: 12
Joined: Tue Oct 04, 2016 6:20 pm
antibot: No, of course not.

Re: Annotations on logarithmic scaled plot

Post by pl477150 » Mon Feb 06, 2017 5:21 pm

I found something that looks like what I am trying to do. But when I use this code I do not get the same results. :(

http://stackoverflow.com/questions/1132 ... jfreechart

Code: Select all

XYPlot plot2 = (XYPlot) chart.getPlot();
LegendTitle lt = new LegendTitle(plot2);
lt.setItemFont(new Font("Dialog", Font.PLAIN, 9));
lt.setBackgroundPaint(new Color(200, 200, 255, 100));
lt.setFrame(new BlockBorder(Color.BLACK));
lt.setPosition(RectangleEdge.BOTTOM);
XYTitleAnnotation ta = new XYTitleAnnotation(0.98, 0.02, lt, RectangleAnchor.BOTTOM_RIGHT);

ta.setMaxWidth(0.48);
plot2.addAnnotation(ta);
On my plot it always places the new legend to the right of the lines, almost like a layout manager would if you added two items to a container.

paradoxoff
Posts: 1634
Joined: Sat Feb 17, 2007 1:51 pm

Re: Annotations on logarithmic scaled plot

Post by paradoxoff » Mon Feb 06, 2017 5:58 pm

Here is the Annotation class that I mentioned. It should not be considered "complete", at least not with regard to Serialization and implementation of equals.

Code: Select all


package org.jfree.chart.annotations;

import java.awt.Graphics2D;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.io.Serializable;
import org.jfree.chart.HashUtilities;
import org.jfree.chart.axis.CategoryAxis;
import org.jfree.chart.axis.ValueAxis;
import org.jfree.chart.plot.CategoryPlot;
import org.jfree.chart.plot.PlotRenderingInfo;
import org.jfree.chart.plot.XYPlot;
import org.jfree.chart.title.Title;
import org.jfree.ui.RectangleAnchor;
import org.jfree.chart.block.RectangleConstraint;
import org.jfree.data.Range;
import org.jfree.ui.RectangleInsets;
import org.jfree.ui.Size2D;
import org.jfree.util.ObjectUtilities;
import org.jfree.util.UnitType;
import org.jfree.util.PublicCloneable;


public class AreaTitleAnnotation extends AbstractXYAnnotation implements
        CategoryAnnotation, XYAnnotation, Cloneable, PublicCloneable,
        Serializable {

    /**
     * For serialization.
     */
    private static final long serialVersionUID = 1L;

    /**
     * The insets of the annotation.
     */
    private RectangleInsets insets;

    /**
     * The anchor of the annotation.
     */
    private RectangleAnchor anchor;

    /**
     * The position of the annotation.
     */
    private RectangleAnchor position;

    /**
     * The title.
     */
    private Title title;

    /**
     * The maximum width of the annotation, either relative or absolute.
     */
    private double maxWidth;

    /**
     * The maximum height of the annotation, either relative or absolute.
     */
    private double maxHeight;

    /**
     * The type of the maxWidth, either relative or absolute.
     */
    private UnitType maxWidthUnitType;

    /**
     * The type of the maxheight, either relative or absolute.
     */
    private UnitType maxHeightUnitType;
    
    /**
     * A flag indicating whether the supplied coordintaes should be used to 
     * calculate the anchor of the annotation.
     */
    private boolean useXYCoordinates;
    
    /**
     * The x coordinate of the annotation.
     */
    private double xCoordinate;
    
    /**
     * The y coordinate of the annotation.
     */
    private double yCoordinate;
    

    /**
     * Creates a new annotation.
     *
     * @param title  the title (<code>null</code>  permitted).
     */
    public AreaTitleAnnotation(Title title) {
        super();
        this.title = title;
        insets = RectangleInsets.ZERO_INSETS;
        anchor = RectangleAnchor.TOP_LEFT;
        position = RectangleAnchor.TOP_LEFT;
        maxWidth = 1.0;
        maxHeight = 1.0;
        maxWidthUnitType = UnitType.RELATIVE;
        maxHeightUnitType = UnitType.RELATIVE;
        xCoordinate = 0.0;
        yCoordinate = 0.0;
        useXYCoordinates = false;
        
    }

    /**
     * Returns the insets for the annotation.
     *
     * @return The insets.
     */
    public RectangleInsets getAnnotationInsets() {
        return this.insets;
    }

    /**
     * Sets the insets and notifies registered listeners.
     *
     * @param insets  the insets around the annotation.
     */
    public void setAnnotationInsets(RectangleInsets insets) {
        this.insets = insets;
        fireAnnotationChanged();
    }

    /**
     * Returns the anchor for the annotationn, i.e. the point with which it is
     * attached to its position..
     *
     * @return The anchor.
     */
    public RectangleAnchor getAnnotationAchor() {
        return this.anchor;
    }

    /**
     * Sets the anchor of the annotation, i.e. the point with which it is
     * attached to its position, and notifies registered listeners.
     * 
     * @param anchor  The anchor.
     */
    public void setAnnotationAnchor(RectangleAnchor anchor) {
        this.anchor = anchor;
        fireAnnotationChanged();
    }

    /**
     * Returns the position for the annotationn, i.e. the place where it will
     * show up inside the data area.
     *
     * @return The position.
     */
    public RectangleAnchor getAnnotationPosition() {
        return this.position;
    }

    /**
     * Sets the position for the annotationn, i.e. the place where it will
     * show up inside the data area, and notifies registered listeners.
     *
     * @param anchor The anchor.
     */
    public void setAnnotationPosition(RectangleAnchor anchor) {
        this.position = anchor;
        fireAnnotationChanged();
    }

    /**
     * Returns the maximum width for the annotation, either absolute in pixels
     * or relative to the data area depending on the maxWidthUnitType.
     * 
     * @return The width.
     */
    public double getMaxWidth() {
        return this.maxWidth;
    }

    /**
     * Sets the maximum width for the annotation, either absolute in pixels
     * or relative to the data area depending on the maxWidthUnitType, and
     * notifies registered listeners.
     * 
     * @param w  The width.
     */
    public void setMaxWidth(double w) {
        this.maxWidth = w;
        fireAnnotationChanged();
    }

    /**
     * Returns the maximum height for the annotation, either absolute in pixels
     * or relative to the data area depending on the maxHeightUnitType.
     * 
     * @return The height.
     */
    public double getMaxHeight() {
        return this.maxHeight;
    }

    /**
     * Sets the maximum height for the annotation, either absolute in pixels
     * or relative to the data area depending on the maxHeightUnitType, and
     * notifies registered listeners.
     * 
     * @param h  The width.
     */
    public void setMaxHeight(double h) {
        this.maxHeight = h;
        fireAnnotationChanged();
    }

    /**
     * Returns a strategy how the maxWidth value will be interpreted: either 
     * absolute in pixels (UnitType.ABSOLUTE) or relative to the data area 
     * (UnitType.RELATIVE)
     * 
     * @return The type.
     */
    public UnitType getMaxWidthUnitType() {
        return this.maxWidthUnitType;
    }

    /**
     * Sets a strategy how the maxWidth value will be interpreted: either 
     * absolute in pixels (UnitType.ABSOLUTE) or relative to the data area 
     * (UnitType.RELATIVE), and notifies registered listeners.
     * 
     * @param ut The type.
     */
    public void setMaxWidthUnitType(UnitType ut) {
        this.maxWidthUnitType = ut;
        fireAnnotationChanged();
    }

    /**
     * Returns a strategy how the maxHeightvalue will be interpreted: either 
     * absolute in pixels (UnitType.ABSOLUTE) or relative to the data area 
     * (UnitType.RELATIVE)
     * 
     * @return The type.
     */
    public UnitType getMaxHeightUnitType() {
        return this.maxHeightUnitType;
    }

    /**
     * Sets a strategy how the maxWidth value will be interpreted: either 
     * absolute in pixels (UnitType.ABSOLUTE) or relative to the data area 
     * (UnitType.RELATIVE), and notifies registered listeners.
     * 
     * @param ut The type.
     */
    public void setMaxHeightUnitType(UnitType ut) {
        this.maxHeightUnitType = ut;
        fireAnnotationChanged();
    }

    /**
     * Returns the x coordinate for the anchor of the annotation, starting from
     * the top left of the data area after application of the rectangle 
     * constraint.
     * 
     * @return The x coordinate.
     */
    public double getXCoordinate() {
        return this.xCoordinate;
    }

    /**
     * Sets the x coordinate for the anchor of the annotation, starting from the
     * top left of the data area after application of the rectangle constraint.
     * This value will only be used if the flag useXYCoordinates is set to true.
     * Values > 1.0 will be interpreted as absolute values, values <= 1.0 will
     * be interpreted as values relative to the width of the data area.
     * Registered listeners will be notifed about the change.
     * 
     * @param x  The x  coordinate.
     */
    public void setXCoordinate(double x) {
        this.xCoordinate = x;
        fireAnnotationChanged();
    }

    /**
     * Returns the y coordinate for the anchor of the annotation, starting from
     * the top left of the data area after application of the rectangle 
     * constraint.
     * 
     * @return The x coordinate.
     */
    public double getYCoordinate() {
        return this.yCoordinate;
    }

    /**
     * Sets the y coordinate for the anchor of the annotation, starting from the
     * top left of the data area after application of the rectangle constraint.
     * This value will only be used if the flag useXYCoordinates is set to true.
     * Values > 1.0 will be interpreted as absolute values, values <= 1.0 will
     * be interpreted as values relative to the height of the data area.
     * Registered listeners will be notifed about the change.
     * 
     * @param y  The y coordinate.
     */
    public void setYCoordinate(double y) {
        this.yCoordinate = y;
        fireAnnotationChanged();
    }

    /**
     * Returns a flag that indicates wether the x and y coordinate will be used
     * to calculate the top left point of the annotation.
     * 
     * @return The flag.
     */
    public boolean getUseXYCoordinates() {
        return this.useXYCoordinates;
    }

    /**
     * Sets a flag that indicates wether the x and y coordinate will be used
     * to calculate the top left point of the annotation, and notifies
     * registered listeners about the change.
     * 
     * @param flag  The flag.
     */
    public void setUseXYCoordinates(boolean flag) {
        this.useXYCoordinates = flag;
        fireAnnotationChanged();
    }

    /**
     * Returns the title that will be rendered at the position and anchor of the
     * annotation.
     * 
     * @return The title.
     */
    public Title getTitle() {
        return this.title;
    }

    /**
     * Sets the title that will be rendered at the position and anchor of the
     * annotation, and notifies registered listeners.
     * 
     * @param t  THe title.
     */
    public void setTitle(Title t) {
        this.title = t;
        fireAnnotationChanged();
    }

    /**
     * Draws the annotation. This method is called by the drawing code in the
     * {@link XYPlot} class.
     * 
     * @param g2  the graphics device.
     * @param plot  the plot.
     * @param dataArea  the data area.
     * @param domainAxis  the domain axis.
     * @param rangeAxis  the range axis.
     * @param rendererIndex  the renderer index.
     * @param info  if supplied, this info object will be populated with
     *              entity information.
     */
    public void draw(Graphics2D g2, XYPlot plot, Rectangle2D dataArea,
            ValueAxis domainAxis, ValueAxis rangeAxis,
            int rendererIndex,
            PlotRenderingInfo info) {
        draw(g2, dataArea, info, rendererIndex);

    }

    /**
     * Draws the annotation. This method is called by the drawing code in the
     * {@link CategoryPlot} class.
     *
     * @param g2  the graphics device.
     * @param plot  the plot.
     * @param dataArea  the data area.
     * @param domainAxis  the domain axis.
     * @param rangeAxis  the range axis.
     */
    public void draw(Graphics2D g2, CategoryPlot plot, Rectangle2D dataArea,
            CategoryAxis domainAxis, ValueAxis rangeAxis) {
        draw(g2, dataArea, null, 0);
    }

    /**
     * Draws the annotation.
     *
     * @param g2  the graphics device.
     * @param dataArea  the data area.
     * @param info  if supplied, this info object will be populated with
     *              entity information.
     * @param rendererIndex  the renderer index.
     */

    public void draw(Graphics2D g2, Rectangle2D dataArea, PlotRenderingInfo info, int rendererIndex) {
        if(title == null) return;
        Rectangle2D constrained = insets.createInsetRectangle(dataArea,
                true, true);
        double maxW = constrained.getWidth();
        double maxH = constrained.getHeight();
        if (maxWidthUnitType == UnitType.ABSOLUTE) {
            maxW = Math.min(maxW, maxWidth);
        } else if (maxWidthUnitType == UnitType.RELATIVE) {
            maxW = Math.min(maxW, maxW * maxWidth);
        }
        if (maxHeightUnitType == UnitType.ABSOLUTE) {
            maxH = Math.min(maxH, maxHeight);
        } else if (maxHeightUnitType == UnitType.RELATIVE) {
            maxH = Math.min(maxH, maxH * maxHeight);
        }
        RectangleConstraint rc = new RectangleConstraint(
                new Range(0, maxW), new Range(0, maxH));
        Size2D size = this.title.arrange(g2, rc);
        Point2D origin = null;
        if(useXYCoordinates){
            double xOffset = (xCoordinate <= 1.0) ? xCoordinate * dataArea.getWidth() : xCoordinate;
            double yOffset = (yCoordinate <= 1.0) ? yCoordinate * dataArea.getHeight() : yCoordinate;
            origin = new Point2D.Double(constrained.getX() + xOffset, constrained.getY() + yOffset);
        }
        else{
            origin = RectangleAnchor.coordinates(constrained, position);
        }
        double xOffset = 0;
        double yOffset = 0;
        //nothing to do for RectangleAnchor.TOP_LEFT
        if (this.anchor.equals(RectangleAnchor.TOP)) {
            xOffset = -size.width / 2.0;
        } else if (this.anchor.equals(RectangleAnchor.TOP_RIGHT)) {
            xOffset = -size.width;
        } else if (this.anchor.equals(RectangleAnchor.LEFT)) {
            yOffset = -size.height / 2.0;
        } else if (this.anchor.equals(RectangleAnchor.CENTER)) {
            xOffset = -size.width / 2.0;
            yOffset = -size.height / 2.0;
        } else if (this.anchor.equals(RectangleAnchor.RIGHT)) {
            xOffset = -size.width;
            yOffset = -size.height / 2.0;
        } else if (this.anchor.equals(RectangleAnchor.BOTTOM_LEFT)) {
            yOffset = -size.height;
        } else if (this.anchor.equals(RectangleAnchor.BOTTOM)) {
            xOffset = -size.width / 2.0;
            yOffset = -size.height;
        } else if (this.anchor.equals(RectangleAnchor.BOTTOM_RIGHT)) {
            xOffset = -size.width;
            yOffset = -size.height;
        }
        Rectangle2D titleRect = new Rectangle2D.Double(origin.getX() + xOffset,
                origin.getY() + yOffset, size.getWidth(), size.getHeight());
        this.title.draw(g2, titleRect);
        super.addEntity(info, titleRect, rendererIndex, getToolTipText(),
                getURL());
    }

    /**
     * Tests this object for equality with an arbitrary object.
     *
     * @param obj the object (<code>null</code> permitted).
     *
     * @return A boolean.
     */
    public boolean equals(Object obj) {
        if (obj == this) {
            return true;
        }
        if (!(obj instanceof AreaTitleAnnotation)) {
            return false;
        }
        AreaTitleAnnotation that = (AreaTitleAnnotation) obj;
        if (this.maxWidth != that.maxWidth) {
            return false;
        }
        if (this.maxHeight != that.maxHeight) {
            return false;
        }
        if (!this.maxWidthUnitType.equals(that.maxWidthUnitType)) {
            return false;
        }
        if (!this.maxHeightUnitType.equals(that.maxHeightUnitType)) {
            return false;
        }
        if (!this.anchor.equals(that.anchor)) {
            return false;
        }
        if (!this.position.equals(that.position)) {
            return false;
        }
        if (!this.insets.equals(that.insets)) {
            return false;
        }
        if (!ObjectUtilities.equal(this.title, that.title)) {
            return false;
        }
        return super.equals(obj);
    }

    /**
     * Returns a hash code for this object.
     *
     * @return A hash code.
     */
    public int hashCode() {
        int result = 197;
        result = HashUtilities.hashCode(result, this.anchor);
        result = HashUtilities.hashCode(result, this.position);
        result = HashUtilities.hashCode(result, this.maxWidth);
        result = HashUtilities.hashCode(result, this.maxHeight);
        result = HashUtilities.hashCode(result, this.title);
        result = HashUtilities.hashCode(result, this.insets);
        return result;
    }

    /**
     * Returns a clone of the annotation.
     *
     * @return A clone.
     *
     * @throws CloneNotSupportedException if the annotation can't be cloned.
     */
    public Object clone() throws CloneNotSupportedException {
        return super.clone();
    }

}

and here is some sample code to show its use:

Code: Select all

public class BarChart1 {

    public static void main(String[] args) {
        DefaultCategoryDataset dcd = new DefaultCategoryDataset();
        dcd.addValue(5, "Row 1", "Col 1");
        dcd.addValue(10, "Row 1", "Col 2");
        dcd.addValue(2, "Row 2", "Col 1");
        dcd.addValue(7, "Row 2", "Col 2");
        BarRenderer br = new BarRenderer();
        CategoryPlot plot = new CategoryPlot(dcd, new CategoryAxis("Category"), new NumberAxis("Values"), br);
        TextTitle tt = new TextTitle("This is\na multiline text\nto demonstrate the wrapping");
        tt.setTextAlignment(HorizontalAlignment.LEFT);
        AreaTitleAnnotation ata = new AreaTitleAnnotation(tt);
        ata.setAnnotationInsets(new RectangleInsets(10, 10, 10, 10));
        ata.setAnnotationAnchor(RectangleAnchor.TOP_LEFT);
        plot.addAnnotation(ata);
        plot.setOrientation(PlotOrientation.HORIZONTAL);
        JFreeChart chart = new JFreeChart(plot);
        ChartPanel cp = new ChartPanel(chart);
        JFrame f = new JFrame("Bar Chart");

        f.getContentPane().add(cp);
        f.pack();
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        f.setVisible(true);
    }
}
If you need special explanations, please let me know.

pl477150
Posts: 12
Joined: Tue Oct 04, 2016 6:20 pm
antibot: No, of course not.

Re: Annotations on logarithmic scaled plot

Post by pl477150 » Wed Feb 08, 2017 1:30 pm

Could you explain how to control where the annotation is placed?

Everything I have tried seems to move it up off of the plot.

paradoxoff
Posts: 1634
Joined: Sat Feb 17, 2007 1:51 pm

Re: Annotations on logarithmic scaled plot

Post by paradoxoff » Wed Feb 08, 2017 1:57 pm

There are several ways to calculate the point of the data area where the annotation will be attached :
- if useXYCoordinates is true, then there are two possibilities:
- if the value for one coordinate is less than or equal to 1.0, it will be interpreted as fraction of the total width or height of the data area
- if the value for one coordinate is greater than 1.0, it will be interpreted as an absolute pixel value
In both cases, the origin is the upper left edge of the chart.
- if useXYCoordinates is false, then the "attachement point" is determined from the "position" parameter. This is an instance of RectangleAnchor. There are nine different possibilities for RectangleAnchor: BOTTOM_LEFT, BOTTOM_RIGHT, TOP_LEFT and TOP_RIGHT are the edges of a rectangle, LEFT, RIGHT, TOP, and BOTTOM are points on the borders between the edges, and CENTER is... the center.

The "anchor" parameter indicates the point with which the annotation is attached to the data area. This is also an instance of RectangleAnchor Think of the data area as a pinboard and the annotation as a slip of paper. If you fix the paper on the pinboard with a needle, the "position" indicates the place on the pinboard and the "anchor" the position on the paper where the needle is going through.

The default parameters should work well. If the anchor and the position are the RectangleAnchor, things should be ok as well. A combination of "opposite" RectangleAnchors (BOTTOM_LEFT for the anchor, TOP_RIGHT for the position, or RIGHT for the anchor and LEFT for the position) will place the annotation completely outside the data area.

If you are still having trouble, please post a runnable piece of code.

pl477150
Posts: 12
Joined: Tue Oct 04, 2016 6:20 pm
antibot: No, of course not.

Re: Annotations on logarithmic scaled plot

Post by pl477150 » Wed Feb 08, 2017 2:49 pm

Well I am afraid I am not nearly as advanced in java as you are. I am not able to post any running code, I can't create any that seems to work.

How would you change your example above to place the annotation in the lower left corner?
The "anchor" parameter indicates the point with which the annotation is attached to the data area. This is also an instance of RectangleAnchor Think of the data area as a pinboard and the annotation as a slip of paper. If you fix the paper on the pinboard with a needle, the "position" indicates the place on the pinboard and the "anchor" the position on the paper where the needle is going through.
I understand your meaning here, but in your code what would be the "anchor" and what would be the "position"?

When I try changing

Code: Select all

ata.setAnnotationAnchor(RectangleAnchor.TOP_LEFT);
to

Code: Select all

ata.setAnnotationAnchor(RectangleAnchor.BOTTOM_LEFT);
this will place the annotation above the top of the data area. So, based on what you have said, this must be causing the RectangleAnchors to have opposite positions? This seems to be what I am confused about. Is this change affecting the "anchor" or the "position"? And then, how would I change the other?

And thank you for your help and patience.

paradoxoff
Posts: 1634
Joined: Sat Feb 17, 2007 1:51 pm

Re: Annotations on logarithmic scaled plot

Post by paradoxoff » Wed Feb 08, 2017 3:05 pm

ata.setAnnotationAnchor(RectangleAnchor ra) defines the anchor variable (the attachment point on the paper), and atat.setAnnotationPosition(RectangleAnchor ra) defines the position variable (the attachment point on the pinboard).
The default for both parameters is RectangleAnchor.TOP_LEFT. Try to change both to RectangleAnchor.BOTTOM_LEFT and see whether this is looking as needed.

Locked