You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ctakes.apache.org by se...@apache.org on 2015/02/19 19:06:17 UTC

svn commit: r1660963 [15/19] - in /ctakes/sandbox/timelanes: META-INF/ edu/ edu/mayo/ edu/mayo/bmi/ edu/mayo/bmi/annotation/ edu/mayo/bmi/annotation/knowtator/ org/ org/chboston/ org/chboston/cnlp/ org/chboston/cnlp/anafora/ org/chboston/cnlp/anafora/a...

Added: ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/gui/timeline/stack/TimelineStack.java
URL: http://svn.apache.org/viewvc/ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/gui/timeline/stack/TimelineStack.java?rev=1660963&view=auto
==============================================================================
--- ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/gui/timeline/stack/TimelineStack.java (added)
+++ ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/gui/timeline/stack/TimelineStack.java Thu Feb 19 18:06:13 2015
@@ -0,0 +1,348 @@
+package org.chboston.cnlp.timeline.gui.timeline.stack;
+
+import org.chboston.cnlp.gui.VerticalMimicPanel;
+import org.chboston.cnlp.timeline.gui.timeline.JTimelineComponent;
+import org.chboston.cnlp.timeline.gui.timespan.selection.SelectionForwarder;
+import org.chboston.cnlp.timeline.gui.timespan.selection.TimeSpanSelectionModel;
+import org.chboston.cnlp.timeline.timeline.Timeline;
+
+import javax.swing.*;
+import javax.swing.border.Border;
+import javax.swing.border.EmptyBorder;
+import java.awt.*;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+
+/**
+ * Author: SPF
+ * Affiliation: CHIP-NLP
+ * Date: 8/8/13
+ */
+public class TimelineStack extends Box {
+
+   static private final int COLLAPSED_HEIGHT = 25;
+
+   private boolean _collapsing;
+   private int _collapsingHeight;
+
+   private VerticalMimicPanel _titleStack;
+   private VerticalMimicPanel _removeButtonStack;
+   private boolean _collapsed = false;
+
+   static private final ImageIcon X_ICON = new ImageIcon( TimelineStack.class
+         .getResource( "icon/edit-delete-6_12.png" ) );
+
+   public TimelineStack() {
+      super( BoxLayout.Y_AXIS );
+      // JComponents don't like to be long ...
+      setMaximumSize( new Dimension( Integer.MAX_VALUE, Short.MAX_VALUE ) );
+      setBorder( new Border() {
+         @Override
+         public void paintBorder( final Component c,
+                                  final Graphics g,
+                                  final int x,
+                                  final int y,
+                                  final int width,
+                                  final int height ) {
+            Color oldColor = g.getColor();
+            g.setColor( Color.GRAY );
+            g.drawLine( x, y, x + width - 1, y );
+            g.setColor( Color.LIGHT_GRAY );
+            g.drawLine( x, y + 1, x + width - 1, y + 1 );
+            g.setColor( oldColor );
+         }
+
+         @Override
+         public Insets getBorderInsets( final Component c ) {
+            return new Insets( 2, 0, 0, 0 );
+         }
+
+         @Override
+         public boolean isBorderOpaque() {
+            return false;
+         }
+      } );
+   }
+
+
+   public void collapse( final boolean collapse ) {
+      if ( _collapsing ) {
+         return;
+      }
+      if ( !_collapsed && collapse ) {
+         final int high = getPreferredSize().height;
+         final int low = COLLAPSED_HEIGHT;
+         if ( high > low + 3 ) {
+            _collapsingHeight = high;
+            final int incr = -Math.min( Math.max( 2, (high - low) / 10 ), 10 );
+            new CollapseAction( high, low, incr ).start();
+         }
+      } else if ( _collapsed && !collapse ) {
+         final int high = super.getPreferredSize().height;
+         final int low = getPreferredSize().height;
+         if ( high > low + 3 ) {
+            _collapsingHeight = low;
+            final int incr = Math.min( Math.max( 2, (high - low) / 10 ), 10 );
+            new CollapseAction( high, low, incr ).start();
+         }
+      } else {
+         _collapsed = collapse;
+         revalidate();
+      }
+      _collapsed = collapse;
+   }
+
+   private class CollapseAction implements ActionListener {
+      final Timer __timer;
+      final private int __high;
+      final private int __low;
+      final private int __incr;
+
+      private CollapseAction( final int high, final int low, final int incr ) {
+         __high = high;
+         __low = low;
+         __incr = incr;
+         __timer = new Timer( 10, CollapseAction.this );
+         __timer.setRepeats( true );
+      }
+
+      private void start() {
+         _collapsing = true;
+         __timer.start();
+      }
+
+      public void actionPerformed( final ActionEvent event ) {
+         _collapsingHeight += __incr;
+         revalidate();
+         if ( _collapsingHeight <= __low || _collapsingHeight >= __high ) {
+            _collapsing = false;
+            __timer.stop();
+         }
+      }
+   }
+
+   public Dimension getPreferredSize() {
+      final Dimension size = super.getPreferredSize();
+      if ( _collapsing ) {
+         size.height = _collapsingHeight;
+         return size;
+      }
+      if ( !_collapsed ) {
+         return size;
+      }
+      size.height = Math.min( size.height, COLLAPSED_HEIGHT );
+      return size;
+   }
+
+   public VerticalMimicPanel getTitleStack() {
+      if ( _titleStack == null ) {
+         _titleStack = new VerticalMimicPanel( this );
+      }
+      return _titleStack;
+   }
+
+   public VerticalMimicPanel getRemoveButtonStack() {
+      if ( _removeButtonStack == null ) {
+         _removeButtonStack = new VerticalMimicPanel( this );
+      }
+      return _removeButtonStack;
+   }
+
+   public JTimelineComponent addTimeline( final Timeline timeline, final Timeline gridTimeline,
+                                          final TimeSpanSelectionModel selectionModel ) {
+      return addTimeline( timeline, gridTimeline, selectionModel, true );
+   }
+
+   public JTimelineComponent addTimeline( final Timeline timeline, final Timeline gridTimeline,
+                                          final TimeSpanSelectionModel selectionModel, final boolean removable ) {
+      if ( timeline == null ) {
+         return null;
+      }
+      return addTimeline( timeline.getTitle(), null, timeline, gridTimeline, selectionModel, removable );
+   }
+
+   public JTimelineComponent addTimeline( final Timeline timeline, final Color color, final Timeline gridTimeline,
+                                          final TimeSpanSelectionModel selectionModel, final boolean removable ) {
+      if ( timeline == null ) {
+         return null;
+      }
+      return addTimeline( timeline.getTitle(), color, timeline, gridTimeline, selectionModel, removable );
+   }
+
+
+   public JTimelineComponent addTimeline( final String title, final Color color, final Timeline timeline,
+                                          final Timeline gridTimeline,
+                                          final TimeSpanSelectionModel selectionModel ) {
+      return addTimeline( title, color, timeline, gridTimeline, selectionModel, true );
+   }
+
+   public JTimelineComponent addTimeline( final String title, final Color color, final Timeline timeline,
+                                          final Timeline gridTimeline,
+                                          final TimeSpanSelectionModel selectionModel, final boolean removable ) {
+      if ( timeline == null ) {
+         return null;
+      }
+      for ( Component comp : getComponents() ) {
+         if ( comp instanceof JTimelineComponent ) {
+            final String otherTitle = ((JTimelineComponent)comp).getModel().getTitle();
+            if ( otherTitle.equals( title ) ) {
+               // Already have the timeline ???
+               return (JTimelineComponent)comp;
+            }
+         }
+      }
+      final JTimelineComponent jTimeline = createTimelineComponent( timeline, gridTimeline, selectionModel );
+      add( jTimeline );
+      String htmlTitle = title;
+      if ( title.contains( "/" ) ) {
+         htmlTitle = "<html><p align=right>" + title.replace( "/", "<br>" ) + "</p></html>";
+      }
+      getTitleStack().addComponentFor( jTimeline, new WidthLabel( htmlTitle, color ) );
+      if ( removable ) {
+         final JButton removeButton = new JButton( new RemoveTimelineAction( title, timeline ) );
+         removeButton.setContentAreaFilled( false );
+         removeButton.setFocusPainted( false );
+         removeButton.setPreferredSize( new Dimension( 16, 12 ) );
+         removeButton.setBorder( new EmptyBorder( 0, 2, 0, 0 ) );
+         getRemoveButtonStack().addComponentFor( jTimeline, removeButton );
+      }
+      return jTimeline;
+   }
+
+   public void removeTimeline( final String title, final Timeline timeline ) {
+      if ( title.startsWith( "All " ) || getComponentCount() == 1 ) {
+         return;
+      }
+      for ( Component comp : getComponents() ) {
+         if ( comp instanceof JTimelineComponent ) {
+            final String otherTitle = ((JTimelineComponent)comp).getModel().getTitle();
+            if ( otherTitle.equals( title ) ) {
+               SelectionForwarder.getInstance().removeSelectionModel( ((JTimelineComponent)comp).getSelectionModel() );
+               getTitleStack().removeComponentFor( comp );
+               getRemoveButtonStack().removeComponentFor( comp );
+               remove( comp );
+               break;
+            }
+         }
+      }
+      revalidate();
+   }
+
+
+   public void clearTimelines() {
+      for ( Component comp : getComponents() ) {
+         if ( comp instanceof JTimelineComponent ) {
+//            final String title = ((JTimelineComponent) comp).getModel().getTitle();
+//            if ( title.startsWith( "All " ) ) {
+//               continue;
+//            }
+            SelectionForwarder.getInstance().removeSelectionModel( ((JTimelineComponent)comp).getSelectionModel() );
+            getTitleStack().removeComponentFor( comp );
+            getRemoveButtonStack().removeComponentFor( comp );
+            remove( comp );
+         }
+      }
+      revalidate();
+   }
+
+
+   static private JTimelineComponent createTimelineComponent( final Timeline timeline,
+                                                              final Timeline gridTimeline,
+                                                              final TimeSpanSelectionModel selectionModel ) {
+      final JTimelineComponent jTimeline = new JTimelineComponent( timeline, gridTimeline );
+      if ( selectionModel != null ) {
+         jTimeline.setSelectionModel( selectionModel );
+      }
+      jTimeline.setBorder( new Border() {
+         @Override
+         public void paintBorder( final Component c,
+                                  final Graphics g,
+                                  final int x,
+                                  final int y,
+                                  final int width,
+                                  final int height ) {
+            Color oldColor = g.getColor();
+            g.setColor( Color.GRAY );
+            g.drawLine( x, y + height - 1, x + width - 1, y + height - 1 );
+            g.setColor( oldColor );
+         }
+
+         @Override
+         public Insets getBorderInsets( final Component c ) {
+            return new Insets( 0, 0, 1, 0 );
+         }
+
+         @Override
+         public boolean isBorderOpaque() {
+            return false;
+         }
+      } );
+      return jTimeline;
+   }
+
+   private class RemoveTimelineAction extends AbstractAction {
+      final private Timeline __timeline;
+      final private String __title;
+
+      private RemoveTimelineAction( final String title, final Timeline timeline ) {
+         super( "", X_ICON );
+         putValue( SHORT_DESCRIPTION, title );
+         putValue( LONG_DESCRIPTION, title );
+         __timeline = timeline;
+         __title = title;
+      }
+
+      public void actionPerformed( final ActionEvent event ) {
+         removeTimeline( __title, __timeline );
+      }
+   }
+
+
+   static private class WidthLabel extends JLabel {
+      private WidthLabel( final String text, final Color color ) {
+         super( text );
+         setBorder( new EmptyBorder( 0, 0, 0, 5 ) );
+         setHorizontalAlignment( JLabel.RIGHT );
+         setVerticalAlignment( JLabel.CENTER );
+         setToolTipText( text );
+         final Font oldFont = getFont();
+         final Font newFont = oldFont.deriveFont( Font.BOLD );
+         setFont( newFont );
+         setHorizontalTextPosition( JLabel.LEFT );
+         addSquare( color );
+         // Doesn't work
+         //         setMaximumSize( new Dimension( 120, Short.MAX_VALUE ) );
+      }
+
+      public Dimension getPreferredSize() {
+         final Dimension size = super.getPreferredSize();
+         size.width = Math.max( size.width, 40 );
+         size.width = Math.min( size.width, 120 );
+         return size;
+      }
+
+      private void addSquare( final Color color ) {
+         if ( color == null ) {
+            return;
+         }
+         final Icon icon = new Icon() {
+            public void paintIcon( Component c, Graphics g, int x, int y ) {
+               g.setColor( color );
+               g.fillRect( x, y, getIconWidth(), getIconHeight() );
+            }
+
+            public int getIconWidth() {
+               return 8;
+            }
+
+            public int getIconHeight() {
+               return 8;
+            }
+         };
+         setIcon( icon );
+      }
+
+   }
+
+
+}

