Crosshairs and multiple range axes

A discussion forum for JFreeChart (a 2D chart library for the Java platform).
Locked
tobermory
Posts: 10
Joined: Thu Jul 06, 2017 8:11 pm
antibot: No, of course not.

Crosshairs and multiple range axes

Post by tobermory » Tue Feb 06, 2018 10:01 pm

I am trying to add 3 YCrosshairs to an XYPlot, following the CrosshairOverlayDemo1 and CrosshairOverlayDemo2 examples.

In both those examples, there is a single range axis. My plot has 3 datasets and 3 range axes. When I add the 3 yCrosshairs, following the code from Demo2, only 1 Y crosshair is ever shown, and that corresponds to the range axis that was added first. I was hoping to show all 3 y crosshairs.

I added debug to convince myself that the y values ARE being looked up, in the ChartMouseListener e.g.

for (int i = 0; i < yCrosshairs.length; i++) {
double y = DatasetUtilities.findYValue(plot.getDataset(i), 0, x);

// MY DEBUG, IS BEING CALLED FOR ALL 3 Y CROSSHAIRS
System.out.println( i + " " + y );

// EVEN THOUGH THIS CALLED, ONLY A VISUAL FOR FIRST Y CROSSHAIR
this.yCrosshairs.setValue(y);
}

This question seems to follow that of this earlier thread:

http://www.jfree.org/phpBB2/viewtopic.php?f=3&t=29617

which didn't get resolved as far as I can tell.

Can anyone confirm, either way, whether crosshairs will work when a plot has MANY range axes?

Thanks

Stuart

tobermory
Posts: 10
Joined: Thu Jul 06, 2017 8:11 pm
antibot: No, of course not.

Re: Crosshairs and multiple range axes

Post by tobermory » Thu Feb 08, 2018 1:27 am

My initial post omitted to mention that my app was built against JFreeChart 1.0.19. I noted in the release notes to 1.5.0 that bug #36 has been fixed, and that it related to crosshairs and multiple axes. I was thus excited to rebuild my code against the newer 1.5.0 lib.

Alas I am still getting the same results. It seems as though my crosshairs ARE being managed, since my DEBUG statment (previous post) IS being called for my THREE y crosshairs. But, I still actually SEE only the first y crosshair, the one corresponding to the first rangeAxis added to the xy plot.

I should probably add that my plot has HORIZONTAL orientation, unlike those in the crosshairs demos. Further, my domainAxis is INVERTED.

Stuart

tobermory
Posts: 10
Joined: Thu Jul 06, 2017 8:11 pm
antibot: No, of course not.

Re: Crosshairs and multiple range axes

Post by tobermory » Thu Feb 08, 2018 2:13 am

Well hurrah for open source software! Given that my 'crosshairs on multiple range axes' program wasn't working, I thought to take a look in CrosshairOverlay.java for 1.5.0 on github. Plain to see in there that only the FIRST rangeAxis was being used in computing rendering of yCrosshairs.

So I simply subclassed that class and overrode methods where appropriate. Hey presto, it now all works for me. My impl is thus:

class MyCrosshairOverlay extends CrosshairOverlay {

/*
The domain and range crosshair lists in superclass are private,
so we shadow them. See addDomainCrosshair and addRangeCrosshair
*/
private final List<Crosshair> shadowD, shadowR;

MyCrosshairOverlay() {
super();
shadowD = new ArrayList<>();
shadowR = new ArrayList<>();
}

@Override
public void addDomainCrosshair(Crosshair crosshair) {
super.addDomainCrosshair( crosshair );
shadowD.add( crosshair );
}

@Override
public void addRangeCrosshair(Crosshair crosshair) {
super.addRangeCrosshair( crosshair );
shadowR.add( crosshair );
}

/**
* Paints the crosshairs in the layer.
*
* @param g2 the graphics target.
* @param chartPanel the chart panel.
*/
@Override
public void paintOverlay(Graphics2D g2, ChartPanel chartPanel) {
Shape savedClip = g2.getClip();
Rectangle2D dataArea = chartPanel.getScreenDataArea();
g2.clip(dataArea);
JFreeChart chart = chartPanel.getChart();
XYPlot plot = (XYPlot) chart.getPlot();
ValueAxis dAxis = plot.getDomainAxis();
RectangleEdge dAxisEdge = plot.getDomainAxisEdge();

for( Crosshair c : shadowD ) {
if( !c.isVisible() )
continue;
double x = c.getValue();
double xx = dAxis.valueToJava2D(x, dataArea, dAxisEdge);
if (plot.getOrientation() == PlotOrientation.VERTICAL) {
drawVerticalCrosshair(g2, dataArea, xx, c );
}
else {
drawHorizontalCrosshair(g2, dataArea, xx, c );
}
}
int N = plot.getRangeAxisCount();
for( int i = 0; i < N; i++ ) {
ValueAxis rAxis = plot.getRangeAxis(i);
RectangleEdge rAxisEdge = plot.getRangeAxisEdge(i);
for( Crosshair c : shadowR ) {
if( !c.isVisible() )
continue;
double y = c.getValue();
double yy = rAxis.valueToJava2D(y, dataArea, rAxisEdge);
if (plot.getOrientation() == PlotOrientation.VERTICAL) {
drawHorizontalCrosshair(g2, dataArea, yy, c );
}
else {
drawVerticalCrosshair(g2, dataArea, yy, c );
}
}
}
g2.setClip(savedClip);
}
}

