javafx - JavaFX 图表中的缩放选择矩形未显示,但缩放工作正常

标签 javafx charts

我编写了一个在 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);

    }
}

//fancychart.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);

关于javafx - JavaFX 图表中的缩放选择矩形未显示,但缩放工作正常,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/34687153/

相关文章:

php - Google 为您的客户提供的图表?

ios - 使用带有 iOS swift 3 的图表库的饼图

angular - 如何在 Angular Chart.js 中创建 'backgroundColor' 渐变

java - 停止 JUnit 完成,直到所有其他线程都已停止

Javafx 实时线程更新

java - 在 slider 上放置线标记

JavaFX + Maven + Ojdbc6 + IntelliJ(使用适用于 Windows 的 Javafx 应用程序部署 oracle jar)

JavaFx:获取点击的按钮值

sql - Oracle Apex 4.2 图表胡佛问题

charts - 删除谷歌图表中的填充?