Indice
Target framework: GX-TXT 0.9.8.10-dev-031
Audience: developers writing scientific modules and plot-producing plugins
Language: English
License note: examples are intended for GPL-3.0-or-later GX-TXT modules unless the module declares otherwise.
1. Purpose
This manual defines the recommended way to produce scientific plots in GX-TXT without duplicating plotting infrastructure or bypassing framework state.
The central rule is:
A scientific module should own scientific meaning and scientific data. The framework should own reusable plotting presentation state. Generic scientific rendering should be delegated to the public
plot.datasetAPIs whenever the graph can be represented by the public PlotSpec / Graph Series model.
A conforming module should not invent a private renderer for functionality that already exists in GX-TXT. In particular, a module should not maintain its own independent implementations of:
- line, scatter, line+marker, step, bar, error bar or uncertainty-band drawing;
- axes ranges;
- display units;
- grid;
- line width;
- fonts and DPI;
- legend placement and layout;
- multi-panel geometry;
- Level Scale;
- annotations;
- region selection;
- plot variants;
- rerender/cache behavior;
- export/print metadata.
The framework already exposes public APIs for these functions.
This manual covers the complete integration path:
Canonical Dataset / Analysis Source
↓
Dataset Variable Schema
↓
Table Stream API
↓
scientific calculation
↓
Dataset Publication API
↓
Graph Series API
↓
PlotSpec v2 through PLOT_DATASET_API_v3
↓
Graphics / Axes / Layout / Legend / Level Scale / Annotation
↓
Variants / USE / rerender
↓
PNG + .gxtxt_plotmeta + export/print
2. Architectural rule: never confuse data state with presentation state
GX-TXT deliberately separates scientific state from presentation state.
Scientific state
Scientific state includes:
- Dataset identity;
- stable Variable IDs;
- Analysis Source identity;
- scientific filters;
- derived Dataset values;
- family/group membership;
- uncertainty variables;
- error variables;
- model outputs;
- scientific graph meaning.
Presentation state
Presentation state includes:
- global/module graphics;
- DPI;
- width/height;
- font sizes;
- default line width;
- per-Series style;
- axis display units;
- axis ranges;
- grid;
- log/linear display scale;
- Level Scale;
- legend placement/layout;
- plot layout margins;
- multi-panel assignment;
- annotations;
- region-selection state;
- variant-specific presentation.
A common implementation mistake is to duplicate presentation fields inside the scientific module. Do not do that unless the setting changes scientific meaning.
For example, this is normally wrong:
[graph.my.plot.config.line_width]
type=double
default=1.5
if the value exists only to make the plotted line thicker.
Line width already belongs to Graphics inheritance and optional per-Series presentation. Adding another graph-level line-width control creates a competing authority.
A scientific configuration field is appropriate only when the value changes the scientific representation itself.
3. Public APIs used by scientific plotting modules
A modern plot-producing module should understand the following public surfaces.
| API | Responsibility |
|---|---|
| Analysis Source Map API v1 | framework-owned N-source Dataset selection |
| Analysis Source Access Runtime v2 | runtime Dataset/source metadata |
| Dataset Variable Schema API v3 | stable variable identity and semantics |
| Table Stream API v3 | backend-neutral chunked table access |
| Dataset Publication API v2 | publish derived canonical Datasets |
| Graph Provider API v1 | declare graph offerings |
| Graph Workflow API v1 | activation, USE/NON USE, graph-addressed workflow |
| Graph Series API v1 | persistent scientific X/Y series bindings |
| Plot Module API v1 | persistent plot variants |
| Graphics Inheritance API v1 | global → module → plot/variant graphics |
| PLOT_DATASET_API_v3 | current public plotting facade |
| PLOT_DATASET_PLOTSPEC_v2 | declarative 2D/3D plot model |
| Series Editor Extension API v1 | provider-owned per-Series presentation controls |
| Graph Configuration Extension API v1 | provider-specific graph controls |
| Axis Configuration Extension API v1 | provider-specific axis-dialog controls |
| Level Scale API v1 | linear/dB/dBFS/FS display transform |
| Plot Annotation API v1 | interactive figure annotations |
| Plot Region Selection API v1 | framework-owned data-coordinate selection |
| Multi-Y Axis Plot API v1 | left/right Y rendering |
| Render Cache API v1 | cache-only rerender path |
| Unit / Dimension APIs | scientific unit compatibility and display conversion |
For a normal new integration, activate PLOT_DATASET_API_v3, not v1 and v2 individually:
::GX::modulePublicApiActivate plot.dataset PLOT_DATASET_API_v3
The v3 facade loads the stable v1/v2 implementation as required.
Part I — Scientific data access
4. Use Dataset IDs and Variable IDs, not paths
A module must treat these as authoritative identities:
Dataset ID
Variable ID
Examples:
dataset_id = curve.replicate.profiles
x_variable_id = v0001
y_variable_id = v0004
Do not treat any of these as scientific identity:
CSV filename
absolute path
column number
display label
current variable name
PNG filename
A variable may be renamed while its Variable ID remains stable.
A Dataset may be stored as CSV today and HDF5 later without changing scientific identity.
5. Analysis Source Map: let the framework select inputs
A multi-source analysis module should opt into Analysis Source Map API v1.
Descriptor:
[analysis_sources]
api_version=1
ids=primary
dynamic=1
[analysis_source.primary]
required=1
contracts=
With dynamic=1, GX-TXT provides:
+ Add source
Remove
The module must not build a private Dataset selector for the same task.
Bindings may be source-qualified:
x=primary.x
family=primary.family_value
y=primary.y
An old unqualified binding remains an alias of the primary source where supported.
Important
The runtime may contain a convenience path= entry for compatibility, but a modern module must not use that path as its data-access authority.
Do not do this:
path = gxtxt_ini_get(cfg, 'analysis_source.primary.path', '');
if (exist(path,'file') != 2)
error('missing dataset');
endif
That rejects valid provider-backed Datasets and reintroduces storage coupling.
Use the Dataset ID exposed by the Analysis Source runtime and open it through the framework.
6. Table Stream API v3: the default way to read table-shaped Datasets
For rank-1 variables sharing one logical row dimension, use Table Stream API v3.
Octave Analysis Source form
opts = struct('start', 0, 'count', -1, 'require_direct', false);
S = gxtxt_table_stream_open_source(
runtime_cfg,
'primary',
{'v_time','v_voltage'},
4096,
opts);
while (true)
[S, chunk] = gxtxt_table_stream_next(S);
if (chunk.eof)
break;
endif
% Consume only this bounded chunk.
endwhile
meta = gxtxt_table_stream_metadata(S);
diag = gxtxt_table_stream_diagnostics(S);
S = gxtxt_table_stream_close(S);
Direct Dataset form
S = gxtxt_table_stream_open_dataset(
session,
'waveform.main',
{'v_time','v_voltage'},
4096,
opts);
Why this matters
Table Stream v3 is backend-neutral:
module
-> Table Stream API v3
-> Canonical Dataset API v2
-> CSV / HDF5 / future provider
The module must not detect the backend itself.
Never do this in new scientific code
fid = fopen(dataset_path, 'r');
header = fgetl(fid);
csvread(...);
dlmread(...);
textscan(...);
unless the file is a user-supplied non-Dataset input whose format is explicitly owned by that module.
Once a scientific object has been imported and registered as a GX-TXT Dataset, use Dataset APIs.
7. Table Stream transport: do not assume blocking line I/O
GX-TXT's standard Octave bridges may use popen2, whose output is nonblocking.
A bridge protocol record may arrive in fragments.
Do not write code that assumes:
line = fgetl(out_fid);
always returns one complete response immediately.
Use the framework bridge reader when available:
line = gxtxt_persistent_bridge_readline(out_fid, pid);
This is especially important for large Base64 typed-binary records.
A premature close can create the misleading sequence:
empty bridge response
broken pipe
where the real problem is transport framing rather than scientific calculation.
8. General n-dimensional Datasets
Table Stream is for table-shaped data.
Do not flatten:
- matrices;
- volumetric fields;
- images;
- n-dimensional spectra;
- structured multidimensional scientific arrays
just to make them fit Table Stream.
Use Canonical Dataset API v2 / the appropriate domain API directly for n-dimensional data.
Part II — Publishing derived scientific results
9. Publish reusable results as canonical Datasets
If a calculation produces data that another module, plot, worksheet or exporter may need, publish a Dataset.
Examples:
fit.predictions
fit.residuals
curve.replicate.traces
curve.replicate.profiles
statistics.summary
Do not create one private CSV per visual trace only because plotting is easier that way.
Prefer long-form canonical Datasets.
Example:
x, family_value, mean, lower, upper
0.0, 25, ...
0.1, 25, ...
...
0.0, 50, ...
This can drive:
- family overlays;
- exact-value filters;
- uncertainty bands;
- Series editor;
- downstream analysis.
10. Dataset Publication API v2
Use Dataset Publication API v2 for derived results.
A published Dataset should provide:
- stable Dataset ID;
- declared contract;
- stable variable IDs;
- dtype;
- unit;
- semantic role;
- dimensions/coordinates where applicable;
- integrity/finalization through the API.
Do not manually append records to metadata/datasets.ini.
Do not invent a parallel Dataset registry.
Do not rely on current canonical column names as permanent scientific identity.
Part III — Declaring graphs
11. Graph Provider API v1
A module declares stable graph types in module.ini.
Example:
[graphs]
provider_api_version=1
ids=my.analysis.overlay;my.analysis.residuals
default_ids=my.analysis.overlay
rerender=standard_octave
[graph.my.analysis.overlay]
name=Measured and fitted
default_enabled=1
file=01_overlay.png
supports_variants=1
variant_data_model=series
series_editor=standard
supports_legend=1
legend_editor=standard
supports_annotations=1
annotation_editor=standard
supports_region_selection=1
region_selection=rectangle
config_fields=title;x_label;y_label
Graph IDs are types, not dynamic instances.
Correct:
my.analysis.overlay
my.analysis.residuals
Wrong:
my.analysis.overlay.family25
my.analysis.overlay.family50
my.analysis.overlay.family100
Dynamic scientific members should normally be represented as Series, filters or Dataset values.
12. Graph Provider responsibilities
The framework owns:
- graph enable/disable state;
- Graphs GUI;
- persistence;
- output-contract assembly;
- USE/NON USE;
- common presentation services.
The module owns:
- scientific meaning of each graph;
- which derived Dataset/variables represent that graph;
- any truly scientific configuration.
When generic scientific plotting is sufficient, the module should delegate actual rendering to plot.dataset.
Part IV — Graph Series
13. Graph Series are the persistent scientific binding model
A Graph Series binds:
module_id
graph_id
variant_id
series_id
dataset_id
x_variable_id
y_variable_id
axis
label
enabled
order
filters
family/secondary/order variables
Basic Tcl example:
::GX::graphSeriesSet my.module my.graph measured [dict create \
dataset_id fit.result \
x_variable_id v0001 \
y_variable_id v0002 \
axis left \
label "Measured" \
enabled 1 \
order 1]
Graph Series identity is persistent.
Do not rebuild scientific identity from display names on every run.
14. Row filters
Use Graph Series filters instead of generating per-member CSV files.
Example:
set f [dict create \
filter_variable_id v0003 \
predicate exact_equal \
value 25]
::GX::graphSeriesSet my.module my.graph family_25 [dict create \
dataset_id curve.family \
x_variable_id v0001 \
y_variable_id v0002 \
axis left \
label "25 mA" \
row_filters [list $f]]
Supported predicates include:
exact_equal
not_equal
less_than
less_equal
greater_than
greater_equal
between_inclusive
in_set
is_finite
is_nonfinite
Filters combine with AND semantics.
15. Family expansion
For ordinary family traces, use the public family expansion helper where appropriate:
set created [::GX::graphSeriesExpandGroups \
plot.dataset plot.dataset.xy \
family_dataset \
v_x v_y v_family \
[dict create \
series_prefix spectrum \
label_template {Spectrum {member}}]]
This creates one ordinary exact-value filtered Graph Series per family member.
Why explicit expansion is sometimes required
Not every representation can be combined with family_variable_id.
A concrete example is an uncertainty band.
This may be rejected:
one Series
family_variable_id = family
band = lower/upper
The correct representation is:
Series family_25
row_filter family == 25
line + band
Series family_50
row_filter family == 50
line + band
General rule:
If a provider capability does not support implicit family grouping for a representation, expand the family into ordinary filtered Series. Do not modify
plot.datasetto accept an ambiguous representation.
16. Standard Series editor
Declare:
series_editor=standard
The standard editor owns generic scientific fields:
- Enabled;
- Dataset;
- X;
- Y;
- family variable;
- secondary variable;
- ordering variable;
- filters;
- axis left/right;
- label;
- ordering;
- add/update/remove/move.
Do not duplicate these fields in a private GUI.
17. Provider-specific Series presentation
If the module needs additional representation controls, use Series Editor Extension API v1.
Example registration:
::GX::seriesEditorExtensionRegister my.module.series [dict create \
module_id my.module \
graph_ids {my.analysis.overlay;my.analysis.residuals} \
build_proc ::MyModule::seriesBuild \
load_proc ::MyModule::seriesLoad \
validate_proc ::MyModule::seriesValidate \
save_proc ::MyModule::seriesSave \
close_proc ::MyModule::seriesClose]
The extension may own presentation such as:
kind
color
line_style
line_width
marker
marker_size
marker_fill
opacity
but it must not create a second scientific Series registry.
Scope provider state by at least:
module_id + graph_id + variant_id + series_id
Part V — PlotSpec and plot.dataset
18. Use PLOT_DATASET_API_v3
For new integrations:
::GX::modulePublicApiActivate plot.dataset PLOT_DATASET_API_v3
Use namespace:
::GX::PlotDatasetAPIv3
API v3 is the current facade. Declarative plot structure remains PlotSpec v2.
19. Minimal lifecycle
::GX::modulePublicApiActivate plot.dataset PLOT_DATASET_API_v3
set spec [::GX::PlotDatasetAPIv3::newSpec \
-graph_id plot.dataset.xy \
-title "Result"]
# Fill panels and Series using stable Dataset/Variable IDs.
::GX::PlotDatasetAPIv3::validateSpec $spec
set ref [::GX::PlotDatasetAPIv3::createPlotFromSpec $spec variant]
::GX::PlotDatasetAPIv3::render $ref
set a [::GX::PlotDatasetAPIv3::artifacts $ref]
set png [dict get $a graph_artifact]
For persistent producer integration:
set ref [::GX::PlotDatasetAPIv3::upsertPlotFromSpec \
my.module \
"my.module:result:overlay" \
$spec]
upsertPlotFromSpec reuses a persistent provider-keyed Plot Dataset Variant and applies the validated PlotSpec.
20. PlotSpec structure
Conceptually:
PlotSpec
├─ schema
├─ graph_type
├─ graph_id
├─ title
├─ multi_panel
├─ defaults.style
├─ panels[]
│ ├─ id
│ ├─ title
│ ├─ axes
│ ├─ series[]
│ └─ annotations[]
└─ output
For ordinary 2D scientific graphs:
graph_type = cartesian_2d
graph_id = plot.dataset.xy
Use plot.dataset.xy_region only when its single-panel region-specific semantics are required.
21. One-panel example
set spec [::GX::PlotDatasetAPIv3::newSpec \
-graph_id plot.dataset.xy \
-title "Measured response"]
set series [dict create \
id measured \
dataset_id experiment.result \
x_variable_id v0001 \
y_variable_id v0002 \
axis left \
kind line_marker \
label "Measured"]
set panel [dict create \
id main \
title "" \
axes [dict create] \
annotations {} \
series [list $series]]
dict set spec panels [list $panel]
::GX::PlotDatasetAPIv3::validateSpec $spec
22. Multi-panel example
set spec [::GX::PlotDatasetAPIv3::newSpec \
-graph_id plot.dataset.xy \
-title "Experiment" \
-multi_panel 1]
set p1 [dict create \
id electrical \
title "Electrical" \
axes [dict create] \
annotations {} \
series [list \
[dict create id voltage \
dataset_id dA \
x_variable_id v0001 \
y_variable_id v0002 \
axis left]]]
set p2 [dict create \
id thermal \
title "Thermal" \
axes [dict create] \
annotations {} \
series [list \
[dict create id temperature \
dataset_id dB \
x_variable_id v0101 \
y_variable_id v0102 \
axis left]]]
dict set spec panels [list $p1 $p2]
Panels share the X display domain but may use different source Datasets and different source X variables if scientifically compatible.
Do not implement stacked axes manually.
Part VI — Series representation and style
23. Supported 2D kinds
Common PlotSpec kinds include:
auto
line
scatter
line_marker
step
errorbar
band
bar
histogram
box_whisker
Use representation semantics rather than drawing primitives manually.
24. Style inheritance
Style precedence is:
framework/global defaults
↓
plot.dataset graph defaults
↓
PlotSpec defaults.style
↓
series.style
Common style fields:
color
line_style
line_width
marker
marker_size
marker_fill
opacity
Critical distinction
These are not equivalent:
field absent
inherit
auto
concrete value
Meaning:
- absent: do not override parent;
- inherit: explicitly keep parent;
- auto: ask renderer for automatic value at this layer;
- concrete value: override parent.
Important rule for module authors
If Graphics should control line width, do not write:
dict set series style line_width 1.5
as a default.
That concrete value overrides Graphics inheritance.
Correct:
# no line_width key at all
or, if a field must be serialized:
line_width = inherit
Only write a concrete per-Series line width when the user explicitly requested a Series override.
This rule applies equally to markers, color, opacity and other style fields.
Part VII — Uncertainty and errors
25. Error bars use Variable bindings
Do not embed error vectors in configuration.
Symmetric:
dict set series y_error [dict create \
mode symmetric \
variable_id v0004]
Asymmetric:
dict set series y_error [dict create \
mode asymmetric \
lower_variable_id v0005 \
upper_variable_id v0006]
The error variables must refer to the same Dataset rows as X/Y.
26. Uncertainty bands use absolute lower/upper variables
dict set series band [dict create \
lower_variable_id v0005 \
upper_variable_id v0006 \
draw_center 1]
The band uses absolute endpoints, not ± magnitudes.
Example scientific Dataset:
x
mean
lower_95
upper_95
Then:
Y = mean
band.lower = lower_95
band.upper = upper_95
No interpolation should be introduced merely for plotting.
27. Family + band: expand first
Do not assume this is legal:
dict create \
family_variable_id family \
band $band
For representations unsupported with implicit family grouping, expand into explicit filtered Series.
This was an important real-world failure mode:
plot.dataset API: family grouping does not accept band
The correct response is to change the producer PlotSpec, not to patch the renderer.
Part VIII — Graphics inheritance
28. Graphics inheritance hierarchy
Graphics precedence begins with:
global graphics
↓
module graphics override
↓
plot / variant-specific presentation
Global graphics owns:
- output width;
- output height;
- DPI;
- font unit;
- font size;
- axis-label font;
- title font;
- default line width;
- grid defaults;
- background;
- legend defaults.
Module graphics may override only selected fields.
Blank/inherit falls through.
29. Runtime effective graphics
Resolved values are transported to the renderer and recorded under:
[graphics_effective]
Typical process environment also contains values such as:
GXTXT_PLOT_WIDTH_PX
GXTXT_PLOT_HEIGHT_PX
GXTXT_PNG_DPI
GXTXT_PLOT_FONT_SIZE
GXTXT_PLOT_LINE_WIDTH
...
A module should consume the resolved state, not invent a competing default.
Part IX — Axes, grid and Plot layout
30. CORE Axes state is authoritative
For current GX-TXT, common Axes state is framework-owned.
Use the shared:
Axes / grid / 3D / color
dialog.
Do not create duplicate graph config fields for:
x_min
x_max
y_min
y_max
y2_min
y2_max
tick step
tick format
grid
display unit
unless the value is scientifically different from the generic axis concept.
31. PlotSpec axis fields are compatibility/API input, not a second final authority
PlotSpec may contain:
label
display_unit
scale
min
max
But overlapping saved CORE Axes state is authoritative for display unit and manual bounds for that graph/variant.
This means a plugin should not repeatedly force PlotSpec ranges on every run after the user has edited the shared Axes dialog.
32. Plot layout is per graph
GX-TXT Plot layout includes:
layout_auto
layout_margin_left
layout_margin_right
layout_margin_top
layout_margin_bottom
layout_gap
layout_ylabel_dist
These values are part of the common per-graph axis/layout state.
A delegated renderer must receive the state for the graph being rendered.
A common integration bug is:
user edits layout for producer graph A
renderer actually uses backend graph B
backend B never receives A's layout state
Result: sliders move in the GUI but do nothing to the rendered graph.
If a producer-owned graph delegates to a backend plot, explicitly project the producer's layout/axis state onto the backend graph key before render.
33. Multi-panel layout
Do not create manual subplot() geometry for normal GX-TXT scientific panels.
Use PlotSpec panels and shared layout state.
The framework owns:
- stacked positioning;
- shared X behavior;
- panel gaps;
- margins;
- external legend bands;
- Y-label spacing;
- panel-local Y/Y2 overrides.
Part X — Legend
34. Use the standard legend service
Descriptor example:
supports_legend=1
legend_editor=standard
legend_placements=inside;above;below;left;right;manual
legend_layouts=row;stacked;wrapped
The shared editor owns presentation:
placement
location
alignment
layout
columns
font
background
border
external margin
manual X/Y
Do not call Octave legend() directly in a module-private renderer when the graph declares standard legend support.
35. Legend content versus legend presentation
These are different.
The module may define scientific content such as:
legend_mode=family
legend_mode=source_replicate
legend_mode=none
The framework defines presentation such as:
below + row
right + stacked
inside + manual position
Do not mix them into one setting.
Part XI — Level Scale
36. Level Scale is a presentation transform
Level Scale API v1 supports:
linear
dB
dBFS
FS
It does not rewrite Dataset values.
Example:
::GX::levelScaleSet graph_id y [dict create \
scale dB \
quantity_kind amplitude \
amplitude_basis peak \
reference_value 1 \
reference_unit V \
zero_policy missing]
The transform occurs after unit conversion.
Do not pre-convert canonical Dataset data to dB merely because the user wants a dB display.
37. Selection and nonlinear Level Scale
A level-transformed Y axis is not generally affine-invertible to signed canonical data.
GX-TXT metadata records the transform.
X-only selection remains valid.
Y rectangle conversion may be refused rather than returning an ambiguous inverse.
A plugin should not override that safety behavior.
Part XII — Annotations
38. Interactive annotations
Descriptor:
supports_annotations=1
annotation_editor=standard
annotation_types=text;line;arrow;rectangle;ellipse
annotation_coordinate_spaces=figure_norm
Interactive annotations are framework presentation state.
The plugin should not maintain a private annotation store for the same purpose.
39. PlotSpec reference annotations
PlotSpec also supports scientific reference annotations attached to a panel, such as:
dict set panel annotations [list \
[dict create \
type reference_line \
axis x \
value 3.0 \
label "Lamp on" \
line_style dashed]]
These are distinct from interactive figure annotations.
Use them when the annotation is a declarative part of the plotted scientific result.
Part XIII — Region Selection / Select Area
40. Descriptor opt-in
For a selectable graph:
supports_region_selection=1
region_selection=rectangle
or:
region_selection=x_range
No custom selection widget is required.
41. Framework ownership
The framework owns:
- mouse selection UI;
- preview pixel → data-coordinate mapping;
- persistence;
- variant isolation;
- runtime delivery.
The plugin receives:
[plot.region.my.graph]
enabled=1
space=data
axis=primary
x_min=...
x_max=...
y_min=...
y_max=...
Octave:
R = gxtxt_plot_region_from_runtime(runtime_cfg, 'my.graph');
42. .gxtxt_plotmeta is mandatory for correct graphical selection
Plots exported through the standard graphics path have metadata under:
<plot-dir>/.gxtxt_plotmeta/<plot-name>.ini
The sidecar records:
- actual rendered data-frame geometry;
- image dimensions;
- axis limits;
- scale;
- direction;
- unit mapping;
- Level Scale metadata.
If a module copies a backend PNG to a producer-owned graph artifact, it must preserve the corresponding metadata sidecar as well.
Copying only the PNG can make the image visible while breaking Select Region.
Never synthesize .gxtxt_plotmeta manually.
43. Region Selection limitations
Current rectangle mapping supports normal single-panel 2D geometry.
Do not claim support for a graph geometry that the API cannot map reliably.
The framework should refuse unsupported geometry rather than guess.
Part XIV — Variants
44. Declare variants explicitly
Descriptor:
supports_variants=1
variant_data_model=series
For Graph-Series-based scientific plots, variant_data_model=series is normally the correct choice.
A variant should isolate:
- Graph Series;
- provider-specific Series presentation;
- axes;
- legend;
- Level Scale;
- region;
- graphics overrides;
- applicable provider state.
45. Do not use module_cache when the user needs full Series functionality
A cache-only variant model is intentionally more limited.
If the graph should expose:
- Dataset/X/Y;
- family;
- filters;
- ordering;
- standard Series editor;
use the Series data model.
46. Provider-owned state must include variant ID
If an extension stores:
series style
panel membership
provider-specific configuration
scope it by:
module
graph
variant
series
Otherwise p0001 and p0002 can overwrite each other.
Part XV — USE / NON USE and graph scheduling
47. Graph Provider enabled is not the same as USE
GX-TXT keeps these concepts separate.
- Provider enabled: graph offering belongs to the current module configuration.
- USE: user wants this graph scheduled/rendered.
- Variant enabled: independent variant state.
Do not infer one from another.
The runtime's effective scheduling state should come from Graph Workflow API, not a plugin-owned boolean.
Part XVI — Render and rerender
48. Full render
Use:
::GX::PlotDatasetAPIv3::render $ref
Full render may:
- open Datasets;
- prepare arrays;
- rebuild cache;
- write artifacts.
49. Rerender
Use:
::GX::PlotDatasetAPIv3::rerender $ref
Rerender should be used for presentation-only changes when preparation remains valid.
Examples:
title
line style
line width
legend
axes range
display units
layout
annotation
Level Scale
A change to an auxiliary data binding such as a different uncertainty variable may invalidate preparation and require full render.
Do not implement "rerender" by always rerunning the complete scientific analysis.
50. renderCommand for a host-controlled process
A producer that already owns scheduling may request the public command:
set item [::GX::PlotDatasetAPIv3::renderCommand $ref render]
or:
set item [::GX::PlotDatasetAPIv3::renderCommand $ref rerender]
This returns the framework-built queue item.
The producer should not reconstruct:
runtime.ini
Octave command line
private plot.dataset entrypoints
artifact paths
itself.
Part XVII — Two integration patterns
51. Pattern A: plot.dataset owns the visible plot
This is the simplest and preferred pattern when producer ownership is not required.
Flow:
module publishes Dataset
↓
module creates/upserts plot.dataset PlotSpec
↓
plot.dataset Variant is visible in Graphs
↓
all plot.dataset UI/API functions work directly
Advantages:
- minimal bridge code;
- all plot.dataset controls naturally address the actual owner;
- variants and rerender are direct.
The Graphs module column will identify plot.dataset.
52. Pattern B: scientific module owns graph identity, plot.dataset renders it
Use this only when the Graphs UI must continue to identify the producing module/graph.
This pattern is more demanding.
upsertPlotFromSpec() does not transfer canonical ownership from plot.dataset to another module. It creates/reuses a plot.dataset Variant keyed to the producer.
Therefore a producer-owned wrapper must synchronize presentation state correctly.
Conceptual flow:
producer graph state
- Graph Series
- axes/layout
- legend
- Level Scale
- Graphics
- region
- variant
↓
build PlotSpec
↓
upsert hidden/stable plot.dataset backend Variant
↓
project producer presentation state to backend
↓
renderCommand(render|rerender)
↓
backend artifact
↓
producer artifact + matching .gxtxt_plotmeta
Critical rules for this pattern
- Producer Graph Series remain the scientific authority.
- Do not let the backend Variant become a stale second scientific authority.
- Reapply the current PlotSpec when Series identity changes.
- Project current axes/layout state before render.
- Project legend state before render.
- Project Level Scale before render.
- Project effective Graphics before render.
- Preserve per-variant isolation.
- Copy the matching
.gxtxt_plotmetawith the PNG if the visible artifact is producer-owned. - Use
renderCommand(..., rerender)for rerender. - Do not expose the hidden backend as a duplicate visible plot if the producer artifact is the intended UI object.
This pattern should be encapsulated in a reusable bridge rather than reimplemented differently in every module.
Part XVIII — Graph configuration extensions
53. Graph Configuration Extension API v1
Use this only for provider-specific controls that do not belong in the generic descriptor fields.
Registration:
::GX::graphConfigExtensionRegister my.renderer.graph_config [dict create \
module_id my.renderer \
graph_ids {my.renderer.xy} \
build_proc ::MyRenderer::graphConfigBuild \
close_proc ::MyRenderer::graphConfigClose]
Build only inside the supplied parent.
Do not inspect private CORE widget paths.
54. Axis Configuration Extension API v1
Use this only for provider-specific axis/panel presentation not already owned by CORE.
Registration:
::GX::axisConfigExtensionRegister my.plot.axes [dict create \
match_proc ::MyPlot::matchesArtifact \
build_proc ::MyPlot::buildAxisControls \
close_proc ::MyPlot::closeAxisControls]
CORE remains authoritative for common:
min/max
tick step
tick format
grid
panel Y/Y2 numeric state
alignment
Do not duplicate those controls in an extension.
Part XIX — Common failure modes
55. Failure: module reads analysis_source.*.path
Symptom
Works for one CSV Session, fails for provider-backed or rematerialized Datasets.
Cause
Storage path is being treated as scientific identity.
Fix
Use:
analysis_source.<id>.dataset_id
Variable IDs
Table Stream API v3
56. Failure: line width from Module Graphics appears ignored
Symptom
GXTXT_PLOT_LINE_WIDTH=12 is visible in the process command, but the graph still renders with a thin fixed line.
Cause
The producer inserted:
series.style.line_width = 1.5
into the PlotSpec.
Per-Series style wins.
Fix
Omit line_width or set it to inherit unless the user explicitly selected a Series override.
57. Failure: Plot layout sliders move but output does not change
Cause
The GUI is editing graph key A but the delegated renderer is using graph key B.
Fix
Project the producer graph's common axis/layout state to the backend graph/variant before generating the backend runtime.
58. Failure: Select Region button exists but selection is wrong or unavailable
Check:
- descriptor has
supports_region_selection=1; - geometry is supported;
- current PNG has a matching
.gxtxt_plotmeta/*.ini; - metadata was produced for the current PNG;
- copied artifacts preserved the sidecar;
- display unit / Level Scale state is current.
Never guess axes geometry from margins.
59. Failure: family grouping does not accept band
Cause
A grouped family Series is carrying a representation unsupported by implicit grouping.
Fix
Expand into ordinary exact-value filtered Series, one per family.
60. Failure: old Series remain after rerun
Cause
Producer reused a backend Variant but did not apply the new PlotSpec/Graph Series state.
Fix
Use:
upsertPlotFromSpec
or explicitly applySpec on the existing public plot reference.
Do not assume a historical ensure operation updates an existing template automatically.
61. Failure: empty bridge response + broken pipe
Cause
A nonblocking popen2 pipe was read as if it were blocking/record-oriented.
Fix
Use the GX-TXT persistent bridge record reader.
Also make Tcl bridge failures visible on stderr so a transport error does not hide the original exception.
62. Failure: graph is visible but editing tools act on the wrong module
Cause
The rendered artifact is physically owned by a plot.dataset Variant while the producer assumes ownership was transferred.
Fix
Choose one integration model deliberately:
- direct
plot.datasetownership; or - producer-owned wrapper with explicit state projection and artifact mapping.
Do not mix the two models implicitly.
Part XX — Anti-patterns
63. Do not write a private renderer for standard scientific plots
Avoid production module code such as:
figure(...)
plot(...)
scatter(...)
bar(...)
errorbar(...)
legend(...)
xlim(...)
ylim(...)
subplot(...)
print(...)
when the same graph can be represented using public Plot Dataset APIs.
This is not just a code-style issue. A private renderer will usually miss some of:
- Graphics inheritance;
- Axes editor;
- Plot layout;
- legend editor;
- variants;
- rerender;
- Level Scale;
- annotations;
- Select Region;
- metadata;
- future central improvements.
64. Do not manually edit framework persistence
Do not manually patch:
metadata/manifest.ini
metadata/datasets.ini
Graph Series numbered sections
plot_variant sections
runtime.ini
render_cache.mat
Use the public APIs.
65. Do not create one Dataset per trace unless scientifically necessary
Prefer:
one long-form Dataset
+ family variable
+ filters
over:
trace_001.csv
trace_002.csv
trace_003.csv
...
Long-form representation keeps identity, filtering and reuse coherent.
66. Do not use names as stable bindings
Wrong:
Y = column named "Voltage"
Correct:
dataset_id = d001
y_variable_id = v0007
Display names are presentation.
Part XXI — Recommended module workflow
67. Calculation run
Recommended full sequence:
1. Framework writes module runtime.
2. Module resolves Analysis Sources by Dataset ID.
3. Module resolves stable Variable IDs.
4. Module opens Table Stream API v3.
5. Module performs scientific calculation.
6. Module publishes derived Datasets through Dataset Publication API.
7. Module creates/updates Graph Series on those Datasets.
8. Module builds current PlotSpec.
9. Module validates PlotSpec.
10. Module upserts/updates the plot.dataset backend.
11. Module projects common presentation state if using producer-owned graph identity.
12. Module invokes public render.
13. Module preserves standard metadata sidecars.
14. Framework verifies output contract.
68. Rerender run
1. Framework regenerates current presentation runtime.
2. Module does not redo scientific calculation.
3. Module loads current cache / backend plot reference.
4. Module reapplies current presentation state.
5. Module invokes public rerender.
6. New PNG and metadata replace the old artifact atomically.
Part XXII — Minimal descriptor template
69. Recommended 2D scientific graph descriptor
[graphs]
provider_api_version=1
ids=my.analysis.result
default_ids=my.analysis.result
rerender=standard_octave
[graph.my.analysis.result]
name=Analysis result
default_enabled=1
file=01_result.png
supports_variants=1
variant_data_model=series
series_editor=standard
supports_legend=1
legend_editor=standard
legend_placements=inside;above;below;left;right;manual
legend_layouts=row;stacked;wrapped
supports_annotations=1
annotation_editor=standard
annotation_types=text;line;arrow;rectangle;ellipse
annotation_coordinate_spaces=figure_norm
supports_region_selection=1
region_selection=rectangle
config_fields=title;x_label;y_label
Do not add generic style/range/layout controls here unless they have scientific semantics not already represented by the common APIs.
Part XXIII — Minimal Tcl plotting service example
70. Create/update one persistent plot.dataset Variant
::GX::modulePublicApiActivate plot.dataset PLOT_DATASET_API_v3
set spec [::GX::PlotDatasetAPIv3::newSpec \
-graph_id plot.dataset.xy \
-title "Analysis result"]
set s1 [dict create \
id result \
dataset_id analysis.result \
x_variable_id v0001 \
y_variable_id v0002 \
axis left \
kind line \
label "Result"]
set panel [dict create \
id main \
axes [dict create] \
annotations {} \
series [list $s1]]
dict set spec panels [list $panel]
::GX::PlotDatasetAPIv3::validateSpec $spec
set ref [::GX::PlotDatasetAPIv3::upsertPlotFromSpec \
my.analysis \
"my.analysis:result" \
$spec]
set renderResult [::GX::PlotDatasetAPIv3::render $ref]
set artifacts [::GX::PlotDatasetAPIv3::artifacts $ref]
set png [dict get $artifacts graph_artifact]
Part XXIV — Validation checklist
71. Data access
Before release, verify:
- No Dataset scientific access depends on absolute paths.
- No Dataset scientific access depends on column numbers.
- Table-shaped Datasets use Table Stream API v3.
- Variable binding uses stable IDs.
- Derived reusable data is published through Dataset Publication API.
- No private Dataset registry exists.
72. Graph identity
- Graph IDs are stable graph types.
- Graph Series carry dynamic scientific members.
- Graph Provider enable state and USE are not conflated.
- Variants have isolated state.
- Producer ownership versus plot.dataset ownership is explicit.
73. Rendering
- No private renderer exists for standard supported plot types.
- PlotSpec validates successfully.
- No stale backend PlotSpec survives scientific binding changes.
- Full render and rerender are distinct.
- Backend artifacts are retrieved through API rather than guessed paths.
74. Graphics and style
- Module Graphics controls default line width.
- Series style overrides Graphics only when explicitly set.
- Missing/inherit/auto are handled distinctly.
- DPI, fonts and output dimensions come from Graphics inheritance.
- No duplicate graph-level generic style controls exist.
75. Axes and layout
- Shared Axes editor controls common ranges/grid/display units.
- Plot layout sliders affect the actual rendered graph key.
- Multi-panel graphs use PlotSpec panels, not private subplot geometry.
- Panel-local Y/Y2 state remains isolated.
- Shared-X compatibility is validated.
76. Legend and annotations
- Standard legend editor works.
- Legend content and legend presentation are separate concepts.
- Standard annotation editor works.
- PlotSpec scientific reference annotations are used only where appropriate.
77. Region selection
- Descriptor opt-in is correct.
- PNG has current
.gxtxt_plotmeta. - Producer-owned artifact copies preserve the matching metadata file.
- Unsupported geometry is not advertised as selectable.
- Variant regions remain variant-specific.
78. Cache and persistence
- Rerender does not rerun the scientific calculation unnecessarily.
- Session reopen preserves Graph Series.
- Session reopen preserves variant state.
- Session reopen preserves common presentation state.
- Provider-owned extension state includes variant identity.
Part XXV — Debugging method
79. Diagnose by authority layer
When a graph ignores a setting, do not immediately modify the renderer.
Determine which layer owns the setting.
Example: line width.
Check in order:
1. Global Graphics
2. Module Graphics
3. Variant Graphics
4. PlotSpec defaults.style
5. Series style
The last concrete value wins.
Example: Y range.
Check:
1. CORE Axes state
2. PlotSpec compatibility fallback
3. renderer receives final state
Example: Select Region.
Check:
1. descriptor capability
2. visible artifact identity
3. sidecar metadata
4. pixel calibration
5. data/display-unit mapping
80. Inspect runtime before changing code
Useful evidence includes:
[graphics_effective]
[graph_provider]
[graph_series]
[axis.<graph-id>]
[level_scale.<graph-id>.<axis>]
[plot.region.<graph-id>]
The process command also exposes resolved graphics environment variables.
If the correct value is already in runtime but not in the image, investigate downstream precedence.
If the value is absent from runtime, investigate framework state projection or descriptor/API integration.
81. Validate PlotSpec before render
Always call:
::GX::PlotDatasetAPIv3::validateSpec $spec
before blaming the renderer.
Validation is expected to reject unsupported combinations.
An explicit validation error is preferable to a visually plausible but scientifically ambiguous graph.
Part XXVI — Framework source references
For GX-TXT 0.9.8.10-dev-031, the primary reference documents are:
docs/api/SCIENTIFIC_PLOT_FRAMEWORK_API_v1.md
docs/api/PLOT_DATASET_API_v1.md
docs/api/PLOT_DATASET_API_v2.md
docs/api/PLOT_DATASET_API_v3.md
docs/api/GRAPH_PROVIDER_API_v1.md
docs/api/GRAPH_WORKFLOW_API_v1.md
docs/api/GRAPH_SERIES_API_v1.md
docs/api/PLOT_MODULE_API_v1.md
docs/api/GRAPHICS_INHERITANCE_API_v1.md
docs/api/SERIES_EDITOR_EXTENSION_API_v1.md
docs/api/GRAPH_CONFIG_EXTENSION_API_v1.md
docs/api/AXIS_CONFIG_EXTENSION_API_v1.md
docs/api/LEVEL_SCALE_API_v1.md
docs/api/PLOT_ANNOTATION_API_v1.md
docs/api/PLOT_REGION_SELECTION_API_v1.md
docs/api/TABLE_STREAM_API_v3.md
docs/api/DATASET_VARIABLE_SCHEMA_API_v3.md
docs/api/DATASET_PUBLICATION_API_v2.md
docs/api/ANALYSIS_SOURCE_MAP_API_v1.md
docs/api/ANALYSIS_SOURCE_ACCESS_RUNTIME_v2.md
Relevant implementation surfaces include:
core/tcl/graph_series_api_v1.tcl
core/tcl/graph_workflow_api_v1.tcl
core/tcl/plot_layout_controls_v1.tcl
core/tcl/plot_region_selection_api_v1.tcl
core/tcl/scientific_plot_framework_api_v1.tcl
core/tcl/series_editor_extension_api_v1.tcl
core/tcl/level_scale_api_v1.tcl
plugins/modules/plot.dataset/api/plot_dataset_api_v1.tcl
plugins/modules/plot.dataset/api/plot_dataset_api_v2.tcl
plugins/modules/plot.dataset/api/plot_dataset_api_v3.tcl
Part XXVII — Final rules
A correct GX-TXT scientific plotting module should follow these rules:
- Use Dataset IDs and stable Variable IDs.
- Read registered table Datasets through Table Stream API v3.
- Publish reusable derived results through Dataset Publication API.
- Represent scientific curves through Graph Series.
- Represent generic scientific plots through PlotSpec and
PLOT_DATASET_API_v3. - Do not create a private renderer for functionality already provided by
plot.dataset. - Let Graphics inheritance own generic visual defaults.
- Do not override Graphics with hard-coded Series styles unless the user explicitly requested an override.
- Let CORE Axes/Grid/Layout own generic axes and geometry.
- Use the standard Legend, Annotation, Level Scale and Region Selection APIs.
- Preserve
.gxtxt_plotmetawhenever the visible graph artifact is copied or proxied. - Use Series variants when the user needs full Dataset/X/Y/Series editing.
- Keep render and rerender separate.
- Validate PlotSpec and accept explicit API failures instead of introducing silent renderer fallbacks.
- When a required representation is missing from public APIs, extend the central public plotting API rather than creating a module-private implementation.
Following these rules makes a graph participate in the entire GX-TXT plotting environment rather than merely producing a PNG.