org.jfree.chart.fx.ChartCanvas Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of jfreechart Show documentation
Show all versions of jfreechart Show documentation
JFreeChart is a class library, written in Java, for generating charts.
Utilising the Java2D APIs, it currently supports bar charts, pie charts,
line charts, XY-plots and time series plots.
/* ===========================================================
* JFreeChart : a free chart library for the Java(tm) platform
* ===========================================================
*
* (C) Copyright 2000-2014, by Object Refinery Limited and Contributors.
*
* Project Info: http://www.jfree.org/jfreechart/index.html
*
* This library is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or
* (at your option) any later version.
*
* This library is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
* License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
* USA.
*
* [Oracle and Java are registered trademarks of Oracle and/or its affiliates.
* Other names may be trademarks of their respective owners.]
*
* ----------------
* ChartCanvas.java
* ----------------
* (C) Copyright 2014, by Object Refinery Limited and Contributors.
*
* Original Author: David Gilbert (for Object Refinery Limited);
* Contributor(s): -;
*
* Changes:
* --------
* 25-Jun-2014 : Version 1 (DG);
* 19-Jul-2014 : Add clearRect() call for each draw (DG);
*
*/
package org.jfree.chart.fx;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;
import java.util.List;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.control.Tooltip;
import javafx.scene.input.MouseEvent;
import javafx.scene.input.ScrollEvent;
import org.jfree.chart.ChartMouseEvent;
import org.jfree.chart.ChartRenderingInfo;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.entity.ChartEntity;
import org.jfree.chart.event.ChartChangeEvent;
import org.jfree.chart.event.ChartChangeListener;
import org.jfree.chart.fx.interaction.AnchorHandlerFX;
import org.jfree.chart.fx.interaction.DispatchHandlerFX;
import org.jfree.chart.fx.interaction.ChartMouseEventFX;
import org.jfree.chart.fx.interaction.ChartMouseListenerFX;
import org.jfree.chart.fx.interaction.TooltipHandlerFX;
import org.jfree.chart.fx.interaction.ScrollHandlerFX;
import org.jfree.chart.fx.interaction.PanHandlerFX;
import org.jfree.chart.fx.interaction.MouseHandlerFX;
import org.jfree.chart.plot.PlotRenderingInfo;
import org.jfree.chart.util.ParamChecks;
/**
* A canvas for displaying a {@link JFreeChart} in JavaFX. You can use the
* canvas directly to display charts, but usually the {@link ChartViewer}
* class (which embeds a canvas) is a better option.
*
* The canvas installs several default mouse handlers, if you don't like the
* behaviour provided by these you can retrieve the handler by ID and
* disable or remove it (the IDs are "tooltip", "scroll", "anchor", "pan" and
* "dispatch").
*
*
THE API FOR THIS CLASS IS SUBJECT TO CHANGE IN FUTURE RELEASES. This is
* so that we can incorporate feedback on the (new) JavaFX support in
* JFreeChart.
*
* @since 1.0.18
*/
public class ChartCanvas extends Canvas implements ChartChangeListener {
/** The chart being displayed in the canvas (never null). */
private JFreeChart chart;
/**
* The graphics drawing context (will be an instance of FXGraphics2D).
*/
private Graphics2D g2;
/**
* The anchor point (can be null) is usually updated to reflect the most
* recent mouse click and is used during chart rendering to update
* crosshairs belonging to the chart.
*/
private Point2D anchor;
/** The chart rendering info from the most recent drawing of the chart. */
private ChartRenderingInfo info;
/** The tooltip object for the canvas (can be null). */
private Tooltip tooltip;
/**
* A flag that controls whether or not tooltips will be generated from the
* chart as the mouse pointer moves over it.
*/
private boolean tooltipEnabled;
/** Storage for registered chart mouse listeners. */
private transient List chartMouseListeners;
/** The current live handler (can be null). */
private MouseHandlerFX liveHandler;
/**
* The list of available live mouse handlers (can be empty but not null).
*/
private List availableMouseHandlers;
/** The auxiliary mouse handlers (can be empty but not null). */
private List auxiliaryMouseHandlers;
/**
* Creates a new canvas to display the supplied chart in JavaFX.
*
* @param chart the chart ({@code null} not permitted).
*/
public ChartCanvas(JFreeChart chart) {
ParamChecks.nullNotPermitted(chart, "chart");
this.chart = chart;
this.chart.addChangeListener(this);
this.tooltip = null;
this.tooltipEnabled = true;
this.chartMouseListeners = new ArrayList();
widthProperty().addListener(evt -> draw());
heightProperty().addListener(evt -> draw());
this.g2 = new FXGraphics2D(getGraphicsContext2D());
this.liveHandler = null;
this.availableMouseHandlers = new ArrayList();
this.availableMouseHandlers.add(new PanHandlerFX("pan", true, false,
false, false));
this.auxiliaryMouseHandlers = new ArrayList();
this.auxiliaryMouseHandlers.add(new TooltipHandlerFX("tooltip"));
this.auxiliaryMouseHandlers.add(new ScrollHandlerFX("scroll"));
this.auxiliaryMouseHandlers.add(new AnchorHandlerFX("anchor"));
this.auxiliaryMouseHandlers.add(new DispatchHandlerFX("dispatch"));
setOnMouseMoved((MouseEvent e) -> { handleMouseMoved(e); });
setOnMouseClicked((MouseEvent e) -> { handleMouseClicked(e); });
setOnMousePressed((MouseEvent e) -> { handleMousePressed(e); });
setOnMouseDragged((MouseEvent e) -> { handleMouseDragged(e); });
setOnMouseReleased((MouseEvent e) -> { handleMouseReleased(e); });
setOnScroll((ScrollEvent event) -> { handleScroll(event); });
}
/**
* Returns the chart that is being displayed by this node.
*
* @return The chart (never {@code null}).
*/
public JFreeChart getChart() {
return this.chart;
}
/**
* Sets the chart to be displayed by this node.
*
* @param chart the chart ({@code null} not permitted).
*/
public void setChart(JFreeChart chart) {
ParamChecks.nullNotPermitted(chart, "chart");
this.chart.removeChangeListener(this);
this.chart = chart;
this.chart.addChangeListener(this);
draw();
}
/**
* Returns the rendering info from the most recent drawing of the chart.
*
* @return The rendering info (possibly {@code null}).
*/
public ChartRenderingInfo getRenderingInfo() {
return this.info;
}
/**
* Returns the flag that controls whether or not tooltips are enabled.
* The default value is {@code true}. The {@link TooltipHandlerFX}
* class will only update the tooltip if this flag is set to
* {@code true}.
*
* @return The flag.
*/
public boolean isTooltipEnabled() {
return this.tooltipEnabled;
}
/**
* Sets the flag that controls whether or not tooltips are enabled.
*
* @param tooltipEnabled the new flag value.
*/
public void setTooltipEnabled(boolean tooltipEnabled) {
this.tooltipEnabled = tooltipEnabled;
}
/**
* Set the anchor point and forces a redraw of the chart (the anchor point
* is used to determine the position of the crosshairs on the chart, if
* they are visible).
*
* @param anchor the anchor ({@code null} permitted).
*/
public void setAnchor(Point2D anchor) {
this.anchor = anchor;
this.chart.setNotify(true); // force a redraw
}
/**
* Registers a listener to receive {@link ChartMouseEvent} notifications.
*
* @param listener the listener ({@code null} not permitted).
*/
public void addChartMouseListener(ChartMouseListenerFX listener) {
ParamChecks.nullNotPermitted(listener, "listener");
this.chartMouseListeners.add(listener);
}
/**
* Removes a listener from the list of objects listening for chart mouse
* events.
*
* @param listener the listener.
*/
public void removeChartMouseListener(ChartMouseListenerFX listener) {
this.chartMouseListeners.remove(listener);
}
/**
* Returns the mouse handler with the specified ID, or {@code null} if
* there is no handler with that ID. This method will look for handlers
* in both the regular and auxiliary handler lists.
*
* @param id the ID ({@code null} not permitted).
*
* @return The handler with the specified ID
*/
public MouseHandlerFX getMouseHandler(String id) {
for (MouseHandlerFX h: this.availableMouseHandlers) {
if (h.getID().equals(id)) {
return h;
}
}
for (MouseHandlerFX h: this.auxiliaryMouseHandlers) {
if (h.getID().equals(id)) {
return h;
}
}
return null;
}
/**
* Adds a mouse handler to the list of available handlers (handlers that
* are candidates to take the position of live handler). The handler must
* have an ID that uniquely identifies it amongst the handlers registered
* with this canvas.
*
* @param handler the handler ({@code null} not permitted).
*/
public void addMouseHandler(MouseHandlerFX handler) {
if (!this.hasUniqueID(handler)) {
throw new IllegalArgumentException(
"There is already a handler with that ID ("
+ handler.getID() + ").");
}
this.availableMouseHandlers.add(handler);
}
/**
* Removes a handler from the list of available handlers.
*
* @param handler the handler ({@code null} not permitted).
*/
public void removeMouseHandler(MouseHandlerFX handler) {
this.availableMouseHandlers.remove(handler);
}
/**
* Validates that the specified handler has an ID that uniquely identifies
* it amongst the existing handlers for this canvas.
*
* @param handler the handler ({@code null} not permitted).
*
* @return A boolean.
*/
private boolean hasUniqueID(MouseHandlerFX handler) {
for (MouseHandlerFX h: this.availableMouseHandlers) {
if (handler.getID().equals(h.getID())) {
return false;
}
}
for (MouseHandlerFX h: this.auxiliaryMouseHandlers) {
if (handler.getID().equals(h.getID())) {
return false;
}
}
return true;
}
/**
* Clears the current live handler. This method is intended for use by the
* handlers themselves, you should not call it directly.
*/
public void clearLiveHandler() {
this.liveHandler = null;
}
/**
* Draws the content of the canvas and updates the
* {@code renderingInfo} attribute with the latest rendering
* information.
*/
public final void draw() {
GraphicsContext ctx = getGraphicsContext2D();
ctx.save();
double width = getWidth();
double height = getHeight();
if (width > 0 && height > 0) {
ctx.clearRect(0, 0, width, height);
this.info = new ChartRenderingInfo();
this.chart.draw(this.g2, new Rectangle((int) width, (int) height),
this.anchor, this.info);
}
ctx.restore();
this.anchor = null;
}
/**
* Returns the data area (the area inside the axes) for the plot or subplot.
*
* @param point the selection point (for subplot selection).
*
* @return The data area.
*/
public Rectangle2D findDataArea(Point2D point) {
PlotRenderingInfo plotInfo = this.info.getPlotInfo();
Rectangle2D result;
if (plotInfo.getSubplotCount() == 0) {
result = plotInfo.getDataArea();
}
else {
int subplotIndex = plotInfo.getSubplotIndex(point);
if (subplotIndex == -1) {
return null;
}
result = plotInfo.getSubplotInfo(subplotIndex).getDataArea();
}
return result;
}
/**
* Return {@code true} to indicate the canvas is resizable.
*
* @return {@code true}.
*/
@Override
public boolean isResizable() {
return true;
}
/**
* Sets the tooltip text, with the (x, y) location being used for the
* anchor. If the text is {@code null}, no tooltip will be displayed.
* This method is intended for calling by the {@link TooltipHandlerFX}
* class, you won't normally call it directly.
*
* @param text the text ({@code null} permitted).
* @param x the x-coordinate of the mouse pointer.
* @param y the y-coordinate of the mouse pointer.
*/
public void setTooltip(String text, double x, double y) {
if (text != null) {
if (this.tooltip == null) {
this.tooltip = new Tooltip(text);
Tooltip.install(this, this.tooltip);
} else {
this.tooltip.setText(text);
this.tooltip.setAnchorX(x);
this.tooltip.setAnchorY(y);
}
} else {
Tooltip.uninstall(this, this.tooltip);
this.tooltip = null;
}
}
/**
* Handles a mouse pressed event by (1) selecting a live handler if one
* is not already selected, (2) passing the event to the live handler if
* there is one, and (3) passing the event to all enabled auxiliary
* handlers.
*
* @param e the mouse event.
*/
private void handleMousePressed(MouseEvent e) {
if (this.liveHandler == null) {
for (MouseHandlerFX handler: this.availableMouseHandlers) {
if (handler.isEnabled() && handler.hasMatchingModifiers(e)) {
this.liveHandler = handler;
}
}
}
if (this.liveHandler != null) {
this.liveHandler.handleMousePressed(this, e);
}
// pass on the event to the auxiliary handlers
for (MouseHandlerFX handler: this.auxiliaryMouseHandlers) {
if (handler.isEnabled()) {
handler.handleMousePressed(this, e);
}
}
}
/**
* Handles a mouse moved event by passing it on to the registered handlers.
*
* @param e the mouse event.
*/
private void handleMouseMoved(MouseEvent e) {
if (this.liveHandler != null && this.liveHandler.isEnabled()) {
this.liveHandler.handleMouseMoved(this, e);
}
for (MouseHandlerFX handler: this.auxiliaryMouseHandlers) {
if (handler.isEnabled()) {
handler.handleMouseMoved(this, e);
}
}
}
/**
* Handles a mouse dragged event by passing it on to the registered
* handlers.
*
* @param e the mouse event.
*/
private void handleMouseDragged(MouseEvent e) {
if (this.liveHandler != null && this.liveHandler.isEnabled()) {
this.liveHandler.handleMouseDragged(this, e);
}
// pass on the event to the auxiliary handlers
for (MouseHandlerFX handler: this.auxiliaryMouseHandlers) {
if (handler.isEnabled()) {
handler.handleMouseDragged(this, e);
}
}
}
/**
* Handles a mouse released event by passing it on to the registered
* handlers.
*
* @param e the mouse event.
*/
private void handleMouseReleased(MouseEvent e) {
if (this.liveHandler != null && this.liveHandler.isEnabled()) {
this.liveHandler.handleMouseReleased(this, e);
}
// pass on the event to the auxiliary handlers
for (MouseHandlerFX handler: this.auxiliaryMouseHandlers) {
if (handler.isEnabled()) {
handler.handleMouseReleased(this, e);
}
}
}
/**
* Handles a mouse released event by passing it on to the registered
* handlers.
*
* @param e the mouse event.
*/
private void handleMouseClicked(MouseEvent e) {
if (this.liveHandler != null && this.liveHandler.isEnabled()) {
this.liveHandler.handleMouseClicked(this, e);
}
// pass on the event to the auxiliary handlers
for (MouseHandlerFX handler: this.auxiliaryMouseHandlers) {
if (handler.isEnabled()) {
handler.handleMouseClicked(this, e);
}
}
}
/**
* Handles a scroll event by passing it on to the registered handlers.
*
* @param e the scroll event.
*/
protected void handleScroll(ScrollEvent e) {
if (this.liveHandler != null && this.liveHandler.isEnabled()) {
this.liveHandler.handleScroll(this, e);
}
for (MouseHandlerFX handler: this.auxiliaryMouseHandlers) {
if (handler.isEnabled()) {
handler.handleScroll(this, e);
}
}
}
/**
* Receives a notification from the chart that it has been changed and
* responds by redrawing the chart entirely.
*
* @param event event information.
*/
@Override
public void chartChanged(ChartChangeEvent event) {
draw();
}
public void dispatchMouseMovedEvent(Point2D point, MouseEvent e) {
double x = point.getX();
double y = point.getY();
ChartEntity entity = this.info.getEntityCollection().getEntity(x, y);
ChartMouseEventFX event = new ChartMouseEventFX(this.chart, e, entity);
for (ChartMouseListenerFX listener : this.chartMouseListeners) {
listener.chartMouseMoved(event);
}
}
public void dispatchMouseClickedEvent(Point2D point, MouseEvent e) {
double x = point.getX();
double y = point.getY();
ChartEntity entity = this.info.getEntityCollection().getEntity(x, y);
ChartMouseEventFX event = new ChartMouseEventFX(this.chart, e, entity);
for (ChartMouseListenerFX listener : this.chartMouseListeners) {
listener.chartMouseClicked(event);
}
}
}