11 changed files with 0 additions and 931 deletions
@ -1,214 +0,0 @@ |
|||||
package com.project.report; |
|
||||
|
|
||||
import com.project.analyzer.PostAnalyzer; |
|
||||
import com.project.model.PostInfo; |
|
||||
|
|
||||
import java.io.BufferedWriter; |
|
||||
import java.io.FileWriter; |
|
||||
import java.io.IOException; |
|
||||
import java.nio.charset.StandardCharsets; |
|
||||
import java.nio.file.Files; |
|
||||
import java.nio.file.Paths; |
|
||||
import java.time.LocalDateTime; |
|
||||
import java.time.format.DateTimeFormatter; |
|
||||
import java.util.Map; |
|
||||
|
|
||||
public class HTMLReportGenerator { |
|
||||
|
|
||||
private static final String OUTPUT_DIR = "d:\\java\\project\\reports"; |
|
||||
|
|
||||
public static void generateReport(PostAnalyzer analyzer) { |
|
||||
try { |
|
||||
Files.createDirectories(Paths.get(OUTPUT_DIR)); |
|
||||
|
|
||||
String timestamp = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMdd_HHmmss")); |
|
||||
String filename = "report_" + timestamp + ".html"; |
|
||||
String filepath = OUTPUT_DIR + "/" + filename; |
|
||||
|
|
||||
try (BufferedWriter writer = new BufferedWriter( |
|
||||
new FileWriter(filepath, StandardCharsets.UTF_8))) { |
|
||||
|
|
||||
writer.write(generateHTMLContent(analyzer)); |
|
||||
} |
|
||||
|
|
||||
System.out.println("HTML报告已生成: " + filepath); |
|
||||
|
|
||||
} catch (IOException e) { |
|
||||
System.err.println("生成HTML报告时出错: " + e.getMessage()); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
private static String generateHTMLContent(PostAnalyzer analyzer) { |
|
||||
StringBuilder html = new StringBuilder(); |
|
||||
|
|
||||
html.append("<!DOCTYPE html>\n"); |
|
||||
html.append("<html lang=\"zh-CN\">\n"); |
|
||||
html.append("<head>\n"); |
|
||||
html.append(" <meta charset=\"UTF-8\">\n"); |
|
||||
html.append(" <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n"); |
|
||||
html.append(" <title>图文帖子数据分析报告</title>\n"); |
|
||||
html.append(" <style>\n"); |
|
||||
html.append(" * { margin: 0; padding: 0; box-sizing: border-box; }\n"); |
|
||||
html.append(" body { font-family: 'Microsoft YaHei', Arial, sans-serif; background: #f5f5f5; padding: 20px; }\n"); |
|
||||
html.append(" .container { max-width: 1200px; margin: 0 auto; background: white; padding: 30px; border-radius: 10px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); }\n"); |
|
||||
html.append(" h1 { color: #333; text-align: center; margin-bottom: 10px; }\n"); |
|
||||
html.append(" .subtitle { color: #666; text-align: center; margin-bottom: 30px; font-size: 14px; }\n"); |
|
||||
html.append(" .section { margin-bottom: 40px; }\n"); |
|
||||
html.append(" .section h2 { color: #2c3e50; border-bottom: 3px solid #3498db; padding-bottom: 10px; margin-bottom: 20px; }\n"); |
|
||||
html.append(" table { width: 100%; border-collapse: collapse; margin-bottom: 20px; }\n"); |
|
||||
html.append(" th, td { padding: 12px; text-align: left; border-bottom: 1px solid #ddd; }\n"); |
|
||||
html.append(" th { background: #3498db; color: white; font-weight: bold; }\n"); |
|
||||
html.append(" tr:hover { background: #f8f9fa; }\n"); |
|
||||
html.append(" .stat-card { display: inline-block; width: 200px; padding: 20px; margin: 10px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; border-radius: 10px; text-align: center; }\n"); |
|
||||
html.append(" .stat-card h3 { font-size: 36px; margin-bottom: 10px; }\n"); |
|
||||
html.append(" .stat-card p { font-size: 14px; opacity: 0.9; }\n"); |
|
||||
html.append(" .chart-container { text-align: center; margin: 20px 0; }\n"); |
|
||||
html.append(" .chart-container img { max-width: 100%; height: auto; border: 1px solid #ddd; border-radius: 5px; }\n"); |
|
||||
html.append(" .summary { background: #e8f4f8; padding: 20px; border-radius: 10px; margin-bottom: 30px; }\n"); |
|
||||
html.append(" .summary h3 { color: #2c3e50; margin-bottom: 15px; }\n"); |
|
||||
html.append(" .summary ul { list-style-position: inside; color: #555; }\n"); |
|
||||
html.append(" .summary li { margin: 8px 0; }\n"); |
|
||||
html.append(" </style>\n"); |
|
||||
html.append("</head>\n"); |
|
||||
html.append("<body>\n"); |
|
||||
html.append(" <div class=\"container\">\n"); |
|
||||
html.append(" <h1>图文帖子数据分析报告</h1>\n"); |
|
||||
html.append(" <p class=\"subtitle\">生成时间: ").append(LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))).append("</p>\n"); |
|
||||
|
|
||||
html.append(generateSummarySection(analyzer)); |
|
||||
html.append(generateSentimentSection(analyzer)); |
|
||||
html.append(generateEngagementSection(analyzer)); |
|
||||
html.append(generateAuthorSection(analyzer)); |
|
||||
html.append(generateChartsSection()); |
|
||||
|
|
||||
html.append(" </div>\n"); |
|
||||
html.append("</body>\n"); |
|
||||
html.append("</html>"); |
|
||||
|
|
||||
return html.toString(); |
|
||||
} |
|
||||
|
|
||||
private static String generateSummarySection(PostAnalyzer analyzer) { |
|
||||
StringBuilder section = new StringBuilder(); |
|
||||
|
|
||||
int totalPosts = analyzer.getPosts().size(); |
|
||||
double avgLikes = analyzer.getPosts().stream() |
|
||||
.mapToInt(PostInfo::getLikeCount) |
|
||||
.average() |
|
||||
.orElse(0); |
|
||||
|
|
||||
section.append(" <div class=\"section\">\n"); |
|
||||
section.append(" <div class=\"stat-card\">\n"); |
|
||||
section.append(" <h3>").append(totalPosts).append("</h3>\n"); |
|
||||
section.append(" <p>帖子总数</p>\n"); |
|
||||
section.append(" </div>\n"); |
|
||||
section.append(" <div class=\"stat-card\">\n"); |
|
||||
section.append(" <h3>").append(String.format("%.1f", avgLikes)).append("</h3>\n"); |
|
||||
section.append(" <p>平均点赞</p>\n"); |
|
||||
section.append(" </div>\n"); |
|
||||
section.append(" </div>\n"); |
|
||||
|
|
||||
section.append(" <div class=\"summary\">\n"); |
|
||||
section.append(" <h3>分析摘要</h3>\n"); |
|
||||
section.append(" <ul>\n"); |
|
||||
section.append(" <li>本次分析共收集 ").append(totalPosts).append(" 条图文帖子数据</li>\n"); |
|
||||
section.append(" <li>数据来源:D:\计量经济学\计量实验资料及作业要求\计量实验资料及作业要求\图文帖子原始信息计量实验使用</li>\n"); |
|
||||
section.append(" <li>分析内容包括情感倾向分布、互动指标、热门作者等多个维度</li>\n"); |
|
||||
section.append(" <li>通过数据可视化展示分析结果,便于直观理解</li>\n"); |
|
||||
section.append(" </ul>\n"); |
|
||||
section.append(" </div>\n"); |
|
||||
|
|
||||
return section.toString(); |
|
||||
} |
|
||||
|
|
||||
private static String generateSentimentSection(PostAnalyzer analyzer) { |
|
||||
StringBuilder section = new StringBuilder(); |
|
||||
Map<String, Long> sentimentData = analyzer.getSentimentDistributionData(); |
|
||||
|
|
||||
section.append(" <div class=\"section\">\n"); |
|
||||
section.append(" <h2>情感倾向分布分析</h2>\n"); |
|
||||
section.append(" <table>\n"); |
|
||||
section.append(" <tr><th>情感倾向</th><th>帖子数量</th><th>占比</th></tr>\n"); |
|
||||
|
|
||||
long total = sentimentData.values().stream().mapToLong(Long::longValue).sum(); |
|
||||
|
|
||||
for (Map.Entry<String, Long> entry : sentimentData.entrySet()) { |
|
||||
double percent = (entry.getValue() * 100.0) / total; |
|
||||
section.append(" <tr><td>").append(entry.getKey()) |
|
||||
.append("</td><td>").append(entry.getValue()) |
|
||||
.append("</td><td>").append(String.format("%.1f%%", percent)) |
|
||||
.append("</td></tr>\n"); |
|
||||
} |
|
||||
|
|
||||
section.append(" </table>\n"); |
|
||||
section.append(" </div>\n"); |
|
||||
|
|
||||
return section.toString(); |
|
||||
} |
|
||||
|
|
||||
private static String generateEngagementSection(PostAnalyzer analyzer) { |
|
||||
StringBuilder section = new StringBuilder(); |
|
||||
Map<String, Double> engagementData = analyzer.getEngagementData(); |
|
||||
|
|
||||
section.append(" <div class=\"section\">\n"); |
|
||||
section.append(" <h2>互动指标分析</h2>\n"); |
|
||||
section.append(" <table>\n"); |
|
||||
section.append(" <tr><th>指标</th><th>平均值</th></tr>\n"); |
|
||||
|
|
||||
for (Map.Entry<String, Double> entry : engagementData.entrySet()) { |
|
||||
section.append(" <tr><td>").append(entry.getKey()) |
|
||||
.append("</td><td>").append(String.format("%.1f", entry.getValue())) |
|
||||
.append("</td></tr>\n"); |
|
||||
} |
|
||||
|
|
||||
section.append(" </table>\n"); |
|
||||
section.append(" </div>\n"); |
|
||||
|
|
||||
return section.toString(); |
|
||||
} |
|
||||
|
|
||||
private static String generateAuthorSection(PostAnalyzer analyzer) { |
|
||||
StringBuilder section = new StringBuilder(); |
|
||||
Map<String, Integer> authorData = analyzer.getAuthorPostCount(); |
|
||||
|
|
||||
section.append(" <div class=\"section\">\n"); |
|
||||
section.append(" <h2>热门作者排行TOP10</h2>\n"); |
|
||||
section.append(" <table>\n"); |
|
||||
section.append(" <tr><th>排名</th><th>作者</th><th>帖子数量</th></tr>\n"); |
|
||||
|
|
||||
int rank = 1; |
|
||||
for (Map.Entry<String, Integer> entry : authorData.entrySet()) { |
|
||||
section.append(" <tr><td>").append(rank++) |
|
||||
.append("</td><td>").append(entry.getKey()) |
|
||||
.append("</td><td>").append(entry.getValue()) |
|
||||
.append("</td></tr>\n"); |
|
||||
} |
|
||||
|
|
||||
section.append(" </table>\n"); |
|
||||
section.append(" </div>\n"); |
|
||||
|
|
||||
return section.toString(); |
|
||||
} |
|
||||
|
|
||||
private static String generateChartsSection() { |
|
||||
StringBuilder section = new StringBuilder(); |
|
||||
|
|
||||
section.append(" <div class=\"section\">\n"); |
|
||||
section.append(" <h2>数据可视化图表</h2>\n"); |
|
||||
section.append(" <div class=\"chart-container\">\n"); |
|
||||
section.append(" <h3>情感倾向分布</h3>\n"); |
|
||||
section.append(" <img src=\"../charts/sentiment_distribution.png\" alt=\"情感倾向分布图\">\n"); |
|
||||
section.append(" </div>\n"); |
|
||||
section.append(" <div class=\"chart-container\">\n"); |
|
||||
section.append(" <h3>互动指标分析</h3>\n"); |
|
||||
section.append(" <img src=\"../charts/engagement_metrics.png\" alt=\"互动指标图\">\n"); |
|
||||
section.append(" </div>\n"); |
|
||||
section.append(" <div class=\"chart-container\">\n"); |
|
||||
section.append(" <h3>热门作者排行</h3>\n"); |
|
||||
section.append(" <img src=\"../charts/author_ranking.png\" alt=\"作者排行图\">\n"); |
|
||||
section.append(" </div>\n"); |
|
||||
section.append(" </div>\n"); |
|
||||
|
|
||||
return section.toString(); |
|
||||
} |
|
||||
} |
|
||||
@ -1,67 +0,0 @@ |
|||||
package com.project; |
|
||||
|
|
||||
import com.project.analyzer.PostAnalyzer; |
|
||||
import com.project.chart.SimpleChartGenerator; |
|
||||
import com.project.model.PostInfo; |
|
||||
import com.project.reader.ExcelReader; |
|
||||
import com.project.report.HTMLReportGenerator; |
|
||||
import com.project.storage.DataStorage; |
|
||||
import com.project.util.DataCleaner; |
|
||||
|
|
||||
import java.util.List; |
|
||||
import java.util.Scanner; |
|
||||
|
|
||||
public class Main { |
|
||||
|
|
||||
public static void main(String[] args) { |
|
||||
System.out.println("========================================"); |
|
||||
System.out.println(" Java网络爬虫与数据分析系统"); |
|
||||
System.out.println("========================================\n"); |
|
||||
|
|
||||
String dataFilePath = "D:\\计量经济学\\计量实验资料及作业要求\\计量实验资料及作业要求\\图文帖子原始信息计量实验使用.xlsx"; |
|
||||
String outputDir = "d:\\java\\project\\data"; |
|
||||
int maxRows = 300; |
|
||||
|
|
||||
try { |
|
||||
System.out.println("开始读取本地数据文件..."); |
|
||||
System.out.println("数据文件: " + dataFilePath); |
|
||||
System.out.println("读取前 " + maxRows + " 条数据"); |
|
||||
|
|
||||
List<PostInfo> rawPosts = ExcelReader.readExcelData(dataFilePath, maxRows); |
|
||||
|
|
||||
if (rawPosts.isEmpty()) { |
|
||||
System.out.println("未获取到任何数据,程序退出"); |
|
||||
return; |
|
||||
} |
|
||||
|
|
||||
System.out.println("\n开始数据清洗..."); |
|
||||
List<PostInfo> cleanedPosts = DataCleaner.cleanPosts(rawPosts); |
|
||||
|
|
||||
System.out.println("\n保存数据到文件..."); |
|
||||
DataStorage.saveToCSV(cleanedPosts, outputDir); |
|
||||
DataStorage.saveToJSON(cleanedPosts, outputDir); |
|
||||
|
|
||||
System.out.println("\n开始数据分析..."); |
|
||||
PostAnalyzer analyzer = new PostAnalyzer(cleanedPosts); |
|
||||
analyzer.analyzeAll(); |
|
||||
|
|
||||
System.out.println("\n生成图表..."); |
|
||||
SimpleChartGenerator.generateAllCharts(analyzer); |
|
||||
|
|
||||
System.out.println("\n生成HTML报告..."); |
|
||||
HTMLReportGenerator.generateReport(analyzer); |
|
||||
|
|
||||
System.out.println("\n========================================"); |
|
||||
System.out.println(" 程序执行完成!"); |
|
||||
System.out.println("========================================"); |
|
||||
System.out.println("\n输出文件位置:"); |
|
||||
System.out.println("- 数据文件: " + outputDir); |
|
||||
System.out.println("- 图表文件: d:\\java\\project\\charts"); |
|
||||
System.out.println("- 报告文件: d:\\java\\project\\reports"); |
|
||||
|
|
||||
} catch (Exception e) { |
|
||||
System.err.println("程序执行出错: " + e.getMessage()); |
|
||||
e.printStackTrace(); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,200 +0,0 @@ |
|||||
package com.project.analyzer; |
|
||||
|
|
||||
import com.project.model.PostInfo; |
|
||||
|
|
||||
import java.util.*; |
|
||||
import java.util.stream.Collectors; |
|
||||
|
|
||||
public class PostAnalyzer { |
|
||||
|
|
||||
private final List<PostInfo> posts; |
|
||||
|
|
||||
public PostAnalyzer(List<PostInfo> posts) { |
|
||||
this.posts = posts; |
|
||||
} |
|
||||
|
|
||||
public List<PostInfo> getPosts() { |
|
||||
return posts; |
|
||||
} |
|
||||
|
|
||||
public void analyzeAll() { |
|
||||
System.out.println("\n========== 数据分析报告 ==========\n"); |
|
||||
|
|
||||
analyzeSentimentDistribution(); |
|
||||
analyzeEngagementMetrics(); |
|
||||
analyzePopularAuthors(); |
|
||||
analyzeContentLength(); |
|
||||
analyzeTemporalTrends(); |
|
||||
|
|
||||
System.out.println("\n========== 分析完成 ==========\n"); |
|
||||
} |
|
||||
|
|
||||
public void analyzeSentimentDistribution() { |
|
||||
System.out.println("【情感倾向分布分析】"); |
|
||||
System.out.println("----------------------------------------"); |
|
||||
|
|
||||
Map<String, Long> sentimentCounts = posts.stream() |
|
||||
.collect(Collectors.groupingBy( |
|
||||
PostInfo::getSentiment, |
|
||||
Collectors.counting() |
|
||||
)); |
|
||||
|
|
||||
System.out.printf("%-20s %s%n", "情感倾向", "帖子数量"); |
|
||||
System.out.println("----------------------------------------"); |
|
||||
|
|
||||
sentimentCounts.entrySet().stream() |
|
||||
.sorted(Map.Entry.<String, Long>comparingByValue().reversed()) |
|
||||
.forEach(entry -> System.out.printf("%-20s %d%n", entry.getKey(), entry.getValue())); |
|
||||
|
|
||||
System.out.println(); |
|
||||
} |
|
||||
|
|
||||
public void analyzeEngagementMetrics() { |
|
||||
System.out.println("【互动指标分析】"); |
|
||||
System.out.println("----------------------------------------"); |
|
||||
|
|
||||
double avgLikes = posts.stream() |
|
||||
.mapToInt(PostInfo::getLikeCount) |
|
||||
.average() |
|
||||
.orElse(0); |
|
||||
|
|
||||
double avgComments = posts.stream() |
|
||||
.mapToInt(PostInfo::getCommentCount) |
|
||||
.average() |
|
||||
.orElse(0); |
|
||||
|
|
||||
double avgViews = posts.stream() |
|
||||
.mapToInt(PostInfo::getViewCount) |
|
||||
.average() |
|
||||
.orElse(0); |
|
||||
|
|
||||
System.out.printf("平均点赞数: %.1f%n", avgLikes); |
|
||||
System.out.printf("平均评论数: %.1f%n", avgComments); |
|
||||
System.out.printf("平均浏览量: %.1f%n", avgViews); |
|
||||
|
|
||||
System.out.println(); |
|
||||
} |
|
||||
|
|
||||
public void analyzePopularAuthors() { |
|
||||
System.out.println("【热门作者排行】"); |
|
||||
System.out.println("----------------------------------------"); |
|
||||
System.out.printf("%-30s %10s %10s %10s%n", "作者", "帖子数", "总点赞", "总评论"); |
|
||||
System.out.println("----------------------------------------"); |
|
||||
|
|
||||
Map<String, List<PostInfo>> authorPosts = posts.stream() |
|
||||
.collect(Collectors.groupingBy(PostInfo::getAuthor)); |
|
||||
|
|
||||
authorPosts.entrySet().stream() |
|
||||
.sorted(Map.Entry.<String, List<PostInfo>>comparingByValue((a, b) -> b.size() - a.size())) |
|
||||
.limit(10) |
|
||||
.forEach(entry -> { |
|
||||
String author = entry.getKey(); |
|
||||
List<PostInfo> authorPostList = entry.getValue(); |
|
||||
int postCount = authorPostList.size(); |
|
||||
int totalLikes = authorPostList.stream().mapToInt(PostInfo::getLikeCount).sum(); |
|
||||
int totalComments = authorPostList.stream().mapToInt(PostInfo::getCommentCount).sum(); |
|
||||
|
|
||||
System.out.printf("%-30s %10d %10d %10d%n", |
|
||||
author.length() > 28 ? author.substring(0, 28) : author, |
|
||||
postCount, totalLikes, totalComments); |
|
||||
}); |
|
||||
|
|
||||
System.out.println(); |
|
||||
} |
|
||||
|
|
||||
public void analyzeContentLength() { |
|
||||
System.out.println("【内容长度分析】"); |
|
||||
System.out.println("----------------------------------------"); |
|
||||
|
|
||||
double avgLength = posts.stream() |
|
||||
.mapToInt(post -> post.getContent().length()) |
|
||||
.average() |
|
||||
.orElse(0); |
|
||||
|
|
||||
int maxLength = posts.stream() |
|
||||
.mapToInt(post -> post.getContent().length()) |
|
||||
.max() |
|
||||
.orElse(0); |
|
||||
|
|
||||
int minLength = posts.stream() |
|
||||
.mapToInt(post -> post.getContent().length()) |
|
||||
.min() |
|
||||
.orElse(0); |
|
||||
|
|
||||
System.out.printf("平均内容长度: %.1f 字符%n", avgLength); |
|
||||
System.out.printf("最长内容: %d 字符%n", maxLength); |
|
||||
System.out.printf("最短内容: %d 字符%n", minLength); |
|
||||
|
|
||||
System.out.println(); |
|
||||
} |
|
||||
|
|
||||
public void analyzeTemporalTrends() { |
|
||||
System.out.println("【时间趋势分析】"); |
|
||||
System.out.println("----------------------------------------"); |
|
||||
|
|
||||
Map<String, Long> monthlyPosts = posts.stream() |
|
||||
.filter(post -> post.getPostDate() != null) |
|
||||
.collect(Collectors.groupingBy( |
|
||||
post -> post.getPostDate().format(java.time.format.DateTimeFormatter.ofPattern("yyyy-MM")), |
|
||||
Collectors.counting() |
|
||||
)); |
|
||||
|
|
||||
System.out.printf("%-10s %s%n", "月份", "帖子数量"); |
|
||||
System.out.println("----------------------------------------"); |
|
||||
|
|
||||
monthlyPosts.entrySet().stream() |
|
||||
.sorted(Map.Entry.comparingByKey()) |
|
||||
.forEach(entry -> System.out.printf("%-10s %d%n", entry.getKey(), entry.getValue())); |
|
||||
|
|
||||
System.out.println(); |
|
||||
} |
|
||||
|
|
||||
public Map<String, Long> getSentimentDistributionData() { |
|
||||
return posts.stream() |
|
||||
.collect(Collectors.groupingBy( |
|
||||
PostInfo::getSentiment, |
|
||||
Collectors.counting() |
|
||||
)); |
|
||||
} |
|
||||
|
|
||||
public Map<String, Double> getEngagementData() { |
|
||||
Map<String, Double> engagementData = new LinkedHashMap<>(); |
|
||||
|
|
||||
double avgLikes = posts.stream() |
|
||||
.mapToInt(PostInfo::getLikeCount) |
|
||||
.average() |
|
||||
.orElse(0); |
|
||||
|
|
||||
double avgComments = posts.stream() |
|
||||
.mapToInt(PostInfo::getCommentCount) |
|
||||
.average() |
|
||||
.orElse(0); |
|
||||
|
|
||||
double avgViews = posts.stream() |
|
||||
.mapToInt(PostInfo::getViewCount) |
|
||||
.average() |
|
||||
.orElse(0); |
|
||||
|
|
||||
engagementData.put("点赞", avgLikes); |
|
||||
engagementData.put("评论", avgComments); |
|
||||
engagementData.put("浏览", avgViews); |
|
||||
|
|
||||
return engagementData; |
|
||||
} |
|
||||
|
|
||||
public Map<String, Integer> getAuthorPostCount() { |
|
||||
return posts.stream() |
|
||||
.collect(Collectors.groupingBy( |
|
||||
PostInfo::getAuthor, |
|
||||
Collectors.summingInt(post -> 1) |
|
||||
)).entrySet().stream() |
|
||||
.sorted(Map.Entry.<String, Integer>comparingByValue().reversed()) |
|
||||
.limit(10) |
|
||||
.collect(Collectors.toMap( |
|
||||
Map.Entry::getKey, |
|
||||
Map.Entry::getValue, |
|
||||
(e1, e2) -> e1, |
|
||||
LinkedHashMap::new |
|
||||
)); |
|
||||
} |
|
||||
} |
|
||||
@ -1,2 +0,0 @@ |
|||||
# java |
|
||||
|
|
||||
@ -1,165 +0,0 @@ |
|||||
package com.project.chart; |
|
||||
|
|
||||
import com.project.analyzer.PostAnalyzer; |
|
||||
|
|
||||
import java.awt.*; |
|
||||
import java.awt.image.BufferedImage; |
|
||||
import java.io.File; |
|
||||
import java.io.IOException; |
|
||||
import java.nio.file.Files; |
|
||||
import java.nio.file.Paths; |
|
||||
import java.util.Map; |
|
||||
import javax.imageio.ImageIO; |
|
||||
|
|
||||
public class SimpleChartGenerator { |
|
||||
|
|
||||
private static final String OUTPUT_DIR = "d:\\java\\project\\charts"; |
|
||||
private static final int WIDTH = 800; |
|
||||
private static final int HEIGHT = 600; |
|
||||
|
|
||||
public static void generateAllCharts(PostAnalyzer analyzer) { |
|
||||
try { |
|
||||
Files.createDirectories(Paths.get(OUTPUT_DIR)); |
|
||||
|
|
||||
generateSentimentChart(analyzer); |
|
||||
generateEngagementChart(analyzer); |
|
||||
generateAuthorChart(analyzer); |
|
||||
|
|
||||
System.out.println("\n所有图表已生成,保存在: " + OUTPUT_DIR); |
|
||||
|
|
||||
} catch (IOException e) { |
|
||||
System.err.println("创建图表目录时出错: " + e.getMessage()); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
public static void generateSentimentChart(PostAnalyzer analyzer) { |
|
||||
Map<String, Long> data = analyzer.getSentimentDistributionData(); |
|
||||
|
|
||||
BufferedImage image = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_RGB); |
|
||||
Graphics2D g2d = image.createGraphics(); |
|
||||
|
|
||||
g2d.setColor(Color.WHITE); |
|
||||
g2d.fillRect(0, 0, WIDTH, HEIGHT); |
|
||||
|
|
||||
g2d.setColor(Color.BLACK); |
|
||||
g2d.setFont(new Font("宋体", Font.BOLD, 24)); |
|
||||
g2d.drawString("情感倾向分布", 300, 40); |
|
||||
|
|
||||
int barWidth = 150; |
|
||||
int startX = 200; |
|
||||
int startY = 500; |
|
||||
int maxHeight = 400; |
|
||||
|
|
||||
long maxValue = data.values().stream().max(Long::compare).orElse(1L); |
|
||||
|
|
||||
int index = 0; |
|
||||
for (Map.Entry<String, Long> entry : data.entrySet()) { |
|
||||
int barHeight = (int) ((entry.getValue() * 1.0 / maxValue) * maxHeight); |
|
||||
|
|
||||
g2d.setColor(new Color(70, 130, 180)); |
|
||||
g2d.fillRect(startX + index * (barWidth + 50), startY - barHeight, barWidth, barHeight); |
|
||||
|
|
||||
g2d.setColor(Color.BLACK); |
|
||||
g2d.setFont(new Font("宋体", Font.PLAIN, 14)); |
|
||||
g2d.drawString(entry.getKey(), startX + index * (barWidth + 50) + 50, startY + 20); |
|
||||
g2d.drawString(String.valueOf(entry.getValue()), startX + index * (barWidth + 50) + 60, startY - barHeight - 5); |
|
||||
|
|
||||
index++; |
|
||||
} |
|
||||
|
|
||||
g2d.dispose(); |
|
||||
saveImage(image, "sentiment_distribution.png"); |
|
||||
} |
|
||||
|
|
||||
public static void generateEngagementChart(PostAnalyzer analyzer) { |
|
||||
Map<String, Double> data = analyzer.getEngagementData(); |
|
||||
|
|
||||
BufferedImage image = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_RGB); |
|
||||
Graphics2D g2d = image.createGraphics(); |
|
||||
|
|
||||
g2d.setColor(Color.WHITE); |
|
||||
g2d.fillRect(0, 0, WIDTH, HEIGHT); |
|
||||
|
|
||||
g2d.setColor(Color.BLACK); |
|
||||
g2d.setFont(new Font("宋体", Font.BOLD, 24)); |
|
||||
g2d.drawString("互动指标分析", 300, 40); |
|
||||
|
|
||||
int barWidth = 150; |
|
||||
int startX = 200; |
|
||||
int startY = 500; |
|
||||
int maxHeight = 400; |
|
||||
|
|
||||
double maxValue = data.values().stream().max(Double::compare).orElse(1.0); |
|
||||
|
|
||||
int index = 0; |
|
||||
for (Map.Entry<String, Double> entry : data.entrySet()) { |
|
||||
int barHeight = (int) ((entry.getValue() / maxValue) * maxHeight); |
|
||||
|
|
||||
g2d.setColor(new Color(60, 179, 113)); |
|
||||
g2d.fillRect(startX + index * (barWidth + 50), startY - barHeight, barWidth, barHeight); |
|
||||
|
|
||||
g2d.setColor(Color.BLACK); |
|
||||
g2d.setFont(new Font("宋体", Font.PLAIN, 14)); |
|
||||
g2d.drawString(entry.getKey(), startX + index * (barWidth + 50) + 60, startY + 20); |
|
||||
g2d.drawString(String.format("%.1f", entry.getValue()), startX + index * (barWidth + 50) + 50, startY - barHeight - 5); |
|
||||
|
|
||||
index++; |
|
||||
} |
|
||||
|
|
||||
g2d.dispose(); |
|
||||
saveImage(image, "engagement_metrics.png"); |
|
||||
} |
|
||||
|
|
||||
public static void generateAuthorChart(PostAnalyzer analyzer) { |
|
||||
Map<String, Integer> data = analyzer.getAuthorPostCount(); |
|
||||
|
|
||||
BufferedImage image = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_RGB); |
|
||||
Graphics2D g2d = image.createGraphics(); |
|
||||
|
|
||||
g2d.setColor(Color.WHITE); |
|
||||
g2d.fillRect(0, 0, WIDTH, HEIGHT); |
|
||||
|
|
||||
g2d.setColor(Color.BLACK); |
|
||||
g2d.setFont(new Font("宋体", Font.BOLD, 24)); |
|
||||
g2d.drawString("热门作者排行TOP10", 280, 40); |
|
||||
|
|
||||
int barHeight = 35; |
|
||||
int startY = 80; |
|
||||
int startX = 200; |
|
||||
int maxWidth = 500; |
|
||||
|
|
||||
int maxValue = data.values().stream().max(Integer::compare).orElse(1); |
|
||||
|
|
||||
int index = 0; |
|
||||
for (Map.Entry<String, Integer> entry : data.entrySet()) { |
|
||||
int barWidth = (int) ((entry.getValue() * 1.0 / maxValue) * maxWidth); |
|
||||
|
|
||||
g2d.setColor(new Color(255, 140, 0)); |
|
||||
g2d.fillRect(startX, startY + index * (barHeight + 10), barWidth, barHeight); |
|
||||
|
|
||||
g2d.setColor(Color.BLACK); |
|
||||
g2d.setFont(new Font("宋体", Font.PLAIN, 12)); |
|
||||
String author = entry.getKey(); |
|
||||
if (author.length() > 15) { |
|
||||
author = author.substring(0, 15) + "..."; |
|
||||
} |
|
||||
g2d.drawString(author, 50, startY + index * (barHeight + 10) + 23); |
|
||||
g2d.drawString(String.valueOf(entry.getValue()), startX + barWidth + 10, startY + index * (barHeight + 10) + 23); |
|
||||
|
|
||||
index++; |
|
||||
} |
|
||||
|
|
||||
g2d.dispose(); |
|
||||
saveImage(image, "author_ranking.png"); |
|
||||
} |
|
||||
|
|
||||
private static void saveImage(BufferedImage image, String filename) { |
|
||||
try { |
|
||||
File file = new File(OUTPUT_DIR, filename); |
|
||||
ImageIO.write(image, "PNG", file); |
|
||||
System.out.println("图表已保存: " + file.getAbsolutePath()); |
|
||||
} catch (IOException e) { |
|
||||
System.err.println("保存图表失败: " + e.getMessage()); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,34 +0,0 @@ |
|||||
public class DataCleaner { |
|
||||
public static void main(String[] args) { |
|
||||
int[] sensorData = {85, -5, 92, 0, 105, 999, 88, 76}; |
|
||||
|
|
||||
int validSum = 0; // 有效数据总和
|
|
||||
int validCount = 0; // 有效数据个数
|
|
||||
|
|
||||
// 遍历数组,按规则处理数据
|
|
||||
for (int num : sensorData) { |
|
||||
// 规则3:致命错误,遇到999直接终止流程
|
|
||||
if (num == 999) { |
|
||||
System.out.println("致命错误:传感器掉线,终止处理"); |
|
||||
break; |
|
||||
} |
|
||||
// 规则2:无效数据(0/负数/大于100且不是999),跳过并告警
|
|
||||
if (num <= 0 || num > 100) { |
|
||||
System.out.println("警告:发现越界数据[" + num + "],已跳过"); |
|
||||
continue; |
|
||||
} |
|
||||
// 规则1:正常范围(1-100),计入总和与计数
|
|
||||
validSum += num; |
|
||||
validCount++; |
|
||||
} |
|
||||
|
|
||||
// 规则4:最终输出平均值
|
|
||||
if (validCount > 0) { |
|
||||
// 避免整数除法陷阱:将总和转为double再计算
|
|
||||
double average = (double) validSum / validCount; |
|
||||
System.out.printf("有效数据的平均值为:%.2f%n", average); |
|
||||
} else { |
|
||||
System.out.println("无有效数据"); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,63 +0,0 @@ |
|||||
public class BankAccount { |
|
||||
// 私有属性
|
|
||||
private final String accountNumber; |
|
||||
private String ownerName; |
|
||||
private double balance; |
|
||||
|
|
||||
// 构造方法
|
|
||||
public BankAccount(String accountNumber, String ownerName) { |
|
||||
this.accountNumber = accountNumber; |
|
||||
this.ownerName = ownerName; |
|
||||
this.balance = 0.0; |
|
||||
} |
|
||||
|
|
||||
// Getter 方法
|
|
||||
public String getAccountNumber() { |
|
||||
return accountNumber; |
|
||||
} |
|
||||
|
|
||||
public String getOwnerName() { |
|
||||
return ownerName; |
|
||||
} |
|
||||
|
|
||||
public double getBalance() { |
|
||||
return balance; |
|
||||
} |
|
||||
|
|
||||
// Setter 方法
|
|
||||
public void setOwnerName(String ownerName) { |
|
||||
this.ownerName = ownerName; |
|
||||
} |
|
||||
|
|
||||
// 存款操作
|
|
||||
public void deposit(double amount) { |
|
||||
if (amount > 0) { |
|
||||
balance += amount; |
|
||||
System.out.println("存款成功!当前余额:" + balance); |
|
||||
} else { |
|
||||
System.out.println("存款金额必须大于 0"); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
// 取款操作
|
|
||||
public void withdraw(double amount) { |
|
||||
if (amount > 0) { |
|
||||
if (amount <= balance) { |
|
||||
balance -= amount; |
|
||||
System.out.println("取款成功!当前余额:" + balance); |
|
||||
} else { |
|
||||
System.out.println("余额不足,无法取款"); |
|
||||
} |
|
||||
} else { |
|
||||
System.out.println("取款金额必须大于 0"); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
// 显示账户信息
|
|
||||
public void displayInfo() { |
|
||||
System.out.println("账号:" + accountNumber); |
|
||||
System.out.println("户主:" + ownerName); |
|
||||
System.out.println("余额:" + balance); |
|
||||
System.out.println(); |
|
||||
} |
|
||||
} |
|
||||
@ -1,154 +0,0 @@ |
|||||
// Car.java
|
|
||||
public class Car { |
|
||||
// 私有属性
|
|
||||
private final String licensePlate; |
|
||||
private String brand; |
|
||||
private String model; |
|
||||
private double dailyRent; |
|
||||
private boolean isRented; |
|
||||
|
|
||||
// 静态变量:统计车辆总数
|
|
||||
private static int totalCars = 0; |
|
||||
|
|
||||
// 全参构造方法
|
|
||||
public Car(String licensePlate, String brand, String model, double dailyRent) { |
|
||||
this.licensePlate = licensePlate; |
|
||||
this.brand = brand; |
|
||||
this.model = model; |
|
||||
setDailyRent(dailyRent); |
|
||||
this.isRented = false; |
|
||||
totalCars++; |
|
||||
} |
|
||||
|
|
||||
// 重载构造方法(日租金默认300)
|
|
||||
public Car(String licensePlate, String brand, String model) { |
|
||||
this(licensePlate, brand, model, 300.0); |
|
||||
} |
|
||||
|
|
||||
// Getter/Setter
|
|
||||
public String getLicensePlate() { |
|
||||
return licensePlate; |
|
||||
} |
|
||||
|
|
||||
public String getBrand() { |
|
||||
return brand; |
|
||||
} |
|
||||
|
|
||||
public void setBrand(String brand) { |
|
||||
this.brand = brand; |
|
||||
} |
|
||||
|
|
||||
public String getModel() { |
|
||||
return model; |
|
||||
} |
|
||||
|
|
||||
public void setModel(String model) { |
|
||||
this.model = model; |
|
||||
} |
|
||||
|
|
||||
public double getDailyRent() { |
|
||||
return dailyRent; |
|
||||
} |
|
||||
|
|
||||
public void setDailyRent(double dailyRent) { |
|
||||
if (dailyRent > 0) { |
|
||||
this.dailyRent = dailyRent; |
|
||||
} else { |
|
||||
System.out.println("日租金必须大于0,设置失败!"); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
public boolean isRented() { |
|
||||
return isRented; |
|
||||
} |
|
||||
|
|
||||
// 业务方法:租车
|
|
||||
public void rentCar() { |
|
||||
if (isRented) { |
|
||||
System.out.println("车辆已租出,无法再次租用!"); |
|
||||
} else { |
|
||||
isRented = true; |
|
||||
System.out.println("车辆已成功租出!"); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
public class Car { |
|
||||
private final String licensePlate; |
|
||||
private String brand; |
|
||||
private String model; |
|
||||
private double dailyRent; |
|
||||
private boolean isRented; |
|
||||
private static int totalCars = 0; |
|
||||
|
|
||||
// 全参构造方法
|
|
||||
public Car(String licensePlate, String brand, String model, double dailyRent) { |
|
||||
this.licensePlate = licensePlate; |
|
||||
this.brand = brand; |
|
||||
this.model = model; |
|
||||
setDailyRent(dailyRent); |
|
||||
this.isRented = false; |
|
||||
totalCars++; |
|
||||
} |
|
||||
|
|
||||
// 重载构造方法
|
|
||||
public Car(String licensePlate, String brand, String model) { |
|
||||
this(licensePlate, brand, model, 300.0); |
|
||||
} |
|
||||
|
|
||||
// Getter/Setter
|
|
||||
public String getLicensePlate() { return licensePlate; } |
|
||||
public String getBrand() { return brand; } |
|
||||
public void setBrand(String brand) { this.brand = brand; } |
|
||||
public String getModel() { return model; } |
|
||||
public void setModel(String model) { this.model = model; } |
|
||||
public double getDailyRent() { return dailyRent; } |
|
||||
|
|
||||
public void setDailyRent(double dailyRent) { |
|
||||
if (dailyRent > 0) { |
|
||||
this.dailyRent = dailyRent; |
|
||||
} else { |
|
||||
System.out.println("日租金必须大于0,设置失败!"); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
public boolean isRented() { return isRented; } |
|
||||
|
|
||||
// 租车方法
|
|
||||
public void rentCar() { |
|
||||
if (isRented) { |
|
||||
System.out.println("车辆已租出,无法再次租用!"); |
|
||||
} else { |
|
||||
isRented = true; |
|
||||
System.out.println("车辆已成功租出!"); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
// 还车方法
|
|
||||
public void returnCar() { |
|
||||
if (!isRented) { |
|
||||
System.out.println("车辆未被租用,无需归还!"); |
|
||||
} else { |
|
||||
isRented = false; |
|
||||
System.out.println("车辆已成功归还!"); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
// 计算租金
|
|
||||
public double calculateRent(int days) { |
|
||||
return dailyRent * days; |
|
||||
} |
|
||||
|
|
||||
// 静态方法:获取总车辆数
|
|
||||
public static int getTotalCars() { |
|
||||
return totalCars; |
|
||||
} |
|
||||
|
|
||||
// 打印车辆信息
|
|
||||
public void displayInfo() { |
|
||||
System.out.println("车牌号:" + licensePlate + |
|
||||
" | 品牌:" + brand + |
|
||||
" | 型号:" + model + |
|
||||
" | 日租金:" + dailyRent + |
|
||||
" | 状态:" + (isRented ? "已出租" : "可租")); |
|
||||
} |
|
||||
} |
|
||||
Binary file not shown.
@ -1,32 +0,0 @@ |
|||||
public class TestCar { |
|
||||
public static void main(String[] args) { |
|
||||
// 创建3个Car对象
|
|
||||
Car car1 = new Car("京A12345", "特斯拉", "Model 3", 280.0); |
|
||||
Car car2 = new Car("沪B67890", "比亚迪", "汉EV"); |
|
||||
Car car3 = new Car("粤C24680", "丰田", "凯美瑞", 220.0); |
|
||||
|
|
||||
// 输出车辆信息
|
|
||||
System.out.println("=== 所有车辆信息 ==="); |
|
||||
car1.displayInfo(); |
|
||||
car2.displayInfo(); |
|
||||
car3.displayInfo(); |
|
||||
System.out.println("总车辆数:" + Car.getTotalCars()); |
|
||||
|
|
||||
// 测试租车/还车
|
|
||||
System.out.println("\n=== 测试租车/还车 ==="); |
|
||||
car1.rentCar(); |
|
||||
car1.rentCar(); |
|
||||
car1.returnCar(); |
|
||||
car1.returnCar(); |
|
||||
|
|
||||
// 测试租金计算
|
|
||||
System.out.println("\n=== 测试租金计算 ==="); |
|
||||
double rent = car2.calculateRent(5); |
|
||||
System.out.println("租用5天总租金:" + rent + "元"); |
|
||||
|
|
||||
// 测试日租金校验
|
|
||||
System.out.println("\n=== 测试日租金校验 ==="); |
|
||||
car3.setDailyRent(-100.0); // 改为double类型字面量
|
|
||||
System.out.println("修改后日租金:" + car3.getDailyRent()); |
|
||||
} |
|
||||
} |
|
||||
Loading…
Reference in new issue