scale problem when getting coordinates of the cursor

A discussion forum for JFreeChart (a 2D chart library for the Java platform).
Locked
Cdr

scale problem when getting coordinates of the cursor

Post by Cdr » Wed Oct 19, 2005 5:13 pm

Hello,

I've seen lots of threads about getting the plot coordinates of the mouse cursor, and I could manage to do that with the last release using some code like

Code: Select all

Point2D translate(Point p){
		XYPlot plot = panel.getChart().getXYPlot();
		Point2D p2D = panel.translateScreenToJava2D(p);
		swap(p2D);
		p2D.setLocation(plot.getDomainAxis().java2DToValue(p2D.getX(), panel.getScreenDataArea(), plot.getDomainAxisEdge()),plot.getRangeAxis().java2DToValue(p2D.getY(), panel.getScreenDataArea(), plot.getRangeAxisEdge())) ;
		
		return p2D ;
	}
In this case Point p is caught from a MouseEvent ...

This seems to work fine, but when rescaling the ChartPanel or the Frame holding it, the results are not correct anymore.

I read in the previous posts that the Rectangle passed as parameter to java2DToValue had to be scaled but I thought the method getScreenDataArea alread returns a scaled area ... the API changed a bit since the last posts about that so I'm a bit confused.

Any clue ?

Thanks,
Cédric.

Guest

Post by Guest » Wed Oct 19, 2005 5:15 pm

Just forgot to say "swap" is used to change (x,y) into (y,x) in case the orientation of the plot is HORIZONTAL.

david.gilbert
JFreeChart Project Leader
Posts: 11734
Joined: Fri Mar 14, 2003 10:29 am
antibot: No, of course not.
Contact:

Post by david.gilbert » Wed Oct 19, 2005 5:51 pm

Try this demo which I will be adding to the collection that ships with the JFreeChart Developer Guide:

Code: Select all

/* -----------------------
 * MouseListenerDemo4.java
 * -----------------------
 * (C) Copyright 2005, by Object Refinery Limited.
 *
 */

package demo;

import java.awt.Point;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;

import org.jfree.chart.ChartFactory;
import org.jfree.chart.ChartMouseEvent;
import org.jfree.chart.ChartMouseListener;
import org.jfree.chart.ChartPanel;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.axis.ValueAxis;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.chart.plot.XYPlot;
import org.jfree.data.xy.XYDataset;
import org.jfree.data.xy.XYSeries;
import org.jfree.data.xy.XYSeriesCollection;
import org.jfree.ui.ApplicationFrame;
import org.jfree.ui.RectangleEdge;
import org.jfree.ui.RefineryUtilities;

/**
 * An example showing how to convert the mouse location to chart coordinates.
 */
public class MouseListenerDemo4 extends ApplicationFrame  
                                implements ChartMouseListener {

    private JFreeChart chart;
    
    private ChartPanel chartPanel;
    
    /**
     * A demonstration application showing how to pick up mouse clicks on the 
     * legend.
     *
     * @param title  the frame title.
     */
    public MouseListenerDemo4(String title) {
        super(title);
        String chartTitle = "Mouse Listener Demo 4";
        XYDataset dataset = createDataset();
        this.chart = ChartFactory.createXYLineChart(chartTitle, "X", "Y", 
                dataset, PlotOrientation.VERTICAL, true, true, false);
        chartPanel = new ChartPanel(this.chart);
        chartPanel.setPreferredSize(new java.awt.Dimension(500, 270));
        chartPanel.setMouseZoomable(true, false);
        chartPanel.addChartMouseListener(this);
        setContentPane(chartPanel);
    }

    /**
     * Creates a sample dataset.
     *
     * @return The dataset.
     */
    public XYDataset createDataset() {
        XYSeries series = new XYSeries("Series 1");
        series.add(12.5, 11.0);
        series.add(15.0, 9.3);
        series.add(20.0, 21.0);
        XYSeriesCollection dataset = new XYSeriesCollection();
        dataset.addSeries(series);
        return dataset;
    }

    /**
     * Receives chart mouse click events.
     *
     * @param event  the event.
     */
    public void chartMouseClicked(ChartMouseEvent event) {
        int mouseX = event.getTrigger().getX();
        int mouseY = event.getTrigger().getY();
        System.out.println("x = " + mouseX + ", y = " + mouseY);        
        Point2D p = chartPanel.translateScreenToJava2D(
                new Point(mouseX, mouseY));
        XYPlot plot = (XYPlot) chart.getPlot();
        Rectangle2D plotArea = chartPanel.getScreenDataArea();
        ValueAxis domainAxis = plot.getDomainAxis();
        RectangleEdge domainAxisEdge = plot.getDomainAxisEdge();
        ValueAxis rangeAxis = plot.getRangeAxis();
        RectangleEdge rangeAxisEdge = plot.getRangeAxisEdge();
        double chartX = domainAxis.java2DToValue(p.getX(), plotArea, 
                domainAxisEdge);
        double chartY = rangeAxis.java2DToValue(p.getY(), plotArea, 
                rangeAxisEdge);
        System.out.println("Chart: x = " + chartX + ", y = " + chartY);
    }

    /**
     * Receives chart mouse moved events.
     *
     * @param event  the event.
     */
    public void chartMouseMoved(ChartMouseEvent event) {
        // ignore
    }
    
    /**
     * Starting point for the demonstration application.
     *
     * @param args  ignored.
     */
    public static void main(String[] args) {
        MouseListenerDemo4 demo = new MouseListenerDemo4(
            "Mouse Listener Demo 4");
        demo.pack();
        RefineryUtilities.centerFrameOnScreen(demo);
        demo.setVisible(true);
    }

}
David Gilbert
JFreeChart Project Leader

