Crosshairs, tracelines, markers

A discussion forum for JFreeChart (a 2D chart library for the Java platform).
Locked
Sir Henry
Posts: 7
Joined: Mon Jun 26, 2006 3:29 pm

Crosshairs, tracelines, markers

Post by Sir Henry » Fri Jul 07, 2006 3:03 pm

Hi @ all,

I seem to be in some confusion about crosshairs, trace lines, and markers. :roll:

I'll try to describe what I want to achieve: I have a simple 2D x/y line plot. I want a crosshair cursor to be continuously updated during mouse movements, with the x crosshair following the mouse and the y crosshair moving to the respective y value. So this seems to be a variant of the trace lines where the y line is not following the mouse movements but depends on the data.

What I did is this: To use the crosshair lines I overwrote mouseMoved() in my derived ChartPanel class and call mouseClicked() in it. This does the trick of continuously updating the crosshair cursor but it unnecessarily redraws the entire chart. First area for improvement. :wink:

Secondly, I do not seem to get the desired effect with the call of setRangeCrosshairLockedOnData(true) - the range crosshair strangely remains locked on the highest value and never changes. This might be a misunderstanding on my side - perhaps I need to always set the range crosshair value myself if it is to lock on data? Any hint here?

Perhaps I am using the wrong feature and somebody can enlighten me about the purpose of crosshairs, trace lines, and markers, e.g. is there a way to use the trace lines for what I want to achieve?

Next step will be a feature by which the user clicks on the chart thus setting a marker to the current crosshair point, then by moving the crosshair away from this point, the distances between that marker and the current crosshair are continuously displayed in a corner of the plot.

Has anybody got some experience with applications like this and could possibly give me a helping hand? Any hint will be welcome. :)
Cheers, Sir Henry

gribas
Posts: 32
Joined: Thu Jan 26, 2006 5:34 pm

Post by gribas » Mon Jul 10, 2006 7:40 pm

Hi,
I seem to be in some confusion about crosshairs, trace lines, and markers.
Well, from my point of view trace lines only follow the mouse movement, they are used so the user can have a better notion of the current x/y values under the mouse pointer.
Markers are just static references.
Crosshairs follow the data, they can act like movable markers or cursors.
Secondly, I do not seem to get the desired effect with the call of setRangeCrosshairLockedOnData(true) - the range crosshair strangely remains locked on the highest value and never changes. This might be a misunderstanding on my side - perhaps I need to always set the range crosshair value myself if it is to lock on data? Any hint here?
I believe the crosshair tries to lock on the point closest to the mouse click, so if you're moving your mouse horizontally near to the top border it is expected that only the highest values are selected.

I think you're on the right way. What you want is a mix between the crosshair and the trace line. I'd rewrite the traceline code from ChartPanel to achieve the behaviour you want.
First, take a look at the source code to see how the ChartPanel is able to determinate the closest data point given a screen coordinate. Use that to rewrite the traceline code so that the horizontal traceline will always be over a data point.
Next step will be a feature by which the user clicks on the chart thus setting a marker to the current crosshair point, then by moving the crosshair away from this point, the distances between that marker and the current crosshair are continuously displayed in a corner of the plot.
You can add a ChartMouseListner to achieve that. You can capture the anchor point (mouse click) and the distance (mouse move). There are some methods in the Axis classes that let you translate screen coordinates do data coordinates.

Code: Select all

    /**
     * Returns the domain data value given a screen coordinate.
     * @param x the x-coordinate of the screen
     * @return the corresponding domain data value
     */
    private double getDomainDataValue(final int x) {
        // get the current domain value
        XYPlot plot = chart.getXYPlot(); 
        double xx = plot.getDomainAxis().java2DToValue(
                x, getScreenDataArea(), plot.getDomainAxisEdge()
            );
        return xx;
    }
    
    /**
     * Returns the X screen coordinate given a  domain data value.
     * @param xDataValue the domain data value
     * @return the corresponding x-coordinate of the screen
     */
    private int getXScreenCoordinate(double xDataValue) {
        // get the current domain value
        XYPlot plot = chart.getXYPlot(); 
        double xx = plot.getDomainAxis().valueToJava2D(
                xDataValue, getScreenDataArea(), plot.getDomainAxisEdge()
            );
        
        // apply scale if any and return (SCALE MESS UP EVERYTHING)
        //return translateJava2DToScreen(new Point2D.Double(xx,0)).x;
        return (int) Math.round(xx);
    }
