Skip to content

Instantly share code, notes, and snippets.

@james-d
Created October 31, 2013 16:29
Show Gist options
  • Save james-d/7252698 to your computer and use it in GitHub Desktop.
Save james-d/7252698 to your computer and use it in GitHub Desktop.
Example of a LineChart that can be zoomed via mouse.
import java.util.Collections;
import java.util.Random;
import javafx.application.Application;
import javafx.beans.binding.BooleanBinding;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.geometry.Point2D;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.chart.Axis;
import javafx.scene.chart.LineChart;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.XYChart.Data;
import javafx.scene.chart.XYChart.Series;
import javafx.scene.control.Button;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
public class ZoomableLineChart extends Application {
private static final int NUM_DATA_POINTS = 1000 ;
@Override
public void start(Stage primaryStage) {
final LineChart<Number, Number> chart = createChart();
final StackPane chartContainer = new StackPane();
chartContainer.getChildren().add(chart);
final Rectangle zoomRect = new Rectangle();
zoomRect.setManaged(false);
zoomRect.setFill(Color.LIGHTSEAGREEN.deriveColor(0, 1, 1, 0.5));
chartContainer.getChildren().add(zoomRect);
setUpZooming(zoomRect, chart);
final HBox controls = new HBox(10);
controls.setPadding(new Insets(10));
controls.setAlignment(Pos.CENTER);
final Button zoomButton = new Button("Zoom");
final Button resetButton = new Button("Reset");
zoomButton.setOnAction(new EventHandler<ActionEvent>() {
@Override
public void handle(ActionEvent event) {
doZoom(zoomRect, chart);
}
});
resetButton.setOnAction(new EventHandler<ActionEvent>() {
@Override
public void handle(ActionEvent event) {
final NumberAxis xAxis = (NumberAxis)chart.getXAxis();
xAxis.setLowerBound(0);
xAxis.setUpperBound(1000);
final NumberAxis yAxis = (NumberAxis)chart.getYAxis();
yAxis.setLowerBound(0);
yAxis.setUpperBound(1000);
zoomRect.setWidth(0);
zoomRect.setHeight(0);
}
});
final BooleanBinding disableControls =
zoomRect.widthProperty().lessThan(5)
.or(zoomRect.heightProperty().lessThan(5));
zoomButton.disableProperty().bind(disableControls);
controls.getChildren().addAll(zoomButton, resetButton);
final BorderPane root = new BorderPane();
root.setCenter(chartContainer);
root.setBottom(controls);
final Scene scene = new Scene(root, 600, 400);
primaryStage.setScene(scene);
primaryStage.show();
}
private LineChart<Number, Number> createChart() {
final NumberAxis xAxis = createAxis();
final NumberAxis yAxis = createAxis();
final LineChart<Number, Number> chart = new LineChart<>(xAxis, yAxis);
chart.setAnimated(false);
chart.setCreateSymbols(false);
chart.setData(generateChartData());
return chart ;
}
private NumberAxis createAxis() {
final NumberAxis xAxis = new NumberAxis();
xAxis.setAutoRanging(false);
xAxis.setLowerBound(0);
xAxis.setUpperBound(1000);
return xAxis;
}
private ObservableList<Series<Number, Number>> generateChartData() {
final Series<Number, Number> series = new Series<>();
series.setName("Data");
final Random rng = new Random();
for (int i=0; i<NUM_DATA_POINTS; i++) {
Data<Number, Number> dataPoint = new Data<Number, Number>(i, rng.nextInt(1000));
series.getData().add(dataPoint);
}
return FXCollections.observableArrayList(Collections.singleton(series));
}
private void setUpZooming(final Rectangle rect, final Node zoomingNode) {
final ObjectProperty<Point2D> mouseAnchor = new SimpleObjectProperty<>();
zoomingNode.setOnMousePressed(new EventHandler<MouseEvent>() {
@Override
public void handle(MouseEvent event) {
mouseAnchor.set(new Point2D(event.getX(), event.getY()));
rect.setWidth(0);
rect.setHeight(0);
}
});
zoomingNode.setOnMouseDragged(new EventHandler<MouseEvent>() {
@Override
public void handle(MouseEvent event) {
double x = event.getX();
double y = event.getY();
rect.setX(Math.min(x, mouseAnchor.get().getX()));
rect.setY(Math.min(y, mouseAnchor.get().getY()));
rect.setWidth(Math.abs(x - mouseAnchor.get().getX()));
rect.setHeight(Math.abs(y - mouseAnchor.get().getY()));
}
});
}
private void doZoom(Rectangle zoomRect, LineChart<Number, Number> chart) {
Point2D zoomTopLeft = new Point2D(zoomRect.getX(), zoomRect.getY());
Point2D zoomBottomRight = new Point2D(zoomRect.getX() + zoomRect.getWidth(), zoomRect.getY() + zoomRect.getHeight());
final NumberAxis yAxis = (NumberAxis) chart.getYAxis();
Point2D yAxisInScene = yAxis.localToScene(0, 0);
final NumberAxis xAxis = (NumberAxis) chart.getXAxis();
Point2D xAxisInScene = xAxis.localToScene(0, 0);
double xOffset = zoomTopLeft.getX() - yAxisInScene.getX() ;
double yOffset = zoomBottomRight.getY() - xAxisInScene.getY();
double xAxisScale = xAxis.getScale();
double yAxisScale = yAxis.getScale();
xAxis.setLowerBound(xAxis.getLowerBound() + xOffset / xAxisScale);
xAxis.setUpperBound(xAxis.getLowerBound() + zoomRect.getWidth() / xAxisScale);
yAxis.setLowerBound(yAxis.getLowerBound() + yOffset / yAxisScale);
yAxis.setUpperBound(yAxis.getLowerBound() - zoomRect.getHeight() / yAxisScale);
System.out.println(yAxis.getLowerBound() + " " + yAxis.getUpperBound());
zoomRect.setWidth(0);
zoomRect.setHeight(0);
}
public static void main(String[] args) {
launch(args);
}
}
Copy link

