private Map<FcgVertex, Point2D> arrangeNewVertices() {

		/*
		 	Add the new row above (or below) the existing row that is being expanded.  So,
		 	the new graph will appear as so:
		 	
		 		v3.1  v3.2   v3.4  v3.5   v3.6
		 				v2.1      v2.2
		 					 v1.1
		 					 
		 	Where the '3.x' nodes are those being added.  They will be centered in relation to
		 	the existing row.
		 	
		 */

		BowTieLayout bowTie = (BowTieLayout) graphLayout;
		boolean isCondensed = bowTie.isCondensedLayout();
		int widthPadding = isCondensed ? GraphViewerUtils.EXTRA_LAYOUT_COLUMN_SPACING_CONDENSED
				: GraphViewerUtils.EXTRA_LAYOUT_COLUMN_SPACING;

		// More space the further away from center, for aesthetics/readability (as the graph 
		// gets larger, more edges are added, cluttering up the display).
		widthPadding *= expandingLevel.getDistance();
		int heightPadding = calculateHeightPadding(isCondensed);

		FcgLevel parentLevel = expandingLevel.parent();
		List<FcgVertex> parentLevelVertices = newVertexCollection.getVerticesByLevel(parentLevel);
		if (parentLevelVertices.isEmpty()) {
			// this can happen if all the new edges being added already exist in the graph
			// at a different level than the parent
			return Collections.emptyMap();
		}

		Rectangle existingRowBounds = getBounds(parentLevelVertices);
		Msg.trace(this, "existing row bounds " + existingRowBounds);
		double existingY = existingRowBounds.y;
		double existingCenterX = existingRowBounds.x + (existingRowBounds.width / 2);

		// Layout all vertices at the new level, even the hidden ones, so the new ones fit
		// within the overall level.  This allows future expansions to put the new nodes in 
		// the correct spot.
		List<FcgVertex> allLevelVertices = newVertexCollection.getAllVerticesAtNewLevel();
		double newRowWidth = getWidth(allLevelVertices, widthPadding);
		double newRowHeight = getHeight(allLevelVertices);
		double newRowX = existingCenterX - (newRowWidth / 2);

		double newRowY = 0;
		if (newVertexCollection.isIncoming()) {
			newRowY = existingY - newRowHeight - heightPadding;
		}
		else {
			newRowY = existingY + existingRowBounds.height + heightPadding;
		}

		Msg.trace(this, "new row bounds " +
			new Rectangle2D.Double(newRowX, newRowY, newRowWidth, newRowHeight));

		Map<FcgVertex, Point2D> locations = getExistingLocations(allLevelVertices);
		if (!locations.isEmpty()) {
			// use the existing locations so that the nodes appear where the user expects
			return locations;
		}

		RenderContext<FcgVertex, FcgEdge> renderContext = viewer.getRenderContext();
		Function<? super FcgVertex, Shape> shaper = renderContext.getVertexShapeTransformer();

		double x = newRowX;
		double y = newRowY;

		int n = allLevelVertices.size();
		for (int i = 0; i < n; i++) {
			FcgVertex v = allLevelVertices.get(i);
			Rectangle myBounds = shaper.apply(v).getBounds();
			double myHalf = myBounds.width / 2;

			double nextHalf = 0;
			boolean isLast = i == n - 1;
			if (!isLast) {
				FcgVertex nextV = allLevelVertices.get(i + 1);
				Rectangle nextBounds = shaper.apply(nextV).getBounds();
				nextHalf = nextBounds.width / 2;
			}

			Point2D p = new Point2D.Double(x, y);
			locations.put(v, p);

			double vWidth = myHalf + widthPadding + nextHalf;
			Msg.trace(this, v + " at x,width: " + x + "," + vWidth);
			x += vWidth;
		}

		return locations;
	}
Constructor

@param viewer the graph viewer
@param newVertexCollection the collection of new vertices and edges being addeds
@param useAnimation true to use animation
Ghidra/Features/GraphFunctionCalls/src/main/java/functioncalls/graph/job/BowTieExpandVerticesJob.java