Regards,
Gustavo

Sir Henry
Posts: 7
Joined: Mon Jun 26, 2006 3:29 pm

Post by Sir Henry » Tue Jul 11, 2006 7:57 am

Thanks a lot for your response, Gustavo, I appreciate it very much. While I was waiting for a reply I moved into the same direction as you described. Unfortunately, all trace line code is private, so I have to copy much of it into my derived classes. :cry:

There is another drawback of using trace lines: They disappear when the mouse is not moved and a plot update happens, because they are not redrawn during paintComponent(). My first idea was to remember the last mouseEvent in mouseMoved(), override paintComponent() and add a mouseMoved(lastMouseEvent) after calling super.paintComponent(). However, the effect is not satisfactory - sometimes there are two trace lines, sometimes none. I have not found out why this is, but it would be more convenient anyway to be able to call drawHorizontal/VerticalAxisTrace() directly from a derived class.

I will post any further development here. :)

NB. BTW, I also bought the developer guide, but it does not help with my problem. I appreciate very much the work that David is putting into this project, and I am happy to pay my share, but this guide is not much more than a printout of what is in the API anyway.
Cheers, Sir Henry

gribas
Posts: 32
Joined: Thu Jan 26, 2006 5:34 pm

Post by gribas » Tue Jul 11, 2006 12:30 pm

Unfortunately, all trace line code is private, so I have to copy much of it into my derived classes.
Yeah, that's awkward. You could recompile ChartPanel changing all private methods to protected or you could also copy the whole class and create a new one whithout any relationship with ChartPanel. That way you could also remove features you don't use.
There is another drawback of using trace lines: They disappear when the mouse is not moved and a plot update happens, because they are not redrawn during paintComponent(). My first idea was to remember the last mouseEvent in mouseMoved(), override paintComponent() and add a mouseMoved(lastMouseEvent) after calling super.paintComponent(). However, the effect is not satisfactory - sometimes there are two trace lines, sometimes none. I have not found out why this is, but it would be more convenient anyway to be able to call drawHorizontal/VerticalAxisTrace() directly from a derived class.
The tracelines are saved in two Line2D variables:

Code: Select all

/** Horizontal traceline. */
protected transient Line2D horizontalTraceline;
/** Vertical traceline. */
private transient Line2D verticalTraceline;
Use them to create a redrawTraceline method and call it from paintComponent:

Code: Select all



    /**
     * Paints the component by drawing the chart to fill the entire component,
     * but allowing for the insets (which will be non-zero if a border has been
     * set for this component).  To increase performance (at the expense of
     * memory), an off-screen buffer image can be used.
     *
     * @param g  the graphics device for drawing on.
     */
    public void paintComponent(Graphics g) {
        super.paintComponent(g);
        if (this.chart == null) {
            return;
        }
        Graphics2D g2 = (Graphics2D) g.create();

        //
        // many lines of code...
        //

        redrawHorizontalTraceline(g2);
        redrawVerticalTraceline(g2);
    }



    private void redrawVerticalTraceline(Graphics2D g2) {
        if (null == verticalTraceline) return;
 
       g2.setXORMode(java.awt.Color.orange);

        Rectangle2D dataArea = getScreenDataArea();      
        double x = verticalTraceline.getX1();

        if (((int) dataArea.getMinX() <= x) && (x < (int) dataArea.getMaxX())) { 
            g2.draw(verticalTraceline);
        }
    }



Regards,
Gustavo

Sir Henry
Posts: 7
Joined: Mon Jun 26, 2006 3:29 pm

Post by Sir Henry » Thu Jul 13, 2006 2:44 pm

Thanks again for your suggestions. :)

However, I do not like the idea of copying the entire ChartPanel and adapting it to my needs. I have decided to accept the drawback of disappearing trace lines for the time being. 8)
Cheers, Sir Henry

wzwei28
Posts: 105
Joined: Wed Mar 16, 2005 9:17 am
Location: Malaysia, Earth
Contact:

Post by wzwei28 » Thu Aug 03, 2006 4:59 am

I have did similar tracelines (crosshair) before... here is the system url

I use GMT + 8:00
http://chart.n2nconnect.com/FAChart/NNSP.jsp

Minimum requirements: JRE 1.4.2_04 and above

I will post the source code on JFreechart related library and overriden classes before 31/08/06. Hope can help everyone.

regards,
Zi Wei.

Locked