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 bindingThe type for channels bound to roles.
val bind :
?imply:'d Scale.t ->
?guide:bool ->
?stack:Stack.offset ->
('d, 'r) Role.t ->
('d, 'r) channel ->
bindingbind ~imply ~guide ~stack r c binds the channel c to the role r, where:
implyis the specification whose properties the mark implies on the scalecreads (Merging), of the kind ofc's data; its name and transform are ignored, and so isimplyifcis a constant.rectandrulebind a length with~imply:(Scale.linear ~zero:true ()), andrectthe band position of a bar with~imply:(Scale.band ~padding:0.2 ()).guideimplies whether the scalecreads has a guide, an axis or a legend:truethat it has one,falsethat it has none. It merges as the scale's properties do (Merging): two marks implying different values raise inresolve, and an explicitaxisorlegendoverrides both. Unset, it implies nothing.stackmakesc, quantities bound toxory, a stacked length. In each panel the rows that share their value of the mark's other position,yforxandxfory, 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 byStack.intervalswith the offsetstack: those that are not negative upward from0., 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 scalecreads is fitted to the ends of the rows and the starts it places,extentgives each row its interval, andget,normalizedandpositionsits end. Unset,cis 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 rowsThe type for the rows of a mark in one panel.
shape rows is the shape of the mark (Data and rows).
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.
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.
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.
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 optionscale 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.
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.
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.
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.
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 ofxandx2, each its band on a band scale and its value otherwise; - if
xis stacked (bind), the interval its stack gives the row, from its start to its end; - otherwise, if
xreads a band scale, its band; - otherwise, if
xreads a quantitative scale, a length: from the normalised value of0.clamped into the domain, which is the domain's lower end on a log scale, tox; - for
xon a temporal scale and for a constantx, fromxtox; - if the mark binds no
x, from0.to1..
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.projectionprojection rows is the panel's projection, which puts the normalised position (x, y) at Coord.point (projection rows) x y on the page.
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).
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 text :
?halign:Text.Layout.halign ->
?valign:Text.Layout.valign ->
rows ->
Color.t ->
P2.t ->
Text.t ->
Picture.ttext ~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).
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 reducerThe type for reducers.
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.
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.
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
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) ->
tv ~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 acoordabove it overrides.shape, the shape of the mark's data where its channels do not state it, such as the datum axes ofimage. 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 entrykof a legend ofnentries, the rows have the shape[|n|], the index[|k|]and the legend's id (Ids), with whichdrawtags the swatch. Unset, it isdraw.
Raises Invalid_argument if:
- two bindings bind one role or roles of one name;
x2is bound withoutxory2withouty;xandx2, oryandy2, are bound to one channel of quantities and one of categories;xis stacked andx2bound,yis stacked andy2bound, or bothxandyare stacked (bind);- a channel with a
scaleor atitleis bound to a role that reads no scale,textor aRole.value; shapehas a negative dimension;- the channels and
shapedo not broadcast, or aHugin.dimor aHugin.indexdoes not fit their shape (Data and rows).