2009-09-07 82 views
13

Tengo una aplicación que actualiza una variable de entre 5 y 50 veces por segundo y estoy buscando una forma de dibujar un gráfico XY continuo de este cambio en tiempo real.Gráficos en tiempo real en Java

Aunque JFreeChart no se recomienda para una tasa de actualización tan alta, muchos usuarios todavía dicen que funciona para ellos. Intenté usar la demo this y la modifiqué para que mostrara una variable aleatoria, pero parece consumir el 100% del uso de la CPU todo el tiempo. Incluso si ignoro eso, no quiero estar restringido a la clase ui de JFreeChart para construir formularios (aunque no estoy seguro de cuáles son sus capacidades exactamente). ¿Sería posible integrarlo con los "formularios" y menús desplegables de Java? (como están disponibles en VB) De lo contrario, ¿hay alguna alternativa que pueda analizar?

EDIT: Soy nuevo en Swing, por lo que he juntado un código sólo para probar la funcionalidad de JFreeChart con ella (evitando el uso de la clase de ApplicationFrame jfree ya que no estoy seguro de cómo eso funcionará con los cuadros y botones combinados de Swing). En este momento, el gráfico se está actualizando de inmediato y el uso de la CPU es alto. ¿Sería posible almacenar el valor con un nuevo Milisegundo() y actualizarlo tal vez dos veces por segundo? Además, ¿puedo agregar otros componentes al resto de JFrame sin interrumpir JFreeChart? ¿Como podría hacerlo? frame.getContentPane(). add (nuevo Botón ("Hacer clic")) parece sobrescribir el gráfico.

package graphtest; 

import java.util.Random; 
import javax.swing.JFrame; 
import org.jfree.chart.ChartFactory; 
import org.jfree.chart.ChartPanel; 
import org.jfree.chart.JFreeChart; 
import org.jfree.chart.axis.ValueAxis; 
import org.jfree.chart.plot.XYPlot; 
import org.jfree.data.time.Millisecond; 
import org.jfree.data.time.TimeSeries; 
import org.jfree.data.time.TimeSeriesCollection; 

public class Main { 
    static TimeSeries ts = new TimeSeries("data", Millisecond.class); 

    public static void main(String[] args) throws InterruptedException { 
     gen myGen = new gen(); 
     new Thread(myGen).start(); 

     TimeSeriesCollection dataset = new TimeSeriesCollection(ts); 
     JFreeChart chart = ChartFactory.createTimeSeriesChart(
      "GraphTest", 
      "Time", 
      "Value", 
      dataset, 
      true, 
      true, 
      false 
     ); 
     final XYPlot plot = chart.getXYPlot(); 
     ValueAxis axis = plot.getDomainAxis(); 
     axis.setAutoRange(true); 
     axis.setFixedAutoRange(60000.0); 

     JFrame frame = new JFrame("GraphTest"); 
     frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 
     ChartPanel label = new ChartPanel(chart); 
     frame.getContentPane().add(label); 
     //Suppose I add combo boxes and buttons here later 

     frame.pack(); 
     frame.setVisible(true); 
    } 

    static class gen implements Runnable { 
     private Random randGen = new Random(); 

     public void run() { 
      while(true) { 
       int num = randGen.nextInt(1000); 
       System.out.println(num); 
       ts.addOrUpdate(new Millisecond(), num); 
       try { 
        Thread.sleep(20); 
       } catch (InterruptedException ex) { 
        System.out.println(ex); 
       } 
      } 
     } 
    } 

} 

Respuesta

7

Si su variable se actualiza tan rápido, no tiene sentido actualizar un gráfico cada vez.

¿Ha pensado en almacenar en búfer los cambios de variables y actualizar el gráfico en un hilo diferente, por ejemplo, cada 5 s? Debería encontrar que JFreeChart puede manejar bien estas tasas de actualización.

Dado que JFreeChart es una biblioteca de escritorio normal, puede integrarlo con una aplicación Swing estándar muy fácilmente. O bien, puede usarlo para trazar a través de una aplicación web (al renderizar a JPEG/PNG, etc. JFreeChart también puede generar mapas de imágenes automáticamente, para que pueda usar mouseover etc.)

+0

