Can Java – jfreechart-fx 1.0.1 diagrams interact with fxgraphics2d built and rendered images?

jfreechart-fx 1.0.1 diagrams interact with fxgraphics2d built and rendered images?… here is a solution to the problem.

jfreechart-fx 1.0.1 diagrams interact with fxgraphics2d built and rendered images?

I’m currently trying to figure out how the split of jfreechart into swing (1.5) and JavaFX (1.0.1) affects the JavaFX part. As far as I understand (with very limited knowledge on this topic) jfree-fx using fxgraphics2d to convert its original Swing component (?) Draw into FX-canvas to add this into the JavaFX node.

Now my question is, can the fxgraphics2d object still interact with it? I mean things like tooltips and scrolling and things like that normal jfreechart provides. Due to my limited knowledge and time, I was wondering if it would be worth digging into jfree-fx if the charts could still interact with it, or if the charts were just pictures of the actual charts and couldn’t interact with them. Then I need to find a better solution.

I am currently learning how to build candlestick charts in my Java application. Although I managed to build a chart using only JavaFX, once hundreds of candlesticks were drawn, it performed very poorly.

Then I came across jfreechart and I read that its performance is much higher than the internal JavaFX chart possibilities. So today I managed to build my first chart with jfreechart-fx and the performance was not bad. Also, I found it more intuitive to build these charts… But I’m not sure if the jfree-fx version only prints images or real chart objects to nodes. (I read somewhere about converting charts to images to improve drawing performance…)

Thank you for any information on this topic.

For example, here is my JFreeChart class, it is drawn correctly, but I can’t use the mouse to interact with the chart at all. For example. I want to zoom in/out using the mouse wheel, I want to pan the chart left/right by clicking the left mouse button. That’s why I’m worried that I’m only looking at images now. All the working solutions I’ve found through Google seem to be for JFreeChart and not JFreeChart-FX.

package org.ezstrats.jfreeChart;

import javafx.collections.ObservableList;
import org.ezstrats.model.chartData.Candlestick;
import org.ezstrats.model.chartData.Chart;
import org.ezstrats.model.chartData.Exchange;
import org.jfree.chart.ChartPanel;
import org.jfree.chart.ChartRenderingInfo;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.axis.DateAxis;
import org.jfree.chart.axis.NumberAxis;
import org.jfree.chart.labels.HighLowItemLabelGenerator;
import org.jfree.chart.labels.StandardXYToolTipGenerator;
import org.jfree.chart.plot.CombinedDomainXYPlot;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.chart.plot.PlotRenderingInfo;
import org.jfree.chart.plot.XYPlot;
import org.jfree.chart.renderer.xy.CandlestickRenderer;
import org.jfree.chart.renderer.xy.XYBarRenderer;
import org.jfree.data.time.FixedMillisecond;
import org.jfree.data.time.TimeSeries;
import org.jfree.data.time.TimeSeriesCollection;
import org.jfree.data.time.ohlc.OHLCSeries;
import org.jfree.data.time.ohlc.OHLCSeriesCollection;

import javax.swing.*;
import java.awt.*;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionAdapter;
import java.text.DateFormat;
import java.text.DecimalFormat;
import java.text.SimpleDateFormat;

