Generated reference › API — ICoreEssentials/UI/ICoreChartBase
kind: generated#api#icoreessentials-ui-icorechartbase

API — ICoreEssentials/UI/ICoreChartBase

The public contract of 11 header(s) under ICoreEssentials/UI/ICoreChartBase — 13 class/struct definition(s), 160 declaration(s). Each section shows the header's banner and its public (and protected-virtual) surface exactly as the file writes it.

ICoreChartAxisItem.h#

ICoreEssentials/UI/ICoreChartBase/ICoreChartAxisItem.h

ICoreChartAxisItem#

ICoreChartAxisItem.h:54 · class · bases public ICoreGraphicsObject · pImpl · 22 declaration(s)

class ICoreChartAxisItem : public ICoreGraphicsObject {
public:
    explicit ICoreChartAxisItem(ICoreChartAxisSide side, ICoreNativeItem* parent = nullptr);

    // Out of line: Impl must be complete where the unique_ptr's deleter is
    // instantiated.
    ~ICoreChartAxisItem() override;

    [[nodiscard]] ICoreChartAxisSide side() const;

    // ---- Where it draws --------------------------------------------------

    // The data region, in the PARENT's coordinates. The item places itself so
    // that this rectangle plus its own gutter is covered.
    void setPlotArea(const ICoreRect& plotAreaInParentCoords);

    // The same rectangle expressed in THIS item's coordinates -- offset by the
    // gutter for a Y ruler, at the origin for an X ruler. Public because the
    // render tests assert against it and because a caller checking where a
    // tick landed needs the same frame the item used.
    [[nodiscard]] ICoreRect plotAreaInOwnCoords() const;

    // The room this ruler needs OUTSIDE the plot area for its labels and tick
    // marks, measured from the label font and the strings actually set. Zero
    // when the ruler has no ticks or its labels are hidden. This is what the
    // root's setAxisLabelGutters() wants.
    [[nodiscard]] double requiredGutterThickness() const;

    [[nodiscard]] ICoreRect contentBounds() const override;

    // ---- What it reads ---------------------------------------------------

    // ⚠ A Y RULER'S RANGE IS STILL MINIMUM-FIRST. `minimum` is the low end in
    // DATA space and stays the low end even though it is drawn at the BOTTOM;
    // the item passes the pixel endpoints bottom-first to the scale, which is
    // where the whole y-flip lives. There is no inverted flag here either.
    void setRange(const ICoreChartRange& range);
    [[nodiscard]] ICoreChartRange range() const;

    // The marks to draw. Positions are recomputed from the range and the plot
    // area, so a tick's own `position` field is ignored -- only `value` and
    // `major` are read. That way a caller may hand over ticks generated
    // against a different pixel span without them landing wrong.
    void setTicks(const std::vector<ICoreChartTick>& ticks);
    [[nodiscard]] std::vector<ICoreChartTick> ticks() const;

    // ---- Labels ----------------------------------------------------------

    // Explicit label strings, one per MAJOR tick in order. Handed rather than
    // formatted for the same reason the ticks are: a ruler that formats its
    // own numbers has an opinion about them. Clearing it returns the item to
    // its default formatting.
    void setTickLabels(const std::vector<ICoreString>& labels);
    void clearTickLabels();

    // The label for a major tick, as it would be drawn -- an explicit string
    // if one was set, otherwise the default formatting of the tick's value.
    [[nodiscard]] ICoreString labelForMajorTick(int majorIndex) const;

    void setTickLabelFont(const ICoreFont& font);
    void setTickLabelsVisible(bool visible);

    // ---- Style -----------------------------------------------------------

    void setStyle(const ICoreChartAxisStyle& style);
    [[nodiscard]] ICoreChartAxisStyle style() const;

    void setGridVisible(bool visible);
    void setMinorGridVisible(bool visible);

protected:
    // The grid, the shade bands and the axis furniture all draw here; the
    // default rounded body is replaced, since a ruler has no body of its own.
    void paintBody(ICorePainter& painter) override;
    void paintContent(ICorePainter& painter) override;

private:
    class Impl;                    // the two-line residue; state lives here
    std::unique_ptr<Impl> impl;
};

ICoreChartAxisSet.h#

ICoreEssentials/UI/ICoreChartBase/ICoreChartAxisSet.h

ICoreChartAxisSet#

ICoreChartAxisSet.h:43 · class · pImpl · 21 declaration(s)

class ICoreChartAxisSet {
public:
    // `parentPlotItem` is the chart's root item; the axes are created as its
    // children so they move and clip with it.
    explicit ICoreChartAxisSet(ICoreNativeItem* parentPlotItem = nullptr);

    ~ICoreChartAxisSet();

    // ---- The seam's verbs, spelled exactly as ICoreChartBase publishes them -

    // Creates whichever of the two axes does not exist yet, with a default
    // ruler. ⚠ It does NOT destroy or restyle an axis that already exists --
    // see the header note. Callers that repaint immediately afterwards are
    // carrying a workaround for the old toolkit and may drop it.
    void buildDefaultAxes();

