How to write a java library that would display graphs (charts) with large datasets (100-200 thousand points)? I think that such a large graph does not need to be displayed entirely, I probably only need to display a part of it, for example, a thousand points. But I need to be able to move around the graph and scale it.
It is also necessary that the charts are built in real time. As I understand it, for any action with the chart, the data must be displayed again, that is, thereby updated.
Maybe for speed it will be possible to do parallel execution of some parts of the code for drawing the chart. Because the speed of execution will be the main thing in this library.
I also plan to convert the chart to svg to be inserted into the html page.
Now I just build a graph using JavaFX, and it is very slow to build on a couple of thousand points and JavaFX does not seem to have tools for scaling.
public class FXLineChart {
private List<Long> dataset;
public FXLineChart(List<Long> dataset) {
this.dataset = dataset;
}
public void drawChart(Stage stage) {
stage.setTitle("Chart");
final NumberAxis xAxis = new NumberAxis();
final NumberAxis yAxis = new NumberAxis();
final LineChart<Number, Number> lineChart =
new LineChart<Number, Number>(xAxis, yAxis);
lineChart.setTitle("Check dataset");
XYChart.Series series = new XYChart.Series();
for (int i = 0; i < dataset.size(); i++) {
series.getData().add(new XYChart.Data(i, dataset.get(i)));
}
lineChart.getData().add(series);
Scene scene = new Scene(lineChart, 800, 600);
stage.setScene(scene);
stage.show();
}
}
JFreeChart is slightly better, but the chart slows down at several tens of thousands of points. I did not find information about real time in JFreeChart.
Maybe someone has a code for a similar task or idea? The challenge is that according to the assignment it should be its own library.
Related
#Override
public void start(Stage stage) {
// The class Random is used to randomize the dice rolls
Random random = new Random();
NumberAxis xAxis = new NumberAxis();
// y-axes represents the average of the rolls. The average is always between [1-6]
NumberAxis yAxis = new NumberAxis(1, 6, 1);
LineChart<Number, Number> lineChart = new LineChart<>(xAxis, yAxis);
// removing elements of the chart, e.g. circles on points
lineChart.setLegendVisible(false);
lineChart.setAnimated(false);
lineChart.setCreateSymbols(false);
// we create a variable representing the data and add it to the chart
XYChart.Series average = new XYChart.Series();
lineChart.getData().add(average);
new AnimationTimer() {
private long previous;
private long sum;
private long count;
#Override
public void handle(long current) {
if (current - previous < 100_000_000L) {
return;
}
previous = current;
// roll the dice
int number = random.nextInt(6) + 1;
// we grow the sum and increment the count
sum += number;
count++;
// we add a new data point to the chart
average.getData().add(new XYChart.Data(count, 1.0 * sum / count));
}
}.start();
Scene scene = new Scene(lineChart, 400, 300);
stage.setScene(scene);
stage.show();
}
I am doing the mooc.fi Java course, right now at part 14, and there was an example code I wanted to try out. This is the problematic part of the code.
When I paste this into the DiceRolling class (that extends Application) above the main method, the IDE loses all its functionalities. What it means is that Ctrl+Space shows no suggestion nowhere in the code, if I select part of the code with mouse I cannot Ctrl+X it out, stops checking if the code has mistakes in it (talking about the red underline), and it just overall stops checking the code. I could narrow the reason down to the new ActionTimer part of the code. The problem occurs only after the first curly bracket is written there.
I have experienced this problem earlier, at that time I was creating a new ChangeListener inside a lambda expression and the first curly bracket caused the problem there too. I have no idea why it happens, or if it has a meaning or it's just a bug.
I've discovered what I believe is a memory leak in JavaFX (1.8u40 GA and 1.8u60 b10 EA) BarChart triggered by replacing all the data values in the series. This is exasperated by our application which does this several times a second.
Using jvisualvm shows an uncontrolled growth in the number of javafx.scene.layout.StackPane instances which eventually results in an OutOfMemoryError. Styled StackPane nodes are used internally by BarChart for the bars.
I've tried a different strategies to update the list. All exhibit the same issue.
// 1
series.getData().clear();
series.getData().addAll(list);
// 2
series.getData().setAll(list);
// 3
series.setData(list)
Interestingly the example in the Oracle BarChart tutorial updates values by adding all the bars/points first, and then mutating them using XYChart.Data.setYValue(). This could work, but is not convenient for us as the number of data points can vary dynamically.
Specific questions
Is there anyway to avoid this issue, other than a extra logic using the setYValue() approach above.
Have I stumbled across an actual memory leak in JavaFX? or just an artefact of my misuse of the API? Surely freeing internal nodes when data is updated is JavaFX responsibility
Example
public class ChartUpdate extends Application {
private int clock;
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage stage) {
CategoryAxis xAxis = new CategoryAxis();
NumberAxis yAxis = new NumberAxis(0, 100, 10);
yAxis.setAutoRanging(false);
BarChart<String, Number> graph = new BarChart<>(xAxis, yAxis);
graph.setAnimated(false);
Series<String, Number> series = new Series<>();
graph.getData().add(series);
stage.setScene(new Scene(graph));
stage.show();
Timeline timeLine = new Timeline();
timeLine.getKeyFrames().add(
new KeyFrame(Duration.millis(500),
(e) -> {
ObservableList<Data<String, Number>> list = FXCollections.observableArrayList();
for (int i = 0; i < 100; i++) {
list.add(new Data<>(String.valueOf(i), (clock + i) % 100));
}
series.setData(list);
clock++;
}));
timeLine.setCycleCount(Animation.INDEFINITE);
timeLine.play();
}
}
Update (16th April 2015) Raised https://bugs.openjdk.java.net/browse/JDK-8094805 and posted to OpenJFX mailing list
In summary this was a genuine issue accepted by JavaFX team and fixed by JDK-8094805 in 8u60
See https://bugs.openjdk.java.net/browse/JDK-8094805 for more detail.
I would like to cut down in code by using the same variable name for two possible objects (JavaFX Chart objects in this case). The reason for this is because after this for loop some processing to the chart happens that is identically.
I tried downcasting the chart but it gives me an "unknown class" error.
Chart chart;
if (chartClass.equals(LineChart.class))
chart = new LineChart<Number, Number>(xAxis, yAxis);
else
chart = new AreaChart<Number, Number>(xAxis, yAxis);
for (int i = 0; i < variablesToPlot.length; i++) {
series[i] = new LineChart.Series<Number, Number>();
// this chart must be LineChart or AreaChart (but not general chart) for getData() to work
((chartClass) chart).getData().add(series[i]);
}
// here some chart processing that is equal for both charts
What I am looking for kind of sounds like dynamic typing (which Java is not capable of as far as I know), but is there a way to solve this problem other than writing the code twice?
Change the declaration of Chart chart to XYChart chart.
The common superclass of LineChart and AreaChart is XYChart.
You will then be able to cast it to XYChart and use the getData method on it.
It will result in:
XYChart chart;
if (chartClass.equals(LineChart.class))
chart = new LineChart<Number, Number>(xAxis, yAxis);
else
chart = new AreaChart<Number, Number>(xAxis, yAxis);
for (int i = 0; i < variablesToPlot.length; i++) {
series[i] = new LineChart.Series<Number, Number>();
// this chart must be LineChart or AreaChart (but not general chart) for getData() to work
chart.getData().add(series[i]);
}
First off, I am new to Java and to Stackoverflow. So I hope I can supply enough clarity in my question.
My goal is to create a box plot using jfreechart to keep track of measurement values from every day use. I want to do this by storing minimal amount of data ie. by storing statists of mean, standard deviation, median, 1Q,3Q, min and maximum. This should then be visualized by a box plot for each day measured.
I have looked at the box plot demo here
http://www.java2s.com/Code/Java/Chart/JFreeChartBoxAndWhiskerDemo.htm
In this demo they create the dataset and add all the values to the dataset, then adds it to the plot. The dataset itself contains methods to return the mean, median etc. of the dataset to be able to create the plot. See the code below for a snip from the demo in the link above.
DefaultBoxAndWhiskerCategoryDataset dataset = new DefaultBoxAndWhiskerCategoryDataset();
//some type of algorithm to add values to the dataset
dataset.add(//values, series and type here);
// Return the finished dataset
CategoryAxis xAxis = new CategoryAxis("Type");
NumberAxis yAxis = new NumberAxis("Value");
yAxis.setAutoRangeIncludesZero(false);
BoxAndWhiskerRenderer renderer = new BoxAndWhiskerRenderer();
renderer.setFillBox(false);
renderer.setToolTipGenerator(new BoxAndWhiskerToolTipGenerator());
CategoryPlot plot = new CategoryPlot(dataset, xAxis, yAxis,
renderer);
JFreeChart chart = new JFreeChart("Box-and-Whisker Demo",
new Font("SansSerif", Font.BOLD, 14), plot, true);
So my question is, how should I do to just add the median, Q1,Q3, mean, minimum and maximum values to create the box plot? Because in the demo above they base the plot of a complete sample set.
You can create your own dataset class and use it to create the chart.
Create your own implementation of BoxAndWhiskerCategoryDataset and use it in place of DefaultBoxAndWhiskerCategoryDataset.
I am making use of the JFreeChart library to plot the progress of a genetic algorithm in real time.
I'm using Swing for the UI. I have a panel where I draw all the various parameters for the algorithm, and a ChartPanel object. This object is drawn before I call the algorithm's search method (which updates the chart's XYSeries object at each generation), and at the end of the search, with all the values being accurately plotted.
According to the docs, the ChartPanel object is redrawn when its respective chart is updated. Obviously, the Swing panel itself isn't being redrawn until after the search is done, and I call repaint(), but what can I do to fix this?
This is the chart code:
public class XYSeriesDemo {
private static final long serialVersionUID = 1L;
private XYSeries series;
private JFreeChart chart;
public XYSeriesDemo(String str) {
series = new XYSeries(str);
XYSeriesCollection data = new XYSeriesCollection(series);
chart = createChart(data);
}
public XYSeries getSeries() {
return series;
}
public ChartPanel getChartPanel() {
return new ChartPanel(chart);
}
private JFreeChart createChart(final XYDataset data) {
JFreeChart chart = ChartFactory.createXYLineChart(
"Best fitness across generations",
"Generation",
"Fitness",
data,
PlotOrientation.VERTICAL,
true,
true,
false
);
XYPlot plot = chart.getXYPlot();
ValueAxis axis = plot.getDomainAxis();
axis.setAutoRange(true);
axis = plot.getRangeAxis();
axis.setAutoRange(true);
return chart;
}
}
In my panel constructor, I'm doing the following (this gets an empty chart drawn):
demo = new XYSeriesDemo("Best fitness");
this.add(demo.getChartPanel());
This is the method that the Swing frame calls in my JPanel object when the user orders a search:
public void solve() {
gen = new Random(seed);
XYSeries s = demo.getSeries();
GeneticAlgorithm ga = new GeneticAlgorithm(pop, crossoverP, mutationP,
eliteSize, maxGens, gen, s);
best = ga.search();
state = State.SOLVED;
time = ga.getTime() / 1E9;
}
At each generation, the search method in the algorithm simply does:
series.add(generation, pop.getBestFitness());
Thank you for reading.
Make sure that you are updating the dataset or series for the chart, ideally directly. The chart should refresh itself.
I would recommend buying the JFreeChart developer guide as it includes all sorts of examples including dynamic charts. The cost of the developer guide is what supports JFreeChart development.
I think you call your search process in EDT because of that it can't repaint components.
For updating your panel from code try to use SwingWorker, it can update UI and continue background process. You can find a lot of examples of using in Internet.
Or you can try to use Executors for background search process and updating UI.
I think this will work for you
JFreeChart jf = // Your JFreeChart chart Object.
ChartPanel chartPanel = new ChartPanel(jf);
myPanel.add(chartPanel);
myPanel.repaint();
myPanel.revalidate();