psyclaw 0.28.0 → 0.28.2

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 (167) hide show
  1. package/README.md +5 -5
  2. package/agents/recommended/catalog.json +99 -0
  3. package/dist/apps/panel/index.html +5 -5
  4. package/dist/src/adapters/pi/extension.js +164 -18
  5. package/dist/src/adapters/pi/extension.js.map +1 -1
  6. package/dist/src/adapters/pi/rpc.d.ts +2 -0
  7. package/dist/src/adapters/pi/rpc.js +9 -2
  8. package/dist/src/adapters/pi/rpc.js.map +1 -1
  9. package/dist/src/agents/recommended-personas.d.ts +42 -0
  10. package/dist/src/agents/recommended-personas.js +135 -0
  11. package/dist/src/agents/recommended-personas.js.map +1 -0
  12. package/dist/src/ars/mode-editor.d.ts +20 -0
  13. package/dist/src/ars/mode-editor.js +57 -0
  14. package/dist/src/ars/mode-editor.js.map +1 -0
  15. package/dist/src/ars/profile.d.ts +4 -1
  16. package/dist/src/ars/profile.js +23 -6
  17. package/dist/src/ars/profile.js.map +1 -1
  18. package/dist/src/cli.js +6 -3
  19. package/dist/src/cli.js.map +1 -1
  20. package/dist/src/creation/contracts.d.ts +3 -0
  21. package/dist/src/creation/service.js +17 -5
  22. package/dist/src/creation/service.js.map +1 -1
  23. package/dist/src/index.d.ts +1 -0
  24. package/dist/src/index.js +1 -0
  25. package/dist/src/index.js.map +1 -1
  26. package/dist/src/orchestration/bundled-personas.d.ts +16 -0
  27. package/dist/src/orchestration/bundled-personas.js +101 -0
  28. package/dist/src/orchestration/bundled-personas.js.map +1 -0
  29. package/dist/src/orchestration/effects.d.ts +8 -0
  30. package/dist/src/orchestration/effects.js +39 -0
  31. package/dist/src/orchestration/effects.js.map +1 -0
  32. package/dist/src/orchestration/personas.d.ts +2 -1
  33. package/dist/src/orchestration/personas.js +20 -7
  34. package/dist/src/orchestration/personas.js.map +1 -1
  35. package/dist/src/orchestration/pi-executor.d.ts +9 -5
  36. package/dist/src/orchestration/pi-executor.js +55 -22
  37. package/dist/src/orchestration/pi-executor.js.map +1 -1
  38. package/dist/src/orchestration/research-agents.d.ts +3 -7
  39. package/dist/src/orchestration/research-agents.js +5 -69
  40. package/dist/src/orchestration/research-agents.js.map +1 -1
  41. package/dist/src/panel/server.js +6 -0
  42. package/dist/src/panel/server.js.map +1 -1
  43. package/dist/src/style/cli-ui.d.ts +14 -0
  44. package/dist/src/style/cli-ui.js +45 -0
  45. package/dist/src/style/cli-ui.js.map +1 -1
  46. package/dist/src/tui/skill-manager.js +1 -1
  47. package/dist/src/tui/skill-manager.js.map +1 -1
  48. package/dist/src/updates/update.js +9 -3
  49. package/dist/src/updates/update.js.map +1 -1
  50. package/package.json +11 -3
  51. package/vendor/academic-paper-skills/LICENSE +21 -0
  52. package/vendor/academic-paper-skills/NOTICE.md +7 -0
  53. package/vendor/academic-paper-skills/PSYCLAW_SOURCE.json +14 -0
  54. package/vendor/academic-paper-skills/academic-paper-composer/SKILL.md +840 -0
  55. package/vendor/academic-paper-skills/academic-paper-composer/references/section_guides.md +675 -0
  56. package/vendor/academic-paper-skills/academic-paper-composer/references/writing_standards.md +629 -0
  57. package/vendor/academic-paper-skills/academic-paper-composer/scripts/chapter_quality_check.py +470 -0
  58. package/vendor/academic-paper-skills/academic-paper-composer/scripts/final_evaluation.py +550 -0
  59. package/vendor/academic-paper-skills/academic-paper-strategist/SKILL.md +670 -0
  60. package/vendor/academic-paper-skills/academic-paper-strategist/references/quality_standards.md +336 -0
  61. package/vendor/academic-paper-skills/academic-paper-strategist/references/search_strategy.md +459 -0
  62. package/vendor/academic-paper-skills/academic-paper-strategist/scripts/evaluate_samples.py +300 -0
  63. package/vendor/academic-paper-skills/academic-paper-strategist/scripts/gap_analysis.py +399 -0
  64. package/vendor/ars/pi/wrapper.js +9 -0
  65. package/vendor/nature-skills/LICENSE +201 -0
  66. package/vendor/nature-skills/NOTICE.md +7 -0
  67. package/vendor/nature-skills/PSYCLAW_SOURCE.json +19 -0
  68. package/vendor/nature-skills/skills/nature-figure/README.md +107 -0
  69. package/vendor/nature-skills/skills/nature-figure/README_EN.md +107 -0
  70. package/vendor/nature-skills/skills/nature-figure/SKILL.md +154 -0
  71. package/vendor/nature-skills/skills/nature-figure/agents/openai.yaml +4 -0
  72. package/vendor/nature-skills/skills/nature-figure/evals/evals.json +325 -0
  73. package/vendor/nature-skills/skills/nature-figure/manifest.yaml +103 -0
  74. package/vendor/nature-skills/skills/nature-figure/references/ai-graphical-abstract-workflow.md +129 -0
  75. package/vendor/nature-skills/skills/nature-figure/references/api.md +576 -0
  76. package/vendor/nature-skills/skills/nature-figure/references/asset-adaptation.md +72 -0
  77. package/vendor/nature-skills/skills/nature-figure/references/backend-selection.md +118 -0
  78. package/vendor/nature-skills/skills/nature-figure/references/chart-types.md +348 -0
  79. package/vendor/nature-skills/skills/nature-figure/references/common-patterns.md +371 -0
  80. package/vendor/nature-skills/skills/nature-figure/references/demos.md +58 -0
  81. package/vendor/nature-skills/skills/nature-figure/references/design-theory.md +467 -0
  82. package/vendor/nature-skills/skills/nature-figure/references/figure-contract.md +122 -0
  83. package/vendor/nature-skills/skills/nature-figure/references/figure-legend-conventions.md +90 -0
  84. package/vendor/nature-skills/skills/nature-figure/references/multipanel-evidence-architecture.md +268 -0
  85. package/vendor/nature-skills/skills/nature-figure/references/nature-2026-observations.md +124 -0
  86. package/vendor/nature-skills/skills/nature-figure/references/nature-article-requirements.md +149 -0
  87. package/vendor/nature-skills/skills/nature-figure/references/openrouter-image-generation.md +141 -0
  88. package/vendor/nature-skills/skills/nature-figure/references/qa-contract.md +344 -0
  89. package/vendor/nature-skills/skills/nature-figure/references/r-template-index.md +66 -0
  90. package/vendor/nature-skills/skills/nature-figure/references/r-workflow.md +234 -0
  91. package/vendor/nature-skills/skills/nature-figure/references/template-catalog.md +33 -0
  92. package/vendor/nature-skills/skills/nature-figure/references/tutorials.md +260 -0
  93. package/vendor/nature-skills/skills/nature-figure/requirements.txt +1 -0
  94. package/vendor/nature-skills/skills/nature-figure/scripts/audit_figure_collisions.py +742 -0
  95. package/vendor/nature-skills/skills/nature-figure/scripts/audit_panel_alignment.py +933 -0
  96. package/vendor/nature-skills/skills/nature-figure/scripts/audit_pdf_text.py +152 -0
  97. package/vendor/nature-skills/skills/nature-figure/scripts/figure_safety.py +50 -0
  98. package/vendor/nature-skills/skills/nature-figure/scripts/generate_openrouter_schematic.py +260 -0
  99. package/vendor/nature-skills/skills/nature-figure/scripts/nature_figure_backend.py +93 -0
  100. package/vendor/nature-skills/skills/nature-figure/scripts/panel_alignment.R +188 -0
  101. package/vendor/nature-skills/skills/nature-figure/scripts/plot_templates.py +604 -0
  102. package/vendor/nature-skills/skills/nature-figure/scripts/validate_figure.py +835 -0
  103. package/vendor/nature-skills/skills/nature-figure/static/core/contract.md +55 -0
  104. package/vendor/nature-skills/skills/nature-figure/static/core/stance.md +41 -0
  105. package/vendor/nature-skills/skills/nature-figure/static/fragments/backend/python.md +56 -0
  106. package/vendor/nature-skills/skills/nature-figure/static/fragments/backend/r.md +63 -0
  107. package/vendor/nature-skills/skills/nature-figure/tests/test_figure_safety.py +209 -0
  108. package/vendor/nature-skills/skills/nature-polishing/README.md +58 -0
  109. package/vendor/nature-skills/skills/nature-polishing/README_EN.md +58 -0
  110. package/vendor/nature-skills/skills/nature-polishing/SKILL.md +111 -0
  111. package/vendor/nature-skills/skills/nature-polishing/agents/openai.yaml +4 -0
  112. package/vendor/nature-skills/skills/nature-polishing/manifest.yaml +101 -0
  113. package/vendor/nature-skills/skills/nature-polishing/references/latex-layout.md +211 -0
  114. package/vendor/nature-skills/skills/nature-polishing/references/nat-comms-2025-diction.md +73 -0
  115. package/vendor/nature-skills/skills/nature-polishing/references/phrasebank-playbook.md +175 -0
  116. package/vendor/nature-skills/skills/nature-polishing/references/published-article-patterns.md +126 -0
  117. package/vendor/nature-skills/skills/nature-polishing/references/section-moves.md +252 -0
  118. package/vendor/nature-skills/skills/nature-polishing/references/style-guardrails.md +94 -0
  119. package/vendor/nature-skills/skills/nature-polishing/references/writing-strategy.md +160 -0
  120. package/vendor/nature-skills/skills/nature-polishing/static/core/failure-modes.md +29 -0
  121. package/vendor/nature-skills/skills/nature-polishing/static/core/output-format.md +21 -0
  122. package/vendor/nature-skills/skills/nature-polishing/static/core/stance.md +26 -0
  123. package/vendor/nature-skills/skills/nature-polishing/static/fragments/journal/generic.md +17 -0
  124. package/vendor/nature-skills/skills/nature-polishing/static/fragments/journal/nat-comms.md +38 -0
  125. package/vendor/nature-skills/skills/nature-polishing/static/fragments/journal/nat-mach-intell.md +102 -0
  126. package/vendor/nature-skills/skills/nature-polishing/static/fragments/journal/nature.md +19 -0
  127. package/vendor/nature-skills/skills/nature-polishing/static/fragments/language/en.md +19 -0
  128. package/vendor/nature-skills/skills/nature-polishing/static/fragments/language/zh-to-en.md +19 -0
  129. package/vendor/nature-skills/skills/nature-polishing/static/fragments/paper_type/algorithmic.md +18 -0
  130. package/vendor/nature-skills/skills/nature-polishing/static/fragments/paper_type/hypothesis.md +17 -0
  131. package/vendor/nature-skills/skills/nature-polishing/static/fragments/paper_type/methods.md +35 -0
  132. package/vendor/nature-skills/skills/nature-polishing/static/fragments/paper_type/research.md +29 -0
  133. package/vendor/nature-skills/skills/nature-polishing/static/fragments/paper_type/review.md +17 -0
  134. package/vendor/nature-skills/skills/nature-polishing/static/fragments/section/abstract.md +26 -0
  135. package/vendor/nature-skills/skills/nature-polishing/static/fragments/section/conclusion.md +15 -0
  136. package/vendor/nature-skills/skills/nature-polishing/static/fragments/section/discussion.md +48 -0
  137. package/vendor/nature-skills/skills/nature-polishing/static/fragments/section/intro.md +33 -0
  138. package/vendor/nature-skills/skills/nature-polishing/static/fragments/section/methods.md +26 -0
  139. package/vendor/nature-skills/skills/nature-polishing/static/fragments/section/results.md +61 -0
  140. package/vendor/nature-skills/skills/nature-polishing/static/fragments/section/title.md +17 -0
  141. package/vendor/nature-skills/skills/nature-ref-verifier/README.md +44 -0
  142. package/vendor/nature-skills/skills/nature-ref-verifier/README_EN.md +44 -0
  143. package/vendor/nature-skills/skills/nature-ref-verifier/SKILL.md +196 -0
  144. package/vendor/nature-skills/skills/nature-ref-verifier/agents/openai.yaml +4 -0
  145. package/vendor/nature-skills/skills/nature-ref-verifier/manifest.yaml +19 -0
  146. package/vendor/nature-skills/skills/nature-ref-verifier/references/common-patterns.md +96 -0
  147. package/vendor/nature-skills/skills/nature-shared/README.md +43 -0
  148. package/vendor/nature-skills/skills/nature-shared/README_EN.md +43 -0
  149. package/vendor/nature-skills/skills/nature-shared/SKILL.md +33 -0
  150. package/vendor/nature-skills/skills/nature-shared/agents/openai.yaml +6 -0
  151. package/vendor/nature-skills/skills/nature-shared/core/consistency-sweep.md +122 -0
  152. package/vendor/nature-skills/skills/nature-shared/core/discussion-argument-language.md +192 -0
  153. package/vendor/nature-skills/skills/nature-shared/core/ethics.md +88 -0
  154. package/vendor/nature-skills/skills/nature-shared/core/main-text-discipline.md +179 -0
  155. package/vendor/nature-skills/skills/nature-shared/core/nature-abstract.md +171 -0
  156. package/vendor/nature-skills/skills/nature-shared/core/nature-introduction.md +164 -0
  157. package/vendor/nature-skills/skills/nature-shared/core/nature-results-discussion.md +215 -0
  158. package/vendor/nature-skills/skills/nature-shared/core/paper-type-taxonomy.md +41 -0
  159. package/vendor/nature-skills/skills/nature-shared/core/reader-workflow.md +21 -0
  160. package/vendor/nature-skills/skills/nature-shared/core/research-compliance.md +192 -0
  161. package/vendor/nature-skills/skills/nature-shared/core/terminology-ledger.md +58 -0
  162. package/vendor/nature-skills/skills/nature-shared/journal-formats/nat-comms.md +139 -0
  163. package/vendor/nature-skills/skills/nature-shared/journal-formats/nature-machine-intelligence.md +431 -0
  164. package/vendor/nature-skills/skills/nature-shared/journal-formats/nature.md +313 -0
  165. package/vendor/nature-skills/skills/nature-shared/manifest.yaml +52 -0
  166. package/vendor/nature-skills/skills/nature-shared/scripts/check_consistency.py +273 -0
  167. package/vendor/nature-skills/skills/nature-shared/tests/test_check_consistency.py +63 -0
