paperplotter 0.1.3__tar.gz → 0.1.4__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (86) hide show
  1. {paperplotter-0.1.3/paperplotter.egg-info → paperplotter-0.1.4}/PKG-INFO +9 -4
  2. {paperplotter-0.1.3 → paperplotter-0.1.4}/README.md +9 -4
  3. paperplotter-0.1.4/examples/Layout/declarative_nested_layout.png +0 -0
  4. paperplotter-0.1.4/examples/Layout/declarative_nested_layout_example.py +91 -0
  5. {paperplotter-0.1.3 → paperplotter-0.1.4}/paperplot/core.py +89 -15
  6. {paperplotter-0.1.3 → paperplotter-0.1.4}/paperplot/mixins/domain.py +36 -64
  7. paperplotter-0.1.4/paperplot/mixins/generic.py +95 -0
  8. {paperplotter-0.1.3 → paperplotter-0.1.4/paperplotter.egg-info}/PKG-INFO +9 -4
  9. {paperplotter-0.1.3 → paperplotter-0.1.4}/paperplotter.egg-info/SOURCES.txt +2 -0
  10. {paperplotter-0.1.3 → paperplotter-0.1.4}/paperplotter.egg-info/top_level.txt +1 -0
  11. {paperplotter-0.1.3 → paperplotter-0.1.4}/pyproject.toml +1 -1
  12. paperplotter-0.1.3/paperplot/mixins/generic.py +0 -144
  13. {paperplotter-0.1.3 → paperplotter-0.1.4}/LICENSE +0 -0
  14. {paperplotter-0.1.3 → paperplotter-0.1.4}/MANIFEST.in +0 -0
  15. {paperplotter-0.1.3 → paperplotter-0.1.4}/examples/Data_Analysis_Utils/data_analysis_utils_example.png +0 -0
  16. {paperplotter-0.1.3 → paperplotter-0.1.4}/examples/Data_Analysis_Utils/data_analysis_utils_example.py +0 -0
  17. {paperplotter-0.1.3 → paperplotter-0.1.4}/examples/Data_Analysis_Utils/utility_functions_example.png +0 -0
  18. {paperplotter-0.1.3 → paperplotter-0.1.4}/examples/Data_Analysis_Utils/utility_functions_example.py +0 -0
  19. {paperplotter-0.1.3 → paperplotter-0.1.4}/examples/Domain_Specific_Plots/bifurcation_diagram_example.png +0 -0
  20. {paperplotter-0.1.3 → paperplotter-0.1.4}/examples/Domain_Specific_Plots/bifurcation_diagram_example.py +0 -0
  21. {paperplotter-0.1.3 → paperplotter-0.1.4}/examples/Domain_Specific_Plots/concentration_map_example.png +0 -0
  22. {paperplotter-0.1.3 → paperplotter-0.1.4}/examples/Domain_Specific_Plots/concentration_map_example.py +0 -0
  23. {paperplotter-0.1.3 → paperplotter-0.1.4}/examples/Domain_Specific_Plots/domain_specific_plots.png +0 -0
  24. {paperplotter-0.1.3 → paperplotter-0.1.4}/examples/Domain_Specific_Plots/domain_specific_plots_example.py +0 -0
  25. {paperplotter-0.1.3 → paperplotter-0.1.4}/examples/Domain_Specific_Plots/learning_curve_example.png +0 -0
  26. {paperplotter-0.1.3 → paperplotter-0.1.4}/examples/Domain_Specific_Plots/learning_curve_example.py +0 -0
  27. {paperplotter-0.1.3 → paperplotter-0.1.4}/examples/Domain_Specific_Plots/phasor_diagram_example.png +0 -0
  28. {paperplotter-0.1.3 → paperplotter-0.1.4}/examples/Domain_Specific_Plots/phasor_diagram_example.py +0 -0
  29. {paperplotter-0.1.3 → paperplotter-0.1.4}/examples/Domain_Specific_Plots/power_timeseries_example.png +0 -0
  30. {paperplotter-0.1.3 → paperplotter-0.1.4}/examples/Domain_Specific_Plots/power_timeseries_example.py +0 -0
  31. {paperplotter-0.1.3 → paperplotter-0.1.4}/examples/Features_Customization/advanced_customization.py +0 -0
  32. {paperplotter-0.1.3 → paperplotter-0.1.4}/examples/Features_Customization/advanced_customization_figure.png +0 -0
  33. {paperplotter-0.1.3 → paperplotter-0.1.4}/examples/Features_Customization/cleanup_demonstration.py +0 -0
  34. {paperplotter-0.1.3 → paperplotter-0.1.4}/examples/Features_Customization/cleanup_demonstration_figure.png +0 -0
  35. {paperplotter-0.1.3 → paperplotter-0.1.4}/examples/Features_Customization/composite_figure.png +0 -0
  36. {paperplotter-0.1.3 → paperplotter-0.1.4}/examples/Features_Customization/composite_figure_example.py +0 -0
  37. {paperplotter-0.1.3 → paperplotter-0.1.4}/examples/Features_Customization/error_handling_test.py +0 -0
  38. {paperplotter-0.1.3 → paperplotter-0.1.4}/examples/Features_Customization/feature_expansion_example.py +0 -0
  39. {paperplotter-0.1.3 → paperplotter-0.1.4}/examples/Features_Customization/feature_expansion_figure.png +0 -0
  40. {paperplotter-0.1.3 → paperplotter-0.1.4}/examples/Features_Customization/fig_annotation_example.png +0 -0
  41. {paperplotter-0.1.3 → paperplotter-0.1.4}/examples/Features_Customization/fig_annotation_example.py +0 -0
  42. {paperplotter-0.1.3 → paperplotter-0.1.4}/examples/Features_Customization/fig_text_example.png +0 -0
  43. {paperplotter-0.1.3 → paperplotter-0.1.4}/examples/Features_Customization/fig_text_example.py +0 -0
  44. {paperplotter-0.1.3 → paperplotter-0.1.4}/examples/Features_Customization/global_controls_example.py +0 -0
  45. {paperplotter-0.1.3 → paperplotter-0.1.4}/examples/Features_Customization/global_controls_figure.png +0 -0
  46. {paperplotter-0.1.3 → paperplotter-0.1.4}/examples/Features_Customization/heatmap_colorbar_example.py +0 -0
  47. {paperplotter-0.1.3 → paperplotter-0.1.4}/examples/Features_Customization/heatmap_default_figure.png +0 -0
  48. {paperplotter-0.1.3 → paperplotter-0.1.4}/examples/Features_Customization/heatmap_shared_colorbar_figure.png +0 -0
  49. {paperplotter-0.1.3 → paperplotter-0.1.4}/examples/Features_Customization/highlighting_example.png +0 -0
  50. {paperplotter-0.1.3 → paperplotter-0.1.4}/examples/Features_Customization/highlighting_example.py +0 -0
  51. {paperplotter-0.1.3 → paperplotter-0.1.4}/examples/Features_Customization/multi_plot_grid.py +0 -0
  52. {paperplotter-0.1.3 → paperplotter-0.1.4}/examples/Features_Customization/multi_plot_grid_figure.png +0 -0
  53. {paperplotter-0.1.3 → paperplotter-0.1.4}/examples/Features_Customization/resources/placeholder_image.png +0 -0
  54. {paperplotter-0.1.3 → paperplotter-0.1.4}/examples/Layout/advanced_layout_example.py +0 -0
  55. {paperplotter-0.1.3 → paperplotter-0.1.4}/examples/Layout/advanced_layout_figure.png +0 -0
  56. {paperplotter-0.1.3 → paperplotter-0.1.4}/examples/Layout/aspect_ratio_example.py +0 -0
  57. {paperplotter-0.1.3 → paperplotter-0.1.4}/examples/Layout/aspect_ratio_mosaic.png +0 -0
  58. {paperplotter-0.1.3 → paperplotter-0.1.4}/examples/Layout/aspect_ratio_simple_grid.png +0 -0
  59. {paperplotter-0.1.3 → paperplotter-0.1.4}/examples/Layout/block_span_example.py +0 -0
  60. {paperplotter-0.1.3 → paperplotter-0.1.4}/examples/Layout/block_span_figure.png +0 -0
  61. {paperplotter-0.1.3 → paperplotter-0.1.4}/examples/Layout/row_span_example.py +0 -0
  62. {paperplotter-0.1.3 → paperplotter-0.1.4}/examples/Layout/row_span_figure.png +0 -0
  63. {paperplotter-0.1.3 → paperplotter-0.1.4}/examples/Styles_Aesthetics/aesthetic_and_processing_example.png +0 -0
  64. {paperplotter-0.1.3 → paperplotter-0.1.4}/examples/Styles_Aesthetics/aesthetic_and_processing_example.py +0 -0
  65. {paperplotter-0.1.3 → paperplotter-0.1.4}/examples/Styles_Aesthetics/statistical_annotation_example.png +0 -0
  66. {paperplotter-0.1.3 → paperplotter-0.1.4}/examples/Styles_Aesthetics/statistical_annotation_example.py +0 -0
  67. {paperplotter-0.1.3 → paperplotter-0.1.4}/examples/Styles_Aesthetics/style_gallery_example.py +0 -0
  68. {paperplotter-0.1.3 → paperplotter-0.1.4}/examples/Styles_Aesthetics/style_gallery_flat.png +0 -0
  69. {paperplotter-0.1.3 → paperplotter-0.1.4}/examples/Styles_Aesthetics/style_gallery_nord.png +0 -0
  70. {paperplotter-0.1.3 → paperplotter-0.1.4}/examples/Styles_Aesthetics/style_gallery_presentation.png +0 -0
  71. {paperplotter-0.1.3 → paperplotter-0.1.4}/examples/Styles_Aesthetics/style_gallery_publication.png +0 -0
  72. {paperplotter-0.1.3 → paperplotter-0.1.4}/examples/Styles_Aesthetics/style_gallery_solarized_light.png +0 -0
  73. {paperplotter-0.1.3 → paperplotter-0.1.4}/paperplot/__init__.py +0 -0
  74. {paperplotter-0.1.3 → paperplotter-0.1.4}/paperplot/exceptions.py +0 -0
  75. {paperplotter-0.1.3 → paperplotter-0.1.4}/paperplot/mixins/__init__.py +0 -0
  76. {paperplotter-0.1.3 → paperplotter-0.1.4}/paperplot/mixins/modifiers.py +0 -0
  77. {paperplotter-0.1.3 → paperplotter-0.1.4}/paperplot/styles/flat.mplstyle +0 -0
  78. {paperplotter-0.1.3 → paperplotter-0.1.4}/paperplot/styles/nord.mplstyle +0 -0
  79. {paperplotter-0.1.3 → paperplotter-0.1.4}/paperplot/styles/presentation.mplstyle +0 -0
  80. {paperplotter-0.1.3 → paperplotter-0.1.4}/paperplot/styles/publication.mplstyle +0 -0
  81. {paperplotter-0.1.3 → paperplotter-0.1.4}/paperplot/styles/solarized_light.mplstyle +0 -0
  82. {paperplotter-0.1.3 → paperplotter-0.1.4}/paperplot/utils.py +0 -0
  83. {paperplotter-0.1.3 → paperplotter-0.1.4}/paperplotter.egg-info/dependency_links.txt +0 -0
  84. {paperplotter-0.1.3 → paperplotter-0.1.4}/paperplotter.egg-info/requires.txt +0 -0
  85. {paperplotter-0.1.3 → paperplotter-0.1.4}/setup.cfg +0 -0
  86. {paperplotter-0.1.3 → paperplotter-0.1.4}/tests/test_plotter.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: paperplotter
