Combineddomainxyplot Not Rescaling Domain Axis

CombinedDomainXYPlot not rescaling domain axis

ANSWERING MY OWN QUESTION:

I managed to refresh the axis using a little hack:

 mainPlot.getDomainAxis().setAutoRange(false);
mainPlot.getDomainAxis().setAutoRange(true);

It is not nice but it does the trick. Nevertheless, I wish someone could post a nicer solution...

Here is the code using non-custom data set that does not work. Please run it and then click on a big button called click multiple times to hide a few series. The domain axis is not rescaled.

import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.List;
import java.util.Random;
import javax.swing.*;
import org.jfree.chart.ChartPanel;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.axis.DateAxis;
import org.jfree.chart.axis.NumberAxis;
import org.jfree.chart.event.ChartChangeEvent;
import org.jfree.chart.event.ChartChangeListener;
import org.jfree.chart.plot.CombinedDomainXYPlot;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.chart.plot.XYPlot;
import org.jfree.chart.renderer.xy.StandardXYItemRenderer;
import org.jfree.chart.renderer.xy.XYItemRenderer;
import org.jfree.data.xy.XYSeries;
import org.jfree.data.xy.XYSeriesCollection;
import org.jfree.ui.RectangleEdge;

public class Runner {

private static Random rand = new Random();

public static void main(String[] args) {
XYSeriesCollection data = new XYSeriesCollection();
int max = rand.nextInt(2) + 2;
for (int i = 0; i < max; i++) {
data.addSeries(generateSeries("Series" + (i + 1)));
}
final XYItemRenderer renderer1 = new StandardXYItemRenderer();
final XYPlot plot1 = new XYPlot(data, null, new DateAxis("Dates"), renderer1);

data = new XYSeriesCollection();
for (int i = 0; i < max; i++) {
data.addSeries(generateSeries("Series" + (i + 1)));
}
final XYPlot plot2 = new XYPlot(data, null, new NumberAxis("Numbers"), renderer1);

final CombinedDomainXYPlot plot = new CombinedDomainXYPlot(new NumberAxis("Domain"));
plot.setGap(10.0);

// add the subplots...
plot.add(plot1, 1);
plot.add(plot2, 1);
plot.setOrientation(PlotOrientation.VERTICAL);

// return a new chart containing the overlaid plot...
final JFreeChart chart = new JFreeChart("CombinedDomainXYPlot Demo",
JFreeChart.DEFAULT_TITLE_FONT, plot, true);
chart.getLegend().setPosition(RectangleEdge.RIGHT);

chart.addChangeListener(new ChartChangeListener() {

boolean changed = false;

@Override
public void chartChanged(ChartChangeEvent event) {
if (!changed) {
} else {
changed = false;
}
}
});

ChartPanel panel = new ChartPanel(chart);
JPanel panel2 = new JPanel(new BorderLayout(0, 10));
panel2.add(panel, BorderLayout.CENTER);
JButton b = new JButton("Click");
b.addActionListener(new ActionListener() {

@Override
public void actionPerformed(ActionEvent e) {
CombinedDomainXYPlot plot = (CombinedDomainXYPlot) chart.getXYPlot();
List l = plot.getSubplots();
int index = rand.nextInt(plot1.getSeriesCount() + plot2.getSeriesCount());
boolean b = renderer1.isSeriesVisible(index);
renderer1.setSeriesVisible(index, false);
}
});
panel2.add(b, BorderLayout.NORTH);
panel2.setVisible(true);

JFrame frame = new JFrame("dsadsa");
frame.add(panel2);
frame.setSize(800, 600);
frame.setVisible(true);
}

private static XYSeries generateSeries(String key) {
XYSeries series = new XYSeries(key);
int points = 15;
double val = 0.0;
double x = 0.0;
for (int i = 0; i < points; i++) {
val += rand.nextDouble() * 6 - 3;
x += rand.nextDouble() * 4;
series.add(x, val);
}
return series;
}
}

Secondary domain axis narrower than primary

The comparison approach shown in your fragment appears sound, but I suspect that your unseen data has multiple, varying timestamps for a given hourly interval, which the DateAxis faithfully reports.

The variation below clears the smaller Calendar fields, as shown here, to synthesize a DAYS worth of data. The resulting Hour instances have matching hour fields in each series. The example also omits the ChartFactory to avoid the unwanted ChartTheme axis label font.

To get the result you want, you're going have to resample your data to have matching hour fields for each period that you add() to a TimeSeries.

image

import java.awt.Color;
import java.awt.EventQueue;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import javax.swing.JFrame;
import org.jfree.chart.ChartPanel;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.axis.AxisLocation;
import org.jfree.chart.axis.DateAxis;
import org.jfree.chart.axis.NumberAxis;
import org.jfree.chart.plot.XYPlot;
import org.jfree.chart.renderer.xy.XYLineAndShapeRenderer;
import org.jfree.data.time.Hour;
import org.jfree.data.time.TimeSeries;
import org.jfree.data.time.TimeSeriesCollection;

