Added Sunburst Chart (named entities)

This commit is contained in:
vysitor 2025-03-20 22:56:06 +01:00
parent 381aa65734
commit 8a10ef9364
5 changed files with 123 additions and 4 deletions

View file

@ -1,11 +1,11 @@
package org.texttechnologylab.project.gruppe_05_1.rest;
import de.tudarmstadt.ukp.dkpro.core.api.syntax.type.constituent.S;
import io.javalin.http.Context;
import io.javalin.openapi.*;
import org.texttechnologylab.project.gruppe_05_1.database.MongoPprUtils;
import org.texttechnologylab.project.gruppe_05_1.domain.html.HtmlSpeech;
import org.texttechnologylab.project.gruppe_05_1.domain.html.ParlamentarierDetails;
import org.texttechnologylab.project.gruppe_05_1.domain.nlp.NamedEntity;
import org.texttechnologylab.project.gruppe_05_1.domain.nlp.Token;
import org.texttechnologylab.project.gruppe_05_1.domain.nlp.Topic;
import org.texttechnologylab.project.gruppe_05_1.domain.speech.SpeechMetaData;
@ -117,9 +117,49 @@ public class SpeechController {
speech.getNlp().setPosList((List) new ArrayList<Token>()); // Ensure it's never null
}
// NLP: Named Entities
if ((speech.getNlp().getNamedEntities() != null)
&& (speech.getNlp().getNamedEntities().size() > 0)) {
Map<String, Map<String, Integer>> namedEntitiesMapOfMaps = new HashMap<>();
for (NamedEntity ne : speech.getNlp().getNamedEntities()) {
String type = ne.getType();
String text = ne.getText();
if (namedEntitiesMapOfMaps.containsKey(type)) {
// Named Entity Type bekannt...
Map<String, Integer> typeAppearance = namedEntitiesMapOfMaps.get(type);
if (typeAppearance.containsKey(text)) {
// ... und der Text auch bekannt --> erhöhe die Anzahl um 1
typeAppearance.replace(
text,
typeAppearance.get(text) + 1) ;
} else {
// ... aber der Text unbekannt --> erstelle einen neuen Eintrag für den Text und füge diesen dem Type-Eintrag hinzu
// TODO: DELETE
//Map<String, Integer> firstTextAppearance = new HashMap<>();
//firstTextAppearance.put(type, 1);
typeAppearance.put(text, 1);
//namedEntitiesMapOfMaps.put(type, firstTextAppearance);
}
} else {
// Named Entity Type unbekannt: erstelle einen neuen Eintrag für Type sowie einen Eintrag für den ihm gehörigen Text
Map<String, Integer> firstTextAppearance = new HashMap<>();
firstTextAppearance.put(text, 1);
namedEntitiesMapOfMaps.put(type, firstTextAppearance);
}
}
attributes.put("na_info", namedEntitiesMapOfMaps);
} else {
attributes.put("na_info", null);
}
// TODO: Token wird momentan etwas komisch abgespeichert, da im Attribut text die POS art steht, und in pos die Anzahl dieser POS arten. Umstrukturieren damit keine Verwirrung herrscht
// NLP: Sentiments
// NLP: Sentiments - TODO
if (speech.getNlp().getSentiments() != null) {
}

View file

@ -0,0 +1,68 @@
<svg id="namedEntitiesSunBurstChart" width="100%" height="100%"></svg>
<script>
var namedEntitiesData = {
"name": "Named Entities", "children": [
<#list neMap as neType, innerMap>
<#include "namedEntitiesTypeEntry.ftl"> <#sep>,
</#list>
]
};
var ne_sunburst_width = 1000;
var ne_sunburst_height = 800;
var ne_sunburst_radius = Math.min(ne_sunburst_width, ne_sunburst_height) / 2;
var ne_sunburst_color = d3.scaleOrdinal(d3.schemeCategory10);
var g = d3.select('#namedEntitiesSunBurstChart')
.attr('width', ne_sunburst_width)
.attr('height', ne_sunburst_height)
.append('g')
.attr('transform', 'translate(' + ne_sunburst_width / 2 + ',' + ne_sunburst_height / 2 + ')');
var partition = d3.partition()
.size([2 * Math.PI, ne_sunburst_radius]);
var root = d3.hierarchy(namedEntitiesData)
.sum(function (d) { return d.size; });
partition(root);
var arc = d3.arc()
.startAngle(function (d) { return d.x0; })
.endAngle(function (d) { return d.x1; })
.innerRadius(function (d) { return d.y0; })
.outerRadius(function (d) { return d.y1; });
g.selectAll('path')
.data(root.descendants())
.enter().append('path')
.attr("display", function (d) { return d.depth ? null : "none"; })
.attr("d", arc)
.style('stroke', '#fff')
.style("fill", function (d) { return ne_sunburst_color((d.children ? d : d.parent).data.name); });
g.selectAll('text')
.data(root.descendants())
.enter().append('text')
.attr("transform", function (d) {
// Calculate the angle of the text
var angle = (d.x0 + d.x1) / 2;
var x = (d.y0 + d.y1) / 2 * Math.sin(angle); // Calculate the x position based on the angle
var y = (d.y0 + d.y1) / 2 * -Math.cos(angle); // Calculate the y position based on the angle
// If it's the root, center it perfectly
if (d.depth === 0) {
x = 0;
y = 0;
}
return "translate(" + x + "," + y + ")";
})
.attr("dy", ".35em") // Adjust vertical positioning
.style("text-anchor", "middle")
.style("font-size", "12px") // Adjust font size if necessary
.text(function (d) { return d.data.name; });
</script>

Before

Width:  |  Height:  |  Size: 0 B

After

Width:  |  Height:  |  Size: 2.4 KiB

Before After
Before After

View file

@ -0,0 +1 @@
{"name": "${neText}", "size": ${count}}

View file

@ -0,0 +1,10 @@
{
"name": "${neType}",
"children": [
<#list innerMap as neText, count>
<#include "namedEntitiesTextEntry.ftl"> <#sep>,
</#list>
]
}

View file

@ -29,9 +29,9 @@
</div>
<div class="chart">
<#if s.nlp.namedEntities??>
<#if na_info??>
<h3>Named Entities Information (als Sunburst Chart)</h3>
<#assign nea = s.nlp.namedEntities>
<#assign neMap = na_info>
<#include "namedEntitiesSunburstChart.ftl">
<#else>
<h3>Keine Named Entities Information für diese Rede verfügbar</h3>