Skip to the content.

Unified Dynamic Annotation Visualizer

UDAV is designed to enable different disciplines to display their automatic pre-processing results in a schema-based and reproducible, dynamic and interactive way without the need to hard-code manual and user-defined visualizations for each new project.

Features

Widgets

UDAV currently contains the following widgets:

Demo

You can find a demo here where you can play around a little.

Architecture

The architecture of UDAV includes a backend and frontend component that can interact with each other via an API layer, both instantiated within a Docker image. Nevertheless, it can be used on any system, as all system components are encapsulated in a Docker container. Importing data is performed using a standardized procedure as part of an existing DUUI pipeline. This involves a set of documents being read by the reader and auto- matically pre-processed by a set of NLP processes through a pipeline. At the end of this pipeline is a Java-based component that serves as an importer for UDAV and ingests all annotated documents into the integrated database.

architecture

Getting Started

Requirements

Quick Start (Docker Compose)

  1. Clone the repository:

    git clone https://github.com/texttechnologylab/Unified-Dynamic-Annotation-Visualizer.git
    cd Unified-Dynamic-Annotation-Visualizer
    
  2. Create your .env file by copying the provided example:

    cp .env.example .env
    
  3. Start the application:

    docker compose up -d
    

    This starts PostgreSQL and the UDAV application. The web UI is available at http://localhost:8080 once the container is healthy (usually within ~30–60 seconds).

Note: If you’re looking for a small demo without any setup, check our open demo.


Importing DUUI Annotation Data

For the full configuration reference, see DUUI Importer — Configuration Reference.

To import XMI/GZ annotation files produced by DUUI pipelines, you need to configure the importer in your .env before starting the containers.

Important: DUUI_IMPORTER_PATH and DUUI_IMPORTER_TYPE_SYSTEM_PATH must be absolute paths on your host machine — Docker Compose mounts them into the container automatically.

1. Set the path to your annotation files:

DUUI_IMPORTER_PATH=/absolute/path/to/your/xmi/files

2. Set the file extension matching your corpus (.xmi for uncompressed, .gz for gzip-compressed):

DUUI_IMPORTER_FILE_ENDING=.xmi
# or
DUUI_IMPORTER_FILE_ENDING=.gz

3. (Optional) Set the path to an external TypeSystem XML if you want to use a custom type system instead of letting UDAV auto-detect it from the XMI files:

DUUI_IMPORTER_TYPE_SYSTEM_PATH=/absolute/path/to/your/typesystem

Note: If DUUI_IMPORTER_TYPE_SYSTEM_PATH is left empty, the type system is auto-detected from the XMI files. If you set it, point it to the folder containing your TypeSystem XML file.

4. Enable the importer and start:

DUUI_IMPORTER=true
docker compose up -d

The importer runs on startup and processes all matching files in the configured directory. Import progress is logged and visible via:

docker compose logs -f udav

Example .env for DUUI import

# Database
DB_USER=postgres
DB_PASS=postgres
POSTGRES_DB=udav

# DUUI Importer
DUUI_IMPORTER=true
DUUI_IMPORTER_PATH=/data/my-corpus/xmi-files
DUUI_IMPORTER_FILE_ENDING=.gz
DUUI_IMPORTER_WORKERS=4
DUUI_IMPORTER_CAS_POOL_SIZE=12
DUUI_IMPORTER_TYPE_SYSTEM_PATH=

# Java memory (adjust to your system)
JAVA_OPTS=-Xmx10G -Xms1024m

Source Build and Generators

To visualize annotations from imported UIMA documents subject to user requirements, UDAV implements a Data Generation Layer with two modules:

Data API

UDAV provides a RESTful API for fetching the data to be visualized on the frontend. The API is built using Java and Spring Boot, and it interacts with the integrated database to retrieve the necessary data based on the user’s requests. The API endpoints are designed to support various types of queries, allowing for flexible data retrieval that can be easily consumed by the frontend components. The API is constructed in a modular way. With Controller, Service and Repository layers, it follows the common best practices for building maintainable and scalable APIs. The Controller layer handles incoming HTTP requests and maps them to the appropriate Service methods. The Service layer contains the business logic for processing the requests and interacting with the Repository layer, which is responsible for database operations. This makes it easy to extend the API with new endpoints or modify existing ones as needed, without affecting other parts of the application. The API also includes error handling and validation to ensure that requests are processed correctly and that any issues are communicated back to the client in a clear and consistent manner.

The only exception is the data API, which uses a custom ChartHandler to support different types of charts in a modular way. Each chart type has its own handler.

Webpage

The frontend uses Freemarker to render the html templates, d3.js for some of the visualization components, gridstack.js for the draggable grid in the editor, Floating UI for some UI components and Bootstrap for icons and styling.