Eso parece una buena idea, aunque tendría que amortiguar algún tipo de marca de tiempo también. Sin embargo, JFreeChart parece ocupar el 100% del uso de la CPU si tengo 2 o 50 actualizaciones por segundo. – thodinc

+2

Bueno, está haciendo bastante - regenerando el gráfico desde cero cada vez. Así que recomendaría una actualización un poco menos frecuente (por supuesto depende de su máquina y su carga adicional ...) –

0

Respondido antes de here. Su variable cambia hasta 50 veces por segundo, pero en la mayoría de los casos no necesitará actualizar cada vez que se realice un cambio. En su lugar, puede actualizar el gráfico a intervalos regulares (cada 100 ms, por ejemplo).

+0

Ya he visto algunas de las otras bibliotecas mencionadas en ese hilo, pero no hay demasiadas que puedo integrar con los cuadros desplegables de Java y otros objetos. Todos parecen lanzarse como aplicaciones separadas. Actualmente estoy mirando JRobin, que podría funcionar, pero es un poco más complejo de usar. – thodinc

1

Si los datos se actualizan con más frecuencia de la que puede generar el gráfico, entonces debe tener una tarea en un subproceso separado que regenera el gráfico, y comienza otra regeneración cuando termina. No tiene mucho sentido ejecutarlo más de una vez, pero si resulta ser una carga de CPU demasiado alta, puede reducir la frecuencia con la que se reinicia. Si las actualizaciones no entran, no desencadena la regeneración. Hice algo así en mi Zocalo project recientemente. Hace todo menos la limitación.

package net.commerce.zocalo.freechart; 

// Copyright 2009 Chris Hibbert. All rights reserved. 

// This software is published under the terms of the MIT license, a copy 
// of which has been included with this distribution in the LICENSE file. 

import java.util.concurrent.atomic.AtomicBoolean; 
import java.util.concurrent.ExecutorService; 
import java.util.concurrent.Executors; 
import java.util.concurrent.Callable; 
import java.util.concurrent.Future; 
import java.util.Map; 
import java.util.HashMap; 

/** Schedule a task like generating a price history graph. Multiple requests may come 
in sporadically. We want to ensure that only one is being processed at a time. If we're 
busy processing when a request comes in, we'll remember to start another when this one is 
done. Multiple requests that come in while processing will spur a single restart. */ 
public class ChartScheduler { 
    static private Logger log = Logger.getLogger(ChartScheduler.class); 
    static private Map<String, ChartScheduler> schedulers = new HashMap<String, ChartScheduler>(); 
    private AtomicBoolean generating = new AtomicBoolean(false); 
    private AtomicBoolean requested = new AtomicBoolean(false); 
    private ExecutorService threads = Executors.newCachedThreadPool(); 
    private Callable<Boolean> callable; 
    private int runs = 0; 
    private String name; 


    private ChartScheduler(String name, final Runnable worker) { 
     this.name = name; 
     callable = new Callable<Boolean>() { 
      public Boolean call() throws Exception { 
       worker.run(); 
       runs++; 
       restartIfNeeded(); 
       return true; 
      } 
     }; 
    } 

    public static ChartScheduler create(String name, Runnable worker) { 
     ChartScheduler sched = find(name); 
     if (sched == null) { 
      sched = new ChartScheduler(name, worker); 
      schedulers.put(name, sched); 
     } 
     return sched; 
    } 

    public static ChartScheduler find(String name) { 
     return schedulers.get(name); 
    } 

    public boolean generateNewChart() { 
     requested.set(true); 
     if (generating.compareAndSet(false, true)) { 
      startNewThread(); 
      return true; 
     } else { 
      return false; 
     } 
    } 

    private Future<Boolean> startNewThread() { 
     generating.set(true); 
     requested.set(false); 

     return threads.submit(callable); 
    } 

    private boolean restartIfNeeded() { 
     generating.set(false); 
     if (requested.get()) { 
      return generateNewChart(); 

     } else { 
      return false; 
     } 
    } 

    public boolean isBusy() { 
     return generating.get(); 
    } 

    public int runs() { 
     return runs; 
    } 
} 
0

Quizás pueda usar dos hilos. Uno para la actualización de su prioridad de bruja variable es 10. Y un segundo hilo que pinta tan rápido como sea posible es igual a 5.

Tuve que hacer lo mismo en un juego que estoy escribiendo.

Es posible que no haya entendido su pregunta.

0