    [[nodiscard]] bool hasAxis(ICoreChartAxisSide side) const;

    // Zero when there is no axis on that side, matching what the seam's
    // callers already tolerate.
    [[nodiscard]] double axisMinimum(ICoreChartAxisSide side) const;
    [[nodiscard]] double axisMaximum(ICoreChartAxisSide side) const;

    // Sets the ruler and regenerates its ticks. A no-op when there is no axis
    // on that side.
    void setAxisRange(ICoreChartAxisSide side, double minimum, double maximum);

    [[nodiscard]] bool isAxisLogarithmic(ICoreChartAxisSide side) const;

    // Swaps a linear ruler for a base-10 logarithmic one, IN PLACE. The range
    // is carried over and rounded out to whole decades, clamped away from
    // zero -- a log ruler has no position for a non-positive number.
    //
    // False when there is no axis on that side or it is already logarithmic,
    // which is the seam's contract unchanged.
    bool convertAxisToLogarithmic(ICoreChartAxisSide side);

    void styleAllAxes(const ICoreChartAxisStyle& style);

    // ---- Layout, which is a two-step settle by construction ---------------

    // Hand it the plot area the root computed. The axes place themselves
    // against it and re-measure their gutters, so the answer to
    // gutterFor() below may change as a result -- which is why laying a chart
    // out means asking, subtracting, and telling.
    void setPlotArea(const ICoreRect& plotAreaInParentCoords);

    // What this side's ruler needs outside the plot area, for the root's
    // setAxisLabelGutters(). Zero when the axis does not exist.
    [[nodiscard]] double gutterFor(ICoreChartAxisSide side) const;

    // ---- Autoscale ---------------------------------------------------------

    // Sets the ruler to the smallest round interval containing `values`,
    // using the arithmetic this axis is already on. An empty or unusable set
    // yields the kind's default ruler rather than an unreadable one.
    void autoscaleAxis(ICoreChartAxisSide side, const std::vector<double>& values);

    // ---- Interaction: every verb here is a RANGE EDIT ---------------------
    //
    // ⚠ THREE SPACES MEET AT `zoomToRect`, AND THEY ARE ONLY THE SAME SPACE BY
    // ARRANGEMENT. The chart this replaces was ONE object that received the
    // mouse, parented the rubber-band overlay and performed the zoom, so
    // `event.pos`, the overlay's rectangle and the zoom's input were the same
    // coordinates for free. Here they are three different items, and nothing
    // makes them agree by construction.
    //
    // The arrangement that keeps them agreeing, stated so it can be checked
    // rather than assumed:
    //
    //   * the ROOT plot item receives the mouse, so `event.pos` is in ITEM
    //     coordinates -- the root's own frame, origin at its top-left;
    //   * the rubber-band overlay is a CHILD of that root, so the rectangle it
    //     is given is in the same frame;
    //   * `zoomToRect` below therefore takes ITEM coordinates too, and does
    //     the one conversion to plot-area pixels itself, in one place.
    //
    // ⚠ IT IS THE PLOT AREA, NOT THE ITEM, THAT MAPS TO DATA. The rulers span
    // the plot area; the item is bigger by its margins, its title band and its
    // gutters. Handing item coordinates straight to the scale would zoom to
    // the wrong data by exactly the gutter width -- which looks almost right,
    // which is why it gets shipped.
    void zoomToRect(const ICoreRect& rectInItemCoords);

    // `factor` > 1 zooms in about the centre of each ruler -- the arithmetic
    // centre on a linear one, the GEOMETRIC centre on a logarithmic one, so a
    // decade-spanning plot keeps the decade under the pointer where it was.
    void zoomByFactor(double factor);

    // Back to the ranges in force before zooming or panning began. Setting a
    // range explicitly -- setAxisRange, autoscaleAxis, a log conversion --
    // establishes a new home; zooming and panning do not.
    void resetZoom();

    // The middle-button drag: how far the POINTER moved, in pixels.
    //
    // ⚠ THE CONTENT FOLLOWS THE POINTER, which is the opposite of shifting the
    // ruler and is why the implementation negates. Drag right and the plot
    // slides right, bringing SMALLER x into view. Drag down and HIGHER y comes
    // into view, because screen y grows downward while data y grows upward --
    // the asymmetry is real and is preserved exactly as the editor has it.
    //
    // ⚠ GATED TO LINEAR AXES, PRESERVING TODAY'S BEHAVIOUR: a logarithmic
    // chart does not pan. That is carried forward deliberately rather than
    // fixed in passing -- enabling it is a behaviour change, and the scale
    // core can already do it (icoreChartPannedByPixels keeps a log ruler's
    // RATIO), so it is a one-line decision for whoever owns the product, not
    // a missing capability.
    void panByPixels(double horizontalPixels, double verticalPixels);

    // Whether this side exists AND is linear. The pan gate is spelled as one
    // question because both call sites ask exactly it.
    [[nodiscard]] bool hasLinearAxis(ICoreChartAxisSide side) const;

