JavaFX图表中的缩放选择矩形未显示,但缩放有效



我写了一个在JavaFX图表上实现缩放功能的示例程序,我从GitHub项目中找到了Zoom类,并正在重用它。我的挑战是,当我拖动鼠标选择某个区域进行缩放时,所选区域矩形在Windows 7、Linux、Mac OS X中不会显示,但在Windows 10中运行良好。我缺少什么?我如何使selectedRectangle显示出来,以便用户知道他们正在放大到什么区域?

以下是编译和运行此程序所需的所有文件:

/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package testlinechartgraphs;
import java.util.ArrayList;
import java.util.Random;
import javafx.application.Application;
import static javafx.application.Application.launch;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.geometry.Pos;
import javafx.geometry.Side;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.chart.LineChart;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.XYChart;
import javafx.scene.control.CheckBox;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
public class TestLineChartGraphs extends Application {
    final static ObservableList<XYChart.Series<Number, Number>> lineChartData = FXCollections.observableArrayList();
    @Override
    public void start(Stage stage) {
        stage.setTitle("Line Chart Sample");
        //defining the axes
        final NumberAxis xAxis = new NumberAxis();
        final NumberAxis yAxis = new NumberAxis();
        xAxis.setLabel("Number of Month");
        Random randomNumbers = new Random();
        ArrayList<Integer> arrayList = new ArrayList<>();
        //creating the chart
        final LineChart<Number, Number> lineChart
                = new LineChart<Number, Number>(xAxis, yAxis);
        lineChart.setTitle("Stock Monitoring, 2010");
        lineChart.setLegendSide(Side.RIGHT);
        int randomCount = randomNumbers.nextInt(14)+1;
        //System.out.println("randomCount = " + randomCount);
        for (int i = 0; i < randomCount; i++) {
            XYChart.Series series = new XYChart.Series();
            series.setName("series_" + i);
            for (int k = 0; k < 20; k++) {
                int x = randomNumbers.nextInt(50);
                series.getData().add(new XYChart.Data(k, x));
            }
            //seriesList.add(series);
            lineChartData.add(series);
        }

        lineChart.setData(lineChartData);
        final StackPane chartContainer = new StackPane();
        Zoom zoom = new Zoom(lineChart, chartContainer);
        chartContainer.getChildren()
                .add(lineChart);
        BorderPane borderPane = new BorderPane();
        borderPane.setCenter(chartContainer);
        //borderPane.setCenter(lineChart);
        borderPane.setBottom(getLegend());
////        
        //Scene scene = new Scene(lineChart, 800, 600);
        Scene scene = new Scene(borderPane, 800, 600);
        //lineChart.getData().addAll(series, series1);
        stage.setScene(scene);
        scene.getStylesheets().addAll("file:///C:/Users/siphoh/Documents/NetBeansProjects/WiresharkSeqNum/src/fancychart.css");
        //scene.getStylesheets().addAll(getClass().getResource("fancychart.css").toExternalForm());
        stage.show();
    }
    public static Node getLegend() {
        HBox hBox = new HBox();
        for (final XYChart.Series<Number, Number> series : lineChartData) {
            CheckBox checkBox = new CheckBox(series.getName());

            checkBox.setSelected(true);
            checkBox.setOnAction(event -> {
                if (lineChartData.contains(series)) {
                    lineChartData.remove(series);
                } else {
                    lineChartData.add(series);
                }
            });
            hBox.getChildren().add(checkBox);
        }
        hBox.setAlignment(Pos.CENTER);
        hBox.setSpacing(20);
        hBox.setStyle("-fx-padding: 0 10 20 10");
        return hBox;
    }
    public static void main(String[] args) {
        launch(args);
    }
}

//Zoom.java类:

package testlinechartgraphs;
/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
/**
 *
 * @author *************
 */
import javafx.event.EventHandler;
import javafx.geometry.Point2D;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.XYChart;
import javafx.scene.control.Label;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.Pane;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
/**
 * This class adds a zoom functionality to a given XY chart. Zoom means that a
 user can select a region in the chart that should be displayed at a larger
 scale.
 *
 */
