how to add 3 legend for a single series barchart?? JAVAFX - css

Here is my code to generate 10 bars of different colors. I want to add legend respectively but it only shows yellow legend i can change its color but i want 3 legend.
I think it shows only 1 color because there is only 1 series. Is it possible to add more than 1 legend for a single series?
output:
or if i can add this image as legend to the middle left of my chart
i need how to display image in bar chart or how to create 3 different labels for a single series bar chart
import javafx.application.Application;
import javafx.beans.value.*;
import javafx.scene.*;
import javafx.scene.chart.*;
import javafx.stage.Stage;
public class DynamicallyColoredBarChart extends Application {
#Override
public void start(Stage stage) {
final CategoryAxis xAxis = new CategoryAxis();
xAxis.setLabel("Bars");
final NumberAxis yAxis = new NumberAxis();
yAxis.setLabel("Value");
final BarChart<String, Number> bc = new BarChart<>(xAxis, yAxis);
bc.setLegendVisible(false);
XYChart.Series series1 = new XYChart.Series();
for (int i = 0; i < 10; i++) {
// change color of bar if value of i is >5 than red if i>8 than blue
final XYChart.Data<String, Number> data = new XYChart.Data("Value " + i, i);
data.nodeProperty().addListener(new ChangeListener<Node>() {
#Override
public void changed(ObservableValue<? extends Node> ov, Node oldNode, Node newNode) {
if (newNode != null) {
if (data.getYValue().intValue() > 8) {
newNode.setStyle("-fx-bar-fill: navy;");
} else if (data.getYValue().intValue() > 5) {
newNode.setStyle("-fx-bar-fill: red;");
}
}
}
});
series1.getData().add(data);
}
bc.getData().add(series1);
stage.setScene(new Scene(bc));
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}

To put an image to the left you can just add image and chart to HBox:
HBox root = new HBox(5);
root.getChildren().addAll(image, bc);
stage.setScene(new Scene(root));

Related

JavaFX: subclassing LineChart to add second Y axis [duplicate]

Peace be upon you!
How to draw multiple axis using JavaFX charts API?
Following is an image of a trend in a huge software where some real time as well as history data is being plotted. There are two pens registered and separate axis for each pen is defined on the same trend.
I have been trying to do exactly this in JavaFX 2.0 charts. I have been able to plot a real time chart which is as follows:
I have been researching about multiple axis using JavaFX and could find the this link but I think this is an older version of JavaFX using FXML. However, I am using JavaFX regular classes to accomplish this.
HELP!!!
Here you can find my solution - MultipleAxesLineChart. It is not generic and just fits my needs, but I think it can give a good sight of how it can be done by using StackPane.
Place two charts in a StackPane.
Use css lookups on the top chart to translate (using translate-x and translate-y) it's axes and labels so that they can be read independently of the bottom chart. Leave the top chart's data plot so that it overlays the bottom chart. Modify colors and legends (or mix chart styles, e.g. line and bar) so that it is obvious which data plot belongs to which series.
Some of the techniques above are demonstrated:
In the answer to: Move tick label JavaFx 2 (which shows how to draw multiple axes on what seems to be a single chart).
In this sample code which layers a linechart on a barchart (but without the axis translation).
public class MultipleAxesLineChart extends StackPane {
private final LineChart baseChart;
private final ObservableList<LineChart> backgroundCharts = FXCollections.observableArrayList();
private final Map<LineChart, Color> chartColorMap = new HashMap<>();
private final double yAxisWidth = 60;
private final AnchorPane detailsWindow;
private final double yAxisSeparation = 20;
private double strokeWidth = 0.3;
public MultipleAxesLineChart(LineChart baseChart, Color lineColor) {
this(baseChart, lineColor, null);
}
public MultipleAxesLineChart(LineChart baseChart, Color lineColor, Double strokeWidth) {
if (strokeWidth != null) {
this.strokeWidth = strokeWidth;
}
this.baseChart = baseChart;
chartColorMap.put(baseChart, lineColor);
styleBaseChart(baseChart);
styleChartLine(baseChart, lineColor);
setFixedAxisWidth(baseChart);
setAlignment(Pos.CENTER_LEFT);
backgroundCharts.addListener((Observable observable) -> rebuildChart());
detailsWindow = new AnchorPane();
bindMouseEvents(baseChart, this.strokeWidth);
rebuildChart();
}
private void bindMouseEvents(LineChart baseChart, Double strokeWidth) {
final DetailsPopup detailsPopup = new DetailsPopup();
getChildren().add(detailsWindow);
detailsWindow.getChildren().add(detailsPopup);
detailsWindow.prefHeightProperty().bind(heightProperty());
detailsWindow.prefWidthProperty().bind(widthProperty());
detailsWindow.setMouseTransparent(true);
setOnMouseMoved(null);
setMouseTransparent(false);
final Axis xAxis = baseChart.getXAxis();
final Axis yAxis = baseChart.getYAxis();
final Line xLine = new Line();
final Line yLine = new Line();
yLine.setFill(Color.GRAY);
xLine.setFill(Color.GRAY);
yLine.setStrokeWidth(strokeWidth/2);
xLine.setStrokeWidth(strokeWidth/2);
xLine.setVisible(false);
yLine.setVisible(false);
final Node chartBackground = baseChart.lookup(".chart-plot-background");
for (Node n: chartBackground.getParent().getChildrenUnmodifiable()) {
if (n != chartBackground && n != xAxis && n != yAxis) {
n.setMouseTransparent(true);
}
}
chartBackground.setCursor(Cursor.CROSSHAIR);
chartBackground.setOnMouseEntered((event) -> {
chartBackground.getOnMouseMoved().handle(event);
detailsPopup.setVisible(true);
xLine.setVisible(true);
yLine.setVisible(true);
detailsWindow.getChildren().addAll(xLine, yLine);
});
chartBackground.setOnMouseExited((event) -> {
detailsPopup.setVisible(false);
xLine.setVisible(false);
yLine.setVisible(false);
detailsWindow.getChildren().removeAll(xLine, yLine);
});
chartBackground.setOnMouseMoved(event -> {
double x = event.getX() + chartBackground.getLayoutX();
double y = event.getY() + chartBackground.getLayoutY();
xLine.setStartX(10);
xLine.setEndX(detailsWindow.getWidth()-10);
xLine.setStartY(y+5);
xLine.setEndY(y+5);
yLine.setStartX(x+5);
yLine.setEndX(x+5);
yLine.setStartY(10);
yLine.setEndY(detailsWindow.getHeight()-10);
detailsPopup.showChartDescrpition(event);
if (y + detailsPopup.getHeight() + 10 < getHeight()) {
AnchorPane.setTopAnchor(detailsPopup, y+10);
} else {
AnchorPane.setTopAnchor(detailsPopup, y-10-detailsPopup.getHeight());
}
if (x + detailsPopup.getWidth() + 10 < getWidth()) {
AnchorPane.setLeftAnchor(detailsPopup, x+10);
} else {
AnchorPane.setLeftAnchor(detailsPopup, x-10-detailsPopup.getWidth());
}
});
}
private void styleBaseChart(LineChart baseChart) {
baseChart.setCreateSymbols(false);
baseChart.setLegendVisible(false);
baseChart.getXAxis().setAutoRanging(false);
baseChart.getXAxis().setAnimated(false);
baseChart.getYAxis().setAnimated(false);
}
private void setFixedAxisWidth(LineChart chart) {
chart.getYAxis().setPrefWidth(yAxisWidth);
chart.getYAxis().setMaxWidth(yAxisWidth);
}
private void rebuildChart() {
getChildren().clear();
getChildren().add(resizeBaseChart(baseChart));
for (LineChart lineChart : backgroundCharts) {
getChildren().add(resizeBackgroundChart(lineChart));
}
getChildren().add(detailsWindow);
}
private Node resizeBaseChart(LineChart lineChart) {
HBox hBox = new HBox(lineChart);
hBox.setAlignment(Pos.CENTER_LEFT);
hBox.prefHeightProperty().bind(heightProperty());
hBox.prefWidthProperty().bind(widthProperty());
lineChart.minWidthProperty().bind(widthProperty().subtract((yAxisWidth+yAxisSeparation)*backgroundCharts.size()));
lineChart.prefWidthProperty().bind(widthProperty().subtract((yAxisWidth+yAxisSeparation)*backgroundCharts.size()));
lineChart.maxWidthProperty().bind(widthProperty().subtract((yAxisWidth+yAxisSeparation)*backgroundCharts.size()));
return lineChart;
}
private Node resizeBackgroundChart(LineChart lineChart) {
HBox hBox = new HBox(lineChart);
hBox.setAlignment(Pos.CENTER_LEFT);
hBox.prefHeightProperty().bind(heightProperty());
hBox.prefWidthProperty().bind(widthProperty());
hBox.setMouseTransparent(true);
lineChart.minWidthProperty().bind(widthProperty().subtract((yAxisWidth + yAxisSeparation) * backgroundCharts.size()));
lineChart.prefWidthProperty().bind(widthProperty().subtract((yAxisWidth + yAxisSeparation) * backgroundCharts.size()));
lineChart.maxWidthProperty().bind(widthProperty().subtract((yAxisWidth + yAxisSeparation) * backgroundCharts.size()));
lineChart.translateXProperty().bind(baseChart.getYAxis().widthProperty());
lineChart.getYAxis().setTranslateX((yAxisWidth + yAxisSeparation) * backgroundCharts.indexOf(lineChart));
return hBox;
}
public void addSeries(XYChart.Series series, Color lineColor) {
NumberAxis yAxis = new NumberAxis();
NumberAxis xAxis = new NumberAxis();
// style x-axis
xAxis.setAutoRanging(false);
xAxis.setVisible(false);
xAxis.setOpacity(0.0); // somehow the upper setVisible does not work
xAxis.lowerBoundProperty().bind(((NumberAxis) baseChart.getXAxis()).lowerBoundProperty());
xAxis.upperBoundProperty().bind(((NumberAxis) baseChart.getXAxis()).upperBoundProperty());
xAxis.tickUnitProperty().bind(((NumberAxis) baseChart.getXAxis()).tickUnitProperty());
// style y-axis
yAxis.setSide(Side.RIGHT);
yAxis.setLabel(series.getName());
// create chart
LineChart lineChart = new LineChart(xAxis, yAxis);
lineChart.setAnimated(false);
lineChart.setLegendVisible(false);
lineChart.getData().add(series);
styleBackgroundChart(lineChart, lineColor);
setFixedAxisWidth(lineChart);
chartColorMap.put(lineChart, lineColor);
backgroundCharts.add(lineChart);
}
private void styleBackgroundChart(LineChart lineChart, Color lineColor) {
styleChartLine(lineChart, lineColor);
Node contentBackground = lineChart.lookup(".chart-content").lookup(".chart-plot-background");
contentBackground.setStyle("-fx-background-color: transparent;");
lineChart.setVerticalZeroLineVisible(false);
lineChart.setHorizontalZeroLineVisible(false);
lineChart.setVerticalGridLinesVisible(false);
lineChart.setHorizontalGridLinesVisible(false);
lineChart.setCreateSymbols(false);
}
private String toRGBCode(Color color) {
return String.format("#%02X%02X%02X",
(int) (color.getRed() * 255),
(int) (color.getGreen() * 255),
(int) (color.getBlue() * 255));
}
private void styleChartLine(LineChart chart, Color lineColor) {
chart.getYAxis().lookup(".axis-label").setStyle("-fx-text-fill: " + toRGBCode(lineColor) + "; -fx-font-weight: bold;");
Node seriesLine = chart.lookup(".chart-series-line");
seriesLine.setStyle("-fx-stroke: " + toRGBCode(lineColor) + "; -fx-stroke-width: " + strokeWidth + ";");
}
public Node getLegend() {
HBox hBox = new HBox();
final CheckBox baseChartCheckBox = new CheckBox(baseChart.getYAxis().getLabel());
baseChartCheckBox.setSelected(true);
baseChartCheckBox.setStyle("-fx-text-fill: " + toRGBCode(chartColorMap.get(baseChart)) + "; -fx-font-weight: bold;");
baseChartCheckBox.setDisable(true);
baseChartCheckBox.getStyleClass().add("readonly-checkbox");
baseChartCheckBox.setOnAction(event -> baseChartCheckBox.setSelected(true));
hBox.getChildren().add(baseChartCheckBox);
for (final LineChart lineChart : backgroundCharts) {
CheckBox checkBox = new CheckBox(lineChart.getYAxis().getLabel());
checkBox.setStyle("-fx-text-fill: " + toRGBCode(chartColorMap.get(lineChart)) + "; -fx-font-weight: bold");
checkBox.setSelected(true);
checkBox.setOnAction(event -> {
if (backgroundCharts.contains(lineChart)) {
backgroundCharts.remove(lineChart);
} else {
backgroundCharts.add(lineChart);
}
});
hBox.getChildren().add(checkBox);
}
hBox.setAlignment(Pos.CENTER);
hBox.setSpacing(20);
hBox.setStyle("-fx-padding: 0 10 20 10");
return hBox;
}
private class DetailsPopup extends VBox {
private DetailsPopup() {
setStyle("-fx-border-width: 1px; -fx-padding: 5 5 5 5px; -fx-border-color: gray; -fx-background-color: whitesmoke;");
setVisible(false);
}
public void showChartDescrpition(MouseEvent event) {
getChildren().clear();
Long xValueLong = Math.round((double)baseChart.getXAxis().getValueForDisplay(event.getX()));
HBox baseChartPopupRow = buildPopupRow(event, xValueLong, baseChart);
if (baseChartPopupRow != null) {
getChildren().add(baseChartPopupRow);
}
for (LineChart lineChart : backgroundCharts) {
HBox popupRow = buildPopupRow(event, xValueLong, lineChart);
if (popupRow == null) continue;
getChildren().add(popupRow);
}
}
private HBox buildPopupRow(MouseEvent event, Long xValueLong, LineChart lineChart) {
Label seriesName = new Label(lineChart.getYAxis().getLabel());
seriesName.setTextFill(chartColorMap.get(lineChart));
Number yValueForChart = getYValueForX(lineChart, xValueLong.intValue());
if (yValueForChart == null) {
return null;
}
Number yValueLower = Math.round(normalizeYValue(lineChart, event.getY() - 10));
Number yValueUpper = Math.round(normalizeYValue(lineChart, event.getY() + 10));
Number yValueUnderMouse = Math.round((double) lineChart.getYAxis().getValueForDisplay(event.getY()));
// make series name bold when mouse is near given chart's line
if (isMouseNearLine(yValueForChart, yValueUnderMouse, Math.abs(yValueLower.doubleValue()-yValueUpper.doubleValue()))) {
seriesName.setStyle("-fx-font-weight: bold");
}
HBox popupRow = new HBox(10, seriesName, new Label("["+yValueForChart+"]"));
return popupRow;
}
private double normalizeYValue(LineChart lineChart, double value) {
Double val = (Double) lineChart.getYAxis().getValueForDisplay(value);
if (val == null) {
return 0;
} else {
return val;
}
}
private boolean isMouseNearLine(Number realYValue, Number yValueUnderMouse, Double tolerance) {
return (Math.abs(yValueUnderMouse.doubleValue() - realYValue.doubleValue()) < tolerance);
}
public Number getYValueForX(LineChart chart, Number xValue) {
List<XYChart.Data> dataList = ((List<XYChart.Data>)((XYChart.Series)chart.getData().get(0)).getData());
for (XYChart.Data data : dataList) {
if (data.getXValue().equals(xValue)) {
return (Number)data.getYValue();
}
}
return null;
}
}
}
MultipleAxisMainChart:
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.chart.LineChart;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.XYChart;
import javafx.scene.layout.BorderPane;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
import java.util.function.Function;
public class MultipleAxesLineChartMain extends Application {
public static final int X_DATA_COUNT = 3600;
#Override
public void start(Stage primaryStage) throws Exception{
NumberAxis xAxis = new NumberAxis(0, X_DATA_COUNT, 200);
NumberAxis yAxis = new NumberAxis();
yAxis.setLabel("Series 1");
LineChart baseChart = new LineChart(xAxis, yAxis);
baseChart.getData().add(prepareSeries("Series 1", (x) -> (double)x));
MultipleAxesLineChart chart = new MultipleAxesLineChart(baseChart, Color.RED);
chart.addSeries(prepareSeries("Series 2", (x) -> (double)x*x),Color.BLUE);
chart.addSeries(prepareSeries("Series 3", (x) -> (double)-x*x),Color.GREEN);
chart.addSeries(prepareSeries("Series 4", (x) -> ((double) (x-250))*x),Color.DARKCYAN);
chart.addSeries(prepareSeries("Series 5", (x) -> ((double)(x+100)*(x-200))),Color.BROWN);
primaryStage.setTitle("MultipleAxesLineChart");
BorderPane borderPane = new BorderPane();
borderPane.setCenter(chart);
borderPane.setBottom(chart.getLegend());
Scene scene = new Scene(borderPane, 1024, 600);
scene.getStylesheets().add(getClass().getResource("style.css").toExternalForm());
primaryStage.setScene(scene);
primaryStage.show();
}
private XYChart.Series<Number, Number> prepareSeries(String name, Function<Integer, Double> function) {
XYChart.Series<Number, Number> series = new XYChart.Series<>();
series.setName(name);
for (int i = 0; i < X_DATA_COUNT; i++) {
series.getData().add(new XYChart.Data<>(i, function.apply(i)));
}
return series;
}
public static void main(String[] args) {
launch(args);
}
}
made a little change about the UI, resuting left and right to have same number of yaxis, based on Maciej‘s answer。
preview

Adding horizontal lines in javafx barchart [duplicate]

My desktop application has a timer for starting and stopping a test. On the graph, I want to create two vertical lines to indicate the start and stop time. "Adding vertical lines to StackPane with JavaFX" won't work for my case because I don't want the lines to stay at the same position and those lines should be drawn within the plot not the layout. When the user zooms on the chart, those vertical lines should move corresponding to where the user zooms. Thanks for any tip.
Here are my codes for creating the chart:
LineChart<Number, Number> chart = new LineChart<Number, Number>(xAxis, yAxis, dataset);
xAxis.setLabel("time(s)");
yAxis.setLabel("deg/s");
You need to extend the LineChart class and override the layoutPlotChildren method in order to show your markers.
Kleopatra did a very good example for a Scatter chart. The code below is a modified version for a line chart and has both vertical and horizontal markers:
public class LineChartSample extends Application {
#Override public void start(Stage stage) {
final NumberAxis xAxis = new NumberAxis();
final NumberAxis yAxis = new NumberAxis();
xAxis.setLabel("Number of Month");
final LineChartWithMarkers<Number,Number> lineChart = new LineChartWithMarkers<Number,Number>(xAxis,yAxis);
XYChart.Series series = new XYChart.Series();
series.setName("My portfolio");
series.getData().add(new XYChart.Data(1, 23));
series.getData().add(new XYChart.Data(2, 14));
series.getData().add(new XYChart.Data(3, 15));
series.getData().add(new XYChart.Data(4, 24));
series.getData().add(new XYChart.Data(5, 34));
series.getData().add(new XYChart.Data(6, 36));
series.getData().add(new XYChart.Data(7, 22));
series.getData().add(new XYChart.Data(8, 45));
series.getData().add(new XYChart.Data(9, 43));
series.getData().add(new XYChart.Data(10, 17));
series.getData().add(new XYChart.Data(11, 29));
series.getData().add(new XYChart.Data(12, 25));
lineChart.getData().add(series);
Data<Number, Number> horizontalMarker = new Data<>(0, 25);
lineChart.addHorizontalValueMarker(horizontalMarker);
Data<Number, Number> verticalMarker = new Data<>(10, 0);
lineChart.addVerticalValueMarker(verticalMarker);
Slider horizontalMarkerSlider = new Slider(yAxis.getLowerBound(), yAxis.getUpperBound(), 0);
horizontalMarkerSlider.setOrientation(Orientation.VERTICAL);
horizontalMarkerSlider.setShowTickLabels(true);
horizontalMarkerSlider.valueProperty().bindBidirectional(horizontalMarker.YValueProperty());
horizontalMarkerSlider.minProperty().bind(yAxis.lowerBoundProperty());
horizontalMarkerSlider.maxProperty().bind(yAxis.upperBoundProperty());
Slider verticalMarkerSlider = new Slider(xAxis.getLowerBound(), xAxis.getUpperBound(), 0);
verticalMarkerSlider.setOrientation(Orientation.HORIZONTAL);
verticalMarkerSlider.setShowTickLabels(true);
verticalMarkerSlider.valueProperty().bindBidirectional(verticalMarker.XValueProperty());
verticalMarkerSlider.minProperty().bind(xAxis.lowerBoundProperty());
verticalMarkerSlider.maxProperty().bind(xAxis.upperBoundProperty());
BorderPane borderPane = new BorderPane();
borderPane.setCenter( lineChart);
borderPane.setTop(verticalMarkerSlider);
borderPane.setRight(horizontalMarkerSlider);
Scene scene = new Scene(borderPane,800,600);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
private class LineChartWithMarkers<X,Y> extends LineChart {
private ObservableList<Data<X, Y>> horizontalMarkers;
private ObservableList<Data<X, Y>> verticalMarkers;
public LineChartWithMarkers(Axis<X> xAxis, Axis<Y> yAxis) {
super(xAxis, yAxis);
horizontalMarkers = FXCollections.observableArrayList(data -> new Observable[] {data.YValueProperty()});
horizontalMarkers.addListener((InvalidationListener)observable -> layoutPlotChildren());
verticalMarkers = FXCollections.observableArrayList(data -> new Observable[] {data.XValueProperty()});
verticalMarkers.addListener((InvalidationListener)observable -> layoutPlotChildren());
}
public void addHorizontalValueMarker(Data<X, Y> marker) {
Objects.requireNonNull(marker, "the marker must not be null");
if (horizontalMarkers.contains(marker)) return;
Line line = new Line();
marker.setNode(line );
getPlotChildren().add(line);
horizontalMarkers.add(marker);
}
public void removeHorizontalValueMarker(Data<X, Y> marker) {
Objects.requireNonNull(marker, "the marker must not be null");
if (marker.getNode() != null) {
getPlotChildren().remove(marker.getNode());
marker.setNode(null);
}
horizontalMarkers.remove(marker);
}
public void addVerticalValueMarker(Data<X, Y> marker) {
Objects.requireNonNull(marker, "the marker must not be null");
if (verticalMarkers.contains(marker)) return;
Line line = new Line();
marker.setNode(line );
getPlotChildren().add(line);
verticalMarkers.add(marker);
}
public void removeVerticalValueMarker(Data<X, Y> marker) {
Objects.requireNonNull(marker, "the marker must not be null");
if (marker.getNode() != null) {
getPlotChildren().remove(marker.getNode());
marker.setNode(null);
}
verticalMarkers.remove(marker);
}
#Override
protected void layoutPlotChildren() {
super.layoutPlotChildren();
for (Data<X, Y> horizontalMarker : horizontalMarkers) {
Line line = (Line) horizontalMarker.getNode();
line.setStartX(0);
line.setEndX(getBoundsInLocal().getWidth());
line.setStartY(getYAxis().getDisplayPosition(horizontalMarker.getYValue()) + 0.5); // 0.5 for crispness
line.setEndY(line.getStartY());
line.toFront();
}
for (Data<X, Y> verticalMarker : verticalMarkers) {
Line line = (Line) verticalMarker.getNode();
line.setStartX(getXAxis().getDisplayPosition(verticalMarker.getXValue()) + 0.5); // 0.5 for crispness
line.setEndX(line.getStartX());
line.setStartY(0d);
line.setEndY(getBoundsInLocal().getHeight());
line.toFront();
}
}
}
}
In order to add more marker lines, just use this:
Data<Number, Number> verticalMarker = new Data<>(10, 0);
lineChart.addVerticalValueMarker(verticalMarker);
Of course you could as well use a rectangle instead of a line like this:
private ObservableList<Data<X, X>> verticalRangeMarkers;
public LineChartWithMarkers(Axis<X> xAxis, Axis<Y> yAxis) {
...
verticalRangeMarkers = FXCollections.observableArrayList(data -> new Observable[] {data.XValueProperty()});
verticalRangeMarkers = FXCollections.observableArrayList(data -> new Observable[] {data.YValueProperty()}); // 2nd type of the range is X type as well
verticalRangeMarkers.addListener((InvalidationListener)observable -> layoutPlotChildren());
}
public void addVerticalRangeMarker(Data<X, X> marker) {
Objects.requireNonNull(marker, "the marker must not be null");
if (verticalRangeMarkers.contains(marker)) return;
Rectangle rectangle = new Rectangle(0,0,0,0);
rectangle.setStroke(Color.TRANSPARENT);
rectangle.setFill(Color.BLUE.deriveColor(1, 1, 1, 0.2));
marker.setNode( rectangle);
getPlotChildren().add(rectangle);
verticalRangeMarkers.add(marker);
}
public void removeVerticalRangeMarker(Data<X, X> marker) {
Objects.requireNonNull(marker, "the marker must not be null");
if (marker.getNode() != null) {
getPlotChildren().remove(marker.getNode());
marker.setNode(null);
}
verticalRangeMarkers.remove(marker);
}
protected void layoutPlotChildren() {
...
for (Data<X, X> verticalRangeMarker : verticalRangeMarkers) {
Rectangle rectangle = (Rectangle) verticalRangeMarker.getNode();
rectangle.setX( getXAxis().getDisplayPosition(verticalRangeMarker.getXValue()) + 0.5); // 0.5 for crispness
rectangle.setWidth( getXAxis().getDisplayPosition(verticalRangeMarker.getYValue()) - getXAxis().getDisplayPosition(verticalRangeMarker.getXValue()));
rectangle.setY(0d);
rectangle.setHeight(getBoundsInLocal().getHeight());
rectangle.toBack();
}
}
used like this:
Data<Number, Number> verticalRangeMarker = new Data<>(4, 10);
lineChart.addVerticalRangeMarker(verticalRangeMarker);
To make it look like a range:
I'm not sure which question you are referring to. You can basically do all this with some binding magic: the trick is to map the x value of the line to coordinates relative to the xAxis using xAxis.getDisplayPosition(...). Then you need to transform that coordinate to the coordinate relative to the container holding the chart and the line: the easiest way to do this is to first transform to Scene coordinates using xAxis.localToScene(...) and then to the coordinates of the container, using container.sceneToLocal(...).
Then you just need to let the binding observe everything that it needs to watch for changes: these will be the (numerical) bounds of the axes, the (graphical) bounds of the chart, and, if the line is going to move, a property representing its x-value.
Here is an SSCCE. In this example, I use a Slider to move the line around. I also make the line visible only if it's in range, and bind the y-coordinates so it spans the yAxis.
import java.util.Random;
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.beans.value.ObservableDoubleValue;
import javafx.geometry.Insets;
import javafx.geometry.Point2D;
import javafx.scene.Scene;
import javafx.scene.chart.LineChart;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.XYChart;
import javafx.scene.chart.XYChart.Data;
import javafx.scene.chart.XYChart.Series;
import javafx.scene.control.Slider;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.Pane;
import javafx.scene.shape.Line;
import javafx.stage.Stage;
public class LineChartWithVerticalLine extends Application {
#Override
public void start(Stage primaryStage) {
NumberAxis xAxis = new NumberAxis();
NumberAxis yAxis = new NumberAxis();
LineChart<Number, Number> chart = new LineChart<>(xAxis, yAxis);
chart.getData().add(createSeries());
Pane chartHolder = new Pane();
chartHolder.getChildren().add(chart);
DoubleProperty lineX = new SimpleDoubleProperty();
Slider slider = new Slider();
slider.minProperty().bind(xAxis.lowerBoundProperty());
slider.maxProperty().bind(xAxis.upperBoundProperty());
slider.setPadding(new Insets(20));
lineX.bind(slider.valueProperty());
chartHolder.getChildren().add(createVerticalLine(chart, xAxis, yAxis, chartHolder, lineX));
BorderPane root = new BorderPane(chartHolder, null, null, slider, null);
Scene scene = new Scene(root, 800, 600);
primaryStage.setScene(scene);
primaryStage.show();
}
private Line createVerticalLine(XYChart<Number, Number> chart, NumberAxis xAxis, NumberAxis yAxis, Pane container, ObservableDoubleValue x) {
Line line = new Line();
line.startXProperty().bind(Bindings.createDoubleBinding(() -> {
double xInAxis = xAxis.getDisplayPosition(x.get());
Point2D pointInScene = xAxis.localToScene(xInAxis, 0);
double xInContainer = container.sceneToLocal(pointInScene).getX();
return xInContainer ;
},
x,
chart.boundsInParentProperty(),
xAxis.lowerBoundProperty(),
xAxis.upperBoundProperty()));
line.endXProperty().bind(line.startXProperty());
line.startYProperty().bind(Bindings.createDoubleBinding(() -> {
double lowerY = yAxis.getDisplayPosition(yAxis.getLowerBound());
Point2D pointInScene = yAxis.localToScene(0, lowerY);
double yInContainer = container.sceneToLocal(pointInScene).getY();
return yInContainer ;
},
chart.boundsInParentProperty(),
yAxis.lowerBoundProperty()));
line.endYProperty().bind(Bindings.createDoubleBinding(() -> {
double upperY = yAxis.getDisplayPosition(yAxis.getUpperBound());
Point2D pointInScene = yAxis.localToScene(0, upperY);
double yInContainer = container.sceneToLocal(pointInScene).getY();
return yInContainer ;
},
chart.boundsInParentProperty(),
yAxis.lowerBoundProperty()));
line.visibleProperty().bind(
Bindings.lessThan(x, xAxis.lowerBoundProperty())
.and(Bindings.greaterThan(x, xAxis.upperBoundProperty())).not());
return line ;
}
private Series<Number, Number> createSeries() {
Series<Number, Number> series = new Series<>();
series.setName("Data");
Random rng = new Random();
for (int i=0; i<=20; i++) {
series.getData().add(new Data<>(i, rng.nextInt(101)));
}
return series ;
}
public static void main(String[] args) {
launch(args);
}
}
I was able to create a drag and zoom in feature using the Line Chart Example mentioned here. The code listens to the mouse events and adds to the vertical ranges, which makes it appear to be dragging. JavaFX Drag and Zoom Line Chart Example
/**
* The ChartView.
*/
public class ChartController {
private ChartViewModel chartViewModel;
private CustomLineChart<Number, Number> lineChart;
private NumberAxis xAxis;
private NumberAxis yAxis;
private XYChart.Series<Number, Number> series;
private List<Integer> data;
private boolean mouseDragged;
private double initialNumberStart;
private double initialNumberEnd;
#FXML
private VBox mainContainer;
#FXML
private HBox chartContainer;
/**
* The constructor.
*/
public ChartController() {
chartViewModel = new ChartViewModel();
mouseDragged = false;
}
/**
* The initialize method.
*/
public void initialize() {
createChart();
handleEvents();
}
/**
* Handles the events.
*/
private void handleEvents() {
lineChart.setOnMousePressed(pressed -> {
int minSize = 1;
// Get coordinate from the scene and transform to coordinates from the chart axis
Point2D firstSceneCoordinate = new Point2D(pressed.getSceneX(), pressed.getSceneY());
double firstX = xAxis.sceneToLocal(firstSceneCoordinate).getX();
lineChart.setOnMouseDragged(dragged -> {
mouseDragged = true;
Point2D draggedSceneCoordinate = new Point2D(dragged.getSceneX(), dragged.getSceneY());
double draggedX = xAxis.sceneToLocal(draggedSceneCoordinate).getX();
List<Double> numbers = filterSeries(firstX, draggedX);
int size = numbers.size();
double numberStart = size > minSize ? numbers.get(0) : initialNumberStart;
double numberEnd = numbers.size() > minSize ? numbers.get(size - 1) : initialNumberEnd;
if (size > minSize) {
lineChart.addVerticalRangeLines(new Data<>(numberStart, numberEnd));
}
lineChart.setOnMouseReleased(released -> {
if (mouseDragged) {
initialNumberStart = numberStart;
initialNumberEnd = numberEnd;
mouseDragged = false;
redrawChart();
}
});
});
});
}
/**
* Creates the charts.
*/
private void createChart() {
xAxis = new NumberAxis();
yAxis = new NumberAxis();
lineChart = new CustomLineChart<>(xAxis, yAxis);
data = chartViewModel.getData();
createSeries(data);
lineChart.getData().add(series);
initialNumberStart = 1;
initialNumberEnd = data.size() - 1;
chartContainer.getChildren().add(lineChart);
HBox.setHgrow(lineChart, Priority.ALWAYS);
}
/**
* Creates the series for the line chart.
*
* #param numbers The list of numbers for the series
*/
private void createSeries(List<Integer> numbers) {
int size = numbers.size();
series = new XYChart.Series<>();
series.setName("Example");
for (int i = 0; i < size; i++) {
series.getData().add(new XYChart.Data<Number, Number>(i, numbers.get(i)));
}
}
/**
* Filters the nodes and returns the node x positions within the firstX and lastX positions.
*
* #param firstX The first x position
* #param lastX The last x position
* #return The x positions for the nodes within the firstX and lastX
*/
private List<Double> filterSeries(double firstX, double lastX) {
List<Double> nodeXPositions = new ArrayList<>();
lineChart.getData().get(0).getData().forEach(node -> {
double nodeXPosition = lineChart.getXAxis().getDisplayPosition(node.getXValue());
if (nodeXPosition >= firstX && nodeXPosition <= lastX) {
nodeXPositions.add(Double.parseDouble(node.getXValue().toString()));
}
});
return nodeXPositions;
}
/**
* Updates the series for the chart.
*/
private void updateSeries() {
lineChart.getData().remove(0);
lineChart.getData().add(series);
}
/**
* Redraws the chart.
*/
private void redrawChart() {
List<Integer> filteredSeries = new ArrayList<>();
data.forEach(number -> {
if (number >= initialNumberStart && number <= initialNumberEnd) {
filteredSeries.add(number);
}
});
if (!filteredSeries.isEmpty()) {
createSeries(filteredSeries);
updateSeries();
lineChart.removeVerticalRangeLines();
}
}
/**
* Resets the series for the chart.
*
* #param event The event
*/
#FXML
void resetChart(ActionEvent event) {
createSeries(data);
updateSeries();
}
}

JavaFX - How to position the axis label to avoid conflicting with the tick marks

The below code is a simple line chart where the yAxis tick labels are wide in terms of their character length/width. The yAxis label positioned by default is placed too close to the yAxis, meaning the text conflicts with the tick marks. Is it possible to avoid this by increasing the gap between the label and the axis?
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.chart.LineChart;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.XYChart;
import javafx.stage.Stage;
public class Example extends Application {
#Override
public void start(Stage stage) {
final NumberAxis xAxis = new NumberAxis(0, 10, 2);
final NumberAxis yAxis = new NumberAxis(1.3378, 1.3390, 0.0001);
xAxis.setLabel("xAxis");
yAxis.setLabel("yAxis");
yAxis.setTickLabelFormatter(new NumberAxis.DefaultFormatter(yAxis){
#Override public String toString(Number object){
return String.format("%1.4f", object); }
});
final LineChart<Number, Number> lineChart = new LineChart<Number, Number>(xAxis, yAxis);
XYChart.Series series = new XYChart.Series();
final XYChart.Data d1 = new XYChart.Data(0.0,1.3379);
final XYChart.Data d2 = new XYChart.Data(2.0,1.3387);
final XYChart.Data d3 = new XYChart.Data(2.5,1.3385);
final XYChart.Data d4 = new XYChart.Data(3.5,1.3387);
final XYChart.Data d5 = new XYChart.Data(8.0,1.3378);
final XYChart.Data d6 = new XYChart.Data(9.5,1.3388);
series.getData().addAll(d1, d2, d3, d4, d5, d6);
lineChart.getData().add(series);
lineChart.setLegendVisible(false);
final Scene scene = new Scene(lineChart, 400, 300);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
I don't think it is possible to relocate "yAxis" label or increase the gap, but you may want to use the following workaround to avoid the text conficts:
lineChart.getYAxis().setTickLabelRotation(-90);
And you will get the following chart:
It is possible to locate the label of an axis.
For Example to move the Label 10 Pixel to the left, just use:
lineChart.getYAxis()
.lookup(".axis-label")
.setStyle("-fx-label-padding: -10 0 0 0;");

How to change color of bar in a bar chart using Javafx [duplicate]

Here is my code which generates bar chart of 10 values from 0 to 10 . i want to change the color of bars as follows
if i>5 color==red
if i>8 color==blue
so the final out will be 0-5(default yellow bars) 6-8(Red bars) 9(blue bar)
kindly help me..
thanks
public class BarChartSample extends Application {
#Override
public void start(Stage stage) {
stage.setTitle("Bar Chart Sample");
final CategoryAxis xAxis = new CategoryAxis();
final NumberAxis yAxis = new NumberAxis();
final BarChart < String, Number > bc = new BarChart < String, Number > (xAxis, yAxis);
bc.setTitle("Country Summary");
xAxis.setLabel("bars");
yAxis.setLabel("Value");
XYChart.Series series1 = new XYChart.Series();
series1.setName("...");
for (int i = 0; i < 10; i++) {
//here i want to change color of bar if value of i is >5 than red if i>8 than blue
series1.getData().add(new XYChart.Data("Value", i));
}
}
public static void main(String[] args) {
launch(args);
}
}
I created a sample solution.
The solution works by setting the bar's -fx-bar-fill color to a different color based on the value of the bar's data.
final XYChart.Data<String, Number> data = new XYChart.Data("Value " + i , i);
data.nodeProperty().addListener(new ChangeListener<Node>() {
#Override public void changed(ObservableValue<? extends Node> ov, Node oldNode, Node newNode) {
if (newNode != null) {
if (data.getYValue().intValue() > 8 ) {
newNode.setStyle("-fx-bar-fill: navy;");
} else if (data.getYValue().intValue() > 5 ) {
newNode.setStyle("-fx-bar-fill: firebrick;");
}
}
}
});

How to remove legends in javafx line chart

I am trying to plot javafx bar graph using line chart. Each bar line is drawn using a vertical line drawn with two points and line symbol removed. There could be many series(Bar line) in my application but want to show only two legends only.
Currently legends were shown as many series been added. Somehow i am able to show only two legends and hided others. But now problem exist with spaces used by hided legends.
My current code is as below:-
package graph;
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.XYChart;
import javafx.scene.control.Tooltip;
import javafx.stage.Stage;
import com.sun.javafx.charts.Legend;
public class BarGraphUsingLineChart extends Application {
final NumberAxis xAxis = new NumberAxis();
final NumberAxis yAxis = new NumberAxis();
final MyLineChart<Number,Number> lineChart =
new MyLineChart<Number,Number>(xAxis,yAxis);
private boolean valid=true;
private boolean invalid=true;
#Override public void start(Stage stage) {
stage.setTitle("Bar Chart Using Lines");
xAxis.setLabel("Month");
lineChart.setTitle("BAR CHART DEMO");
ObservableList<XYChart.Series<Number,Number>> graphData = FXCollections.observableArrayList();
for(int i=1; i<=10;i++)
{
if(i%2==0)
{
graphData.add(drawBarline(i*10, i*5, true));
}
else{
graphData.add(drawBarline(i*10, i*5, false));
}
}
// Dont show symbol of line charts
lineChart.setCreateSymbols(false);
Scene scene = new Scene(lineChart,800,600);
lineChart.setData(graphData);
stage.setScene(scene);
stage.getScene().getStylesheets().add("/graph/BarChart.css");
updateStyleSheet();
stage.show();
}
private XYChart.Series<Number, Number> drawBarline(Number xAxis, Number yAxis, boolean valid)
{
XYChart.Series<Number, Number> channel_Series = new XYChart.Series<Number, Number>();
channel_Series.getData().add(new XYChart.Data<Number, Number>(yAxis, xAxis ));
channel_Series.getData().add(new XYChart.Data<Number, Number>(yAxis, 0.0 ));
if(valid) {
channel_Series.setName("Valid");
}
else
{
channel_Series.setName("Invalid");
}
return channel_Series;
}
private void updateStyleSheet()
{
for(Node symbol : lineChart.lookupAll(".chart-legend-item")){
if(valid)
{
((Legend)symbol.getParent()).getItems().get(0).setText("Valid");
valid=false;
}
else if(invalid){
((Legend)symbol.getParent()).getItems().get(1).setText("Invalid");
invalid=false;
}
else
{
symbol.setVisible(false);
}
}
// Beloc code removes all the legends
//lineChart.setLegendVisible(false);
for (XYChart.Series<Number, Number> s : lineChart.getData()) {
if(("Valid").equals(s.getName()))
{
s.getNode().setStyle("-fx-stroke: #0000FF; ");
}
else
{
s.getNode().setStyle("-fx-stroke: #FF0000; ");
}
for (XYChart.Data<Number, Number> d : s.getData()) {
Tooltip.install(d.getNode(), new Tooltip("Frequency: "+
d.getXValue()+ " THz, Power: "+
d.getYValue().doubleValue()+" unit"));
}
}
}
public static void main(String[] args) {
launch(args);
}
}
BarChart.css contains are as below:-
.default-color0.chart-legend-item-symbol{
-fx-background-color: #0000FF;
}
.default-color1.chart-legend-item-symbol{
-fx-background-color: #FF0000;
}
Please help me to remove legends or shrink the components where legends are been added. Thanks alot
Since you are already dealing with Legend, you can work with its items, removing those you don't need, so the legend shows only two items.
Using streams, you can mark the first two items as "Valid"/"Invalid" and the rest as "Remove", for instance, and finally you just remove these last items.
private void updateStyleSheet() {
Legend legend = (Legend)lineChart.lookup(".chart-legend");
AtomicInteger count = new AtomicInteger();
legend.getItems().forEach(item->{
if(count.get()==0){
item.setText("Valid");
} else if(count.get()==1){
item.setText("Invalid");
} else {
item.setText("Remove");
}
count.getAndIncrement();
});
legend.getItems().removeIf(item->item.getText().equals("Remove"));
...
}

Resources