@@ -0,0 +1,576 @@
1
+ # API Reference — Nature Figure Making
2
+
3
+ ## Contents
4
+
5
+ - [Constants](#constants)
6
+ - [MANDATORY font + SVG rules (always first, no exceptions)](#mandatory-font-svg-rules-always-first-no-exceptions)
7
+ - [apply_publication_style()](#apply_publication_style)
8
+ - [is_dark(hex_color, threshold=128)](#is_darkhex_color-threshold128)
9
+ - [add_panel_label(ax, label, ...)](#add_panel_labelax-label)
10
+ - [style_dark_image_ax(ax, ...)](#style_dark_image_axax)
11
+ - [make_grouped_bar(ax, categories, series, labels, ...)](#make_grouped_barax-categories-series-labels)
12
+ - [make_trend(ax, x, y_series, labels, ...)](#make_trendax-x-y_series-labels)
13
+ - [make_forest_plot(ax, labels, estimates, ci_low, ci_high, ...)](#make_forest_plotax-labels-estimates-ci_low-ci_high)
14
+ - [make_heatmap(ax, matrix, ...)](#make_heatmapax-matrix)
15
+ - [Numerical and annotation safety helpers](#numerical-and-annotation-safety-helpers)
16
+ - [require_matplotlib_panel_alignment(fig, ...)](#require_matplotlib_panel_alignmentfig-)
17
+ - [finalize_figure(fig, out_path, ...)](#finalize_figurefig-out_path)
18
+ - [Validation Rules](#validation-rules)
19
+ - [Conventions](#conventions)
20
+
21
+
22
+ Conventions, constants, and reusable code blocks. Implement in your script or adapt as needed.
23
+
24
+ ---
25
+
26
+ ## Constants
27
+
28
+ ### PALETTE
29
+
30
+ ```python
31
+ PALETTE = {
32
+ "blue_main": "#0F4D92",
33
+ "blue_secondary": "#3775BA",
34
+ "green_1": "#DDF3DE",
35
+ "green_2": "#AADCA9",
36
+ "green_3": "#8BCF8B",
37
+ "red_1": "#F6CFCB",
38
+ "red_2": "#E9A6A1",
39
+ "red_strong": "#B64342",
40
+ "neutral_light": "#CFCECE",
41
+ "neutral_mid": "#767676",
42
+ "neutral_dark": "#4D4D4D",
43
+ "neutral_black": "#272727",
44
+ "gold": "#FFD700",
45
+ "teal": "#42949E",
46
+ "violet": "#9A4D8E",
47
+ "magenta":"#EA84DD",
48
+ }
49
+
50
+ DEFAULT_COLORS = [
51
+ PALETTE["blue_main"],
52
+ PALETTE["green_3"],
53
+ PALETTE["red_strong"],
54
+ PALETTE["teal"],
55
+ PALETTE["violet"],
56
+ PALETTE["neutral_light"],
57
+ ]
58
+
59
+ PALETTE_NMI_PASTEL = {
60
+ "baseline_dark": "#484878",
61
+ "baseline_mid": "#7884B4",
62
+ "baseline_soft": "#B4C0E4",
63
+ "ours_tiny": "#E4E4F0",
64
+ "ours_base": "#E4CCD8",
65
+ "ours_large": "#F0C0CC",
66
+ "bg_lilac": "#E0E0F0",
67
+ "bg_aqua": "#E0F0F0",
68
+ "bg_peach": "#F0E0D0",
69
+ "neutral_light": "#D8D8D8",
70
+ "neutral_mid": "#A8A8A8",
71
+ "neutral_dark": "#606060",
72
+ "delta_up": "#2E9E44",
73
+ "delta_down": "#E53935",
74
+ }
75
+
76
+ DEFAULT_COLORS_NMI_PASTEL = [
77
+ PALETTE_NMI_PASTEL["baseline_dark"],
78
+ PALETTE_NMI_PASTEL["baseline_mid"],
79
+ PALETTE_NMI_PASTEL["baseline_soft"],
80
+ PALETTE_NMI_PASTEL["ours_tiny"],
81
+ PALETTE_NMI_PASTEL["ours_base"],
82
+ PALETTE_NMI_PASTEL["ours_large"],
83
+ ]
84
+
85
+ PALETTE_NATURE_IMAGING = {
86
+ "bg": "#000000",
87
+ "context": "#B8B8B8",
88
+ "cyan": "#22D7E6",
89
+ "magenta": "#FF2AD4",
90
+ "white": "#FFFFFF",
91
+ }
92
+
93
+ PALETTE_NATURE_MATERIAL = {
94
+ "aqua": "#77D7D1",
95
+ "teal": "#33B5A5",
96
+ "lilac": "#B9A7E8",
97
+ "violet": "#7C6CCF",
98
+ "callout_red": "#E53935",
99
+ "neutral": "#D9D9D9",
100
+ }
101
+
102
+ PALETTE_NATURE_CLINICAL = {
103
+ "baseline": "#272727",
104
+ "week6": "#E28E2C",
105
+ "week13": "#D24B40",
106
+ "week26": "#5B8FD6",
107
+ "year1": "#7BAA5B",
108
+ "year2": "#C45AD6",
109
+ "group_band": "#F2E6D9",
110
+ }
111
+
112
+ PALETTE_NATURE_GENOMICS = {
113
+ "neutral_light": "#D8D8D8",
114
+ "neutral_mid": "#8F8F8F",
115
+ "wave1": "#D9544D",
116
+ "wave2": "#5B7FCA",
117
+ "wave3": "#B89BD9",
118
+ "outline": "#4D4D4D",
119
+ }
120
+ ```
121
+
122
+ Use `DEFAULT_COLORS` when color itself carries explicit semantic meaning (`hero`, `baseline`, `positive variant`).
123
+ Use `DEFAULT_COLORS_NMI_PASTEL` when several compared methods belong to one or two related families and the page
124
+ should feel visually unified.
125
+
126
+ ---
127
+
128
+ ## MANDATORY font + SVG rules (always first, no exceptions)
129
+
130
+ These three lines are **non-negotiable** and must appear at the top of every script,
131
+ before any figure is created. They guarantee editable text in SVG output:
132
+
133
+ ```python
134
+ plt.rcParams['font.family'] = 'sans-serif'
135
+ plt.rcParams['font.sans-serif'] = ['Arial', 'DejaVu Sans', 'Liberation Sans']
136
+ plt.rcParams['svg.fonttype'] = 'none' # keeps text as <text> nodes, not paths
137
+ ```
138
+
139
+ **Why `svg.fonttype = 'none'`**: matplotlib's default (`'path'`) converts every
140
+ glyph to a bezier path, making text unselectable, unsearchable, and impossible to
141
+ re-align in Illustrator / Inkscape. With `'none'`, text stays as SVG `<text>` elements
142
+ and font substitution happens at render time.
143
+
144
+ **Output format**: always save as `.svg` (primary). PNG/PDF are optional secondary
145
+ exports. Never use `.png` alone when the figure contains text that may need adjustment.
146
+
147
+ ---
148
+
149
+ ## apply_publication_style()
150
+
151
+ ```python
152
+ def apply_publication_style(font_size=16, axes_linewidth=2.5, use_tex=False):
153
+ """Apply Nature-style rcParams. Call once before creating any figures."""
154
+ # ── MANDATORY: editable SVG text ──────────────────────────────────────────
155
+ plt.rcParams['font.family'] = 'sans-serif'
156
+ plt.rcParams['font.sans-serif'] = ['Arial', 'DejaVu Sans', 'Liberation Sans']
157
+ plt.rcParams['svg.fonttype'] = 'none'
158
+ # ── Layout & style ────────────────────────────────────────────────────────
159
+ plt.rcParams['font.size'] = font_size
160
+ plt.rcParams['axes.spines.right'] = False
161
+ plt.rcParams['axes.spines.top'] = False
162
+ plt.rcParams['axes.linewidth'] = axes_linewidth
163
+ plt.rcParams['legend.frameon'] = False
164
+ if use_tex:
165
+ plt.rcParams['text.usetex'] = True
166
+ ```
167
+
168
+ **Presets:**
169
+ - Large bar panels: `apply_publication_style(font_size=24, axes_linewidth=3)`
170
+ - Compact figures: `apply_publication_style(font_size=15, axes_linewidth=2)`
171
+ - Dense journal-width multi-panels: `apply_publication_style(font_size=8, axes_linewidth=1)`
172
+ - LaTeX labels: `apply_publication_style(use_tex=True)`
173
+
174
+ ---
175
+
176
+ ## is_dark(hex_color, threshold=128)
177
+
178
+ ```python
179
+ def is_dark(hex_color, threshold=128):
180
+ """Return True if hex color is dark (use white text on it)."""
181
+ c = hex_color.lstrip('#')
182
+ r, g, b = int(c[0:2], 16), int(c[2:4], 16), int(c[4:6], 16)
183
+ return (0.299*r + 0.587*g + 0.114*b) < threshold
184
+ ```
185
+
186
+ ---
187
+
188
+ ## add_panel_label(ax, label, ...)
189
+
190
+ ```python
191
+ def add_panel_label(ax, label, x=0, y=1, x_offset_pt=-4, y_offset_pt=3,
192
+ fontsize=8, color='black', fontweight='bold', va='bottom'):
193
+ """Place a panel label with a fixed physical offset from an axes anchor."""
194
+ from matplotlib.transforms import ScaledTranslation
195
+ offset = ScaledTranslation(
196
+ x_offset_pt / 72,
197
+ y_offset_pt / 72,
198
+ ax.figure.dpi_scale_trans,
199
+ )
200
+ ax.text(
201
+ x, y, label,
202
+ transform=ax.transAxes + offset,
203
+ fontsize=fontsize,
204
+ fontweight=fontweight,
205
+ color=color,
206
+ ha='left',
207
+ va=va,
208
+ )
209
+ ```
210
+
211
+ The point offset keeps labels aligned when one panel spans two rows and its
212
+ neighbouring axes are shorter; a shared axes-fraction offset such as `y=1.02`
213
+ would produce different physical displacements. For dark image plates, move
214
+ the label inside the panel and switch to white:
215
+ `add_panel_label(ax, 'a', x=0.01, y=0.98, x_offset_pt=0, y_offset_pt=0, color='white', va='top')`
216
+
217
+ ---
218
+
219
+ ## style_dark_image_ax(ax, ...)
220
+
221
+ ```python
222
+ def style_dark_image_ax(ax, facecolor='black'):
223
+ """Prepare an axes for microscopy / rendering plates."""
224
+ ax.set_facecolor(facecolor)
225
+ ax.set_xticks([])
226
+ ax.set_yticks([])
227
+ for spine in ax.spines.values():
228
+ spine.set_visible(False)
229
+ return ax
230
+ ```
231
+
232
+ ---
233
+
234
+ ## make_grouped_bar(ax, categories, series, labels, ...)
235
+
236
+ ```python
237
+ def make_grouped_bar(ax, categories, series, labels,
238
+ ylabel='Value', colors=None,
239
+ annotate=False, bar_width=0.8,
240
+ series_spread=None, error_kw=None):
241
+ """
242
+ Grouped bar chart.
243
+
244
+ Parameters
245
+ ----------
246
+ ax : matplotlib Axes
247
+ categories : list[str] — x-axis category names (length K)
248
+ series : list[array] — one array per group (each length K)
249
+ labels : list[str] — legend label per group
250
+ ylabel : str
251
+ colors : list[str] | None — defaults to DEFAULT_COLORS; override with
252
+ DEFAULT_COLORS_NMI_PASTEL for unified-family figures
253
+ annotate : bool — print value above each bar
254
+ bar_width : float — total width for all bars in one category
255
+ error_kw : dict — passed to ax.bar as error_kw
256
+ series_spread : list[array] | None — one uncertainty array per series
257
+
258
+ Returns
259
+ -------
260
+ list[BarContainer]
261
+ """
262
+ import numpy as np
263
+ if colors is None:
264
+ colors = DEFAULT_COLORS
265
+ if error_kw is None:
266
+ error_kw = {'elinewidth': 2, 'capthick': 2, 'capsize': 10}
267
+ n_groups = len(series)
268
+ n_cats = len(categories)
269
+ w = bar_width / n_groups
270
+ x = np.arange(n_cats)
271
+ flat_values = np.concatenate([np.asarray(values, dtype=float) for values in series])
272
+ label_pad = 0.02 * max(float(np.ptp(flat_values)), float(np.max(np.abs(flat_values))), 1.0)
273
+ containers = []
274
+ for i, (vals, label, color) in enumerate(zip(series, labels, colors)):
275
+ spread = None if series_spread is None else np.asarray(series_spread[i], dtype=float)
276
+ offset = (i - (n_groups - 1) / 2) * w
277
+ bars = ax.bar(x + offset, vals, width=w, label=label,
278
+ color=color, edgecolor='black', linewidth=1.5,
279
+ yerr=spread, error_kw=error_kw)
280
+ containers.append(bars)
281
+ if annotate:
282
+ for j, (bar, val) in enumerate(zip(bars, vals)):
283
+ upper = bar.get_height() + (0 if spread is None else spread[j])
284
+ ax.text(bar.get_x() + bar.get_width() / 2,
285
+ upper + label_pad,
286
+ f'{val:.2f}', ha='center', va='bottom', fontsize=10)
287
+ ax.set_xticks(x)
288
+ ax.set_xticklabels(categories)
289
+ ax.set_ylabel(ylabel)
290
+ ax.legend()
291
+ return containers
292
+ ```
293
+
294
+ ---
295
+
296
+ ## make_trend(ax, x, y_series, labels, ...)
297
+
298
+ ```python
299
+ def make_trend(ax, x, y_series, labels,
300
+ colors=None, ylabel=None, xlabel=None,
301
+ show_shadow=True, shadow_alpha=0.15,
302
+ lw=2.5, marker='o', markersize=8):
303
+ """
304
+ Multi-line trend plot.
305
+
306
+ Parameters
307
+ ----------
308
+ x : array-like — shared x values
309
+ y_series : list[array] — one 1D array per line
310
+ labels : list[str]
311
+ show_shadow : bool — fill_between ± std if y_series contains 2D arrays (rows=runs)
312
+ """
313
+ import numpy as np
314
+ if colors is None:
315
+ colors = DEFAULT_COLORS
316
+ for y, label, color in zip(y_series, labels, colors):
317
+ y = np.asarray(y)
318
+ if y.ndim == 2:
319
+ mean, std = y.mean(0), y.std(0)
320
+ else:
321
+ mean, std = y, None
322
+ ax.plot(x, mean, color=color, lw=lw, marker=marker,
323
+ markersize=markersize, label=label)
324
+ if show_shadow and std is not None:
325
+ ax.fill_between(x, mean - std, mean + std,
326
+ color=color, alpha=shadow_alpha)
327
+ if ylabel:
328
+ ax.set_ylabel(ylabel)
329
+ if xlabel:
330
+ ax.set_xlabel(xlabel)
331
+ ax.legend()
332
+ ```
333
+
334
+ ---
335
+
336
+ ## make_forest_plot(ax, labels, estimates, ci_low, ci_high, ...)
337
+
338
+ ```python
339
+ def make_forest_plot(ax, labels, estimates, ci_low, ci_high,
340
+ colors=None, ref=0.0, xlabel=None, xlim=None,
341
+ marker='o', markersize=5, lw=1.5):
342
+ """
343
+ Minimal forest plot helper for Nature-style clinical/statistical panels.
344
+ """
345
+ import numpy as np
346
+ y = np.arange(len(labels))[::-1]
347
+ if colors is None:
348
+ colors = ['#B64342'] * len(labels)
349
+ for yi, est, lo, hi, color in zip(y, estimates, ci_low, ci_high, colors):
350
+ ax.plot([lo, hi], [yi, yi], color=color, lw=lw)
351
+ ax.plot(est, yi, marker=marker, ms=markersize, color=color)
352
+ ax.axvline(ref, color='#767676', linestyle='--', linewidth=1.2, alpha=0.8)
353
+ ax.set_yticks(y)
354
+ ax.set_yticklabels(labels)
355
+ if xlabel:
356
+ ax.set_xlabel(xlabel)
357
+ if xlim is not None:
358
+ ax.set_xlim(xlim)
359
+ ax.spines['right'].set_visible(False)
360
+ ax.spines['top'].set_visible(False)
361
+ ```
362
+
363
+ Use pale `ax.axhspan(...)` bands behind contiguous label groups when you need the
364
+ clinical-triptych look from `Nature`.
365
+
366
+ ---
367
+
368
+ ## make_heatmap(ax, matrix, ...)
369
+
370
+ ```python
371
+ def make_heatmap(ax, matrix, x_labels=None, y_labels=None,
372
+ cmap='magma', cbar_label=None, annotate=False,
373
+ fmt='{:.2f}', fontsize=12):
374
+ """
375
+ 2D heatmap with optional colorbar and cell annotations.
376
+ """
377
+ import numpy as np
378
+ import matplotlib as mpl
379
+ im = ax.imshow(matrix, cmap=cmap, aspect='auto')
380
+ if cbar_label:
381
+ cbar = ax.figure.colorbar(im, ax=ax)
382
+ cbar.set_label(cbar_label)
383
+ if x_labels:
384
+ ax.set_xticks(range(len(x_labels)))
385
+ ax.set_xticklabels(x_labels, rotation=30, ha='right', rotation_mode='anchor')
386
+ if y_labels:
387
+ ax.set_yticks(range(len(y_labels)))
388
+ ax.set_yticklabels(y_labels)
389
+ if annotate:
390
+ norm = mpl.colors.Normalize(vmin=matrix.min(), vmax=matrix.max())
391
+ cm_obj = plt.get_cmap(cmap)
392
+ for (i, j), val in np.ndenumerate(matrix):
393
+ r, g, b, _ = cm_obj(norm(val))
394
+ lum = 0.299*r + 0.587*g + 0.114*b
395
+ color = 'white' if lum < 0.5 else 'black'
396
+ ax.text(j, i, fmt.format(val), ha='center', va='center',
397
+ fontsize=fontsize, color=color)
398
+ ax.set_frame_on(False)
399
+ ```
400
+
401
+ ---
402
+
403
+ ## Numerical and annotation safety helpers
404
+
405
+ Copy `scripts/figure_safety.py` beside the plotting script, or add that scripts directory to `PYTHONPATH`. Then import the tested helpers rather than calling `np.interp` directly on a curve whose direction has not been asserted:
406
+
407
+ ```python
408
+ from figure_safety import interp_monotone, label_y_above
409
+
410
+ n_equivalent = interp_monotone(target_error, error_curve, example_counts)
411
+ label_y = label_y_above(metric_center, metric_spread)
412
+ ```
413
+
414
+ `interp_monotone` accepts strictly increasing or decreasing `xp`, reverses a decreasing grid and its paired values together, and rejects duplicates/direction changes. `label_y_above` places a shared annotation above the upper uncertainty extent rather than relying on a fixed `LABEL_Y`.
415
+
416
+ For compact mathematical labels, prefer supported Unicode glyphs such as `R²` over `$R^2$` when the meaning is unchanged, then audit the exported PDF:
417
+
418
+ ```bash
419
+ python skills/nature-figure/scripts/audit_pdf_text.py figure.pdf --min-pt 5
420
+ python skills/nature-figure/scripts/audit_figure_collisions.py figure.pdf \
421
+ --json-out figure.collision-audit.json \
422
+ --overlay-pdf figure.collision-audit.pdf
423
+ ```
424
+
425
+ ---
426
+
427
+ ## require_matplotlib_panel_alignment(fig, ...)
428
+
429
+ For every multi-panel figure, measure the final Matplotlib axes positions after
430
+ the last `tight_layout`, constrained-layout draw, legend, colorbar or manual
431
+ position change and before saving. Copy `scripts/audit_panel_alignment.py`
432
+ beside the plotting source or add the skill's scripts directory to
433
+ `PYTHONPATH`, then use the blocking helper:
434
+
435
+ ```python
436
+ from audit_panel_alignment import require_matplotlib_panel_alignment
437
+
438
+ alignment = require_matplotlib_panel_alignment(
439
+ fig,
440
+ json_out="figures/figure.alignment.json",
441
+ overlay_svg="figures/figure.alignment.svg",
442
+ tolerance_pt=1.5,
443
+ gutter_tolerance_pt=1.5,
444
+ require_panel_labels=True,
445
+ strict=True,
446
+ )
447
+ ```
448
+
449
+ The helper calls `fig.canvas.draw()` and converts final axes positions to
450
+ physical points. Ordinary `plt.subplots()` and `GridSpec` layouts infer row and
451
+ column groups from `SubplotSpec`, then check:
452
+
453
+ - common top/bottom edges and equal heights within each row;
454
+ - equal final widths for three or four same-row panels with equal grid spans;
455
+ - common left/right edges and equal widths within each column;
456
+ - shared top/bottom or left/right grid boundaries when adjacent panels have
457
+ unequal spans, including `left two + right one` and `left one + right two`;
458
+ - repeated horizontal/vertical gutters;
459
+ - bold lowercase top-left panel-label anchors when detectable;
460
+ - plot-area rectangle overlap.
461
+
462
+ Ordinary unequal-span `GridSpec` layouts are inferred automatically: a two-row
463
+ spanning panel is compared with the appropriate upper and lower small panels at
464
+ the shared outer edges, without imposing an invalid equal-height comparison.
465
+ For ordinary horizontal `1 × 3` and `1 × 4` grids, equal column spans must also
466
+ produce equal final plot-area widths. An intentional unequal `width_ratios`
467
+ layout needs a panel-specific `panel-width` exemption with a reason.
468
+ For nested or separately created grids, declare intended comparisons instead
469
+ of asking the auditor to guess them:
470
+
471
+ ```python
472
+ require_matplotlib_panel_alignment(
473
+ fig,
474
+ axes=[ax_a, ax_b, ax_c, ax_d],
475
+ panel_ids=["a", "b", "c", "d"],
476
+ row_groups=[["a", "b"], ["c", "d"]],
477
+ column_groups=[["a", "c"], ["b", "d"]],
478
+ exclude_axes=[colorbar.ax, inset_ax],
479
+ exemptions=[
480
+ {
481
+ "panels": ["a"],
482
+ "checks": ["column", "panel-width"],
483
+ "reason": "hero panel intentionally spans two grid columns",
484
+ }
485
+ ],
486
+ json_out="figures/figure.alignment.json",
487
+ strict=True,
488
+ )
489
+ ```
490
+
491
+ Every exemption needs panel ids, exact checks and a reason. Prefer omitting a
492
+ hero panel, inset, legend-only axis or colorbar from unrelated comparison groups
493
+ over globally increasing the `1.5 pt` tolerance. Exit-equivalent failures raise
494
+ `PanelAlignmentError`; do not catch and ignore it in a delivery script. A
495
+ single-panel figure is outside this gate, while a multi-panel figure without
496
+ valid comparable groups is `NOT AUDITABLE`, not a pass.
497
+
498
+ The optional SVG contains measured rectangles only and is QA-only. Preserve
499
+ the JSON with the figure bundle, then run PDF glyph and collision QA on the
500
+ exported figure.
501
+
502
+ ---
503
+
504
+ ## finalize_figure(fig, out_path, ...)
505
+
506
+ ```python
507
+ def finalize_figure(fig, out_path, formats=None, dpi=300,
508
+ pad=2, bbox_inches=None, close=True,
509
+ alignment_options=None):
510
+ """
511
+ Apply tight_layout and save figure. For multi-panel figures, run
512
+ require_matplotlib_panel_alignment() after this layout operation and before
513
+ the save loop; do not close the figure before alignment QA.
514
+
515
+ Parameters
516
+ ----------
517
+ out_path : str — path without extension, or with extension
518
+ formats : list — e.g. ['png', 'pdf']. If None, uses extension of out_path.
519
+ dpi : int — 300 standard, 600 for dense bar panels
520
+ pad : float — tight_layout pad (2 default, 1 for compact multi-panel)
521
+ alignment_options : dict | None — explicit groups/exemptions for unusual layouts
522
+ """
523
+ import os
524
+ from pathlib import Path
525
+ base = Path(out_path)
526
+ if formats is None:
527
+ formats = [base.suffix.lstrip('.') or 'png']
528
+ base = base.with_suffix('')
529
+ os.makedirs(base.parent, exist_ok=True)
530
+ fig.tight_layout(pad=pad)
531
+ options = {} if alignment_options is None else dict(alignment_options)
532
+ require_matplotlib_panel_alignment(
533
+ fig,
534
+ json_out=str(base) + ".alignment.json",
535
+ overlay_svg=str(base) + ".alignment.svg",
536
+ strict=True,
537
+ **options,
538
+ )
539
+ saved = []
540
+ for fmt in formats:
541
+ p = str(base) + f'.{fmt}'
542
+ kw = {}
543
+ if bbox_inches is not None:
544
+ kw['bbox_inches'] = bbox_inches
545
+ fig.savefig(p, dpi=dpi, **kw)
546
+ saved.append(p)
547
+ if close:
548
+ plt.close(fig)
549
+ return saved
550
+ ```
551
+
552
+ ---
553
+
554
+ ## Validation Rules
555
+
556
+ - `make_grouped_bar`: `len(categories)` must equal length of each array in `series`.
557
+ - `make_grouped_bar`: if `series_spread` is supplied, it must mirror `series`; annotations clear `value + spread`.
558
+ - `make_trend`: each array in `y_series` must have same length as `x`.
559
+ - `make_trend`: 2D run/seed arrays show a standard-deviation band by default; use a different definition only when explicitly justified and documented.
560
+ - `make_heatmap`: `matrix` must be 2D; `x_labels` length = `matrix.shape[1]`; `y_labels` length = `matrix.shape[0]`.
561
+ - `finalize_figure`: supported formats — `png`, `pdf`, `svg`, `eps`, `jpg`, `tif`.
562
+
563
+ ---
564
+
565
+ ## Conventions
566
+
567
+ - Save outputs under `./figures/` (or path given by user); `finalize_figure` creates parent dirs.
568
+ - In headless / batch runs, set non-interactive backend before importing pyplot:
569
+ ```python
570
+ import matplotlib
571
+ matplotlib.use('Agg')
572
+ import matplotlib.pyplot as plt
573
+ ```
574
+ - Always `plt.close(fig)` after saving to free memory.
575
+ - For multi-panel figures, prefer one baseline family plus one hero family; reserve green/red for delta cues.
576
+ - When color roles, resolution, or layout are underspecified and would change the figure, confirm with user before finalizing.
@@ -0,0 +1,72 @@
1
+ # Plotting Asset Adaptation
2
+
3
+ Use this reference when reusing a bundled example, a preview image, or a user-provided plotting script. Treat examples as visual and structural starting points, not as evidence that a script is compatible with new data.
4
+
5
+ ## Choose the reuse level
6
+
7
+ Assign every candidate to one of four levels before editing it:
8
+
9
+ | Level | Use when | Allowed changes |
10
+ |---|---|---|
11
+ | Exact reuse | Scientific meaning, data shape, transformations, and backend all match | Input path, labels, and output prefix only |
12
+ | Structural adaptation | Scientific meaning and dimensionality match, but field names or group labels differ | Explicit field mapping plus documented transform guards |
13
+ | Style-only inheritance | The plot family is useful but the data structure or statistic differs | Palette, typography, spacing, marker, legend, and annotation conventions only |
14
+ | Build anew | The candidate answers a different question or would require replacing its statistical logic | Do not force the template; implement the confirmed figure contract directly |
15
+
16
+ Do not call a script production-ready merely because it renders its bundled example.
17
+
18
+ ## Inspect before mapping
19
+
20
+ 1. Open the companion preview when one exists.
21
+ 2. State what the candidate actually displays: dimensionality, mark type, grouping, statistic, uncertainty, transforms, and annotations.
22
+ 3. State what the requested panel must answer.
23
+ 4. Reject structural reuse when those meanings differ. A 2D joint-density plot is not a reusable implementation of several 1D marginal densities, and a benchmark bar chart is not automatically a valid small-sample biological comparison.
24
+
25
+ ## Map the data contract
26
+
27
+ Write an explicit mapping before changing code:
28
+
29
+ ```text
30
+ template field -> user field -> role -> units -> allowed values
31
+ group field -> user field -> category order
32
+ replicate unit -> source rows/images -> biological or technical
33
+ uncertainty -> source field or calculation -> definition
34
+ ```
35
+
36
+ Confirm ambiguous mappings with the user. Never choose convenient columns silently. Keep identifiers separate from measurements and preserve the requested category order unless a scientifically justified ordering is declared.
37
+
38
+ ## Guard transformations
39
+
40
+ Check every inherited transformation against the new data:
41
+
42
+ - Log axes and logarithms require strictly positive values unless a declared signed-log or pseudocount method is scientifically justified.
43
+ - Ratios and normalized values require finite denominators and a defined zero-denominator policy.
44
+ - Square-root transforms require non-negative inputs.
45
+ - Min-max scaling requires non-constant finite ranges.
46
+ - Binning and density estimation require enough distinct observations; record bin or bandwidth choices.
47
+ - Correlation, PCA, clustering, and statistical annotations require explicit missing-value handling and an appropriate replicate unit.
48
+
49
+ If a guard fails, change the transformation only when the scientific meaning remains valid and record the change. Otherwise use style-only inheritance or build anew.
50
+
51
+ ## Preserve data integrity
52
+
53
+ - Use all supplied observations and requested variables by default.
54
+ - Do not downsample for aesthetics or rendering speed. Use rasterization, hexbin/density marks, transparent points, aggregation with a stated rule, or backend-native large-data rendering.
55
+ - If the analysis requires filtering, record the exact predicate and before/after row, column, replicate, or image counts.
56
+ - When the user explicitly requests sampling, record the method, sample size, seed, and whether sampling changes any inferential claim.
57
+ - Never leave simulated values in a production deliverable. Isolate demos behind an explicit demo flag or a separate example file.
58
+
59
+ ## Adapt without erasing provenance
60
+
61
+ Copy the candidate into the task workspace before editing. Keep source assets unchanged. Preserve license and attribution notices, but do not expose private local paths or private template identifiers in generated figures, legends, manuscript text, or user-facing reports.
62
+
63
+ Record the reuse level and source category in internal QA notes. The adaptation method in this reference incorporates portable ideas from the Apache-2.0 `academic-figure-skill` workflow while replacing its path-bound runners and project-specific assumptions.
64
+
65
+ ## Validate and deliver
66
+
67
+ 1. Run the adapted script with representative real input using the selected backend.
68
+ 2. Run `python scripts/validate_figure.py path/to/script.py` or the corresponding `.R` file.
69
+ 3. Run `python scripts/audit_pdf_text.py path/to/figure.pdf --min-pt 5` and `python scripts/audit_figure_collisions.py path/to/figure.pdf --json-out path/to/figure.collision-audit.json` on the final export.
70
+ 4. Treat static and geometry validation as preflight only; fix collision FAIL findings and review every WARN, but do not infer statistical correctness or complete visual quality from a pass.
71
+ 5. Inspect SVG/PDF text editability, raster resolution, clipping, ambiguous overlays, color accessibility, and readability at final physical size.
72
+ 6. Include the field mapping, exclusions, transform changes, collision report, and remaining caveats in the QA notes.