From 36e2453a7eb322dad0fac309ac843e6a88c9718b Mon Sep 17 00:00:00 2001 From: Artorias Date: Sun, 23 Mar 2025 17:31:03 +0100 Subject: [PATCH] Implemented rudimentary Chart overview for all Speeches. --- .../gruppe_05_1/database/MongoDBHandler.java | 8 +- .../gruppe_05_1/rest/FrontEndController.java | 82 +++++++++++++++++++ .../project/gruppe_05_1/rest/RESTHandler.java | 3 + src/main/resources/templates/charts.ftl | 52 ++++++++++++ src/main/resources/templates/header.ftl | 2 + src/main/resources/templates/posBarChart.ftl | 2 +- .../resources/templates/topicsBubbleChart.ftl | 2 + 7 files changed, 146 insertions(+), 5 deletions(-) create mode 100644 src/main/resources/templates/charts.ftl diff --git a/src/main/java/org/texttechnologylab/project/gruppe_05_1/database/MongoDBHandler.java b/src/main/java/org/texttechnologylab/project/gruppe_05_1/database/MongoDBHandler.java index fc263d4..f64534e 100644 --- a/src/main/java/org/texttechnologylab/project/gruppe_05_1/database/MongoDBHandler.java +++ b/src/main/java/org/texttechnologylab/project/gruppe_05_1/database/MongoDBHandler.java @@ -4,11 +4,10 @@ import com.mongodb.MongoClientSettings; import com.mongodb.MongoCredential; import com.mongodb.ServerAddress; import com.mongodb.bulk.BulkWriteResult; -import com.mongodb.client.MongoClient; -import com.mongodb.client.MongoClients; -import com.mongodb.client.MongoCollection; -import com.mongodb.client.MongoDatabase; +import com.mongodb.client.*; import com.mongodb.client.model.*; +import org.texttechnologylab.project.gruppe_05_1.domain.html.HtmlSpeech; +import org.texttechnologylab.project.gruppe_05_1.domain.nlp.Topic; import org.texttechnologylab.project.gruppe_05_1.exceptions.ServerErrorException; import org.texttechnologylab.project.gruppe_05_1.exceptions.SessionNotFoundException; import org.bson.Document; @@ -781,6 +780,7 @@ public class MongoDBHandler { } } + public boolean sessionExists(String sessionNumber) { Document filter = new Document("sessionId", Integer.valueOf(sessionNumber)); long count = sessionsCollection.countDocuments(filter); diff --git a/src/main/java/org/texttechnologylab/project/gruppe_05_1/rest/FrontEndController.java b/src/main/java/org/texttechnologylab/project/gruppe_05_1/rest/FrontEndController.java index c5f38df..5df9760 100644 --- a/src/main/java/org/texttechnologylab/project/gruppe_05_1/rest/FrontEndController.java +++ b/src/main/java/org/texttechnologylab/project/gruppe_05_1/rest/FrontEndController.java @@ -1,13 +1,16 @@ package org.texttechnologylab.project.gruppe_05_1.rest; +import com.mongodb.client.MongoCollection; import gnu.trove.impl.sync.TSynchronizedShortObjectMap; import io.javalin.http.Context; import io.javalin.openapi.*; import org.apache.commons.collections.bag.SynchronizedSortedBag; +import org.texttechnologylab.project.gruppe_05_1.database.MongoDBHandler; 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.NamedEntity; 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; @@ -15,13 +18,24 @@ import org.texttechnologylab.project.gruppe_05_1.domain.speech.SpeechMetaData; import org.texttechnologylab.project.gruppe_05_1.util.Logger; import org.texttechnologylab.project.gruppe_05_1.util.PPRUtils; import org.texttechnologylab.project.gruppe_05_1.xml.speeches.Interfaces.Speech; +import com.mongodb.client.AggregateIterable; +import com.mongodb.client.model.Aggregates; +import com.mongodb.client.model.Accumulators; +import com.mongodb.client.model.Facet; +import com.mongodb.client.model.Filters; +import com.mongodb.client.model.Projections; +import org.bson.Document; +import org.bson.conversions.Bson; +import java.util.*; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.concurrent.CompletableFuture; import java.util.stream.Collectors; +import static javax.management.Query.eq; import static org.texttechnologylab.project.gruppe_05_1.util.PPRUtils.listFractionsFromMembers; public class FrontEndController { @@ -67,6 +81,74 @@ public class FrontEndController { ctx.render("parlamentarier.ftl", attributes); } + public static void getCharts(Context ctx) { + MongoCollection col = MongoPprUtils.getSpeechCollection(); + + List topicsPipeline = List.of( + Aggregates.match(Filters.exists("analysisResults.topics.0")), + Aggregates.unwind("$analysisResults.topics"), + Aggregates.group( + "$analysisResults.topics.topic", + Accumulators.sum("totalScore", "$analysisResults.topics.score") + ) + ); + List posPipeline = List.of( + Aggregates.match(Filters.exists("analysisResults.tokens")), + Aggregates.unwind("$analysisResults.tokens"), + Aggregates.group("$analysisResults.tokens.pos", Accumulators.sum("count", 1)) + ); + List nePipeline = List.of( + Aggregates.match(Filters.exists("analysisResults.namedEntities")), + Aggregates.unwind("$analysisResults.namedEntities"), + Aggregates.group("$analysisResults.namedEntities.type", Accumulators.sum("count", 1)) + ); + List sentimentsPipeline = List.of( + Aggregates.match(Filters.exists("analysisResults.sentiments")), + Aggregates.project(Projections.computed("firstSentiment", + new Document("$arrayElemAt", List.of("$analysisResults.sentiments", 0)))), + Aggregates.replaceRoot("$firstSentiment") + ); + + CompletableFuture> topicsF = CompletableFuture.supplyAsync(() -> col.aggregate(topicsPipeline).into(new ArrayList<>())); + CompletableFuture> posF = CompletableFuture.supplyAsync(() -> col.aggregate(posPipeline).into(new ArrayList<>())); + CompletableFuture> neF = CompletableFuture.supplyAsync(() -> col.aggregate(nePipeline).into(new ArrayList<>())); + CompletableFuture> sentF = CompletableFuture.supplyAsync(() -> col.aggregate(sentimentsPipeline).into(new ArrayList<>())); + + CompletableFuture.allOf(topicsF, posF, neF, sentF).join(); + + List aggregatedTopics = topicsF.join().stream() + .map(d -> new Topic(d.getString("_id"), d.getDouble("totalScore"), null)) + .collect(Collectors.toList()); + + List aggregatedPOS = posF.join().stream() + .map(d -> new Token(d.getString("_id"), String.valueOf(d.getInteger("count")), "")) + .collect(Collectors.toList()); + + Map> aggregatedNE = new HashMap<>(); + neF.join().forEach(d -> { + List entities = d.getList("entities", Document.class); + Map typeMap = (entities == null) + ? new HashMap<>() + : entities.stream() + .collect(Collectors.toMap( + e -> e.getString("text"), + e -> e.getInteger("count") + )); + aggregatedNE.put(d.getString("_id"), typeMap); + }); + + List aggregatedSentiments = Sentiment.readSentimentsFromMongo(sentF.join()); + + ctx.render("charts.ftl", Map.of( + "aggregatedTopics", aggregatedTopics, + "aggregatedPOS", aggregatedPOS, + "aggregatedNE", aggregatedNE, + "aggregatedSentiments", aggregatedSentiments + )); + } + + + /* TODO: Achtung: getParlamentarierDetails gibt es ab jetzt LEDIGLICH im ParlamentarierController! */ diff --git a/src/main/java/org/texttechnologylab/project/gruppe_05_1/rest/RESTHandler.java b/src/main/java/org/texttechnologylab/project/gruppe_05_1/rest/RESTHandler.java index f45ab21..5b80639 100644 --- a/src/main/java/org/texttechnologylab/project/gruppe_05_1/rest/RESTHandler.java +++ b/src/main/java/org/texttechnologylab/project/gruppe_05_1/rest/RESTHandler.java @@ -66,6 +66,9 @@ public class RESTHandler { app.get("/reden", SpeechController::listAllSpeeches); // zeige alle Reden an (Filtern möglich) + // Charts + app.get("/charts", FrontEndController::getCharts); + app.get("/export/pdf/speech/{id}", SpeechesLatexExportController::exportSpeech); // exportiere eine Rede als PDF app.get("/export/pdf/speaker/{id}", SpeechesLatexExportController::exportSpeechesFromSpeaker); // exportiere alle Reden eines Parlamentariers als PDF app.get("/export/pdf/topic/{topic}", SpeechesLatexExportController::exportSpeechesWithTopic); // exportiere alle Reden zu einem Thema als PDF diff --git a/src/main/resources/templates/charts.ftl b/src/main/resources/templates/charts.ftl new file mode 100644 index 0000000..efdefc7 --- /dev/null +++ b/src/main/resources/templates/charts.ftl @@ -0,0 +1,52 @@ + + + Parliament Explorer + +<#include "header.ftl"> + + +

Chart Sortiment

+
+
+ <#if aggregatedTopics?? && (aggregatedTopics?size gt 0)> +

Topics Information (als Bubble Chart)

+ <#assign condenseTopicInformation = aggregatedTopics> + <#include "topicsBubbleChart.ftl"> + <#else> +

Keine Topics Information verfügbar

+ +
+ +
+ <#if aggregatedPOS?? && (aggregatedPOS?size gt 0)> +

POS Information (als Bar Chart)

+ <#assign posList = aggregatedPOS> + <#include "posBarChart.ftl"> + <#else> +

Keine POS Information verfügbar

+ +
+ +
+ <#if aggregatedSentiment?? && (aggregatedSentiment?size gt 0)> +

Sentiments Information (als Radar Chart)

+ <#-- Wrap the aggregated sentiment map in a list so that the chart partial can iterate --> + <#assign sentiments = [aggregatedSentiment]> + <#include "sentimentsRadarChart.ftl"> + <#else> +

Keine Sentiments Information verfügbar

+ +
+ +
+ <#if aggregatedNE?? && (aggregatedNE?size gt 0)> +

Named Entities Information (als Sunburst Chart)

+ <#assign neMap = aggregatedNE> + <#include "namedEntitiesSunburstChart.ftl"> + <#else> +

Keine Named Entities Information verfügbar

+ +
+
+ +<#include "footer.ftl"> diff --git a/src/main/resources/templates/header.ftl b/src/main/resources/templates/header.ftl index dabcc1e..8d7294b 100644 --- a/src/main/resources/templates/header.ftl +++ b/src/main/resources/templates/header.ftl @@ -12,6 +12,8 @@
Reden
+ Charts +
Exportieren
Über diff --git a/src/main/resources/templates/posBarChart.ftl b/src/main/resources/templates/posBarChart.ftl index 4f0fb8f..c6be459 100644 --- a/src/main/resources/templates/posBarChart.ftl +++ b/src/main/resources/templates/posBarChart.ftl @@ -15,7 +15,7 @@ posData.push({ pos: "No Data", count: 0 }); - console.log("Final POS Data being used:", posData); + console.log("POS Data:", posData); var svg = d3.select("#posBarchart") .attr("width", barChartWidth) diff --git a/src/main/resources/templates/topicsBubbleChart.ftl b/src/main/resources/templates/topicsBubbleChart.ftl index 7eb2885..378596c 100644 --- a/src/main/resources/templates/topicsBubbleChart.ftl +++ b/src/main/resources/templates/topicsBubbleChart.ftl @@ -8,6 +8,8 @@ ]; + console.log("Topics Data:", topicsData); + const topics_bc_width = 1000; const topics_bc_height = 800;