Browse Source

esperimenti sugli eventi delle annotazioni

pull/1384/head
mauro 3 years ago
parent
commit
a0f0406454
3 changed files with 234 additions and 69 deletions
  1. 95
      frontend/components/tasks/sequenceLabeling/EntityItem.vue
  2. 195
      frontend/components/tasks/sequenceLabeling/EntityItemBox.vue
  3. 13
      frontend/pages/projects/_id/sequence-labeling/index.vue

95
frontend/components/tasks/sequenceLabeling/EntityItem.vue

@ -1,32 +1,36 @@
<template>
<v-menu
v-if="label"
v-model="showMenu"
offset-y
v-if="label"
v-model="showMenu"
offset-y
>
<template v-slot:activator="{ on }">
<span :style="{ borderColor: color }" class="highlight bottom" v-on="on">
<span class="highlight__content">{{ content }}<v-icon class="delete" @click.stop="remove">mdi-close-circle</v-icon></span><span :data-label="label" :style="{ backgroundColor: color, color: textColor }" class="highlight__label" />
<span :id="'spn-' + spanid" :style="{ borderColor: color }" class="highlight bottom" v-on="on">
<span class="highlight__content">{{ content }}<v-icon class="delete" @click.stop="remove">mdi-close-circle</v-icon><span
:class="{ active: spanid === selectedChunkId }" class="iconify target-selector" data-icon="mdi-link-variant"
@click.stop="onLinkClick"></span></span><span :data-label="label" :style="{ backgroundColor: color, color: textColor }"
class="highlight__label"/>
</span>
</template>
<v-list
dense
min-width="150"
max-height="400"
class="overflow-y-auto"
dense
min-width="150"
max-height="400"
class="overflow-y-auto"
>
<v-list-item
v-for="(item, i) in labels"
:key="i"
v-shortkey.once="[item.suffixKey]"
@shortkey="update(item)"
@click="update(item)"
v-for="(item, i) in labels"
:key="i"
v-shortkey.once="[item.suffixKey]"
@shortkey="update(item)"
@click="update(item)"
>
<v-list-item-content>
<v-list-item-title v-text="item.text" />
<v-list-item-title v-text="item.text"/>
</v-list-item-content>
<v-list-item-action>
<v-list-item-action-text v-text="item.suffixKey" />
<v-list-item-action-text v-text="item.suffixKey"/>
</v-list-item-action>
</v-list-item>
</v-list>
@ -35,10 +39,15 @@
</template>
<script>
import { idealColor } from '~/plugins/utils.js'
import {idealColor} from '~/plugins/utils.js'
export default {
props: {
spanid: {
type: Number,
default: 0,
required: true
},
content: {
type: String,
default: '',
@ -59,6 +68,10 @@ export default {
},
newline: {
type: Boolean
},
selectedChunkId: {
type: Number,
default: -1
}
},
data() {
@ -78,6 +91,10 @@ export default {
},
remove() {
this.$emit('remove')
},
onLinkClick() {
this.$emit('selectLinkSource');
this.showMenu = false;
}
}
}
@ -87,46 +104,76 @@ export default {
.highlight.blue {
background: #edf4fa !important;
}
.highlight.bottom {
display: block;
white-space: normal;
}
.highlight:first-child {
margin-left: 0;
}
.highlight {
border: 2px solid;
margin: 4px 6px 4px 3px;
vertical-align: middle;
box-shadow: 2px 4px 20px rgba(0,0,0,.1);
box-shadow: 2px 4px 20px rgba(0, 0, 0, .1);
position: relative;
cursor: default;
min-width: 26px;
line-height: 22px;
display: flex;
}
.highlight .delete {
top:-15px;
left:-13px;
position:absolute;
top: -15px;
left: -13px;
position: absolute;
display: none;
}
.highlight:hover .delete {
display: block;
}
.highlight .target-selector:before {
content: 'L';
}
.highlight .target-selector {
position: absolute;
top: -12px;
right: -11px;
width: 20px;
background: rgba(0, 0, 0, 0.54);
color: #ffffff;
border-radius: 30px;
cursor: pointer;
text-align: center;
}
.highlight .target-selector.active {
display: block;
background: #008ad6;
color: #ffffff;
}
.highlight__content {
display: flex;
flex-wrap: wrap;
align-items: center;
padding: 2px 2px 0px 6px;
padding: 2px 2px 0 6px;
background: #ffffff;
}
.highlight.bottom .highlight__content:after {
content: " ";
padding-right: 3px;
}
.highlight__label {
line-height: 14px;
padding-top: 1px;
align-items: center;
justify-content: center;
display: flex;
@ -138,6 +185,7 @@ export default {
user-select: none;
color: white;
}
.highlight__label::after {
content: attr(data-label);
display: block;
@ -145,6 +193,7 @@ export default {
-webkit-font-smoothing: subpixel-antialiased;
letter-spacing: .1em;
}
.newline {
width: 100%;
}

195
frontend/components/tasks/sequenceLabeling/EntityItemBox.vue

@ -1,50 +1,59 @@
<template>
<div class="highlight-container highlight-container--bottom-labels" @click="open" @touchend="open">
<entity-item
v-for="(chunk, i) in chunks"
:key="i"
:content="chunk.text"
:newline="chunk.newline"
:label="chunk.label"
:color="chunk.color"
:labels="labels"
@remove="deleteAnnotation(chunk.id)"
@update="updateEntity($event.id, chunk.id)"
/>
<v-menu
v-model="showMenu"
:position-x="x"
:position-y="y"
absolute
offset-y
>
<v-list
dense
min-width="150"
max-height="400"
class="overflow-y-auto"
>
<v-list-item
v-for="(label, i) in labels"
:key="i"
v-shortkey="[label.suffixKey]"
@shortkey="assignLabel(label.id)"
@click="assignLabel(label.id)"
>
<v-list-item-content>
<v-list-item-title v-text="label.text" />
</v-list-item-content>
<v-list-item-action>
<v-list-item-action-text v-text="label.suffixKey" />
</v-list-item-action>
</v-list-item>
</v-list>
</v-menu>
</div>
<div id="connections-wrapper">
<div class="highlight-container highlight-container--bottom-labels" @click="open" @touchend="open">
<entity-item
v-for="(chunk, i) in chunks"
:key="i"
:spanid="chunk.id"
:content="chunk.text"
:newline="chunk.newline"
:label="chunk.label"
:color="chunk.color"
:labels="labels"
:selected-chunk-id="selectedChunkId"
@remove="deleteAnnotation(chunk.id)"
@update="updateEntity($event.id, chunk.id)"
@selectLinkSource="selectLinkSource(chunk)"
/>
<v-menu
v-model="showMenu"
:position-x="x"
:position-y="y"
absolute
offset-y
>
<v-list
dense
min-width="150"
max-height="400"
class="overflow-y-auto"
>
<v-list-item
v-for="(label, i) in labels"
:key="i"
v-shortkey="[label.suffixKey]"
@shortkey="assignLabel(label.id)"
@click="assignLabel(label.id)"
>
<v-list-item-content>
<v-list-item-title v-text="label.text"/>
</v-list-item-content>
<v-list-item-action>
<v-list-item-action-text v-text="label.suffixKey"/>
</v-list-item-action>
</v-list-item>
</v-list>
</v-menu>
</div>
<canvas id="connections">
</canvas>
</div>
</template>
<script>
import EntityItem from './EntityItem'
export default {
components: {
EntityItem
@ -79,6 +88,16 @@ export default {
type: Function,
default: () => ([]),
required: true
},
selectedChunkId: {
type: Number,
default: -1,
required: true
},
selectLinkSource: {
type: Function,
default: () => ([]),
required: true
}
},
data() {
@ -111,7 +130,8 @@ export default {
id: entity.id,
label: label.text,
color: label.backgroundColor,
text: piece
text: piece,
selectedAsLinkSource: false
})
}
// add the rest of text.
@ -126,6 +146,71 @@ export default {
return obj
}
},
updated() {
this.$nextTick(() => {
// SIMULATION ONLY
// svuota il canvas adeguandolo alla dimensione reale del <div> col testo
const parentPos = document.getElementById('connections-wrapper').getBoundingClientRect();
const canvas = document.getElementById('connections');
canvas.width = parentPos.width;
canvas.height = parentPos.height;
const ctx = canvas.getContext('2d');
ctx.clearRect(0, 0, parentPos.width, parentPos.height);
// simulo una relazione tra le prime due annotazioni
let one, two;
this.chunks.forEach(function(chunk) {
if (chunk.id) {
if (!one) {
one = chunk;
} else if (!two) {
two = chunk;
}
}
});
// disegno la pseudo relazione nel canvas
if (one && two) {
let childPos = document.getElementById('spn-' + one.id).getBoundingClientRect();
const x1 = (childPos.x + childPos.width / 2) - parentPos.x;
const y1 = (childPos.y + childPos.height / 2) - parentPos.y;
childPos = document.getElementById('spn-' + two.id).getBoundingClientRect();
const x2 = (childPos.x + childPos.width / 2) - parentPos.x;
const y2 = (childPos.y + childPos.height / 2) - parentPos.y;
ctx.lineWidth = 3;
ctx.moveTo(x1, y1);
if (y1 === y2) {
ctx.strokeStyle = one.color;
ctx.lineTo(x1, y1 + 25);
ctx.stroke();
const gradient = ctx.createLinearGradient(x1, y1 + 25, x2, y1 + 25);
gradient.addColorStop(0, one.color);
gradient.addColorStop(1, two.color);
ctx.strokeStyle = gradient;
ctx.lineTo(x2, y1 + 25);
ctx.stroke();
ctx.lineTo(x2, y2);
ctx.stroke();
} else {
const gradient = ctx.createLinearGradient(x1, y1, x2, y2);
gradient.addColorStop(0, one.color);
gradient.addColorStop(1, two.color);
ctx.strokeStyle = gradient;
ctx.lineTo(x2, y2);
ctx.stroke();
}
}
});
},
methods: {
makeChunks(text) {
const chunks = []
@ -152,6 +237,7 @@ export default {
})
return chunks
},
show(e) {
e.preventDefault()
this.showMenu = false
@ -161,6 +247,7 @@ export default {
this.showMenu = true
})
},
setSpanInfo() {
let selection
// Modern browsers.
@ -180,6 +267,7 @@ export default {
this.start = [...preSelectionRange.toString()].length
this.end = this.start + [...range.toString()].length
},
validateSpan() {
if ((typeof this.start === 'undefined') || (typeof this.end === 'undefined')) {
return false
@ -200,12 +288,14 @@ export default {
}
return true
},
open(e) {
this.setSpanInfo()
if (this.validateSpan()) {
this.show(e)
}
},
assignLabel(labelId) {
if (this.validateSpan()) {
this.addEntity(this.start, this.end, labelId)
@ -222,14 +312,31 @@ export default {
.highlight-container.highlight-container--bottom-labels {
align-items: flex-start;
}
.highlight-container {
line-height: 42px!important;
line-height: 50px !important;
display: flex;
flex-wrap: wrap;
white-space: pre-wrap;
cursor: default;
position: relative;
z-index: 1;
}
.highlight-container.highlight-container--bottom-labels .highlight.bottom {
margin-top: 6px;
}
#connections-wrapper {
position: relative;
}
#connections-wrapper canvas {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 0;
}
</style>

13
frontend/pages/projects/_id/sequence-labeling/index.vue

@ -17,7 +17,7 @@
class="d-flex d-sm-none"
/>
</template>
<template v-slot:content>
<template v-slot:content >
<v-card>
<v-card-text class="title">
<entity-item-box
@ -27,6 +27,8 @@
:delete-annotation="remove"
:update-entity="update"
:add-entity="add"
:selected-chunk-id="selectedChunkId"
:select-link-source="selectLinkSource"
/>
</v-card-text>
</v-card>
@ -77,7 +79,8 @@ export default {
docs: [],
labels: [],
project: {},
enableAutoLabeling: false
enableAutoLabeling: false,
selectedChunkId: -1
}
},
@ -148,6 +151,12 @@ export default {
const approved = !this.doc.isApproved
await this.$services.document.approve(this.projectId, this.doc.id, approved)
await this.$fetch()
},
selectLinkSource(chunk) {
console.log(chunk.id);
console.log(this.selectedChunkId);
this.selectedChunkId = (this.selectedChunkId === chunk.id) ? -1 : chunk.id;
}
},

Loading…
Cancel
Save