figscribe 0.0.2__tar.gz → 0.2.0__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 (207) hide show
  1. figscribe-0.2.0/.gitattributes +3 -0
  2. figscribe-0.2.0/.github/workflows/tests.yml +48 -0
  3. figscribe-0.2.0/CHANGELOG.md +107 -0
  4. figscribe-0.2.0/CLAUDE.md +47 -0
  5. figscribe-0.2.0/PKG-INFO +395 -0
  6. figscribe-0.2.0/README.md +355 -0
  7. figscribe-0.2.0/docs/decisions.md +1843 -0
  8. figscribe-0.2.0/docs/testing.md +78 -0
  9. figscribe-0.2.0/examples/gaussian-scan/scan.csv +44 -0
  10. figscribe-0.2.0/examples/gaussian-scan/scan.figscribe +115 -0
  11. figscribe-0.2.0/packaging/icons/figscribe.icns +0 -0
  12. figscribe-0.2.0/packaging/icons/figscribe.ico +0 -0
  13. figscribe-0.2.0/packaging/icons/figscribe.png +0 -0
  14. figscribe-0.2.0/packaging/make_icons.py +54 -0
  15. {figscribe-0.0.2 → figscribe-0.2.0}/plan/project-brief.md +13 -8
  16. figscribe-0.2.0/plan/roadmap.md +1144 -0
  17. {figscribe-0.0.2 → figscribe-0.2.0}/pyproject.toml +48 -2
  18. figscribe-0.2.0/src/figscribe/__init__.py +8 -0
  19. figscribe-0.2.0/src/figscribe/__main__.py +5 -0
  20. figscribe-0.2.0/src/figscribe/allan.py +100 -0
  21. figscribe-0.2.0/src/figscribe/assistant/__init__.py +5 -0
  22. figscribe-0.2.0/src/figscribe/assistant/bench.py +743 -0
  23. figscribe-0.2.0/src/figscribe/assistant/chat.py +660 -0
  24. figscribe-0.2.0/src/figscribe/assistant/endpoint.py +102 -0
  25. figscribe-0.2.0/src/figscribe/assistant/mcpserver.py +143 -0
  26. figscribe-0.2.0/src/figscribe/assistant/toolserver.py +132 -0
  27. figscribe-0.2.0/src/figscribe/cli.py +136 -0
  28. figscribe-0.2.0/src/figscribe/codegen/__init__.py +10 -0
  29. figscribe-0.2.0/src/figscribe/codegen/script.py +1562 -0
  30. figscribe-0.2.0/src/figscribe/commands/__init__.py +62 -0
  31. figscribe-0.2.0/src/figscribe/commands/core.py +320 -0
  32. figscribe-0.2.0/src/figscribe/commands/history.py +95 -0
  33. figscribe-0.2.0/src/figscribe/commands/ops.py +449 -0
  34. figscribe-0.2.0/src/figscribe/convert.py +126 -0
  35. figscribe-0.2.0/src/figscribe/examples.py +17 -0
  36. figscribe-0.2.0/src/figscribe/fit/__init__.py +58 -0
  37. figscribe-0.2.0/src/figscribe/fit/expression.py +229 -0
  38. figscribe-0.2.0/src/figscribe/fit/models.py +115 -0
  39. figscribe-0.2.0/src/figscribe/fit/ops.py +273 -0
  40. figscribe-0.2.0/src/figscribe/fit/run.py +321 -0
  41. figscribe-0.2.0/src/figscribe/gui/__init__.py +11 -0
  42. figscribe-0.2.0/src/figscribe/gui/app.py +39 -0
  43. figscribe-0.2.0/src/figscribe/gui/assistantdialog.py +176 -0
  44. figscribe-0.2.0/src/figscribe/gui/axisform.py +439 -0
  45. figscribe-0.2.0/src/figscribe/gui/chatpanel.py +243 -0
  46. figscribe-0.2.0/src/figscribe/gui/clipboard.py +38 -0
  47. figscribe-0.2.0/src/figscribe/gui/codepanel.py +135 -0
  48. figscribe-0.2.0/src/figscribe/gui/databar.py +142 -0
  49. figscribe-0.2.0/src/figscribe/gui/document.py +251 -0
  50. figscribe-0.2.0/src/figscribe/gui/effect.py +154 -0
  51. figscribe-0.2.0/src/figscribe/gui/errors.py +47 -0
  52. figscribe-0.2.0/src/figscribe/gui/figures.py +172 -0
  53. figscribe-0.2.0/src/figscribe/gui/fitdialog.py +641 -0
  54. figscribe-0.2.0/src/figscribe/gui/fonts/IBMPlexMono-Medium.ttf +0 -0
  55. figscribe-0.2.0/src/figscribe/gui/fonts/IBMPlexMono-Regular.ttf +0 -0
  56. figscribe-0.2.0/src/figscribe/gui/fonts/IBMPlexSans-Medium.ttf +0 -0
  57. figscribe-0.2.0/src/figscribe/gui/fonts/IBMPlexSans-Regular.ttf +0 -0
  58. figscribe-0.2.0/src/figscribe/gui/fonts/IBMPlexSans-SemiBold.ttf +0 -0
  59. figscribe-0.2.0/src/figscribe/gui/fonts/OFL.txt +93 -0
  60. figscribe-0.2.0/src/figscribe/gui/icons/check.svg +1 -0
  61. figscribe-0.2.0/src/figscribe/gui/icons/chevron-down.svg +1 -0
  62. figscribe-0.2.0/src/figscribe/gui/icons/figscribe-small.svg +24 -0
  63. figscribe-0.2.0/src/figscribe/gui/icons/figscribe.svg +31 -0
  64. figscribe-0.2.0/src/figscribe/gui/importdialog.py +508 -0
  65. figscribe-0.2.0/src/figscribe/gui/inplace.py +119 -0
  66. figscribe-0.2.0/src/figscribe/gui/mainwindow.py +1519 -0
  67. figscribe-0.2.0/src/figscribe/gui/markers.py +75 -0
  68. figscribe-0.2.0/src/figscribe/gui/markings.py +84 -0
  69. figscribe-0.2.0/src/figscribe/gui/names.py +264 -0
  70. figscribe-0.2.0/src/figscribe/gui/numbers.py +50 -0
  71. figscribe-0.2.0/src/figscribe/gui/outline.py +387 -0
  72. figscribe-0.2.0/src/figscribe/gui/palette.py +124 -0
  73. figscribe-0.2.0/src/figscribe/gui/plotdialog.py +413 -0
  74. figscribe-0.2.0/src/figscribe/gui/preview.py +1189 -0
  75. figscribe-0.2.0/src/figscribe/gui/problems.py +35 -0
  76. figscribe-0.2.0/src/figscribe/gui/properties.py +2084 -0
  77. figscribe-0.2.0/src/figscribe/gui/resize.py +151 -0
  78. figscribe-0.2.0/src/figscribe/gui/reveal.py +38 -0
  79. figscribe-0.2.0/src/figscribe/gui/start.py +376 -0
  80. figscribe-0.2.0/src/figscribe/gui/theme.py +452 -0
  81. figscribe-0.2.0/src/figscribe/gui/widgets.py +153 -0
  82. figscribe-0.2.0/src/figscribe/io/__init__.py +16 -0
  83. figscribe-0.2.0/src/figscribe/io/csvfile.py +133 -0
  84. figscribe-0.2.0/src/figscribe/io/errors.py +9 -0
  85. figscribe-0.2.0/src/figscribe/io/files.py +32 -0
  86. figscribe-0.2.0/src/figscribe/io/numpyfile.py +79 -0
  87. figscribe-0.2.0/src/figscribe/io/provider.py +95 -0
  88. figscribe-0.2.0/src/figscribe/io/refresh.py +166 -0
  89. figscribe-0.2.0/src/figscribe/model/__init__.py +97 -0
  90. figscribe-0.2.0/src/figscribe/model/artists.py +490 -0
  91. figscribe-0.2.0/src/figscribe/model/axes.py +120 -0
  92. figscribe-0.2.0/src/figscribe/model/base.py +50 -0
  93. figscribe-0.2.0/src/figscribe/model/data.py +105 -0
  94. figscribe-0.2.0/src/figscribe/model/figure.py +101 -0
  95. figscribe-0.2.0/src/figscribe/model/project.py +78 -0
  96. figscribe-0.2.0/src/figscribe/model/styles.py +93 -0
  97. figscribe-0.2.0/src/figscribe/model/validate.py +620 -0
  98. figscribe-0.2.0/src/figscribe/render/__init__.py +14 -0
  99. figscribe-0.2.0/src/figscribe/render/export.py +55 -0
  100. figscribe-0.2.0/src/figscribe/render/figure.py +743 -0
  101. figscribe-0.2.0/src/figscribe/render/labels.py +108 -0
  102. figscribe-0.2.0/src/figscribe/render/layout.py +96 -0
  103. figscribe-0.2.0/src/figscribe/render/ticks.py +164 -0
  104. figscribe-0.2.0/src/figscribe/suggest.py +140 -0
  105. figscribe-0.2.0/src/figscribe/templates.py +77 -0
  106. figscribe-0.2.0/src/figscribe/tools/__init__.py +43 -0
  107. figscribe-0.2.0/src/figscribe/tools/operations.py +544 -0
  108. figscribe-0.2.0/src/figscribe/tools/spec.py +296 -0
  109. figscribe-0.2.0/src/figscribe/tools/workspace.py +114 -0
  110. figscribe-0.2.0/src/figscribe/units.py +233 -0
  111. figscribe-0.2.0/tests/assistant/__init__.py +0 -0
  112. figscribe-0.2.0/tests/assistant/fakeserver.py +64 -0
  113. figscribe-0.2.0/tests/assistant/test_bench.py +125 -0
  114. figscribe-0.2.0/tests/assistant/test_chat.py +606 -0
  115. figscribe-0.2.0/tests/assistant/test_mcp.py +372 -0
  116. figscribe-0.2.0/tests/codegen/__init__.py +0 -0
  117. figscribe-0.2.0/tests/codegen/test_equivalence.py +979 -0
  118. figscribe-0.2.0/tests/codegen/test_script.py +186 -0
  119. figscribe-0.2.0/tests/commands/__init__.py +0 -0
  120. figscribe-0.2.0/tests/commands/test_commands.py +442 -0
  121. figscribe-0.2.0/tests/commands/test_everyday.py +270 -0
  122. figscribe-0.2.0/tests/commands/test_figures.py +122 -0
  123. figscribe-0.2.0/tests/commands/test_ops.py +173 -0
  124. figscribe-0.2.0/tests/commands/test_panels.py +111 -0
  125. figscribe-0.2.0/tests/conftest.py +115 -0
  126. figscribe-0.2.0/tests/dataio/__init__.py +0 -0
  127. figscribe-0.2.0/tests/dataio/test_csv.py +220 -0
  128. figscribe-0.2.0/tests/dataio/test_numpy.py +72 -0
  129. figscribe-0.2.0/tests/dataio/test_refresh.py +165 -0
  130. figscribe-0.2.0/tests/fit/__init__.py +0 -0
  131. figscribe-0.2.0/tests/fit/test_expression.py +124 -0
  132. figscribe-0.2.0/tests/fit/test_refit.py +150 -0
  133. figscribe-0.2.0/tests/fit/test_run.py +332 -0
  134. figscribe-0.2.0/tests/gui/__init__.py +0 -0
  135. figscribe-0.2.0/tests/gui/everything.py +169 -0
  136. figscribe-0.2.0/tests/gui/test_axis_form.py +225 -0
  137. figscribe-0.2.0/tests/gui/test_axis_zoom.py +115 -0
  138. figscribe-0.2.0/tests/gui/test_code_panel.py +60 -0
  139. figscribe-0.2.0/tests/gui/test_databar.py +119 -0
  140. figscribe-0.2.0/tests/gui/test_document.py +122 -0
  141. figscribe-0.2.0/tests/gui/test_errors.py +47 -0
  142. figscribe-0.2.0/tests/gui/test_every_action.py +185 -0
  143. figscribe-0.2.0/tests/gui/test_every_field.py +311 -0
  144. figscribe-0.2.0/tests/gui/test_everyday.py +354 -0
  145. figscribe-0.2.0/tests/gui/test_figures.py +181 -0
  146. figscribe-0.2.0/tests/gui/test_find.py +169 -0
  147. figscribe-0.2.0/tests/gui/test_fit.py +452 -0
  148. figscribe-0.2.0/tests/gui/test_images.py +146 -0
  149. figscribe-0.2.0/tests/gui/test_import.py +338 -0
  150. figscribe-0.2.0/tests/gui/test_inplace.py +122 -0
  151. figscribe-0.2.0/tests/gui/test_interaction.py +231 -0
  152. figscribe-0.2.0/tests/gui/test_left_out.py +108 -0
  153. figscribe-0.2.0/tests/gui/test_look.py +358 -0
  154. figscribe-0.2.0/tests/gui/test_mainwindow.py +182 -0
  155. figscribe-0.2.0/tests/gui/test_markings.py +113 -0
  156. figscribe-0.2.0/tests/gui/test_notes.py +126 -0
  157. figscribe-0.2.0/tests/gui/test_numbers.py +55 -0
  158. figscribe-0.2.0/tests/gui/test_panels.py +179 -0
  159. figscribe-0.2.0/tests/gui/test_plot_dialog.py +221 -0
  160. figscribe-0.2.0/tests/gui/test_plot_menu.py +104 -0
  161. figscribe-0.2.0/tests/gui/test_preview.py +122 -0
  162. figscribe-0.2.0/tests/gui/test_properties.py +619 -0
  163. figscribe-0.2.0/tests/gui/test_reading_options.py +39 -0
  164. figscribe-0.2.0/tests/gui/test_resize.py +144 -0
  165. figscribe-0.2.0/tests/gui/test_reveal.py +36 -0
  166. figscribe-0.2.0/tests/gui/test_right_axis.py +91 -0
  167. figscribe-0.2.0/tests/gui/test_several.py +135 -0
  168. figscribe-0.2.0/tests/gui/test_start.py +159 -0
  169. figscribe-0.2.0/tests/gui/test_ticks.py +75 -0
  170. figscribe-0.2.0/tests/gui/test_units_panel.py +107 -0
  171. figscribe-0.2.0/tests/gui/test_without_effect.py +323 -0
  172. figscribe-0.2.0/tests/render/__init__.py +0 -0
  173. figscribe-0.2.0/tests/render/baseline/converted.png +0 -0
  174. figscribe-0.2.0/tests/render/baseline/error_band.png +0 -0
  175. figscribe-0.2.0/tests/render/baseline/errorbar.png +0 -0
  176. figscribe-0.2.0/tests/render/baseline/line.png +0 -0
  177. figscribe-0.2.0/tests/render/baseline/panel_labels.png +0 -0
  178. figscribe-0.2.0/tests/render/baseline/reference_lines.png +0 -0
  179. figscribe-0.2.0/tests/render/baseline/scatter.png +0 -0
  180. figscribe-0.2.0/tests/render/baseline/two_panels.png +0 -0
  181. figscribe-0.2.0/tests/render/conftest.py +41 -0
  182. figscribe-0.2.0/tests/render/test_conversion.py +114 -0
  183. figscribe-0.2.0/tests/render/test_export.py +90 -0
  184. figscribe-0.2.0/tests/render/test_fit_render.py +80 -0
  185. figscribe-0.2.0/tests/render/test_images.py +223 -0
  186. figscribe-0.2.0/tests/render/test_labels.py +108 -0
  187. figscribe-0.2.0/tests/render/test_render.py +765 -0
  188. figscribe-0.2.0/tests/test_allan.py +146 -0
  189. figscribe-0.2.0/tests/test_convert.py +83 -0
  190. figscribe-0.2.0/tests/test_edge_data.py +225 -0
  191. figscribe-0.2.0/tests/test_examples.py +33 -0
  192. figscribe-0.2.0/tests/test_field_coverage.py +108 -0
  193. figscribe-0.2.0/tests/test_left_out.py +177 -0
  194. figscribe-0.2.0/tests/test_model_roundtrip.py +253 -0
  195. figscribe-0.2.0/tests/test_packaging_icons.py +26 -0
  196. figscribe-0.2.0/tests/test_styles.py +62 -0
  197. figscribe-0.2.0/tests/test_suggest.py +73 -0
  198. figscribe-0.2.0/tests/test_units.py +200 -0
  199. figscribe-0.2.0/tests/test_validate_references.py +297 -0
  200. figscribe-0.2.0/tests/tools/__init__.py +0 -0
  201. figscribe-0.2.0/tests/tools/test_tools.py +783 -0
  202. figscribe-0.0.2/PKG-INFO +0 -73
  203. figscribe-0.0.2/README.md +0 -51
  204. figscribe-0.0.2/src/figscribe/__init__.py +0 -7
  205. {figscribe-0.0.2 → figscribe-0.2.0}/.github/workflows/release.yml +0 -0
  206. {figscribe-0.0.2 → figscribe-0.2.0}/.gitignore +0 -0
  207. {figscribe-0.0.2 → figscribe-0.2.0}/LICENSE +0 -0
