package application.layouts;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;

import com.mxgraph.layout.mxGraphLayout;
import com.mxgraph.model.mxCell;
import com.mxgraph.model.mxGeometry;
import com.mxgraph.model.mxIGraphModel;
import com.mxgraph.view.mxCellState;
import com.mxgraph.view.mxGraph;
import com.mxgraph.view.mxGraphView;

public class DAGLayout extends mxGraphLayout {
	private mxIGraphModel graphModel;
	private mxGraphView view;
	private Map<mxCell, Set<mxCell>> dotEdges;
	private List<List<mxCell>> lines;
	private List<Integer> order;
	private Map<mxCell, List<mxCell>> hierarchicalRecursionOrder;
	private Set<Integer> usedLineIndex;
	private List<mxCell> cells;
	private Set<mxCell> roots;
	private Map<mxCell, mxCell> myRoot;
	private Set<mxCell> resources;
	private Set<mxCell> channels;
	private Set<mxCell> eventChannels;
	private Map<mxCell, List<Integer>> vertexToDist;
	private Map<mxCell, List<Integer>> cellToLineIndex;
	private Map<mxCell, Map<String, Double>> cellGeo;
	final double MOVE_X = 100;
	final double MOVE_Y = 50;
	final double WIDTH = 80;
	final double HEIGHT = 30;
	final double SHIFT = WIDTH / 4;
	final double SPACE = 10;
	final double MARGIN_WIDTH = 30;
	final double MARGIN_HEIGHT = 30;
	final double delta = Math.pow(10, -3);

	public DAGLayout(mxGraph graph) {
		super(graph);
		
		graphModel = graph.getModel();
		view = graph.getView();
		dotEdges = new HashMap<>();
		lines = new ArrayList<>();
		order = new ArrayList<>();
		hierarchicalRecursionOrder = new HashMap<>();
		usedLineIndex = new HashSet<>();
		cells = new ArrayList<>();
		roots = new HashSet<>();
		myRoot = new HashMap<>();
		resources = new HashSet<>();
		channels = new HashSet<>();
		eventChannels = new HashSet<>();
		vertexToDist = new HashMap<>();
		cellToLineIndex = new HashMap<>();
		cellGeo = new HashMap<>();
	}
	
	public void execute(Object parent) {
		graphModel.beginUpdate();
		try {
			// 繝｢繝�繝ｫ縺九ｉresource, channel, eventChannel縺ｫ蛻�縺代ｋ
			for (int i = 0; i < graphModel.getChildCount(parent); i++) {
				mxCell cell = (mxCell) graphModel.getChildAt(parent, i);
				
				if (graphModel.isVertex(cell)) {
					mxCellState state = view.getState(cell);
					cells.add(cell);
					
					// cell縺罫esource縺ｧ縺ゅｋ
					if ("ellipse".equals(state.getStyle().get("shape"))) {
						roots.add(cell);
						hierarchicalRecursionOrder.put(cell, new ArrayList<>());
						familySearch(cell, cell, 0);
					}
					
					if ("rectangle".equals(state.getStyle().get("shape"))) {
						if ("true".equals(state.getStyle().get("dashed"))) {
							continue;
						}
						
						// cell縺憩ventChannel縺ｧ縺ゅｋ縺句愛螳�
						boolean bEventChannel = true;
						for (int j = 0; j < cell.getEdgeCount(); j++) {
							mxCell edge = (mxCell) cell.getEdgeAt(j);
							if (edge.getTarget() == cell) {
								bEventChannel = false;
							}
						}
						
						// cell縺憩ventChannel縺ｧ縺ゅｋ
						if (bEventChannel) {
							eventChannels.add(cell);
						// cell縺慶hannel縺ｧ縺ゅｋ
						} else {
							channels.add(cell);
						}
						hierarchicalRecursionOrder.put(cell, new ArrayList<>());
					}
				}
			}
			
			// 蜈ｨ縺ｦ縺ｮcell縺ｮ蠎ｧ讓吶�∝､ｧ縺阪＆縺ｪ縺ｩ繧貞�晄悄蛹�
			for (mxCell c : cells) {
				c.getGeometry().setX(0);
				c.getGeometry().setY(0);
				
				if (resources.contains(c)) {
					view.getState(c).getStyle().put("verticalAlign", "top");
					c.getGeometry().setWidth(WIDTH);
					c.getGeometry().setHeight(HEIGHT);
				}
			}
			
			// dotEdges繧貞�晄悄蛹�
			for (mxCell c : cells) {
				dotEdges.put(c, new HashSet<>());
			}
			
			// line繧呈ｧ狗ｯ峨☆繧�
			for (mxCell ec : eventChannels) {
				List<mxCell> newline = new ArrayList<mxCell>();
				lines.add(newline);
				constructionLine(ec, 0);
			}
			
			// cell縺ｮ霍晞屬繧貞�崎ｨ育ｮ�
			reculcDist();
			
			// cell縺悟ｱ槭☆繧詰ine縺ｮindex繧呈ｱゅａ繧�
			for (int i = 0; i < lines.size(); i++) {
				for (mxCell cell : lines.get(i)) {
					if (cellToLineIndex.get(cell) == null) {
						cellToLineIndex.put(cell, new ArrayList<>());
					}
					cellToLineIndex.get(cell).add(i);
				}
			}
			
			// 荳ｦ縺ｹ繧詰ine縺ｮ鬆�逡ｪ繧偵た繝ｼ繝医☆繧�
			sortLines();
			
			Boolean isVersion1 = true;
			
			System.out.println(vertexToDist);
			// cell繧帝�咲ｽｮ縺吶ｋ
			if (isVersion1) {
				// channel縺ｮ謇�螳壹�ｮ菴咲ｽｮ縺碁嚴螻､讒矩��蜀�驛ｨ縺ｫ縺ｪ繧九→縺阪�∽ｸ九↓縺壹ｉ縺励※驟咲ｽｮ縺吶ｋ繝ｬ繧､繧｢繧ｦ繝�
				layout1((mxCell) parent);
			} else {
				// channel繧帝嚴螻､讒矩��蜀�驛ｨ縺ｧ繧よ園螳壹�ｮ菴咲ｽｮ縺ｫ驟咲ｽｮ縺吶ｋ繝ｬ繧､繧｢繧ｦ繝�
				layout2((mxCell) parent);
			}
		} finally {
			graphModel.endUpdate();
		}
	}
	