3
- Version: 0.1.3
3
+ Version: 0.1.4
4
4
  Summary: 一个为科研论文设计的声明式 Matplotlib 封装库,让复杂图表的创建变得简单直观。
5
5
  Author-email: VerNe <yuu_seeing@foxmail.com>
6
6
  License-Expression: MIT
@@ -36,6 +36,7 @@ Dynamic: license-file
36
36
  * **🎨 声明式链式调用**: 像写句子一样构建你的图表,例如 `plotter.add_line(...).set_title(...).set_xlabel(...)`。
37
37
  * **🏷️ 基于标签的控制**: 给每个子图一个独一无二的 `tag`,之后就可以随时通过 `tag` 对其进行任何修改,告别混乱的 `axes[i][j]` 索引。
38
38
  * **🧩 强大的布局系统**: 无论是简单的 `(行, 列)` 网格,还是使用 `mosaic` 实现的跨行跨列复杂布局,都能轻松定义。
39
+ * **🧱 声明式嵌套布局**: 通过一个字典即可一次性定义包含子网格的复杂层级布局,并使用 `'容器.子图'` 这样的直观路径进行引用,完美实现“图中图”。
39
40
  * **📐 灵活的尺寸控制**: 除了传统的 `figsize`,还可以通过 `subplot_aspect` 指定子图单元格的宽高比,让 `PaperPlot` 自动计算最合适的画布尺寸。
40
41
  * **✨ 内置科研主题**: 提供多种专业美观的内置样式,如 `publication`, `presentation` 等,一键切换图表风格。
41
42
  * **🌐 全局图层级标注**: 提供了在整个画布(Figure)上添加文本、线条、方框和标签的 API,非常适合添加全局注释或高亮一组图表。
@@ -68,10 +69,13 @@ df_scatter = pd.DataFrame({
68
69
  })
69
70
 
70
71
  # 2. 初始化 Plotter 并绘图
72
+ # 对于简单布局,可以直接使用元组 (rows, cols)
71
73
  plotter = pp.Plotter(layout=(1, 2), figsize=(10, 4))
72
74
 
73
- # 3. 添加图表并使用 tag 标记
75
+ # 3. 顺序添加图表,Plotter会自动填充网格
76
+ # 第一次调用 add_line 会画在左边
74
77
  plotter.add_line(data=df_line, x='time', y='signal', tag='time_series')
78
+ # 第二次调用 add_scatter 会画在右边
75
79
  plotter.add_scatter(data=df_scatter, x='x', y='y', tag='scatter_plot')
76
80
 
77
81
  # 4. 通过 tag 设置标题和标签
@@ -96,7 +100,8 @@ plotter.save("quick_start_figure.png")
96
100
 
97
101
  | 示例 | 描述 | 关键功能 |
98
102
  | :--- | :--- | :--- |
99
- | **高级布局**<br/> `Layout/advanced_layout_example.py` | 展示如何使用列表定义一个跨列的复杂布局。 | `layout=[['A', 'B', 'B'], ...]`<br/>`get_ax_by_name()` |
103
+ | **声明式嵌套布局**<br/> `Layout/declarative_nested_layout_example.py` | 使用字典来声明式地定义一个包含子网格的复杂、多层级布局,实现“图中图”的效果。 | `layout={...}`<br/> `tag='容器.子图'` |
104
+ | **高级布局**<br/> `Layout/advanced_layout_example.py` | 展示如何使用列表定义一个跨列的复杂布局。 | `layout=[['A', 'B', 'B'], ...]` |
100
105
  | **行跨越**<br/> `Layout/row_span_example.py` | 创建一个图表,其中某个子图跨越多行。 | `layout=[['A', 'B'], ['A', 'C']]` |
101
106
  | **块跨越**<br/> `Layout/block_span_example.py` | 创建一个图表,其中某个子图同时跨越多行和多列。 | `layout=[['A', 'A', 'B'], ['A', 'A', 'C']]` |
102
107
  | **固定子图宽高比**<br/> `Layout/aspect_ratio_example.py` | 在不指定 `figsize` 的情况下,通过 `subplot_aspect` 保证每个子图单元格的宽高比,Plotter 会自动计算画布大小。 | `subplot_aspect=(16, 9)` |
@@ -105,7 +110,7 @@ plotter.save("quick_start_figure.png")
105
110
 