public class Zoom {
    private static final String INFO_LABEL_ID = "zoomInfoLabel";
    private final Pane pane;
    private final XYChart<Number, Number> chart;
    private final NumberAxis xAxis;
    private final NumberAxis yAxis;
    private final SelectionRectangle selectionRectangle;
    private Label infoLabel;
    private Point2D selectionRectangleStart;
    private Point2D selectionRectangleEnd;
    /**
     * Create a new instance of this class with the given chart and pane
     * instances. The {@link Pane} instance is needed as a parent for the
     * rectangle that represents the user selection.
     *
     * @param chart the xy chart to which the zoom support should be added
     * @param pane the pane on which the selection rectangle will be drawn.
     */
    public Zoom(XYChart<Number, Number> chart, Pane pane) {
        this.pane = pane;
        this.chart = chart;
        this.xAxis = (NumberAxis) chart.getXAxis();
        this.yAxis = (NumberAxis) chart.getYAxis();
        selectionRectangle = new SelectionRectangle();
        pane.getChildren().add(selectionRectangle);
        addDragSelectionMechanism();
        addInfoLabel();
    }
    /**
     * The info label shows a short info text that tells the user how to unreset
     * the zoom level.
     */
    private void addInfoLabel() {
        infoLabel = new Label("Click ESC to reset the zoom level.");
        infoLabel.setId(INFO_LABEL_ID);
        pane.getChildren().add(infoLabel);
        StackPane.setAlignment(infoLabel, Pos.TOP_RIGHT);
        infoLabel.setVisible(false);
    }
    /**
     * Adds a mechanism to select an area in the chart that should be displayed
     * at larged scale.
     */
    private void addDragSelectionMechanism() {
        pane.addEventHandler(MouseEvent.MOUSE_PRESSED, new MousePressedHandler());
        pane.addEventHandler(MouseEvent.MOUSE_DRAGGED, new MouseDraggedHandler());
        pane.addEventHandler(MouseEvent.MOUSE_RELEASED, new MouseReleasedHandler());
        pane.addEventHandler(KeyEvent.KEY_RELEASED, new EscapeKeyHandler());
    }
    private Point2D computeRectanglePoint(double eventX, double eventY) {
        double lowerBoundX = computeOffsetInChart(xAxis, false);
        double upperBoundX = lowerBoundX + xAxis.getWidth();
        double lowerBoundY = computeOffsetInChart(yAxis, true);
        double upperBoundY = lowerBoundY + yAxis.getHeight();
        // make sure the rectangle's end point is in the interval defined by the lower and upper bounds for each
        // dimension
        double x = Math.max(lowerBoundX, Math.min(eventX, upperBoundX));
        double y = Math.max(lowerBoundY, Math.min(eventY, upperBoundY));
        return new Point2D(x, y);
    }
    /**
     * Computes the pixel offset of the given node inside the chart node.
     *
     * @param node the node for which to compute the pixel offset
     * @param vertical flag that indicates whether the horizontal or the
     * vertical dimension should be taken into account
     * @return the offset inside the chart node
     */
    private double computeOffsetInChart(Node node, boolean vertical) {
        double offset = 0;
        do {
            if (vertical) {
                offset += node.getLayoutY();
            } else {
                offset += node.getLayoutX();
            }
            node = node.getParent();
        } while (node != chart);
        return offset;
    }
    /**
     *
     */
    private final class MousePressedHandler implements EventHandler<MouseEvent> {
        @Override
        public void handle(final MouseEvent event) {
            // do nothing for a right-click
            if (event.isSecondaryButtonDown()) {
                return;
            }
            // store position of initial click
            selectionRectangleStart = computeRectanglePoint(event.getX(), event.getY());
            event.consume();
        }
    }
    /**
     *
     */
    private final class MouseDraggedHandler implements EventHandler<MouseEvent> {
        @Override
        public void handle(final MouseEvent event) {
            // do nothing for a right-click
            if (event.isSecondaryButtonDown()) {
                return;
            }
            // store current cursor position
            selectionRectangleEnd = computeRectanglePoint(event.getX(), event.getY());
            double x = Math.min(selectionRectangleStart.getX(), selectionRectangleEnd.getX());
            double y = Math.min(selectionRectangleStart.getY(), selectionRectangleEnd.getY());
            double width = Math.abs(selectionRectangleStart.getX() - selectionRectangleEnd.getX());
            double height = Math.abs(selectionRectangleStart.getY() - selectionRectangleEnd.getY());
            drawSelectionRectangle(x, y, width, height);
            event.consume();
        }
        /**
         * Draws a selection box in the view.
         *
         * @param x the x position of the selection box
         * @param y the y position of the selection box
         * @param width the width of the selection box
         * @param height the height of the selection box
         */
        private void drawSelectionRectangle(final double x, final double y, final double width, final double height) {
            selectionRectangle.setVisible(true);
            selectionRectangle.setX(x);
            selectionRectangle.setY(y);
            selectionRectangle.setWidth(width);
            selectionRectangle.setHeight(height);
            //selectionRectangle.setFill(Color.LIGHTSEAGREEN.deriveColor(0, 1, 1, 0.5));
            //System.out.println("Draw the rectangle ...");
        }
    }
    /**
     *
     */
    private final class MouseReleasedHandler implements EventHandler<MouseEvent> {
        /**
         * Defines a minimum width for the selected area. If the selected
         * rectangle is not wider than this value, no zooming will take place.
         * This helps prevent accidental zooming.
         */
        private static final double MIN_RECTANGE_WIDTH = 10;
        /**
         * Defines a minimum height for the selected area. If the selected
         * rectangle is not wider than this value, no zooming will take place.
         * This helps prevent accidental zooming.
         */
        private static final double MIN_RECTANGLE_HEIGHT = 10;
        @Override
        public void handle(final MouseEvent event) {
            hideSelectionRectangle();
            if (selectionRectangleStart == null || selectionRectangleEnd == null) {
                return;
            }
            if (isRectangleSizeTooSmall()) {
                return;
            }
            setAxisBounds();
            showInfo();
            selectionRectangleStart = null;
            selectionRectangleEnd = null;
            // needed for the key event handler to receive events
            pane.requestFocus();
            event.consume();
        }
        private boolean isRectangleSizeTooSmall() {
            double width = Math.abs(selectionRectangleEnd.getX() - selectionRectangleStart.getX());
            double height = Math.abs(selectionRectangleEnd.getY() - selectionRectangleStart.getY());
            return width < MIN_RECTANGE_WIDTH || height < MIN_RECTANGLE_HEIGHT;
        }
        /**
         * Hides the selection rectangle.
         */
        private void hideSelectionRectangle() {
            selectionRectangle.setVisible(false);
        }
        private void setAxisBounds() {
            disableAutoRanging();
            // compute new bounds for the chart's x and y axes
            double selectionMinX = Math.min(selectionRectangleStart.getX(), selectionRectangleEnd.getX());
            double selectionMaxX = Math.max(selectionRectangleStart.getX(), selectionRectangleEnd.getX());
            double selectionMinY = Math.min(selectionRectangleStart.getY(), selectionRectangleEnd.getY());
            double selectionMaxY = Math.max(selectionRectangleStart.getY(), selectionRectangleEnd.getY());
            setHorizontalBounds(selectionMinX, selectionMaxX);
            setVerticalBounds(selectionMinY, selectionMaxY);
        }
        private void disableAutoRanging() {
            xAxis.setAutoRanging(false);
            yAxis.setAutoRanging(false);
        }
        private void showInfo() {
            infoLabel.setVisible(true);
        }
        /**
         * Sets new bounds for the chart's x axis.
         *
         * @param minPixelPosition the x position of the selection rectangle's
         * left edge (in pixels)
         * @param maxPixelPosition the x position of the selection rectangle's
         * right edge (in pixels)
         */
        private void setHorizontalBounds(double minPixelPosition, double maxPixelPosition) {
            double currentLowerBound = xAxis.getLowerBound();
            double currentUpperBound = xAxis.getUpperBound();
            double offset = computeOffsetInChart(xAxis, false);
            setLowerBoundX(minPixelPosition, currentLowerBound, currentUpperBound, offset);
            setUpperBoundX(maxPixelPosition, currentLowerBound, currentUpperBound, offset);
        }
        /**
         * Sets new bounds for the chart's y axis.
         *
         * @param minPixelPosition the y position of the selection rectangle's
         * upper edge (in pixels)
         * @param maxPixelPosition the y position of the selection rectangle's
         * lower edge (in pixels)
         */
        private void setVerticalBounds(double minPixelPosition, double maxPixelPosition) {
            double currentLowerBound = yAxis.getLowerBound();
            double currentUpperBound = yAxis.getUpperBound();
            double offset = computeOffsetInChart(yAxis, true);
            setLowerBoundY(maxPixelPosition, currentLowerBound, currentUpperBound, offset);
            setUpperBoundY(minPixelPosition, currentLowerBound, currentUpperBound, offset);
        }
        private void setLowerBoundX(double pixelPosition, double currentLowerBound, double currentUpperBound,
                double offset) {
            double newLowerBound = computeBound(pixelPosition, offset, xAxis.getWidth(), currentLowerBound,
                    currentUpperBound, false);
            xAxis.setLowerBound(newLowerBound);
        }
        private void setUpperBoundX(double pixelPosition, double currentLowerBound, double currentUpperBound,
                double offset) {
            double newUpperBound = computeBound(pixelPosition, offset, xAxis.getWidth(), currentLowerBound,
                    currentUpperBound, false);
            xAxis.setUpperBound(newUpperBound);
        }
        private void setLowerBoundY(double pixelPosition, double currentLowerBound, double currentUpperBound,
                double offset) {
            double newLowerBound = computeBound(pixelPosition, offset, yAxis.getHeight(), currentLowerBound,
                    currentUpperBound, true);
            yAxis.setLowerBound(newLowerBound);
        }
        private void setUpperBoundY(double pixelPosition, double currentLowerBound, double currentUpperBound,
                double offset) {
            double newUpperBound = computeBound(pixelPosition, offset, yAxis.getHeight(), currentLowerBound,
                    currentUpperBound, true);
            yAxis.setUpperBound(newUpperBound);
        }
        private double computeBound(double pixelPosition, double pixelOffset, double pixelLength, double lowerBound,
                double upperBound, boolean axisInverted) {
            double pixelPositionWithoutOffset = pixelPosition - pixelOffset;
            double relativePosition = pixelPositionWithoutOffset / pixelLength;
            double axisLength = upperBound - lowerBound;
            // The screen's y axis grows from top to bottom, whereas the chart's y axis goes from bottom to top.
            // That's
            // why we need to have this distinction here.
            double offset = 0;
            int sign = 0;
            if (axisInverted) {
                offset = upperBound;
                sign = -1;
            } else {
                offset = lowerBound;
                sign = 1;
            }
            double newBound = offset + sign * relativePosition * axisLength;
            return newBound;
        }
    }
    /**
     *
     */
    private final class EscapeKeyHandler implements EventHandler<KeyEvent> {
        @Override
        public void handle(KeyEvent event) {
            // the ESCAPE key lets the user reset the zoom level
            if (KeyCode.ESCAPE.equals(event.getCode())) {
                resetAxisBounds();
                hideInfo();
            }
        }
        private void resetAxisBounds() {
            xAxis.setAutoRanging(true);
            yAxis.setAutoRanging(true);
        }
        private void hideInfo() {
            infoLabel.setVisible(false);
        }
    }
}

