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
.
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; invokessetFixedRangeAxisSpaceForSubplots()
indraw()
.CombinedRangeXYPlot
supports range panning; invokessetFixedDomainAxisSpaceForSubplots()
indraw()
.
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);
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:
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...
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
Java Event-Dispatching Thread Explanation
Centering a Jlabel on a JPAnel
Differencebetween Atomic/Volatile/Synchronized
Where Is Array's Length Property Defined
What Is Suppresswarnings ("Unchecked") in Java
When Should a Class Be Comparable And/Or Comparator
Jar Bundler Using Osxadapter Causing Application to Lag or Terminate
Setting Background Images in Jframe
Java Multi-Threading & Safe Publication
Setting User Agent of a Java Urlconnection
What Is an Outofmemoryerror and How to Debug and Fix It
Converting Array to List in Java
Comparing Strings with == Which Are Declared Final in Java
How to Extract a Substring Using Regex
Why Is Hibernate Open Session in View Considered a Bad Practice
Java Wait Cursor Display Problem
How to Derive Module Descriptor for Auto Generated Module Names in Java 9