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
46 changes: 40 additions & 6 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,17 @@ guide in the documentation says what to change in your code.

## [Unreleased]

### Changed
- The badge names the CoolProp and CoolPlot versions by default
(`BrandingSettings.versions=True`), below the equation of state, so a
chart says what drew it. Specs saved before hold `"versions": false`
and keep their badge; `{"references": "none", "versions": False}`
draws a chart without badge.
- `cycle_states`, `state`, `state_at` and `mix` say which way `units=`
works: on `cycle_states` it converts the states returned (as dicts),
on the others it only sets the units of the input and the StatePoint
returned stays in SI units. The how-to on units explains why (#33).

### Added
- `Chart.add_line` and `Chart.remove_line` (issue #32): single lines of
constant value drawn on top of the families, each with its own name,
Expand All @@ -18,6 +29,35 @@ guide in the documentation says what to change in your code.
`lines`), drawn with the scene roles `line` and `line_label`, and
composed the same by coolplot-core 1.6; an older core leaves charts
with single lines to the Python composer.
- The critical point and the triple point can be marked with a dot in the
colour of the saturation curve: `diagram.marked_points = ("critical",
"triple")` (#39). The triple point marks both ends of the saturation
curve at the triple temperature. A point outside the region of the
chart is not drawn, and the axes are widened a little to show the dots
whole. Mixtures and pseudo-pure blends mark the critical point only.
The spec record holds `marked_points` only when it is set, so records
of 1.5 read as before.
- A saturation scale just inside the frame, right of the vertical axis
(#39): `diagram.saturation_scale = True`, or `{"step": 10, "Q": 0}`
for a tick every 10 K at the bubble points of a blend. Next to a
pressure axis it shows the saturation temperatures, next to a
temperature axis the saturation pressures, up to the critical point.
Overlapping values are thinned out; labels, the legend and the badge
keep clear of the scale. The spec record holds `saturation_scale`
only when it is set.
- Labels inside the dome (#39): `settings.with_(labels={"dome": True})`
labels each labelled isotherm (isobar on a T-s chart) once more just
right of the bubble line and just left of the dew line, on top of the
labels of the label mode. They follow the slope of a blend's
isotherms, keep clear of other labels, the legend, the badge, the
saturation scale and cycles, and are left out where they do not fit,
as near the critical point. `LabelSettings.dome` is written to the
record only when true.
- coolplot-core 1.6 composes the marked points, the saturation scale and
the labels inside the dome; the compose record carries their states as
`"points"`, `"scale"` and `"dome"` of the domain. The bridge refuses
any of them for a core older than 1.6, so the Python composer draws
them, with a warning.
- Example `examples/co2_phase_diagram.py`: a full phase diagram of CO2 on
the p-T chart. CoolPlot draws the vapour pressure curve; the example
adds the melting line from CoolProp, the sublimation line of Span and
Expand All @@ -27,12 +67,6 @@ guide in the documentation says what to change in your code.
melting line of the ices from CoolProp and the sublimation line of ice
Ih from IAPWS R14-08 (2011).

### Changed
- `cycle_states`, `state`, `state_at` and `mix` say which way `units=`
works: on `cycle_states` it converts the states returned (as dicts),
on the others it only sets the units of the input and the StatePoint
returned stays in SI units. The how-to on units explains why (#33).

## [1.5.0] - 2026-10-30

### Added
Expand Down
16 changes: 14 additions & 2 deletions cpp/include/coolplot/compose.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@
// The input is one JSON record, so that any caller can hand it over:
//
// {"spec": <chart spec>,
// "domain": {"tp_box": [T_min, T_max, p_min, p_max], "ranges_SI": {key: [min, max]}},
// "domain": {"tp_box": [T_min, T_max, p_min, p_max], "ranges_SI": {key: [min, max]},
// "points": {name: {key: value}} (optional, since 1.6),
// "scale": [[saturation_SI, axis_SI], ...] (optional, since 1.6)},
// "curves": [{"request": {"item_id", "key", "value_SI", "points", "labels", "anchor_SI"}, "curve": <curve record>}],
// (anchor_SI, since 1.1: the saturation temperature or pressure the line passes through when its
// family is placed by saturation states, null otherwise; the label shows it. Since 1.6 the curves
Expand All @@ -18,6 +20,16 @@
// "previous_labels": [<text item>] or null,
// "sources": [<badge line>] (optional, since 1.5)}
//
// "points" holds the states of the critical and triple points the spec marks
// ("marked_points"), by name: "critical", "triple_liquid" and "triple_vapour".
// They need CoolProp, so the caller computes them as
// coolplot.thermo.provider.marked_point_states does. The composer draws those
// inside the T-p box and widens the axes a little to show their dots whole.
// "scale" holds the ticks of the saturation scale of the spec
// ("saturation_scale"): the saturation temperature or pressure and the value
// on the vertical axis at that saturation state, as
// coolplot.engine.Engine computes them.
//
// "sources" are the badge lines naming the sources of the properties (the
// references of the equation of state and the versions line), which need
// CoolProp, so the caller computes them as coolplot.engine.Engine.source_lines
Expand All @@ -31,7 +43,7 @@

namespace coolplot {

extern const double Z_ISOLINE, Z_SATURATION, Z_LINE, Z_LABEL, Z_PROCESS, Z_STATES;
extern const double Z_ISOLINE, Z_SATURATION, Z_SCALE, Z_LINE, Z_POINT, Z_LABEL, Z_PROCESS, Z_STATES;

/// The scene of a chart from the compose record above.
Scene compose(const json& input);
Expand Down
13 changes: 13 additions & 0 deletions cpp/include/coolplot/layout.h
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,19 @@ std::vector<Item> place_margin_labels(const std::vector<LabelRequest>& requests,
double gap_pt = 3.0);

/// State numbers moved around their markers so they do not cover each other.
/// Where a line meets the bubble line (xb, yb) and the dew line (xd, yd), in display units.
struct DomeEnds
{
double xb, yb, xd, yd;
};

/// Two more labels for each request with ends, inside the dome next to the bubble and dew lines (LabelSettings.dome, 1.6):
/// on the chord between the ends, DOME_GAP_PT from each end, upright; left out where they do not fit, leave the frame,
/// come near a cycle or overlap a placed label (``labels``), a box of ``occupied`` or an earlier dome label.
std::vector<Item> place_dome_labels(const std::vector<LabelRequest>& requests, const std::map<std::string, DomeEnds>& ends, const Frame& frame,
const std::vector<KeepOut>& keep_out, double keep_out_pt, const std::vector<TakenBox>& occupied,
const std::vector<Item>& labels);

std::vector<Item> place_state_numbers(const std::vector<Item>& numbers, const Frame& frame, const std::vector<double>& marker_x,
const std::vector<double>& marker_y);

Expand Down
16 changes: 15 additions & 1 deletion cpp/include/coolplot/spec.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ struct LabelSettings
int tick_digits = 6;
/// "unicode" (rho, deg C and m3 with their own characters) or "ascii"; a labels record without it is "ascii"
std::string symbols = "unicode";
/// Label the lines of the dome family once more inside the dome, next to the bubble and dew lines (since 1.6)
bool dome = false;

/// The edges set for a family, or the family's own.
std::vector<std::string> preferred_edges(const std::string& family, const std::vector<std::string>& fallback_edges) const;
Expand Down Expand Up @@ -141,7 +143,19 @@ struct DiagramSpec
bool has_y_view = false;
Range y_view_SI;
std::string title;

/// The special states marked with a dot: "critical" and or "triple", in that order (since 1.6)
std::vector<std::string> marked_points;
/// A scale of saturation states right of the vertical axis (since 1.6): T_sat next to a pressure axis,
/// p_sat next to a temperature axis; its ticks come with the domain
bool has_saturation_scale = false;
double saturation_scale_Q = 1.0; ///< vapour quality of the saturation states the scale shows

/// The family labelled inside the dome: "T", or "p" where temperature is an axis; empty for the p-T chart.
std::string dome_key() const;
/// The quantity of the saturation scale: "T" next to a pressure axis, "p" next to a temperature axis.
std::string scale_key() const;

bool marks(const std::string& point) const;
/// LineStyle changes for one family (a quantity key) or the saturation curve ("saturation"),
/// an empty object if none.
json style_override(const std::string& key) const;
Expand Down
Loading
Loading