diff --git a/client/src/visualizer.js b/client/src/visualizer.js index cb8e4e4..9cab000 100644 --- a/client/src/visualizer.js +++ b/client/src/visualizer.js @@ -126,7 +126,7 @@ var Visualizer = (function($, window, undefined) { return span; }; - var EventDesc = function(id, triggerId, roles, klass) { + var EventDesc = function(id, triggerId, roles, klass, facet="none") { this.id = id; this.triggerId = triggerId; var roleList = this.roles = []; @@ -137,6 +137,7 @@ var Visualizer = (function($, window, undefined) { this.equiv = true; } else if (klass == "relation") { this.relation = true; + this.facet = facet; } // this.leftSpans = undefined; // this.rightSpans = undefined; @@ -176,6 +177,7 @@ var Visualizer = (function($, window, undefined) { } else if (eventDesc.relation) { this.relation = true; this.eventDescId = eventNo; + this.facet = eventDesc.facet } // this.marked = undefined; }; @@ -647,8 +649,8 @@ var Visualizer = (function($, window, undefined) { t2 = rel[2][1][1]; } data.eventDescs[rel[0]] = - // (id, triggerId, roles, klass) - new EventDesc(t1, t1, [[rel[1], t2]], 'relation'); + // (id, triggerId, roles, klass, facet) + new EventDesc(t1, t1, [[rel[1], t2]], 'relation', rel[3]); }); // attributes @@ -2929,21 +2931,15 @@ Util.profileStart('before render'); recGetRelatedEquivSpans(related, spans, data); }); } - var getRelatedQuasiSpans = function(id, spans, data) { - spans[id] = true; + var getRelatedBinarySpans = function(id, spans, data) { var span = data.spans[id]; - $.each(span.incoming, function(arcNo, arc) { - var related = arc.origin; - if (related in spans || arc.type != "Quasi") - return true; - spans[related] = true; - }); $.each(span.outgoing, function(arcNo, arc) { var related = arc.target; - if (related in spans || arc.type != "Quasi") + if (related in spans || !arc.relation) return true; - spans[related] = true; - }); + spans[related] = arc; + + }); } var highlightSpan = function(span, highlight, highlightGroup, bgColor, highlightRounding) { @@ -2976,7 +2972,6 @@ Util.profileStart('before render'); // (spanTypes.SPAN_DEFAULT && spanTypes.SPAN_DEFAULT.bgColor) || // '#ffffff'); var bgColor = "#99FF66"; - var bgColor2 = '#FF66FF'; highlight = []; highlightSpan(span, highlight, highlightGroup, bgColor, highlightRounding); @@ -2990,31 +2985,24 @@ Util.profileStart('before render'); var related = {}; recGetRelatedEquivSpans(id, related, data); - - var spanIds = []; + $.each(related, function(spanId, dummy) { - spanIds.push('rect[data-span-id="' + spanId + '"]'); var relatedSpan = data.spans[spanId]; highlightSpan(relatedSpan, highlight, highlightGroup, bgColor, highlightRounding); }); - - //hack to higlight quasi identity - var quasi = {}; - getRelatedQuasiSpans(id, quasi, data); - var count = 0; - for(var prop in quasi) { - if(quasi.hasOwnProperty(prop)) - ++count; - } - if (count > 1) { - $.each(quasi, function(spanId, dummy) { - spanIds.push('rect[data-span-id="' + spanId + '"]'); - var relatedSpan = data.spans[spanId]; - highlightSpan(relatedSpan, highlight, highlightGroup, bgColor2, highlightRounding); - }); - } - - highlightSpans = $svg.find(spanIds.join(', ')).parent().addClass('highlight'); + + // hack to highlight relations + var binary = {}; + getRelatedBinarySpans(id, binary, data); + $.each(binary, function(spanId, arc) { + var relatedSpan = data.spans[spanId]; + var color = relationTypesHash['SPAN_DEFAULT'].color; + if (relationTypesHash[arc.type] != null) { + color = relationTypesHash[arc.type].color; + } + highlightSpan(relatedSpan, highlight, highlightGroup, color, highlightRounding); + }); + dispatcher.post('displayRelationsComments', [id, data, relationTypesHash]); } forceRedraw(); } else if (!that.arcDragOrigin && (id = target.attr('data-arc-role'))) { diff --git a/client/src/visualizer_ui.js b/client/src/visualizer_ui.js index ec1c589..ccbb401 100644 --- a/client/src/visualizer_ui.js +++ b/client/src/visualizer_ui.js @@ -259,12 +259,45 @@ var VisualizerUI = (function($, window, undefined) { element.css({ top: y, left: x }); }; + var adjustToSpan = function(spanId, element, offset, top, right) { + var span = $(spanId); + var spanX = span.position().left; + var spanY = span.position().top; + // get the real width, without wrapping + element.css({ left: 0, top: 0 }); + var screenHeight = $(window).height(); + var screenWidth = $(window).width(); + // FIXME why the hell is this 22 necessary?!? + var elementHeight = element.height() + 22; + var elementWidth = element.width() + 22; + var x, y; + offset = offset || 0; + if (top) { + y = spanY - elementHeight - offset; + if (y < 0) top = false; + } + if (!top) { + y = spanY + offset; + } + if (right) { + x = spanX + offset; + if (x >= screenWidth - elementWidth) right = false; + } + if (!right) { + x = x = spanX - elementWidth - offset; + } + if (y < 0) y = 0; + if (x < 0) x = 0; + element.css({ top: y, left: x }); + }; + var commentPopup = $('#commentpopup'); var commentDisplayed = false; var displayCommentTimer = null; var displayComment = function(evt, target, comment, commentText, commentType, immediately) { var idtype; + if (commentType) { // label comment by type, with special case for default note type var commentLabel; @@ -468,8 +501,58 @@ var VisualizerUI = (function($, window, undefined) { }); }; + var displayRelationCommentTimers = []; + var relationsPopupsContainer = $('#relationspopups'); + var displayRelationsComments = function(sourceId, data, relationTypesHash) { + + for (timer in displayRelationCommentTimers) { + clearTimeout(timer); + } + displayRelationCommentTimers = []; + + var span = data.spans[sourceId]; + $.each(span.outgoing, function(arcNo, arc) { + var related = arc.target; + if (!arc.relation) + return true; + + var relType = "Unknown Relation"; + if (relationTypesHash[arc.type] != null) { + relType = relationTypesHash[arc.type].labels[1]; + } + var targetId = arc.target; + var comment = ('<div class="comment_text">' + + Util.escapeHTML(relType) + + '</div>'); + var targetFullId = 'rect[data-span-id="' + targetId + '"]'; + + // display initial comment HTML + commentLabel = '<b>Facet:</b> '; + comment += commentLabel + Util.escapeHTMLwithNewlines(arc.facet); + + relationsPopupsContainer.append("<div id='relationpopup' class='relTo"+targetId+"'></div>") + var popup = relationsPopupsContainer.children().last(); + + var idtype = 'comment_relation'; + popup[0].className = idtype; + popup.html(comment); + adjustToSpan(targetFullId, popup, 10, true, true); + + /* slight "tooltip" delay to allow highlights to be seen + before the popup obstructs them. */ + var displayRelationCommentTimer = setTimeout(function() { + + popup.stop(true, true).fadeIn(); + + }, 500); + displayRelationCommentTimers.push(displayRelationCommentTimer); + + }); + }; + var onDocChanged = function() { commentPopup.hide(); + relationsPopupsContainer.empty(); commentDisplayed = false; }; @@ -518,6 +601,7 @@ var VisualizerUI = (function($, window, undefined) { var hideComment = function() { clearTimeout(displayCommentTimer); + relationsPopupsContainer.empty(); if (commentDisplayed) { commentPopup.stop(true, true).fadeOut(function() { commentDisplayed = false; }); } @@ -2284,6 +2368,7 @@ var VisualizerUI = (function($, window, undefined) { on('annotationIsAvailable', annotationIsAvailable). on('messages', displayMessages). on('displaySpanComment', displaySpanComment). + on('displayRelationsComments', displayRelationsComments). on('displayArcComment', displayArcComment). on('displaySentComment', displaySentComment). on('docChanged', onDocChanged). diff --git a/diff.xhtml b/diff.xhtml index fd283c9..150c311 100644 --- a/diff.xhtml +++ b/diff.xhtml @@ -143,6 +143,7 @@ <div id="messages" class="messages"/> <div id="pulluptrigger"/> <div id="commentpopup"/> + <div id="relationspopups"/> <div id="header" class="ui-widget"> <div id="mainHeader" class="ui-widget-header"> <div id="mainlogo" class="logo unselectable">brat</div> diff --git a/index.xhtml b/index.xhtml index 2cddea1..590ace9 100644 --- a/index.xhtml +++ b/index.xhtml @@ -105,6 +105,7 @@ <div id="messages" class="messages"/> <div id="pulluptrigger"/> <div id="commentpopup"/> + <div id="relationspopups"/> <div id="header" class="ui-widget"> <div id="mainHeader" class="ui-widget-header"> <div id="mainlogo" class="logo unselectable">brat</div> diff --git a/offline.xhtml b/offline.xhtml index a68ba42..178e4dc 100644 --- a/offline.xhtml +++ b/offline.xhtml @@ -48,6 +48,7 @@ <img id="spinner" src="static/img/spinner.gif"/> <div id="messages"/> <div id="commentpopup"/> + <div id="relationspopups"/> <div id="header" class="ui-widget"> <div id="mainHeader" class="ui-widget-header"> <div id="mainlogo" class="logo unselectable">brat</div> diff --git a/server/src/annotation.py b/server/src/annotation.py index ed5c4b4..c1805a5 100755 --- a/server/src/annotation.py +++ b/server/src/annotation.py @@ -756,18 +756,22 @@ class Annotations(object): except ValueError: raise IdedAnnotationLineSyntaxError(id, self.ann_line, self.ann_line_num+1, input_file_path) - if len(args) != 2: - Messager.error('Error parsing relation: must have exactly two arguments') + if len(args) < 2: + Messager.error('Error parsing relation: must be Arg1 Arg2 (optional)Facet') raise IdedAnnotationLineSyntaxError(id, self.ann_line, self.ann_line_num+1, input_file_path) + facet = "none" + if len(args) == 3: + facet = args[2][0] + if args[0][0] == args[1][0]: - Messager.error('Error parsing relation: arguments must not be identical') + Messager.error('Error parsing relation: related entities must not be identical') raise IdedAnnotationLineSyntaxError(id, self.ann_line, self.ann_line_num+1, input_file_path) return BinaryRelationAnnotation(id, type, args[0][0], args[0][1], args[1][0], args[1][1], - data_tail, source_id=input_file_path) + data_tail, facet, source_id=input_file_path) def _parse_equiv_annotation(self, dummy, data, data_tail, input_file_path): # NOTE: first dummy argument to have a uniform signature with other @@ -1541,25 +1545,27 @@ class BinaryRelationAnnotation(IdedAnnotation): Represented in standoff as - ID\tTYPE ARG1:ID1 ARG2:ID2 + ID\tTYPE ARG1:ID1 ARG2:ID2 Facet(optional) Where ARG1 and ARG2 are arbitrary (but not identical) labels. """ - def __init__(self, id, type, arg1l, arg1, arg2l, arg2, tail, source_id=None): + def __init__(self, id, type, arg1l, arg1, arg2l, arg2, tail, facet="none", source_id=None): IdedAnnotation.__init__(self, id, type, tail, source_id=source_id) self.arg1l = arg1l self.arg1 = arg1 self.arg2l = arg2l self.arg2 = arg2 + self.facet = facet def __str__(self): - return u'%s\t%s %s:%s %s:%s%s' % ( + return u'%s\t%s %s:%s %s:%s %s%s' % ( self.id, self.type, self.arg1l, self.arg1, self.arg2l, self.arg2, + self.facet, self.tail ) diff --git a/server/src/document.py b/server/src/document.py index 199fbdc..84b7795 100644 --- a/server/src/document.py +++ b/server/src/document.py @@ -684,7 +684,7 @@ def _enrich_json_with_data(j_dic, ann_obj): j_dic['relations'].append( [unicode(rel_ann.id), unicode(rel_ann.type), [(rel_ann.arg1l, rel_ann.arg1), - (rel_ann.arg2l, rel_ann.arg2)]] + (rel_ann.arg2l, rel_ann.arg2)], rel_ann.facet] ) for tb_ann in ann_obj.get_textbounds(): diff --git a/style-vis.css b/style-vis.css index 55f1e97..6240618 100644 --- a/style-vis.css +++ b/style-vis.css @@ -393,6 +393,28 @@ fieldset legend { border-radius: 3px; max-width: 80%; } +#relationpopup { + font-family: 'Liberation Sans', Verdana, Arial, Helvetica, sans-serif; + position: fixed; + top: 0; + left: 0; + opacity: 0.95; + padding: 10px; + display: none; + border: 1px outset #000000; + background-color: #f5f5f9; + /* background-color: #d7e7ee; */ + /* background-color: #eeeeee; */ + color: #000000; + z-index: 20; + -moz-box-shadow: 5px 5px 5px #aaaaaa; + -webkit-box-shadow: 5px 5px 5px #aaaaaa; + box-shadow: 5px 5px 5px #aaaaaa; + -moz-border-radius: 3px; + -webkit-border-radius: 3px; + border-radius: 3px; + max-width: 80%; +} #more_info_readme { height: 350px; }