I was hacking on a application today and wanted a dot grid in the background. Canvas is well suited to this as you don’t need 1000s of nodes for every dot on the grid. Canvas by default has a fixed size so I wrapped it in a Pane to make it Resizable. I thought this might be a generally useful example to show how to create a resizable canvas node. Also if you ever need a grid, it could be applied to a normal line grid just as easily. So here is a little test app with a single node of my DotGrid.
Test Application
import javafx.application.Application; import javafx.scene.Scene; import javafx.scene.paint.Color; import javafx.stage.Stage; /** * Test app for DotGrid */ import javafx.application.Application; import javafx.scene.Scene; import javafx.scene.paint.Color; import javafx.stage.Stage; /** * Test app for DotGrid */ public class DotGridTestApp extends Application { @Override public void start(Stage stage) throws Exception { stage.setScene(new Scene(new DotGrid(), Color.WHITE)); stage.show(); } public static void main(String[] args) { launch(args); } }
Dot Grid Class
import javafx.scene.canvas.Canvas; import javafx.scene.canvas.GraphicsContext; import javafx.scene.layout.Pane; import javafx.scene.paint.Color; /** * A node that draws a triangle grid of dots using canvas */ public class DotGrid extends Pane { private static final double SPACING_X = 25; private static final double SPACING_Y = 20; private static final double RADIUS = 1.5; private Canvas canvas = new Canvas(); public DotGrid() { getChildren().add(canvas); } @Override protected void layoutChildren() { final int top = (int)snappedTopInset(); final int right = (int)snappedRightInset(); final int bottom = (int)snappedBottomInset(); final int left = (int)snappedLeftInset(); final int w = (int)getWidth() - left - right; final int h = (int)getHeight() - top - bottom; canvas.setLayoutX(left); canvas.setLayoutY(top); if (w != canvas.getWidth() || h != canvas.getHeight()) { canvas.setWidth(w); canvas.setHeight(h); GraphicsContext g = canvas.getGraphicsContext2D(); g.clearRect(0, 0, w, h); g.setFill(Color.gray(0,0.2)); for (int x = 0; x < w; x += SPACING_X) { for (int y = 0; y < h; y += SPACING_Y) { double offsetY = (y%(2*SPACING_Y)) == 0 ? SPACING_X /2 : 0; g.fillOval(x-RADIUS+offsetY,y-RADIUS,RADIUS+RADIUS,RADIUS+RADIUS); } } } } }
There really is not much too it, when the layout is applied to Pane check if size has changed and redraw the canvas. There are a couple of optimizations you could do here if performance was a issue.
- When the Pane gets bigger don’t clear, just draw any new dots needed. If you were animating the size growing then this might be a good win.
- Draw the dot once into a WritableImage using another Canvas or Circle node and snapshot(). Then draw that image for each dot. That way turn out to be a performance win, but maybe not. If you had more complex dots, like company logos then this would probably be a good gain.
In this case I have not set any preferred size for the DotGrid so it will be whatever size its parent wants it to be. If you need it to be a minimum size you could call setPrefSize() or setMinSize() in the constructor.
Regarding a resizable canvas, see also:
http://dlemmermann.wordpress.com/2014/04/10/javafx-tip-1-resizable-canvas/
Just looked at your method, it has the advantage of being a single node but there is one issue with it. When it is resized it will draw twice at a minimum, when width and height are set. If you trigger draw off layout you will get at most one draw per frame. You could just override the layout method in your Canvas subclass. You may need to call requestLayout() from the width and height listeners.
Hey nice setup Jasper. Good Post!
This solution are also usefull when your Canvas placed deep inside your scene graph, or in other words – Canvas have long chain of parent Nodes.
This is more actual when you develop editor with the many standard controls and Canvas at center area of your app.
Thanx for your post!
hi Jasper,
your post is helping me a lot, but I have a question. is it possible to draw lines between the dots? so that one can see a hexagonal grid, but one is still able to add different event handlers to the specific points and lines.
thanks a lot!
Great example. I think `offsetY` should be renamed to `offsetX`.
Very nice; this will save me a lot of headaches.
The problem with this grid is that once you resize the canvas the previous data of canvas i.e whatever you have drawn on it automatically erased.