	// 髫主ｱ､讒矩��繧貞�榊ｸｰ逧�縺ｫ謗｢邏｢
	public void familySearch(mxCell r, mxCell p, int d) {
		int childNum = graphModel.getChildCount(p);
		
		myRoot.put(p, r);
		resources.add(p);
		if (r != p) {
			hierarchicalRecursionOrder.get(r).add(p);
		}
		
		for (int i = 0; i < childNum; i++) {
			mxCell child = (mxCell) graphModel.getChildAt(p, i);
			cells.add(child);
			resources.add(child);
			familySearch(r, child, d + 1);
		}
	}
	
	// line繧呈ｧ狗ｯ峨☆繧�
	public void constructionLine(mxCell cur, int d) {
		// vertexToDist{key : cell, value : [霍晞屬縺ｮ譛�蟆丞�､, 霍晞屬縺ｮ譛�螟ｧ蛟､]} 繧貞�晄悄蛹�
		if (vertexToDist.get(cur) == null) {
			vertexToDist.put(cur, Arrays.asList(d, d));
		} else {
			// 閾ｪ霄ｫ縺悟性縺ｾ繧後ｋline縺ｮ荳ｭ縺ｧ荳�逡ｪ螟ｧ縺阪＞霍晞屬繧剃ｿ晏ｭ�
			List<Integer> dists = vertexToDist.get(cur);
			dists.set(0, Math.max(dists.get(0), d));
			dists.set(1, dists.get(0));
		}
		
		lines.get(lines.size() - 1).add(cur);
		
		int tagCount = 0;  // edge縺ｮ蟋狗せ縺ｨ縺ｪ繧議ell縺ｨ縺励※蜿ら�ｧ縺輔ｌ縺溷屓謨ｰ
		for (int i = 0; i < cur.getEdgeCount(); i++) {
			mxCell edge = (mxCell) cur.getEdgeAt(i);
			mxCellState state = view.getState(edge);
			mxCell target = (mxCell) edge.getTarget();
			
			// cur縺悟ｧ狗せ縺ｨ縺ｪ繧菊dge縺ｮ邨らせ縺ｨ縺ｪ繧逆arget縺瑚�ｪ霄ｫ縲√ｂ縺励￥縺ｯ蟄伜惠縺励↑縺�蝣ｴ蜷医ｒ髯､縺�
			if ((cur != target) && (target != null)) {
				// edge縺ｮ遞ｮ鬘槭′"dashed"縺ｮ蝣ｴ蜷�
				if ("true".equals(state.getStyle().get("dashed"))) {
					state.getStyle().put("strokeColor", "#800080");  // edge縺ｮ濶ｲ繧貞､画峩
					dotEdges.get(cur).add(target);  // dotEdges{key : 蟋狗せ縺ｨ縺ｪ繧議ell, value : 邨らせ縺ｨ縺ｪ繧議ell}
				} else {
					tagCount++;
					// 蟋狗せ縺ｨ縺励※隍�謨ｰ蝗槫盾辣ｧ縺輔ｌ縺ｦ縺�繧句�ｴ蜷�
					if (tagCount > 1) {
						// cur縺ｾ縺ｧ縺ｮline縺ｮ諠�蝣ｱ繧貞�肴ｧ狗ｯ峨＠縺ｦ縲√◎縺薙°繧画眠縺励＞line繧呈ｧ狗ｯ峨☆繧�
						List<mxCell> newline = new ArrayList<mxCell>(lines.get(lines.size() - 1));
						while (newline.get(newline.size() - 1).getId() != cur.getId()) {
							newline.remove(newline.size() - 1);
						}
						lines.add(newline);
						constructionLine(target, d + 1);
						// 蟋狗せ縺ｨ縺励※蛻昴ａ縺ｦ蜿ら�ｧ縺輔ｌ縺溷�ｴ蜷�
					} else {
						constructionLine(target, d + 1);
					}
				}
			}
		}
	}
	
	// cell縺ｮ霍晞屬繧貞�崎ｨ育ｮ励☆繧�
	public void reculcDist() {		
		// 霍晞屬縺ｮ譖ｴ譁ｰ縺瑚｡後ｏ繧後↑縺上↑繧九∪縺ｧ郢ｰ繧願ｿ斐☆
		while (true) {			
			boolean isChanging = false;
			
			// 蜷御ｸ�縺ｮline縺ｫ蜷ｫ縺ｾ繧後ｋ閾ｪ霄ｫ縺ｮ1縺､謇句燕縺ｮ霍晞屬縺ｨ閾ｪ霄ｫ縺ｮ霍晞屬繧呈ｯ碑ｼ�縺励※縲∵ｭ｣縺励＞霍晞屬繧定ｨ育ｮ�
			for (List<mxCell> line : lines) {
				for (int i = 1; i < line.size(); i++) {
					mxCell cur = line.get(i);
					mxCell pre = line.get(i - 1);
					if (!(resources.contains(cur) && resources.contains(pre))) {
						if (vertexToDist.get(cur).get(0) < vertexToDist.get(pre).get(1) + 1) {
							List<Integer> dists = vertexToDist.get(cur);
							dists.set(0, Math.max(dists.get(0), vertexToDist.get(pre).get(1) + 1));
							dists.set(1, Math.max(dists.get(0), dists.get(1)));
							isChanging = true;
						}
					}
				}
			}
			
			// 髫主ｱ､讒矩��繧定��諷ｮ縺励◆cell縺ｮ霍晞屬繧定ｨ育ｮ�
			for (mxCell r : roots) {
				for (mxCell c : hierarchicalRecursionOrder.get(r)) {
					mxCell p = (mxCell) graphModel.getParent(c);
					if (!vertexToDist.containsKey(c)) {
						vertexToDist.put(c, Arrays.asList(vertexToDist.get(p).get(0), vertexToDist.get(p).get(0)));
					} else {
						if (vertexToDist.get(p).get(0) > vertexToDist.get(c).get(0)) {
							List<Integer> dists = vertexToDist.get(c);
							dists.set(0, vertexToDist.get(p).get(0));
							dists.set(1, Math.max(dists.get(0), dists.get(1)));
							isChanging = true;
						}
						
						if (vertexToDist.get(p).get(1) < vertexToDist.get(c).get(1)) {
							List<Integer> dists = vertexToDist.get(p);
							dists.set(1, vertexToDist.get(c).get(1));
							isChanging = true;
						}
					}
				}
			}
			
			// 1蠎ｦ繧ょ､画峩縺檎┌縺�縺ｨ繝ｫ繝ｼ繝励ｒ謚懊￠繧�
			if (!isChanging) {
				break;
			}
		}
	}
	