    // ---- Access, for whoever composes the chart ---------------------------

    // Null when the axis does not exist. Not owning -- the set does.
    [[nodiscard]] ICoreChartAxisItem* axisItem(ICoreChartAxisSide side) const;

    [[nodiscard]] ICoreChartRange rangeFor(ICoreChartAxisSide side) const;

    // How many minor marks sit between majors on a LINEAR ruler. Zero by
    // default, which is what the chart shows today; a logarithmic ruler
    // ignores it in favour of its own 2..9 mantissa minors.
    void setMinorsPerMajor(int minors);

private:
    class Impl;                    // the two-line residue; state lives here
    std::unique_ptr<Impl> impl;
};

ICoreChartBase.h#

ICoreEssentials/UI/ICoreChartBase/ICoreChartBase.h

ICoreChartBase -- the plot item every ICore chart is built on, and the wrapper half of the chart seam: ICoreChart derives from THIS and names no toolkit type of its own.

CONVERTED BY P8.4. The toolkit's chart is Impl now, and it does NOT derive from this class. The same two consequences every converted tier states:

  • A SUBCLASS MUST OVERRIDE THE HOOKS, NEVER THE TOOLKIT VIRTUALS. A

wheelEvent() or mousePressEvent() on a subclass of this type is called by NOTHING -- no diagnostic. The wheelScrolled/mousePressed/... hooks below are the spellings that still reach the toolkit.

  • THIS TYPE IS NO LONGER A QObject. It cannot be a connect context, a

QPointer target, or a stylesheet selector. Theme subscriptions go through subscribe()'s ICoreNativeItem overload; queued work through

ICoreChartAxisStyle#

ICoreChartBase.h:51 · struct · 0 declaration(s)

The ink an axis is painted with.

struct ICoreChartAxisStyle {
public:
    ICoreColor labelColor;        // the numbers running along the ruler
    ICoreColor titleColor;
    ICoreColor lineColor;         // the rule itself and its tick marks
    ICoreColor gridColor;
    ICoreColor minorGridColor;
    bool shadesVisible = false;   // the alternating bands behind the plot
};
};

File-scope declarations#

// Which of a plot's two rulers an axis call is about. There is exactly one of
// each: the chart's own accessors assume a single horizontal and a single
// vertical axis, which is what every plot in this editor has.
enum class ICoreChartAxisSide {
    X,
    Y
};

ICoreChartLegendItem.h#

ICoreEssentials/UI/ICoreChartBase/ICoreChartLegendItem.h

ICoreChartLegendItem#

ICoreChartLegendItem.h:43 · class · bases public ICoreGraphicsObject · pImpl · 18 declaration(s)

ICoreChartLegendItem -- the key: one row per series, swatch and name.

class ICoreChartLegendItem : public ICoreGraphicsObject {
public:
    explicit ICoreChartLegendItem(ICoreNativeItem* parent = nullptr);

    // Out of line: Impl must be complete where the unique_ptr's deleter is
    // instantiated.
    ~ICoreChartLegendItem() override;

    // ---- What it lists ---------------------------------------------------

    // The series to key, in drawing order. Non-owning; the plot owns them.
    // A null entry is skipped rather than crashing -- a chart mid-teardown can
    // hold one.
    void setSeries(const std::vector<ICoreChartSeriesItem*>& series);

    // Re-read names and colours from the same items. Cheaper than setSeries()
    // and the call a rename or a recolour makes.
    void refresh();

    [[nodiscard]] int rowCount() const;

    // ---- Appearance ------------------------------------------------------

    void setLabelColor(const ICoreColor& color);
    [[nodiscard]] ICoreColor labelColor() const;

    void setLabelFont(const ICoreFont& font);
    [[nodiscard]] ICoreFont labelFont() const;

    // The ground behind the key. Off by default, which is what the seam's
    // setLegendBackgroundVisible(false) asks for and what today's plots show.
    void setBackgroundVisible(bool visible);
    [[nodiscard]] bool backgroundVisible() const;
    void setBackgroundColor(const ICoreColor& color);

    // ---- Geometry --------------------------------------------------------

    // The size this legend needs for its current rows, measured from the label
    // font -- the same producer/consumer split C3.1 used for axis gutters, so
    // the root can lay it out without guessing.
    [[nodiscard]] ICoreRect requiredBounds() const;

    [[nodiscard]] ICoreRect contentBounds() const override;

protected:
    void paintContent(ICorePainter& painter) override;

    // ⚠ THE NAMES ARE A HOVER READOUT, so this item tracks the pointer. It
    // takes no mouse BUTTON (the constructor declines them all): a legend sits
    // over the plot at z 10 and a press on it belongs to whatever is beneath.
    void pointerEntered(const ICoreMouseEvent& event) override;
    void pointerMoved(const ICoreMouseEvent& event) override;
    void pointerLeft() override;

private:
    class Impl;                    // the two-line residue; state lives here
    std::unique_ptr<Impl> impl;
};

ICoreChartLinePathBase.h#

