davidjurgens's picture
Deploy: Potato — Codebook Annotation
aceb1b2 verified
Raw
History Blame Contribute Delete
5.08 kB
"""
Visual Analog Scale (VAS) Layout
A continuous line scale with no tick marks or discrete bins. Annotators click/drag
to a position, returning a precise float value. Psychophysically superior to Likert
scales for fine-grained judgments.
Research: Stevens (1957) "On the Psychophysical Law"; standard in clinical pain assessment.
"""
import logging
from .identifier_utils import (
safe_generate_layout,
generate_element_identifier,
generate_validation_attribute,
escape_html_content,
generate_layout_attributes
)
logger = logging.getLogger(__name__)
# Defaults
DEFAULT_MIN = 0
DEFAULT_MAX = 100
DEFAULT_SHOW_VALUE = False
def generate_vas_layout(annotation_scheme):
"""
Generate HTML for a Visual Analog Scale interface.
Args:
annotation_scheme (dict): Configuration including:
- name: Schema identifier
- description: Display description
- left_label: Label for the left endpoint
- right_label: Label for the right endpoint
- min_value: Minimum value (default 0)
- max_value: Maximum value (default 100)
- show_value: Whether to show numeric value after selection (default False)
- precision: Decimal places to round to (default 1)
Returns:
tuple: (html_string, key_bindings)
"""
return safe_generate_layout(annotation_scheme, _generate_vas_layout_internal)
def _generate_vas_layout_internal(annotation_scheme):
schema_name = annotation_scheme['name']
description = annotation_scheme['description']
left_label = annotation_scheme.get('left_label', '')
right_label = annotation_scheme.get('right_label', '')
min_value = annotation_scheme.get('min_value', DEFAULT_MIN)
max_value = annotation_scheme.get('max_value', DEFAULT_MAX)
show_value = annotation_scheme.get('show_value', DEFAULT_SHOW_VALUE)
precision = annotation_scheme.get('precision', 1)
layout_attrs = generate_layout_attributes(annotation_scheme)
validation = generate_validation_attribute(annotation_scheme)
identifiers = generate_element_identifier(schema_name, schema_name, "range")
# The key difference from slider: a fine precision-based step (vs the
# slider's integer ticks), no tick marks, no value display by default,
# minimal styling. Step is derived from precision so the scale is
# effectively continuous (precision=1 → step=0.1, precision=2 → 0.01).
step_value = 10 ** -precision if precision > 0 else 1
initial_value = round((min_value + max_value) / 2, precision)
value_display = ""
if show_value:
value_display = f"""
<div class="vas-value-display" id="{identifiers['id']}-value-display">
<span id="{identifiers['name']}-value">—</span>
</div>
"""
html = f"""
<form id="{escape_html_content(schema_name)}" class="annotation-form shadcn-vas-container"
action="javascript:void(0)"
data-annotation-id="{escape_html_content(str(annotation_scheme.get('annotation_id', '')))}"
data-annotation-type="vas"
data-schema-name="{escape_html_content(schema_name)}"
{layout_attrs}>
<fieldset schema_name="{escape_html_content(schema_name)}">
<legend class="shadcn-vas-title">{escape_html_content(description)}</legend>
<div class="vas-scale-wrapper">
<div class="vas-labels">
<span class="vas-label-left">{escape_html_content(left_label)}</span>
<span class="vas-label-right">{escape_html_content(right_label)}</span>
</div>
<div class="vas-track-container">
<input type="range"
class="vas-input annotation-input"
id="{identifiers['id']}"
name="{identifiers['name']}"
schema="{identifiers['schema']}"
label_name="{identifiers['label_name']}"
validation="{validation}"
min="{min_value}"
max="{max_value}"
step="{step_value}"
value="{initial_value}">
<div class="vas-track-line"></div>
</div>
{value_display}
</div>
</fieldset>
</form>
"""
# Inline JS to round value and update display
if show_value:
html += f"""
<script>
(function() {{
var input = document.getElementById("{identifiers['id']}");
var display = document.getElementById("{identifiers['name']}-value");
var precision = {precision};
function updateDisplay() {{
if (display) display.textContent = parseFloat(input.value).toFixed(precision);
}}
input.addEventListener('input', updateDisplay);
}})();
</script>
"""
logger.info(f"Generated VAS layout for {schema_name}")
return html, []