	// line繧偵た繝ｼ繝医☆繧�(縺溘□縺励�∝ｮ滄圀縺ｫ縺ｯlines蜀�縺ｮ隕∫ｴ�縺ｮ鬆�逡ｪ縺ｯ蜈･繧梧崛縺医★縲∝他縺ｳ蜃ｺ縺咎��逡ｪ繧剃ｿ晏ｭ倥＠縺殪rder繧剃ｽ懈��)
	public void sortLines() {		
		for (int i = 0; i < lines.size(); i++) {
			List<mxCell> line = lines.get(i);
			
			// eventChannel縺九ｉ縺ｮ蜃ｺ蜉帛�医′root縺ｧ縺ゅｋ蝣ｴ蜷�
			if (roots.contains(line.get(1))) {
				for (mxCell c : line) {
					addLine(c);  // order縺ｫline縺ｮindex繧呈�ｼ邏阪☆繧�
				}
			}
		}
	}
	
	// order縺ｫline縺ｮindex繧呈�ｼ邏阪☆繧�
	public void addLine(mxCell c) {
		if (cellToLineIndex.get(c) != null) {
			for (int i : cellToLineIndex.get(c)) {
				if (!usedLineIndex.contains(i)) {
					order.add(i);
				}
				usedLineIndex.add(i);
			}
		}
		
		// root縺悟ｱ槭☆繧詰ine縺ｮindex繧呈�ｼ邏阪＠縺溷ｾ後�∝ｭ仙ｭｫ縺悟ｱ槭☆繧詰ine縺ｮindex繧帝��谺｡譬ｼ邏阪☆繧�
		for (int i = 0; i < graphModel.getChildCount(c); i++) {
			mxCell child = (mxCell) graphModel.getChildAt(c, i);
			addLine(child);
		}
	}
	