ICoreEssentials/UI/ICoreChartBase/ICoreChartLinePathBase.h

ICoreChartLinePathBase -- one traced line on a plot, and the wrapper half of the line-path seam: ICoreChartLinePath derives from THIS and names no toolkit type of its own.

CONVERTED BY P8.4. The toolkit's line series is Impl now, and it does NOT derive from this class. This type is no longer a QObject: it cannot be a connect context or a QPointer target -- ICoreChartLinePathRef re-seats its liveness guard on nativeObjectHandle() for exactly that reason.

What it adds is the API a path needs spelled in ICore types -- points, the stroke, the point labels, the scatter marker -- plus one hook so a subclass never touches a toolkit signal.

The visibility/opacity/point-visibility accessors below USED to be absent on

ICoreChartLinePathBase#

ICoreChartLinePathBase.h:45 · class · bases public ICoreNativeObject · pImpl · 35 declaration(s)

class ICoreChartLinePathBase : public ICoreNativeObject {
public:
    // The Impl's QObject base, stated with its own cast in the .cpp (R2).
    ICoreNativeHandle nativeObjectHandle() const override;

    explicit ICoreChartLinePathBase(ICoreChartBase* parentChart);

    // Out of line: `Impl` must be complete where the unique_ptr's deleter is
    // instantiated.
    ~ICoreChartLinePathBase() override;

    // ---- Points ----------------------------------------------------------

    int pointCount() const;
    ICorePoint pointAt(int index) const;
    ICoreList<ICorePoint> pathPoints() const;

    // Replaces the drawn points wholesale -- one repaint rather than one per
    // point, which is what a streaming plot needs.
    void replacePoints(const ICoreList<ICorePoint>& points);

    // The tightest rectangle around the drawn points. Null when there are none.
    ICoreRect pointsBoundingBox() const;

    // ---- Identity --------------------------------------------------------

    ICoreString lineName() const;
    void setLineName(const ICoreString& name);

    // ---- Visibility and opacity ------------------------------------------
    //
    // Same names, same plain-value signatures the toolkit had, so the sites
    // that used to reach these through inheritance compile untouched.

    void setVisible(bool visible);
    bool isVisible() const;

    void setOpacity(double opacity);
    double opacity() const;

    void setPointsVisible(bool visible);
    bool pointsVisible() const;

    void setPointLabelsVisible(bool visible);
    bool pointLabelsVisible() const;

    // Drawn, but not listed in the legend, not among the chart's pathNames()
    // and never picked by a press -- context rather than data, such as a
    // scope's faded traces of its previous runs. Set it before addPath().
    void setDecorative(bool decorative);
    bool isDecorative() const;

    // ---- Stroke ----------------------------------------------------------

    ICoreColor lineColor() const;
    void setLineColor(const ICoreColor& color);

    // Whole pixels, which is what the toolkit's pen carries and what the
    // saved state has always recorded -- a fractional width here would change
    // the digits that land in existing project files.
    int lineWidth() const;
    void setLineWidth(int width);

    ICorePenStyle lineStyle() const;
    void setLineStyle(ICorePenStyle style);

    ICorePenCap lineCap() const;
    void setLineCap(ICorePenCap cap);

    ICorePenJoin lineJoin() const;
    void setLineJoin(ICorePenJoin join);

    // ---- Point labels ----------------------------------------------------

    ICoreColor labelsColor() const;
    void setLabelsColor(const ICoreColor& color);

    ICoreFont labelsFont() const;
    void setLabelsFont(const ICoreFont& font);

    // ---- Scatter ---------------------------------------------------------

    // Draws each point as a marker of the given shape and size (in pixels).
    // `keepConnectingLine` false leaves a pure scatter; true overlays markers
    // on a normal line plot.
    //
    // Implemented with the toolkit's light-marker path rather than a scatter
    // series on purpose: everything around a path -- the chart's side panel,
    // CSV export, state save/restore, bounds tracking -- is typed to the line
    // path, so a second series class would need every one of them widened
    // before a scatter path behaved like a first-class path.
    //
    // The line is drawn with the series pen, so removing the pen is what turns
    // this into a pure scatter. The colour is still set either way, so the
    // legend entry and the side panel's swatch match the markers.
    void setPointMarker(ICoreChartMarkerShape shape, const ICoreColor& color,
                        double markerSizePx, bool keepConnectingLine);

protected:
    // The user clicked the path; `point` is the press location ON the path, in
    // plot coordinates. Subscribed once against the Impl, so a subclass
    // overrides this instead of touching a toolkit signal.
    virtual void pointClicked(const ICorePoint& point);

private:
    class Impl;                    // the two-line residue; state lives here
    std::unique_ptr<Impl> impl;
};

File-scope declarations#

// How a path's points are drawn when it is a scatter rather than a signal.
enum class ICoreChartMarkerShape {
    Circle,
    Cross,
    Square,
    Diamond
};

ICoreChartPlotItem.h#

ICoreEssentials/UI/ICoreChartBase/ICoreChartPlotItem.h

ICoreChartPlotItem#

