Whole chart clicking: identifying parts of chart
Whole chart clicking: identifying parts of chart
Hi
I would like to build an application that will allow the user to build a chart in a WYSIWYG way.
For what I intend to do, I would need to be able to identify what part of the graph the user clicked upon.
i.e.: if he clicks on the chart on the range axis, he'll get an editor that will allow him/her to configure the range axis (all the options pertaining to the range axis). If he clicks on the legend, he'll be able to configure the legend (title, footer, ...). If he clicks on the grid, he'll be able to configure the color, size, etc. of the grid!
The chartPanel.addChartMouseListener(...) and ChartMouseEvent.getEntity only seems to work for series and polies on the series...
How should I go about it? any ideas are welcomed!
Is there a method like jfreeChart.getChartElementAtScreenCoordinates(Coord2D) or something like it ?
Added:
I've seen some info in these different posts :
http://www.jfree.org/phpBB2/viewtopic.p ... ight=click
and others on click detection
but none talk about identifying a click on an axis or grid for example.
Should/could these not be also chart entities ?
Thanks
I would like to build an application that will allow the user to build a chart in a WYSIWYG way.
For what I intend to do, I would need to be able to identify what part of the graph the user clicked upon.
i.e.: if he clicks on the chart on the range axis, he'll get an editor that will allow him/her to configure the range axis (all the options pertaining to the range axis). If he clicks on the legend, he'll be able to configure the legend (title, footer, ...). If he clicks on the grid, he'll be able to configure the color, size, etc. of the grid!
The chartPanel.addChartMouseListener(...) and ChartMouseEvent.getEntity only seems to work for series and polies on the series...
How should I go about it? any ideas are welcomed!
Is there a method like jfreeChart.getChartElementAtScreenCoordinates(Coord2D) or something like it ?
Added:
I've seen some info in these different posts :
http://www.jfree.org/phpBB2/viewtopic.p ... ight=click
and others on click detection
but none talk about identifying a click on an axis or grid for example.
Should/could these not be also chart entities ?
Thanks
Tzaphkiel


*BUMP !
Would anyone be so kind as to consider this message and actually say something about it ?
I'm considering doing some changes in the sub-classes of Plot to incorporate a member that would store the axisSpace of the last drawned graph and adding a method to give access to this member variable... would that be a way of doing it ?
Any other ideas from the active comunity ?
Would anyone be so kind as to consider this message and actually say something about it ?
I'm considering doing some changes in the sub-classes of Plot to incorporate a member that would store the axisSpace of the last drawned graph and adding a method to give access to this member variable... would that be a way of doing it ?
Any other ideas from the active comunity ?
Tzaphkiel


-
- JFreeChart Project Leader
- Posts: 11734
- Joined: Fri Mar 14, 2003 10:29 am
- antibot: No, of course not.
- Contact:
Re: Whole chart clicking: identifying parts of chart
Certainly the axes could (and should) be added as chart entities. It would require a little extra code in the draw() method for each axis class, to add a new entity for the area occupied by the axis. I've added a feature request so I don't lose track of this:Tzaphkiel wrote:but none talk about identifying a click on an axis or grid for example.
Should/could these not be also chart entities ?
https://sourceforge.net/tracker/index.p ... tid=365494
David Gilbert
JFreeChart Project Leader
Read my blog
Support JFree via the Github sponsorship program
JFreeChart Project Leader


