mirror of https://github.com/doccano/doccano.git
pythondatasetsactive-learningtext-annotationdatasetnatural-language-processingdata-labelingmachine-learningannotation-tool
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.
228 lines
6.2 KiB
228 lines
6.2 KiB
<template lang="pug">
|
|
div(@click="setSelectedRange")
|
|
span.text-sequence(
|
|
v-for="r in chunksWithLabel"
|
|
v-bind:class="getChunkClass(r)"
|
|
v-bind:data-tooltip="id2label[r.label].text"
|
|
v-bind:style="{ \
|
|
color: id2label[r.label].text_color, \
|
|
backgroundColor: id2label[r.label].background_color \
|
|
}"
|
|
)
|
|
span
|
|
span(
|
|
v-for="highlightedChunk in getSearchHighlightedChunks(textPart(r))"
|
|
v-bind:class="highlightedChunk.highlight && 'has-background-warning'"
|
|
) {{ highlightedChunk.content }}
|
|
button.delete.is-small(v-if="id2label[r.label].text_color", v-on:click="removeLabel(r)")
|
|
</template>
|
|
|
|
<script>
|
|
export default {
|
|
props: {
|
|
labels: {
|
|
type: Array, // [{id: Integer, color: String, text: String}]
|
|
default: () => [],
|
|
},
|
|
searchQuery: {
|
|
type: String,
|
|
default: '',
|
|
},
|
|
text: {
|
|
type: String,
|
|
default: '',
|
|
},
|
|
entityPositions: {
|
|
type: Array, // [{'startOffset': 10, 'endOffset': 15, 'label_id': 1}]
|
|
default: () => [],
|
|
},
|
|
},
|
|
|
|
data: () => ({
|
|
startOffset: 0,
|
|
endOffset: 0,
|
|
}),
|
|
|
|
computed: {
|
|
sortedEntityPositions() {
|
|
/* eslint-disable vue/no-side-effects-in-computed-properties */
|
|
this.entityPositions = this.entityPositions.sort((a, b) => a.start_offset - b.start_offset);
|
|
return this.entityPositions;
|
|
/* eslint-enable vue/no-side-effects-in-computed-properties */
|
|
},
|
|
|
|
chunks() {
|
|
const res = [];
|
|
let left = 0;
|
|
for (let i = 0; i < this.sortedEntityPositions.length; i++) {
|
|
const e = this.sortedEntityPositions[i];
|
|
const l = this.makeLabel(left, e.start_offset);
|
|
res.push(l);
|
|
res.push(e);
|
|
left = e.end_offset;
|
|
}
|
|
const l = this.makeLabel(left, this.text.length);
|
|
res.push(l);
|
|
|
|
return res;
|
|
},
|
|
|
|
chunksWithLabel() {
|
|
return this.chunks.filter(r => this.id2label[r.label]);
|
|
},
|
|
|
|
id2label() {
|
|
const id2label = {};
|
|
// default value;
|
|
id2label[-1] = {
|
|
text_color: '',
|
|
background_color: '',
|
|
};
|
|
for (let i = 0; i < this.labels.length; i++) {
|
|
const label = this.labels[i];
|
|
id2label[label.id] = label;
|
|
}
|
|
return id2label;
|
|
},
|
|
},
|
|
|
|
watch: {
|
|
entityPositions() {
|
|
this.resetRange();
|
|
},
|
|
},
|
|
|
|
methods: {
|
|
getSearchHighlightedChunks(text) {
|
|
if (this.searchQuery) {
|
|
const chunks = [];
|
|
let currentText = text;
|
|
let nextIndex;
|
|
|
|
do {
|
|
nextIndex = currentText.toLowerCase().indexOf(this.searchQuery.toLowerCase());
|
|
|
|
if (nextIndex !== -1) {
|
|
chunks.push({
|
|
content: currentText.substring(0, nextIndex),
|
|
highlight: false,
|
|
});
|
|
chunks.push({
|
|
content: currentText.substring(nextIndex, nextIndex + this.searchQuery.length),
|
|
highlight: true,
|
|
});
|
|
nextIndex += this.searchQuery.length;
|
|
currentText = currentText.substring(nextIndex);
|
|
} else {
|
|
chunks.push({
|
|
content: currentText.substring(nextIndex),
|
|
highlight: false,
|
|
});
|
|
}
|
|
} while (nextIndex !== -1);
|
|
|
|
return chunks.filter(({ content }) => content);
|
|
}
|
|
|
|
return [{ content: text, highlight: false }];
|
|
},
|
|
|
|
getChunkClass(chunk) {
|
|
if (!chunk.id) {
|
|
return {};
|
|
}
|
|
|
|
const label = this.id2label[chunk.label];
|
|
return [
|
|
'tooltip is-tooltip-bottom',
|
|
{ tag: label.text_color },
|
|
];
|
|
},
|
|
|
|
setSelectedRange() {
|
|
let start;
|
|
let end;
|
|
if (window.getSelection) {
|
|
const range = window.getSelection().getRangeAt(0);
|
|
const preSelectionRange = range.cloneRange();
|
|
preSelectionRange.selectNodeContents(this.$el);
|
|
preSelectionRange.setEnd(range.startContainer, range.startOffset);
|
|
start = [...preSelectionRange.toString()].length;
|
|
end = start + [...range.toString()].length;
|
|
} else if (document.selection && document.selection.type !== 'Control') {
|
|
const selectedTextRange = document.selection.createRange();
|
|
const preSelectionTextRange = document.body.createTextRange();
|
|
preSelectionTextRange.moveToElementText(this.$el);
|
|
preSelectionTextRange.setEndPoint('EndToStart', selectedTextRange);
|
|
start = [...preSelectionTextRange.text].length;
|
|
end = start + [...selectedTextRange.text].length;
|
|
}
|
|
this.startOffset = start;
|
|
this.endOffset = end;
|
|
console.log(start, end); // eslint-disable-line no-console
|
|
},
|
|
|
|
validRange() {
|
|
if (this.startOffset === this.endOffset) {
|
|
return false;
|
|
}
|
|
if (this.startOffset > this.text.length || this.endOffset > this.text.length) {
|
|
return false;
|
|
}
|
|
if (this.startOffset < 0 || this.endOffset < 0) {
|
|
return false;
|
|
}
|
|
for (let i = 0; i < this.entityPositions.length; i++) {
|
|
const e = this.entityPositions[i];
|
|
if ((e.start_offset <= this.startOffset) && (this.startOffset < e.end_offset)) {
|
|
return false;
|
|
}
|
|
if ((e.start_offset < this.endOffset) && (this.endOffset < e.end_offset)) {
|
|
return false;
|
|
}
|
|
if ((this.startOffset < e.start_offset) && (e.start_offset < this.endOffset)) {
|
|
return false;
|
|
}
|
|
if ((this.startOffset < e.end_offset) && (e.end_offset < this.endOffset)) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
},
|
|
|
|
resetRange() {
|
|
this.startOffset = 0;
|
|
this.endOffset = 0;
|
|
},
|
|
|
|
textPart(r) {
|
|
return [...this.text].slice(r.start_offset, r.end_offset).join('');
|
|
},
|
|
|
|
addLabel(labelId) {
|
|
if (this.validRange()) {
|
|
const label = {
|
|
start_offset: this.startOffset,
|
|
end_offset: this.endOffset,
|
|
label: labelId,
|
|
};
|
|
this.$emit('add-label', label);
|
|
}
|
|
},
|
|
|
|
removeLabel(index) {
|
|
this.$emit('remove-label', index);
|
|
},
|
|
|
|
makeLabel(startOffset, endOffset) {
|
|
const label = {
|
|
id: 0,
|
|
label: -1,
|
|
start_offset: startOffset,
|
|
end_offset: endOffset,
|
|
};
|
|
return label;
|
|
},
|
|
},
|
|
};
|
|
</script>
|