ICoreChartPlotItem.h:39 · class · bases public ICoreGraphicsObject · pImpl · 21 declaration(s)

class ICoreChartPlotItem : public ICoreGraphicsObject {
public:
    explicit ICoreChartPlotItem(ICoreNativeItem* parent = nullptr);

    // Out of line: the Impl below is forward-declared.
    ~ICoreChartPlotItem() override;

    // ---- Geometry --------------------------------------------------------

    // Pins the item to exactly this size. The chart library underneath this
    // tier needed three calls to set a widget's minimum, preferred and maximum
    // together; a plain scene item has one size, and this is it.
    void setPlotItemSize(double width, double height);

    // The item's own rectangle, origin at (0,0).
    //
    // ⚠ contentBounds() AND plotItemBounds() ARE THE SAME RECTANGLE, and both
    // spellings are kept on purpose. contentBounds() is what the scene and the
    // base class ask; plotItemBounds() is the name ICoreChartBase publishes,
    // and the editor positions a chart's two axis-title items against it at
    // fixed offsets. Keeping the second name means the public class forwards
    // rather than translates.
    [[nodiscard]] ICoreRect contentBounds() const override;
    [[nodiscard]] ICoreRect plotItemBounds() const;

    // The data region bounded by the axes, in item coordinates. Empty until
    // the item has been given a size, which is what ICoreChartBase promises.
    [[nodiscard]] ICoreRect plotAreaBounds() const;

    // Padding between the item's edge and everything drawn inside it. Every
    // call site in this tree passes (0, 0, 0, 0) today, so this exists to
    // preserve an existing capability rather than to style anything.
    void setPlotMargins(double left, double top, double right, double bottom);

    // What the axis items reserve for their TICK LABELS -- the Y ruler's want
    // room on the left, the X ruler's at the bottom. Zero for every side until
    // C3.1 exists, which is why a chart with no axes gets its whole margin box
    // as plot area.
    //
    // ⚠ NOT axis TITLES. Those are separate editor-owned text items
    // positioned against plotItemBounds(), and no axis title text is ever set
    // on the chart itself. Reserving room for one here would move every
    // existing plot.
    void setAxisLabelGutters(double left, double top, double right, double bottom);

    // The chart's OWN padding, the same on all four sides, applied outside
    // setPlotMargins() rather than instead of it. Zero by default.
    //
    // ⚠ ADDED AT C7.1, AND IT EXISTS BECAUSE QChart HAD ONE (§0.21). QChart
    // deflated its content by margins() -- 20 px per side by default -- and
    // NOTHING in this tree ever changed that; the seam's setPlotMargins() went
    // to the graphics LAYOUT's contents margins, which is a different inset and
    // is what every call site sets to zero. So a plot item with no padding is
    // not "the same chart with the margins the caller asked for": it is a chart
    // missing 20 px on every side that the editor's axis-title offsets
    // (13 / 10 / 20, ICoreChart::updateAllChildrenPositions) were calibrated
    // against, and the first render through the swapped seam showed exactly
    // that -- "amplitude" over the Y ruler and the last X tick label clipped.
    //
    // Kept as a setter with a ZERO default rather than a constant, so this
    // class still answers "the plot area is the margin box" for a caller that
    // asks for nothing, which is what C2.1's harness measures. The 20 is set
    // by the seam, which is the layer that owes QChart parity.
    void setChartPadding(double padding);
    [[nodiscard]] double chartPadding() const;

    // How much room to leave for the LEGEND, between the title band and the
    // plot area. Zero by default, which is a chart with no legend and is what
    // C2.1's own harness measures.
    //
    // ⚠ ADDED AT C7.1 (§0.21). The legend is a child item like the axes, not
    // something this class paints -- so this reserves the space and
    // legendBandBounds() below says where it went, and the seam moves the
    // legend into it. Reserving rather than overlaying is what QtCharts did
    // and is the difference between a key beside the data and a key ON it.
    void setLegendBandHeight(double height);

    // Where that reserved band ended up, in item coordinates. Zero-height when
    // nothing was reserved, so a caller can position into it unconditionally.
    [[nodiscard]] ICoreRect legendBandBounds() const;

    // ---- Appearance ------------------------------------------------------

    void setPlotTitle(const ICoreString& title);
    [[nodiscard]] ICoreString plotTitle() const;

    // ⚠ AN EMPTY TITLE RESERVES NO BAND AT ALL, and that is a behaviour to
    // preserve rather than an optimisation: the editor sets the title to ""
    // for an embedded plot on every reset, and a band reserved for an empty
    // string would shift the plot area of every one of them down by a line.
    void setTitleFont(const ICoreFont& font);
    void setTitleColor(const ICoreColor& color);

    // The chart rectangle, behind everything.
    void setChartBackgroundColor(const ICoreColor& color);

    // The data region. Setting the colour also makes it visible: it is off by
    // default, and the two have always travelled together.
    void setPlotAreaBackgroundColor(const ICoreColor& color);
    void setPlotAreaBackgroundVisible(bool visible);

protected:
    void paintBody(ICorePainter& painter) override;
    void paintContent(ICorePainter& painter) override;

private:
    class Impl;                    // the two-line residue; state lives here
    std::unique_ptr<Impl> impl;
};

