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
Crosshairs and multiple range axes
Re: Crosshairs and multiple range axes
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
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
Re: Crosshairs and multiple range axes
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
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
Re: Crosshairs and multiple range axes
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
/**
* 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
-
- 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
Interesting, I will take a closer look at this.
David Gilbert
JFreeChart Project Leader
Read my blog
Support JFree via the Github sponsorship program
JFreeChart Project Leader
Read my blog
Support JFree via the Github sponsorship program