Are jfreechart-fx 1.0.1 charts interactable with or just an image built and presented by fxgraphics2d?

I am currently trying to figure out, howjfreechart‘s splitting into swing(1.5) and JavaFX (1.0.1) affects the JavaFX part. As far as I (very limited knowledge on this topic) understand The jfree-fx uses fxgraphics2d to draw its original swing components(?) into an FX-canvas to add this into JavaFX nodes.

Now my question is, if that fxgraphics2d object is still interactable with? I mean things like tooltips and scrolling and similar stuff normal jfreecharts offer. Since my knowledge and time is somewhat limited, I would want to know, if it is worth digging deeper into jfree-fx (if those charts are still interactable with) or if those charts are solely pictures of the actual chart and not interactable with. Then I would need to find a better solution.

I am currently learning how to build a candlestick chart within my java application. While I managed to build a chart only using JavaFX, its performance was really bad as soon as there were drawn some hundred candlesticks.

Then I came across jfreechart, of which I read, that its performance is well above the internal JavaFX charting possibilities. So today I managed to construct my first chart with jfreechart-fx and performance is quite ok. Further I find it much more intuitive to build those charts… but I am not sure if the jfree-fx version only prints images or real chart objects to the nodes. (I read somewhere something about converting a chart into an image to increase performance of drawing…)

Thank you for any information on that topic.

For example here is my JFreeChart Class, which is drawn correctly, but I just do not get any interaction with the chart with my mouse. E.g. I’d like to zoom in/out using the mousewheel and I’d like to pan the chart to the left/right by clickhold leftmouse. That why I am concerned that I am only looking at an image right now. All viable solutions I find through google seem to address only 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;
    }
}

Answer

I haven’t looked at jFreeChart in detail, but I think the main difference between it and the in-built JavaFX charting API is that jFreeChart uses a canvas for its implementation, whereas the in-built charts use the scene graph. Roughly, though not exactly, its similar to this definition of a retained mode (scene graph) vs immediate mode (canvas).

It’s probably possible to add interactivity to canvas rendered graphics. It is likely technically challenging beyond basic whole-chart zoom and drag ops. Implementing or demonstrating the addition of such interactivity with canvas rendered graphics is beyond what I would be prepared to do within the context of a StackOverflow answer.

JFreeChart includes an interaction package:

I suggest you investigate the interaction package, try using it and see if it offers the level of interaction you need.

As Roger mentions in the comments, you can get some basic interaction on a JFreeChartFX chart by wrapping the chart in a ChartViewer using ChartViewer(JFreeChart myChart).

Related question:

An aside on Canvas vs SceneGraph

This info on how canvas works is included so that you might have a better idea of what is going on here (note everything here may not be 100% correct, but is close enough to help understanding).

Technically, JavaFX only uses a SceneGraph for rendering. How canvas is internally implemented, as far as I understand, is that each canvas is a node in the scenegraph and comes with a command queue of drawing instructions. When you draw to the canvas, it doesn’t draw immediately, instead it puts the drawing commands into a queue, then, at some point, before the next 60fps drawing pulse completes, it renders those to an image buffer that it relays into a JavaFX node. Old commands are forgotten by the canvas command queue once executed, so everything just ends up as pixels eventually. You can keep track of drawing commands within your application and re-issue them to repaint the canvas from scratch if you wish, but canvas won’t help with that.

What JFreeChartFX is doing is providing an adapter which makes a JavaFX canvas look like a Swing painting surface, so that the heavy lifting and internal engine of JFreeChart can be used to issue all of the drawing commands, and those can be rendered to either a JavaFX canvas or Swing canvas depending upon the desired output UI tech.

If JFreeChart also provided a similar adapter for JavaFX events, rather than Swing events, and if JFreeChart already has a way to do interactivity using Swing events, then it could potentially add interactivity to JFreeChartFX using a similar adapter or replacement for Swing event mechanisms. Perhaps that is what the interaction package linked above is doing.

Leave a Reply

Your email address will not be published. Required fields are marked *