7 Replies Latest reply: Apr 22, 2013 2:56 PM by jsmith RSS

    Charts: how do I determine when the mouse is in the plot area

    JGagnon
      I'm trying to implement logic that will do the following:

      Panning plot within plot display area by clicking and dragging mouse

      If the user clicks and drags in the chart plot area (the region where the chart plot actually displays, not including the axes, legends, titles, etc.), I want to be able to provide the effect of panning the plot (e.g. similar to grabbing and dragging a map in Google maps) within the plot display. This makes the most sense, of course, if the user has zoomed in to some level and the entire plot is no longer fully visible within the display.

      Zooming on a plot

      If the user places the mouse somewhere in the chart plot area and uses their mouse wheel, I want to be able to provide the effect of zooming in and out of the plot. The plot area itself must remain at its original size, just the representation of the plot needs to simulate the effect of being zoomed in or out on.

      Thoughts, suggestions, ...

      To make the zoom and pan effect, my thought was to adjust the axes bounds as appropriate to simulate these effects. The trick for some of what I'm planning on doing is finding out where the mouse is in the plot area (not the whole chart) so that I can find the x,y offsets from the plot origin (which appears to be in the upper left - at the chart level anyway). For panning, I don't think I care what the offset is, just that it's within the plot. For zooming, I may need to know where the mouse is in the plot so that the zooming could be centered on that location in the plot. This makes me think that it would also be helpful to find out how to translate the mouse x,y pixel position to a corresponding x,y value in the plot data domain.

      I've been experimenting with attaching event handlers to detect various mouse actions and what information can be gleaned from these events. So far I have not found anything that allows me to determine the bounds of the plot region in the overall chart. I can get the mouse x,y position, but I don't know where it is in relation to the plot area bounds. I don't want to initiate a pan of a plot if the user happens to drag their mouse somewhere outside the plot area (i.e. over an axis, legend, etc.). Also, it would not make sense to start a zoom on the chart unless the mouse is over the plot.

      Please let me know if this seems to be a reasonable approach, and if not, please suggest any ideas for alternatives. Any help, ideas and suggestions regarding how to solve these issues and/or determine this information would be welcome.
        • 1. Re: Charts: how do I determine when the mouse is in the plot area
          James_D
          I've not tried this, but my first attempt would be to try to add the mouse listeners to the plot area itself, instead of to the chart. I think you can get the plot area with
          Group plotContent = (Group)chart.lookup(".plot-content");

          Again, that's untested...
          • 2. Re: Charts: how do I determine when the mouse is in the plot area
            JGagnon
            Hmm, calling the logic suggested does return an object that you can get information about, but it's confusing what I'm seeing. I've already been printing out some of the attributes of the charts and axes (bounds, sizes, etc.) and did the same for the item returned from the logic that was suggested. Some of what I see kind of makes sense, but other values do not make sense at all (at least not to me).

            Actually, this brings to light a larger topic with regards to charts in general and that is what kind of information is available to help developers understand the "lay of the land" (or the "lay of the chart" if you will) of how charts are represented visually? I can make some reasonable guesses as to what some of the attributes mean, but others are less clear. It would be very helpful to graphically see how charts are laid out and which attributes represent particular measurements, etc. on the layout. Almost like a wireframe where all of the dimensions are indicated so that we can understand how to make use of the attributes available. In the same vein, it would also be helpful to have clear definitions of the attributes so that it's understood what aspect of the chart they are telling us about. I realize that some of this is determined when the chart is rendered and also that charts share many common attributes with other UI components.

            To provide a concrete example (there are probably some attributes that have been left out from the full set available):

            CHART INFO

            local bounds:
            width: 750.0, height: 500.0, depth: 0.0
            min X: 0.0, min Y: 0.0, min Z: 0.0
            max X: 750.0, max Y: 500.0, max Z: 0.0

            parent bounds:
            width: 750.0, height: 500.0, depth: 0.0
            min X: 0.0, min Y: 0.0, min Z: 0.0
            max X: 750.0, max Y: 500.0, max Z: 0.0

            baseline offset: 15.407519340515137 What is this and why is it not a whole number? What role does it play?
            height: 500.0, width: 750.0
            insets: top: 5.0, bottom: 5.0, left: 5.0, right: 5.0

            layout bounds:
            width: 750.0, height: 500.0, depth: 0.0
            min X: 0.0, min Y: 0.0, min Z: 0.0
            max X: 750.0, max Y: 500.0, max Z: 0.0

            content bias: null
            padding: top: 5.0, bottom: 5.0, left: 5.0, right: 5.0
            scale: X: 1.0, Y: 1.0, Z: 1.0
            translate: X: 0.0, Y: 0.0, Z: 0.0

            AXIS INFO

            X-axis: zero: 0.0, Y-axis: zero: 409.0 I don't understand these

            X-AXIS
            X-axis: tick unit: 1.0, tick length: 8.0
            width: 686.0, height: 36.0, depth: 0.0
            min X: 0.0, min Y: 0.0, min Z: 0.0
            max X: 686.0, max Y: 36.0, max Z: 0.0
            scale: 6.86, scale X: 1.0, scale Y: 1.0

            Y-AXIS
            Y-axis: tick unit: 1.0, tick length: 8.0
            width: 35.0, height: 409.0, depth: 0.0
            min X: 0.0, min Y: 0.0, min Z: 0.0
            max X: 35.0, max Y: 409.0, max Z: 0.0
            scale: -40.9, scale X: 1.0, scale Y: 1.0

            PLOT INFO

            Many of these values are not whole numbers either - is this related somehow to baseline offsets?

            local bounds:
            width: 695.9130859375, height: 96.41386413574219, depth: 0.0
            min X: -8.068097114562988, min Y: 158.93190002441406, min Z: 0.0
            max X: 687.844988822937, max Y: 255.34576416015625, max Z: 0.0

            The parent bounds don't seem to match up with the chart itself (assuming that is the parent)

            parent bounds:
            width: 695.9130859375, height: 96.41386413574219, depth: 0.0
            min X: 36.93190383911133, min Y: 168.93190002441406, min Z: 0.0
            max X: 732.8449897766113, max Y: 265.34576416015625, max Z: 0.0

            baseline offset: 247.34576416015625

            layout bounds:
            width: 695.9130859375, height: 96.41386413574219, depth: 0.0
            min X: -8.068097114562988, min Y: 158.93190002441406, min Z: 0.0
            max X: 687.844988822937, max Y: 255.34576416015625, max Z: 0.0

            content bias: null
            scale: X: 1.0, Y: 1.0, Z: 1.0
            translate: X: 0.0, Y: 0.0, Z: 0.0

            Edited by: 998038 on Apr 22, 2013 9:13 AM
            • 3. Re: Charts: how do I determine when the mouse is in the plot area
              JGagnon
              On a slightly different (but related) subject, where can I find an up-to-date source of information that describes the IDs and class names that JavaFX uses? I assume the ".plot-content" is pertaining to the ID of the chart plot area (or plot "child" of the chart?). Where can I find the names of the various objects that can be "looked up"?
              • 4. Re: Charts: how do I determine when the mouse is in the plot area
                JGagnon
                Update:

                I did the same thing, except instead of getting the ".plot-content" I got ".chart-plot-background". This seems to be getting me closer to what I think I'm looking for.

                PLOT BACKGROUND INFO

                local bounds:
                width: 686.0, height: 409.0, depth: 0.0
                min X: 0.0, min Y: 0.0, min Z: 0.0
                max X: 686.0, max Y: 409.0, max Z: 0.0

                parent bounds:
                width: 686.0, height: 409.0, depth: 0.0
                min X: 45.0, min Y: 10.0, min Z: 0.0
                max X: 731.0, max Y: 419.0, max Z: 0.0

                baseline offset: 409.0
                height: 409.0, width: 686.0
                insets: top: 0.0, bottom: 0.0, left: 0.0, right: 0.0

                layout bounds:
                width: 686.0, height: 409.0, depth: 0.0
                min X: 0.0, min Y: 0.0, min Z: 0.0
                max X: 686.0, max Y: 409.0, max Z: 0.0

                content bias: null
                padding: top: 0.0, bottom: 0.0, left: 0.0, right: 0.0
                scale: X: 1.0, Y: 1.0, Z: 1.0
                translate: X: 0.0, Y: 0.0, Z: 0.0

                Many of these values seem to align with what I'm expecting to see with regards to some of the axis attributes and also information I print out when I click on various areas of the chart (I print the X,Y position). I think I'm getting close (with a few more differences to resolve). Let me know if this makes sense to you.

                Thanks
                • 5. Re: Charts: how do I determine when the mouse is in the plot area
                  shakir.gusaroff
                  Where can I find the names of the various objects that can be "looked up"?
                  Take a look at the caspian.css file:
                  http://hg.openjdk.java.net/openjfx/2.2/master/rt/raw-file/tip/javafx-ui-controls/src/com/sun/javafx/scene/control/skin/caspian/caspian.css
                  • 6. Re: Charts: how do I determine when the mouse is in the plot area
                    James_D
                    I experimented a bit and it seems the plot-content is the Group containing, well, the content of the plot. So in a line chart it's the smallest rectangle containing all the lines and the graphic at the data point (little circles, by default). What you probably need instead is the chart-plot-background.

                    Here's a quick example of a panning chart. There are probably better ways to manage the mouse dragging (it behaves somewhat badly if you drag out of the plot area and then back in, for example) but it gives you the basic idea. My original idea of adding a mouse directly to the plot area failed as the event doesn't get propagated to the plot region if you click on part of the plot content (a line, for example).
                    import java.util.Random;
                    
                    import javafx.application.Application;
                    import javafx.beans.property.DoubleProperty;
                    import javafx.beans.property.SimpleDoubleProperty;
                    import javafx.collections.FXCollections;
                    import javafx.collections.ObservableList;
                    import javafx.event.EventHandler;
                    import javafx.geometry.Point2D;
                    import javafx.scene.Scene;
                    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.input.MouseEvent;
                    import javafx.scene.layout.BorderPane;
                    import javafx.scene.layout.Region;
                    import javafx.stage.Stage;
                    
                    public class PanningChart extends Application {
                    
                      @Override
                      public void start(Stage primaryStage) {
                        final NumberAxis xaxis = new NumberAxis("x", 0, 10, 1);
                        final NumberAxis yaxis = new NumberAxis("y", 0, 100, 10);
                        final LineChart<Number, Number> chart = new LineChart<>(xaxis, yaxis);
                        chart.setAnimated(false);
                    
                        chart.setData(createData());
                    
                        final Region plotArea = (Region) chart.lookup(".chart-plot-background");
                        final DoubleProperty lastMouseX = new SimpleDoubleProperty();
                        final DoubleProperty lastMouseY = new SimpleDoubleProperty();
                    
                        chart.setOnMousePressed(new EventHandler<MouseEvent>() {
                          @Override
                          public void handle(MouseEvent event) {
                            final double x = event.getX();
                            final double y = event.getY();
                            if (plotArea.getBoundsInParent().contains(new Point2D(x, y))) {
                              lastMouseX.set(x);
                              lastMouseY.set(y);
                            }
                          }
                        });
                    
                        chart.setOnMouseDragged(new EventHandler<MouseEvent>() {
                          @Override
                          public void handle(MouseEvent event) {
                            final double x = event.getX();
                            final double y = event.getY();
                            if (plotArea.getBoundsInParent().contains(new Point2D(x, y))) {
                              moveAxis(xaxis, x, lastMouseX);
                              moveAxis(yaxis, y, lastMouseY);
                            }
                          }
                        });
                    
                        final BorderPane root = new BorderPane();
                        root.setCenter(chart);
                    
                        primaryStage.setScene(new Scene(root, 600, 400));
                        primaryStage.show();
                      }
                    
                      private void moveAxis(NumberAxis axis, double mouseLocation,
                          DoubleProperty lastMouseLocation) {
                        double scale = axis.getScale();
                        double delta = (mouseLocation - lastMouseLocation.get()) / scale;
                        axis.setLowerBound(axis.getLowerBound() - delta);
                        axis.setUpperBound(axis.getUpperBound() - delta);
                        lastMouseLocation.set(mouseLocation);
                      }
                    
                      private ObservableList<Series<Number, Number>> createData() {
                        final ObservableList<Data<Number, Number>> data = FXCollections
                            .observableArrayList();
                        final Random rng = new Random();
                        for (int x = 0; x <= 10; x++) {
                          data.add(new Data<Number, Number>(x, rng.nextDouble() * 100));
                        }
                        return FXCollections.singletonObservableList(new Series<Number, Number>(data));
                      }
                    
                      public static void main(String[] args) {
                        launch(args);
                      }
                    }
                    998038 wrote:On a slightly different (but related) subject, where can I find an up-to-date source of information that describes the IDs and class names that JavaFX uses? I assume the ".plot-content" is pertaining to the ID of the chart plot area (or plot "child" of the chart?). Where can I find the names of the various objects that can be "looked up"?
                    I generally try to avoid lookups as they feel a bit fragile. However occasionally there seems to be no other way, as in this case. The [url http://docs.oracle.com/javafx/2/api/javafx/scene/doc-files/cssref.html]CSS reference guide documents the css class substructure of each Node type.
                    • 7. Re: Charts: how do I determine when the mouse is in the plot area
                      jsmith
                      Where can I find the names of the various objects that can be "looked up"?
                      In addition to shakir's link to the caspian.css reference, you can use the ScenicView tool:
                      http://fxexperience.com/scenic-view/

                      You can also use this bit of code to recursively dump the nodes in the scene graph (which will include the css classes and ids attached to each node):
                      // debugging routine to dump the scene graph.
                      public  static void dump(Node n) { dump(n, 0); }
                      private static void dump(Node n, int depth) {
                        for (int i = 0; i < depth; i++) System.out.print("  ");
                        System.out.println(n);
                        if (n instanceof Parent) for (Node c : ((Parent) n).getChildrenUnmodifiable()) dump(c, depth + 1);
                      }
                      A couple of other relevant pieces of information:

                      1. Some nodes get created lazily and are not present until a pulse after the node has actually been displayed in an active scene. So sometimes, you need to wait a pulse before a lookup will work. The wait can be accomplised invoking the lookup after stage.show call has been or (if you are dynamically modifying the scene graph) via an AnimationTimer handle method.

                      2. Some graphs are animated, so sometimes you need to turn off animation on the graphs for the lookup to work as you expect (as you generally don't want to perform a lookup while the graph is in an intermediate animated state).
                      I generally try to avoid lookups as they feel a bit fragile. However occasionally there seems to be no other way, as in this case.
                      +1