You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

JavaFX中TableView内双向变色滑块实现实时股票走势可视化

Alright, let's walk through how to build this real-time stock trend visualization in JavaFX TableView. I've built similar custom cell renderers for financial UIs before, so here's a hands-on, step-by-step solution that covers all your requirements:

1. Define a Reactive Stock Data Model

First, create a data class using JavaFX properties to enable automatic UI updates when data changes. This model will hold all the key stock metrics we need:

import javafx.beans.property.DoubleProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;

public class StockData {
    private final DoubleProperty openingPrice = new SimpleDoubleProperty(); // 当日开盘价
    private final DoubleProperty currentPrice = new SimpleDoubleProperty(); // 当前价格
    private final DoubleProperty dailyHigh = new SimpleDoubleProperty(); // 当日最高价
    private final DoubleProperty dailyLow = new SimpleDoubleProperty(); // 当日最低价
    private final ObservableList<Double> trendData = FXCollections.observableArrayList(); // 实时走势序列

    // Getters, setters, and property accessors
    public double getOpeningPrice() { return openingPrice.get(); }
    public void setOpeningPrice(double price) { openingPrice.set(price); }
    public DoubleProperty openingPriceProperty() { return openingPrice; }

    public double getCurrentPrice() { return currentPrice.get(); }
    public void setCurrentPrice(double price) { currentPrice.set(price); }
    public DoubleProperty currentPriceProperty() { return currentPrice; }

    public double getDailyHigh() { return dailyHigh.get(); }
    public void setDailyHigh(double price) { dailyHigh.set(price); }
    public DoubleProperty dailyHighProperty() { return dailyHigh; }

    public double getDailyLow() { return dailyLow.get(); }
    public void setDailyLow(double price) { dailyLow.set(price); }
    public DoubleProperty dailyLowProperty() { return dailyLow; }

    public ObservableList<Double> getTrendData() { return trendData; }

    // Add a new trend point and auto-update high/low
    public void addTrendPoint(double price) {
        trendData.add(price);
        if (price > getDailyHigh()) setDailyHigh(price);
        if (price < getDailyLow()) setDailyLow(price);
    }
}

The core of this implementation is a custom TableCell that draws the trend line, price slider, and high/low labels. We'll override updateItem to bind data and layoutChildren to handle the drawing logic:

import javafx.scene.control.TableCell;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Path;
import javafx.scene.shape.LineTo;
import javafx.scene.shape.MoveTo;
import javafx.scene.shape.Rectangle;
import javafx.scene.text.Text;

public class StockTrendTableCell extends TableCell<StockData, StockData> {
    private final Text lowPriceLabel = new Text();
    private final Text highPriceLabel = new Text();
    private final Rectangle priceSlider = new Rectangle(10, 10); // 双向滑动的价格标记
    private StockData currentStock;

    public StockTrendTableCell() {
        // Set up slider drag interaction (bidirectional sliding)
        priceSlider.setOnMouseDragged(e -> {
            if (currentStock == null || getHeight() == 0) return;
            // Map mouse Y position to price range
            double priceRange = currentStock.getDailyHigh() - currentStock.getDailyLow();
            double normalizedY = e.getY() / getHeight();
            double newPrice = currentStock.getDailyHigh() - (normalizedY * priceRange);
            currentStock.setCurrentPrice(newPrice);
        });

        // Style the slider for visibility
        priceSlider.setStroke(Color.BLACK);
        priceSlider.setStrokeWidth(1);
    }

    @Override
    protected void updateItem(StockData stock, boolean empty) {
        super.updateItem(stock, empty);
        currentStock = stock;

        if (empty || stock == null) {
            setGraphic(null);
            return;
        }

        // Bind labels to stock data (auto-updates when values change)
        lowPriceLabel.textProperty().bind(stock.dailyLowProperty().asString("%.2f"));
        highPriceLabel.textProperty().bind(stock.dailyHighProperty().asString("%.2f"));

        // Auto-switch slider color when crossing opening price
        stock.currentPriceProperty().addListener((obs, oldPrice, newPrice) -> {
            double current = newPrice.doubleValue();
            priceSlider.setFill(current >= stock.getOpeningPrice() ? Color.GREEN : Color.YELLOW);
        });

        // Trigger layout refresh
        requestLayout();
    }