106
111
  | 示例 | 描述 | 关键功能 |
107
112
  | :--- | :--- | :--- |
108
- | **多图网格**<br/> `Features_Customization/multi_plot_grid.py` | 在一个网格中混合绘制不同类型的图表(线图、柱状图、散点图、热图)。 | `add_line()`, `add_bar()`, `add_scatter()`, `add_heatmap()` |
113
+ | **多图网格**<br/> `Features_Customization/multi_plot_grid.py` | 在一个网格中通过链式调用混合绘制不同类型的图表。 | `plotter.add_...().add_...()` |
109
114
  | **高级定制**<br/> `Features_Customization/advanced_customization.py` | 演示如何使用 `get_ax()` "逃生舱口" 来获取原生的 Matplotlib `Axes` 对象,并添加任意 `Patch`(如椭圆)。 | `get_ax()`, `ax.add_patch()` |
110
115
  | **全局控制**<br/> `Features_Customization/global_controls_example.py` | 展示如何设置全局标题 (`suptitle`) 和创建全局图例。 | `set_suptitle()`, `add_global_legend()` |
111
116
  | **共享颜色条**<br/> `Features_Customization/heatmap_colorbar_example.py` | 为多个热图创建一个共享的、能反映全局数据范围的颜色条。 | `add_heatmap(cbar=False)`, `cleanup_heatmaps()` |
@@ -12,6 +12,7 @@
12
12
  * **🎨 声明式链式调用**: 像写句子一样构建你的图表,例如 `plotter.add_line(...).set_title(...).set_xlabel(...)`。
13
13
  * **🏷️ 基于标签的控制**: 给每个子图一个独一无二的 `tag`,之后就可以随时通过 `tag` 对其进行任何修改,告别混乱的 `axes[i][j]` 索引。
14
14
  * **🧩 强大的布局系统**: 无论是简单的 `(行, 列)` 网格,还是使用 `mosaic` 实现的跨行跨列复杂布局,都能轻松定义。
15
+ * **🧱 声明式嵌套布局**: 通过一个字典即可一次性定义包含子网格的复杂层级布局,并使用 `'容器.子图'` 这样的直观路径进行引用,完美实现“图中图”。
15
16
  * **📐 灵活的尺寸控制**: 除了传统的 `figsize`,还可以通过 `subplot_aspect` 指定子图单元格的宽高比,让 `PaperPlot` 自动计算最合适的画布尺寸。
16
17
  * **✨ 内置科研主题**: 提供多种专业美观的内置样式,如 `publication`, `presentation` 等,一键切换图表风格。
17
18
  * **🌐 全局图层级标注**: 提供了在整个画布(Figure)上添加文本、线条、方框和标签的 API,非常适合添加全局注释或高亮一组图表。
@@ -44,10 +45,13 @@ df_scatter = pd.DataFrame({
44
45
  })
45
46
 
46
47
  # 2. 初始化 Plotter 并绘图
48
+ # 对于简单布局,可以直接使用元组 (rows, cols)
47
49
  plotter = pp.Plotter(layout=(1, 2), figsize=(10, 4))
48
50
 
49
- # 3. 添加图表并使用 tag 标记
51
+ # 3. 顺序添加图表,Plotter会自动填充网格
52
+ # 第一次调用 add_line 会画在左边
50
53
  plotter.add_line(data=df_line, x='time', y='signal', tag='time_series')
54
+ # 第二次调用 add_scatter 会画在右边
51
55
  plotter.add_scatter(data=df_scatter, x='x', y='y', tag='scatter_plot')
52
56
 
53
57
  # 4. 通过 tag 设置标题和标签
@@ -72,7 +76,8 @@ plotter.save("quick_start_figure.png")
72
76
 
73
77
  | 示例 | 描述 | 关键功能 |
74
78
  | :--- | :--- | :--- |
75
- | **高级布局**<br/> `Layout/advanced_layout_example.py` | 展示如何使用列表定义一个跨列的复杂布局。 | `layout=[['A', 'B', 'B'], ...]`<br/>`get_ax_by_name()` |
79
+ | **声明式嵌套布局**<br/> `Layout/declarative_nested_layout_example.py` | 使用字典来声明式地定义一个包含子网格的复杂、多层级布局,实现“图中图”的效果。 | `layout={...}`<br/> `tag='容器.子图'` |
80
+ | **高级布局**<br/> `Layout/advanced_layout_example.py` | 展示如何使用列表定义一个跨列的复杂布局。 | `layout=[['A', 'B', 'B'], ...]` |
76
81
  | **行跨越**<br/> `Layout/row_span_example.py` | 创建一个图表,其中某个子图跨越多行。 | `layout=[['A', 'B'], ['A', 'C']]` |
77
82
  | **块跨越**<br/> `Layout/block_span_example.py` | 创建一个图表,其中某个子图同时跨越多行和多列。 | `layout=[['A', 'A', 'B'], ['A', 'A', 'C']]` |
78
83
  | **固定子图宽高比**<br/> `Layout/aspect_ratio_example.py` | 在不指定 `figsize` 的情况下,通过 `subplot_aspect` 保证每个子图单元格的宽高比,Plotter 会自动计算画布大小。 | `subplot_aspect=(16, 9)` |
@@ -81,7 +86,7 @@ plotter.save("quick_start_figure.png")
81
86
 
82
87
  | 示例 | 描述 | 关键功能 |
83
88
  | :--- | :--- | :--- |
84
- | **多图网格**<br/> `Features_Customization/multi_plot_grid.py` | 在一个网格中混合绘制不同类型的图表(线图、柱状图、散点图、热图)。 | `add_line()`, `add_bar()`, `add_scatter()`, `add_heatmap()` |
89
+ | **多图网格**<br/> `Features_Customization/multi_plot_grid.py` | 在一个网格中通过链式调用混合绘制不同类型的图表。 | `plotter.add_...().add_...()` |
85
90
  | **高级定制**<br/> `Features_Customization/advanced_customization.py` | 演示如何使用 `get_ax()` "逃生舱口" 来获取原生的 Matplotlib `Axes` 对象,并添加任意 `Patch`(如椭圆)。 | `get_ax()`, `ax.add_patch()` |
86
91
  | **全局控制**<br/> `Features_Customization/global_controls_example.py` | 展示如何设置全局标题 (`suptitle`) 和创建全局图例。 | `set_suptitle()`, `add_global_legend()` |
87
92
  | **共享颜色条**<br/> `Features_Customization/heatmap_colorbar_example.py` | 为多个热图创建一个共享的、能反映全局数据范围的颜色条。 | `add_heatmap(cbar=False)`, `cleanup_heatmaps()` |
@@ -131,4 +136,4 @@ plotter.save("quick_start_figure.png")
131
136
 
132
137
  ## 许可证
133
138
 