Added: ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/gui/timeline/stack/icon/edit-delete-6_12.png
URL: http://svn.apache.org/viewvc/ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/gui/timeline/stack/icon/edit-delete-6_12.png?rev=1660963&view=auto
==============================================================================
Binary file - no diff available.

Propchange: ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/gui/timeline/stack/icon/edit-delete-6_12.png
------------------------------------------------------------------------------
    svn:mime-type = application/octet-stream

Added: ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/gui/timeline/ui/AbstractTimelineUI.java
URL: http://svn.apache.org/viewvc/ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/gui/timeline/ui/AbstractTimelineUI.java?rev=1660963&view=auto
==============================================================================
--- ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/gui/timeline/ui/AbstractTimelineUI.java (added)
+++ ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/gui/timeline/ui/AbstractTimelineUI.java Thu Feb 19 18:06:13 2015
@@ -0,0 +1,199 @@
+package org.chboston.cnlp.timeline.gui.timeline.ui;
+
+import org.chboston.cnlp.timeline.gui.timeline.TimelineComponent;
+import org.chboston.cnlp.timeline.gui.timespan.TimeSpanDrawUtil;
+import org.chboston.cnlp.timeline.timeline.Timeline;
+import org.chboston.cnlp.timeline.timespan.TimeSpan;
+import org.chboston.cnlp.timeline.timespan.plus.PointedTimeSpan;
+
+import javax.swing.*;
+import java.awt.*;
+
+/**
+ * Author: SPF
+ * Affiliation: CHIP-NLP
+ * Date: 8/2/13
+ */
+abstract public class AbstractTimelineUI extends TimelineUI {
+
+   protected CellRendererPane _rendererPane;
+
+   /**
+    * {@inheritDoc}
+    */
+   @Override
+   abstract public Rectangle doLayout( final JComponent component );
+
+   abstract protected void resetLayout();
+
+   abstract protected int getY( final int x1, final int x2 );
+
+   abstract protected int getHeight( final int x1, final int x2 );
+
+   abstract protected Rectangle getCachedBounds();
+
+   abstract protected Rectangle getCachedTimeSpanBounds( final TimeSpan timeSpan );
+
+   abstract protected void paintComponent( final Graphics g, final JComponent component );
+
+   /**
+    * {@inheritDoc}
+    */
+   @Override
+   public void installUI( final JComponent component ) {
+      _rendererPane = new CellRendererPane();
+      component.add( _rendererPane );
+      //      installDefaults();
+      //      installDefaults2();
+      //      installListeners();
+      //      installKeyboardActions();
+   }
+
+   /**
+    * {@inheritDoc}
+    */
+   @Override
+   public void uninstallUI( final JComponent component ) {
+      //      uninstallDefaults();
+      //      uninstallListeners();
+      //      uninstallKeyboardActions();
+      if ( _rendererPane == null ) {
+         return;
+      }
+      component.remove( _rendererPane );
+      _rendererPane = null;
+   }
+
+   /**
+    * {@inheritDoc}
+    */
+   @Override
+   public void paint( final Graphics g, final JComponent component ) {
+      final Rectangle bounds = component.getBounds();
+      final Rectangle layout = doLayout( component );
+      if ( bounds.equals( layout ) ) {
+         final Graphics myG = g.create();
+         paintComponent( myG, component );
+         myG.dispose();
+      } else {
+         component.setBounds( layout );
+      }
+   }
+
+   // KLUDGE to get sizing of OldJTimeline to work properly within a BoxLayout
+   // Add any parent panel with a BoxLayout to a Panel without a BoxLayout or OldJTimeline sizing will not work
+   private int getParentWidth( final Component child ) {
+      final Component parent = child.getParent();
+      if ( parent == null ) {
+         return child.getWidth();
+      }
+      if ( parent instanceof JViewport || parent instanceof JScrollPane
+           || parent instanceof CellRendererPane
+           || (parent instanceof JComponent && ((JComponent)parent).getLayout() instanceof BoxLayout) ) {
+         return getParentWidth( parent );
+      }
+      return parent.getWidth();
+   }
+
+   protected int getTotalWidth( final Component component ) {
+      int width = getParentWidth( component );
+      if ( width == 0 ) {
+         width = 1000;
+      }
+      return width;
+   }
+
+   protected Rectangle getTimeSpanBounds( final long beginDrawMillis, final double xform,
+                                          final TimeSpan timeSpan ) {
+      final Rectangle cachedBounds = getCachedTimeSpanBounds( timeSpan );
+      if ( cachedBounds != null ) {
+         return cachedBounds;
+      }
+      final long begin = timeSpan.getStartMillis();
+      final long end = timeSpan.getStopMillis();
+      double x1 = (begin - beginDrawMillis) / xform;
+      double x2 = (end - beginDrawMillis) / xform;
+      if ( x1 >= x2 ) {
+         x2 = x1 + 1;
+      }
+      x1 -= TimeSpanDrawUtil.getStartOffset( timeSpan );
+      x2 += TimeSpanDrawUtil.getEndOffset( timeSpan );
+      final int y = getY( (int)x1, (int)x2 );
+      int width = Math.max( (int)(x2 - x1), 1 );
+      final int height = getHeight( (int)x1, (int)x2 );
+      return new Rectangle( (int)x1, y, width, height );
+   }
+
+   /**
+    * {@inheritDoc}
+    */
+   @Override
+   public Rectangle getTimeSpanBounds( final JComponent component, final TimeSpan timeSpan ) {
+      final Rectangle cachedBounds = getCachedTimeSpanBounds( timeSpan );
+      if ( cachedBounds != null ) {
+         return cachedBounds;
+      }
+      final int totalWidth = getTotalWidth( component );
+      final TimelineComponent timelineComponent = (TimelineComponent)component;
+      final long beginDrawMillis = timelineComponent.getBeginDrawMillis();
+      final long drawTimeDelta = timelineComponent.getDrawTimeDelta();
+      final double xform = drawTimeDelta / (double)totalWidth;
+      return getTimeSpanBounds( beginDrawMillis, xform, timeSpan );
+   }
+
+
+   /**
+    * {@inheritDoc}
+    */
+   @Override
+   public TimeSpan getClosestTimeSpanForLocation( final JComponent component, final int x, final int y ) {
+      final int totalWidth = getTotalWidth( component );
+      final TimelineComponent timelineComponent = (TimelineComponent)component;
+      final long beginDrawMillis = timelineComponent.getBeginDrawMillis();
+      final long drawTimeDelta = timelineComponent.getDrawTimeDelta();
+      final double xform = drawTimeDelta / (double)totalWidth;
+      int smallestWidth = Integer.MAX_VALUE;
+      PointedTimeSpan bestTimeSpan = null;
+      final Timeline timeline = timelineComponent.getModel();
+      for ( PointedTimeSpan timeSpan : timeline ) {
+         final Rectangle bounds = getTimeSpanBounds( beginDrawMillis, xform, timeSpan );
+         if ( bounds.contains( x, y ) && bounds.width < smallestWidth ) {
+            smallestWidth = bounds.width;
+            bestTimeSpan = timeSpan;
+         }
+      }
+      return bestTimeSpan;
+   }
+
+   // TODO SIZES
+
+   /**
+    * {@inheritDoc}
+    */
+   @Override
+   public Dimension getPreferredSize( final JComponent component ) {
+      final Rectangle layout = doLayout( component );
+      return layout.getSize();
+   }
+
+
+   /**
+    * {@inheritDoc}
+    */
+   @Override
+   public int getBaseline( JComponent c, int width, int height ) {
+      super.getBaseline( c, width, height );
+      final Dimension size = getPreferredSize( c );
+      return size.height;
+   }
+
+   /**
+    * {@inheritDoc}
+    */
+   @Override
+   public Component.BaselineResizeBehavior getBaselineResizeBehavior( JComponent c ) {
+      return Component.BaselineResizeBehavior.CONSTANT_ASCENT;
+   }
+
+
+}