	// cell繧帝�咲ｽｮ縺吶ｋ
	public void layout1(mxCell s) {
		/* cell縺ｮ蠎ｧ讓吶�ｯ縲…ell縺ｮparent縺ｮ蟾ｦ荳翫�ｮ蠎ｧ讓吶ｒ蝓ｺ貅悶↓x縺ｨy繧偵←繧後□縺代★繧峨☆縺九〒螳夂ｾｩ縺輔ｌ縺ｦ縺�繧�
		   繧医▲縺ｦ險育ｮ励�ｮ驛ｽ蜷井ｸ翫�（d=1 縺ｮcell(繝ｬ繧､繧｢繧ｦ繝育判髱｢)縺ｮ蟾ｦ荳翫ｒ蝓ｺ貅悶↓縺励◆蠎ｧ讓吶ｒcellGeo縺ｫ菫晏ｭ倥＠縺ｦ縺翫￥ */
		
		Set<mxCell> movedSet = new HashSet<>();  // 驟咲ｽｮ貂医∩縺ｮcell繧堤ｮ｡逅�
		Map<Integer, List<mxCell>> movedMap = new TreeMap<>();  // 驟咲ｽｮ貂医∩縺ｮcell縺ｮ霍晞屬縺斐→縺ｮ驟咲ｽｮ鬆�繧堤ｮ｡逅�
		Map<Integer, Double> distToMaxX = new HashMap<>();  // 霍晞屬縺斐→縺ｮcell縺ｮ蜿ｳ遶ｯ縺ｮx蠎ｧ讓吶�ｮ譛�螟ｧ蛟､繧堤ｮ｡逅�
		double maxY = 0;
		int maxD = 0;
		
		putCellGeo(s, 0, 0);
		
		// 霍晞屬縺ｮ譛�螟ｧ蛟､繧呈ｱゅａ繧�
		for (mxCell c : cells) {
			maxD = Math.max(maxD, vertexToDist.get(c).get(1));
		}
		
		// 蛻晄悄蛹�
		distToMaxX.put(-1, 0.0);
		for (int d = 0; d <= maxD; d++) {
			movedMap.put(d, new ArrayList<>());
			distToMaxX.put(d, -1.0);
		}
		
		for (int i : order) {
			double centerY = -1;  // 驟咲ｽｮ莠亥ｮ壹�ｮy蠎ｧ讓�(荳ｭ螟ｮ)
			for (mxCell cur : lines.get(i)) {
				// 譌｢縺ｫ驟咲ｽｮ貂医∩縺ｪ繧峨せ繧ｭ繝�繝�
				if (movedSet.contains(cur)) {
					continue;
				}
				
				int d = vertexToDist.get(cur).get(0);  // cur縺ｮ霍晞屬
				double x = distToMaxX.get(d - 1) + MOVE_X;  // 驟咲ｽｮ莠亥ｮ壹�ｮx蠎ｧ讓�
				double endX = 0;  // 驟咲ｽｮ縺励◆cell縺ｮ蜿ｳ遶ｯ縺ｮx蠎ｧ讓�
				double endY = 0;  // 驟咲ｽｮ縺励◆cell縺ｮ荳狗ｫｯ縺ｮy蠎ｧ讓�
				
				// cur縺罫oot, channel, eventChannel縺ｫ螻槭☆繧議ell縺ｧ縺ゅｋ蝣ｴ蜷�
				if (roots.contains(cur) || channels.contains(cur) || eventChannels.contains(cur)) {
					if (centerY == -1) {
						centerY = maxY + MOVE_Y + cur.getGeometry().getHeight() / 2;
					}
					double y = centerY - cur.getGeometry().getHeight() / 2;  // 驟咲ｽｮ莠亥ｮ壹�ｮy蠎ｧ讓�
					double ny = y;
					double nx = x;
					
					// 莠亥ｮ壹＠縺ｦ縺�繧句ｺｧ讓吶↓驟咲ｽｮ縺吶ｋ縺ｨ莉悶�ｮ繝ｪ繧ｽ繝ｼ繧ｹ蜀�驛ｨ縺ｫ驟咲ｽｮ縺輔ｌ縺ｦ縺励∪縺�蝣ｴ蜷医�∽ｸ九↓縺壹ｉ縺�
					for (mxCell m : movedSet) {
						if (roots.contains(m) || channels.contains(m)) {
							if (cellOverlap(m.getGeometry().getX(), m.getGeometry().getY(), m.getGeometry().getWidth(), m.getGeometry().getHeight(), x, y, cur.getGeometry().getWidth(), cur.getGeometry().getHeight())) {
								ny = m.getGeometry().getY() + m.getGeometry().getHeight() + MOVE_Y;
							}
						}
					}
					
					// 莠亥ｮ壹�ｮ蠎ｧ讓吶↓驟咲ｽｮ縺励※縲∝推遞ｮ蜃ｦ逅�繧定｡後≧
					cur.getGeometry().setX(nx);
					cur.getGeometry().setY(ny);
					endX = nx + cur.getGeometry().getWidth();
					endY = ny + cur.getGeometry().getHeight();
					putCellGeo(cur, nx, ny);
					addMoved(movedSet, movedMap, cur, d);
					graphModel.setGeometry(cur, (mxGeometry) cur.getGeometry().clone());
					
					// 霍晞屬縺慧縺ｮ譛�螟ｧ縺ｮx蠎ｧ讓吶′譖ｴ譁ｰ縺輔ｌ繧句�ｴ蜷医�∵里縺ｫ驟咲ｽｮ貂医∩縺ｮcell繧偵◎繧後↓蠢懊§縺ｦ蜿ｳ縺ｫ縺壹ｉ縺�
					if (distToMaxX.get(d) < endX) {
						updateDistToMaxX(distToMaxX, movedSet, movedMap, d, maxD, endX);
					}
					maxY = Math.max(maxY, endY);  // 驟咲ｽｮ貂医∩縺ｮcell縺ｮ荳ｭ縺ｧ縺ｮ荳狗ｫｯ縺ｮ蠎ｧ讓吶�ｮ譛�螟ｧ
					
					// root縺ｮ蟄仙ｭｫ繧偵∪縺ｨ繧√※驟咲ｽｮ縺吶ｋ
					for (mxCell c : hierarchicalRecursionOrder.get(cur)) {
						d = vertexToDist.get(c).get(0);
						nx = x;  // 驟咲ｽｮ莠亥ｮ壹�ｮx蠎ｧ讓�
						mxCell parent = (mxCell) graphModel.getParent(c);
						
						// 霍晞屬縺瑚ｦｪ縺ｨ遲峨＠縺�蝣ｴ蜷医�∬ｦｪ縺ｮx蠎ｧ讓吶ｈ繧雁ｰ代＠螟ｧ縺阪￥縺吶ｋ
						if (d == vertexToDist.get(parent).get(0)) {
							nx = cellGeo.get(parent).get("x") + MARGIN_WIDTH;
						} else {
							nx = distToMaxX.get(d - 1) + MOVE_X;
						}
						
						// 驥阪↑縺｣縺ｦ縺励∪縺�蜈�蠑溘Μ繧ｽ繝ｼ繧ｹ縺悟ｭ伜惠縺吶ｋ蝣ｴ蜷医�∽ｸ九↓縺壹ｉ縺�
						double brotherMaxY = MOVE_Y;
						for (int k = 0; k < graphModel.getChildCount(parent); k++) {
							mxCell child = (mxCell) graphModel.getChildAt(parent, k);
							if (child == c) {
								continue;
							}
							
							if (movedSet.contains(child) && (vertexToDist.get(child).get(0) <= d && d <= vertexToDist.get(child).get(1))) {
								brotherMaxY = Math.max(brotherMaxY, child.getGeometry().getY() + child.getGeometry().getHeight() + SPACE);
							}
						}
						
						// 莠亥ｮ壹�ｮ蠎ｧ讓吶↓驟咲ｽｮ縺励※縲∝推遞ｮ蜃ｦ逅�繧定｡後≧
						c.getGeometry().setX(nx - cellGeo.get(parent).get("x"));
						c.getGeometry().setY(brotherMaxY);
						graphModel.setGeometry(c, (mxGeometry) c.getGeometry().clone());
						addMoved(movedSet, movedMap, c, d);
						putCellGeo(c, nx, cellGeo.get(parent).get("y") + brotherMaxY);
						
						endX = cellGeo.get(c).get("x") + c.getGeometry().getWidth();
						endY = cellGeo.get(c).get("y") + c.getGeometry().getHeight();
						endY = resize(parent, endX, endY);  // 蟄仙ｭｫ縺瑚ｦｪ縺ｮ蜀�驛ｨ縺ｫ蜷ｫ縺ｾ繧後↑縺�蝣ｴ蜷医�∬ｦｪ縺ｮ螟ｧ縺阪＆繧貞､画峩縺吶ｋ
						
						// 霍晞屬縺慧縺ｮ譛�螟ｧ縺ｮx蠎ｧ讓吶′譖ｴ譁ｰ縺輔ｌ繧句�ｴ蜷医�∵里縺ｫ驟咲ｽｮ貂医∩縺ｮcell繧偵◎繧後↓蠢懊§縺ｦ蜿ｳ縺ｫ縺壹ｉ縺�
						if (distToMaxX.get(d) < endX) {
							updateDistToMaxX(distToMaxX, movedSet, movedMap, d, maxD, endX);
						}
						maxY = Math.max(maxY, endY);
					}		
				}
			}
		}
		
		// 轤ｹ邱壹�ｮ霎ｺ縺碁㍾縺ｪ縺｣縺ｦ縺�繧句�ｴ蜷医�∝承縺ｫ縺壹ｉ縺�
		for (mxCell c : resources) {
			for (mxCell cc : dotEdges.get(c)) {		
				for (Map.Entry<mxCell, Set<mxCell>> entry : dotEdges.entrySet()) {
					mxCell u = entry.getKey();
					for (mxCell v : entry.getValue()) {
						// c-cc, u-v縺悟酔縺賄otEdge繧貞盾辣ｧ縺励※縺�繧句�ｴ蜷�
						if ((c == u && cc == v) || (c == v && cc == u)) {
							continue;
						}
						
						double []posC = {cellGeo.get(c).get("x") + c.getGeometry().getWidth() / 2, cellGeo.get(c).get("y") + c.getGeometry().getHeight() / 2};
						double []posCC = {cellGeo.get(cc).get("x") + cc.getGeometry().getWidth() / 2, cellGeo.get(cc).get("y") + cc.getGeometry().getHeight() / 2};
						double []posU = {cellGeo.get(u).get("x") + u.getGeometry().getWidth() / 2, cellGeo.get(u).get("y") + u.getGeometry().getHeight() / 2};
						double []posV = {cellGeo.get(v).get("x") + v.getGeometry().getWidth() / 2, cellGeo.get(v).get("y") + v.getGeometry().getHeight() / 2};
						
						if (isStraightLine(posC, posCC, posU, posV)) {
							c.getGeometry().setX(c.getGeometry().getX() + SHIFT);
							cellGeo.get(c).replace("x", cellGeo.get(c).get("x"));
							cc.getGeometry().setX(cc.getGeometry().getX() + SHIFT);
							cellGeo.get(cc).replace("x", cellGeo.get(cc).get("x") + SHIFT);
							graphModel.setGeometry(c, c.getGeometry());
							graphModel.setGeometry(cc, cc.getGeometry());
						}
					}
				}
			}
		}
		
		// eventChannel繧貞�ｺ蜉帛�医�ｮy蠎ｧ讓吶↓謠�縺医ｋ
		for (int i : order) {
			mxCell cur = lines.get(i).get(1);
			List<mxCell> ecs = new ArrayList<>();
			for (int j : cellToLineIndex.get(cur)) {
				if (!ecs.contains(lines.get(j).get(0)) && cur == lines.get(j).get(1)) {
					ecs.add(lines.get(j).get(0));
				}
			}

			double centerY = cellGeo.get(cur).get("y") + cur.getGeometry().getHeight() / 2;
			double y = 0;
			if (ecs.size() % 2 != 0) {
				y = centerY - ecs.size()*HEIGHT / 2 - ((ecs.size() - 1) / 2)*SPACE;
			} else {
				y = centerY - ecs.size()*HEIGHT / 2 - (ecs.size() - 1)*SPACE / 2;
			}

			for (int j : order) {
				mxCell other = lines.get(j).get(0);
				if (ecs.contains(other)) {
					continue;
				}
			}
			
			for (mxCell ec : ecs) {
				ec.getGeometry().setY(y);
				graphModel.setGeometry(ec, ec.getGeometry());
				cellGeo.get(ec).replace("y", y);
				y += HEIGHT + SPACE;
			}
		}
		
		// eventChannel繧馳蠎ｧ讓吶↓縺､縺�縺ｦ譏�鬆�縺ｫ繧ｽ繝ｼ繝医☆繧�
		List<mxCell> sortedEcs = new ArrayList<>();
		Set<mxCell> usedEcs = new HashSet<>();
		for (int i = 0; i < eventChannels.size(); i++) {
			mxCell minEc = movedMap.get(0).get(0);
			double ecMinY = Double.MAX_VALUE;
			for (mxCell ec : movedMap.get(0)) {
				if (usedEcs.contains(ec)) {
					continue;
				}
				if (cellGeo.get(ec).get("y") < ecMinY) {
					minEc = ec;
					ecMinY = cellGeo.get(ec).get("y");
				}
			}
			sortedEcs.add(minEc);
			usedEcs.add(minEc);
		}
		
		// 繧ｽ繝ｼ繝医�ｮ鬆�逡ｪ縺ｫ蠢懊§縺ｦ縲�驥阪↑縺｣縺ｦ縺�繧菊ventChannel繧剃ｸ九↓縺壹ｉ縺�
		for (int i = 1; i < sortedEcs.size(); i++) {
			mxCell cur = sortedEcs.get(i);
			mxCell pre = sortedEcs.get(i - 1);
			
			if (cellGeo.get(cur).get("y") < cellGeo.get(pre).get("y") + pre.getGeometry().getHeight() + SPACE) {
				double dy = cellGeo.get(pre).get("y") + pre.getGeometry().getHeight() + SPACE - cellGeo.get(cur).get("y");
				cur.getGeometry().setY(cur.getGeometry().getY() + dy);
				cellGeo.get(cur).replace("y", cellGeo.get(cur).get("y") + dy);
				graphModel.setGeometry(cur, cur.getGeometry());
			}
		}
	}
	
