基于DeepSeek实现PDF嵌入SVG图片无损放大
1. PDF中效果图
2. 询问Deepseek进行代码书写,不断优化后结果
/**
* SVG工具类,用于生成价格趋势的SVG图表
*/
public class SvgUtils {
// SVG画布尺寸
private static final int WIDTH = 800;
private static final int HEIGHT = 500;
private static final int PADDING = 60;
// 生成SVG折线图
public static String generatePriceTrendSvg(List<PriceData> data, String title, String subtitle) {
if (data.isEmpty()) {
return "<svg xmlns='http://www.w3.org/2000/svg' width='" + WIDTH + "' height='" + HEIGHT + "'></svg>";
}
// 计算价格和时间的范围
double minPrice = data.stream().mapToDouble(PriceData::getPrice).min().orElse(0) * 0.8;
double maxPrice = data.stream().mapToDouble(PriceData::getPrice).max().orElse(1) * 1.2;
// 解析最小和最大日期
String minYearMonth = data.stream().map(PriceData::getYearmonth).min(String::compareTo).orElse("2024-06");
String maxYearMonth = data.stream().map(PriceData::getYearmonth).max(String::compareTo).orElse("2025-01");
// 构建SVG内容
StringBuilder svg = new StringBuilder();
svg.append(String.format("<svg xmlns='http://www.w3.org/2000/svg' width='%d' height='%d'>", WIDTH, HEIGHT));
// 画背景
svg.append(String.format("<rect width='%d' height='%d' fill='#ffffff'/>", WIDTH, HEIGHT));
// 添加标题和副标题(居中显示)
svg.append(String.format("<text x='%d' y='%d' font-size='20' font-family='Arial' fill='#333333' font-weight='bold' text-anchor='middle'>%s</text>",
WIDTH / 2, 30, title));
svg.append(String.format("<text x='%d' y='%d' font-size='14' font-family='Arial' fill='#666666' text-anchor='middle'>%s</text>",
WIDTH / 2, 50, subtitle));
// 画虚线网格线
svg.append(drawGrid(minYearMonth, maxYearMonth, minPrice, maxPrice));
// 画深色坐标轴
svg.append(drawAxis(minYearMonth, maxYearMonth, minPrice, maxPrice));
// 画浅色平滑折线
svg.append(drawSmoothLine(data, minYearMonth, maxYearMonth, minPrice, maxPrice));
svg.append("</svg>");
return svg.toString();
}
// 画虚线网格线
private static String drawGrid(String minYearMonth, String maxYearMonth, double minPrice, double maxPrice) {
StringBuilder grid = new StringBuilder();
// 水平网格线(价格)
int numHorizontalLines = 5;
for (int i = 0; i <= numHorizontalLines; i++) {
double price = minPrice + (maxPrice - minPrice) * i / numHorizontalLines;
int y = HEIGHT - PADDING - (int) ((price - minPrice) * (HEIGHT - 2 * PADDING) / (maxPrice - minPrice));
grid.append(String.format("<line x1='%d' y1='%d' x2='%d' y2='%d' stroke='#e0e0e0' stroke-width='1' stroke-dasharray='5,5'/>",
PADDING, y, WIDTH - PADDING, y));
}
// 垂直网格线(时间)
List<String> yearMonths = generateYearMonths(minYearMonth, maxYearMonth);
for (String yearMonth : yearMonths) {
int x = PADDING + (int) ((getMonthIndex(yearMonth) - getMonthIndex(minYearMonth)) * (WIDTH - 2 * PADDING) / (yearMonths.size() - 1));
grid.append(String.format("<line x1='%d' y1='%d' x2='%d' y2='%d' stroke='#e0e0e0' stroke-width='1' stroke-dasharray='5,5'/>",
x, PADDING, x, HEIGHT - PADDING));
}
return grid.toString();
}
// 画深色坐标轴
private static String drawAxis(String minYearMonth, String maxYearMonth, double minPrice, double maxPrice) {
StringBuilder axis = new StringBuilder();
// X轴(时间)
axis.append(String.format("<line x1='%d' y1='%d' x2='%d' y2='%d' stroke='#333333' stroke-width='2'/>",
PADDING, HEIGHT - PADDING, WIDTH - PADDING, HEIGHT - PADDING));
// Y轴(价格)
axis.append(String.format("<line x1='%d' y1='%d' x2='%d' y2='%d' stroke='#333333' stroke-width='2'/>",
PADDING, PADDING, PADDING, HEIGHT - PADDING));
// X轴标签
List<String> yearMonths = generateYearMonths(minYearMonth, maxYearMonth);
for (String yearMonth : yearMonths) {
int x = PADDING + (int) ((getMonthIndex(yearMonth) - getMonthIndex(minYearMonth)) * (WIDTH - 2 * PADDING) / (yearMonths.size() - 1));
axis.append(String.format("<text x='%d' y='%d' font-size='12' font-family='Arial' fill='#000000' text-anchor='middle'>%s</text>",
x, HEIGHT - PADDING + 20, yearMonth));
}
// Y轴标签
int numYLabels = 5;
for (int i = 0; i <= numYLabels; i++) {
double price = minPrice + (maxPrice - minPrice) * i / numYLabels;
int y = HEIGHT - PADDING - (int) ((price - minPrice) * (HEIGHT - 2 * PADDING) / (maxPrice - minPrice));
axis.append(String.format("<text x='%d' y='%d' font-size='12' font-family='Arial' fill='#000000' text-anchor='end'>%.2f</text>",
PADDING - 10, y + 5, price));
}
return axis.toString();
}
// 画浅色平滑折线
private static String drawSmoothLine(List<PriceData> data, String minYearMonth, String maxYearMonth, double minPrice, double maxPrice) {
if (data.size() < 2) {
return "";
}
StringBuilder path = new StringBuilder("<path d='M");
for (int i = 0; i < data.size(); i++) {
PriceData point = data.get(i);
int x = PADDING + (int) ((getMonthIndex(point.getYearmonth()) - getMonthIndex(minYearMonth)) * (WIDTH - 2 * PADDING) / (getMonthIndex(maxYearMonth) - getMonthIndex(minYearMonth)));
int y = HEIGHT - PADDING - (int) ((point.getPrice() - minPrice) * (HEIGHT - 2 * PADDING) / (maxPrice - minPrice));
if (i == 0) {
path.append(x).append(",").append(y);
} else {
// 计算控制点以实现平滑效果
PriceData prevPoint = data.get(i - 1);
int prevX = PADDING + (int) ((getMonthIndex(prevPoint.getYearmonth()) - getMonthIndex(minYearMonth)) * (WIDTH - 2 * PADDING) / (getMonthIndex(maxYearMonth) - getMonthIndex(minYearMonth)));
int prevY = HEIGHT - PADDING - (int) ((prevPoint.getPrice() - minPrice) * (HEIGHT - 2 * PADDING) / (maxPrice - minPrice));
int ctrlX1 = (prevX + x) / 2;
int ctrlY1 = prevY;
int ctrlX2 = (prevX + x) / 2;
int ctrlY2 = y;
path.append(" C").append(ctrlX1).append(",").append(ctrlY1)
.append(" ").append(ctrlX2).append(",").append(ctrlY2)
.append(" ").append(x).append(",").append(y);
}
}
path.append("' fill='none' stroke='#66B3FF' stroke-width='2'/>");
return path.toString();
}
// 生成从 minYearMonth 到 maxYearMonth 的连续月份列表
private static List<String> generateYearMonths(String minYearMonth, String maxYearMonth) {
List<String> yearMonths = new ArrayList<>();
int year = Integer.parseInt(minYearMonth.substring(0, 4));
int month = Integer.parseInt(minYearMonth.substring(5));
while (true) {
yearMonths.add(String.format("%04d-%02d", year, month));
if (yearMonths.get(yearMonths.size() - 1).equals(maxYearMonth)) {
break;
}
month++;
if (month > 12) {
month = 1;
year++;
}
}
return yearMonths;
}
// 将 yearmonth 转换为索引(从 0 开始)
private static int getMonthIndex(String yearMonth) {
int year = Integer.parseInt(yearMonth.substring(0, 4));
int month = Integer.parseInt(yearMonth.substring(5));
return (year - 2024) * 12 + (month - 1); // 假设最小年份是 2024
}
public static void main(String[] args) {
List<PriceData> data = PriceData.getSampleData();
String title = "价格走势图";
String subtitle = "2024-06 至 2025-01";
String svg = SvgUtils.generatePriceTrendSvg(data, title, subtitle);
System.out.println(svg);
}
}
@Data
public class PriceData {
private String yearmonth;
private Double price;
public PriceData() {}
public PriceData(String yearmonth, Double price) {
this.yearmonth = yearmonth;
this.price = price;
}
public static List<PriceData> getSampleData() {
List<PriceData> data = new ArrayList<>();
data.add(new PriceData("2025-01", 100.0)); // 2023-01-01
data.add(new PriceData("2025-02", 105.0)); // 2023-01-02
data.add(new PriceData("2025-03", 102.0)); // 2023-01-03
data.add(new PriceData("2025-04", 110.0)); // 2023-01-04
data.add(new PriceData("2025-05", 108.0)); // 2023-01-05
return data;
}
}
OK, 生成的SVG嵌入到html网页中进行渲染即可