Ok, I've been hacking at this for almost all day with no success. I'm trying to use JFreeChart to create a grid of XYPlot
s where the domain and range axes are linked for each column and row of plots, respectively. That is to say, the plots in the same row have the same range axis range, and the plots in a column have the same domain axis range.
I was able to achieve the functionality by using a hacked CombinedDomainXYPlot
of CombinedRangeXYPlot
s of XYPlot
s. Basically I made some XYPlot
objects and added them to CombinedRangeXYPlot
objects, then added those CombinedRangeXYPlot
objects to an instance of CombinedDomainXYPlot
that doesnt draw a domain axis. (Maybe there is another way to stack plots instead of CombinedDomainXYPlot
, since I'm not using the combined domain axis functionality.)
The ranges scale together for each row, as expected. By adding the same domain axis to each subplot in a column, I was able to get the domains to scale together for each column. Result is shown below.
I have two problems right now - first, I would like to get rid of the axis labels below each row and just have them on the bottom, but keep the scales linked.
Second, the labels for the range axes are of the edge of the window - how do I get them back?
And, in general, I would like to understand how CombinedRangeXYPlot
and CombinedRangeXYPlot
use the same axis range for multiple plots without drawing the axes below each plot.
EDIT: Here is the code for a working demo:
Main class
public class GridBlockPlotFrameExample {
private final JFrame frame;
private final XYPlot[][] phiPhiPlots;
private final XYPlot[] phiDPlots;
public GridBlockPlotFrameExample() {
frame = new JFrame("Density Plot");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
phiDPlots = new XYPlot[4];
phiPhiPlots = new XYPlot[4][4];
createSubPlots();
CombinedRangeXYPlot[] rowPlots = new CombinedRangeXYPlot[phiDPlots.length + 1];
for (int i = 0; i < phiPhiPlots.length; i++) {
rowPlots[i + 1] = new CombinedRangeXYPlot();
for (int j = 0; j < phiPhiPlots[i].length; j++) {
if (phiPhiPlots[i][j] != null) {
rowPlots[i + 1].add(phiPhiPlots[i][j]);
} else {
rowPlots[i + 1].add(new XYPlot());
}
}
}
rowPlots[0] = new CombinedRangeXYPlot();
for (XYPlot phiDPlot : phiDPlots) {
rowPlots[0].add(phiDPlot);
}
StackedXYPlot gridPlot = new StackedXYPlot();
for (int i = rowPlots.length - 1; i >= 1; i--) {
XYPlot rowPlot = rowPlots[i];
gridPlot.add(rowPlot, 2);
}
gridPlot.add(rowPlots[0], 1);
JFreeChart chart = new JFreeChart("gridplot", JFreeChart.DEFAULT_TITLE_FONT, gridPlot, false);
chart.setBackgroundPaint(Color.WHITE);
ChartPanel panel = new ChartPanel(chart);
panel.setPreferredSize(new Dimension(300, 300));
panel.setMouseWheelEnabled(false);
panel.setRangeZoomable(true);
panel.setDomainZoomable(true);
frame.setContentPane(panel);
frame.pack();
RefineryUtilities.centerFrameOnScreen(frame);
}
private void createSubPlots() {
for (int i = 0; i < phiDPlots.length; i++) {
phiDPlots[i] = createPlot(createDataset());
}
XYPlot tempPlot;
for (int i = 0; i < phiPhiPlots.length; i++) {
for (int j = 0; j < phiPhiPlots.length; j++) {
tempPlot = createPlot(createDataset());
phiPhiPlots[j][i] = tempPlot; // (sic) YES this inversion is intentional
tempPlot.setDomainAxis((NumberAxis) phiDPlots[i].getDomainAxis());
}
}
}
private XYPlot createPlot(XYZDataset data) {
NumberAxis xAxis = new NumberAxis("X");
xAxis.setStandardTickUnits(NumberAxis.createIntegerTickUnits());
xAxis.setLowerMargin(0.0);
xAxis.setUpperMargin(0.0);
xAxis.setAxisLinePaint(Color.white);
xAxis.setTickMarkPaint(Color.white);
NumberAxis yAxis = new NumberAxis("Y");
yAxis.setStandardTickUnits(NumberAxis.createIntegerTickUnits());
yAxis.setLowerMargin(0.0);
yAxis.setUpperMargin(0.0);
yAxis.setAxisLinePaint(Color.white);
yAxis.setTickMarkPaint(Color.white);
XYBlockRenderer renderer = new XYBlockRenderer();
PaintScale scale = new GrayPaintScale(-2.0, 1.0);
renderer.setPaintScale(scale);
XYPlot plot = new XYPlot(data, xAxis, yAxis, renderer);
plot.setBackgroundPaint(Color.lightGray);
plot.setDomainGridlinesVisible(false);
plot.setRangeGridlinePaint(Color.white);
plot.setAxisOffset(new RectangleInsets(5, 5, 5, 5));
plot.setOutlinePaint(Color.blue);
return plot;
}
private XYZDataset createDataset() {
return new XYZDataset() {
@Override
public int getSeriesCount() {
return 1;
}
@Override
public int getItemCount(int series) {
return 10000;
}
@Override
public Number getX(int series, int item) {
return new Double(getXValue(series, item));
}
@Override
public double getXValue(int series, int item) {
return item / 100 - 50;
}
@Override
public Number getY(int series, int item) {
return new Double(getYValue(series, item));
}
@Override
public double getYValue(int series, int item) {
return item - (item / 100) * 100 - 50;
}
@Override
public Number getZ(int series, int item) {
return new Double(getZValue(series, item));
}
@Override
public double getZValue(int series, int item) {
double x = getXValue(series, item);
double y = getYValue(series, item);
return Math.sin(Math.sqrt(x * x + y * y) / 5.0);
}
@Override
public void addChangeListener(DatasetChangeListener listener) {
// ignore - this dataset never changes
}
@Override
public void removeChangeListener(DatasetChangeListener listener) {
// ignore
}
@Override
public DatasetGroup getGroup() {
return null;
}
@Override
public void setGroup(DatasetGroup group) {
// ignore
}
@Override
public Comparable getSeriesKey(int series) {
return "sin(sqrt(x + y))";
}
@Override
public int indexOf(Comparable seriesKey) {
return 0;
}
@Override
public DomainOrder getDomainOrder() {
return DomainOrder.ASCENDING;
}
};
}
public void show() {
frame.setVisible(true);
}
public static void main(String[] args) {
GridBlockPlotFrameExample example = new GridBlockPlotFrameExample();
example.show();
}
}
StackedXYPlot class
public class StackedXYPlot extends CombinedDomainXYPlot {
public StackedXYPlot() {
super(null);
}
@Override
public void draw(Graphics2D g2, Rectangle2D area, Point2D anchor, PlotState parentState,
PlotRenderingInfo info) {
// set up info collection...
if (info != null) {
info.setPlotArea(area);
}
// adjust the drawing area for plot insets (if any)...
RectangleInsets insets = getInsets();
insets.trim(area);
setFixedRangeAxisSpaceForSubplots(null);
AxisSpace space = calculateAxisSpace(g2, area);
Rectangle2D dataArea = space.shrink(area, null);
// set the width and height of non-shared axis of all sub-plots
setFixedRangeAxisSpaceForSubplots(space);
// draw all the subplots
for (int i = 0; i < getSubplots().size(); i++) {
XYPlot plot = (XYPlot) getSubplots().get(i);
PlotRenderingInfo subplotInfo = null;
if (info != null) {
subplotInfo = new PlotRenderingInfo(info.getOwner());
info.addSubplotInfo(subplotInfo);
}
plot.draw(g2, this.subplotAreas[i], anchor, parentState, subplotInfo);
}
if (info != null) {
info.setDataArea(dataArea);
}
}
public int findSubplotIndex(PlotRenderingInfo info, Point2D source) {
ParamChecks.nullNotPermitted(info, "info");
ParamChecks.nullNotPermitted(source, "source");
XYPlot result = null;
return info.getSubplotIndex(source);
}
/**
* Multiplies the range on the range axis/axes by the specified factor.
*
* @param factor the zoom factor.
* @param info the plot rendering info (<code>null</code> not permitted).
* @param source the source point (<code>null</code> not permitted).
*/
@Override
public void zoomDomainAxes(double factor, PlotRenderingInfo info, Point2D source) {
zoomDomainAxes(factor, info, source, false);
}
/**
* Multiplies the range on the range axis/axes by the specified factor.
*
* @param factor the zoom factor.
* @param state the plot state.
* @param source the source point (in Java2D coordinates).
* @param useAnchor use source point as zoom anchor?
*/
@Override
public void zoomDomainAxes(double factor, PlotRenderingInfo state, Point2D source,
boolean useAnchor) {
// delegate 'state' and 'source' argument checks...
int subplotIndex = findSubplotIndex(state, source);
XYPlot subplot = null;
if (subplotIndex >= 0) {
subplot = (XYPlot) getSubplots().get(subplotIndex);
}
if (subplot != null) {
subplot.zoomDomainAxes(factor, state.getSubplotInfo(subplotIndex), source, useAnchor);
} else {
// if the source point doesn't fall within a subplot, we do the
// zoom on all subplots...
Iterator iterator = getSubplots().iterator();
while (iterator.hasNext()) {
subplot = (XYPlot) iterator.next();
subplot.zoomDomainAxes(factor, state, source, useAnchor);
}
}
}
/**
* Zooms in on the range axes.
*
* @param lowerPercent the lower bound.
* @param upperPercent the upper bound.
* @param info the plot rendering info (<code>null</code> not permitted).
* @param source the source point (<code>null</code> not permitted).
*/
@Override
public void zoomDomainAxes(double lowerPercent, double upperPercent, PlotRenderingInfo info,
Point2D source) {
// delegate 'info' and 'source' argument checks...
int subplotIndex = findSubplotIndex(info, source);
XYPlot subplot = null;
if (subplotIndex >= 0) {
subplot = (XYPlot) getSubplots().get(subplotIndex);
}
if (subplot != null) {
subplot.zoomDomainAxes(lowerPercent, upperPercent, info.getSubplotInfo(subplotIndex), source);
} else {
// if the source point doesn't fall within a subplot, we do the
// zoom on all subplots...
Iterator iterator = getSubplots().iterator();
while (iterator.hasNext()) {
subplot = (XYPlot) iterator.next();
subplot.zoomDomainAxes(lowerPercent, upperPercent, info, source);
}
}
}
/**
* Multiplies the range on the range axis/axes by the specified factor.
*
* @param factor the zoom factor.
* @param info the plot rendering info (<code>null</code> not permitted).
* @param source the source point (<code>null</code> not permitted).
*/
@Override
public void zoomRangeAxes(double factor, PlotRenderingInfo info, Point2D source) {
zoomRangeAxes(factor, info, source, false);
}
/**
* Multiplies the range on the range axis/axes by the specified factor.
*
* @param factor the zoom factor.
* @param state the plot state.
* @param source the source point (in Java2D coordinates).
* @param useAnchor use source point as zoom anchor?
*/
@Override
public void zoomRangeAxes(double factor, PlotRenderingInfo state, Point2D source,
boolean useAnchor) {
// delegate 'state' and 'source' argument checks...
int subplotIndex = findSubplotIndex(state, source);
XYPlot subplot = null;
if (subplotIndex >= 0) {
subplot = (XYPlot) getSubplots().get(subplotIndex);
}
if (subplot != null) {
subplot.zoomRangeAxes(factor, state.getSubplotInfo(subplotIndex), source, useAnchor);
} else {
// if the source point doesn't fall within a subplot, we do the
// zoom on all subplots...
Iterator iterator = getSubplots().iterator();
while (iterator.hasNext()) {
subplot = (XYPlot) iterator.next();
subplot.zoomRangeAxes(factor, state, source, useAnchor);
}
}
}
/**
* Zooms in on the range axes.
*
* @param lowerPercent the lower bound.
* @param upperPercent the upper bound.
* @param info the plot rendering info (<code>null</code> not permitted).
* @param source the source point (<code>null</code> not permitted).
*/
@Override
public void zoomRangeAxes(double lowerPercent, double upperPercent, PlotRenderingInfo info,
Point2D source) {
// delegate 'info' and 'source' argument checks...
int subplotIndex = findSubplotIndex(info, source);
XYPlot subplot = null;
if (subplotIndex >= 0) {
subplot = (XYPlot) getSubplots().get(subplotIndex);
}
if (subplot != null) {
subplot.zoomRangeAxes(lowerPercent, upperPercent, info.getSubplotInfo(subplotIndex), source);
} else {
// if the source point doesn't fall within a subplot, we do the
// zoom on all subplots...
Iterator iterator = getSubplots().iterator();
while (iterator.hasNext()) {
subplot = (XYPlot) iterator.next();
subplot.zoomRangeAxes(lowerPercent, upperPercent, info, source);
}
}
}
}
I believe the only other thing I had to do to get the StackedXYPlot to work is change visibility of CombinedDomainXYPlot.subplotAreas
to protected
.
I noticed with this example that the mouse zoom of the domain axis is off - but it does propagate to the other plots in the column.
Thanks,
Igor
P.S. the reason I want to eliminate the plots below the plot is because in the end I need to plot at least a 6x7 grid of plots and with that many, the labels take up most of the space.
Edit: I have accepted Eric's answer as functional, but I am working on a less hackish way of doing it - How CombinedDomainXYPlot and CombinedRangeXYPlot share Axis information with subplots. I'll update there if I get it completely functional.
That's really a tough one...
This is how close I got, starting from your code:
Set the domain axes invisible:
ValueAxis a = phiDPlots[i].getDomainAxis();
a.setVisible(false);
tempPlot.setDomainAxis((NumberAxis) phiDPlots[i].getDomainAxis());
Set the subplot's domain axes visible if drawing the last row:
// draw all the subplots
for (int i = 0; i < this.getSubplots().size(); i++) {
CombinedRangeXYPlot plot = (CombinedRangeXYPlot) this.getSubplots().get(i);
PlotRenderingInfo subplotInfo = null;
if (info != null) {
subplotInfo = new PlotRenderingInfo(info.getOwner());
info.addSubplotInfo(subplotInfo);
}
if(i==getSubplots().size()-1){ // If the last row
for(int j=0; j < plot.getSubplots().size(); j++)
((XYPlot)plot.getSubplots().get(j)).getDomainAxis().setVisible(true);
}
plot.draw(g2, this.subplotAreas[i], anchor, parentState, subplotInfo);
if(i==getSubplots().size()-1){ // If the last row
for(int j=0; j < plot.getSubplots().size(); j++)
((XYPlot)plot.getSubplots().get(j)).getDomainAxis().setVisible(false);
}
}
This works, but somehow only after a refresh/resize of the window, because the last row of graphs is too compressed vertically...