	// cell繧帝�咲ｽｮ縺吶ｋ
	public void layout2(mxCell s) {
		/* cell縺ｮ蠎ｧ讓吶�ｯ縲…ell縺ｮparent縺ｮ蟾ｦ荳翫�ｮ蠎ｧ讓吶ｒ蝓ｺ貅悶↓x縺ｨy繧偵←繧後□縺代★繧峨☆縺九〒螳夂ｾｩ縺輔ｌ縺ｦ縺�繧�
		   繧医▲縺ｦ險育ｮ励�ｮ驛ｽ蜷井ｸ翫�（d=1 縺ｮcell(繝ｬ繧､繧｢繧ｦ繝育判髱｢)縺ｮ蟾ｦ荳翫ｒ蝓ｺ貅悶↓縺励◆蠎ｧ讓吶ｒcellGeo縺ｫ菫晏ｭ倥＠縺ｦ縺翫￥ */
		
		Set<mxCell> movedSet = new HashSet<>();  // 驟咲ｽｮ貂医∩縺ｮcell繧堤ｮ｡逅�
		Map<Integer, List<mxCell>> movedMap = new TreeMap<>();  // 驟咲ｽｮ貂医∩縺ｮcell縺ｮ霍晞屬縺斐→縺ｮ驟咲ｽｮ鬆�繧堤ｮ｡逅�
		Map<Integer, Double> distToMaxX = new HashMap<>();  // 霍晞屬縺斐→縺ｮcell縺ｮ蜿ｳ遶ｯ縺ｮx蠎ｧ讓吶�ｮ譛�螟ｧ蛟､繧堤ｮ｡逅�
		double maxY = 0;
		int maxD = 0;
		
		putCellGeo(s, 0, 0);
		
		// 霍晞屬縺ｮ譛�螟ｧ蛟､繧呈ｱゅａ繧�
		for (mxCell c : cells) {
			maxD = Math.max(maxD, vertexToDist.get(c).get(1));
		}
		
		// 蛻晄悄蛹�
		distToMaxX.put(-1, 0.0);
		for (int d = 0; d <= maxD; d++) {
			movedMap.put(d, new ArrayList<>());
			distToMaxX.put(d, -1.0);
		}
		
		for (int i : order) {
			double centerY = -1;  // 驟咲ｽｮ莠亥ｮ壹�ｮy蠎ｧ讓�(荳ｭ螟ｮ)
			for (int j = 0; j < lines.get(i).size(); j++) {
				mxCell cur = lines.get(i).get(j);
				
				// 譌｢縺ｫ驟咲ｽｮ貂医∩縺ｪ繧峨せ繧ｭ繝�繝�
				if (movedSet.contains(cur)) {
					continue;
				}
				
				int d = vertexToDist.get(cur).get(0);  // cur縺ｮ霍晞屬
				double x = distToMaxX.get(d - 1) + MOVE_X;  // 驟咲ｽｮ莠亥ｮ壹�ｮx蠎ｧ讓�
				double endX = 0;  // 驟咲ｽｮ縺励◆cell縺ｮ蜿ｳ遶ｯ縺ｮx蠎ｧ讓�
				double endY = 0;  // 驟咲ｽｮ縺励◆cell縺ｮ荳狗ｫｯ縺ｮy蠎ｧ讓�
				
				// cur縺罫oots縺ｨeventChannel縺ｫ螻槭☆繧九→縺阪�ｮcell縺ｮ驟咲ｽｮ
				if (roots.contains(cur) || eventChannels.contains(cur)) {
					if (centerY == -1) {
						centerY = maxY + MOVE_Y + cur.getGeometry().getHeight() / 2;
					}
					double y = centerY - cur.getGeometry().getHeight() / 2;  // 驟咲ｽｮ莠亥ｮ壹�ｮy蠎ｧ讓�
					double ny = y;
					double nx = x;
					
					// 莠亥ｮ壹�ｮ蠎ｧ讓吶↓驟咲ｽｮ縺励※縲∝推遞ｮ蜃ｦ逅�繧定｡後≧
					cur.getGeometry().setX(nx);
					cur.getGeometry().setY(ny);
					endX = nx + cur.getGeometry().getWidth();
					endY = ny + cur.getGeometry().getHeight();
					putCellGeo(cur, nx, ny);
					addMoved(movedSet, movedMap, cur, d);
					graphModel.setGeometry(cur, (mxGeometry) cur.getGeometry().clone());
					
					// 霍晞屬縺慧縺ｮ譛�螟ｧ縺ｮx蠎ｧ讓吶′譖ｴ譁ｰ縺輔ｌ繧句�ｴ蜷医�∵里縺ｫ驟咲ｽｮ貂医∩縺ｮcell繧偵◎繧後↓蠢懊§縺ｦ蜿ｳ縺ｫ縺壹ｉ縺�
					if (distToMaxX.get(d) < endX) {
						updateDistToMaxX(distToMaxX, movedSet, movedMap, d, maxD, endX);
					}
					maxY = Math.max(maxY, endY);  // 驟咲ｽｮ貂医∩縺ｮcell縺ｮ荳ｭ縺ｧ縺ｮ荳狗ｫｯ縺ｮ蠎ｧ讓吶�ｮ譛�螟ｧ
					
					// root縺ｮ蟄仙ｭｫ繧偵∪縺ｨ繧√※驟咲ｽｮ縺吶ｋ
					for (mxCell c : hierarchicalRecursionOrder.get(cur)) {
						d = vertexToDist.get(c).get(0);
						nx = x;  // 驟咲ｽｮ莠亥ｮ壹�ｮx蠎ｧ讓�
						mxCell parent = (mxCell) graphModel.getParent(c);
						
						// 霍晞屬縺瑚ｦｪ縺ｨ遲峨＠縺�蝣ｴ蜷医�∬ｦｪ縺ｮx蠎ｧ讓吶ｈ繧雁ｰ代＠螟ｧ縺阪￥縺吶ｋ
						if (d == vertexToDist.get(parent).get(0)) {
							nx = cellGeo.get(parent).get("x") + MARGIN_WIDTH;
						} else {
							nx = distToMaxX.get(d - 1) + MOVE_X;
						}
						
						// 驥阪↑縺｣縺ｦ縺励∪縺�蜈�蠑溘Μ繧ｽ繝ｼ繧ｹ縺悟ｭ伜惠縺吶ｋ蝣ｴ蜷医�∽ｸ九↓縺壹ｉ縺�
						double brotherMaxY = MOVE_Y;
						for (int k = 0; k < graphModel.getChildCount(parent); k++) {
							mxCell child = (mxCell) graphModel.getChildAt(parent, k);
							if (child == c) {
								continue;
							}
							
							if (movedSet.contains(child) && (vertexToDist.get(child).get(0) <= d && d <= vertexToDist.get(child).get(1))) {
								brotherMaxY = Math.max(brotherMaxY, child.getGeometry().getY() + child.getGeometry().getHeight() + SPACE);
							}
						}
						
						// 莠亥ｮ壹�ｮ蠎ｧ讓吶↓驟咲ｽｮ縺励※縲∝推遞ｮ蜃ｦ逅�繧定｡後≧
						c.getGeometry().setX(nx - cellGeo.get(parent).get("x"));
						c.getGeometry().setY(brotherMaxY);
						graphModel.setGeometry(c, (mxGeometry) c.getGeometry().clone());
						addMoved(movedSet, movedMap, c, d);
						putCellGeo(c, nx, cellGeo.get(parent).get("y") + brotherMaxY);
						
						endX = cellGeo.get(c).get("x") + c.getGeometry().getWidth();
						endY = cellGeo.get(c).get("y") + c.getGeometry().getHeight();
						endY = resize(parent, endX, endY);  // 蟄仙ｭｫ縺瑚ｦｪ縺ｮ蜀�驛ｨ縺ｫ蜷ｫ縺ｾ繧後↑縺�蝣ｴ蜷医�∬ｦｪ縺ｮ螟ｧ縺阪＆繧貞､画峩縺吶ｋ
						
						// 霍晞屬縺慧縺ｮ譛�螟ｧ縺ｮx蠎ｧ讓吶′譖ｴ譁ｰ縺輔ｌ繧句�ｴ蜷医�∵里縺ｫ驟咲ｽｮ貂医∩縺ｮcell繧偵◎繧後↓蠢懊§縺ｦ蜿ｳ縺ｫ縺壹ｉ縺�
						if (distToMaxX.get(d) < endX) {
							updateDistToMaxX(distToMaxX, movedSet, movedMap, d, maxD, endX);
						}
						maxY = Math.max(maxY, endY);  // 驟咲ｽｮ貂医∩縺ｮcell縺ｮ荳ｭ縺ｧ縺ｮ荳狗ｫｯ縺ｮ蠎ｧ讓吶�ｮ譛�螟ｧ
					}
				}
				
				// cur縺慶hannel縺ｫ螻槭☆繧九→縺阪�ｮcell縺ｮ驟咲ｽｮ
				if (channels.contains(cur)) {
					mxCell pre = lines.get(i).get(j - 1);
					centerY = cellGeo.get(pre).get("y") + pre.getGeometry().getHeight() / 2;
					double y = centerY - cur.getGeometry().getHeight() / 2;
					
					cur.getGeometry().setX(x);
					cur.getGeometry().setY(y);
					endX = x + cur.getGeometry().getWidth();
					endY = y + cur.getGeometry().getHeight();
					putCellGeo(cur, x, y);
					addMoved(movedSet, movedMap, cur, d);
					graphModel.setGeometry(cur, (mxGeometry) cur.getGeometry().clone());
					
					// 霍晞屬縺慧縺ｮ譛�螟ｧ縺ｮx蠎ｧ讓吶′譖ｴ譁ｰ縺輔ｌ繧句�ｴ蜷医�∵里縺ｫ驟咲ｽｮ貂医∩縺ｮcell繧偵◎繧後↓蠢懊§縺ｦ蜿ｳ縺ｫ縺壹ｉ縺�
					if (distToMaxX.get(d) < endX) {
						updateDistToMaxX(distToMaxX, movedSet, movedMap, d, maxD, endX);
					}
					maxY = Math.max(maxY, endY);  // 驟咲ｽｮ貂医∩縺ｮcell縺ｮ荳ｭ縺ｧ縺ｮ荳狗ｫｯ縺ｮ蠎ｧ讓吶�ｮ譛�螟ｧ
				}
			}
		}
		
		// 轤ｹ邱壹�ｮ霎ｺ縺碁㍾縺ｪ縺｣縺ｦ縺�繧句�ｴ蜷医�∝承縺ｫ縺壹ｉ縺�
		for (mxCell c : resources) {
			for (mxCell cc : dotEdges.get(c)) {		
				for (Map.Entry<mxCell, Set<mxCell>> entry : dotEdges.entrySet()) {
					mxCell u = entry.getKey();
					for (mxCell v : entry.getValue()) {
						if ((c == u && cc == v) || (c == v && cc == u)) {
							continue;
						}
						
						double []posC = {cellGeo.get(c).get("x") + c.getGeometry().getWidth() / 2, cellGeo.get(c).get("y") + c.getGeometry().getHeight() / 2};
						double []posCC = {cellGeo.get(cc).get("x") + cc.getGeometry().getWidth() / 2, cellGeo.get(cc).get("y") + cc.getGeometry().getHeight() / 2};
						double []posU = {cellGeo.get(u).get("x") + u.getGeometry().getWidth() / 2, cellGeo.get(u).get("y") + u.getGeometry().getHeight() / 2};
						double []posV = {cellGeo.get(v).get("x") + v.getGeometry().getWidth() / 2, cellGeo.get(v).get("y") + v.getGeometry().getHeight() / 2};
						
						if (isStraightLine(posC, posCC, posU, posV)) {
							c.getGeometry().setX(c.getGeometry().getX() + SHIFT);
							cellGeo.get(c).replace("x", cellGeo.get(c).get("x"));
							cc.getGeometry().setX(cc.getGeometry().getX() + SHIFT);
							cellGeo.get(cc).replace("x", cellGeo.get(cc).get("x") + SHIFT);
							graphModel.setGeometry(c, c.getGeometry());
							graphModel.setGeometry(cc, cc.getGeometry());
						}
					}
				}
			}
		}
		
		// eventChannel繧貞�ｺ蜉帛�医�ｮy蠎ｧ讓吶↓謠�縺医ｋ
		for (int i : order) {
			mxCell cur = lines.get(i).get(1);
			List<mxCell> ecs = new ArrayList<>();
			for (int j : cellToLineIndex.get(cur)) {
				if (!ecs.contains(lines.get(j).get(0)) && cur == lines.get(j).get(1)) {
					ecs.add(lines.get(j).get(0));
				}
			}
			
			double centerY = cellGeo.get(cur).get("y") + cur.getGeometry().getHeight() / 2;
			double y = 0;
			if (ecs.size() % 2 != 0) {
				y = centerY - ecs.size()*HEIGHT / 2 - ((ecs.size() - 1) / 2)*SPACE;
			} else {
				y = centerY - ecs.size()*HEIGHT / 2 - (ecs.size() - 1)*SPACE / 2;
			}

			for (int j : order) {
				mxCell other = lines.get(j).get(0);
				if (ecs.contains(other)) {
					continue;
				}
			}
			
			for (mxCell ec : ecs) {
				ec.getGeometry().setY(y);
				graphModel.setGeometry(ec, ec.getGeometry());
				cellGeo.get(ec).replace("y", y);
				y += HEIGHT + SPACE;
			}
		}
		
		// eventChannel繧馳蠎ｧ讓吶↓縺､縺�縺ｦ譏�鬆�縺ｫ繧ｽ繝ｼ繝医☆繧�
		List<mxCell> sortedEcs = new ArrayList<>();
		Set<mxCell> usedEcs = new HashSet<>();
		for (int i = 0; i < eventChannels.size(); i++) {
			mxCell minEc = movedMap.get(0).get(0);
			double ecMinY = Double.MAX_VALUE;
			for (mxCell ec : movedMap.get(0)) {
				if (usedEcs.contains(ec)) {
					continue;
				}
				if (cellGeo.get(ec).get("y") < ecMinY) {
					minEc = ec;
					ecMinY = cellGeo.get(ec).get("y");
				}
			}
			sortedEcs.add(minEc);
			usedEcs.add(minEc);
		}
		
		// 繧ｽ繝ｼ繝医�ｮ鬆�逡ｪ縺ｫ蠢懊§縺ｦ縲�驥阪↑縺｣縺ｦ縺�繧菊ventChannel繧剃ｸ九↓縺壹ｉ縺�
		for (int i = 1; i < sortedEcs.size(); i++) {
			mxCell cur = sortedEcs.get(i);
			mxCell pre = sortedEcs.get(i - 1);
			
			if (cellGeo.get(cur).get("y") < cellGeo.get(pre).get("y") + pre.getGeometry().getHeight() + SPACE) {
				double dy = cellGeo.get(pre).get("y") + pre.getGeometry().getHeight() + SPACE - cellGeo.get(cur).get("y");
				cur.getGeometry().setY(cur.getGeometry().getY() + dy);
				cellGeo.get(cur).replace("y", cellGeo.get(cur).get("y") + dy);
				graphModel.setGeometry(cur, cur.getGeometry());
			}
		}
	}
	
