Having a XY Line Chart I would like compress/expand data visualization both for X and Y axis by left mouse click, keep pressed and drag left/right and up/down.
Here is a chart example
and here is the code to plot sample data
public class BaseXYChart extends Application {
#Override
public void start(Stage stage) {
stage.setTitle("Linear plot");
final CategoryAxis xAxis = new CategoryAxis();
final NumberAxis yAxis = new NumberAxis(1, 22, 0.5);
yAxis.setTickLabelFormatter(new NumberAxis.DefaultFormatter(yAxis){
#Override
public String toString(Number object){
return String.format("%7.2f", object);
}
});
final LineChart<String, Number>lineChart = new LineChart<String, Number>(xAxis, yAxis);
lineChart.setCreateSymbols(false);
lineChart.setAlternativeRowFillVisible(false);
lineChart.setLegendVisible(false);
XYChart.Series series1 = new XYChart.Series();
series1.getData().add(new XYChart.Data("Jan", 1));
series1.getData().add(new XYChart.Data("Feb", 1.5));
series1.getData().add(new XYChart.Data("Mar", 2));
series1.getData().add(new XYChart.Data("Apr", 2.5));
series1.getData().add(new XYChart.Data("May", 3));
series1.getData().add(new XYChart.Data("Jun", 4));
series1.getData().add(new XYChart.Data("Jul", 6));
series1.getData().add(new XYChart.Data("Aug", 9));
series1.getData().add(new XYChart.Data("Sep", 12));
series1.getData().add(new XYChart.Data("Oct", 15));
series1.getData().add(new XYChart.Data("Nov", 20));
series1.getData().add(new XYChart.Data("Dec", 22));
BorderPane pane = new BorderPane();
pane.setCenter(lineChart);
Scene scene = new Scene(pane, 800, 600);
lineChart.getData().addAll(series1);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
How can I accomplish this? I haven't found any examples anywhere!
Thanks.
Add picture
Result after left mouse click, pressed and drag on Y Axis from top to bottom
Same result should be for X Axis to get a compressed line data by left/right mouse drag
If I understood your question correctly, perhaps you could use something like the following which will resize the chart based on clicking and dragging on the axes.
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.event.EventHandler;
import javafx.scene.*;
import javafx.scene.chart.*;
import javafx.scene.effect.DropShadow;
import javafx.scene.effect.Effect;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
public class DraggableAxisResizableChart extends Application {
private static final int UNDEFINED = -1;
public static void main(String[] args) { launch(args); }
#Override public void start(Stage stage) {
final NumberAxis xAxis = new NumberAxis();
final NumberAxis yAxis = new NumberAxis();
final LineChart<Number, Number> chart = new LineChart(
xAxis, yAxis,
FXCollections.observableArrayList(
new XYChart.Series("April", FXCollections.observableArrayList(
new XYChart.Data(0, 4), new XYChart.Data(1, 10), new XYChart.Data(2, 18), new XYChart.Data(3, 15)
))
)
);
chart.setPrefSize(400, 300);
chart.setMaxSize(400, 300);
makeXAxisDraggable(xAxis, chart);
makeYAxisDraggable(yAxis, chart);
StackPane layout = new StackPane();
layout.getChildren().add(chart);
stage.setScene(new Scene(layout, 800, 600));
stage.show();
}
private void makeXAxisDraggable(final NumberAxis xAxis, final LineChart<Number, Number> chart) {
final Delta d = new Delta();
xAxis.setOnMouseDragged(new EventHandler<MouseEvent>() {
#Override public void handle(MouseEvent event) {
if (d.x == UNDEFINED) {
d.x = event.getSceneX();
d.y = event.getSceneY();
} else {
chart.setMaxHeight(
chart.getPrefHeight() * (
(chart.getPrefHeight() + (event.getSceneY() - d.y) * 2) / chart.getPrefHeight()
)
);
}
}
});
xAxis.setOnMouseReleased(new EventHandler<MouseEvent>() {
#Override public void handle(MouseEvent event) {
d.x = UNDEFINED; d.y = UNDEFINED;
chart.setPrefSize(chart.getMaxWidth(), chart.getMaxHeight());
}
});
addMouseoverGlow(xAxis);
}
private void makeYAxisDraggable(final NumberAxis yAxis, final LineChart<Number, Number> chart) {
final Delta d = new Delta();
yAxis.setOnMouseDragged(new EventHandler<MouseEvent>() {
#Override public void handle(MouseEvent event) {
if (d.x == -1) {
d.x = event.getSceneX();
d.y = event.getSceneY();
} else {
chart.setMaxWidth(
chart.getPrefWidth() * (
(chart.getPrefWidth() - (event.getSceneX() - d.x) * 2) / chart.getPrefWidth()
)
);
}
}
});
yAxis.setOnMouseReleased(new EventHandler<MouseEvent>() {
#Override public void handle(MouseEvent event) {
d.x = UNDEFINED; d.y = UNDEFINED;
chart.setPrefSize(chart.getMaxWidth(), chart.getMaxHeight());
}
});
addMouseoverGlow(yAxis);
}
// create a glow feedback effect on a node when the mouse is hovered over it.
private void addMouseoverGlow(final Node n) {
final Effect glow = new DropShadow(10, Color.GOLDENROD);
n.setOnMouseEntered(new EventHandler<MouseEvent>() {
#Override public void handle(MouseEvent event) {
n.setEffect(glow);
}
});
n.setOnMouseExited(new EventHandler<MouseEvent>() {
#Override public void handle(MouseEvent event) {
n.setEffect(null);
}
});
}
// records a relative point location.
class Delta { double x = UNDEFINED, y = UNDEFINED; }
}
An alternate implementation could use a scale on the node.
The implementation above leaves slight ghost trails as the graph is resized, so you may want to fix that up somehow, if the example proves useful.
Related
This code below draw lines on an XY lineChart
import javafx.application.Application;
import javafx.event.EventHandler;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.chart.CategoryAxis;
import javafx.scene.chart.LineChart;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.XYChart;
import javafx.scene.control.Label;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Line;
import javafx.scene.shape.LineTo;
import javafx.scene.shape.MoveTo;
import javafx.scene.shape.Path;
import javafx.stage.Stage;
public class Lines extends Application {
Path path;
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage stage) {
final CategoryAxis xAxis = new CategoryAxis();
final NumberAxis yAxis = new NumberAxis(0.5, 9.5, 0.1);
yAxis.setTickUnit(1);
//yAxis.setPrefWidth(35);
yAxis.setMinorTickCount(10);
yAxis.setTickLabelFormatter(new NumberAxis.DefaultFormatter(yAxis) {
#Override
public String toString(Number object) {
String label;
label = String.format("%7.2f", object.floatValue());
return label;
}
});
final LineChart<String, Number> lineChart = new LineChart<String, Number>(xAxis, yAxis);
lineChart.setCreateSymbols(false);
lineChart.setAlternativeRowFillVisible(false);
lineChart.setLegendVisible(false);
XYChart.Series series1 = new XYChart.Series();
series1.getData().add(new XYChart.Data("Jan", 1));
series1.getData().add(new XYChart.Data("Feb", 4.5));
series1.getData().add(new XYChart.Data("Mar", 2.5));
series1.getData().add(new XYChart.Data("Apr", 6.5));
series1.getData().add(new XYChart.Data("May", 4.5));
series1.getData().add(new XYChart.Data("Jun", 8.5));
series1.getData().add(new XYChart.Data("Jul", 6.5));
BorderPane bp = new BorderPane();
bp.setCenter(lineChart);
Scene scene = new Scene(bp, 800, 600);
lineChart.setAnimated(false);
lineChart.getData().addAll(series1);
Lines.MouseHandler mh = new Lines.MouseHandler( bp );
bp.setOnMouseClicked( mh );
bp.setOnMouseMoved( mh );
stage.setScene(scene);
path = new Path();
path.setStrokeWidth(1);
path.setStroke(Color.BLACK);
scene.setOnMouseDragged(mh);
scene.setOnMousePressed(mh);
bp.getChildren().add(path);
stage.setScene(scene);
stage.show();
}
class MouseHandler implements EventHandler< MouseEvent > {
private boolean gotFirst = false;
private Line line;
private Pane pane;
private double x1, y1, x2, y2;
public MouseHandler( Pane pane ) {
this.pane = pane;
}
#Override
public void handle( MouseEvent event ) {
if( event.getEventType() == MouseEvent.MOUSE_CLICKED ) {
if( !gotFirst ) {
x1 = x2 = event.getX();
y1 = y2 = event.getY();
line = new Line( x1, y1, x2, y2 );
pane.getChildren().add( line );
gotFirst = true;
}
else {
line = null;
gotFirst = false;
}
}
else {
if( line != null ) {
x2 = event.getX();
y2 = event.getY();
// update line
line.setEndX( x2 );
line.setEndY( y2 );
}
}
}
}
}
I would like to copy (clone) a line once drawn, such as this picture
So, first left mouse click on point 1, then second mouse click on point 2: my goal is to have now a copy of this line (same lenght, same slope) that I can place anywhere on the chart, in this example by a third left mouse click on point 3.
How to do this?
Thanks!
This code draw lines on a chart by left mouse click and move
import javafx.application.Application;
import javafx.event.EventHandler;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.chart.CategoryAxis;
import javafx.scene.chart.LineChart;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.XYChart;
import javafx.scene.control.Label;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Line;
import javafx.scene.shape.LineTo;
import javafx.scene.shape.MoveTo;
import javafx.scene.shape.Path;
import javafx.stage.Stage;
public class Lines extends Application {
Path path;
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage stage) {
final CategoryAxis xAxis = new CategoryAxis();
final NumberAxis yAxis = new NumberAxis(1, 21, 0.1);
yAxis.setTickUnit(1);
yAxis.setPrefWidth(35);
yAxis.setMinorTickCount(10);
yAxis.setTickLabelFormatter(new NumberAxis.DefaultFormatter(yAxis) {
#Override
public String toString(Number object) {
String label;
label = String.format("%7.2f", object.floatValue());
return label;
}
});
final LineChart<String, Number> lineChart = new LineChart<String, Number>(xAxis, yAxis);
lineChart.setCreateSymbols(false);
lineChart.setAlternativeRowFillVisible(false);
lineChart.setLegendVisible(false);
XYChart.Series series1 = new XYChart.Series();
series1.getData().add(new XYChart.Data("Jan", 1));
series1.getData().add(new XYChart.Data("Feb", 4));
series1.getData().add(new XYChart.Data("Mar", 2.5));
series1.getData().add(new XYChart.Data("Apr", 5));
series1.getData().add(new XYChart.Data("May", 6));
series1.getData().add(new XYChart.Data("Jun", 8));
series1.getData().add(new XYChart.Data("Jul", 12));
series1.getData().add(new XYChart.Data("Aug", 8));
series1.getData().add(new XYChart.Data("Sep", 11));
series1.getData().add(new XYChart.Data("Oct", 13));
series1.getData().add(new XYChart.Data("Nov", 10));
series1.getData().add(new XYChart.Data("Dec", 20));
BorderPane bp = new BorderPane();
bp.setCenter(lineChart);
Scene scene = new Scene(bp, 800, 600);
lineChart.setAnimated(false);
lineChart.getData().addAll(series1);
Lines.MouseHandler mh = new Lines.MouseHandler( bp );
bp.setOnMouseClicked( mh );
bp.setOnMouseMoved( mh );
stage.setScene(scene);
path = new Path();
path.setStrokeWidth(1);
path.setStroke(Color.BLACK);
scene.setOnMouseDragged(mh);
scene.setOnMousePressed(mh);
bp.getChildren().add(path);
stage.setScene(scene);
stage.show();
}
class MouseHandler implements EventHandler< MouseEvent > {
private boolean gotFirst = false;
private Line line;
private Pane pane;
private double x1, y1, x2, y2;
public MouseHandler( Pane pane ) {
this.pane = pane;
}
#Override
public void handle( MouseEvent event ) {
if( event.getEventType() == MouseEvent.MOUSE_CLICKED ) {
if( !gotFirst ) {
x1 = x2 = event.getX();
y1 = y2 = event.getY();
line = new Line( x1, y1, x2, y2 );
pane.getChildren().add( line );
gotFirst = true;
}
else {
line = null;
gotFirst = false;
}
}
else {
if( line != null ) {
x2 = event.getX();
y2 = event.getY();
// update line
line.setEndX( x2 );
line.setEndY( y2 );
}
}
}
}
}
My question is: how to edit such lines once plotted?
In example, once one or more line(s) are drawn I would like to select one of these, and by right mouse click and using a pop-up menu, delete it, modify length, (to make it shorter or longer) or change line slope.
Thanks all.
The following modifications will add functionality to move the line when dragged with left mouse button.
Also, the line will be removed when clicked with right mouse button.
This is the line handler callback class.
class LineHandler implements EventHandler< MouseEvent > {
double x, y;
Pane pane;
public LineHandler(Pane pane){
this.pane = pane;
}
#Override
public void handle( MouseEvent e ) {
Line l = (Line) e.getSource();
// remove line on right click
if( e.getEventType() == MouseEvent.MOUSE_PRESSED
&& e.isSecondaryButtonDown() ) {
pane.getChildren().remove( l );
} else if( e.getEventType() == MouseEvent.MOUSE_DRAGGED
&& e.isPrimaryButtonDown() ) {
double tx = e.getX();
double ty = e.getY();
double dx = tx - x;
double dy = ty - y;
l.setStartX( l.getStartX() + dx );
l.setStartY( l.getStartY() + dy );
l.setEndX( l.getEndX() + dx );
l.setEndY( l.getEndY() + dy );
x = tx;
y = ty;
} else if( e.getEventType() == MouseEvent.MOUSE_ENTERED ) {
// just to show that the line is selected
x = e.getX();
y = e.getY();
l.setStroke( Color.RED );
} else if( e.getEventType() == MouseEvent.MOUSE_EXITED ) {
l.setStroke( Color.BLACK );
}
// should not pass event to the parent
e.consume();
}
}
Create the line handler in mouse handler class:
private LineHandler lineHandler;
public MouseHandler( Pane pane ) {
this.pane = pane;
lineHandler = new LineHandler(pane);
}
Add the handler to each line in the else clause of !gotFirst
} else {
line.setOnMouseEntered( lineHandler );
line.setOnMouseExited( lineHandler );
line.setOnMouseDragged( lineHandler );
line.setOnMousePressed( lineHandler );
// to consume the event
line.setOnMouseClicked( lineHandler );
line.setOnMouseReleased( lineHandler );
line = null;
gotFirst = false;
}
You can add the line remove functionality to a popup event.
I my code below I plot two series in a SplitPane, but I have some white borders I would like to remove, so that the grids will fill all the chart space.
Here is a picture to show where I would like to remove
and this is my code
import javafx.application.Application;
import javafx.application.Platform;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.collections.ObservableList;
import javafx.event.EventHandler;
import javafx.geometry.Orientation;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.chart.BarChart;
import javafx.scene.chart.CategoryAxis;
import javafx.scene.chart.Chart;
import javafx.scene.chart.LineChart;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.XYChart;
import javafx.scene.control.SplitPane;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
public class XyChartInSplitMove extends Application {
SplitPane splitPane1 = null;
BorderPane pane;
BorderPane pane2;
XYChart.Series series1 = new XYChart.Series();
XYChart.Series series2 = new XYChart.Series();
SimpleDoubleProperty rectinitX = new SimpleDoubleProperty();
SimpleDoubleProperty rectinitY = new SimpleDoubleProperty();
#Override
public void start(Stage stage) {
final NumberAxis xAxis = new NumberAxis(1, 12, 1);
final NumberAxis yAxis = new NumberAxis(0.53000, 0.53910, 0.0005);
xAxis.setAnimated(false);
yAxis.setAnimated(false);
xAxis.setScaleX(0);
xAxis.setVisible(false);
yAxis.setTickLabelFormatter(new NumberAxis.DefaultFormatter(yAxis) {
#Override
public String toString(Number object) {
return String.format("%7.5f", object);
}
});
final LineChart<Number, Number> lineChart1 = new LineChart<Number, Number>(xAxis, yAxis);
lineChart1.setCreateSymbols(false);
lineChart1.setAlternativeRowFillVisible(false);
lineChart1.setAnimated(false);
lineChart1.setLegendVisible(false);
series1.getData().add(new XYChart.Data(1, 0.53185));
series1.getData().add(new XYChart.Data(2, 0.532235));
series1.getData().add(new XYChart.Data(3, 0.53234));
series1.getData().add(new XYChart.Data(4, 0.538765));
series1.getData().add(new XYChart.Data(5, 0.53442));
series1.getData().add(new XYChart.Data(6, 0.534658));
series1.getData().add(new XYChart.Data(7, 0.53023));
series1.getData().add(new XYChart.Data(8, 0.53001));
series1.getData().add(new XYChart.Data(9, 0.53589));
series1.getData().add(new XYChart.Data(10, 0.53476));
series1.getData().add(new XYChart.Data(11, 0.530123));
series1.getData().add(new XYChart.Data(12, 0.531035));
lineChart1.getData().addAll(series1);
pane = new BorderPane();
pane.setCenter(lineChart1);
splitPane1 = new SplitPane();
splitPane1.setOrientation(Orientation.VERTICAL);
splitPane1.getItems().addAll(pane);
splitPane1.setDividerPosition(0, 1);
Platform.runLater(new Runnable() {
#Override
public void run() {
double percSplit;
ObservableList<SplitPane.Divider> splitDiv = splitPane1.getDividers();
percSplit = 1 / (double) (splitDiv.size() + 1);
for (int i = 0; i < splitDiv.size(); i++) {
splitPane1.setDividerPosition(i, percSplit);
percSplit += 1 / (double) (splitDiv.size() + 1);
}
}
});
//BarChart
final CategoryAxis xAxis2 = new CategoryAxis();
final NumberAxis yAxis2 = new NumberAxis();
yAxis2.setTickUnit(1);
yAxis2.setPrefWidth(35);
yAxis2.setMinorTickCount(10);
yAxis2.setTickLabelFormatter(new NumberAxis.DefaultFormatter(yAxis2) {
#Override
public String toString(Number object) {
String label;
label = String.format("%7.2f", object.floatValue());
return label;
}
});
final BarChart<String, Number> barChart2 = new BarChart<String, Number>(xAxis2, yAxis2);
barChart2.setAlternativeRowFillVisible(false);
barChart2.setLegendVisible(false);
barChart2.setAnimated(false);
series2.getData().add(new XYChart.Data("Jan", 1));
series2.getData().add(new XYChart.Data("Feb", 3));
series2.getData().add(new XYChart.Data("Mar", 1.5));
series2.getData().add(new XYChart.Data("Apr", 3));
series2.getData().add(new XYChart.Data("May", 4.5));
series2.getData().add(new XYChart.Data("Jun", 5));
series2.getData().add(new XYChart.Data("Jul", 4));
series2.getData().add(new XYChart.Data("Aug", 8));
series2.getData().add(new XYChart.Data("Sep", 16.5));
series2.getData().add(new XYChart.Data("Oct", 13.9));
series2.getData().add(new XYChart.Data("Nov", 17));
series2.getData().add(new XYChart.Data("Dec", 20));
barChart2.getData().addAll(series2);
pane2 = new BorderPane();
pane2.setCenter(barChart2);
Platform.runLater(new Runnable() {
#Override
public void run() {
double percSplit;
splitPane1.getItems().addAll(pane2);
ObservableList<SplitPane.Divider> splitDiv = splitPane1.getDividers();
percSplit = 1 / (double) (splitDiv.size() + 1);
for (int i = 0; i < splitDiv.size(); i++) {
splitPane1.setDividerPosition(i, percSplit);
percSplit += 1 / (double) (splitDiv.size() + 1);
}
}
});
Scene scene = new Scene(splitPane1, 800, 600);
stage.setScene(scene);
pane.setOnMouseClicked(mouseHandler);
pane.setOnMouseDragged(mouseHandler);
pane.setOnMouseEntered(mouseHandler);
pane.setOnMouseExited(mouseHandler);
pane.setOnMouseMoved(mouseHandler);
pane.setOnMouseReleased(mouseHandler);
stage.show();
}
EventHandler<MouseEvent> mouseHandler = new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent mouseEvent) {
if (mouseEvent.getEventType() == MouseEvent.MOUSE_DRAGGED || mouseEvent.getEventType() == MouseEvent.MOUSE_MOVED) {
LineChart<Number, Number> lineChart = (LineChart<Number, Number>) pane.getCenter();
NumberAxis yAxis = (NumberAxis) lineChart.getYAxis();
NumberAxis xAxis = (NumberAxis) lineChart.getXAxis();
double newXlower = xAxis.getLowerBound(), newXupper = xAxis.getUpperBound();
double newYlower = yAxis.getLowerBound(), newYupper = yAxis.getUpperBound();
double delta = 0.3;
if (mouseEvent.getEventType() == MouseEvent.MOUSE_DRAGGED) {
if (rectinitX.get() < mouseEvent.getX()) {
newXlower = xAxis.getLowerBound() - delta;
newXupper = xAxis.getUpperBound() - delta;
} else if (rectinitX.get() > mouseEvent.getX()) {
newXlower = xAxis.getLowerBound() + delta;
newXupper = xAxis.getUpperBound() + delta;
}
xAxis.setLowerBound(newXlower);
xAxis.setUpperBound(newXupper);
// Y-Axis Moving
if (rectinitY.get() < mouseEvent.getY()) {
newYlower = yAxis.getLowerBound() + delta / 1000;
newYupper = yAxis.getUpperBound() + delta / 1000;
} else if (rectinitY.get() > mouseEvent.getY()) {
newYlower = yAxis.getLowerBound() - delta / 1000;
newYupper = yAxis.getUpperBound() - delta / 1000;
}
yAxis.setLowerBound(newYlower);
yAxis.setUpperBound(newYupper);
}
rectinitX.set(mouseEvent.getX());
rectinitY.set(mouseEvent.getY());
BarChart<String, Number> barChart2 = (BarChart<String, Number>) pane2.getCenter();
double chartWidth = xAxis.getWidth();
double axisSpan = xAxis.getUpperBound() - xAxis.getLowerBound();
double displacement = (chartWidth / axisSpan) * (1 - newXlower);
for (Node node : barChart2.getChildrenUnmodifiable()) {
if (node.getClass().getEnclosingClass() == Chart.class) {
for (Node node2 : ((Parent) node).getChildrenUnmodifiable()) {
if ((node2 == barChart2.getXAxis())) {
node2.translateXProperty().set(displacement);
}
if (node2.getClass().getEnclosingClass() == XYChart.class) {
for (Node node3 : ((Parent) node2).getChildrenUnmodifiable()) {
if (node3.getClass() == Group.class) {
node3.translateXProperty().set(displacement);
}
}
}
}
}
}
}
}
};
public static void main(String[] args) {
launch(args);
}
}
To hide the upper X axis I have used xAxis.setScaleX(0); but I am not sure if this is the best way to hide X axis.
Any help really appreciated
Define a style.css:
.chart {
-fx-padding: 1px;
}
.chart-content {
-fx-padding: 0px;
}
Add this file to the stylesheets of app:
scene.getStylesheets().add(this.getClass().getResource("style.css").toExternalForm());
See the results.
Further. To hide the gap between top and bottom charts, you can move top chart to down like this, the gap appears on the top however:
...
...
stage.show();
// It should be after stage.show()
lineChart1.setTranslateY(xAxis.getHeight());
// Or give constant value if you want to put this code before the stage.show()
// lineChart1.setTranslateY(28);
My class below plots two charts in a splitpane.
By left mouse click and drag the upper line chart moves left/right (and up/down).
I would like to move both panes together left/right when I click and drag with left mouse on the upper graph: how to accomplish this?
Thanks
public class XyChartInSplitMove extends Application {
SplitPane splitPane1 = null;
BorderPane pane;
BorderPane pane2;
XYChart.Series series1 = new XYChart.Series();
XYChart.Series series2 = new XYChart.Series();
SimpleDoubleProperty rectinitX = new SimpleDoubleProperty();
SimpleDoubleProperty rectinitY = new SimpleDoubleProperty();
#Override
public void start(Stage stage) {
final NumberAxis xAxis = new NumberAxis(1, 12, 1);
final NumberAxis yAxis = new NumberAxis(0.53000, 0.53910, 0.0005);
xAxis.setAnimated(false);
yAxis.setAnimated(false);
yAxis.setTickLabelFormatter(new NumberAxis.DefaultFormatter(yAxis) {
#Override
public String toString(Number object) {
return String.format("%7.5f", object);
}
});
final LineChart<Number, Number> lineChart1 = new LineChart<Number, Number>(xAxis, yAxis);
lineChart1.setCreateSymbols(false);
lineChart1.setAlternativeRowFillVisible(false);
lineChart1.setAnimated(false);
lineChart1.setLegendVisible(false);
series1.getData().add(new XYChart.Data(1, 0.53185));
series1.getData().add(new XYChart.Data(2, 0.532235));
series1.getData().add(new XYChart.Data(3, 0.53234));
series1.getData().add(new XYChart.Data(4, 0.538765));
series1.getData().add(new XYChart.Data(5, 0.53442));
series1.getData().add(new XYChart.Data(6, 0.534658));
series1.getData().add(new XYChart.Data(7, 0.53023));
series1.getData().add(new XYChart.Data(8, 0.53001));
series1.getData().add(new XYChart.Data(9, 0.53589));
series1.getData().add(new XYChart.Data(10, 0.53476));
series1.getData().add(new XYChart.Data(11, 0.530123));
series1.getData().add(new XYChart.Data(12, 0.531035));
lineChart1.getData().addAll(series1);
pane = new BorderPane();
pane.setCenter(lineChart1);
splitPane1 = new SplitPane();
splitPane1.setOrientation(Orientation.VERTICAL);
splitPane1.getItems().addAll(pane);
splitPane1.setDividerPosition(0, 1);
Platform.runLater(new Runnable() {
#Override
public void run() {
double percSplit;
ObservableList<SplitPane.Divider> splitDiv = splitPane1.getDividers();
percSplit = 1/(double)(splitDiv.size()+1);
for (int i = 0; i< splitDiv.size(); i++) {
splitPane1.setDividerPosition(i, percSplit);
percSplit += 1/(double)(splitDiv.size()+1);
}
}
});
//BarChart
final CategoryAxis xAxis2 = new CategoryAxis();
final NumberAxis yAxis2 = new NumberAxis();
yAxis2.setTickUnit(1);
yAxis2.setPrefWidth(35);
yAxis2.setMinorTickCount(10);
yAxis2.setTickLabelFormatter(new NumberAxis.DefaultFormatter(yAxis2){
#Override
public String toString(Number object){
String label;
label = String.format("%7.2f", object.floatValue());
return label;
}
});
final BarChart<String, Number>BarChart2 = new BarChart<String, Number>(xAxis2, yAxis2);
BarChart2.setAlternativeRowFillVisible(false);
BarChart2.setLegendVisible(false);
BarChart2.setAnimated(false);
XYChart.Series series2 = new XYChart.Series();
series2.getData().add(new XYChart.Data("Jan", 1));
series2.getData().add(new XYChart.Data("Feb", 3));
series2.getData().add(new XYChart.Data("Mar", 1.5));
series2.getData().add(new XYChart.Data("Apr", 3));
series2.getData().add(new XYChart.Data("May", 4.5));
series2.getData().add(new XYChart.Data("Jun", 5));
series2.getData().add(new XYChart.Data("Jul", 4));
series2.getData().add(new XYChart.Data("Aug", 8));
series2.getData().add(new XYChart.Data("Sep", 16.5));
series2.getData().add(new XYChart.Data("Oct", 13.9));
series2.getData().add(new XYChart.Data("Nov", 17));
series2.getData().add(new XYChart.Data("Dec", 20));
BarChart2.getData().addAll(series2);
Platform.runLater(new Runnable() {
#Override
public void run() {
double percSplit;
splitPane1.getItems().addAll(BarChart2);
ObservableList<SplitPane.Divider> splitDiv = splitPane1.getDividers();
percSplit = 1/(double)(splitDiv.size()+1);
for (int i = 0; i< splitDiv.size(); i++) {
splitPane1.setDividerPosition(i, percSplit);
percSplit += 1/(double)(splitDiv.size()+1);
}
}
});
Scene scene = new Scene(splitPane1, 800, 600);
stage.setScene(scene);
pane.setOnMouseClicked(mouseHandler);
pane.setOnMouseDragged(mouseHandler);
pane.setOnMouseEntered(mouseHandler);
pane.setOnMouseExited(mouseHandler);
pane.setOnMouseMoved(mouseHandler);
pane.setOnMouseReleased(mouseHandler);
stage.show();
}
EventHandler<MouseEvent> mouseHandler = new EventHandler<MouseEvent>(){
#Override
public void handle(MouseEvent mouseEvent) {
if (mouseEvent.getEventType() == MouseEvent.MOUSE_DRAGGED || mouseEvent.getEventType() == MouseEvent.MOUSE_MOVED){
LineChart<Number, Number> lineChart = (LineChart<Number, Number>) pane.getCenter();
NumberAxis yAxis = (NumberAxis) lineChart.getYAxis();
NumberAxis xAxis = (NumberAxis) lineChart.getXAxis();
double Tgap = xAxis.getWidth()/(xAxis.getUpperBound() - xAxis.getLowerBound());
double newXlower=xAxis.getLowerBound(), newXupper=xAxis.getUpperBound();
double newYlower=yAxis.getLowerBound(), newYupper=yAxis.getUpperBound();
double xAxisShift = getSceneShift(xAxis);
double yAxisShift = getSceneShift(yAxis);
double yAxisStep=yAxis.getHeight()/(yAxis.getUpperBound()-yAxis.getLowerBound());
double CurrentPrice=yAxis.getUpperBound()-((mouseEvent.getY()-yAxisShift)/yAxisStep);
double Delta=0.3;
if(mouseEvent.getEventType() == MouseEvent.MOUSE_DRAGGED){
if(rectinitX.get() < mouseEvent.getX()){
newXlower=xAxis.getLowerBound()-Delta;
newXupper=xAxis.getUpperBound()-Delta;
}
else if(rectinitX.get() > mouseEvent.getX()){
newXlower=xAxis.getLowerBound()+Delta;
newXupper=xAxis.getUpperBound()+Delta;
}
xAxis.setLowerBound( newXlower );
xAxis.setUpperBound( newXupper );
//========== Y-Axis Moving ============================
if(rectinitY.get() < mouseEvent.getY()){
newYlower=yAxis.getLowerBound()+Delta/1000;
newYupper=yAxis.getUpperBound()+Delta/1000;
}
else if(rectinitY.get() > mouseEvent.getY()){
newYlower=yAxis.getLowerBound()-Delta/1000;
newYupper=yAxis.getUpperBound()-Delta/1000;
}
yAxis.setLowerBound(newYlower);
yAxis.setUpperBound(newYupper);
}
rectinitX.set(mouseEvent.getX());
rectinitY.set(mouseEvent.getY());
if(mouseEvent.getEventType() == MouseEvent.MOUSE_MOVED && mouseEvent.getY()>yAxisShift && mouseEvent.getY()<yAxisShift+yAxis.getHeight() && mouseEvent.getX()>xAxisShift && mouseEvent.getX()<xAxisShift+xAxis.getWidth()){
double XX=((mouseEvent.getX() - xAxisShift) / Tgap) + xAxis.getLowerBound();
double YY=CurrentPrice;
int XLB=(int) xAxis.getLowerBound();
int XUB=(int) xAxis.getUpperBound();
}
}
}
};
private static double getSceneShift(Node node) {
double shift = 0;
do {
shift += node.getLayoutX();
node = node.getParent();
} while (node != null);
return shift;
}
private static String getHIstLOstY(XYChart.Series S,int XLowerBound,int XUpperBound) {
double ValLOst=1000000;
double ValHIst=-1000000;
for(int i=XLowerBound; i<XUpperBound; i++){
double P=GetPrice(S,i);
if(ValHIst<P){
ValHIst=P;
}
if(ValLOst>P){
ValLOst=P;
}
}
return Double.toString(ValLOst) + "," + Double.toString(ValHIst);
}
private static double GetPrice(XYChart.Series S,int IX) {
Object SVal=S.getData().get(IX);
String Temp=SVal.toString().replaceAll("Data", "");
Temp=Temp.replace("[", "");
Temp=Temp.replace("]", "");
String[] TempArray=Temp.split(",");
return Double.parseDouble(TempArray[1]);
}
public static void main(String[] args) {
launch(args);
}
}
Option 1 (simple):
Imagine you have the same mouse handlers in both charts, then you can drag both charts separately. In this circumstances you need to update mouse handlers in top chart to call same events in bottom chart (minus y coordinate difference)
Option 2 (good):
Bind bottom chart axis ranges to top chart ones (yes, it may involve some complex math) as you did it for chart's starting states. So on update of top chart bottom chart will get updated automatically.
I have a toggle buttons group with 4 different pointers
When I select the crosshair button ( right most one ) I have as follow
Each button calls a different line pointer follower :
left most one button: it is a simple pointer without line;
second display only vertical line pointer movement linked;
third one only horizontal line pointer movement linked;
a crosshair.
I would like to create a generic class to set the selected line so I can call it in the draw routine.
This generic/abstract class "will contains" the specific line pointer follower code when the appropriate button is selected, in this way the draw routine will only refer to the generic class to plot the select pointer line follower.
The code with crosshair is:
public class JavaFXApplicationMove extends Application {
Path path;
BorderPane pane;
Rectangle rect;
Line LH;
Line LV;
XYChart.Series series1 = new XYChart.Series();
SimpleDoubleProperty rectinitX = new SimpleDoubleProperty();
SimpleDoubleProperty rectinitY = new SimpleDoubleProperty();
SimpleDoubleProperty rectX = new SimpleDoubleProperty();
SimpleDoubleProperty rectY = new SimpleDoubleProperty();
#Override
public void start(Stage stage) {
stage.setTitle("Lines plot");
final NumberAxis xAxis = new NumberAxis(1, 12, 1);
final NumberAxis yAxis = new NumberAxis(0.53000, 0.53910, 0.0005);
xAxis.setAnimated(false);
yAxis.setAnimated(false);
yAxis.setTickLabelFormatter(new NumberAxis.DefaultFormatter(yAxis) {
#Override
public String toString(Number object) {
return String.format("%7.5f", object);
}
});
//final LineChart<String, Number> lineChart = new LineChart<String, Number> (xAxis, yAxis);
final LineChart<Number, Number> lineChart = new LineChart<Number, Number>(xAxis, yAxis);
lineChart.setCreateSymbols(false);
lineChart.setAlternativeRowFillVisible(false);
lineChart.setAnimated(true);
series1.setName("Stock1");
series1.getData().add(new XYChart.Data(1, 0.53185));
series1.getData().add(new XYChart.Data(2, 0.532235));
series1.getData().add(new XYChart.Data(3, 0.53234));
series1.getData().add(new XYChart.Data(4, 0.538765));
series1.getData().add(new XYChart.Data(5, 0.53442));
series1.getData().add(new XYChart.Data(6, 0.534658));
series1.getData().add(new XYChart.Data(7, 0.53023));
series1.getData().add(new XYChart.Data(8, 0.53001));
series1.getData().add(new XYChart.Data(9, 0.53589));
series1.getData().add(new XYChart.Data(10, 0.53476));
pane = new BorderPane();
pane.setCenter(lineChart);
Scene scene = new Scene(pane, 800, 600);
lineChart.getData().addAll(series1);
stage.setScene(scene);
path = new Path();
path.setStrokeWidth(5);
path.setStroke(Color.RED);
scene.setOnMouseClicked(mouseHandler);
scene.setOnMouseDragged(mouseHandler);
scene.setOnMouseEntered(mouseHandler);
scene.setOnMouseExited(mouseHandler);
scene.setOnMouseMoved(mouseHandler);
scene.setOnMousePressed(mouseHandler);
scene.setOnMouseReleased(mouseHandler);
rect = new Rectangle();
rect.setFill(Color.web("yellow", 0.3));
rect.setStroke(Color.MAGENTA);
rect.setStrokeDashOffset(50);
rect.widthProperty().bind(rectX.subtract(rectinitX));
rect.heightProperty().bind(rectY.subtract(rectinitY));
pane.getChildren().add(rect);
//LH=new Line();
LH=LineBuilder.create()
.startX(0)
.startY(0)
.endX(10)
.endY(.535)
.strokeWidth(1)
.stroke(Color.BLACK)
.build();
pane.getChildren().add(LH);
LV=LineBuilder.create()
.startX(0)
.startY(0)
.endX(10)
.endY(.535)
.strokeWidth(1)
.stroke(Color.BLACK)
.build();
pane.getChildren().add(LV);
stage.show();
}
EventHandler<MouseEvent> mouseHandler = new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent mouseEvent) {
if (mouseEvent.getEventType() == MouseEvent.MOUSE_PRESSED) {
rect.setX(mouseEvent.getX());
rect.setY(mouseEvent.getY());
rectinitX.set(mouseEvent.getX());
rectinitY.set(mouseEvent.getY());
LH.setStartX(0);
LH.setStartY(0);
LH.setEndX(0);
LH.setEndY(0);
LV.setStartX(0);
LV.setStartY(0);
LV.setEndX(0);
LV.setEndY(0);
} else if (mouseEvent.getEventType() == MouseEvent.MOUSE_RELEASED) {
rectX.set(mouseEvent.getX());
rectY.set(mouseEvent.getY());
// Hide the rectangle
rectX.set(0);
rectY.set(0);
} else if (mouseEvent.getEventType() == MouseEvent.MOUSE_DRAGGED || mouseEvent.getEventType() == MouseEvent.MOUSE_MOVED) {
LineChart<Number, Number> lineChart = (LineChart<Number, Number>) pane.getCenter();
NumberAxis yAxis = (NumberAxis) lineChart.getYAxis();
NumberAxis xAxis = (NumberAxis) lineChart.getXAxis();
System.out.println("(a) xAxis.getLowerBound() "+xAxis.getLowerBound()+" "+xAxis.getUpperBound());
double Tgap = xAxis.getWidth()/(xAxis.getUpperBound() - xAxis.getLowerBound());
double newXlower=xAxis.getLowerBound(), newXupper=xAxis.getUpperBound();
double newYlower=yAxis.getLowerBound(), newYupper=yAxis.getUpperBound();
double xAxisShift = getSceneShift(xAxis);
double yAxisShift = getSceneShift(yAxis);
double yAxisStep=yAxis.getHeight()/(yAxis.getUpperBound()-yAxis.getLowerBound());
double CurrentPrice=yAxis.getUpperBound()-((mouseEvent.getY()-yAxisShift)/yAxisStep);
double Delta=0.3;
if(mouseEvent.getEventType() == MouseEvent.MOUSE_DRAGGED){
if(rectinitX.get() < mouseEvent.getX()){
newXlower=xAxis.getLowerBound()-Delta;
newXupper=xAxis.getUpperBound()-Delta;
}
else if(rectinitX.get() > mouseEvent.getX()){
newXlower=xAxis.getLowerBound()+Delta;
newXupper=xAxis.getUpperBound()+Delta;
}
xAxis.setLowerBound( newXlower );
xAxis.setUpperBound( newXupper );
if(rectinitY.get() < mouseEvent.getY()){
newYlower=yAxis.getLowerBound()+Delta/1000;
newYupper=yAxis.getUpperBound()+Delta/1000;
}
else if(rectinitY.get() > mouseEvent.getY()){
newYlower=yAxis.getLowerBound()-Delta/1000;
newYupper=yAxis.getUpperBound()-Delta/1000;
}
yAxis.setLowerBound(newYlower);
yAxis.setUpperBound(newYupper);
}
//System.out.println("(b) xAxis.getLowerBound() "+xAxis.getLowerBound()+" "+xAxis.getUpperBound());
rectinitX.set(mouseEvent.getX());
rectinitY.set(mouseEvent.getY());
if(mouseEvent.getEventType() == MouseEvent.MOUSE_MOVED && mouseEvent.getY()>yAxisShift && mouseEvent.getY()<yAxisShift+yAxis.getHeight() && mouseEvent.getX()>xAxisShift && mouseEvent.getX()<xAxisShift+xAxis.getWidth()){
LH.setStartX(xAxisShift);
LH.setStartY(mouseEvent.getY());
LH.setEndX(xAxisShift+xAxis.getWidth());
LH.setEndY(mouseEvent.getY());
LV.setStartX(mouseEvent.getX());
LV.setStartY(yAxisShift);
LV.setEndX(mouseEvent.getX());
LV.setEndY(yAxisShift+yAxis.getHeight());
double XX=((mouseEvent.getX() - xAxisShift) / Tgap) + xAxis.getLowerBound();
double YY=CurrentPrice;
series1.setName(String.format("%.2g%n",XX) + ", " + String.format("%.4g%n",YY));
int XLB=(int) xAxis.getLowerBound();
int XUB=(int) xAxis.getUpperBound();
}
}
}
};
private static double getSceneShift(Node node) {
double shift = 0;
do {
shift += node.getLayoutX();
node = node.getParent();
} while (node != null);
return shift;
}
private static String getHIstLOstY(XYChart.Series S,int XLowerBound,int XUpperBound) {
double ValLOst=1000000;
double ValHIst=-1000000;
for(int i=XLowerBound; i<XUpperBound; i++){
double P=GetPrice(S,i);
if(ValHIst<P){
ValHIst=P;
}
if(ValLOst>P){
ValLOst=P;
}
}
return Double.toString(ValLOst) + "," + Double.toString(ValHIst);
}
private static double GetPrice(XYChart.Series S,int IX) {
Object SVal=S.getData().get(IX);
//return SVal.toString().toLowerCase();
String Temp=SVal.toString().replaceAll("Data", "");
Temp=Temp.replace("[", "");
Temp=Temp.replace("]", "");
String[] TempArray=Temp.split(",");
return Double.parseDouble(TempArray[1]);
}
public static void main(String[] args) {
launch(args);
}
}
All your drawing routing in handed in EventHandler<MouseEvent>. So easiest way to change crosshair type would be substituting various handlers.
By going a bit deeper you'll see that every crosshair drawing is based on similar parameters and can be divided in 3 steps: start, update and end.
From what I see in your code important parameters are sceneShiftX, sceneShiftY, mouseX, mouseY. So, you need to refactor out drawing code from mouse event handler to a separate class with next methods:
public class SimpleCrosshair {
public void start(double mouseX, double mouseY);
public void update(double sceneShiftX, double sceneShiftY, double mouseX, double mouseY);
public void finish();
}
You put crosshair related code from MouseEvent.MOUSE_PRESSED block to start(), from MouseEvent.MOUSE_RELEASED to finish() and from MouseEvent.MOUSE_DRAGGED to update().
Now as you separated all your drawing data from mouse events and can introduce an interface Crosshair which will look similar to class above and make SimpleCrosshair implement Crosshair.
The rest is easy. Create classes implementing Crosshair which will do drawing for other crosshair type and introduce field private Crosshair currentCrosshair; which you can update with concrete implementation on toolbar button click.
And your mouse events handler will look like (aside from zoom logic):
EventHandler<MouseEvent> mouseHandler = new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent mouseEvent) {
if (mouseEvent.getEventType() == MouseEvent.MOUSE_PRESSED) {
currentCrosshair.start(mouseEvent.getX(), mouseEvent.getY());
} else if (mouseEvent.getEventType() == MouseEvent.MOUSE_RELEASED) {
currentCrosshair.finish();
} else if (mouseEvent.getEventType() == MouseEvent.MOUSE_DRAGGED || mouseEvent.getEventType() == MouseEvent.MOUSE_MOVED) {
LineChart<Number, Number> lineChart = (LineChart<Number, Number>) pane.getCenter();
NumberAxis yAxis = (NumberAxis) lineChart.getYAxis();
NumberAxis xAxis = (NumberAxis) lineChart.getXAxis();
double xAxisShift = getSceneShift(xAxis);
double yAxisShift = getSceneShift(yAxis);
currentCrosshair.update(xAxisShift, yAxisShift, mouseEvent.getX(), mouseEvent.getY());
}
}
}