ghost commented Feb 21, 2018

@polisettyManoj
Hey i just saw your post.

Step 1. Create class called Data -> Example Company (AAPL)

Step 2. There are just a few changes you need to make

  • Make a ComboBox Field within MainApp

`private ComboBox companyComboBox;
private String symbol;'

  • From the class company, you are going to make two fields that are called symbol and company -> make getMethods

  • Make a constructor for the Company class
    - Company
    - Parameters: String symbol, String name
    - super
    -this.symbol = symbol

  • Make a method called getCompanies()
    public static List<Company> getCompanies() { List<Company> company =new ArrayList<>(); list.add(new Company("AAPL", "AAPL"); return company }
    Part 3: Head back to MainApp

  • create a field called listCompany

    • ObservableList listCompany = FXCollections.observableArrayList();
  • within the init Method
    - ComboBox<Company> companyComboBox = new ComboBox<>(); companyComboBox.setItems(listCompany);
    - companyComboBox.getSelectionModel().selectedItemProperty().addListener((
    ChangeListener<? super Company>)
    (ObservableValue<? extends Company> observable, Company oldValue, Company newValue) -> {
    if (newValue.getSymbol != null) {
    symbol = newValue.getSymbol();
    // call to method start()
    start();
    } else {
    // do something else
    }
    }

  • next add companyComboBox to Hbox

  • `private static void start() {

      executor = Executors.newCachedThreadPool(r ->  {
      	
      	Thread thread = new Thread(r);
      	thread.setDaemon(true);
      	return thread;
      
      });
      
      AddToQueue add = new AddToQueue();
      executor.execute(add);
      prepareTimeline();
    

    }`

  • and finally to add the real-time data
    - Data is provided by "https://github.com/WojciechZankowski/iextrading4j"
    - private static class AddToQueue implements Runnable {

      private IEXTradingClient iexTradingClient;
      private Quote quote;
    
    
      @Override
      public void run() {
      	try {
              BigDecimal last = getQuotes().getLatestPrice();
      		BigDecimal volume = getQuotes().getLatestVolume();
    
      		queue1.add(last);
      		queue2.add(volume);
    
      		Thread.sleep(1000);
      		executor.execute(this);
      		
      	} catch (InterruptedException e) {
      		e.printStackTrace();
      	}
      }
    
      private Quote getQuotes() {
          new LiveStockQuoteService(symbol);
          iexTradingClient = IEXTradingClient.create();
          this.quote = iexTradingClient.executeRequest(new QuoteRequestBuilder()
                  .withSymbol(symbol)
                  .build());
          return quote;
      }
    

    }

  • The rest should be pretty easy to do yourself

@BonkersTBobcat
Copy link

BonkersTBobcat commented Jun 22, 2020

There's a small bug in this, where the zoom is actually a bit to the right of the zoom band that you see visually. This is caused by the yAxis point (0,0) being at the top-left of the yAxis, and not taking into account the thickness of the axis. When the code gets the xOffset, it should take into acount the right side of the axis, not the left.

Line 150 should be as follows:

double xOffset = zoomTopLeft.getX() - (yAxisInScene.getX() + yAxis.getWidth());

@AndreNL22
Copy link

Line 150 should be as follows:

double xOffset = zoomTopLeft.getX() - (yAxisInScene.getX() - yAxis.getWidth());

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment