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.
- figscribe-0.2.0/.gitattributes +3 -0
- figscribe-0.2.0/.github/workflows/tests.yml +48 -0
- figscribe-0.2.0/CHANGELOG.md +107 -0
- figscribe-0.2.0/CLAUDE.md +47 -0
- figscribe-0.2.0/PKG-INFO +395 -0
- figscribe-0.2.0/README.md +355 -0
- figscribe-0.2.0/docs/decisions.md +1843 -0
- figscribe-0.2.0/docs/testing.md +78 -0
- figscribe-0.2.0/examples/gaussian-scan/scan.csv +44 -0
- figscribe-0.2.0/examples/gaussian-scan/scan.figscribe +115 -0
- figscribe-0.2.0/packaging/icons/figscribe.icns +0 -0
- figscribe-0.2.0/packaging/icons/figscribe.ico +0 -0
- figscribe-0.2.0/packaging/icons/figscribe.png +0 -0
- figscribe-0.2.0/packaging/make_icons.py +54 -0
- {figscribe-0.0.2 → figscribe-0.2.0}/plan/project-brief.md +13 -8
- figscribe-0.2.0/plan/roadmap.md +1144 -0
- {figscribe-0.0.2 → figscribe-0.2.0}/pyproject.toml +48 -2
- figscribe-0.2.0/src/figscribe/__init__.py +8 -0
- figscribe-0.2.0/src/figscribe/__main__.py +5 -0
- figscribe-0.2.0/src/figscribe/allan.py +100 -0
- figscribe-0.2.0/src/figscribe/assistant/__init__.py +5 -0
- figscribe-0.2.0/src/figscribe/assistant/bench.py +743 -0
- figscribe-0.2.0/src/figscribe/assistant/chat.py +660 -0
- figscribe-0.2.0/src/figscribe/assistant/endpoint.py +102 -0
- figscribe-0.2.0/src/figscribe/assistant/mcpserver.py +143 -0
- figscribe-0.2.0/src/figscribe/assistant/toolserver.py +132 -0
- figscribe-0.2.0/src/figscribe/cli.py +136 -0
- figscribe-0.2.0/src/figscribe/codegen/__init__.py +10 -0
- figscribe-0.2.0/src/figscribe/codegen/script.py +1562 -0
- figscribe-0.2.0/src/figscribe/commands/__init__.py +62 -0
- figscribe-0.2.0/src/figscribe/commands/core.py +320 -0
- figscribe-0.2.0/src/figscribe/commands/history.py +95 -0
- figscribe-0.2.0/src/figscribe/commands/ops.py +449 -0
- figscribe-0.2.0/src/figscribe/convert.py +126 -0
- figscribe-0.2.0/src/figscribe/examples.py +17 -0
- figscribe-0.2.0/src/figscribe/fit/__init__.py +58 -0
- figscribe-0.2.0/src/figscribe/fit/expression.py +229 -0
- figscribe-0.2.0/src/figscribe/fit/models.py +115 -0
- figscribe-0.2.0/src/figscribe/fit/ops.py +273 -0
- figscribe-0.2.0/src/figscribe/fit/run.py +321 -0
- figscribe-0.2.0/src/figscribe/gui/__init__.py +11 -0
- figscribe-0.2.0/src/figscribe/gui/app.py +39 -0
- figscribe-0.2.0/src/figscribe/gui/assistantdialog.py +176 -0
- figscribe-0.2.0/src/figscribe/gui/axisform.py +439 -0
- figscribe-0.2.0/src/figscribe/gui/chatpanel.py +243 -0
- figscribe-0.2.0/src/figscribe/gui/clipboard.py +38 -0
- figscribe-0.2.0/src/figscribe/gui/codepanel.py +135 -0
- figscribe-0.2.0/src/figscribe/gui/databar.py +142 -0
- figscribe-0.2.0/src/figscribe/gui/document.py +251 -0
- figscribe-0.2.0/src/figscribe/gui/effect.py +154 -0
- figscribe-0.2.0/src/figscribe/gui/errors.py +47 -0
- figscribe-0.2.0/src/figscribe/gui/figures.py +172 -0
- figscribe-0.2.0/src/figscribe/gui/fitdialog.py +641 -0
- figscribe-0.2.0/src/figscribe/gui/fonts/IBMPlexMono-Medium.ttf +0 -0
- figscribe-0.2.0/src/figscribe/gui/fonts/IBMPlexMono-Regular.ttf +0 -0
- figscribe-0.2.0/src/figscribe/gui/fonts/IBMPlexSans-Medium.ttf +0 -0
- figscribe-0.2.0/src/figscribe/gui/fonts/IBMPlexSans-Regular.ttf +0 -0
- figscribe-0.2.0/src/figscribe/gui/fonts/IBMPlexSans-SemiBold.ttf +0 -0
- figscribe-0.2.0/src/figscribe/gui/fonts/OFL.txt +93 -0
- figscribe-0.2.0/src/figscribe/gui/icons/check.svg +1 -0
- figscribe-0.2.0/src/figscribe/gui/icons/chevron-down.svg +1 -0
- figscribe-0.2.0/src/figscribe/gui/icons/figscribe-small.svg +24 -0
- figscribe-0.2.0/src/figscribe/gui/icons/figscribe.svg +31 -0
- figscribe-0.2.0/src/figscribe/gui/importdialog.py +508 -0
- figscribe-0.2.0/src/figscribe/gui/inplace.py +119 -0
- figscribe-0.2.0/src/figscribe/gui/mainwindow.py +1519 -0
- figscribe-0.2.0/src/figscribe/gui/markers.py +75 -0
- figscribe-0.2.0/src/figscribe/gui/markings.py +84 -0
- figscribe-0.2.0/src/figscribe/gui/names.py +264 -0
- figscribe-0.2.0/src/figscribe/gui/numbers.py +50 -0
- figscribe-0.2.0/src/figscribe/gui/outline.py +387 -0
- figscribe-0.2.0/src/figscribe/gui/palette.py +124 -0
- figscribe-0.2.0/src/figscribe/gui/plotdialog.py +413 -0
- figscribe-0.2.0/src/figscribe/gui/preview.py +1189 -0
- figscribe-0.2.0/src/figscribe/gui/problems.py +35 -0
- figscribe-0.2.0/src/figscribe/gui/properties.py +2084 -0
- figscribe-0.2.0/src/figscribe/gui/resize.py +151 -0
- figscribe-0.2.0/src/figscribe/gui/reveal.py +38 -0
- figscribe-0.2.0/src/figscribe/gui/start.py +376 -0
- figscribe-0.2.0/src/figscribe/gui/theme.py +452 -0
- figscribe-0.2.0/src/figscribe/gui/widgets.py +153 -0
- figscribe-0.2.0/src/figscribe/io/__init__.py +16 -0
- figscribe-0.2.0/src/figscribe/io/csvfile.py +133 -0
- figscribe-0.2.0/src/figscribe/io/errors.py +9 -0
- figscribe-0.2.0/src/figscribe/io/files.py +32 -0
- figscribe-0.2.0/src/figscribe/io/numpyfile.py +79 -0
- figscribe-0.2.0/src/figscribe/io/provider.py +95 -0
- figscribe-0.2.0/src/figscribe/io/refresh.py +166 -0
- figscribe-0.2.0/src/figscribe/model/__init__.py +97 -0
- figscribe-0.2.0/src/figscribe/model/artists.py +490 -0
- figscribe-0.2.0/src/figscribe/model/axes.py +120 -0
- figscribe-0.2.0/src/figscribe/model/base.py +50 -0
- figscribe-0.2.0/src/figscribe/model/data.py +105 -0
- figscribe-0.2.0/src/figscribe/model/figure.py +101 -0
- figscribe-0.2.0/src/figscribe/model/project.py +78 -0
- figscribe-0.2.0/src/figscribe/model/styles.py +93 -0
- figscribe-0.2.0/src/figscribe/model/validate.py +620 -0
- figscribe-0.2.0/src/figscribe/render/__init__.py +14 -0
- figscribe-0.2.0/src/figscribe/render/export.py +55 -0
- figscribe-0.2.0/src/figscribe/render/figure.py +743 -0
- figscribe-0.2.0/src/figscribe/render/labels.py +108 -0
- figscribe-0.2.0/src/figscribe/render/layout.py +96 -0
- figscribe-0.2.0/src/figscribe/render/ticks.py +164 -0
- figscribe-0.2.0/src/figscribe/suggest.py +140 -0
- figscribe-0.2.0/src/figscribe/templates.py +77 -0
- figscribe-0.2.0/src/figscribe/tools/__init__.py +43 -0
- figscribe-0.2.0/src/figscribe/tools/operations.py +544 -0
- figscribe-0.2.0/src/figscribe/tools/spec.py +296 -0
- figscribe-0.2.0/src/figscribe/tools/workspace.py +114 -0
- figscribe-0.2.0/src/figscribe/units.py +233 -0
- figscribe-0.2.0/tests/assistant/__init__.py +0 -0
- figscribe-0.2.0/tests/assistant/fakeserver.py +64 -0
- figscribe-0.2.0/tests/assistant/test_bench.py +125 -0
- figscribe-0.2.0/tests/assistant/test_chat.py +606 -0
- figscribe-0.2.0/tests/assistant/test_mcp.py +372 -0
- figscribe-0.2.0/tests/codegen/__init__.py +0 -0
- figscribe-0.2.0/tests/codegen/test_equivalence.py +979 -0
- figscribe-0.2.0/tests/codegen/test_script.py +186 -0
- figscribe-0.2.0/tests/commands/__init__.py +0 -0
- figscribe-0.2.0/tests/commands/test_commands.py +442 -0
- figscribe-0.2.0/tests/commands/test_everyday.py +270 -0
- figscribe-0.2.0/tests/commands/test_figures.py +122 -0
- figscribe-0.2.0/tests/commands/test_ops.py +173 -0
- figscribe-0.2.0/tests/commands/test_panels.py +111 -0
- figscribe-0.2.0/tests/conftest.py +115 -0
- figscribe-0.2.0/tests/dataio/__init__.py +0 -0
- figscribe-0.2.0/tests/dataio/test_csv.py +220 -0
- figscribe-0.2.0/tests/dataio/test_numpy.py +72 -0
- figscribe-0.2.0/tests/dataio/test_refresh.py +165 -0
- figscribe-0.2.0/tests/fit/__init__.py +0 -0
- figscribe-0.2.0/tests/fit/test_expression.py +124 -0
- figscribe-0.2.0/tests/fit/test_refit.py +150 -0
- figscribe-0.2.0/tests/fit/test_run.py +332 -0
- figscribe-0.2.0/tests/gui/__init__.py +0 -0
- figscribe-0.2.0/tests/gui/everything.py +169 -0
- figscribe-0.2.0/tests/gui/test_axis_form.py +225 -0
- figscribe-0.2.0/tests/gui/test_axis_zoom.py +115 -0
- figscribe-0.2.0/tests/gui/test_code_panel.py +60 -0
- figscribe-0.2.0/tests/gui/test_databar.py +119 -0
- figscribe-0.2.0/tests/gui/test_document.py +122 -0
- figscribe-0.2.0/tests/gui/test_errors.py +47 -0
- figscribe-0.2.0/tests/gui/test_every_action.py +185 -0
- figscribe-0.2.0/tests/gui/test_every_field.py +311 -0
- figscribe-0.2.0/tests/gui/test_everyday.py +354 -0
- figscribe-0.2.0/tests/gui/test_figures.py +181 -0
- figscribe-0.2.0/tests/gui/test_find.py +169 -0
- figscribe-0.2.0/tests/gui/test_fit.py +452 -0
- figscribe-0.2.0/tests/gui/test_images.py +146 -0
- figscribe-0.2.0/tests/gui/test_import.py +338 -0
- figscribe-0.2.0/tests/gui/test_inplace.py +122 -0
- figscribe-0.2.0/tests/gui/test_interaction.py +231 -0
- figscribe-0.2.0/tests/gui/test_left_out.py +108 -0
- figscribe-0.2.0/tests/gui/test_look.py +358 -0
- figscribe-0.2.0/tests/gui/test_mainwindow.py +182 -0
- figscribe-0.2.0/tests/gui/test_markings.py +113 -0
- figscribe-0.2.0/tests/gui/test_notes.py +126 -0
- figscribe-0.2.0/tests/gui/test_numbers.py +55 -0
- figscribe-0.2.0/tests/gui/test_panels.py +179 -0
- figscribe-0.2.0/tests/gui/test_plot_dialog.py +221 -0
- figscribe-0.2.0/tests/gui/test_plot_menu.py +104 -0
- figscribe-0.2.0/tests/gui/test_preview.py +122 -0
- figscribe-0.2.0/tests/gui/test_properties.py +619 -0
- figscribe-0.2.0/tests/gui/test_reading_options.py +39 -0
- figscribe-0.2.0/tests/gui/test_resize.py +144 -0
- figscribe-0.2.0/tests/gui/test_reveal.py +36 -0
- figscribe-0.2.0/tests/gui/test_right_axis.py +91 -0
- figscribe-0.2.0/tests/gui/test_several.py +135 -0
- figscribe-0.2.0/tests/gui/test_start.py +159 -0
- figscribe-0.2.0/tests/gui/test_ticks.py +75 -0
- figscribe-0.2.0/tests/gui/test_units_panel.py +107 -0
- figscribe-0.2.0/tests/gui/test_without_effect.py +323 -0
- figscribe-0.2.0/tests/render/__init__.py +0 -0
- figscribe-0.2.0/tests/render/baseline/converted.png +0 -0
- figscribe-0.2.0/tests/render/baseline/error_band.png +0 -0
- figscribe-0.2.0/tests/render/baseline/errorbar.png +0 -0
- figscribe-0.2.0/tests/render/baseline/line.png +0 -0
- figscribe-0.2.0/tests/render/baseline/panel_labels.png +0 -0
- figscribe-0.2.0/tests/render/baseline/reference_lines.png +0 -0
- figscribe-0.2.0/tests/render/baseline/scatter.png +0 -0
- figscribe-0.2.0/tests/render/baseline/two_panels.png +0 -0
- figscribe-0.2.0/tests/render/conftest.py +41 -0
- figscribe-0.2.0/tests/render/test_conversion.py +114 -0
- figscribe-0.2.0/tests/render/test_export.py +90 -0
- figscribe-0.2.0/tests/render/test_fit_render.py +80 -0
- figscribe-0.2.0/tests/render/test_images.py +223 -0
- figscribe-0.2.0/tests/render/test_labels.py +108 -0
- figscribe-0.2.0/tests/render/test_render.py +765 -0
- figscribe-0.2.0/tests/test_allan.py +146 -0
- figscribe-0.2.0/tests/test_convert.py +83 -0
- figscribe-0.2.0/tests/test_edge_data.py +225 -0
- figscribe-0.2.0/tests/test_examples.py +33 -0
- figscribe-0.2.0/tests/test_field_coverage.py +108 -0
- figscribe-0.2.0/tests/test_left_out.py +177 -0
- figscribe-0.2.0/tests/test_model_roundtrip.py +253 -0
- figscribe-0.2.0/tests/test_packaging_icons.py +26 -0
- figscribe-0.2.0/tests/test_styles.py +62 -0
- figscribe-0.2.0/tests/test_suggest.py +73 -0
- figscribe-0.2.0/tests/test_units.py +200 -0
- figscribe-0.2.0/tests/test_validate_references.py +297 -0
- figscribe-0.2.0/tests/tools/__init__.py +0 -0
- figscribe-0.2.0/tests/tools/test_tools.py +783 -0
- figscribe-0.0.2/PKG-INFO +0 -73
- figscribe-0.0.2/README.md +0 -51
- figscribe-0.0.2/src/figscribe/__init__.py +0 -7
- {figscribe-0.0.2 → figscribe-0.2.0}/.github/workflows/release.yml +0 -0
- {figscribe-0.0.2 → figscribe-0.2.0}/.gitignore +0 -0
- {figscribe-0.0.2 → figscribe-0.2.0}/LICENSE +0 -0
|
@@ -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.
|
figscribe-0.2.0/PKG-INFO
ADDED
|
@@ -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).
|