diff --git a/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/.ratings b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/.ratings
index 79b9ab81603cc8ffd1e7878d8acbb734448cc36f..06fc22528dedfb67b78ed83a82ce341b10743e09 100644
--- a/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/.ratings
+++ b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/.ratings
@@ -1,12 +1,12 @@
 DiagramCoordinate.java 6b00aec99054d4cd19003a72bd4e5e774ac6a641 GREEN
-DiagramLayers.java 155cbb47a5f0aaa0025320ae607e6777f3a2d2e8 GREEN
-DiagramViewer.java 920bb0f4ee6dd9ac6b607e44c01f04a413b2e2ed GREEN
+DiagramLayers.java aa1f95dbae290c8b00202abe4385b01b8f36e5ab GREEN
+DiagramViewer.java e5afa84170823a396c2f80864eb752366d34eb92 GREEN
 DiagramViewerDefaultTags.java 6230763252409c60009ab8887b4ef582cf883229 GREEN
-DiagramViewerFeatures.java 397c9600193df18e865f1ff7c829df577c56d383 GREEN
+DiagramViewerFeatures.java 3dd78d9c117fc156924a151c6f8d770c53c103bc GREEN
 DiagramViewerSelection.java e833f592543bc97077907d980a39b123fc4044e6 GREEN
 EDragGesture.java 5cfa098d3877db11981c2750e5e103156d62fc5e GREEN
 FeedbackChange.java b088fa89af648f1674f2f9c1f7f99d585ce801ca GREEN
-GridCanvasVisual.java 734027d56af342cd01ff445ba9347b8dbb6c83c2 GREEN
-MVCBundleManager.java e4892a571fd26eccc5e4e9b2256432721723f542 GREEN
+GridCanvasVisual.java e7c83211e0fce2b0d55aac77d0203950286646b9 GREEN
+MVCBundleManager.java 18667b4ed98da124b7c1bc7a103e95232df9ad49 GREEN
 MouseState.java 3d9993f799d5d74bc74ac03b46e4a1857c4d267e GREEN
 SVGExporter.java cbbd1eceb2910fd5c1693e05c5303a193127b9db GREEN
diff --git a/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/DiagramLayers.java b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/DiagramLayers.java
index 155cbb47a5f0aaa0025320ae607e6777f3a2d2e8..aa1f95dbae290c8b00202abe4385b01b8f36e5ab 100644
--- a/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/DiagramLayers.java
+++ b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/DiagramLayers.java
@@ -14,12 +14,9 @@ import org.fortiss.tooling.common.ui.javafx.lwfxef.mvc.IMVCBundle;
 import javafx.collections.ObservableList;
 import javafx.scene.Group;
 import javafx.scene.Node;
