Working on some old project and would like to implement some futures for self-learning. Trying to figure out how can I do interpolation on my surface, need to eliminate "artifacts", it does not look greate with them. Already found some information about interpolation in JavaFX and they are 7 types of :
Linear
Discrete
Ease-in
Ease-out
Ease-both
Spline
Tangent
Was thinking between Linear and Tangent, but honestly i do not know how exactly it would be.
If it is going about Tangent interpolation, it has to be between frames ? Linear is one of the argument of the KeyValue Constructor.
That is how it looks atm with those artefacts and animation does not look "smooth".
That is the code responsible of creating surface and animation.
package sample;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
public class Main extends Application {
#Override
public void start(Stage primaryStage) throws Exception{
// 1D - We does not need that
// Parent root = FXMLLoader.load(getClass().getResource("sample.fxml"));
Parent root = FXMLLoader.load(getClass().getResource("packet3D/Window3DSceneView.fxml"));
primaryStage.setTitle("Animation 1D");
primaryStage.setScene(new Scene(root, 1200, 800));
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
package sample.packet3D;
import java.util.function.Function;
import org.fxyz.cameras.CameraTransformer;
import org.fxyz.shapes.primitives.SurfacePlotMesh;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.scene.Group;
import javafx.scene.PerspectiveCamera;
import javafx.scene.PointLight;
import javafx.scene.paint.Color;
import javafx.scene.shape.CullFace;
import javafx.scene.transform.Rotate;
import javafx.util.Duration;
public class Window3DBuilder{
private Group group;
private SurfacePlotMesh surface;
private final CameraTransformer cameraTransformer;
private final PerspectiveCamera perspectiveCamera;
private PointLight light;
private double time = 0 ;
private double lambda = 1;
private double k = (2 * Math.PI) / lambda;
private final Rotate rotateX = new Rotate(180, Rotate.X_AXIS);
private final Rotate rotateY = new Rotate(-90, Rotate.Y_AXIS);
private Function<Double, Double> gaussFunctionR;
public Window3DBuilder( Group group, PerspectiveCamera perspectiveCamera ) {
this.group = group;
this.perspectiveCamera = perspectiveCamera;
cameraTransformer = new CameraTransformer();
}
public void createScene() {
createSurface();
group.getChildren().addAll( surface, cameraTransformer );
createCamera();
createLight();
update();
}
private void createCamera() {
cameraTransformer.setTranslate(0, 0, 0);
cameraTransformer.getChildren().addAll(perspectiveCamera);
perspectiveCamera.setNearClip(0.1);
perspectiveCamera.setFarClip(100000.0);
perspectiveCamera.setTranslateX((group.getBoundsInLocal().getMaxX() + group.getBoundsInLocal().getMinX()) / 2d);
perspectiveCamera.setTranslateY((group.getBoundsInLocal().getMaxY() + group.getBoundsInLocal().getMinY()) / 2d);
double max = Math.max(group.getBoundsInLocal().getWidth(), group.getBoundsInLocal().getHeight());
perspectiveCamera.setTranslateZ(-2 * max);
}
private void createLight() {
light = new PointLight(Color.WHITE);
cameraTransformer.getChildren().add(light);
light.setTranslateX(perspectiveCamera.getTranslateX());
light.setTranslateY(perspectiveCamera.getTranslateY());
light.setTranslateZ(perspectiveCamera.getTranslateZ() / 10);
}
private void createSurface() {
this.gaussFunctionR = x -> 1 / (Math.pow( ( 1 + Math.pow(4 * time, 2 )), (1/4)))
* Math.exp(-(( 1 / Math.pow(1 + (4 * time), 2)) * Math.pow(x - (k * time), 2) ))
* Math.cos((1/2) * Math.atan(-(2 * time)) + 1 / Math.pow(1 + (4 * time), 2)
* ((k + (2 * time * x)) * x - Math.pow(1/2 * time * k, 2))) ;
surface = new SurfacePlotMesh(p -> gaussFunctionR.apply(Math.sqrt(p.getY()*p.getY()+p.getX()*p.getX())) ,15, 15, 100, 100, 2);
surface.setCullFace(CullFace.NONE);
surface.setTextureModeVertices3D(1530, p -> p.magnitude());
surface.getTransforms().addAll(rotateX, rotateY);
}
private void update() {
Timeline timeLine = new Timeline();
KeyFrame keyFrame = new KeyFrame(Duration.millis(50), e -> {
surface.setFunction2D(p -> gaussFunctionR.apply(Math.sqrt(p.getY()*p.getY()+p.getX()*p.getX())));
increaseTime();
});
timeLine.getKeyFrames().add(keyFrame);
timeLine.setCycleCount(Timeline.INDEFINITE);
timeLine.setAutoReverse(true);
timeLine.play();
}
private void increaseTime(){
this.time += 0.001;
}
public CameraTransformer getCameraTransformer() {
return this.cameraTransformer;
}
}
package sample.packet3D;
import org.fxyz.cameras.CameraTransformer;
import javafx.beans.InvalidationListener;
import javafx.beans.Observable;
import javafx.fxml.FXML;
import javafx.scene.Group;
import javafx.scene.PerspectiveCamera;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.AnchorPane;
public class Window3DController {
#FXML
private AnchorPane anchorPane;
#FXML
private Group group;
private Window3DBuilder window3dBuilder;
private PerspectiveCamera perspectiveCamera;
private CameraTransformer cameraTransformer;
private double mousePosX;
private double mousePosY;
private double mouseOldX;
private double mouseOldY;
private double mouseDeltaX;
private double mouseDeltaY;
#FXML
public void initialize() {
perspectiveCamera = new PerspectiveCamera(true);
window3dBuilder = new Window3DBuilder(group, perspectiveCamera);
window3dBuilder.createScene();
cameraTransformer = window3dBuilder.getCameraTransformer();
group.sceneProperty().addListener(new InvalidationListener() {
#Override
public void invalidated(Observable observable) {
group.getScene().setCamera(perspectiveCamera);
group.sceneProperty().removeListener(this);
}
});
}
#FXML
public void onKeyPressed(KeyEvent event) {
double change = 10.0;
if(event.isShiftDown()) { change = 50.0; }
KeyCode keycode = event.getCode();
if(keycode == KeyCode.W) { perspectiveCamera.setTranslateZ(perspectiveCamera.getTranslateZ() + change); }
if(keycode == KeyCode.S) { perspectiveCamera.setTranslateZ(perspectiveCamera.getTranslateZ() - change); }
if(keycode == KeyCode.A) { perspectiveCamera.setTranslateX(perspectiveCamera.getTranslateX() - change); }
if(keycode == KeyCode.D) { perspectiveCamera.setTranslateX(perspectiveCamera.getTranslateX() + change); }
}
#FXML
private void onMousePressed(MouseEvent me) {
mousePosX = me.getSceneX();
mousePosY = me.getSceneY();
mouseOldX = me.getSceneX();
mouseOldY = me.getSceneY();
}
#FXML
private void onMouseDragged(MouseEvent me) {
mouseOldX = mousePosX;
mouseOldY = mousePosY;
mousePosX = me.getSceneX();
mousePosY = me.getSceneY();
mouseDeltaX = (mousePosX - mouseOldX);
mouseDeltaY = (mousePosY - mouseOldY);
double modifier = 10.0;
double modifierFactor = 0.1;
if (me.isControlDown()) {
modifier = 0.1;
}
if (me.isShiftDown()) {
modifier = 50.0;
}
if (me.isPrimaryButtonDown()) {
cameraTransformer.ry.setAngle(((cameraTransformer.ry.getAngle() + mouseDeltaX * modifierFactor * modifier * 2.0) % 360 + 540) % 360 - 180); // +
cameraTransformer.rx.setAngle(((cameraTransformer.rx.getAngle() - mouseDeltaY * modifierFactor * modifier * 2.0) % 360 + 540) % 360 - 180); // -
} else if (me.isSecondaryButtonDown()) {
double z = perspectiveCamera.getTranslateZ();
double newZ = z + mouseDeltaX * modifierFactor * modifier;
perspectiveCamera.setTranslateZ(newZ);
} else if (me.isMiddleButtonDown()) {
cameraTransformer.t.setX(cameraTransformer.t.getX() + mouseDeltaX * modifierFactor * modifier * 0.3); // -
cameraTransformer.t.setY(cameraTransformer.t.getY() + mouseDeltaY * modifierFactor * modifier * 0.3); // -
}
}
}
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.effect.*?>
<?import javafx.scene.canvas.*?>
<?import javafx.scene.*?>
<?import javafx.scene.shape.*?>
<?import javafx.scene.control.*?>
<?import java.lang.*?>
<?import javafx.scene.layout.*?>
<AnchorPane fx:id="anchorPane" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" onMouseDragged="#onMouseDragged" onMousePressed="#onMousePressed" prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="sample.packet3D.Window3DController">
<children>
<Group fx:id="group" onKeyPressed="#onKeyPressed">
</Group>
</children>
</AnchorPane>
Related
I'm trying to write a program with an equalizer, a frequency analyzer and a sound level meter. The model part seems to work very well but I'm experimenting some bugs with the IHM.
My last bug is with the level meter. After a while (from few milliseconds to few seconds), it freezes and don't update anymore. So, here is a (simplified) version of it. I added the runnable part to test and reproduce the bug. Of course, this bug appears sooner when I add other graphical components which also need to refresh very frequently. For example, the frequency analyze is represented by a line-chart with something like 1000 points.
public class LevelMeter2 extends Parent implements Runnable {
private IntegerProperty levelMeterHeight = new SimpleIntegerProperty();
private Rectangle led;
private IntegerProperty height = new SimpleIntegerProperty();
private IntegerProperty width = new SimpleIntegerProperty();
private DoubleProperty linearValue = new SimpleDoubleProperty();
private Color backgroundColor=Color.BLACK;
private double minLinearValue, maxLinearValue;
public LevelMeter2 (int height2, int width2) {
this.height.set(height2);
this.levelMeterHeight.bind(height.multiply(0.9));
this.width.set(width2);
linearValue.set(1.0);
minLinearValue = Math.pow(10, -60.0/100);
maxLinearValue = Math.pow(10, 3.0/100)-minLinearValue;
Rectangle levelMeterShape = new Rectangle();
levelMeterShape.widthProperty().bind(width);
levelMeterShape.heightProperty().bind(height);
levelMeterShape.setStroke(backgroundColor);
this.getChildren().add(levelMeterShape);
led = new Rectangle();
led.widthProperty().bind(width.multiply(0.8));
led.translateXProperty().bind(width.multiply(0.1));
led.heightProperty().bind(levelMeterHeight.multiply(linearValue));
led.setFill(Color.AQUA);
Rotate rotate = new Rotate();
rotate.pivotXProperty().bind(width.multiply(0.8).divide(2));
rotate.pivotYProperty().bind(height.divide(2));
rotate.setAngle(180);
led.getTransforms().add(rotate);
this.getChildren().add(led);
}
public double convertdBToLinearValue (double dB) {
return ((double)Math.round(100 * ((Math.pow(10, dB/100)-minLinearValue)/maxLinearValue)) ) /100 ;
//return (Math.pow(10, dB/100)-minLinearValue)/maxLinearValue;
}
public double convertLinearValueTodB (double linearValue) {
return 100*Math.log10(linearValue*maxLinearValue+minLinearValue);
}
public void setValue (double dB) {
if (dB>3) {
dB=3;
}
linearValue.setValue(convertdBToLinearValue(dB));
}
#Override
public void run() {
int i = 0;
double value=-20;
while (i<1000) {
setValue(value);
value = (Math.random()-0.5)*10+value;
if (value>3) {
value=3;
}
if (value<-60) {
value=-60;
}
i++;
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("END OF WHILE");
}
}
And a "Main" to test it :
public class MainGraph extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) throws Exception {
HBox pane = new HBox();
LevelMeter2 levelMeter = new LevelMeter2(300,30);
Thread t = new Thread(levelMeter);
pane.getChildren().add(levelMeter);
t.start();
Scene scene = new Scene(pane, 300, 300);
primaryStage.setScene(scene);
primaryStage.setTitle("Test IHM");
primaryStage.setOnCloseRequest( event -> {
System.out.println("FIN");
System.exit(0);
});
primaryStage.show();
}
}
What's wrong with my code ? How can I write a more robust code that will allow me high refresh rates of my IHM ? Or how can I prevent from freezing ?
Thank you for you help.
I would suggest you move away from Threads and use something from JavaFX Animation package. In this example Timeline is used. This code is set to run at a rate of about 60 fps. You can adjust that using Duration.millis().
Main
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
/**
*
* #author blj0011
*/
public class JavaFXApplication342 extends Application
{
#Override
public void start(Stage primaryStage)
{
LevelMeter2 levelMeter = new LevelMeter2(300, 30);
Button button = new Button("Start");
button.setOnAction((event) -> {
switch (button.getText()) {
case "Start":
levelMeter.startAnimation();
button.setText("Stop");
break;
case "Stop":
levelMeter.stopAnimation();
button.setText("Start");
break;
}
});
HBox pane = new HBox(levelMeter, button);
Scene scene = new Scene(pane, 300, 300);
primaryStage.setScene(scene);
primaryStage.setTitle("Test IHM");
primaryStage.setOnCloseRequest(event -> {
System.out.println("FIN");
System.exit(0);
});
primaryStage.show();
}
/**
* #param args the command line arguments
*/
public static void main(String[] args)
{
launch(args);
}
}
LevelMeter2
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.scene.Parent;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.scene.transform.Rotate;
import javafx.util.Duration;
public final class LevelMeter2 extends Parent
{
private final IntegerProperty levelMeterHeight = new SimpleIntegerProperty();
Timeline timeline;
double value = -20;
private final Rectangle led;
private final IntegerProperty height = new SimpleIntegerProperty();
private final IntegerProperty width = new SimpleIntegerProperty();
private final DoubleProperty linearValue = new SimpleDoubleProperty();
private final Color backgroundColor = Color.BLACK;
private final double minLinearValue;
private final double maxLinearValue;
public LevelMeter2(int height2, int width2)
{
this.height.set(height2);
this.levelMeterHeight.bind(height.multiply(0.9));
this.width.set(width2);
linearValue.set(1.0);
minLinearValue = Math.pow(10, -60.0 / 100);
maxLinearValue = Math.pow(10, 3.0 / 100) - minLinearValue;
Rectangle levelMeterShape = new Rectangle();
levelMeterShape.widthProperty().bind(width);
levelMeterShape.heightProperty().bind(height);
levelMeterShape.setStroke(backgroundColor);
this.getChildren().add(levelMeterShape);
led = new Rectangle();
led.widthProperty().bind(width.multiply(0.8));
led.translateXProperty().bind(width.multiply(0.1));
led.heightProperty().bind(levelMeterHeight.multiply(linearValue));
led.setFill(Color.AQUA);
Rotate rotate = new Rotate();
rotate.pivotXProperty().bind(width.multiply(0.8).divide(2));
rotate.pivotYProperty().bind(height.divide(2));
rotate.setAngle(180);
led.getTransforms().add(rotate);
getChildren().add(led);
timeline = new Timeline(new KeyFrame(Duration.millis(16), (event) -> {
setValue(value);
value = (Math.random() - 0.5) * 10 + value;
if (value > 3) {
value = 3;
}
if (value < -60) {
value = -60;
}
}));
timeline.setCycleCount(Timeline.INDEFINITE);
}
public double convertdBToLinearValue(double dB)
{
return ((double) Math.round(100 * ((Math.pow(10, dB / 100) - minLinearValue) / maxLinearValue))) / 100;
}
public double convertLinearValueTodB(double linearValue)
{
return 100 * Math.log10(linearValue * maxLinearValue + minLinearValue);
}
public void setValue(double dB)
{
if (dB > 3) {
dB = 3;
}
linearValue.setValue(convertdBToLinearValue(dB));
}
public void startAnimation()
{
timeline.play();
}
public void stopAnimation()
{
timeline.stop();
}
}
Multiple LevelMeters Example:
Main
import java.util.ArrayList;
import java.util.List;
import javafx.animation.ParallelTransition;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.HBox;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
/**
*
* #author blj0011
*/
public class JavaFXApplication342 extends Application
{
#Override
public void start(Stage primaryStage)
{
List<LevelMeter2> levelMeter2s = new ArrayList();
List<Timeline> metersTimelines = new ArrayList();
for (int i = 0; i < 9; i++) {
LevelMeter2 levelMeter2 = new LevelMeter2(300, 30);
levelMeter2s.add(levelMeter2);
metersTimelines.add(levelMeter2.getTimeline());
}
ParallelTransition parallelTransition = new ParallelTransition();
parallelTransition.getChildren().addAll(metersTimelines);
Button button = new Button("Start");
button.setOnAction((event) -> {
switch (button.getText()) {
case "Start":
parallelTransition.play();
button.setText("Stop");
break;
case "Stop":
parallelTransition.stop();
button.setText("Start");
break;
}
});
HBox hBox = new HBox();
hBox.getChildren().addAll(levelMeter2s);
VBox vBox = new VBox(hBox, new StackPane(button));
Scene scene = new Scene(vBox, 300, 350);
primaryStage.setScene(scene);
primaryStage.setTitle("Test IHM");
primaryStage.setOnCloseRequest(event -> {
System.out.println("FIN");
System.exit(0);
});
primaryStage.show();
}
/**
* #param args the command line arguments
*/
public static void main(String[] args)
{
launch(args);
}
}
LevelMeter2
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.scene.Parent;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.scene.transform.Rotate;
import javafx.util.Duration;
public final class LevelMeter2 extends Parent
{
private final IntegerProperty levelMeterHeight = new SimpleIntegerProperty();
Timeline timeline;
double value = -20;
private final Rectangle led;
private final IntegerProperty height = new SimpleIntegerProperty();
private final IntegerProperty width = new SimpleIntegerProperty();
private final DoubleProperty linearValue = new SimpleDoubleProperty();
private final Color backgroundColor = Color.BLACK;
private final double minLinearValue;
private final double maxLinearValue;
public LevelMeter2(int height2, int width2)
{
this.height.set(height2);
this.levelMeterHeight.bind(height.multiply(0.9));
this.width.set(width2);
linearValue.set(1.0);
minLinearValue = Math.pow(10, -60.0 / 100);
maxLinearValue = Math.pow(10, 3.0 / 100) - minLinearValue;
Rectangle levelMeterShape = new Rectangle();
levelMeterShape.widthProperty().bind(width);
levelMeterShape.heightProperty().bind(height);
levelMeterShape.setStroke(backgroundColor);
this.getChildren().add(levelMeterShape);
led = new Rectangle();
led.widthProperty().bind(width.multiply(0.8));
led.translateXProperty().bind(width.multiply(0.1));
led.heightProperty().bind(levelMeterHeight.multiply(linearValue));
led.setFill(Color.AQUA);
Rotate rotate = new Rotate();
rotate.pivotXProperty().bind(width.multiply(0.8).divide(2));
rotate.pivotYProperty().bind(height.divide(2));
rotate.setAngle(180);
led.getTransforms().add(rotate);
getChildren().add(led);
timeline = new Timeline(new KeyFrame(Duration.millis(25), (event) -> {
setValue(value);
value = (Math.random() - 0.5) * 10 + value;
if (value > 3) {
value = 3;
}
if (value < -60) {
value = -60;
}
}));
timeline.setCycleCount(Timeline.INDEFINITE);
}
public double convertdBToLinearValue(double dB)
{
return ((double) Math.round(100 * ((Math.pow(10, dB / 100) - minLinearValue) / maxLinearValue))) / 100;
}
public double convertLinearValueTodB(double linearValue)
{
return 100 * Math.log10(linearValue * maxLinearValue + minLinearValue);
}
public void setValue(double dB)
{
if (dB > 3) {
dB = 3;
}
linearValue.setValue(convertdBToLinearValue(dB));
}
public void startAnimation()
{
timeline.play();
}
public void stopAnimation()
{
timeline.stop();
}
public Timeline getTimeline()
{
return timeline;
}
}
Your implementation of run() appears to be updating the scene graph from a background thread. As discussed in Concurrency in JavaFX:
The JavaFX scene graph…is not thread-safe and can only be accessed and modified from the UI thread also known as the JavaFX Application thread. Implementing long-running tasks on the JavaFX Application thread inevitably makes an application UI unresponsive."
Instead, use a Task, illustrated here and here. Your implementation of call() can collect data asynchronously and notify the GUI of the current state via updateValue(). Your valueProperty() listener can then invoke setValue() safely. Because "Updates are coalesced to prevent saturation of the FX event queue," your application will perform satisfactorily even on older hardware.
Alternatively, if your audio source is one of the supported Media types, AudioBarChartApp, also seen here, updates the data model of a BarChart in an AudioSpectrumListener registered with the corresponding MediaPlayer. The image below displays pink noise.
private XYChart.Data<String, Number>[] series1Data;
…
audioSpectrumListener = (double timestamp, double duration,
float[] magnitudes, float[] phases) -> {
for (int i = 0; i < series1Data.length; i++) {
series1Data[i].setYValue(magnitudes[i] + 60);
}
};
I have a problem with zooming and panning image in ScrollPane. So far I have code like this:
Image image = imageView.getImage();
scrollPane.setPrefViewportWidth(0d);
scrollPane.setPrefViewportHeight(0d);
imageView.setFitWidth(0d);
imageView.setFitHeight(0d);
Bounds viewportBounds = scrollPane.getViewportBounds();
boolean vertical = image.getWidth() > image.getHeight();
if (imageView.getRotate() == 90 || imageView.getRotate() == 270d) {
vertical = !vertical;
}
imageView.setPreserveRatio(true);
double propX = viewportBounds.getWidth() / image.getWidth();
double propY = viewportBounds.getHeight() / image.getHeight();
boolean xLead = !(propX > propY);
imageView.setScaleX((xLead) ? propX : propY);
imageView.setScaleY((xLead) ? propX : propY);
scrollPane.setContent(imageView);
scrollPane.setPannable(true);
scrollPane.setHvalue(scrollPane.getHmin() + (scrollPane.getHmax() - scrollPane.getHmin()) / 2); // center the scroll contents.
scrollPane.setVvalue(scrollPane.getVmin() + (scrollPane.getVmax() - scrollPane.getVmin()) / 2);
zoom(imageView);
private void zoom(ImageView imagePannable) {
imagePannable.setOnScroll(
new EventHandler<ScrollEvent>() {
#Override
public void handle(ScrollEvent event) {
double zoomFactor = 1.20;
double deltaY = event.getDeltaY();
if (deltaY < 0) {
zoomFactor = 0.80;
}
imagePannable.setScaleX(imagePannable.getScaleX() * zoomFactor);
imagePannable.setScaleY(imagePannable.getScaleY() * zoomFactor);
event.consume();
}
});
}
What I want to do is align image relative to mouse pointer not to center of image everytime.
I also have a problem with large images (like maps which are 8*A4 size for example). When I zooming this maps pannable function stop working. What is wrong with this code? Thanks for helps!
Several people (including me) have had this same question. I got my answer here.
In the interest of clarity, here is a working example of a panning & zooming pane using a rectangle as the zoomed node. I have implemented this in a slightly more complex way with an ImageView.
import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.event.EventHandler;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.ScrollPane;
import javafx.scene.control.ScrollPane.ScrollBarPolicy;
import javafx.scene.input.MouseButton;
import javafx.scene.input.MouseEvent;
import javafx.scene.input.ScrollEvent;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.scene.shape.StrokeType;
import javafx.stage.Stage;
import javafx.util.Duration;
public class ZoomAndPanExample extends Application {
private ScrollPane scrollPane = new ScrollPane();
private final DoubleProperty zoomProperty = new SimpleDoubleProperty(1.0d);
private final DoubleProperty deltaY = new SimpleDoubleProperty(0.0d);
private final Group group = new Group();
public static void main(String[] args) {
Application.launch(args);
}
#Override
public void start(Stage primaryStage) {
scrollPane.setPannable(true);
scrollPane.setHbarPolicy(ScrollBarPolicy.NEVER);
scrollPane.setVbarPolicy(ScrollBarPolicy.NEVER);
AnchorPane.setTopAnchor(scrollPane, 10.0d);
AnchorPane.setRightAnchor(scrollPane, 10.0d);
AnchorPane.setBottomAnchor(scrollPane, 10.0d);
AnchorPane.setLeftAnchor(scrollPane, 10.0d);
AnchorPane root = new AnchorPane();
Rectangle rect = new Rectangle(80, 60);
rect.setStroke(Color.NAVY);
rect.setFill(Color.NAVY);
rect.setStrokeType(StrokeType.INSIDE);
group.getChildren().add(rect);
// create canvas
PanAndZoomPane panAndZoomPane = new PanAndZoomPane();
zoomProperty.bind(panAndZoomPane.myScale);
deltaY.bind(panAndZoomPane.deltaY);
panAndZoomPane.getChildren().add(group);
SceneGestures sceneGestures = new SceneGestures(panAndZoomPane);
scrollPane.setContent(panAndZoomPane);
panAndZoomPane.toBack();
scrollPane.addEventFilter( MouseEvent.MOUSE_CLICKED, sceneGestures.getOnMouseClickedEventHandler());
scrollPane.addEventFilter( MouseEvent.MOUSE_PRESSED, sceneGestures.getOnMousePressedEventHandler());
scrollPane.addEventFilter( MouseEvent.MOUSE_DRAGGED, sceneGestures.getOnMouseDraggedEventHandler());
scrollPane.addEventFilter( ScrollEvent.ANY, sceneGestures.getOnScrollEventHandler());
root.getChildren().add(scrollPane);
Scene scene = new Scene(root, 600, 400);
primaryStage.setScene(scene);
primaryStage.show();
}
class PanAndZoomPane extends Pane {
public static final double DEFAULT_DELTA = 1.3d;
DoubleProperty myScale = new SimpleDoubleProperty(1.0);
public DoubleProperty deltaY = new SimpleDoubleProperty(0.0);
private Timeline timeline;
public PanAndZoomPane() {
this.timeline = new Timeline(60);
// add scale transform
scaleXProperty().bind(myScale);
scaleYProperty().bind(myScale);
}
public double getScale() {
return myScale.get();
}
public void setScale( double scale) {
myScale.set(scale);
}
public void setPivot( double x, double y, double scale) {
// note: pivot value must be untransformed, i. e. without scaling
// timeline that scales and moves the node
timeline.getKeyFrames().clear();
timeline.getKeyFrames().addAll(
new KeyFrame(Duration.millis(200), new KeyValue(translateXProperty(), getTranslateX() - x)),
new KeyFrame(Duration.millis(200), new KeyValue(translateYProperty(), getTranslateY() - y)),
new KeyFrame(Duration.millis(200), new KeyValue(myScale, scale))
);
timeline.play();
}
public void fitWidth () {
double scale = getParent().getLayoutBounds().getMaxX()/getLayoutBounds().getMaxX();
double oldScale = getScale();
double f = scale - oldScale;
double dx = getTranslateX() - getBoundsInParent().getMinX() - getBoundsInParent().getWidth()/2;
double dy = getTranslateY() - getBoundsInParent().getMinY() - getBoundsInParent().getHeight()/2;
double newX = f*dx + getBoundsInParent().getMinX();
double newY = f*dy + getBoundsInParent().getMinY();
setPivot(newX, newY, scale);
}
public void resetZoom () {
double scale = 1.0d;
double x = getTranslateX();
double y = getTranslateY();
setPivot(x, y, scale);
}
public double getDeltaY() {
return deltaY.get();
}
public void setDeltaY( double dY) {
deltaY.set(dY);
}
}
/**
* Mouse drag context used for scene and nodes.
*/
class DragContext {
double mouseAnchorX;
double mouseAnchorY;
double translateAnchorX;
double translateAnchorY;
}
/**
* Listeners for making the scene's canvas draggable and zoomable
*/
public class SceneGestures {
private DragContext sceneDragContext = new DragContext();
PanAndZoomPane panAndZoomPane;
public SceneGestures( PanAndZoomPane canvas) {
this.panAndZoomPane = canvas;
}
public EventHandler<MouseEvent> getOnMouseClickedEventHandler() {
return onMouseClickedEventHandler;
}
public EventHandler<MouseEvent> getOnMousePressedEventHandler() {
return onMousePressedEventHandler;
}
public EventHandler<MouseEvent> getOnMouseDraggedEventHandler() {
return onMouseDraggedEventHandler;
}
public EventHandler<ScrollEvent> getOnScrollEventHandler() {
return onScrollEventHandler;
}
private EventHandler<MouseEvent> onMousePressedEventHandler = new EventHandler<MouseEvent>() {
public void handle(MouseEvent event) {
sceneDragContext.mouseAnchorX = event.getX();
sceneDragContext.mouseAnchorY = event.getY();
sceneDragContext.translateAnchorX = panAndZoomPane.getTranslateX();
sceneDragContext.translateAnchorY = panAndZoomPane.getTranslateY();
}
};
private EventHandler<MouseEvent> onMouseDraggedEventHandler = new EventHandler<MouseEvent>() {
public void handle(MouseEvent event) {
panAndZoomPane.setTranslateX(sceneDragContext.translateAnchorX + event.getX() - sceneDragContext.mouseAnchorX);
panAndZoomPane.setTranslateY(sceneDragContext.translateAnchorY + event.getY() - sceneDragContext.mouseAnchorY);
event.consume();
}
};
/**
* Mouse wheel handler: zoom to pivot point
*/
private EventHandler<ScrollEvent> onScrollEventHandler = new EventHandler<ScrollEvent>() {
#Override
public void handle(ScrollEvent event) {
double delta = PanAndZoomPane.DEFAULT_DELTA;
double scale = panAndZoomPane.getScale(); // currently we only use Y, same value is used for X
double oldScale = scale;
panAndZoomPane.setDeltaY(event.getDeltaY());
if (panAndZoomPane.deltaY.get() < 0) {
scale /= delta;
} else {
scale *= delta;
}
double f = (scale / oldScale)-1;
double dx = (event.getX() - (panAndZoomPane.getBoundsInParent().getWidth()/2 + panAndZoomPane.getBoundsInParent().getMinX()));
double dy = (event.getY() - (panAndZoomPane.getBoundsInParent().getHeight()/2 + panAndZoomPane.getBoundsInParent().getMinY()));
panAndZoomPane.setPivot(f*dx, f*dy, scale);
event.consume();
}
};
/**
* Mouse click handler
*/
private EventHandler<MouseEvent> onMouseClickedEventHandler = new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
if (event.getButton().equals(MouseButton.PRIMARY)) {
if (event.getClickCount() == 2) {
panAndZoomPane.resetZoom();
}
}
if (event.getButton().equals(MouseButton.SECONDARY)) {
if (event.getClickCount() == 2) {
panAndZoomPane.fitWidth();
}
}
}
};
}
}
I want to create a bouncer app using JavaFX. I used Pane to show moving shapes (circle, Rectange) on it. The problem happens when I resize the window to a larger area. When I do that, then the newly created area doesn't show moving shapes properly. What happens in this new area: shapes change color from black to white and they leave a white trace.
The shapes are moved using the setTranslateX (and Y) methods.
I also included a sample code below the screen shots. The code isn't the same as the one on the recorded screens, but it produces the same problem.
window with original size
resized window with an area where the shapes are not drawn properly
The FXML document named view.fxml
<?import javafx.scene.shape.*?>
<?import javafx.geometry.*?>
<?import javafx.scene.control.*?>
<?import java.lang.*?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.layout.AnchorPane?>
<BorderPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="com.company.bouncer.Controller">
<center>
<Pane fx:id="paneField" prefHeight="344.0" prefWidth="600.0" BorderPane.alignment="CENTER" />
</center>
<top>
<HBox alignment="CENTER_LEFT" prefHeight="56.0" prefWidth="600.0" spacing="30.0" BorderPane.alignment="CENTER">
<children>
<Button fx:id="btnLaunch" mnemonicParsing="false" onAction="#handleBtn" text="Launch" />
</children>
<opaqueInsets>
<Insets />
</opaqueInsets>
<BorderPane.margin>
<Insets />
</BorderPane.margin>
<padding>
<Insets left="30.0" />
</padding>
</HBox>
</top>
</BorderPane>
Main class that extends Application
package com.company.bouncer;
import java.io.IOException;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.stage.Stage;
import javafx.scene.Parent;
import javafx.scene.Scene;
public class Main extends Application {
#Override
public void start(Stage primaryStage) throws IOException {
Parent root = FXMLLoader.load(getClass().getResource("/view.fxml"));
primaryStage.setTitle("Bouncer");
primaryStage.setScene(new Scene(root, 600, 400));
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
/**
* called when the window is closed
*/
#Override
public void stop() throws Exception {
Controller.stopExecutor();
super.stop();
}
}
** Controller **
package com.company.bouncer;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.ResourceBundle;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.geometry.Insets;
import javafx.scene.control.Button;
import javafx.scene.layout.Background;
import javafx.scene.layout.BackgroundFill;
import javafx.scene.layout.CornerRadii;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.scene.shape.Shape;
public class Controller implements Initializable {
#FXML
private Button btnLaunch;
#FXML
private Pane paneField;
private ShapePlatform shapeRunnable;
private static ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
#Override
public void initialize(URL location, ResourceBundle resources) {
shapeRunnable = new ShapePlatform();
paneField.setBackground(new Background(new BackgroundFill(Color.web("#aaaaaa"), CornerRadii.EMPTY, Insets.EMPTY)));
executor.scheduleAtFixedRate(shapeRunnable, 0, 10, TimeUnit.MILLISECONDS);
}
#FXML
private void handleBtn(){
shapeRunnable.generateShapeMover();
}
public static void stopExecutor(){
executor.shutdown();
}
public class ShapePlatform implements Runnable {
private List<ShapeMover> shapeList = new ArrayList<>();
/**
* Constructor
* #param parentPane Pane on which the shapes will be displayed
*/
public ShapePlatform(){
}
/**
* creates a new shape and adds it to the shapeList
*/
public void generateShapeMover(){
Shape newShape = new Rectangle(0, 0, 100, 80);
paneField.getChildren().add(newShape);
//position the object in some random location on the pane
newShape.setTranslateX(300);
newShape.setTranslateY(300);
//wrap it in shape mover
ShapeMover shapeMover = new ShapeMover(newShape);
shapeList.add(shapeMover);
}
/**
* executes one frame of moving objects
*/
private void moveAllOnce(){
shapeList.forEach(sm -> sm.move());
}
/**
* moves all objects, checks any intersections between objects
* and changes their direction if there is an intersection
*/
#Override
public void run() {
moveAllOnce();
}
public class ShapeMover {
private Shape shape;
private int xDir = 1;
private int yDir = 1;
private int periodSpeed = 1;
private int periodSpeedCountDown = periodSpeed;
/**
* constructs the object
* #param shape - shape to be moved
*/
public ShapeMover(Shape shape){
this.shape = shape;
}
/**
* moves object for one iteration
*/
public void move(){
if(periodSpeedCountDown == 0){
shape.setTranslateX(shape.getTranslateX() + xDir);
shape.setTranslateY(shape.getTranslateY() + yDir);
periodSpeedCountDown = periodSpeed;
} else {
periodSpeedCountDown--;
}
}
}
}
}
So, the problem was actually that I was running the movement handling background thread which probably produced my problem. With a suggestion from #James_D I implemented the movement handling using extention of AnimationTimer, and now the menitoned problem is gone.
The new code is int under the text. I still have to implement a mechanism to handle the right timing as now there are no periods defined in AnimationTimer
public class Controller implements Initializable {
#FXML
private Button btnLaunch;
#FXML
private Pane paneField;
private ShapePlatform shapeAnimTimer;
private static ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
#Override
public void initialize(URL location, ResourceBundle resources) {
paneField.setBackground(new Background(new BackgroundFill(Color.web("#aaaaaa"), CornerRadii.EMPTY, Insets.EMPTY)));
shapeAnimTimer = new ShapePlatform();
shapeAnimTimer.start();
}
#FXML
private void handleBtn(){
shapeAnimTimer.generateShapeMover();
}
public static void stopExecutor(){
executor.shutdown();
}
public class ShapePlatform extends AnimationTimer {
private List<ShapeMover> shapeList = new ArrayList<>();
/**
* creates a new shape and adds it to the shapeList
*/
public void generateShapeMover(){
Shape newShape = new Rectangle(0, 0, 100, 80);
paneField.getChildren().add(newShape);
//position the object in some random location on the pane
newShape.setTranslateX(300);
newShape.setTranslateY(300);
//wrap it in shape mover
ShapeMover shapeMover = new ShapeMover(newShape);
shapeList.add(shapeMover);
}
/**
* executes one frame of moving objects
*/
private void moveAllOnce(){
shapeList.forEach(sm -> sm.move());
}
/**
* moves all objects, checks any intersections between objects
* and changes their direction if there is an intersection
* #param now current frame
*/
#Override
public void handle(long now) {
System.out.println(now);
moveAllOnce();
}
public class ShapeMover {
private Shape shape;
private int xDir = 1;
private int yDir = 1;
private int periodSpeed = 1;
private int periodSpeedCountDown = periodSpeed;
/**
* constructs the object
* #param shape - shape to be moved
*/
public ShapeMover(Shape shape){
this.shape = shape;
}
/**
* moves object for one iteration
*/
public void move(){
if(periodSpeedCountDown == 0){
shape.setTranslateX(shape.getTranslateX() + xDir);
shape.setTranslateY(shape.getTranslateY() + yDir);
periodSpeedCountDown = periodSpeed;
} else {
periodSpeedCountDown--;
}
}
}
}
}
Need to make a sphere in JavaFX "blink" over time, like fade in and fade out. Is it possible? Or at least change shades of color. I managed to make it blink by changing SelfIlluminationMap property of the PhongMaterial I am using, with this code
import javafx.animation.*;
import javafx.application.Application;
import javafx.scene.*;
import javafx.scene.image.Image;
import javafx.scene.paint.*;
import javafx.scene.shape.*;
import javafx.scene.transform.*;
import javafx.stage.Stage;
import javafx.util.Duration;
public class JavaFXApplication15 extends Application {
Image im = new Image("bump.jpg");
PhongMaterial ph = new PhongMaterial(Color.GREEN);
int nums = 0;
UpdateTimer timer;
private class UpdateTimer extends AnimationTimer {
int counter = 0;
#Override
public void handle(long now) {
if (counter == 20) {
update();
counter = 0;
}
counter++;
}
}
private Parent createContent() throws Exception {
timer = new UpdateTimer();
ph = new PhongMaterial(Color.YELLOW, null, null, null, im);
Box box = new Box(5, 5, 5);
box.setMaterial(ph);
// Create and position camera
PerspectiveCamera camera = new PerspectiveCamera(true);
camera.getTransforms().addAll(
new Rotate(-20, Rotate.X_AXIS),
new Translate(0, 0, -50)
);
// Build the Scene Graph
Group root = new Group();
root.getChildren().add(camera);
root.getChildren().add(box);
// Use a SubScene
SubScene subScene = new SubScene(
root,
300, 300,
true,
SceneAntialiasing.BALANCED
);
subScene.setFill(Color.ALICEBLUE);
subScene.setCamera(camera);
Group group = new Group();
group.getChildren().add(subScene);
return group;
}
void update() {
if (ph.getSelfIlluminationMap() != null) {
ph.setSelfIlluminationMap(null);
} else {
ph.setSelfIlluminationMap(im);
}
}
#Override
public void start(Stage stage) throws Exception {
stage.setResizable(false);
Scene scene = new Scene(createContent());
stage.setScene(scene);
stage.show();
timer.start();
}
public static void main(String[] args) {
launch(args);
}
}
but is it possible to make it fade in and out? With some kind of transition possibly?
Just to see the effect, I tried animating the specularColor of a sphere's PhongMaterial. Starting from this example, I followed the approach shown here to get a color lookup table of equally spaced brightness values of a given hue.
private final Queue<Color> clut = new LinkedList<>();
The implementation of handle() simply cycles through the table.
#Override
public void handle(long now) {
redMaterial.setSpecularColor(clut.peek());
clut.add(clut.remove());
}
If the result is appealing, a more flexible approach might accrue from using a concrete subclass of Transition.
private final Animation animation = new Transition() {
{
setCycleDuration(Duration.millis(1000));
setAutoReverse(true);
setCycleCount(INDEFINITE);
}
#Override
protected void interpolate(double d) {
redMaterial.setSpecularColor(Color.hsb(LITE.getHue(), 1, d));
redMaterial.setDiffuseColor(Color.hsb(DARK.getHue(), 1, d / 2));
}
};
import java.util.LinkedList;
import java.util.Queue;
import javafx.animation.AnimationTimer;
import javafx.animation.Animation;
import javafx.animation.Transition;
import javafx.application.Application;
import javafx.geometry.Point3D;
import javafx.scene.Group;
import javafx.scene.PerspectiveCamera;
import javafx.scene.Scene;
import javafx.scene.input.MouseEvent;
import javafx.scene.input.ScrollEvent;
import javafx.scene.paint.Color;
import javafx.scene.paint.PhongMaterial;
import javafx.scene.shape.Box;
import javafx.scene.shape.DrawMode;
import javafx.scene.shape.Sphere;
import javafx.scene.transform.Rotate;
import javafx.scene.transform.Transform;
import javafx.stage.Stage;
import javafx.util.Duration;
/**
* #see https://stackoverflow.com/a/44447913/230513
* #see https://stackoverflow.com/a/37755149/230513
* #see https://stackoverflow.com/a/37743539/230513
* #see https://stackoverflow.com/a/37370840/230513
*/
public class TriadBox extends Application {
private static final double SIZE = 300;
private final Content content = Content.create(SIZE);
private double mousePosX, mousePosY, mouseOldX, mouseOldY, mouseDeltaX, mouseDeltaY;
private static final class Content {
private static final double WIDTH = 3;
private static final Color LITE = Color.RED;
private static final Color DARK = Color.RED.darker().darker();
private final Xform group = new Xform();
private final Group cube = new Group();
private final Group axes = new Group();
private final Box xAxis;
private final Box yAxis;
private final Box zAxis;
private final Box box;
private final Sphere sphere;
private final PhongMaterial redMaterial = new PhongMaterial();
private final UpdateTimer timer = new UpdateTimer();
private class UpdateTimer extends AnimationTimer {
private static final double N = 32d;
private final Queue<Color> clut = new LinkedList<>();
public UpdateTimer() {
for (int i = 0; i < N; i++) {
clut.add(Color.hsb(LITE.getHue(), 1, 1 - (i / N)));
}
for (int i = 0; i < N; i++) {
clut.add(Color.hsb(LITE.getHue(), 1, i / N));
}
}
#Override
public void handle(long now) {
redMaterial.setSpecularColor(clut.peek());
clut.add(clut.remove());
}
}
private final Animation animation = new Transition() {
{
setCycleDuration(Duration.millis(1000));
setAutoReverse(true);
setCycleCount(INDEFINITE);
}
#Override
protected void interpolate(double d) {
redMaterial.setSpecularColor(Color.hsb(LITE.getHue(), 1, d));
redMaterial.setDiffuseColor(Color.hsb(DARK.getHue(), 1, d / 2));
}
};
private static Content create(double size) {
Content c = new Content(size);
c.cube.getChildren().addAll(c.box, c.sphere);
c.axes.getChildren().addAll(c.xAxis, c.yAxis, c.zAxis);
c.group.getChildren().addAll(c.cube, c.axes);
return c;
}
private Content(double size) {
double edge = 3 * size / 4;
xAxis = createBox(edge, WIDTH, WIDTH, edge);
yAxis = createBox(WIDTH, edge / 2, WIDTH, edge);
zAxis = createBox(WIDTH, WIDTH, edge / 4, edge);
box = new Box(edge, edge / 2, edge / 4);
box.setDrawMode(DrawMode.LINE);
sphere = new Sphere(8);
redMaterial.setDiffuseColor(DARK);
redMaterial.setSpecularColor(LITE);
sphere.setMaterial(redMaterial);
sphere.setTranslateX(edge / 2);
sphere.setTranslateY(-edge / 4);
sphere.setTranslateZ(-edge / 8);
}
private Box createBox(double w, double h, double d, double edge) {
Box b = new Box(w, h, d);
b.setMaterial(new PhongMaterial(Color.AQUA));
b.setTranslateX(-edge / 2 + w / 2);
b.setTranslateY(edge / 4 - h / 2);
b.setTranslateZ(edge / 8 - d / 2);
return b;
}
}
private static class Xform extends Group {
private final Point3D px = new Point3D(1.0, 0.0, 0.0);
private final Point3D py = new Point3D(0.0, 1.0, 0.0);
private Rotate r;
private Transform t = new Rotate();
public void rx(double angle) {
r = new Rotate(angle, px);
this.t = t.createConcatenation(r);
this.getTransforms().clear();
this.getTransforms().addAll(t);
}
public void ry(double angle) {
r = new Rotate(angle, py);
this.t = t.createConcatenation(r);
this.getTransforms().clear();
this.getTransforms().addAll(t);
}
public void rz(double angle) {
r = new Rotate(angle);
this.t = t.createConcatenation(r);
this.getTransforms().clear();
this.getTransforms().addAll(t);
}
}
#Override
public void start(Stage primaryStage) throws Exception {
primaryStage.setTitle("JavaFX 3D");
Scene scene = new Scene(content.group, SIZE * 2, SIZE * 2, true);
primaryStage.setScene(scene);
scene.setFill(Color.BLACK);
PerspectiveCamera camera = new PerspectiveCamera(true);
camera.setFarClip(SIZE * 6);
camera.setTranslateZ(-2 * SIZE);
scene.setCamera(camera);
scene.setOnMousePressed((MouseEvent e) -> {
mousePosX = e.getSceneX();
mousePosY = e.getSceneY();
mouseOldX = e.getSceneX();
mouseOldY = e.getSceneY();
});
scene.setOnMouseDragged((MouseEvent e) -> {
mouseOldX = mousePosX;
mouseOldY = mousePosY;
mousePosX = e.getSceneX();
mousePosY = e.getSceneY();
mouseDeltaX = (mousePosX - mouseOldX);
mouseDeltaY = (mousePosY - mouseOldY);
if (e.isShiftDown()) {
content.group.rz(-mouseDeltaX * 180.0 / scene.getWidth());
} else if (e.isPrimaryButtonDown()) {
content.group.rx(+mouseDeltaY * 180.0 / scene.getHeight());
content.group.ry(-mouseDeltaX * 180.0 / scene.getWidth());
} else if (e.isSecondaryButtonDown()) {
camera.setTranslateX(camera.getTranslateX() - mouseDeltaX * 0.1);
camera.setTranslateY(camera.getTranslateY() - mouseDeltaY * 0.1);
}
});
scene.setOnScroll((final ScrollEvent e) -> {
camera.setTranslateZ(camera.getTranslateZ() + e.getDeltaY());
});
primaryStage.show();
//content.timer.start();
content.animation.play();
}
public static void main(String[] args) {
launch(args);
}
}
I would like to change line color based on ColorPicker input during runtime.
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
import java.util.logging.Level;
import java.util.logging.Logger;
import javafx.animation.AnimationTimer;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.event.Event;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.chart.AreaChart;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.XYChart.Series;
import javafx.scene.control.ColorPicker;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class MainApp extends Application
{
private static final int MAX_DATA_POINTS = 50;
private Series series;
private int xSeriesData = 0;
private final ConcurrentLinkedQueue<Number> dataQ = new ConcurrentLinkedQueue<>();
private ExecutorService executor;
private AddToQueue addToQueue;
private Timeline timeline2;
private NumberAxis xAxis;
private NumberAxis yAxis;
private AreaChart<Number, Number> sc;
private void init(Stage primaryStage)
{
xAxis = new NumberAxis(0, MAX_DATA_POINTS, MAX_DATA_POINTS / 10);
xAxis.setForceZeroInRange(false);
xAxis.setAutoRanging(false);
yAxis = new NumberAxis();
yAxis.setAutoRanging(true);
sc = new AreaChart<Number, Number>(xAxis, yAxis);
sc.setAnimated(false);
sc.setId("liveAreaChart");
sc.setTitle("Animated Area Chart");
//-- Chart Series
series = new AreaChart.Series<Number, Number>();
series.setName("Area Chart Series");
sc.getData().add(series);
final ColorPicker colorPicker = new ColorPicker();
colorPicker.setOnAction(new EventHandler()
{
#Override
public void handle(Event t)
{
changeColor(colorPicker.getValue().getRed(), colorPicker.getValue().getGreen(), colorPicker.getValue().getBlue(), colorPicker.getValue().getOpacity());
}
});
VBox root = new VBox(5, colorPicker, sc);
primaryStage.setScene(new Scene(root));
}
private void changeColor(double redColor, double greenColor, double blueColor, double opacity)
{
/* int redColor=0, greenColor=127, blueColor=195;
double opacity=0.4;
*/
sc.setStyle("CHART_COLOR_1: rgb(" + redColor + "," + greenColor + "," + blueColor + ");"
+ "CHART_COLOR_1_TRANS_20: rgba(" + redColor + "," + greenColor + "," + blueColor + ");");
}
#Override
public void start(Stage primaryStage) throws Exception
{
init(primaryStage);
primaryStage.show();
//-- Prepare Executor Services
//-- Prepare Executor Services
executor = Executors.newCachedThreadPool(new ThreadFactory()
{
#Override
public Thread newThread(Runnable r)
{
Thread thread = new Thread(r);
thread.setDaemon(true);
return thread;
}
});
addToQueue = new AddToQueue();
executor.execute(addToQueue);
//-- Prepare Timeline
prepareTimeline();
}
public static void main(String[] args)
{
launch(args);
}
private class AddToQueue implements Runnable
{
#Override
public void run()
{
try
{
// add a item of random data to queue
dataQ.add(Math.random());
Thread.sleep(50);
executor.execute(this);
}
catch (InterruptedException ex)
{
Logger.getLogger(MainApp.class.getName()).log(Level.SEVERE, null, ex);
}
}
}
//-- Timeline gets called in the JavaFX Main thread
private void prepareTimeline()
{
// Every frame to take any data from queue and add to chart
new AnimationTimer()
{
#Override
public void handle(long now)
{
addDataToSeries();
}
}.start();
}
private void addDataToSeries()
{
for (int i = 0; i < 20; i++)
{ //-- add 20 numbers to the plot+
if (dataQ.isEmpty())
break;
series.getData().add(new AreaChart.Data(xSeriesData++, dataQ.remove()));
}
// remove points to keep us at no more than MAX_DATA_POINTS
if (series.getData().size() > MAX_DATA_POINTS)
{
series.getData().remove(0, series.getData().size() - MAX_DATA_POINTS);
}
// update
xAxis.setLowerBound(xSeriesData - MAX_DATA_POINTS);
xAxis.setUpperBound(xSeriesData - 1);
}
}
The part which is most unknown to me is how I can get the color of the line during runtime?
Can you help me to implement this, please?
It will be great if I can chose the color from Context menu.
Update: after I select color the diagram is always black.
You need to convert the RGB value ranges from 0.0 - 0.1 to 0 - 255:
private void changeColor( double redColor, double greenColor, double blueColor, double opacity )
{
int r = ( int ) Math.round( redColor * 255.0 );
int g = ( int ) Math.round( greenColor * 255.0 );
int b = ( int ) Math.round( blueColor * 255.0 );
sc.setStyle( "CHART_COLOR_1: rgb(" + r + "," + g + "," + b + ");"
+ "CHART_COLOR_1_TRANS_20: rgba(" + r + "," + g + "," + b + "," + 0.2 + ");" );
}
You may also set the initial default color of ColorPicker with the one defined in modena.css for CHART_COLOR_1:
final ColorPicker colorPicker = new ColorPicker( Color.web( "#f3622d" ) );