Added: ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/gui/timeline/ui/BasicTimelineUI.java
URL: http://svn.apache.org/viewvc/ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/gui/timeline/ui/BasicTimelineUI.java?rev=1660963&view=auto
==============================================================================
--- ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/gui/timeline/ui/BasicTimelineUI.java (added)
+++ ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/gui/timeline/ui/BasicTimelineUI.java Thu Feb 19 18:06:13 2015
@@ -0,0 +1,296 @@
+package org.chboston.cnlp.timeline.gui.timeline.ui;
+
+import org.chboston.cnlp.timeline.gui.timeline.JTimelineComponent;
+import org.chboston.cnlp.timeline.gui.timespan.TimeSpanDrawUtil;
+import org.chboston.cnlp.timeline.gui.timespan.plus.TimeSpanRenderer;
+import org.chboston.cnlp.timeline.timeline.Timeline;
+import org.chboston.cnlp.timeline.timespan.DefaultTimeSpan;
+import org.chboston.cnlp.timeline.timespan.TimeSpan;
+import org.chboston.cnlp.timeline.timespan.plus.PointedTimeSpan;
+import org.chboston.cnlp.timeline.timespan.plus.TimeSpanLengthComparator;
+
+import javax.swing.*;
+import java.awt.*;
+import java.util.*;
+
+/**
+ * Author: SPF
+ * Affiliation: CHIP-NLP
+ * Date: 8/2/13
+ */
+public class BasicTimelineUI extends AbstractTimelineUI {
+
+
+   static private final Color DOC_TIME_COLOR = new Color( 0, 127, 127, 63 );
+
+   // For vertical layout
+   final private java.util.List<Set<Point>> _yEndpointsList = new ArrayList<>();
+
+   // Span Layout cache
+   final private Map<TimeSpan, Rectangle> _layoutCacheMap = new HashMap<>();
+
+   // Recalculating the bounds can be expensive when there are dozens of time lines on the screen
+   private Rectangle _cachedBounds = new Rectangle();
+
+   private boolean _drawGridOnly;
+   final private Collection<Integer> _gridLineXs = new HashSet<>();
+   final private Collection<Integer> _fuzzyGridLineXs = new HashSet<>();
+
+   private TimeSpan _referenceDay;
+
+   /**
+    * @param drawGridOnly true if the ui should only draw the grid of timespans
+    */
+   public void setDrawGridOnly( final boolean drawGridOnly ) {
+      _drawGridOnly = drawGridOnly;
+   }
+
+   /**
+    * {@inheritDoc}
+    */
+   @Override
+   public Rectangle doLayout( final JComponent component ) {
+      final int totalWidth = getTotalWidth( component );
+      if ( _cachedBounds.x == 0 && _cachedBounds.width == totalWidth ) {
+         // DefaultTimeline has not been scrolled or resized horizontally, no need to recompute TimeSpan bounds
+         // DefaultTimeline may have moved up or down by resize of sibling
+         _cachedBounds.y = component.getY();
+         return _cachedBounds;
+      }
+      resetLayout();
+      final JTimelineComponent timelineComponent = (JTimelineComponent)component;
+      final long beginDrawMillis = timelineComponent.getBeginDrawMillis();
+      final long drawTimeDelta = timelineComponent.getDrawTimeDelta();
+      final double xform = drawTimeDelta / (double)totalWidth;
+      final Timeline timeline = timelineComponent.getModel();
+      final java.util.List<PointedTimeSpan> timeSpansByWidth = new ArrayList<>( timeline.getTimeSpans() );
+      Collections.sort( timeSpansByWidth, TimeSpanLengthComparator.getInstance() );
+      // Create the bounds for each TimeSpan
+      for ( PointedTimeSpan timeSpan : timeSpansByWidth ) {
+         final Rectangle bounds = getTimeSpanBounds( beginDrawMillis, xform, timeSpan );
+         _layoutCacheMap.put( timeSpan, bounds );
+      }
+      final long docTimeStart = timeline.getReferenceMillis();
+      final long docTimeStop = docTimeStart + 1000 * 60 * 60 * 24 - 1;
+      _referenceDay = new DefaultTimeSpan( docTimeStart, docTimeStop );
+      final int height = Math.max( TimeSpanDrawUtil.TIMESPAN_CELL_HEIGHT * 3,
+            _yEndpointsList.size() * TimeSpanDrawUtil.TIMESPAN_CELL_HEIGHT );
+      _cachedBounds = new Rectangle( 0, component.getY(), totalWidth, height );
+      final Timeline gridModel = timelineComponent.getGridModel();
+      for ( PointedTimeSpan timeSpan : gridModel ) {
+         addGridTimeSpanBounds( beginDrawMillis, xform, timeSpan );
+      }
+      return _cachedBounds;
+   }
+
+   protected void addGridTimeSpanBounds( final long beginDrawMillis, final double xform,
+                                         final TimeSpan timeSpan ) {
+      final long begin = timeSpan.getStartMillis();
+      final long end = timeSpan.getStopMillis();
+      double x1 = (begin - beginDrawMillis) / xform;
+      double x2 = (end - beginDrawMillis) / xform;
+//      if ( x1 >= x2 ) {
+//         x2 = x1 + 1;
+//      }
+      if ( timeSpan.isFuzzyDate() && timeSpan instanceof PointedTimeSpan ) {
+         addGridX( (int)x1, ((PointedTimeSpan)timeSpan).getStartTime().isFuzzy() );
+         addGridX( (int)x2, ((PointedTimeSpan)timeSpan).getStopTime().isFuzzy() );
+         return;
+      }
+      addGridX( (int)x1, timeSpan.isFuzzyDate() );
+      addGridX( (int)x2, timeSpan.isFuzzyDate() );
+   }
+
+   protected void addGridX( final int x, final boolean isFuzzy ) {
+      if ( isFuzzy ) {
+         _fuzzyGridLineXs.add( x );
+      } else {
+         _gridLineXs.add( x );
+      }
+   }
+
+   /**
+    * {@inheritDoc}
+    */
+   @Override
+   protected void resetLayout() {
+      _cachedBounds.width = 0;
+      _layoutCacheMap.clear();
+      _yEndpointsList.clear();
+      _gridLineXs.clear();
+      _fuzzyGridLineXs.clear();
+   }
+
+   /**
+    * {@inheritDoc}
+    */
+   @Override
+   protected int getY( final int x1, final int x2 ) {
+      // get Y for time span
+      final int size = _yEndpointsList.size();
+      int validLayer = 0;
+      for ( int i = 0; i < size; i++ ) {
+         final Set<Point> layerPoints = _yEndpointsList.get( i );
+         boolean overlap = false;
+         for ( Point p : layerPoints ) {
+            if ( (x1 >= p.x && x1 <= p.y) || (x2 >= p.x && x2 <= p.y)
+                 || (p.x >= x1 && p.x <= x2) || (p.y >= x1 && p.y <= x2) ) {
+               // spans overlap
+               overlap = true;
+               break;
+            }
+         }
+         if ( !overlap ) {
+            break;
+         }
+         validLayer = i + 1;
+      }
+      // Pad the timespans with a minimum gap
+      final Point newPoint = new Point( x1 - TimeSpanDrawUtil.TIMESPAN_CELL_HEIGHT,
+            x2 + TimeSpanDrawUtil.TIMESPAN_CELL_HEIGHT );
+      if ( validLayer < size ) {
+         final Set<Point> layerPoints = _yEndpointsList.get( validLayer );
+         layerPoints.add( newPoint );
+      } else {
+         final Set<Point> newLayer = new HashSet<>( 1 );
+         newLayer.add( newPoint );
+         _yEndpointsList.add( newLayer );
+      }
+      return validLayer * TimeSpanDrawUtil.TIMESPAN_CELL_HEIGHT;
+   }
+
+   /**
+    * {@inheritDoc}
+    */
+   @Override
+   protected int getHeight( final int x1, final int x2 ) {
+      return TimeSpanDrawUtil.TIMESPAN_CELL_HEIGHT;
+   }
+
+   /**
+    * {@inheritDoc}
+    */
+   @Override
+   protected Rectangle getCachedBounds() {
+      return _cachedBounds;
+   }
+
+   /**
+    * {@inheritDoc}
+    */
+   @Override
+   protected Rectangle getCachedTimeSpanBounds( final TimeSpan timeSpan ) {
+      return _layoutCacheMap.get( timeSpan );
+   }
+
+
+   private void paintDocTime( final Graphics2D g2d, final long beginDrawMillis, final double xform ) {
+      if ( _referenceDay == null ) {
+         return;
+      }
+      final long begin = _referenceDay.getStartMillis();
+      final long end = _referenceDay.getStopMillis();
+      double x1 = (begin - beginDrawMillis) / xform;
+      double x2 = (end - beginDrawMillis) / xform;
+      if ( x1 >= x2 ) {
+         x2 = x1 + 1;
+      }
+      x1 -= TimeSpanDrawUtil.getStartOffset( _referenceDay );
+      x2 += TimeSpanDrawUtil.getEndOffset( _referenceDay );
+      final double width = x2 - x1;
+      if ( width < 5 ) {
+         x1 -= (5 - width) / 2;
+         x2 = x1 + 5;
+      }
+      final Rectangle clipBounds = g2d.getClipBounds();
+      g2d.setColor( DOC_TIME_COLOR );
+      g2d.fillRect( (int)x1, clipBounds.y, (int)(x2 - x1), clipBounds.height );
+   }
+
+   private void paintGridLines( final Graphics2D g2d ) {
+      // Drawing directly is much faster than using a grid renderer
+      // Store unique grid X values, otherwise we may have redundant draws
+      if ( _gridLineXs.isEmpty() ) {
+         return;
+      }
+      final Rectangle clipBounds = g2d.getClipBounds();
+      final int y1 = clipBounds.y;
+      final int y2 = y1 + clipBounds.height;
+      g2d.setColor( Color.LIGHT_GRAY );
+      for ( Integer gridLineX : _gridLineXs ) {
+         final int x = gridLineX;
+         for ( int y = y1; y < y2 - 2; y += 4 ) {
+            g2d.drawLine( x, y, x, y + 2 );
+         }
+      }
+   }
+
+   private void paintFuzzyGridLines( final Graphics2D g2d ) {
+      // Drawing directly is much faster than using a grid renderer
+      // Store unique grid X values, otherwise we may have redundant draws
+      if ( _fuzzyGridLineXs.isEmpty() ) {
+         return;
+      }
+      final Rectangle clipBounds = g2d.getClipBounds();
+      final int y1 = clipBounds.y;
+      final int y2 = y1 + clipBounds.height;
+      g2d.setColor( Color.LIGHT_GRAY );
+      for ( Integer gridLineX : _fuzzyGridLineXs ) {
+         final int x = gridLineX;
+         for ( int y = y1; y < y2 - 2; y += 4 ) {
+            g2d.drawLine( x - 1, y, x, y + 1 );
+            g2d.drawLine( x + 1, y + 2, x, y + 3 );
+         }
+      }
+   }
+
+   private void paintTimeSpans( final JTimelineComponent timelineComponent,
+                                final Graphics2D g2d, final Iterable<PointedTimeSpan> timeline,
+                                final long beginDrawMillis, final double xform ) {
+      final Rectangle clipBounds = g2d.getClipBounds();
+      final Collection<PointedTimeSpan> selectedTimeSpans = timelineComponent.getSelectedTimeSpans();
+      for ( PointedTimeSpan timeSpan : timeline ) {
+         final Rectangle bounds = getTimeSpanBounds( beginDrawMillis, xform, timeSpan );
+         if ( bounds.width <= 0 || bounds.height <= 0 || !bounds.intersects( clipBounds ) ) {
+            continue;
+         }
+         // Renderer loses track of expanded toggle ??
+         final boolean expanded = false;
+         final TimeSpanRenderer renderer = timelineComponent.getTimeSpanRenderer( timeSpan.getClass() );
+         final boolean isSelected = selectedTimeSpans.contains( timeSpan );
+         final JComponent rendererComponent = renderer.getTimeSpanRendererComponent( timelineComponent, timeSpan,
+               isSelected, expanded, false );
+         if ( bounds.width < 5 ) {
+            bounds.x -= (5 - bounds.width) / 2;
+            bounds.width = 5;
+         }
+         _rendererPane.paintComponent( g2d, rendererComponent, timelineComponent,
+               bounds.x, bounds.y, bounds.width, bounds.height, true );
+      }
+   }
+
+   /**
+    * {@inheritDoc}
+    */
+   @Override
+   protected void paintComponent( final Graphics g, final JComponent component ) {
+      final int totalWidth = _cachedBounds.width;
+      final JTimelineComponent timelineComponent = (JTimelineComponent)component;
+      final Timeline timeline = timelineComponent.getModel();
+      final long beginDrawMillis = timelineComponent.getBeginDrawMillis();
+      final long drawTimeDelta = timelineComponent.getDrawTimeDelta();
+      final double xform = drawTimeDelta / (double)totalWidth;
+      final Graphics2D g2d = (Graphics2D)g.create();
+      paintDocTime( g2d, beginDrawMillis, xform );
+      paintGridLines( g2d );
+      paintFuzzyGridLines( g2d );
+      if ( !_drawGridOnly ) {
+         paintTimeSpans( timelineComponent, g2d, timeline, beginDrawMillis, xform );
+      }
+      g2d.dispose();
+      // remove any renderer components that were left on the RendererPane
+      _rendererPane.removeAll();
+   }
+
+
+}