	// cell縺ｮ蠎ｧ讓吶ｒ逋ｻ骭ｲ
	public void putCellGeo(mxCell c, double x, double y) {
		cellGeo.put(c, new HashMap<>());
		cellGeo.get(c).put("x", x);
		cellGeo.get(c).put("y", y);
	}
	
	// movedSet, movedMap縺ｫ驟咲ｽｮ貂医∩縺ｮcell繧定ｿｽ蜉�
	public void addMoved(Set<mxCell> ms, Map<Integer, List<mxCell>> mm, mxCell c, int d) {
		ms.add(c);
		mm.get(d).add(c);
	}
	
	// 2縺､縺ｮ霎ｺ縺碁㍾縺ｪ縺｣縺ｦ縺�繧九°蛻､螳�
	public boolean isStraightLine(double []u1, double []v1, double []u2, double []v2) {
		double gradient1 = 0;
		double length1 = Math.pow(u1[0] - v1[0], 2) + Math.pow(u1[1] - v1[1], 2);
		double gradient2 = 0;
		double length2 = Math.pow(u2[0] - v2[0], 2) + Math.pow(u2[1] - v2[1], 2);
		boolean isVertical = false;
		
		if (u1[0] == v1[0]) {
			isVertical = true;
		} else {
			gradient1 = (u1[1] - v1[1]) / (u1[0] - v1[0]);
		}
		
		if (isVertical) {
			if (u2[0] != v2[0]) {
				return false;
			} else {
				gradient2 = (u2[1] - v2[1]) / (u2[0] - v2[0]);
			}
		} else {
			if (gradient1 - gradient2 > delta) {
				return false;
			}
		}
		
		double [][]a = {u1, v1};
		double [][]b = {u2, v2};
		double maxLength = 0;
		for (double []c1 : a) {
			for (double []c2 : b) {
				double gradient3 = 0;
				double length3 = Math.pow(c1[0] - c2[0], 2) + Math.pow(c1[1] - c2[1], 2);
				
				if(isVertical) {
					if (c1[0] == c2[0]) {
						maxLength = Math.max(maxLength, length3);
					} else {
						return false;
					}
				} else {
					if (c1[0] == c2[0]) {
						return false;
					} else {
						gradient3 = (c2[1] - c1[1]) / (c2[0] - c1[0]);
						if (gradient1 - gradient3 > delta) {
							return false;
						}
						maxLength = Math.max(maxLength, length3);
					}
				}
			}
		}
		
		if (length1 + length2 < maxLength) {
			return false;
		} else {
			return true;
		}
	}
	
