Search code examples
javadynamicjfreechart

How to make plotting with "jfreecharts" more dynamic?


Introduction

My goal is to input a set of products and choose an option for display such as:

System.out.print("Input number of products to represent: ");
      prodNum = scan.nextInt();
      String[] prodName = new String[prodNum];
System.out.print("Enter which products to represent: ");
     for (int i = 0; i < prodName.length; i++)
         {
           prodName[i] = scan.next();
         }
System.out.println("----Options----");
System.out.println("1. Cantidad");
System.out.println("2. Calidad");
System.out.println("3. RealmQ");
System.out.println("4. Coste");
      option = scan.nextInt();                

      userOptionWas(option);

then by using JFreeCharts I wanted to be able to choose how many lines (for each product I input) and what option to display (quantity, quality and stuff) which is stored inside some obviously named arrays.

My approach

  private XYDataset multipleLine(int prodNum, String[] prodName, int option){

   XYSeriesCollection dataset = new XYSeriesCollection();

   XYSeries product3 = new XYSeries("product3");
   XYSeries product4 = new XYSeries("product4");
   XYSeries product5 = new XYSeries("product5");
   XYSeries product6 = new XYSeries("product6");
   XYSeries product7 = new XYSeries("product7");
   XYSeries product8 = new XYSeries("product8");

   switch(prodNum)
   {
       case(2):
       {

           if (option == 1)
           {
                XYSeries product1 = new XYSeries(prodName[0]);
                XYSeries product2 = new XYSeries(prodName[1]);

                for (int i = 0; i < CSVinput.cantidad.length; i++)
                {
                    try
                    {
                        if (CSVinput.nombre[i].equals(prodName[0]))
                        {                            
                            product1.add(i, CSVinput.cantidad[i]);
                        }
                    }catch(ArrayIndexOutOfBoundsException e){}
                    }
           }
           if (option == 2)
           {

           }
           if (option == 3)
           {

           }
           if (option == 4)
           {

           }

       }
   }       
   return dataset;
 }

Halfway through implementing my idea I started to wonder if it is not possible to do it, or just I am in the wrong path to my goal.

What I expect

  • I want to be able to create different plots from a simple menu I showed before.
  • If it is possible, dynamic (if I input 2 products the program displays 2 lines referring to each product)

Extra Notes

  • I am new at JFreeCharts.

  • Take a moment here at product1.add(i, CSVinput.cantidad[i]); where I use (int)"i" instead of my (String)"date" format. Why I am not able to use a String? Is there any way to bypass this?

What I expect from this question

I would like to know more efficient and friendly ways to achieve this without too much unnecessary complexity.

Data Example

2018/12/29-Tejido,321 908,13.55,43.18,$15.98,
2018/12/29-Ropa,195 045,20.55,45.93,$123.01,
2018/12/29-Gorra de visera,126 561,17.43,42.32,$79.54,
2018/12/29-Cerveza,80 109,3.37,17.93,$12.38,
2018/12/29-Mercancías de playa,75 065,11.48,39.73,$105.93,
2018/12/29-Bebidas alcohólicas,31 215,4.84,27.90,$32.29,
2018/12/29-Artículos de cuero,19 098,23.13,44.09,$198.74,
2018/12/29-Bolsas y carteras,7 754,23.09,41.34,$1 176.54,
2018/12/30-Tejido,252 229,12.86,43.14,$18.87,
2018/12/30-Ropa,132 392,18.09,46.02,$177.58,
2018/12/30-Gorra de visera,87 676,14.42,42.46,$122.48,
2018/12/30-Cerveza,44 593,2.72,17.79,$18.71,
2018/12/30-Mercancías de playa,44 593,8.26,39.56,$200.78,
2018/12/30-Bebidas alcohólicas,27 306,4.30,23.88,$31.95,
2018/12/30-Artículos de cuero,16 147,21.08,43.91,$207.49,
2018/12/30-Bolsas y carteras,6 552,21.11,40.59,$1 195.41,
2019/01/02-Tejido,321 908,13.55,43.18,$15.98,
2019/01/02-Ropa,195 045,20.55,45.93,$123.01,
2019/01/02-Gorra de visera,126 561,17.43,42.32,$79.54,
2019/01/02-Cerveza,80 109,3.37,17.93,$12.38,
2019/01/02-Mercancías de playa,75 065,11.48,39.73,$105.93,
2019/01/02-Bebidas alcohólicas,31 215,4.84,27.90,$32.29,
2019/01/02-Artículos de cuero,19 098,23.13,44.09,$198.74,
2019/01/02-Bolsas y carteras,7 754,23.09,41.34,$1 176.54,
2019/01/03-Tejido,1 164 607,12.87,43.14,$15.54,
2019/01/03-Ropa,131 409,17.18,45.97,$161.17,
2019/01/03-Gorra de visera,79 242,13.54,43.17,$100.38,
2019/01/03-Cerveza,48 332,2.80,18.10,$17.48,
2019/01/03-Mercancías de playa,46 157,8.70,38.39,$180.54,
2019/01/03-Bebidas alcohólicas,25 210,4.04,23.72,$33.52,
2019/01/03-Artículos de cuero,14 321,19.56,39.92,$216.00,
2019/01/03-Bolsas y carteras,5 814,19.85,39.68,$1 227.29,

EDIT:

Thanks a lot for the help I think I have learnt but the way is never ending as by some reason, the instance which takes the dataset does not plot the dataset What I am missing?.

PD: Let me know in the comments if you need CSVinput in order to provide a solution.

