Skip to content

Commit 6e806fb

Browse files
committed
Prefill initial spans
1 parent 8f560c5 commit 6e806fb

File tree

6 files changed

+146
-225
lines changed

6 files changed

+146
-225
lines changed

css/widget.css

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,22 +8,21 @@
88
}
99
.label {
1010
margin: 5px 10px 5px 0;
11-
border: 1px solid white;
1211
cursor: pointer;
1312
display: block;
1413
padding: 1px 10px;
1514
position: relative;
1615
border-radius: 4px;
1716
font-size: 16px;
17+
18+
background-color: #D1D5DB;
1819
}
1920
.selected {
2021
background: white;
21-
color: rgb(31, 41, 55);
2222
}
2323
.labelContainer {
2424
display: flex;
2525
flex-grow: 1;
26-
color: #fff;
2726
font-weight: bold;
2827
font-family: "Roboto Condensed", "Arial Narrow", sans-serif;
2928
text-transform: uppercase;
@@ -34,10 +33,10 @@
3433
.topBar {
3534
display: flex;
3635
align-items: center;
37-
color: #fff;
36+
color: #1F2937;
3837
width: 100%;
3938
padding: 12px;
40-
background: rgb(31, 41, 55);
39+
background: #F3F4F6;
4140
box-sizing: border-box;
4241
font-size: 20px;
4342
}

examples/introduction.ipynb

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,13 @@
1818
},
1919
{
2020
"cell_type": "code",
21-
"execution_count": 4,
21+
"execution_count": 2,
2222
"metadata": {},
2323
"outputs": [
2424
{
2525
"data": {
2626
"application/vnd.jupyter.widget-view+json": {
27-
"model_id": "e67aa91552c84d7bb5f20bacb49aa9a5",
27+
"model_id": "2ed48d10079542f2b9cc81bce88d0614",
2828
"version_major": 2,
2929
"version_minor": 0
3030
},
@@ -43,16 +43,33 @@
4343
" \"We investigated the influence of interfraction interval (IFI) on treatment outcome in patients with stage III non-small-cell lung cancer (NSCLC) treated with hyperfractionated radiation therapy (Hfx RT) with or without concurrent chemotherapy (CHT). During 3 randomized phase III and 1 phase II study, a total of 536 patients were treated with Hfx RT alone or with concurrent carboplatin/etoposide. Two hundred eighty-five patients were treated with IFI of 4.5-5.0 hours, while 251 patients were treated with IFI of 5.5-6.0 hours. (4.5-5.0 hours) IFI led to better overall survival (OS) (P = 0.0000) and local recurrence-free survival (LRFS) (P = 0.0000). Multivariate analyses showed IFI to be an independent prognosticator of both OS and LRFS. These results were confirmed when we separated all patients (n = 536) into those treated with Hfx RT only (n = 127) and those treated with concur-rent RT/CHT (n = 409). Various RT-related high-grade acute toxicity was not different between the 2 IFI, but patients treated with shorter IFI had a significantly higher incidence of hematological toxicity (P = 0.002). None of the late high-grade toxicities were different between the 2 interfraction intervals. Using regression analysis, it was shown that IFI was not a significant predictor of any of acute or late high-grade (=3) toxicity. IFI is an important prognosticator of OS and LRFS in patients with stage III NSCLC treated with Hfx RT with or without concurrent carboplatin/etoposide. IFI led to higher incidence only of hematological toxicity, but was not predictive of any acute or late high-grade (=3) toxicity. A carefully designed randomized trial seems necessary to give better insight into the issue of optimal IFI in this disease.\",\n",
4444
"]\n",
4545
"\n",
46+
"initial_spans = [\n",
47+
"[\n",
48+
" {'start': 83, 'end': 88, 'text': 'NSCLC', 'label': 'condition'},\n",
49+
" {'start': 1475, 'end': 1485, 'text': 'pemetrexed', 'label': 'intervention'},\n",
50+
" {'start': 1991,\n",
51+
" 'end': 2024,\n",
52+
" 'text': 'safety and tolerability of AUY922',\n",
53+
" 'label': 'outcome'}\n",
54+
"],\n",
55+
" [],\n",
56+
" [{'start': 110,\n",
57+
" 'end': 136,\n",
58+
" 'text': 'non-small-cell lung cancer',\n",
59+
" 'label': 'condition'}]\n",
60+
"]\n",
61+
"\n",
4662
"other = jupyterannotate.AnnotateWidget(\n",
4763
" docs=docs,\n",
64+
" spans=initial_spans,\n",
4865
" labels=[\"condition\", \"intervention\", \"outcome\"],\n",
4966
")\n",
5067
"other"
5168
]
5269
},
5370
{
5471
"cell_type": "code",
55-
"execution_count": 6,
72+
"execution_count": 3,
5673
"metadata": {},
5774
"outputs": [
5875
{
@@ -71,7 +88,7 @@
7188
" 'label': 'condition'}]]"
7289
]
7390
},
74-
"execution_count": 6,
91+
"execution_count": 3,
7592
"metadata": {},
7693
"output_type": "execute_result"
7794
}

src/annotate.ts

Lines changed: 0 additions & 214 deletions
Original file line numberDiff line numberDiff line change
@@ -4,217 +4,3 @@ export interface Span {
44
text: string;
55
label: string;
66
}
7-
8-
interface Props {
9-
[key: string]: any;
10-
}
11-
interface Component {
12-
render: (props?: Props) => HTMLElement;
13-
}
14-
15-
const Labels = (
16-
labels: string[],
17-
onLabelChange: (label: string) => void
18-
): Component => {
19-
const el = document.createElement("div");
20-
el.classList.add("labelContainer");
21-
22-
function render(props?: Props) {
23-
el.innerHTML = "";
24-
25-
labels.forEach((label) => {
26-
const labelEl = document.createElement("div");
27-
labelEl.classList.add("label");
28-
if (label === props?.selectedLabel) {
29-
labelEl.classList.add("selected");
30-
}
31-
labelEl.innerHTML = label;
32-
labelEl.addEventListener("click", () => onLabelChange(label));
33-
el.appendChild(labelEl);
34-
});
35-
36-
return el;
37-
}
38-
39-
return {
40-
render,
41-
};
42-
};
43-
44-
const Nav = (onNext: () => void, onPrev: () => void) => {
45-
const el = document.createElement("div");
46-
el.classList.add("nav");
47-
48-
function render() {
49-
el.innerHTML = "";
50-
const prevEl = document.createElement("div");
51-
prevEl.classList.add("navLink", "prev");
52-
prevEl.innerHTML = "⏵";
53-
prevEl.addEventListener("click", onPrev);
54-
55-
const nextEl = document.createElement("div");
56-
nextEl.classList.add("navLink", "next");
57-
nextEl.innerHTML = "⮕";
58-
nextEl.addEventListener("click", onNext);
59-
60-
el.appendChild(prevEl);
61-
el.appendChild(nextEl);
62-
63-
return el;
64-
}
65-
66-
return { render };
67-
};
68-
69-
const TopBar = (
70-
labels: string[],
71-
onLabelChange: (label: string) => void,
72-
onNext: () => void,
73-
onPrev: () => void
74-
): Component => {
75-
const el = document.createElement("div");
76-
el.classList.add("topBar");
77-
const labelComponent = Labels(labels, onLabelChange);
78-
const navComponent = Nav(onNext, onPrev);
79-
80-
function render(props?: Props) {
81-
el.innerHTML = "";
82-
el.appendChild(
83-
labelComponent.render({ selectedLabel: props?.selectedLabel })
84-
);
85-
el.appendChild(navComponent.render());
86-
return el;
87-
}
88-
89-
return {
90-
render,
91-
};
92-
};
93-
94-
export function annotate(
95-
initialText: string,
96-
labels: string[],
97-
initialSpans: Span[],
98-
onChange: (spans: Span[]) => void
99-
): HTMLElement {
100-
// TODO this is state
101-
let spans = initialSpans || [];
102-
let selectedLabel = labels.length ? labels[0] : "";
103-
104-
const wrapperEl = document.createElement("div");
105-
const contentEl = document.createElement("div");
106-
contentEl.classList.add("content");
107-
contentEl.innerHTML = initialText;
108-
109-
const onChangeLabel = (label: string) => {
110-
if (selectedLabel !== label) {
111-
selectedLabel = label;
112-
navComponent.render({ selectedLabel });
113-
}
114-
};
115-
116-
const onNext = () => {
117-
console.log("Next");
118-
};
119-
120-
const onPrev = () => {
121-
console.log("Prev");
122-
};
123-
124-
wrapperEl.innerHTML = "";
125-
const navComponent = TopBar(labels, onChangeLabel, onNext, onPrev);
126-
wrapperEl.appendChild(navComponent.render({ selectedLabel }));
127-
wrapperEl.appendChild(contentEl);
128-
129-
function getSpanEl(text: string, span: Span) {
130-
const spanEl = document.createElement("span");
131-
spanEl.classList.add("span");
132-
const content = text.slice(span.start, span.end);
133-
spanEl.title = content;
134-
135-
const spanLabelEl = document.createElement("span");
136-
spanLabelEl.classList.add("spanLabel");
137-
spanLabelEl.innerHTML = span.label;
138-
139-
const spanContentEl = document.createElement("span");
140-
spanContentEl.innerHTML = content;
141-
142-
spanEl.appendChild(spanContentEl);
143-
spanEl.appendChild(spanLabelEl);
144-
145-
spanEl.addEventListener("click", () => {
146-
spans = spans.filter((s: Span) => s.start !== span.start);
147-
updateSpans();
148-
});
149-
150-
return spanEl;
151-
}
152-
153-
function renderSpans() {
154-
contentEl.innerHTML = "";
155-
contentEl.appendChild(getHighlightedText(initialText));
156-
}
157-
158-
function updateSpans() {
159-
onChange(spans);
160-
renderSpans();
161-
}
162-
163-
function getHighlightedText(text: string) {
164-
const textEl = document.createElement("div");
165-
let prevOffset = 0;
166-
167-
spans
168-
.sort((a, b) => (a.start > b.start ? 1 : -1))
169-
.forEach((span) => {
170-
const prevEl = document.createElement("span");
171-
prevEl.dataset.offset = `${prevOffset}`;
172-
prevEl.innerHTML = text.slice(prevOffset, span.start);
173-
textEl.appendChild(prevEl);
174-
textEl.appendChild(getSpanEl(text, span));
175-
176-
prevOffset = span.end;
177-
});
178-
179-
const prevEl = document.createElement("span");
180-
prevEl.dataset.offset = `${prevOffset}`;
181-
prevEl.innerHTML = text.slice(prevOffset);
182-
textEl.appendChild(prevEl);
183-
184-
return textEl;
185-
}
186-
187-
function onSelect(event: any): void {
188-
const dataset = event.target?.dataset || {};
189-
const offset = parseInt(dataset.offset || "0", 10);
190-
const selected = window.getSelection();
191-
const selectedText = selected?.toString() || "";
192-
if (!selectedText.trim() || !selected) {
193-
return;
194-
}
195-
196-
const start =
197-
selected.anchorOffset > selected.focusOffset
198-
? selected.focusOffset
199-
: selected.anchorOffset;
200-
const end =
201-
selected.anchorOffset < selected.focusOffset
202-
? selected.focusOffset
203-
: selected.anchorOffset;
204-
205-
spans = spans.concat([
206-
{
207-
start: start + offset,
208-
end: end + offset,
209-
text: selectedText,
210-
label: selectedLabel,
211-
},
212-
]);
213-
updateSpans();
214-
}
215-
216-
contentEl.addEventListener("mouseup", onSelect);
217-
renderSpans();
218-
219-
return wrapperEl;
220-
}

src/components/Annotate.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,19 +8,23 @@ import Highlightable from "./Highlightable";
88
interface Props {
99
docs: string[];
1010
labels: string[];
11+
initialSpans: Span[][];
1112
onUpdateSpans: (span: Span[][]) => void;
1213
}
1314

1415
export default function Annotate({
1516
docs,
1617
labels,
18+
initialSpans,
1719
onUpdateSpans,
1820
}: Props): VNode {
1921
const totalDocs = docs.length;
2022
const [selectedLabel, setSelectedLabel] = useState<string>("");
2123
const [docIndex, setDocIndex] = useState<number>(0);
2224
const [docSpans, setDocSpans] = useState<Span[][]>(
23-
[...Array(totalDocs).keys()].map(() => [])
25+
initialSpans.length
26+
? initialSpans
27+
: [...Array(totalDocs).keys()].map(() => [])
2428
);
2529

2630
const text = useMemo<string>(() => {

0 commit comments

Comments
 (0)