Added: ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/gui/timeline/ui/TimelineUI.java
URL: http://svn.apache.org/viewvc/ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/gui/timeline/ui/TimelineUI.java?rev=1660963&view=auto
==============================================================================
--- ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/gui/timeline/ui/TimelineUI.java (added)
+++ ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/gui/timeline/ui/TimelineUI.java Thu Feb 19 18:06:13 2015
@@ -0,0 +1,25 @@
+package org.chboston.cnlp.timeline.gui.timeline.ui;
+
+import org.chboston.cnlp.timeline.timespan.TimeSpan;
+
+import javax.swing.*;
+import javax.swing.plaf.ComponentUI;
+import java.awt.*;
+
+/**
+ * Author: SPF
+ * Affiliation: CHIP-NLP
+ * Date: 8/30/12
+ */
+abstract public class TimelineUI extends ComponentUI {
+
+
+   public abstract Rectangle doLayout( final JComponent component );
+
+   public abstract Rectangle getTimeSpanBounds( final JComponent component, final TimeSpan timeSpan );
+
+   public abstract TimeSpan getClosestTimeSpanForLocation( final JComponent component,
+                                                           final int x, final int y );
+
+
+}

Added: ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/gui/timespan/TimeSpanDrawUtil.java
URL: http://svn.apache.org/viewvc/ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/gui/timespan/TimeSpanDrawUtil.java?rev=1660963&view=auto
==============================================================================
--- ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/gui/timespan/TimeSpanDrawUtil.java (added)
+++ ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/gui/timespan/TimeSpanDrawUtil.java Thu Feb 19 18:06:13 2015
@@ -0,0 +1,107 @@
+package org.chboston.cnlp.timeline.gui.timespan;
+
+import org.chboston.cnlp.timeline.gui.event.EventColor;
+import org.chboston.cnlp.timeline.timespan.EndPointer;
+import org.chboston.cnlp.timeline.timespan.TimeSpan;
+import org.chboston.cnlp.timeline.timespan.plus.PointedTimeSpan;
+import org.chboston.cnlp.timeline.timespan.plus.TimeEndPoint;
+
+import java.awt.*;
+
+/**
+ * Author: SPF
+ * Affiliation: CHIP-NLP
+ * Date: 11/9/12
+ */
+final public class TimeSpanDrawUtil {
+
+   static public final int LONG_X_OFFSET = 10;
+   static public final int SHORT_X_OFFSET = 5;
+
+   public static final int TIMESPAN_CELL_HEIGHT = 12;
+   static public final int LINE_THICKNESS = 5;
+   static public final int LINE_THINNESS = 3;
+
+   static public final int FULL_SPAN_HEIGHT = 8;
+   static public final int MIN_SPAN_HEIGHT = 3;
+   static public final int MIN_EVENT_HEIGHT = 2;
+
+
+   private TimeSpanDrawUtil() {
+   }
+
+   static private int getStartEndPointerOffset( final PointedTimeSpan timeSpan ) {
+      final TimeEndPoint startTime = timeSpan.getStartTime();
+      final EndPointer startPointer = startTime.getPointer();
+      switch ( startPointer ) {
+         case BEFORE:
+            return LONG_X_OFFSET;
+         case AFTER:
+            return 0;
+         case EQUAL:
+            return 0;
+         case OVERLAP:
+            return LONG_X_OFFSET;//SHORT_X_OFFSET;
+      }
+      return 0;
+   }
+
+   static private int getStopEndPointerOffset( final PointedTimeSpan timeSpan ) {
+      final TimeEndPoint stopTime = timeSpan.getStopTime();
+      final EndPointer stopPointer = stopTime.getPointer();
+      switch ( stopPointer ) {
+         case BEFORE:
+            return 0;
+         case AFTER:
+            return LONG_X_OFFSET;
+         case EQUAL:
+            return 0;
+         case OVERLAP:
+            return LONG_X_OFFSET;//SHORT_X_OFFSET;
+      }
+      return 0;
+   }
+
+   static public int getStartOffset( final TimeSpan timeSpan ) {
+      if ( timeSpan instanceof PointedTimeSpan ) {
+         return getStartEndPointerOffset( (PointedTimeSpan)timeSpan );
+      }
+      return 0;
+   }
+
+   static public int getEndOffset( final TimeSpan timeSpan ) {
+      if ( timeSpan instanceof PointedTimeSpan ) {
+         return getStopEndPointerOffset( (PointedTimeSpan)timeSpan );
+      }
+      return 0;
+   }
+
+   static public boolean isEndOnly( final TimeSpan timeSpan ) {
+//      if ( timeSpan instanceof TimeSpanPlus ) {
+//         return isEndOnly( ((TimeSpanPlus)timeSpan).getStartTime(), ((TimeSpanPlus)timeSpan).getStopTime() );
+//      }
+      return false;
+   }
+
+
+//   static private boolean isEndOnly( final TimeEndPoint startTimePoint, final TimeEndPoint stopTimePoint ) {
+//      return ((startTimePoint.getPointer() == EndPointer.BEFORE &&
+//            && stopTimePoint == TimeEndPoint.BEFORE)
+//            || (stopTimePoint == TimeEndPoint.AFTER
+//            && startTimePoint == TimeEndPoint.AFTER));
+//   }
+
+   static public Color getPlainColor( final boolean isSelected, final boolean isFocused, final boolean isFuzzy ) {
+      return EventColor.getTimeSpanColor( isSelected, isFocused, isFuzzy ).getColor();
+   }
+
+   static public Color getPlainColor( final boolean isSelected, final boolean isFocused, final boolean isFuzzy,
+                                      final int width ) {
+      if ( width < SHORT_X_OFFSET ) {
+         return EventColor.getTimeSpanColor( isSelected, isFocused, false ).getColor();
+      }
+      return EventColor.getTimeSpanColor( isSelected, isFocused, isFuzzy ).getColor();
+   }
+
+
+}

Added: ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/gui/timespan/TimeSpanSelectionEvent.java
URL: http://svn.apache.org/viewvc/ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/gui/timespan/TimeSpanSelectionEvent.java?rev=1660963&view=auto
==============================================================================
--- ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/gui/timespan/TimeSpanSelectionEvent.java (added)
+++ ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/gui/timespan/TimeSpanSelectionEvent.java Thu Feb 19 18:06:13 2015
@@ -0,0 +1,74 @@
+package org.chboston.cnlp.timeline.gui.timespan;
+
+import org.chboston.cnlp.timeline.timespan.TimeSpan;
+
+import java.util.Collection;
+import java.util.EventObject;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Author: SPF
+ * Affiliation: CHIP-NLP
+ * Date: 9/20/12
+ */
+final public class TimeSpanSelectionEvent extends EventObject {
+   private final Set<TimeSpan> _selectionSet;
+   private final Set<String> _selectionTexts;
+   private final boolean _isAdjusting;
+
+   /**
+    * Represents a change in selection status between {@code firstIndex} and
+    * {@code lastIndex}, inclusive. {@code firstIndex} is less than or equal to
+    * {@code lastIndex}. The selection of at least one index within the range will
+    * have changed.
+    *
+    * @param selections  all selections to which this event applies
+    * @param isAdjusting whether or not this is one in a series of
+    *                    multiple events, where changes are still being made
+    */
+   public TimeSpanSelectionEvent( final Object source, final Collection<TimeSpan> selections,
+                                  final Collection<String> selectionTexts, final boolean isAdjusting ) {
+      super( source );
+      _selectionSet = new HashSet<>( selections );
+      _selectionTexts = new HashSet<>( selectionTexts );
+      _isAdjusting = isAdjusting;
+   }
+
+   public Collection<TimeSpan> getSelectedTimeSpans() {
+      return _selectionSet;
+   }
+
+   public Collection<String> getSelectionTexts() {
+      return _selectionTexts;
+   }
+
+   /**
+    * Returns whether or not this is one in a series of multiple events,
+    * where changes are still being made. See the documentation for
+    * {@link javax.swing.ListSelectionModel#setValueIsAdjusting} for
+    * more details on how this is used.
+    *
+    * @return {@code true} if this is one in a series of multiple events,
+    * where changes are still being made
+    */
+   public boolean getValueIsAdjusting() {
+      return _isAdjusting;
+   }
+
+   /**
+    * Returns a {@code String} that displays and identifies this
+    * object's properties.
+    *
+    * @return a String representation of this object
+    */
+   public String toString() {
+      String properties =
+            " source=" + getSource() +
+            " selections= " + _selectionSet.toString() +
+            " isAdjusting= " + _isAdjusting +
+            " ";
+      return getClass().getName() + "[" + properties + "]";
+   }
+
+}

Added: ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/gui/timespan/TimeSpanSelectionListener.java
URL: http://svn.apache.org/viewvc/ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/gui/timespan/TimeSpanSelectionListener.java?rev=1660963&view=auto
==============================================================================
--- ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/gui/timespan/TimeSpanSelectionListener.java (added)
+++ ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/gui/timespan/TimeSpanSelectionListener.java Thu Feb 19 18:06:13 2015
@@ -0,0 +1,18 @@
+package org.chboston.cnlp.timeline.gui.timespan;
+
+import java.util.EventListener;
+
+/**
+ * Author: SPF
+ * Affiliation: CHIP-NLP
+ * Date: 9/20/12
+ */
+public interface TimeSpanSelectionListener extends EventListener {
+   /**
+    * Called whenever the value of the selection changes.
+    *
+    * @param event the event that characterizes the change.
+    */
+   void valueChanged( final TimeSpanSelectionEvent event );
+
+}

