Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions src/gleplot/figure.py
Original file line number Diff line number Diff line change
Expand Up @@ -1769,6 +1769,21 @@ def _write_axes_content(
label=fs_data.get("label"),
yaxis=fs_data.get("yaxis", "y"),
)
elif series_type == "bar":
writer.add_bar_from_file(
fs_data["data_file"],
fs_data["x_col"],
fs_data["y_col"],
color=fs_data.get("color", "RED"),
)
elif series_type == "fill":
writer.add_fill_from_file(
fs_data["data_file"],
fs_data["x_col"],
fs_data["y1_col"],
fs_data["y2_col"],
color=fs_data.get("color", "LIGHTBLUE"),
)
else:
writer.add_errorbar_from_file(
fs_data["data_file"],
Expand Down
44 changes: 44 additions & 0 deletions src/gleplot/writer.py
Original file line number Diff line number Diff line change
Expand Up @@ -1656,6 +1656,50 @@ def add_plot_line_from_file(

self.lines_gle.append(line_cmd)

def add_bar_from_file(
self,
data_file: str,
x_col: int,
y_col: int,
color: str = "RED",
):
"""Add a bar series that references columns in an external data file.

Used for a ``bar`` dataset the recognizer could not resolve (e.g. a
missing data file): re-emits the ``data``/``bar`` shape verbatim so
GLE fails with its own honest missing-file error rather than
gleplot silently degrading the bar into a plain dataset.
"""
d_name = f"d{self.dataset_index}"
self.dataset_index += 1
self.lines_gle.append(
f" data {_format_data_filename(data_file)} {d_name}=c{x_col},c{y_col}"
)
self.lines_gle.append(f" bar {d_name} fill {color}")

def add_fill_from_file(
self,
data_file: str,
x_col: int,
y1_col: int,
y2_col: int,
color: str = "LIGHTBLUE",
):
"""Add a fill-between series that references columns in an external
data file.

Used for a ``fill`` dataset pair the recognizer could not resolve;
see :meth:`add_bar_from_file`.
"""
d1_name = f"d{self.dataset_index}"
d2_name = f"d{self.dataset_index + 1}"
self.dataset_index += 2
self.lines_gle.append(
f" data {_format_data_filename(data_file)} "
f"{d1_name}=c{x_col},c{y1_col} {d2_name}=c{x_col},c{y2_col}"
)
self.lines_gle.append(f" fill {d1_name},{d2_name} color {color}")

def add_fill_between(
self,
x: np.ndarray,
Expand Down
112 changes: 112 additions & 0 deletions tests/parser/test_recognizer.py
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,118 @@ def test_broken_data_becomes_file_series_with_error(tmp_path):
assert "data golden_0.dat d1=c1,c2" in text


def test_broken_bar_becomes_file_series_with_error(tmp_path):
"""``bar dN fill COLOR`` against a missing data file -> FileSeries('bar')."""
_gleplot_axes._global_data_file_counter = 0
fig = golden.bar()
gle_path = tmp_path / "f.gle"
fig.savefig_gle(str(gle_path))

(tmp_path / "golden_0.dat").unlink()

recognized = parse_gle_figure(gle_path)
ax = recognized.figure.axes_list[0]

assert ax.bars == []
assert len(ax.file_series) == 1
fs = ax.file_series[0]
assert set(fs.keys()) == {
"series_type",
"data_file",
"x_col",
"y_col",
"color",
"data_error",
}
assert fs["series_type"] == "bar"
assert fs["data_file"] == "golden_0.dat"
assert fs["x_col"] == 1
assert fs["y_col"] == 2
assert fs["color"] == "ORANGE"
assert fs["data_error"]

assert any(w.startswith("data:") for w in recognized.warnings)

# Re-save preserves the "bar" shape verbatim rather than degrading it to
# a plain dataset -- GLE will still fail on the missing file, but the
# recovered figure keeps meaning "this was a bar chart" across saves.
out = tmp_path / "again.gle"
recognized.figure.savefig_gle(str(out))
text = out.read_text(encoding="utf-8")
assert "data golden_0.dat d1=c1,c2" in text
assert "bar d1 fill ORANGE" in text

# Fixed point: re-parsing the recovered output reproduces the identical
# recognized field set and a byte-identical next save.
reparsed = parse_gle_figure(out)
fs2 = reparsed.figure.axes_list[0].file_series[0]
assert {k: v for k, v in fs2.items() if k != "data_error"} == {
k: v for k, v in fs.items() if k != "data_error"
}
out2 = tmp_path / "again2.gle"
reparsed.figure.savefig_gle(str(out2))
assert out2.read_text(encoding="utf-8") == text


def test_broken_fill_becomes_file_series_with_error(tmp_path):
"""``fill dA,dB color COLOR`` against a missing data file -> FileSeries('fill').

Uses a standalone fixture (rather than ``golden.fill_between``, which also
plots a line) so the recovered dataset numbering is unambiguous.
"""
_gleplot_axes._global_data_file_counter = 0
fig = gleplot.figure(data_prefix="golden")
ax = fig.add_subplot(1, 1, 1)
x = [0, 1, 2, 3, 4]
ax.fill_between(x, [0, 0, 0, 0, 0], [0, 1, 4, 9, 16], color="lightblue")
gle_path = tmp_path / "f.gle"
fig.savefig_gle(str(gle_path))

(tmp_path / "golden_0.dat").unlink()

recognized = parse_gle_figure(gle_path)
ax = recognized.figure.axes_list[0]

assert ax.fills == []
assert len(ax.file_series) == 1
fs = ax.file_series[0]
assert set(fs.keys()) == {
"series_type",
"data_file",
"x_col",
"y1_col",
"y2_col",
"color",
"data_error",
}
assert fs["data_file"] == "golden_0.dat"
assert fs["x_col"] == 1
assert fs["y1_col"] == 2
assert fs["y2_col"] == 3
assert fs["color"] == "LIGHTBLUE"
assert fs["data_error"]

assert any(w.startswith("data:") for w in recognized.warnings)

# Re-save must not crash (the 'fill' variant has no y_col, only
# y1_col/y2_col) and preserves the "fill" shape verbatim.
out = tmp_path / "again.gle"
recognized.figure.savefig_gle(str(out))
text = out.read_text(encoding="utf-8")
assert "d1=c1,c2 d2=c1,c3" in text
assert "fill d1,d2 color LIGHTBLUE" in text

# Fixed point across a second parse/save cycle.
reparsed = parse_gle_figure(out)
fs2 = reparsed.figure.axes_list[0].file_series[0]
assert {k: v for k, v in fs2.items() if k != "data_error"} == {
k: v for k, v in fs.items() if k != "data_error"
}
out2 = tmp_path / "again2.gle"
reparsed.figure.savefig_gle(str(out2))
assert out2.read_text(encoding="utf-8") == text


# --------------------------------------------------------------------------- #
# Hand-written tolerance
# --------------------------------------------------------------------------- #
Expand Down
Loading