I'm working on a web application in which a servlet generates an image of a chart which is embedded in a web page.
I'd like to implement dynamic crosshairs using javascript so that as a user moves the mouse over the chart, the cross hairs move to track the mouse and text on the web page indicating the current value under the crosshair is updated**
In order to do this I need to be able to match the mouse position relative to the chart image with the points plotted by JFreeChart. I also need to know the bounds of the plot area within the image generated by JFreeChart.
I first thought that getting some sort of callback each time a point was plotted would be the way to go and had a look at the way cross hairs work hoping that I could get some sort of callback similar to CrosshairState.updateCrosshairPoint when each point was plotted. However the the CrosshairState class does not seem to be intended for use outside of JFreeChart code.
An alternative method seems to be to contruct separate maps from pixels (Java2D coordinates) to values in the dataset using ValueAxis.java2DToValue() these could be encoded as JSON and passed back to the javascript client via an AJAX call.
I've looked through the documentation and JFreeChart source but haven't found a more convenient method. Does what I'm describing sound about right or is there a better way?
thanks!
Dave
** In fact I already have javascript code to so this for a old version of the site before we changed to using java & JFreeChart. The crosshairs are implemented as images moved over the chart image using javascript so the chart image does not need to be modified.
Mapping pixels back to dataset values to generate crosshairs
-
- Posts: 9
- Joined: Tue Jul 06, 2010 7:10 pm
- antibot: No, of course not.
-
- Posts: 9
- Joined: Tue Jul 06, 2010 7:10 pm
- antibot: No, of course not.
Re: Mapping pixels back to dataset values to generate crosshairs
I got most of the way there so I'll post the code that seems to work
First pass an instance of ChartRenderingInfo to the draw() function
For the date axis, because it is not continuous (there are excluded dates because of the SegmentedTimeLine and excluded dates with no data) I map back from each pixel in the resulting image to the date value. I omit pixels where the date has not changed - the long variable resolution is used to set the coarseness of the test (currently is set to number of milliseconds in a day):
The Y axis is easier as I can treat double valued data as continuous. The only complication is that some of my charts are Combined Domain Plots and so I need to check for the presence of sub-plots.
In saveYAxisRenderData:
I've adapted the above to remove some of my custom classes/methods that store this data - hopefully I have not introduced any bugs.
First pass an instance of ChartRenderingInfo to the draw() function
Code: Select all
ChartRenderingInfo info = new ChartRenderingInfo()
jchart.draw(g2, chartWidthAndHeight, info);
Code: Select all
DateAxis dateAxis = (DateAxis) plot.getDomainAxis();
Date minDate = dateAxis.getMinimumDate();
Date maxDate = dateAxis.getMaximumDate();
int minX = (int) dateAxis.dateToJava2D(minDate, plotInfo.getDataArea(), plot.getDomainAxisEdge());
int maxX = (int) dateAxis.dateToJava2D(xAxisData.getMaxValue(), plotInfo.getDataArea(), plot.getDomainAxisEdge());
long lastEmitted = 0;
for (int x=minX; x<=maxX; ++x) {
double val = dateAxis.java2DToValue((double) x, plotInfo.getDataArea(), plot.getDomainAxisEdge());
Date xDate = new Date((new Double(val)).longValue());
long valDivRes = xDate.getTime() / resolution;
if (valDivRes > lastEmitted) {
// store the pixel x, and date xDate in a list that gets converted to JSON and sent back to Javascript code on client
}
}
Code: Select all
if (info.getPlotInfo().getSubplotCount() > 0) {
CombinedDomainXYPlot cplot = (CombinedDomainXYPlot) plot;
List subplots = cplot.getSubplots();
for (int s=0; s<subplots.size(); ++s) {
XYPlot subplot = (XYPlot) subplots.get(s);
saveYAxisRenderData(null, subplot.getRangeAxis(), subplot, info.getPlotInfo().getSubplotInfo(s)));
}
} else {
saveYAxisRenderData(null, plot.getRangeAxis(), plot, info.getPlotInfo()));
}
Code: Select all
double minValue = rangeAxis.getLowerBound();
double maxValue = rangeAxis.getUpperBound();
int minY = (int) rangeAxis.valueToJava2D(yAxisData.getMinValue(), plotInfo.getDataArea(), plot.getRangeAxisEdge()));
int maxY = (int) rangeAxis.valueToJava2D(yAxisData.getMaxValue(), plotInfo.getDataArea(), plot.getRangeAxisEdge()));