Structure

The frontend is located in the resources folder of the Java project. This directory contains all static assets and server-rendered templates, following a clear separation between static resources and template logic.

The static directory includes stylesheets, images, and JavaScript logic, while templates contains the FreeMarker templates used to render the HTML pages. Both directories are structured into pages and shared subdirectories: pages contains page-specific content for each page of the application, while shared provides reusable code and resources used across multiple pages. The api directory holds the api communication and the widgets directory contains the available widgets divided into charts and static widgets.

Global CSS variables are defined in variables.css. The widgets are registered in the widgets.js file. The packages directory contains third-party dependencies.

Overview of the folder structure:

📁 resources
├─ 📁 static
│  ├─ 📁 css
│  │  ├─ 📁 pages
│  │  ├─ 📁 shared
│  │  └─ 📄 variables.css
│  ├─ 📁 data
│  ├─ 📁 img
│  ├─ 📁 js
│  │  ├─ 📁 api
│  │  ├─ 📁 pages
│  │  │  ├─ 📁 editor
│  │  │  │  ├─ 📁 configs
│  │  │  │  ├─ 📁 controller
│  │  │  │  ├─ 📁 utils
│  │  │  │  ├─ 📄 Editor.js
│  │  │  ├─ 📁 index
│  │  │  └─ 📁 view
│  │  │     ├─ 📁 filter
│  │  │     ├─ 📁 toolbar
│  │  │     ├─ 📁 utils
│  │  │     └─ 📄 View.js
│  │  ├─ 📁 shared
│  │  │  ├─ 📁 classes
│  │  │  └─ 📁 modules
│  │  ├─ 📁 widgets
│  │  │  ├─ 📁 charts
│  │  │  ├─ 📁 static
│  │  │  ├─ 📄 D3Visualization.js
│  │  │  ├─ 📄 WidgetInterface.js
│  │  │  └─ 📄 widgets.js
│  ├─ 📁 packages
│  └─ 📄 favicon.ico
└─ 📁 templates
   ├─ 📁 error
   ├─ 📁 pages
   │  ├─ 📁 editor
   │  │  ├─ 📄 editor.ftl
   │  │  ├─ 📄 editorGrid.ftl
   │  │  └─ 📄 editorSidebar.ftl
   │  ├─ 📁 index
   │  └─ 📁 view
   └─ 📁 shared

Tutorials

If you want to change the primary color of the application or other general properties you can simply change them in the variables.css file.

Below are some instructions for adding new components to the webpage.

Adding a new chart widget

To add a new chart widget, follow these steps:

  1. Create a new JavaScript class in the widgets/charts folder. This class will define the widget’s configuration and rendering.

  2. Define the defaultConfig object. This is the initial configuration for the widget after creation. Include:
    • title: The display title of the chart.
    • type: The chart’s type (must match the class name).
    • generator: Will be set by the user later.
    • options: Chart-specific options.
    • icon: The icon will be displayed in the editor.
    • w: The initial width of the widget in grid cells.
    • h: The initial height of the widget in grid cells.
  3. Define the formConfig object. This configures the modal form where users can edit the chart’s settings. Use the property paths in defaultConfig for the keys. Each field requires:
    • type: The input type (see inputFactories.js for available types).
    • label: The label displayed to the user.
    • options (optional): Additional configuration for the input.
  4. Extend the WidgetInterface class and implement a init and a render method.
    • The init method will be called once after creation of the widget and should contain the first data fetch and rendering as well as the configuration of the controls.
    • The render method will be called every time the chart data changes, for example after a filter is applied.
  5. If your new widget uses d3.js to create an svg, you can extend the D3Visualization class instead. This class already provides helpful functions for svg initialization and resizing, tooltips or axis creation.

  6. You can use this template as a starting point:

    export default class NewChart extends WidgetInterface {
      static defaultConfig = {
        type: "NewChart",
        title: "New Chart",
        generator: { id: "" },
        options: {},
        icon: "bi bi-chart",
        w: 6,
        h: 6,
      };
      static formConfig = {
        title: {
          type: "text",
          label: "Title",
        },
        "generator.id": {
          type: "select",
          label: "Generator",
          options: () => getGeneratorOptions("CategoryNumber"),
        },
      };
    
      constructor(root, config) {
        super(root, config);
      }
    
      clear() {
        // Clear and set-up chart inside chart area
      }
    
      init() {
        const { data, meta } = await this.fetch();
        this.render(data[0]);
    
        // Initialize export options
        this.exports.init(meta.total > 1);
        this.filter = {
          // Initialize filter
        };
    
        // Add controls ...
      }
    
      render(data) {
        this.clear();
    
        // Render chart data ...
    
        this.data = data;
      }
    }
    
  7. Finally, register the new widget by adding it to the widgets.js file so it’s available in the editor:

    export default {
      BarChart,
      LineChart,
      // ...
      NewChart,
    };
    

