1

I'm simulating dummy real-time data using DynamicTimeSeriesCollection, like this. During random intervals, the data being passed to the plot should 'dropout' to simulate a network connection loss. At this point, this plot should stop drawing and only start plotting the data after the dropout has subsided.

I subclassed XYLineAndShapeRenderer and overrode the getItemLineVisible() method:

@Override
        public boolean getItemLineVisible(int series, int item){
            if(offline){
                return false;
            }else{
                return true;
            }
        }

However when offline is true, all points are still being drawn on the graph.

public class Test extends ApplicationFrame {

    private static final String TITLE = "Dynamic Series";
    private static final String START = "Start";
    private static final String STOP = "Stop";
    private static final int COUNT = 1000*60;
    private static final int FAST = 1; //1000/FAST = occurrences per second real time
    private static final int REALTIME = FAST * 1000;
    private static final Random random = new Random();
    private static final double threshold = 35;
    private double gateStart = ThreadLocalRandom.current().nextInt(0, 101);
    private boolean returning = false;
    private boolean offline = false;
    private Timer timer;
    private Calendar startDate;
    private static final int simulationSpeed = 1000/FAST;
    private final TimeSeries seriesA = new TimeSeries("A");

    public Test(final String title) throws ParseException {
        super(title);

        SimpleDateFormat formatter = new SimpleDateFormat("dd/mm/yyyy HH:mm", Locale.ENGLISH);
        PriceParser parser = new PriceParser();
        List<List<String>> priceData = parser.parse();
        Date date = formatter.parse(priceData.get(0).get(0));
        startDate = Calendar.getInstance();
        startDate.setTime(date);
        Calendar timeBaseStartDate = Calendar.getInstance();
        timeBaseStartDate.setTime(startDate.getTime());
        timeBaseStartDate.add(Calendar.SECOND, -COUNT);
        final TimeSeriesCollection dataset = new TimeSeriesCollection();
        dataset.addSeries(this.seriesA);

        JFreeChart chart = createChart(dataset);
        final JComboBox combo = new JComboBox();
        combo.addItem("Fast");
        combo.addItem("Real-time");
        combo.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                if ("Fast".equals(combo.getSelectedItem())) {
                    timer.setDelay(FAST);
                } else {
                    timer.setDelay(REALTIME);
                }
            }
        });

        JFrame frame = new JFrame("Test");
        JLabel label = new JLabel("Network connectivity lost.");

        this.add(new ChartPanel(chart), BorderLayout.CENTER);
        JPanel btnPanel = new JPanel(new FlowLayout());
        btnPanel.add(combo);
        JPanel test = new JPanel();
        test.add(label);
        this.add(btnPanel, BorderLayout.SOUTH);

        // frame.add(btnPanel);
        //frame.add(test);

        timer = new Timer(FAST, new ActionListener() {
            Date timeToCheck = formatter.parse(priceData.get(0).get(0));
            Calendar pauseResume = Calendar.getInstance();
            Calendar offlineTime = Calendar.getInstance();
            boolean paused = false;
            boolean waiting = false;
            //boolean offline = false;
            double currentPrice;
            float[] newData = new float[1];
            PopupFactory pf = PopupFactory.getSharedInstance();
            Popup popup;

            @Override
            public void actionPerformed(ActionEvent e) {
                Date datasetTime = new Date();
                if(offline){
                    System.out.println("Offline: "+offlineTime.getTime());
                    System.out.println("Current: "+datasetTime);
                    if(offlineTime.getTime().compareTo(datasetTime) == 0){
                        offline = false;
                        System.out.println("Im no longer offline");
                        popup.hide();
                    }
                }

                if(ThreadLocalRandom.current().nextInt(0, 1001) > 999 && !offline){
                    offline = true;
                    offlineTime.setTime(datasetTime);
                    offlineTime.add(Calendar.SECOND, ThreadLocalRandom.current().nextInt(1, 5)*10);

//                    dataset.addValue(0, 0, null);
                    popup = pf.getPopup(btnPanel, label, 900, 300);
                    popup.show();
                }

                if(timeToCheck.compareTo(datasetTime) == 0){
                    currentPrice = Double.valueOf(priceData.get(0).get(1));
                    paused = currentPrice >= threshold;
                    priceData.remove(0);
                    try {
                        timeToCheck = formatter.parse(priceData.get(0).get(0));
                    } catch (ParseException ex) {
                        ex.printStackTrace();
                    }
                }

                if(!paused) {
                    if (Math.round(gateStart) * 10 / 10.0 == 100d) {
                        returning = true;
                    } else if (Math.round(gateStart) * 10 / 10.0 == 0) {
                        returning = false;
                    }
                    if (returning) {
                        gateStart -= 0.1d;
                    } else {
                        gateStart += 0.1d;
                    }
                }else{
                    if(datasetTime.compareTo(pauseResume.getTime()) == 0 && currentPrice < threshold){
                        paused = false;
                        waiting = false;
                    }else{
                        if(Math.round(gateStart)*10/10.0 == 0 || Math.round(gateStart)*10/10.0 == 100){
                            if(!waiting){
                                pauseResume.setTime(datasetTime);
                                pauseResume.add(Calendar.SECOND, 120);
                            }
                            waiting = true;
                        }else{
                            if(Math.round(gateStart)*10/10.0 >= 50){
                                gateStart += 0.1d;
                            }else if(Math.round(gateStart)*10/10.0 < 50){
                                gateStart -= 0.1d;
                            }
                        }
                    }
                }
                newData[0] = (float)gateStart;
                seriesA.addOrUpdate(new Second(), gateStart);
            }
        });
    }

    private JFreeChart createChart(final XYDataset dataset) {
        final JFreeChart result = ChartFactory.createTimeSeriesChart(
                TITLE, "Time", "Shearer Position", dataset, true, true, false);
        final XYPlot plot = result.getXYPlot();
        plot.setDomainZeroBaselineVisible(false);
        XYLineAndShapeRendererTest renderer = new XYLineAndShapeRendererTest(true, false);
        plot.setRenderer(renderer);
        DateAxis domain = (DateAxis)plot.getDomainAxis();

        Calendar endDate = Calendar.getInstance();
        endDate.setTime(new Date());
        endDate.add(Calendar.HOUR_OF_DAY, 12);
        System.out.println(new Date());
        System.out.println(endDate.getTime());
        domain.setRange(new Date(), endDate.getTime());
        domain.setTickUnit(new DateTickUnit(DateTickUnitType.HOUR, 1));
        domain.setDateFormatOverride(new SimpleDateFormat("HH:mm"));

        ValueAxis range = plot.getRangeAxis();
        range.setRange(0, 100);

        return result;
    }

    private class XYLineAndShapeRendererTest extends XYLineAndShapeRenderer {

        private boolean drawSeriesLineAsPath;

        public XYLineAndShapeRendererTest(boolean line, boolean shapes){
            super(line, shapes);
        }

        @Override
        public Paint getItemPaint(int row, int col) {
            if(!offline){
                return super.getItemPaint(row, col);
            }else{
                return new Color(0, 0, 0);
            }

        }
    }

    private void start() {
        timer.start();
    }

    public static void main(final String[] args) {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                Test demo = null; //pass date in from csv
                try {
                    demo = new Test(TITLE);
                } catch (ParseException e) {
                    e.printStackTrace();
                }
                demo.pack();
                RefineryUtilities.centerFrameOnScreen(demo);
                demo.setVisible(true);
                demo.start();
            }
        });
    }
}

