public static <V, E> Shape getEdgeShapeInGraphSpace(VisualizationServer<V, E> viewer, E e) {

		Layout<V, E> layout = viewer.getGraphLayout();
		Pair<V> pair = layout.getGraph().getEndpoints(e);
		V startVertex = pair.getFirst();
		V endVertex = pair.getSecond();

		Point2D startVertexCenter = getVertexCenterPointInGraphSpace(viewer, startVertex);
		if (startVertexCenter == null) {
			return null;
		}

		Point2D endVertexCenter = getVertexCenterPointInGraphSpace(viewer, endVertex);
		if (endVertexCenter == null) {
			return null;
		}

		boolean isLoop = startVertex.equals(endVertex);
		double startX = (float) startVertexCenter.getX();
		double startY = (float) startVertexCenter.getY();
		double endX = (float) endVertexCenter.getX();
		double endY = (float) endVertexCenter.getY();

		RenderContext<V, E> renderContext = viewer.getRenderContext();
		if (isLoop) {
			//
			// Our edge loops are sized and positioned according to the shared
			// code in the utils class.  We do this so that our hit detection matches our rendering.
			//
			Function<? super V, Shape> vertexShapeTransformer =
				renderContext.getVertexShapeTransformer();
			Shape vertexShape = getVertexShapeForEdge(endVertex, vertexShapeTransformer);
			return createHollowEgdeLoopInGraphSpace(vertexShape, startX, startY);
		}

		// translate the edge from 0,0 to the starting vertex point
		AffineTransform xform = AffineTransform.getTranslateInstance(startX, startY);
		Shape edgeShape = renderContext.getEdgeShapeTransformer().apply(e);

		double deltaX = endX - startX;
		double deltaY = endY - startY;

		// rotate the edge to the angle between the vertices
		double theta = Math.atan2(deltaY, deltaX);
		xform.rotate(theta);

		// stretch the edge to span the distance between the vertices
		double dist = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
		xform.scale(dist, 1.0f);

		// apply the transformations; converting the given shape from model space into graph space
		return xform.createTransformedShape(edgeShape);
	}
Transforms the given edge loop shape to graph space, which includes updating
the size and location of the loop to be relative to the vertex.

@param edgeLoopShape The shape to transform
@param vertexShape The shape of the vertex for which the edge is being created
@param x The x coordinate of the vertex
@param y The y coordinate of the vertex
@return the transformed edge loop shape
Ghidra/Framework/Graph/src/main/java/ghidra/graph/viewer/GraphViewerUtils.java