134
- 本项目采用 [MIT License](LICENSE)授权。
139
+ 本项目采用 [MIT License](LICENSE)授权。
@@ -0,0 +1,91 @@
1
+ # examples/Layout/declarative_nested_layout_example.py
2
+
3
+ import paperplot as pp
4
+ import pandas as pd
5
+ import numpy as np
6
+ import matplotlib.pyplot as plt
7
+
8
+ print(f"--- Running Example: {__file__} ---")
9
+
10
+ # --- 1. 准备数据 ---
11
+ # 主图数据
12
+ df_line = pd.DataFrame({
13
+ 'x': np.linspace(0, 2 * np.pi, 100),
14
+ 'y': np.sin(np.linspace(0, 2 * np.pi, 100))
15
+ })
16
+ # 子网格的热图数据
17
+ heatmap_data_1 = pd.DataFrame(np.random.rand(8, 50))
18
+ heatmap_data_2 = pd.DataFrame(np.random.rand(8, 50))
19
+ heatmap_data_3 = pd.DataFrame(np.random.rand(8, 50))
20
+ # Y轴标签
21
+ y_labels = ['~NH₂', 'v(C-N-C)', 'v(C-C)ring']
22
+ all_heatmap_data = [heatmap_data_1, heatmap_data_2, heatmap_data_3]
23
+
24
+ try:
25
+ # --- 2. 使用字典声明式地定义整个嵌套布局 ---
26
+ # 这是新API的核心:一个字典描述了所有层级和结构
27
+ nested_layout = {
28
+ # 'main' 定义了顶层 1x2 布局
29
+ 'main': [
30
+ ['main_plot', 'heatmap_group']
31
+ ],
32
+ # 'subgrids' 定义了 'heatmap_group' 区域如何被再次划分
33
+ 'subgrids': {
34
+ 'heatmap_group': {
35
+ # 'layout' 定义了 3x1 的内部网格,并为每个单元命名
36
+ 'layout': [
37
+ ['nh2_map'],
38
+ ['cnc_map'],
39
+ ['ring_map']
40
+ ],
41
+ # 'hspace' 控制这个子网格内部的垂直间距,使其紧凑
42
+ 'hspace': 0.05
43
+ }
44
+ }
45
+ }
46
+
47
+ # --- 3. 初始化 Plotter ---
48
+ # 直接将定义好的字典传给 layout 参数
49
+ # layout_engine=None 确保我们可以手动控制间距
50
+ plotter = pp.Plotter(layout=nested_layout, figsize=(12, 6), layout_engine=None)
51
+ plotter.set_suptitle("Declarative Nested Layout Example", fontsize=16, weight='bold')
52
+
53
+ # --- 4. 填充主图区域 ---
54
+ # 直接使用顶层布局中定义的名字 'main_plot' 作为 tag
55
+ plotter.add_line(data=df_line, x='x', y='y', tag='main_plot')
56
+ plotter.set_title('main_plot', 'Standard Plot')
57
+ plotter.set_xlabel('main_plot', 'X-axis')
58
+ plotter.set_ylabel('main_plot', 'Y-axis')
59
+
60
+ # --- 5. 填充子网格区域 ---
61
+ # 使用 '容器名.子图名' 的层级结构来直接引用嵌套的子图
62
+ subgrid_names = ['nh2_map', 'cnc_map', 'ring_map']
63
+ for i, sub_name in enumerate(subgrid_names):
64
+ # 构造层级 tag, e.g., 'heatmap_group.nh2_map'
65
+ hierarchical_tag = f"heatmap_group.{sub_name}"
66
+
67
+ plotter.add_heatmap(data=all_heatmap_data[i], tag=hierarchical_tag)
68
+ plotter.set_ylabel(hierarchical_tag, y_labels[i], fontsize=14, weight='bold')
69
+
70
+ # --- 6. 对子网格进行精细化设置 ---
71
+ # a. 只在最顶部的子图上添加标题
72
+ plotter.set_title('heatmap_group.nh2_map', 'Nested Heatmap Group', fontsize=14)
73
+
74
+ # b. 隐藏不需要的 X 轴刻度标签,实现共享X轴的效果
75
+ # (这里我们假设您已经实现了更精细的 hide_axes 或 tick_params)
76
+ plotter.tick_params('heatmap_group.nh2_map', axis='x', labelbottom=False)
77
+ plotter.tick_params('heatmap_group.cnc_map', axis='x', labelbottom=False)
78
+
79
+ # c. 只在最底部的子图上添加 X 轴标签
80
+ plotter.set_xlabel('heatmap_group.ring_map', 'Time (min)', fontsize=14, weight='bold')
81
+
82
+ # --- 7. 保存图像 ---
83
+ plotter.save("declarative_nested_layout.png")
84
+
85
+ except (pp.PaperPlotError, ValueError, KeyError) as e:
86
+ print(f"\nAn error occurred:\n{e}")
87
+ finally:
88
+ plt.close('all')
89
+
90
+ print(f"\n--- Finished Example: {__file__} ---")
91
+ print("A new file 'declarative_nested_layout.png' was generated.")
@@ -1,4 +1,7 @@
1
1
  import logging
2
+
3
+ from matplotlib.gridspec import GridSpecFromSubplotSpec
4
+
2
5
  logger = logging.getLogger(__name__)
3
6
  from typing import Optional, Union, List, Tuple, Callable, Dict
4
7
  import matplotlib.pyplot as plt
@@ -91,7 +94,9 @@ class Plotter(GenericPlotsMixin, ModifiersMixin, DomainSpecificPlotsMixin):
91
94
  self.axes_dict: Dict[Union[str, int], plt.Axes] = {}
92
95
  self.axes: List[plt.Axes] = []
93
96
 
94
- if isinstance(layout, tuple) and len(layout) == 2:
97
+ if isinstance(layout, dict):
98
+ self._create_nested_layout(layout)
99
+ elif isinstance(layout, tuple) and len(layout) == 2:
95
100
  n_rows, n_cols = layout
96
101
  for r in range(n_rows):
97
102
  for c in range(n_cols):
@@ -114,11 +119,58 @@ class Plotter(GenericPlotsMixin, ModifiersMixin, DomainSpecificPlotsMixin):
114
119
  else:
115
120
  raise ValueError("Invalid layout format. Must be (rows, cols) tuple or list of lists for mosaic.")
116
121
 
117
- self.tag_to_ax: Dict[Union[str, int], plt.Axes] = {} # This will store user-assigned tags to actual axes
122
+ # self.tag_to_ax: Dict[Union[str, int], plt.Axes] = {} # This will store user-assigned tags to actual axes
123
+ self.tag_to_ax = self.axes_dict.copy()
124
+
118
125
  self.tag_to_mappable: Dict[Union[str, int], plt.cm.ScalarMappable] = {}
119
126
  self.current_ax_index: int = 0
120
127
  self.next_default_tag: int = 1
121
128
 
129
+ def _create_nested_layout(self, layout_def: Dict):
130
+ """
131
+ [私有] 根据声明式定义,递归创建嵌套布局。
132
+ """
133
+ main_layout_list = layout_def['main']
134
+ subgrids_def = layout_def.get('subgrids', {})
135
+
136
+ # 1. 创建主网格
137
+ parsed_main_layout, (n_rows, n_cols) = utils.parse_mosaic_layout(main_layout_list)
138
+ main_gs = self.fig.add_gridspec(n_rows, n_cols)
139
+
140
+ # 2. 遍历主网格中的所有命名区域
141
+ for name, spec in parsed_main_layout.items():
142
+
143
+ # 检查这个区域是否需要被进一步划分为子网格
144
+ if name in subgrids_def:
145
+ # --- 是一个容器,需要创建子网格 ---
146
+ subgrid_info = subgrids_def[name]
147
+ sub_layout_list = subgrid_info['layout']
148
+ subgrid_kwargs = {k: v for k, v in subgrid_info.items() if k != 'layout'}
149
+
150
+ # 创建 GridSpecFromSubplotSpec
151
+ main_subplot_spec = main_gs[spec['row_start']:spec['row_start'] + spec['row_span'],
152
+ spec['col_start']:spec['col_start'] + spec['col_span']]
153
+
154
+ parsed_sub_layout, (sub_rows, sub_cols) = utils.parse_mosaic_layout(sub_layout_list)
155
+ sub_gs = GridSpecFromSubplotSpec(sub_rows, sub_cols, subplot_spec=main_subplot_spec, **subgrid_kwargs)
156
+
157
+ # 在子网格中创建最终的 Axes
158
+ for sub_name, sub_spec in parsed_sub_layout.items():
159
+ # 生成层级名称,例如:'heatmap_container.nh2_map'
160
+ hierarchical_name = f"{name}.{sub_name}"
161
+
162
+ ax = self.fig.add_subplot(sub_gs[sub_spec['row_start']:sub_spec['row_start'] + sub_spec['row_span'],
163
+ sub_spec['col_start']:sub_spec['col_start'] + sub_spec['col_span']])
164
+
165
+ self.axes_dict[hierarchical_name] = ax
166
+ self.axes.append(ax)
167
+
168
+ else:
169
+ ax = self.fig.add_subplot(main_gs[spec['row_start']:spec['row_start'] + spec['row_span'],
170
+ spec['col_start']:spec['col_start'] + spec['col_span']])
171
+ self.axes_dict[name] = ax
172
+ self.axes.append(ax)
173
+
122
174
  def _get_ax_by_tag(self, tag: Union[str, int]) -> plt.Axes:
123
175
  """
124
176
  通过tag获取对应的Axes对象。
@@ -139,29 +191,32 @@ class Plotter(GenericPlotsMixin, ModifiersMixin, DomainSpecificPlotsMixin):
139
191
  def _get_next_ax_and_assign_tag(self, tag: Optional[Union[str, int]] = None) -> Tuple[plt.Axes, Union[str, int]]:
140
192
  """
141
193
  获取下一个可用的Axes对象,并为其分配一个tag。
194
+ 此方法用于顺序绘图模式。
142
195
  """
143
- claimed_axes = set(self.tag_to_ax.values())
144
-
145
- ax_to_use = None
146
- while self.current_ax_index < len(self.axes):
147
- potential_ax = self.axes[self.current_ax_index]
148
- if potential_ax not in claimed_axes:
149
- ax_to_use = potential_ax
150
- break
151
- self.current_ax_index += 1
152
-
153
- if ax_to_use is None:
196
+ # 步骤 1: 检查索引是否已经超出了可用 Axes 的范围。
197
+ if self.current_ax_index >= len(self.axes):
154
198
  raise PlottingSpaceError(len(self.axes))
155
199
 
200
+ # 步骤 2: 根据当前索引直接获取下一个 Axes。
201
+ # 我们不再检查它是否已被“认领”,因为我们就是要覆盖或赋予它新标签。
202
+ ax_to_use = self.axes[self.current_ax_index]
203
+
204
+ # 步骤 3: 处理并检查新标签是否重复。
156
205
  current_tag = tag if tag is not None else self.next_default_tag
157
206
  if current_tag in self.tag_to_ax:
207
+ # 顺序绘图模式下,一个已经存在的标签意味着重复,这是不允许的。
208
+ # 声明式模式会在 _resolve_ax 中被提前捕获,不会进入此方法。
158
209
  raise DuplicateTagError(current_tag)
159
-
210
+
160
211
  if tag is None:
161
212
  self.next_default_tag += 1
162
-
213
+
214
+ # 步骤 4: 将新标签与选定的 Axes 关联起来。
163
215
  self.tag_to_ax[current_tag] = ax_to_use
216
+
217
+ # 步骤 5: 将索引向前移动,为下一次调用做准备。
164
218
  self.current_ax_index += 1
219
+
165
220
  return ax_to_use, current_tag
166
221
 
167
222
  def get_ax(self, tag: Union[str, int]) -> plt.Axes:
@@ -178,3 +233,22 @@ class Plotter(GenericPlotsMixin, ModifiersMixin, DomainSpecificPlotsMixin):
178
233
  available_names = list(self.axes_dict.keys()) if isinstance(self.axes_dict, dict) else []
179
234
  raise ValueError(f"Name '{name}' not found in layout. Available names are: {available_names}")
180
235
  return self.axes_dict[name]
236
+
237
+ def _resolve_ax(self, tag: Optional[Union[str, int]] = None, ax: Optional[plt.Axes] = None) -> plt.Axes:
238
+ """[私有] 智能解析并返回正确的 Axes 对象。"""
239
+ _ax: plt.Axes
240
+ if ax is not None:
241
+ _ax = ax
242
+ # 如果用户同时提供了 ax 和 tag,需要注册这个关联
243
+ if tag is not None:
244
+ # 检查是否存在冲突
245
+ if tag in self.tag_to_ax and self.tag_to_ax[tag] is not _ax:
246
+ raise DuplicateTagError(tag)
247
+ self.tag_to_ax[tag] = _ax
248
+ elif tag is not None and tag in self.tag_to_ax:
249
+ # tag 在布局时已定义,直接使用
250
+ _ax = self.tag_to_ax[tag]
251
+ else:
252
+ # ax 未提供,tag 也是新的或 None,按顺序分配
253
+ _ax, _ = self._get_next_ax_and_assign_tag(tag)
254
+ return _ax
@@ -31,16 +31,11 @@ class DomainSpecificPlotsMixin:
31
31
  Returns:
32
32
  Plotter: 返回Plotter实例以支持链式调用。
33
33
  """
34
- if ax is None:
35
- ax, _ = self._get_next_ax_and_assign_tag(tag)
36
- elif tag is not None:
37
- if tag in self.tag_to_ax:
38
- raise DuplicateTagError(tag)
39
- self.tag_to_ax[tag] = ax
34
+ _ax = self._resolve_ax(tag, ax)
40
35
 
41
36
  for i, y_col in enumerate(y_cols):
42
37
  label = kwargs.pop('label', y_col) # 如果用户没有提供label,则使用列名
43
- ax.plot(data[x], data[y_col] + i * offset, label=label, **kwargs)
38
+ _ax.plot(data[x], data[y_col] + i * offset, label=label, **kwargs)
44
39
 
45
40
  return self
46
41
 
@@ -61,24 +56,19 @@ class DomainSpecificPlotsMixin:
61
56
  Returns:
62
57
  Plotter: 返回Plotter实例以支持链式调用。
63
58
  """
64
- if ax is None:
65
- ax, tag = self._get_next_ax_and_assign_tag(tag)
66
- elif tag is not None:
67
- if tag in self.tag_to_ax:
68
- raise DuplicateTagError(tag)
69
- self.tag_to_ax[tag] = ax
59
+ _ax = self._resolve_ax(tag, ax)
70
60
 
71
61
  create_cbar = kwargs.pop('cbar', True)
72
62
  kwargs.setdefault('cmap', 'inferno') # 默认使用 inferno 颜色映射
73
63
 
74
64
  import seaborn as sns
75
- sns.heatmap(data, ax=ax, cbar=create_cbar, **kwargs)
65
+ sns.heatmap(data, ax=_ax, cbar=create_cbar, **kwargs)
76
66
 
77
- if tag and ax.collections:
78
- self.tag_to_mappable[tag] = ax.collections[0]
67
+ if tag and _ax.collections:
68
+ self.tag_to_mappable[tag] = _ax.collections[0]
79
69
 
80
- ax.set_xlabel(kwargs.pop('xlabel', 'X (μm)'))
81
- ax.set_ylabel(kwargs.pop('ylabel', 'Y (μm)'))
70
+ _ax.set_xlabel(kwargs.pop('xlabel', 'X (μm)'))
71
+ _ax.set_ylabel(kwargs.pop('ylabel', 'Y (μm)'))
82
72
 
83
73
  return self
84
74
 
@@ -101,12 +91,7 @@ class DomainSpecificPlotsMixin:
101
91
  Returns:
102
92
  Plotter: 返回Plotter实例以支持链式调用。
103
93
  """
104
- if ax is None:
105
- ax, _ = self._get_next_ax_and_assign_tag(tag)
106
- elif tag is not None:
107
- if tag in self.tag_to_ax:
108
- raise DuplicateTagError(tag)
109
- self.tag_to_ax[tag] = ax
94
+ _ax = self._resolve_ax(tag, ax)
110
95
 
111
96
  if normalize:
112
97
  matrix = matrix.astype('float') / matrix.sum(axis=1)[:, np.newaxis]
@@ -121,10 +106,10 @@ class DomainSpecificPlotsMixin:
121
106
  kwargs.setdefault('fmt', fmt)
122
107
  kwargs.setdefault('cmap', 'Blues')
123
108
 
124
- sns.heatmap(df_cm, ax=ax, **kwargs)
109
+ sns.heatmap(df_cm, ax=_ax, **kwargs)
125
110
 
126
- ax.set_xlabel('Predicted Label')
127
- ax.set_ylabel('True Label')
111
+ _ax.set_xlabel('Predicted Label')
112
+ _ax.set_ylabel('True Label')
128
113
 
129
114
  return self
130
115
 
@@ -145,27 +130,22 @@ class DomainSpecificPlotsMixin:
145
130
  Returns:
146
131
  Plotter: 返回Plotter实例以支持链式调用。
147
132
  """
148
- if ax is None:
149
- ax, _ = self._get_next_ax_and_assign_tag(tag)
150
- elif tag is not None:
151
- if tag in self.tag_to_ax:
152
- raise DuplicateTagError(tag)
153
- self.tag_to_ax[tag] = ax
133
+ _ax = self._resolve_ax(tag, ax)
154
134
 
155
135
  # 绘制每个类别的ROC曲线
156
136
  for key in fpr.keys():
157
137
  label = f'{key} (AUC = {roc_auc[key]:.2f})'
158
- ax.plot(fpr[key], tpr[key], label=label, **kwargs)
138
+ _ax.plot(fpr[key], tpr[key], label=label, **kwargs)
159
139
 
160
140
  # 绘制对角参考线
161
- ax.plot([0, 1], [0, 1], 'k--', lw=2)
141
+ _ax.plot([0, 1], [0, 1], 'k--', lw=2)
162
142
 
163
- ax.set_xlim([0.0, 1.0])
164
- ax.set_ylim([0.0, 1.05])
165
- ax.set_xlabel('False Positive Rate')
166
- ax.set_ylabel('True Positive Rate')
167
- ax.set_title('Receiver Operating Characteristic (ROC) Curve')
168
- ax.legend(loc="lower right")
143
+ _ax.set_xlim([0.0, 1.0])
144
+ _ax.set_ylim([0.0, 1.05])
145
+ _ax.set_xlabel('False Positive Rate')
146
+ _ax.set_ylabel('True Positive Rate')
147
+ _ax.set_title('Receiver Operating Characteristic (ROC) Curve')
148
+ _ax.legend(loc="lower right")
169
149
 
170
150
  return self
171
151
 
@@ -188,15 +168,10 @@ class DomainSpecificPlotsMixin:
188
168
  Returns:
189
169
  Plotter: 返回Plotter实例以支持链式调用。
190
170
  """
191
- if ax is None:
192
- ax, _ = self._get_next_ax_and_assign_tag(tag)
193
- elif tag is not None:
194
- if tag in self.tag_to_ax:
195
- raise DuplicateTagError(tag)
196
- self.tag_to_ax[tag] = ax
171
+ _ax = self._resolve_ax(tag, ax)
197
172
 
198
173
  import seaborn as sns
199
- sns.scatterplot(data=data, x=x_pc, y=y_pc, hue=hue, ax=ax, **kwargs)
174
+ sns.scatterplot(data=data, x=x_pc, y=y_pc, hue=hue, ax=_ax, **kwargs)
200
175
 
201
176
  return self
202
177
 
@@ -221,31 +196,26 @@ class DomainSpecificPlotsMixin:
221
196
  Returns:
222
197
  Plotter: 返回Plotter实例以支持链式调用。
223
198
  """
224
- if ax is None:
225
- ax, _ = self._get_next_ax_and_assign_tag(tag)
226
- elif tag is not None:
227
- if tag in self.tag_to_ax:
228
- raise DuplicateTagError(tag)
229
- self.tag_to_ax[tag] = ax
199
+ _ax = self._resolve_ax(tag, ax)
230
200
 
231
201
  # 绘制时间序列线
232
202
  for y_col in y_cols:
233
203
  label = kwargs.pop('label', y_col)
234
- ax.plot(data[x], data[y_col], label=label, **kwargs)
204
+ _ax.plot(data[x], data[y_col], label=label, **kwargs)
235
205
 
236
206
  # 标记事件
237
207
  if events and isinstance(events, dict):
238
208
  utils.add_event_markers(
239
- ax=ax,
209
+ ax=_ax,
240
210
  event_dates=list(events.values()),
241
211
  labels=list(events.keys())
242
212
  )
243
213
 
244
214
  # 设置默认标签和图例
245
- ax.set_xlabel(kwargs.pop('xlabel', 'Time (s)'))
246
- ax.set_ylabel(kwargs.pop('ylabel', 'Value'))
247
- if any(ax.get_legend_handles_labels()):
248
- ax.legend()
215
+ _ax.set_xlabel(kwargs.pop('xlabel', 'Time (s)'))
216
+ _ax.set_ylabel(kwargs.pop('ylabel', 'Value'))
217
+ if any(_ax.get_legend_handles_labels()):
218
+ _ax.legend()
249
219
 
250
220
  return self
251
221
 
@@ -276,21 +246,23 @@ class DomainSpecificPlotsMixin:
276
246
  DuplicateTagError: 如果尝试使用一个已经存在的tag。
277
247
  TagNotFoundError: 如果指定的tag未找到。
278
248
  """
249
+ _ax = self._resolve_ax(tag, ax)
250
+
279
251
  if len(magnitudes) != len(angles):
280
252
  raise ValueError("Magnitudes and angles lists must have the same length.")
281
253
 
282
254
  _target_ax: plt.Axes
283
255
  _assigned_tag: Union[str, int]
284
256
 
285
- if ax is None:
257
+ if _ax is None:
286
258
  _target_ax, _assigned_tag = self._get_next_ax_and_assign_tag(tag)
287
259
  else:
288
260
  if tag is None:
289
261
  raise ValueError("When 'ax' is explicitly provided, a 'tag' must also be provided.")
290
- if tag in self.tag_to_ax and self.tag_to_ax[tag] != ax:
262
+ if tag in self.tag_to_ax and self.tag_to_ax[tag] != _ax:
291
263
  raise DuplicateTagError(tag)
292
- self.tag_to_ax[tag] = ax
293
- _target_ax = ax
264
+ self.tag_to_ax[tag] = _ax
265
+ _target_ax = _ax
294
266
  _assigned_tag = tag
295
267
 
296
268
  # 检查轴是否为极坐标投影
