Unified Corpus Explorer
Biodiversität_Nova
CORE T0 Webseiten Funkmast
GerParCor_Reichstag
Zobodat_with_bert_topics
NEG-African
Unified Corpus Explorer
"The annotation and exploration of large text corpora, both automatic and manual, present significant challenges across multiple disciplines, including linguistics, digital humanities, biology, and legal science. These challenges are exacerbated by the heterogeneity of processing methods, which complicates corpus visualization, interaction, and integration. To address these issues, we introduce the Unified Corpus Explorer (UCE), a standardized, dockerized, open-source, and dynamic Natural Language Processing (NLP) application designed for flexible and scalable corpus navigation. Herein, UCE utilizes the UIMA format for NLP annotations as a standardized input, constructing interfaces and features around those annotations while dynamically adapting to the corpora and their extracted annotations. We evaluate UCE based on a user study and demonstrate its versatility as a corpus explorer based on generative AI." (Boenisch et al., 2025) The Unified Corpus Explorer was developed by the Text Technology Lab as a means to unify the heterogeneous data landscape of NLP. This is a demonstration of UCE's capabilities, showcased on a variety of corpora, each from a different domain and presenting different challenges.
Corpora
A biodiversity corpus gathered within the BIOfid project. Contains old biology books, which were scanned and OCR extracted and finally put into UCE.
CORE T0 Webseiten Funkmast
CORE Project
Websites from the Funkmast-Task in T0 as part of the CORE project.
GerParCor_Reichstag
TTLab
Part of the GerParCor corpus, but only the plenary documents of the 'German Reichstag'. Contains all plenary minutes and documents concerning the historic German Reichstag.
Zobodat_with_bert_topics
TTLab
Another biodiversity corpus excerpt, used to showcase the Topic Modelling capabilities of UCE.
Containing Negation Samples for the two languages: Akan and Likpakpaanl, being researched by and provided through the NEGLab.
Team
The team behind UCE is part of the Text Technology Lab of the Goethe-University, Frankfurt.
Prof. Alexander Mehler
(Supervisor)
Professor Mehler is the supervisor of the UCE project.
Mail
Website
Robert-Mayer-Straße 10 60325 Frankfurt am Main
Kevin Bönisch
(Lead Developer)
Mr. Bönisch is responsible for developing and maintaining UCE.
Mail
Website
Robert-Mayer-Straße 10 60325 Frankfurt am Main
Giuseppe Abrami
(Developer)
Member of the Text Technology Lab and Lead Developer of the Docker Unified UIMA Interface , UCE's sibling software.
Mail
Website
Robert-Mayer-Straße 10 60325 Frankfurt am Main
Manuel Schaaf
(Developer)
PhD candidate, member of the Text Technology Lab and Developer of UCE.
Mail
Website
Robert-Mayer-Straße 10 60325 Frankfurt am Main
Leon Hammerla
(Developer)
PhD candidate, member of the Text Technology Lab and Developer of UCE.
Mail
Website
Robert-Mayer-Straße 10 60325 Frankfurt am Main
Bhuvanesh Verma
(Developer)
PhD candidate, member of the Text Technology Lab and Developer of UCE.
Mail
Website
Robert-Mayer-Straße 10 60325 Frankfurt am Main
Daniel Baumartz
(Developer)
PhD candidate, member of the Text Technology Lab and Developer of UCE.
Mail
Website
Robert-Mayer-Straße 10 60325 Frankfurt am Main
Mevlüt Bagci
(Developer)
PhD candidate and member of the Text Technology Lab and Developer of UCE.
Mail
Website
Robert-Mayer-Straße 10 60325 Frankfurt am Main
Click on one of the annotations, documents or other wiki elements.
Contact
Prof. Dr. Alexander Mehler
Mail
Website
Robert-Mayer-Straße 10 60325 Frankfurt am Main
Imprint
Version 1.0.1
-->
import {ChartJS} from '/js/visualization/chartjs.js';
import {UCEMap} from '/js/visualization/uceMap.js';
import {D3JS} from '/js/visualization/d3js.js';
import {ECharts} from '/js/visualization/echarts.js';
var GraphVizHandler = (function () {
GraphVizHandler.prototype.activeCharts = {};
function GraphVizHandler() {
console.log('Created GraphViz Handler.');
}
GraphVizHandler.prototype.createUceMap = function (target, readonly = false) {
const chartId = generateUUID();
const uceMap = new UCEMap(target, readonly);
this.activeCharts[chartId] = uceMap;
activatePopovers();
return uceMap;
}
/**
* [CHARTJS] -> Creates a pie chart into the given $target (jquery object)
*/
GraphVizHandler.prototype.createBasicChart = async function (target, title, data, type = null) {
const chartId = generateUUID();
let wrapper = document.createElement('div');
wrapper.classList.add('chart-container');
wrapper.setAttribute('data-id', chartId);
const canvas = document.createElement('canvas');
const canvasContainer = document.createElement('div');
canvasContainer.classList.add('canvas-container');
canvasContainer.appendChild(canvas);
wrapper.appendChild(canvasContainer);
const menu = `
`;
wrapper.insertAdjacentHTML('beforeend', menu);
target.appendChild(wrapper);
const jsChart = new ChartJS(canvas, title);
jsChart.setData(data);
if (type) {
jsChart.setType(type);
}
this.activeCharts[chartId] = jsChart;
return jsChart;
}
GraphVizHandler.prototype.createWordCloud = async function (target, title, wordData) {
if (!wordData || !Array.isArray(wordData) || wordData.length === 0) {
console.error('Invalid data provided to drawTopicWordCloud:', wordData);
return;
}
target.innerHTML = '';
const cloudContainer = document.createElement('div');
cloudContainer.className = 'word-cloud-container';
if (title) {
const titleElement = document.createElement('h5');
titleElement.className = 'word-cloud-title text-center';
titleElement.textContent = title;
target.insertBefore(titleElement, target.firstChild);
}
const maxWeight = Math.max(...wordData.map(item => item.weight));
const minWeight = Math.min(...wordData.map(item => item.weight));
const weightRange = maxWeight - minWeight;
wordData.forEach(item => {
const word = document.createElement('div');
word.className = 'word-cloud-item';
word.textContent = item.term;
const normalizedWeight = weightRange === 0 ? 1 : (item.weight - minWeight) / weightRange;
const fontSize = 12 + (normalizedWeight * 24);
word.style.fontSize = fontSize + "px";
word.style.cursor = 'default';
word.style.color = getColorForWeight(normalizedWeight);
word.addEventListener('mouseover', () => {
word.classList.add('hovered');
const tooltip = document.createElement('div');
tooltip.className = 'word-tooltip';
tooltip.textContent = "Weight: " + item.weight.toFixed(4);
document.body.appendChild(tooltip);
const rect = word.getBoundingClientRect();
tooltip.style.top = (rect.top + word.offsetHeight + 50) + "px";
tooltip.style.left = (rect.left + (word.offsetWidth / 5)) + "px";
tooltip.style.transform = 'translateX(-50%)';
word._tooltip = tooltip;
});
word.addEventListener('mouseout', () => {
word.classList.remove('hovered');
if (word._tooltip) {
word._tooltip.remove();
word._tooltip = null;
}
});
cloudContainer.appendChild(word);
});
target.appendChild(cloudContainer);
}
GraphVizHandler.prototype.getColorForWeight = function (weight) {
return getColorForWeight(weight);
}
GraphVizHandler.prototype.createSankeyChart = async function (target, title, linksData, nodesData,onClick = null) {
const chartId = generateUUID();
const option = {
title: {
text: title,
top: 'bottom',
left: 'right'
},
tooltip: {
trigger: 'item',
triggerOn: 'mousemove',
formatter: function (params) {
if (params.dataType === 'edge') {
return params.data.source + ' → ' + params.data.target + ': ' + params.data.value + ' ';
} else {
return params.name;
}
}
},
series: {
type: 'sankey',
layout: 'none',
data: nodesData,
links: linksData,
emphasis: {
focus: 'adjacency',
label: {
show: true,
color: '#000'
}
},
lineStyle: {
color: 'gradient',
curveness: 0.5
},
label: {
//color: '#000'
show: false
}
}
};
const echart = new ECharts(target, option); // Pass full option
this.activeCharts[chartId] = echart;
echart.getInstance().on('click', function (params) {
if (onClick && typeof onClick === 'function') {
onClick(params);
}
if (params.dataType === 'node') {
const clickedNode = params.name;
const connectedNodes = new Set([clickedNode]);
const connectedLinks = new Set();
linksData.forEach(link => {
if (link.source === clickedNode || link.target === clickedNode) {
connectedNodes.add(link.source);
connectedNodes.add(link.target);
connectedLinks.add(link.source + '->' + link.target);
}
});
const updatedNodes = nodesData.map(node => ({
...node,
label: {
show: connectedNodes.has(node.name)
},
itemStyle: {
...(node.itemStyle || {}),
opacity: connectedNodes.has(node.name) ? 1 : 0.2
}
}));
const updatedLinks = linksData.map(link => ({
...link,
lineStyle: {
...(link.lineStyle || {}),
opacity: connectedLinks.has(link.source + '->' + link.target) ? 1 : 0.1
}
}));
echart.getInstance().setOption({
series: [{
data: updatedNodes,
links: updatedLinks
}]
});
}
else {
const resetNodes = nodesData.map(node => ({
...node,
label: { show: false },
// itemStyle: {
// ...(node.itemStyle || {}),
// opacity: 0.5
// }
}));
const resetLinks = linksData.map(link => ({
...link,
lineStyle: {
color: 'gradient',
curveness: 0.5
},
}));
echart.getInstance().setOption({
series: [{
data: resetNodes,
links: resetLinks
}]
});
}
});
return echart;
};
GraphVizHandler.prototype.createMiniBarChart = function ({
data = [], // Array of [label, value]
title = '', // Chart title
labelPrefix = '', // Optional prefix for title like "Topics for"
labelHighlight = '', // The highlighted part (e.g., entity/topic name)
primaryColor = '#5470C6', // Default bar color
secondaryColor = '#91CC75', // Alternate bar color
usePrimaryForEntity = false, // Toggle color logic
barHeight = 10, // Height of bars in px
maxBarWidth = 100, // Max bar width in px
minLabelWidth = 70, // Width of label column in px
fontSize = 10 // Font size for values
} = {}) {
// This function creates a simple mini bar chart in HTML which can be used in tooltips or small displays as hover actions.
const maxVal = Math.max(...data.map(([_, v]) => v)) || 1;
const barsHtml = data.map(([label, value]) => {
const width = Math.round((value / maxVal) * maxBarWidth);
return (
'
' +
'' + label + ' ' +
' ' +
'' + value + ' ' +
'
'
);
}).join('');
const fullTitle = '' + labelPrefix + ' ';
return (
'
' +
'
' + (title || fullTitle) + '
' +
(barsHtml || '
No associations
') +
'
'
);
}
GraphVizHandler.prototype.createBarLineChart = async function (
target,
title,
config,
tooltipFormatter,
onClick = null
) {
const chartId = generateUUID();
const {
xData,
seriesData,
yLabel = 'Count'
} = config;
const option = {
tooltip: {
trigger: 'axis',
enterable: true,
backgroundColor: '#fff',
borderColor: '#ccc',
borderWidth: 1,
textStyle: {
color: '#000',
fontSize: 12
},
formatter: tooltipFormatter
},
title: {
text: title,
left: 'center'
},
legend: {
data: seriesData.map(s => s.name),
top: 'auto'
},
xAxis: {
type: 'category',
name: 'X',
data: xData
},
yAxis: {
type: 'value',
name: yLabel
},
series: []
};
seriesData.forEach(s => {
option.series.push({
name: s.name,
type: 'bar',
data: s.data,
itemStyle: {
color: s.color,
opacity: 0.15
},
barGap: '-100%',
z: 1
});
option.series.push({
name: s.name,
type: 'line',
data: s.data,
symbol: 'circle',
symbolSize: 10,
lineStyle: { width: 3, color: s.color },
itemStyle: { color: s.color },
z: 2
});
});
const echart = new ECharts(target, option);
this.activeCharts[chartId] = echart;
if (onClick && typeof onClick === 'function') {
echart.getInstance().on('click', onClick);
}
return echart;
};
GraphVizHandler.prototype.createChordChart = async function (
target,
title,
data,
tooltipFormatter = null,
onClick = null
) {
const chartId = generateUUID();
const hasGraphData = data.nodes && data.links && data.categories;
const option = {
title: { text: title, left: 'center' },
tooltip: {
trigger: 'item',
enterable: hasGraphData,
formatter: hasGraphData
? tooltipFormatter
: function (params) {
if (params.dataType === 'edge') {
return params.data.source + ' → ' + params.data.target + ': ' + params.data.value + ' ';
}
return params.name;
}
},
legend: hasGraphData ? {
data: data.categories.map(c => c.name),
left: '10px',
orient: 'vertical',
position: 'right'
} : undefined,
series: hasGraphData ? [{
type: 'graph',
layout: 'circular',
circular: { rotateLabel: true },
data: data.nodes,
links: data.links,
categories: data.categories,
roam: true,
label: { rotate: 90, show: true },
itemStyle: { borderWidth: 1, borderColor: '#aaa' },
lineStyle: { opacity: 0.5, width: 2, curveness: 0.3 },
emphasis: { focus: 'adjacency', label: { show: true } }
}] : {
type: 'chord',
data: data.nodes,
links: data.links,
emphasis: {
focus: 'adjacency',
label: {
show: true,
color: '#000'
}
},
itemStyle: {
borderWidth: 1,
borderColor: '#fff'
}
}
};
const echart = new ECharts(target, option);
this.activeCharts[chartId] = echart;
echart.getInstance().on('click', function (params) {
if (onClick && typeof onClick === 'function') {
onClick(params);
}
});
return echart;
};
GraphVizHandler.prototype.createHeatMap = async function (
target,
title,
matrix,
labels,
series_name= null,
tooltipFormatter = null,
onClick = null
) {
const chartId = generateUUID();
const option = {
title: {
text: title,
left: 'center'
},
tooltip: {
position: 'top',
formatter: tooltipFormatter,
},
grid: {
left: '15%',
bottom: '15%',
containLabel: true
},
xAxis: {
type: 'category',
data: labels,
splitArea: {
show: true
},
axisLabel: {
rotate: 45
}
},
yAxis: {
type: 'category',
data: labels,
splitArea: {
show: true
}
},
visualMap: {
min: 0,
max: Math.max(...matrix.map(d => d[2])),
calculable: true,
orient: 'horizontal',
left: 'center',
bottom: '5%'
},
series: [{
name: series_name || 'Heatmap',
type: 'heatmap',
data: matrix,
roam: true,
label: {
show: false
},
emphasis: {
itemStyle: {
shadowBlur: 10,
shadowColor: 'rgba(0, 0, 0, 0.5)'
}
}
}]
};
const echart = new ECharts(target, option);
this.activeCharts[chartId] = echart;
echart.getInstance().on('click', function (params) {
if (onClick && typeof onClick === 'function') {
onClick(params);
}
});
return echart;
};
GraphVizHandler.prototype.createNetworkGraph = async function (
target,
title,
nodes,
links,
tooltipFormatter = null,
onClick = null
) {
const chartId = generateUUID();
const option = {
title: { text: '', left: 'center' },
tooltip: {},
xAxis: { show: false, min: 'dataMin', max: 'dataMax' },
yAxis: { show: false, min: 'dataMin', max: 'dataMax' },
animationDuration: 1500,
animationEasingUpdate: 'quinticInOut',
series: [{
type: 'graph',
layout: 'force',
draggable: true,
force: {
edgeLength: 5,
repulsion: 10,
gravity: 0.5
},
data: nodes,
edges: links,
roam: true,
symbolSize: 10,
label: { show: false },
emphasis: {
focus: 'adjacency',
lineStyle: {
width: 10
}
},
itemStyle: { borderColor: '#fff', borderWidth: 1 }
}]
};
const echart = new ECharts(target, option);
this.activeCharts[chartId] = echart;
echart.getInstance().on('click', function (params) {
if (onClick && typeof onClick === 'function') {
onClick(params);
}
});
return echart;
};
return GraphVizHandler;
}());
function getNewGraphVizHandler() {
return new GraphVizHandler();
}
function getColorForWeight(weight) {
const r = Math.floor(255 * (1 - weight));
const g = Math.floor(200 * weight);
const b = 0;
return "rgba(" + r + ", " + g + ", " + b + ", 0.5)";
}
window.graphVizHandler = getNewGraphVizHandler();
import {DrawflowJS} from '/js/visualization/drawflowjs.js';
var FlowVizHandler = (function () {
FlowVizHandler.prototype.activeFlows = {};
function FlowVizHandler() {
console.log('Created FlowVizHandler Handler.');
}
FlowVizHandler.prototype.createFlowChart = async function (target, initialNode) {
const flowId = generateUUID();
const flow = new DrawflowJS(target);
flow.init(initialNode);
this.activeFlows[flowId] = flow;
activatePopovers();
}
FlowVizHandler.prototype.createNewFromLinkableNode = async function (unique, target) {
let container = undefined;
if (target === undefined || target === '') {
container = document.getElementById('full-flow-container');
$(container).parent('#flow-chart-modal').show();
container.innerHTML = ''; // reset the last chart
} else {
container = target;
container.innerHTML = ''; // reset the last chart
// Add a loader
$(container).append(`
`);
}
$.ajax({
url: '/api/wiki/linkable/node',
type: "POST",
data: JSON.stringify({
unique: unique,
}),
contentType: "application/json",
success: function (response) {
const node = JSON.parse(response);
window.flowVizHandler.createFlowChart(container, node);
$(container).find('.full-loader').fadeOut(125);
},
error: (xhr, status, error) => {
showMessageModal("Unknown Error", "There was an unknown error loading the linkable node.");
}
}).always(() => {
$(container).find('.full-loader').fadeOut(125);
});
}
return FlowVizHandler;
}());
function getNewFlowVizHandler() {
return new FlowVizHandler();
}
window.flowVizHandler = getNewFlowVizHandler();
/**
* This triggers whenever a button to
*/
$('body').on('click', '.open-linkable-node', function () {
const unique = $(this).data('unique');
const target = $(this).data('target');
window.flowVizHandler.createNewFromLinkableNode(unique, target);
})