A limited solution
Hey there,
I've written this code for my app that might be useful for you-- basically identifying the axis that was clicked on according to datastructures populated upon draw. I added this to a subclass of XYPlot...
This certainly solved my problem of knowing where the user clicked.
-g
/**
* These are left and bottom insets for the axes
* -- need to add them for top and bottom later
*/
private double[] bottomAxisInsets;
private ValueAxis[] bottomAxisObj;
private double[] leftAxisInsets;
private ValueAxis[] leftAxisObj;
public ValueAxis axisClicked(Point pt) {
// System.out.println("");
//
// System.out.println("bottominsets: " + doubleArrayToStr(bottomAxisInsets));
// System.out.println("leftinsets: " + doubleArrayToStr(leftAxisInsets));
// create rectangles for left
Rectangle[] leftRects = new Rectangle[leftAxisInsets.length];
if (leftRects.length > 0) { // initialize first
leftRects[0] = new Rectangle(
(int) leftAxisInsets[0],
(int) axisDataArea.getY(),
(int) axisDataArea.getX() - (int) leftAxisInsets[0],
(int) axisDataArea.getHeight()
);
// System.out.println(" leftrect 0 " + leftRects[0]);
}
for (int i = 1; i < leftAxisInsets.length; i++) {
leftRects = new Rectangle(
(int) leftAxisInsets,
(int) leftAxisInsets[i-1],
(int) leftAxisInsets[i-1] - (int) leftAxisInsets,
(int) axisDataArea.getHeight()
);
// System.out.println(" leftrect " + i + " " + leftRects);
}
// create rectangles for bottom
Rectangle[] bottomRects = new Rectangle[bottomAxisInsets.length];
if (bottomRects.length > 0) { // initialize first
bottomRects[0] = new Rectangle(
(int) axisDataArea.getX(),
(int) axisDataArea.getY() + (int) axisDataArea.getHeight(),
(int) axisDataArea.getWidth(),
(int) bottomAxisInsets[0] - (int) (axisDataArea.getY() + axisDataArea.getHeight())
);
// System.out.println(" bottomRect 0 " + bottomRects[0]);
}
for (int i = 1; i < bottomRects.length; i++) {
bottomRects = new Rectangle(
(int) axisDataArea.getX(),
(int) bottomAxisInsets[i-1],
(int) axisDataArea.getWidth(),
(int) bottomAxisInsets - (int) bottomAxisInsets[i-1]
);
// System.out.println(" bottomRect " + i + " " + bottomRects);
}
for (int i = 0; i < leftRects.length; i++) {
if (leftRects.contains(pt)) return leftAxisObj;
}
for (int i = 0; i < bottomRects.length; i++) {
if (bottomRects.contains(pt)) return bottomAxisObj[i];
}
return null;
}
/**
* A utility method for drawing the axes.
*
* @param g2 the graphics device (<code>null</code> not permitted).
* @param plotArea the plot area (<code>null</code> not permitted).
* @param dataArea the data area (<code>null</code> not permitted).
* @param plotState collects information about the plot (<code>null</code>
* permitted).
*
* @return A map containing the state for each axis drawn.
*/
protected Map<ValueAxis, AxisState> drawAxes(Graphics2D g2,
Rectangle2D plotArea,
Rectangle2D dataArea,
PlotRenderingInfo plotState) {
AxisCollection axisCollection = new AxisCollection();
// add domain axes to lists...
for (int index = 0; index < this.domainAxes.size(); index++) {
ValueAxis axis = (ValueAxis) this.domainAxes.get(index);
if (axis != null) {
axisCollection.add(axis, getDomainAxisEdge(index));
}
}
// add range axes to lists...
for (int index = 0; index < this.rangeAxes.size(); index++) {
ValueAxis yAxis = (ValueAxis) this.rangeAxes.get(index);
if (yAxis != null) {
axisCollection.add(yAxis, getRangeAxisEdge(index));
}
}
Map<ValueAxis, AxisState> axisStateMap = new HashMap<ValueAxis, AxisState>();
// draw the top axes
double cursor = dataArea.getMinY() - this.axisOffset.calculateTopOutset(
dataArea.getHeight()
);
Iterator iterator = axisCollection.getAxesAtTop().iterator();
while (iterator.hasNext()) {
ValueAxis axis = (ValueAxis) iterator.next();
AxisState info = axis.draw(
g2, cursor, plotArea, dataArea, RectangleEdge.TOP, plotState
);
cursor = info.getCursor();
axisStateMap.put(axis, info);
}
// draw the bottom axes
int i = 0;
bottomAxisInsets = new double[axisCollection.getAxesAtBottom().size()];
bottomAxisObj = new ValueAxis[axisCollection.getAxesAtBottom().size()];
cursor = dataArea.getMaxY()
+ this.axisOffset.calculateBottomOutset(dataArea.getHeight());
iterator = axisCollection.getAxesAtBottom().iterator();
while (iterator.hasNext()) {
ValueAxis axis = (ValueAxis) iterator.next();
AxisState info = axis.draw(
g2, cursor, plotArea, dataArea, RectangleEdge.BOTTOM, plotState
);
cursor = info.getCursor();
axisStateMap.put(axis, info);
bottomAxisInsets[i] = cursor;
bottomAxisObj[i] = axis;
i++;
// System.out.println(" bottom axes " + i++ + " cursor " + cursor);
}
// draw the left axes
i = 0;
axisDataArea = dataArea;
leftAxisInsets = new double[axisCollection.getAxesAtLeft().size()];
leftAxisObj = new ValueAxis[axisCollection.getAxesAtLeft().size()];
cursor = dataArea.getMinX()
- this.axisOffset.calculateLeftOutset(dataArea.getWidth());
iterator = axisCollection.getAxesAtLeft().iterator();
while (iterator.hasNext()) {
ValueAxis axis = (ValueAxis) iterator.next();
AxisState info = axis.draw(
g2, cursor, plotArea, dataArea, RectangleEdge.LEFT, plotState
);
cursor = info.getCursor();
axisStateMap.put(axis, info);
leftAxisInsets[i] = cursor;
leftAxisObj[i] = axis;
i++;
// System.out.println(" left axes " + i++ + " cursor " + cursor + " data area " + dataArea.getX());
}
// draw the right axes
cursor = dataArea.getMaxX()
+ this.axisOffset.calculateRightOutset(dataArea.getWidth());
iterator = axisCollection.getAxesAtRight().iterator();
while (iterator.hasNext()) {
ValueAxis axis = (ValueAxis) iterator.next();
AxisState info = axis.draw(
g2, cursor, plotArea, dataArea, RectangleEdge.RIGHT, plotState
);
cursor = info.getCursor();
axisStateMap.put(axis, info);
}
return axisStateMap;
}
I've written this code for my app that might be useful for you-- basically identifying the axis that was clicked on according to datastructures populated upon draw. I added this to a subclass of XYPlot...
This certainly solved my problem of knowing where the user clicked.
-g
/**
* These are left and bottom insets for the axes
* -- need to add them for top and bottom later
*/
private double[] bottomAxisInsets;
private ValueAxis[] bottomAxisObj;
private double[] leftAxisInsets;
private ValueAxis[] leftAxisObj;
public ValueAxis axisClicked(Point pt) {
// System.out.println("");
//
// System.out.println("bottominsets: " + doubleArrayToStr(bottomAxisInsets));
// System.out.println("leftinsets: " + doubleArrayToStr(leftAxisInsets));
// create rectangles for left
Rectangle[] leftRects = new Rectangle[leftAxisInsets.length];
if (leftRects.length > 0) { // initialize first
leftRects[0] = new Rectangle(
(int) leftAxisInsets[0],
(int) axisDataArea.getY(),
(int) axisDataArea.getX() - (int) leftAxisInsets[0],
(int) axisDataArea.getHeight()
);
// System.out.println(" leftrect 0 " + leftRects[0]);
}
for (int i = 1; i < leftAxisInsets.length; i++) {
leftRects = new Rectangle(
(int) leftAxisInsets,
(int) leftAxisInsets[i-1],
(int) leftAxisInsets[i-1] - (int) leftAxisInsets,
(int) axisDataArea.getHeight()
);
// System.out.println(" leftrect " + i + " " + leftRects);
}
// create rectangles for bottom
Rectangle[] bottomRects = new Rectangle[bottomAxisInsets.length];
if (bottomRects.length > 0) { // initialize first
bottomRects[0] = new Rectangle(
(int) axisDataArea.getX(),
(int) axisDataArea.getY() + (int) axisDataArea.getHeight(),
(int) axisDataArea.getWidth(),
(int) bottomAxisInsets[0] - (int) (axisDataArea.getY() + axisDataArea.getHeight())
);
// System.out.println(" bottomRect 0 " + bottomRects[0]);
}
for (int i = 1; i < bottomRects.length; i++) {
bottomRects = new Rectangle(
(int) axisDataArea.getX(),
(int) bottomAxisInsets[i-1],
(int) axisDataArea.getWidth(),
(int) bottomAxisInsets - (int) bottomAxisInsets[i-1]
);
// System.out.println(" bottomRect " + i + " " + bottomRects);
}
for (int i = 0; i < leftRects.length; i++) {
if (leftRects.contains(pt)) return leftAxisObj;
}
for (int i = 0; i < bottomRects.length; i++) {
if (bottomRects.contains(pt)) return bottomAxisObj[i];
}
return null;
}
/**
* A utility method for drawing the axes.
*
* @param g2 the graphics device (<code>null</code> not permitted).
* @param plotArea the plot area (<code>null</code> not permitted).
* @param dataArea the data area (<code>null</code> not permitted).
* @param plotState collects information about the plot (<code>null</code>
* permitted).
*
* @return A map containing the state for each axis drawn.
*/
protected Map<ValueAxis, AxisState> drawAxes(Graphics2D g2,
Rectangle2D plotArea,
Rectangle2D dataArea,
PlotRenderingInfo plotState) {
AxisCollection axisCollection = new AxisCollection();
// add domain axes to lists...
for (int index = 0; index < this.domainAxes.size(); index++) {
ValueAxis axis = (ValueAxis) this.domainAxes.get(index);
if (axis != null) {
axisCollection.add(axis, getDomainAxisEdge(index));
}
}
// add range axes to lists...
for (int index = 0; index < this.rangeAxes.size(); index++) {
ValueAxis yAxis = (ValueAxis) this.rangeAxes.get(index);
if (yAxis != null) {
axisCollection.add(yAxis, getRangeAxisEdge(index));
}
}
Map<ValueAxis, AxisState> axisStateMap = new HashMap<ValueAxis, AxisState>();
// draw the top axes
double cursor = dataArea.getMinY() - this.axisOffset.calculateTopOutset(
dataArea.getHeight()
);
Iterator iterator = axisCollection.getAxesAtTop().iterator();
while (iterator.hasNext()) {
ValueAxis axis = (ValueAxis) iterator.next();
AxisState info = axis.draw(
g2, cursor, plotArea, dataArea, RectangleEdge.TOP, plotState
);
cursor = info.getCursor();
axisStateMap.put(axis, info);
}
// draw the bottom axes
int i = 0;
bottomAxisInsets = new double[axisCollection.getAxesAtBottom().size()];
bottomAxisObj = new ValueAxis[axisCollection.getAxesAtBottom().size()];
cursor = dataArea.getMaxY()
+ this.axisOffset.calculateBottomOutset(dataArea.getHeight());
iterator = axisCollection.getAxesAtBottom().iterator();
while (iterator.hasNext()) {
ValueAxis axis = (ValueAxis) iterator.next();
AxisState info = axis.draw(
g2, cursor, plotArea, dataArea, RectangleEdge.BOTTOM, plotState
);
cursor = info.getCursor();
axisStateMap.put(axis, info);
bottomAxisInsets[i] = cursor;
bottomAxisObj[i] = axis;
i++;
// System.out.println(" bottom axes " + i++ + " cursor " + cursor);
}
// draw the left axes
i = 0;
axisDataArea = dataArea;
leftAxisInsets = new double[axisCollection.getAxesAtLeft().size()];
leftAxisObj = new ValueAxis[axisCollection.getAxesAtLeft().size()];
cursor = dataArea.getMinX()
- this.axisOffset.calculateLeftOutset(dataArea.getWidth());
iterator = axisCollection.getAxesAtLeft().iterator();
while (iterator.hasNext()) {
ValueAxis axis = (ValueAxis) iterator.next();
AxisState info = axis.draw(
g2, cursor, plotArea, dataArea, RectangleEdge.LEFT, plotState
);
cursor = info.getCursor();
axisStateMap.put(axis, info);
leftAxisInsets[i] = cursor;
leftAxisObj[i] = axis;
i++;
// System.out.println(" left axes " + i++ + " cursor " + cursor + " data area " + dataArea.getX());
}
// draw the right axes
cursor = dataArea.getMaxX()
+ this.axisOffset.calculateRightOutset(dataArea.getWidth());
iterator = axisCollection.getAxesAtRight().iterator();
while (iterator.hasNext()) {
ValueAxis axis = (ValueAxis) iterator.next();
AxisState info = axis.draw(
g2, cursor, plotArea, dataArea, RectangleEdge.RIGHT, plotState
);
cursor = info.getCursor();
axisStateMap.put(axis, info);
}
return axisStateMap;
}
Thankyou for sharing your code 
it is approximately what I intended to do but I neither had the time nor the understanding of what the AxisState and Cursor were (which I still don't quite grasp....) to do it !
I haven't tried the code yet, but it seems to me that what you do is a bit of an overkill...
Let me explain myself:
It seems from what I understand and what I read in your code that you store all the (small) rectangles (accounting for all the "pieces" of the axis) rather than creating a rectangleArea that engulfs the whole axis area...
Wouldn't it be easier(and probably faster) to build a single area for the whole axis (one for bottom, one for left, ...)
Or is it that you're really accounting for multiple insets being put on the one axis ?
I realise that I'm probaby wrong here
but it probably comes from my miss-understanding of "what is" the Cursor, AxisState and what is contained in the axisCollection (is it all the axis - as in two domain axis, or is it all the bits and pieces forming the one axis ?)...
Thanks for lighting my lantern (could you give me a brief explanation of what those classes represent?)

it is approximately what I intended to do but I neither had the time nor the understanding of what the AxisState and Cursor were (which I still don't quite grasp....) to do it !
I haven't tried the code yet, but it seems to me that what you do is a bit of an overkill...

Let me explain myself:
It seems from what I understand and what I read in your code that you store all the (small) rectangles (accounting for all the "pieces" of the axis) rather than creating a rectangleArea that engulfs the whole axis area...
Wouldn't it be easier(and probably faster) to build a single area for the whole axis (one for bottom, one for left, ...)
Or is it that you're really accounting for multiple insets being put on the one axis ?
I realise that I'm probaby wrong here

Thanks for lighting my lantern (could you give me a brief explanation of what those classes represent?)

Tzaphkiel


I don't really know what AxisState does, but I do know Cursor contains an int that refers to the beginning pixel location of the axis. All I'm doing is storing that info so that a click can be found again later, and mapped back to the appropriate axis.
I use arrays instead of a single variable to store the axis because I need support for multiple axis identification. I'm using this method to be able to give the user the ability to scroll the graph based on the axis.
This seems like the simplest hack I could put together that would allow identification... perhaps AxisState passes the cursor back as well, but I didn't want to dig around too much more.
I use arrays instead of a single variable to store the axis because I need support for multiple axis identification. I'm using this method to be able to give the user the ability to scroll the graph based on the axis.
This seems like the simplest hack I could put together that would allow identification... perhaps AxisState passes the cursor back as well, but I didn't want to dig around too much more.
-
- Posts: 42
- Joined: Fri Dec 02, 2005 9:43 pm
- Location: China