ICoreChartPlotLayout.h#

ICoreEssentials/UI/ICoreChartBase/ICoreChartPlotLayout.h

ICoreChartPlotLayout -- how a chart's item rectangle becomes a plot area, and nothing else.

One pass, four subtractions, in this order:

item rectangle -> deflate by the MARGINS -> take the TITLE BAND off the top -> take the LEGEND BAND off what is left of the top -> deflate by the AXIS-LABEL GUTTERS -> what is left is the PLOT AREA

⚠ THIS HEADER NAMES NO ICore TYPE AND NO TOOLKIT TYPE, for the reason ICoreChartScale.h states at length beside it: the arithmetic under the plot is the part that must survive Qt -> AppKit -> WinUI untouched, and a unit

ICoreChartPlotInsets#

ICoreChartPlotLayout.h:42 · struct · 0 declaration(s)

A per-side thickness.

struct ICoreChartPlotInsets {
public:
    double left = 0.0;
    double top = 0.0;
    double right = 0.0;
    double bottom = 0.0;
};
};

ICoreChartPlotBox#

ICoreChartPlotLayout.h:51 · struct · 0 declaration(s)

A rectangle in ITEM coordinates, origin at the item's top-left, y growing downward.

struct ICoreChartPlotBox {
public:
    double x = 0.0;
    double y = 0.0;
    double width = 0.0;
    double height = 0.0;
};
};

ICoreChartPlotLayoutRequest#

ICoreChartPlotLayout.h:59 · struct · 0 declaration(s)

What the layout pass is asked.

struct ICoreChartPlotLayoutRequest {
public:
    double itemWidth = 0.0;
    double itemHeight = 0.0;

    ICoreChartPlotInsets margins;
    ICoreChartPlotInsets gutters;

    // Zero when the chart has no title. Measuring it is the item's job (it
    // owns the font); reserving it is this file's.
    double titleBandHeight = 0.0;

    // Zero when the chart has no legend, or has one that lists nothing.
    //
    // ⚠ ADDED AT C7.1, AND IT IS A BAND RATHER THAN A GUTTER ON PURPOSE. The
    // seam had exactly two places to put the legend: reserved space, or on top
    // of the data. QtCharts reserved -- its key sits top-centre with the plot
    // pushed down under it -- and a floating legend over the polyline was
    // §0.17's defect #3. The alternative considered and rejected was passing
    // the height through gutters.top, which would have worked and would have
    // left the next reader a call site whose name says "axis labels" and whose
    // argument is a legend.
    //
    // Measuring it is the legend item's job (it owns the label font, and
    // publishes requiredBounds()); reserving it is this file's -- the same
    // producer/consumer split as the title band above and the gutters below.
    double legendBandHeight = 0.0;
};
};

ICoreChartPlotAreas#

ICoreChartPlotLayout.h:88 · struct · 0 declaration(s)

What it answers.

struct ICoreChartPlotAreas {
public:
    // The item's own rectangle -- always at the origin, and always exactly
    // what the plot item reports as its bounds. The editor anchors a chart's
    // axis-title items to that rectangle at fixed offsets, so it is not free
    // to move.
    ICoreChartPlotBox item;

    // Where the plot title is drawn. Zero-height when there is no title, so a
    // painter can draw it unconditionally and paint nothing.
    ICoreChartPlotBox titleBand;

    // Where the legend is placed, directly under the title band and the full
    // width of the margin box. Zero-height when nothing is keyed. The legend
    // is a CHILD ITEM rather than something the plot item paints, so this is
    // a rectangle to position it in, not one to draw in.
    ICoreChartPlotBox legendBand;

    // The data region, bounded by the axes.
    ICoreChartPlotBox plotArea;
};
};

ICoreChartScale.h#

ICoreEssentials/UI/ICoreChartBase/ICoreChartScale.h

ICoreChartScale -- the arithmetic under a plot's rulers, and nothing else.

Everything a chart needs to answer "where on screen does this number go, and which numbers get a tick" lives here, as free functions over plain doubles:

  • data -> pixel and pixel -> data, linear and base-10 logarithmic
  • 1-2-5 "nice" tick selection, majors and minors
  • autoscale from a set of sample values
  • zoom, as a range edit

⚠ THIS HEADER DELIBERATELY NAMES NO ICore TYPE AND NO TOOLKIT TYPE. Not ICoreRect, not ICorePoint, not ICoreList -- plain doubles and std::vector. That is not laziness about the wrapper types, it is the point of the file: this is the one piece of the chart that must survive Qt -> AppKit -> WinUI

ICoreChartRange#

ICoreChartScale.h:54 · struct · 0 declaration(s)

A ruler's data-space interval, closed at both ends, plus which arithmetic maps it.

struct ICoreChartRange {
public:
    double minimum = 0.0;
    double maximum = 1.0;
    ICoreChartScaleKind kind = ICoreChartScaleKind::Linear;
};
};

