Commit 9484ca64fa3019727bd28d2a4beddc764913f2e4
1 parent
29efb79e
Added facet attribute and popups for binary relations.
Showing
8 changed files
with
148 additions
and
44 deletions
client/src/visualizer.js
... | ... | @@ -126,7 +126,7 @@ var Visualizer = (function($, window, undefined) { |
126 | 126 | return span; |
127 | 127 | }; |
128 | 128 | |
129 | - var EventDesc = function(id, triggerId, roles, klass) { | |
129 | + var EventDesc = function(id, triggerId, roles, klass, facet="none") { | |
130 | 130 | this.id = id; |
131 | 131 | this.triggerId = triggerId; |
132 | 132 | var roleList = this.roles = []; |
... | ... | @@ -137,6 +137,7 @@ var Visualizer = (function($, window, undefined) { |
137 | 137 | this.equiv = true; |
138 | 138 | } else if (klass == "relation") { |
139 | 139 | this.relation = true; |
140 | + this.facet = facet; | |
140 | 141 | } |
141 | 142 | // this.leftSpans = undefined; |
142 | 143 | // this.rightSpans = undefined; |
... | ... | @@ -176,6 +177,7 @@ var Visualizer = (function($, window, undefined) { |
176 | 177 | } else if (eventDesc.relation) { |
177 | 178 | this.relation = true; |
178 | 179 | this.eventDescId = eventNo; |
180 | + this.facet = eventDesc.facet | |
179 | 181 | } |
180 | 182 | // this.marked = undefined; |
181 | 183 | }; |
... | ... | @@ -647,8 +649,8 @@ var Visualizer = (function($, window, undefined) { |
647 | 649 | t2 = rel[2][1][1]; |
648 | 650 | } |
649 | 651 | data.eventDescs[rel[0]] = |
650 | - // (id, triggerId, roles, klass) | |
651 | - new EventDesc(t1, t1, [[rel[1], t2]], 'relation'); | |
652 | + // (id, triggerId, roles, klass, facet) | |
653 | + new EventDesc(t1, t1, [[rel[1], t2]], 'relation', rel[3]); | |
652 | 654 | }); |
653 | 655 | |
654 | 656 | // attributes |
... | ... | @@ -2929,21 +2931,15 @@ Util.profileStart('before render'); |
2929 | 2931 | recGetRelatedEquivSpans(related, spans, data); |
2930 | 2932 | }); |
2931 | 2933 | } |
2932 | - var getRelatedQuasiSpans = function(id, spans, data) { | |
2933 | - spans[id] = true; | |
2934 | + var getRelatedBinarySpans = function(id, spans, data) { | |
2934 | 2935 | var span = data.spans[id]; |
2935 | - $.each(span.incoming, function(arcNo, arc) { | |
2936 | - var related = arc.origin; | |
2937 | - if (related in spans || arc.type != "Quasi") | |
2938 | - return true; | |
2939 | - spans[related] = true; | |
2940 | - }); | |
2941 | 2936 | $.each(span.outgoing, function(arcNo, arc) { |
2942 | 2937 | var related = arc.target; |
2943 | - if (related in spans || arc.type != "Quasi") | |
2938 | + if (related in spans || !arc.relation) | |
2944 | 2939 | return true; |
2945 | - spans[related] = true; | |
2946 | - }); | |
2940 | + spans[related] = arc; | |
2941 | + | |
2942 | + }); | |
2947 | 2943 | } |
2948 | 2944 | |
2949 | 2945 | var highlightSpan = function(span, highlight, highlightGroup, bgColor, highlightRounding) { |
... | ... | @@ -2976,7 +2972,6 @@ Util.profileStart('before render'); |
2976 | 2972 | // (spanTypes.SPAN_DEFAULT && spanTypes.SPAN_DEFAULT.bgColor) || |
2977 | 2973 | // '#ffffff'); |
2978 | 2974 | var bgColor = "#99FF66"; |
2979 | - var bgColor2 = '#FF66FF'; | |
2980 | 2975 | |
2981 | 2976 | highlight = []; |
2982 | 2977 | highlightSpan(span, highlight, highlightGroup, bgColor, highlightRounding); |
... | ... | @@ -2990,31 +2985,24 @@ Util.profileStart('before render'); |
2990 | 2985 | |
2991 | 2986 | var related = {}; |
2992 | 2987 | recGetRelatedEquivSpans(id, related, data); |
2993 | - | |
2994 | - var spanIds = []; | |
2988 | + | |
2995 | 2989 | $.each(related, function(spanId, dummy) { |
2996 | - spanIds.push('rect[data-span-id="' + spanId + '"]'); | |
2997 | 2990 | var relatedSpan = data.spans[spanId]; |
2998 | 2991 | highlightSpan(relatedSpan, highlight, highlightGroup, bgColor, highlightRounding); |
2999 | 2992 | }); |
3000 | - | |
3001 | - //hack to higlight quasi identity | |
3002 | - var quasi = {}; | |
3003 | - getRelatedQuasiSpans(id, quasi, data); | |
3004 | - var count = 0; | |
3005 | - for(var prop in quasi) { | |
3006 | - if(quasi.hasOwnProperty(prop)) | |
3007 | - ++count; | |
3008 | - } | |
3009 | - if (count > 1) { | |
3010 | - $.each(quasi, function(spanId, dummy) { | |
3011 | - spanIds.push('rect[data-span-id="' + spanId + '"]'); | |
3012 | - var relatedSpan = data.spans[spanId]; | |
3013 | - highlightSpan(relatedSpan, highlight, highlightGroup, bgColor2, highlightRounding); | |
3014 | - }); | |
3015 | - } | |
3016 | - | |
3017 | - highlightSpans = $svg.find(spanIds.join(', ')).parent().addClass('highlight'); | |
2993 | + | |
2994 | + // hack to highlight relations | |
2995 | + var binary = {}; | |
2996 | + getRelatedBinarySpans(id, binary, data); | |
2997 | + $.each(binary, function(spanId, arc) { | |
2998 | + var relatedSpan = data.spans[spanId]; | |
2999 | + var color = relationTypesHash['SPAN_DEFAULT'].color; | |
3000 | + if (relationTypesHash[arc.type] != null) { | |
3001 | + color = relationTypesHash[arc.type].color; | |
3002 | + } | |
3003 | + highlightSpan(relatedSpan, highlight, highlightGroup, color, highlightRounding); | |
3004 | + }); | |
3005 | + dispatcher.post('displayRelationsComments', [id, data, relationTypesHash]); | |
3018 | 3006 | } |
3019 | 3007 | forceRedraw(); |
3020 | 3008 | } else if (!that.arcDragOrigin && (id = target.attr('data-arc-role'))) { |
... | ... |
client/src/visualizer_ui.js
... | ... | @@ -259,12 +259,45 @@ var VisualizerUI = (function($, window, undefined) { |
259 | 259 | element.css({ top: y, left: x }); |
260 | 260 | }; |
261 | 261 | |
262 | + var adjustToSpan = function(spanId, element, offset, top, right) { | |
263 | + var span = $(spanId); | |
264 | + var spanX = span.position().left; | |
265 | + var spanY = span.position().top; | |
266 | + // get the real width, without wrapping | |
267 | + element.css({ left: 0, top: 0 }); | |
268 | + var screenHeight = $(window).height(); | |
269 | + var screenWidth = $(window).width(); | |
270 | + // FIXME why the hell is this 22 necessary?!? | |
271 | + var elementHeight = element.height() + 22; | |
272 | + var elementWidth = element.width() + 22; | |
273 | + var x, y; | |
274 | + offset = offset || 0; | |
275 | + if (top) { | |
276 | + y = spanY - elementHeight - offset; | |
277 | + if (y < 0) top = false; | |
278 | + } | |
279 | + if (!top) { | |
280 | + y = spanY + offset; | |
281 | + } | |
282 | + if (right) { | |
283 | + x = spanX + offset; | |
284 | + if (x >= screenWidth - elementWidth) right = false; | |
285 | + } | |
286 | + if (!right) { | |
287 | + x = x = spanX - elementWidth - offset; | |
288 | + } | |
289 | + if (y < 0) y = 0; | |
290 | + if (x < 0) x = 0; | |
291 | + element.css({ top: y, left: x }); | |
292 | + }; | |
293 | + | |
262 | 294 | var commentPopup = $('#commentpopup'); |
263 | 295 | var commentDisplayed = false; |
264 | 296 | |
265 | 297 | var displayCommentTimer = null; |
266 | 298 | var displayComment = function(evt, target, comment, commentText, commentType, immediately) { |
267 | 299 | var idtype; |
300 | + | |
268 | 301 | if (commentType) { |
269 | 302 | // label comment by type, with special case for default note type |
270 | 303 | var commentLabel; |
... | ... | @@ -468,8 +501,58 @@ var VisualizerUI = (function($, window, undefined) { |
468 | 501 | }); |
469 | 502 | }; |
470 | 503 | |
504 | + var displayRelationCommentTimers = []; | |
505 | + var relationsPopupsContainer = $('#relationspopups'); | |
506 | + var displayRelationsComments = function(sourceId, data, relationTypesHash) { | |
507 | + | |
508 | + for (timer in displayRelationCommentTimers) { | |
509 | + clearTimeout(timer); | |
510 | + } | |
511 | + displayRelationCommentTimers = []; | |
512 | + | |
513 | + var span = data.spans[sourceId]; | |
514 | + $.each(span.outgoing, function(arcNo, arc) { | |
515 | + var related = arc.target; | |
516 | + if (!arc.relation) | |
517 | + return true; | |
518 | + | |
519 | + var relType = "Unknown Relation"; | |
520 | + if (relationTypesHash[arc.type] != null) { | |
521 | + relType = relationTypesHash[arc.type].labels[1]; | |
522 | + } | |
523 | + var targetId = arc.target; | |
524 | + var comment = ('<div class="comment_text">' + | |
525 | + Util.escapeHTML(relType) + | |
526 | + '</div>'); | |
527 | + var targetFullId = 'rect[data-span-id="' + targetId + '"]'; | |
528 | + | |
529 | + // display initial comment HTML | |
530 | + commentLabel = '<b>Facet:</b> '; | |
531 | + comment += commentLabel + Util.escapeHTMLwithNewlines(arc.facet); | |
532 | + | |
533 | + relationsPopupsContainer.append("<div id='relationpopup' class='relTo"+targetId+"'></div>") | |
534 | + var popup = relationsPopupsContainer.children().last(); | |
535 | + | |
536 | + var idtype = 'comment_relation'; | |
537 | + popup[0].className = idtype; | |
538 | + popup.html(comment); | |
539 | + adjustToSpan(targetFullId, popup, 10, true, true); | |
540 | + | |
541 | + /* slight "tooltip" delay to allow highlights to be seen | |
542 | + before the popup obstructs them. */ | |
543 | + var displayRelationCommentTimer = setTimeout(function() { | |
544 | + | |
545 | + popup.stop(true, true).fadeIn(); | |
546 | + | |
547 | + }, 500); | |
548 | + displayRelationCommentTimers.push(displayRelationCommentTimer); | |
549 | + | |
550 | + }); | |
551 | + }; | |
552 | + | |
471 | 553 | var onDocChanged = function() { |
472 | 554 | commentPopup.hide(); |
555 | + relationsPopupsContainer.empty(); | |
473 | 556 | commentDisplayed = false; |
474 | 557 | }; |
475 | 558 | |
... | ... | @@ -518,6 +601,7 @@ var VisualizerUI = (function($, window, undefined) { |
518 | 601 | |
519 | 602 | var hideComment = function() { |
520 | 603 | clearTimeout(displayCommentTimer); |
604 | + relationsPopupsContainer.empty(); | |
521 | 605 | if (commentDisplayed) { |
522 | 606 | commentPopup.stop(true, true).fadeOut(function() { commentDisplayed = false; }); |
523 | 607 | } |
... | ... | @@ -2284,6 +2368,7 @@ var VisualizerUI = (function($, window, undefined) { |
2284 | 2368 | on('annotationIsAvailable', annotationIsAvailable). |
2285 | 2369 | on('messages', displayMessages). |
2286 | 2370 | on('displaySpanComment', displaySpanComment). |
2371 | + on('displayRelationsComments', displayRelationsComments). | |
2287 | 2372 | on('displayArcComment', displayArcComment). |
2288 | 2373 | on('displaySentComment', displaySentComment). |
2289 | 2374 | on('docChanged', onDocChanged). |
... | ... |
diff.xhtml
... | ... | @@ -143,6 +143,7 @@ |
143 | 143 | <div id="messages" class="messages"/> |
144 | 144 | <div id="pulluptrigger"/> |
145 | 145 | <div id="commentpopup"/> |
146 | + <div id="relationspopups"/> | |
146 | 147 | <div id="header" class="ui-widget"> |
147 | 148 | <div id="mainHeader" class="ui-widget-header"> |
148 | 149 | <div id="mainlogo" class="logo unselectable">brat</div> |
... | ... |
index.xhtml
... | ... | @@ -105,6 +105,7 @@ |
105 | 105 | <div id="messages" class="messages"/> |
106 | 106 | <div id="pulluptrigger"/> |
107 | 107 | <div id="commentpopup"/> |
108 | + <div id="relationspopups"/> | |
108 | 109 | <div id="header" class="ui-widget"> |
109 | 110 | <div id="mainHeader" class="ui-widget-header"> |
110 | 111 | <div id="mainlogo" class="logo unselectable">brat</div> |
... | ... |
offline.xhtml
... | ... | @@ -48,6 +48,7 @@ |
48 | 48 | <img id="spinner" src="static/img/spinner.gif"/> |
49 | 49 | <div id="messages"/> |
50 | 50 | <div id="commentpopup"/> |
51 | + <div id="relationspopups"/> | |
51 | 52 | <div id="header" class="ui-widget"> |
52 | 53 | <div id="mainHeader" class="ui-widget-header"> |
53 | 54 | <div id="mainlogo" class="logo unselectable">brat</div> |
... | ... |
server/src/annotation.py
... | ... | @@ -756,18 +756,22 @@ class Annotations(object): |
756 | 756 | except ValueError: |
757 | 757 | raise IdedAnnotationLineSyntaxError(id, self.ann_line, self.ann_line_num+1, input_file_path) |
758 | 758 | |
759 | - if len(args) != 2: | |
760 | - Messager.error('Error parsing relation: must have exactly two arguments') | |
759 | + if len(args) < 2: | |
760 | + Messager.error('Error parsing relation: must be Arg1 Arg2 (optional)Facet') | |
761 | 761 | raise IdedAnnotationLineSyntaxError(id, self.ann_line, self.ann_line_num+1, input_file_path) |
762 | 762 | |
763 | + facet = "none" | |
764 | + if len(args) == 3: | |
765 | + facet = args[2][0] | |
766 | + | |
763 | 767 | if args[0][0] == args[1][0]: |
764 | - Messager.error('Error parsing relation: arguments must not be identical') | |
768 | + Messager.error('Error parsing relation: related entities must not be identical') | |
765 | 769 | raise IdedAnnotationLineSyntaxError(id, self.ann_line, self.ann_line_num+1, input_file_path) |
766 | 770 | |
767 | 771 | return BinaryRelationAnnotation(id, type, |
768 | 772 | args[0][0], args[0][1], |
769 | 773 | args[1][0], args[1][1], |
770 | - data_tail, source_id=input_file_path) | |
774 | + data_tail, facet, source_id=input_file_path) | |
771 | 775 | |
772 | 776 | def _parse_equiv_annotation(self, dummy, data, data_tail, input_file_path): |
773 | 777 | # NOTE: first dummy argument to have a uniform signature with other |
... | ... | @@ -1541,25 +1545,27 @@ class BinaryRelationAnnotation(IdedAnnotation): |
1541 | 1545 | |
1542 | 1546 | Represented in standoff as |
1543 | 1547 | |
1544 | - ID\tTYPE ARG1:ID1 ARG2:ID2 | |
1548 | + ID\tTYPE ARG1:ID1 ARG2:ID2 Facet(optional) | |
1545 | 1549 | |
1546 | 1550 | Where ARG1 and ARG2 are arbitrary (but not identical) labels. |
1547 | 1551 | """ |
1548 | - def __init__(self, id, type, arg1l, arg1, arg2l, arg2, tail, source_id=None): | |
1552 | + def __init__(self, id, type, arg1l, arg1, arg2l, arg2, tail, facet="none", source_id=None): | |
1549 | 1553 | IdedAnnotation.__init__(self, id, type, tail, source_id=source_id) |
1550 | 1554 | self.arg1l = arg1l |
1551 | 1555 | self.arg1 = arg1 |
1552 | 1556 | self.arg2l = arg2l |
1553 | 1557 | self.arg2 = arg2 |
1558 | + self.facet = facet | |
1554 | 1559 | |
1555 | 1560 | def __str__(self): |
1556 | - return u'%s\t%s %s:%s %s:%s%s' % ( | |
1561 | + return u'%s\t%s %s:%s %s:%s %s%s' % ( | |
1557 | 1562 | self.id, |
1558 | 1563 | self.type, |
1559 | 1564 | self.arg1l, |
1560 | 1565 | self.arg1, |
1561 | 1566 | self.arg2l, |
1562 | 1567 | self.arg2, |
1568 | + self.facet, | |
1563 | 1569 | self.tail |
1564 | 1570 | ) |
1565 | 1571 | |
... | ... |
server/src/document.py
... | ... | @@ -684,7 +684,7 @@ def _enrich_json_with_data(j_dic, ann_obj): |
684 | 684 | j_dic['relations'].append( |
685 | 685 | [unicode(rel_ann.id), unicode(rel_ann.type), |
686 | 686 | [(rel_ann.arg1l, rel_ann.arg1), |
687 | - (rel_ann.arg2l, rel_ann.arg2)]] | |
687 | + (rel_ann.arg2l, rel_ann.arg2)], rel_ann.facet] | |
688 | 688 | ) |
689 | 689 | |
690 | 690 | for tb_ann in ann_obj.get_textbounds(): |
... | ... |
style-vis.css
... | ... | @@ -393,6 +393,28 @@ fieldset legend { |
393 | 393 | border-radius: 3px; |
394 | 394 | max-width: 80%; |
395 | 395 | } |
396 | +#relationpopup { | |
397 | + font-family: 'Liberation Sans', Verdana, Arial, Helvetica, sans-serif; | |
398 | + position: fixed; | |
399 | + top: 0; | |
400 | + left: 0; | |
401 | + opacity: 0.95; | |
402 | + padding: 10px; | |
403 | + display: none; | |
404 | + border: 1px outset #000000; | |
405 | + background-color: #f5f5f9; | |
406 | + /* background-color: #d7e7ee; */ | |
407 | + /* background-color: #eeeeee; */ | |
408 | + color: #000000; | |
409 | + z-index: 20; | |
410 | + -moz-box-shadow: 5px 5px 5px #aaaaaa; | |
411 | + -webkit-box-shadow: 5px 5px 5px #aaaaaa; | |
412 | + box-shadow: 5px 5px 5px #aaaaaa; | |
413 | + -moz-border-radius: 3px; | |
414 | + -webkit-border-radius: 3px; | |
415 | + border-radius: 3px; | |
416 | + max-width: 80%; | |
417 | +} | |
396 | 418 | #more_info_readme { |
397 | 419 | height: 350px; |
398 | 420 | } |
... | ... |