public class JFreeCandlestickChart extends JPanel {

private static final DateFormat READABLE_TIME_FORMAT = new SimpleDateFormat("kk:mm:ss");

private OHLCSeries ohlcSeries;
    private TimeSeries volumeSeries;
    private JFreeChart candlestickChart;

public JFreeCandlestickChart(String title) {
        ObservableList<Candlestick> candlesticks = Exchange.getCandlesticks();
         Create new chart
        candlestickChart = createChart(title, candlesticks);
         Create new chart panel
        final ChartPanel chartPanel = new ChartPanel(candlestickChart);
        chartPanel.setPreferredSize(new Dimension(832, 468));
        chartPanel.getChart().getXYPlot().getDomainAxis().setAutoRange(false);
        chartPanel.getChart().getXYPlot().getDomainAxis().setLowerBound(candlesticks.get(candlesticks.size() - 300).getTimestampOpen());
        chartPanel.getChart().getXYPlot().getDomainAxis().setUpperBound(candlesticks.get(candlesticks.size() - 1).getTimestampOpen());
         Enable zooming - not workign?! ...
        chartPanel.setMouseZoomable(true);
        chartPanel.setMouseWheelEnabled(true);
        chartPanel.addMouseMotionListener(new MouseMotionAdapter() {
            @Override
            public void mouseDragged(MouseEvent e) {
                 process before
                super.mouseDragged(e);
                chartPanel.getChart().getXYPlot().getDomainAxis().configure();
                 process after
            }
        });

add(chartPanel, BorderLayout.CENTER);
    }

public JFreeChart createChart(String title, ObservableList<Candlestick> candlesticks){

/**
         * 1st:
         * Creating candlestick subplot
         */
         Create OHLCSeriesCollection as a price dataset for candlestick chart
        OHLCSeriesCollection candlestickDataset = new OHLCSeriesCollection();
        ohlcSeries = new OHLCSeries("Price");
        candlestickDataset.addSeries(ohlcSeries);

 Create candlestick chart priceAxis
        NumberAxis priceAxis = new NumberAxis("Price");
        priceAxis.setAutoRangeIncludesZero(false);

 Create candlestick chart renderer
        CandlestickRenderer candlestickRenderer = new CandlestickRenderer(CandlestickRenderer.WIDTHMETHOD_AVERAGE,
                false,
                new HighLowItemLabelGenerator(new SimpleDateFormat("kk:mm"), new DecimalFormat("0.00000000")));

 Create candlestickSubplot
        XYPlot candlestickSubplot = new XYPlot(candlestickDataset, null, priceAxis, candlestickRenderer);
        candlestickSubplot.setBackgroundPaint(Color.white);

/**
         * 2nd:
         * Creating volume subplot
         */
         creates TimeSeriesCollection as a volume dataset for volume chart
        TimeSeriesCollection volumeDataset = new TimeSeriesCollection();
        volumeSeries = new TimeSeries("Volume");
        volumeDataset.addSeries(volumeSeries);

 Create volume chart volumeAxis
        NumberAxis volumeAxis = new NumberAxis("Volume");
        volumeAxis.setAutoRangeIncludesZero(true);

 Set to no decimal
        volumeAxis.setNumberFormatOverride(new DecimalFormat("0"));

 Create volume chart renderer
        XYBarRenderer timeRenderer = new XYBarRenderer();
        timeRenderer.setShadowVisible(false);
        timeRenderer.setDefaultToolTipGenerator(new StandardXYToolTipGenerator("Volume--> Time={1} Size={2}",
                new SimpleDateFormat("kk:mm"), new DecimalFormat("0")));

 Create volumeSubplot
        XYPlot volumeSubplot = new XYPlot(volumeDataset, null, volumeAxis, timeRenderer);
        volumeSubplot.setBackgroundPaint(Color.white);

/**
         * 3rd:
         * Adding Candles to this chart
         **/
         for (Candlestick candle: candlesticks){
            addCandleToChart(candle.getTimestampOpen(),
                    candle.getPriceOpen(),
                    candle.getPriceHigh(),
                    candle.getPriceLow(),
                    candle.getPriceClose(),
                    candle.getVolumeQuote());
        }

/**
         * 4th:
         * Create chart main plot with two subplots (candlestickSubplot,
         * volumeSubplot) and one common dateAxis
         */
         Creating charts common dateAxis
        DateAxis dateAxis = new DateAxis("Time");
        dateAxis.setDateFormatOverride(new SimpleDateFormat("dd.mm.yy kk:mm"));
        dateAxis.setRange();
         reduce the default left/right margin from 0.05 to 0.02
        dateAxis.setLowerMargin(0.02);
        dateAxis.setUpperMargin(0.02);
        dateAxis.setLabelAngle(0);

 Create mainPlot
        CombinedDomainXYPlot mainPlot = new CombinedDomainXYPlot(dateAxis);
        mainPlot.setGap(10.0);
        mainPlot.add(candlestickSubplot, 4);
        mainPlot.add(volumeSubplot, 1);
        mainPlot.setOrientation(PlotOrientation.VERTICAL);
        mainPlot.setDomainPannable(true);

JFreeChart chart = new JFreeChart(title, JFreeChart.DEFAULT_TITLE_FONT, mainPlot, false);
        chart.removeLegend();

 Einbetten in JScrollPaenl??? um Scrollen zu ermöglichen...
        ChartPanel chartPanel = new ChartPanel(chart);

return chart;
    }

/**
     * Fill series with data.
     *
     * @param c opentime
     * @param o openprice
     * @param h highprice
     * @param l lowprice
     * @param c closeprice
     * @param v volume
     */
    private void addCandleToChart(long time, double o, double h, double l, double c, double v) {
         Add bar to the data. Let's repeat the same bar
        FixedMillisecond t = new FixedMillisecond(time);
        READABLE_TIME_FORMAT.parse(String.valueOf(time)));
        ohlcSeries.add(t, o, h, l, c);
        volumeSeries.add(t, v);
    }

