github ↗
docs menu

Module Hugin.Mark

Marks.

Mark.v makes a mark from channel bindings and a draw function. Nothing is read when a mark is made: draw reads each binding's data, reduced where it lives, and calls the draw function once for each panel the mark has a row in, with that panel's rows.

Bindings

#type binding

The type for channels bound to roles.

#val bind : ?imply:'d Scale.t -> ?guide:bool -> ?stack:Stack.offset -> ('d, 'r) Role.t -> ('d, 'r) channel -> binding

bind ~imply ~guide ~stack r c binds the channel c to the role r, where:

  • imply is the specification whose properties the mark implies on the scale c reads (Merging), of the kind of c's data; its name and transform are ignored, and so is imply if c is a constant. rect and rule bind a length with ~imply:(Scale.linear ~zero:true ()), and rect the band position of a bar with ~imply:(Scale.band ~padding:0.2 ()).
  • guide implies whether the scale c reads has a guide, an axis or a legend: true that it has one, false that it has none. It merges as the scale's properties do (Merging): two marks implying different values raise in resolve, and an explicit axis or legend overrides both. Unset, it implies nothing.
  • stack makes c, quantities bound to x or y, a stacked length. In each panel the rows that share their value of the mark's other position, y for x and x for y, and their facet categories form a column, one per panel if the other position is unbound, and the lengths of a column are laid end to end in the order of their rows by Stack.intervals with the offset stack: those that are not negative upward from 0., the negative ones downward. A dropped row (Missing values) stacks nothing, and a row whose end its scale cannot place is dropped. A start it cannot place, such as the zero a pile starts from on a log scale, is clamped into the domain, as a length's zero is. The scale c reads is fitted to the ends of the rows and the starts it places, extent gives each row its interval, and get, normalized and positions its end. Unset, c is not stacked.

Raises Invalid_argument if stack is given and r is neither x nor y or c holds no quantities.

Rows

The rows of a mark in one panel are those its facet channels put there, in the order of their data. A dropped row (Missing values) keeps its place: positions, points and extent put it at nan, which Picture.stamp, Curve.path and Path skip or break at, while index gives its datum and get and normalized its own values, missing only where they are missing.

The domain of a panel is the unit square of normalised positions, its edges included. A draw function clips positions to it and leaves ink whole (Missing values): it joins positions into paths and gives them to project, which cuts them at the domain's edges, and places symbols and texts at points, which are nan outside it. Ink is clipped only where it has no geometry to cut, as image clips an image to the domain. Rows are valid only during the call of the function given them.

#type rows

The type for the rows of a mark in one panel.

#val id : rows -> id

id rows is the id of the mark, or of the legend in a swatch (v).

#val length : rows -> int

length rows is the number of rows of rows.

#val shape : rows -> int array

shape rows is the shape of the mark (Data and rows).

#val index : rows -> int array

index rows is the datum of each row, as its index in the mark's shape flattened in row-major order. Rows keep their data's indices through reductions.

#val get : rows -> ('d, 'r) Role.t -> 'r array option

get rows r is Some vs, the value of each row for the role r in its range, positions normalised and a constant repeated, or None if the mark binds no channel to r. A missing value is the unknown colour of its scale for a colour, nan for a float, the first symbol of its scale for a symbol, the first dash pattern of its scale for a dash and the empty text for a text. The array is fresh.

#val normalized : rows -> ('d, 'r) Role.t -> float array option

normalized rows r is Some us, the value of each row for the role r normalised by its scale, before the role's range and map_range, or None if r is unbound, bound to a constant or reads no scale. The array is fresh.

#val range : rows -> ('d, 'r) Role.t -> (float -> 'r) option

range rows r is Some f if r is bound to a channel that reads a scale, and None if it is unbound, bound to a constant or reads no scale. f u is the value of r at the normalised value u, mapped by the channel's map_range: on a continuous scale, the value the range of r gives u (Roles), as the scale's specification or else the theme sets it, and u itself for a position; on a band scale, the value of the category whose step holds u (Scale.invert). For nan, and on a band scale for a u that no step holds, it is the value get gives a missing value. With range rows r = Some f, get rows r is Option.map (Array.map f) (normalized rows r).

