I have access to a database that returns the temperature of a location and time of that location every 5 seconds.
I have an idea of plotting the time on the x-axis.
And probably by using the java swing timer I would be able to add data into the graph every 5 seconds.
However, I do not know how to implement that because I thought of adding a timer in createDataset( ) but since it returns a dataset, I won't be able to achieve it.
Any idea how I would be able to add data into the graph every 5 seconds?
Here is my code:
import java.awt.Color;
import java.awt.BasicStroke;
import org.jfree.chart.ChartPanel;
import org.jfree.chart.JFreeChart;
import org.jfree.data.xy.XYDataset;
import org.jfree.data.xy.XYSeries;
import org.jfree.ui.ApplicationFrame;
import org.jfree.ui.RefineryUtilities;
import org.jfree.chart.plot.XYPlot;
import org.jfree.chart.ChartFactory;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.data.xy.XYSeriesCollection;
import org.jfree.chart.renderer.xy.XYLineAndShapeRenderer;
public class XYLineChart_AWT extends ApplicationFrame {
public XYLineChart_AWT( String applicationTitle, String chartTitle ) {
super(applicationTitle);
JFreeChart xylineChart = ChartFactory.createXYLineChart(
chartTitle ,
"Time" ,
"Temperature" ,
createDataset() ,
PlotOrientation.VERTICAL ,
true , true , false);
ChartPanel chartPanel = new ChartPanel( xylineChart );
chartPanel.setPreferredSize( new java.awt.Dimension( 560 , 367 ) );
final XYPlot plot = xylineChart.getXYPlot( );
XYLineAndShapeRenderer renderer = new XYLineAndShapeRenderer( );
renderer.setSeriesPaint( 0 , Color.RED );
renderer.setSeriesStroke( 0 , new BasicStroke( 4.0f ) );
plot.setRenderer( renderer );
setContentPane( chartPanel );
}
private XYDataset createDataset( ) {
final XYSeries temp = new XYSeries( "Temperature" );
//time = getTime(); //returns a float time in seconds.milliseconds
//temperature = getTemp(); //returns a number temperature
//I want to add data into temp every 5 seconds but i don't know how to do it
temp.add( 1.0 , 1.0 );
temp.add( 2.0 , 4.0 );
temp.add( 3.0 , 3.0 );
final XYSeriesCollection dataset = new XYSeriesCollection( );
dataset.addSeries( temp );
return dataset;
}
public static void main( String[ ] args ) {
XYLineChart_AWT chart = new XYLineChart_AWT("Temp",
"Temperature of some location");
chart.pack( );
RefineryUtilities.centerFrameOnScreen( chart );
chart.setVisible( true );
}
}
Rather than putting a timer in your createDataset() method you can instead spawn a new thread from your main method that modifies your JFreeChart dataset every 5 seconds.
For example you could do it something like this:
public static void main( String[ ] args ) {
XYLineChart_AWT chart = new XYLineChart_AWT("Temp",
"Temperature of some location");
chart.pack( );
RefineryUtilities.centerFrameOnScreen( chart );
chart.setVisible( true );
//now make your timer
int delay = 5000; //milliseconds
ActionListener timerAction = new ActionListener() {
public void actionPerformed(ActionEvent evt) {
//some code here to get and modify your dataset so it can be updated
// ----
// ----
//now apply your new dataset to your JFreeChart
xylineChart.getXYPlot().setDataset(myNewDataset);
}
};
new Timer(delay, timerAction).start();
}
Remember to add some code to remove old entries in your dataset so that the chart remains readable and all the values on the Time axis remain the same distance apart between different datasets, for example make sure there are no more than 24 items (2 minutes of data) plotted at a time.
Related
I need some basic help formatting date/time so that I can add it to my JFreeChart TimeSeries. Because my code is adding duplicates I get the error below the second time through the loop.
I have a temperature sensor we use in healthcare and I need to plot the temp that is read every 30 minutes. So I need my x-axis to be Month-Day-Hour-Minute but I just dont know how to get my time formatted correctly. The line below were I am having issue specifically is " timeSeries.add(new Day(gc.getTime()), temp );"
It's been a really long time since college so I apologize, I think I am close but just can't close the gap. Any help would be appreciated.
thx! L
Output
Sat Nov 20 12:41:00 PST 2021: 72.5 F
TimeSeries.add 20-November-2021
Sat Nov 20 13:11:00 PST 2021: 70.7 F
TimeSeries.add 20-November-2021
Run-time Error when adding duplicates
Exception in thread "main" org.jfree.data.general.SeriesException: You are attempting to add an observation for the time period 20-November-2021 but the series already contains an observation for that time period. Duplicates are not permitted. Try using the addOrUpdate() method.
at org.jfree.data.time.TimeSeries.add(TimeSeries.java:701)
at org.jfree.data.time.TimeSeries.add(TimeSeries.java:746)
at org.jfree.data.time.TimeSeries.add(TimeSeries.java:732)
at dumpMission.main(dumpMission.java:212)
TimeSeries timeSeries = new TimeSeries("Date");
TimeSeriesCollection timeSeriesCollectionTemps = new TimeSeriesCollection();
long time = time_stamp.getTime().getTime() + owc.getFirstLogOffset(state);
GregorianCalendar gc = new GregorianCalendar();
for (int i = 0; i < log.length; i++){
gc.setTime(new Date(time));
System.out.println(gc.getTime() + ": " + Convert.toFahrenheit(owc.decodeTemperature(log [i])) + " F");
time += sample_rate * 60 * 1000;
double temp = Convert.toFahrenheit(owc.decodeTemperature(log [i]));
System.out.println("TimeSeries.add " + new Day(gc.getTime()));
//add needs org.jfree.data.time.RegularTimePeriod
//need to plot month-day-hour-minute
timeSeries.add(new Day(gc.getTime()), temp ); // NEED HELP to get into unique month/day/hour/minute
timeSeriesCollectionTemps.addSeries(timeSeries);
}
As shown here, you can instantiate Minute or any suitable RegularTimePeriod to get the desired effect. As shown here, you can parse your sample date using SimpleDateFormat or ZonedDateTime; the example below uses the former.
import java.awt.Dimension;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.TimeZone;
import javax.swing.JFrame;
import org.jfree.chart.ChartFactory;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.axis.DateAxis;
import org.jfree.chart.plot.XYPlot;
import org.jfree.chart.ChartPanel;
import org.jfree.chart.renderer.xy.XYLineAndShapeRenderer;
import org.jfree.data.time.Minute;
import org.jfree.data.time.TimeSeries;
import org.jfree.data.time.TimeSeriesCollection;
import org.jfree.data.xy.XYDataset;
/**
* #see https://stackoverflow.com/q/70071299/230513
* #see https://stackoverflow.com/a/12481509/230513
*/
public class TimeTest {
private static XYDataset createDataset() throws ParseException {
SimpleDateFormat f = new SimpleDateFormat("EEE MMM dd HH:mm:ss z yyyy");
TimeSeries series = new TimeSeries("Temperature");
series.add(new Minute(f.parse("Sat Nov 20 12:41:00 PST 2021")), 72.5);
series.add(new Minute(f.parse("Sat Nov 20 13:11:00 PST 2021")), 70.7);
return new TimeSeriesCollection(series);
}
private static JFreeChart createChart(final XYDataset dataset) {
SimpleDateFormat f = new SimpleDateFormat("HH:mm");
f.setTimeZone(TimeZone.getTimeZone("PST"));
JFreeChart chart = ChartFactory.createTimeSeriesChart(
"Test", "Time", "Temperture °F", dataset, true, true, false);
XYPlot plot = (XYPlot) chart.getPlot();
XYLineAndShapeRenderer r = (XYLineAndShapeRenderer) plot.getRenderer();
r.setDefaultShapesVisible(true);
DateAxis domain = (DateAxis) plot.getDomainAxis();
domain.setDateFormatOverride(f);
domain.setVerticalTickLabels(true);
return chart;
}
public static void main(String[] args) throws ParseException {
JFrame f = new JFrame();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
XYDataset dataset = createDataset();
JFreeChart chart = createChart(dataset);
ChartPanel chartPanel = new ChartPanel(chart) {
#Override
public Dimension getPreferredSize() {
return new Dimension(400, 250);
}
};
f.add(chartPanel);
f.pack();
f.setLocationRelativeTo(null);
f.setVisible(true);
}
}
In JFreeChart how can I ask the RangeAxis to be automatically 'optimize' itself?
Right now I have bunch of data to be displayed between 25.00 and 27.00, but the range always starts from 0 which does not look nice:
I've tried to set plot.getRangeAxis().setAutoRange(true), but it does not work. Of course I can calculate my min and max values then later I can set it with setRange(min, max), but is there any bundled function to do this?
Repro:
import org.jfree.chart.ChartPanel;
import org.jfree.chart.ChartFactory;
import org.jfree.chart.JFreeChart;
import org.jfree.ui.ApplicationFrame;
import org.jfree.ui.RefineryUtilities;
import org.jfree.chart.plot.CategoryPlot;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.data.category.DefaultCategoryDataset;
public class LineChart_AWT extends ApplicationFrame {
public LineChart_AWT( String applicationTitle , String chartTitle ) {
super(applicationTitle);
JFreeChart lineChart = ChartFactory.createLineChart(
chartTitle,
"Dates","Temperature",
createDataset(),
PlotOrientation.VERTICAL,
true,true,false);
CategoryPlot plot = (CategoryPlot) lineChart.getPlot();
//plot.getRangeAxis().setRange(25, 27);
//plot.getRangeAxis().setAutoRange(true);
ChartPanel chartPanel = new ChartPanel( lineChart );
chartPanel.setPreferredSize( new java.awt.Dimension( 560 , 367 ) );
setContentPane( chartPanel );
}
private DefaultCategoryDataset createDataset( ) {
DefaultCategoryDataset dataset = new DefaultCategoryDataset( );
dataset.addValue( 26.44,"Temperature","2019-08-18 00:00");
dataset.addValue( 26.2,"Temperature","2019-08-18 01:00");
dataset.addValue( 25.93,"Temperature","2019-08-18 02:00");
dataset.addValue( 25.71,"Temperature","2019-08-18 03:00");
dataset.addValue( 25.54,"Temperature","2019-08-18 04:00");
dataset.addValue( 25.42,"Temperature","2019-08-18 05:00");
dataset.addValue( 25.25,"Temperature","2019-08-18 06:00");
dataset.addValue( 25.19,"Temperature","2019-08-18 07:00");
dataset.addValue( 25.25,"Temperature","2019-08-18 08:00");
dataset.addValue( 25.36,"Temperature","2019-08-18 09:00");
dataset.addValue( 25.52,"Temperature","2019-08-18 10:00");
dataset.addValue( 25.86,"Temperature","2019-08-18 11:00");
dataset.addValue( 26.51,"Temperature","2019-08-18 12:00");
dataset.addValue( 26.82,"Temperature","2019-08-18 13:00");
return dataset;
}
public static void main( String[ ] args ) {
LineChart_AWT chart = new LineChart_AWT(
"X-axis demo" ,
"Y-range is wrong");
chart.pack( );
RefineryUtilities.centerFrameOnScreen( chart );
chart.setVisible( true );
}
}
In addition, it must be specified with NumberAxis#setAutoRangeIncludesZero whether the zero-value should be contained in the displayed interval:
plot.getRangeAxis().setAutoRange(true); // uncomment
((NumberAxis)plot.getRangeAxis()).setAutoRangeIncludesZero(false); // add
Results in:
I am trying to read in a .csv file but I am stuck on how once I've read each line on the csv file and how to add it to the XYDataset.
My algorithm is as follows: read in .csv file -> read each line -> add to dataset -> create chart -> output as frame
data.csv has 4 columns: Time, X, Y, Z
How do I add each point (time,x), (time,y), (time,z) to the dataset?
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import org.jfree.chart.ChartFactory;
import org.jfree.chart.ChartPanel;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.data.xy.XYDataset;
import org.jfree.data.xy.XYSeries;
import org.jfree.data.xy.XYSeriesCollection;
import org.jfree.ui.ApplicationFrame;
import org.jfree.ui.RefineryUtilities;
import com.opencsv.CSVReader;
public class Test extends ApplicationFrame
{
XYDataset dataset;
JFreeChart chart;
final ChartPanel chartPanel;
final int chartWidth = 560;
final int chartHeight = 367;
CSVReader reader;
String[] readNextLine;
XYSeries series;
public Test(String applicationTitle) throws IOException
{
super(applicationTitle);
dataset = createDataset();
chart = createChart(dataset);
chartPanel = new ChartPanel(chart);
chartPanel.setPreferredSize(new java.awt.Dimension(chartHeight, chartWidth));
}
public XYDataset createDataset() throws NumberFormatException, IOException
{
dataset = new XYSeriesCollection();
try
{
reader = new CSVReader(new FileReader("data.csv"));
while((readNextLine = reader.readNext()) != null)
{
if (readNextLine != null)
{
//add values to dataset
}
}
}
catch (FileNotFoundException e)
{
System.out.println("File not found!");
}
return dataset;
}
public JFreeChart createChart(XYDataset dataset) throws NumberFormatException, IOException
{
chart = ChartFactory.createXYLineChart(
"Acceleration vs Time", //chart title
"Time", //domain axis label
"Acceleration", //range axis label
createDataset(), //data
PlotOrientation.VERTICAL, //the plot orientation
true, //legend
true, //tooltips
false); //urls
return chart;
}
public static void main(String[] args) throws IOException
{
final Test demo = new Test("Test XY Line chart");
demo.pack();
RefineryUtilities.centerFrameOnScreen(demo);
demo.setVisible(true);
}
}
The first 10 lines of my dataset look like this:
time x y z
0.003 -0.13 0.83 0.6
0.009 -0.12 0.83 0.61
0.012 -0.12 0.82 0.6
0.018 -0.13 0.81 0.61
0.021 -0.13 0.8 0.61
0.025 -0.12 0.8 0.61
0.033 -0.12 0.79 0.6
0.034 -0.11 0.79 0.6
0.039 -0.11 0.79 0.58
0.044 -0.11 0.77 0.57
Where your comment is - you need to first go through the elements of readNextLine where each element of the String[] will contain the value from your file for a given row (assuming your file is correctly formatted as you said). So - you need to turn these into variables. You haven't specified what format these are in, or what data type Time,X, Y and Z are.
Given that you're looking at an XY chart and your axes are labelled Time and acceleration - I'm going to guess that they're all Floating Point numbers, with Time in decimal seconds and the other variables as accelerations in the various axes. NOTE - if this assumption is wrong - you might need to use a different converter such as Integer.valueOf(), but the principle is the same.
before your while loop, set up the series that you want to add to. You'll add to them row by row within the while loop
final XYSeries seriesX = new XYSeries("X");
final XYSeries seriesY = new XYSeries("Y");
final XYSeries seriesZ = new XYSeries("Z");
within the while loop where your comment is
//add values to dataset
double Time = Double.valueOf(readNextLine[0]);
double X = Double.valueOf(readNextLine[1]);
double Y = Double.valueOf(readNextLine[2]);
double Z = Double.valueOf(readNextLine[3]);
seriesX.add(Time, X);
seriesY.add(Time, Y);
seriesZ.add(Time, Z);
and after the while loop add the 3 series to the collection before you return it:
dataset.addSeries(seriesX);
dataset.addSeries(seriesY);
dataset.addSeries(seriesZ);
Lastly - two comments
Your logic is a little weird around dataset creation. In your constructor, you create the dataset and pass it to createChart(). This is quite standard. But you then call createDataset() inline within the call to ChartFactory.createXYLineChart(, thus rendering the first creation of data pointless and doing it all again. It will work, but is wasteful and might be masking something else that you're intending the code to do.
The if check in createDataset() is redundant - you're already in a while loop based on a condition which means it will always be true.
if (readNextLine != null)
{
EDIT - added full working version to clear confusion in several comments
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import org.jfree.chart.ChartFactory;
import org.jfree.chart.ChartPanel;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.data.xy.XYDataset;
import org.jfree.data.xy.XYSeries;
import org.jfree.data.xy.XYSeriesCollection;
import org.jfree.ui.ApplicationFrame;
import org.jfree.ui.RefineryUtilities;
import au.com.bytecode.opencsv.CSVReader;
public class Test extends ApplicationFrame {
/**
*
*/
private static final long serialVersionUID = 1L;
XYSeriesCollection dataset;
JFreeChart chart;
final ChartPanel chartPanel;
final int chartWidth = 560;
final int chartHeight = 367;
CSVReader reader;
String[] readNextLine;
XYSeries series;
public Test(String applicationTitle) throws IOException {
super(applicationTitle);
dataset = createDataset();
chart = createChart(dataset);
chartPanel = new ChartPanel(chart);
chartPanel.setPreferredSize(new java.awt.Dimension(chartHeight,
chartWidth));
this.add(chartPanel);
}
public XYSeriesCollection createDataset() throws NumberFormatException,
IOException {
dataset = new XYSeriesCollection();
try {
reader = new CSVReader(new FileReader("res\\data.csv"),'\t');
// Read the header and chuck it away
readNextLine = reader.readNext();
// Set up series
final XYSeries seriesX = new XYSeries("X");
final XYSeries seriesY = new XYSeries("Y");
final XYSeries seriesZ = new XYSeries("Z");
while ((readNextLine = reader.readNext()) != null) {
// add values to dataset
double Time = Double.valueOf(readNextLine[0]);
double X = Double.valueOf(readNextLine[1]);
double Y = Double.valueOf(readNextLine[2]);
double Z = Double.valueOf(readNextLine[3]);
seriesX.add(Time, X);
seriesY.add(Time, Y);
seriesZ.add(Time, Z);
}
System.out.println(seriesX.getMaxX() + "; " + seriesX.getMaxY());
dataset.addSeries(seriesX);
dataset.addSeries(seriesY);
dataset.addSeries(seriesZ);
} catch (FileNotFoundException e) {
System.out.println("File not found!");
}
return dataset;
}
public JFreeChart createChart(XYDataset dataset)
throws NumberFormatException, IOException {
chart = ChartFactory.createXYLineChart("Acceleration vs Time", // chart
// title
"Time", // domain axis label
"Acceleration", // range axis label
dataset, // data
PlotOrientation.VERTICAL, // the plot orientation
true, // legend
true, // tooltips
false); // urls
return chart;
}
public static void main(String[] args) throws IOException {
System.out.println("In here, to create a Test");
final Test demo = new Test("Test XY Line chart");
System.out.println("Created, pakcking");
demo.pack();
RefineryUtilities.centerFrameOnScreen(demo);
demo.setVisible(true);
}
}
Output with OP supplied first 10 data points
Hello i have to make a program to display power curves, and therefore i need to display three different plots on one window.
The different kind of plots are XY (just points), bar, and XY with lines.
My problem(s) : somehow i can get only two of the charts to get drawn AND i can't change the colors of the single chart correctly.
EDIT : When i put as comment the declaration of the third chart, the second one finally gets drawn. Is it impossible to draw three charts ?
Any help will be greatly appreciated, thanks ;)
import org.jfree.chart.ChartPanel;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.axis.NumberAxis;
import org.jfree.chart.axis.NumberTickUnit;
import org.jfree.chart.axis.ValueAxis;
import org.jfree.chart.plot.DatasetRenderingOrder;
import org.jfree.chart.plot.XYPlot;
import org.jfree.chart.renderer.xy.XYBarRenderer;
import org.jfree.chart.renderer.xy.XYItemRenderer;
import org.jfree.chart.renderer.xy.XYLineAndShapeRenderer;
import org.jfree.data.xy.IntervalXYDataset;
import org.jfree.data.xy.XYSeries;
import org.jfree.data.xy.XYSeriesCollection;
import org.jfree.ui.ApplicationFrame;
public class OverlaidPlot extends ApplicationFrame
{
final XYSeries series0 = new XYSeries("Graph0");
final XYSeries series1 = new XYSeries("Graph1");
final XYSeries series2 = new XYSeries("Graph2");
public OverlaidXYPlotDemo(final String title)
{
super(title);
final JFreeChart chart = createOverlaidChart();
final ChartPanel panel = new ChartPanel(chart, true, true, true, true, true);
panel.setPreferredSize(new java.awt.Dimension(800, 600));
setContentPane(panel);
}
public void addElem0(double x, double y)
{
this.series0.add(x, y);
}
public void addElem1(double x, double y)
{
this.series1.add(x, y);
}
public void addElem2(double x, double y)
{
this.series2.add(x, y);
}
private JFreeChart createOverlaidChart()
{
final NumberAxis domainAxis = new NumberAxis("Speed (m/s)");
final ValueAxis rangeAxis = new NumberAxis("Power (kw)");
// create plot ...
final IntervalXYDataset data0 = createDataset0();
final XYItemRenderer renderer0 = new XYBarRenderer(0.20);
// change "new XYBarRenderer(0.20)" to "StandardXYItemRenderer()" if you want to change type of graph
final XYPlot plot = new XYPlot(data0, domainAxis, rangeAxis, renderer0);
// add a second dataset and renderer...
final IntervalXYDataset data1 = createDataset1();
final XYLineAndShapeRenderer renderer1 = new XYLineAndShapeRenderer(false, true);
// arguments of new XYLineAndShapeRenderer are to activate or deactivate the display of points or line. Set first argument to true if you want to draw lines between the points for e.g.
plot.setDataset(1, data1);
plot.setRenderer(1, renderer1);
// add a third dataset and renderer...
final IntervalXYDataset data2 = createDataset2();
final XYLineAndShapeRenderer renderer2 = new XYLineAndShapeRenderer(true, true);
// arguments of new XYLineAndShapeRenderer are to activate or deactivate the display of points or line. Set first argument to true if you want to draw lines between the points for e.g.
plot.setDataset(1, data2);
plot.setRenderer(1, renderer2);
plot.setDatasetRenderingOrder(DatasetRenderingOrder.FORWARD);
NumberAxis domain = (NumberAxis) plot.getDomainAxis();/*
domain.setRange(0.00, 30);*/
domain.setTickUnit(new NumberTickUnit(0.5));
domain.setVerticalTickLabels(true);
// return a new chart containing the overlaid plot...
return new JFreeChart("Test", JFreeChart.DEFAULT_TITLE_FONT, plot, true);
}
private IntervalXYDataset createDataset0()
{
// create dataset 0...
final XYSeriesCollection coll0 = new XYSeriesCollection(series0);
return coll0;
}
private IntervalXYDataset createDataset1()
{
// create dataset 1...
final XYSeriesCollection coll1 = new XYSeriesCollection(series1);
return coll1;
}
private IntervalXYDataset createDataset2()
{
// create dataset 2...
final XYSeriesCollection coll2 = new XYSeriesCollection(series2);
return coll2;
}
}
You have two datasets at the same index within the plot - make sure you're setting each dataset to a unique index:
plot.setDataset(2, data2);
plot.setRenderer(2, renderer2);
After changing this, I ran your example with some test data and was able to see all three data sets plotted.
I'm trying to have two axes on the same data.
The data is a couple of DefaultTableXYDatasets. The plot is a XYPlot, and I have two XYLineAndShapeRenderers and one StackedXYAreaRenderer2.
All data is in meters for the y-values, and I want to have one axis displaying it in meters and one axis displaying it in feet. Now this feels like a common thing to do, but I can't decide on the most obvious way to do it. One way that works would be to duplicate the data and have the y-values in feet, then add another NumberAxis and be done with it.
But I thought it would be wiser to subclass NumberAxis, or inject some functionality into NumberAxis to scale the values. Or should I go with the first approach?
What do you think?
To avoid duplicating data, you can use the XYPlot method mapDatasetToRangeAxes() to map a dataset index to a list of axis indices. In the example below, meters is the principle axis, and the range of the corresponding feet axis is scaled accordingly, as shown here. Note that invokeLater() is required to ensure that the feet axis is scaled after any change in the meters axis.
import java.awt.EventQueue;
import java.util.Arrays;
import java.util.List;
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.NumberAxis;
import org.jfree.chart.axis.ValueAxis;
import org.jfree.chart.event.AxisChangeEvent;
import org.jfree.chart.plot.XYPlot;
import org.jfree.chart.renderer.xy.XYItemRenderer;
import org.jfree.chart.renderer.xy.XYLineAndShapeRenderer;
import org.jfree.data.xy.XYDataset;
import org.jfree.data.xy.XYSeries;
import org.jfree.data.xy.XYSeriesCollection;
/**
* #see https://stackoverflow.com/q/13358758/230513
*/
public class AxisTest {
private static final int N = 5;
private static final double FEET_PER_METER = 3.28084;
private static XYDataset createDataset() {
XYSeriesCollection data = new XYSeriesCollection();
final XYSeries series = new XYSeries("Data");
for (int i = -N; i < N * N; i++) {
series.add(i, i);
}
data.addSeries(series);
return data;
}
private JFreeChart createChart(XYDataset dataset) {
NumberAxis meters = new NumberAxis("Meters");
NumberAxis feet = new NumberAxis("Feet");
ValueAxis domain = new NumberAxis();
XYItemRenderer renderer = new XYLineAndShapeRenderer();
XYPlot plot = new XYPlot(dataset, domain, meters, renderer);
plot.setRangeAxis(1, feet);
plot.setRangeAxisLocation(1, AxisLocation.BOTTOM_OR_LEFT);
List<Integer> axes = Arrays.asList(0, 1);
plot.mapDatasetToRangeAxes(0, axes);
scaleRange(feet, meters);
meters.addChangeListener((AxisChangeEvent event) -> {
EventQueue.invokeLater(() -> {
scaleRange(feet, meters);
});
});
JFreeChart chart = new JFreeChart("Axis Test",
JFreeChart.DEFAULT_TITLE_FONT, plot, true);
return chart;
}
private void scaleRange(NumberAxis feet, NumberAxis meters) {
feet.setRange(meters.getLowerBound() * FEET_PER_METER,
meters.getUpperBound() * FEET_PER_METER);
}
private void display() {
JFrame f = new JFrame("AxisTest");
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.add(new ChartPanel(createChart(createDataset())));
f.pack();
f.setLocationRelativeTo(null);
f.setVisible(true);
}
public static void main(String[] args) {
EventQueue.invokeLater(() -> {
new AxisTest().display();
});
}
}
Alternatively, you can use a JCheckBox to flip between the two series, meters & feet, as shown in this related example. Using the methods available to XYLineAndShapeRenderer, you can hide a second series' lines, shapes and legend. The series itself must be visible to establish the axis range.
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);
}
});
}
});