colorized categories!

This commit is contained in:
Josh de Leeuw 2022-01-06 10:26:53 -05:00
parent 0825be0443
commit 919f7e63db

View File

@ -22,6 +22,20 @@ const info = <const>{
default: [], default: [],
array: true, array: true,
}, },
colors: {
type: ParameterType.STRING,
array: true,
default: [
"#e41a1c",
"#377eb8",
"#4daf4a",
"#984ea3",
"#ff7f00",
"#ffff33",
"#a65628",
"#f781bf",
],
},
}, },
}; };
@ -40,15 +54,26 @@ class ImageTextAnnotationPlugin implements JsPsychPlugin<Info> {
private img_container: HTMLElement; private img_container: HTMLElement;
private is_drawing = false; private is_drawing = false;
private active_box: AnnotationBox; private active_box: AnnotationBox;
private active_label: string;
private boxes: Array<AnnotationBox> = []; private boxes: Array<AnnotationBox> = [];
private display_element: HTMLElement; private display_element: HTMLElement;
private deselect_all_flag = true; private deselect_all_flag = true;
private categories = [];
private active_category = { id: null, label: "?", color: "#444" };
private palette: Array<string>;
constructor(private jsPsych: JsPsych) {} constructor(private jsPsych: JsPsych) {}
trial(display_element: HTMLElement, trial: TrialType<Info>) { trial(display_element: HTMLElement, trial: TrialType<Info>) {
this.display_element = display_element; this.display_element = display_element;
this.palette = trial.colors;
for (let i = 0; i < trial.labels.length; i++) {
this.categories.push({
id: i,
color: trial.colors[i],
label: trial.labels[i],
});
}
this.add_css(); this.add_css();
this.renderDisplay(trial); this.renderDisplay(trial);
@ -75,14 +100,14 @@ class ImageTextAnnotationPlugin implements JsPsychPlugin<Info> {
</div> </div>
<div id='annotation-options'> <div id='annotation-options'>
`; `;
let i = 1; let i = 0;
for (const l of trial.labels) { for (const l of trial.labels) {
html += `<div><input type="radio" id="opt${i}" name="annotate_label" value="${l}"><label for="opt${i}">${l}</label></div>`; html += `<div style="--main-color:${trial.colors[i]}"><input type="radio" id="opt${i}" name="annotate_label" value="${l}"><label for="opt${i}">${l}</label></div>`;
i++; i++;
} }
html += ` // html += `
<div><input type="radio" id="opt${i}" name="annotate_label" value=""><label for="opt${i}"><input id="opt${i}_text" type="text"></label></div> // <div><input type="radio" id="opt${i}" name="annotate_label" value=""><label for="opt${i}"><input id="opt${i}_text" type="text"></label></div>
`; // `;
html += ` html += `
</div> </div>
</div> </div>
@ -91,6 +116,8 @@ class ImageTextAnnotationPlugin implements JsPsychPlugin<Info> {
this.display_element.innerHTML = html; this.display_element.innerHTML = html;
this.add_new_label();
this.img_container = this.display_element.querySelector("#annotated-image-container"); this.img_container = this.display_element.querySelector("#annotated-image-container");
} }
@ -108,13 +135,6 @@ class ImageTextAnnotationPlugin implements JsPsychPlugin<Info> {
this.deselect_all_flag = true; this.deselect_all_flag = true;
}); });
document.addEventListener("click", this.deselect_all); document.addEventListener("click", this.deselect_all);
this.display_element
.querySelector('input[type="text"]')
.addEventListener("change", this.add_new_label);
this.display_element
.querySelector('input[type="text"]')
.addEventListener("change", this.update_labels);
} }
private add_css() { private add_css() {
@ -135,21 +155,28 @@ class ImageTextAnnotationPlugin implements JsPsychPlugin<Info> {
#jspsych-annotation-display label { #jspsych-annotation-display label {
border-radius: 0.75em; border-radius: 0.75em;
background-color: green; background-color: var(--main-color);
color: white; color: white;
padding: 0.35em 0.5em; padding: 0.35em 0.5em;
margin: 0.25em 0em; margin-bottom: 0.25em;
cursor: pointer; cursor: pointer;
display: block; display: block;
line-height: normal; line-height: normal;
transition: margin-right 0.2s;
} }
#jspsych-annotation-display label::before { #jspsych-annotation-display input[type="radio"] + label::before {
content: url('data:image/svg+xml; utf8, <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="white"><path d="M20.285 2l-11.285 11.567-5.286-5.011-3.714 3.716 9 8.728 15-15.285z"/></svg>'); content: url('data:image/svg+xml; utf8, <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="white"><path d="M20.285 2l-11.285 11.567-5.286-5.011-3.714 3.716 9 8.728 15-15.285z"/></svg>');
display: inline-block; display: inline-block;
padding-right: 0.5em; padding-right: 0.5em;
position: relative; position: relative;
top: 0.15em; top: 0.15em;
transform: scale(0);
transition: transform 0.2s;
}
#jspsych-annotation-display input[type="radio"]:checked + label::before {
transform: scale(1);
} }
#jspsych-annotation-display label input[type="text"] { #jspsych-annotation-display label input[type="text"] {
@ -172,10 +199,6 @@ class ImageTextAnnotationPlugin implements JsPsychPlugin<Info> {
outline: none; outline: none;
} }
#jspsych-annotation-display input[type="radio"]:checked + label {
background-color: grey;
}
#jspsych-annotation-display #annotated-image-container { #jspsych-annotation-display #annotated-image-container {
cursor: crosshair; cursor: crosshair;
position: relative; position: relative;
@ -187,11 +210,11 @@ class ImageTextAnnotationPlugin implements JsPsychPlugin<Info> {
} }
#jspsych-annotation-display #annotated-image-container .annotation-box { #jspsych-annotation-display #annotated-image-container .annotation-box {
border: 1px solid green; border: 1px solid var(--main-color);
position: absolute; position: absolute;
color:green; color: var(--main-color);
user-select: none; user-select: none;
background-color: #00800040; background-color: var(--very-transparent-color);
} }
#jspsych-annotation-display #annotated-image-container .annotation-box:hover { #jspsych-annotation-display #annotated-image-container .annotation-box:hover {
@ -204,9 +227,9 @@ class ImageTextAnnotationPlugin implements JsPsychPlugin<Info> {
text-align:left; text-align:left;
line-height:1em; line-height:1em;
color: white; color: white;
background-color: green; background-color: var(--main-color);
border-radius: 5px; border-radius: 5px;
border: 1px solid green; border: 1px solid var(--main-color);
box-shadow: 1px 1px 3px rgba(0,0,0,0.5); box-shadow: 1px 1px 3px rgba(0,0,0,0.5);
position:absolute; position:absolute;
top:2px; top:2px;
@ -262,8 +285,8 @@ class ImageTextAnnotationPlugin implements JsPsychPlugin<Info> {
visibility: hidden; visibility: hidden;
width: 4px; width: 4px;
height: 4px; height: 4px;
background-color: #00800080; background-color: var(--transparent-color);
border: 1px solid #00800080; border: 1px solid var(--transparent-color);
position: absolute; position: absolute;
user-select: none; user-select: none;
transition: height 0.25s, width 0.25s, left 0.25s, top 0.25s, right 0.25s, bottom 0.25s; transition: height 0.25s, width 0.25s, left 0.25s, top 0.25s, right 0.25s, bottom 0.25s;
@ -368,7 +391,7 @@ class ImageTextAnnotationPlugin implements JsPsychPlugin<Info> {
if (this.is_drawing) { if (this.is_drawing) {
this.active_box.finishDrawing(); this.active_box.finishDrawing();
this.active_box.setLabel(this.active_label); this.active_box.setCategory(this.active_category);
this.active_box.select(); this.active_box.select();
@ -405,10 +428,10 @@ class ImageTextAnnotationPlugin implements JsPsychPlugin<Info> {
} }
private handle_radio_change(e) { private handle_radio_change(e) {
this.active_label = (e.target as HTMLFormElement).value; this.active_category = this.categories[(e.target as HTMLFormElement).id.substring(3, 4)];
for (const b of this.boxes) { for (const b of this.boxes) {
if (b.isSelected()) { if (b.isSelected()) {
b.setLabel(this.active_label); b.setCategory(this.active_category);
} }
} }
} }
@ -421,25 +444,37 @@ class ImageTextAnnotationPlugin implements JsPsychPlugin<Info> {
} }
} }
private add_new_label(e) { private add_new_label(e?) {
const container = this.display_element.querySelector("#annotation-options"); const container = this.display_element.querySelector("#annotation-options");
const options = container.querySelectorAll('input[type="radio"]').length; const category_id = this.categories.length;
this.categories.push({
id: category_id,
label: "",
color: this.palette[category_id],
});
const html = ` const html = `
<div><input type="radio" id="opt${options + 1}" name="annotate_label" value=""><label for="${ <div style="--main-color: ${this.palette[category_id]}"><input type="radio" id="opt${category_id}" name="annotate_label" value=""><label for="opt${category_id}"><input id="opt${category_id}_text" type="text"></label></div>
options + 1
}"><input id="opt${options + 1}_text" type="text"></label></div>
`; `;
container.insertAdjacentHTML("beforeend", html); container.insertAdjacentHTML("beforeend", html);
container container
.querySelector(`#opt${options + 1}_text`) .querySelector(`#opt${category_id}_text`)
.addEventListener("change", this.add_new_label); .addEventListener("change", this.add_new_label);
container container
.querySelector(`#opt${options + 1}_text`) .querySelector(`#opt${category_id}_text`)
.addEventListener("change", this.update_labels); .addEventListener("change", this.update_labels);
e.target.removeEventListener("change", this.add_new_label); container.querySelector(`#opt${category_id}_text`).addEventListener("click", (e) => {
(e.target as HTMLFormElement).parentElement.parentElement.querySelector("input").checked =
true;
});
if (e) {
e.target.removeEventListener("change", this.add_new_label);
}
container container
.querySelector(`#opt${options + 1}`) .querySelector(`#opt${category_id}`)
.addEventListener("change", this.handle_radio_change); .addEventListener("change", this.handle_radio_change);
} }
@ -476,6 +511,8 @@ class AnnotationBox {
private selected = false; private selected = false;
private plugin: ImageTextAnnotationPlugin; private plugin: ImageTextAnnotationPlugin;
private modifiable = true; private modifiable = true;
private color = "#444444";
private category = null;
constructor(x, y, box_list, container, plugin) { constructor(x, y, box_list, container, plugin) {
autoBind(this); autoBind(this);
@ -493,6 +530,8 @@ class AnnotationBox {
this.element = el; this.element = el;
this.setColor(this.color);
this.container.appendChild(this.element); this.container.appendChild(this.element);
} }
@ -505,6 +544,18 @@ class AnnotationBox {
} }
} }
setCategory(category) {
this.category = category.id;
this.setLabel(category.label);
this.setColor(category.color);
}
setColor(color) {
this.element.style.setProperty("--main-color", color);
this.element.style.setProperty("--transparent-color", color + "80");
this.element.style.setProperty("--very-transparent-color", color + "40");
}
setLabel(label) { setLabel(label) {
if (label) { if (label) {
this.label = label; this.label = label;