	// 2縺､縺ｮcell縺碁㍾縺ｪ縺｣縺ｦ縺�繧九°蛻､螳�
	public boolean cellOverlap(double ax, double ay, double aw, double ah, double bx, double by, double bw, double bh) {
		if (((ax <= bx) && (bx <= ax + aw)) || ((bx <= ax) && (ax <= bx + bw))) {
			if (((ay <= by) && (by <= ay + ah)) || ((by <= ay) && (ay <= by + bh))) {
				return true;
			}
		}
		return false;
	}
	
	// 豺ｱ縺輔′d縺ｮdistToMaxX縺ｫ譖ｴ譁ｰ縺悟�･繧九→縲∵ｷｱ縺輔′d繧医ｊ螟ｧ縺阪＞驟咲ｽｮ貂医∩縺ｮcell繧貞承縺ｫ縺壹ｉ縺�
	public void updateDistToMaxX(Map<Integer, Double> distToMaxX, Set<mxCell> movedSet, Map<Integer, List<mxCell>> movedMap, int d, int md, double endX) {
		double preEndX = distToMaxX.get(d);
		distToMaxX.replace(d, endX);
		for (int i = d + 1; i <= md; i++) {
			if (distToMaxX.get(i) == -1) {
				distToMaxX.replace(i, endX);
			}
		}
		
		for (Map.Entry<Integer, List<mxCell>> entry : movedMap.entrySet()) {
			if (entry.getKey() <= d) {
				continue;
			}
			
			for (mxCell cell : entry.getValue()) {
				double newX = cellGeo.get(cell).get("x") + endX - preEndX;
				mxCell cp = (mxCell) cell.getParent();
				relocateX(cell, newX, cellGeo.get(cp).get("x"));
				
				if (!(roots.contains(cell) || channels.contains(cell))) {
					mxCell ancestor = (mxCell) graphModel.getParent(cell);
					resize(ancestor, cellGeo.get(cell).get("x") + cell.getGeometry().getWidth(), cellGeo.get(cell).get("y") + cell.getGeometry().getHeight());
				}
				if (distToMaxX.get(entry.getKey()) < cellGeo.get(cell).get("x") + cell.getGeometry().getWidth()) {
					distToMaxX.replace(entry.getKey(), cellGeo.get(cell).get("x") + cell.getGeometry().getWidth());
					for (int i = entry.getKey() + 1; i <= md; i++) {
						if (distToMaxX.get(i) == -1) {
							distToMaxX.replace(i, cellGeo.get(cell).get("x") + cell.getGeometry().getWidth());
						}
					}
				}
			}
		}
	}
	
