topdrawerx 0.5.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.
- topdrawerx-0.5.0/LICENSE +21 -0
- topdrawerx-0.5.0/PKG-INFO +203 -0
- topdrawerx-0.5.0/README.md +169 -0
- topdrawerx-0.5.0/pyproject.toml +52 -0
- topdrawerx-0.5.0/setup.cfg +4 -0
- topdrawerx-0.5.0/src/topdrawerx/__init__.py +66 -0
- topdrawerx-0.5.0/src/topdrawerx/backends/__init__.py +9 -0
- topdrawerx-0.5.0/src/topdrawerx/backends/json_backend.py +19 -0
- topdrawerx-0.5.0/src/topdrawerx/backends/matplotlib_backend.py +370 -0
- topdrawerx-0.5.0/src/topdrawerx/charsets.py +242 -0
- topdrawerx-0.5.0/src/topdrawerx/cli.py +98 -0
- topdrawerx-0.5.0/src/topdrawerx/commands/__init__.py +31 -0
- topdrawerx-0.5.0/src/topdrawerx/commands/_util.py +39 -0
- topdrawerx-0.5.0/src/topdrawerx/commands/annotate.py +165 -0
- topdrawerx-0.5.0/src/topdrawerx/commands/axes.py +128 -0
- topdrawerx-0.5.0/src/topdrawerx/commands/draw.py +125 -0
- topdrawerx-0.5.0/src/topdrawerx/commands/frame.py +48 -0
- topdrawerx-0.5.0/src/topdrawerx/commands/legend.py +93 -0
- topdrawerx-0.5.0/src/topdrawerx/commands/meta.py +174 -0
- topdrawerx-0.5.0/src/topdrawerx/commands/page.py +96 -0
- topdrawerx-0.5.0/src/topdrawerx/commands/read.py +35 -0
- topdrawerx-0.5.0/src/topdrawerx/commands/setcmd.py +257 -0
- topdrawerx-0.5.0/src/topdrawerx/commands/title.py +139 -0
- topdrawerx-0.5.0/src/topdrawerx/compat.py +98 -0
- topdrawerx-0.5.0/src/topdrawerx/data.py +128 -0
- topdrawerx-0.5.0/src/topdrawerx/display.py +441 -0
- topdrawerx-0.5.0/src/topdrawerx/errors.py +57 -0
- topdrawerx-0.5.0/src/topdrawerx/lexer.py +181 -0
- topdrawerx-0.5.0/src/topdrawerx/palettes.py +48 -0
- topdrawerx-0.5.0/src/topdrawerx/registry.py +108 -0
- topdrawerx-0.5.0/src/topdrawerx/repl.py +175 -0
- topdrawerx-0.5.0/src/topdrawerx/session.py +292 -0
- topdrawerx-0.5.0/src/topdrawerx/state.py +208 -0
- topdrawerx-0.5.0/src/topdrawerx/styles/classic.tdx +13 -0
- topdrawerx-0.5.0/src/topdrawerx/styles/notebook.tdx +8 -0
- topdrawerx-0.5.0/src/topdrawerx/styles/paper.tdx +9 -0
- topdrawerx-0.5.0/src/topdrawerx/styles/poster.tdx +8 -0
- topdrawerx-0.5.0/src/topdrawerx/styles/talk.tdx +8 -0
- topdrawerx-0.5.0/src/topdrawerx/styles.py +140 -0
- topdrawerx-0.5.0/src/topdrawerx/text.py +182 -0
- topdrawerx-0.5.0/src/topdrawerx.egg-info/PKG-INFO +203 -0
- topdrawerx-0.5.0/src/topdrawerx.egg-info/SOURCES.txt +57 -0
- topdrawerx-0.5.0/src/topdrawerx.egg-info/dependency_links.txt +1 -0
- topdrawerx-0.5.0/src/topdrawerx.egg-info/entry_points.txt +2 -0
- topdrawerx-0.5.0/src/topdrawerx.egg-info/requires.txt +8 -0
- topdrawerx-0.5.0/src/topdrawerx.egg-info/top_level.txt +1 -0
- topdrawerx-0.5.0/tests/test_annotate.py +130 -0
- topdrawerx-0.5.0/tests/test_axes.py +90 -0
- topdrawerx-0.5.0/tests/test_case.py +135 -0
- topdrawerx-0.5.0/tests/test_golden.py +30 -0
- topdrawerx-0.5.0/tests/test_legend.py +100 -0
- topdrawerx-0.5.0/tests/test_lexer.py +66 -0
- topdrawerx-0.5.0/tests/test_panels.py +148 -0
- topdrawerx-0.5.0/tests/test_reference.py +32 -0
- topdrawerx-0.5.0/tests/test_registry.py +37 -0
- topdrawerx-0.5.0/tests/test_render.py +45 -0
- topdrawerx-0.5.0/tests/test_session.py +189 -0
- topdrawerx-0.5.0/tests/test_styles.py +106 -0
- topdrawerx-0.5.0/tests/test_text.py +27 -0
topdrawerx-0.5.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Ken Suzuki
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: topdrawerx
|
|
3
|
+
Version: 0.5.0
|
|
4
|
+
Summary: A TopDrawer-flavoured plotting language for making publication figures
|
|
5
|
+
Author: Ken Suzuki
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/KenSuzukiRCNP/topdrawerx
|
|
8
|
+
Project-URL: Source, https://github.com/KenSuzukiRCNP/topdrawerx
|
|
9
|
+
Project-URL: Issues, https://github.com/KenSuzukiRCNP/topdrawerx/issues
|
|
10
|
+
Project-URL: Changelog, https://github.com/KenSuzukiRCNP/topdrawerx/blob/trunk/CHANGELOG.md
|
|
11
|
+
Keywords: plotting,visualization,particle physics,topdrawer,matplotlib
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Environment :: Console
|
|
14
|
+
Classifier: Intended Audience :: Science/Research
|
|
15
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
16
|
+
Classifier: Operating System :: OS Independent
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
22
|
+
Classifier: Topic :: Scientific/Engineering :: Physics
|
|
23
|
+
Classifier: Topic :: Scientific/Engineering :: Visualization
|
|
24
|
+
Requires-Python: >=3.10
|
|
25
|
+
Description-Content-Type: text/markdown
|
|
26
|
+
License-File: LICENSE
|
|
27
|
+
Requires-Dist: matplotlib>=3.7
|
|
28
|
+
Provides-Extra: repl
|
|
29
|
+
Requires-Dist: prompt_toolkit>=3.0; extra == "repl"
|
|
30
|
+
Provides-Extra: dev
|
|
31
|
+
Requires-Dist: pytest>=7; extra == "dev"
|
|
32
|
+
Requires-Dist: prompt_toolkit>=3.0; extra == "dev"
|
|
33
|
+
Dynamic: license-file
|
|
34
|
+
|
|
35
|
+
# topdrawerx
|
|
36
|
+
|
|
37
|
+
[](https://github.com/KenSuzukiRCNP/topdrawerx/actions/workflows/tests.yml)
|
|
38
|
+
[](https://pypi.org/project/topdrawerx/)
|
|
39
|
+
[](https://pypi.org/project/topdrawerx/)
|
|
40
|
+
[](LICENSE)
|
|
41
|
+
|
|
42
|
+
**A small command language for making publication figures — and the REPL to
|
|
43
|
+
go with it.** Type a few lines, get a figure. The file stays readable, the
|
|
44
|
+
session *is* the script, and the defaults are the ones a physics journal wants.
|
|
45
|
+
|
|
46
|
+
The package is `topdrawerx`; the command you type is `tdx`.
|
|
47
|
+
|
|
48
|
+
```
|
|
49
|
+
SET ORDER X Y DY
|
|
50
|
+
2.0 1.1 0.4
|
|
51
|
+
4.0 3.6 0.5
|
|
52
|
+
6.0 6.2 0.6
|
|
53
|
+
PLOT
|
|
54
|
+
JOIN DASHES
|
|
55
|
+
TITLE LEFT 'σ (μb)'
|
|
56
|
+
TITLE BOTTOM '$p_{K^-}$ (GeV/c)'
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
<img src="docs/images/panels-1.png" width="520" alt="a spectrum with a ratio panel beneath it">
|
|
60
|
+
|
|
61
|
+
The grammar is TopDrawer's — SLAC's plotting program from the late 1970s, which
|
|
62
|
+
a generation of particle physicists typed at daily and a few of us still miss.
|
|
63
|
+
topdrawerx keeps what made it pleasant, drops what only existed to serve card
|
|
64
|
+
readers and pen plotters, and renders through matplotlib.
|
|
65
|
+
|
|
66
|
+
## Why you might want it
|
|
67
|
+
|
|
68
|
+
**The session is the script.** Type at the prompt, watch the figure change, then
|
|
69
|
+
`SAVE work.tdx` — and that file reproduces the figure exactly, next year, on
|
|
70
|
+
another machine. Settings apply retroactively: `SET SCALE Y LOG` after you have
|
|
71
|
+
already plotted does the right thing, and `UNDO` works, because every command is
|
|
72
|
+
replayed from a log rather than smeared onto a canvas.
|
|
73
|
+
|
|
74
|
+
**The defaults are already right for a paper.** Black on white, serif, ticks
|
|
75
|
+
inward on all four sides, error bars without caps, 1/2/5 tick intervals. You are
|
|
76
|
+
not spending your afternoon undoing someone's idea of a pretty chart.
|
|
77
|
+
|
|
78
|
+
**Physics figures are one command each.** Systematic error boxes are `BOX` over
|
|
79
|
+
data with `DX`/`DY` columns. Upper limits are `ARROW DOWN`. A ratio panel under a
|
|
80
|
+
spectrum, sharing the axis, is two `SET WINDOW`s.
|
|
81
|
+
|
|
82
|
+
**It is small.** One dependency (matplotlib), about thirty commands, a plain-text
|
|
83
|
+
file format your colleague can read without installing anything.
|
|
84
|
+
|
|
85
|
+
## Install
|
|
86
|
+
|
|
87
|
+
```sh
|
|
88
|
+
pip install topdrawerx # or: pip install "topdrawerx[repl]"
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Python ≥ 3.10, matplotlib ≥ 3.7. The optional `[repl]` extra adds line editing
|
|
92
|
+
and completion via `prompt_toolkit`.
|
|
93
|
+
|
|
94
|
+
## A minute with it
|
|
95
|
+
|
|
96
|
+
```sh
|
|
97
|
+
tdx # interactive, with a live plot window
|
|
98
|
+
tdx figure.tdx # run a file, then stay interactive
|
|
99
|
+
tdx figure.tdx -o fig.pdf # batch: run it and write the figure
|
|
100
|
+
tdx --check old/*.top # what would these legacy files need?
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
At the prompt:
|
|
104
|
+
|
|
105
|
+
```
|
|
106
|
+
tdx> SET ORDER X Y DY
|
|
107
|
+
tdx> 2.0 1.1 0.4
|
|
108
|
+
tdx> 4.0 3.6 0.5
|
|
109
|
+
tdx> 6.0 6.2 0.6
|
|
110
|
+
tdx> PLOT
|
|
111
|
+
tdx> JOIN
|
|
112
|
+
tdx> SET SCALE Y LOG ← applies to what you already drew
|
|
113
|
+
tdx> LEGEND 'K⁻ beam'
|
|
114
|
+
tdx> SAVE 'fig.pdf' ← or SAVE 'work.tdx' for the script
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
The output format comes from the file name — `.pdf`, `.png`, `.svg`. There is no
|
|
118
|
+
device to configure. Several pages going to a PDF become one multi-page file.
|
|
119
|
+
|
|
120
|
+
From Python, the same engine:
|
|
121
|
+
|
|
122
|
+
```python
|
|
123
|
+
import topdrawerx as tdx
|
|
124
|
+
|
|
125
|
+
s = tdx.Session()
|
|
126
|
+
s.run(open("figure.tdx").read())
|
|
127
|
+
fig = tdx.figure(s.frame) # a real matplotlib Figure, yours to adjust
|
|
128
|
+
tdx.save(s.frames, "out.pdf")
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
## Gallery
|
|
132
|
+
|
|
133
|
+
Every one of these is a file in [`examples/`](examples), rendered by
|
|
134
|
+
`tools/render_examples.py`.
|
|
135
|
+
|
|
136
|
+
| | |
|
|
137
|
+
| --- | --- |
|
|
138
|
+
| [`demo.tdx`](examples/demo.tdx)<br>points, errors, a curve, a histogram | <img src="docs/images/demo-1.png" width="330"> |
|
|
139
|
+
| [`annotate.tdx`](examples/annotate.tdx)<br>systematic boxes, upper limits, placed text | <img src="docs/images/annotate.png" width="330"> |
|
|
140
|
+
| [`styles.tdx`](examples/styles.tdx)<br>`SET STYLE TALK`, a palette, a legend | <img src="docs/images/styles.png" width="330"> |
|
|
141
|
+
| [`panels.tdx`](examples/panels.tdx)<br>`ZONE 2 2` | <img src="docs/images/panels-2.png" width="330"> |
|
|
142
|
+
| [`legacy_case.top`](examples/legacy_case.top)<br>a 1980s file, unchanged | <img src="docs/images/legacy_case.png" width="330"> |
|
|
143
|
+
|
|
144
|
+
`styles.tdx` with `PAPER` instead of `TALK` is the same figure in monochrome
|
|
145
|
+
serif at journal size — one word.
|
|
146
|
+
|
|
147
|
+
## How it compares
|
|
148
|
+
|
|
149
|
+
| | |
|
|
150
|
+
| --- | --- |
|
|
151
|
+
| **gnuplot** | The closest relative: scriptable, has a REPL. topdrawerx has a smaller, more regular grammar (verb, qualifiers, no punctuation), physics defaults, and retroactive settings with `UNDO`. |
|
|
152
|
+
| **Grace / xmgrace** | GUI-first and largely unmaintained. topdrawerx is text-first, so figures live in version control. |
|
|
153
|
+
| **ROOT** | An analysis framework that also draws. topdrawerx is a plotting tool with one dependency. |
|
|
154
|
+
| **matplotlib** | The engine underneath. If you want a library, use it directly; topdrawerx is for when you want a *language* and a prompt. |
|
|
155
|
+
|
|
156
|
+
## Old TopDrawer files
|
|
157
|
+
|
|
158
|
+
A `.top` file from 1985 usually just plots. `CASE` lines are converted into
|
|
159
|
+
Unicode and maths, the plotter settings are accepted and ignored, and the legacy
|
|
160
|
+
symbol codes work:
|
|
161
|
+
|
|
162
|
+
```
|
|
163
|
+
TITLE TOP 'K2-3P R D2-3X0C12+3' → K⁻p → D⁻Ξ_c⁺
|
|
164
|
+
CASE ' X XL W X XFXLXX X'
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
Commands topdrawerx does not know are skipped with a note, and the file still
|
|
168
|
+
plots; `--strict` turns that off. `tdx --check <files>` reports which commands a
|
|
169
|
+
directory of old files actually uses — which is how the implementation order
|
|
170
|
+
gets decided.
|
|
171
|
+
|
|
172
|
+
Being faithful to the original is a nice-to-have, not the goal: where the 1978
|
|
173
|
+
constraint and the 2026 user disagree, the user wins.
|
|
174
|
+
|
|
175
|
+
## Documentation
|
|
176
|
+
|
|
177
|
+
- [Command reference](docs/reference.md) — generated from the source, so it
|
|
178
|
+
cannot drift.
|
|
179
|
+
- [Design notes](docs/design.md) — how the replay model, the display list and
|
|
180
|
+
the command registry fit together, and how to add a command.
|
|
181
|
+
- [Changelog](CHANGELOG.md).
|
|
182
|
+
|
|
183
|
+
## Status and scope
|
|
184
|
+
|
|
185
|
+
Version 0.x: working, tested (159 tests), and used for real figures, but the
|
|
186
|
+
grammar may still move. Planned next: `FIT` and `SPLINE`, `CONTOUR` with simple
|
|
187
|
+
3-D data, then a numpy API and a Jupyter cell magic. Deliberately not planned:
|
|
188
|
+
control flow (`IF`, `REPEAT`) — Python is the better scripting language and the
|
|
189
|
+
Python API is the seam — HBOOK, and most of the data arithmetic.
|
|
190
|
+
|
|
191
|
+
This is a personal project, shared because others may find it useful. Bug
|
|
192
|
+
reports and patches are welcome; replies may be slow, and there is no support
|
|
193
|
+
promise. If you use it in a paper, there is a DOI in [CITATION.cff](CITATION.cff).
|
|
194
|
+
|
|
195
|
+
## Licence and attribution
|
|
196
|
+
|
|
197
|
+
MIT — see [LICENSE](LICENSE).
|
|
198
|
+
|
|
199
|
+
TopDrawer was written at the Stanford Linear Accelerator Center. topdrawerx is
|
|
200
|
+
an independent reimplementation of its command language and is not affiliated
|
|
201
|
+
with, endorsed by, or derived from SLAC's code. The character-set tables in
|
|
202
|
+
`src/topdrawerx/charsets.py` were transcribed from the published TopDrawer
|
|
203
|
+
reference manual, and each cites the section it came from.
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
# topdrawerx
|
|
2
|
+
|
|
3
|
+
[](https://github.com/KenSuzukiRCNP/topdrawerx/actions/workflows/tests.yml)
|
|
4
|
+
[](https://pypi.org/project/topdrawerx/)
|
|
5
|
+
[](https://pypi.org/project/topdrawerx/)
|
|
6
|
+
[](LICENSE)
|
|
7
|
+
|
|
8
|
+
**A small command language for making publication figures — and the REPL to
|
|
9
|
+
go with it.** Type a few lines, get a figure. The file stays readable, the
|
|
10
|
+
session *is* the script, and the defaults are the ones a physics journal wants.
|
|
11
|
+
|
|
12
|
+
The package is `topdrawerx`; the command you type is `tdx`.
|
|
13
|
+
|
|
14
|
+
```
|
|
15
|
+
SET ORDER X Y DY
|
|
16
|
+
2.0 1.1 0.4
|
|
17
|
+
4.0 3.6 0.5
|
|
18
|
+
6.0 6.2 0.6
|
|
19
|
+
PLOT
|
|
20
|
+
JOIN DASHES
|
|
21
|
+
TITLE LEFT 'σ (μb)'
|
|
22
|
+
TITLE BOTTOM '$p_{K^-}$ (GeV/c)'
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
<img src="docs/images/panels-1.png" width="520" alt="a spectrum with a ratio panel beneath it">
|
|
26
|
+
|
|
27
|
+
The grammar is TopDrawer's — SLAC's plotting program from the late 1970s, which
|
|
28
|
+
a generation of particle physicists typed at daily and a few of us still miss.
|
|
29
|
+
topdrawerx keeps what made it pleasant, drops what only existed to serve card
|
|
30
|
+
readers and pen plotters, and renders through matplotlib.
|
|
31
|
+
|
|
32
|
+
## Why you might want it
|
|
33
|
+
|
|
34
|
+
**The session is the script.** Type at the prompt, watch the figure change, then
|
|
35
|
+
`SAVE work.tdx` — and that file reproduces the figure exactly, next year, on
|
|
36
|
+
another machine. Settings apply retroactively: `SET SCALE Y LOG` after you have
|
|
37
|
+
already plotted does the right thing, and `UNDO` works, because every command is
|
|
38
|
+
replayed from a log rather than smeared onto a canvas.
|
|
39
|
+
|
|
40
|
+
**The defaults are already right for a paper.** Black on white, serif, ticks
|
|
41
|
+
inward on all four sides, error bars without caps, 1/2/5 tick intervals. You are
|
|
42
|
+
not spending your afternoon undoing someone's idea of a pretty chart.
|
|
43
|
+
|
|
44
|
+
**Physics figures are one command each.** Systematic error boxes are `BOX` over
|
|
45
|
+
data with `DX`/`DY` columns. Upper limits are `ARROW DOWN`. A ratio panel under a
|
|
46
|
+
spectrum, sharing the axis, is two `SET WINDOW`s.
|
|
47
|
+
|
|
48
|
+
**It is small.** One dependency (matplotlib), about thirty commands, a plain-text
|
|
49
|
+
file format your colleague can read without installing anything.
|
|
50
|
+
|
|
51
|
+
## Install
|
|
52
|
+
|
|
53
|
+
```sh
|
|
54
|
+
pip install topdrawerx # or: pip install "topdrawerx[repl]"
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Python ≥ 3.10, matplotlib ≥ 3.7. The optional `[repl]` extra adds line editing
|
|
58
|
+
and completion via `prompt_toolkit`.
|
|
59
|
+
|
|
60
|
+
## A minute with it
|
|
61
|
+
|
|
62
|
+
```sh
|
|
63
|
+
tdx # interactive, with a live plot window
|
|
64
|
+
tdx figure.tdx # run a file, then stay interactive
|
|
65
|
+
tdx figure.tdx -o fig.pdf # batch: run it and write the figure
|
|
66
|
+
tdx --check old/*.top # what would these legacy files need?
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
At the prompt:
|
|
70
|
+
|
|
71
|
+
```
|
|
72
|
+
tdx> SET ORDER X Y DY
|
|
73
|
+
tdx> 2.0 1.1 0.4
|
|
74
|
+
tdx> 4.0 3.6 0.5
|
|
75
|
+
tdx> 6.0 6.2 0.6
|
|
76
|
+
tdx> PLOT
|
|
77
|
+
tdx> JOIN
|
|
78
|
+
tdx> SET SCALE Y LOG ← applies to what you already drew
|
|
79
|
+
tdx> LEGEND 'K⁻ beam'
|
|
80
|
+
tdx> SAVE 'fig.pdf' ← or SAVE 'work.tdx' for the script
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
The output format comes from the file name — `.pdf`, `.png`, `.svg`. There is no
|
|
84
|
+
device to configure. Several pages going to a PDF become one multi-page file.
|
|
85
|
+
|
|
86
|
+
From Python, the same engine:
|
|
87
|
+
|
|
88
|
+
```python
|
|
89
|
+
import topdrawerx as tdx
|
|
90
|
+
|
|
91
|
+
s = tdx.Session()
|
|
92
|
+
s.run(open("figure.tdx").read())
|
|
93
|
+
fig = tdx.figure(s.frame) # a real matplotlib Figure, yours to adjust
|
|
94
|
+
tdx.save(s.frames, "out.pdf")
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## Gallery
|
|
98
|
+
|
|
99
|
+
Every one of these is a file in [`examples/`](examples), rendered by
|
|
100
|
+
`tools/render_examples.py`.
|
|
101
|
+
|
|
102
|
+
| | |
|
|
103
|
+
| --- | --- |
|
|
104
|
+
| [`demo.tdx`](examples/demo.tdx)<br>points, errors, a curve, a histogram | <img src="docs/images/demo-1.png" width="330"> |
|
|
105
|
+
| [`annotate.tdx`](examples/annotate.tdx)<br>systematic boxes, upper limits, placed text | <img src="docs/images/annotate.png" width="330"> |
|
|
106
|
+
| [`styles.tdx`](examples/styles.tdx)<br>`SET STYLE TALK`, a palette, a legend | <img src="docs/images/styles.png" width="330"> |
|
|
107
|
+
| [`panels.tdx`](examples/panels.tdx)<br>`ZONE 2 2` | <img src="docs/images/panels-2.png" width="330"> |
|
|
108
|
+
| [`legacy_case.top`](examples/legacy_case.top)<br>a 1980s file, unchanged | <img src="docs/images/legacy_case.png" width="330"> |
|
|
109
|
+
|
|
110
|
+
`styles.tdx` with `PAPER` instead of `TALK` is the same figure in monochrome
|
|
111
|
+
serif at journal size — one word.
|
|
112
|
+
|
|
113
|
+
## How it compares
|
|
114
|
+
|
|
115
|
+
| | |
|
|
116
|
+
| --- | --- |
|
|
117
|
+
| **gnuplot** | The closest relative: scriptable, has a REPL. topdrawerx has a smaller, more regular grammar (verb, qualifiers, no punctuation), physics defaults, and retroactive settings with `UNDO`. |
|
|
118
|
+
| **Grace / xmgrace** | GUI-first and largely unmaintained. topdrawerx is text-first, so figures live in version control. |
|
|
119
|
+
| **ROOT** | An analysis framework that also draws. topdrawerx is a plotting tool with one dependency. |
|
|
120
|
+
| **matplotlib** | The engine underneath. If you want a library, use it directly; topdrawerx is for when you want a *language* and a prompt. |
|
|
121
|
+
|
|
122
|
+
## Old TopDrawer files
|
|
123
|
+
|
|
124
|
+
A `.top` file from 1985 usually just plots. `CASE` lines are converted into
|
|
125
|
+
Unicode and maths, the plotter settings are accepted and ignored, and the legacy
|
|
126
|
+
symbol codes work:
|
|
127
|
+
|
|
128
|
+
```
|
|
129
|
+
TITLE TOP 'K2-3P R D2-3X0C12+3' → K⁻p → D⁻Ξ_c⁺
|
|
130
|
+
CASE ' X XL W X XFXLXX X'
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
Commands topdrawerx does not know are skipped with a note, and the file still
|
|
134
|
+
plots; `--strict` turns that off. `tdx --check <files>` reports which commands a
|
|
135
|
+
directory of old files actually uses — which is how the implementation order
|
|
136
|
+
gets decided.
|
|
137
|
+
|
|
138
|
+
Being faithful to the original is a nice-to-have, not the goal: where the 1978
|
|
139
|
+
constraint and the 2026 user disagree, the user wins.
|
|
140
|
+
|
|
141
|
+
## Documentation
|
|
142
|
+
|
|
143
|
+
- [Command reference](docs/reference.md) — generated from the source, so it
|
|
144
|
+
cannot drift.
|
|
145
|
+
- [Design notes](docs/design.md) — how the replay model, the display list and
|
|
146
|
+
the command registry fit together, and how to add a command.
|
|
147
|
+
- [Changelog](CHANGELOG.md).
|
|
148
|
+
|
|
149
|
+
## Status and scope
|
|
150
|
+
|
|
151
|
+
Version 0.x: working, tested (159 tests), and used for real figures, but the
|
|
152
|
+
grammar may still move. Planned next: `FIT` and `SPLINE`, `CONTOUR` with simple
|
|
153
|
+
3-D data, then a numpy API and a Jupyter cell magic. Deliberately not planned:
|
|
154
|
+
control flow (`IF`, `REPEAT`) — Python is the better scripting language and the
|
|
155
|
+
Python API is the seam — HBOOK, and most of the data arithmetic.
|
|
156
|
+
|
|
157
|
+
This is a personal project, shared because others may find it useful. Bug
|
|
158
|
+
reports and patches are welcome; replies may be slow, and there is no support
|
|
159
|
+
promise. If you use it in a paper, there is a DOI in [CITATION.cff](CITATION.cff).
|
|
160
|
+
|
|
161
|
+
## Licence and attribution
|
|
162
|
+
|
|
163
|
+
MIT — see [LICENSE](LICENSE).
|
|
164
|
+
|
|
165
|
+
TopDrawer was written at the Stanford Linear Accelerator Center. topdrawerx is
|
|
166
|
+
an independent reimplementation of its command language and is not affiliated
|
|
167
|
+
with, endorsed by, or derived from SLAC's code. The character-set tables in
|
|
168
|
+
`src/topdrawerx/charsets.py` were transcribed from the published TopDrawer
|
|
169
|
+
reference manual, and each cites the section it came from.
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "topdrawerx"
|
|
7
|
+
version = "0.5.0"
|
|
8
|
+
description = "A TopDrawer-flavoured plotting language for making publication figures"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
license = { text = "MIT" }
|
|
12
|
+
authors = [{ name = "Ken Suzuki" }]
|
|
13
|
+
keywords = ["plotting", "visualization", "particle physics", "topdrawer", "matplotlib"]
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Development Status :: 4 - Beta",
|
|
16
|
+
"Environment :: Console",
|
|
17
|
+
"Intended Audience :: Science/Research",
|
|
18
|
+
"License :: OSI Approved :: MIT License",
|
|
19
|
+
"Operating System :: OS Independent",
|
|
20
|
+
"Programming Language :: Python :: 3",
|
|
21
|
+
"Programming Language :: Python :: 3.10",
|
|
22
|
+
"Programming Language :: Python :: 3.11",
|
|
23
|
+
"Programming Language :: Python :: 3.12",
|
|
24
|
+
"Programming Language :: Python :: 3.13",
|
|
25
|
+
"Topic :: Scientific/Engineering :: Physics",
|
|
26
|
+
"Topic :: Scientific/Engineering :: Visualization",
|
|
27
|
+
]
|
|
28
|
+
dependencies = [
|
|
29
|
+
"matplotlib>=3.7",
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
[project.urls]
|
|
33
|
+
Homepage = "https://github.com/KenSuzukiRCNP/topdrawerx"
|
|
34
|
+
Source = "https://github.com/KenSuzukiRCNP/topdrawerx"
|
|
35
|
+
Issues = "https://github.com/KenSuzukiRCNP/topdrawerx/issues"
|
|
36
|
+
Changelog = "https://github.com/KenSuzukiRCNP/topdrawerx/blob/trunk/CHANGELOG.md"
|
|
37
|
+
|
|
38
|
+
[project.optional-dependencies]
|
|
39
|
+
repl = ["prompt_toolkit>=3.0"]
|
|
40
|
+
dev = ["pytest>=7", "prompt_toolkit>=3.0"]
|
|
41
|
+
|
|
42
|
+
[project.scripts]
|
|
43
|
+
tdx = "topdrawerx.cli:main"
|
|
44
|
+
|
|
45
|
+
[tool.setuptools.packages.find]
|
|
46
|
+
where = ["src"]
|
|
47
|
+
|
|
48
|
+
[tool.setuptools.package-data]
|
|
49
|
+
topdrawerx = ["styles/*.tdx"]
|
|
50
|
+
|
|
51
|
+
[tool.pytest.ini_options]
|
|
52
|
+
testpaths = ["tests"]
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"""tdx -- a TopDrawer-flavoured plotting language for modern Python.
|
|
2
|
+
|
|
3
|
+
Quick use::
|
|
4
|
+
|
|
5
|
+
import topdrawerx
|
|
6
|
+
|
|
7
|
+
session = tdx.Session()
|
|
8
|
+
session.run('''
|
|
9
|
+
SET ORDER X Y DY
|
|
10
|
+
1.0 2.0 0.1
|
|
11
|
+
2.0 3.9 0.2
|
|
12
|
+
PLOT
|
|
13
|
+
JOIN
|
|
14
|
+
TITLE LEFT '$d\\sigma/d\\Omega$ (μb/sr)'
|
|
15
|
+
''')
|
|
16
|
+
tdx.save(session.frames, "figure.pdf")
|
|
17
|
+
|
|
18
|
+
The command language, the display list and the backends are separate on
|
|
19
|
+
purpose: see :mod:`tdx.session` for why the whole log is replayed on every
|
|
20
|
+
command, and :mod:`tdx.display` for the boundary the backends sit behind.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
from __future__ import annotations
|
|
24
|
+
|
|
25
|
+
from .errors import (
|
|
26
|
+
AmbiguousCommand,
|
|
27
|
+
ArgumentError,
|
|
28
|
+
DataError,
|
|
29
|
+
LexError,
|
|
30
|
+
TdxError,
|
|
31
|
+
UnknownCommand,
|
|
32
|
+
)
|
|
33
|
+
from .session import Context, Session, render_script, replay
|
|
34
|
+
from . import commands # noqa: F401 (importing this fills the registries)
|
|
35
|
+
|
|
36
|
+
__version__ = "0.5.0"
|
|
37
|
+
|
|
38
|
+
__all__ = [
|
|
39
|
+
"Session",
|
|
40
|
+
"Context",
|
|
41
|
+
"render_script",
|
|
42
|
+
"replay",
|
|
43
|
+
"save",
|
|
44
|
+
"figure",
|
|
45
|
+
"TdxError",
|
|
46
|
+
"LexError",
|
|
47
|
+
"UnknownCommand",
|
|
48
|
+
"AmbiguousCommand",
|
|
49
|
+
"ArgumentError",
|
|
50
|
+
"DataError",
|
|
51
|
+
"__version__",
|
|
52
|
+
]
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def save(frames, path: str) -> list[str]:
|
|
56
|
+
"""Write display-list frames to a file (format from the suffix)."""
|
|
57
|
+
from .backends import matplotlib_backend
|
|
58
|
+
|
|
59
|
+
return matplotlib_backend.save(frames, path)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def figure(frame):
|
|
63
|
+
"""Render one frame into a matplotlib ``Figure`` you can keep working on."""
|
|
64
|
+
from .backends import matplotlib_backend
|
|
65
|
+
|
|
66
|
+
return matplotlib_backend.make_figure(frame)
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"""Backends turn a display list into something you can look at.
|
|
2
|
+
|
|
3
|
+
``matplotlib`` is the default; ``json`` is used by the tests. Nothing above
|
|
4
|
+
this package knows which one is in use.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from . import json_backend, matplotlib_backend # noqa: F401
|
|
8
|
+
|
|
9
|
+
__all__ = ["json_backend", "matplotlib_backend"]
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"""JSON backend -- the test oracle.
|
|
2
|
+
|
|
3
|
+
Golden-file tests compare display lists, not images: stable across matplotlib
|
|
4
|
+
versions, readable in a diff, and they fail with a message you can act on.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import json
|
|
10
|
+
|
|
11
|
+
from ..display import Frame
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def to_dict(frames: list[Frame]) -> dict:
|
|
15
|
+
return {"frames": [f.to_dict() for f in frames]}
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def dumps(frames: list[Frame], indent: int = 2) -> str:
|
|
19
|
+
return json.dumps(to_dict(frames), indent=indent, sort_keys=True, ensure_ascii=False)
|