Skip to content
AnyChart

AnyChart

Mojo struct 🡭

AnyChart

@memory_only
struct AnyChart

A chart whose mark type has been erased, so charts of different marks can share a List for layers, facets and figures. It keeps everything a chart shares by value, copies of the shared channel structs the composition code reads (empty for a mark without them), the mark id, and the mark itself behind a pointer with the functions that draw, copy and free it, specialized when the chart is erased. Only the marks a program erases get instantiated.

Erasing has to name the mark’s render for every draw target it may meet, since the mark type is gone by the time a target is chosen, and each of those is a full render pipeline to compile: the three a render_layers() program never uses cost it about 17 CPU-s (#838). AnyChart(chart) and erased() bind all four, which a List[AnyChart] needs because any entry may consume it. bound[...]() and erased_with[...]() bind only the targets named and leave the other slots on a stub that raises, for the variadic entries that erase inside a call that knows which targets it draws marks into and never hand the chart on. Layers draw every mark through their own layer renderers and bind none; facets draw each cell through the slot for their output target. (render_grid()’s align_axes probe draws through the Canvas slot whatever the output, which is fine: it takes a List[AnyChart].)

record() draws into a DisplayList, a fifth slot that no entry taking a List[AnyChart] uses, so erased() leaves it unbound and a chart that will be recorded after erasing asks for it with erased_with[display_list=True]() (#862). A target type with no slot of its own is a compile error in render_mark, never a draw through another target’s slot.

mark[M]() gives the mark back to code that has checked id.

Fields

  • mark_id (Mark)
  • caps (_Capabilities)
  • settings (_ChartSettings)
  • style (_MarkStyle)
  • annotations (_AnnotationData)
  • width (Int)
  • height (Int)
  • continuous (_ContinuousData)
  • categorical (_CategoricalData)
  • channels (_ChannelData)
  • y_err (_ErrorBarData)
  • distribution (_DistributionData)

Implemented traits

AnyType, ChartLike, Copyable, Deinitable, Movable

Methods

__init__

fn def __init__[M: MarkType](out self, chart: Chart[M])

Parameters:

  • M (MarkType)

Args:

  • chart (Chart[M])
  • self (Self)

Returns:

Self

fn def __init__[M: MarkType](out self, chart: Chart[M], *, canvas: def(Int, mut Canvas, _MarkStyle, _AnnotationData, _ChartSettings, Int, Int, Int, Int, Bool, Float64, Float64, Bool, mut FontCache, Bool) raises thin -> _RenderResult, svg: def(Int, mut SvgCanvas, _MarkStyle, _AnnotationData, _ChartSettings, Int, Int, Int, Int, Bool, Float64, Float64, Bool, mut FontCache, Bool) raises thin -> _RenderResult, pdf: def(Int, mut PdfCanvas, _MarkStyle, _AnnotationData, _ChartSettings, Int, Int, Int, Int, Bool, Float64, Float64, Bool, mut FontCache, Bool) raises thin -> _RenderResult, bounds: def(Int, mut BoundsTarget, _MarkStyle, _AnnotationData, _ChartSettings, Int, Int, Int, Int, Bool, Float64, Float64, Bool, mut FontCache, Bool) raises thin -> _RenderResult, display_list: def(Int, mut DisplayList, _MarkStyle, _AnnotationData, _ChartSettings, Int, Int, Int, Int, Bool, Float64, Float64, Bool, mut FontCache, Bool) raises thin -> _RenderResult)

Parameters:

  • M (MarkType)

Args:

  • chart (Chart[M])
  • canvas (def(Int, mut Canvas, _MarkStyle, _AnnotationData, _ChartSettings, Int, Int, Int, Int, Bool, Float64, Float64, Bool, mut FontCache, Bool) raises thin -> _RenderResult)
  • svg (def(Int, mut SvgCanvas, _MarkStyle, _AnnotationData, _ChartSettings, Int, Int, Int, Int, Bool, Float64, Float64, Bool, mut FontCache, Bool) raises thin -> _RenderResult)
  • pdf (def(Int, mut PdfCanvas, _MarkStyle, _AnnotationData, _ChartSettings, Int, Int, Int, Int, Bool, Float64, Float64, Bool, mut FontCache, Bool) raises thin -> _RenderResult)
  • bounds (def(Int, mut BoundsTarget, _MarkStyle, _AnnotationData, _ChartSettings, Int, Int, Int, Int, Bool, Float64, Float64, Bool, mut FontCache, Bool) raises thin -> _RenderResult)
  • display_list (def(Int, mut DisplayList, _MarkStyle, _AnnotationData, _ChartSettings, Int, Int, Int, Int, Bool, Float64, Float64, Bool, mut FontCache, Bool) raises thin -> _RenderResult)
  • self (Self)

Returns:

Self

fn def __init__(out self, *, copy: Self)

Args:

  • copy (Self)
  • self (Self)

Returns:

Self

__deinit__

fn def __deinit__(deinit self)

Args:

  • self (Self)

bound

@staticmethod
fn def bound[canvas: Bool, svg: Bool, pdf: Bool, bounds: Bool, display_list: Bool, M: MarkType](chart: Chart[M]) -> Self

chart erased with only the named targets’ renders bound; see the struct docstring. The other slots raise if drawn into.

Parameters:

  • canvas (Bool)
  • svg (Bool)
  • pdf (Bool)
  • bounds (Bool)
  • display_list (Bool)
  • M (MarkType)

Args:

  • chart (Chart[M])

Returns:

Self

mark

fn def mark[M: MarkType](self) -> ref[MutUnsafeAnyOrigin] M

The mark, for code that has checked id; raises otherwise rather than reinterpreting one mark’s memory as another’s.

Parameters:

  • M (MarkType)

Args:

  • self (Self)

Returns:

ref[MutUnsafeAnyOrigin] M

Raises:

id

fn def id(self) -> Mark

Args:

  • self (Self)

Returns:

Mark

canvas_width

fn def canvas_width(self) -> Int

Args:

  • self (Self)

Returns:

Int

canvas_height

fn def canvas_height(self) -> Int

Args:

  • self (Self)

Returns:

Int

chart_settings

fn def chart_settings(self) -> _ChartSettings

Args:

  • self (Self)

Returns:

_ChartSettings

mark_style

fn def mark_style(self) -> _MarkStyle

Args:

  • self (Self)

Returns:

_MarkStyle

chart_annotations

fn def chart_annotations(self) -> _AnnotationData

Args:

  • self (Self)

Returns:

_AnnotationData

scaled_by

fn def scaled_by(self, factor: Float64) -> Self

Args:

  • self (Self)
  • factor (Float64)

Returns:

Self

size

fn def size(var self, width: Int, height: Int) -> Self

Set the dimensions render()/render_svg()/save() construct their target at. Defaults to 640x420.

The unit is a point, 1/72 inch, which is what makes a figure size mean something physical (#372). On the raster backends one point is one pixel at the default resolution, so nothing about the old reading changes; save(..., dpi=300) keeps the physical size and multiplies the pixels. In a PDF it is a point on the page. size_inches()/size_mm() say the same thing in the units a page is usually specified in.

Args:

  • self (Self)
  • width (Int): Figure width in points.
  • height (Int): Figure height in points.

Returns:

Self: Self, for further chaining.

size_inches

fn def size_inches(var self, width: Float64, height: Float64) -> Self

size() in inches: 72 points to the inch, rounded to whole points (#372).

A figure for a journal column or a slide is specified physically, and size(468, 312) does not read as “6.5 by 4.3 inches” to anyone.

Args:

  • self (Self)
  • width (Float64): Figure width in inches.
  • height (Float64): Figure height in inches.

Returns:

Self: Self, for further chaining.

size_mm

fn def size_mm(var self, width: Float64, height: Float64) -> Self

size() in millimeters: 25.4 mm to the inch and 72 points to the inch, rounded to whole points (#372).

Args:

  • self (Self)
  • width (Float64): Figure width in millimeters.
  • height (Float64): Figure height in millimeters.

Returns:

Self: Self, for further chaining.

theme

fn def theme(var self, t: Theme) -> Self

Attach a full Theme to this plot, replacing the default one. Every styling knob lives on Theme.

Args:

  • self (Self)
  • t (Theme): The Theme to attach, replacing whatever was set before (the default Theme() if this is the first call).

Returns:

Self: Self, for further chaining.

tooltips

fn def tooltips(var self, policy: Tooltips) -> Self

Set whether this chart’s data get SVG hover tooltips, overriding Theme.tooltips for this plot only. Tooltips.ON titles every datum, Tooltips.OFF none, and Tooltips.AUTO titles them when there are at most Theme.auto_tooltip_limit (tooltips.mojo). Precedence is this call, then the theme.

Tooltips.ON on a mark without tooltips raises when the chart renders; which marks have them is MarkType.supports_tooltips.

Args:

  • self (Self)
  • policy (Tooltips): Tooltips.ON, Tooltips.OFF or Tooltips.AUTO.

Returns:

Self: Self, for further chaining.

labels

fn def labels(var self, title: String = "", subtitle: String = "", x_title: String = "", y_title: String = "", description: String = "") -> Self

Set the chart title/subtitle and/or axis titles. Named x_title/ y_title rather than x/y so a call next to .encode(x=..., y=...) never reads as setting data.

Each is independent and defaults to "" (not set); layout space is reserved only for the non-empty visible ones (title/subtitle/ x_title/y_title – description draws nothing). subtitle draws directly beneath title, smaller and in Theme.subtitle_color; with no title it draws at the top position title would have used.

x_title/y_title caption whatever is drawn along the bottom/left edge, whichever axis that is for the mark’s orientation. Mark.ARC has no axes, so x_title/y_title raise at render() time there; only title/subtitle/description apply.

description is an SVG <desc> – longer, screen-reader-only context subtitle alone can’t carry since it’s drawn on the chart itself. save()/save_layers()/save_facets() write it (falling back to subtitle when empty) automatically whenever title is set; see accessible_svg_string()’s own docstring for the full markup.

Any of the four drawn labels may hold mathematics between $ signs: "$\sigma^2$", "Rate $\frac{\Delta y}{\Delta x}$". Inside the dollars, Latin letters and lowercase Greek are italic variables and everything else upright; ^ and _ attach scripts, \frac{}{} stacks a fraction, \sqrt{} draws a radical, \mathrm{} forces upright text, and \alpha…\Omega and operators such as \times, \leq and \sum name symbols – the full list is in dataviz/core/mathtext.mojo. A lone $ is a price and stays plain; write \$ for a literal one beside math. The space an expression needs above and below the line is measured and reserved. A label that does not parse raises at render time, naming the label and the character, rather than drawing a guess.

Args:

  • self (Self)
  • title (String): The chart’s title. Left empty (the default), reserves no layout space for it.
  • subtitle (String): A secondary line shown under the title, independent of whether title is also set.
  • x_title (String): Caption for whatever’s drawn along the bottom edge; raises at render() time on Mark.ARC.
  • y_title (String): Caption for whatever’s drawn along the left edge; raises at render() time on Mark.ARC.
  • description (String): Optional longer SVG <desc> text, drawn nowhere on the chart itself; falls back to subtitle when left empty.

Returns:

Self: Self, for further chaining.

series_name

fn def series_name(var self, name: String) -> Self

Name this layer for render_layers()’s/render_layers_svg()’s per-layer legend: a swatch (this layer’s own Theme.mark_color) plus name, one row per named layer, drawn before any per-point color/color_categories legend a Mark. POINT layer has of its own. A Plot.secondary_axis() layer’s row gets " (right axis)" appended, so the reader knows which axis it reads against.

render_layers()/render_layers_svg() and _render_bar_combo_layers (the Mark.BAR-combo path) only; render()/render_svg() ignore it, since a standalone plot has only one series, nothing for a legend entry to distinguish. Layers with no name draw no row.

See the Cookbook’s own “Layer Legend” recipe (docs/src/ cookbook_recipes/layer_legend.mojo) for a full worked example.

Args:

  • self (Self)
  • name (String): This layer’s label in the per-layer legend. Left empty (the default, via not calling this), the layer draws no legend row.

Returns:

Self: Self, for further chaining.

annotate_line

fn def annotate_line(var self, value: Float64, label: String = "") -> Self

Add a horizontal reference line at value on the y-axis (ECharts’ markLine with a fixed value; no auto-computed average/max/min modes). Each call adds a line. label, when non-empty, draws to the right of the line in Theme.annotation_color; the line spans the full plot width, solid (canvas has no dashed-stroke primitive). A value outside the mark’s padded y-domain draws nothing.

Only meaningful on a mark whose y-axis is a continuous LinearScale, checked at render() time via _RenderResult.has_y_scale: Mark.POINT/LINE/AREA/ EFFECT_SCATTER and every mark sharing _CategoricalFrame (BAR/ LOLLIPOP/WATERFALL/BOX/CANDLESTICK/BULLET/GROUPED_BAR/ STACKED_BAR/STREAMGRAPH). Other marks raise. Also wired into render_facets() (per cell) and render_layers() (per layer, against that layer’s own primary or secondary y-scale).

See the Cookbook’s own “Reference Line” recipe (docs/src/ cookbook_recipes/annotate_line.mojo) for a full worked example.

Args:

  • self (Self)
  • value (Float64): The y-value to draw the line at. Outside the mark’s (padded) y-domain, draws nothing.
  • label (String): Drawn to the right of the line when non-empty; left empty (the default), the line draws with no label.

Returns:

Self: Self, for further chaining – render()/render_svg() raise later if the mark has no genuine continuous y-axis.

annotate_area

fn def annotate_area(var self, y0: Float64, y1: Float64, label: String = "") -> Self

Add a shaded horizontal band from y0 to y1 on the y-axis (ECharts’ markArea with a fixed pair). Each call adds a band. label, when non-empty, draws inside the band near its top edge in Theme.annotation_color. y0/y1 may be given in either order.

A band partially overlapping the mark’s padded y-domain clips to the visible portion; one with no overlap draws nothing. Drawn on top of the mark at Theme.annotation_area_color’s partial opacity, so the mark’s ink shows through.

Same mark support and facets/layers wiring as annotate_line().

See the Cookbook’s own “Reference Band” recipe (docs/src/ cookbook_recipes/annotate_area.mojo) for a full worked example.

Args:

  • self (Self)
  • y0 (Float64): One edge of the band; need not be the lower one.
  • y1 (Float64): The other edge of the band; whichever of y0/y1 is smaller becomes the band’s bottom edge.
  • label (String): Drawn inside the band near its top edge when non-empty; left empty (the default), the band draws with no label.

Returns:

Self: Self, for further chaining – render()/render_svg() raise later if the mark has no genuine continuous y-axis.

annotate_vline

fn def annotate_vline(var self, value: Float64, label: String = "") -> Self

Add a vertical reference line at value on the x-axis: annotate_line()’s mirror image, with the same fixed-value scope, additive behavior, styling, and out-of-domain skip.

Narrower mark support: only Mark.POINT/LINE/AREA/ EFFECT_SCATTER, the marks with a continuous x-axis; a categorical x-axis has no numeric value to place a line against (see _RenderResult). Raises on an unsupported mark. Also wired into render_facets() (per cell) and render_layers() (per layer, against the one shared continuous x-scale every layer uses – raises on a Mark.BAR combo chart’s categorical x-axis instead, same as a standalone unsupported mark).

See the Cookbook’s own “Vertical Reference Line” recipe (docs/ docs/cookbook_recipes/annotate_vline.mojo) for a full worked example.

Args:

  • self (Self)
  • value (Float64): The x-value to draw the line at. Outside the mark’s (padded) x-domain, draws nothing.
  • label (String): Drawn near the line when non-empty; left empty (the default), the line draws with no label.

Returns:

Self: Self, for further chaining – render()/render_svg() raise later if the mark has no genuine continuous x-axis.

annotate_point

fn def annotate_point(var self, x: Float64, y: Float64, label: String = "") -> Self

Add a single labeled point at (x, y) (ECharts’ markPoint with a fixed coordinate): a small filled marker in Theme.annotation_color, with label just above it when non-empty. Each call adds a point.

Needs a continuous coordinate on both axes, so only Mark.POINT/ LINE/AREA/EFFECT_SCATTER support it; raises otherwise. A point outside the padded domain on either axis draws nothing. Also wired into render_facets() (per cell) and render_layers() (per layer, against that layer’s own primary or secondary y-scale and the one shared x-scale).

See the Cookbook’s own “Point Marker” recipe (docs/src/ cookbook_recipes/annotate_point.mojo) for a full worked example.

Args:

  • self (Self)
  • x (Float64): The point’s x-coordinate. Outside the mark’s (padded) x-domain, draws nothing.
  • y (Float64): The point’s y-coordinate. Outside the mark’s (padded) y-domain, draws nothing.
  • label (String): Drawn just above the point when non-empty; left empty (the default), the point draws with no label.

Returns:

Self: Self, for further chaining – render()/render_svg() raise later if the mark has no genuine continuous x/y-axis.

annotate_arrow

fn def annotate_arrow(var self, x: Float64, y: Float64, text: String, text_x: Float64, text_y: Float64) -> Self

Point at (x, y) with an arrow, labeled text placed at (text_x, text_y). Each call adds an arrow.

This is the only annotation that can be placed in empty space, which is what makes it usable on a crowded chart where every other overlay lands on data – annotate_point()’s label sits a fixed gap above its marker and cannot be moved. An arrow is often the whole point of a chart going into a document: it is what turns a plot into an argument.

Both ends are in data coordinates. Everything else in this API is in data space, and a second convention would need explaining every time it appeared. The cost is that a label position has to be chosen against the data’s own range; the benefit is that it stays put when the chart is resized.

Straight arrows only. Curved connectors, head styles and shrink factors are refinements on top of a feature that did not exist; the straight case carries most of the value.

Needs a continuous coordinate on both axes, so only Mark.POINT/ LINE/AREA/EFFECT_SCATTER support it; raises otherwise. An arrow with either end outside the padded domain is skipped whole rather than clipped – half an arrow points at nothing.

Args:

  • self (Self)
  • x (Float64): The target’s x-coordinate, where the head lands.
  • y (Float64): The target’s y-coordinate.
  • text (String): The label, drawn centered at (text_x, text_y). Empty draws the arrow alone.
  • text_x (Float64): The label’s x-coordinate, in data space.
  • text_y (Float64): The label’s y-coordinate, in data space.

Returns:

Self: Self, for further chaining – render()/render_svg() raise later if the mark has no genuine continuous x/y-axis.

annotate_band

fn def annotate_band(var self, x: List[Float64], y_lower: List[Float64], y_upper: List[Float64], label: String = "") -> Self

Shade the region between two curves that vary with x: a confidence band around a trend line, or a min/max envelope. annotate_area()’s band is a constant (y0, y1) pair; this takes two parallel lists keyed by x. Each call adds a band.

x/y_lower/y_upper must be the same length and every y_upper[i] >= y_lower[i], both checked at render() time. x need not be sorted; the top edge traces (x[i], y_upper[i]) in order and the bottom edge walks back in reverse.

Filled in Theme.annotation_area_color with straight edges (no Theme.line_smoothing). label, when non-empty, centers above the band’s middle x-index on its upper edge.

Only Mark.POINT/LINE/AREA/EFFECT_SCATTER, as for annotate_point(). A band is clipped to the overlapping range on both axes; one with no overlap draws nothing. Same facets/layers wiring as annotate_point().

See the Cookbook’s own “Confidence Band” recipe (docs/src/ cookbook_recipes/annotate_band.mojo) for a full worked example.

Args:

  • self (Self)
  • x (List[Float64]): The band’s x column, one entry per (y_lower, y_upper) pair, in the order the edges should trace.
  • y_lower (List[Float64]): The band’s bottom edge, one entry per x.
  • y_upper (List[Float64]): The band’s top edge, one entry per x; every value must be >= y_lower’s value at that same index.
  • label (String): Drawn centered above the band’s middle point when non-empty; left empty (the default), the band draws with no label.

Returns:

Self: Self, for further chaining – render()/render_svg() raise later if the lengths mismatch, an upper/lower value is inverted, or the mark has no genuine continuous x/y-axis.

annotate_best_fit

fn def annotate_best_fit(var self, show_equation: Bool = False, show_r_squared: Bool = False, label: String = "", ci: Float64 = 0.94999999999999996) -> Self

Overlay an ordinary-least-squares best-fit line computed from this plot’s own _continuous.x/_continuous.y at render() time, so it works whether called before or after .encode(). Not additive: the last call wins, since the fit is determined by the data.

Drawn as a solid line in Theme.annotation_color across the mark’s full padded x-domain. show_equation/show_r_squared each add one line of text right-aligned near the plot’s top-right corner; label, when non-empty, draws as a heading above them. R-squared is 1 - SS_res/SS_tot, defined as 1.0 when SS_tot is 0.0.

Only Mark.POINT/LINE/AREA/EFFECT_SCATTER. Raises at render() time with fewer than 2 points or when every x value is identical. Also wired into render_facets() (per cell, fit to that cell’s own data) and render_layers() (per layer, fit to that layer’s own data against its primary or secondary y-scale and the shared x-scale).

See the Cookbook’s own “Best-Fit Trend Line” recipe (docs/src/ cookbook_recipes/best_fit_line.mojo) for a full worked example.

Args:

  • self (Self)
  • show_equation (Bool): Draw the fitted line’s own y = mx + b text when True; False (the default) draws only the line itself.
  • show_r_squared (Bool): Draw the fit’s R-squared text when True; False (the default) omits it.
  • label (String): An optional heading drawn above the equation/ R-squared text; left empty (the default), no heading draws (independent of show_equation/show_r_ squared – a label with both left False still draws only the line, no text at all).
  • ci (Float64): Two-sided confidence level for a band around the fitted line. 0.95 (the default) shades the 95% confidence interval of the fitted mean at each x; pass 0.0 for the bare line. The band is narrowest at the mean of x and flares toward the ends, which is the point of drawing it: a line without one invites the reader to trust the slope more than the data supports (#352). Levels carried: 0.90, 0.95, 0.99. A band needs three points – with two, the line passes through both and there is no residual error to size it from – so with fewer the line draws alone. Drawn in Theme.annotation_area_color, under the line.

Returns:

Self

annotate_smooth

fn def annotate_smooth(var self, method: SmoothMethod = SmoothMethod.LOESS, span: Float64 = 0.75, degree: Int = Int(2)) -> Self

Overlay a curved trend line fitted to this plot’s own x/y data (#147) – annotate_best_fit()’s straight line, for data a straight line does not describe.

  • SmoothMethod.LOESS (the default): locally weighted regression. At each x, a polynomial of degree (1 or 2) is fitted to the nearest span share of the points, weighted by the tricube of their distance, and evaluated there. span sets how smooth: smaller follows the data more closely. The defaults, span=0.75 and degree=2, are the conventional ones for local regression.
  • SmoothMethod.POLYNOMIAL: one least-squares polynomial of degree over every point; span is unused.

Drawn across the data’s own x range, in annotate_best_fit()’s color and line style, clipped to the plot area. Unlike annotate_best_fit() there is no confidence band, equation or R-squared: those are defined for the straight-line fit.

Args:

  • self (Self)
  • method (SmoothMethod): SmoothMethod.LOESS or SmoothMethod.POLYNOMIAL.
  • span (Float64): LOESS’s window as a share of the points, in (0, 1].
  • degree (Int): LOESS’s local degree (1 or 2), or the polynomial’s.

Returns:

Self: Self, for further chaining – render()/render_svg() raise later if the mark has no continuous x/y axes, or the data cannot support the fit (too few points or distinct x values for the degree, or a span outside (0, 1]).

scale_y_log

fn def scale_y_log(var self) -> Self

Scale the y-axis logarithmically (base 10). Every y value, and every y-axis annotation value, must be strictly positive; render()/ render_svg() raise otherwise (see _log_data_extent()).

Mark.POINT/LINE/EFFECT_SCATTER only, and standalone render()/ render_svg() only: a categorical mark has no continuous y-domain, Mark.AREA’s y-domain is forced through zero (which has no logarithm), and render_layers() combines layers into one linear scale. Every tick/gridline/point/annotation still goes through LinearScale.to_pixel() with real-unit values; see that method.

See the Cookbook’s own “Log Scale (Y-Axis)” recipe (docs/src/ cookbook_recipes/log_scale_y.mojo) for a full worked example.

Args:

  • self (Self)

Returns:

Self: Self, for further chaining.

scale_x_log

fn def scale_x_log(var self) -> Self

scale_y_log()’s x-axis mirror. Mark.POINT/LINE/AREA/ EFFECT_SCATTER (x is never forced through zero, so AREA is allowed here), standalone render()/render_svg() only.

See the Cookbook’s own “Log Scale (X-Axis)” recipe (docs/src/ cookbook_recipes/log_scale_x.mojo) for a full worked example.

Args:

  • self (Self)

Returns:

Self: Self, for further chaining.

scale_y_symlog

fn def scale_y_symlog(var self, linthresh: Float64 = 1) -> Self

Scale the y-axis symmetrically logarithmically: linear within [-linthresh, linthresh], logarithmic beyond it, continuous where they meet (#368).

What scale_y_log() cannot do. A log axis needs strictly positive values, so a series that crosses zero – a temperature anomaly, a profit and loss, a residual – cannot go on one at all; and a linear axis collapses every small value against the largest. Symlog keeps zero, keeps the sign, and still resolves several orders of magnitude on each side.

linthresh is the half-width of the linear region in data units, and must be positive. It sets what counts as “near zero”: values inside it are laid out linearly, so the noise floor of the measurement is usually the right choice. The linear region takes exactly as much axis as one decade of the logarithmic region, which is the only ratio that makes the two halves comparable without a second knob.

Mark.POINT/LINE/AREA/EFFECT_SCATTER, standalone render()/render_svg() only, the same scope scale_y_log() has. Mutually exclusive with scale_y_log(); setting both raises at render time rather than silently preferring one.

Args:

  • self (Self)
  • linthresh (Float64): Half-width of the linear region around zero, in data units. Must be positive.

Returns:

Self: Self, for further chaining.

scale_x_symlog

fn def scale_x_symlog(var self, linthresh: Float64 = 1) -> Self

scale_y_symlog()’s x-axis mirror, with the same scope and the same linthresh meaning (#368).

Args:

  • self (Self)
  • linthresh (Float64): Half-width of the linear region around zero, in data units. Must be positive.

Returns:

Self: Self, for further chaining.

scale_x_domain

fn def scale_x_domain(var self, min: Float64, max: Float64) -> Self

Pin the x-axis domain to the given minimum and maximum, replacing _data_extent()’s 5%-padded domain (or _log_data_extent()’s when scale_x_log() is also set). For comparable charts across runs, a fixed reference range, or a zoomed-in view, without padding the data with fake points.

Mark.POINT/LINE/AREA/EFFECT_SCATTER only (the same marks _render_generic’s continuous path draws), standalone render()/ render_svg() and render_facets() only – render_layers() raises if any layer sets this, since a shared axis across several layers needs one shared answer, not one per layer; the categorical marks (Mark.BAR, LOLLIPOP, …) raise too, left for a supported. render_facets() applies this per cell, so every cell sharing the same override reads as one shared domain – the facets counterpart to shared_y_scale=True for the x-axis (which has no shared_y_scale equivalent otherwise).

A point outside the domain still computes a real (off-plot) pixel position via LinearScale.to_pixel(), same as it would if it merely fell outside a padded auto-computed domain; the SVG viewBox’s own default overflow: hidden clips it at the canvas edge, and the raster Canvas’s own pixel buffer bounds-checks every draw call, so nothing paints outside the plot in either backend.

Args:

  • self (Self)
  • min (Float64): The domain’s lower bound. For a log x-axis (scale_x_log()), must be > 0.
  • max (Float64): The domain’s upper bound; must be > min.

Returns:

Self: Self, for further chaining – render()/render_svg() raise later if min >= max, the mark doesn’t support this, or (with scale_x_log()) min <= 0.

scale_y_domain

fn def scale_y_domain(var self, min: Float64, max: Float64) -> Self

scale_x_domain()’s y-axis mirror – see that method’s own docstring for the shared rules. Overrides _zero_baseline_y_ extent()’s forced-zero domain on Mark.AREA too: an explicit The explicit domain is used exactly, zero baseline or not.

Args:

  • self (Self)
  • min (Float64): The domain’s lower bound. For a log y-axis (scale_y_log()), must be > 0.
  • max (Float64): The domain’s upper bound; must be > min.

Returns:

Self: Self, for further chaining – render()/render_svg() raise later if min >= max, the mark doesn’t support this, or (with scale_y_log()) min <= 0.

scale_x_ticks

fn def scale_x_ticks(var self, values: List[Float64], labels: List[String] = List()) -> Self

Put the x-axis major ticks exactly at values, instead of at the 1-2-5 positions the axis would choose (#368).

For an axis whose meaningful positions are not round numbers: a threshold, a target, the two dates a study ran between. The gridline, the tick mark and the label all move together, because they are one tick.

labels replaces the formatted numbers when given, one per position, which is how an axis reads Q1 Q2 Q3 Q4 over values that are really 1 2 3 4. Left empty, the positions are formatted the way computed ticks are, at one decimal count for the whole set.

A position outside the axis domain is dropped rather than drawn: its pixel would fall outside the plot rect and its label would print in the margin beside nothing. scale_x_domain() is what moves the domain; this only says where the ticks go inside it.

Minor ticks are not derived from an explicit set, since the caller said where the ticks belong and a subdivision of an irregular set has no meaning.

Args:

  • self (Self)
  • values (List[Float64]): Tick positions, in the axis’s own units.
  • labels (List[String]): One label per position, or empty to format the positions.

Returns:

Self: Self, for further chaining – render()/render_svg() raise later if the list is empty, a label count does not match, a position is not finite, a position is not positive on a log axis, or the mark does not support this.

scale_y_ticks

fn def scale_y_ticks(var self, values: List[Float64], labels: List[String] = List()) -> Self

scale_x_ticks()’s y-axis mirror – see that method’s docstring for the shared rules.

The y labels are what the left margin is measured from, so an explicit set widens or narrows the plot rect to fit itself, exactly as computed labels do.

Args:

  • self (Self)
  • values (List[Float64]): Tick positions, in the axis’s own units.
  • labels (List[String]): One label per position, or empty to format the positions.

Returns:

Self: Self, for further chaining – see scale_x_ticks().

scale_x_reverse

fn def scale_x_reverse(var self) -> Self

Run the x-axis right to left: the domain’s low end lands on the plot rect’s right edge (#368).

For a quantity that reads better descending – a rank where 1 is best, a countdown, a depth below a surface. Only the two pixel positions swap. The domain stays ascending, so the ticks are the same ticks in the same order and every mark keeps handing the scale the same values; what changes is where they land.

Args:

  • self (Self)

Returns:

Self: Self, for further chaining – render()/render_svg() raise later if the mark does not support this.

scale_y_reverse

fn def scale_y_reverse(var self) -> Self

Run the y-axis top to bottom: the domain’s low end lands on the plot rect’s top edge (#368).

scale_x_reverse()’s mirror. On Mark.IMSHOW, whose y-axis already counts downward so that row 0 is at the top, this composes rather than competes: it puts row 0 back at the bottom.

Args:

  • self (Self)

Returns:

Self: Self, for further chaining – see scale_x_reverse().

equal_aspect

fn def equal_aspect(var self) -> Self

Give one data unit the same pixel length on both axes (#368).

For anything whose two axes are the same kind of quantity, where the shape of what is drawn is part of what it says: a map, a circle that has to look round, a residual plot read against the 45-degree line.

The plot rect shrinks; the domains do not grow. With equal aspect something has to give, and the two choices are showing a wider range than the caller asked for or leaving part of the figure empty. This leaves space: the rect keeps the aspect the data implies and centers itself in the room it had. Nothing is drawn outside the data’s own range, and the axis labels, ticks and margins are the ones measured for the domains as given.

Not available on a log or time axis, where a “data unit” is not a constant length: raises at render() time rather than claiming a guarantee it cannot keep.

Args:

  • self (Self)

Returns:

Self: Self, for further chaining – render()/render_svg() raise later on a log or time axis, or if the mark does not support this.

scale_color_domain

fn def scale_color_domain(var self, min: Float64, max: Float64) -> Self

Pin the continuous color domain to the given minimum and maximum, replacing the [min, max] this mark would otherwise take from its own colored values. scale_x_domain()’s color counterpart, and the thing that makes two color-encoded charts comparable.

Without it every continuous-color mark derives its own limits, so two panels of the same quantity are drawn against two different scales and look identical while meaning different things – the reader has no way to see the difference, because the only place it shows is in two legends whose numbers nobody cross-checks. shared_color_domain() computes one domain across several charts’ data to pass here; it is the counterpart to shared_bin_edges().

Applies to every mark that colors by a continuous value: Mark.HEATMAP, CALENDAR_HEATMAP, CORRPLOT, IMSHOW, PCOLORMESH, HIST2D, HEXBIN, CONTOUR, CONTOURF, TRICONTOUR, TRICONTOURF, TRIPCOLOR, QUIVER, STREAMPLOT, and Plot.encode(color=...)’s continuous channel on Mark.POINT/SINGLE_AXIS/EFFECT_SCATTER. Anything else raises rather than accepting a setting it would ignore. Mark.BOXENPLOT is not on the list even though it builds a ColorScale: its ramp runs over the depth of the letter-value nest, not over a data value, so an explicit data domain has nothing to say about it.

Values outside the domain are not dropped; they clamp to the ramp’s end color, because GradientStops.color_at() clamps t outside [0, 1]. A separate under/over color would say more, and is left for its own change (see the module note on _color_scale_for).

The color legend follows automatically: every mark hands the legend the same ColorScale it colored with, so the labels read the overridden domain, not the data’s.

Args:

  • self (Self)
  • min (Float64): The value the ramp’s low end means.
  • max (Float64): The value the ramp’s high end means; must be > min.

Returns:

Self: Self, for further chaining – the render raises later if min >= max or the mark has no continuous color channel.

scale_color_thresholds

fn def scale_color_thresholds(var self, boundaries: List[Float64]) -> Self

Color in discrete bands instead of a continuous ramp (#370).

n boundaries make n - 1 bands, and every value in a band gets one flat color. That is what a reader needs when the question is “which category is this” rather than “how much”: soil pH bands, risk tiers, a legend with named ranges.

Intervals are lower-inclusive, [b[i], b[i+1]), with the last closed at the top so the domain maximum has somewhere to go. A value exactly on an interior boundary belongs to the band above it. That is the one place this is easy to get wrong, so it is stated here and tested.

Values below the first boundary take the lowest band and values above the last take the highest, rather than raising. Clipping rather than rejecting keeps a shared threshold list usable across facets whose data ranges differ.

Boundaries must be strictly increasing, checked at render time along with the rest of the color domain.

Not combinable with scale_color_log() or scale_color_center(): thresholds already say where every band starts, so there is nothing left for either to place.

Args:

  • self (Self)
  • boundaries (List[Float64]): The band edges, at least two, strictly increasing.

Returns:

Self: Self, for further chaining.

Raises:

scale_color_under

fn def scale_color_under(var self, color: Color) -> Self

Color values below the color domain with color instead of the ramp’s low end (#370).

Without it an out-of-range value clamps: a reading of -40 on a domain starting at 0 is painted the same as a reading of 0, and the chart says the two are alike. A distinct color says “this is off the scale”, which is a different statement and usually the one that matters – a sensor out of range, a region with no data of its own, a value the domain was deliberately narrowed to exclude.

Applies to every mark scale_color_domain() applies to, and to every form of the ramp: continuous, logarithmic and banded. With scale_color_thresholds() the band edges are the range, so “below” means below the first boundary.

The color legend shows it, as a block at the low end of the bar inside the bar’s own footprint, so the legend costs exactly the room it did before and its end labels stay attached to the ends of the ramp, where those numbers are true.

Args:

  • self (Self)
  • color (Color): The color for a value below the domain.

Returns:

Self: Self, for further chaining.

scale_color_over

fn def scale_color_over(var self, color: Color) -> Self

Color values above the color domain with color instead of the ramp’s high end – scale_color_under()’s mirror, and see that method’s docstring for the shared rules (#370).

Values exactly at the domain maximum belong to the ramp, not to the over color: the top end is part of the range, which is the rule the last threshold band already follows.

Args:

  • self (Self)
  • color (Color): The color for a value above the domain.

Returns:

Self: Self, for further chaining.

scale_color_log

fn def scale_color_log(var self) -> Self

Normalize color by log10 instead of linearly (#370).

A linear ramp cannot resolve values spread over several orders of magnitude: on a 1-to-10,000 domain everything below 1,000 lands in the first tenth of the ramp and reads as one color. With this, equal ratios get equal color distance, so 1 to 10 spans as much of the ramp as 1,000 to 10,000.

The domain must be strictly positive, whether it came from the data or from scale_color_domain(). That is checked at render time, because until then the mark’s own limits are not known.

Not combinable with scale_color_center(): centering places the neutral color by linear distance from each end, which a log domain does not preserve.

Args:

  • self (Self)

Returns:

Self: Self, for further chaining.

Raises:

scale_color_center

fn def scale_color_center(var self, center: Float64) -> Self

Pin the middle of the color ramp to center, so a diverging ramp’s neutral color lands on a value that means something – zero, a baseline, a target – instead of on the numeric midpoint of whatever the data happened to span.

A diverging ramp’s whole claim is that its middle is neutral and its two ends are opposite. Placed at the data’s midpoint that claim is simply false: over values from -2 to +10 the neutral color sits at +4, so half the positive range is painted in the color that is supposed to mean “negative”. Centering fixes the mapping rather than the data, and the two arms are then free to be different sizes – which is the honest picture when the data is not symmetric.

Separate from scale_color_domain() on purpose. Centering is about where the ramp’s middle goes, not about what its ends mean, and the two are wanted independently: a center on its own re-places the middle inside the data’s own limits, which is the common case, while a domain on its own leaves the ramp symmetric. Folding both into one call would have forced every caller who wants a centered ramp to also state limits they had no opinion about.

center must lie strictly inside the resolved color domain. It is not quietly widened to fit: widening would move the ramp’s ends, so a chart asking only “center this at zero” would get different end colors than the one beside it, which is the silent disagreement the whole feature exists to remove. The render raises and names the domain instead, so the fix is an explicit scale_color_domain().

This is reached by moving the ramp’s stops instead of bending the value projection; see ColorScale.from_theme_centered() for why that route is the one that keeps the legend honest.

Defined for any ramp, not only a three-stop diverging one – it means “the color at offset 0.5 lands on center” whatever the stops are. On a sequential ramp like colormaps.viridis() that is legal but rarely useful.

Args:

  • self (Self)
  • center (Float64): The value the ramp’s middle color sits on. Must be strictly between the color domain’s min and max.

Returns:

Self: Self, for further chaining – the render raises later if center is not strictly inside the domain, or the mark has no continuous color channel.

secondary_axis

fn def secondary_axis(var self) -> Self

Draw this layer’s y values against a second, independent y-domain on the plot’s right edge instead of render_layers()’s shared left-axis domain (ECharts’ yAxisIndex: 1, as a boolean since only two y-axes are ever drawn). For a combo chart whose series have different units; see _render_layers_generic.

render_layers()/render_layers_svg() only; render()/ render_svg() raise if a plot with this set reaches them. At least one layer must stay on the primary axis. The secondary axis gets an axis line, ticks, and tick labels on the right edge but no gridlines. This layer’s Plot.labels() y_title captions the secondary axis (see _secondary_axis_y_title).

See the Cookbook’s own “Dual Y-Axis” recipe (docs/src/ cookbook_recipes/dual_axis.mojo) for a full worked example.

Args:

  • self (Self)

Returns:

Self: Self, for further chaining.

erased

fn def erased(self) -> Self

Args:

  • self (Self)

Returns:

Self

erased_with

fn def erased_with[canvas: Bool = False, svg: Bool = False, pdf: Bool = False, bounds: Bool = False, display_list: Bool = False](self) -> Self

A copy with the slots this chart was erased with: the mark type is gone, so no slot can be bound here.

Parameters:

  • canvas (Bool)
  • svg (Bool)
  • pdf (Bool)
  • bounds (Bool)
  • display_list (Bool)

Args:

  • self (Self)

Returns:

Self

capabilities

fn def capabilities(self) -> _Capabilities

Args:

  • self (Self)

Returns:

_Capabilities

x_data

fn def x_data(self) -> List[Float64]

Args:

  • self (Self)

Returns:

List[Float64]

y_data

fn def y_data(self) -> List[Float64]

Args:

  • self (Self)

Returns:

List[Float64]

render_mark

fn def render_mark[T: DrawTarget](self, mut target: T, ox0: Int, oy0: Int, ox1: Int, oy1: Int, has_shared_y_domain: Bool, shared_y_min: Float64, shared_y_max: Float64, shared_y_is_log: Bool, *, mut cache: FontCache, vector_target: Bool) -> _RenderResult

Parameters:

  • T (DrawTarget)

Args:

  • self (Self)
  • target (T)
  • ox0 (Int)
  • oy0 (Int)
  • ox1 (Int)
  • oy1 (Int)
  • has_shared_y_domain (Bool)
  • shared_y_min (Float64)
  • shared_y_max (Float64)
  • shared_y_is_log (Bool)
  • cache (FontCache)
  • vector_target (Bool)

Returns:

_RenderResult

Raises: