Search code examples
javajfreechart

JFreeChart: Dynamic point selection in ChartPanel using ChartMouseListener and mouse move


I have a chart using JFreeChart where after click it put the markers on a related position (see figure below).

What I need is to change the position not after click but on mouse move.

I know that using a module ChartMouseListener I can extend its functionlity.

    ChartMouseListener l = new ChartMouseListener() {
        @Override
        public void chartMouseMoved(ChartMouseEvent e) {
            int newX = e.getTrigger().getX();
            int newY = e.getTrigger().getY();
            System.out.println("chartMouseMoved to " + newX + " " + newY);
        }
        @Override
        public void chartMouseClicked(ChartMouseEvent e) {
        }
    };
    chartPanel.addChartMouseListener(l);

How to set the selected axis in the chart?

JFreeChart withd aditional axis

    package org.jfree.chart.demo;

    import java.awt.Color;
    import java.text.SimpleDateFormat;

    import javax.swing.JPanel;

    import org.jfree.chart.ChartFactory;
    import org.jfree.chart.ChartMouseEvent;
    import org.jfree.chart.ChartMouseListener;
    import org.jfree.chart.ChartPanel;
    import org.jfree.chart.JFreeChart;
    import org.jfree.chart.StandardChartTheme;
    import org.jfree.chart.axis.DateAxis;
    import org.jfree.chart.entity.XYItemEntity;
    import org.jfree.chart.plot.XYPlot;
    import org.jfree.chart.renderer.xy.XYItemRenderer;
    import org.jfree.chart.renderer.xy.XYLineAndShapeRenderer;
    import org.jfree.data.time.Month;
    import org.jfree.data.time.TimeSeries;
    import org.jfree.data.time.TimeSeriesCollection;
    import org.jfree.data.xy.XYDataset;
    import org.jfree.ui.ApplicationFrame;
    import org.jfree.ui.RectangleInsets;
    import org.jfree.ui.RefineryUtilities;

    /**
     * An example of a time series chart. For the most part, default settings are
     * used, except that the renderer is modified to show filled shapes (as well as
     * lines) at each data point.
     */
    public class TimeSeriesChartDemo1 extends ApplicationFrame {

        private static final long serialVersionUID = 1L;

        {
            // set a theme using the new shadow generator feature available in
            // 1.0.14 - for backwards compatibility it is not enabled by default
            ChartFactory
                    .setChartTheme(new StandardChartTheme("JFree/Shadow", true));
        }

        /**
         * A demonstration application showing how to create a simple time series
         * chart. This example uses monthly data.
         * 
         * @param title
         *            the frame title.
         */
        public TimeSeriesChartDemo1(String title) {
            super(title);
            ChartPanel chartPanel = (ChartPanel) createDemoPanel();
            chartPanel.setPreferredSize(new java.awt.Dimension(800, 600));
            setContentPane(chartPanel);
            ChartMouseListener l = new ChartMouseListener() {
                @Override
                public void chartMouseMoved(ChartMouseEvent e) {
                    int newX = e.getTrigger().getX();
                    int newY = e.getTrigger().getY();
                    System.out.println("chartMouseMoved to " + newX + " " + newY);
                }
                @Override
                public void chartMouseClicked(ChartMouseEvent e) {
                }
            };
            System.out.println(chartPanel.getMouseListeners()[0]);
            chartPanel.addChartMouseListener(l);
        }

        /**
         * Creates a chart.
         * 
         * @param dataset
         *            a dataset.
         * 
         * @return A chart.
         */
        private static JFreeChart createChart(XYDataset dataset) {

            JFreeChart chart = ChartFactory.createTimeSeriesChart(
                    "Legal & General Unit Trust Prices", // title
                    "Date", // x-axis label
                    "Price Per Unit", // y-axis label
                    dataset, // data
                    true, // create legend?
                    true, // generate tooltips?
                    true // generate URLs?
                    );

            chart.setBackgroundPaint(Color.white);

            XYPlot plot = (XYPlot) chart.getPlot();
            plot.setBackgroundPaint(Color.lightGray);
            plot.setDomainGridlinePaint(Color.white);
            plot.setRangeGridlinePaint(Color.white);
            plot.setAxisOffset(new RectangleInsets(5.0, 5.0, 5.0, 5.0));
            plot.setDomainCrosshairVisible(true);
            plot.setRangeCrosshairVisible(true);

            XYItemRenderer r = plot.getRenderer();
            if (r instanceof XYLineAndShapeRenderer) {
                XYLineAndShapeRenderer renderer = (XYLineAndShapeRenderer) r;
                renderer.setBaseShapesVisible(true);
                renderer.setBaseShapesFilled(true);
                renderer.setDrawSeriesLineAsPath(true);
            }

            DateAxis axis = (DateAxis) plot.getDomainAxis();
            axis.setDateFormatOverride(new SimpleDateFormat("MMM-yyyy"));

            return chart;

        }

        /**
         * Creates a dataset, consisting of two series of monthly data.
         * 
         * @return The dataset.
         */
        private static XYDataset createDataset() {

            TimeSeries s1 = new TimeSeries("L&G European Index Trust");
            s1.add(new Month(2, 1), .8);
            s1.add(new Month(3, 1), .3);
            s1.add(new Month(4, 1), .8);
            s1.add(new Month(5, 1), .6);
            s1.add(new Month(6, 1), .8);
            s1.add(new Month(7, 1), .3);
            s1.add(new Month(8, 1), .9);
            s1.add(new Month(9, 1), .7);
            s1.add(new Month(10, 1), .2);
            s1.add(new Month(11, 1), .8);
            s1.add(new Month(12, 1), .6);
            s1.add(new Month(1, 2), .9);
            s1.add(new Month(2, 2), .7);
            s1.add(new Month(3, 2), .3);
            s1.add(new Month(4, 2), .9);
            s1.add(new Month(5, 2), .8);
            s1.add(new Month(6, 2), .0);
            s1.add(new Month(7, 2), .8);

            TimeSeries s2 = new TimeSeries("L&G UK Index Trust");
            s2.add(new Month(2, 1), .6);
            s2.add(new Month(3, 1), .2);
            s2.add(new Month(4, 1), .2);
            s2.add(new Month(5, 1), .1);
            s2.add(new Month(6, 1), .6);
            s2.add(new Month(7, 1), .2);
            s2.add(new Month(8, 1), .5);
            s2.add(new Month(9, 1), .7);
            s2.add(new Month(10, 1), .5);
            s2.add(new Month(11, 1), .1);
            s2.add(new Month(12, 1), .3);
            s2.add(new Month(1, 2), .7);
            s2.add(new Month(2, 2), .0);
            s2.add(new Month(3, 2), .6);
            s2.add(new Month(4, 2), .2);
            s2.add(new Month(5, 2), .6);
            s2.add(new Month(6, 2), .8);
            s2.add(new Month(7, 2), .6);

            // ******************************************************************
            // More than demo applications are included with the JFreeChart
            // Developer Guide...for more information, see:
            //
            // > http://www.object-refinery.com/jfreechart/guide.html
            //
            // ******************************************************************

            TimeSeriesCollection dataset = new TimeSeriesCollection();
            dataset.addSeries(s1);
            dataset.addSeries(s2);

            return dataset;

        }

        /**
         * Creates a panel for the demo (used by SuperDemo.java).
         * 
         * @return A panel.
         */
        public static JPanel createDemoPanel() {
            JFreeChart chart = createChart(createDataset());
            ChartPanel panel = new ChartPanel(chart);
            panel.setFillZoomRectangle(true);
            panel.setMouseWheelEnabled(true);
            return panel;
        }

        /**
         * Starting point for the demonstration application.
         * 
         * @param args
         *            ignored.
         */
        public static void main(String[] args) {

            TimeSeriesChartDemo1 demo = new TimeSeriesChartDemo1(
                    "Time Series Chart Demo 1");
            demo.pack();
            RefineryUtilities.centerFrameOnScreen(demo);
            demo.setVisible(true);

        }

    }

