/* * Copyright 2004-2010 Information & Software Engineering Group (188/1) * Institute of Software Technology and Interactive Systems * Vienna University of Technology, Austria * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.ifs.tuwien.ac.at/dm/somtoolbox/license.html * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package at.tuwien.ifs.somtoolbox.apps.viewer.controls.psomserver; import java.awt.BasicStroke; import java.awt.Color; import java.awt.geom.Point2D; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import edu.umd.cs.piccolo.PNode; import edu.umd.cs.piccolo.nodes.PPath; import edu.umd.cs.piccolo.util.PBounds; import edu.umd.cs.piccolox.nodes.PLine; import edu.umd.cs.piccolox.util.LineShape; import at.tuwien.ifs.somtoolbox.apps.viewer.CommonSOMViewerStateData; import at.tuwien.ifs.somtoolbox.apps.viewer.GeneralUnitPNode; import at.tuwien.ifs.somtoolbox.apps.viewer.MapPNode; import at.tuwien.ifs.somtoolbox.data.InputDatum; /** * @author Jakob Frank * @version $Id: PathMerger.java 3590 2010-05-21 10:43:45Z mayer $ */ public class PathMerger { private final MapPNode map; private PNode root, borderRoot, connsRoot, circlesRoot; private List<PNode> drawedStuff; private boolean debug; public void setDebug(boolean debug) { this.debug = debug; } public PathMerger(MapPNode map) { this(map, false); } public PathMerger(MapPNode map, boolean debug) { this.map = map; this.debug = debug; this.drawedStuff = new LinkedList<PNode>(); root = new PNode(); borderRoot = new PNode(); connsRoot = new PNode(); circlesRoot = new PNode(); root.addChild(borderRoot); root.addChild(connsRoot); root.addChild(circlesRoot); map.addChild(root); } public void unitBasedMerge(PNode[] paths) { if (paths.length != 2) { return; } deleteAllDrawnStuff(); List<GeneralUnitPNode> u1 = convertPathToUnits(paths[0]); List<GeneralUnitPNode> u2 = convertPathToUnits(paths[1]); for (GeneralUnitPNode unit1 : u1) { GeneralUnitPNode unit2 = findClosest(unit1, u2); System.out.printf("Pair: %d/%d - %d/%d%n", unit1.getUnit().getXPos(), unit1.getUnit().getYPos(), unit2.getUnit().getXPos(), unit2.getUnit().getYPos()); float x1 = (float) (unit1.getX() + unit1.getWidth() / 2); float y1 = (float) (unit1.getY() + unit1.getHeight() / 2); float x2 = (float) (unit2.getX() + unit2.getWidth() / 2); float y2 = (float) (unit2.getY() + unit2.getHeight() / 2); PPath p = PPath.createLine(x1, y1, x2, y2); p.setPickable(false); p.setStrokePaint(Color.white); drawedStuff.add(p); paths[0].addChild(p); } for (GeneralUnitPNode unit1 : u2) { GeneralUnitPNode unit2 = findClosest(unit1, u1); System.out.printf("Pair: %d/%d - %d/%d%n", unit1.getUnit().getXPos(), unit1.getUnit().getYPos(), unit2.getUnit().getXPos(), unit2.getUnit().getYPos()); float x1 = (float) (unit1.getX() + unit1.getWidth() / 2); float y1 = (float) (unit1.getY() + unit1.getHeight() / 2); float x2 = (float) (unit2.getX() + unit2.getWidth() / 2); float y2 = (float) (unit2.getY() + unit2.getHeight() / 2); PPath p = PPath.createLine(x1, y1, x2, y2); p.setPickable(false); p.setStrokePaint(Color.black); drawedStuff.add(p); paths[1].addChild(p); } } public void deleteAllDrawnStuff() { while (drawedStuff.size() > 0) { drawedStuff.remove(0).removeFromParent(); } root.removeAllChildren(); } public void newIntermediateMapMerge(PNode[] paths) { if (paths.length != 2) { return; } deleteAllDrawnStuff(); PLine l1 = (PLine) paths[0].getChild(0); PLine l2 = (PLine) paths[1].getChild(0); PLine border = new PLine(); Point2D s1 = l1.getPoint(0, new Point2D.Double()); int i1 = 0, i2 = 0; double minD = Double.MAX_VALUE; for (int i = 0; i < l2.getPointCount(); i++) { double curD = s1.distanceSq(l2.getPoint(i, new Point2D.Double())); if (curD < minD) { minD = curD; i2 = i; } } for (; i1 < l1.getPointCount() && i2 < l2.getPointCount(); i1++, i2++) { Point2D middle = getMiddlePoint(l1.getPoint(i1, new Point2D.Double()), l2.getPoint(i2, new Point2D.Double())); // System.out.printf("LinePoint: %f/%f%n", middle.getX() / map.getWidth(), middle.getY() / map.getHeight()); border.addPoint(border.getPointCount(), middle.getX(), middle.getY()); } border.setStroke(new BasicStroke(7, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND)); border.setStrokePaint(Color.black); drawedStuff.add(border); borderRoot.addChild(border); } public void newIntermediateInputSpaceMerge(PNode[] paths) { if (paths.length != 2) { return; } deleteAllDrawnStuff(); List<GeneralUnitPNode> ul1 = convertPathToUnits(paths[0]); List<GeneralUnitPNode> ul2 = convertPathToUnits(paths[1]); PLine border = new PLine(); int i1 = 0, i2 = 0; GeneralUnitPNode s1 = ul1.get(i1); double minD = Double.MAX_VALUE; for (int i = 0; i < ul2.size(); i++) { double curD = distSq(s1.getUnit().getWeightVector(), ul2.get(i).getUnit().getWeightVector()); if (curD < minD) { minD = curD; i2 = i; } } CommonSOMViewerStateData state = CommonSOMViewerStateData.getInstance(); for (; i1 < ul1.size() && i2 < ul2.size(); i1++, i2++) { double[] m1 = ul1.get(i1).getUnit().getWeightVector(); double[] m2 = ul2.get(i2).getUnit().getWeightVector(); double[] middle = getMiddlePoint(m1, m2); GeneralUnitPNode m = map.getUnit(state.growingLayer.getWinner(new InputDatum("", middle))); // System.out.println("AddPoint"); border.addPoint(border.getPointCount(), m.getX() + m.getWidth() / 2, m.getY() + m.getHeight() / 2); } border.setStroke(new BasicStroke(7, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND)); border.setStrokePaint(Color.black); drawedStuff.add(border); borderRoot.addChild(border); } private double dist(double[] d1, double[] d2) { return Math.sqrt(distSq(d1, d2)); } private double distSq(double[] d1, double[] d2) { double res = 0; for (int i = 0; i < d1.length; i++) { res += Math.pow(d1[i] - d2[i], 2); } return res; } private double[] getMiddlePoint(double[] d1, double[] d2) { double[] res = new double[d1.length]; double dist = dist(d1, d2) / 2; for (int i = 0; i < res.length; i++) { res[i] = d1[i] + dist * (d1[i] - d2[i]); } return res; } public void lineBasedMerge(PNode[] paths) { if (paths.length != 2) { return; } deleteAllDrawnStuff(); PLine l1 = (PLine) paths[0].getChild(0); PLine l2 = (PLine) paths[1].getChild(0); PLine border = new PLine(); findBorder(l1, l2, Color.white, border); findBorder(l2, l1, Color.white, border); Point2D s1 = l1.getPoint(0, new Point2D.Double()); Point2D s2 = l2.getPoint(0, new Point2D.Double()); Point2D e2 = l2.getPoint(l2.getPointCount() - 1, new Point2D.Double()); Point2D current; if (s1.distance(s2) < s1.distance(e2)) { current = getMiddlePoint(s1, s2); } else { current = getMiddlePoint(s1, e2); } LinkedList<Point2D> borderPointList = new LinkedList<Point2D>(); for (int i = 0; i < border.getPointCount(); i++) { borderPointList.add(border.getPoint(i, new Point2D.Double())); } PPath startPoint = createCircle(current, 7f); border = new PLine(); border.addPoint(0, current.getX(), current.getY()); while (borderPointList.size() > 0) { Point2D next = findClosest(current, borderPointList); border.addPoint(border.getChildrenCount(), next.getX(), next.getY()); borderPointList.remove(next); current = next; } border.setStroke(new BasicStroke(7, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND)); border.setStrokePaint(Color.black); startPoint.setStrokePaint(Color.white); if (debug) { drawedStuff.add(startPoint); border.addChild(startPoint); } drawedStuff.add(border); borderRoot.addChild(border); } /** * Find the border between two lines. * * @param l1 first Line * @param l2 second Line * @param c {@link Color} of the border * @param border where to add the border */ private void findBorder(PLine l1, PLine l2, Color c, PLine border) { float width = ((BasicStroke) l1.getStroke()).getLineWidth(); BasicStroke s = new BasicStroke(2, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND); LineShape ls = l1.getLineReference(); for (int i = 0; i < ls.getPointCount(); i++) { Point2D p1 = ls.getPoint(i, new java.awt.geom.Point2D.Double()); Point2D p2 = findClosest(p1, l2); Point2D middle = getMiddlePoint(p1, p2); border.addPoint(border.getChildrenCount(), middle.getX(), middle.getY()); if (debug) { PLine l = new PLine(); l.addPoint(0, p1.getX(), p1.getY()); l.addPoint(1, p2.getX(), p2.getY()); l.setStroke(s); l.setStrokePaint(c); l.setTransparency(.4f); drawedStuff.add(l); l1.addChild(l); PPath m = createCircle(middle, width / 4); m.setTransparency(.4f); m.setStrokePaint(Color.red); drawedStuff.add(m); l1.addChild(m); PPath a = createCircle(p1, width / 2); a.setTransparency(.4f); a.setStroke(s); a.setStrokePaint(c); a.removeAllChildren(); drawedStuff.add(a); l1.addChild(a); } } } /** * Create a circle {@link PNode} * * @param center the center of the circle * @param radius the radius (size) of the circle * @return a round {@link PNode} */ private PPath createCircle(Point2D center, float radius) { return PPath.createEllipse((float) center.getX() - radius, (float) center.getY() - radius, 2 * radius, 2 * radius); } /** * Get the middle point between the Points. * * @param p1 first Point * @param p2 second Point * @return the Point in the middle */ private Point2D getMiddlePoint(Point2D p1, Point2D p2) { return new java.awt.geom.Point2D.Double(p1.getX() + .5f * (p2.getX() - p1.getX()), p1.getY() + .5f * (p2.getY() - p1.getY())); } private Point2D findClosest(Point2D point, PLine candidates) { Point2D closest = null, current = null; double minDist = Double.MAX_VALUE; LineShape ls = candidates.getLineReference(); for (int i = 0; i < ls.getPointCount(); i++) { current = ls.getPoint(i, new java.awt.geom.Point2D.Double()); double d = point.distanceSq(current); if (d < minDist) { minDist = d; closest = current; } } return closest; } private Point2D findClosest(Point2D point, List<Point2D> candidates) { Point2D closest = null, current = null; double minDist = Double.MAX_VALUE; for (int i = 0; i < candidates.size(); i++) { current = candidates.get(i); double d = point.distanceSq(current); if (d < minDist) { minDist = d; closest = current; } } return closest; } private <A extends PNode> A findClosest(A unit, List<A> candidates) { A closest = null; double minDist = Double.MAX_VALUE; for (A c : candidates) { double curDist = Math.pow(c.getX() - unit.getX(), 2) + Math.pow(c.getY() - unit.getY(), 2); if (curDist < minDist) { minDist = curDist; closest = c; } } return closest; } private List<GeneralUnitPNode> convertPathToUnits(PNode node) { LinkedList<GeneralUnitPNode> units = new LinkedList<GeneralUnitPNode>(); Iterator<?> children = node.getChildrenIterator(); GeneralUnitPNode lastU1 = null; while (children.hasNext()) { PNode child = (PNode) children.next(); if (child instanceof PLine) { PLine line = (PLine) child; float width = ((BasicStroke) line.getStroke()).getLineWidth(); Point2D p = null; for (int i = 0; i < line.getPointCount(); i++) { p = line.getPoint(i, p); GeneralUnitPNode u = map.getGeneralUnitPNodeAtPos(p); if (u != lastU1) { units.add(u); lastU1 = u; } if (debug) { PLine l = new PLine(); l.addPoint(0, p.getX(), p.getY()); l.addPoint(1, u.getX() + u.getWidth() / 2, u.getY() + u.getHeight() / 2); l.setStrokePaint(Color.white); l.setTransparency(.4f); l.setStroke(new BasicStroke(width / 4, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND)); drawedStuff.add(l); line.addChild(l); PPath a = createCircle(p, width / 2); a.setTransparency(.4f); a.setStroke(new BasicStroke(width / 4)); a.setStrokePaint(Color.white); a.removeAllChildren(); drawedStuff.add(a); line.addChild(a); } } } } return units; } @SuppressWarnings("unused") private String printNode(PNode node) { PBounds b = node.getBounds(); return String.format("%s (%.2f %.2f %.2f %.2f)", node.getClass().getSimpleName(), b.x, b.y, b.height, b.width); } public void highlightIntersectingUnits(PNode node, boolean target) { for (GeneralUnitPNode n : convertPathToUnits(node)) { n.setSelected(target); } } public void concatPaths(PNode[] paths) { if (paths.length != 2) { return; } deleteAllDrawnStuff(); PLine l1 = (PLine) paths[0].getChild(0); PLine l2 = (PLine) paths[1].getChild(0); Point2D s1 = l1.getPoint(0, new Point2D.Double()); Point2D e1 = l1.getPoint(l1.getPointCount() - 1, new Point2D.Double()); Point2D s2 = l2.getPoint(0, new Point2D.Double()); Point2D e2 = l2.getPoint(l2.getPointCount() - 1, new Point2D.Double()); Point2D from, to; if (e1.distanceSq(s2) < e2.distanceSq(s1)) { // 1 after 2 from = e1; to = s2; } else { // 2 after 1 from = e2; to = s1; } final int steps = 5; double xStep = (to.getX() - from.getX()) / steps; double yStep = (to.getY() - from.getY()) / steps; // TODO: Allow routing in different spaces... PLine trans = new PLine(); for (int i = 0; i <= steps; i++) { trans.addPoint(i, from.getX() + xStep * i, from.getY() + yStep * i); if (debug && i != 0 && i != steps) { PPath z = createCircle(trans.getPoint(i, new Point2D.Double()), ((BasicStroke) l1.getStroke()).getLineWidth() / 2); z.setStrokePaint(Color.BLACK); z.setPaint(null); circlesRoot.addChild(z); } } trans.addChild(createCircle(trans.getPoint(0, new Point2D.Double()), ((BasicStroke) l1.getStroke()).getLineWidth() / 2 * 1.25f)); trans.setStroke(l1.getStroke()); trans.setStrokePaint(Color.MAGENTA); borderRoot.addChild(trans); if (debug) { PPath s = createCircle(from, ((BasicStroke) l1.getStroke()).getLineWidth() / 2); s.setStrokePaint(Color.WHITE); s.setPaint(Color.WHITE); s.setTransparency(.7f); circlesRoot.addChild(s); PPath e = createCircle(to, ((BasicStroke) l1.getStroke()).getLineWidth() / 2); e.setStrokePaint(Color.BLACK); e.setPaint(Color.BLACK); e.setTransparency(.7f); circlesRoot.addChild(e); } } public PLine getBorderLine() { if (borderRoot.getChildrenCount() == 1) { return (PLine) borderRoot.getChild(0); } return null; } }