import java.awt.Color;
import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.time.ZonedDateTime;
import java.util.Date;
import java.util.Scanner;
import org.jfree.chart.ChartFactory;
import org.jfree.chart.ChartPanel;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.plot.XYPlot;
import org.jfree.data.general.SeriesException;
import org.jfree.data.time.Day;
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;

import webscrapper.CSVinput;

/**
 *
 * @author Jonathan
 */
public class TimeSeriesChartExample extends ApplicationFrame{

    //To parse dates below and be able to debug outside the loop
    public static String[] dateSplit;

    //Instace that declares properties and uses the dataset to create the chart
    public TimeSeriesChartExample(String title) 
    {
       super(title);
       // Create dataset
       XYDataset dataset = createDataset();
       // Create chart
       JFreeChart chart = ChartFactory.createTimeSeriesChart(
           "Titulo", // Chart
           "Date", // X-Axis Label
           "Number", // Y-Axis Label
           dataset);

        //Changes background color
        XYPlot plot = (XYPlot)chart.getPlot();
        plot.setBackgroundPaint(new Color(255,228,196));  
    }
    //Create the set of data to be plotted
    private XYDataset createDataset() 
    {
        //A collection of datasets
        TimeSeriesCollection dataset = new TimeSeriesCollection();
        //The object were this new series are added thus it's name
        TimeSeries prod1 = new TimeSeries("Cantidad");

            //"For each" name stored in the array
            for (int i = 0; i < CSVinput.nombre.length; i++)
            {
                //If name equals 
                if ("Ropa".equals(CSVinput.nombre[i]))
                {
                    //Pass the "i" position to work it
                    dateSplit = CSVinput.fecha[i].split("/");
                        //Parse the date
                        int day = Integer.parseInt(dateSplit[2]);
                        int month = Integer.parseInt(dateSplit[1]);
                        int year = Integer.parseInt(dateSplit[0]);
                        System.out.println("day is: " + day);
                    //Pass the parsed date and the value from CSV.cantidad at "i" position referring to the name "Ropa"
                    prod1.add(new Day(day, month, year), CSVinput.cantidad[i]);
                    System.out.println(CSVinput.cantidad[i]);//Debug stuff
                }         
            }                    
            //Add everything to the dataset
            dataset.addSeries(prod1);     
        //Return it
        return dataset;
    }
    private XYDataset createDataset2()
    {
        //A collection of datasets
        TimeSeriesCollection dataset = new TimeSeriesCollection();
        //The object were this new series are added thus it's name
        TimeSeries prod2 = new TimeSeries("Cantidad");

            //"For each" name stored in the array
            for (int i = 0; i < CSVinput.nombre.length; i++)
            {
                //If name equals 
                if (CSVinput.nombre[i] == "Tejido")
                {
                    //Pass the "i" position to work it
                    dateSplit = CSVinput.fecha[i].split("/");
                        //Parse the date
                        int day = Integer.parseInt(dateSplit[0]);
                        int month = Integer.parseInt(dateSplit[1]);
                        int year = Integer.parseInt(dateSplit[2]);
                    //Pass the parsed date and the value from CSV.cantidad at "i" position referring to the name "Ropa"
                    prod2.add(new Day(day, month, year), CSVinput.cantidad[i]);
                    System.out.println(CSVinput.cantidad[i]);//Debug stuff
                }         
            }                    
            //Add everything to the dataset
            dataset.addSeries(prod2);     
        //Return it
        return dataset;
    }

    public static void main(String[] args) throws FileNotFoundException
    {       
       //Custom class to import data from the csv into arrays (TODO: make it dynamic)
       CSVinput.ImportData("caca.csv");

       /* //More debugg stuff
       dateSplit = CSVinput.fecha[1].split("/");
       System.out.println("year: " + dateSplit[0] + " month: " + dateSplit[1] + " day: " + dateSplit[2]);
       */

       //Create an instance of the previous class 
       final TimeSeriesChartExample example = new TimeSeriesChartExample("ItWorks!");
       example.pack(); //Part from the ApplicationFrame
       RefineryUtilities.centerFrameOnScreen(example); //Render the graph at the center of screen
       example.setVisible(true);  //make it visible
    }
    }

Solution

  • First, focus on you program's data model:

    • If your series include a time domain, consider using a TimeSeriesCollection of TimeSeries rather than XYSeries; TimeSeriesChartDemo1, cited here, is a basic example; parse dates as shown here and here; for database access, consider a JDBCXYDataset.

    • As you read the data for each series, store the series in a List<TimeSeries>, rather than an array; if latency is a problem, do the parsing in the background, as shown here.

    The exact details will depend on your application's design, but using the observer pattern will minimize complexity; the basic principle is to update the model (Dataset) and the listening view (JFreeChart) will update itself in response. Using Swing for example, you can control the displayed chart in several ways:

    • Use your List<TimeSeries> to construct a suitable ListModel<TimeSeries>; for the series menu, make your chosen model a DefaultComboBoxModel<TimeSeries> for a JComboBox or a DefaultListModel<TimeSeries> for a JList.

    • Add a suitable listener to your menu component; give your XYPlot a TimeSeriesCollection; use addSeries() or removeSeries() to alter the plot as a series is selected; alternatively, use setSeriesVisible() to toggle visibility, as shown here.

    For a command-line interface, the interactions are less complex but also less versatile:

    • Use a List<TimeSeries> to build your TimeSeriesCollection.

    • Use the TimeSeriesCollection methods to control what the listening XYPlot displays.