Mapping pixels back to dataset values to generate crosshairs

A discussion forum for JFreeChart (a 2D chart library for the Java platform).
Locked
davesnowdon
Posts: 9
Joined: Tue Jul 06, 2010 7:10 pm
antibot: No, of course not.

Mapping pixels back to dataset values to generate crosshairs

Post by davesnowdon » Mon Oct 25, 2010 6:49 pm

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.

davesnowdon
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

Post by davesnowdon » Mon Mar 14, 2011 2:54 pm

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

Code: Select all

ChartRenderingInfo info = new ChartRenderingInfo()
jchart.draw(g2, chartWidthAndHeight, info);
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):

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

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()));
}
In saveYAxisRenderData:

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()));
I've adapted the above to remove some of my custom classes/methods that store this data - hopefully I have not introduced any bugs.

Locked