    @Override
    protected void layoutChildren() {
        super.layoutChildren();
        if (currentStock == null || getWidth() == 0 || getHeight() == 0) return;

        // Position high/low labels (bottom-left and bottom-right)
        double labelOffset = 5;
        lowPriceLabel.relocate(labelOffset, getHeight() - lowPriceLabel.getLayoutBounds().getHeight() - labelOffset);
        highPriceLabel.relocate(
            getWidth() - highPriceLabel.getLayoutBounds().getWidth() - labelOffset,
            getHeight() - highPriceLabel.getLayoutBounds().getHeight() - labelOffset
        );

        // Draw the trend line using Path (better for continuous data)
        ObservableList<Double> trendPoints = currentStock.getTrendData();
        if (trendPoints.size() < 2) return;

        double cellWidth = getWidth();
        double cellHeight = getHeight();
        double priceRange = currentStock.getDailyHigh() - currentStock.getDailyLow();
        double xStep = cellWidth / (trendPoints.size() - 1);

        Path trendPath = new Path();
        for (int i = 0; i < trendPoints.size(); i++) {
            double price = trendPoints.get(i);
            double x = i * xStep;
            // Map price to Y coordinate (higher prices = lower Y in UI)
            double y = cellHeight - ((price - currentStock.getDailyLow()) / priceRange) * cellHeight;

            if (i == 0) {
                trendPath.getElements().add(new MoveTo(x, y));
            } else {
                trendPath.getElements().add(new LineTo(x, y));
            }
        }
        trendPath.setStroke(Color.GRAY);
        trendPath.setStrokeWidth(2);

        // Position the price slider based on current price
        double sliderY = cellHeight - ((currentStock.getCurrentPrice() - currentStock.getDailyLow()) / priceRange) * cellHeight;
        priceSlider.relocate(getWidth() / 2 - 5, sliderY - 5); // Center horizontally

        // Assemble all elements into the cell's graphic
        Pane cellPane = new Pane(trendPath, lowPriceLabel, highPriceLabel, priceSlider);
        setGraphic(cellPane);
    }
}
3. Feed Real-Time Data to the Model

To simulate or connect to a real-time data stream, you can use a timer (for simulation) or WebSocket (for live data). Here's a quick simulation example:

import javafx.application.Platform;
import java.util.Timer;
import java.util.TimerTask;

public class RealTimeDataFeeder {
    public static void startSimulation(StockData stock) {
        Timer timer = new Timer();
        // Push new data point every second
        timer.scheduleAtFixedRate(new TimerTask() {
            @Override
            public void run() {
                double currentPrice = stock.getCurrentPrice();
                // Simulate small price fluctuations
                double newPrice = currentPrice + (Math.random() - 0.5) * 1.5;
                // Update on JavaFX application thread
                Platform.runLater(() -> {
                    stock.addTrendPoint(newPrice);
                    stock.setCurrentPrice(newPrice);
                });
            }
        }, 0, 1000);
    }
}
4. Configure TableView to Use the Custom Cell

Finally, set up your TableView in the main application to use the custom cell factory:

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.stage.Stage;

public class StockTrendTableApp extends Application {
    @Override
    public void start(Stage stage) {
        TableView<StockData> tableView = new TableView<>();

        // Create trend column
        TableColumn<StockData, StockData> trendColumn = new TableColumn<>("实时走势");
        trendColumn.setCellValueFactory(cellData -> new javafx.beans.property.SimpleObjectProperty<>(cellData.getValue()));
        trendColumn.setCellFactory(col -> new StockTrendTableCell());
        trendColumn.setPrefWidth(350); // Set a wide enough width for the trend

        tableView.getColumns().add(trendColumn);

        // Add test stock data
        StockData testStock = new StockData();
        testStock.setOpeningPrice(150.0);
        testStock.setCurrentPrice(150.0);
        testStock.setDailyHigh(150.0);
        testStock.setDailyLow(150.0);
        tableView.getItems().add(testStock);

        // Start real-time data simulation
        RealTimeDataFeeder.startSimulation(testStock);

        Scene scene = new Scene(tableView, 400, 300);
        stage.setTitle("Real-Time Stock Trend");
        stage.setScene(scene);
        stage.show();
    }

    public static void main(String[] args) {
        launch(args);
    }
}
Key Notes to Fine-Tune
  • Color Logic: The slider automatically switches between green (above/equal to opening price) and yellow (below opening price) via a listener on the currentPriceProperty.
  • Bidirectional Slider: The drag event maps the mouse position to the stock's daily price range, letting you slide the marker up/down to adjust the current price.
  • Dynamic High/Low: Every new data point automatically updates the daily high/low values, which are reflected in the bottom labels.
  • Performance: If you're dealing with hundreds of data points, consider limiting the trendData list to a fixed window (e.g., last 50 points) to keep rendering smooth.

内容的提问来源于stack exchange,提问作者iCoder

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.19 07:39:23