Browse Source

migliorata la grafica delle relazioni

aggiunte le api di lettura/inserimento relazioni
pull/1384/head
mauro 4 years ago
parent
commit
1fedb3758a
7 changed files with 209 additions and 83 deletions
  1. 167
      frontend/components/tasks/sequenceLabeling/EntityItemBox.vue
  2. 46
      frontend/domain/models/links/link.ts
  3. 2
      frontend/domain/models/links/linkRepository.ts
  4. 5
      frontend/i18n/en/projects/links.js
  5. 59
      frontend/pages/projects/_id/sequence-labeling/index.vue
  6. 9
      frontend/repositories/links/apiLinkRepository.ts
  7. 4
      frontend/services/application/tasks/sequenceLabeling/sequenceLabelingApplicationService.ts

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

@ -111,7 +111,8 @@ export default {
},
sourceLinkType: {
type: Object,
default: () => {},
default: () => {
},
required: true
},
selectSource: {
@ -165,6 +166,7 @@ export default {
sortedEntities() {
return this.entities.slice().sort((a, b) => a.startOffset - b.startOffset)
},
chunks() {
let chunks = []
let startOffset = 0
@ -184,13 +186,32 @@ export default {
color: label.backgroundColor,
text: piece,
selectedAsLinkSource: false,
links: [] // must use targetId to get target label (it's not stored)
links: entity.links ? entity.links.map(link => {
return {
id: link.id,
type: link.type,
color: this.getColor(link.type),
targetId: link.annotation_id_2,
targetLabel: null // target label can be computed only after all chunks are made, see line 204
}
}) : null
})
}
// add the rest of text.
chunks = chunks.concat(this.makeChunks(characters.slice(startOffset, characters.length).join('')))
return chunks
chunks = chunks.concat(this.makeChunks(characters.slice(startOffset, characters.length).join('')));
// populate the links. Must be done after chunk creation
chunks.forEach(chunk => {
if (chunk.links) {
chunk.links.forEach(link => {
link.targetLabel = chunks.find(target => target.id === link.targetId).text;
});
}
});
return chunks;
},
labelObject() {
const obj = {}
for (const label of this.labels) {
@ -206,37 +227,130 @@ export default {
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);
this.chunks.forEach(function(fromChunk) {
if (fromChunk.links) {
fromChunk.links.forEach(function(link) {
let childPos = document.getElementById('spn-' + fromChunk.id).getBoundingClientRect();
const x1 = (childPos.x + childPos.width / 2) - parentPos.x;
const y1 = (childPos.y + childPos.height / 2) - parentPos.y;
const topPoints = this.drawnCountPoints(this.chunks.length);
const bottomPoints = this.drawnCountPoints(this.chunks.length);
const chunks = this.chunks;
chunks.forEach(function(sourceChunk, sourceNdx) {
if (sourceChunk.links) {
sourceChunk.links.forEach(function(link) {
let childPos = document.getElementById('spn-' + sourceChunk.id).getBoundingClientRect();
const y1 = childPos.y - parentPos.y;
childPos = document.getElementById('spn-' + link.targetId).getBoundingClientRect();
const x2 = (childPos.x + childPos.width / 2) - parentPos.x;
const y2 = (childPos.y + childPos.height / 2) - parentPos.y;
const y2 = childPos.y - parentPos.y;
const targetNdx = chunks.findIndex(ch => ch.id === link.targetId);
if (y1 < y2) {
bottomPoints[sourceNdx].count++;
topPoints[targetNdx].count++;
} else if (y1 > y2) {
topPoints[sourceNdx].count++;
bottomPoints[targetNdx].count++;
} else {
bottomPoints[sourceNdx].count++;
bottomPoints[targetNdx].count++;
}
});
}
});
chunks.forEach(function(sourceChunk, sourceNdx) {
if (sourceChunk.links) {
sourceChunk.links.forEach(function(link) {
const sourcePos = document.getElementById('spn-' + sourceChunk.id).getBoundingClientRect();
let x1 = sourcePos.x - parentPos.x;
let y1 = sourcePos.y - parentPos.y;
const targetPos = document.getElementById('spn-' + link.targetId).getBoundingClientRect();
let x2 = targetPos.x - parentPos.x;
let y2 = targetPos.y - parentPos.y;
const targetNdx = chunks.findIndex(ch => ch.id === link.targetId);
ctx.beginPath();
ctx.lineWidth = 3;
ctx.moveTo(x1, y1);
ctx.strokeStyle = link.color;
if (y1 === y2) {
ctx.lineTo(x1, y1 + 35);
if (y1 < y2) {
bottomPoints[sourceNdx].drawn++;
topPoints[targetNdx].drawn++;
x1 += bottomPoints[sourceNdx].drawn * sourcePos.width / (bottomPoints[sourceNdx].count + 1);
y1 += sourcePos.height;
x2 += topPoints[targetNdx].drawn * targetPos.width / (topPoints[targetNdx].count + 1);
ctx.moveTo(x1, y1);
ctx.lineTo(x1, y1 + 12);
ctx.lineTo(x2, y2 - 12);
ctx.lineTo(x2, y2);
ctx.stroke();
ctx.lineTo(x2, y1 + 35);
ctx.fillStyle = link.color;
ctx.beginPath();
ctx.moveTo(x2, y2);
ctx.lineTo(x2 - 3, y2 - 5);
ctx.lineTo(x2 + 3, y2 - 5);
ctx.lineTo(x2, y2);
ctx.closePath();
ctx.stroke();
} else if (y1 > y2) {
topPoints[sourceNdx].drawn++;
bottomPoints[targetNdx].drawn++;
x1 += topPoints[sourceNdx].drawn * sourcePos.width / (topPoints[sourceNdx].count + 1);
x2 += bottomPoints[targetNdx].drawn * targetPos.width / (bottomPoints[targetNdx].count + 1);
y2 += targetPos.height;
ctx.moveTo(x1, y1);
ctx.lineTo(x1, y1 - 12);
ctx.lineTo(x2, y2 + 12);
ctx.lineTo(x2, y2);
ctx.stroke();
ctx.fillStyle = link.color;
ctx.beginPath();
ctx.moveTo(x2, y2);
ctx.lineTo(x2 - 3, y2 + 5);
ctx.lineTo(x2 + 3, y2 + 5);
ctx.lineTo(x2, y2);
ctx.closePath();
ctx.stroke();
} else {
bottomPoints[sourceNdx].drawn++;
bottomPoints[targetNdx].drawn++;
x1 += bottomPoints[sourceNdx].drawn * sourcePos.width / (bottomPoints[sourceNdx].count + 1);
y1 += sourcePos.height;
x2 += bottomPoints[targetNdx].drawn * targetPos.width / (bottomPoints[targetNdx].count + 1);
y2 += targetPos.height;
ctx.moveTo(x1, y1);
ctx.lineTo(x1, y1 + 12);
ctx.lineTo(x2, y2 + 12);
ctx.lineTo(x2, y2);
ctx.stroke();
ctx.fillStyle = link.color;
ctx.beginPath();
ctx.moveTo(x2, y2);
ctx.lineTo(x2 - 3, y2 + 5);
ctx.lineTo(x2 + 3, y2 + 5);
ctx.lineTo(x2, y2);
ctx.closePath();
ctx.stroke();
}
});
@ -339,6 +453,27 @@ export default {
this.start = 0
this.end = 0
}
},
getColor(typeId) {
const type = this.linkTypes.find(type => type.id === typeId);
if (type) {
return type.color;
}
return "#787878";
},
drawnCountPoints(size) {
const points = Array(size);
for (let i = 0; i < points.length; i++) {
points[i] = {
drawn: 0,
count: 0
}
}
return points;
}
}
}

46
frontend/domain/models/links/link.ts

@ -1,49 +1,3 @@
// export class LinkItemList {
// constructor(public linkItems: LinkTypeItem[]) {}
//
// static valueOf(items: LinkTypeItem[]): LinkItemList {
// return new LinkItemList(items)
// }
//
// add(item: LinkTypeItem) {
// this.linkItems.push(item)
// }
//
// update(item: LinkTypeItem) {
// const index = this.linkItems.findIndex(label => label.id === item.id)
// this.linkItems.splice(index, 1, item)
// }
//
// delete(item: LinkTypeItem) {
// this.linkItems = this.linkItems.filter(label => label.id !== item.id)
// }
//
// bulkDelete(items: LinkItemList) {
// const ids = items.ids()
// this.linkItems = this.linkItems.filter(label => !ids.includes(label.id))
// }
//
// count(): Number {
// return this.linkItems.length
// }
//
// ids(): Number[]{
// return this.linkItems.map(item => item.id)
// }
//
// get nameList(): string[] {
// return this.linkItems.map(item => item.name)
// }
//
// get usedKeys(): string[] {
// return []
// }
//
// toArray(): Object[] {
// return this.linkItems.map(item => item.toObject())
// }
// }
export class LinkTypeItem {
constructor(
public id: number,

2
frontend/domain/models/links/linkRepository.ts

@ -1,6 +1,8 @@
import {LinkItem} from '~/domain/models/links/link'
export interface LinkRepository {
list(projectId: string): Promise<LinkItem[]>
create(projectId: string, link: LinkItem): Promise<LinkItem>
update(projectId: string, linkId: number, linkType: number): Promise<LinkItem>

5
frontend/i18n/en/projects/links.js

@ -0,0 +1,5 @@
export default {
createLink: 'Create Link',
linkName: 'Link name',
linkMessage: 'Link name is required'
}

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

@ -92,6 +92,7 @@ export default {
annotations: [],
docs: [],
labels: [],
links: [],
linkTypes: [],
project: {},
enableAutoLabeling: false,
@ -139,7 +140,23 @@ export default {
methods: {
async list(docId) {
this.hideAllLinkMenus();
this.annotations = await this.$services.sequenceLabeling.list(this.projectId, docId)
const annotations = await this.$services.sequenceLabeling.list(this.projectId, docId);
const links = await this.$services.sequenceLabeling.listLinks(this.projectId);
annotations.forEach(function(annotation) {
annotation.links = links.filter(link => link.annotation_id_1 === annotation.id);
});
this.annotations = annotations;
this.links = links;
},
populateLinks() {
const links = this.links;
this.annotations.forEach(function(annotation) {
annotation.links = links.filter(link => link.annotation_id_1 === annotation.id);
});
},
async remove(id) {
@ -183,19 +200,19 @@ export default {
this.sourceChunk = chunk;
},
selectTarget(chunk) {
async selectTarget(chunk) {
// skips duplicated links
if (!chunk.links.find(ch => ch.id === this.sourceChunk.id)) {
// await this.$services.sequenceLabeling.createLink(this.projectId, this.sourceChunk.id, chunk.id, this.sourceLinkType.id, this.getUserId)
// await this.list(this.doc.id)
await this.$services.sequenceLabeling.createLink(this.projectId, this.sourceChunk.id, chunk.id, this.sourceLinkType.id, this.getUserId)
await this.list(this.doc.id)
this.sourceChunk.links.push({
id: -1,
type: this.sourceLinkType.id,
color: this.sourceLinkType.color,
targetId: chunk.id,
targetLabel: chunk.text
});
// this.sourceChunk.links.push({
// id: -1,
// type: this.sourceLinkType.id,
// color: this.sourceLinkType.color,
// targetId: chunk.id,
// targetLabel: chunk.text
// });
}
this.hideAllLinkMenus();
},
@ -204,25 +221,25 @@ export default {
this.sourceLink = link;
},
async deleteLink(id, ndx) {
await this.$services.sequenceLabeling.deleteLink(this.projectId, this.sourceChunk.links[ndx].id)
await this.list(this.doc.id)
deleteLink(id, ndx) {
// await this.$services.sequenceLabeling.deleteLink(this.projectId, this.sourceChunk.links[ndx].id)
// await this.list(this.doc.id)
// this.sourceChunk.links.splice(ndx, 1);
// this.sourceLink = NONE;
this.sourceChunk.links.splice(ndx, 1);
this.sourceLink = NONE;
this.hideAllLinkMenus();
},
async selectNewLinkType(type) {
await this.$services.sequenceLabeling.updateLink(this.projectId, 0, this.sourceLinkType.id)
await this.list(this.doc.id)
// this.sourceLinkType = type;
selectNewLinkType(type) {
this.sourceLinkType = type;
},
changeLinkType(type) {
if (this.sourceLink) {
// await this.$services.sequenceLabeling.updateLink(this.projectId, 0, this.sourceLinkType.id)
// await this.list(this.doc.id)
this.sourceLink.type = type.id;
this.sourceLink.color = type.color;
}

9
frontend/repositories/links/apiLinkRepository.ts

@ -1,6 +1,8 @@
import ApiService from '@/services/api.service'
import {LinkRepository} from "~/domain/models/links/linkRepository";
import {LinkItem} from "~/domain/models/links/link";
import {LabelItem} from "~/domain/models/label/label";
import {LabelItemResponse} from "~/repositories/label/apiLabelRepository";
export interface LinkResponse {
id: number
@ -17,6 +19,13 @@ export class ApiLinkRepository implements LinkRepository {
) {
}
async list(projectId: string): Promise<LinkItem[]> {
const url = `/projects/${projectId}/annotation_relations`
const response = await this.request.get(url)
const responseLinks: LinkResponse[] = response.data
return responseLinks.map(link => LinkItem.valueOf(link))
}
async create(projectId: string, item: LinkItem): Promise<LinkItem> {
const url = `/projects/${projectId}/annotation_relations`
const response = await this.request.post(url, item.toObject())

4
frontend/services/application/tasks/sequenceLabeling/sequenceLabelingApplicationService.ts

@ -27,6 +27,10 @@ export class SequenceLabelingApplicationService extends AnnotationApplicationSer
await this.repository.update(projectId, docId, annotationId, labelId)
}
public async listLinks(projectId: string): Promise<LinkItem[]> {
return await this.linkRepository.list(projectId);
}
public async createLink(projectId: string, sourceId: number, targetId: number, linkType: number, userId: number): Promise<void> {
const link = new LinkItem(0, sourceId, targetId, linkType, userId, (new Date()).toISOString());
await this.linkRepository.create(projectId, link);

Loading…
Cancel
Save