Merge branch 'Sentiments-Radar-Chart' into 'main'
Sentiments radar chart See merge request s1188354/multimodal_parliament_explorer_05_1!9
This commit is contained in:
commit
5856b87b25
6 changed files with 176 additions and 30 deletions
|
@ -68,8 +68,8 @@ public class HtmlSpeech {
|
|||
List<Document> namedEntitiesDocs = nlpDoc.get("namedEntities", MongoDBHandler.DOC_LIST_CLASS);
|
||||
nlp.setNamedEntities(NamedEntity.readNamedEntitiesFromMongo(namedEntitiesDocs));
|
||||
|
||||
List<Document> sentimentsDocs = nlpDoc.get("sentiments", MongoDBHandler.DOC_LIST_CLASS);
|
||||
nlp.setSentiments(Sentiment.readSentimentsFromMongo(sentimentsDocs));
|
||||
List<Document> sentimentDocs = nlpDoc.get("sentiments", MongoDBHandler.DOC_LIST_CLASS);
|
||||
nlp.setSentiments(List.of(Sentiment.readFirstSentimentFromMongo(sentimentDocs)));
|
||||
|
||||
List<Document> topicsDocs = nlpDoc.get("topics", MongoDBHandler.DOC_LIST_CLASS);
|
||||
nlp.setTopics(Topic.readTopicsFromMongo(topicsDocs));
|
||||
|
|
|
@ -99,24 +99,14 @@ public class Sentiment {
|
|||
.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param sentimentDocs Die Sentiment-Dokumente (Speech --> analysisResults --> sentiment) aus der MongoDB lesen.
|
||||
* Das erste Dokument ist für die gesamte Rede, Sentiments 1..n entsprechen Sentences 0..n-1
|
||||
* @return
|
||||
*/
|
||||
public static List<Sentiment> readSentimentsFromMongo(List<Document> sentimentDocs) {
|
||||
List<Sentiment> sentiments = new ArrayList<>();
|
||||
for (Document doc : sentimentDocs) {
|
||||
sentiments.add(new Sentiment(
|
||||
doc.getInteger("begin"),
|
||||
doc.getInteger("end"),
|
||||
doc.getDouble("score"),
|
||||
doc.getDouble("pos"),
|
||||
doc.getDouble("neu"),
|
||||
doc.getDouble("neg")
|
||||
));
|
||||
}
|
||||
return sentiments;
|
||||
public static Sentiment readFirstSentimentFromMongo(List<Document> sentimentDocs) {
|
||||
Document doc = sentimentDocs.get(0);
|
||||
return new Sentiment(doc.getInteger("begin"),
|
||||
doc.getInteger("end"),
|
||||
doc.getDouble("score"),
|
||||
doc.getDouble("pos"),
|
||||
doc.getDouble("neu"),
|
||||
doc.getDouble("neg")
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ 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.Parlamentarier;
|
||||
import org.texttechnologylab.project.gruppe_05_1.domain.html.ParlamentarierDetails;
|
||||
import org.texttechnologylab.project.gruppe_05_1.domain.nlp.Sentiment;
|
||||
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;
|
||||
|
@ -151,6 +152,7 @@ public class FrontEndController {
|
|||
@OpenApiResponse(status = "200", content = {@OpenApiContent(from = Speech.class)})
|
||||
})
|
||||
public static void showSpeech(Context ctx) {
|
||||
String parlamentarierId = ctx.pathParam("id");
|
||||
String redeId = ctx.pathParam("redeId");
|
||||
|
||||
Map<String, Object> attributes = new HashMap<>();
|
||||
|
@ -158,6 +160,10 @@ public class FrontEndController {
|
|||
HtmlSpeech speech = MongoPprUtils.getSpeechByKey(redeId);
|
||||
attributes.put("s", speech);
|
||||
|
||||
// Foto des Abgeordnetes
|
||||
String picture = MongoPprUtils.getParlamentarierPictureByID(parlamentarierId);
|
||||
attributes.put("picture", picture);
|
||||
|
||||
// NLP: Topic
|
||||
if ((speech.getNlp() != null) && (speech.getNlp().getTopics() != null)) {
|
||||
Map<String, Double> topics = Topic.condenseTopicInformation(speech.getNlp().getTopics()); // Daten "verdichten"...
|
||||
|
@ -187,9 +193,27 @@ public class FrontEndController {
|
|||
speech.getNlp().setPosList((List) new ArrayList<Token>()); // Ensure it's never 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
|
||||
Sentiment sentimentObj = null;
|
||||
if (speech.getNlp() != null && speech.getNlp().getSentiments() != null && !speech.getNlp().getSentiments().isEmpty()) {
|
||||
Sentiment first = speech.getNlp().getSentiments().get(0);
|
||||
sentimentObj = new Sentiment(
|
||||
first.getBegin(),
|
||||
first.getEnd(),
|
||||
first.getSentiment(),
|
||||
first.getNegative(),
|
||||
first.getNeutral(),
|
||||
first.getPositive()
|
||||
);
|
||||
|
||||
|
||||
} else {
|
||||
// No sentiment found: use a default sentiment with zero values
|
||||
sentimentObj = new Sentiment(0, 0, 0.0, 0.0, 0.0, 0.0);
|
||||
}
|
||||
System.out.println("DEBUG: Speech Sentiment - " + sentimentObj);
|
||||
attributes.put("sentiment", sentimentObj);
|
||||
|
||||
ctx.render("speech.ftl", attributes);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -62,7 +62,7 @@ public class RESTHandler {
|
|||
|
||||
// Reden
|
||||
app.get("/reden/{id}", FrontEndController::listSpeeches); // zeige Reden eines Parlamentariers an
|
||||
app.get("/reden/{id}/{redeId}", SpeechController::showSpeech); // zeige eine bestimmte Rede des Parlamentariers an
|
||||
app.get("/reden/{id}/{redeId}", FrontEndController::showSpeech); // zeige eine bestimmte Rede des Parlamentariers an
|
||||
|
||||
app.get("/reden", SpeechController::listAllSpeeches); // zeige alle Reden an (Filtern möglich)
|
||||
}
|
||||
|
|
|
@ -15,13 +15,9 @@
|
|||
</#if>
|
||||
|
||||
|
||||
<#if s.nlp.sentiments??>
|
||||
<h3>SentimentsInformation (als Radar Chart)</h3>
|
||||
<#if s.nlp.overallSentiment??>
|
||||
<#assign overallSentiment = s.nlp.overallSentiment>
|
||||
</#if>
|
||||
<#assign sentiments = s.nlp.sentiments>
|
||||
<#include "sentimentsRadarChart.ftl">
|
||||
<#if sentiment??>
|
||||
<h3>Sentiments Information (als Radar Chart)</h3>
|
||||
<#include "sentimentsRadarChart.ftl">
|
||||
<#else>
|
||||
<h3>Keine Sentiments Information für diese Rede verfügbar</h3>
|
||||
</#if>
|
||||
|
|
|
@ -0,0 +1,136 @@
|
|||
<svg id="sentimentsRadarChart"></svg>
|
||||
<script src="https://d3js.org/d3.v6.min.js"></script>
|
||||
<script>
|
||||
// Retrieve sentiment values from the passed attribute "sentiment"
|
||||
var sentimentData = {
|
||||
pos: ${sentiment.positive?number},
|
||||
neu: ${sentiment.neutral?number},
|
||||
neg: ${sentiment.negative?number}
|
||||
};
|
||||
console.log("Sentiment Data:", sentimentData);
|
||||
|
||||
// Set up SVG dimensions and center the chart
|
||||
var width = 1000, height = 1000;
|
||||
var svg = d3.select("#sentimentsRadarChart")
|
||||
.attr("width", width)
|
||||
.attr("height", height);
|
||||
var centerX = width / 2, centerY = height / 2;
|
||||
var maxRadius = 200; // Maximum radius for the chart
|
||||
|
||||
// Create a radial scale assuming sentiment values are normalized between 0 and 1
|
||||
var rScale = d3.scaleLinear()
|
||||
.domain([0, 1])
|
||||
.range([0, maxRadius]);
|
||||
|
||||
// Define the three axes for the radar chart with fixed angles (in degrees)
|
||||
var axes = [
|
||||
{ axis: "Positive", angle: 0, value: sentimentData.pos },
|
||||
{ axis: "Neutral", angle: 120, value: sentimentData.neu },
|
||||
{ axis: "Negative", angle: 240, value: sentimentData.neg }
|
||||
];
|
||||
|
||||
// Function to convert polar coordinates to cartesian
|
||||
function polarToCartesian(cx, cy, r, angleDegrees) {
|
||||
var angleRadians = (angleDegrees - 90) * Math.PI / 180;
|
||||
return {
|
||||
x: cx + r * Math.cos(angleRadians),
|
||||
y: cy + r * Math.sin(angleRadians)
|
||||
};
|
||||
}
|
||||
|
||||
// Draw the concentric grid (web) lines
|
||||
var gridSteps = 5; // Number of concentric polygons
|
||||
for (var i = 1; i <= gridSteps; i++) {
|
||||
var step = i / gridSteps;
|
||||
var gridPoints = axes.map(function(d) {
|
||||
var r = step * maxRadius;
|
||||
return polarToCartesian(centerX, centerY, r, d.angle);
|
||||
});
|
||||
svg.append("path")
|
||||
.datum(gridPoints)
|
||||
.attr("d", d3.line()
|
||||
.x(function(d) { return d.x; })
|
||||
.y(function(d) { return d.y; })
|
||||
.curve(d3.curveLinearClosed)
|
||||
)
|
||||
.attr("stroke", "#aaa")
|
||||
.attr("stroke-width", 1)
|
||||
.attr("fill", "none");
|
||||
}
|
||||
|
||||
// Draw axis lines and labels
|
||||
axes.forEach(function(d) {
|
||||
var endPoint = polarToCartesian(centerX, centerY, maxRadius, d.angle);
|
||||
svg.append("line")
|
||||
.attr("x1", centerX)
|
||||
.attr("y1", centerY)
|
||||
.attr("x2", endPoint.x)
|
||||
.attr("y2", endPoint.y)
|
||||
.attr("stroke", "#ccc")
|
||||
.attr("stroke-width", 1);
|
||||
|
||||
var labelOffset = 20;
|
||||
var labelPoint = polarToCartesian(centerX, centerY, maxRadius + labelOffset, d.angle);
|
||||
svg.append("text")
|
||||
.attr("x", labelPoint.x)
|
||||
.attr("y", labelPoint.y)
|
||||
.attr("text-anchor", "middle")
|
||||
.style("font-size", "14px")
|
||||
.text(d.axis);
|
||||
});
|
||||
|
||||
// Compute radar chart data points from sentiment values
|
||||
var radarPoints = axes.map(function(d) {
|
||||
var r = rScale(d.value);
|
||||
return polarToCartesian(centerX, centerY, r, d.angle);
|
||||
});
|
||||
console.log("Radar Points:", radarPoints);
|
||||
|
||||
// Create a closed polygon from the radar points
|
||||
var radarLine = d3.line()
|
||||
.x(function(d) { return d.x; })
|
||||
.y(function(d) { return d.y; })
|
||||
.curve(d3.curveLinearClosed);
|
||||
|
||||
svg.append("path")
|
||||
.datum(radarPoints)
|
||||
.attr("d", radarLine)
|
||||
.attr("stroke", "blue")
|
||||
.attr("stroke-width", 2)
|
||||
.attr("fill", "blue")
|
||||
.attr("fill-opacity", 0.3);
|
||||
|
||||
// Create a tooltip div appended to the body and style it
|
||||
var tooltip = d3.select("body").append("div")
|
||||
.attr("id", "tooltip")
|
||||
.style("position", "absolute")
|
||||
.style("background", "#fff")
|
||||
.style("border", "1px solid #ccc")
|
||||
.style("padding", "4px 8px")
|
||||
.style("border-radius", "4px")
|
||||
.style("pointer-events", "none")
|
||||
.style("font-size", "12px")
|
||||
.style("box-shadow", "0px 0px 5px rgba(0,0,0,0.3)")
|
||||
.style("display", "none");
|
||||
|
||||
// Draw circles at each radar point and attach tooltip events
|
||||
axes.forEach(function(d, i) {
|
||||
var point = radarPoints[i];
|
||||
svg.append("circle")
|
||||
.attr("cx", point.x)
|
||||
.attr("cy", point.y)
|
||||
.attr("r", 4)
|
||||
.attr("fill", "red")
|
||||
.on("mouseover", function(event) {
|
||||
tooltip.style("display", "block")
|
||||
.html(d.axis + ": " + d.value);
|
||||
})
|
||||
.on("mousemove", function(event) {
|
||||
tooltip.style("left", (event.pageX + 10) + "px")
|
||||
.style("top", (event.pageY - 10) + "px");
|
||||
})
|
||||
.on("mouseout", function() {
|
||||
tooltip.style("display", "none");
|
||||
});
|
||||
});
|
||||
</script>
|
Before Width: | Height: | Size: 0 B After Width: | Height: | Size: 4.8 KiB |
Loading…
Add table
Add a link
Reference in a new issue