:idea: Read my blog
:idea: Support JFree via the Github sponsorship program

Guest

Post by Guest » Wed Oct 19, 2005 7:11 pm

Thank you for the quick reply !

This, especially the chartMouseClicked method part, looks pretty much like the code I was using, besides the swap which would have no effect since you use PlotOrientation.VERTICAL orientation. I admit my code is less readable though :lol:

This indeed gives the right result as long as you don't resize the Frame, as far as I could notice. I can't figure out why it doesn't match the coordinates once the frame is resized. Or am I doing something wrong ?

I'm going to analyse that somewhat more ...

Cdr

Post by Cdr » Thu Oct 20, 2005 8:01 am

Also note that the resizing problem is not likely to appear in your demo since you use the "pack()" method ...

Try either to remove that command, or to resize your window manually, and you will notice that the click response doesn't match the axis coordinates anymore.

For example, in this Demo, clicking (16,10) gives

Code: Select all

Chart: x = 16.001935541951745, y = 9.88046805083229
which is very satisfying, but one I resize to, let's say, twice the size of the original frame, clicking the same point gives
Chart: x = 15.321226880184915, y = 9.97047236377891
If I make it full screen, I get

Code: Select all

Chart: x = 13.429990133302695, y = 16.2928429498237
This is wrong !! :cry:

Any workaround to that ? It would be a bit restrictive to force the chart to keep its "natural" size ^^;

Thanks,
Cédric.

Cdr
Posts: 9
Joined: Thu Oct 20, 2005 8:39 am
Location: Belgium
Contact:

Post by Cdr » Thu Oct 20, 2005 2:27 pm

I tried to rescale the point into the "packed" space before doing the treatment above, but it doesn't help, or I get it wrong .. it's not so straightforward with the insets and others ...
Cdr
Theory is when we know everything but nothing works.
Practice is when everything works but noone knows why.
Here we joined theory and practice : nothing works and noone knows why ...

skunk
Posts: 1087
Joined: Thu Jun 02, 2005 10:14 pm
Location: Brisbane, Australia

Post by skunk » Thu Oct 20, 2005 4:03 pm

Did you try attatching a ComponentListener to the container and firing a ChartChanged event when the size of the container changes? This will force the chart to get regenerated and the axes will reflect the new bounds.

Cdr
Posts: 9
Joined: Thu Oct 20, 2005 8:39 am
Location: Belgium
Contact:

Post by Cdr » Fri Oct 21, 2005 8:45 am

I'm not sure if this is exactly what you mean, but I tried this

Code: Select all

		chartPanel.addComponentListener(new ComponentListener(){
			public void componentHidden(ComponentEvent arg0) {}
			public void componentMoved(ComponentEvent arg0) {}
			public void componentResized(ComponentEvent arg0) {
				chartPanel.getChart().fireChartChanged();
			}
			public void componentShown(ComponentEvent arg0) {	
			}
		});
It doesn't change anything as far as I could notice. The problem is still the same. But they idea is interesting ... The ChartPanel shouldn't resize its content like a picture (here one can notice the chart and even title shapes are modified), but rather leave the title and texts the way they are and zoom in the chart ... e.g. the markers shouldn't change size, but the view on the chart should be modified.

I don't know if this solution would solve the first problem but it's interesting ...

Any clue on how to do that ?
Cdr
Theory is when we know everything but nothing works.
Practice is when everything works but noone knows why.
Here we joined theory and practice : nothing works and noone knows why ...

Cdr
Posts: 9
Joined: Thu Oct 20, 2005 8:39 am
Location: Belgium
Contact:

Post by Cdr » Fri Oct 21, 2005 8:52 am