/**
* @see https://stackoverflow.com/a/55584636/230513
* @see https://stackoverflow.com/q/55553082/230513
*/
public class HourTest {

private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("dd-MMM HH");
private static final int DAYS = 1;

private Calendar getCalendar() {
Calendar c = Calendar.getInstance();
c.set(Calendar.HOUR_OF_DAY, 0);
c.set(Calendar.MINUTE, 0);
c.set(Calendar.SECOND, 0);
c.set(Calendar.MILLISECOND, 0);return c;
}

private void display() {
JFrame f = new JFrame("HourTest");
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

TimeSeries timeSeries = new TimeSeries("Number of visitors");
TimeSeries refSeries = new TimeSeries("Reference number of visitors");
Calendar c1 = getCalendar();
Calendar c2 = getCalendar();
c2.add(Calendar.DAY_OF_YEAR, -DAYS);
for (int i = 0; i < 24 * DAYS; i++) {
timeSeries.add(new Hour(c1.getTime()), i + 2);
c1.add(Calendar.HOUR, 1);
refSeries.add(new Hour(c2.getTime()), i + 1);
c2.add(Calendar.HOUR, 1);
}
TimeSeriesCollection dataset = new TimeSeriesCollection(timeSeries);
TimeSeriesCollection refDataset = new TimeSeriesCollection(refSeries);
NumberAxis valueAxis = new NumberAxis("Number of visitors");
valueAxis.setAutoRangeIncludesZero(false);
DateAxis timeAxis = new DateAxis("Hours");
timeAxis.setDateFormatOverride(DATE_FORMAT);
DateAxis refAxis = new DateAxis("Reference hours");
refAxis.setDateFormatOverride(DATE_FORMAT);
XYPlot plot = new XYPlot(dataset, timeAxis, valueAxis, null);
plot.setBackgroundPaint(Color.WHITE);
plot.setDomainGridlinePaint(Color.LIGHT_GRAY);
plot.setRangeGridlinePaint(Color.LIGHT_GRAY);
plot.setDomainAxis(1, refAxis);
plot.setDomainAxisLocation(1, AxisLocation.BOTTOM_OR_RIGHT);
plot.setDataset(1, refDataset);
plot.mapDatasetToDomainAxis(1, 1);
XYLineAndShapeRenderer renderer0 = new XYLineAndShapeRenderer();
renderer0.setSeriesPaint(0, Color.red);
XYLineAndShapeRenderer renderer1 = new XYLineAndShapeRenderer();
renderer1.setSeriesPaint(0, Color.blue);
plot.setRenderer(0, renderer0);
plot.setRenderer(1, renderer1);
plot.setDomainPannable(true);
plot.setRangePannable(true);
JFreeChart timeChart = new JFreeChart("Main stats", plot);
timeChart.setBackgroundPaint(Color.white);

f.add(new ChartPanel(timeChart));
f.pack();
f.setLocationRelativeTo(null);
f.setVisible(true);
}

public static void main(String[] args) {
EventQueue.invokeLater(new HourTest()::display);
}
}

CombinedDomainXYPlot - does range panning work at all?

I get the same result using this example. It's not documented explicitly, but it makes sense. Empirically, only the combined (shared) axis supports panning, even if the subplots would otherwise support panning on the orthogonal (independent) axis:

  • CombinedDomainXYPlot supports domain panning; invokes setFixedRangeAxisSpaceForSubplots() in draw().

  • CombinedRangeXYPlot supports range panning; invokes setFixedDomainAxisSpaceForSubplots() in draw().

JFreeChart combinedDomainXYPlot - maintaining order of plots

I'd use a ListSelectionModel, which is available to both JList, shown here, and JTable, shown here. The former has a flexible layout, while the latter has a convenient JCheckbox renderer/editor.

Lets assume your model ultimately produces a List<Plot> named selected. You can loop though the getSubplots() list to remove() all current plots, then loop through your List<Plot> to add() each selected plot back.

Addendum: If the subplots are otherwise identical, you may be able to to add() or remove() a minimum number of subplots and replace the models of those that remain using setDataset(), shown here. It's slightly more complicated but perhaps less visually disruptive.

Set the size of plots in a CombinedDomain JFreeChart

As shown here, subplots added to a CombinedDomainXYPlot are given equal weight. As shown here, you can specify the desired weight when you add() a subplot.

The weight determines how much space is allocated to the subplot relative to all the other subplots.

To get equal size, either use the default:

plot.add(subplotTop);
plot.add(subplotBottom);

or specify equal sizes:

plot.add(subplotTop, 1);
plot.add(subplotBottom, 1);

Sample Image

How can I share DomainAxis/RangeAxis across subplots without drawing them on each plot?

That's really a tough one...

This is how close I got, starting from your code:

  1. Set the domain axes invisible:

    ValueAxis a = phiDPlots[i].getDomainAxis();
    a.setVisible(false);
    tempPlot.setDomainAxis((NumberAxis) phiDPlots[i].getDomainAxis());
  2. 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...

Sample Image

Multiple axes on the same data

Eventually i settled on this solution, it might not be the most elegant but it worked. I have a second axis feetAxis, and added a AxisChangeListener on the first axis called meterAxis. When the meterAxis changes set the range on feetAxis.

I used SwingUtilities.invokeLater, otherwise the range would be incorrect when zooming out of the chart, then the feetAxis would only go from 0 to 1. Didn't check why though.

feetAxis = new NumberAxis("Height [ft]");
metersAxis = new NumberAxis("Height [m]");
pathPlot.setRangeAxis(0, metersAxis);
pathPlot.setRangeAxis(1, feetAxis);

metersAxis.addChangeListener(new AxisChangeListener() {

@Override
public void axisChanged(AxisChangeEvent event) {

SwingUtilities.invokeLater(new Runnable() {

@Override
public void run() {
feetAxis.setRange(metersAxis.getLowerBound() * MetersToFeet, metersAxis.getUpperBound() * MetersToFeet);
}
});
}
});


Related Topics



Leave a reply



Submit