-import javafx.scene.transform.Scale;
 
 /** {@link Group} of layers for the {@link DiagramViewer}. */
 public final class DiagramLayers extends Group {
-	/** The diagram viewer of this layer group. */
-	private final DiagramViewer viewer;
 	/** The bottom layer. */
 	private final Layer bottomLayer = new Layer();
 	/** The content node layer. */
@@ -42,12 +39,9 @@ public final class DiagramLayers extends Group {
 	private final Layer topLayer = new Layer();
 	/** The current mouse drag state associated with this bundle's visual. */
 	private final MouseState mouseState;
-	/** The scale to be applied for zooming the content of the layers. */
-	private Scale scale = new Scale();
 
 	/** Constructor. */
 	public DiagramLayers(DiagramViewer viewer) {
-		this.viewer = viewer;
 		ObservableList<Node> c = getChildren();
 		c.add(bottomLayer);
 		c.add(contentLayer);
@@ -144,25 +138,6 @@ public final class DiagramLayers extends Group {
 		topLayer.clear();
 	}
 
-	/** Registers the scroll listener and the zoom scale transformation. */
-	private void registerScrollListenerAndZoomScale(Node node) {
-		node.setOnScroll(viewer.getScrollingHandler());
-		node.getTransforms().add(scale);
-	}
-
-	/** Unregisters the scroll listener and the zoom scale transformation. */
-	private void unregisterScrollListenerAndZoomScale(Node node) {
-		node.setOnScroll(null);
-		node.getTransforms().remove(scale);
-	}
-
-	/** Scales all nodes in each layer. */
-	/* package */ void setScale(double factor) {
-		scale.setX(factor);
-		scale.setY(factor);
-		scale.setZ(factor);
-	}
-
 	/** Interface for layers. */
 	public static interface ILayer {
 		/**
@@ -181,6 +156,7 @@ public final class DiagramLayers extends Group {
 
 	/** Implementation of layers. */
 	private class Layer extends Group implements ILayer {
+
 		/** {@inheritDoc} */
 		@Override
 		public void add(Node node, IMVCBundle bundle) {
@@ -188,7 +164,6 @@ public final class DiagramLayers extends Group {
 				return;
 			}
 			getChildren().add(node);
-			registerScrollListenerAndZoomScale(node);
 			mouseState.registerMouseListeners(node, bundle);
 		}
 
@@ -199,7 +174,6 @@ public final class DiagramLayers extends Group {
 				return;
 			}
 			mouseState.unregisterMouseListeners(node);
-			unregisterScrollListenerAndZoomScale(node);
 			getChildren().remove(node);
 		}
 
@@ -208,7 +182,6 @@ public final class DiagramLayers extends Group {
 		public void clear() {
 			for(Node node : getChildren()) {
 				mouseState.unregisterMouseListeners(node);
-				unregisterScrollListenerAndZoomScale(node);
 			}
 			getChildren().clear();
 		}
diff --git a/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/DiagramViewer.java b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/DiagramViewer.java
index 920bb0f4ee6dd9ac6b607e44c01f04a413b2e2ed..e5afa84170823a396c2f80864eb752366d34eb92 100644
--- a/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/DiagramViewer.java
+++ b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/DiagramViewer.java
@@ -10,10 +10,11 @@
 package org.fortiss.tooling.common.ui.javafx.lwfxef;
 
 import static java.lang.Math.abs;
-import static java.lang.Math.ceil;
 import static java.lang.Math.max;
 import static java.lang.Math.min;
 import static java.util.Objects.requireNonNull;
+import static javafx.geometry.Orientation.HORIZONTAL;
+import static javafx.geometry.Orientation.VERTICAL;
 import static javafx.geometry.VPos.TOP;
 import static javafx.scene.layout.GridPane.setHgrow;
 import static javafx.scene.layout.GridPane.setVgrow;
@@ -49,6 +50,7 @@ import org.fortiss.tooling.common.ui.javafx.lwfxef.mvc.MVCBundleTag;
 import org.fortiss.tooling.common.ui.javafx.lwfxef.visual.IVisualFactory;
 
 import javafx.event.EventHandler;
+import javafx.geometry.BoundingBox;
 import javafx.geometry.Bounds;
 import javafx.geometry.Orientation;
 import javafx.geometry.Point2D;
@@ -61,6 +63,8 @@ import javafx.scene.control.ScrollBar;
 import javafx.scene.image.Image;
 import javafx.scene.image.ImageView;
 import javafx.scene.input.KeyEvent;
+import javafx.scene.input.MouseButton;
+import javafx.scene.input.MouseEvent;
 import javafx.scene.input.ScrollEvent;
 import javafx.scene.layout.GridPane;
 import javafx.scene.layout.Pane;
@@ -69,6 +73,9 @@ import javafx.scene.paint.Paint;
 import javafx.scene.shape.Line;
 import javafx.scene.shape.Rectangle;
 import javafx.scene.text.Text;
+import javafx.scene.transform.Affine;
+import javafx.scene.transform.Transform;
+import javafx.scene.transform.Translate;
 
 /**
  * This class represents the diagram viewer node, which manages the grid
@@ -108,6 +115,8 @@ public class DiagramViewer {
 	private final Line linkLineFeedback = new Line();
 	/** The current context menu. */
 	private ContextMenu contextMenu = null;
+	/** All scaling and translation of the diagram content is done through this transform. */
+	private Affine transform = new Affine();
 
 	/** A debug label. */
 	private Label debugLabel = new Label("");
@@ -138,53 +147,42 @@ public class DiagramViewer {
 		// viewer pane
 		viewerPane = new Pane();
 		layers.boundsInLocalProperty().addListener((obs, ov, nv) -> {
-			updateHorizontalScrollbar();
-			updateVerticalScrollbar();
+			updateScrollBars();
 		});
+		layers.getTransforms().add(transform);
+
 		viewerPane.getChildren().add(layers);
 		viewerPane.widthProperty().addListener(evt -> {
 			updateGridCanvasSize();
-			updateHorizontalScrollbar();
+			updateScrollBars();
 		});
 		viewerPane.heightProperty().addListener(evt -> {
 			updateGridCanvasSize();
-			updateVerticalScrollbar();
+			updateScrollBars();
 		});
 		// focus event handling
 		viewerPane.focusedProperty().addListener((obs, ov, nv) -> {
 			viewerManager.handleFocus(nv, getLayers());
 		});
 
+		viewerPane.addEventFilter(MouseEvent.MOUSE_CLICKED, evt -> {
+			if(evt.isAltDown()) {
+				Point2D p = layers.sceneToLocal(evt.getSceneX(), evt.getSceneY());
+				scrollToCenter(new DiagramCoordinate(p.getX(), p.getY()));
+			}
+		});
+
 		// grid pane for viewer and scroll bars
 		scrolledPane = new GridPane();
 		scrolledPane.add(viewerPane, 1, 1);
 		setHgrow(viewerPane, ALWAYS);
 		setVgrow(viewerPane, ALWAYS);
 
-		// vertical scrollbar
-		verticalScrollbar = new ScrollBar();
-		verticalScrollbar.setOrientation(Orientation.VERTICAL);
+		verticalScrollbar = createContentScrollBar(VERTICAL);
 		scrolledPane.add(verticalScrollbar, 2, 1);
-		verticalScrollbar.valueProperty().addListener((obs, ov, nv) -> {
-			hideContextMenu();
-			double zf = features.getCurrentZoomFactor();
-			double vszf = features.getVerticalSpacing() * zf;
-			double deltaY = nv.doubleValue() * vszf;
-			layers.setTranslateY(-deltaY);
-			viewerManager.gridCanvasVisual.setScrollY(nv.doubleValue());
-		});
 
-		// horizontal scrollbar
-		horizontalScrollbar = new ScrollBar();
+		horizontalScrollbar = createContentScrollBar(HORIZONTAL);
 		scrolledPane.add(horizontalScrollbar, 1, 2);
-		horizontalScrollbar.valueProperty().addListener((obs, ov, nv) -> {
-			hideContextMenu();
-			double zf = features.getCurrentZoomFactor();
-			double hszf = features.getHorizontalSpacing() * zf;
-			double deltaX = nv.doubleValue() * hszf;
-			layers.setTranslateX(-deltaX);
-			viewerManager.gridCanvasVisual.setScrollX(nv.doubleValue());
-		});
 
 		viewerPane.getChildren().add(debugLabel);
 		debugLabel.setLayoutX(20);
@@ -213,6 +211,31 @@ public class DiagramViewer {
 
 		// update the viewer content
 		viewerManager.updateContentObjects();
+
+		// add event filters for scrolling and moving viewerPane
+		viewerPane.addEventFilter(ScrollEvent.SCROLL, getScrollingFilter());
+		ContentDragController contentDragController = new ContentDragController();
+		contentDragController.registerEventFilters(viewerPane);
+	}
+
+	/** Constructs a scrollbar controlling the content */
+	private ScrollBar createContentScrollBar(Orientation orientation) {
+		ScrollBar scrollbar = new ScrollBar();
+		scrollbar.setOrientation(orientation);
+		scrollbar.valueProperty().addListener((obs, ov, nv) -> {
+			hideContextMenu();
+			Point2D diagramTranslation = layers.parentToLocal(0, 0);
+			switch(orientation) {
+				case HORIZONTAL:
+					transform.appendTranslation(-nv.doubleValue() + diagramTranslation.getX(), 0);
+					break;
+				case VERTICAL:
+					transform.appendTranslation(0, -nv.doubleValue() + diagramTranslation.getY());
+					break;
+			}
+			viewerManager.gridCanvasVisual.updateNodes(layers);
+		});
+		return scrollbar;
 	}
 
 	/** Returns viewerPane. */
@@ -242,19 +265,7 @@ public class DiagramViewer {
 		contextMenu = new ContextMenu();
 		contextMenu.getItems().addAll(items);
 		contextMenu.setAutoHide(true);
-		Point2D locationOnNode = diagramLocation.getLocal(node);
-		Point2D screenLocation;
-		double zf = features.getCurrentZoomFactor();
-		if(viewerManager.gridCanvasVisual.isGridCanvas(node)) {
-			double hsd = features.getHorizontalSpacing() * getHorizontalScrollBarValue() * zf;
-			double vsd = features.getVerticalSpacing() * getVerticalScrollBarValue() * zf;
-			Point2D correction = locationOnNode.multiply(zf).subtract(hsd, vsd);
-			screenLocation = node.localToScreen(correction);
-		} else {
-			Bounds b = node.getBoundsInLocal();
-			screenLocation = node.localToScreen(locationOnNode.getX() + b.getMinX(),
-					locationOnNode.getY() + b.getMinY());
-		}
+		Point2D screenLocation = layers.localToScreen(diagramLocation);
 		contextMenu.show(node, screenLocation.getX(), screenLocation.getY());
 	}
 
@@ -278,45 +289,44 @@ public class DiagramViewer {
 		return exporter.export();
 	}
 
-	/** Updates the vertical scrollbar. */
-	private void updateVerticalScrollbar() {
-		Bounds vpb = viewerPane.getLayoutBounds();
-		Bounds cb = layers.getLayoutBounds();
-		double vs = features.getVerticalSpacing();
-		double contentHeight = max(ceil(cb.getMaxY() / vs), 2);
-		double viewportHeight = ceil(vpb.getHeight() / vs);
-		double zf = features.getCurrentZoomFactor();
-		// this allows scrolling the content nearly out of sight (two grid spaces)
-		double max = contentHeight - 2 * zf;
-		verticalScrollbar.setMax(max);
-		double visi = max * (viewportHeight / (viewportHeight + contentHeight - 2));
-		verticalScrollbar.setVisibleAmount(visi);
-		verticalScrollbar.setUnitIncrement(1);
-		verticalScrollbar.setBlockIncrement(5);
-		if(verticalScrollbar.getValue() > max) {
-			verticalScrollbar.setValue(max);
-		}
-	}
+	/** Updates the scrollbars. */
+	private void updateScrollBars() {
+		// The size of the content (in diagram coordinates)
+		Bounds contentBounds = layers.getBoundsInLocal();
+		double contentHeight = contentBounds.getHeight();
+		double contentWidth = contentBounds.getWidth();
+		// The size of the visible content (in diagram coordinates)
+		Bounds visibleBounds = layers.parentToLocal(viewerPane.getLayoutBounds());
+		double visibleWidth = visibleBounds.getWidth();
+		double visibleHeight = visibleBounds.getHeight();
+
+		verticalScrollbar.setMin(0);
+		horizontalScrollbar.setMin(0);
+		verticalScrollbar.setMax(contentHeight);
+		horizontalScrollbar.setMax(contentWidth);
+
+		Point2D diagramTopLeft = layers.parentToLocal(0, 0);
+		verticalScrollbar.setValue(diagramTopLeft.getY());
+		horizontalScrollbar.setValue(diagramTopLeft.getX());
+
+		// Including blank space to the bottom, the height of the content
+		// is (contentHeight + visibleHeight), of which visibleHeight is
+		// visible. Since the vertical scrollbar goes from 0 to contentHeight,
+		// this means we need:
+		// contentHeight / verticalVisibleAmount
+		// == (contentHeight + visibleHeight) / visibleHeight
+		// Hence:
+		double verticalVisibleAmount =
+				contentHeight * visibleHeight / (contentHeight + visibleHeight);
+		double horizontalVisibleAmount =
+				contentWidth * visibleWidth / (contentWidth + visibleWidth);
+		verticalScrollbar.setVisibleAmount(verticalVisibleAmount);
+		horizontalScrollbar.setVisibleAmount(horizontalVisibleAmount);
 
-	/** Updates the horizontal scrollbar. */
-	private void updateHorizontalScrollbar() {
-		Bounds vpb = viewerPane.getLayoutBounds();
-		Bounds cb = layers.getLayoutBounds();
-		double hs = features.getHorizontalSpacing();
-		double contentWidth = max(ceil(cb.getMaxX() / hs), 2);
-		double viewportWidth = ceil(vpb.getWidth() / hs);
-		double zf = features.getCurrentZoomFactor();
-
-		// this allows scrolling the content nearly out of sight (two grid spaces)
-		double max = contentWidth - 2 * zf;
-		horizontalScrollbar.setMax(max);
-		double visi = max * (viewportWidth / (viewportWidth + contentWidth - 2));
-		horizontalScrollbar.setVisibleAmount(visi);
+		verticalScrollbar.setUnitIncrement(1);
 		horizontalScrollbar.setUnitIncrement(1);
-		horizontalScrollbar.setBlockIncrement(5);
-		if(horizontalScrollbar.getValue() > max) {
-			horizontalScrollbar.setValue(max);
-		}
+		verticalScrollbar.setBlockIncrement(verticalScrollbar.getVisibleAmount());
+		horizontalScrollbar.setBlockIncrement(horizontalScrollbar.getVisibleAmount());
 	}
 
 	/** Update the size of the grid canvas. */
@@ -429,33 +439,71 @@ public class DiagramViewer {
 		return viewerManager.modelFactory;
 	}
 
+	/** Clamp content so that it can't be moved beyond the borders. */
+	private void enforceBounds() {
+		// The size of the content (in diagram coordinates)
+		Bounds contentBounds = layers.getBoundsInLocal();
+		// Crop at the top left corner
+		BoundingBox enforceContentBounds =
+				new BoundingBox(0, 0, contentBounds.getMaxX(), contentBounds.getMaxY());
+		Bounds contentInParent = layers.localToParent(enforceContentBounds);
+
+		double leftBoundCorrection = max(contentInParent.getMinX(), 0);
+		double topBoundCorrection = max(contentInParent.getMinY(), 0);
+		double rightBoundCorrection = min(contentInParent.getMaxX(), 0);
+		double bottomBoundCorrection = min(contentInParent.getMaxY(), 0);
+		Point2D clampedInParent = new Point2D(leftBoundCorrection + rightBoundCorrection,
+				topBoundCorrection + bottomBoundCorrection);
+		Point2D correctionLocal = layers.parentToLocal(clampedInParent);
+		Point2D originLocal = layers.parentToLocal(0, 0);
+		transform.appendTranslation(originLocal.getX() - correctionLocal.getX(),
+				originLocal.getY() - correctionLocal.getY());
+	}
+
+	/** Apply a transformation to the content. */
+	void appendContentTransform(Transform t) {
+		transform.append(t);
+		enforceBounds();
+		updateScrollBars();
+		viewerManager.gridCanvasVisual.updateNodes(layers);
+	}
+
 	/** Returns the scroll event handler. */
-	/* package */ EventHandler<? super ScrollEvent> getScrollingHandler() {
+	private EventHandler<? super ScrollEvent> getScrollingFilter() {
 		return evt -> {
+			evt.consume();
 			if(evt.isControlDown()) {
 				if(features.getZoomFactorIndex() == -1) {
-					evt.consume();
 					return;
 				}
 				if(evt.getDeltaY() > 0) {
-					features.zoomIn();
-					evt.consume();
+					Point2D pivot = layers.sceneToLocal(evt.getSceneX(), evt.getSceneY());
+					features.zoomIn(pivot.getX(), pivot.getY());
 				} else if(evt.getDeltaY() < 0) {
-					features.zoomOut();
-					evt.consume();
+					Point2D pivot = layers.sceneToLocal(evt.getSceneX(), evt.getSceneY());
+					features.zoomOut(pivot.getX(), pivot.getY());
 				}
 			} else if(evt.isShiftDown()) {
+				Point2D p0 = layers.screenToLocal(0, 0);
+				Point2D p1 = layers.screenToLocal(1, 1);
 				double hmax = horizontalScrollbar.getMax();
 				double hmin = horizontalScrollbar.getMin();
 				double nval = horizontalScrollbar.getValue() -
-						evt.getDeltaY() / features.getHorizontalSpacing();
+						evt.getDeltaY() * abs(p1.getX() - p0.getX());
 				horizontalScrollbar.setValue(max(hmin, min(hmax, nval)));
 			} else {
+				Point2D p0 = layers.screenToLocal(0, 0);
+				Point2D p1 = layers.screenToLocal(1, 1);
 				double vmax = verticalScrollbar.getMax();
 				double vmin = verticalScrollbar.getMin();
-				double nval = verticalScrollbar.getValue() -
-						evt.getDeltaY() / features.getVerticalSpacing();
-				verticalScrollbar.setValue(max(vmin, min(vmax, nval)));
+				double vval =
+						verticalScrollbar.getValue() - evt.getDeltaY() * abs(p1.getY() - p0.getY());
+				double hmax = horizontalScrollbar.getMax();
+				double hmin = horizontalScrollbar.getMin();
+				double hval = horizontalScrollbar.getValue() -
+						evt.getDeltaX() * abs(p1.getX() - p0.getX());
+				verticalScrollbar.setValue(max(vmin, min(vmax, vval)));
+				horizontalScrollbar.setValue(max(hmin, min(hmax, hval)));
 			}
 		};
 	}
@@ -486,11 +534,7 @@ public class DiagramViewer {
 	 * using the underlying model.
 	 */
 	public void updateFromModel() {
-		double vscroll = verticalScrollbar.getValue();
-		double hscroll = horizontalScrollbar.getValue();
 		viewerManager.updateContentObjects();
-		verticalScrollbar.setValue(vscroll);
-		horizontalScrollbar.setValue(hscroll);
 	}
 
 	/**
@@ -533,9 +577,7 @@ public class DiagramViewer {
 		setSingleSelectedMVCBundle(null);
 	}
 
-	/**
-	 * Converts the given absolute diagram coordinates to node-local coordinates.
-	 */
+	/** Converts the given absolute diagram coordinates to node-local coordinates. */
 	/* package */ DiagramCoordinate convertEventToDiagramCoordinates(Point2D absoluteLocation,
 			Node node) {
 		return convertEventToDiagramCoordinates(absoluteLocation.getX(), absoluteLocation.getY(),
@@ -545,37 +587,25 @@ public class DiagramViewer {
 	/** Converts the given mouse event coordinates to node-local coordinates. */
 	/* package */ DiagramCoordinate convertEventToDiagramCoordinates(double evtX, double evtY,
 			Node node) {
-		if(node == viewerManager.gridCanvasVisual.getGridCanvas()) {
-			return convertGridCanvasCoordinate(evtX, evtY);
-		}
-		return new DiagramCoordinate(evtX, evtY);
+		Point2D scene = node.localToScene(evtX, evtY);
+		Point2D diagram = layers.sceneToLocal(scene);
+		return new DiagramCoordinate(diagram.getX(), diagram.getY());
 	}
 
-	/** Converts the grid canvas coordinate to the diagram coordinate space. */
-	public DiagramCoordinate convertGridCanvasCoordinate(double xOnCanvas, double yOnCanvas) {
-		double zf = features.getCurrentZoomFactor();
-		double x = xOnCanvas / zf + features.getHorizontalSpacing() * getHorizontalScrollBarValue();
-		double y = yOnCanvas / zf + features.getVerticalSpacing() * getVerticalScrollBarValue();
-		return new DiagramCoordinate(x, y);
+	/** By default, zoom fixes the top left corner */
+	/* package */ DiagramCoordinate getDefaultZoomPivot() {
+		Point2D pivot = layers.parentToLocal(0, 0);
+		return new DiagramCoordinate(pivot.getX(), pivot.getY());
 	}
 
 	/** Scrolls the diagram to center at the given coordinates. */
 	public void scrollToCenter(DiagramCoordinate center) {
-		double zf = features.getCurrentZoomFactor();
-		Bounds bounds = viewerPane.getLayoutBounds();
-		double upperLeftX = center.getX() - bounds.getMinX() / zf;
-		if(upperLeftX < 0) {
-			upperLeftX = 0;
-		}
-		double newHSBValue = upperLeftX / features.getHorizontalSpacing();
-		double upperLeftY = center.getY() - bounds.getMinY() / zf;
-		if(upperLeftY < 0) {
-			upperLeftY = 0;
-		}
-		double newVSBValue = upperLeftY - features.getVerticalSpacing();
-
-		horizontalScrollbar.setValue(newHSBValue);
-		verticalScrollbar.setValue(newVSBValue);
+		Point2D centerScene =
+				viewerPane.localToScene(viewerPane.getWidth() / 2, viewerPane.getHeight() / 2);
+		Point2D currentCenter = layers.sceneToLocal(centerScene);
+		double dx = currentCenter.getX() - center.getX();
+		double dy = currentCenter.getY() - center.getY();
+		appendContentTransform(new Translate(dx, dy));
 	}
 
 	/** Updates the feedback for selection rectangle. */
@@ -622,10 +652,7 @@ public class DiagramViewer {
 		}
 	}
 
-	/**
-	 * Starts the feedback of the link creation line and shows the possible target
-	 * feedbacks.
-	 */
+	/** Starts the feedback of the link creation line and shows the possible target feedbacks. */
 	public void startNewLinkLineFeedback(IMVCBundle linkStartBundle, Node node,
 			Point2D locationInDiagram) {
 		if(node == null || locationInDiagram == null) {
@@ -662,7 +689,7 @@ public class DiagramViewer {
 		clearLinkTargetFeedback();
 	}
 
-	/** Clears the link target feedback fronm all possible link targets. */
+	/** Clears the link target feedback from all possible link targets. */
 	private void clearLinkTargetFeedback() {
 		for(IMVCBundle possibleTarget : viewerManager.possibleLinkTargets) {
 			possibleTarget.removeTag(LINK_TARGET_ALLOWED_TAG);
@@ -857,4 +884,64 @@ public class DiagramViewer {
 			return null;
 		}
 	}
+
+	/** Mouse controller for moving the content using control-drag. */
+	private final class ContentDragController {
+		/** Last point within a control-drag gesture. */
+		private Point2D dragPoint;
+		/**
+		 * Has the mouse button been released within a control-drag gesture? If so, the next click
+		 * event should be filtered out.
+		 */
+		private boolean buttonReleased;
+
+		/** Register the event filters of this controller. */
+		/* package */ void registerEventFilters(Node node) {
+			node.addEventFilter(MouseEvent.MOUSE_DRAGGED, dragDetected);
+			node.addEventFilter(MouseEvent.ANY, dragged);
+			node.addEventFilter(MouseEvent.MOUSE_RELEASED, mouseReleased);
+			node.addEventFilter(MouseEvent.MOUSE_CLICKED, mouseClicked);
+		}
+
+		/** Control-drag gesture starts when drag is detected. */
+		private final EventHandler<MouseEvent> dragDetected = evt -> {
+			if(dragPoint == null && evt.isControlDown() && evt.getButton() == MouseButton.PRIMARY) {
+				dragPoint = getLayers().sceneToLocal(evt.getSceneX(), evt.getSceneY());
+				buttonReleased = false;
+				evt.consume();
+			}
+		};
+
+		/** While dragging, the content is translated appropriately. */
+		private final EventHandler<MouseEvent> dragged = evt -> {
+			if(dragPoint != null) {
+				evt.consume();
+
+				Point2D evtLayer = getLayers().sceneToLocal(evt.getSceneX(), evt.getSceneY());
+
+				Point2D move = evtLayer.subtract(dragPoint);
+				double dx = move.getX();
+				double dy = move.getY();
+				appendContentTransform(new Translate(dx, dy));
+				dragPoint = getLayers().sceneToLocal(evt.getSceneX(), evt.getSceneY());
+			}
+		};
+
+		/** If the mouse button is released, the gesture should be stopped. */
+		private final EventHandler<MouseEvent> mouseReleased = evt -> {
+			if(dragPoint != null && evt.getButton() == MouseButton.PRIMARY) {
+				dragPoint = null;
+				buttonReleased = true;
+				evt.consume();
+			}
+		};
+
+		/** The first click event after the released event should be filtered. */
+		private final EventHandler<MouseEvent> mouseClicked = evt -> {
+			if(buttonReleased && evt.getButton() == MouseButton.PRIMARY) {
+				evt.consume();
+			}
+			buttonReleased = false;
+		};
+	}
 }
diff --git a/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/DiagramViewerFeatures.java b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/DiagramViewerFeatures.java
index 397c9600193df18e865f1ff7c829df577c56d383..3dd78d9c117fc156924a151c6f8d770c53c103bc 100644
--- a/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/DiagramViewerFeatures.java
+++ b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/DiagramViewerFeatures.java
@@ -14,9 +14,12 @@ import static java.lang.Math.max;
 import static java.util.Objects.requireNonNull;
 import static javafx.scene.paint.Color.LIGHTGRAY;
 
+import java.util.Objects;
+
 import javafx.geometry.Dimension2D;
 import javafx.geometry.Point2D;
 import javafx.scene.paint.Color;
+import javafx.scene.transform.Scale;
 
 /**
  * This class handles the state of all features of the {@link DiagramViewer}, which can be toggled
@@ -25,10 +28,14 @@ import javafx.scene.paint.Color;
 public final class DiagramViewerFeatures {
 	/** The diagram viewer. */
 	private final DiagramViewer viewer;
-	/** The zoom factors. */
-	private double[] zoomFactors = new double[] {0.125, 0.25, 0.5, 0.75, 1, 1.5, 2, 4, 8};
+	/**
+	 * The zoom factors. The values are chosen to have a roughly constant factor between them, to
+	 * make interactive scrolling less jumpy.
+	 */
+	private double[] zoomFactors = new double[] {0.125, 0.25, 0.3, 0.4, 0.5, 0.65, 0.80, 1, 1.25,
+			1.5, 2.0, 2.5, 3.5, 4.5, 6};
 	/** The current zoom factor array index. */
-	private int zoomFactorIndex = 4; // Index of 1
+	private int zoomFactorIndex = 7; // Index of 1
 	/** Flag indicating whether link highlighting is enabled. */
 	private boolean linkHighlightingEnabled = false;
 	/** Flag if interaction area shading is active. */
@@ -81,23 +88,40 @@ public final class DiagramViewerFeatures {
 
 	/** Sets the zoom factor index. */
 	public void setZoomFactorIndex(int zoomFactorIndex) {
+		Objects.checkIndex(zoomFactorIndex, zoomFactors.length);
+		double factor = zoomFactors[zoomFactorIndex] / zoomFactors[this.zoomFactorIndex];
 		this.zoomFactorIndex = zoomFactorIndex;
-		viewer.updateAllVisuals();
+		DiagramCoordinate pivot = viewer.getDefaultZoomPivot();
+		viewer.appendContentTransform(new Scale(factor, factor, pivot.getX(), pivot.getY()));
 	}
 
 	/** Increases zoom factor index by one up to the maximum. */
 	public void zoomIn() {
+		DiagramCoordinate pivot = viewer.getDefaultZoomPivot();
+		zoomIn(pivot.getX(), pivot.getY());
+	}
+
+	/** Increases zoom factor index by one up to the maximum. */
+	public void zoomIn(double pivotX, double pivotY) {
 		if(zoomFactorIndex < zoomFactors.length - 1) {
 			zoomFactorIndex++;
-			viewer.updateAllVisuals();
+			double factor = zoomFactors[zoomFactorIndex] / zoomFactors[zoomFactorIndex - 1];
+			viewer.appendContentTransform(new Scale(factor, factor, pivotX, pivotY));
 		}
 	}
 
 	/** Decreases zoom factor index by one down to the minimum. */
 	public void zoomOut() {
+		DiagramCoordinate pivot = viewer.getDefaultZoomPivot();
+		zoomOut(pivot.getX(), pivot.getY());
+	}
+
+	/** Decreases zoom factor index by one down to the minimum. */
+	public void zoomOut(double pivotX, double pivotY) {
 		if(zoomFactorIndex > 0) {
 			zoomFactorIndex--;
-			viewer.updateAllVisuals();
+			double factor = zoomFactors[zoomFactorIndex] / zoomFactors[zoomFactorIndex + 1];
+			viewer.appendContentTransform(new Scale(factor, factor, pivotX, pivotY));
 		}
 	}
 
diff --git a/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/GridCanvasVisual.java b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/GridCanvasVisual.java
index 734027d56af342cd01ff445ba9347b8dbb6c83c2..e7c83211e0fce2b0d55aac77d0203950286646b9 100644
--- a/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/GridCanvasVisual.java
+++ b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/GridCanvasVisual.java
@@ -9,6 +9,10 @@
  *******************************************************************************/
 package org.fortiss.tooling.common.ui.javafx.lwfxef;
 
+import static java.lang.Double.doubleToLongBits;
+
+import java.util.Objects;
+
 import org.fortiss.tooling.common.ui.javafx.lwfxef.DiagramViewerFeatures.IndicatorType;
 import org.fortiss.tooling.common.ui.javafx.lwfxef.mvc.IMVCBundle;
 import org.fortiss.tooling.common.ui.javafx.lwfxef.visual.IVisual;
@@ -19,7 +23,9 @@ import javafx.geometry.Rectangle2D;
 import javafx.scene.Node;
 import javafx.scene.canvas.Canvas;
 import javafx.scene.canvas.GraphicsContext;
+import javafx.scene.layout.Pane;
 import javafx.scene.paint.Color;
+import javafx.scene.shape.Rectangle;
 
 /**
  * {@link IVisual} for the diagram background. Note that the {@link Canvas} used here is always as
@@ -30,27 +36,46 @@ import javafx.scene.paint.Color;
 final class GridCanvasVisual implements IVisual {
 	/** The diagram bundle. */
 	private final DiagramViewer viewer;
-	/** The FX canvas to be drawn on. */
+
+	/** The pane to contain the whole grid. */
+	private final Pane pane;
+
+	/**
+	 * The FX canvas to draw the grid. The canvas is one grid column/row larger than the pane. It
+	 * gets moved on the pane when the position of the grid changes. In this way, the canvas only
+	 * needs to be redrawn when scale or grid configuration change. Moving the final image is much
+	 * faster than redrawing the canvas.
+	 */
 	private final Canvas gridCanvas;
-	/** The displacement caused by scroll bars. */
-	private DiagramCoordinate displacement = new DiagramCoordinate(0, 0);
+
+	/**
+	 * A rectangle for the left outer border. The border is not painted with the canvas to avoid
+	 * having to redraw the canvas when changing location.
+	 */
+	private final Rectangle leftOuterBorder;
+
+	/** A rectangle for the top outer border. */
+	private final Rectangle topOuterBorder;
+
+	/** Width of the whole shown grid. */
+	double width;
+
+	/** Height of the whole shown grid. */
+	double height;
 
 	/** Constructor. */
 	public GridCanvasVisual(DiagramViewer viewer) {
 		this.viewer = viewer;
-		// the background grid canvas
-		this.gridCanvas = new Canvas(100, 100);
-		gridCanvas.widthProperty().addListener(evt -> {
-			paintGrid();
-		});
-		gridCanvas.heightProperty().addListener(evt -> {
-			paintGrid();
-		});
+		this.pane = new Pane();
+		this.gridCanvas = new Canvas();
+		this.leftOuterBorder = new Rectangle();
+		this.topOuterBorder = new Rectangle();
+		this.pane.getChildren().addAll(gridCanvas, leftOuterBorder, topOuterBorder);
 	}
 
 	/** Returns gridCanvas. */
-	/* package */Canvas getGridCanvas() {
-		return gridCanvas;
+	/* package */Node getGridNode() {
+		return pane;
 	}
 
 	/** {@inheritDoc} */
@@ -74,27 +99,25 @@ final class GridCanvasVisual implements IVisual {
 	/** {@inheritDoc} */
 	@Override
 	public void updateNodes(DiagramLayers layers) {
-		if(gridCanvas.getParent() == null) {
-			// grid canvas is added below the diagram layers
-			viewer.getViewerPane().getChildren().add(0, gridCanvas);
-			viewer.getLayers().getMouseState().registerMouseListeners(gridCanvas, getMVCBundle());
-			gridCanvas.setOnScroll(viewer.getScrollingHandler());
+		if(pane.getParent() == null) {
+			// grid is added below the diagram layers
+			viewer.getViewerPane().getChildren().add(0, pane);
+			viewer.getLayers().getMouseState().registerMouseListeners(pane, getMVCBundle());
 		}
-		paintGrid();
+		updateContent();
 	}
 
 	/** {@inheritDoc} */
 	@Override
 	public void removeAllVisuals(DiagramLayers layers) {
-		viewer.getLayers().getMouseState().unregisterMouseListeners(gridCanvas);
-		viewer.getViewerPane().getChildren().remove(gridCanvas);
-		gridCanvas.setOnScroll(null);
+		viewer.getLayers().getMouseState().unregisterMouseListeners(pane);
+		viewer.getViewerPane().getChildren().remove(pane);
 	}
 
 	/** {@inheritDoc} */
 	@Override
 	public Rectangle2D getModelBounds() {
-		Bounds b = gridCanvas.getBoundsInParent();
+		Bounds b = pane.getBoundsInParent();
 		return new Rectangle2D(b.getMinX(), b.getMinY(), b.getWidth(), b.getHeight());
 	}
 
@@ -115,49 +138,189 @@ final class GridCanvasVisual implements IVisual {
 	/** {@inheritDoc} */
 	@Override
 	public void requestFocus() {
-		gridCanvas.requestFocus();
+		pane.requestFocus();
 	}
 
-	/** Paints the grid. */
-	public void paintGrid() {
+	/** Returns the zoom factor from current content transformation. */
+	private double getZoomFactor() {
+		Point2D unitVector = new Point2D(1, 1);
+		Point2D transformedVector =
+				viewer.getLayers().getLocalToParentTransform().deltaTransform(unitVector);
+		return transformedVector.getX();
+	}
+
+	/** Structure to represent the current location of the grid. */
+	private static class GridOffset {
+		/** Grid x coordinate of upper left corner. */
+		int gridX;
+
+		/** Grid y coordinate of upper left corner. */
+		int gridY;
+
+		/** Insets in the grid square between (gridX, gridY) and (gridX + 1, gridY +1). */
+		Point2D insets;
+	}
+
+	/** Compute the grid offset current content transformation. */
+	private GridOffset getGridOffset() {
 		DiagramViewerFeatures features = viewer.getFeatures();
-		double zf = features.getCurrentZoomFactor();
-		double width = gridCanvas.getWidth();
-		double height = gridCanvas.getHeight();
-		GraphicsContext gc = gridCanvas.getGraphicsContext2D();
-		// background
-		Color backgroundColor = features.getBackgroundColor();
-		gc.setFill(backgroundColor);
-		gc.setStroke(backgroundColor);
-		gc.fillRect(0, 0, width, height);
-		// outer border and indicators
-		Color indicatorColor = features.getIndicatorColor();
-		gc.setStroke(indicatorColor);
-		gc.setFill(indicatorColor);
-		double hSpacing = features.getHorizontalSpacing();
-		double vSpacing = features.getVerticalSpacing();
-		double dx = displacement.getX() % 1.0;
-		double dy = displacement.getY() % 1.0;
-		double zfhs = zf * hSpacing;
-		double zfvs = zf * vSpacing;
-		if(features.isDrawOuterBorder()) {
-			if(displacement.getX() < 1.0) {
-				gc.fillRect(0, 0, zfhs * (1.0 - dx), height);
+		Point2D p0 = viewer.getLayers().sceneToLocal(pane.localToScene(0, 0));
+
+		GridOffset gridOffset = new GridOffset();
+		gridOffset.gridX = (int)(p0.getX() / features.getHorizontalSpacing());
+		gridOffset.gridY = (int)(p0.getY() / features.getVerticalSpacing());
+		gridOffset.insets = new Point2D((p0.getX() / features.getHorizontalSpacing()) % 1.0,
+				(p0.getY() / features.getVerticalSpacing()) % 1.0);
+		return gridOffset;
+	}
+
+	/**
+	 * Structure representing the parameters that go into the drawing of the grid canvas. It is
+	 * useful to determine if the canvas needs repainting.
+	 */
+	private static class GridParameters {
+
+		/** Horizontal grid spacing. */
+		private double hSpacing;
+
+		/** Vertical grid spacing. */
+		private double vSpacing;
+
+		/** Zoom factor. */
+		private double zoomFactor;
+
+		/** Grid indicator size. */
+		private double indicatorSize;
+
+		/** Grid indicator type. */
+		private IndicatorType indicatorType;
+
+		/** Grid background color. */
+		private Color backgroundColor;
+
+		/** Grid indicator color. */
+		private Color indicatorColor;
+
+		/** {@inheritDoc} */
+		@Override
+		public int hashCode() {
+			return Objects.hash(backgroundColor, hSpacing, indicatorColor, indicatorSize,
+					indicatorType, vSpacing, zoomFactor);
+		}
+
+		/**
+		 * Auto-generated (by Eclipse) deep equality method.
+		 * 
+		 * {@inheritDoc}
+		 */
+		@Override
+		public boolean equals(Object obj) {
+			if(this == obj) {
+				return true;
+			}
+			if(obj == null) {
+				return false;
 			}
-			if(displacement.getY() < 1.0) {
-				gc.fillRect(0, 0, width, zfvs * (1.0 - dy));
+			if(getClass() != obj.getClass()) {
+				return false;
 			}
+			GridParameters other = (GridParameters)obj;
+
+			// Compare all member variables individually for equality.
+			// The equality of doubles is tested using {@code doubleToLongBits},
+			// as explained in {@link java.lang.Double#equals(Object)}.
+			return Objects.equals(backgroundColor, other.backgroundColor) &&
+					doubleToLongBits(hSpacing) == doubleToLongBits(other.hSpacing) &&
+					Objects.equals(indicatorColor, other.indicatorColor) &&
+					doubleToLongBits(indicatorSize) == doubleToLongBits(other.indicatorSize) &&
+					indicatorType == other.indicatorType &&
+					doubleToLongBits(vSpacing) == doubleToLongBits(other.vSpacing) &&
+					doubleToLongBits(zoomFactor) == doubleToLongBits(other.zoomFactor);
 		}
+	}
+
+	/** Update the pane node. */
+	private void updateContent() {
+		DiagramViewerFeatures features = viewer.getFeatures();
+
+		GridParameters parameters = new GridParameters();
+		parameters.hSpacing = features.getHorizontalSpacing();
+		parameters.vSpacing = features.getVerticalSpacing();
+		parameters.zoomFactor = getZoomFactor();
+		parameters.indicatorType = features.getIndicatorType();
+		parameters.backgroundColor = features.getBackgroundColor();
+		parameters.indicatorColor = features.getIndicatorColor();
+		parameters.indicatorSize = features.getIndicatorSize();
+
+		paintCanvas(parameters);
+
+		GridOffset gridOffset = getGridOffset();
+		double zfhs = parameters.zoomFactor * parameters.hSpacing;
+		double zfvs = parameters.zoomFactor * parameters.vSpacing;
+		gridCanvas.relocate(-gridOffset.insets.getX() * zfhs, -gridOffset.insets.getY() * zfvs);
+
+		pane.getChildren().removeAll(leftOuterBorder, topOuterBorder);
+		if(features.isDrawOuterBorder()) {
+			pane.getChildren().addAll(leftOuterBorder, topOuterBorder);
+			leftOuterBorder.relocate(-(gridOffset.gridX + gridOffset.insets.getX()) * zfhs, 0);
+			leftOuterBorder.setWidth(zfhs);
+			leftOuterBorder.setHeight(height);
+			leftOuterBorder.setStrokeWidth(0.0);
+			leftOuterBorder.setFill(features.getIndicatorColor());
+
+			topOuterBorder.relocate(0, -(gridOffset.gridY + gridOffset.insets.getY()) * zfhs);
+			topOuterBorder.setWidth(width);
+			topOuterBorder.setHeight(zfvs);
+			topOuterBorder.setStrokeWidth(0.0);
+			topOuterBorder.setFill(features.getIndicatorColor());
+		}
+	}
+
+	/** The parameters of what is currently drawn on the gridCanvas. */
+	private GridParameters currentGridCanvasParameters = null;
+
+	/** Draw gridCanvas with the given parameters. */
+	private void paintCanvas(GridParameters parameters) {
+		double targetCanvasWidth = width + parameters.zoomFactor * parameters.hSpacing;
+		double targetCanvasHeight = height + parameters.zoomFactor * parameters.vSpacing;
+
+		boolean resized = false;
+		resized |= gridCanvas.getWidth() != targetCanvasWidth;
+		resized |= gridCanvas.getHeight() != targetCanvasHeight;
+
+		if(resized) {
+			gridCanvas.setWidth(targetCanvasWidth);
+			gridCanvas.setHeight(targetCanvasHeight);
+		}
+
+		if(!resized && parameters.equals(currentGridCanvasParameters)) {
+			// grid is already drawn on canvas
+			return;
+		}
+		currentGridCanvasParameters = parameters;
+
+		GraphicsContext gc = gridCanvas.getGraphicsContext2D();
+		// background
+		gc.setFill(parameters.backgroundColor);
+		gc.setStroke(parameters.backgroundColor);
+		gc.fillRect(0, 0, gridCanvas.getWidth(), gridCanvas.getHeight());
+
 		// abort if no indicators should be drawn
-		IndicatorType indicatorType = features.getIndicatorType();
+		IndicatorType indicatorType = parameters.indicatorType;
 		if(indicatorType == IndicatorType.INVISIBLE) {
 			return;
 		}
+
 		// draw grid indicators
-		double isize = viewer.getFeatures().getIndicatorSize() * zf;
-		for(double x = zfhs * (1.0 - dx); x < width; x += zfhs) {
-			for(double y = zfvs * (1.0 - dy); y < height; y += zfvs) {
-				drawIndicator(gc, x, y, isize, indicatorType);
+		gc.setStroke(parameters.indicatorColor);
+		gc.setFill(parameters.indicatorColor);
+		double zfhs = parameters.zoomFactor * parameters.hSpacing;
+		double zfvs = parameters.zoomFactor * parameters.vSpacing;
+		double isize = parameters.indicatorSize * parameters.zoomFactor;
+
+		for(double x = zfhs; x < gridCanvas.getWidth(); x += zfhs) {
+			for(double y = zfvs; y < gridCanvas.getHeight(); y += zfvs) {
+				drawIndicator(gc, Math.floor(x) + 0.5, Math.floor(y) + 0.5, isize, indicatorType);
 			}
 		}
 	}
@@ -199,41 +362,21 @@ final class GridCanvasVisual implements IVisual {
 	/** {@inheritDoc} */
 	@Override
 	public EDragGesture getDragGesture(Node node, DiagramCoordinate diagramLocation) {
-		if(node == gridCanvas) {
+		if(node == pane) {
 			return EDragGesture.SELECTION;
 		}
 		return EDragGesture.NONE;
 	}
 
-	/** Sets horizontal scroll displacement. */
-	public void setScrollX(double canvasDeltaX) {
-		displacement = new DiagramCoordinate(canvasDeltaX, displacement.getY());
-		paintGrid();
-	}
-
-	/** Sets vertical scroll displacement. */
-	public void setScrollY(double canvasDeltaY) {
-		displacement = new DiagramCoordinate(displacement.getX(), canvasDeltaY);
-		paintGrid();
-	}
-
 	/** Sets the width of the grid canvas. */
 	public void setWidth(double w) {
-		gridCanvas.setWidth(w);
+		this.width = w;
+		updateContent();
 	}
 
 	/** Sets the height of the grid canvas. */
 	public void setHeight(double h) {
-		gridCanvas.setHeight(h);
-	}
-
-	/** Relocates the given scene point to local coordinates. */
-	public Point2D sceneToLocal(Point2D locationInScene) {
-		return gridCanvas.sceneToLocal(locationInScene);
-	}
-
-	/** Returns whether the given node is the grid canvas. */
-	public boolean isGridCanvas(Node source) {
-		return source == gridCanvas;
+		this.height = h;
+		updateContent();
 	}
 }
diff --git a/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/MVCBundleManager.java b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/MVCBundleManager.java
index e4892a571fd26eccc5e4e9b2256432721723f542..18667b4ed98da124b7c1bc7a103e95232df9ad49 100644
--- a/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/MVCBundleManager.java
+++ b/org.fortiss.tooling.common.ui/src/org/fortiss/tooling/common/ui/javafx/lwfxef/MVCBundleManager.java
@@ -502,7 +502,6 @@ import org.fortiss.tooling.common.ui.javafx.lwfxef.visual.IVisualFactory;
 
 	/** Updates every visual (and only its appearance, not the structure). */
 	/* package */ void updateAllVisuals() {
-		layers.setScale(viewer.getFeatures().getCurrentZoomFactor());
 		viewer.updateGridCanvasSize();
 		gridCanvasVisual.updateNodes(layers);
 		for(IContentMVCBundle b : contentBundles.values()) {