package timeflow.vis.timeline; import timeflow.data.db.*; import timeflow.data.db.filter.*; import timeflow.data.time.*; import timeflow.model.*; import timeflow.vis.*; import java.util.*; import java.awt.*; /* * A VisualEncoding takes the info about which fields to translate to * which visual aspects, and applies that to particular Acts. */ public class TimelineVisuals { private Map trackTable = new HashMap(); ArrayList trackList = new ArrayList(); private TimeScale timeScale = new TimeScale(); private Rectangle bounds = new Rectangle(); private boolean frameChanged; private int numShown = 0; private Interval globalInterval; public double scale = 1; public enum Layout { TIGHT, LOOSE, GRAPH }; private Layout layoutStyle = Layout.TIGHT; private VisualEncoder encoder; private TFModel model; private int fullHeight; public int getFullHeight() { return fullHeight; } public TimelineVisuals(TFModel model) { this.model = model; encoder = new VisualEncoder(model); } public TimeScale getTimeScale() { return timeScale; } public Rectangle getBounds() { return bounds; } public void setBounds(int x, int y, int w, int h) { bounds.setBounds(x, y, w, h); timeScale.setLow(x); timeScale.setHigh(x + w); frameChanged = true; } public Layout getLayoutStyle() { return layoutStyle; } public void setLayoutStyle(Layout style) { layoutStyle = style; layout(); } public Interval getFitToVisibleRange() { ActList acts = model.getActs(); // add a little bit to the right so we can see labels... ActDB db = getModel().getDB(); Field endField = db.getField(VirtualField.END); Interval i = null; if (endField == null) { i = DBUtils.range(acts, VirtualField.START); } else { i = DBUtils.range(acts, new Field[] { db.getField(VirtualField.START), endField }); } if (i.length() == 0) { i.expand(globalInterval.length() / 20); } i = i.subinterval(-.05, 1.1); i.intersection(globalInterval); return i; } public void fitToVisible() { Interval i = getFitToVisibleRange(); setTimeBounds(i.start, i.end); } public void zoomOut() { setTimeBounds(globalInterval.start, globalInterval.end); } public void setTimeBounds(long first, long last) { timeScale.setDateRange(first, last); frameChanged = true; model.setViewInterval(new Interval(first, last)); } public Interval getGlobalInterval() { if (globalInterval == null && model != null && model.getDB() != null) { createGlobalInterval(); } return globalInterval; } public void createGlobalInterval() { globalInterval = DBUtils.range(model.getDB().all(), VirtualField.START).subinterval(-.05, 1.1); } public Interval getViewInterval() { return timeScale.getInterval(); } public java.util.List getVisualActs() { return encoder.getVisualActs(); } public void layoutIfChanged() { if (frameChanged) { layout(); } } public void init(boolean majorChange) { note(new TFEvent(majorChange ? TFEvent.Type.DATABASE_CHANGE : TFEvent.Type.ACT_CHANGE, null)); } public void note(TFEvent e) { ActList all = null; if (e.type == TFEvent.Type.DATABASE_CHANGE) { all = model.getDB().all(); createGlobalInterval(); Interval i = guessInitialViewInterval(all, globalInterval); setTimeBounds(i.start, i.end); } if (e.affectsRowSet()) { all = model.getDB().all(); encoder.createVisualActs(); createGlobalInterval(); } else { encoder.createVisualActs(); } Interval v = model.getViewInterval(); if (v != null && v.start != timeScale.getInterval().start) { timeScale.getInterval().translateTo(v.start); } updateVisuals(); } private Interval guessInitialViewInterval(ActList acts, Interval fullRange) { if (acts.size() < 50) { return fullRange.copy(); } Interval best = null; int most = -1; double d = Math.max(.1, 50. / acts.size()); d = Math.min(1. / 3, d); for (double x = 0; x < 1 - d; x += d / 4) { Interval i = fullRange.subinterval(x, x + d); TimeIntervalFilter f = new TimeIntervalFilter(i, getModel().getDB().getField(VirtualField.START)); int num = 0; for (Act a : acts) { if (f.accept(a)) { num++; } } if (num > most) { most = num; best = i; } } return best; } public void updateVisuals() { scale = 1; updateVisualEncoding(); layout(); } public TFModel getModel() { return model; } public int getNumTracks() { return trackList.size(); } public double layout() { ActList acts = model.getActs(); if (acts == null) { return scale; } double labelHeight = 30; double min = bounds.height == 0 ? 0 : labelHeight / bounds.height; double cellH = (double)getModel().getDisplay().getInt("timeline.item.height.min") / bounds.height; double maxCount = 0; // Set the minimum scale for (TimelineTrack t : trackList) { //double height = t.size() * scale / (double) numShown; maxCount = Math.max(t.size(), maxCount); } scale = Math.min(cellH * numShown, scale); scale = Math.max(min * numShown / maxCount, scale); double top = 0; for (TimelineTrack t : trackList) { double height = Math.max(min, t.size() * scale / (double) numShown); t.layout(top, height, this); top += height; } fullHeight = (int) (top * bounds.height); Collections.sort(trackList); frameChanged = false; return scale; } private void updateVisualEncoding() { java.util.List acts = encoder.apply(); // now arrange on tracks trackTable = new HashMap(); trackList = new ArrayList(); numShown = 0; for (VisualAct v : acts) { if (!v.isVisible()) { continue; } numShown++; String s = v.getTrackString(); TimelineTrack t = trackTable.get(s); if (t == null) { t = new TimelineTrack(s); trackTable.put(s, t); trackList.add(t); } t.add(v); v.setTrack(t); } /* // the following code is no longer used, but could come in handy again one day... // If there is more than one "small" track, then we will coalesce them into // one bigger "miscellaneous" track. int minSize=numShown/30;//Math.max(3,numShown/30); ArrayList small=new ArrayList(); for (TimelineTrack t: trackList) { if (t.size()1) { // create a new Track for "miscellaneous." TimelineTrack misc=new TimelineTrack(Display.MISC_CODE); trackList.add(misc); trackTable.put(misc.label, misc); // remove the old tracks. for (TimelineTrack t:small) { trackList.remove(t); trackTable.remove(t.label); for (VisualAct v: t.visualActs) { v.setTrack(misc); misc.add(v); } } // sort miscellaneous items in time order. //Collections.sort(misc.visualActs); } */ for (TimelineTrack t : trackList) { Collections.sort(t.visualActs); } } }