Excuse the formatting, this was cut/paste from emacs!

Stuart

tobermory
Posts: 10
Joined: Thu Jul 06, 2017 8:11 pm
antibot: No, of course not.

Re: Crosshairs and multiple range axes

Post by tobermory » Thu Feb 08, 2018 2:44 am

The code above had a nasty built-in assumption that range crosshair N was mapped to range axis N. This was OK in my app, but would fail in general. I thus added a new method mapCrosshairToRangeAxis, taking a lead from XYPlot.mapDatasetToRangeAxis(). The improved class is now


/**
* A CrosshairOverlay that supports many range axis crosshairs mapped
* to different range axes. Default CrosshairOverlay does not
* support this. Use mapCrosshairToRangeAxis to bind
* range crosshairs to range axes
*/
class MyCrosshairOverlay extends CrosshairOverlay {

/*
The domain and range crosshair lists in superclass are private,
so we shadow them. See addDomainCrosshair and addRangeCrosshair
*/
private final List<Crosshair> shadowD, shadowR;
private final int[] rangeAxes;

MyCrosshairOverlay() {
super();
shadowD = new ArrayList<>();
shadowR = new ArrayList<>();
/*
These rangeAxes all being zero initially will mean
that the default range axis used for ALL yCrosshairs
will be range axis 0. Use mapCrosshairToRangeAxis to
alter. 16 yCrosshairs should suffice.
*/
rangeAxes = new int[16];
}

@Override
public void addDomainCrosshair(Crosshair crosshair) {
super.addDomainCrosshair( crosshair );
shadowD.add( crosshair );
}

@Override
public void addRangeCrosshair(Crosshair crosshair) {
super.addRangeCrosshair( crosshair );
shadowR.add( crosshair );

}

/**
* In the spirit of mapDatasetToRangeAxis, binding
* a range crosshair to a particular range axis.
*/
public void mapCrosshairToRangeAxis( int crosshairIndex,
int rangeAxisIndex ) {
rangeAxes[crosshairIndex] = rangeAxisIndex;
}

/**
* Paints the crosshairs in the layer.
*
* @param g2 the graphics target.
* @param chartPanel the chart panel.
*/
@Override
public void paintOverlay(Graphics2D g2, ChartPanel chartPanel) {
Shape savedClip = g2.getClip();
Rectangle2D dataArea = chartPanel.getScreenDataArea();
g2.clip(dataArea);
JFreeChart chart = chartPanel.getChart();
XYPlot plot = (XYPlot) chart.getPlot();
ValueAxis dAxis = plot.getDomainAxis();
RectangleEdge dAxisEdge = plot.getDomainAxisEdge();

// Expected ONE domain crosshair, mapped to default domain axis
for( Crosshair c : shadowD ) {
if( !c.isVisible() )
continue;
double x = c.getValue();
double xx = dAxis.valueToJava2D(x, dataArea, dAxisEdge);
if (plot.getOrientation() == PlotOrientation.VERTICAL) {
drawVerticalCrosshair(g2, dataArea, xx, c );
}
else {
drawHorizontalCrosshair(g2, dataArea, xx, c );
}
}
for( int i = 0; i < shadowR.size(); i++ ) {
Crosshair c = shadowR.get(i);
if( !c.isVisible() )
continue;
int rangeAxisIndex = rangeAxes;
if( rangeAxisIndex >= plot.getRangeAxisCount() )
continue;
ValueAxis rAxis = plot.getRangeAxis( rangeAxisIndex );
RectangleEdge rAxisEdge = plot.getRangeAxisEdge
( rangeAxisIndex);
double y = c.getValue();
double yy = rAxis.valueToJava2D(y, dataArea, rAxisEdge);
if (plot.getOrientation() == PlotOrientation.VERTICAL) {
drawHorizontalCrosshair(g2, dataArea, yy, c );
}
else {
drawVerticalCrosshair(g2, dataArea, yy, c );
}
}
g2.setClip(savedClip);
}
}

Stu

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

Re: Crosshairs and multiple range axes

Post by david.gilbert » Fri Feb 09, 2018 7:34 am

Interesting, I will take a closer look at this.
David Gilbert
JFreeChart Project Leader

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

Locked