//SelectionRectangle.java类

package testlinechartgraphs;
/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
/**
 *
 * @author **************
 */
import javafx.scene.shape.Rectangle;
/**
 * Represents an area on the screen that was selected by a mouse drag operation.
 *
 */
public class SelectionRectangle extends Rectangle {
    private static final String STYLE_CLASS_SELECTION_BOX = "chart-selection-rectangle";
    public SelectionRectangle() {
        getStyleClass().addAll(STYLE_CLASS_SELECTION_BOX);
        setVisible(false);
        setManaged(false);
        setMouseTransparent(true);
    }
}

//fancyhart.css文件

.chart-line-symbol {
    -fx-scale-x: 0.5;
    -fx-scale-y: 0.5;
}
.chart-popup-label {
    -fx-padding: 1 3 1 3;
    -fx-border-radius: 1;
    -fx-border-width: 1;
    -fx-opacity: 0.7;
    -fx-effect: dropshadow( two-pass-box , rgba(0,0,0,0.3) , 8, 0.0 , 0 , 3 );
}
.chart-legend-item {
    -fx-padding : 1 23 1 23;
}
.chart-legend-item-symbol {
   -fx-scale-x: 0.8;
   -fx-scale-y: 0.8;
}

.chart-selection-rectangle {
    -fx-stroke: rgba(135, 206, 250, 0.8);
    -fx-stroke-type: inside;
    -fx-fill: rgba(135, 206, 250, 0.2);
}



#zoomInfoLabel {
    -fx-background-color: rgba(135, 206, 250, 0.8);
    -fx-font-size: 14;
    -fx-padding: 3;
    -fx-background-radius: 2;
}

如有任何帮助,我们将不胜感激。

谢谢,

您的订单是错误的。你有

    final StackPane chartContainer = new StackPane();
    Zoom zoom = new Zoom(lineChart, chartContainer);
    chartContainer.getChildren().add(lineChart);

这意味着您首先创建容器,然后添加缩放矩形,然后添加图表。所以缩放矩形在图表的背面。

你需要这样做:

    final StackPane chartContainer = new StackPane();
    chartContainer.getChildren().add(lineChart);
    Zoom zoom = new Zoom(lineChart, chartContainer);

最新更新