Added: ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/gui/timespan/list/TimeSpanCellRenderer.java
URL: http://svn.apache.org/viewvc/ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/gui/timespan/list/TimeSpanCellRenderer.java?rev=1660963&view=auto
==============================================================================
--- ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/gui/timespan/list/TimeSpanCellRenderer.java (added)
+++ ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/gui/timespan/list/TimeSpanCellRenderer.java Thu Feb 19 18:06:13 2015
@@ -0,0 +1,177 @@
+package org.chboston.cnlp.timeline.gui.timespan.list;
+
+
+import org.chboston.cnlp.timeline.timespan.plus.PointedTimeSpan;
+import org.chboston.cnlp.timeline.timespan.plus.TimeSpanPlus;
+
+import javax.swing.*;
+import javax.swing.border.EmptyBorder;
+import java.awt.*;
+
+/**
+ * @author SPF , chip-nlp
+ * @version %I%
+ * @since 10/2/2014
+ */
+public class TimeSpanCellRenderer extends JLabel {
+
+   public TimeSpanCellRenderer() {
+      setBorder( new EmptyBorder( 0, 0, 0, 0 ) );
+      setForeground( Color.BLACK );
+      setFont( getFont().deriveFont( Font.BOLD, getFont().getSize() + 2 ) );
+      setOpaque( true );
+   }
+
+   public void displayTimeSpan( final PointedTimeSpan timeSpan ) {
+      setBorder( new EmptyBorder( 12, 4, 4, 0 ) );
+      if ( timeSpan.equals( TimeSpanPlus.UNKNOWN_TIMESPAN_PLUS ) ) {
+         setText( "Unknown Time Span" );
+         setToolTipText( "Unknown Time Span" );
+      } else {
+         setText( timeSpan.toString() );
+         setToolTipText( timeSpan.toString() );
+      }
+   }
+
+   public void clearTimeSpan( final PointedTimeSpan timeSpan ) {
+      setBorder( new EmptyBorder( 0, 0, 0, 0 ) );
+      setText( "" );
+      if ( timeSpan.equals( TimeSpanPlus.UNKNOWN_TIMESPAN_PLUS ) ) {
+         setToolTipText( "Unknown Time Span" );
+      } else {
+         setToolTipText( timeSpan.toString() );
+      }
+   }
+
+   /**
+    * Overridden for performance reasons.
+    * See the <a href="#override">Implementation Note</a>
+    * for more information.
+    */
+   @Override
+   public void validate() {
+   }
+
+   /**
+    * Overridden for performance reasons.
+    * See the <a href="#override">Implementation Note</a>
+    * for more information.
+    *
+    * @since 1.5
+    */
+   @Override
+   public void invalidate() {
+   }
+
+   /**
+    * Overridden for performance reasons.
+    * See the <a href="#override">Implementation Note</a>
+    * for more information.
+    *
+    * @since 1.5
+    */
+   @Override
+   public void repaint() {
+   }
+
+   /**
+    * Overridden for performance reasons.
+    * See the <a href="#override">Implementation Note</a>
+    * for more information.
+    */
+   @Override
+   public void revalidate() {
+   }
+
+   /**
+    * Overridden for performance reasons.
+    * See the <a href="#override">Implementation Note</a>
+    * for more information.
+    */
+   @Override
+   public void repaint( long tm, int x, int y, int width, int height ) {
+   }
+
+   /**
+    * Overridden for performance reasons.
+    * See the <a href="#override">Implementation Note</a>
+    * for more information.
+    */
+   @Override
+   public void repaint( Rectangle r ) {
+   }
+
+
+   /**
+    * Overridden for performance reasons.
+    * See the <a href="#override">Implementation Note</a>
+    * for more information.
+    */
+   @Override
+   public void firePropertyChange( String propertyName, byte oldValue, byte newValue ) {
+   }
+
+   /**
+    * Overridden for performance reasons.
+    * See the <a href="#override">Implementation Note</a>
+    * for more information.
+    */
+   @Override
+   public void firePropertyChange( String propertyName, char oldValue, char newValue ) {
+   }
+
+   /**
+    * Overridden for performance reasons.
+    * See the <a href="#override">Implementation Note</a>
+    * for more information.
+    */
+   @Override
+   public void firePropertyChange( String propertyName, short oldValue, short newValue ) {
+   }
+
+   /**
+    * Overridden for performance reasons.
+    * See the <a href="#override">Implementation Note</a>
+    * for more information.
+    */
+   @Override
+   public void firePropertyChange( String propertyName, int oldValue, int newValue ) {
+   }
+
+   /**
+    * Overridden for performance reasons.
+    * See the <a href="#override">Implementation Note</a>
+    * for more information.
+    */
+   @Override
+   public void firePropertyChange( String propertyName, long oldValue, long newValue ) {
+   }
+
+   /**
+    * Overridden for performance reasons.
+    * See the <a href="#override">Implementation Note</a>
+    * for more information.
+    */
+   @Override
+   public void firePropertyChange( String propertyName, float oldValue, float newValue ) {
+   }
+
+   /**
+    * Overridden for performance reasons.
+    * See the <a href="#override">Implementation Note</a>
+    * for more information.
+    */
+   @Override
+   public void firePropertyChange( String propertyName, double oldValue, double newValue ) {
+   }
+
+   /**
+    * Overridden for performance reasons.
+    * See the <a href="#override">Implementation Note</a>
+    * for more information.
+    */
+   @Override
+   public void firePropertyChange( String propertyName, boolean oldValue, boolean newValue ) {
+   }
+
+}

Added: ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/gui/timespan/plus/AbstractTimeSpanRenderer.java
URL: http://svn.apache.org/viewvc/ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/gui/timespan/plus/AbstractTimeSpanRenderer.java?rev=1660963&view=auto
==============================================================================
--- ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/gui/timespan/plus/AbstractTimeSpanRenderer.java (added)
+++ ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/gui/timespan/plus/AbstractTimeSpanRenderer.java Thu Feb 19 18:06:13 2015
@@ -0,0 +1,134 @@
+package org.chboston.cnlp.timeline.gui.timespan.plus;
+
+
+import org.chboston.cnlp.timeline.gui.timeline.TimelineComponent;
+import org.chboston.cnlp.timeline.timespan.plus.PointedTimeSpan;
+
+import javax.swing.*;
+import java.awt.*;
+
+/**
+ * Author: SPF
+ * Affiliation: CHIP-NLP
+ * Date: 8/2/13
+ */
+abstract public class AbstractTimeSpanRenderer extends JComponent implements TimeSpanRenderer {
+
+   // TODO - new highlight coloring of currently selected list event(s)
+
+   private boolean _isSelected;
+   private boolean _focused;
+   private boolean _isExpanded;
+   private PointedTimeSpan _timeSpan;
+
+   public AbstractTimeSpanRenderer() {
+      super();
+      setOpaque( true );
+   }
+
+
+   @Override
+   public JComponent getTimeSpanRendererComponent( final TimelineComponent timelineComponent,
+                                                   final PointedTimeSpan timeSpan,
+                                                   boolean selected, boolean expanded,
+                                                   boolean Focused ) {
+      _isSelected = selected;
+      _focused = Focused;
+      _isExpanded = expanded;
+      _timeSpan = timeSpan;
+      setToolTipText( timeSpan.toString() );
+      return this;
+   }
+
+   public boolean isFuzzy() {
+      return _timeSpan.isFuzzyDate();
+   }
+
+   public boolean isSelected() {
+      return _isSelected;
+   }
+
+   public boolean isFocused() {
+      return _focused;
+   }
+
+   public boolean isExpanded() {
+      return _isExpanded;
+   }
+
+   protected PointedTimeSpan getTimeSpan() {
+      return _timeSpan;
+   }
+
+
+   /**
+    * Overridden for performance reasons.
+    * See the <a href="#override">Implementation Note</a>
+    * for more information.
+    *
+    * @since 1.5
+    */
+   public void invalidate() {
+   }
+
+   /**
+    * Overridden for performance reasons.
+    * See the <a href="#override">Implementation Note</a>
+    * for more information.
+    */
+   public void validate() {
+   }
+
+   /**
+    * Overridden for performance reasons.
+    * See the <a href="#override">Implementation Note</a>
+    * for more information.
+    */
+   public void revalidate() {
+   }
+
+   /**
+    * Overridden for performance reasons.
+    * See the <a href="#override">Implementation Note</a>
+    * for more information.
+    */
+   public void repaint( long tm, int x, int y, int width, int height ) {
+   }
+
+   /**
+    * Overridden for performance reasons.
+    * See the <a href="#override">Implementation Note</a>
+    * for more information.
+    */
+   public void repaint( Rectangle r ) {
+   }
+
+   /**
+    * Overridden for performance reasons.
+    * See the <a href="#override">Implementation Note</a>
+    * for more information.
+    *
+    * @since 1.5
+    */
+   public void repaint() {
+   }
+
+
+   /**
+    * Overridden for performance reasons.
+    * See the <a href="#override">Implementation Note</a>
+    * for more information.
+    */
+   protected void firePropertyChange( String propertyName, Object oldValue, Object newValue ) {
+   }
+
+   /**
+    * Overridden for performance reasons.
+    * See the <a href="#override">Implementation Note</a>
+    * for more information.
+    */
+   public void firePropertyChange( String propertyName, boolean oldValue, boolean newValue ) {
+   }
+
+
+}

