Implemented rudimentary Chart overview for all Speeches.

This commit is contained in:
Artorias 2025-03-23 17:31:03 +01:00
parent 6ef665c38f
commit 36e2453a7e
7 changed files with 146 additions and 5 deletions

View file

@ -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);

View file

@ -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<Document> col = MongoPprUtils.getSpeechCollection();
List<Bson> 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<Bson> posPipeline = List.of(
Aggregates.match(Filters.exists("analysisResults.tokens")),
Aggregates.unwind("$analysisResults.tokens"),
Aggregates.group("$analysisResults.tokens.pos", Accumulators.sum("count", 1))
);
List<Bson> nePipeline = List.of(
Aggregates.match(Filters.exists("analysisResults.namedEntities")),
Aggregates.unwind("$analysisResults.namedEntities"),
Aggregates.group("$analysisResults.namedEntities.type", Accumulators.sum("count", 1))
);
List<Bson> 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<List<Document>> topicsF = CompletableFuture.supplyAsync(() -> col.aggregate(topicsPipeline).into(new ArrayList<>()));
CompletableFuture<List<Document>> posF = CompletableFuture.supplyAsync(() -> col.aggregate(posPipeline).into(new ArrayList<>()));
CompletableFuture<List<Document>> neF = CompletableFuture.supplyAsync(() -> col.aggregate(nePipeline).into(new ArrayList<>()));
CompletableFuture<List<Document>> sentF = CompletableFuture.supplyAsync(() -> col.aggregate(sentimentsPipeline).into(new ArrayList<>()));
CompletableFuture.allOf(topicsF, posF, neF, sentF).join();
List<Topic> aggregatedTopics = topicsF.join().stream()
.map(d -> new Topic(d.getString("_id"), d.getDouble("totalScore"), null))
.collect(Collectors.toList());
List<Token> aggregatedPOS = posF.join().stream()
.map(d -> new Token(d.getString("_id"), String.valueOf(d.getInteger("count")), ""))
.collect(Collectors.toList());
Map<String, Map<String, Integer>> aggregatedNE = new HashMap<>();
neF.join().forEach(d -> {
List<Document> entities = d.getList("entities", Document.class);
Map<String, Integer> 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<Sentiment> 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!
*/

View file

@ -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

View file

@ -0,0 +1,52 @@
<head>
<link rel="stylesheet" href="index.css">
<title>Parliament Explorer</title>
</head>
<#include "header.ftl">
<script src="https://d3js.org/d3.v7.min.js"></script>
<body>
<h2>Chart Sortiment</h2>
<div class="chart-container">
<div class="chart">
<#if aggregatedTopics?? && (aggregatedTopics?size gt 0)>
<h3>Topics Information (als Bubble Chart)</h3>
<#assign condenseTopicInformation = aggregatedTopics>
<#include "topicsBubbleChart.ftl">
<#else>
<h3>Keine Topics Information verfügbar</h3>
</#if>
</div>
<div class="chart">
<#if aggregatedPOS?? && (aggregatedPOS?size gt 0)>
<h3>POS Information (als Bar Chart)</h3>
<#assign posList = aggregatedPOS>
<#include "posBarChart.ftl">
<#else>
<h3>Keine POS Information verfügbar</h3>
</#if>
</div>
<div class="chart">
<#if aggregatedSentiment?? && (aggregatedSentiment?size gt 0)>
<h3>Sentiments Information (als Radar Chart)</h3>
<#-- Wrap the aggregated sentiment map in a list so that the chart partial can iterate -->
<#assign sentiments = [aggregatedSentiment]>
<#include "sentimentsRadarChart.ftl">
<#else>
<h3>Keine Sentiments Information verfügbar</h3>
</#if>
</div>
<div class="chart">
<#if aggregatedNE?? && (aggregatedNE?size gt 0)>
<h3>Named Entities Information (als Sunburst Chart)</h3>
<#assign neMap = aggregatedNE>
<#include "namedEntitiesSunburstChart.ftl">
<#else>
<h3>Keine Named Entities Information verfügbar</h3>
</#if>
</div>
</div>
</body>
<#include "footer.ftl">

View file

@ -12,6 +12,8 @@
<br>
<a href="/reden">Reden</a>
<br>
<a href="/charts">Charts</a>
<br>
<a href="/export">Exportieren</a>
<br>
<a href="/about">Über</a>

View file

@ -15,7 +15,7 @@
posData.push({ pos: "No Data", count: 0 });
</#if>
console.log("Final POS Data being used:", posData);
console.log("POS Data:", posData);
var svg = d3.select("#posBarchart")
.attr("width", barChartWidth)

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

Before After
Before After

View file

@ -8,6 +8,8 @@
</#list>
];
console.log("Topics Data:", topicsData);
const topics_bc_width = 1000;
const topics_bc_height = 800;

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 2.6 KiB

Before After
Before After