@kgdata/annotation
Version:
annotation
381 lines (322 loc) • 10.9 kB
Markdown
---
mobile: false
nav:
title: annotation
path: /annotation
---
## kgAnnotation
普通实践:
```tsx
import React, { useState, useEffect, useRef } from 'react';
import Annotation from '@kgdata/annotation';
import 'antd/dist/antd.css';
import { Modal, Radio, Form } from 'antd';
import mockData from './data.json';
const cyData = Object.assign({}, mockData);
const App = () => {
const [form] = Form.useForm();
const [isModalVisible, setIsModalVisible] = useState(false);
const [annotationData, setAnnotationData] = useState(cyData);
const [action, setAction] = useState(null);
const [textSelectedIndex, setTextSelectedIndex] = useState([]);
const [textSelected, seTextSelected] = useState('');
const [actionType, setActionType] = useState<
| 'LABELCREATE'
| 'LABELUPDATE'
| 'LABELDELETE'
| 'CONNECTIONCREATE'
| 'CONNECTIONUPDATE'
| 'CONNECTIONDELETE'
>('LABELCREATE');
const selectLabelRef = useRef();
const annotatorStoreRef = useRef();
const connectionRef = useRef();
const selectConnectionRef = useRef();
useEffect(() => {
if (textSelected) {
setIsModalVisible(true);
}
}, [textSelected]);
const onTextSelected = (startIndex, endIndex) => {
const { content } = annotationData;
setTextSelectedIndex([startIndex, endIndex]);
seTextSelected(content.slice(startIndex, endIndex));
setActionType('LABELCREATE');
};
const onLabelDoubleClicked = (id, e) => {
const { labels, content } = annotatorStoreRef.current;
const { startIndex, endIndex, categoryId } = labels.filter(
(v) => v.id === id,
)[0];
seTextSelected(content.slice(startIndex, endIndex));
setIsModalVisible(true);
form.setFieldsValue({ type: categoryId });
setActionType('LABELUPDATE');
selectLabelRef.current = id;
};
const onLabelRightClicked = (id, e) => {
setActionType('LABELDELETE');
// selectLabelRef.current = id;
setAction({ type: 'LABELDELETE', data: { labelId: id } });
};
const onTwoLabelsClicked = (first, second) => {
setActionType('CONNECTIONCREATE');
setIsModalVisible(true);
connectionRef.current = [first, second];
};
const onConnectionDoubleClicked = (id, e) => {
const { connections } = annotatorStoreRef.current;
const { categoryId } = connections.filter((v) => v.id === id)[0];
setIsModalVisible(true);
form.setFieldsValue({ type: categoryId });
setActionType('CONNECTIONUPDATE');
selectConnectionRef.current = id;
};
const onConnectionRightClicked = (id, e) => {
setActionType('CONNECTIONDELETE');
setAction({ type: 'CONNECTIONDELETE', data: { connectionId: id } });
};
const handleOk = () => {
const {
content,
labelCategories,
labels,
connectionCategories,
connections,
} = annotationData;
form.validateFields().then((res) => {
const { type } = res;
switch (actionType) {
case 'LABELCREATE':
setAction({
type: 'LABELCREATE',
data: {
categoryId: type,
startIndex: textSelectedIndex[0],
endIndex: textSelectedIndex[1],
},
});
break;
case 'LABELUPDATE':
setAction({
type: 'LABELUPDATE',
data: { labelId: selectLabelRef.current, categoryId: type },
});
break;
case 'CONNECTIONCREATE':
setAction({
type: 'CONNECTIONCREATE',
data: {
categoryId: type,
fromId: connectionRef.current[0],
toId: connectionRef.current[1],
},
});
break;
case 'CONNECTIONUPDATE':
setAction({
type: 'CONNECTIONUPDATE',
data: {
connectionId: selectConnectionRef.current,
categoryId: type,
},
});
break;
default:
break;
}
setIsModalVisible(false);
});
};
const onAnnotatorUpdate = (store) => {
console.log('store..........', store);
annotatorStoreRef.current = store;
};
return (
<div>
<Annotation
annotationData={annotationData}
action={action}
onTextSelected={onTextSelected}
onLabelDoubleClicked={onLabelDoubleClicked}
onLabelRightClicked={onLabelRightClicked}
onTwoLabelsClicked={onTwoLabelsClicked}
onConnectionDoubleClicked={onConnectionDoubleClicked}
onConnectionRightClicked={onConnectionRightClicked}
onAnnotatorUpdate={onAnnotatorUpdate}
/>
<Modal
title="新建标注"
visible={isModalVisible}
onOk={handleOk}
onCancel={() => setIsModalVisible(false)}
>
{actionType.indexOf('LABEL') !== -1 ? (
<Form form={form}>
<p>划词内容:{textSelected}</p>
<p>语义类型:</p>
<Form.Item
name="type"
initialValue={annotationData.labelCategories[0].id}
>
<Radio.Group>
{annotationData.labelCategories.map((item) => {
return (
<Radio key={item.id} value={item.id}>
{item.text}
</Radio>
);
})}
</Radio.Group>
</Form.Item>
</Form>
) : (
<Form form={form}>
<Form.Item
name="type"
initialValue={annotationData.connectionCategories[0].id}
>
<Radio.Group>
{annotationData.connectionCategories.map((item) => {
return (
<Radio key={item.id} value={item.id}>
{item.text}
</Radio>
);
})}
</Radio.Group>
</Form.Item>
</Form>
)}
</Modal>
</div>
);
};
export default App;
```
# Reference API
## CSS
目前支持设置:
- 整个元素的宽度(这是指定你要渲染出的`svg`元素的宽度的唯一方法),默认为容器宽度
```css
#annotation-container > svg {
width: 500px;
}
```
## annotationData
在 annotationData 为 JSON 时,格式如下:
```json
{
"content": "文本内容",
"labelCategories": [
{
"id": Label类型Id,
"text": Label文字,
"color": Label颜色,
"borderColor": Label边框颜色
},
{
"id": Label类型Id,
"text": Label文字,
"color": Label颜色,
"borderColor": Label边框颜色
},
...
],
"labels": [
{
"id": LabelId,
"categoryId": Label类型,
"startIndex": Label开始位置(包含),
"endIndex": Label结束位置(不包含)
},
{
"id": LabelId,
"categoryId": Label类型,
"startIndex": Label开始位置(包含),
"endIndex": Label结束位置(不包含)
},
...
],
"connectionCategories": [
{
"id": Connection类型Id,
"text": Connection文字
},
...
],
"connections": [
{
"id": ConnectionId,
"categoryId": Connection类型,
"fromId": Connection开始的Label的id,
"toId": Connection结束的Label的id
}
]
}
```
## action
用户触发事件后,通过 action 更新视图
| 参数 | 意义 |
| ---------- | ------------------ |
| type | action type |
| data | 当前 action 所需参数,通过 events 获取 |
| Action Type | 说明 | 参数 |
| ------------------ | --------------------------- | ---------------------------------- |
| `LABELCREATE` | 创建 Label | (categoryId, startIndex, endIndex) |
| `LABELUPDATE` | 修改 Label 的 category | (labelId, categoryId) |
| `LABELDELETE` | 删除 Label | (labelId) |
| `CONNECTIONCREATE` | 创建 Connection | (categoryId, fromId, toId) |
| `CONNECTIONUPDATE` | 修改 Connection 的 category | (connectionId, categoryId) |
| `CONNECTIONDELETE` | 删除 Connection | (connectionId) |
## Events
#### onTextSelected
在用户在页面上选取了一段文本后,会触发`onTextSelected`事件。
这个 event 会带两个参数,我们将其分别称为`startIndex`和`endIndex`:
| 参数 | 意义 |
| ---------- | ------------------ |
| startIndex | 选取部分的开始坐标 |
| endIndex | 选取部分的结束坐标 |
它们代表用户选取了在原文本中的`[startIndex, endIndex)`部分。
#### onLabelRightClicked
在用户右键点击了一个 Label 后会触发这个事件。
这个 event 会带两个参数,为被点击的标注的 ID 和被点击事件本身:
| 参数 | 意义 |
| ----- | ----------------- |
| id | 被点击的标注的 id |
| event | 点击事件 |
#### onLabelDoubleClicked
在用户双击了一个 Label 后会触发这个事件。
| 参数 | 意义 |
| ----- | ----------------- |
| id | 被点击的标注的 id |
| event | 点击事件 |
#### onTwoLabelsClicked
在用户先后左键点击了两个 Label 后会触发这个事件。
这个 event 会带两个参数,我们将其分别称为`first`和`second`:
| 参数 | 意义 |
| ------ | --------------------- |
| first | 第一个点击的标注的 id |
| second | 第二个点击的标注的 id |
他们代表了用户先后点击了 id 为`first`和`second`的两个标注。
#### onConnectionRightClicked
在用户右键点击了一个连接的文字部分后会触发这个事件。
这个 event 会带两个参数,为被点击的连接的 ID 和点击事件本身:
| 参数 | 意义 |
| ----- | ----------------- |
| id | 被点击的连接的 id |
| event | 点击事件 |
#### onConnectionDoubleClicked
在用户双击了一个连接的文字部分后会触发这个事件。
| 参数 | 意义 |
| ----- | ----------------- |
| id | 被点击的连接的 id |
| event | 点击事件 |
#### onAnnotatorUpdate
在标注视图更新后,获取当前 annotationData
| 参数 | 意义 |
| ----- | -------------- |
| store | 更新后视图数据 |
## 参考插件
https://github.com/synyi/poplar
其他功能,待确认需求后增加