---
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

其他功能，待确认需求后增加
