Line chart symbols clipped by axis when they lie on the axis - java

I have a line chart with a defined axis range, and am plotting points as part of the line that lie on the axis. By default the points and the line segments that lie on the axis are clipped, as shown in the below screen shot;
My code is as below;
package com.jtech;
import javafx.application.Application;
import javafx.event.EventHandler;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.chart.Axis;
import javafx.scene.chart.LineChart;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.XYChart;
import javafx.scene.input.MouseEvent;
import javafx.stage.Stage;
public class ShowSymbol extends Application {
#Override
public void start(Stage stage) {
final NumberAxis xAxis = new NumberAxis(0,4,1);
final NumberAxis yAxis = new NumberAxis(0,10,2);
final LineChart<Number, Number> lineChart = new LineChart<>(xAxis, yAxis);
XYChart.Series series = new XYChart.Series();
final XYChart.Data d1 = new XYChart.Data(0.0, 0.0);
final XYChart.Data d2 = new XYChart.Data(0.5, 2.0);
final XYChart.Data d3 = new XYChart.Data(1.0, 2.5);
final XYChart.Data d4 = new XYChart.Data(1.5, 3.5);
final XYChart.Data d5 = new XYChart.Data(2.0, 10.0);
final XYChart.Data d6 = new XYChart.Data(2.5, 10.0);
final XYChart.Data d7 = new XYChart.Data(3.0, 0.0);
final XYChart.Data d8 = new XYChart.Data(3.5, 0.0);
final XYChart.Data d9 = new XYChart.Data(4.0, 8.0);
series.getData().addAll(d1,d2,d3,d4,d5,d6,d7,d8,d9);
lineChart.getData().add(series);
lineChart.setLegendVisible(false);
final Scene scene = new Scene(lineChart, 400, 300);
scene.getStylesheets().add(getClass().getResource("/css/show.css").toExternalForm());
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
where the style sheet contains;
.chart-series-line { -fx-stroke: grey; -fx-stroke-width: 4.0px; -fx-effect: null; }
.chart-line-symbol {
-fx-background-color: #860061, white;
-fx-background-insets: 0, 2;
-fx-background-radius: 0;
-fx-padding: 4px;
}
I would like to keep the axis range as specified (0->4 and 0->10), but show the complete segments of the symbols and lines that lie on them. Is this possible?

The problem you are facing is that the plot area is clipped. Code excerpt from XYChart.java:
private final Group plotArea = new Group(){
#Override public void requestLayout() {} // suppress layout requests
};
private final Group plotContent = new Group();
private final Rectangle plotAreaClip = new Rectangle();
public XYChart(Axis<X> xAxis, Axis<Y> yAxis) {
...
getChartChildren().addAll(plotBackground,plotArea,xAxis,yAxis);
...
plotArea.setClip(plotAreaClip);
...
}
A solution might be to simply disable the clip. But that most probably will give you other troubles (line overlapping, etc). Nontheless, since there is no proper API for this, here's a quick & dirty way about how you could disable the clipping:
Region chartContent = (Region) lineChart.lookup(".chart-content");
for( Node node: chartContent.getChildrenUnmodifiable()) {
if( node instanceof Group) {
Group plotArea= (Group) node;
plotArea.setClip(null);
}
}
It'll look like this then:
Bottom line: You're better off with implementing your own chart (e. g. use and change the source of LineChart and XYChart) that supports this feature. Or you file a change request for a proper solution.

Related

JavaFX LineChart not drawing circle?

This chart shows the problem:
I have JavaFX program that calculates data and draws a chart, but why points are not connected properly? I have tried many things, even creating two separate series, but it doesn't work.
public void createScatterChart(){
final NumberAxis xAxis = new NumberAxis();
final NumberAxis yAxis = new NumberAxis();
final SmoothedChart<Number,Number> smoothedChart = new SmoothedChart<>(xAxis, yAxis);
XYChart.Series series1 = new XYChart.Series();
XYChart.Series series2 = new XYChart.Series();
XYChart.Series series3 = new XYChart.Series();
for(int i = 0 ; i < this.r.size() ; i ++)
{
series1.getData().add(new XYChart.Data(this.r.get(i) * Math.cos(Math.toRadians(this.nodes.get(i))),this.r.get(i) * Math.sin(Math.toRadians(this.nodes.get(i)))));
//series2.getData().add(new XYChart.Data(this.r.get(i) * Math.cos(Math.toRadians(this.nodes.get(i) * this.xArray[i][0])),this.r.get(i) * Math.sin(Math.toRadians(this.nodes.get(i) * this.xArray[i][0]))));
}
smoothedChart.getData().add(series1);
smoothedChart.getData().add(series2);
Stage stage = new Stage();
Scene scene = new Scene(smoothedChart,800,600);
stage.setScene(scene);
stage.show();
}
A similar problem is examined here, in which the solution hinges on the data sort order. Looking at LineChart, SortingPolicy.NONE specifies "The data should be left in the order defined by the list in XYChart.dataProperty()."
I had to change chart from my SmoothChart to standard LineChart.
Depending on your approach to smoothing, you may encounter the kind of cubic spline artifacts examined here, which also occurs in jfreechart-fx. An approach using Bézier curves is adduced here.
As tested using synthetic data:
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.chart.LineChart;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.XYChart;
import javafx.scene.control.ChoiceBox;
import javafx.scene.control.Tooltip;
import javafx.scene.layout.Pane;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
/**
* #see https://stackoverflow.com/a/72607616/230513
* #see https://stackoverflow.com/a/2510048/230513
*/
public class ChartTest extends Application {
private static final int N = 32;
#Override
public void start(Stage stage) {
var xAxis = new NumberAxis();
var yAxis = new NumberAxis();
var series = new XYChart.Series();
series.setName("Data");
for (int i = 0; i <= N; i++) {
var t = 2 * Math.PI * i / N;
var x = Math.cos(t);
var y = Math.sin(t);
series.getData().add(new XYChart.Data(x, y));
}
var chart = new LineChart<Number, Number>(xAxis, yAxis);
chart.getData().add(series);
ObservableList<LineChart.SortingPolicy> policies
= FXCollections.observableArrayList(LineChart.SortingPolicy.values());
var policy = new ChoiceBox<LineChart.SortingPolicy>(policies);
policy.setTooltip(new Tooltip("Choose a data sorting policy."));
policy.getSelectionModel().select(chart.getAxisSortingPolicy());
chart.axisSortingPolicyProperty().bind(policy.valueProperty());
Pane root = new StackPane(chart, policy);
StackPane.setAlignment(policy, Pos.TOP_RIGHT);
stage.setScene(new Scene(root));
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}

Line painting in painting program JavaFX

I created a simple paint program which allows the user to choose between 4 shapes(line,circle,rectangle,ellipse) , the user can change the width height and stroke width , he can save the design he makes , undo and redo , the user can also choose the stroke type (solid or dashed) ,I'm almost done with the program but I'm facing one problem , the line is getting displayed not in the way I want it to be displayed as the photo below :
So the line here is of width 32 and height 32 , my line is getting printed in a different way.Is it correct the way I'm printing my line ?
NOTE : I'm using the line shape in the code to make the code small.
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.geometry.Insets;
import javafx.scene.Cursor;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.control.*;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.shape.Line;
import javafx.stage.Stage;
public class paintLine extends Application {
#Override
public void start(Stage primaryStage) {
Image image1 = new
Image("C:\\Users\\Mhamd\\Desktop\\laol\\src\\resources\\Daco_70400.png",
100, 100, false, false);
ImageView view1 = new ImageView(image1);
view1.setFitHeight(40);
view1.setPreserveRatio(true);
ToggleButton linebtn = new ToggleButton();
linebtn.setGraphic(view1);
ToggleButton[] toolsArr = {linebtn};
ToggleGroup tools = new ToggleGroup();
for (ToggleButton tool : toolsArr) {
tool.setMinWidth(50);
tool.setToggleGroup(tools);
tool.setCursor(Cursor.HAND);
}
ColorPicker cpLine = new ColorPicker(Color.BLACK);
ColorPicker cpFill = new ColorPicker(Color.TRANSPARENT);
TextField textWidth = new TextField("32");
TextField textHeight = new TextField("32");
TextField contouring = new TextField("2");
Label line_color = new Label("Line Color");
Label fill_color = new Label("Fill Color");
Label line_width = new Label("3.0");
Label imgWidth = new Label("Width");
Label imgHeight = new Label("Height");
String week_days[] =
{"Solid", "Dotted"};
ChoiceBox choiceBox = new ChoiceBox(FXCollections
.observableArrayList(week_days));
VBox btns = new VBox(10);
btns.getChildren().addAll(linebtn, imgWidth, textWidth, imgHeight,
textHeight, line_color, cpLine,
fill_color, cpFill, line_width, contouring, choiceBox);
btns.setPadding(new Insets(5));
btns.setStyle("-fx-background-color: #999");
btns.setPrefWidth(100);
Canvas canvas = new Canvas(1080, 790);
GraphicsContext gc;
gc = canvas.getGraphicsContext2D();
gc.setLineWidth(1);
Line line = new Line();
canvas.setOnMouseClicked(e -> {
if (linebtn.isSelected()) {
// double widthSize =
Double.parseDouble(textWidth.getText());
// double heightSize =
Double.parseDouble(textHeight.getText());
double strokeWidth =
Double.parseDouble(contouring.getText());
// gc.setLineWidth(strokeWidth);
gc.setStroke(cpLine.getValue());
if (choiceBox.getSelectionModel().isSelected(0)) {
gc.setLineWidth(strokeWidth);
} else if (choiceBox.getSelectionModel().isSelected(1)) {
gc.setLineWidth(strokeWidth);
gc.setLineDashes(10);
}
gc.setFill(cpFill.getValue());
line.setStartX(e.getX());
line.setStartY(e.getY());
line.setEndX(e.getX() / 2);
line.setEndY(e.getY() / 2);
gc.strokeLine(line.getStartX(), line.getStartY(),
line.getEndX(), line.getEndY());
}
});
BorderPane pane = new BorderPane();
pane.setRight(btns);
pane.setCenter(canvas);
Scene scene = new Scene(pane, 1200, 800);
primaryStage.setTitle("Paint");
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
Focusing on just drawing a line interactively, you need to at least do the following:
Record the initial point at which the mouse was pressed and then
Render the line when you know where the mouse was released:
private double startX;
private double startY;
…
canvas.setOnMousePressed(e -> {
startX = e.getX();
startY = e.getY();
});
canvas.setOnMouseReleased(e -> {
gc.strokeLine(startX, startY, e.getX(), e.getY());
});
This works as shown above, but the problem then becomes how to draw the line while dragging—without damaging previous work. One solution is to add each new line to a List<Line> and render the accumulated lines with each update:
private final List<Line> lines = new ArrayList<>();
…
canvas.setOnMouseReleased(e -> {
lines.add(new Line(startX, startY, e.getX(), e.getY()));
});
canvas.setOnMouseDragged(e -> {
if (lineButton.isSelected()) {
…
gc.clearRect(0, 0, canvas.getWidth(), canvas.getHeight());
lines.forEach(l -> {
gc.strokeLine(l.getStartX(), l.getStartY(), l.getEndX(), l.getEndY());
});
gc.strokeLine(startX, startY, e.getX(), e.getY());
}
});
A more general solution, which illustrates undo() and redo(), is offered here.

JavaFX: Is there a way for Y axis to be positive upwards with origin at bottom-left?

The JavaFX coordinate system draws Y coords from the top of screen, and is positive downwards. I would like it to be positive upwards, and start from the bottom of screen.
There needs to be a translate, and the text nodes need to be flipped.
And with that, hopefully the drawn rectangle will be positioned the "natural" way we do it in math class. With its bottom-left at the origin, expanding to the top-right.
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.chart.*;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
public class FlippedExampleChart extends Application {
private LineChart<Number, Number> chart;
#Override
public void start(Stage primaryStage) throws Exception {
final NumberAxis xAxis = new NumberAxis();
final NumberAxis yAxis = new NumberAxis();
// Flip the axis
yAxis.setScaleY(-1);
// TODO How to translate to bottom of screen.
// TODO How to flip the text nodes.
this.chart = new LineChart<Number, Number>(xAxis, yAxis) {
#Override
public void layoutPlotChildren() {
super.layoutPlotChildren();
double height = yAxis.getDisplayPosition(100);
Rectangle r = new Rectangle(0, 0, 50, height);
r.setFill(Color.GREEN);
getPlotChildren().addAll(r);
}
};
this.chart.setAnimated(false);
VBox vbox = new VBox(this.chart);
Scene scene = new Scene(vbox, 400, 200);
primaryStage.setScene(scene);
primaryStage.setHeight(600);
primaryStage.setWidth(400);
primaryStage.show();
}
public static void main(String[] args) {
Application.launch(args);
}
}
I'm assuming here the aim is to draw a shape using the coordinate system defined by the chart axes.
The easiest way is probably to transform the shape instead of the axis. You can create a utility method for this:
private Transform chartDisplayTransform(NumberAxis xAxis, NumberAxis yAxis) {
return new Affine(
xAxis.getScale(), 0, xAxis.getDisplayPosition(0),
0, yAxis.getScale(), yAxis.getDisplayPosition(0)
);
}
One other note about your code: the layoutPlotChildren() method doesn't necessarily remove nodes, so you may end up adding more rectangles than you expect with the code you posted.
Here's a version of your code that uses this method (and ensures the rectangle is only added once).
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.chart.LineChart;
import javafx.scene.chart.NumberAxis;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.scene.transform.Affine;
import javafx.scene.transform.Transform;
import javafx.stage.Stage;
public class FlippedExampleChart extends Application {
private LineChart<Number, Number> chart;
#Override
public void start(Stage primaryStage) throws Exception {
final NumberAxis xAxis = new NumberAxis();
final NumberAxis yAxis = new NumberAxis();
// Flip the axis
// yAxis.setScaleY(-1);
Rectangle r = new Rectangle(0, 0, 50, 100);
r.setFill(Color.GREEN);
this.chart = new LineChart<Number, Number>(xAxis, yAxis) {
#Override
public void layoutPlotChildren() {
super.layoutPlotChildren();
r.getTransforms().setAll(chartDisplayTransform(xAxis, yAxis));
// note nodes don't get removed from the plot children, and this method may be
// called often:
if (!getPlotChildren().contains(r)) {
getPlotChildren().add(r);
}
}
};
this.chart.setAnimated(false);
VBox vbox = new VBox(this.chart);
Scene scene = new Scene(vbox, 400, 200);
primaryStage.setScene(scene);
primaryStage.setHeight(600);
primaryStage.setWidth(400);
primaryStage.show();
}
private Transform chartDisplayTransform(NumberAxis xAxis, NumberAxis yAxis) {
return new Affine(xAxis.getScale(), 0, xAxis.getDisplayPosition(0), 0, yAxis.getScale(),
yAxis.getDisplayPosition(0));
}
public static void main(String[] args) {
Application.launch(args);
}
}
And the result:
If you have multiple nodes to treat this way, the strategy is to add them to a Group, and apply the transform to the Group:
#Override
public void start(Stage primaryStage) throws Exception {
final NumberAxis xAxis = new NumberAxis();
final NumberAxis yAxis = new NumberAxis();
Group extraNodes = new Group();
this.chart = new LineChart<Number, Number>(xAxis, yAxis) {
#Override
public void layoutPlotChildren() {
super.layoutPlotChildren();
Rectangle r1 = new Rectangle(0, 0, 50, 100);
r1.setFill(Color.GREEN);
Rectangle r2 = new Rectangle(70, 0, 30, 20);
r2.setFill(Color.AQUAMARINE);
extraNodes.getChildren().setAll(r1, r2);
extraNodes.getTransforms().setAll(chartDisplayTransform(xAxis, yAxis));
// note nodes don't get removed from the plot children, and this method may be
// called often:
if (!getPlotChildren().contains(extraNodes)) {
getPlotChildren().add(extraNodes);
}
}
};
this.chart.setAnimated(false);
VBox vbox = new VBox(this.chart);
Scene scene = new Scene(vbox, 400, 200);
primaryStage.setScene(scene);
primaryStage.setHeight(600);
primaryStage.setWidth(400);
primaryStage.show();
}
Also see this related question

How to add circle on JavaFX LineChart [duplicate]

I am going to add some shapes on LineChart. I put LineChart and AnchorPane into the StackPane. I added shapes to AnchorPane by getting x and y coordinates from the chart series. Here is example.
LineChartApp.java
package shapes;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.stage.Stage;
public class LineChartApp extends Application {
#Override
public void start(Stage primaryStage) throws Exception {
primaryStage.setScene(new Scene(new ChartContent()));
primaryStage.setMaximized(true);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
ChartContent.java
package shapes;
import java.util.ArrayList;
import java.util.List;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Side;
import javafx.scene.Node;
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.layout.AnchorPane;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Shape;
import javafx.util.Duration;
public class ChartContent extends StackPane {
private AnchorPane objectsLayer;
private LineChart<Number, Number> chart;
private NumberAxis xAxis;
private NumberAxis yAxis;
private Series<Number, Number> series = new Series<Number, Number>();
private int level = 0;
private int datas[][] = { { 15, 8, 12, 11, 16, 21, 13 },
{ 10, 24, 20, 16, 31, 25, 44 }, { 88, 60, 105, 75, 151, 121, 137 },
{ 1000, 1341, 1211, 1562, 1400, 1600, 1550 }
};
private List<Shape> shapes = new ArrayList<Shape>();
public ChartContent() {
xAxis = new NumberAxis();
yAxis = new NumberAxis();
yAxis.setSide(Side.RIGHT);
yAxis.setForceZeroInRange(false);
xAxis.setForceZeroInRange(false);
chart = new LineChart<Number, Number>(xAxis, yAxis);
chart.setCreateSymbols(false);
chart.setLegendVisible(false);
chart.setAnimated(false);
chart.setVerticalZeroLineVisible(false);
Timeline timer = new Timeline(new KeyFrame(Duration.seconds(5),
new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
chartRefresh();
}
}));
timer.setCycleCount(datas.length - 1);
timer.play();
objectsLayer = new AnchorPane();
objectsLayer.prefHeightProperty().bind(heightProperty());
objectsLayer.prefWidthProperty().bind(widthProperty());
getChildren().addAll(chart, objectsLayer);
chartRefresh();
}
private void chartRefresh() {
series.getData().clear();
if (level < datas.length) {
for (int i = 0; i < datas[level].length; i++) {
series.getData().add(
new Data<Number, Number>(i, datas[level][i]));
}
}
level++;
chart.getData().clear();
chart.getData().add(series);
series.getNode().setStyle("-fx-stroke:blue;-fx-stroke-width:1");
reDrawShapes(series);
}
private void reDrawShapes(Series<Number, Number> series) {
Node chartPlotBackground = chart.lookup(".chart-plot-background");
chartPlotBackground.setStyle("-fx-background-color:white");
Circle circle;
objectsLayer.getChildren().removeAll(shapes);
shapes.clear();
double top = chart.getPadding().getTop(), left = chart.getPadding()
.getLeft();
double minX = chartPlotBackground.getBoundsInParent().getMinX();
double minY = chartPlotBackground.getBoundsInParent().getMinY();
for (Data<Number, Number> data : series.getData()) {
circle = new Circle(minX
+ chart.getXAxis().getDisplayPosition(data.getXValue())
+ left, minY
+ chart.getYAxis().getDisplayPosition(data.getYValue())
+ top, 3, Color.RED);
shapes.add(circle);
}
objectsLayer.getChildren().addAll(shapes);
}
}
I am refreshing chart series every five seconds and redrawing its shapes as well. But after the shapes added to the AnchorPane, they are not there where I expect them to be.
Expected Result
Actual Result
First, note that for the exact functionality you're trying to achieve, this can be done simply by setting a node on the data.
(Aside: it could be argued, and I would argue, that making a node a property of the data displayed in the chart violates pretty much every good practice on the separation of view from data in UI development. The Chart API has a number of bad design flaws, imho, and this is one of them. There probably should be something like a Function<Data<X,Y>, Node> nodeFactory property of the Chart itself for this. However, it is what it is.)
private void chartRefresh() {
series.getData().clear();
if (level < datas.length) {
for (int i = 0; i < datas[level].length; i++) {
Data<Number, Number> data = new Data<Number, Number>(i, datas[level][i]);
data.setNode(new Circle(3, Color.RED));
series.getData().add(data);
}
}
level++;
chart.getData().clear();
chart.getData().add(series);
series.getNode().setStyle("-fx-stroke:blue;-fx-stroke-width:1");
// reDrawShapes(series);
}
This works if your node is simple enough that centering it on the point is what you need.
If you want something more complex, for which this doesn't work, the supported mechanism is to subclass the chart class and override the layoutPlotChildren() method. Here's the complete class using this approach:
import java.util.ArrayList;
import java.util.List;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Side;
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.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Shape;
import javafx.util.Duration;
public class ChartContent extends StackPane {
private LineChart<Number, Number> chart;
private NumberAxis xAxis;
private NumberAxis yAxis;
private Series<Number, Number> series = new Series<Number, Number>();
private int level = 0;
private int datas[][] = { { 15, 8, 12, 11, 16, 21, 13 },
{ 10, 24, 20, 16, 31, 25, 44 }, { 88, 60, 105, 75, 151, 121, 137 },
{ 1000, 1341, 1211, 1562, 1400, 1600, 1550 }
};
public ChartContent() {
xAxis = new NumberAxis();
yAxis = new NumberAxis();
yAxis.setSide(Side.RIGHT);
yAxis.setForceZeroInRange(false);
xAxis.setForceZeroInRange(false);
chart = new LineChart<Number, Number>(xAxis, yAxis) {
private List<Shape> shapes = new ArrayList<>();
#Override
public void layoutPlotChildren() {
super.layoutPlotChildren();
getPlotChildren().removeAll(shapes);
shapes.clear();
for (Data<Number, Number> d : series.getData()) {
double x = xAxis.getDisplayPosition(d.getXValue());
double y = yAxis.getDisplayPosition(d.getYValue());
shapes.add(new Circle(x, y, 3, Color.RED));
}
getPlotChildren().addAll(shapes);
}
};
chart.setCreateSymbols(false);
chart.setLegendVisible(false);
chart.setAnimated(false);
chart.setVerticalZeroLineVisible(false);
Timeline timer = new Timeline(new KeyFrame(Duration.seconds(5),
new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
chartRefresh();
}
}));
timer.setCycleCount(datas.length - 1);
timer.play();
getChildren().addAll(chart);
chartRefresh();
}
private void chartRefresh() {
series.getData().clear();
if (level < datas.length) {
for (int i = 0; i < datas[level].length; i++) {
Data<Number, Number> data = new Data<Number, Number>(i, datas[level][i]);
data.setNode(new Circle(3, Color.RED));
series.getData().add(data);
}
}
level++;
chart.getData().clear();
chart.getData().add(series);
series.getNode().setStyle("-fx-stroke:blue;-fx-stroke-width:1");
}
}
This results in
You can use this technique to, for example, add best fit lines to scatter plots or trend lines to line charts, etc.
I can't tell exactly why the code you used doesn't work, but it makes several assumptions about how the layout is managed (i.e. the location of chart-plot-background in relation to the overall chart itself) and also about when measurements are taken in order to do things like compute the scale in the axes for the mapping from "chart coordinates" to "pixel coordinates". It's not too hard to imagine these becoming invalid when the data changes and only being recalculated at the beginning of the layout process, for example. Logging the "data values" (data.getXValue() and data.getYValue()) alongside the values you get from Axis.getDisplayValue(...) for those values suggests that something akin to the latter explanation may be the case, as those definitely do not seem to produce the correct transformations.
Hooking into the layoutPlotChildren() method is more reliable.

Move tick label JavaFx 2

Is it possible to move/shift the tick labels into the chart. Currently I see api's to hide/show tick labels is there an API that moves the tick labels inside the chart? If there isn't an API then is there a technique that I can use/apply to get this done?
Current code
public class Graph extends Application{
private NumberAxis xAxis;
private NumberAxis yAxis;
public static void main(final String[] args)
{
launch(args);
}
#Override
public void start(final Stage primaryStage) throws Exception
{
xAxis = new NumberAxis(0, 300, 20);
xAxis.setAutoRanging(false);
xAxis.setAnimated(false);
xAxis.setMinorTickVisible(false);
xAxis.setTickLabelsVisible(false);
xAxis.setTickMarkVisible(false);
yAxis = new NumberAxis(30, 240, 30);
yAxis.setAutoRanging(false);
yAxis.setAnimated(false);
yAxis.setTickMarkVisible(false);
yAxis.setMinorTickVisible(false);
yAxis.setMinorTickCount(3);
final LineChart<Number, Number> ctg = new LineChart<>(xAxis, yAxis);
ctg.setAnimated(false);
ctg.setCreateSymbols(false);
ctg.setAlternativeRowFillVisible(false);
ctg.setLegendVisible(false);
ctg.setHorizontalGridLinesVisible(true);
ctg.setVerticalGridLinesVisible(true);
Series<Number, Number> series = new LineChart.Series<>();
ctg.getData().add(series);
for (int i = 0; i < 300; i += 5) {
XYChart.Series minorYGrid = new XYChart.Series();
minorYGrid.getData().add(new XYChart.Data(i, 30));
minorYGrid.getData().add(new XYChart.Data(i, 240));
ctg.getData().add(minorYGrid);
}
for (int i = 40; i <= 240; i += 10) {
XYChart.Series minorXGrid = new XYChart.Series();
minorXGrid.getData().add(new XYChart.Data(0, i));
minorXGrid.getData().add(new XYChart.Data(500, i));
ctg.getData().add(minorXGrid);
}
final Scene scene = new Scene(ctg, 1600, 400);
scene.getStylesheets().add("resources/application.css");
primaryStage.setScene(scene);
primaryStage.show();
}
}
Current result
Expected result
Translating a Single Axis
You can translate the y axis on the chart.
For example:
yAxis.translateXProperty().bind(
xAxis.widthProperty().divide(2)
);
To ensure the axis is displayed on top of the chart, you could set the depth buffer to true on the scene and set the z co-ordinate of the yAxis to -1.
yAxis.setTranslateZ(-1);
Translating Multiple Axes
Your "Expected result" actually has multiple vertical axes. Unfortunately there is no clone method to clone a node in JavaFX. So you will have to create a new axis and layer it on top of the chart. One way to accomplish that (which is a bit overkill and inefficient), is to create a completely new chart and layer it on top of the old one, similar to what is done in the solution to draw layers of XYCharts. The other way to do it, which is probably preferable but a bit trickier, would be just create another axis and stack it over the original chart.
Sample Code
MultiAxisChart.java
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.chart.LineChart;
import javafx.scene.chart.NumberAxis;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
public class MultiAxisChart extends Application {
#Override
public void start(final Stage primaryStage) throws Exception {
final StackPane chartStack = createChartStack();
final Scene scene = new Scene(chartStack, 1600, 400, true);
primaryStage.setScene(scene);
primaryStage.show();
}
private StackPane createChartStack() {
LineChart<Number, Number> bottomChart = createChart();
LineChart<Number, Number> topChart = createChart();
bottomChart.getYAxis().translateXProperty().bind(
bottomChart.getXAxis().widthProperty().multiply(1.0/3)
);
topChart.getYAxis().translateXProperty().bind(
topChart.getXAxis().widthProperty().multiply(2.0/3)
);
bottomChart.getYAxis().setTranslateZ(-1);
topChart.getYAxis().setTranslateZ(-1);
topChart.getStylesheets().addAll(getClass().getResource(
"overlay-chart.css"
).toExternalForm());
return new StackPane(bottomChart, topChart);
}
private LineChart<Number, Number> createChart() {
NumberAxis xAxis = new NumberAxis(0, 300, 20);
xAxis.setAutoRanging(false);
xAxis.setAnimated(false);
xAxis.setMinorTickVisible(false);
xAxis.setTickLabelsVisible(false);
xAxis.setTickMarkVisible(false);
NumberAxis yAxis = new NumberAxis(30, 240, 30);
yAxis.setAutoRanging(false);
yAxis.setAnimated(false);
yAxis.setTickMarkVisible(false);
yAxis.setMinorTickVisible(false);
yAxis.setMinorTickCount(3);
final LineChart<Number, Number> ctg = new LineChart<>(xAxis, yAxis);
ctg.setAnimated(false);
ctg.setCreateSymbols(false);
ctg.setAlternativeRowFillVisible(false);
ctg.setLegendVisible(false);
ctg.setHorizontalGridLinesVisible(true);
ctg.setVerticalGridLinesVisible(true);
return ctg;
}
public static void main(final String[] args) {
launch(args);
}
}
overlay-chart.css
/** file: overlay-chart.css (place in same directory as MultiAxisChart) */
.chart-plot-background {
-fx-background-color: transparent;
}

Categories

Resources