Showing
2 changed files
with
69 additions
and
13 deletions
| 1 | #!/usr/bin/env python3 | 1 | #!/usr/bin/env python3 |
| 2 | """ | 2 | """ |
| 3 | -生成覆盖全部允许block类型的演示 IR,用于验证 HTML 与 PDF 渲染。 | 3 | +生成覆盖全部允许block类型的演示 IR,用于验证 HTML / PDF / Markdown 渲染。 |
| 4 | 4 | ||
| 5 | 执行后会在 `final_reports/ir` 写入一份带时间戳的 IR, | 5 | 执行后会在 `final_reports/ir` 写入一份带时间戳的 IR, |
| 6 | -并分别在 `final_reports/html` 与 `final_reports/pdf` 输出对应的渲染文件。 | 6 | +并分别在 `final_reports/html`、`final_reports/pdf` 与 `final_reports/md` |
| 7 | +输出对应的渲染文件。 | ||
| 7 | """ | 8 | """ |
| 8 | 9 | ||
| 9 | from __future__ import annotations | 10 | from __future__ import annotations |
| @@ -21,7 +22,7 @@ if str(ROOT) not in sys.path: | @@ -21,7 +22,7 @@ if str(ROOT) not in sys.path: | ||
| 21 | from ReportEngine.core import DocumentComposer | 22 | from ReportEngine.core import DocumentComposer |
| 22 | from ReportEngine.ir import IRValidator | 23 | from ReportEngine.ir import IRValidator |
| 23 | from ReportEngine.ir.schema import ENGINE_AGENT_TITLES | 24 | from ReportEngine.ir.schema import ENGINE_AGENT_TITLES |
| 24 | -from ReportEngine.renderers import HTMLRenderer, PDFRenderer | 25 | +from ReportEngine.renderers import HTMLRenderer, MarkdownRenderer, PDFRenderer |
| 25 | from ReportEngine.utils.config import settings | 26 | from ReportEngine.utils.config import settings |
| 26 | 27 | ||
| 27 | 28 | ||
| @@ -508,6 +509,49 @@ def build_chapters() -> list[dict]: | @@ -508,6 +509,49 @@ def build_chapters() -> list[dict]: | ||
| 508 | ], | 509 | ], |
| 509 | }, | 510 | }, |
| 510 | } | 511 | } |
| 512 | + horizontal_bar_chart_block = { | ||
| 513 | + "type": "widget", | ||
| 514 | + "widgetId": "demo-horizontal-voice", | ||
| 515 | + "widgetType": "chart.js/bar", | ||
| 516 | + "props": { | ||
| 517 | + # 通过 indexAxis 切换横向柱状图 | ||
| 518 | + "type": "bar", | ||
| 519 | + "options": { | ||
| 520 | + "indexAxis": "y", | ||
| 521 | + "plugins": {"legend": {"position": "right"}}, | ||
| 522 | + "scales": {"x": {"title": {"display": True, "text": "提及量(万)"}}}, | ||
| 523 | + }, | ||
| 524 | + }, | ||
| 525 | + "data": { | ||
| 526 | + "labels": ["微博", "短视频", "社区论坛", "新闻客户端"], | ||
| 527 | + "datasets": [ | ||
| 528 | + { | ||
| 529 | + "label": "声量对比", | ||
| 530 | + "data": [42, 58, 27, 36], | ||
| 531 | + "backgroundColor": ["#2ecc71", "#3498db", "#9b59b6", "#f39c12"], | ||
| 532 | + } | ||
| 533 | + ], | ||
| 534 | + }, | ||
| 535 | + } | ||
| 536 | + pie_chart_block = { | ||
| 537 | + "type": "widget", | ||
| 538 | + "widgetId": "demo-stance-pie", | ||
| 539 | + "widgetType": "chart.js/pie", | ||
| 540 | + "props": { | ||
| 541 | + "type": "pie", | ||
| 542 | + "options": {"plugins": {"legend": {"position": "bottom"}}}, | ||
| 543 | + }, | ||
| 544 | + "data": { | ||
| 545 | + "labels": ["支持", "中立", "质疑"], | ||
| 546 | + "datasets": [ | ||
| 547 | + { | ||
| 548 | + "label": "立场分布", | ||
| 549 | + "data": [36, 28, 21], | ||
| 550 | + "backgroundColor": ["#27ae60", "#f1c40f", "#c0392b"], | ||
| 551 | + } | ||
| 552 | + ], | ||
| 553 | + }, | ||
| 554 | + } | ||
| 511 | doughnut_chart_block = { | 555 | doughnut_chart_block = { |
| 512 | "type": "widget", | 556 | "type": "widget", |
| 513 | "widgetId": "demo-sentiment-share", | 557 | "widgetId": "demo-sentiment-share", |
| @@ -713,12 +757,14 @@ def build_chapters() -> list[dict]: | @@ -713,12 +757,14 @@ def build_chapters() -> list[dict]: | ||
| 713 | "type": "paragraph", | 757 | "type": "paragraph", |
| 714 | "inlines": [ | 758 | "inlines": [ |
| 715 | { | 759 | { |
| 716 | - "text": "折线/柱状/饼图/雷达/极区/散点/气泡等多类型图表,用于验证 Chart.js 兼容性。", | 760 | + "text": "折线 / 柱状(含横向、堆叠)/ 饼图 / 圆环 / 雷达 / 极区 / 散点 / 气泡等多类型图表,用于验证 Chart.js 兼容性。", |
| 717 | } | 761 | } |
| 718 | ], | 762 | ], |
| 719 | }, | 763 | }, |
| 720 | widget_block, | 764 | widget_block, |
| 721 | stacked_bar_chart_block, | 765 | stacked_bar_chart_block, |
| 766 | + horizontal_bar_chart_block, | ||
| 767 | + pie_chart_block, | ||
| 722 | doughnut_chart_block, | 768 | doughnut_chart_block, |
| 723 | radar_chart_block, | 769 | radar_chart_block, |
| 724 | polar_area_chart_block, | 770 | polar_area_chart_block, |
| @@ -768,14 +814,16 @@ def validate_chapters(chapters: list[dict]) -> None: | @@ -768,14 +814,16 @@ def validate_chapters(chapters: list[dict]) -> None: | ||
| 768 | raise ValueError(f"{chapter.get('chapterId', 'unknown')} 校验失败: {errors}") | 814 | raise ValueError(f"{chapter.get('chapterId', 'unknown')} 校验失败: {errors}") |
| 769 | 815 | ||
| 770 | 816 | ||
| 771 | -def render_and_save(document_ir: dict, timestamp: str) -> tuple[Path, Path, Path]: | ||
| 772 | - """将 IR 保存为 JSON,并渲染 HTML / PDF,返回三个路径。""" | 817 | +def render_and_save(document_ir: dict, timestamp: str) -> tuple[Path, Path, Path, Path]: |
| 818 | + """将 IR 保存为 JSON,并渲染 HTML / PDF / Markdown,返回四个路径。""" | ||
| 773 | ir_dir = Path(settings.DOCUMENT_IR_OUTPUT_DIR) | 819 | ir_dir = Path(settings.DOCUMENT_IR_OUTPUT_DIR) |
| 774 | html_dir = Path(settings.OUTPUT_DIR) / "html" | 820 | html_dir = Path(settings.OUTPUT_DIR) / "html" |
| 775 | pdf_dir = Path(settings.OUTPUT_DIR) / "pdf" | 821 | pdf_dir = Path(settings.OUTPUT_DIR) / "pdf" |
| 822 | + md_dir = Path(settings.OUTPUT_DIR) / "md" | ||
| 776 | ir_dir.mkdir(parents=True, exist_ok=True) | 823 | ir_dir.mkdir(parents=True, exist_ok=True) |
| 777 | html_dir.mkdir(parents=True, exist_ok=True) | 824 | html_dir.mkdir(parents=True, exist_ok=True) |
| 778 | pdf_dir.mkdir(parents=True, exist_ok=True) | 825 | pdf_dir.mkdir(parents=True, exist_ok=True) |
| 826 | + md_dir.mkdir(parents=True, exist_ok=True) | ||
| 779 | 827 | ||
| 780 | ir_path = ir_dir / f"report_ir_all_blocks_demo_{timestamp}.json" | 828 | ir_path = ir_dir / f"report_ir_all_blocks_demo_{timestamp}.json" |
| 781 | ir_path.write_text(json.dumps(document_ir, ensure_ascii=False, indent=2), encoding="utf-8") | 829 | ir_path.write_text(json.dumps(document_ir, ensure_ascii=False, indent=2), encoding="utf-8") |
| @@ -789,7 +837,12 @@ def render_and_save(document_ir: dict, timestamp: str) -> tuple[Path, Path, Path | @@ -789,7 +837,12 @@ def render_and_save(document_ir: dict, timestamp: str) -> tuple[Path, Path, Path | ||
| 789 | pdf_path = pdf_dir / f"report_pdf_all_blocks_demo_{timestamp}.pdf" | 837 | pdf_path = pdf_dir / f"report_pdf_all_blocks_demo_{timestamp}.pdf" |
| 790 | pdf_renderer.render_to_pdf(document_ir, pdf_path) | 838 | pdf_renderer.render_to_pdf(document_ir, pdf_path) |
| 791 | 839 | ||
| 792 | - return ir_path, html_path, pdf_path | 840 | + md_renderer = MarkdownRenderer() |
| 841 | + md_content = md_renderer.render(document_ir, ir_file_path=str(ir_path)) | ||
| 842 | + md_path = md_dir / f"report_md_all_blocks_demo_{timestamp}.md" | ||
| 843 | + md_path.write_text(md_content, encoding="utf-8") | ||
| 844 | + | ||
| 845 | + return ir_path, html_path, pdf_path, md_path | ||
| 793 | 846 | ||
| 794 | 847 | ||
| 795 | def main() -> int: | 848 | def main() -> int: |
| @@ -817,12 +870,13 @@ def main() -> int: | @@ -817,12 +870,13 @@ def main() -> int: | ||
| 817 | composer = DocumentComposer() | 870 | composer = DocumentComposer() |
| 818 | document_ir = composer.build_document(report_id, metadata, chapters) | 871 | document_ir = composer.build_document(report_id, metadata, chapters) |
| 819 | 872 | ||
| 820 | - ir_path, html_path, pdf_path = render_and_save(document_ir, timestamp) | 873 | + ir_path, html_path, pdf_path, md_path = render_and_save(document_ir, timestamp) |
| 821 | 874 | ||
| 822 | print("✅ 演示 IR 生成完成") | 875 | print("✅ 演示 IR 生成完成") |
| 823 | print(f"IR: {ir_path}") | 876 | print(f"IR: {ir_path}") |
| 824 | print(f"HTML: {html_path}") | 877 | print(f"HTML: {html_path}") |
| 825 | print(f"PDF: {pdf_path}") | 878 | print(f"PDF: {pdf_path}") |
| 879 | + print(f"MD: {md_path}") | ||
| 826 | return 0 | 880 | return 0 |
| 827 | 881 | ||
| 828 | 882 |
| @@ -13,8 +13,10 @@ | @@ -13,8 +13,10 @@ | ||
| 13 | - pie (饼图) | 13 | - pie (饼图) |
| 14 | - doughnut (圆环图) | 14 | - doughnut (圆环图) |
| 15 | - radar (雷达图) | 15 | - radar (雷达图) |
| 16 | -- polarArea (极地区域图) | 16 | +- polararea (极地区域图) |
| 17 | - scatter (散点图) | 17 | - scatter (散点图) |
| 18 | +- bubble (气泡图) | ||
| 19 | +- horizontalbar (横向柱状图) | ||
| 18 | """ | 20 | """ |
| 19 | 21 | ||
| 20 | from __future__ import annotations | 22 | from __future__ import annotations |
| @@ -66,18 +68,18 @@ class ChartValidator: | @@ -66,18 +68,18 @@ class ChartValidator: | ||
| 66 | 68 | ||
| 67 | # 支持的图表类型 | 69 | # 支持的图表类型 |
| 68 | SUPPORTED_CHART_TYPES = { | 70 | SUPPORTED_CHART_TYPES = { |
| 69 | - 'line', 'bar', 'pie', 'doughnut', 'radar', 'polarArea', 'scatter', | ||
| 70 | - 'bubble', 'horizontalBar' | 71 | + 'line', 'bar', 'pie', 'doughnut', 'radar', 'polararea', 'scatter', |
| 72 | + 'bubble', 'horizontalbar' | ||
| 71 | } | 73 | } |
| 72 | 74 | ||
| 73 | # 需要labels的图表类型 | 75 | # 需要labels的图表类型 |
| 74 | LABEL_REQUIRED_TYPES = { | 76 | LABEL_REQUIRED_TYPES = { |
| 75 | - 'line', 'bar', 'radar', 'polarArea', 'pie', 'doughnut' | 77 | + 'line', 'bar', 'radar', 'polararea', 'pie', 'doughnut' |
| 76 | } | 78 | } |
| 77 | 79 | ||
| 78 | # 需要数值数据的图表类型 | 80 | # 需要数值数据的图表类型 |
| 79 | NUMERIC_DATA_TYPES = { | 81 | NUMERIC_DATA_TYPES = { |
| 80 | - 'line', 'bar', 'radar', 'polarArea', 'pie', 'doughnut' | 82 | + 'line', 'bar', 'radar', 'polararea', 'pie', 'doughnut' |
| 81 | } | 83 | } |
| 82 | 84 | ||
| 83 | # 需要特殊数据格式的图表类型 | 85 | # 需要特殊数据格式的图表类型 |
-
Please register or login to post a comment