Multiple X and multiple Y axis

A discussion forum for JFreeChart (a 2D chart library for the Java platform).
Locked
mrseguin
Posts: 4
Joined: Fri Mar 25, 2005 12:11 pm

Multiple X and multiple Y axis

Post by mrseguin » Wed Feb 08, 2017 11:22 am

Hello,
I am trying and I failed to succeed to make my chart :-(
I have 3 trends of different units : one weight, one temperature, one (stirring) speed this will need 3 different range axis. And later will come more data series linked to this series/units.
The first domain axis is a Time axis : I have several sample of data measurement, one sample of all series, at the same moment.
And up to that, I have no problem to make my chart. I am lucky.

Then comes a second domain axis : the second domain axis is the number of hours relative to the beginning time.

Example :

- domain axis 0 = 2017-01-15 14:00, domain axis 1 = 0,0 hours
- domain axis 0 = 2017-01-15 15:00, domain axis 1 = 1,0 hours
- domain axis 0 = 2017-01-16 15:00, domain axis 1 = 25,0 hours
etc.

But I am failing to align the 0,0 hours to the 14:00 on the chart, and same for the greatest time that I am failing to align.

I hope that someone can give me a tip to which Jfree class or method I have to use to realize this.


Here is my code and screen captures :

Here is the overview of the whole chart : https://1drv.ms/i/s!Am8iOaZq2pJfhG2wUjyiUFWhbe5J

Here is the detail of what is not ok : https://1drv.ms/i/s!Am8iOaZq2pJfhG7lCrgxthALu5aT

And here is a short source code to make this, based on MultipleAxisDemo3.java
---------------------------------------------------------------------------------

Code: Select all

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import javax.swing.JPanel;

import org.jfree.chart.ChartFactory;
import org.jfree.chart.ChartPanel;
import org.jfree.chart.ChartUtilities;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.axis.AxisLocation;
import org.jfree.chart.axis.NumberAxis;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.chart.plot.XYPlot;
import org.jfree.chart.renderer.xy.XYLineAndShapeRenderer;
import org.jfree.data.time.Second;
import org.jfree.data.time.TimeSeries;
import org.jfree.data.time.TimeSeriesCollection;
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;

public class MultipleXYaxis2 extends ApplicationFrame {


    public MultipleXYaxis2(String title) {
        super(title);
        JPanel chartPanel = createDemoPanel();
        chartPanel.setPreferredSize(new java.awt.Dimension(1000, 600));
        setContentPane(chartPanel);
    }

    private static JFreeChart createChart() {

         // DATASET 0 = Weight, DOMAIN AXIS 0 = Time, RANGE AXIS 0 = Weight
        XYDataset dataset0Weight = createDatasetDomainTime("Weight (Kg)", COLUMN_WEIGHT );

        JFreeChart chart = ChartFactory.createTimeSeriesChart(
            "Multiple X and Y axis",
            "Time of the day",
            "Weight",
            dataset0Weight,
            true,
            true,
            false);

        XYPlot plot = (XYPlot) chart.getPlot();
        plot.setOrientation(PlotOrientation.VERTICAL);

          // DATASET 2 = Temperatures, DOMAIN AXIS 1 = Hours, RANGE AXIS 1 = Temperature

        NumberAxis xAxis1 = new NumberAxis("Hours since start");
        plot.setDomainAxis(1, xAxis1);
        plot.setDomainAxisLocation(1, AxisLocation.BOTTOM_OR_LEFT);
        /*XYSeries serieXY = new XYSeries("Hours since start");
        serieXY.add(0.0,0);
        serieXY.add(54.3827,0);
        xAxis1.setLowerBound(0);
        xAxis1.setUpperBound(54.3827);
        xAxis1.setRange(0,54.3827);
        xAxis1.setAutoRange(true);
        xAxis1.setLowerMargin(0);
        xAxis1.setUpperMargin(54.3827);
        xAxis1.setAutoRangeIncludesZero(true);
        xAxis1.setAutoRangeStickyZero(true);
        xAxis1.setAutoRange(true);
        XYSeriesCollection datasetXY = new XYSeriesCollection();
        datasetXY.addSeries(serieXY);
        plot.setDataset(3, datasetXY);
        plot.mapDatasetToDomainAxis(3, 1);*/
             
        
        NumberAxis yAxis1Temp = new NumberAxis("Temperature");
        plot.setRangeAxis(1, yAxis1Temp);
        plot.setRangeAxisLocation(1, AxisLocation.BOTTOM_OR_RIGHT);
        
        XYDataset datasetTemperature = createDatasetDomainHours("Temperature", COLUMN_TEMPERATURE_PROD);
        plot.setDataset(2, datasetTemperature);
        plot.mapDatasetToDomainAxis(2, 1);
        plot.mapDatasetToRangeAxis(2, 1);
        
        // DATASET 1 = Stirrer, DOMAIN AXIS 0 = Time, RANGE AXIS 2 = Stirrer
        
        NumberAxis yAxis2stirrers = new NumberAxis("Stirrer RPM");
        plot.setRangeAxis(2, yAxis2stirrers);
        plot.setRangeAxisLocation(2, AxisLocation.BOTTOM_OR_RIGHT);

        XYDataset datasetStirrer = createDatasetDomainTime("Inner stirrer", COLUMN_INNER_STIRRER);
        plot.setDataset(1, datasetStirrer);
        plot.mapDatasetToDomainAxis(1, 0);
        plot.mapDatasetToRangeAxis(1, 2);
        
        plot.setRenderer(1, new XYLineAndShapeRenderer(true, false));
        plot.setRenderer(2, new XYLineAndShapeRenderer(true, false));
        plot.setRenderer(3, new XYLineAndShapeRenderer(true, false));
        ChartUtilities.applyCurrentTheme(chart);

        return chart;

    }

    static int COLUMN_WEIGHT           = 0;
    static int COLUMN_TEMPERATURE_PROD = 1;
    static int COLUMN_INNER_STIRRER  = 2;
        
    static float dataSeries[][] = { 
            {100f,  70f, 20f },
            {102f,  65f, 15f },
            {110f,  65f, 15f },
            {123f,  70f, 15f },
            {123f,  75f, 20f },
            {110f, 100f,  5f },
            { 90f,  22f,  0f }
                            };
    static String timeSeries[] = { "2017-01-31 13:00:04",
                             "2017-01-31 18:00:00",
                             "2017-01-31 22:00:00",
                             "2017-02-01 02:22:00",
                             "2017-02-01 06:22:00",
                             "2017-02-01 07:00:00",
                             "2017-02-01 11:22:58"
                           };
    static SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
                
    private static Second sampleSecond(int i) {
            Date d = new Date();

            try {
                d = df.parse(timeSeries[i]);
            } catch(ParseException e) {  }
            Second s = new Second(d);
            return s;
    }
    
    private static Double sampleHour(long startTime, int i) {
            Date d = new Date();
            Double hours;
            try {
                d = df.parse(timeSeries[i]);
            } catch(ParseException e) {  }
            long timeSample = d.getTime();
            hours = ((double) (timeSample - startTime)) / (double) (60 * 60 * 1000);
            return hours;
    }
        
    // hours since start
    private static XYDataset createDatasetDomainHours(String name, int dataSeriesColumn) {
            Date startDate = new Date();
            long startTime;
            Number sampleTime;
            try {
                startDate = df.parse(timeSeries[0]);
            } catch(ParseException e) {  }
            startTime = startDate.getTime();
            XYSeries series = new XYSeries(name);
            for( int i=0; i < timeSeries.length; i++) {
                sampleTime = sampleHour( startTime,  i );
                series.add( sampleTime, dataSeries[ i ][dataSeriesColumn]);
                //System.out.println(i+" -> "+sampleTime);
            }          
        XYSeriesCollection dataset = new XYSeriesCollection();
        dataset.addSeries(series);
        return dataset;

    }
    private static XYDataset createDatasetDomainTime(String name, int dataSeriesColumn) {
            TimeSeries series = new TimeSeries( name );
            for( int i=0; i < timeSeries.length; i++) {
                Second s = sampleSecond( i );
                series.add( s, dataSeries[ i ][dataSeriesColumn]);
            }          
        TimeSeriesCollection dataset = new TimeSeriesCollection();
        dataset.addSeries(series);
        return dataset;

    }
    
    
    /**
     * Creates a panel for the demo (used by SuperDemo.java).
     *
     * @return A panel.
     */
    public static JPanel createDemoPanel() {
        JFreeChart chart = createChart();
        return new ChartPanel(chart);
    }

    /**
     * Starting point for the demonstration application.
     *
     * @param args  ignored.
     */
    public static void main(String[] args) {

        MultipleXYaxis2 demo = new MultipleXYaxis2(
                "Based on JFreeChart: MultipleAxisDemo3.java");
        demo.pack();
        RefineryUtilities.centerFrameOnScreen(demo);
        demo.setVisible(true);

    }

}
------------------------------------------------------

david.gilbert
JFreeChart Project Leader
Posts: 11734
Joined: Fri Mar 14, 2003 10:29 am
antibot: No, of course not.
Contact:

Re: Multiple X and multiple Y axis

Post by david.gilbert » Wed Feb 08, 2017 1:19 pm

Hi,

I don't have time right now to suggest a complete solution, but I have a feeling that your axis showing the hours since 14:00 should be a DateAxis and you should use the RelativeDateFormat class to override the tick formatting. There would still be some work to do to ensure the axes align correctly, but I think it will be easier to manage if both the axes are DateAxis instances.
David Gilbert
JFreeChart Project Leader

:idea: Read my blog
:idea: Support JFree via the Github sponsorship program

mrseguin
Posts: 4
Joined: Fri Mar 25, 2005 12:11 pm

Re: Multiple X and multiple Y axis

Post by mrseguin » Mon Feb 13, 2017 3:21 pm

Hello,

Thanks a lot for the tip, this looks like the solution. :-)

I have changed programming to use RelativeDateFormat and it works well.
Then I did a change from RelativeDateFormat.java to create the float/hours format I was needing and works well.

One problem is when I am using Menu > Auto range > Both axis then second domain axis is moving to minus 412740.
Do you know and can you show me the way to overide this Autorange menu, please ?

Thanks,

Michel.

mrseguin
Posts: 4
Joined: Fri Mar 25, 2005 12:11 pm

Re: Multiple X and multiple Y axis

Post by mrseguin » Thu Mar 23, 2017 11:11 am

Hello,
Here is the code to calculate a second X axis from a first X axis.
First X axis is a Number axis : 0,0 Hours is our start, and 50 Hours if our right end.
Second X axis is a date axis calcultated from 0,0 to 50 hours bounds.

This is also a sample for who want multiple X and multiple Y axis together.
Michel.

Code: Select all

/* ----------------------
 * MultipleAxisDemo3.java
 * ----------------------
 * (C) Copyright 2003-2014, by Object Refinery Limited.
 *
 * http://www.object-refinery.com
 *
 */
package javafxchart;

import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import javax.swing.JPanel;

import org.jfree.chart.ChartFactory;
import org.jfree.chart.ChartPanel;
import org.jfree.chart.ChartUtilities;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.axis.Axis;
import org.jfree.chart.axis.AxisLocation;
import org.jfree.chart.axis.DateAxis;
import org.jfree.chart.axis.NumberAxis;
import org.jfree.chart.axis.ValueAxis;
import org.jfree.chart.event.AxisChangeEvent;
import org.jfree.chart.event.AxisChangeListener;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.chart.plot.XYPlot;
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;
import org.jfree.ui.ApplicationFrame;
import org.jfree.ui.RefineryUtilities;

public class MultipleXYaxis5 extends ApplicationFrame {

    static int weightAxisMax = 0;
    static long startTime = 0,
            endTime = 0;

    static NumberAxis xAxis0hours = null;
    static DateAxis xAxis1time = null;
    static NumberAxis yAxis0weight = null;

    public MultipleXYaxis5(String title) {
        super(title);
        JPanel chartPanel = createDemoPanel();
        chartPanel.setPreferredSize(new java.awt.Dimension(1000, 600));
        setContentPane(chartPanel);
    }

    private static JFreeChart createChart() {
        weightAxisMax = 3500;
        startTime = getStartTime();
        // DATASET 0 = Weight, DOMAIN AXIS 0 = Time, RANGE AXIS 0 = Weight
        XYDataset dataset0Weight = createDatasetDomainHours("Weight", COLUMN_WEIGHT);
        JFreeChart chart = ChartFactory.createXYLineChart("Multiple X and Y axis",
                    "Hours since start", "", dataset0Weight, PlotOrientation.HORIZONTAL, true, false, false);

        XYPlot plot = (XYPlot) chart.getPlot();
        xAxis0hours = (NumberAxis) plot.getDomainAxis();
       
       
        plot.setOrientation(PlotOrientation.VERTICAL);
        plot.setDomainCrosshairVisible(true);
        //plot.setRangeCrosshairVisible(true);
        // Attach an AxisChangeListener to the first axis and give that listener a reference to the second axis.
        //In the axisChanged method, retrieve the axis from the AxisChangeEvent, cast that to a ValueAxis, get its lower and upper bound,
        //multiply both with the desired value, and set the bound for the second axis."

        // AxisChangeListener
        // DATASET 2 = Temperatures, DOMAIN AXIS 0 = Time, RANGE AXIS 1 = Temperature

        xAxis1time = new DateAxis("Date and time");
        xAxis1time.setVerticalTickLabels(true);
        DateFormat formatter = new SimpleDateFormat("dd.MM  HH:mm");
        xAxis1time.setDateFormatOverride(formatter);

        plot.setDomainAxis(1, xAxis1time);
        plot.setDomainAxisLocation(1, AxisLocation.BOTTOM_OR_LEFT);
        adjustHoursAxis();
       
        // Range Axis 0 : Weight
       
        yAxis0weight = (NumberAxis) plot.getRangeAxis();
       
        // Range Axis 1  : Temperature
       
        NumberAxis yAxis1Temp = new NumberAxis("Temperature");
        yAxis1Temp.setUpperBound(300.0);
        plot.setRangeAxis(1, yAxis1Temp);
        plot.setRangeAxisLocation(1, AxisLocation.BOTTOM_OR_RIGHT);

        XYDataset datasetTemperature = createDatasetDomainHours("Temperature", COLUMN_TEMPERATURE);
        plot.setDataset(2, datasetTemperature);
        plot.mapDatasetToDomainAxis(2, 0);
        plot.mapDatasetToRangeAxis(2, 1);

        // DATASET 1 = Stirrer, DOMAIN AXIS 0 = Time, RANGE AXIS 2 = Stirrer
        NumberAxis yAxis2stirrer = new NumberAxis("Stirrer RPM");

        yAxis2stirrer.setLowerBound(-50.0);
        yAxis2stirrer.setUpperBound(+50.0);
        plot.setRangeAxis(2, yAxis2stirrer);
        plot.setRangeAxisLocation(2, AxisLocation.BOTTOM_OR_RIGHT);
       
        XYDataset datasetStirrer = createDatasetDomainHours("Stirrer", COLUMN_STIRRER);
        plot.setDataset(1, datasetStirrer);
        plot.mapDatasetToDomainAxis(1, 0);
        plot.mapDatasetToRangeAxis(1, 2);

        plot.setRenderer(1, new XYLineAndShapeRenderer(true, false));
        plot.setRenderer(2, new XYLineAndShapeRenderer(true, false));
        plot.setRenderer(3, new XYLineAndShapeRenderer(true, false));
        ChartUtilities.applyCurrentTheme(chart);

        AxisRangeAdjuster5 adj = new AxisRangeAdjuster5();
        adj.registerAxis(getStartTime(), getEndTime(), (NumberAxis) plot.getDomainAxis(), xAxis1time);
        return chart;

    }