@@ -0,0 +1,3 @@
1
+ # Data files are hashed into project files; line ending conversion on
2
+ # checkout would change the hash.
3
+ examples/** -text
@@ -0,0 +1,48 @@
1
+ name: Tests
2
+
3
+ on:
4
+ push:
5
+ branches: [main, develop]
6
+ pull_request:
7
+
8
+ permissions:
9
+ contents: read
10
+
11
+ jobs:
12
+ ruff:
13
+ runs-on: ubuntu-latest
14
+ steps:
15
+ - uses: actions/checkout@v5
16
+ - uses: actions/setup-python@v6
17
+ with:
18
+ python-version: "3.13"
19
+ - name: Install
20
+ run: python -m pip install -e ".[dev]"
21
+ - name: Lint
22
+ run: ruff check --output-format=github .
23
+ - name: Format
24
+ run: ruff format --check .
25
+
26
+ pytest:
27
+ runs-on: ${{ matrix.os }}
28
+ env:
29
+ QT_QPA_PLATFORM: offscreen
30
+ strategy:
31
+ fail-fast: false
32
+ matrix:
33
+ os: [ubuntu-latest, windows-latest, macos-latest]
34
+ python-version: ["3.11", "3.13"]
35
+ steps:
36
+ - uses: actions/checkout@v5
37
+ - uses: actions/setup-python@v6
38
+ with:
39
+ python-version: ${{ matrix.python-version }}
40
+ - name: Install Qt's system libraries
41
+ if: runner.os == 'Linux'
42
+ run: sudo apt-get update && sudo apt-get install -y libegl1 libxkbcommon0 libfontconfig1 libdbus-1-3
43
+ - name: Install
44
+ run: python -m pip install -e ".[dev]"
45
+ # The slow tests operate every field and action of the GUI; once is
46
+ # enough, on one platform (decision 0028).
47
+ - name: Run tests
48
+ run: python -m pytest -q -n auto --dist worksteal ${{ (matrix.os != 'ubuntu-latest' || matrix.python-version != '3.13') && '-m "not slow"' || '' }}
@@ -0,0 +1,107 @@
1
+ # Changelog
2
+
3
+ What changed in each release of figscribe. Project files written by a
4
+ release open in every later one.
5
+
6
+ ## 0.2.0 — 2026-10-29
7
+
8
+ **Figures**
9
+ - Several figures in one project: tabs above the figure, *Insert > Add
10
+ Figure* (with the look of the shown one), duplicate, rename, reorder and
11
+ delete from a tab or the outline; *File > Export All Figures…*;
12
+ `figscribe script --figure NAME` and `--all`.
13
+ - Error bands: an error bar plot draws its y errors as a shaded band around
14
+ the line, for dense data.
15
+ - The Allan deviation: σ_y(τ) of an evenly sampled series, frequency or
16
+ phase, on log-log axes, for the stability of clocks, lasers and sensors.
17
+ The script computes it with a few lines of NumPy, no extra package.
18
+ - The plot dialog shows the kinds as tiles, the columns as a sentence, a
19
+ preview with the data, and which panel the plot goes to.
20
+ - Settings that do nothing in the current state are greyed out, and their
21
+ tooltip says why.
22
+ - Units and a range per plot: show values in min, h, days, ms or by any
23
+ factor, counted from the start if wanted, and draw, count and fit only the
24
+ rows within a range of x. The axis, its marks and the plot's fits follow;
25
+ the script converts with one line of NumPy per column.
26
+
27
+ **Finding things**
28
+ - A search above the property panel (Ctrl+F) keeps the settings whose name
29
+ or explanation has the typed words.
30
+ - A command palette (Ctrl+K) finds any menu action or setting by typing.
31
+ - The property panel shows the settings used most first; the rest are
32
+ under *More*.
33
+ - Several plots chosen with Ctrl or Shift in the outline: change their
34
+ color, marker or line for all at once, or delete them together.
35
+
36
+ **Editing where it is drawn**
37
+ - Double-click an axis label, title, legend entry or note to edit it in
38
+ place.
39
+ - Drag an inset's corner to resize it, or the gap between two panels.
40
+ - Right-click a plot: *Style like…*, *Bring to front*, *Send to back*,
41
+ *Fit…*, *Hide*.
42
+
43
+ **Everyday actions**
44
+ - *Edit > Duplicate* (Ctrl+D) copies the selected plots, panel or figure.
45
+ A panel comes with its plots, fits and insets, beside the original.
46
+ - *Copy Style* and *Paste Style* (Ctrl+Alt+C, Ctrl+Alt+V) give one or
47
+ several plots the look of another, as one undo step.
48
+ - *Same x range in all panels* and *Same y range in all panels*, from a
49
+ panel's menu in the outline or the preview.
50
+ - *Edit > Copy Figure* (Ctrl+Shift+C) puts the figure on the clipboard as
51
+ PNG and SVG, for slides and documents. When figscribe closes, only the
52
+ bitmap stays on the clipboard.
53
+ - *Show in Finder* (Explorer, file manager) on a data file in the outline.
54
+
55
+ **Fixed**
56
+ - With openai 3.38 or later, the assistant again says why Gemini refused a
57
+ request and how long to wait at a rate limit.
58
+ - A color the panel refused no longer breaks the panel the next time it is
59
+ built.
60
+ - The script names the encoding of a UTF-8 data file with letters beyond
61
+ ASCII, so that it also reads the file on Windows.
62
+
63
+ ## 0.1.0 — 2026-10-26
64
+
65
+ The first release to use: 0.0.1 and 0.0.2 only held the name on PyPI.
66
+
67
+ **Figures**
68
+ - Line, scatter and error bar plots, histograms, images and color meshes
69
+ with color bars, notes of one or several lines with or without an arrow,
70
+ reference lines and shaded ranges.
71
+ - Exact sizes in mm and pt; several panels in a grid, insets, shared axes,
72
+ a second y axis on the right; panel letters for journals.
73
+ - Axes in plain terms: range, linear, log or symlog, major ticks every *n*
74
+ with minor ticks between them, values shown with an SI prefix (Hz drawn
75
+ as kHz), tick marks inward or outward and on all sides.
76
+ - Line widths of the frame, the ticks and the grid, and the grid's color and
77
+ line style; an error bar plot's line has a width apart from its bars.
78
+ - Export as PDF, SVG and PNG.
79
+
80
+ **Data**
81
+ - CSV with automatic detection of delimiter, decimal comma, header and unit
82
+ rows and text encoding; NumPy `.npy` and `.npz`.
83
+ - Each file is recorded with its SHA-256: a changed or moved file is
84
+ reported, and its new version or new place taken on request.
85
+ - A data file can be removed with the plots that draw from it.
86
+
87
+ **Fits**
88
+ - Linear, polynomial, Gaussian, Lorentzian, exponential, or an expression of
89
+ your own, weighted by the error bars, with residuals in a panel below.
90
+ - Fit only a range of x; hold parameters fixed or within bounds; change a
91
+ fit that is already there; copy the result.
92
+ - Leave points out by right-clicking them, or have outliers found and left
93
+ out on confirmation. The data file stays as it is.
94
+
95
+ **The generated script**
96
+ - Every figure as a standalone Python script that needs only NumPy,
97
+ Matplotlib and, for fits, SciPy, and reads as if a person wrote it. A test
98
+ compares its output with figscribe's own pixel by pixel.
99
+
100
+ **Working with it**
101
+ - Select in the figure or the contents list, edit in the property panel,
102
+ undo every step. Zoom and pan with the mouse, one axis at a time over its
103
+ frame; *Use as axis range* keeps the view.
104
+ - An assistant that makes changes described in words: Claude, ChatGPT,
105
+ Google Gemini or a local model, built in (`figscribe[chat]`) or through
106
+ MCP (`figscribe[mcp]`). It sees names, never the data values.
107
+ `figscribe bench` measures how well a model does.
@@ -0,0 +1,47 @@
1
+ # figscribe — notes for coding sessions
2
+
3
+ A desktop GUI for scientific Matplotlib figures that exports standalone Python
4
+ code. Start here:
5
+
6
+ - **What to work on:** [`plan/roadmap.md`](plan/roadmap.md). Take the first
7
+ open task of the lowest open phase unless the user names another, and follow
8
+ its "How to work on this plan" rules. Tasks marked `[decide]` need the
9
+ user's answer before any code.
10
+ - **Why things are as they are:** [`docs/decisions.md`](docs/decisions.md).
11
+ Read the entries for the area you touch before changing it.
12
+ - **The vision and non-goals:** [`plan/project-brief.md`](plan/project-brief.md).
13
+ - **How the GUI is tested:** [`docs/testing.md`](docs/testing.md), and the
14
+ manual checklist there for changes that are seen.
15
+
16
+ ## Architecture in one paragraph
17
+
18
+ The plot model (`src/figscribe/model/`, pydantic) is the single source of
19
+ truth. The renderer (`render/`), the code generator (`codegen/`) and the
20
+ project file all read it; the GUI (`gui/`) and the assistant (`assistant/`,
21
+ `tools/`) change it only through undoable commands (`commands/`). A model
22
+ field is not finished until it reaches the renderer, the code generator, the
23
+ property panel and the assistant tools.
24
+
25
+ ## Commands
26
+
27
+ ```
28
+ pip install -e ".[dev]" # all extras, as CI installs them
29
+ python -m pytest -q -n auto --dist worksteal # in parallel; Qt runs offscreen
30
+ python -m pytest -q -m "not slow" # without the GUI sweeps, while iterating
31
+ ruff check . && ruff format --check . # CI fails on either
32
+ FIGSCRIBE_UPDATE_BASELINES=1 python -m pytest tests/render/test_images.py
33
+ # after an intended rendering change;
34
+ # look at the new images before committing
35
+ ```
36
+
37
+ ## Conventions
38
+
39
+ - Everything in the repository is English, commit messages included.
40
+ - Test first; for a bug, a failing test that reproduces it comes first.
41
+ - One task per branch, branched from `develop`; pull request into `develop`;
42
+ CI must be green on Linux, Windows and macOS. `main` only gets releases.
43
+ - Commits use the repository's configured identity (f4ren); no
44
+ Co-Authored-By trailer.
45
+ - No new dependency without a rationale in `docs/decisions.md`.
46
+ - Generated scripts must read as if a person wrote them: no figscribe
47
+ helpers, named variables, comments where they help.
@@ -0,0 +1,395 @@
1
+ Metadata-Version: 2.5
2
+ Name: figscribe
3
+ Version: 0.2.0
4
+ Summary: A lean desktop GUI for scientific Matplotlib plots that exports clean, standalone Python code instead of a proprietary project file.
5
+ Project-URL: Homepage, https://github.com/f4ren/figscribe
6
+ Project-URL: Source, https://github.com/f4ren/figscribe
7
+ Project-URL: Issues, https://github.com/f4ren/figscribe/issues
8
+ Author-email: f4ren <faren@researchpoint.net>
9
+ License-Expression: MIT
10
+ License-File: LICENSE
11
+ Keywords: code-generation,gui,matplotlib,plotting,science
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Environment :: X11 Applications :: Qt
14
+ Classifier: Intended Audience :: Science/Research
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Topic :: Scientific/Engineering :: Visualization
20
+ Requires-Python: >=3.11
21
+ Requires-Dist: matplotlib>=3.8
22
+ Requires-Dist: numpy>=1.26
23
+ Requires-Dist: pydantic>=2.9
24
+ Requires-Dist: pyside6>=6.6
25
+ Requires-Dist: scipy>=1.11
26
+ Provides-Extra: chat
27
+ Requires-Dist: anthropic>=1.15; extra == 'chat'
28
+ Requires-Dist: openai>=1.60; extra == 'chat'
29
+ Provides-Extra: dev
30
+ Requires-Dist: anthropic>=1.15; extra == 'dev'
31
+ Requires-Dist: mcp>=2.3; extra == 'dev'
32
+ Requires-Dist: openai>=1.60; extra == 'dev'
33
+ Requires-Dist: pytest-qt>=4.4; extra == 'dev'
34
+ Requires-Dist: pytest-xdist>=3.6; extra == 'dev'
35
+ Requires-Dist: pytest>=8; extra == 'dev'
36
+ Requires-Dist: ruff>=0.15; extra == 'dev'
37
+ Provides-Extra: mcp
38
+ Requires-Dist: mcp>=2.3; extra == 'mcp'
39
+ Description-Content-Type: text/markdown
40
+
41
+ # figscribe
42
+
43
+ A lean desktop application for scientific figures: build and fine-tune plots
44
+ interactively, and get **clean, standalone Python code** (Matplotlib) as the
45
+ result instead of a proprietary project file.
46
+
47
+ For scientists and engineers who find Origin unwieldy but don't want to
48
+ hand-write every detail of a Matplotlib figure just to fine-tune it.
49
+
50
+ - **What you see is what you get**: the preview, the exported PDF and the
51
+ generated script draw the same figure, checked pixel by pixel in the tests.
52
+ - **Sizes as journals state them**: millimetres and points, with layouts for
53
+ single and double columns.
54
+ - **Everything is undoable**, including changes made by an AI assistant.
55
+ - **Your data stays yours**: an assistant sees file and column names, never
56
+ the values, and can run entirely on your own computer.
57
+
58
+ ## Contents
59
+
60
+ - [Status](#status)
61
+ - [Installation](#installation)
62
+ - [Getting started](#getting-started)
63
+ - [Features](#features)
64
+ - [The generated code](#the-generated-code)
65
+ - [AI assistants](#ai-assistants)
66
+ - [Keyboard and mouse](#keyboard-and-mouse)
67
+ - [Command line](#command-line)
68
+ - [How it works](#how-it-works)
69
+ - [Scope](#scope)
70
+ - [Development](#development)
71
+
72
+ ## Status
73
+
74
+ **Alpha.** The features below are implemented and tested on Linux, Windows
75
+ and macOS with Python 3.11 and 3.13. Behaviour may still change; project files
76
+ of a release keep opening in later ones. Still to come: style templates,
77
+ snapping and alignment guides, and installers that need no Python (see the
78
+ [roadmap](https://github.com/f4ren/figscribe/blob/main/plan/roadmap.md)). What changed in each release is in the
79
+ [changelog](https://github.com/f4ren/figscribe/blob/main/CHANGELOG.md).
80
+
81
+ ## Installation
82
+
83
+ figscribe needs Python 3.11 or newer. The simplest way is
84
+ [pipx](https://pipx.pypa.io) or [uv](https://docs.astral.sh/uv/), which give
85
+ figscribe an environment of its own and put the `figscribe` command on the
86
+ path:
87
+
88
+ ```
89
+ pipx install figscribe # or: uv tool install figscribe
90
+ figscribe
91
+ ```
92
+
93
+ `pip install figscribe` in a virtual environment works as well. With Qt,
94
+ Matplotlib and SciPy the installation takes about 1.5 GB.
95
+
96
+ Optional parts:
97
+
98
+ | Extra | For |
99
+ |---|---|
100
+ | `pipx install "figscribe[chat]"` | the built-in AI assistant (Claude, ChatGPT, Gemini, local models) |
101
+ | `pipx install "figscribe[mcp]"` | driving figscribe from an MCP client such as Claude Desktop |
102
+ | `pipx install "figscribe[chat,mcp]"` | both |
103
+
104
+ To add an extra later, install again with `--force`. To update:
105
+ `pipx upgrade figscribe`. To work on figscribe itself, see
106
+ [Development](#development).
107
+
108
+ ## Getting started
109
+
110
+ Start `figscribe`. The start page offers three ways in:
111
+
112
+ - **Open a data file** (CSV, `.npy`, `.npz`). The import dialog detects
113
+ delimiter, decimal comma, header and unit rows, shows a preview and suggests
114
+ a first plot, e.g. *U against t with error bars dU*.
115
+ - **Pick a layout**: single column (85 × 60 mm), double column
116
+ (175 × 70 mm), data with fit and residuals, two panels side by side, or a
117
+ 16:9 slide.
118
+ - **Open the example**, a Gaussian scan with error bars.
119
+
120
+ Then click anything in the figure or in the contents list to select it, and
121
+ change it in the property panel on the right. *Insert* adds data, plots,
122
+ fits, notes and panels; *File > Export* writes the figure or its Python
123
+ script.
124
+
125
+ ## Features
126
+
127
+ **Data**
128
+ - CSV files with automatic detection of delimiter, decimal comma, header and
129
+ unit rows, and of the text encoding (UTF-8, or cp1252 and Latin-1 as older
130
+ Windows programs write them); NumPy `.npy` and `.npz` files (read without pickles, so a data
131
+ file cannot run code).
132
+ - A data file is removed with the plots that draw from it, in one undo step;
133
+ figscribe asks first and names them.
134
+ - Each data file is recorded with its SHA-256. If it changes later,
135
+ figscribe says so instead of silently plotting different data, shows
136
+ what changed, and takes the new version on request, or lets you point it
137
+ to where a moved file went.
138
+
139
+ **Plots**
140
+ - Line, scatter, error bars, histograms, images and color meshes with color
141
+ bars (linear or logarithmic), notes of one or several lines with or
142
+ without an arrow, reference lines and shaded ranges.
143
+ - The Allan deviation of a time series (fractional frequency or phase), on
144
+ log-log axes, for the stability of clocks, lasers and sensors.
145
+ - Curve fits: linear, polynomial, Gaussian, Lorentzian, exponential, or your
146
+ own expression such as `a*exp(-x/tau)+c`. Error bars weight the fit; the
147
+ result shows each parameter with its 1σ uncertainty, the reduced χ² and
148
+ the residuals, and can be copied. Residuals go into their own panel below.
149
+ - Fit only a range of x; hold a parameter at its start value or keep it
150
+ within bounds; change a fit that is already there.
151
+ - Leave points out by right-clicking them, or let the fit dialog find
152
+ outliers and leave them out once you confirm. The data file stays as it
153
+ is: the points are drawn apart, in light grey or a color and marker of
154
+ your choice, and the script leaves out the same rows, with the reason.
155
+ - Show a plot's values in other units, e.g. seconds as hours since the
156
+ start of the measurement or volts as mV, and draw it from part of its data
157
+ only. The axis, its marks and the fits follow in the same step.
158
+
159
+ **Layout and style**
160
+ - Several figures in one project, as tabs above the figure: add one with
161
+ the look of the others, duplicate, rename, reorder.
162
+ - Several panels in a grid: add one to the right, left, above or below, or
163
+ as an inset. Shared x and y axes, a second y axis on the right.
164
+ - Exact figure size in mm, font sizes in pt, linear, log and symlog axes,
165
+ axis ranges, major ticks every *n* with minor ticks between them, tick
166
+ marks inward or outward and on all four sides, grid, LaTeX in labels
167
+ (`$\Delta U$`), units in labels as `U (V)`, `U / V` or `U [V]`.
168
+ - Panel letters, (a), (b), … in the style a journal asks for.
169
+ - Line widths of the frame, the tick marks and the grid, tick lengths, and
170
+ a dashed or dotted grid.
171
+ - Values shown with an SI prefix: frequencies in Hz drawn as `0 … 225` with
172
+ the label `f (kHz)`, while the data stay in Hz.
173
+
174
+ **Working with the figure**
175
+ - Click to select, edit in the property panel; every change is one undo
176
+ step. Click an axis, its numbers or its label, to set its range, ticks and
177
+ label in plain terms: *major ticks every 50*, *4 minor ticks between
178
+ them*; with values shown in kHz you type the range in kHz too. Hovering
179
+ over a field explains it.
180
+ - Colors come from the figure's color cycle, the colors used lately, or
181
+ *Same as…* another plot; a plot's context menu brings it to the front or
182
+ sends it to the back.
183
+ - Numbers can be typed with a decimal comma (`1,5`), a minus copied from a
184
+ PDF (`−3`) and spaces between thousands (`10 000`); lists of them are then
185
+ separated by semicolons (`0; 0,5; 1`).
186
+ - Drag the legend and notes into place. Zoom with the mouse wheel and pan by
187
+ dragging to look closer; *Use as axis range* keeps that view.
188
+ - Double-click an axis label, a title, a legend entry or a note to edit it
189
+ where it is drawn. Drag an inset's corner to resize it, or the gap between
190
+ two panels to share the room between them differently. Right-click a plot
191
+ to style it like another, bring it to the front, fit or hide it.
192
+ - The **Python code** drawer under the figure shows the generated script,
193
+ live, as you edit.
194
+
195
+ **Export**
196
+ - PDF, SVG and PNG at the exact size; *File > Export All Figures…* writes
197
+ every figure of the project at once.
198
+ - A standalone Python script (see below).
199
+ - The project itself as a readable JSON file (`.figscribe`).
200
+
201
+ ## The generated code
202
+
203
+ *File > Export Python Script…* (or `figscribe script PROJECT.figscribe -o
204
+ plot.py`) writes a script that needs only NumPy, Matplotlib and, for fits,
205
+ SciPy. It is written the way a person would write it: no figscribe helpers,
206
+ named variables, comments where they help. From the example project's script
207
+ (shortened):
208
+
209
+ ```python
210
+ scan_path = HERE / "scan.csv"
211
+ # The figure was made from exactly this file; delete the check to plot a newer one.
212
+ if hashlib.sha256(scan_path.read_bytes()).hexdigest() != (
213
+ "380a81c153b9c2f47f51c67b4478a251b9e98fd1ba0e533b010e9fb5eab92394"
214
+ ):
215
+ raise SystemExit(f"{scan_path} changed since the figure was made")
216
+ t, U, dU = np.loadtxt(scan_path, delimiter=",", skiprows=3, unpack=True)
217
+
218
+ fig = plt.figure(figsize=(85 / 25.4, 60 / 25.4), dpi=300, layout="constrained")
219
+ ax = fig.add_subplot()
220
+ ax.set_xlabel("$t$ (s)")
221
+ ax.set_ylabel("$U$ (V)")
222
+ ax.errorbar(t, U, yerr=dU, linestyle="none", marker="o", markersize=4,
223
+ capsize=2, elinewidth=1, label="measurement")
224
+ ax.legend(fontsize=8, loc="best")
225
+ fig.savefig(HERE / "scan.pdf")
226
+ ```
227
+
228
+ Fits are redone in the script with `scipy.optimize.curve_fit` from the same
229
+ start values. A test runs the generated scripts and compares their PNG pixel
230
+ by pixel with figscribe's own export.
231
+
232
+ ## AI assistants
233
+
234
+ Describe a change in words, *"two panels, residuals below, shared x axis"*,
235
+ and an assistant makes it in the open project. It works through the same
236
+ commands as the GUI, so every change appears at once and can be undone.
237
+
238
+ What an assistant sees: the project's structure and the names of files and
239
+ columns, never the data values. It sees the rendered figure only if you
240
+ allow it (*Assistant > Let It See the Figure*).
241
+
242
+ ### The built-in chat
243
+
244
+ *Assistant > Chat* (Ctrl+Shift+A) opens the chat under the figure;
245
+ *Assistant > Choose Model…* selects what it talks to:
246
+
247
+ - **Claude** through the Anthropic API: set `ANTHROPIC_API_KEY` or sign in
248
+ with `ant auth login`.
249
+ - **ChatGPT**, or any server with the OpenAI chat completions API: set
250
+ `OPENAI_API_KEY`.
251
+ - **Google Gemini**: a key from Google AI Studio, in `GEMINI_API_KEY`. The
252
+ Gemini API has a free tier. *Load models* also lists models that a new key
253
+ cannot use or that do not answer; `gemini-3.5-flash` works (October 2026).
254
+ - **A local model** in LM Studio (`http://localhost:1234/v1`), Ollama
255
+ (`http://localhost:11434/v1`), vLLM or llama.cpp: nothing leaves your
256
+ computer. *Load models* lists what the server offers; choose one that
257
+ supports tool calling.
258
+
259
+ figscribe remembers the model and server. An API key typed into figscribe is
260
+ kept for the session only and never saved.
261
+
262
+ ### Other assistants through MCP
263
+
264
+ figscribe is also an MCP server, for Claude Desktop, Claude Code, IDEs or
265
+ front ends for local models:
266
+
267
+ ```
268
+ pipx install "figscribe[mcp]"
269
+ claude mcp add figscribe -- figscribe mcp # e.g. for Claude Code
270
+ ```
271
+
272
+ Then enable *Assistant > Allow MCP Access*; *Assistant > How to Connect…*
273
+ shows the configuration for other clients.
274
+
275
+ ### Without paying per request
276
+
277
+ - **A local model** costs nothing and keeps everything on your computer;
278
+ how well it works depends on its size (see below).
279
+ - **Gemini's free tier**: a key from Google AI Studio, without billing.
280
+ It allows 5 requests a minute and 20 a day per model (October 2026), and
281
+ one change takes the assistant two to four: enough to try the assistant,
282
+ a handful of changes a day. figscribe says how long to wait; another
283
+ model has its own limit. Outside the EU, EEA,
284
+ Switzerland and the UK, Google may use free-tier requests to improve its
285
+ products and have people read them; inside, its paid-service data terms
286
+ apply. Either way the model sees names, not data values.
287
+ - **A subscription** (Claude Pro or Max, ChatGPT Plus) does not give API
288
+ access, and Anthropic does not allow its sign-in in other applications.
289
+ It can drive figscribe through MCP instead: in Claude Desktop or Claude
290
+ Code with your Claude plan, or in Gemini CLI with a Google account.
291
+
292
+ ### Which model is good enough?
293
+
294
+ `figscribe bench` measures it: it gives a model 21 typical requests, from
295
+ easy (an axis label, a range, a colour) to hard (an inset zoomed in, a fit
296
+ with residual panel), on the example project and checks each result
297
+ automatically.
298
+
299
+ ```
300
+ figscribe bench --base-url http://localhost:1234/v1 --model qwen3-30b --no-vision
301
+ figscribe bench --model claude-sonnet-5-5 --json sonnet.json
302
+ ```
303
+
304
+ `figscribe bench --list` shows the cases, `--level easy` runs only the easy
305
+ ones, `--repeat 3` evens out variation between runs.
306
+
307
+ ## Keyboard and mouse
308
+
309
+ | Action | Keys |
310
+ |---|---|
311
+ | New, open, save, save as, quit | the system's usual keys (Ctrl+N, Ctrl+O, Ctrl+S, …) |
312
+ | Undo, redo, delete the selection | Ctrl+Z, Ctrl+Shift+Z or Ctrl+Y, Del |
313
+ | Duplicate the selected plots, panel or figure | Ctrl+D |
314
+ | Copy a plot's style, paste it onto others | Ctrl+Alt+C, Ctrl+Alt+V |
315
+ | Copy the figure to the clipboard (PNG and SVG) | Ctrl+Shift+C |
316
+ | Import data | Ctrl+I |
317
+ | Find a setting of the selection | Ctrl+F, then type |
318
+ | Find any setting or menu action | Ctrl+K, then type and Enter |
319
+ | Add plot, add fit | Ctrl+Shift+P, Ctrl+Shift+F |
320
+ | Export figure, export Python script | Ctrl+E, Ctrl+Shift+E |
321
+ | Show the Python code | Ctrl+Shift+Y |
322
+ | Show the assistant | Ctrl+Shift+A |
323
+ | Zoom a panel | mouse wheel; with Shift only x, with Ctrl or Alt only y |
324
+ | Pan a panel | drag its background |
325
+ | Edit a label, title, legend entry or note | double-click it |
326
+ | Back to the axis ranges of the model | Esc |
327
+
328
+ On macOS, Cmd takes the place of Ctrl.
329
+
330
+ ## Command line
331
+
332
+ ```
333
+ figscribe [PROJECT.figscribe] open the GUI, optionally with a project
334
+ figscribe script PROJECT.figscribe [--figure NAME] [-o FILE]
335
+ write the Python script for a figure
336
+ (by default the first)
337
+ figscribe script PROJECT.figscribe --all [-o FOLDER]
338
+ write a script for every figure
339
+ figscribe mcp serve the open project to an MCP client
340
+ figscribe bench [--help] measure a model on typical assistant requests
341
+ figscribe --version
342
+ ```
343
+
344
+ ## How it works
345
+
346
+ A typed, declarative plot model is the single source of truth:
347
+
348
+ ```
349
+ Plot model (typed, JSON-serializable)
350
+ ├──> Renderer: model -> Matplotlib figure (preview AND export)
351
+ ├──> Code generator: model -> standalone Python script
352
+ └──> Persistence: model <-> JSON project file
353
+ ```
354
+
355
+ The GUI and the assistants only ever change the model, through commands with
356
+ undo and redo; they never touch Matplotlib objects. Preview and export share
357
+ the renderer, and the code generator mirrors its calls, so all three show the
358
+ same figure.
359
+
360
+ Design decisions and their reasons are recorded in
361
+ [`docs/decisions.md`](https://github.com/f4ren/figscribe/blob/main/docs/decisions.md); the project brief is in
362
+ [`plan/project-brief.md`](https://github.com/f4ren/figscribe/blob/main/plan/project-brief.md) and the work list in
363
+ [`plan/roadmap.md`](https://github.com/f4ren/figscribe/blob/main/plan/roadmap.md).
364
+
365
+ ## Scope
366
+
367
+ Planned beyond what exists: HDF5 import, style templates for journals,
368
+ snapping and alignment guides, installers.
369
+
370
+ Deliberately out of scope: spreadsheet-style data editing, a statistics
371
+ suite, a scripting language of its own, and reading generated or other
372
+ Python code back in. An analysis comes in only as a standard figure of some
373
+ field that the script can compute in a few lines, such as the Allan
374
+ deviation (decision 0036).
375
+
376
+ ## Development
377
+
378
+ ```
379
+ git clone https://github.com/f4ren/figscribe.git
380
+ cd figscribe
381
+ pip install -e ".[dev]"
382
+ pytest -n auto # all tests, in parallel, the GUI ones without windows
383
+ ruff check . && ruff format --check .
384
+ ```
385
+
386
+ The tests include image comparisons of the renderer, the pixel comparison
387
+ of generated scripts, undo for every command, and GUI tests with pytest-qt.
388
+ Work happens test-first on one branch per feature, merged by pull request
389
+ once CI is green on Linux, Windows and macOS.
390
+
391
+ Built with Python, PySide6 (Qt), Matplotlib, NumPy, SciPy and Pydantic.
392
+
393
+ ## License
394
+
395
+ MIT, see [LICENSE](https://github.com/f4ren/figscribe/blob/main/LICENSE).