Reputation: 17151
I want to draw an image behind the LineChart so that any grid and lines are drawn over the image. It seems LineChart would need to expose some lower level accessor to the Canvas so this could be done.
public class ChartBgImageTest {
public ChartBgImageTest() {
final CategoryAxis xAxis = new CategoryAxis();
final NumberAxis yAxis = new NumberAxis(1, 21, 0.1);
final LineChart<String, Number> lineChart = new LineChart<String, Number>(xAxis, yAxis);
BufferedImage bufferedImage = GraphicsEnvironment
.getLocalGraphicsEnvironment()
.getDefaultScreenDevice()
.getDefaultConfiguration()
.createCompatibleImage(600, 400);
BufferedImage img = new BufferedImage(600, 400, BufferedImage.TYPE_INT_RGB);
img.createGraphics().fillRect(0, 0, 50, 50);
// Fill img code here
Graphics g2d = bufferedImage.createGraphics();
g2d.drawImage(img, 0, 0, null);
// TODO How to draw the img to lineChart background.
// So that any grids or lines on the chart are drawn above the img.
}
}
Upvotes: 0
Views: 292
Reputation: 46170
The charts in the javafx.scene.chart
package don't use a Canvas
to draw the charts. They instead use the scene graph. In your question you're creating an image on the fly, but assuming you have an already-created image, possibly as an embedded resource, you could use CSS to add the image to the chart's background. Looking at the XYChart
section of JavaFX CSS Reference Guide, you'll see one of the substructures is labeled chart-plot-background
and is a Region
, which means a background image can be applied to it via CSS. For example:
App.java:
import java.util.Random;
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.chart.LineChart;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.XYChart.Data;
import javafx.scene.chart.XYChart.Series;
import javafx.stage.Stage;
public class App extends Application {
@Override
public void start(Stage primaryStage) {
var chart = new LineChart<>(new NumberAxis(), new NumberAxis(), createChartData());
chart.getXAxis().setLabel("X");
chart.getYAxis().setLabel("Y");
var scene = new Scene(chart, 1000, 650);
scene.getStylesheets().add(getClass().getResource("/style.css").toString());
primaryStage.setScene(scene);
primaryStage.show();
}
private static ObservableList<Series<Number, Number>> createChartData() {
var random = new Random();
var chartData = FXCollections.<Series<Number, Number>>observableArrayList();
for (int i = 1; i <= 3; i++) {
var data = FXCollections.<Data<Number, Number>>observableArrayList();
for (int j = 0; j <= 15; j++) {
data.add(new Data<>(j, random.nextInt(250)));
}
chartData.add(new Series<>("Series #" + i, data));
}
return chartData;
}
}
style.css:
.chart-plot-background {
-fx-background-image: url(/* your URL */);
-fx-background-size: cover;
}
That will only draw the image behind the actual chart content (i.e. the data). The axis, legend, and surrounding space won't have the image behind it. If you want the entire chart to have the background image then you can utilize the fact that Chart
, which all chart implementations inherit from, extends from Region
. Change the CSS to:
.chart {
-fx-background-image: url(/* your URL */);
-fx-background-size: cover;
}
.chart-plot-background,
.chart-legend {
-fx-background-color: null;
}
If you want something between the plot background and the entire chart you can add the image to .chart-content
(documented here).
If you have to stay within code (e.g. because you're creating the image on the fly) then you'll have to get a reference to the necessary Region
. To do that you can use Node#lookup(String)
. Be aware, however, that you may need to wait until the chart has been rendered on screen before calling lookup
, as it's possible the needed descendant node has not been created and added to the scene graph beforehand (this is especially true of controls).
import java.util.Random;
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.chart.LineChart;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.XYChart.Data;
import javafx.scene.chart.XYChart.Series;
import javafx.scene.image.Image;
import javafx.scene.layout.Background;
import javafx.scene.layout.BackgroundImage;
import javafx.scene.layout.BackgroundPosition;
import javafx.scene.layout.BackgroundSize;
import javafx.scene.layout.Region;
import javafx.stage.Stage;
public class App extends Application {
@Override
public void start(Stage primaryStage) {
var chart = new LineChart<>(new NumberAxis(), new NumberAxis(), createChartData());
chart.setTitle("Example Chart");
chart.getXAxis().setLabel("X");
chart.getYAxis().setLabel("Y");
var scene = new Scene(chart, 1000, 650);
primaryStage.setScene(scene);
primaryStage.show();
var plotBackground = (Region) chart.lookup(".chart-plot-background");
plotBackground.setBackground(
new Background(
new BackgroundImage(
new Image(/* your URL */),
null,
null,
BackgroundPosition.CENTER,
new BackgroundSize(0, 0, false, false, true, false))));
}
private static ObservableList<Series<Number, Number>> createChartData() {
var random = new Random();
var chartData = FXCollections.<Series<Number, Number>>observableArrayList();
for (int i = 1; i <= 3; i++) {
var data = FXCollections.<Data<Number, Number>>observableArrayList();
for (int j = 0; j <= 15; j++) {
data.add(new Data<>(j, random.nextInt(250)));
}
chartData.add(new Series<>("Series #" + i, data));
}
return chartData;
}
}
Notice that you can't use BufferedImage
directly with the JavaFX API. If you need to actually draw an Image
in code you have a few options:
WritableImage
directly (via its PixelWriter
). This does not provide a nice API, such as fillRect(...)
.Canvas
then create a snapshot of the result.SwingFXUtils#toFXImage(BufferedImage,WritableImage)
to convert a BufferedImage
into a JavaFX Image
Upvotes: 4