    static int COLUMN_WEIGHT = 0;
    static int COLUMN_TEMPERATURE = 1;
    static int COLUMN_STIRRER = 2;

    static float dataSeries[][] = {
        { 100f, 70f, 20f},
        {1200f, 70f, 20f},
        {2300f, 70f, 45f},
        {3002f, 65f, 45f},
        {3502f, 65f, 30f},
        {3802f, 65f, 30f},
        {3810f, 65f, 15f},
        {3805f, 70f, 15f},
        {3720f, 70f, 0f},
        {2500f, 75f, -20f},
        {1300f, 75f, -20f},
        {1000f, 100f, 5f},
        { 430f, 100f, 5f},
        {  30f, 22f,   0f}
    };
    static String timeSeries[]
            = { "2017-01-31 13:00:04", // ->  0,0 hours
                "2017-01-31 13:30:00",
                "2017-01-31 13:31:00",
                "2017-01-31 18:00:00",
                "2017-01-31 18:01:00",
                "2017-01-31 21:58:00",
                "2017-01-31 22:00:00",
                "2017-02-01 02:22:00",
                "2017-02-01 02:24:00",
                "2017-02-01 06:22:00",
                "2017-02-01 07:22:00",
                "2017-02-01 07:24:00",
                "2017-02-01 13:20:00",
                "2017-02-02 13:22:58"
        };
    static SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");


