From 552b7e0631f72943f0d1fac77610283bf2def7f9 Mon Sep 17 00:00:00 2001 From: seveibar Date: Wed, 11 Nov 2020 21:48:17 -0500 Subject: [PATCH 1/4] refactor sequence item to start optimization process --- src/components/Document/index.js | 149 +++---------------- src/components/Document/index.story.js | 46 +++--- src/components/SequenceItem/index.js | 158 +++++++++++++++++++++ src/components/SequenceItem/index.story.js | 33 +++++ 4 files changed, 241 insertions(+), 145 deletions(-) create mode 100644 src/components/SequenceItem/index.js create mode 100644 src/components/SequenceItem/index.story.js diff --git a/src/components/Document/index.js b/src/components/Document/index.js index 940d936..caf456e 100644 --- a/src/components/Document/index.js +++ b/src/components/Document/index.js @@ -6,13 +6,13 @@ import type { Relationship } from "../../types" import { styled } from "@material-ui/styles" -import stringToSequence from "../../string-to-sequence.js" -import Tooltip from "@material-ui/core/Tooltip" import RelationshipArrows from "../RelationshipArrows" import colors from "../../colors" import ArrowToMouse from "../ArrowToMouse" import { useTimeout, useWindowSize } from "react-use" +import SequenceItem from "../SequenceItem" import classNames from "classnames" +import useEventCallback from "use-event-callback" const Container = styled("div")(({ relationshipsOn }) => ({ lineHeight: 1.5, @@ -21,50 +21,6 @@ const Container = styled("div")(({ relationshipsOn }) => ({ flexWrap: "wrap" })) -const SequenceItem = styled("span")(({ color, relationshipsOn }) => ({ - display: "inline-flex", - cursor: "pointer", - backgroundColor: color, - color: "#fff", - padding: 4, - margin: 4, - marginBottom: relationshipsOn ? 64 : 4, - paddingLeft: 10, - paddingRight: 10, - borderRadius: 4, - userSelect: "none", - boxSizing: "border-box", - "&.unlabeled": { - color: "#333", - paddingTop: 4, - paddingBottom: 4, - paddingLeft: 2, - paddingRight: 2, - ".notSpace:hover": { - paddingTop: 2, - paddingBottom: 2, - paddingLeft: 0, - paddingRight: 0, - border: `2px dashed #ccc` - } - } -})) - -const LabeledText = styled("div")({ - display: "inline-flex", - cursor: "pointer", - alignSelf: "center", - fontSize: 11, - width: 18, - height: 18, - alignItems: "center", - justifyContent: "center", - marginLeft: 4, - borderRadius: 9, - color: "#fff", - backgroundColor: "rgba(0,0,0,0.2)" -}) - type Props = { sequence: Array, relationships: Array, @@ -126,6 +82,8 @@ export default function Document({ highlightedItems.push(i) } + const onRemoveLabel = useEventCallback(sequenceItemIndex => {}) + return ( {sequence.map((seq, i) => ( { - if (!elm) return - sequenceItemPositionsRef.current[seq.textId] = { - offset: { - left: elm.offsetLeft, - top: elm.offsetTop, - width: elm.offsetWidth, - height: elm.offsetHeight - } - } - }} + {...seq} + sequenceItemIndex={i} + sequenceItemPositionsRef={sequenceItemPositionsRef} relationshipsOn={Boolean(relationships)} - onMouseUp={e => { - if (!createRelationshipsMode) return - if (!secondSequenceItem) { - setFirstSequenceItem(null) - setSecondSequenceItem(null) - onCreateEmptyRelationship([firstSequenceItem, seq.textId]) - } else { - setFirstSequenceItem(null) - setSecondSequenceItem(null) - } - }} - onMouseDown={() => { - if (createRelationshipsMode) { - if (!firstSequenceItem) { - setFirstSequenceItem(seq.textId) - } - } else { - if (seq.label) return - changeHighlightedRange([i, i]) - } - }} - onMouseMove={() => { - if (!mouseDown) return - if (!createRelationshipsMode) { - if (seq.label) return - if (i !== lastSelected) { - changeHighlightedRange([ - firstSelected === null ? i : firstSelected, - i - ]) - } - } - }} - className={classNames( - seq.label ? "label" : "unlabeled", - seq.text.trim().length > 0 && "notSpace" - )} - color={ - seq.label - ? seq.color || colorLabelMap[seq.label] || "#333" - : !createRelationshipsMode && - seq.text !== " " && - highlightedItems.includes(i) - ? "#ccc" - : "inherit" - } - key={i} - > - {seq.label ? ( - -
{seq.text}
-
- ) : ( -
{seq.text}
- )} - {seq.label && !createRelationshipsMode && ( - { - e.stopPropagation() - onSequenceChange( - sequence - .flatMap(s => (s !== seq ? s : stringToSequence(s.text))) - .filter(s => s.text.length > 0) - ) - }} - > - {"\u2716"} - - )} -
+ createRelationshipsMode={createRelationshipsMode} + onChangeFirstSequenceItem={setFirstSequenceItem} + onChangeSecondSequenceItem={setSecondSequenceItem} + onCreateEmptyRelationship={onCreateEmptyRelationship} + onChangeHighlightedRange={changeHighlightedRange} + firstSequenceItem={firstSequenceItem} + secondSequenceItem={secondSequenceItem} + mouseDown={mouseDown} + firstSelected={firstSelected} + lastSelected={lastSelected} + isHighlighted={highlightedItems.includes(i)} + onRemoveLabel={onRemoveLabel} + color={seq.color || colorLabelMap[seq.label]} + /> ))} {firstSequenceItem && !secondSequenceItem && ( )) @@ -41,15 +41,15 @@ storiesOf("Document", module) Math.random() < 0.9 ? { text: text + " ", textId: `l${i}` } : { - text: text + " ", - textId: `l${i}`, - label: - "somelabel" + - Math.random() - .toString() - .slice(-4), - color: "#9638F9" - } + text: text + " ", + textId: `l${i}`, + label: + "somelabel" + + Math.random() + .toString() + .slice(-4), + color: "#9638F9" + } )} relationships={[ { @@ -60,3 +60,15 @@ storiesOf("Document", module) ]} /> )) + .add("Character Sequence", () => ( + ({ + text: c + })) + } + /> + )) \ No newline at end of file diff --git a/src/components/SequenceItem/index.js b/src/components/SequenceItem/index.js new file mode 100644 index 0000000..543f79a --- /dev/null +++ b/src/components/SequenceItem/index.js @@ -0,0 +1,158 @@ +import React from "react" +import classNames from "classnames" +import { styled, Tooltip } from "@material-ui/core" +import stringToSequence from "../../string-to-sequence.js" + +const SequenceItemContainer = styled("span")(({ color, relationshipsOn }) => ({ + display: "inline-flex", + cursor: "pointer", + backgroundColor: color, + color: "#fff", + padding: 4, + margin: 4, + marginBottom: relationshipsOn ? 64 : 4, + paddingLeft: 10, + paddingRight: 10, + borderRadius: 4, + userSelect: "none", + boxSizing: "border-box", + "&.unlabeled": { + color: "#333", + paddingTop: 4, + paddingBottom: 4, + paddingLeft: 2, + paddingRight: 2, + ".notSpace:hover": { + paddingTop: 2, + paddingBottom: 2, + paddingLeft: 0, + paddingRight: 0, + border: `2px dashed #ccc` + } + } +})) + +const XContainer = styled("div")({ + display: "inline-flex", + cursor: "pointer", + alignSelf: "center", + fontSize: 11, + width: 18, + height: 18, + alignItems: "center", + justifyContent: "center", + marginLeft: 4, + borderRadius: 9, + color: "#fff", + backgroundColor: "rgba(0,0,0,0.2)" +}) + +export const SequenceItem = ({ + textId, + text, + label, + color, + sequenceItemIndex, + sequenceItemPositionsRef, + relationshipsOn, + createRelationshipsMode, + onChangeFirstSequenceItem, + onChangeSecondSequenceItem, + onCreateEmptyRelationship, + onChangeHighlightedRange, + firstSequenceItem, + secondSequenceItem, + mouseDown, + firstSelected, + lastSelected, + colorLabelMap, + isHighlighted, + onRemoveLabel +}) => { + return ( + { + if (!elm) return + sequenceItemPositionsRef.current[textId] = { + offset: { + left: elm.offsetLeft, + top: elm.offsetTop, + width: elm.offsetWidth, + height: elm.offsetHeight + } + } + }} + relationshipsOn={relationshipsOn} + onMouseUp={e => { + if (!createRelationshipsMode) return + if (!secondSequenceItem) { + onChangeFirstSequenceItem(null) + onChangeSecondSequenceItem(null) + onCreateEmptyRelationship([firstSequenceItem, textId]) + } else { + onChangeFirstSequenceItem(null) + onChangeSecondSequenceItem(null) + } + }} + onMouseDown={() => { + if (createRelationshipsMode) { + if (!firstSequenceItem) { + onChangeFirstSequenceItem(textId) + } + } else { + if (label) return + onChangeHighlightedRange([sequenceItemIndex, sequenceItemIndex]) + } + }} + onMouseMove={() => { + if (!mouseDown) return + if (!createRelationshipsMode) { + if (label) return + if (sequenceItemIndex !== lastSelected) { + onChangeHighlightedRange([ + firstSelected === null ? sequenceItemIndex : firstSelected, + sequenceItemIndex + ]) + } + } + }} + className={classNames( + label ? "label" : "unlabeled", + text.trim().length > 0 && "notSpace" + )} + color={ + label + ? color || "#333" + : !createRelationshipsMode && text !== " " && isHighlighted + ? "#ccc" + : "inherit" + } + > + {label ? ( + +
{text}
+
+ ) : ( +
{text}
+ )} + {label && !createRelationshipsMode && ( + { + e.stopPropagation() + onRemoveLabel(sequenceItemIndex) + // onSequenceChange( + // sequence + // .flatMap(s => (s !== seq ? s : stringToSequence(s.text))) + // .filter(s => s.text.length > 0) + // ) + }} + > + + + )} +
+ ) +} + +export default SequenceItem diff --git a/src/components/SequenceItem/index.story.js b/src/components/SequenceItem/index.story.js new file mode 100644 index 0000000..ae54c0c --- /dev/null +++ b/src/components/SequenceItem/index.story.js @@ -0,0 +1,33 @@ +// @flow + +import React from "react" + +import { storiesOf } from "@storybook/react" +import { action } from "@storybook/addon-actions" +import SequenceItem from "./" + +storiesOf("SequenceItem", module).add("Basic", () => { + return ( + + ) +}) From e4c117e7c345b0399459cb63e430e924c528947d Mon Sep 17 00:00:00 2001 From: seveibar Date: Wed, 11 Nov 2020 22:16:49 -0500 Subject: [PATCH 2/4] memoize sequence item --- src/components/Document/index.js | 1 + src/components/SequenceItem/index.js | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/components/Document/index.js b/src/components/Document/index.js index caf456e..70100b7 100644 --- a/src/components/Document/index.js +++ b/src/components/Document/index.js @@ -117,6 +117,7 @@ export default function Document({ isHighlighted={highlightedItems.includes(i)} onRemoveLabel={onRemoveLabel} color={seq.color || colorLabelMap[seq.label]} + key={i} /> ))} {firstSequenceItem && !secondSequenceItem && ( diff --git a/src/components/SequenceItem/index.js b/src/components/SequenceItem/index.js index 543f79a..2b7e9f1 100644 --- a/src/components/SequenceItem/index.js +++ b/src/components/SequenceItem/index.js @@ -1,4 +1,4 @@ -import React from "react" +import React, { memo } from "react" import classNames from "classnames" import { styled, Tooltip } from "@material-ui/core" import stringToSequence from "../../string-to-sequence.js" @@ -155,4 +155,4 @@ export const SequenceItem = ({ ) } -export default SequenceItem +export default memo(SequenceItem) From 4b7e647e1171943d83e0a72b071c76ae6e49862d Mon Sep 17 00:00:00 2001 From: seveibar Date: Wed, 11 Nov 2020 22:32:07 -0500 Subject: [PATCH 3/4] fix remove sequence item --- src/components/Document/index.js | 11 ++++++++++- src/components/SequenceItem/index.js | 5 ----- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src/components/Document/index.js b/src/components/Document/index.js index 70100b7..739664b 100644 --- a/src/components/Document/index.js +++ b/src/components/Document/index.js @@ -12,6 +12,7 @@ import ArrowToMouse from "../ArrowToMouse" import { useTimeout, useWindowSize } from "react-use" import SequenceItem from "../SequenceItem" import classNames from "classnames" +import stringToSequence from "../../string-to-sequence" import useEventCallback from "use-event-callback" const Container = styled("div")(({ relationshipsOn }) => ({ @@ -82,7 +83,15 @@ export default function Document({ highlightedItems.push(i) } - const onRemoveLabel = useEventCallback(sequenceItemIndex => {}) + const onRemoveLabel = useEventCallback(sequenceItemIndex => { + onSequenceChange( + sequence + .flatMap((s, i) => + i !== sequenceItemIndex ? s : stringToSequence(s.text) + ) + .filter(s => s.text.length > 0) + ) + }) return ( { e.stopPropagation() onRemoveLabel(sequenceItemIndex) - // onSequenceChange( - // sequence - // .flatMap(s => (s !== seq ? s : stringToSequence(s.text))) - // .filter(s => s.text.length > 0) - // ) }} > From 69e9f5242cf55dda5c3eef4c4fd76b03769f8a37 Mon Sep 17 00:00:00 2001 From: seveibar Date: Wed, 11 Nov 2020 23:43:53 -0500 Subject: [PATCH 4/4] fix: custom regex --- src/components/DocumentLabeler/index.js | 13 ++++++---- src/components/NLPAnnotator/index.story.js | 24 +++++++++++++++++++ src/components/RelationshipAnnotator/index.js | 1 + src/components/SequenceAnnotator/index.js | 3 ++- src/string-to-sequence.js | 8 ++++--- src/types.js | 1 + 6 files changed, 42 insertions(+), 8 deletions(-) diff --git a/src/components/DocumentLabeler/index.js b/src/components/DocumentLabeler/index.js index 9731f34..c339de4 100644 --- a/src/components/DocumentLabeler/index.js +++ b/src/components/DocumentLabeler/index.js @@ -12,9 +12,10 @@ export default function DocumentLabeler(props: LabelDocumentProps) { const [selectedLabels, changeSelectedLabels] = useState( props.initialLabels || (props.initialLabel ? [props.initialLabel] : []) ) - const sequence = useMemo(() => stringToSequence(props.document), [ - props.document - ]) + const sequence = useMemo( + () => stringToSequence(props.document, props.separatorRegex), + [props.document] + ) return (
@@ -58,7 +59,11 @@ export default function DocumentLabeler(props: LabelDocumentProps) { ) })}
- +
) diff --git a/src/components/NLPAnnotator/index.story.js b/src/components/NLPAnnotator/index.story.js index 5551269..2b4a5fc 100644 --- a/src/components/NLPAnnotator/index.story.js +++ b/src/components/NLPAnnotator/index.story.js @@ -32,6 +32,30 @@ storiesOf("NLPAnnotator", module) ]} /> )) + .add("Sequence Labeler with Custom Regex", () => ( + + )) .add("Document Labeler", () => (
{ diff --git a/src/components/SequenceAnnotator/index.js b/src/components/SequenceAnnotator/index.js index 0f16b77..fdb0907 100644 --- a/src/components/SequenceAnnotator/index.js +++ b/src/components/SequenceAnnotator/index.js @@ -18,7 +18,7 @@ export default function SequenceAnnotator(props: SequenceAnnotatorProps) { ? [entity] : stringToSequence(entity.text, props.separatorRegex) ) - : stringToSequence(props.document) + : stringToSequence(props.document, props.separatorRegex) ) const colorLabelMap = useMemo( () => @@ -71,6 +71,7 @@ export default function SequenceAnnotator(props: SequenceAnnotatorProps) {
diff --git a/src/string-to-sequence.js b/src/string-to-sequence.js index 395e513..a4385a9 100644 --- a/src/string-to-sequence.js +++ b/src/string-to-sequence.js @@ -1,8 +1,11 @@ // @flow -const stringToSequence = (doc: string, sepRe: RegExp = /[a-zA-ZÀ-ÿ]+/g) => { +const stringToSequence = ( + doc: string, + sepRe: RegExp | string = /[a-zA-ZÀ-ÿ]+/g +) => { if (typeof sepRe === "string") { - sepRe = new RegExp(sepRe) + sepRe = new RegExp(sepRe, "g") } let m let indices = [0] @@ -15,7 +18,6 @@ const stringToSequence = (doc: string, sepRe: RegExp = /[a-zA-ZÀ-ÿ]+/g) => { } while (m) indices = indices.concat([doc.length]) return indices - .filter((_, i) => indices[i] !== indices[i + 1]) .map((_, i) => ({ text: doc.slice(indices[i], indices[i + 1]), textId: Math.random() diff --git a/src/types.js b/src/types.js index c952c54..19f49de 100644 --- a/src/types.js +++ b/src/types.js @@ -32,6 +32,7 @@ export type LabelDocumentProps = { multipleLabels?: boolean, document: string, initialLabels?: Array, + separatorRegex?: string, onChange: (Array | string | null) => any }