mirror of https://github.com/doccano/doccano.git
pythonannotation-tooldatasetsactive-learningtext-annotationdatasetnatural-language-processingdata-labelingmachine-learning
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
278 lines
6.6 KiB
278 lines
6.6 KiB
<template>
|
|
<div v-shortkey="['esc']" @shortkey="cleanUp">
|
|
<v-annotator
|
|
:dark="$vuetify.theme.dark"
|
|
:rtl="rtl"
|
|
:text="text"
|
|
:entities="entities"
|
|
:entity-labels="entityLabels"
|
|
:relations="relations"
|
|
:relation-labels="relationLabels"
|
|
:allow-overlapping="allowOverlapping"
|
|
:grapheme-mode="graphemeMode"
|
|
:selected-entities="selectedEntities"
|
|
@add:entity="handleAddEvent"
|
|
@click:entity="onEntityClicked"
|
|
@click:relation="onRelationClicked"
|
|
@contextmenu:entity="deleteEntity"
|
|
@contextmenu:relation="deleteRelation"
|
|
/>
|
|
<labeling-menu
|
|
:opened="entityMenuOpened"
|
|
:x="x"
|
|
:y="y"
|
|
:selected-label="currentLabel"
|
|
:labels="entityLabels"
|
|
@close="cleanUp"
|
|
@click:label="addOrUpdateEntity"
|
|
/>
|
|
<labeling-menu
|
|
:opened="relationMenuOpened"
|
|
:x="x"
|
|
:y="y"
|
|
:selected-label="currentRelationLabel"
|
|
:labels="relationLabels"
|
|
@close="cleanUp"
|
|
@click:label="addOrUpdateRelation"
|
|
/>
|
|
</div>
|
|
</template>
|
|
|
|
<script lang="ts">
|
|
import Vue, { PropType } from 'vue'
|
|
import VAnnotator from 'v-annotator'
|
|
import LabelingMenu from './LabelingMenu.vue'
|
|
import { SpanDTO } from '~/services/application/tasks/sequenceLabeling/sequenceLabelingData'
|
|
import 'vue-virtual-scroller/dist/vue-virtual-scroller.css'
|
|
|
|
export default Vue.extend({
|
|
components: {
|
|
VAnnotator,
|
|
LabelingMenu,
|
|
},
|
|
|
|
props: {
|
|
dark: {
|
|
type: Boolean,
|
|
default: false,
|
|
},
|
|
rtl: {
|
|
type: Boolean,
|
|
default: false,
|
|
},
|
|
text: {
|
|
type: String,
|
|
default: "",
|
|
required: true,
|
|
},
|
|
entities: {
|
|
type: Array as PropType<SpanDTO[]>,
|
|
default: () => [],
|
|
required: true,
|
|
},
|
|
entityLabels: {
|
|
type: Array,
|
|
default: () => [],
|
|
required: true,
|
|
},
|
|
relations: {
|
|
type: Array,
|
|
default: () => [],
|
|
},
|
|
relationLabels: {
|
|
type: Array,
|
|
default: () => [],
|
|
},
|
|
allowOverlapping: {
|
|
type: Boolean,
|
|
default: false,
|
|
required: false,
|
|
},
|
|
graphemeMode: {
|
|
type: Boolean,
|
|
default: false,
|
|
},
|
|
selectedLabel: {
|
|
type: Object,
|
|
default: null,
|
|
required: false,
|
|
},
|
|
relationMode: {
|
|
type: Boolean,
|
|
default: false,
|
|
},
|
|
},
|
|
|
|
data() {
|
|
return {
|
|
entityMenuOpened: false,
|
|
relationMenuOpened: false,
|
|
x: 0,
|
|
y: 0,
|
|
startOffset: 0,
|
|
endOffset: 0,
|
|
entity: null as any,
|
|
relation: null as any,
|
|
selectedEntities: [] as SpanDTO[],
|
|
};
|
|
},
|
|
|
|
computed: {
|
|
currentLabel(): any {
|
|
if (this.entity) {
|
|
const label = this.entityLabels.find((label: any) => label.id === this.entity!.label)
|
|
return label
|
|
} else {
|
|
return null
|
|
}
|
|
},
|
|
|
|
currentRelationLabel(): any {
|
|
if (this.relation) {
|
|
const label = this.relationLabels.find((label: any) => label.id === this.relation.labelId)
|
|
return label
|
|
} else {
|
|
return null
|
|
}
|
|
}
|
|
},
|
|
|
|
methods: {
|
|
setOffset(startOffset: number, endOffset: number) {
|
|
this.startOffset = startOffset
|
|
this.endOffset = endOffset
|
|
},
|
|
|
|
setEntity(entityId: number) {
|
|
this.entity = this.entities.find((entity: any) => entity.id === entityId)
|
|
},
|
|
|
|
setRelation(relationId: number) {
|
|
this.relation = this.relations.find((relation: any) => relation.id === relationId)
|
|
},
|
|
|
|
setEntityForRelation(e: Event, entityId: number) {
|
|
const entity = this.entities.find((entity) => entity.id === entityId)!
|
|
const index = this.selectedEntities.findIndex((e) => e.id === entity.id)
|
|
if (index === -1) {
|
|
this.selectedEntities.push(entity)
|
|
} else {
|
|
this.selectedEntities.splice(index, 1)
|
|
}
|
|
if (this.selectedEntities.length === 2) {
|
|
if (this.selectedLabel) {
|
|
this.addRelation(this.selectedLabel.id)
|
|
this.cleanUp()
|
|
} else {
|
|
this.showRelationLabelMenu(e)
|
|
}
|
|
}
|
|
},
|
|
|
|
showEntityLabelMenu(e: any) {
|
|
e.preventDefault()
|
|
this.entityMenuOpened = false
|
|
this.x = e.clientX || e.changedTouches[0].clientX
|
|
this.y = e.clientY || e.changedTouches[0].clientY
|
|
this.$nextTick(() => {
|
|
this.entityMenuOpened = true
|
|
})
|
|
},
|
|
|
|
showRelationLabelMenu(e: any) {
|
|
e.preventDefault()
|
|
this.relationMenuOpened = false
|
|
this.x = e.clientX || e.changedTouches[0].clientX
|
|
this.y = e.clientY || e.changedTouches[0].clientY
|
|
this.$nextTick(() => {
|
|
this.relationMenuOpened = true
|
|
})
|
|
},
|
|
|
|
handleAddEvent(e: any, startOffset: number, endOffset: number) {
|
|
this.setOffset(startOffset, endOffset)
|
|
if (this.selectedLabel) {
|
|
this.addOrUpdateEntity(this.selectedLabel.id)
|
|
} else {
|
|
this.showEntityLabelMenu(e)
|
|
}
|
|
},
|
|
|
|
onEntityClicked(e: any, entityId: number) {
|
|
if (this.relationMode) {
|
|
this.setEntityForRelation(e, entityId)
|
|
} else {
|
|
this.setEntity(entityId)
|
|
this.showEntityLabelMenu(e)
|
|
}
|
|
},
|
|
|
|
onRelationClicked(e: any, relation: any) {
|
|
this.setRelation(relation.id)
|
|
this.showRelationLabelMenu(e)
|
|
},
|
|
|
|
addOrUpdateEntity(labelId: number) {
|
|
if (labelId) {
|
|
if (this.entity) {
|
|
this.updateEntity(labelId)
|
|
} else {
|
|
this.addEntity(labelId)
|
|
}
|
|
} else {
|
|
this.deleteEntity(this.entity)
|
|
}
|
|
this.cleanUp()
|
|
},
|
|
|
|
addOrUpdateRelation(labelId: number) {
|
|
if (labelId) {
|
|
if (this.relation) {
|
|
this.updateRelation(labelId)
|
|
} else {
|
|
this.addRelation(labelId)
|
|
}
|
|
} else {
|
|
this.deleteRelation(this.relation)
|
|
}
|
|
this.cleanUp()
|
|
},
|
|
|
|
addEntity(labelId: number) {
|
|
this.$emit('addEntity', this.startOffset, this.endOffset, labelId)
|
|
},
|
|
|
|
updateEntity(labelId: number) {
|
|
this.$emit('click:entity', this.entity!.id, labelId)
|
|
},
|
|
|
|
deleteEntity(entity: any) {
|
|
this.$emit('contextmenu:entity', entity.id)
|
|
this.cleanUp()
|
|
},
|
|
|
|
cleanUp() {
|
|
this.entityMenuOpened = false
|
|
this.relationMenuOpened = false
|
|
this.entity = null
|
|
this.relation = null
|
|
this.startOffset = 0
|
|
this.endOffset = 0
|
|
this.selectedEntities = []
|
|
},
|
|
|
|
addRelation(labelId: number) {
|
|
const [fromEntity, toEntity] = this.selectedEntities
|
|
this.$emit('addRelation', fromEntity.id, toEntity.id, labelId)
|
|
},
|
|
|
|
updateRelation(labelId: number) {
|
|
this.$emit("click:relation", this.relation.id, labelId)
|
|
},
|
|
|
|
deleteRelation(relation: any) {
|
|
this.$emit('contextmenu:relation', relation.id)
|
|
}
|
|
},
|
|
});
|
|
</script>
|