Построение графиков в реальном времени на Java
У меня есть приложение, которое обновляет переменную примерно от 5 до 50 раз в секунду, и я ищу способ нарисовать непрерывный график этого изменения в реальном времени.
Хотя JFreeChart не рекомендуется для такой высокой скорости обновления, многие пользователи все еще говорят, что он работает для них. Я пробовал использоватьэту демонстрацию и модифицировал ее для отображения случайной величины, но, похоже, она все время использует 100% - ную загрузку процессора. Даже если я проигнорирую это, я не хочу ограничиваться только этим. Класс пользовательского интерфейса JFreeChart для построения форм (хотя я не уверен, каковы именно его возможности). Можно ли интегрировать его с "формами" Java и выпадающими меню? (как доступны в VB) в противном случае, есть ли какие-либо альтернативы, которые я мог бы рассмотреть?
EDIT: я новичок в Swing, поэтому я собрал код только для тестирования функциональности JFreeChart с его помощью (избегая использования класса ApplicationFrame JFree, так как я не уверен, как это будет работать с Комбо-боксы и кнопки Swing). Прямо сейчас график обновляется немедленно, и загрузка процессора высока. Можно ли буферизировать значение с помощью new Millisecond() и обновлять его, возможно, дважды в секунду? Кроме того, могу ли я добавить другие компоненты к остальной части JFrame, не нарушая JFreeChart? Как бы я это сделал? рамка.getContentPane ().добавить ("создать" ("нажать")) кажется, перезаписать график.
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);
}
}
}
}
}
8 ответов:
Если ваша переменная обновляется так быстро, нет смысла обновлять диаграмму каждый раз.
Думали ли вы о буферизации изменений переменных и обновлении диаграммы в другом потоке, скажем, каждые 5 секунд ? Вы должны обнаружить, что JFreeChart хорошо справляется с такими темпами обновления.
Поскольку JFreeChart-это обычная настольная библиотека, вы можете очень легко интегрировать ее со стандартным приложением Swing. Или вы можете использовать его для построения диаграмм через веб-приложение (путем рендеринга в формате JPEG / PNG и т.д. JFreeChart также может автоматически генерировать карты изображений, поэтому вы можете использовать наведение курсора мыши и т. д.)
Согласно этому сообщению в блоге:
Http://jonathanwatmough.com/2008/02/prototyping-code-in-clojure/
Возможно реализовать отображение звуковых спектров в реальном времени с помощью библиотеки KJ DSP:
Http://sirk.sytes.net/software/libs/kjdss/index.htm
Поэтому, если вы можете обойтись довольно простыми диаграммами, это может быть альтернативой JFreeChart.
Если данные обновляются чаще, чем вы можете создать диаграмму, то у вас должна быть задача в отдельном потоке, которая восстанавливает диаграмму и запускает другую регенерацию, когда она будет выполнена. Нет никакого смысла запускать его чаще, чем это, но если это окажется слишком большой нагрузкой на процессор, вы можете уменьшить частоту, с которой он перезапускается. Если обновления не поступают, вы не запускаете повторную генерацию. Я сделал что-то подобное в моем проекте Zocalo недавно. Он делает все, кроме обратного дросселирования.
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; } }
Ответил перед здесь . Ваша переменная изменяется до 50 раз в секунду, но в большинстве случаев вам не нужно будет обновлять каждый раз, когда вносится изменение. Вместо этого вы можете регулярно обновлять график (например, каждые 100 мс).
Ну, я также использую JFreechart для высоких обновлений. JFreeChart обновляет до 10-15 кадров в секунду, но использует 100% - ную загрузку процессора. Но если я хочу обновить его с гораздо большей частотой, он не будет обновляться. Если вы найдете любую библиотеку, которая может быть обновлена на abt 20 fps и может быть использована для разработки приложения на Java, пожалуйста, предложите мне также. Я видел много библиотек JFREECHART FAQ, но я не уверен, что кто-то может быть использован для обновлений со скоростью около 20 кадров в секунду.
Вы должны попробовать диаграммы из VisualVM (часть JDK). Вступление к нему: http://java.dzone.com/news/real-time-charts-java-desktop
Для того, чтобы ваш процессор был значительно ниже 100% и позволял вашему графическому интерфейсу оставаться отзывчивым, вы должны снизить скорость обновления диаграммы. Максимальная скорость обновления около 24 кадров в секунду имеет смысл для графика реального времени; любой более быстрый более или менее неразличим в любом случае. Если ваши данные поступают быстрее, чем эта скорость, вам просто нужно буферизировать их в фоновом режиме и обновить диаграмму на переднем плане с требуемой скоростью обновления. В следующем примере я использую XChart вместе с фоновой нитью
SwingWorker
. Захват данных моделируется со скоростью один на каждые 5 мс, а график обновляется со скоростью 24 кадра в секунду. Эта концепция должна работать с JFreeCharts или любой другой библиотекой диаграмм, а также с небольшими изменениями. Отказ от ответственности: я ведущий разработчик 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) { } } } }