Adding a new generator

To add a new generator, follow these steps:

  1. Create a new JavaScript class in the configs folder of the editor page. This class will define the generator’s configuration.

  2. Define the generator’s token. The token is a short string that will be displayed as an icon in the editor.

  3. Define the defaultConfig object. This is the initial configuration for the generator after creation. Include:
    • name: The display name of the generator.
    • type: The generator’s type (must match the class name).
    • settings: Generator-specific settings.
    • extends: An array of other generators this one extends (optional).
  4. Define the formConfig object. This configures the modal form where users can edit the generator’s settings. Use the property paths in defaultConfig for the keys. Each field requires:
    • type: The input type (see inputFactories.js for available types).
    • label: The label displayed to the user.
    • options (optional): Additional configuration for the input.
  5. You can use this template as a starting point:

    export default class NewGenerator {
      static token = "NG";
      static defaultConfig = {
        name: "New Generator",
        type: "NewGenerator",
        settings: {},
        extends: [],
      };
      static formConfig = {
        name: {
          type: "text",
          label: "Name",
        },
        extends: {
          type: "multiselect",
          label: "Extends (optional)",
          options: () => getGeneratorOptions("NewGenerator"),
        },
      };
    }
    
  6. Finally, register the new generator by adding it to the configs.js file so it’s available in the editor:

    export default {
      TextFormatting,
      CategoryNumber,
      // ...
      NewGenerator,
    };
    

Formular configuration

The widget controls and option modals in the editor are defined through a JSON structure. This structure is used by the ControlsHandler for widgets, as well as by the formConfig parameter of widgets, source and generator configurations. It defines the form input fields, including their types, labels, and available options.

Available input types are implemented in inputFactories.js, which acts as a central registry for creating the form inputs. If additional input types are needed, they can be easily extended by adding new implementations to inputFactories.js.

Screenshots

menu
The main menu in UDAV for selecting and managing different pipeline views.


example4
This pipeline view shows diagrams with annotations for part-of-speech. Filters can be set at the corpus level in the sidebar on the left.


example3
This pipeline view provides some diagrams with annotations for part-of-speech and named entities as well as static text and image to provide a title (top) and captions below each diagram. In the middle, a text selected via UDAV is visualized, where the highlighting corresponds to the colors of the other diagrams due to a shared Generator. The control panel for filtering the displayed data of the bar chart on the right is opened.


editor
Example of the Editor in UDAV for creating and modifying Pipelines as well as for arranging the resulting widgets. The sources and generators can be modified in the sidebar on the left.

Authors / Contributors

Publications

Here we list all publications related to UDAV, which you can also refer to when citing UDAV in your work:

@inproceedings{Dahmann:et:al:2026,
  title = {Towards the Generation and Application of Dynamic Web-Based Visualization of UIMA-based Annotations for Big-Data Corpora with the Help of Unified Dynamic Annotation Visualizer},
  booktitle = {Proceedings of the Fifteenth Language Resources and Evaluation Conference (LREC 2026)},
  year      = {2026},
  pages = {6695--6705},
  author    = {Dahmann, Thiemo and Schneider, Julian and Stephan, Philipp and Abrami, Giuseppe
               and Mehler, Alexander},
  address = {Palma, Mallorca, Spain},
  publisher = {European Language Resources Association (ELRA)},
  editor = {Piperidis, Stelios and Bel, Núria and van den Heuvel, Henk and Ide, Nancy and Krek, Simon and Toral, Antonio},
  doi = {10.63317/5ce2aaity4yz},               
  keywords  = {NLP, UIMA, Annotations, dynamic visualization, uce},
  abstract = {The automatic and manual annotation of unstructured corpora is a routine task in many scientific fields and is supported by a variety of existing software solutions. Despite this variety, few solutions currently support annotation visualization, especially for dynamic generation and interaction. To bridge this gap and visualize annotated corpora based on user-, project-, or corpus-specific aspects, we developed Unified Dynamic Annotation Visualizer (UDAV). UDAV is a web-based solution that implements features not supported by comparable tools, enabling a customizable and extensible toolbox for interacting with annotations and allowing integration into existing big-data frameworks. We exemplify UDAV through a range of visualizations and also provide an evaluation of corpus import and processing performance.},
  pdf = {http://www.lrec-conf.org/proceedings/lrec2026/pdf/2026.lrec2026-1.533.pdf}
}

License

This project is published under the AGPL-3.0 license.