What am I doing wrong?

Rigg97
  • 83
  • 1
  • 13
  • First of all why you use all time api :D Change Calendars to something from new Time API like Instant.now() or save your current milis as long by System.currentTimeMilis(); Also I don't know if basing on the times are good idea. You should put some breakpoints in your code and run debugger mode to check which line leads an error. – MrFisherman Jan 10 '21 at 00:59
  • JFreeChart requires Date objects, that is why I'm using Date and Calendar. – Rigg97 Jan 10 '21 at 01:02
  • `JFreeChart` date abstractions in `org.jfree.data.time` are compatible with both `java.time` and `java.util` at the millisecond level, for [example](https://stackoverflow.com/a/45173688/230513). – trashgod Jan 10 '21 at 15:41
  • Why not append a baseline value when offline? – trashgod Jan 10 '21 at 15:42
  • @trashgod do you mind elaborating? I'm not familiar with baseline's and haven't been able to find much on them – Rigg97 Jan 11 '21 at 00:19
  • I've illustrated the approach below. – trashgod Jan 11 '21 at 02:28

1 Answers1

1

One approach would be to append an appropriate baseline value when off line. Starting from this example, the range is centered on a value of zero millivolts. The modification below adds zeroes between 60 and 90 milliseconds:

image

private float[] gaussianData() {
    float[] a = new float[COUNT];
    for (int i = 0; i < a.length; i++) {
        if (i > 60 && i < 90) a[i] = 0;
        else a[i] = randomValue();
    }
    return a;
}

In my instance, a period of offline should effectively stop graphing.

Use the approach suggested here, which uses setMaximumItemAge() to limit the number of displayed records. Add null values, as suggested here, to interrupt the display. Starting from this example, I got this display with these changes:

image

seriesA.setMaximumItemCount(120);
seriesB.setMaximumItemCount(120);
…
int i;
…
public void addNull() {
    this.seriesA.add(new Millisecond(), null);
    this.seriesB.add(new Millisecond(), null);
}

@Override
public void actionPerformed(ActionEvent e) {
    if (i > 60 && i < 90) {
        demo.addNull();
    } else {
        …
    }
    i++;
}
trashgod
  • 203,806
  • 29
  • 246
  • 1,045
  • Hmm I see what you're saying, but in my instance a period of 'offline' should effectively stop graphing any values as the plot in theory should be getting no data sent to it. In essence, a value of 0 would imply the position (y-axis) of what im tracking has moved to its starting point. – Rigg97 Jan 11 '21 at 04:50
  • I've suggested an alternative above. – trashgod Jan 11 '21 at 10:40
  • How are you adding null to a dynamic time series? Mustn't the value be a float? – Rigg97 Jan 11 '21 at 10:45
  • No; a `TimeSeries` admits `null` values. – trashgod Jan 11 '21 at 10:55
  • Ah sorry, I didn't realise you had linked a TimeSeries example. I've changed my code accordingly. I've seemed to have got it plotting however the rate at which it does so is very slow and I'm not entirely sure why – Rigg97 Jan 11 '21 at 11:23
  • I'd suggest getting the `Graph` example working first then [edit] your question to include a [mcve] that shows your revised approach or open a new question if warranted. – trashgod Jan 11 '21 at 11:41
  • Glad you got it sorted. – trashgod Jan 11 '21 at 14:08