#val scale : rows -> [ `X | `Y ] -> 'd Scale.kind -> 'd Scale.t option

scale rows `X k is Some s, the fitted scale (Resolved.scale) that the channels on x of the panel read, if it has kind k, whether or not the mark binds x, and None if they read none or one of another kind, as in a swatch. scale rows `Y k is likewise on y.

#val ticks : rows -> ('d, 'r) Role.t -> float array option

ticks rows r is Some us, the normalised positions of the major ticks that layout froze for the scale r reads, in increasing order, whether or not a guide shows them, or None if r is unbound, bound to a constant or reads no scale. On a reversed scale the ticks' values decrease along us. The array is fresh.

#val positions : rows -> float array * float array

positions rows is (us, vs), row i being at the normalised position (us.(i), vs.(i)): its normalised x and y, 0.5 for a position the mark does not bind, the centre of its band on a band scale and the end of its interval for a stacked position (bind). A dropped row is at (nan, nan), and a position outside the domain is kept. The arrays are fresh.

#val points : rows -> float array * float array

points rows is (xs, ys), row i being at (xs.(i), ys.(i)) in points on the page: its positions mapped by the panel's projection. A dropped row and a row whose position lies outside the domain are at (nan, nan). The arrays are fresh.

#val extent : rows -> [ `X | `Y ] -> float array * float array

extent rows `X is (a, b), the normalised interval each row covers along x, from a.(i) to b.(i) in either order:

  • if the mark binds x2, the hull of the extents of x and x2, each its band on a band scale and its value otherwise;
  • if x is stacked (bind), the interval its stack gives the row, from its start to its end;
  • otherwise, if x reads a band scale, its band;
  • otherwise, if x reads a quantitative scale, a length: from the normalised value of 0. clamped into the domain, which is the domain's lower end on a log scale, to x;
  • for x on a temporal scale and for a constant x, from x to x;
  • if the mark binds no x, from 0. to 1..

Ends outside the domain are kept. A dropped row covers (nan, nan). extent rows `Y is likewise along y, with y and y2. The arrays are fresh.

#val projection : rows -> Coord.projection

projection rows is the panel's projection, which puts the normalised position (x, y) at Coord.point (projection rows) x y on the page.

#val project : rows -> Path.t -> Path.t

project rows p is p, given in the panel's normalised positions, cut at the domain's edges (Path.crop) and mapped into points on the page by the panel's projection: an open subpath keeps its pieces within the domain, and a closed one the part of its region there, so fill a path closed. Where the projection is not affine, segments are subdivided so that the path follows it. Under Coord.cartesian the cut path is mapped by one affine map (Path.transform).

#val series : rows -> rows list

series rows is rows split into series: two rows are in one series iff they have the same index along every axis of the mark's shape but the last and the same category in every channel that reads a band scale and is bound to a role other than x, x2, y and y2. A missing category counts as one category of its own. Series are in the order of their first rows and keep their rows in order.

#val theme : rows -> Theme.t

theme rows is the theme of the layout being drawn.

#val text : ?halign:Text.Layout.halign -> ?valign:Text.Layout.valign -> rows -> Color.t -> P2.t -> Text.t -> Picture.t

text ~halign ~valign rows c pt s draws s set in the faces of the theme at its base size, aligned about the point pt of the page by halign and valign (Alignment), `Center and `Middle by default, its characters without a colour of their own painted with c. A character that no face has is drawn as its face's .notdef glyph, with a warning (warn).

#val warn : rows -> string -> unit

warn rows msg adds the warning msg, about the mark's data, to the warnings of the drawing (Drawing.warnings), under the mark's id.

Reducers

A reducer draws a mark's rows in its stead when they are many for the density draw is given. m4 and cells choose rows where the data lives, before they are read; raster reads the rows and paints them once. A reducer applies only to the marks that name it (v), and draws what the mark draws from every row, within what each states.

Every mark's picture is then gathered: an image outside a transform or a stamp (Picture.image) with more than 4 cells per device pixel along an axis is replaced by the cells that raster output samples at the density, gathered where they live, painted over the device pixels its box reaches. It draws what the image draws.

#type reducer

The type for reducers.

#val m4 : reducer