Added: ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/gui/timespan/plus/DefaultRelationRenderer.java
URL: http://svn.apache.org/viewvc/ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/gui/timespan/plus/DefaultRelationRenderer.java?rev=1660963&view=auto
==============================================================================
--- ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/gui/timespan/plus/DefaultRelationRenderer.java (added)
+++ ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/gui/timespan/plus/DefaultRelationRenderer.java Thu Feb 19 18:06:13 2015
@@ -0,0 +1,191 @@
+package org.chboston.cnlp.timeline.gui.timespan.plus;
+
+import org.chboston.cnlp.nlp.annotation.attribute.AttributeUtil;
+import org.chboston.cnlp.nlp.annotation.entity.Entity;
+import org.chboston.cnlp.timeline.gui.event.EventColor;
+import org.chboston.cnlp.timeline.gui.timeline.TimelineComponent;
+import org.chboston.cnlp.timeline.timeline.Timeline;
+import org.chboston.cnlp.timeline.timespan.plus.TimeSpanPlus;
+
+import javax.swing.*;
+import java.awt.*;
+import java.util.Collection;
+
+/**
+ * Author: SPF
+ * Affiliation: CHIP-NLP
+ * Date: 8/2/13
+ */
+public class DefaultRelationRenderer extends DefaultTimeSpanRenderer {
+
+
+   static private final TimeSpanPainter UNCERTAIN_PAINTER = new UncertainTimeSpanPainter();
+   static private final TimeSpanPainter NEGATED_PAINTER = new NegatedTimeSpanPainter();
+
+
+   //   static private boolean hasNegativePolarity( final Annotation event ) {
+//      final Attribute polarity = event.getAttribute( DefinedAttributeType.POLARITY.getName() );
+//      // Polarity is "POS" or "NEG" for THYME
+//      return polarity != null && polarity.getValue().equalsIgnoreCase( "NEG" );
+//   }
+//
+//   static private boolean hasUncertainty( final Annotation event ) {
+//      final Attribute modality = event.getAttribute( DefinedAttributeType.CONTEXT_MODALITY.getName() );
+//      // Contextual Modality is "ACTUAL" "HYPOTHETICAL" "HEDGED" or "GENERIC" for THYME
+//      return modality != null
+//            && (modality.getValue().equalsIgnoreCase( "HYPOTHETICAL" )
+//            || modality.getValue().equalsIgnoreCase( "HEDGED" ));
+//   }
+//
+   private int _eventCount;
+   private int _negativeCount;
+   private int _uncertainCount;
+   private boolean _paintNegative;
+   private boolean _paintUncertain;
+
+
+   @Override
+   protected Color getPlainColor( final boolean isSelected, final boolean isFocused, final boolean isFuzzy ) {
+      return Color.RED;
+//      if ( isSelected || (!_paintNegative && !_paintUncertain) ) {
+//         return super.getPlainColor( isSelected, isFocused, isFuzzy );
+//      }
+//      final EventStatus eventStatus = EventStatus.getEventStatus( _paintNegative, _paintUncertain );
+//      return EventColor.getEventColor( eventStatus, isFuzzy ).getColor();
+//      if ( _paintNegative ) {
+//         return isFuzzy ? EventColor.FUZZY_NEGATED.getColor() : EventColor.NEGATED.getColor();
+//      } else if ( _paintUncertain ) {
+//         return isFuzzy ? EventColor.FUZZY_UNCERTAIN.getColor() : EventColor.UNCERTAIN.getColor();
+//      }
+//      return super.getPlainColor( isSelected,  isFocused, isFuzzy );
+   }
+
+   @Override
+   protected Color getPlainColor( final boolean isSelected, final boolean isFocused, final boolean isFuzzy,
+                                  final int width ) {
+      if ( isSelected || (!_paintNegative && !_paintUncertain) ) {
+         return super.getPlainColor( isSelected, isFocused, isFuzzy, width );
+      }
+      if ( _paintNegative ) {
+         return isFuzzy ? EventColor.FUZZY_NEGATED.getColor() : EventColor.NEGATED.getColor();
+      } else if ( _paintUncertain ) {
+         return isFuzzy ? EventColor.FUZZY_UNCERTAIN.getColor() : EventColor.UNCERTAIN.getColor();
+      }
+      return super.getPlainColor( isSelected, isFocused, isFuzzy, width );
+   }
+
+   @Override
+   protected TimeSpanPainter getPainter() {
+      if ( _paintNegative ) {
+         return NEGATED_PAINTER;
+      } else if ( _paintUncertain ) {
+         return UNCERTAIN_PAINTER;
+      }
+      return super.getPainter();
+   }
+
+
+   public JComponent getTimeSpanRendererComponent( final TimelineComponent timelineComponent,
+                                                   final TimeSpanPlus timeSpan,
+                                                   boolean selected, boolean expanded,
+                                                   boolean Focused ) {
+      _eventCount = 0;
+      _negativeCount = 0;
+      _uncertainCount = 0;
+      final Timeline timeline = timelineComponent.getModel();
+      final Collection<Entity> entities = timeline.getEntities( timeSpan );
+      for ( Entity entity : entities ) {
+         if ( AttributeUtil.hasNegativePolarity( entity ) ) {
+            _negativeCount++;
+         }
+         if ( AttributeUtil.hasUncertainty( entity ) ) {
+            _uncertainCount++;
+         }
+         _eventCount++;
+      }
+      super.getTimeSpanRendererComponent( timelineComponent, timeSpan, selected, expanded, Focused );
+      return this;
+   }
+
+
+   @Override
+   public void paint( final Graphics g ) {
+      _paintNegative = false;
+      _paintUncertain = false;
+      if ( isSelected() || (_negativeCount == 0 && _uncertainCount == 0) ) {
+         super.paint( g );
+         return;
+      }
+      if ( _negativeCount == _eventCount ) {
+         _paintNegative = true;
+         super.paint( g );
+         return;
+      } else if ( _uncertainCount == _eventCount ) {
+         _paintUncertain = true;
+         super.paint( g );
+         return;
+      }
+      if ( _negativeCount > 0 && _uncertainCount > 0 && _negativeCount + _uncertainCount < _eventCount ) {
+         final int thirdHeight = getHeight() / 3;
+         final int bottomHeight = getHeight() - thirdHeight - 1;
+         int rightX = getWidth();
+         final Rectangle clipBounds = g.getClipBounds();
+         _paintNegative = true;
+         final Rectangle topClip = new Rectangle( 0, 0, rightX, thirdHeight + 1 );
+         Rectangle clip = topClip.intersection( clipBounds );
+         g.setClip( clip );
+         super.paint( g );
+         _paintNegative = false;
+         final Rectangle bottomClip = new Rectangle( 0, bottomHeight, rightX, thirdHeight + 1 );
+         clip = bottomClip.intersection( clipBounds );
+         g.setClip( clip );
+         super.paint( g );
+         _paintUncertain = true;
+         final Rectangle centerClip = new Rectangle( 0, thirdHeight + 1, rightX, thirdHeight - 2 );
+         clip = centerClip.intersection( clipBounds );
+         g.setClip( clip );
+         super.paint( g );
+         return;
+      }
+      final int halfHeight = getHeight() / 2 - 1;
+      int rightX = getWidth();
+      final Rectangle clipBounds = g.getClipBounds();
+      final Rectangle topClip = new Rectangle( 0, 0, rightX, halfHeight + 1 );
+      Rectangle clip = topClip.intersection( clipBounds );
+      _paintNegative = (_negativeCount > 0);
+      g.setClip( clip );
+      super.paint( g );
+      _paintNegative = false;
+      _paintUncertain = (_uncertainCount > 0);
+      final Rectangle bottomClip = new Rectangle( 0, halfHeight + 1, rightX, halfHeight );
+      clip = bottomClip.intersection( clipBounds );
+      g.setClip( clip );
+      super.paint( g );
+   }
+
+   static final private class UncertainTimeSpanPainter implements TimeSpanPainter {
+      @Override
+      public void paint( final Graphics2D g2d, final int x, final int y, final int width, final int height ) {
+         for ( int y1 = y; y1 < y + height - 2; y1++ ) {
+            for ( int x1 = x; x1 < x + width; x1 += 4 ) {
+               g2d.drawLine( x1, y1, x1 + 1, y1 + 1 );
+            }
+            for ( int x2 = x + 2; x2 < x + width; x2 += 4 ) {
+               g2d.drawLine( x2, y1 + 2, x2 + 1, y1 - 1 );
+            }
+         }
+      }
+   }
+
+
+   static final private class NegatedTimeSpanPainter implements TimeSpanPainter {
+      @Override
+      public void paint( final Graphics2D g2d, final int x, final int y, final int width, final int height ) {
+         for ( int x1 = x; x1 < x + width - 2; x1 += 4 ) {
+            g2d.fillRect( x1, y, 2, height );
+         }
+      }
+   }
+
+
+}

