package edu.byu.cs.roots.opg.chart.circ; import java.awt.Color; import java.awt.Font; import java.util.ArrayList; import java.util.LinkedList; import javax.swing.JPanel; import org.apache.log4j.Logger; import edu.byu.cs.roots.opg.chart.ChartDrawInfo; import edu.byu.cs.roots.opg.chart.ChartMaker; import edu.byu.cs.roots.opg.chart.ChartOptions; import edu.byu.cs.roots.opg.chart.ShapeInfo; import edu.byu.cs.roots.opg.chart.cmds.DrawCmdFilledCircle; import edu.byu.cs.roots.opg.chart.cmds.DrawCmdMoveTo; import edu.byu.cs.roots.opg.chart.cmds.DrawCmdSetFont; import edu.byu.cs.roots.opg.chart.preset.templates.StylingBoxScheme; import edu.byu.cs.roots.opg.gui.OnePageMainGui; import edu.byu.cs.roots.opg.model.Gender; import edu.byu.cs.roots.opg.model.ImageFile; import edu.byu.cs.roots.opg.model.Individual; import edu.byu.cs.roots.opg.model.OpgOptions; import edu.byu.cs.roots.opg.model.OpgSession; import edu.byu.cs.roots.opg.model.PaperWidth; import edu.byu.cs.roots.opg.util.DataFitter; public class CircularChartMaker implements ChartMaker { private static final long serialVersionUID = 1L; public static Logger log = Logger.getLogger(CircularChartMaker.class); private float boxWidth = 90, generationWidth = 130; private int ancesgens = 20, descgens = 7; private float spaceper = .9f; private int ancestorDescendantSeperation; //The radius of the root circle private float rootRadius = 50; private int marginw = 72; private int marginl = 72; private ChartDrawInfo cache = null; private CircularOptions options = null; private float root_angle = 45; private float spouse_root_angle = 15; private float linewidth = 2; private boolean isPrimaryMaker = false; protected ArrayList<ImageFile> images = new ArrayList<ImageFile>(); public ChartOptions convertToSpecificOptions(ChartOptions options) { return new CircularOptions(options); } public void convertOpgOptions(OpgOptions options){ } public ChartDrawInfo getChart(ChartOptions ops, OpgSession session) { if(!session.isChanged() && cache != null) return cache; OpgOptions opgOptions = session.getOpgOptions(); options = (CircularOptions) ops; ChartDrawInfo chart; boxWidth = options.boxWidth; generationWidth = options.boxSpacing+boxWidth; rootRadius = options.rootRadius; //check ancestor generations int maxAncesGens = findMaxDepth(options.getRoot()); for(int i = 1; i < maxAncesGens + 1 ; i++){ //circumference of half circle divided by number of boxes at that generation. if(opgOptions.getMinFontSize() > (((i * generationWidth) + rootRadius)* Math.PI) / (Math.pow(2,i))){ maxAncesGens = i-1; break; } } int maxDescGens = findMaxDescGens(options.getRoot()); // double upperRadiusMaximum = (maxAncesGens * generationWidth) + rootRadius; // double lowerRadiusMaximum = (maxDescGens * generationWidth) + rootRadius; // double horizontalRadiusMaximum = (Math.max(maxAncesGens, maxDescGens) * generationWidth) + rootRadius; // double paperWidth = (options.isLandscape() ? options.getPaperLength() : options.getPaperWidth().width); /* while (upperRadiusMaximum + rootRadius > options.getPaperLength() || (horizontalRadiusMaximum * 2) > options.getPaperWidth().width){ maxAncesGens--; upperRadiusMaximum = maxAncesGens * (generationWidth + rootRadius); horizontalRadiusMaximum = Math.max(maxAncesGens, maxDescGens) * (generationWidth + rootRadius); }*/ opgOptions.setMaxAncesSlider(maxAncesGens, isPrimaryMaker); //options.setMaxAncesGens(20); if(options.getAncesGens() > opgOptions.getMaxAncesSlider()) options.setAncesGens(opgOptions.getMaxAncesSlider(), session); opgOptions.setMaxDescSlider(maxDescGens, isPrimaryMaker); ancesgens = options.getAncesGens(); descgens = options.getDescGens(); if(descgens > 0) ancestorDescendantSeperation = 10; else ancestorDescendantSeperation = 0; //soon I will change this to fit to the text size int ancesheight = (int)(generationWidth * ancesgens + rootRadius); int descheight = (int)(generationWidth * descgens + rootRadius); double width, height; // float spread = 1.5f; int marginy; // int marginx; if(options.isLandscape()){ PaperWidth temp = PaperWidth.findClosestFit(ancesheight+descheight + 2*marginw); if(options.getPaperWidth() != null){ if(temp.width < options.getPaperWidth().width) temp = options.getPaperWidth(); } options.setPaperWidth(temp); height = (int)( options.getPaperWidth().width); width = 2*(Math.max(ancesheight,descheight) + marginl); options.setMinPaperLength(72.0); width = Math.max(options.getPaperLength(), width); options.setPaperLength(width); chart = new ChartDrawInfo((int) width,(int) height); // marginx = marginl; marginy = marginw; } else{ //figure out the proper width as the maximum of the the ancestor and descendant heights PaperWidth temp = PaperWidth.findClosestFit(2*(Math.max(ancesheight,descheight) + marginw)); // System.out.println("size = " + temp.width); if(options.getPaperWidth() != null){ if(temp.width < options.getPaperWidth().width) temp = options.getPaperWidth(); } options.setPaperWidth(temp); width = (int)( options.getPaperWidth().width); height = ancesheight+descheight + 2*marginl; options.setMinPaperLength(72.0); height = Math.max(options.getPaperLength(), height); options.setPaperLength(height); chart = new ChartDrawInfo((int) width, (int) height); // marginx = marginw; marginy = marginl; } chart.addDrawCommand(new CmdSetColor(Color.black)); chart.addDrawCommand(new DrawCmdSetFont(getFont(session, options, opgOptions.getMinFontSize()), Color.black)); chart.addDrawCommand(new DrawCmdMoveTo(width/2, (ancesheight+ancestorDescendantSeperation) + marginy)); if(options.isIncludeSpouses()){ Individual spouse = (options.getRoot().gender == Gender.MALE) ? options.getRoot().fams.get(0).wife : options.getRoot().fams.get(0).husband; Individual left, right; if(spouse.gender == Gender.MALE){ right = spouse; left = options.getRoot(); } else{ left = spouse; right = options.getRoot(); } // System.out.println("drawing semi circles"); chart.addDrawCommand(new DrawSemiCircle(rootRadius, linewidth, Color.black, options.getAncesScheme().getColor(left.id), 90, 180)); chart.addDrawCommand(new DrawSemiCircle(rootRadius, linewidth, Color.black, options.getAncesScheme().getColor(right.id), 270, 180)); chart.addDrawCommand(new DrawCmdMoveTo(width/2, ancesheight + marginy)); drawTree(session, chart, right, 0, 0, 1); drawTree(session, chart, left, 0, 1, 1); chart.addDrawCommand(new DrawCmdSetFont(getFont(session, options, opgOptions.getMinFontSize()), Color.black)); // float rootFontSize = options.getMinFontSize(); ArrayList<String> fit = DataFitter.fit(left, (float) (2*Math.sin(Math.toRadians(spouse_root_angle)) * rootRadius), (float) (Math.cos(Math.toRadians(spouse_root_angle)) * rootRadius) - 2*linewidth,getFont(session, options, opgOptions.getMinFontSize()),""); String[] textlines = new String[fit.size()]; DrawTextBox text = new DrawTextBox(textlines); fit.toArray(textlines); chart.addDrawCommand(new DrawCmdMoveTo(width/2 - (float) (Math.cos(Math.toRadians(spouse_root_angle)) * rootRadius) + linewidth, ancesheight + linewidth + marginy - (float) (Math.sin(Math.toRadians(spouse_root_angle)) * rootRadius))); chart.addDrawCommand(text); fit = DataFitter.fit(right, (float) (2*Math.sin(Math.toRadians(spouse_root_angle)) * rootRadius), (float) (Math.cos(Math.toRadians(spouse_root_angle)) * rootRadius) - 2*linewidth,getFont(session, options, opgOptions.getMinFontSize()),""); textlines = new String[fit.size()]; text = new DrawTextBox(textlines); fit.toArray(textlines); chart.addDrawCommand(new DrawCmdMoveTo(width/2 + linewidth, ancesheight + linewidth + marginy - (float) (Math.sin(Math.toRadians(spouse_root_angle)) * rootRadius))); chart.addDrawCommand(text); // chart.addDrawCommand(new DrawCmdSetFont(getFont(options, rootFontSize), Color.black)); // chart.addDrawCommand(new DrawCmdMoveTo(width/2 - rootsize+ 5,(ancesheight + rootFontSize) + marginy)); // chart.addDrawCommand(new DrawText(left.givenName)); // chart.addDrawCommand(new DrawCmdMoveTo(width/2 - rootsize+5,(ancesheight+2*rootFontSize) + marginy)); // chart.addDrawCommand(new DrawText(left.surname)); // // chart.addDrawCommand(new DrawCmdMoveTo(width/2+5,(ancesheight + rootFontSize) + marginy)); // chart.addDrawCommand(new DrawText(right.givenName)); // chart.addDrawCommand(new DrawCmdMoveTo(width/2+5,(ancesheight+2*rootFontSize) + marginy)); // chart.addDrawCommand(new DrawText(right.surname)); }else{ // System.out.println("drawing circle!!"); chart.addDrawCommand(new DrawCmdFilledCircle(rootRadius, 2, Color.black, options.getAncesScheme().getColor(options.getRoot().id))); chart.addDrawCommand(new DrawCmdMoveTo(width/2, ancesheight + marginy)); drawTree(session, chart, options.getRoot(), 0, 0, 0); // float rootFontSize = options.getMinFontSize(); ArrayList<String> fit = DataFitter.fit(options.getRoot(), (float) (2*Math.sin(Math.toRadians(root_angle)) * rootRadius), (float) (2*Math.cos(Math.toRadians(root_angle)) * rootRadius) - 2*linewidth,getFont(session, options, opgOptions.getMinFontSize()),""); String[] textlines = new String[fit.size()]; DrawTextBox text = new DrawTextBox(textlines); fit.toArray(textlines); chart.addDrawCommand(new DrawCmdMoveTo(width/2 - (float) (Math.cos(Math.toRadians(root_angle)) * rootRadius) + 2*linewidth, ancesheight + 2*linewidth + marginy - (float) (Math.sin(Math.toRadians(root_angle)) * rootRadius))); chart.addDrawCommand(text); // chart.addDrawCommand(new DrawCmdSetFont(getFont(options, rootFontSize), Color.black)); // chart.addDrawCommand(new DrawCmdMoveTo(width/2 - rootsize,(ancesheight + rootFontSize) + marginy)); // chart.addDrawCommand(new DrawText(options.getRoot().givenName)); // chart.addDrawCommand(new DrawCmdMoveTo(width/2 - rootsize,(ancesheight+2*rootFontSize) + marginy)); // chart.addDrawCommand(new DrawText(options.getRoot().surname)); // chart.addDrawCommand(new DrawCmdMoveTo(width/2 - rootsize,(ancesheight+3*rootFontSize) + marginy)); // chart.addDrawCommand(new DrawText(((options.getRoot().birth != null && options.getRoot().birth.date != null)? options.getRoot().birth.date : "____") + " - " + ((options.getRoot().death != null && options.getRoot().death.date != null) ? options.getRoot().death.date : "____"))); } chart.addDrawCommand(new DrawCmdMoveTo(width/2, ancesheight + marginy + 2*ancestorDescendantSeperation)); drawDescTree(session, chart, options.getRoot(), 0, 180, 180); // chart.addDrawCommand(new DrawCmdSetFont(getFont(options, options.getMaxFontSize()), Color.black)); // for(int i = 1;i <= options.getMaxAncesGens();i++){ // int tmp = (int) Math.pow(2, i); // for(int j = 0;j<tmp;j++){ // chart.addDrawCommand(new FilledArcSeg(100 * i, 80, (j*180f/tmp),(180f*.9f/tmp), Color.gray)); // } // } int logoOffset = 60; chart.addDrawCommand(new DrawCmdSetFont(getFont(session, options, opgOptions.getMinFontSize()), Color.black)); chart.addDrawCommand(new DrawCmdMoveTo( chart.getXExtent() - 72 * 5, chart.getYExtent() - logoOffset)); chart.addDrawCommand(new DrawText( "One Page Genealogy Project")); chart.addDrawCommand(new DrawCmdMoveTo( chart.getXExtent() - 72 * 5, chart.getYExtent() - logoOffset + opgOptions.getMinFontSize())); chart.addDrawCommand(new DrawText( "Digital Roots Lab � http://roots.cs.byu.edu/pedigree/")); session.resetChanged(); cache = chart; // if(ops.getBoxes().size() == 0){ // ops.getBoxes().add(new MediaBox((int) (1000*Math.random()), (int) (1000*Math.random()), 100, 100)); // ops.getBoxes().add(new MediaBox((int) (1000*Math.random()), (int) (1000*Math.random()), 100, 100)); // ops.getBoxes().add(new MediaBox((int) (1000*Math.random()), (int) (1000*Math.random()), 100, 100)); // ops.getBoxes().add(new MediaBox((int) (1000*Math.random()), (int) (1000*Math.random()), 100, 100)); // } // chart.setBoxes(ops.getBoxes()); return chart; } private int findMaxDepth(Individual indi){ // System.out.println("Current individual is " + indi.givenName + " " + indi.surname); // if(indi.mother != null) System.out.println("Mother is " + indi.mother.givenName + " " + indi.mother.surname); // else System.out.println("Mother is null"); // if(indi.father != null) System.out.println("Father is " + indi.father.givenName + " " + indi.father.surname); // else System.out.println("Father is null"); if(indi.father == null && indi.mother == null) return 0; if(indi.father == null) return 1 + findMaxDepth(indi.mother); if(indi.mother == null) return 1 + findMaxDepth(indi.father); return 1 + Math.max(findMaxDepth(indi.father), findMaxDepth(indi.mother)); } private int findMaxDescGens(Individual indi){ int gens = 0; if(indi.fams.size() < 1) return 0; for(Individual ind : indi.fams.get(0).children){ gens = Math.max(gens, findMaxDescGens(ind) + 1); } return gens; } // prototype for one way of implementing width adjustment on a decendancy tree // private void createTreeDescriptor(Individual indi, ArrayList<Integer> des, int level){ // if(des.size() < level+1){ // des.add(1); // } // else{ // des.set(level, des.get(level) + 1); // } // if(indi.father != null) createTreeDescriptor(indi.father,des, level+1); // if(indi.mother != null) createTreeDescriptor(indi.mother,des, level+1); // // } private void drawTree(OpgSession session, ChartDrawInfo chart, Individual indi, int level, int pos, int leveloffset ){ if (level != 0) { if(indi == null){ int tmp = (int) Math.pow(2, level + leveloffset); float size = (180f * spaceper / tmp); float space = 180*(1-spaceper)/ (tmp-1); FilledArcSeg com = new FilledArcSeg(rootRadius + level * generationWidth-.5f*boxWidth, boxWidth, (pos * (size + space)), size, Color.white); chart.addDrawCommand(com); } else{ int tmp = (int) Math.pow(2, level + leveloffset); float size = (180f * spaceper / tmp); float space = 180*(1-spaceper)/ (tmp-1); float rad = rootRadius + level * generationWidth-.5f*boxWidth; // System.out.println("Level " + level + " space " + space + " size " + (180f * spaceper / tmp)); FilledArcSeg com = new FilledArcSeg(rad, boxWidth, (pos * (size + space)), size, options.getAncesScheme().getColor(indi.id)); // Color.getHSBColor((float) Math.random(), 1, 1) ArrayList<String> fit = DataFitter.fit(indi, (float) (Math.toRadians(size)* rad), boxWidth,getFont(session, options, session.getOpgOptions().getMinFontSize()),""); com.textlines = new String[fit.size()]; fit.toArray(com.textlines); com.fontsize = session.getOpgOptions().getMinFontSize(); com.maxfontsize = session.getOpgOptions().getMaxFontSize(); com.minfontsize = session.getOpgOptions().getMinFontSize(); chart.addDrawCommand(com); } } if(level >= ancesgens) return; if(indi != null){ if(indi.mother != null || options.includeEmpty) drawTree(session, chart, indi.mother, level + 1, pos*2+1, leveloffset ); if(indi.father != null || options.includeEmpty) drawTree(session, chart, indi.father, level + 1, pos*2, leveloffset ); }else{ if(options.includeEmpty) drawTree(session, chart, null, level + 1, pos*2+1, leveloffset ); if(options.includeEmpty) drawTree(session, chart, null, level + 1, pos*2, leveloffset ); } } private void drawDescTree(OpgSession session, ChartDrawInfo chart, Individual indi, int level, float startang, float sweep ){ if(level != 0){ // int tmp = (int) Math.pow(2, level + leveloffset); // float size = (180f * spaceper / tmp); // float space = 180*(1-spaceper)/ (tmp-1); float rad = rootRadius + level * generationWidth-.5f*boxWidth; FilledArcSeg com = new FilledArcSeg(rad, boxWidth, startang, sweep, options.getDescScheme().getColor(indi.id)); ArrayList<String> fit = DataFitter.fit(indi, (float) (Math.toRadians(sweep)*rad), boxWidth,getFont(session, options, session.getOpgOptions().getMinFontSize()),""); com.textlines = new String[fit.size()]; fit.toArray(com.textlines); // System.out.println(com.textlines); com.fontsize = session.getOpgOptions().getMinFontSize(); com.maxfontsize = session.getOpgOptions().getMaxFontSize(); com.minfontsize = session.getOpgOptions().getMinFontSize(); chart.addDrawCommand(com); } if(level >= descgens ) return; if(indi.fams.size() < 1) return; float childsweep = sweep/(indi.fams.get(0).children.size()); float childnum = 0; float totalchildren = indi.fams.get(0).children.size(); for(Individual ind : indi.fams.get(0).children){ drawDescTree(session, chart, ind, level+1, startang + (childnum/totalchildren) * sweep, childsweep ); childnum++; } } private Font getFont(OpgSession session, ChartOptions options, float size){ int style = 0; if(session.getOpgOptions().isBold()) style|=Font.BOLD; if(session.getOpgOptions().isItalic()) style|=Font.ITALIC; return session.getOpgOptions().getFont().getFont(style, size); } public JPanel getSpecificOptionsPanel(ChartOptions options, OnePageMainGui parent) { return new CircularOptionsPanel(options, parent); } public LinkedList<ShapeInfo> getChartShapes() { return null; } @Override public ShapeInfo getIndiIntersect(double x, double y, int maxAnces, int maxDesc, OpgSession session) { return null; } @Override public LinkedList<ShapeInfo> getChartShapes(int maxAnces, int maxDesc, OpgSession session) { return null; } @Override public void setChartStyle(StylingBoxScheme style) { } @Override public StylingBoxScheme getBoxStyles() { return null; } public ArrayList<ImageFile> getImages(){ return images; } @Override public void setIsPrimaryMaker(boolean set) { isPrimaryMaker = set; } }