I noticed something weird and I suppose it's related to what you said, skunk (don't take this as an insult, but you chose that name :roll: ) :

When resizing a frame holding a chart, let's say make it 3 times wider, in the ChartPanel the chart would look flat, the texts and shapes look quite terrible ... but if you save is as PNG then, the result is pretty, axis and shapes are correct !!

I suppose it would be a good way to solve the problem and to make the result of resizing look much prettier ...

Anyone knows how to ?
Cdr
Theory is when we know everything but nothing works.
Practice is when everything works but noone knows why.
Here we joined theory and practice : nothing works and noone knows why ...

david.gilbert
JFreeChart Project Leader
Posts: 11734
Joined: Fri Mar 14, 2003 10:29 am
antibot: No, of course not.
Contact:

Post by david.gilbert » Fri Oct 21, 2005 11:44 am

I know the problem now. My code is wrong in the case where the ChartPanel does some scaling. Substitute the following in my sample program:

Code: Select all

        //Rectangle2D plotArea = this.chartPanel.getScreenDataArea();
        Rectangle2D plotArea = this.chartPanel.getChartRenderingInfo().getPlotInfo().getDataArea();
The ChartPanel has four attributes: minimumDrawWidth, maximumDrawWidth, minimumDrawHeight and maximumDrawHeight. When the ChartPanel size is outside these dimensions (which you can change to suit your requirements), the chart is drawn at the limiting size and then scaled to fit the panel. For very small panels, this provides a nicer layout, and for very large panels this prevents the off-screen image buffer from consuming large amounts of memory. But it also means that the dimensions that the chart was drawn at do not match the dimensions of the chart as it is displayed on screen (because of the scaling). The getScreenDataArea() method applies the scaling, whereas the new code above returns the actual data area before scaling to fit the screen.
David Gilbert
JFreeChart Project Leader

:idea: Read my blog
:idea: Support JFree via the Github sponsorship program

Cdr
Posts: 9
Joined: Thu Oct 20, 2005 8:39 am
Location: Belgium
Contact:

Post by Cdr » Fri Oct 21, 2005 1:58 pm

David,

Thanks for that explanation, subsituting the code above in your demo or my application indeed gives the correct result :D :D

I also adapted the min/max values so that the chart would always be redrawn. It makes it a bit slower, but prettier.

Thanks for the help !!:)
Cédric.
Cdr
Theory is when we know everything but nothing works.
Practice is when everything works but noone knows why.
Here we joined theory and practice : nothing works and noone knows why ...

develop
Posts: 296
Joined: Wed Mar 23, 2005 10:01 pm

This seems does not work with CombindedDomainXYPlot

Post by develop » Thu Nov 03, 2005 4:46 pm

I use same method to get screen point on CombinedDomainXYPlot.

but then David's code does not work.

that code gives me perfect result if i change DataArea like this :

this.getScreenDataArea(e.getX(),e.getY())

and then pass this area to get y cordinate of rangeAxis.

but what if i just have java2d value and i need to convert that back to screen coordinate and i dont have this e.getX() and e.getY()

(i am trying on paintComponent method and using saved java2dValue)

Thanks
Shital

david.gilbert
JFreeChart Project Leader
Posts: 11734
Joined: Fri Mar 14, 2003 10:29 am
antibot: No, of course not.
Contact:

Post by david.gilbert » Thu Nov 03, 2005 6:02 pm

You'll need to find the data area for the correct subplot, which I guess is going to depend on the point you are looking at. Starting with the ChartRenderingInfo, get the PlotRenderingInfo for the combined plot by calling getPlotInfo(), then the PlotRenderingInfo for a particular subplot by calling getSubplotInfo(). From that you can determine the data area.
David Gilbert
JFreeChart Project Leader

:idea: Read my blog
:idea: Support JFree via the Github sponsorship program

develop
Posts: 296
Joined: Wed Mar 23, 2005 10:01 pm

Post by develop » Thu Nov 03, 2005 8:17 pm

but i just have real axis co ordinate (which has combindedDomainAxis)

how do i find correct subPlot of RangeAxis. (because i dont have component co-oridantes)

and i need to convert this into java2d Co ordinates.

How do i do that ?

skunk
Posts: 1087
Joined: Thu Jun 02, 2005 10:14 pm
Location: Brisbane, Australia

Post by skunk » Thu Nov 03, 2005 10:28 pm

You could sublass CombinedDomainXYPlot and add something like this

Code: Select all

public int getSubplotIndexAtPoint(Point pt) {
	for (int i = 0; i < subplotAreas.length; i++) {
		Rectangle2D r2d = subplotAreas[i];
		if (r2d.contains(pt))
			return i;
	}
	return -1;
}
but subplotAreas is private

Locked