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 [9/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/an...
Added: ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/eventSelection/EventSelectionEvent.java
URL: http://svn.apache.org/viewvc/ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/eventSelection/EventSelectionEvent.java?rev=1660963&view=auto
==============================================================================
--- ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/eventSelection/EventSelectionEvent.java (added)
+++ ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/eventSelection/EventSelectionEvent.java Thu Feb 19 18:06:13 2015
@@ -0,0 +1,75 @@
+package org.chboston.cnlp.timeline.eventSelection;
+
+import org.chboston.cnlp.nlp.annotation.entity.Entity;
+
+import java.util.Collection;
+import java.util.EventObject;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Author: SPF
+ * Affiliation: CHIP-NLP
+ * Date: 7/8/13
+ */
+public class EventSelectionEvent extends EventObject {
+ private final Set<Entity> _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 EventSelectionEvent( final Object source, final Collection<Entity> selections,
+ final Collection<String> selectionTexts, final boolean isAdjusting ) {
+ super( source );
+ _selectionSet = new HashSet<>( selections );
+ _selectionTexts = new HashSet<>( selectionTexts );
+ _isAdjusting = isAdjusting;
+ }
+
+ public Collection<Entity> getSelectedEvents() {
+ 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/eventSelection/EventSelectionListener.java
URL: http://svn.apache.org/viewvc/ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/eventSelection/EventSelectionListener.java?rev=1660963&view=auto
==============================================================================
--- ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/eventSelection/EventSelectionListener.java (added)
+++ ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/eventSelection/EventSelectionListener.java Thu Feb 19 18:06:13 2015
@@ -0,0 +1,20 @@
+package org.chboston.cnlp.timeline.eventSelection;
+
+
+import java.util.EventListener;
+
+/**
+ * Author: SPF
+ * Affiliation: CHIP-NLP
+ * Date: 7/8/13
+ */
+public interface EventSelectionListener extends EventListener {
+
+ /**
+ * Called whenever the value of the selection changes.
+ *
+ * @param event the event that characterizes the change.
+ */
+ void valueChanged( final EventSelectionEvent event );
+
+}
Added: ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/gui/TextHighlightPanel.java
URL: http://svn.apache.org/viewvc/ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/gui/TextHighlightPanel.java?rev=1660963&view=auto
==============================================================================
--- ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/gui/TextHighlightPanel.java (added)
+++ ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/gui/TextHighlightPanel.java Thu Feb 19 18:06:13 2015
@@ -0,0 +1,533 @@
+package org.chboston.cnlp.timeline.gui;
+
+import org.chboston.cnlp.nlp.annotation.classtype.SemanticClassType;
+import org.chboston.cnlp.nlp.annotation.classtype.TemporalClassType;
+import org.chboston.cnlp.nlp.annotation.entity.Entity;
+import org.chboston.cnlp.nlp.annotation.textspan.DefaultDiscontiguousTextSpan;
+import org.chboston.cnlp.nlp.annotation.textspan.DefaultTextSpan;
+import org.chboston.cnlp.nlp.annotation.textspan.DiscontiguousTextSpan;
+import org.chboston.cnlp.nlp.annotation.textspan.TextSpan;
+import org.chboston.cnlp.nlp.util.HashSetMap;
+import org.chboston.cnlp.timeline.gui.event.EventColor;
+import org.chboston.cnlp.timeline.gui.event.EventStatus;
+import org.chboston.cnlp.timeline.gui.event.list.EventListElement;
+import org.chboston.cnlp.timeline.gui.event.text.*;
+import org.chboston.cnlp.timeline.gui.timespan.selection.DefaultTimeSpanSelectionModel;
+import org.chboston.cnlp.timeline.gui.timespan.selection.TimeSpanSelectionEvent;
+import org.chboston.cnlp.timeline.gui.timespan.selection.TimeSpanSelectionListener;
+import org.chboston.cnlp.timeline.gui.timespan.selection.TimeSpanSelectionModel;
+import org.chboston.cnlp.timeline.timeline.Timeline;
+import org.chboston.cnlp.timeline.timespan.plus.PointedTimeSpan;
+import org.chboston.cnlp.timeline.timespan.plus.TimeSpanPlus;
+
+import javax.swing.*;
+import javax.swing.event.ListSelectionEvent;
+import javax.swing.event.ListSelectionListener;
+import javax.swing.plaf.basic.BasicScrollBarUI;
+import javax.swing.text.*;
+import java.awt.*;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.util.*;
+import java.util.logging.Logger;
+
+/**
+ * Author: SPF
+ * Affiliation: CHIP-NLP
+ * Date: 9/2/13
+ */
+final public class TextHighlightPanel extends JTextPane {
+
+ static private final Logger LOGGER = Logger.getLogger( "TextHighlightPanel" );
+
+ static private final boolean DEBUG = false;
+
+ // TODO - swim lane for images, video
+ // TODO - swim lane for icons: radiation, bloodwork, surgery?
+ // TODO - swim lane for locations (body) (s/s, d/d)
+ // TODO - swim lane for geographic locations (test/proc, med)
+ // TODO - swim lane for stacked medication rise and fall
+
+ static public final String SELECTION_MODEL_PROPERTY = "selectionModel";
+
+
+ static private final HighlightPainter FILL_PAINTER = new FillPainter();
+ static private final HighlightPainter UNDERLINE_PAINTER = new UnderlinePainter();
+ static private final HighlightPainter TWO_UNDERLINE_PAINTER = new TwoUnderliner();
+ static private final HighlightPainter WAVE_PAINTER = new WaveUnderliner();
+ static private final HighlightPainter DASH_PAINTER = new DashUnderliner();
+ static private final HighlightPainter DASH_WAVE_PAINTER = new DashWaveUnderliner();
+
+ // static private final TrianglePainter TRIANGLE_PAINTER = new TrianglePainter();
+// X : 0,4 UL ; 0,4 LL ; 4,-4 UR ; 4,-4 LR
+// Y : 0,4 UL ; 4,-4 LL ; 0,4 UR ; 4,-4 LR
+// static private final HighlightPainter SYMPTOM_PAINTER = new TrianglePainter( 0, 4, 0, 4 );
+// static private final HighlightPainter PROCEDURE_PAINTER = new TrianglePainter( 0, 4, 4, -4 );
+// static private final HighlightPainter DISEASE_PAINTER = new TrianglePainter( 4, -4, 4, -4 );
+// static private final HighlightPainter MEDICATION_PAINTER = new TrianglePainter( 4, -4, 0, 4 );
+ static private final HighlightPainter ASTERISK_PAINTER = new AsteriskPainter();
+
+
+ private final Highlighter.HighlightPainter _allLayersPainter = new AllPainter();
+
+
+ private Collection<Integer> _selectedRowYs = new HashSet<>();
+ private int _rowHeight = 0;
+
+ private final ListModel<EventListElement> _eventListModel;
+ private final ListSelectionModel _eventListSelectionModel;
+ private TimeSpanSelectionModel _selectionModel;
+ private final TimeSpanSelectionListener _selectionListener;
+ private JScrollBar _verticalScrollBar;
+
+ private final Collection<TextSpan> _focusTextSpans;
+ private final Collection<TextSpan> _selectedTextSpans;
+ private final Map<TextSpan, SemanticClassType> _semanticTypeMap;
+ private final Map<TextSpan, Entity> _eventMap;
+
+
+ public TextHighlightPanel( final Timeline timeline, final ListModel<EventListElement> eventListModel,
+ final ListSelectionModel eventListSelectionModel ) {
+ setEditable( false );
+// setFont( new Font( "Monospaced", Font.PLAIN, getFont().getSize() ) ); // Didn't help as spacing is already bad
+ _eventListModel = eventListModel;
+ eventListSelectionModel.addListSelectionListener( new PrivateListSelectionListener() );
+ _eventListSelectionModel = eventListSelectionModel;
+ _selectionListener = new PrivateSelectionListener();
+ _focusTextSpans = new HashSet<>();
+ _selectedTextSpans = new HashSet<>();
+ _semanticTypeMap = new HashMap<>();
+ _eventMap = new HashMap<>();
+ final Collection<Entity> events = timeline.getEntities();
+ for ( Entity event : events ) {
+ final TextSpan textSpan = event.getTextSpan();
+ addEvent( textSpan, event );
+ // TODO refactor to use event.isClassType()
+ if ( event.isClassType( SemanticClassType.SIGN_OR_SYMPTOM ) ) {
+ addSemanticHighlight( textSpan, SemanticClassType.SIGN_OR_SYMPTOM );
+ } else if ( event.isClassType( SemanticClassType.PROCEDURE ) ) {
+ addSemanticHighlight( textSpan, SemanticClassType.PROCEDURE );
+ } else if ( event.isClassType( SemanticClassType.DISEASE_DISORDER ) ) {
+ addSemanticHighlight( textSpan, SemanticClassType.DISEASE_DISORDER );
+ } else if ( event.isClassType( SemanticClassType.MEDICATION ) ) {
+ addSemanticHighlight( textSpan, SemanticClassType.MEDICATION );
+ // Also mark text from other semantic types even though they aren't of our 4 main event types
+ } else if ( event.isClassType( SemanticClassType.ANATOMICAL_SITE ) ) {
+ addSemanticHighlight( textSpan, SemanticClassType.ANATOMICAL_SITE );
+ } else if ( event.isClassType( SemanticClassType.CLINICAL_ATTRIBUTE ) ) {
+ addSemanticHighlight( textSpan, SemanticClassType.CLINICAL_ATTRIBUTE );
+ } else if ( event.isClassType( SemanticClassType.PHENOMENA ) ) {
+ addSemanticHighlight( textSpan, SemanticClassType.PHENOMENA );
+ } else if ( event.isClassType( SemanticClassType.MISC ) ) {
+ addSemanticHighlight( textSpan, SemanticClassType.MISC );
+ }
+ }
+ final Collection<Entity> times = timeline.getTimes();
+ for ( Entity timex : times ) {
+ final TextSpan textSpan = timex.getTextSpan();
+ addEvent( textSpan, timex );
+ }
+ setSelectionModel( new DefaultTimeSpanSelectionModel() );
+ addMouseListener( new TimelineMouseHandler( timeline ) );
+ addMouseMotionListener( new TimelineMouseHandler( timeline ) );
+ }
+
+ // TODO enable Timeline Switching
+// public void setTimeline( final Timeline timeline, final EventListModel eventListModel ) {
+// _eventColorMap.clear();
+// _selectedRowYs.clear();
+// _selectedTextSpans.clear();
+// _focusTextSpans.clear();
+// _eventListModel = eventListModel;
+// final Collection<TimeSpanPlus> timeSpans = timeline.getTimeSpans();
+// for ( TimeSpanPlus timeSpan : timeSpans ) {
+// final Collection<Entity> events = timeline.getEntities( timeSpan );
+// for ( Entity event : events ) {
+// final TextSpan textSpan = event.getTextSpan();
+// final boolean isNegated = AttributeUtil.hasNegativePolarity( event );
+// final boolean isUncertain = AttributeUtil.hasUncertainty( event );
+// addEventHighlight( textSpan, isNegated, isUncertain );
+// }
+// }
+// repaint();
+// }
+
+ public void setVerticalScrollBar( final JScrollBar scrollBar ) {
+ _verticalScrollBar = scrollBar;
+ _verticalScrollBar.setUI( new HighlightScrollBarUI() );
+ }
+
+ /**
+ * Sets the TimeSpan selection model for this OldJTimeline to <code>newModel</code>
+ * and registers for listener notifications from the new selection model.
+ *
+ * @param newModel the new selection model
+ * @throws IllegalArgumentException if <code>newModel</code> is <code>null</code>
+ * @see #getSelectionModel
+ */
+ public void setSelectionModel( final TimeSpanSelectionModel newModel ) {
+ if ( newModel == null ) {
+ throw new IllegalArgumentException( "Cannot set a null SelectionModel" );
+ }
+ if ( newModel.equals( _selectionModel ) ) {
+ return;
+ }
+ if ( _selectionModel != null ) {
+ _selectionModel.removeSelectionListener( _selectionListener );
+ }
+ final TimeSpanSelectionModel oldModel = _selectionModel;
+ _selectionModel = newModel;
+ _selectionModel.addSelectionListener( _selectionListener );
+ firePropertyChange( SELECTION_MODEL_PROPERTY, oldModel, newModel );
+ repaint();
+ }
+
+ /**
+ * @return the object that provides TimeSpan selection state, <code>null</code>
+ * if selection is not allowed
+ * @see #setSelectionModel
+ */
+ public TimeSpanSelectionModel getSelectionModel() {
+ return _selectionModel;
+ }
+
+ private void addSelectedHighlight( final TextSpan textSpan ) {
+ if ( textSpan instanceof DefaultDiscontiguousTextSpan ) {
+ for ( TextSpan textSpan1 : (DiscontiguousTextSpan)textSpan ) {
+ addSelectedHighlight( textSpan1 );
+ }
+ return;
+ }
+ try {
+ final Rectangle bounds = modelToView( textSpan.getStartIndex() );
+ _selectedRowYs.add( bounds.y );
+ _selectedTextSpans.add( textSpan );
+ if ( DEBUG ) {
+ System.out.println( textSpan.getStartIndex() + " , " + textSpan.getEndIndex() );
+ }
+ } catch ( BadLocationException blE ) {
+ LOGGER.warning( blE.getMessage() );
+ }
+ }
+
+ private void addEvent( final TextSpan textSpan, final Entity event ) {
+ if ( textSpan instanceof DefaultDiscontiguousTextSpan ) {
+ for ( TextSpan textSpan1 : (DiscontiguousTextSpan)textSpan ) {
+ addEvent( textSpan1, event );
+ }
+ return;
+ }
+ _eventMap.put( textSpan, event );
+ }
+
+
+ private void addSemanticHighlight( final TextSpan textSpan, final SemanticClassType semanticType ) {
+ if ( textSpan instanceof DefaultDiscontiguousTextSpan ) {
+ for ( TextSpan textSpan1 : (DiscontiguousTextSpan)textSpan ) {
+ addSemanticHighlight( textSpan1, semanticType );
+ }
+ return;
+ }
+ _semanticTypeMap.put( textSpan, semanticType );
+ }
+
+ private void addFocusTextSpan( final TextSpan textSpan ) {
+ if ( textSpan instanceof DefaultDiscontiguousTextSpan ) {
+ for ( TextSpan textSpan1 : (DiscontiguousTextSpan)textSpan ) {
+ addFocusTextSpan( textSpan1 );
+ }
+ return;
+ }
+ _focusTextSpans.add( textSpan );
+ }
+
+ private void selectTimeSpans( final TimeSpanSelectionEvent event ) {
+ final String text = getText();
+ if ( text == null || text.isEmpty() ) {
+ return;
+ }
+ _selectedRowYs.clear();
+ _selectedTextSpans.clear();
+ _focusTextSpans.clear();
+
+ int firstOffset = 0;
+ final Map<PointedTimeSpan, Collection<Entity>> selectedEntities = event.getSelectedEntities();
+ if ( selectedEntities != null && !selectedEntities.isEmpty() ) {
+ firstOffset = text.length() - 1;
+ for ( Collection<Entity> entityCollection : selectedEntities.values() ) {
+ for ( Entity entity : entityCollection ) {
+ if ( entity.isClassType( TemporalClassType.TIMEX ) || entity.isClassType( TemporalClassType.DOCTIME ) ) {
+ // This should not happen
+ continue;
+ }
+ final TextSpan textSpan = entity.getTextSpan();
+ addSelectedHighlight( textSpan );
+ firstOffset = Math.min( firstOffset, textSpan.getStartIndex() );
+ // final Collection<Relation> eventEventRelations = _timeline.getEntityEntityRelations( entity );
+ // final EventListElement element = new EventListElement( timeSpan, entity, eventEventRelations,
+ // semanticType, selectionTexts, isFirst );
+ }
+ }
+ }
+ scrollToOffset( firstOffset );
+ }
+
+ /**
+ * {@inheritDoc}
+ * also repaints the vertical scroll bar
+ */
+ @Override
+ public void paint( final Graphics g ) {
+ super.paint( g );
+ // repaint the vertical scroll bar
+ _verticalScrollBar.repaint();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void setText( final String text ) {
+ super.setText( text );
+ try {
+ for ( TextSpan textSpan : _eventMap.keySet() ) {
+ getHighlighter().addHighlight( textSpan.getStartIndex(), textSpan.getEndIndex(), _allLayersPainter );
+ }
+ for ( TextSpan textSpan : _semanticTypeMap.keySet() ) {
+ getHighlighter().addHighlight( textSpan.getStartIndex(), textSpan.getEndIndex(), _allLayersPainter );
+ }
+ } catch ( BadLocationException blE ) {
+ LOGGER.warning( blE.getMessage() );
+ }
+ }
+
+ // Painter for underlined highlights
+ private class AllPainter extends LayeredHighlighter.LayerPainter {
+ @Override
+ public void paint( final Graphics g, final int offs0, final int offs1, final Shape bounds,
+ final JTextComponent comp ) {
+ // Do nothing: this method will never be called
+ }
+
+ @Override
+ public Shape paintLayer( final Graphics g, final int startIndex, final int stopIndex, final Shape bounds,
+ final JTextComponent comp, final View view ) {
+ Rectangle alloc;
+ if ( startIndex == view.getStartOffset() && stopIndex == view.getEndOffset() ) {
+ if ( bounds instanceof Rectangle ) {
+ alloc = (Rectangle)bounds;
+ } else {
+ alloc = bounds.getBounds();
+ }
+ } else {
+ try {
+ final Shape shape = view.modelToView( startIndex,
+ Position.Bias.Forward, stopIndex,
+ Position.Bias.Backward, bounds );
+ alloc = (shape instanceof Rectangle) ? (Rectangle)shape
+ : shape.getBounds();
+ } catch ( BadLocationException blE ) {
+ return null;
+ }
+ }
+ _rowHeight = alloc.height;
+ final TextSpan textSpan = new DefaultTextSpan( startIndex, stopIndex );
+ if ( _focusTextSpans.contains( textSpan ) ) {
+ FILL_PAINTER.paintHighlight( g, alloc, EventColor.FOCUS.getColor() );
+ } else if ( _selectedTextSpans.contains( textSpan ) ) {
+ boolean intersectsFocus = false;
+ for ( TextSpan focusSpan : _focusTextSpans ) {
+ if ( textSpan.intersects( focusSpan ) ) {
+ intersectsFocus = true;
+ break;
+ }
+ }
+ if ( !intersectsFocus ) {
+ FILL_PAINTER.paintHighlight( g, alloc, EventColor.SELECTED.getColor() );
+ }
+ }
+ final Entity event = _eventMap.get( textSpan );
+ if ( event != null ) {
+ if ( event.isClassType( TemporalClassType.TIMEX ) || event.isClassType( TemporalClassType.DOCTIME ) ) {
+ TWO_UNDERLINE_PAINTER.paintHighlight( g, alloc, EventColor.TIMEX.getColor() );
+ } else if ( event.isClassType( TemporalClassType.EVENT ) ) {
+ final EventStatus eventStatus = EventStatus.getEventStatus( event );
+ final Color color = EventColor.getEventColor( eventStatus ).getColor();
+ if ( eventStatus == EventStatus.UNCERTAIN_NEGATED ) {
+ DASH_WAVE_PAINTER.paintHighlight( g, alloc, color );
+ } else if ( eventStatus == EventStatus.UNCERTAIN ) {
+ WAVE_PAINTER.paintHighlight( g, alloc, color );
+ } else if ( eventStatus == EventStatus.NEGATED ) {
+ DASH_PAINTER.paintHighlight( g, alloc, color );
+ } else {
+ UNDERLINE_PAINTER.paintHighlight( g, alloc, color );
+ }
+ }
+ }
+ final SemanticClassType semanticType = _semanticTypeMap.get( textSpan );
+ if ( semanticType != null ) {
+ final Color semanticColor = EventColor.getSemanticColor( semanticType ).getColor();
+ ASTERISK_PAINTER.paintHighlight( g, alloc, semanticColor );
+ }
+ return alloc;
+ }
+ }
+
+
+ private class HighlightScrollBarUI extends BasicScrollBarUI {
+ protected void paintTrack( final Graphics g, final JComponent comp, final Rectangle trackBounds ) {
+ super.paintTrack( g, comp, trackBounds );
+ final double xForm = trackBounds.getHeight() / TextHighlightPanel.this.getHeight();
+ final double xxForm = trackBounds.getWidth() / TextHighlightPanel.this.getWidth();
+ final int rowHeight = Math.max( 2, (int)(_rowHeight * xForm) );
+ final HashSetMap<Color, Rectangle> eventTrackBounds = new HashSetMap<>();
+ try {
+// for ( Map.Entry<TextSpan,Color> eventColor : _eventColorMap.entrySet() ) {
+// final Rectangle bounds = modelToView( eventColor.getKey().getStartIndex() );
+// final int trackX = trackBounds.x + (int)(bounds.x * xxForm);
+// final int trackY = trackBounds.y + (int)(bounds.y * xForm);
+// final Rectangle endBounds = modelToView( eventColor.getKey().getEndIndex() );
+// final int trackWidth = Math.max(1, (int)((endBounds.x + endBounds.width - bounds.x ) * xxForm) );
+// eventTrackBounds.place( eventColor.getValue(),
+// new Rectangle( trackX, trackY, trackWidth, rowHeight ) );
+// }
+ for ( Map.Entry<TextSpan, Entity> eventEntry : _eventMap.entrySet() ) {
+ final Rectangle bounds = modelToView( eventEntry.getKey().getStartIndex() );
+ final int trackX = trackBounds.x + (int)(bounds.x * xxForm);
+ final int trackY = trackBounds.y + (int)(bounds.y * xForm);
+ final Rectangle endBounds = modelToView( eventEntry.getKey().getEndIndex() );
+ final int trackWidth = Math.max( 1, (int)((endBounds.x + endBounds.width - bounds.x) * xxForm) );
+ final EventStatus eventStatus = EventStatus.getEventStatus( eventEntry.getValue() );
+ Color color;
+ if ( eventEntry.getValue().isClassType( TemporalClassType.TIMEX )
+ || eventEntry.getValue().isClassType( TemporalClassType.DOCTIME ) ) {
+ color = EventColor.TIMEX.getColor();
+ } else {
+ color = EventColor.getEventColor( eventStatus ).getColor();
+ }
+ eventTrackBounds.place( color, new Rectangle( trackX, trackY, trackWidth, rowHeight ) );
+ }
+ } catch ( BadLocationException blE ) {
+ LOGGER.warning( blE.getMessage() );
+ }
+ paintEventYs( g, eventTrackBounds );
+ if ( _selectedRowYs.isEmpty() ) {
+ return;
+ }
+ g.setColor( EventColor.SELECTED.getColor() );
+ for ( int y : _selectedRowYs ) {
+ final int trackY = trackBounds.y + (int)(y * xForm);
+ g.fillRect( trackBounds.x, trackY, trackBounds.width, rowHeight );
+ }
+ }
+
+ private void paintEventYs( final Graphics g, final HashSetMap<Color, Rectangle> eventRowBounds ) {
+ for ( Map.Entry<Color, Set<Rectangle>> colorEvents : eventRowBounds.entrySet() ) {
+ g.setColor( colorEvents.getKey() );
+ for ( Rectangle bounds : colorEvents.getValue() ) {
+ g.fillRect( bounds.x, bounds.y, bounds.width, bounds.height );
+ }
+ }
+ }
+ }
+
+
+ private class PrivateSelectionListener implements TimeSpanSelectionListener {
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void valueChanged( final TimeSpanSelectionEvent event ) {
+ if ( event == null || event.getSource() == null || event.getValueIsAdjusting() ) {
+ return;
+ }
+ selectTimeSpans( event );
+ }
+ }
+
+ private void scrollToOffset( final int offset ) {
+ final Rectangle visibleBounds = getVisibleRect();
+ try {
+ final Rectangle textBounds = modelToView( offset );
+ visibleBounds.y = Math.max( 0, textBounds.y - (visibleBounds.height - textBounds.height) / 2 );
+ scrollRectToVisible( visibleBounds );
+ } catch ( BadLocationException blE ) {
+ LOGGER.warning( blE.getMessage() );
+ }
+ final Rectangle bounds = getVisibleRect();
+ repaint( bounds.x, bounds.y, bounds.width, bounds.height );
+ }
+
+ private class PrivateListSelectionListener implements ListSelectionListener {
+ public void valueChanged( final ListSelectionEvent event ) {
+ if ( event == null || event.getValueIsAdjusting() ) {
+ return;
+ }
+ if ( _eventListSelectionModel.isSelectionEmpty() ) {
+ return;
+ }
+ final int leadIndex = _eventListSelectionModel.getLeadSelectionIndex();
+ if ( leadIndex < 0 || leadIndex >= _eventListModel.getSize() ) {
+ return;
+ }
+ final Object value = _eventListModel.getElementAt( leadIndex );
+ if ( value == null ) {
+ return;
+ }
+ _focusTextSpans.clear();
+ final EventListElement element = (EventListElement)value;
+ final Entity entity = element.getEntity();
+ final TextSpan textSpan = entity.getTextSpan();
+ addFocusTextSpan( textSpan );
+ scrollToOffset( textSpan.getStartIndex() );
+ }
+ }
+
+
+ private class TimelineMouseHandler extends MouseAdapter {
+ private final Timeline __timeline;
+
+ private TimelineMouseHandler( final Timeline timeline ) {
+ __timeline = timeline;
+ }
+
+ @Override
+ public void mouseClicked( final MouseEvent event ) {
+ if ( event == null ) {
+ return;
+ }
+ final Point p = event.getPoint();
+ final int textIndex = viewToModel( event.getPoint() );
+ if ( textIndex < 0 ) {
+ return;
+ }
+ Entity selection = null;
+ for ( Map.Entry<TextSpan, Entity> eventEntry : _eventMap.entrySet() ) {
+ if ( eventEntry.getKey().getStartIndex() <= textIndex && textIndex <= eventEntry.getKey().getEndIndex() ) {
+ selection = eventEntry.getValue();
+ break;
+ }
+ }
+ if ( selection == null ) {
+ return;
+ }
+ final Map<PointedTimeSpan, Collection<Entity>> timeSpanEntities = new HashMap<>();
+ final Collection<Entity> selections = Collections.singletonList( selection );
+ for ( PointedTimeSpan timeSpan : __timeline ) {
+ if ( __timeline.getEntities( timeSpan ).contains( selection ) ) {
+ timeSpanEntities.put( timeSpan, selections );
+ }
+ }
+ if ( timeSpanEntities.isEmpty() ) {
+ timeSpanEntities.put( TimeSpanPlus.UNKNOWN_TIMESPAN_PLUS, selections );
+ }
+ final TimeSpanSelectionModel selectionModel = getSelectionModel();
+ selectionModel.setValueIsAdjusting( selectionModel, true );
+ selectionModel.setSelectedTerms( selectionModel, Collections.singletonList( "*" ) );
+ selectionModel.setSelectedEntities( selectionModel, timeSpanEntities );
+ selectionModel.setValueIsAdjusting( selectionModel, false );
+ }
+ }
+
+
+}
Added: ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/gui/calendar/JTimelineCalendar.java
URL: http://svn.apache.org/viewvc/ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/gui/calendar/JTimelineCalendar.java?rev=1660963&view=auto
==============================================================================
--- ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/gui/calendar/JTimelineCalendar.java (added)
+++ ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/gui/calendar/JTimelineCalendar.java Thu Feb 19 18:06:13 2015
@@ -0,0 +1,398 @@
+package org.chboston.cnlp.timeline.gui.calendar;
+
+import org.chboston.cnlp.timeline.oldUI.TimelineDrawUtil;
+import org.chboston.cnlp.timeline.timespan.TimeSpan;
+
+import javax.swing.*;
+import java.awt.*;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.Date;
+
+import static org.chboston.cnlp.timeline.timespan.CalendarUtil.TIME_MILLIS.*;
+
+
+/**
+ * Author: SPF
+ * Affiliation: CHIP-NLP
+ * Date: 11/14/12
+ */
+public class JTimelineCalendar extends JLabel {
+
+ static private final int CALENDER_TICK_HEIGHT = 10;
+ static private final int TEXT_WIDTH_MIN = 75;
+ static private final int TEXT_WIDTH_MAX = 300;
+ static private final int TEXT_WIDTH_INCR = 25;
+
+ static private final DateFormat YEAR_DATE_FORMAT = new SimpleDateFormat( "yyyy" );
+ static private final DateFormat SHORT_MONTH_DATE_FORMAT = new SimpleDateFormat( "M/yyyy" );
+ static private final DateFormat SHORT_DATE_FORMAT = new SimpleDateFormat( "M/d/yyyy" );
+ static private final DateFormat SHORT_DATE_TIME_FORMAT = new SimpleDateFormat( "M/d/yyyy h:mm a" );
+
+ private TimeSpan _timeline;
+
+ public JTimelineCalendar( final TimeSpan timeline ) {
+ _timeline = timeline;
+ }
+
+ public void setTimeline( final TimeSpan timeline ) {
+ _timeline = timeline;
+ }
+
+ private int getFontHeight() {
+ final FontMetrics fm = getFontMetrics( getFont() );
+ return fm.getHeight();
+ }
+
+ @Override
+ public Dimension getPreferredSize() {
+ final FontMetrics fm = getFontMetrics( getFont() );
+ fm.getHeight();
+
+ return new Dimension( super.getPreferredSize().width, getFontHeight() + CALENDER_TICK_HEIGHT );
+ }
+
+ @Override
+ protected void paintComponent( final Graphics g ) {
+ int totalWidth = getPreferredSize().width;
+ if ( totalWidth == 0 ) {
+ // preferred width is 0 on startup
+ totalWidth = getBounds().width;
+ }
+ if ( totalWidth == 0 ) {
+ return;
+ }
+ final long beginDrawMillis = TimelineDrawUtil.getBufferedBeginTime( _timeline.getStartCalendar(),
+ _timeline.getEndCalendar() ).getTimeInMillis();
+ final long endDrawMillis = TimelineDrawUtil.getBufferedEndTime( _timeline.getStartCalendar(),
+ _timeline.getEndCalendar() ).getTimeInMillis();
+
+ final long drawTimeDelta = endDrawMillis - beginDrawMillis;
+ final double xform = drawTimeDelta / (double)totalWidth;
+
+ final long startMillis = _timeline.getStartMillis();
+ final long endMillis = _timeline.getStopMillis();
+ final long deltaMillis = endMillis - startMillis;
+ // Total number of draw pixels
+ final double pixelDelta = deltaMillis / xform;
+
+ for ( int textWidth = TEXT_WIDTH_MIN; textWidth <= TEXT_WIDTH_MAX; textWidth += TEXT_WIDTH_INCR ) {
+ if ( pixelDelta < textWidth ) {
+ // too small
+ continue;
+ }
+ // get back to Millis
+ final double perDateMillis = textWidth * xform;
+ final Calendar checkCalendar = getCalendar( startMillis, perDateMillis );
+ final int calendarField = getCalendarField( perDateMillis );
+ final int calendarIncrement = getCalendarIncrement( perDateMillis );
+ final DateFormat dateFormat = getDateFormat( perDateMillis, calendarIncrement );
+ if ( checkTextWidth( g, dateFormat, checkCalendar, calendarField, calendarIncrement, xform ) ) {
+ final Calendar calendar = getCalendar( startMillis, perDateMillis );
+ if ( calendarIncrement == 1 ) {
+ paintCenteredText( g,
+ dateFormat,
+ calendar,
+ calendarField,
+ calendarIncrement,
+ beginDrawMillis,
+ xform,
+ endDrawMillis );
+ } else {
+ paintStartingText( g,
+ dateFormat,
+ calendar,
+ calendarField,
+ calendarIncrement,
+ beginDrawMillis,
+ xform,
+ endDrawMillis );
+ }
+ break;
+ }
+ }
+ }
+
+
+ static private DateFormat getDateFormat( final double perDateMillis ) {
+ if ( perDateMillis >= DECADE.__millis ) {
+ return YEAR_DATE_FORMAT;
+ } else if ( perDateMillis >= 5 * YEAR.__millis ) {
+ return YEAR_DATE_FORMAT;
+ } else if ( perDateMillis >= 2 * YEAR.__millis ) {
+ return YEAR_DATE_FORMAT;
+ } else if ( perDateMillis >= YEAR.__millis ) {
+ return YEAR_DATE_FORMAT;
+ } else if ( perDateMillis >= 6 * MONTH.__millis ) {
+ return SHORT_MONTH_DATE_FORMAT;
+ } else if ( perDateMillis >= MONTH.__millis ) {
+ return SHORT_MONTH_DATE_FORMAT;
+ } else if ( perDateMillis >= WEEK.__millis ) {
+ return SHORT_DATE_FORMAT;
+ } else if ( perDateMillis >= 2 * DAY.__millis ) {
+ return SHORT_DATE_FORMAT;
+ } else if ( perDateMillis >= DAY.__millis ) {
+ return SHORT_DATE_FORMAT;
+ } else if ( perDateMillis >= DAY.__millis / 2 ) {
+ return SHORT_DATE_TIME_FORMAT;
+ } else if ( perDateMillis >= DAY.__millis / 4 ) {
+ return SHORT_DATE_TIME_FORMAT;
+ }
+ return SHORT_DATE_TIME_FORMAT;
+ }
+
+ static private DateFormat getDateFormat( final double perDateMillis, final int calendarIncrement ) {
+ if ( perDateMillis >= DECADE.__millis ) {
+ return YEAR_DATE_FORMAT;
+ } else if ( perDateMillis >= 5 * YEAR.__millis ) {
+ return YEAR_DATE_FORMAT;
+ } else if ( perDateMillis >= 2 * YEAR.__millis ) {
+ return YEAR_DATE_FORMAT;
+ } else if ( perDateMillis >= YEAR.__millis ) {
+ return YEAR_DATE_FORMAT;
+ } else if ( perDateMillis >= 6 * MONTH.__millis ) {
+ // return SHORT_MONTH_DATE_FORMAT;
+ return SHORT_DATE_FORMAT;
+ } else if ( perDateMillis >= MONTH.__millis ) {
+ if ( calendarIncrement == 1 ) {
+ return SHORT_MONTH_DATE_FORMAT;
+ }
+ return SHORT_DATE_FORMAT;
+ } else if ( perDateMillis >= WEEK.__millis ) {
+ return SHORT_DATE_FORMAT;
+ } else if ( perDateMillis >= 2 * DAY.__millis ) {
+ return SHORT_DATE_FORMAT;
+ } else if ( perDateMillis >= DAY.__millis ) {
+ return SHORT_DATE_FORMAT;
+ } else if ( perDateMillis >= DAY.__millis / 2 ) {
+ return SHORT_DATE_TIME_FORMAT;
+ } else if ( perDateMillis >= DAY.__millis / 4 ) {
+ return SHORT_DATE_TIME_FORMAT;
+ }
+ return SHORT_DATE_TIME_FORMAT;
+ }
+
+ static private int getCalendarField( final double perDateMillis ) {
+ if ( perDateMillis >= DECADE.__millis ) {
+ return Calendar.YEAR;
+ } else if ( perDateMillis >= 5 * YEAR.__millis ) {
+ return Calendar.YEAR;
+ } else if ( perDateMillis >= 2 * YEAR.__millis ) {
+ return Calendar.YEAR;
+ } else if ( perDateMillis >= YEAR.__millis ) {
+ return Calendar.YEAR;
+ } else if ( perDateMillis >= 6 * MONTH.__millis ) {
+ return Calendar.MONTH;
+ } else if ( perDateMillis >= MONTH.__millis ) {
+ return Calendar.MONTH;
+ } else if ( perDateMillis >= WEEK.__millis ) {
+ return Calendar.WEEK_OF_YEAR;
+ } else if ( perDateMillis >= 2 * DAY.__millis ) {
+ return Calendar.DAY_OF_YEAR;
+ } else if ( perDateMillis >= DAY.__millis ) {
+ return Calendar.DAY_OF_YEAR;
+ } else if ( perDateMillis >= DAY.__millis / 2 ) {
+ return Calendar.HOUR_OF_DAY;
+ } else if ( perDateMillis >= DAY.__millis / 4 ) {
+ return Calendar.HOUR_OF_DAY;
+ }
+ return Calendar.HOUR_OF_DAY;
+ }
+
+ final int getCalendarIncrement( final double perDateMillis ) {
+ if ( perDateMillis >= DECADE.__millis ) {
+ return 10;
+ } else if ( perDateMillis >= 5 * YEAR.__millis ) {
+ return 5;
+ } else if ( perDateMillis >= 2 * YEAR.__millis ) {
+ return 2;
+ } else if ( perDateMillis >= YEAR.__millis ) {
+ return 1;
+ } else if ( perDateMillis >= 6 * MONTH.__millis ) {
+ return 6;
+ } else if ( perDateMillis >= MONTH.__millis ) {
+ return 1;
+ } else if ( perDateMillis >= WEEK.__millis ) {
+ return 1;
+ } else if ( perDateMillis >= 2 * DAY.__millis ) {
+ return 2;
+ } else if ( perDateMillis >= DAY.__millis ) {
+ return 1;
+ } else if ( perDateMillis >= DAY.__millis / 2 ) {
+ return 12;
+ } else if ( perDateMillis >= DAY.__millis / 4 ) {
+ return 6;
+ } else if ( perDateMillis >= HOUR.__millis / 6 ) {
+ return 4;
+ } else if ( perDateMillis >= HOUR.__millis / 12 ) {
+ return 2;
+ }
+ return 1;
+ }
+
+ static private Calendar getCalendar( final long startMillis, final double perDateMillis ) {
+ final Calendar calendar = Calendar.getInstance();
+ calendar.setTimeInMillis( startMillis );
+ if ( perDateMillis >= DECADE.__millis ) {
+ calendar.add( Calendar.YEAR, -calendar.get( Calendar.YEAR ) % 10 );
+ calendar.add( Calendar.DAY_OF_YEAR, -calendar.get( Calendar.DAY_OF_YEAR ) + 1 );
+ } else if ( perDateMillis >= 5 * YEAR.__millis ) {
+ calendar.add( Calendar.YEAR, -calendar.get( Calendar.YEAR ) % 5 );
+ calendar.add( Calendar.DAY_OF_YEAR, -calendar.get( Calendar.DAY_OF_YEAR ) + 1 );
+ } else if ( perDateMillis >= 2 * YEAR.__millis ) {
+ calendar.add( Calendar.YEAR, -calendar.get( Calendar.YEAR ) % 2 );
+ calendar.add( Calendar.DAY_OF_YEAR, -calendar.get( Calendar.DAY_OF_YEAR ) + 1 );
+ } else if ( perDateMillis >= YEAR.__millis ) {
+ calendar.add( Calendar.DAY_OF_YEAR, -calendar.get( Calendar.DAY_OF_YEAR ) + 1 );
+ } else if ( perDateMillis >= 6 * MONTH.__millis ) {
+ calendar.add( Calendar.MONTH, -calendar.get( Calendar.MONTH ) % 6 );
+ calendar.add( Calendar.DAY_OF_MONTH, -calendar.get( Calendar.DAY_OF_MONTH ) + 1 );
+ } else if ( perDateMillis >= MONTH.__millis ) {
+ calendar.add( Calendar.DAY_OF_MONTH, -calendar.get( Calendar.DAY_OF_MONTH ) + 1 );
+ } else if ( perDateMillis >= WEEK.__millis ) {
+ calendar.add( Calendar.DAY_OF_WEEK_IN_MONTH, -calendar.get( Calendar.DAY_OF_WEEK_IN_MONTH ) + 1 );
+ calendar.add( Calendar.HOUR_OF_DAY, -calendar.get( Calendar.HOUR_OF_DAY ) );
+ } else if ( perDateMillis >= 2 * DAY.__millis ) {
+ calendar.add( Calendar.DAY_OF_YEAR, -calendar.get( Calendar.DAY_OF_YEAR ) % 2 );
+ } else if ( perDateMillis >= DAY.__millis ) {
+ calendar.add( Calendar.HOUR_OF_DAY, -calendar.get( Calendar.HOUR_OF_DAY ) );
+ } else if ( perDateMillis >= DAY.__millis / 2 ) {
+ calendar.add( Calendar.HOUR_OF_DAY, -calendar.get( Calendar.HOUR_OF_DAY ) % 12 - 1 );
+ } else if ( perDateMillis >= DAY.__millis / 4 ) {
+ calendar.add( Calendar.HOUR_OF_DAY, -calendar.get( Calendar.HOUR_OF_DAY ) % 6 - 1 );
+ } else if ( perDateMillis >= HOUR.__millis / 6 ) {
+ calendar.add( Calendar.HOUR_OF_DAY, -calendar.get( Calendar.HOUR_OF_DAY ) % 4 - 1 );
+ } else if ( perDateMillis >= HOUR.__millis / 12 ) {
+ calendar.add( Calendar.HOUR_OF_DAY, -calendar.get( Calendar.HOUR_OF_DAY ) % 2 - 1 );
+ } else if ( perDateMillis >= HOUR.__millis ) {
+ calendar.add( Calendar.HOUR_OF_DAY, -1 );
+ }
+ return calendar;
+ }
+
+// final Map<String,Integer> _textWidths = new HashMap<>();
+//
+// private int getTextWidth( final Graphics g, final String text ) {
+// Integer width = _textWidths.get( text );
+// if ( width != null ) {
+// return width;
+// }
+// final FontMetrics fm = g.getFontMetrics();
+// final int textWidth = (int) fm.getStringBounds( text, g ).getWidth();
+// _textWidths.put( text, textWidth );
+// return textWidth;
+// }
+
+ // Didn't improve speed
+// final Map<String,BufferedImage> _dateTextImages = new HashMap<>();
+//
+// private BufferedImage getDateTextImage( final Graphics g, final String text ) {
+// BufferedImage dateTextImage = _dateTextImages.get( text );
+// if ( dateTextImage != null ) {
+// return dateTextImage;
+// }
+// final FontMetrics fm = g.getFontMetrics();
+// final Rectangle2D bounds = fm.getStringBounds( text, g );
+// final BufferedImage dateTextImage = new BufferedImage( (int)bounds.getWidth()+10, (int)bounds.getHeight(),
+// BufferedImage.TYPE_INT_ARGB );
+// final Graphics2D g2d = dateTextImage.createGraphics();
+// g2d.setColor( Color.BLACK );
+// g2d.drawString( text, 0, fm.getAscent() );
+// g2d.dispose();
+// _dateTextImages.put( text, dateTextImage );
+// return dateTextImage;
+// }
+
+
+ static private String getDateText( final long millis, final DateFormat dateFormat ) {
+ final Date date = new Date( millis );
+ return dateFormat.format( date );
+ }
+
+ static private int getTextWidth( final Graphics g, final String text ) {
+ final FontMetrics fm = g.getFontMetrics();
+ return (int)fm.getStringBounds( text, g ).getWidth();
+ }
+
+ /**
+ * Make certain that the formatted text dates/times won't be overlapping on screen
+ *
+ * @param g -
+ * @param dateFormat -
+ * @param calendar -
+ * @param calendarField -
+ * @param calendarIncrement -
+ * @param xform -
+ * @return true if the dates will not overlap each other on screen
+ */
+ protected boolean checkTextWidth( final Graphics g, final DateFormat dateFormat, final Calendar calendar,
+ final int calendarField, final int calendarIncrement, final double xform ) {
+ long millis = calendar.getTimeInMillis() + 1;
+ final String text = getDateText( millis, dateFormat );
+ final int textWidth = getTextWidth( g, text ) + 10;
+ final int x1 = (int)(millis / xform);
+ calendar.add( calendarField, calendarIncrement );
+ millis = calendar.getTimeInMillis();
+ final int x2 = (int)(millis / xform);
+ return x2 - x1 > textWidth;
+ }
+
+
+ protected void paintStartingText( final Graphics g, final DateFormat dateFormat, final Calendar calendar,
+ final int calendarField, final int calendarIncrement,
+ final long beginDrawMillis, final double xform, final long endMillis ) {
+ // Clipbounds seem to be ok
+ final Rectangle clipBounds = g.getClipBounds();
+ final int fontHeight = getFontHeight();
+ final int trackHeight = fontHeight + CALENDER_TICK_HEIGHT;
+ long millis = calendar.getTimeInMillis() + 1;
+ int incrementCount = 0;
+ while ( millis <= endMillis + 10 ) {
+ int x = (int)((millis - beginDrawMillis) / xform);
+ if ( clipBounds.contains( x, fontHeight ) ) {
+ if ( incrementCount % calendarIncrement == 0 ) {
+ final String text = getDateText( millis, dateFormat );
+// BufferedImage textImage = getDateTextImage( g, text );
+// g.drawImage( textImage, x-textImage.getWidth()/2, 0, null );
+ final int textWidth = getTextWidth( g, text );
+ final int offset = textWidth / 4;
+ g.drawString( text, x - offset, fontHeight );
+ g.drawLine( x, fontHeight + 1, x, trackHeight );
+ } else {
+ g.drawLine( x, fontHeight + 6, x, trackHeight );
+ }
+ }
+ calendar.add( calendarField, 1 );
+ millis = calendar.getTimeInMillis();
+ incrementCount++;
+ }
+ }
+
+ protected void paintCenteredText( final Graphics g, final DateFormat dateFormat, final Calendar calendar,
+ final int calendarField, final int calendarIncrement,
+ final long beginDrawMillis, final double xform, final long endMillis ) {
+ // Clipbounds seem to be ok
+ final Rectangle clipBounds = g.getClipBounds();
+ final int fontHeight = getFontHeight();
+ final int trackHeight = fontHeight + CALENDER_TICK_HEIGHT;
+ long millis = calendar.getTimeInMillis() + 1;
+ long nextMillis;
+ while ( millis <= endMillis + 10 ) {
+ calendar.add( calendarField, calendarIncrement );
+ nextMillis = calendar.getTimeInMillis();
+ int x = (int)((millis - beginDrawMillis) / xform);
+ int xCenter = (int)(((nextMillis + millis) / 2l - beginDrawMillis) / xform);
+ if ( clipBounds.contains( x, fontHeight ) || clipBounds.contains( xCenter, fontHeight ) ) {
+ final String text = getDateText( millis, dateFormat );
+ final int textWidth = getTextWidth( g, text );
+ final int offset = textWidth / 2;
+ g.drawString( text, xCenter - offset, fontHeight );
+ g.drawLine( x, fontHeight, x, trackHeight );
+ }
+ millis = nextMillis;
+ }
+ }
+
+
+}
Added: ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/gui/corpus/NoteListRenderer.java
URL: http://svn.apache.org/viewvc/ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/gui/corpus/NoteListRenderer.java?rev=1660963&view=auto
==============================================================================
--- ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/gui/corpus/NoteListRenderer.java (added)
+++ ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/gui/corpus/NoteListRenderer.java Thu Feb 19 18:06:13 2015
@@ -0,0 +1,30 @@
+package org.chboston.cnlp.timeline.gui.corpus;
+
+
+import org.chboston.cnlp.nlp.corpus.DefaultNote;
+import org.chboston.cnlp.nlp.corpus.Note;
+
+import javax.swing.*;
+import java.awt.*;
+import java.text.DateFormat;
+import java.util.Date;
+
+/**
+ * Author: SPF
+ * Affiliation: CHIP-NLP
+ * Date: 3/25/14
+ */
+public class NoteListRenderer extends DefaultListCellRenderer {
+
+ public Component getListCellRendererComponent( final JList list, final Object value, final int index,
+ final boolean isSelected, final boolean cellHasFocus ) {
+ if ( value == null || !(value instanceof DefaultNote) ) {
+ return super.getListCellRendererComponent( list, value, index, isSelected, cellHasFocus );
+ }
+ final Note note = (Note)value;
+ final String date = DateFormat.getDateInstance().format( new Date( note.getTimestampMillis() ) );
+ final String textValue = note.getName() + " : " + date;
+ return super.getListCellRendererComponent( list, textValue, index, isSelected, cellHasFocus );
+ }
+
+}
Added: ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/gui/corpus/NoteNavigator.java
URL: http://svn.apache.org/viewvc/ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/gui/corpus/NoteNavigator.java?rev=1660963&view=auto
==============================================================================
--- ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/gui/corpus/NoteNavigator.java (added)
+++ ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/gui/corpus/NoteNavigator.java Thu Feb 19 18:06:13 2015
@@ -0,0 +1,232 @@
+package org.chboston.cnlp.timeline.gui.corpus;
+
+import org.chboston.cnlp.gui.GlobalHotkeyManager;
+import org.chboston.cnlp.nlp.corpus.DefaultNote;
+import org.chboston.cnlp.nlp.corpus.Note;
+
+import javax.swing.*;
+import javax.swing.border.EmptyBorder;
+import javax.swing.event.EventListenerList;
+import javax.swing.event.ListSelectionEvent;
+import javax.swing.event.ListSelectionListener;
+import javax.swing.filechooser.FileNameExtensionFilter;
+import java.awt.*;
+import java.awt.datatransfer.DataFlavor;
+import java.awt.dnd.DnDConstants;
+import java.awt.dnd.DropTarget;
+import java.awt.dnd.DropTargetDropEvent;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.KeyEvent;
+import java.io.File;
+
+/**
+ * Author: SPF
+ * Affiliation: CHIP-NLP
+ * Date: 3/25/14
+ */
+final public class NoteNavigator extends JPanel {
+
+ final boolean DEBUG = false;
+ static private final String TEXT_DOC_PATH
+ = "C:\\Spiffy\\Data\\corpus\\SampleTemporal\\ID000_clinic_demo_gold\\ID000_clinic_demo.txt";
+
+ final private JComboBox<Note> _noteBox;
+ private Note _selectedNote;
+
+ public NoteNavigator( final String defaultDirectory ) {
+ super( new BorderLayout( 10, 10 ) );
+ setBorder( new EmptyBorder( 5, 5, 5, 5 ) );
+ final JLabel label = new JLabel( "Selected Note:" );
+ _noteBox = new JComboBox<>();
+ _noteBox.addActionListener( new NoteSelectionForwarder() );
+ _noteBox.setRenderer( new NoteListRenderer() );
+
+ if ( DEBUG ) {
+ _noteBox.addItem( new DefaultNote( TEXT_DOC_PATH ) );
+ }
+
+ final JButton openChooserButton = new JButton( new OpenFileAction( defaultDirectory ) );
+ add( label, BorderLayout.WEST );
+ add( _noteBox, BorderLayout.CENTER );
+ add( openChooserButton, BorderLayout.EAST );
+
+ setDropTarget( new FileDropTarget() );
+
+ final GlobalHotkeyManager hotKeyManager = GlobalHotkeyManager.getInstance();
+ hotKeyManager.addHotKey( "PreviousNote",
+ KeyStroke.getKeyStroke( KeyEvent.VK_P, KeyEvent.CTRL_MASK, false ),
+ new PreviousNoteAction() );
+ hotKeyManager.addHotKey( "NextNote",
+ KeyStroke.getKeyStroke( KeyEvent.VK_N, KeyEvent.CTRL_MASK, false ),
+ new NextNoteAction() );
+ }
+
+ public Note getSelectedNote() {
+ return _selectedNote;
+ }
+
+ /**
+ * mocks the navigator as a list
+ *
+ * @param listener the {@code ListSelectionListener} to add
+ */
+ public void addListSelectionListener( final ListSelectionListener listener ) {
+ listenerList.add( ListSelectionListener.class, listener );
+ }
+
+ /**
+ * mocks the navigator as a list
+ *
+ * @param listener the {@code ListSelectionListener} to remove
+ */
+ public void removeListSelectionListener( final ListSelectionListener listener ) {
+ listenerList.remove( ListSelectionListener.class, listener );
+ }
+
+
+ /**
+ * Opens the JFileChooser
+ */
+ private class OpenFileAction extends AbstractAction {
+ private final JFileChooser __chooser;
+
+ private OpenFileAction( final String defaultDirectory ) {
+ super( "Open Note" );
+ __chooser = new JFileChooser();
+ if ( defaultDirectory != null ) {
+ final File startingDir = new File( defaultDirectory );
+ if ( startingDir.exists() ) {
+ __chooser.setCurrentDirectory( startingDir );
+ }
+ }
+ __chooser.setFileSelectionMode( JFileChooser.FILES_ONLY );
+ __chooser.addChoosableFileFilter( new FileNameExtensionFilter( "Text File", ".txt" ) );
+ __chooser.addChoosableFileFilter( new FileNameExtensionFilter( "Xmi File", ".xmi", ".xmi.old" ) );
+// __chooser.setAcceptAllFileFilterUsed( false );
+ __chooser.setMultiSelectionEnabled( true );
+ }
+
+ @Override
+ public void actionPerformed( final ActionEvent event ) {
+ final int option = __chooser.showOpenDialog( null );
+ if ( option != JFileChooser.APPROVE_OPTION ) {
+ return;
+ }
+ final File[] files = __chooser.getSelectedFiles();
+ final MutableComboBoxModel<Note> noteListModel = (MutableComboBoxModel<Note>)_noteBox.getModel();
+ for ( File file : files ) {
+ boolean add = true;
+ for ( int i = 0; i < _noteBox.getItemCount(); i++ ) {
+ if ( _noteBox.getItemAt( i ).getPath().equals( file.getPath() ) ) {
+ add = false;
+ break;
+ }
+ }
+ if ( add ) {
+ noteListModel.addElement( new DefaultNote( file.getPath() ) );
+ }
+ }
+ }
+ // TODO implement timeline display
+ }
+
+
+ private class FileDropTarget extends DropTarget {
+ public synchronized void drop( final DropTargetDropEvent event ) {
+ event.acceptDrop( DnDConstants.ACTION_COPY );
+ try {
+ final Object values = event.getTransferable().getTransferData( DataFlavor.javaFileListFlavor );
+ if ( values instanceof Iterable ) {
+ final MutableComboBoxModel<Note> noteListModel = (MutableComboBoxModel<Note>)_noteBox.getModel();
+ for ( Object value : (Iterable)values ) {
+ if ( value instanceof File ) {
+ boolean add = true;
+ for ( int i = 0; i < _noteBox.getItemCount(); i++ ) {
+ if ( _noteBox.getItemAt( i ).getPath().equals( ((File)value).getPath() ) ) {
+ add = false;
+ break;
+ }
+ }
+ if ( add ) {
+ noteListModel.addElement( new DefaultNote( ((File)value).getPath() ) );
+ _noteBox.setSelectedIndex( noteListModel.getSize() - 1 );
+ }
+ }
+ }
+ }
+ } catch ( Exception ex ) {
+ ex.printStackTrace();
+ }
+ }
+ }
+
+
+ private class PreviousNoteAction extends AbstractAction {
+ @Override
+ public void actionPerformed( final ActionEvent event ) {
+ final int index = _noteBox.getSelectedIndex();
+ if ( index > 0 ) {
+ _noteBox.setSelectedIndex( index - 1 );
+ }
+ }
+ }
+
+ private class NextNoteAction extends AbstractAction {
+ @Override
+ public void actionPerformed( final ActionEvent event ) {
+ final int index = _noteBox.getSelectedIndex();
+ if ( index < _noteBox.getItemCount() - 1 ) {
+ _noteBox.setSelectedIndex( index + 1 );
+ }
+ }
+ }
+
+ /**
+ * Notifies {@code ListSelectionListener}s added directly to the list
+ * of selection changes made to the selection model. {@code JList}
+ * listens for changes made to the selection in the selection model,
+ * and forwards notification to listeners added to the list directly,
+ * by calling this method.
+ * <p/>
+ * This method constructs a {@code ListSelectionEvent} with this list
+ * as the source, and the specified arguments, and sends it to the
+ * registered {@code ListSelectionListeners}.
+ *
+ * @param firstIndex the first index in the range, {@code <= lastIndex}
+ * @param lastIndex the last index in the range, {@code >= firstIndex}
+ * @param isAdjusting whether or not this is one in a series of
+ * multiple events, where changes are still being made
+ * @see #addListSelectionListener
+ * @see #removeListSelectionListener
+ * @see javax.swing.event.ListSelectionEvent
+ * @see EventListenerList
+ */
+ private void fireSelectionValueChanged( final int firstIndex, final int lastIndex, final boolean isAdjusting ) {
+ final Object[] listeners = listenerList.getListenerList();
+ final ListSelectionEvent event = new ListSelectionEvent( this, firstIndex, lastIndex, isAdjusting );
+ for ( int i = listeners.length - 2; i >= 0; i -= 2 ) {
+ if ( listeners[ i ] == ListSelectionListener.class ) {
+ ((ListSelectionListener)listeners[ i + 1 ]).valueChanged( event );
+ }
+ }
+ }
+
+ final private class NoteSelectionForwarder implements ActionListener {
+ @Override
+ public void actionPerformed( final ActionEvent event ) {
+ final Object selected = _noteBox.getSelectedItem();
+ if ( selected == null ) {
+ // do something to clear selected timeline
+ return;
+ }
+ if ( selected != _selectedNote ) {
+ // the note selection has changed, forward it to list selection listeners
+ _selectedNote = (Note)selected;
+ final int index = _noteBox.getSelectedIndex();
+ fireSelectionValueChanged( index, index, false );
+ }
+ }
+ }
+
+}
Added: ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/gui/event/EventColor.java
URL: http://svn.apache.org/viewvc/ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/gui/event/EventColor.java?rev=1660963&view=auto
==============================================================================
--- ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/gui/event/EventColor.java (added)
+++ ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/gui/event/EventColor.java Thu Feb 19 18:06:13 2015
@@ -0,0 +1,127 @@
+package org.chboston.cnlp.timeline.gui.event;
+
+
+import org.chboston.cnlp.nlp.annotation.classtype.SemanticClassType;
+
+import java.awt.*;
+
+/**
+ * Author: SPF
+ * Affiliation: CHIP-NLP
+ * Date: 9/6/13
+ */
+public enum EventColor {
+
+ NORMAL( new Color( 0x7C, 0xE4, 0x40 ), "Green" ),
+ // UNCERTAIN( new Color( 255, 200, 0 ), "#CC6600" ),
+ UNCERTAIN( new Color( 255, 127, 0 ), "#FF8000" ),
+ NEGATED( new Color( 0xEC, 0x45, 0x39 ), "Red" ),
+ SELECTED( new Color( 0, 178, 178 ), "#008B8B" ),
+ // SELECTED( (UIManager.getColor( "Table.selectionBackground" ) != null )
+// ? UIManager.getColor( "Table.selectionBackground" )
+// : UIManager.getColor( "textHighlight" ), "#008B8B" ),
+ FOCUS( new Color( 102, 255, 255 ), "Cyan" ),
+ FUZZY_NORMAL( new Color( 0x6A, 0xC0, 0x37, 95 ), "Green" ),
+ FUZZY_UNCERTAIN( new Color( 255, 200, 0, 95 ), "Orange" ),
+ FUZZY_NEGATED( new Color( 0xEC, 0x45, 0x39, 95 ), "Red" ),
+ FUZZY_SELECTED( new Color( 0, 178, 178, 95 ), "#008B8B" ),
+ FUZZY_FOCUS( new Color( 102, 255, 255, 95 ), "Cyan" ),
+ REFERENCE( new Color( 216, 216, 216, 85 ), "#D8D8D8" ),
+
+ TIMEX( Color.GRAY, "NULL" ),
+
+ // TODO Move Semantic Types to another enum
+// SIGN_SYMPTOM( new Color( 247, 129, 243 ), "#F781F3" ),
+// TEST_PROCEDURE( new Color( 129, 159, 247 ), "#819FF7" ),
+// DISEASE_DISORDER( new Color( 127, 0, 0 ), "#7F0008" ),
+// MEDICATION( new Color( 1, 58, 223 ), "#013ADF" );
+ SIGN_SYMPTOM( new Color( 255, 0, 179 ), "NULL" ),
+ TEST_PROCEDURE( Color.BLACK, "NULL" ),
+ DISEASE_DISORDER( new Color( 7, 135, 7 ), "NULL" ),
+ MEDICATION( Color.RED, "NULL" ),
+ // ANATOMICAL_SITE( Color.BLUE, "NULL" ),
+ UNKNOWN( Color.GRAY, "NULL" ),
+ NONE( Color.WHITE, "NULL" );
+
+
+ static public EventColor getEventColor( final EventStatus eventStatus ) {
+ switch ( eventStatus ) {
+ case NEGATED:
+ return NEGATED;
+ case UNCERTAIN_NEGATED:
+ return NEGATED;
+ case UNCERTAIN:
+ return UNCERTAIN;
+ case NORMAL:
+ return NORMAL;
+ }
+ return NORMAL;
+ }
+
+ static public EventColor getEventColor( final EventStatus eventStatus, final boolean isFuzzy ) {
+ if ( !isFuzzy ) {
+ return getEventColor( eventStatus );
+ }
+ switch ( eventStatus ) {
+ case NEGATED:
+ return FUZZY_NEGATED;
+ case UNCERTAIN_NEGATED:
+ return FUZZY_NEGATED;
+ case UNCERTAIN:
+ return FUZZY_UNCERTAIN;
+ case NORMAL:
+ return FUZZY_NORMAL;
+ }
+ return FUZZY_NORMAL;
+ }
+
+
+ static public EventColor getSemanticColor( final SemanticClassType semanticType ) {
+ if ( semanticType == null || semanticType == SemanticClassType.UNKNOWN ) {
+ return NONE;
+ }
+ switch ( semanticType ) {
+ case SIGN_OR_SYMPTOM:
+ return SIGN_SYMPTOM;
+ case PROCEDURE:
+ return TEST_PROCEDURE;
+ case DISEASE_DISORDER:
+ return DISEASE_DISORDER;
+ case MEDICATION:
+ return MEDICATION;
+// case ANATOMICAL_SITE:
+// return ANATOMICAL_SITE;
+ }
+ return UNKNOWN;
+ }
+
+
+ static public EventColor getTimeSpanColor( final boolean isSelected, final boolean isFocused,
+ final boolean isFuzzy ) {
+ if ( isSelected ) {
+ if ( isFocused ) {
+ return isFuzzy ? FUZZY_FOCUS : FOCUS;
+ }
+ return isFuzzy ? FUZZY_SELECTED : SELECTED;
+ }
+ return isFuzzy ? FUZZY_NORMAL : NORMAL;
+ }
+
+
+ private final Color _color;
+ private final String _htmlCode;
+
+ private EventColor( final Color color, final String htmlCode ) {
+ _color = color;
+ _htmlCode = htmlCode;
+ }
+
+ public Color getColor() {
+ return _color;
+ }
+
+ public String getHtmlCode() {
+ return _htmlCode;
+ }
+
+}
Added: ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/gui/event/EventHtmlFactory.java
URL: http://svn.apache.org/viewvc/ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/gui/event/EventHtmlFactory.java?rev=1660963&view=auto
==============================================================================
--- ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/gui/event/EventHtmlFactory.java (added)
+++ ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/gui/event/EventHtmlFactory.java Thu Feb 19 18:06:13 2015
@@ -0,0 +1,411 @@
+package org.chboston.cnlp.timeline.gui.event;
+
+import org.chboston.cnlp.nlp.annotation.annotation.Annotation;
+import org.chboston.cnlp.nlp.annotation.attribute.Attribute;
+import org.chboston.cnlp.nlp.annotation.attribute.AttributeUtil;
+import org.chboston.cnlp.nlp.annotation.classtype.SemanticClassType;
+import org.chboston.cnlp.nlp.annotation.coreference.CoreferenceChain;
+import org.chboston.cnlp.nlp.annotation.entity.Entity;
+import org.chboston.cnlp.nlp.annotation.relation.Relation;
+import org.chboston.cnlp.timeline.gui.search.TimelineSearchUtil;
+import org.chboston.cnlp.timeline.timespan.plus.PointedTimeSpan;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.regex.Pattern;
+
+/**
+ * Author: SPF
+ * Affiliation: CHIP-NLP
+ * Date: 8/27/13
+ */
+final public class EventHtmlFactory {
+
+ static private final Pattern SPACE_PATTERN = Pattern.compile( "\\s\\s\\s+" );
+
+ private EventHtmlFactory() {
+ }
+
+ static private enum HTML_COLOR {
+ DATE_SPAN( "Black" ), DATE_TEXT( "Blue" ),
+ // EVENT_TEXT( "Green" ), EVENT_NEGATED_TEXT( "Red" ), EVENT_UNCERTAIN_TEXT( "#CC6600" ),
+ RELATION( "#7A2770" ), MISC_UMLS( "Gray" ), MODIFIER_UMLS( "Dark Gray" ),
+// SEARCH( "#008B8B" )
+ ;
+ private final String __htmlCode;
+
+ private HTML_COLOR( final String htmlCode ) {
+ __htmlCode = htmlCode;
+ }
+
+ public String toString() {
+ return __htmlCode;
+ }
+ }
+
+ static private enum HTML_TAG {
+ HEADER( "<HTML>" ), FOOTER( "</HTML>" ), EOL( "<BR>" ), LINE( "<HR>" ), BULLET( "• " ),
+ COLOR( "COLOR_TAG" ), FONT_START( "<p style=\"color:" + COLOR.toString() + "\">" ), FONT_END( "</p>" ),
+ BOLD( "<b>" ), END_BOLD( "</b>" ), ITALIC( "<i>" ), END_ITALIC( "</i>" );
+ private final String __htmlCode;
+
+ private HTML_TAG( final String htmlCode ) {
+ __htmlCode = htmlCode;
+ }
+
+ public String toString() {
+ return __htmlCode;
+ }
+ }
+
+
+ static public String createColorLegend() {
+ final StringBuilder sb = new StringBuilder();
+ sb.append( HTML_TAG.HEADER );
+ setFontColor( sb, HTML_COLOR.DATE_SPAN );
+ sb.append( HTML_TAG.BOLD );
+ sb.append( "<CENTER>LEGEND</CENTER>" );
+ sb.append( HTML_TAG.END_BOLD );
+ sb.append( HTML_TAG.LINE );
+ // legend for timespan
+ sb.append( HTML_TAG.BOLD );
+ sb.append( "Event Date/Time" );
+ sb.append( HTML_TAG.END_BOLD );
+ endFontColor( sb );
+ // legend for events
+ appendEventColor( sb, false, false, false );
+ sb.append( HTML_TAG.BULLET );
+ appendEventPrefix( sb, false, false, false, true );
+ sb.append( "Normal Event" );
+ appendEventSuffix( sb, false, false, false, true );
+ endFontColor( sb );
+ // uncertain
+ appendEventColor( sb, false, false, true );
+ sb.append( HTML_TAG.BULLET );
+ appendEventPrefix( sb, false, false, true, true );
+ sb.append( "Uncertain Event" );
+ appendEventSuffix( sb, false, false, true, true );
+ endFontColor( sb );
+ // negated
+ appendEventColor( sb, false, true, false );
+ sb.append( HTML_TAG.BULLET );
+ appendEventPrefix( sb, false, true, false, true );
+ sb.append( "Negated Event" );
+ appendEventSuffix( sb, false, true, false, true );
+ endFontColor( sb );
+ // selected
+ appendEventColor( sb, true, false, false );
+ sb.append( HTML_TAG.BULLET );
+ appendEventPrefix( sb, true, false, false, true );
+ sb.append( "Selected Event" );
+ appendEventSuffix( sb, true, false, false, true );
+ endFontColor( sb );
+ // relation
+ setFontColor( sb, HTML_COLOR.RELATION );
+ sb.append( "    -" );
+ sb.append( HTML_TAG.ITALIC ).append( "Event Relation" );
+ sb.append( HTML_TAG.END_ITALIC );
+ sb.append( HTML_TAG.FOOTER );
+ return sb.toString();
+ }
+
+ static public String createEventHtml( final PointedTimeSpan timeSpan,
+ final Entity entity, final Collection<Relation> eventEvents,
+ final SemanticClassType semanticType, final Collection<String> searchTerms ) {
+ final StringBuilder sb = new StringBuilder();
+ sb.append( HTML_TAG.HEADER );
+ appendEventHtml( sb, timeSpan, entity, eventEvents, semanticType, searchTerms );
+ sb.append( HTML_TAG.FOOTER );
+ return sb.toString();
+ }
+
+
+ static public String createEventHtml( final Entity entity, final Collection<Relation> eventEvents,
+ final SemanticClassType semanticType, final Collection<String> searchTerms ) {
+ final StringBuilder sb = new StringBuilder();
+ sb.append( HTML_TAG.HEADER );
+ appendEventHtml( sb, entity, eventEvents, semanticType, searchTerms );
+ sb.append( HTML_TAG.FOOTER );
+ return sb.toString();
+ }
+
+ static public String createRelationHtml( final Relation eventEvent ) {
+ final StringBuilder sb = new StringBuilder();
+ sb.append( HTML_TAG.HEADER );
+ appendRelationHtml( sb, eventEvent );
+ sb.append( HTML_TAG.FOOTER );
+ return sb.toString();
+ }
+
+ static private void appendRelationHtml( final StringBuilder sb, final Relation tlink ) {
+ setFontColor( sb, HTML_COLOR.RELATION );
+ final String tlinkTypeText = createTlinkTypeText( tlink );
+ if ( !tlinkTypeText.isEmpty() ) {
+ final Entity entity = tlink.getSecondEntity();
+ sb.append( "    -" );
+ sb.append( HTML_TAG.ITALIC ).append( tlinkTypeText ).append( " " );
+// final boolean isSearchResult = TimelineSearchUtil.hasSearchTerm( entity.getSpannedText(), searchTerms );
+ final boolean isSearchResult = false;
+ final boolean isNegative = AttributeUtil.hasNegativePolarity( entity );
+ final boolean isUncertain = AttributeUtil.hasUncertainty( entity );
+ appendEventPrefix( sb, isSearchResult, isNegative, isUncertain, false );
+ sb.append( getNiceText( entity, null ) );
+ appendEventSuffix( sb, isSearchResult, isNegative, isUncertain, false );
+ sb.append( HTML_TAG.END_ITALIC );
+ sb.append( HTML_TAG.EOL );
+ }
+ endFontColor( sb );
+ }
+
+
+ static public void appendEventHtml( final StringBuilder sb, final PointedTimeSpan timeSpan,
+ final Entity entity, final Collection<Relation> eventEvents,
+ final SemanticClassType semanticType, final Collection<String> searchTerms ) {
+ appendTimeSpan( sb, timeSpan );
+ appendEventHtml( sb, entity, eventEvents, semanticType, searchTerms );
+ }
+
+
+ static public void appendEventHtml( final StringBuilder sb,
+ final Entity entity, final Collection<Relation> eventEvents,
+ final SemanticClassType semanticType, final Collection<String> searchTerms ) {
+ appendEvent( sb, entity, semanticType, searchTerms );
+// if ( eventEvents != null && !eventEvents.isEmpty() ) {
+// appendEventEvents( sb, semanticType, eventEvents, searchTerms );
+// }
+ }
+
+
+ static private void appendTimeSpan( final StringBuilder sb, final PointedTimeSpan timeSpan ) {
+ final String dateSpanText = timeSpan.toString();
+ if ( dateSpanText != null && !dateSpanText.isEmpty() ) {
+ setFontColor( sb, HTML_COLOR.DATE_SPAN );
+ sb.append( HTML_TAG.BOLD ).append( dateSpanText ).append( HTML_TAG.END_BOLD );
+ endFontColor( sb );
+ }
+ }
+
+ static private void appendEvent( final StringBuilder sb, final Entity entity, final SemanticClassType semanticType,
+ final Collection<String> searchTerms ) {
+ final boolean isSearchResult = TimelineSearchUtil.hasSearchTerm( entity.getSpannedText(), searchTerms );
+ final boolean isNegative = AttributeUtil.hasNegativePolarity( entity );
+ final boolean isUncertain = AttributeUtil.hasUncertainty( entity );
+ // final boolean isModifier = isModifierUmls( entity );
+ // final boolean isMiscUmls = isMiscUmls( entity );
+ appendEventColor( sb, isSearchResult, isNegative, isUncertain );
+ sb.append( HTML_TAG.BULLET );
+ appendEventPrefix( sb, isSearchResult, isNegative, isUncertain, true );
+ sb.append( getNiceText( entity, semanticType ) );
+ appendEventSuffix( sb, isSearchResult, isNegative, isUncertain, true );
+ endFontColor( sb );
+ }
+
+ static private void appendEventEvents( final StringBuilder sb, final SemanticClassType semanticType,
+ final Collection<Relation> tlinks,
+ final Collection<String> searchTerms ) {
+ final java.util.List<Relation> tlinkList = new ArrayList<>( tlinks );
+ Collections.sort( tlinkList, TlinkComparator.INSTANCE );
+ setFontColor( sb, HTML_COLOR.RELATION );
+ for ( Relation tlink : tlinkList ) {
+ final String tlinkTypeText = createTlinkTypeText( tlink );
+ if ( tlinkTypeText.isEmpty() ) {
+ continue;
+ }
+ final Entity entity = tlink.getSecondEntity();
+ sb.append( "    -" );
+ sb.append( HTML_TAG.ITALIC ).append( tlinkTypeText ).append( " " );
+ final boolean isSearchResult = TimelineSearchUtil.hasSearchTerm( entity.getSpannedText(), searchTerms );
+ final boolean isNegative = AttributeUtil.hasNegativePolarity( entity );
+ final boolean isUncertain = AttributeUtil.hasUncertainty( entity );
+ appendEventPrefix( sb, isSearchResult, isNegative, isUncertain, false );
+ sb.append( getNiceText( entity, semanticType ) );
+ appendEventSuffix( sb, isSearchResult, isNegative, isUncertain, false );
+// if ( isNegative ) {
+// sb.append( "(No) " );
+// }
+// if ( isSearchResult ) {
+// sb.append( HTML_TAG.BOLD );
+// }
+// sb.append( getNiceText( entity, semanticType ) );
+// if ( isUncertain ) {
+// sb.append( " (??)" );
+// }
+// if ( isSearchResult ) {
+// sb.append( HTML_TAG.END_BOLD );
+// }
+ sb.append( HTML_TAG.END_ITALIC );
+ sb.append( HTML_TAG.EOL );
+ }
+ endFontColor( sb );
+ }
+
+
+ // static private boolean isMiscUmls( final Annotation event ) {
+// return false;
+// }
+
+// static private boolean isModifierUmls( final Annotation event ) {
+// return false;
+// }
+
+ // static private void appendDate( final StringBuilder sb, final Annotation date ) {
+ // sb.append( "\"" ).append( date.getSpannedText() ).append( "\"" ).append( HTML_TAG.EOL );
+ // }
+
+// static private void appendEvent( final StringBuilder sb,
+// final Annotation event,
+// final SemanticClassType semanticType,
+// final boolean isSearchResult,
+// final boolean isNegative,
+// final boolean isUncertain ) {
+// if ( isNegative ) {
+// sb.append( HTML_TAG.ITALIC ).append( "(No) " ).append( HTML_TAG.END_ITALIC );
+// }
+// if ( isSearchResult ) {
+// sb.append( HTML_TAG.BOLD );
+// }
+// sb.append( getNiceText( event, semanticType ) );
+// if ( isUncertain ) {
+// sb.append( HTML_TAG.ITALIC ).append( " (??)" ).append( HTML_TAG.END_ITALIC );
+// }
+// if ( isSearchResult ) {
+// sb.append( HTML_TAG.END_BOLD );
+// }
+// }
+
+ static private void appendEventColor( final StringBuilder sb,
+ final boolean isSearchResult,
+ final boolean isNegative,
+ final boolean isUncertain ) {
+ if ( isSearchResult ) {
+// setFontColor( sb, HTML_COLOR.SEARCH );
+ setFontColor( sb, EventColor.SELECTED );
+ } else if ( isNegative ) {
+// setFontColor( sb, HTML_COLOR.EVENT_NEGATED_TEXT );
+ setFontColor( sb, EventColor.NEGATED );
+ } else if ( isUncertain ) {
+// setFontColor( sb, HTML_COLOR.EVENT_UNCERTAIN_TEXT );
+ setFontColor( sb, EventColor.UNCERTAIN );
+ // } else if ( needModifierColor ) {
+ // setFontColor( sb, HTML_COLOR.MODIFIER_UMLS );
+ // } else if ( needMiscUmlsColor ) {
+ // setFontColor( sb, HTML_COLOR.MISC_UMLS );
+ } else {
+// setFontColor( sb, HTML_COLOR.EVENT_TEXT );
+ setFontColor( sb, EventColor.NORMAL );
+ }
+ }
+
+ static private void appendEventPrefix( final StringBuilder sb,
+ final boolean isSearchResult,
+ final boolean isNegative,
+ final boolean isUncertain, final boolean useItalics ) {
+ if ( isNegative ) {
+ if ( useItalics ) {
+ sb.append( HTML_TAG.ITALIC ).append( "(No) " ).append( HTML_TAG.END_ITALIC );
+ } else {
+ sb.append( "(No) " );
+ }
+ }
+ if ( isSearchResult ) {
+ sb.append( HTML_TAG.BOLD );
+ }
+ }
+
+ static private void appendEventSuffix( final StringBuilder sb,
+ final boolean isSearchResult,
+ final boolean isNegative,
+ final boolean isUncertain, final boolean useItalics ) {
+ if ( isUncertain ) {
+ if ( useItalics ) {
+ sb.append( HTML_TAG.ITALIC ).append( " (??)" ).append( HTML_TAG.END_ITALIC );
+ } else {
+ sb.append( " (??)" );
+ }
+ }
+ if ( isSearchResult ) {
+ sb.append( HTML_TAG.END_BOLD );
+ }
+ }
+
+ static private void setFontColor( final StringBuilder sb, final HTML_COLOR htmlColor ) {
+ final String fontColorStart = HTML_TAG.FONT_START.toString().replace( HTML_TAG.COLOR.toString(),
+ htmlColor.toString() );
+ sb.append( fontColorStart );
+ }
+
+ static private void setFontColor( final StringBuilder sb, final EventColor eventColor ) {
+ final String fontColorStart = HTML_TAG.FONT_START.toString().replace( HTML_TAG.COLOR.toString(),
+ eventColor.getHtmlCode() );
+ sb.append( fontColorStart );
+ }
+
+ static private void endFontColor( final StringBuilder sb ) {
+ sb.append( HTML_TAG.FONT_END );
+ }
+
+ static private String getNiceText( final String fullText ) {
+ return SPACE_PATTERN.matcher( fullText ).replaceAll( " ... " );
+ }
+
+
+ static private String getNiceText( final Annotation annotation, final SemanticClassType semanticType ) {
+ if ( semanticType != null && annotation instanceof CoreferenceChain ) {
+ return getCoreferenceText( (CoreferenceChain)annotation, semanticType );
+ }
+ return getNiceText( annotation.getSpannedText() );
+ }
+
+ static private String getCoreferenceText( final CoreferenceChain chain, final SemanticClassType semanticType ) {
+ String semanticText = null;
+ for ( Entity entity : chain ) {
+ if ( entity.getClassType() == semanticType ) {
+ if ( semanticText == null || entity.getSpannedText().length() > semanticText.length() ) {
+ semanticText = entity.getSpannedText();
+ }
+ }
+ }
+ if ( semanticText == null ) {
+ return getNiceText( chain.getSpannedText() );
+ }
+ final String fullText = chain.getSpannedText();
+ final int index = fullText.indexOf( semanticText );
+ if ( index < 0 || index + semanticText.length() > fullText.length() ) {
+ return getNiceText( chain.getSpannedText() );
+ }
+ // Removed for reading clarity (rather than excessive info) 7/8/14 SPF
+// final String notedText = fullText.replace( semanticText, "[" + semanticText + "]" );
+// return getNiceText( notedText );
+ return getNiceText( fullText );
+ }
+
+
+ static private String createTlinkTypeText( final Relation tlink ) {
+ final Attribute relationTypeAttribute = tlink.getAttribute( "Relation Type" );
+ if ( relationTypeAttribute == null ) {
+ // assume equality
+ return "occurs at the same time as";
+ }
+ // There is currently no default relation type in the thyme schema, but we must have one, so use overlap
+ final String relationType = relationTypeAttribute.getValue();
+ if ( relationType.equals( "BEFORE" ) ) {
+ return "occurs before";
+ } else if ( relationType.equals( "AFTER" ) ) {
+ return "occurs after";
+ } else if ( relationType.equals( "OVERLAP" ) ) {
+ return "overlaps";
+ } else if ( relationType.equals( "CONTAINS" ) ) {
+ return "contains";
+ } else if ( relationType.equals( "CONTAINED-BY" ) ) {
+ return "is within";
+ } else if ( relationType.equals( "BEGINS-ON" ) ) {
+ return "begins on";
+ } else if ( relationType.equals( "ENDS-ON" ) ) {
+ return "ends on";
+ }
+ return "overlaps";
+ }
+
+
+}
Added: ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/gui/event/EventStatus.java
URL: http://svn.apache.org/viewvc/ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/gui/event/EventStatus.java?rev=1660963&view=auto
==============================================================================
--- ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/gui/event/EventStatus.java (added)
+++ ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/gui/event/EventStatus.java Thu Feb 19 18:06:13 2015
@@ -0,0 +1,25 @@
+package org.chboston.cnlp.timeline.gui.event;
+
+import org.chboston.cnlp.nlp.annotation.annotation.Annotation;
+import org.chboston.cnlp.nlp.annotation.attribute.AttributeUtil;
+
+/**
+ * @author SPF , chip-nlp
+ * @version %I%
+ * @since 9/24/2014
+ */
+public enum EventStatus {
+ NORMAL, UNCERTAIN, NEGATED, UNCERTAIN_NEGATED, UNKNOWN;
+
+ static public EventStatus getEventStatus( final Annotation event ) {
+ final boolean isNegated = AttributeUtil.hasNegativePolarity( event );
+ final boolean isUncertain = AttributeUtil.hasUncertainty( event );
+ if ( isNegated ) {
+ return isUncertain ? UNCERTAIN_NEGATED : NEGATED;
+ } else if ( isUncertain ) {
+ return UNCERTAIN;
+ }
+ return NORMAL;
+ }
+
+}
Added: ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/gui/event/SemanticShape.java
URL: http://svn.apache.org/viewvc/ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/gui/event/SemanticShape.java?rev=1660963&view=auto
==============================================================================
--- ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/gui/event/SemanticShape.java (added)
+++ ctakes/sandbox/timelanes/org/chboston/cnlp/timeline/gui/event/SemanticShape.java Thu Feb 19 18:06:13 2015
@@ -0,0 +1,75 @@
+package org.chboston.cnlp.timeline.gui.event;
+
+import org.chboston.cnlp.nlp.annotation.classtype.SemanticClassType;
+
+/**
+ * Author: SPF
+ * Affiliation: CHIP-NLP
+ * Date: 9/10/14
+ */
+public enum SemanticShape {
+ // TODO Move Semantic Types to another enum
+ // X : 0,4 UL ; 0,4 LL ; 4,-4 UR ; 4,-4 LR
+ // Y : 0,4 UL ; 4,-4 LL ; 0,4 UR ; 4,-4 LR
+// SIGN_SYMPTOM( 0, 4, 0, 4 ),
+// TEST_PROCEDURE( 0, 4, 4, -4 ),
+// DISEASE_DISORDER( 4, -4, 0, 4 ),
+// MEDICATION( 4, -4, 4, -4 ),
+// // square
+// UNKNOWN( 0, 0, 4, 4 );
+
+ SIGN_SYMPTOM( 0, 6, 0, 6 ),
+ TEST_PROCEDURE( 0, 6, 6, -6 ),
+ DISEASE_DISORDER( 6, 0, 0, 6 ),
+ MEDICATION( 6, 0, 6, -6 ),
+ // square
+ UNKNOWN( 0, 0, 6, 6 );
+
+ static public SemanticShape getShape( final SemanticClassType semanticType ) {
+ if ( semanticType != null ) {
+ switch ( semanticType ) {
+ case SIGN_OR_SYMPTOM:
+ return SemanticShape.SIGN_SYMPTOM;
+ case PROCEDURE:
+ return SemanticShape.TEST_PROCEDURE;
+ case DISEASE_DISORDER:
+ return SemanticShape.DISEASE_DISORDER;
+ case MEDICATION:
+ return SemanticShape.MEDICATION;
+ }
+ }
+ return SemanticShape.UNKNOWN;
+ }
+
+ private final int _xStart;
+ private final int _yStart;
+ private final int _xOffset;
+ private final int _yOffset;
+
+ // X : 0,4 UL ; 0,4 LL ; 4,-4 UR ; 4,-4 LR
+ // Y : 0,4 UL ; 4,-4 LL ; 0,4 UR ; 4,-4 LR
+ private SemanticShape( final int xStart, final int yStart, final int xOffset, final int yOffset ) {
+ _xStart = xStart;
+ _yStart = yStart;
+ _xOffset = xOffset;
+ _yOffset = yOffset;
+ }
+
+ public int getX() {
+ return _xStart;
+ }
+
+ public int getY() {
+ return _yStart;
+ }
+
+ public int getXoffset() {
+ return _xOffset;
+ }
+
+ public int getYoffset() {
+ return _yOffset;
+ }
+
+
+}