@Override
	public Shape apply(E e) {
		V start = e.getStart();
		V end = e.getEnd();

		boolean isLoop = start.equals(end);
		if (isLoop) {
			return GraphViewerUtils.createHollowEgdeLoop();
		}

		Point2D p1 = start.getLocation();
		if (p1 == null) {
			logMissingLocation(e, start);
			return null;
		}

		Point2D p2 = end.getLocation();
		if (p2 == null) {
			logMissingLocation(e, end);
			return null;
		}

		List<Point2D> articulations = e.getArticulationPoints();
		final double originX = p1.getX();
		final double originY = p1.getY();

		// TODO pretty sure this is not needed; keeping for a bit as a reminder of how it used to be
		// int offset = getOverlapOffset(e);
		int offset = 0;
		GeneralPath path = new GeneralPath();
		path.moveTo(0, 0);
		for (Point2D pt : articulations) {
			float x = (float) (pt.getX() - originX) + offset;
			float y = (float) (pt.getY() - originY) + offset;
			path.lineTo(x, y);
			path.moveTo(x, y);
		}

		float p2x = (float) (p2.getX() - originX);
		float p2y = (float) (p2.getY() - originY);
		path.lineTo(p2x, p2y);
		path.moveTo(p2x, p2y);
		path.closePath();

		AffineTransform transform = new AffineTransform();
		final double deltaY = p2.getY() - originY;
		final double deltaX = p2.getX() - originX;
		if (deltaX == 0 && deltaY == 0) {
			// this implies the source and destination node are at the same location, which
			// is possible if the user drags it there or during animations
			return transform.createTransformedShape(path);
		}

		double theta = StrictMath.atan2(deltaY, deltaX);
		transform.rotate(theta);
		double scale = StrictMath.sqrt(deltaY * deltaY + deltaX * deltaX);
		transform.scale(scale, 1.0f);

		//
		// TODO
		// The current design and use of this transformer is a bit odd.   We currently have code
		// to create the edge shape here and in the ArticulatedEdgeRenderer.  Ideally, this 
		// class would be the only one that creates the edge shape.  Then, any clients of the
		// edge transformer would have to take the shape and then transform it to the desired 
		// space (the view or graph space).  The transformations could be done using the 
		// GraphViewerUtils.
		//

		try {
			// TODO it is not clear why this is using an inverse transform; why not just create
			// the transform that we want?
			AffineTransform inverse = transform.createInverse();
			Shape transformedShape = inverse.createTransformedShape(path);
			return transformedShape;
		}
		catch (NoninvertibleTransformException e1) {
			Msg.error(this, "Unexpected exception transforming an edge", e1);
		}

		return null;
	}
Get the shape for this edge

@param e the edge
@return the edge shape
Ghidra/Framework/Graph/src/main/java/ghidra/graph/viewer/shape/ArticulatedEdgeTransformer.java