Build a SCAL/DG3 deliverable: crossplot + stats table in one SVG

The standard deliverable for static modelling is a poroperm crossplot with regression lines and a summary statistics table, exported as one SVG that pastes into PowerPoint or Word. logSuite builds this in three calls; no matplotlib glue.

The minimal pattern

from logsuite import Crossplot

# Per-facies summary table from the same manager + filters used by the
# crossplot — guarantees the numbers match what you're plotting.
stats = manager.PHIE.filter("Facies").stats(
    return_df=True, flat_columns=True,
    methods=["mean", "percentile_10", "percentile_50", "percentile_90"],
)

xplot = Crossplot(manager, x="PHIE", y="PERM", color="Facies", y_log=True,
                  equation_format="petrel", decimals=3,
                  title="Synthetic poroperm — DG3 deliverable shape")

xplot.add_regression_per("Facies", "exponential", legend_loc="upper left")

xplot.add_table_panel(stats, position="bottom",
                      title="Per-facies PHIE summary",
                      formatters={"mean": ".4f", "p10": ".4f",
                                  "p50": ".4f", "p90": ".4f"})

xplot.save("poroperm_deliverable.svg")

Output: a single SVG containing the scatter (with three Petrel-form regression lines, palette colours from manager.Facies.colors, legend in the upper-left) and the stats table beneath, with formatted numbers and N/A for any NaNs.

Layout knobs

  • position="right" puts the table to the right of the chart.

  • table_fraction=0.30 (default) sets how much of the figure dimension the panel takes; the figure grows along that axis so the scatter is not squished.

  • formatters={"col": ".4f"} accepts Python format specs or callables.

MultiIndex tables

If you build a hierarchical summary (e.g. Zone × Facies):

stats = manager.PHIE.filter("Zone").filter("Facies").stats(
    return_df=True, flat_columns=True,
)

add_table_panel flattens MultiIndex column labels with " | " and visually merges repeated outer-level row labels (the outer cell is blanked when the same as the previous row).

Verifying

story_tests/story_7_scal_deliverable.py produces output_story_7.svg end-to-end.