Added: ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/gui/timespan/plus/DefaultRelationRenderer2.java
URL: http://svn.apache.org/viewvc/ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/gui/timespan/plus/DefaultRelationRenderer2.java?rev=1660963&view=auto
==============================================================================
--- ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/gui/timespan/plus/DefaultRelationRenderer2.java (added)
+++ ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/gui/timespan/plus/DefaultRelationRenderer2.java Thu Feb 19 18:06:13 2015
@@ -0,0 +1,501 @@
+package org.chboston.cnlp.timeline.gui.timespan.plus;
+
+import org.chboston.cnlp.nlp.annotation.attribute.AttributeUtil;
+import org.chboston.cnlp.nlp.annotation.entity.Entity;
+import org.chboston.cnlp.timeline.gui.event.EventColor;
+import org.chboston.cnlp.timeline.gui.event.EventStatus;
+import org.chboston.cnlp.timeline.gui.timeline.TimelineComponent;
+import org.chboston.cnlp.timeline.gui.timespan.TimeSpanDrawUtil;
+import org.chboston.cnlp.timeline.timeline.Timeline;
+import org.chboston.cnlp.timeline.timespan.EndPointer;
+import org.chboston.cnlp.timeline.timespan.plus.PointedTimeSpan;
+import org.chboston.cnlp.timeline.timespan.plus.TimeEndPoint;
+
+import javax.swing.*;
+import java.awt.*;
+import java.util.Collection;
+
+/**
+ * Author: SPF
+ * Affiliation: CHIP-NLP
+ * Date: 8/2/13
+ */
+final public class DefaultRelationRenderer2 extends AbstractTimeSpanRenderer {
+
+
+   static private final TimeSpanPainter DEFAULT_PAINTER = new DefaultTimeSpanPainter();
+   static private final TimeSpanPainter UNCERTAIN_PAINTER = new UncertainTimeSpanPainter();
+   static private final TimeSpanPainter NEGATED_PAINTER = new NegatedTimeSpanPainter();
+
+
+   private int _eventCount;
+   private int _negativeCount;
+   private int _uncertainCount;
+
+
+   static private Color getSelectedColor( final boolean isFocused, final boolean isFuzzy ) {
+      return EventColor.getTimeSpanColor( true, isFocused, isFuzzy ).getColor();
+   }
+
+   static private Color getUncertainColor( final boolean isSelected, final boolean isFocused, final boolean isFuzzy ) {
+      return EventColor.getEventColor( EventStatus.UNCERTAIN, isFuzzy ).getColor();
+   }
+
+   static private Color getNegatedColor( final boolean isSelected, final boolean isFocused, final boolean isFuzzy ) {
+      return EventColor.getEventColor( EventStatus.NEGATED, isFuzzy ).getColor();
+   }
+
+   static private Color getAffirmedColor( final boolean isSelected, final boolean isFocused, final boolean isFuzzy ) {
+      return EventColor.getEventColor( EventStatus.NORMAL, isFuzzy ).getColor();
+   }
+
+   @Override
+   public JComponent getTimeSpanRendererComponent( final TimelineComponent timelineComponent,
+                                                   final PointedTimeSpan timeSpan,
+                                                   boolean selected, boolean expanded,
+                                                   boolean Focused ) {
+      _eventCount = 0;
+      _negativeCount = 0;
+      _uncertainCount = 0;
+      final Timeline timeline = timelineComponent.getModel();
+      final Collection<Entity> entities = timeline.getEntities( timeSpan );
+      for ( Entity entity : entities ) {
+         if ( AttributeUtil.hasNegativePolarity( entity ) ) {
+            _negativeCount++;
+         } else if ( AttributeUtil.hasUncertainty( entity ) ) {
+            // Don't want to register a single event as both negated and uncertain - negated overrides
+            _uncertainCount++;
+         }
+         _eventCount++;
+      }
+      super.getTimeSpanRendererComponent( timelineComponent, timeSpan, selected, expanded, Focused );
+      return this;
+   }
+
+   static final private class DrawHeights {
+      private int __marginHeight;
+      private int __affirmedHeight;
+      private int __uncertainHeight;
+      private int __negatedHeight;
+
+      private DrawHeights( final int minHeight, final int maxHeight,
+                           final int eventCount, final int uncertainCount, final int negatedCount ) {
+         final int lineHeight = Math.min( Math.max( minHeight, eventCount ), maxHeight );
+         if ( uncertainCount > 0 ) {
+            if ( uncertainCount == eventCount ) {
+               __uncertainHeight = lineHeight;
+            } else {
+               final double uncertain = uncertainCount / (double)eventCount;
+               __uncertainHeight = Math.max( TimeSpanDrawUtil.MIN_EVENT_HEIGHT,
+                     (int)Math.floor( uncertain * lineHeight ) );
+            }
+         }
+         if ( negatedCount > 0 ) {
+            if ( negatedCount == eventCount ) {
+               __negatedHeight = lineHeight;
+            } else {
+               final double negative = negatedCount / (double)eventCount;
+               __negatedHeight = Math.max( TimeSpanDrawUtil.MIN_EVENT_HEIGHT,
+                     (int)Math.floor( negative * lineHeight ) );
+            }
+         }
+         if ( eventCount == uncertainCount + negatedCount ) {
+            __affirmedHeight = 0;
+         } else {
+            __affirmedHeight = Math.max( TimeSpanDrawUtil.MIN_EVENT_HEIGHT,
+                  lineHeight - __uncertainHeight - __negatedHeight );
+         }
+         __marginHeight = (TimeSpanDrawUtil.TIMESPAN_CELL_HEIGHT
+                           - __affirmedHeight - __uncertainHeight - __negatedHeight) / 2;
+      }
+   }
+
+
+   @Override
+   public void paint( final Graphics g ) {
+      final DrawHeights heights = new DrawHeights( TimeSpanDrawUtil.MIN_SPAN_HEIGHT, TimeSpanDrawUtil.FULL_SPAN_HEIGHT,
+            _eventCount, _uncertainCount, _negativeCount );
+      int y = 0;
+      int width = getWidth();
+      final PointedTimeSpan timeSpan = getTimeSpan();
+      final Graphics2D g2d = (Graphics2D)g.create();
+      if ( isSelected() ) {
+         final Color centerColor = getSelectedColor( false, false );
+         final Color endColor = getSelectedColor( false, true );
+//         final Rectangle marginBounds = new Rectangle( 0, heights.__marginHeight-1, width, 1 );
+         final Rectangle marginBounds = new Rectangle( 0, heights.__marginHeight - 2, width, 2 );
+         paintTimeSpanPlusLine( g2d, marginBounds, centerColor, endColor, timeSpan, DEFAULT_PAINTER );
+      }
+      paintRelativeStart( g2d, timeSpan );
+      paintRelativeStop( g2d, timeSpan );
+      y += heights.__marginHeight;
+      if ( heights.__affirmedHeight > 0 ) {
+         final Color centerColor = getAffirmedColor( isSelected(), false, false );
+         final Color endColor = getAffirmedColor( isSelected(), false, true );
+         final Rectangle affirmedBounds = new Rectangle( 0, y, width, heights.__affirmedHeight );
+         paintTimeSpanPlusLine( g2d, affirmedBounds, centerColor, endColor, timeSpan, DEFAULT_PAINTER );
+         y += heights.__affirmedHeight;
+      }
+      // Paint negated before uncertain so that uncertain "wave" is not covered
+      if ( heights.__negatedHeight > 0 ) {
+         y += heights.__uncertainHeight;
+         final Color centerColor = getNegatedColor( isSelected(), false, false );
+         final Color endColor = getNegatedColor( isSelected(), false, true );
+         final Rectangle negatedBounds = new Rectangle( 0, y, width, heights.__negatedHeight );
+         paintTimeSpanPlusLine( g2d, negatedBounds, centerColor, endColor, timeSpan, NEGATED_PAINTER );
+         y -= heights.__uncertainHeight;
+      }
+      if ( heights.__uncertainHeight > 0 ) {
+         final Color centerColor = getUncertainColor( isSelected(), false, false );
+         final Color endColor = getUncertainColor( isSelected(), false, true );
+         final Rectangle uncertainBounds = new Rectangle( 0, y, width, heights.__uncertainHeight );
+         paintTimeSpanPlusLine( g2d, uncertainBounds, centerColor, endColor, timeSpan, UNCERTAIN_PAINTER );
+         y += heights.__uncertainHeight;
+      }
+//      if ( heights.__negatedHeight > 0 ) {
+//         final Color centerColor = getNegatedColor( isSelected(), false, false );
+//         final Color endColor = getNegatedColor( isSelected(), false, true );
+//         final Rectangle negatedBounds = new Rectangle( 0, y, width, heights.__negatedHeight );
+//         paintTimeSpanPlusLine( g2d, negatedBounds, centerColor, endColor, timeSpan, NEGATED_PAINTER );
+      y += heights.__negatedHeight;
+//      }
+      if ( isSelected() ) {
+         final Color centerColor = getSelectedColor( false, false );
+         final Color endColor = getSelectedColor( false, true );
+//         final Rectangle marginBounds = new Rectangle( 0, y, width, 1 );
+         final Rectangle marginBounds = new Rectangle( 0, y, width, 2 );
+         paintTimeSpanPlusLine( g2d, marginBounds, centerColor, endColor, timeSpan, DEFAULT_PAINTER );
+      }
+      g2d.dispose();
+   }
+
+
+   // TODO  -- Ends on, etc.
+
+   private void paintRelativeStart( final Graphics2D g2d, final PointedTimeSpan timeSpan ) {
+      final int startOffset = TimeSpanDrawUtil.getStartOffset( timeSpan );
+      final TimeEndPoint startTime = timeSpan.getStartTime();
+      switch ( startTime.getPointer() ) {
+         case BEFORE:
+//            if ( timeSpan.isSingleDate()
+//                  && timeSpan.getStopTime().getPointer() == EndPointer.EQUAL ) {
+//               // ~ tlink Ends-On
+//               paintEdgeLine( g2d, 0, startOffset, startTime.isFuzzy() );
+//               return;
+//            }
+            paintSolidTriangle( g2d, 0, startOffset, startTime.isFuzzy() );
+            break;
+         case AFTER:
+            if ( timeSpan.isSingleDate() ) {
+               // ~ tlink After
+               return;
+            }
+            // ~ tlink Contains
+            paintEdgeLine( g2d, 0, startOffset + TimeSpanDrawUtil.LINE_THINNESS, startTime.isFuzzy() );
+            break;
+         case EQUAL:
+//            if ( !timeSpan.isSingleDate()
+//                  || timeSpan.getStopTime().getPointer() == EndPointer.AFTER ) {
+            if ( !timeSpan.isSingleDate() ) {
+               // Equal Start OR ~ tlink Begins-On
+               paintEdgeLine( g2d, 0, startOffset, startTime.isFuzzy() );
+            }
+            break;
+         case OVERLAP:
+            paintContinuation( g2d, 0, startOffset, startTime.isFuzzy() );
+            break;
+      }
+   }
+
+   private void paintRelativeStop( final Graphics2D g2d, final PointedTimeSpan timeSpan ) {
+      final int endOffset = TimeSpanDrawUtil.getEndOffset( timeSpan );
+      final TimeEndPoint timeEndPoint = timeSpan.getStopTime();
+      final int width = getWidth();
+      final int leftX = width - endOffset;
+      switch ( timeEndPoint.getPointer() ) {
+         case BEFORE:
+            if ( timeSpan.isSingleDate() ) {
+               // ~ tlink Before
+               return;
+            }
+            // ~ tlink Contains
+            paintEdgeLine( g2d, width - TimeSpanDrawUtil.LINE_THINNESS, width, timeEndPoint.isFuzzy() );
+            break;
+         case AFTER:
+//            if ( timeSpan.isSingleDate()
+//                  && timeSpan.getStartTime().getPointer() == EndPointer.EQUAL ) {
+//               // ~ tlink Ends-On
+//               paintEdgeLine( g2d, width - TimeSpanDrawUtil.LINE_THINNESS, width, timeEndPoint.isFuzzy() );
+//               return;
+//            }
+            paintSolidTriangle( g2d, width, leftX, timeEndPoint.isFuzzy() );
+            break;
+         case EQUAL:
+//            if ( !timeSpan.isSingleDate()
+//                  || timeSpan.getStartTime().getPointer() == EndPointer.BEFORE ) {
+            if ( !timeSpan.isSingleDate() ) {
+               // Equal Stop OR ~ tlink Ends-On
+               paintEdgeLine( g2d, width - TimeSpanDrawUtil.LINE_THINNESS, width, timeEndPoint.isFuzzy() );
+            }
+            break;
+         case OVERLAP:
+            paintContinuation( g2d, leftX, width, timeEndPoint.isFuzzy() );
+            break;
+      }
+   }
+
+
+   // TODO This would be faster as an image
+   private void paintSolidTriangle( final Graphics2D g2d, final int pointX, final int nockX, final int height ) {
+      final int halfHeight = height / 2 - 1;
+      final int[] xPoints = { pointX, nockX, nockX, pointX, pointX };
+      final int[] yPoints = { halfHeight - 1, 0, height - 1, halfHeight, halfHeight - 1 };
+      g2d.fillPolygon( xPoints, yPoints, xPoints.length );
+   }
+
+   // TODO This would be faster as an image
+   private void paintBorderTriangle( final Graphics2D g2d, final int pointX, final int nockX, final int height ) {
+      final int halfHeight = height / 2 - 1;
+      g2d.drawLine( pointX, halfHeight - 1, nockX, 0 );
+      g2d.drawLine( pointX, halfHeight, nockX, height - 1 );
+      g2d.drawLine( pointX, halfHeight - 1, nockX, 1 );
+      g2d.drawLine( pointX, halfHeight, nockX, height - 2 );
+   }
+
+   // TODO This might be faster as an image
+   private void paintSolidTriangle( final Graphics2D g2d, final int pointX, final int nockX, final boolean isFuzzy ) {
+      final DrawHeights heights = new DrawHeights( TimeSpanDrawUtil.TIMESPAN_CELL_HEIGHT - 1,
+            TimeSpanDrawUtil.TIMESPAN_CELL_HEIGHT - 1,
+            _eventCount, _uncertainCount, _negativeCount );
+      final int x = Math.min( pointX, nockX );
+      final int width = Math.max( pointX, nockX ) - x;
+      final int height = getHeight();
+      int y = 0;
+      final Rectangle clipBounds = g2d.getClipBounds();
+      if ( heights.__affirmedHeight > 0 ) {
+         final Color color = getAffirmedColor( isSelected(), false, isFuzzy );
+         g2d.setColor( color );
+         final Rectangle affirmedBounds = new Rectangle( x, y, width, heights.__affirmedHeight );
+         Rectangle clip = affirmedBounds.intersection( clipBounds );
+         g2d.setClip( clip );
+         paintSolidTriangle( g2d, pointX, nockX, height );
+         y += heights.__affirmedHeight;
+      }
+      if ( heights.__uncertainHeight > 0 ) {
+         final Color color = getUncertainColor( isSelected(), false, isFuzzy );
+         g2d.setColor( color );
+         final Rectangle uncertainBounds = new Rectangle( x, y, width, heights.__uncertainHeight );
+         Rectangle clip = uncertainBounds.intersection( clipBounds );
+         g2d.setClip( clip );
+         paintSolidTriangle( g2d, pointX, nockX, height );
+         y += heights.__uncertainHeight;
+      }
+      if ( heights.__negatedHeight > 0 ) {
+         final Color color = getNegatedColor( isSelected(), false, isFuzzy );
+         g2d.setColor( color );
+         final Rectangle negatedBounds = new Rectangle( x, y, width, heights.__negatedHeight );
+         Rectangle clip = negatedBounds.intersection( clipBounds );
+         g2d.setClip( clip );
+         paintSolidTriangle( g2d, pointX, nockX, height );
+      }
+      g2d.setClip( clipBounds );
+      if ( isSelected() ) {
+         final Color color = getSelectedColor( false, isFuzzy );
+         g2d.setColor( color );
+         paintBorderTriangle( g2d, pointX, nockX, height );
+      }
+   }
+
+
+   private void paintEdgeLine( final Graphics2D g2d, final int leftX, final int rightX, final boolean isFuzzy ) {
+      final DrawHeights heights = new DrawHeights( TimeSpanDrawUtil.MIN_SPAN_HEIGHT, TimeSpanDrawUtil.FULL_SPAN_HEIGHT,
+            _eventCount, _uncertainCount, _negativeCount );
+      int y = 0;
+      final int width = Math.abs( rightX - leftX );
+      boolean marginAdded = false;
+      if ( heights.__affirmedHeight > 0 ) {
+         final Color color = getAffirmedColor( isSelected(), false, isFuzzy );
+         final int height = heights.__marginHeight + heights.__affirmedHeight;
+         g2d.setColor( color );
+         g2d.fillRect( leftX, y, width, height );
+         marginAdded = true;
+         y += height;
+      }
+      if ( heights.__uncertainHeight > 0 ) {
+         final Color color = getUncertainColor( isSelected(), false, isFuzzy );
+         final int height = (marginAdded) ? heights.__uncertainHeight
+                                          : heights.__marginHeight + heights.__uncertainHeight;
+         g2d.setColor( color );
+         g2d.fillRect( leftX, y, width, height );
+         marginAdded = true;
+         y += height;
+      }
+      if ( heights.__negatedHeight > 0 ) {
+         final Color color = getNegatedColor( isSelected(), false, isFuzzy );
+         final int height = (marginAdded) ? heights.__negatedHeight
+                                          : heights.__marginHeight + heights.__negatedHeight;
+         g2d.setColor( color );
+         g2d.fillRect( leftX, y, width, height );
+         y += height;
+      }
+      g2d.fillRect( leftX, y, width, heights.__marginHeight );
+      if ( isSelected() ) {
+         final Color color = getSelectedColor( false, isFuzzy );
+         g2d.setColor( color );
+//         g2d.drawLine( leftX, 0, rightX, 0 );
+//         g2d.drawLine( leftX, TimeSpanDrawUtil.TIMESPAN_CELL_HEIGHT-1, rightX, TimeSpanDrawUtil.TIMESPAN_CELL_HEIGHT-1 );
+         g2d.fillRect( leftX, 0, rightX - leftX, 2 );
+         g2d.fillRect( leftX, TimeSpanDrawUtil.TIMESPAN_CELL_HEIGHT - 2, rightX - leftX, 2 );
+      }
+   }
+
+
+   private void paintTimeSpanPlusLine( final Graphics2D g2d, final Rectangle bounds,
+                                       final Color centerColor, final Color endColor, final PointedTimeSpan timeSpan,
+                                       final TimeSpanPainter painter ) {
+      final TimeEndPoint startTime = timeSpan.getStartTime();
+      final boolean isStartFuzzy = startTime.isFuzzy();
+      final EndPointer startPointer = startTime.getPointer();
+      final TimeEndPoint stopTime = timeSpan.getStopTime();
+      final boolean isStopFuzzy = stopTime.isFuzzy();
+      final EndPointer stopPointer = stopTime.getPointer();
+
+      final int relationStartWidth = TimeSpanDrawUtil.getStartOffset( timeSpan )
+                                     + (startPointer == EndPointer.AFTER ? TimeSpanDrawUtil.LINE_THINNESS : 0);
+      final int relationStopWidth = TimeSpanDrawUtil.getEndOffset( timeSpan )
+                                    + (stopPointer == EndPointer.BEFORE ? TimeSpanDrawUtil.LINE_THINNESS : 0);
+      final int lineStartX = bounds.x + relationStartWidth;
+      final int lineWidth = bounds.width - relationStartWidth - relationStopWidth;
+
+      final Rectangle lineBounds = new Rectangle( lineStartX, bounds.y, lineWidth, bounds.height );
+      paintTimeSpanLine( g2d, lineBounds, centerColor, endColor, isStartFuzzy, isStopFuzzy, painter );
+   }
+
+   private void paintTimeSpanLine( final Graphics2D g2d, final Rectangle bounds,
+                                   final Color centerColor, final Color endColor,
+                                   final boolean isStartFuzzy, final boolean isStopFuzzy,
+                                   final TimeSpanPainter painter ) {
+      final int fuzzyStartWidth = isStartFuzzy ? (int)(bounds.width / 4.d) : 0;
+      final int fuzzyStopWidth = isStopFuzzy ? (int)(bounds.width / 4.d) : 0;
+      final int centerStartX = bounds.x + fuzzyStartWidth;
+      final int centerWidth = bounds.width - fuzzyStartWidth - fuzzyStopWidth;
+      // paint the center
+      g2d.setPaint( centerColor );
+//      g2d.fillRect( centerStartX, bounds.y, centerWidth, bounds.height );
+      painter.paint( g2d, centerStartX, bounds.y, centerWidth, bounds.height );
+      if ( !isStartFuzzy && !isStopFuzzy ) {
+         return;
+      }
+      // paint the fuzzy sides
+      if ( isStartFuzzy ) {
+         final GradientPaint gradient1 = new GradientPaint( bounds.x, bounds.y, endColor,
+               bounds.x + fuzzyStartWidth, bounds.y, centerColor, false );
+         g2d.setPaint( gradient1 );
+//         g2d.fillRect( bounds.x, bounds.y, fuzzyStartWidth, bounds.height );
+         painter.paint( g2d, bounds.x, bounds.y, fuzzyStartWidth, bounds.height );
+      }
+      if ( isStopFuzzy ) {
+         final GradientPaint gradient2 = new GradientPaint(
+               bounds.x + bounds.width - fuzzyStopWidth, bounds.y, centerColor,
+               bounds.x + bounds.width, bounds.y, endColor, false );
+         g2d.setPaint( gradient2 );
+//         g2d.fillRect( bounds.x + bounds.width - fuzzyStopWidth, bounds.y, fuzzyStopWidth, bounds.height );
+         painter.paint( g2d, bounds.x + bounds.width - fuzzyStopWidth, bounds.y, fuzzyStopWidth, bounds.height );
+      }
+   }
+
+
+   public void paintContinuation( final Graphics2D g2d, final int leftX, final int rightX, final boolean isFuzzy ) {
+      final DrawHeights heights = new DrawHeights( TimeSpanDrawUtil.MIN_SPAN_HEIGHT, TimeSpanDrawUtil.FULL_SPAN_HEIGHT,
+            _eventCount, _uncertainCount, _negativeCount );
+      int y = 0;
+      final int width = Math.abs( rightX - leftX );
+      if ( isSelected() ) {
+         final Color color = getSelectedColor( false, isFuzzy );
+         g2d.setColor( color );
+//         g2d.drawLine( leftX, heights.__marginHeight-1, rightX, heights.__marginHeight-1 );
+         g2d.fillRect( leftX, heights.__marginHeight - 2, rightX - leftX, 2 );
+      }
+      y += heights.__marginHeight;
+      if ( heights.__affirmedHeight > 0 ) {
+         final Color color = getAffirmedColor( isSelected(), false, isFuzzy );
+         g2d.setColor( color );
+         g2d.fillRect( leftX, y, width, heights.__affirmedHeight );
+         y += heights.__affirmedHeight;
+      }
+      if ( heights.__uncertainHeight > 0 ) {
+         final Color color = getUncertainColor( isSelected(), false, isFuzzy );
+         g2d.setColor( color );
+         g2d.fillRect( leftX, y, width, heights.__uncertainHeight );
+         y += heights.__uncertainHeight;
+      }
+      if ( heights.__negatedHeight > 0 ) {
+         final Color color = getNegatedColor( isSelected(), false, isFuzzy );
+         g2d.setColor( color );
+         g2d.fillRect( leftX, y, width, heights.__negatedHeight );
+         y += heights.__negatedHeight;
+      }
+      if ( isSelected() ) {
+         final Color color = getSelectedColor( false, isFuzzy );
+         g2d.setColor( color );
+//         g2d.drawLine( leftX, y, rightX, y );
+         g2d.fillRect( leftX, y, rightX - leftX, 2 );
+      }
+   }
+
+
+   protected interface TimeSpanPainter {
+      public void paint( final Graphics2D g2d, final int x, final int y, final int width, final int height );
+   }
+
+   static final private class DefaultTimeSpanPainter implements TimeSpanPainter {
+      @Override
+      public void paint( final Graphics2D g2d, final int x, final int y, final int width, final int height ) {
+         if ( width <= 0 ) {
+            return;
+         }
+         g2d.fillRect( x, y, width, height );
+      }
+   }
+
+   static final private class UncertainTimeSpanPainter implements TimeSpanPainter {
+      @Override
+      public void paint( final Graphics2D g2d, final int x, final int y, final int width, final int height ) {
+         if ( width <= 0 ) {
+            return;
+         }
+         for ( int y1 = y; y1 < y + height; y1++ ) {
+            for ( int x1 = x; x1 < x + width; x1 += 4 ) {
+               g2d.drawLine( x1, y1, x1 + 1, y1 - 1 );
+            }
+            for ( int x2 = x + 2; x2 < x + width; x2 += 4 ) {
+               g2d.drawLine( x2, y1, x2 + 1, y1 + 1 );
+            }
+         }
+      }
+   }
+
+
+   static final private class NegatedTimeSpanPainter implements TimeSpanPainter {
+      @Override
+      public void paint( final Graphics2D g2d, final int x, final int y, final int width, final int height ) {
+         if ( width <= 0 ) {
+            return;
+         }
+         // Make sure that the dashed lines always match up 0-4
+         int start = x % 4;
+         if ( start != 3 ) {
+            g2d.fillRect( x, y, 3 - start, height );
+         }
+         for ( int x1 = x - start + 4; x1 < x - start + width; x1 += 4 ) {
+            g2d.fillRect( x1, y, 3, height );
+         }
+         final int end = (x + width) % 4;
+         if ( end != 0 ) {
+            g2d.fillRect( x + width - end, y, end, height );
+         }
+      }
+   }
+
+
+}