ICoreChartTick#

ICoreChartScale.h:62 · struct · 0 declaration(s)

One mark on a ruler: the number it stands for, where that number lands in pixels, and whether it is a labelled major or an unlabelled minor.

struct ICoreChartTick {
public:
    double value = 0.0;
    double position = 0.0;
    bool major = true;
};
};

File-scope declarations#

// Which arithmetic a ruler runs. Pinned values: this enum is saved in project
// state by way of the axis it describes, so the integers are part of the file
// format and only ever get appended to.
enum class ICoreChartScaleKind : int {
    Linear = 0,
    Logarithmic = 1
};

ICoreChartSceneSeam.h#

ICoreEssentials/UI/ICoreChartBase/ICoreChartSceneSeam.h

IMPLEMENTATION SIDE ONLY -- include from a .cpp, never from a public header.

The two things ICoreChartBase needs that no portable wrapper can answer: given an ITEM, what is the whole SCENE it sits in worth, and can it be drawn onto a painter (A7.4).

⚠ WHY A SEAM RATHER THAN A CALL ON ICoreGraphicsScene, WHICH ALREADY HAS renderTo(). Because the chart never holds the scene WRAPPER. A chart is added to a scene by whoever builds the window, and its own three export routes are documented as rendering "the whole scene's rectangle, which for a window holding stacked charts covers all of them" -- so the chart has an ICoreNativeItem (itself) and nothing else. Reaching a scene from an item is per-backend by nature: one toolkit answers it with QGraphicsItem::scene(), the other with ICoreAppKitSceneNode::scene.

Declares no class of its own — see the file.

ICoreChartSeriesItem.h#

ICoreEssentials/UI/ICoreChartBase/ICoreChartSeriesItem.h

ICoreChartSeriesItem#

ICoreChartSeriesItem.h:47 · class · bases public ICoreGraphicsObject · pImpl · 43 declaration(s)

ICoreChartSeriesItem -- one traced line on a plot, drawn as a scene item.

class ICoreChartSeriesItem : public ICoreGraphicsObject {
public:
    explicit ICoreChartSeriesItem(ICoreNativeItem* parent = nullptr);

    // Out of line: Impl must be complete where the unique_ptr's deleter is
    // instantiated.
    ~ICoreChartSeriesItem() override;

    // ---- Data ------------------------------------------------------------

    // Replaces the drawn points wholesale and repaints ONCE. This is what a
    // streaming plot needs: the sink bridge pushes a whole buffer per tick,
    // and a per-point signal would repaint per point.
    void replacePoints(const std::vector<ICorePoint>& points);

    [[nodiscard]] int pointCount() const;
    [[nodiscard]] ICorePoint pointAt(int index) const;
    [[nodiscard]] std::vector<ICorePoint> points() const;

    // The tightest rectangle around the points, in DATA space. Null when there
    // are none, and it SKIPS unplottable samples -- so a stray infinity cannot
    // stretch an autoscale to the horizon.
    [[nodiscard]] ICoreRect pointsBoundingBox() const;

    // ---- Where it draws --------------------------------------------------

    // The plot area, in the PARENT's coordinates. The item moves itself there
    // and sizes itself to match, so its own space is (0,0,w,h).
    void setPlotArea(const ICoreRect& plotAreaInParentCoords);

    // What the two rulers currently read. A zoom or a pan is this call.
    void setRanges(const ICoreChartRange& horizontal, const ICoreChartRange& vertical);

    // ---- Identity --------------------------------------------------------
    //
    // Held here rather than passed to the legend, so that C5.1 has one place
    // to read a series' name and colour from and cannot disagree with what was
    // drawn.

    [[nodiscard]] ICoreString seriesName() const;
    void setSeriesName(const ICoreString& name);

    // ---- Stroke ----------------------------------------------------------

    [[nodiscard]] ICoreColor lineColor() const;
    void setLineColor(const ICoreColor& color);

    // Whole pixels: the saved state has always recorded an integer width, and
    // a fractional one here would change the digits in existing project files
    // (ICoreChartLinePathBase.h states the same constraint).
    [[nodiscard]] int lineWidth() const;
    void setLineWidth(int width);

    [[nodiscard]] ICorePenStyle lineStyle() const;
    void setLineStyle(ICorePenStyle style);

    [[nodiscard]] ICorePenCap lineCap() const;
    void setLineCap(ICorePenCap cap);

    [[nodiscard]] ICorePenJoin lineJoin() const;
    void setLineJoin(ICorePenJoin join);

    // False draws no connecting line, which is what turns this into a pure
    // scatter once C4.2 adds the markers. The colour is still kept, so the
    // legend swatch and the markers agree.
    [[nodiscard]] bool lineVisible() const;
    void setLineVisible(bool visible);

    // ---- Markers (C4.2) --------------------------------------------------
    //
    // ⚠ The shape enum is `ICoreChartMarkerShape`, taken from the seam header
    // beside this one rather than restated. A second enum with the same four
    // names would need a translation table at C7.1, and a translation table is
    // a restated value: it drifts the first time somebody adds a fifth shape
    // to one of them.