m4 keeps, of the series (series) whose channels other than their positions and facets are constant along them, when they have more than four rows per device-pixel column of the panel and per run, the first, last, lowest and highest rows of each run of consecutive rows whose x lies in one column, and the first row of each run of dropped rows. Columns are the device pixels at the density draw is given, cut at the ends of the x domain, where the path is cut; the rows beyond the panel's pixels make one column on each side. The domain's ends lie in it, and a row on another edge of a column lies in the column after. Only the rows in the panel make runs: the rows of a series that its facets put in other panels neither join nor split them. It applies when x and y read continuous scales, the projection is affine and the mark binds no dash other than const Dash.solid.

A mark naming it strokes each series as a path through its points, whose piece between two consecutive points depends on those two points alone and stays within their bounding box, and that breaks at dropped rows. The reduced path then inks the pixels the whole path inks, within one pixel; the coverage of a pixel may differ for a line thinner than a column.

#val cells : reducer

cells draws a panel's rows as one image whose pixels are their cells, when the projection is affine, x and y read band scales without padding, the mark binds no x2, y2 or stroke, and no two rows share a cell; a cell no row covers paints nothing. Past 4 cells per device pixel along either axis, only the cells that raster output samples are gathered and read. A mark naming it fills the rectangle of each row's extents (extent) with its fill at its opacity and draws nothing else, so the image is exactly its drawing.

#val raster : reducer

raster draws a panel's picture of the mark as one image painted by the raster renderer at the density, over the device pixels the picture reaches (Picture.bounds), when the mark has more than 20,000 rows in the panel or more rows than the panel has device pixels, whatever the output. Rows are painted in their order, so the image is what the picture paints, each component rounded to a level. Composited onto a raster page, which rounds again, it is within one level of the picture drawn there directly.

Making marks

#val broadcast : ?shape:int array -> binding list -> int array

broadcast ~shape bindings is the shape of the mark that v ~shape bindings makes: shape, by default [||], and the shapes of the channels broadcast together (Data and rows).

Raises Invalid_argument if shape has a negative dimension, if the channels and shape do not broadcast, or if a Hugin.dim or a Hugin.index does not fit their shape.

#val v : name:string -> ?reduce:reducer -> ?coord:Coord.t -> ?shape:int array -> ?swatch:(rows -> Picture.t) -> binding list -> (rows -> Picture.t) -> t

v ~name ~reduce ~coord ~shape ~swatch bindings draw is the mark name of the channels bindings, where draw rows is its picture in a panel for that panel's rows, in points on the page. draw applies every role it binds, opacity included, and clips positions to the domain (Rows). draw only selects each panel's rows, gathers the picture's large images (Reducers) and tags the picture with the mark's id and rows (Picture.tag), instance by instance if it is a stamp of one instance per row: it clips nothing, so ink drawn from a row inside the domain shows whole. Other arguments are:

  • name, the kind of mark in messages and printed forms, such as "dot" or "fehu.cart".
  • reduce, the reducer that may draw the mark's rows. Unset, rows are never reduced.
  • coord, the coordinate system the mark implies for the panels it is drawn in, which a coord above it overrides.
  • shape, the shape of the mark's data where its channels do not state it, such as the datum axes of image. It broadcasts with the shapes of the channels (Data and rows). Defaults to [||].
  • swatch, the picture of a legend entry. It is called with one row in a box one em square, onto which the projection maps the unit square, and in which the roles that read the legend's scale take the entry's value, constants keep theirs, colour roles bound to other data take the ink at half its opacity, and other roles are unbound, so that its point is the box's centre (points) and its extents span the box (extent). For the entry k of a legend of n entries, the rows have the shape [|n|], the index [|k|] and the legend's id (Ids), with which draw tags the swatch. Unset, it is draw.

Raises Invalid_argument if:

  • two bindings bind one role or roles of one name;
  • x2 is bound without x or y2 without y;
  • x and x2, or y and y2, are bound to one channel of quantities and one of categories;
  • x is stacked and x2 bound, y is stacked and y2 bound, or both x and y are stacked (bind);
  • a channel with a scale or a title is bound to a role that reads no scale, text or a Role.value;
  • shape has a negative dimension;
  • the channels and shape do not broadcast, or a Hugin.dim or a Hugin.index does not fit their shape (Data and rows).