/* HyperGenealogy.zob * * Copyright (c) 2000, Ted Nelson and Tuomas Lukka * * You may use and distribute under the terms of either the GNU Lesser * General Public License, either version 2 of the license or, * at your choice, any later version. Alternatively, you may use and * distribute under the terms of the XPL. * * See the LICENSE.lgpl and LICENSE.xpl files for the specific terms of * the licenses. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the README * file for more details. * */ /* * Written by Tuomas Lukka */ package org.gzigzag.module; import org.gzigzag.*; import java.awt.*; import java.util.*; import java.io.*; /** A simple keyframe-based lifeline/meeting tracer. * There is: one cell for each meeting, one cell for each person * and one cell for each person of a meeting. * We plot the cells of the people and meetings. * The meeting cell can have a short label. The fuller explanation * should be elsewhere. *

* Dimension names give the dimension along which to find the cell * with the number, except "time", which shows the keyframes. *

* All numbers are floats between 0 and 1. *

* There must be a root cell which starts the list of all persons * on d.persons and the timeline on d.events. */ public class HyperGenealogy implements FlobView, ZOb { public static boolean dbg = false; static final void p(String s) { if(dbg) System.out.println(s); } static final void pa(String s) { System.out.println(s); } static public ZZModule module = new ZZModule() { public ZOb newZOb(String id) { if(id.equals("R")) return new HyperGenealogy(); return null; } public void action(String id, ZZCell code, ZZCell target, ZZView view, ZZView cview, String key, Point pt, ZZScene xi) { } }; STRUCTPARAMS { Font meetfont = new Font("Serif", Font.BOLD, 11); } FontMetrics meetfm; INITIALIZE { meetfm = FONTMETRICS(meetfont); } static public class dc { /** The dimension from the meeting cell to the participants, * and from the root to the peoples' cells. */ final String persons = "d.persons"; /** The dimension from the person's cell to the events in his life, * and from the root cell to all timeline cells. * All these cells should go forwards in the "time" dimension. */ final String events = "d.events"; /** The dimension from a timeline cell to meeting cell. */ final String at = "d.at"; } static dc d = new dc(); public float tofloat(ZZCell c) throws NumberFormatException { return (Float.valueOf(c.getText())).floatValue(); } /** Return the value the given cell has for the particular dimension. * This routine understands the special dimension time, * which it takes to mean the number at the headcell of * d.persons * and d.at. That cell is assumed to be on the timeline. */ public float dim(ZZCell c, String dimname) { try { ZZCell dc; if(dimname.equals("time")) { dc = c.h(d.persons, -1).h(d.at, -1); if(dc == c) dc = null; } else { dc = c.s(dimname, 1); } if(dc == null) return -1; return tofloat(dc); } catch(NumberFormatException e) { return -1; } } /** * @param from Meeting cell */ public void place(FlobSet into, int x, int y, int w, int h, FlobFactory fact, String[] dims, ZZCell from, int depthleft) { from = from.h(d.persons); for(ZZCell c = from.s(d.persons, 1); c != null; c = c.s(d.persons, 1)) { if(into.findFlob("", c) != null) return; float dx = dim(c, dims[0]); if(dx < 0) return; float dy = dim(c, dims[1]); if(dy < 0) return; Flob flob = fact.placeFlob(into, from, c, 1, (int)(x + dx * w), (int)(y + dy * h), 1, (float)0.5, (float)0.5); Flob f2 = fact.placeFlob(into, c.h(d.events), c, 1, (int)(x + dx * w), (int)(y + dy * h + flob.h/2), 1, (float)0.5, (float)0.0); f2.flobPath = "evPers"; if(depthleft <= 0) continue; ZZCell n = c.s(d.events, 1); if(n != null) place(into, x, y, w, h, fact, dims, n, depthleft-1); n = c.s(d.events, -1); if(n != null) place(into, x, y, w, h, fact, dims, n, depthleft-1); } } /** Place the cells at a given instant of time. */ public void placeTime(FlobSet into, int x, int y, int w, int h, FlobFactory fact, String[] dims, ZZCell from, float at) { // This assumes the root... ZZCell root = from; LoopDetector ld = new LoopDetector(); p("Placetime"); for(ZZCell p = root.s(d.persons); p != null; p = p.s(d.persons)) { ld.detect(p); // Find the right time interval. Move poswards until greater // or eq than current. ZZCell prevev = null; float prevt = 0; float t = 0; ZZCell ev; p("Pt1: "+p); for(ev = p.s(d.events); ev != null; ev = ev.s(d.events)) { t = dim(ev, "time"); p("Pt2: "+ev+" "+t); if(t >= at) break; prevev = ev; prevt = t; } p("Pt3: "+prevev+" "+ev+" "); float dx, dy; if(ev == null) continue; if(prevev == null) { if(t > at) continue; dx = dim(ev, dims[0]); if(dx < 0) continue; dy = dim(ev, dims[1]); if(dy < 0) continue; } else { float f = 1 - (at - prevt) / (t - prevt); // Now we've got it in the aim... get the coordinates // and interpolate. float dx0 = dim(prevev, dims[0]); if(dx0 < 0) continue; float dx1 = dim(ev, dims[0]); if(dx1 < 0) continue; float dy0 = dim(prevev, dims[1]); if(dy0 < 0) continue; float dy1 = dim(ev, dims[1]); if(dy1 < 0) continue; dx = f * dx0 + (1-f) * dx1; dy = f * dy0 + (1-f) * dy1; } Flob flob = fact.placeFlob(into, p, p, 1, (int)(x + dx * w), (int)(y + dy * h), 1, (float)0.5, (float)0.5 ); // ZZCell } } // If accursed cell is a timeline cell, do all persons public void raster(final FlobSet into, FlobFactory fact, ZZCell view, String[] dims, ZZCell accursed) { Dimension s = into.getSize(); int x = s.width / 10; int y = s.height / 10; int w = s.width - 2 * x; int h = s.height - 2 * y; if(accursed.s(d.at, 1) != null) { placeTime(into, x, y, w, h, fact, dims, accursed.h(d.events), tofloat(accursed)); } else { place(into, x, y, w, h, fact, dims, accursed.h(d.persons), 3); } // selection: 3 next/prev meetings of current person, // 1 next/prev of those he met. final LineDecor.Builder ldb = new LineDecor.Builder(into, null); final LineDecor.Builder ldbm = new LineDecor.Builder(into, Color.red); final Hashtable done = new Hashtable(); into.iterDepth(new FlobSet.DepthIter() { final void dline(Flob from, String dim, int dir, LineDecor.Builder bu) { ZZCell c = from.c.s(dim, dir); if(c == null) return; if(done.get(c) != null) return; Flob to = into.findFlob("", c); if(to == null) return; //draw the line bu.l(from.x, from.y+from.h/2, to.x, to.y+to.h/2); } public void act(Flob[] flobs, int start, int n) { ldb.startl(4*n, flobs[start].d); ldbm.startl(4*n, flobs[start].d); for(int i=start; i