    // Draws each point as a marker. `markerSizePx` is the marker's full width.
    // `keepConnectingLine` false leaves a pure scatter -- implemented by
    // turning the line off, which is exactly how the seam describes it: "the
    // line is drawn with the series pen, so removing the pen is what turns
    // this into a pure scatter". The colour is kept either way so the legend
    // swatch and the markers agree (C5.1 reads it from here).
    void setPointMarker(ICoreChartMarkerShape shape, const ICoreColor& color,
                        double markerSizePx, bool keepConnectingLine);

    // Markers without changing shape, size or colour -- the seam's
    // pointsVisible() pair.
    [[nodiscard]] bool pointsVisible() const;
    void setPointsVisible(bool visible);

    [[nodiscard]] ICoreChartMarkerShape markerShape() const;
    [[nodiscard]] ICoreColor markerColor() const;
    [[nodiscard]] double markerSize() const;

    // ---- Point labels (C4.2) ---------------------------------------------
    //
    // ⚠ Formatted with ICoreString::number(v, 'g', 6) -- the SAME formatting
    // the CSV export uses (ICoreTextStream reaches the same call). A plot
    // whose labels disagree with its own exported data would be a defect
    // nobody would think to look for.

    [[nodiscard]] bool pointLabelsVisible() const;
    void setPointLabelsVisible(bool visible);

    [[nodiscard]] ICoreColor labelsColor() const;
    void setLabelsColor(const ICoreColor& color);

    [[nodiscard]] ICoreFont labelsFont() const;
    void setLabelsFont(const ICoreFont& font);

    // ⚠ setVisible() and setOpacity() are NOT redeclared here: they are
    // ICoreGraphicsObject's own and already do the right thing. Adding
    // same-name forwarders would hide the base's overload set for no gain.

    // ---- Hit testing and clicks (C4.3) -----------------------------------

    // How far from the drawn ink a press still counts, in pixels. It widens
    // the CLICKABLE outline, never the drawn line -- a 1 px trace stays
    // pickable without getting fatter.
    [[nodiscard]] double pickTolerance() const;
    void setPickTolerance(double pixels);

    // Called with the DATA point nearest the press, in plot coordinates.
    // C7.1 forwards this to the seam's own pointClicked() hook, which
    // documents the same contract: "`point` is the press location ON the
    // path, in plot coordinates".
    void setPointClickedHandler(std::function<void(const ICorePoint&)> handler);

    // ⚠ THE CLICKABLE OUTLINE IS THE INK, NOT THE BOUNDING BOX. A series' box
    // is the whole plot area and is mostly empty; leaving the default would
    // make every click anywhere in the plot hit the topmost series and
    // swallow presses meant for what is behind it. This is the same reason
    // ICoreLinkBranchSegment overrides it -- the precedent
    // ICorePainterPath::strokedRound() was added for.
    //
    // What is clickable follows what is DRAWN: the widened polyline when the
    // line is visible, the markers alone on a pure scatter, and nothing at all
    // when neither is drawn. You cannot click an invisible line.
    [[nodiscard]] ICorePainterPath contentShape() const override;

    // This item's box, always at the origin and always the size of the plot
    // area it was given.
    [[nodiscard]] ICoreRect contentBounds() const override;

    // Resolve a press, given in THIS item's coordinates, to the nearest drawn
    // sample and report it through the point-clicked handler. False when
    // nothing is near enough, or when no handler is installed.
    //
    // ⚠⚠ PUBLIC, AND CALLED BY THE CHART RATHER THAN BY THE SCENE. This item
    // accepts no mouse button at all -- the constructor says why at length, and
    // the short form is that the native scene core hit-tests contentBounds (a
    // rectangle covering the whole plot area) and never contentShape(), so an
    // accepting series swallows every press in the plot. The chart holds the
    // grab and offers the press here through
    // ICoreChartBase::offerPressToSeries().
    [[nodiscard]] bool pressAt(const ICorePoint& pointInItem);

    // ---- Decoration --------------------------------------------------------
    //
    // A DECORATIVE series is drawn and nothing else: the legend gives it no
    // swatch, ICoreChartBase::pathNames() leaves it out, and pressAt() never
    // takes a press for it. It is for ink that is context rather than data --
    // a scope's faded traces of its previous runs -- which a reader must not
    // be able to pick, look up in the legend, or find among the exported
    // columns. Set it before the series is added to its chart: the legend's
    // band is sized when a series arrives.
    [[nodiscard]] bool isDecorative() const;
    void setDecorative(bool decorative);

protected:
    void paintContent(ICorePainter& painter) override;

private:
    class Impl;                    // the two-line residue; state lives here
    std::unique_ptr<Impl> impl;
};

ICoreChartSeriesItemAccess.h#

ICoreEssentials/UI/ICoreChartBase/ICoreChartSeriesItemAccess.h

Declares no class of its own — see the file.