diff --git a/packages/plugin-image-text-annotation/src/index.ts b/packages/plugin-image-text-annotation/src/index.ts index 7c77450e..5aa5b9e0 100644 --- a/packages/plugin-image-text-annotation/src/index.ts +++ b/packages/plugin-image-text-annotation/src/index.ts @@ -22,6 +22,20 @@ const info = { default: [], 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 { private img_container: HTMLElement; private is_drawing = false; private active_box: AnnotationBox; - private active_label: string; private boxes: Array = []; private display_element: HTMLElement; private deselect_all_flag = true; + private categories = []; + private active_category = { id: null, label: "?", color: "#444" }; + private palette: Array; constructor(private jsPsych: JsPsych) {} trial(display_element: HTMLElement, trial: TrialType) { 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.renderDisplay(trial); @@ -75,14 +100,14 @@ class ImageTextAnnotationPlugin implements JsPsychPlugin {
`; - let i = 1; + let i = 0; for (const l of trial.labels) { - html += `
`; + html += `
`; i++; } - html += ` -
- `; + // html += ` + //
+ // `; html += `
@@ -91,6 +116,8 @@ class ImageTextAnnotationPlugin implements JsPsychPlugin { this.display_element.innerHTML = html; + this.add_new_label(); + this.img_container = this.display_element.querySelector("#annotated-image-container"); } @@ -108,13 +135,6 @@ class ImageTextAnnotationPlugin implements JsPsychPlugin { this.deselect_all_flag = true; }); 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() { @@ -135,21 +155,28 @@ class ImageTextAnnotationPlugin implements JsPsychPlugin { #jspsych-annotation-display label { border-radius: 0.75em; - background-color: green; + background-color: var(--main-color); color: white; padding: 0.35em 0.5em; - margin: 0.25em 0em; + margin-bottom: 0.25em; cursor: pointer; display: block; 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, '); display: inline-block; padding-right: 0.5em; position: relative; 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"] { @@ -172,10 +199,6 @@ class ImageTextAnnotationPlugin implements JsPsychPlugin { outline: none; } - #jspsych-annotation-display input[type="radio"]:checked + label { - background-color: grey; - } - #jspsych-annotation-display #annotated-image-container { cursor: crosshair; position: relative; @@ -187,11 +210,11 @@ class ImageTextAnnotationPlugin implements JsPsychPlugin { } #jspsych-annotation-display #annotated-image-container .annotation-box { - border: 1px solid green; + border: 1px solid var(--main-color); position: absolute; - color:green; + color: var(--main-color); user-select: none; - background-color: #00800040; + background-color: var(--very-transparent-color); } #jspsych-annotation-display #annotated-image-container .annotation-box:hover { @@ -204,9 +227,9 @@ class ImageTextAnnotationPlugin implements JsPsychPlugin { text-align:left; line-height:1em; color: white; - background-color: green; + background-color: var(--main-color); border-radius: 5px; - border: 1px solid green; + border: 1px solid var(--main-color); box-shadow: 1px 1px 3px rgba(0,0,0,0.5); position:absolute; top:2px; @@ -262,8 +285,8 @@ class ImageTextAnnotationPlugin implements JsPsychPlugin { visibility: hidden; width: 4px; height: 4px; - background-color: #00800080; - border: 1px solid #00800080; + background-color: var(--transparent-color); + border: 1px solid var(--transparent-color); position: absolute; user-select: none; 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 { if (this.is_drawing) { this.active_box.finishDrawing(); - this.active_box.setLabel(this.active_label); + this.active_box.setCategory(this.active_category); this.active_box.select(); @@ -405,10 +428,10 @@ class ImageTextAnnotationPlugin implements JsPsychPlugin { } 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) { if (b.isSelected()) { - b.setLabel(this.active_label); + b.setCategory(this.active_category); } } } @@ -421,25 +444,37 @@ class ImageTextAnnotationPlugin implements JsPsychPlugin { } } - private add_new_label(e) { + private add_new_label(e?) { 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 = ` -
+
`; container.insertAdjacentHTML("beforeend", html); container - .querySelector(`#opt${options + 1}_text`) + .querySelector(`#opt${category_id}_text`) .addEventListener("change", this.add_new_label); container - .querySelector(`#opt${options + 1}_text`) + .querySelector(`#opt${category_id}_text`) .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 - .querySelector(`#opt${options + 1}`) + .querySelector(`#opt${category_id}`) .addEventListener("change", this.handle_radio_change); } @@ -476,6 +511,8 @@ class AnnotationBox { private selected = false; private plugin: ImageTextAnnotationPlugin; private modifiable = true; + private color = "#444444"; + private category = null; constructor(x, y, box_list, container, plugin) { autoBind(this); @@ -493,6 +530,8 @@ class AnnotationBox { this.element = el; + this.setColor(this.color); + 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) { if (label) { this.label = label;