public void setOhlcSeries(OHLCSeries ohlcSeries) {
        this.ohlcSeries = ohlcSeries;
    }
    public void setVolumeSeries(TimeSeries volumeSeries) {
        this.volumeSeries = volumeSeries;
    }
    public OHLCSeries getOhlcSeries() {
        return ohlcSeries;
    }
    public TimeSeries getVolumeSeries() {
        return volumeSeries;
    }
    public JFreeChart getCandlestickChart() {
        return candlestickChart;
    }
}

Solution

I

haven’t looked into jFreeChart in detail, but I think the main difference between it and the built-in JavaFX Charting API is that jFreeChart implements its implementation using Canvas, while built-in charts use scene graphs. Roughly speaking, though not exactly, it is similar to the Retained Mode (Scene Graph) vs Definition of Immediate Mode (Canvas).

Perhaps you can add interactivity to the graphics rendered by Canvas. In addition to basic full-map zoom and drag operations, it can be technically challenging. In the context of the StackOverflow answer, implementing or demonstrating the interactivity of adding such a class to Canvas rendered graphics is beyond what I was prepared to do.

JFreeChart contains an interaction package:

I recommend that you research the interaction package, try using it, and see if it provides the level of interaction you need.

As Roger mentioned in his comments, by wrapping the chart in In ChartViewer, you can get some basic interaction on JFreeChartFX charts. Use ChartViewer (JFreeChart myChart).

Related questions:

Narration of Canvas and SceneGraph

Include information about how Canvas works so you can better understand what’s going on here (note that everything here may not be 100% correct, but close enough to help understand).

Technically, JavaFX only uses SceneGraph for rendering. As far as I know, each canvas is a node in the scenegraph, with a command queue of drawing instructions. When you draw on Canvas, instead of drawing immediately, it puts drawing commands into a queue, and then at some point, before the next 60fps drawing pulse is complete, it renders those to an image buffer and then relays them to a JavaFX node. Once executed, the Canvas command queue forgets the old command, so everything ends up in pixels. You can trace the drawing commands in your application and reissue them as needed to redraw the Canvas from scratch, but Canvas doesn’t help with that.

What JFreeChartFX is doing is providing an adapter that makes JavaFX Canvas look like a Swing painting surface so that JFreeChart’s heavy lifting and internal engine can be used to issue all the drawing commands that can be rendered to either JavaFX Canvas or Swing Canvas, depending on the desired output UI technology.

If JFreeChart also provides a similar adapter for

JavaFX events instead of Swing events, and if JFreeChart already has a way to interact using Swing events, it may use a similar adapter or replacement for Swing events to add interactivity to JFreeChartFX. Maybe that’s what the interaction package linked above is doing.

Related Problems and Solutions