Solution

  • There are a couple of ways to draw crosshairs in JFreeChart. For the example you've given, the crosshairs are drawn as part of the chart, so each time the position of the crosshairs changes, the chart needs to be redrawn. Doing that on a mouse move event isn't going to be very efficient.

    An alternative is to use the CrosshairOverlay class. This draws crosshairs as an overlay on an existing chart, which avoids the need to redraw the chart when the crosshair values change. That's going to work better for what you want to do.

    There are a couple of examples of this feature included with the JFreeChart Developer Guide (which we encourage JFreeChart users to buy, so they can help the JFreeChart project financially - only a very small percentage do, but that's human nature).

    Here is one of the examples:

    /* --------------------------
     * CrosshairOverlayDemo1.java
     * --------------------------
     * (C) Copyright 2003-2014, by Object Refinery Limited.
     *
     */
    
    package demo;
    
    import java.awt.BasicStroke;
    import java.awt.BorderLayout;
    import java.awt.Color;
    import java.awt.geom.Rectangle2D;
    import javax.swing.JFrame;
    import javax.swing.JPanel;
    import javax.swing.SwingUtilities;
    import org.jfree.chart.ChartFactory;
    import org.jfree.chart.ChartMouseEvent;
    import org.jfree.chart.ChartMouseListener;
    import org.jfree.chart.ChartPanel;
    import org.jfree.chart.JFreeChart;
    import org.jfree.chart.axis.ValueAxis;
    import org.jfree.chart.panel.CrosshairOverlay;
    import org.jfree.chart.plot.Crosshair;
    import org.jfree.chart.plot.XYPlot;
    import org.jfree.data.general.DatasetUtilities;
    import org.jfree.data.xy.XYDataset;
    import org.jfree.data.xy.XYSeries;
    import org.jfree.data.xy.XYSeriesCollection;
    import org.jfree.ui.RectangleEdge;
    
    /**
     * A demo showing crosshairs that follow the data points on an XYPlot.
     */
    public class CrosshairOverlayDemo1 extends JFrame {
    
        static class MyDemoPanel extends JPanel implements ChartMouseListener {
    
            private ChartPanel chartPanel;
    
            private Crosshair xCrosshair;
    
            private Crosshair yCrosshair;
    
            public MyDemoPanel() {
                super(new BorderLayout());
                JFreeChart chart = createChart(createDataset());
                this.chartPanel = new ChartPanel(chart);
                this.chartPanel.addChartMouseListener(this);
                CrosshairOverlay crosshairOverlay = new CrosshairOverlay();
                this.xCrosshair = new Crosshair(Double.NaN, Color.GRAY, 
                        new BasicStroke(0f));
                this.xCrosshair.setLabelVisible(true);
                this.yCrosshair = new Crosshair(Double.NaN, Color.GRAY, 
                        new BasicStroke(0f));
                this.yCrosshair.setLabelVisible(true);
                crosshairOverlay.addDomainCrosshair(xCrosshair);
                crosshairOverlay.addRangeCrosshair(yCrosshair);
                this.chartPanel.addOverlay(crosshairOverlay);
                add(this.chartPanel);
            }
    
            private JFreeChart createChart(XYDataset dataset) {
                JFreeChart chart = ChartFactory.createXYLineChart(
                        "CrosshairOverlayDemo1", "X", "Y", dataset);
                return chart;
            }
    
            private XYDataset createDataset() {
                XYSeries series = new XYSeries("S1");
                for (int x = 0; x < 10; x++) {
                    series.add(x, x + Math.random() * 4.0);
                }
                XYSeriesCollection dataset = new XYSeriesCollection(series);
                return dataset;
            }
    
            @Override
            public void chartMouseClicked(ChartMouseEvent event) {
                // ignore
            }
    
            @Override
            public void chartMouseMoved(ChartMouseEvent event) {
                Rectangle2D dataArea = this.chartPanel.getScreenDataArea();
                JFreeChart chart = event.getChart();
                XYPlot plot = (XYPlot) chart.getPlot();
                ValueAxis xAxis = plot.getDomainAxis();
                double x = xAxis.java2DToValue(event.getTrigger().getX(), dataArea, 
                        RectangleEdge.BOTTOM);
                // make the crosshairs disappear if the mouse is out of range
                if (!xAxis.getRange().contains(x)) { 
                    x = Double.NaN;                  
                }
                double y = DatasetUtilities.findYValue(plot.getDataset(), 0, x);
                this.xCrosshair.setValue(x);
                this.yCrosshair.setValue(y);
            }  
    
        }
    
        public CrosshairOverlayDemo1(String title) {
            super(title);
            setContentPane(createDemoPanel());
        }
    
        /**
         * Creates a panel for the demo (used by SuperDemo.java).
         *
         * @return A panel.
         */
        public static JPanel createDemoPanel() {
            return new MyDemoPanel();
        }
    
        public static void main(String[] args) {
            SwingUtilities.invokeLater(new Runnable() {
                @Override
                public void run() {
                    CrosshairOverlayDemo1 app = new CrosshairOverlayDemo1(
                            "JFreeChart: CrosshairOverlayDemo1.java");
                    app.pack();
                    app.setVisible(true);
                }
            });
        }
    
    }