Bueno, también estoy usando JFreechart para actualizaciones altas. JFreeChart actualiza hasta 10 a 15 cuadros/segundo pero usa 100% de uso de CPU. Pero si quiero actualizarlo a una frecuencia mucho más alta, no se actualizará.Si encuentra alguna biblioteca que pueda actualizarse a 20 fps y se puede usar para desarrollar una aplicación en Java, sugiérame también. He visto muchos archivos de la biblioteca JFreeChart FAQ, pero no estoy seguro de si alguien podría usar actualizaciones de aproximadamente 20 fps.

0

Con el fin de obtener su CPU por debajo del 100% y permitir que su GUI permanezca receptiva, tiene que reducir la velocidad de actualización de su gráfico. Una velocidad máxima de actualización de alrededor de 24 fotogramas por segundo tiene sentido para un gráfico en tiempo real; cualquier más rápido es más o menos indistinguible de todos modos. Si sus datos llegan más rápido que esa tasa, solo necesita almacenarlos en segundo plano y actualizar su tabla en primer plano a la tasa de actualización deseada. En el siguiente ejemplo, uso XChart junto con un hilo de fondo SwingWorker. La captura de datos se simula a una velocidad de uno por cada 5 ms y la tabla se actualiza a 24 fotogramas por segundo. Este concepto debería funcionar con JFreeCharts o cualquier otra biblioteca de gráficos con pequeñas modificaciones. Descargo de responsabilidad: soy el desarrollador principal de XChart.

import java.util.LinkedList; 
import java.util.List; 

import javax.swing.SwingWorker; 

import org.knowm.xchart.QuickChart; 
import org.knowm.xchart.SwingWrapper; 
import org.knowm.xchart.XYChart; 

/** 
* Creates a real-time chart using SwingWorker 
*/ 
public class SwingWorkerRealTime { 

    MySwingWorker mySwingWorker; 
    SwingWrapper<XYChart> sw; 
    XYChart chart; 

    public static void main(String[] args) throws Exception { 

    SwingWorkerRealTime swingWorkerRealTime = new SwingWorkerRealTime(); 
    swingWorkerRealTime.go(); 
    } 

    private void go() { 

    // Create Chart 
    chart = QuickChart.getChart("SwingWorker XChart Real-time Demo", "Time", "Value", "randomWalk", new double[] { 0 }, new double[] { 0 }); 
    chart.getStyler().setLegendVisible(false); 
    chart.getStyler().setXAxisTicksVisible(false); 

    // Show it 
    sw = new SwingWrapper<XYChart>(chart); 
    sw.displayChart(); 

    mySwingWorker = new MySwingWorker(); 
    mySwingWorker.execute(); 
    } 

    private class MySwingWorker extends SwingWorker<Boolean, double[]> { 

    LinkedList<Double> fifo = new LinkedList<Double>(); 

    public MySwingWorker() { 

     fifo.add(0.0); 
    } 

    @Override 
    protected Boolean doInBackground() throws Exception { 

     while (!isCancelled()) { 

     fifo.add(fifo.get(fifo.size() - 1) + Math.random() - .5); 
     if (fifo.size() > 500) { 
      fifo.removeFirst(); 
     } 

     double[] array = new double[fifo.size()]; 
     for (int i = 0; i < fifo.size(); i++) { 
      array[i] = fifo.get(i); 
     } 
     publish(array); 

     try { 
      Thread.sleep(5); 
     } catch (InterruptedException e) { 
      // eat it. caught when interrupt is called 
      System.out.println("MySwingWorker shut down."); 
     } 

     } 

     return true; 
    } 

    @Override 
    protected void process(List<double[]> chunks) { 

     System.out.println("number of chunks: " + chunks.size()); 

     double[] mostRecentDataSet = chunks.get(chunks.size() - 1); 

     chart.updateXYSeries("randomWalk", null, mostRecentDataSet, null); 
     sw.repaintChart(); 

     long start = System.currentTimeMillis(); 
     long duration = System.currentTimeMillis() - start; 
     try { 
     Thread.sleep(40 - duration); // 40 ms ==> 25fps 
     // Thread.sleep(400 - duration); // 40 ms ==> 2.5fps 
     } catch (InterruptedException e) { 
     } 

    } 
    } 
} 

XChart SwingWorker Realtime Java Chart