Whole chart clicking: identifying parts of chart

A discussion forum for JFreeChart (a 2D chart library for the Java platform).
Locked
Tzaphkiel
Posts: 44
Joined: Tue May 13, 2003 1:37 pm
Location: Belgium

Whole chart clicking: identifying parts of chart

Post by Tzaphkiel » Tue Dec 06, 2005 10:42 am

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
Tzaphkiel
Image

Tzaphkiel
Posts: 44
Joined: Tue May 13, 2003 1:37 pm
Location: Belgium

Post by Tzaphkiel » Tue Dec 06, 2005 2:35 pm

Is it plausible to use the reserveSpace() of the ValueAxis class and the resulting AxisSpace to see if the point is in the Axis region ???

Would that be the way of going about it ?

Any suggestions ?
Tzaphkiel
Image

Tzaphkiel
Posts: 44
Joined: Tue May 13, 2003 1:37 pm
Location: Belgium

Post by Tzaphkiel » Fri Dec 09, 2005 9:25 am

*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 ?
Tzaphkiel
Image

david.gilbert
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

Post by david.gilbert » Fri Dec 09, 2005 9:52 am

Tzaphkiel wrote:but none talk about identifying a click on an axis or grid for example.
Should/could these not be also chart entities ?
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:

https://sourceforge.net/tracker/index.p ... tid=365494
David Gilbert
JFreeChart Project Leader

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

rantfoil
Posts: 9
Joined: Tue Dec 06, 2005 9:53 pm

A limited solution

Post by rantfoil » Tue Dec 20, 2005 8:10 pm

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;
}

Tzaphkiel
Posts: 44
Joined: Tue May 13, 2003 1:37 pm
Location: Belgium

Post by Tzaphkiel » Thu Dec 22, 2005 9:33 am

Thankyou for sharing your code :D
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?) :!:
Tzaphkiel
Image

rantfoil
Posts: 9
Joined: Tue Dec 06, 2005 9:53 pm

Post by rantfoil » Sat Dec 31, 2005 3:29 am

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.

James.Cheng
Posts: 42
Joined: Fri Dec 02, 2005 9:43 pm
Location: China

Post by James.Cheng » Tue Jan 03, 2006 7:06 pm

if you use chart image .you can export chart image svg ,and use JS!!!!

Tzaphkiel
Posts: 44
Joined: Tue May 13, 2003 1:37 pm
Location: Belgium

Post by Tzaphkiel » Mon Jan 09, 2006 9:33 am

Thanks for the info (I was on holidays, just came back !) but I don't use JS, it'll be an all java application.

Best wishes for the new year to all that read this !
Tzaphkiel
Image

Locked