@@ -0,0 +1,95 @@
1
+ # paperplot/mixins/generic.py
2
+
3
+ from typing import Optional, Union, List, Callable
4
+ import pandas as pd
5
+ import matplotlib.pyplot as plt
6
+ from ..exceptions import DuplicateTagError
7
+
8
+ class GenericPlotsMixin:
9
+ """
10
+ 包含通用绘图方法的 Mixin 类。
11
+ 这些方法是常见图表类型(如线图、散点图、柱状图等)的直接封装。
12
+ """
13
+
14
+ def add_line(self, data: pd.DataFrame, x: str, y: str, tag: Optional[Union[str, int]] = None,
15
+ ax: Optional[plt.Axes] = None, **kwargs) -> 'Plotter':
16
+ """在子图上绘制线图。"""
17
+ _ax = self._resolve_ax(tag, ax)
18
+ _ax.plot(data[x], data[y], **kwargs)
19
+ return self
20
+
21
+ def add_bar(self, data: pd.DataFrame, x: str, y: str, y_err: Optional[str] = None,
22
+ tag: Optional[Union[str, int]] = None, ax: Optional[plt.Axes] = None, **kwargs) -> 'Plotter':
23
+ """在子图上绘制柱状图。"""
24
+ _ax = self._resolve_ax(tag, ax)
25
+ y_error_values = data[y_err] if y_err and y_err in data else None
26
+ _ax.bar(data[x], data[y], yerr=y_error_values, **kwargs)
27
+ return self
28
+
29
+ def add_scatter(self, data: pd.DataFrame, x: str, y: str, tag: Optional[Union[str, int]] = None,
30
+ ax: Optional[plt.Axes] = None, **kwargs) -> 'Plotter':
31
+ """在子图上绘制散点图。"""
32
+ _ax = self._resolve_ax(tag, ax)
33
+
34
+ resolved_kwargs = kwargs.copy()
35
+ for param in ['s', 'c']:
36
+ if param in resolved_kwargs and isinstance(resolved_kwargs[param], str):
37
+ column_name = resolved_kwargs[param]
38
+ if column_name in data.columns:
39
+ resolved_kwargs[param] = data[column_name]
40
+
41
+ _ax.scatter(data[x], data[y], **resolved_kwargs)
42
+ return self
43
+
44
+ def add_hist(self, data: pd.DataFrame, x: str, tag: Optional[Union[str, int]] = None, ax: Optional[plt.Axes] = None,
45
+ **kwargs) -> 'Plotter':
46
+ """在子图上绘制直方图。"""
47
+ _ax = self._resolve_ax(tag, ax)
48
+ _ax.hist(data[x], **kwargs)
49
+ return self
50
+
51
+ def add_box(self, data: pd.DataFrame, tag: Optional[Union[str, int]] = None, ax: Optional[plt.Axes] = None,
52
+ **kwargs) -> 'Plotter':
53
+ """在子图上绘制箱线图。"""
54
+ _ax = self._resolve_ax(tag, ax)
55
+ import seaborn as sns
56
+ sns.boxplot(data=data, ax=_ax, **kwargs)
57
+ return self
58
+
59
+ def add_heatmap(self, data: pd.DataFrame, tag: Optional[Union[str, int]] = None, ax: Optional[plt.Axes] = None,
60
+ **kwargs) -> 'Plotter':
61
+ """在子图上绘制热图。"""
62
+ _ax = self._resolve_ax(tag, ax)
63
+
64
+ create_cbar = kwargs.pop('cbar', True)
65
+ import seaborn as sns
66
+ sns.heatmap(data, ax=_ax, cbar=create_cbar, **kwargs)
67
+
68
+ # 确保 tag 被正确赋值以便后续使用
69
+ resolved_tag = tag if tag is not None else [k for k, v in self.tag_to_ax.items() if v is _ax][-1]
70
+
71
+ if resolved_tag and hasattr(_ax, 'collections') and _ax.collections:
72
+ self.tag_to_mappable[resolved_tag] = _ax.collections[0]
73
+
74
+ return self
75
+
76
+ def add_seaborn(self, plot_func: Callable, tag: Optional[Union[str, int]] = None, ax: Optional[plt.Axes] = None,
77
+ **kwargs) -> 'Plotter':
78
+ """在子图上绘制Seaborn图。"""
79
+ _ax = self._resolve_ax(tag, ax)
80
+ plot_func(ax=_ax, **kwargs)
81
+ return self
82
+
83
+ def add_blank(self, tag: Optional[Union[str, int]] = None) -> 'Plotter':
84
+ """在下一个可用的子图位置创建一个空白区域。"""
85
+ _ax = self._resolve_ax(tag)
86
+ _ax.axis('off')
87
+ return self
88
+
89
+ def add_regplot(self, data: pd.DataFrame, x: str, y: str, tag: Optional[str] = None, ax: Optional[plt.Axes] = None,
90
+ **kwargs) -> 'Plotter':
91
+ """绘制散点图和线性回归趋势线。"""
92
+ _ax = self._resolve_ax(tag, ax)
93
+ import seaborn as sns
94
+ sns.regplot(data=data, x=x, y=y, ax=_ax, **kwargs)
95
+ return self
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: paperplotter
3
- Version: 0.1.3
3
+ Version: 0.1.4
4
4
  Summary: 一个为科研论文设计的声明式 Matplotlib 封装库,让复杂图表的创建变得简单直观。
5
5
  Author-email: VerNe <yuu_seeing@foxmail.com>
6
6
  License-Expression: MIT
@@ -36,6 +36,7 @@ Dynamic: license-file
36
36
  * **🎨 声明式链式调用**: 像写句子一样构建你的图表,例如 `plotter.add_line(...).set_title(...).set_xlabel(...)`。
37
37
  * **🏷️ 基于标签的控制**: 给每个子图一个独一无二的 `tag`,之后就可以随时通过 `tag` 对其进行任何修改,告别混乱的 `axes[i][j]` 索引。
38
38
  * **🧩 强大的布局系统**: 无论是简单的 `(行, 列)` 网格,还是使用 `mosaic` 实现的跨行跨列复杂布局,都能轻松定义。
39
+ * **🧱 声明式嵌套布局**: 通过一个字典即可一次性定义包含子网格的复杂层级布局,并使用 `'容器.子图'` 这样的直观路径进行引用,完美实现“图中图”。
39
40
  * **📐 灵活的尺寸控制**: 除了传统的 `figsize`,还可以通过 `subplot_aspect` 指定子图单元格的宽高比,让 `PaperPlot` 自动计算最合适的画布尺寸。
40
41
  * **✨ 内置科研主题**: 提供多种专业美观的内置样式,如 `publication`, `presentation` 等,一键切换图表风格。
41
42
  * **🌐 全局图层级标注**: 提供了在整个画布(Figure)上添加文本、线条、方框和标签的 API,非常适合添加全局注释或高亮一组图表。
@@ -68,10 +69,13 @@ df_scatter = pd.DataFrame({
68
69
  })
69
70
 
70
71
  # 2. 初始化 Plotter 并绘图
72
+ # 对于简单布局,可以直接使用元组 (rows, cols)
71
73
  plotter = pp.Plotter(layout=(1, 2), figsize=(10, 4))
72
74
 
73
- # 3. 添加图表并使用 tag 标记
75
+ # 3. 顺序添加图表,Plotter会自动填充网格
76
+ # 第一次调用 add_line 会画在左边
74
77
  plotter.add_line(data=df_line, x='time', y='signal', tag='time_series')
78
+ # 第二次调用 add_scatter 会画在右边
75
79
  plotter.add_scatter(data=df_scatter, x='x', y='y', tag='scatter_plot')
76
80
 
77
81
  # 4. 通过 tag 设置标题和标签
@@ -96,7 +100,8 @@ plotter.save("quick_start_figure.png")
96
100
 
97
101
  | 示例 | 描述 | 关键功能 |
98
102
  | :--- | :--- | :--- |
99
- | **高级布局**<br/> `Layout/advanced_layout_example.py` | 展示如何使用列表定义一个跨列的复杂布局。 | `layout=[['A', 'B', 'B'], ...]`<br/>`get_ax_by_name()` |
103
+ | **声明式嵌套布局**<br/> `Layout/declarative_nested_layout_example.py` | 使用字典来声明式地定义一个包含子网格的复杂、多层级布局,实现“图中图”的效果。 | `layout={...}`<br/> `tag='容器.子图'` |
104
+ | **高级布局**<br/> `Layout/advanced_layout_example.py` | 展示如何使用列表定义一个跨列的复杂布局。 | `layout=[['A', 'B', 'B'], ...]` |
100
105
  | **行跨越**<br/> `Layout/row_span_example.py` | 创建一个图表,其中某个子图跨越多行。 | `layout=[['A', 'B'], ['A', 'C']]` |
101
106
  | **块跨越**<br/> `Layout/block_span_example.py` | 创建一个图表,其中某个子图同时跨越多行和多列。 | `layout=[['A', 'A', 'B'], ['A', 'A', 'C']]` |
102
107
  | **固定子图宽高比**<br/> `Layout/aspect_ratio_example.py` | 在不指定 `figsize` 的情况下,通过 `subplot_aspect` 保证每个子图单元格的宽高比,Plotter 会自动计算画布大小。 | `subplot_aspect=(16, 9)` |
@@ -105,7 +110,7 @@ plotter.save("quick_start_figure.png")
105
110
 
106
111
  | 示例 | 描述 | 关键功能 |
107
112
  | :--- | :--- | :--- |
108
- | **多图网格**<br/> `Features_Customization/multi_plot_grid.py` | 在一个网格中混合绘制不同类型的图表(线图、柱状图、散点图、热图)。 | `add_line()`, `add_bar()`, `add_scatter()`, `add_heatmap()` |
113
+ | **多图网格**<br/> `Features_Customization/multi_plot_grid.py` | 在一个网格中通过链式调用混合绘制不同类型的图表。 | `plotter.add_...().add_...()` |
109
114
  | **高级定制**<br/> `Features_Customization/advanced_customization.py` | 演示如何使用 `get_ax()` "逃生舱口" 来获取原生的 Matplotlib `Axes` 对象,并添加任意 `Patch`(如椭圆)。 | `get_ax()`, `ax.add_patch()` |