    private static long sampleTime(String sTime) {
        Date d = null;
        long time = 0;
        try {
            d = df.parse(sTime);
            time = d.getTime();
        } catch (ParseException e) {
            time = -1;
        }
        return time;
    }
   
    private static double sampleHours(int i) {
        Date d = new Date();

        try {
            d = df.parse(timeSeries[i]);
        } catch (ParseException e) {
        }
        double hours = hoursSinceStart(startTime, d.getTime() );
        return hours;
    }
    private static long getStartTime() {
        return sampleTime(timeSeries[0]);
    }

    private static long getEndTime() {
        return sampleTime(timeSeries[timeSeries.length - 1]);
    }


    public static Double hoursSinceStart(long startTime, long thisTime) {
        Double hours = ((double) (thisTime - startTime)) / (double) (60 * 60 * 1000);
        return hours;
    }

    public static Long timeFromHours(long startTime, Double hours) {
        Long time = ( (long) (hours * 60 * 60 * 1000)) + startTime;
        return time;
    }
    static void adjustHoursAxis() {
        xAxis1time.setLowerBound( timeFromHours( startTime, xAxis0hours.getLowerBound() ) );
        xAxis1time.setUpperBound( timeFromHours( startTime, xAxis0hours.getUpperBound() ) );
    }

           
    private static XYDataset createDatasetDomainHours(String name, int dataSeriesColumn) {
        XYSeries series = new XYSeries(name);
        for (int i = 0; i < timeSeries.length; i++) {
            double hours = sampleHours(i);
            series.add(hours, dataSeries[i][dataSeriesColumn]);
        }
        XYSeriesCollection dataset = new XYSeriesCollection();
        dataset.addSeries(series);
        return dataset;

    }

    /**
     * Creates a panel for the demo.
     *
     * @return A panel.
     */
    public static JPanel createDemoPanel() {
        JFreeChart chart = createChart();
        return new ChartPanel(chart);
    }

    /**
     * Starting point for the demonstration application.
     *
     * @param args ignored.
     */
    public static void main(String[] args) {

        MultipleXYaxis5 demo = new MultipleXYaxis5(
                "Based on JFreeChart: MultipleXYaxis5.java");
        demo.pack();
        RefineryUtilities.centerFrameOnScreen(demo);
        demo.setVisible(true);

    }

}

class AxisRangeAdjuster5 implements AxisChangeListener {

    private DateAxis timeAxis = null;
    private ValueAxis hoursAxis = null;

    private ArrayList<ValueAxis> axes = new ArrayList<ValueAxis>();

    private boolean active = true;

    public void registerAxis(long pStartTime, long pEndTime, ValueAxis pHoursAxis, DateAxis pDateAxis ) {
        timeAxis = pDateAxis;
        hoursAxis = pHoursAxis;
        timeAxis.addChangeListener(this);
        hoursAxis.addChangeListener(this);
    }

    public void axisChanged(AxisChangeEvent ace) {

        if (active) {
            Axis a = ace.getAxis();
            if ((a instanceof DateAxis)) {
                DateAxis da = (DateAxis)a;              
                System.out.println("\"" + da.getLabel() + "\" : " + MultipleXYaxis5.df.format(new Date()));
                System.out.println("axisChanged : Time lower : " + timeAxis.getLowerBound() + " upper  :" + timeAxis.getUpperBound());
                System.out.println("axisChanged : Hour lower : " + hoursAxis.getLowerBound() + " upper  :" + hoursAxis.getUpperBound());
                active = false;
                MultipleXYaxis5.adjustHoursAxis();
                active = true;
            } else if ((a instanceof NumberAxis)) {
                NumberAxis na = (NumberAxis)a;
                System.out.println("\"" + na.getLabel() );
                System.out.println("axisChanged : Time lower : " + timeAxis.getLowerBound() + " upper  :" + timeAxis.getUpperBound());
                System.out.println("axisChanged : Hour lower : " + hoursAxis.getLowerBound() + " upper  :" + hoursAxis.getUpperBound());
                active = false;
                MultipleXYaxis5.adjustHoursAxis();
                active = true;
            }
        }
    }
}

Locked