	// 蟄舌ｒ隕�縺�繧医≧縺ｫ隕ｪ縺ｮ螟ｧ縺阪＆繧貞､画峩
	public double resize(mxCell ancestor, double endX, double endY) {
		while (true) {
			boolean isChanging = false;
			
			if (ancestor.getGeometry().getWidth() < endX - cellGeo.get(ancestor).get("x") + MARGIN_WIDTH) {
				ancestor.getGeometry().setWidth(endX - cellGeo.get(ancestor).get("x") + MARGIN_WIDTH);
				isChanging = true;
			}
			if (ancestor.getGeometry().getHeight() < endY - cellGeo.get(ancestor).get("y") + MARGIN_HEIGHT) {
				ancestor.getGeometry().setHeight(endY - cellGeo.get(ancestor).get("y") + MARGIN_HEIGHT);
				isChanging = true;
			}
			if (isChanging) {
				graphModel.setGeometry(ancestor, ancestor.getGeometry());
			}
			
			endX = cellGeo.get(ancestor).get("x") + ancestor.getGeometry().getWidth();
			endY = cellGeo.get(ancestor).get("y") + ancestor.getGeometry().getHeight();
			
			if (roots.contains(ancestor)) {
				break;
			}
			ancestor = (mxCell) ancestor.getParent();
		}
		
		return endY;
	}
	
	// x蠎ｧ讓吶ｒ螟画峩
	public void relocateX(mxCell c, double nx, double dx) {
		c.getGeometry().setX(nx - dx);
		graphModel.setGeometry(c, (mxGeometry) c.getGeometry().clone());
		cellGeo.get(c).replace("x", nx);
	}
}