package edu.byu.cs.roots.opg.chart.selectvertical; import java.awt.Color; import java.awt.geom.Rectangle2D; import java.util.ArrayList; import java.util.HashMap; import java.util.LinkedList; import edu.byu.cs.roots.opg.chart.ShapeInfo; import edu.byu.cs.roots.opg.chart.cmds.DrawCmdFillRect; import edu.byu.cs.roots.opg.chart.cmds.DrawCmdMoveTo; import edu.byu.cs.roots.opg.chart.cmds.DrawCmdRelLineTo; import edu.byu.cs.roots.opg.chart.cmds.DrawCmdRoundRect; import edu.byu.cs.roots.opg.chart.cmds.DrawCmdText; import edu.byu.cs.roots.opg.model.Event; import edu.byu.cs.roots.opg.model.Individual; import edu.byu.cs.roots.opg.model.OpgOptions; import edu.byu.cs.roots.opg.model.OpgSession; public class AncesBox extends Box { //tree structure variables protected AncesBox father; protected AncesBox mother; protected Event marriage = null; public int maxGensOfTree; //this the maximum number of generations in this individual's tree (1 = self, 2 = self & parent(s), etc.) public int maxGensInTree; //this the maximum number of generations in this individual's tree (1 = self, 2 = self & parent(s), etc.) int gen; // the generation this box is a part of (zero based) int numInGen; //the vertical ordinal number (zero based) of this box in a generation int occuranceNum; //size/position variables double upperSubTreeHeight; double lowerSubTreeHeight; double hPos,vPos; double fatherVOffset; double motherVOffset; static double scaler; double minHeight; //the minimum height for the current configuration of the tree double upperRelBoxBound; //the upper bound of the individual box relative to the base position double lowerRelBoxBound; //the upper bound of the individual box relative to the base position static double boxWidth;//: change to some other way of keeping track of the boxes' widths? static ArrayList<Double> genBoxHeights; //box heights of generations static protected ArrayList<Double> genWidths; static protected ArrayList<BoxFormat> genFormats = new ArrayList<BoxFormat>(); //all duplicate ancestors on the current chart are stored in here by id static protected HashMap<String, Integer> duplicateMap = new HashMap<String, Integer>(); static protected int duplicateIndex = 1; Rectangle2D.Double boxInfo = new Rectangle2D.Double(); BoxDrawer drawBox; public AncesBox(Individual indi) { this.indi = indi; upperBounds = new ArrayList<Double>(1); lowerBounds = new ArrayList<Double>(1); drawBox = new BoxDrawer(BoxFormat.FORMATS.get(7), indi); } /** * Recursively builds a tree of Boxes representing an individual's genealogy without * duplicate subtrees. * @param curGen * @param genPositions * */ void buildBoxTree(ArrayList<ArrayList<AncesBox>> genPositions, int curGen) { maxGensInTree = 1; if (indi.father != null && indi.father.surname != null) { //add father father = new AncesBox(indi.father); //set generational position father.gen = curGen; if (genPositions.size() <= curGen) genPositions.add(new ArrayList<AncesBox>()); genPositions.get(curGen).add(father); father.numInGen = genPositions.get(curGen).size() - 1; if (!duplicateMap.containsKey(indi.father.id)) //not in tree yet { duplicateMap.put(indi.father.id, 0); //non-duplicate father.buildBoxTree(genPositions, curGen+1); maxGensInTree = Math.max(maxGensInTree, father.maxGensInTree + 1); } else { duplicateMap.put(indi.father.id, -1); //duplicate maxGensInTree +=1; } } if (indi.mother != null && indi.mother.surname != null) { //add mother mother = new AncesBox(indi.mother); //set generational position mother.gen = curGen; if (genPositions.size() <= curGen) genPositions.add(new ArrayList<AncesBox>()); genPositions.get(curGen).add(mother); mother.numInGen = genPositions.get(curGen).size() - 1; if (!duplicateMap.containsKey(indi.mother.id)) //not in tree yet { duplicateMap.put(indi.mother.id, 0); //non-duplicate mother.buildBoxTree(genPositions, curGen+1); maxGensInTree = Math.max(maxGensInTree, mother.maxGensInTree + 1); } else { duplicateMap.put(indi.mother.id, -1); //duplicate maxGensInTree +=1; } } } /** * Reset the duplicate map and the duplicate index used for labeling **/ public static void resetDuplicates() { duplicateMap = new HashMap<String, Integer>(); duplicateIndex = 1; } /** * Sets index number for duplicate individual. Expects duplicateMap is already be populated * where an id is mapped to -1 if there is a duplicate, and 0 otherwise. */ public void setDuplicateIndex() { if(duplicateMap.get(indi.id) == -1) duplicateMap.put(indi.id, duplicateIndex++); //new duplicate index } /** * Recursively calculates the minimum coordinates for this individual * (and its subtree(s)) based on the minimum font size * * @param options - chart options that contain the minimum font size * @param curGen - current generation of Individual in this AncesBox (usually starts as 0) * @param duplicateMap */ void calcCoords(int maxGen, int curGen) { if (curGen > maxGen) return; drawBox.update(genFormats.get(curGen)); double boxHeight = drawBox.getHeight(); //double boxHeight = genFormats.get(curGen).getMinHeight(); upperRelBoxBound = drawBox.getHeight()/2; lowerRelBoxBound = -drawBox.getHeight()/2 ; double vertSeperation = 0;//genFormats.get(curGen).getVerticalSpacing(); //: calculate vertical separation //calculate the dimensions for this box upperBounds = new ArrayList<Double>(maxGen - curGen); lowerBounds = new ArrayList<Double>(maxGen - curGen); //calculate first element of upper bounds array for tight fit upperBounds.add(new Double(boxHeight/2.0)); //calculate first element of lower bounds array for tight fit lowerBounds.add(new Double( -(boxHeight/2.0 + vertSeperation ))); upperSubTreeHeight = boxHeight/2.0; lowerSubTreeHeight = -upperSubTreeHeight; int maxGensOfSubTree = 0; if (curGen < maxGen) { if (father != null) { father.calcCoords(maxGen, curGen+1); //keep track of the maximum number of generations in each subTree maxGensOfSubTree = Math.max(maxGensOfSubTree, father.maxGensOfTree); } if (mother != null) { mother.calcCoords(maxGen, curGen+1); //keep track of the maximum number of generations in each subTree if (mother.maxGensOfTree > maxGensOfSubTree) maxGensOfSubTree = mother.maxGensOfTree; } } //set the maximun number of generations in this tree maxGensOfTree = maxGensOfSubTree + 1; //Tight fit (jigsaw puzzle like) intersection //calculate the distances between each of the parents by intersecting their subtrees if ((father != null || mother != null) && curGen < maxGen) { if (father == null) { //add gap onto bottom of lower parent to ensure that different sets of grandparents are spaced father than spouses mother.lowerBounds.set(0, mother.lowerBounds.get(0) - vertSeperation); motherVOffset = -3;////insert vertical offset here //calculate rest of upper and lower bounds array for (int i = 0; i < mother.upperBounds.size(); ++i) upperBounds.add(new Double(motherVOffset + mother.upperBounds.get(i))); for (int i = 0; i < mother.lowerBounds.size(); ++i) lowerBounds.add(new Double(motherVOffset + mother.lowerBounds.get(i))); upperSubTreeHeight = Math.max(upperSubTreeHeight, motherVOffset + mother.upperSubTreeHeight); lowerSubTreeHeight = Math.min(lowerSubTreeHeight, motherVOffset + mother.lowerSubTreeHeight); } else if (mother == null) { //add gap onto bottom of lower parent to ensure that different sets of grandparents are spaced father than spouses father.lowerBounds.set(0, father.lowerBounds.get(0) - vertSeperation); fatherVOffset = 3;////insert vertical offset here //calculate rest of upper and lower bounds array for (int i = 0; i < father.upperBounds.size(); ++i) upperBounds.add(new Double(fatherVOffset + father.upperBounds.get(i))); for (int i = 0; i < father.lowerBounds.size(); ++i) lowerBounds.add(new Double(fatherVOffset + father.lowerBounds.get(i))); upperSubTreeHeight = Math.max(upperSubTreeHeight, fatherVOffset + father.upperSubTreeHeight); lowerSubTreeHeight = Math.min(lowerSubTreeHeight, fatherVOffset + father.lowerSubTreeHeight); } else { //add gap onto bottom of lower parent to ensure that different sets of grandparents are spaced father than spouses mother.lowerBounds.set(0, mother.lowerBounds.get(0) - vertSeperation ); //calculate distance inbetween mother and father and offsets from child double dist = calcIntersectDist2( father , mother, curGen); fatherVOffset = dist / 2.0; motherVOffset = -fatherVOffset; //calculate rest of upper bounds array int i; for (i = 0; i < maxGensOfTree - 1 && i < father.upperBounds.size(); ++i) upperBounds.add(new Double(fatherVOffset + father.upperBounds.get(i))); for ( ; i < maxGensOfTree - 1 && i < mother.upperBounds.size(); ++i) upperBounds.add(new Double(motherVOffset + mother.upperBounds.get(i))); //calculate rest of lower bounds array for (i = 0; i < maxGensOfTree - 1 && i < mother.lowerBounds.size(); ++i) lowerBounds.add(new Double(motherVOffset + mother.lowerBounds.get(i))); for ( ; i < maxGensOfTree - 1 && i < father.lowerBounds.size(); ++i) lowerBounds.add(new Double(fatherVOffset + father.lowerBounds.get(i))); //upperSubTreeHeight = Math.max(upperSubTreeHeight, upperSubTreeHeight + fatherVOffset + father.upperSubTreeHeight); //lowerSubTreeHeight = Math.min(lowerSubTreeHeight, lowerSubTreeHeight + motherVOffset + mother.lowerSubTreeHeight); upperSubTreeHeight = Math.max(Math.max(upperSubTreeHeight, fatherVOffset + father.upperSubTreeHeight), motherVOffset + mother.upperSubTreeHeight); lowerSubTreeHeight = Math.min(Math.min(lowerSubTreeHeight, motherVOffset + mother.lowerSubTreeHeight), fatherVOffset + father.lowerSubTreeHeight); } } } //----------------------------------------------------------------------- /** * this method finds the closest distance two individuals (with their subtrees) can be while only touchng at * one point (assuming the bounds for all generations are at different distances) * * @param father - the individual on top * @param mother - the individual on bottom */ public static double calcIntersectDist(AncesBox father, AncesBox mother, int boxGen) { double maxDist = 0; //int maxGen = Math.min(father.maxGensOfTree, mother.maxGensOfTree); int maxGen = Math.min(father.upperBounds.size(), mother.upperBounds.size() ); int startGen = 0; //find the max distance between them (the closest they can be while "intesecting" only at one point) for (int i = startGen; i < maxGen; ++i) { double curDist = mother.upperBounds.get(i) - father.lowerBounds.get(i) + genFormats.get(boxGen+i+1).getVerticalSpacing(); if (curDist > maxDist) maxDist = curDist; } return maxDist; } /** * Returns the closest distance between two subtrees. Uses the distance between peer generation and the next. */ public static double calcIntersectDist2(AncesBox father, AncesBox mother, int boxGen) { double maxDist = 0; //int maxGen = Math.min(father.maxGensOfTree, mother.maxGensOfTree); int maxGen = Math.min(father.upperBounds.size(), mother.upperBounds.size() ); int startGen = 0; //find the max distance between them (the closest they can be while "intesecting" only at one point) for (int i = startGen; i < maxGen; ++i) { double curDist = mother.upperBounds.get(i) - father.lowerBounds.get(i) + genFormats.get(boxGen+i+1).getVerticalSpacing(); if(father.upperBounds.size() > i+1) { double nextDist = mother.upperBounds.get(i) - father.lowerBounds.get(i+1) + genFormats.get(boxGen+i+2).getVerticalSpacing(); curDist = Math.max(curDist, nextDist); } if(mother.lowerBounds.size() > i+1) { double nextDist = mother.upperBounds.get(i+1) - father.lowerBounds.get(i) + genFormats.get(boxGen+i+2).getVerticalSpacing(); curDist = Math.max(curDist, nextDist); } if (curDist > maxDist) maxDist = curDist; } return maxDist; } //----------------------------------------------------------------------- // public void setRelativePositions(int curGen, int maxGen) // { // //set father box position relative to this box // if (father != null && curGen < maxGen) // { // father.vPos = vPos + fatherVOffset; // father.setRelativePositions(curGen+1, maxGen); // } // //set mother box position relative to this box // if (mother != null) // { // mother.vPos = vPos + motherVOffset; // mother.setRelativePositions(curGen+1, maxGen); // } // } //----------------------------------------------------------------------- public void setIntrusions(boolean[] canIntrudeGen, int curGen) { int maxGen = canIntrudeGen.length; if (curGen < maxGen) { if (father != null && mother != null) { //check if there is enough room to intrude if (((fatherVOffset*scaler + father.lowerRelBoxBound) - genFormats.get(curGen).getVerticalSpacing()*4 + (-motherVOffset*scaler - mother.upperRelBoxBound)) < upperRelBoxBound - lowerRelBoxBound) canIntrudeGen[curGen] = false; } else canIntrudeGen[curGen] = false; } if (father != null && curGen < maxGen) father.setIntrusions(canIntrudeGen, curGen+1); if (mother != null && curGen < maxGen) mother.setIntrusions(canIntrudeGen, curGen+1); } //----------------------------------------------------------------------- double setHeight(double newHeight) { //set the scaler so that chart will be the right height double curHeight = upperSubTreeHeight - lowerSubTreeHeight; //curHeight = Math. if (curHeight < minHeight) throw new IllegalArgumentException ("Cannot scale chart smaller than minimum"); scaler = newHeight/minHeight; return scaler; } //----------------------------------------------------------------------- void setScaler(double newScaler) { if (newScaler < 0.99) throw new IllegalArgumentException ("Cannot scale chart smaller than minimum"); else scaler = newScaler; } //----------------------------------------------------------------------- void drawAncesRootTree(ChartMargins chart, VerticalChartOptions options, ArrayList<ArrayList<AncesBox>> genPositions, double x, double y) { //calculate box width (constant across all generations for now) //boxWidth = (chart.getXExtent()/(options.getAncesGens()+1)) * 0.9; //boxWidth = chart.getXExtent() / ((11.0/10.0 * (options.getAncesGens() + options.getDescGens())) + 1 ); // double[] smallestSizePerGen = new double[options.getAncesGens()+1];//keep track of the smallest box size per generation boolean[] canIntrudeGen = new boolean[options.getAncesGens()]; for (int i = 0; i < canIntrudeGen.length; ++i) canIntrudeGen[i] = options.isAllowIntrusion(); setIntrusions(canIntrudeGen, 0); ///----------------------------------------- //start recursion on self if (!(options.getAncesGens() == 0 && options.getDescGens() > 0) || (options.getAncesGens() == 0 && options.isIncludeSpouses())) drawAncesTreeRec(0, chart, options, canIntrudeGen, x,y); } //----------------------------------------------------------------------- void drawAncesTreeRec(int curGen, ChartMargins chart, VerticalChartOptions options, boolean[] canIntrudeGen, double x, double y) { //draw self box //increase box Width and height to allow intrusion of a box into the next generation double myBoxWidth = boxWidth; boolean intrudes = false; if (curGen < options.getAncesGens() && canIntrudeGen[curGen]) intrudes = canIntrudeGen[curGen]; drawBox.setIntrude(intrudes); //update text layout if (!(curGen == 0 && options.getAncesGens() > 0 && options.getDescGens() > 0) || (options.isIncludeSpouses() && options.getDescGens() > 0)) drawBox(chart, 0, options, chart.xOffset(x), chart.yOffset(y), myBoxWidth, (upperRelBoxBound - lowerRelBoxBound)); if (options.getAncesGens() > curGen) { if (father != null) { //draw father (recursively) try { father.drawAncesTreeRec(curGen+1, chart, options, canIntrudeGen, x + boxWidth*1.1, y + yXForm(fatherVOffset)); } catch (Exception e) { // Auto-generated catch block e.printStackTrace(); } //draw lines to father if (!intrudes) { chart.addDrawCommand(new DrawCmdMoveTo(chart.xOffset(x + boxWidth), chart.yOffset(y))); chart.addDrawCommand(new DrawCmdRelLineTo(boxWidth*0.05,0.0,1, Color.BLACK)); chart.addDrawCommand(new DrawCmdRelLineTo(0.0,yXForm(fatherVOffset),1, Color.BLACK)); chart.addDrawCommand(new DrawCmdRelLineTo(boxWidth*0.05,0.0,1, Color.BLACK)); } else { if(curGen==0 && options.getDescGens() == 0){ chart.addDrawCommand(new DrawCmdMoveTo(chart.xOffset(x + boxWidth/2.0), chart.yOffset(y + upperRelBoxBound))); chart.addDrawCommand(new DrawCmdRelLineTo(0.0,yXForm(fatherVOffset)- upperRelBoxBound ,1, Color.black)); chart.addDrawCommand(new DrawCmdRelLineTo(boxWidth/2.0 + boxWidth*0.1, 0.0,1, Color.black)); } else if (curGen == 0){ chart.addDrawCommand(new DrawCmdMoveTo(chart.xOffset(x + boxWidth/2.0), chart.yOffset(y))); chart.addDrawCommand(new DrawCmdRelLineTo(0.0,yXForm(fatherVOffset) ,1, Color.black)); chart.addDrawCommand(new DrawCmdRelLineTo(boxWidth/2.0 + boxWidth*0.1, 0.0,1, Color.black)); } else{ chart.addDrawCommand(new DrawCmdMoveTo(chart.xOffset(x + boxWidth/2.0), chart.yOffset(y + upperRelBoxBound))); chart.addDrawCommand(new DrawCmdRelLineTo(0.0,yXForm(fatherVOffset) - father.upperRelBoxBound,1, Color.BLACK)); chart.addDrawCommand(new DrawCmdRelLineTo(boxWidth/2.0 + boxWidth*0.1, 0.0,1, Color.BLACK)); } } } if (mother != null) { //draw mother (recursively) mother.drawAncesTreeRec(curGen + 1, chart, options, canIntrudeGen, x + boxWidth*1.1, y + yXForm(motherVOffset)); //draw lines to mother if (!intrudes) { chart.addDrawCommand(new DrawCmdMoveTo(chart.xOffset(x + boxWidth), chart.yOffset(y))); chart.addDrawCommand(new DrawCmdRelLineTo(boxWidth*0.05,0.0,1, Color.BLACK)); chart.addDrawCommand(new DrawCmdRelLineTo(0.0, yXForm(motherVOffset),1, Color.BLACK)); chart.addDrawCommand(new DrawCmdRelLineTo(boxWidth*0.05,0,1, Color.BLACK)); } else { if(curGen == 0 && options.getDescGens() == 0){ chart.addDrawCommand(new DrawCmdMoveTo(chart.xOffset(x + boxWidth/2.0), chart.yOffset(y + lowerRelBoxBound))); chart.addDrawCommand(new DrawCmdRelLineTo(0.0,yXForm(motherVOffset) - lowerRelBoxBound,1, Color.BLACK)); chart.addDrawCommand(new DrawCmdRelLineTo(boxWidth/2.0 + boxWidth*0.1, 0.0,1, Color.BLACK)); } else if(curGen == 0){ chart.addDrawCommand(new DrawCmdMoveTo(chart.xOffset(x + boxWidth/2.0), chart.yOffset(y))); chart.addDrawCommand(new DrawCmdRelLineTo(0.0,yXForm(motherVOffset),1, Color.BLACK)); chart.addDrawCommand(new DrawCmdRelLineTo(boxWidth/2.0 + boxWidth*0.1, 0.0,1, Color.BLACK)); } else{ chart.addDrawCommand(new DrawCmdMoveTo(chart.xOffset(x + boxWidth/2.0), chart.yOffset(y + lowerRelBoxBound))); chart.addDrawCommand(new DrawCmdRelLineTo(0.0,yXForm(motherVOffset) - lowerRelBoxBound,1, Color.BLACK)); chart.addDrawCommand(new DrawCmdRelLineTo(boxWidth/2.0 + boxWidth*0.1, 0.0,1, Color.BLACK)); } } } } } //----------------------------------------------------------------------- void drawBox (ChartMargins chart, double fontSize, VerticalChartOptions options, double x, double y, double width, double height) { //Font font = options.getFont().font.deriveFont((float)fontSize); //abbreviate and fit individual's info into box String dupLabel = (duplicateMap.get(indi.id) != 0)? (" <" + duplicateMap.get(indi.id) + ">") : ""; double totalHeight = height; totalHeight = drawBox.getHeight(); width = drawBox.getWidth(); //update height of ancesBox to match height with text //upperRelBoxBound = totalHeight/2.0; //lowerRelBoxBound = -upperRelBoxBound; //144 126 108 90 72 54 36 18 //DRAW BOX double lineWidth = (options.isBoxBorder())? 1 : 0; //chart.addDrawCommand(new DrawCmdRoundRect(x, y-height/2.0, width ,height, 1, 5, Color.BLACK, Color.black)); //System.out.println(Double.toString(totalHeight)+" "+ Double.toString(height)); if (options.isRoundedCorners()) chart.addDrawCommand(new DrawCmdRoundRect(x, y-totalHeight/2.0, width ,totalHeight, lineWidth, 5, Color.BLACK, options.getAncesScheme().getColor(indi.id), boxInfo)); else chart.addDrawCommand(new DrawCmdFillRect(x, y-totalHeight/2.0, width ,totalHeight, lineWidth, Color.BLACK, options.getAncesScheme().getColor(indi.id), boxInfo)); //draw content of box drawBox.drawTextBox(chart, x, y, dupLabel); chart.addDrawCommand(new DrawCmdMoveTo(x-10,y)); chart.addDrawCommand(new DrawCmdText(drawBox.getBoxFormatIndex()+"")); } /** * Recursively returns a LinkedList of all ancestors displayed on the chart. * @param the list that will get built through recursion * @param the starting generation, usually 0 * @param the max generation to search to, chosen by user * @return a LinkedList of ShapeInfos, storing all visible ancestors */ public LinkedList<ShapeInfo> getBoxes(LinkedList<ShapeInfo> list, int curGen, int maxGen, OpgSession session) { //If an ancestor is not supposed to be drawn, still sticks them on the list to check, since their triangle can be clicked, however //Sends the current generation in as the max, so it wont recurse further. OpgOptions opgOptions = session.getOpgOptions(); if ((father != null) && (curGen < maxGen)) { if(!opgOptions.isCollapsed(indi.father)) list = father.getBoxes(list, curGen + 1, maxGen, session); else list = father.getBoxes(list, maxGen, maxGen, session); } if ((mother != null) && (curGen < maxGen)) { if(!opgOptions.isCollapsed(indi.mother)) list = mother.getBoxes(list, curGen + 1, maxGen, session); else list = mother.getBoxes(list, maxGen, maxGen, session); } if (boxInfo == null) System.out.println("ERROR: Null box info on Ancestor"); else list.add(new ShapeInfo(boxInfo, indi, gen, true)); return list; } /** * Recursively determines if the passed in point clicks any of the ancestors visible. * @param x coord of click * @param y coord of click * @param current generation, usually 0 * @param max generation displayed, chosen by user * @return the ShapeInfo of whoever was clicked, null if no intersect */ public ShapeInfo checkIntersect(double x, double y, int curGen, int maxGen, OpgSession session) { if(boxInfo == null){ System.out.println("debug: WARNING - you tried to find the intersect on" + " an AncesBox for " + indi.namePrefix + ",whose boxInfo == " + "null -> this would result nullPointerException and should be fixed!"); //Andrew - found this bug when looking at my family gedcom file for Elizabeth LaFlesh // next time we meet I'll try to recreate it and we can look at it. return null; } OpgOptions opgOptions = session.getOpgOptions(); ShapeInfo retVal = null; if (boxInfo.contains(x, y)) return new ShapeInfo(boxInfo, indi, gen, true); else { //If an ancestor is not supposed to be drawn, still sticks them on the list to check, since their triangle can be clicked, however //Sends the current generation in as the max, so it wont recurse further. if ((father != null) && (curGen < maxGen)) { if(!opgOptions.isCollapsed(indi.father)) retVal = father.checkIntersect(x, y, curGen + 1, maxGen, session); else retVal = father.checkIntersect(x, y, maxGen, maxGen, session); } if ((retVal == null) && (mother != null) && (curGen < maxGen)) { if(!opgOptions.isCollapsed(indi.mother)) retVal = mother.checkIntersect(x, y, curGen + 1, maxGen, session); else retVal = mother.checkIntersect(x, y, maxGen, maxGen, session); } return retVal; } } //----------------------------------------------------------------------- double yXForm(double y) { return y * scaler; } }