110
115
  | **全局控制**<br/> `Features_Customization/global_controls_example.py` | 展示如何设置全局标题 (`suptitle`) 和创建全局图例。 | `set_suptitle()`, `add_global_legend()` |
111
116
  | **共享颜色条**<br/> `Features_Customization/heatmap_colorbar_example.py` | 为多个热图创建一个共享的、能反映全局数据范围的颜色条。 | `add_heatmap(cbar=False)`, `cleanup_heatmaps()` |
@@ -48,6 +48,8 @@ examples/Layout/aspect_ratio_mosaic.png
48
48
  examples/Layout/aspect_ratio_simple_grid.png
49
49
  examples/Layout/block_span_example.py
50
50
  examples/Layout/block_span_figure.png
51
+ examples/Layout/declarative_nested_layout.png
52
+ examples/Layout/declarative_nested_layout_example.py
51
53
  examples/Layout/row_span_example.py
52
54
  examples/Layout/row_span_figure.png
53
55
  examples/Styles_Aesthetics/aesthetic_and_processing_example.png
@@ -1,3 +1,4 @@
1
1
  dist
2
2
  examples
3
3
  paperplot
4
+ 修改指导
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "paperplotter"
7
- version = "0.1.3"
7
+ version = "0.1.4"
8
8
  authors = [
9
9
  { name="VerNe", email="yuu_seeing@foxmail.com" },
10
10
  ]
@@ -1,144 +0,0 @@
1
- # paperplot/mixins/generic.py
2
-
3
- from typing import Optional, Union, List, Callable
4
- import pandas as pd
5
- import matplotlib.pyplot as plt
6
- from ..exceptions import DuplicateTagError
7
-
8
- class GenericPlotsMixin:
9
- """
10
- 包含通用绘图方法的 Mixin 类。
11
- 这些方法是常见图表类型(如线图、散点图、柱状图等)的直接封装。
12
- """
13
- def add_line(self, data: pd.DataFrame, x: str, y: str, tag: Optional[Union[str, int]] = None, ax: Optional[plt.Axes] = None, **kwargs) -> 'Plotter':
14
- """
15
- 在子图上绘制线图。
16
- """
17
- if ax is None:
18
- ax, _ = self._get_next_ax_and_assign_tag(tag)
19
- elif tag is not None:
20
- if tag in self.tag_to_ax:
21
- raise DuplicateTagError(tag)
22
- self.tag_to_ax[tag] = ax
23
- ax.plot(data[x], data[y], **kwargs)
24
- return self
25
-
26
- def add_bar(self, data: pd.DataFrame, x: str, y: str, y_err: Optional[str] = None, tag: Optional[Union[str, int]] = None, ax: Optional[plt.Axes] = None, **kwargs) -> 'Plotter':
27
- """
28
- 在子图上绘制柱状图。
29
- """
30
- if ax is None:
31
- ax, _ = self._get_next_ax_and_assign_tag(tag)
32
- elif tag is not None:
33
- if tag in self.tag_to_ax:
34
- raise DuplicateTagError(tag)
35
- self.tag_to_ax[tag] = ax
36
- y_error_values = data[y_err] if y_err and y_err in data else None
37
- ax.bar(data[x], data[y], yerr=y_error_values, **kwargs)
38
- return self
39
-
40
- def add_scatter(self, data: pd.DataFrame, x: str, y: str, tag: Optional[Union[str, int]] = None, ax: Optional[plt.Axes] = None, **kwargs) -> 'Plotter':
41
- """
42
- 在子图上绘制散点图。
43
- """
44
- if ax is None:
45
- ax, _ = self._get_next_ax_and_assign_tag(tag)
46
- elif tag is not None:
47
- if tag in self.tag_to_ax:
48
- raise DuplicateTagError(tag)
49
- self.tag_to_ax[tag] = ax
50
-
51
- resolved_kwargs = kwargs.copy()
52
- for param in ['s', 'c']:
53
- if param in resolved_kwargs and isinstance(resolved_kwargs[param], str):
54
- column_name = resolved_kwargs[param]
55
- if column_name in data.columns:
56
- resolved_kwargs[param] = data[column_name]
57
-
58
- ax.scatter(data[x], data[y], **resolved_kwargs)
59
- return self
60
-
61
- def add_hist(self, data: pd.DataFrame, x: str, tag: Optional[Union[str, int]] = None, ax: Optional[plt.Axes] = None, **kwargs) -> 'Plotter':
62
- """
63
- 在子图上绘制直方图。
64
- """
65
- if ax is None:
66
- ax, _ = self._get_next_ax_and_assign_tag(tag)
67
- elif tag is not None:
68
- if tag in self.tag_to_ax:
69
- raise DuplicateTagError(tag)
70
- self.tag_to_ax[tag] = ax
71
- ax.hist(data[x], **kwargs)
72
- return self
73
-
74
- def add_box(self, data: pd.DataFrame, tag: Optional[Union[str, int]] = None, ax: Optional[plt.Axes] = None, **kwargs) -> 'Plotter':
75
- """
76
- 在子图上绘制箱线图。
77
- """
78
- if ax is None:
79
- ax, _ = self._get_next_ax_and_assign_tag(tag)
80
- elif tag is not None:
81
- if tag in self.tag_to_ax:
82
- raise DuplicateTagError(tag)
83
- self.tag_to_ax[tag] = ax
84
-
85
- import seaborn as sns
86
- sns.boxplot(data=data, ax=ax, **kwargs)
87
- return self
88
-
89
- def add_heatmap(self, data: pd.DataFrame, tag: Optional[Union[str, int]] = None, ax: Optional[plt.Axes] = None, **kwargs) -> 'Plotter':
90
- """
91
- 在子图上绘制热图。
92
- """
93
- if ax is None:
94
- ax, tag = self._get_next_ax_and_assign_tag(tag)
95
- elif tag is not None:
96
- if tag in self.tag_to_ax:
97
- raise DuplicateTagError(tag)
98
- self.tag_to_ax[tag] = ax
99
-
100
- create_cbar = kwargs.pop('cbar', True)
101
-
102
- import seaborn as sns
103
- sns.heatmap(data, ax=ax, cbar=create_cbar, **kwargs)
104
-
105
- if tag and hasattr(ax, 'collections') and ax.collections:
106
- self.tag_to_mappable[tag] = ax.collections[0]
107
-
108
- return self
109
-
110
- def add_seaborn(self, plot_func: Callable, tag: Optional[Union[str, int]] = None, ax: Optional[plt.Axes] = None, **kwargs) -> 'Plotter':
111
- """
112
- 在子图上绘制Seaborn图。
113
- """
114
- if ax is None:
115
- ax, _ = self._get_next_ax_and_assign_tag(tag)
116
- elif tag is not None:
117
- if tag in self.tag_to_ax:
118
- raise DuplicateTagError(tag)
119
- self.tag_to_ax[tag] = ax
120
- plot_func(ax=ax, **kwargs)
121
- return self
122
-
123
- def add_blank(self, tag: Optional[Union[str, int]] = None) -> 'Plotter':
124
- """
125
- 在下一个可用的子图位置创建一个空白区域。
126
- """
127
- ax, _ = self._get_next_ax_and_assign_tag(tag)
128
- ax.axis('off')
129
- return self
130
-
131
- def add_regplot(self, data: pd.DataFrame, x: str, y: str, tag: Optional[str] = None, ax: Optional[plt.Axes] = None, **kwargs) -> 'Plotter':
132
- """
133
- 绘制散点图和线性回归趋势线。
134
- """
135
- if ax is None:
136
- ax, _ = self._get_next_ax_and_assign_tag(tag)
137
- elif tag is not None:
138
- if tag in self.tag_to_ax:
139
- raise DuplicateTagError(tag)
140
- self.tag_to_ax[tag] = ax
141
-
142
- import seaborn as sns
143
- sns.regplot(data=data, x=x, y=y, ax=ax, **kwargs)
144
- return self
File without changes
File without changes
File without changes