Tabulated() — documenting scene files / images

the purpose of the 'tabulated' include file is to provide a macro for documenting key parameters, data, or other information inside rendered images, in formatted tables. achieving a consistent "look and feel" across a series of images, for instance when illustrating an object collection, as shown in one of the examples, becomes very easy.

the Tabulated() macro uses a dictionary for its input, and there are a good number of optional keys to "tweak" the appearance and layout of tables. only two keys, however, are actually required to create a table – one to provide the data to display, the second to define the types of values stored in the column(s).

each table is composed of two types of "cell", labels and data, which form the columns and rows, and an overlaying frame/grid. labels are optional, as is the table caption, though tables are rarely useful without. data may be of any of POV-Ray's basic types, that is, booleans and strings, floats and integers, and vectors, can be specified. tables are aligned with their top-left corner at origin (approximately), extending in to +x and -y, are less than half of one POV-Ray unit deep, and are created with a no_shadow modifier. table dimensions are calculated from the space required by the various items, in relation to the respective font's largest glyph. labels, caption, and data each use a different TrueType font, at slightly different, fixed, scales.


  // counts {boxes, cylinders, spheres}
  #declare D = dictionary {
    .DataColumns: array [1] {"I"},
    .DataTable  : array [3] {23,8,17},
    .RowLabels  : array [3] {"b","c","s"}  
  };

  object {
    Tabulated(D)
    scale     some_factor
    translate some_place
  }

for example, say you're experimenting with some complex shape made up from many, still changing numbers of, boxes, spheres, and cylinders. the snippet shows how to make a quick, basic table to document the numbers used in a given "try".

the Scene Description Language (SDL) code is written for the Persistence of Vision Raytracer version 3.8, or later[1].

shortcomings – a couple of known "issues"/bugs: a caption cannot be wider than the table, an error (with message) is thrown; if all labels are empty strings, the boxes for the column names are still shown. the macro, as is, does not provide a means to emphasise, or highlight, individual items, columns, and or rows. both external and internal border lines are drawn with the same (cylinder) radius, currently. while some of this stuff may get done, I am thinking about a different design altogether, a shift away from the current, column-driven approach, to "cell centric", for the next version. a design which will allow column groups, row and or column spanning cells, wrapping long text/string data into multiple lines, images in cells, etc.


the interface

seen from the user's perspective, the include file provides three globally "visible" identifiers: one macro and two variables. there are however others and all visible identifiers, except Tabulated() and the variables, are prefixed 'tabu_'.

variables Tabulated_{min,max}_extent

these variables provide, for convenience, the overall dimensions of the table in 3-vectors, as returned by min_extent() and max_extent() calls. both variables are (re-)created every time Tabulated() runs.

macro Tabulated(dict)

the Tabulated() macro takes a single 'dictionary {}' argument, and returns ("expands to") an 'union {}' of objects forming a table.

of the list of keys below only the first two are required. all others will be given "sensible" default values, where applicable, when omitted. it is ok to add your own key/value pairs to the dictionary (except '.Hilite' and '.Note', which are reserved for future use).

.DataTable: array
this array contains the data values to display in the table. it can be provided "inline", or be an identifier of an existing array. the array can simply be 2D, ie [row][col], or just a single column ([col]). or you may have "sets" of differing values, in which case a 1D array ([row]s) of array mixed can be used. examples of all of these are shown in the demo scenes, see the "examples" section below.
note – all data rows for a given column must contain values of the same type.
there is no default for this mandatory key.
.DataColumns: array [n] string
this array identifies the data type of the values in each respective column. that means there must be as many elements as there are columns in the '.DataTable' (and in the same order ;-)). each element must be a quoted ("little language") string, which controls the conversion of array element value to display string, chosen from the following list:
  • "B" – for boolean type true/false values.
  • "Fp" – for float values, where p gives the desired precision, eg "F3" for three decimal places. the legal range for p is '0 <= p <= 16'.
  • "G" – a default for floats, fixed to six decimals; that is, identical to "F6".
  • "I" – for integer values.
  • "S" – for string values.
  • "Vnp" – for vector values, where n tells the number of "components" in vector, ie '2 <= n <= 5', and p is the precision, as for floats.
there can be up to twenty data columns in a table. this key is mandatory.
.Padding: 2-vector
the .x and .y components of the vector specify the cell-internal amount of padding for labels and data. the values represent percentages of the largest glyph in the respective font. the 'x' value is added on both sides of an item, and 'y' above and below. the default is <.35,.2>, thirty-five and twenty percent, respectively.
.Borders: bool
this optional key is just an on/off "switch" controlling whether the table's frame displays. the default, when omitted, is show the frame.
.Lines: array [2] float
the table's frame ("borders") is drawn using 'cylinder {}' objects. use this key to provide a radius, and a colour index, cf '.Fg'. the built-in defaults are '{.025,4}', for thin, red "lines".
.ColLabels: array [n] string
this key is used to add column name labels to the table. the array must have a single dimension and provide a string element for every data column in the table. there is no default.
.RowLabels: array [n] string
exactly the same as '.ColLabels', except one name string/element for every data row.
.ColWidths: array [n] float
usually, the column widths as calculated from the labels and data, plus padding, look ok. if you need, or want, the table's columns to be wider, use this key to provide the corresponding values, one for every data column. the legal range is from 1 to 40 units, or -1 for "don't care". if a given width is insufficient for displaying the actual label or data, an error message will be shown.
.ColAlign: array [n] int
all column data types have a default alignment, this keys provides overrides. there must be one element/value, from the list of three below, for every data column:
  1. left.
  2. centre.
  3. right.
the defaults for the supported types are to display booleans centred, strings and vectors left-aligned, and all numbers right-aligned.
note – name label alignments cannot be changed; they are fixed to centred for column names, and right-aligned for row names.
.Transpose: bool
whether or not to transpose the table's columns and rows, default 'false'. note – setting this key is not compatible with '.ColWidths', an error results.
.Caption: array mixed
typically this array will have three elements (in order): a caption string, an alignment (cf '.ColAlign'), and a boolean to place the caption above (when true) or below the table. optionally, the array can have a fourth element, a foreground colour index, see '.Fg'. this key has no default.
.Fonts: array [3] string
the code uses three TrueType fonts for labels, data, and captions, and the font names in the array need to be provided in that order. if this key is omitted, the POV-Ray distributed fonts 'timrom.ttf' and 'cyrvetic.ttf' get used, the latter for data.
.Bg: array [3] pigment
the background pigments for labels, data, and alternate data (cf '.Ledger'), in that order. the default is different, light-ish grays for all three, ie 'pigment {color rgb .nn}' type values. use 'pigment {color rgbt 1}' for transparent backgrounds.
.Fg: array [3] int
the foreground/text colour indices, for labels, data, and alternate data (see '.Ledger'), in that order:
  1. black
  2. blue
  3. green
  4. cyan
  5. red
  6. magenta
  7. yellow
  8. white
the default, when omitted, is red labels and all data in black, ie '{4,0,0}'.
.Emissive: bool
selects which, of two, finishes to use for the table objects' appearance. set this key to 'true' in unlit, or low lighting, scenes, to apply a finish using the emission keyword. the default, when omitted, is 'false', ie use a non-emissive finish (for lit scenes). see '.Finishes' below.
.Finishes: array [2] finish
use this key to provide alternatives for the built-in "regular" and emissive finishes (in that order); the code uses '{ambient .1 diffuse .8 specular .1}' and '{ambient 0 emission .85}', respectively; cf '.Emissive'.
.UseBool: array [2] string
by default boolean type data displays as 'no' and 'yes'. use this key to provide alternative wording of your choice, first the false/'no' word, then its complement; for example: {"disabled","enabled"}.
.Ledger: bool
tables with many rows and or columns can sometimes be difficult to take in. a ledger-style background, where alternate rows are coloured differently, can help improve "readability". set this key to enable the feature, however, four or more data rows are needed. the '.Fg' and '.Bg' keys provide the means to adjust/adapt the appearances.
.Logo: bool
this key controls whether an "eye candy" POV-Ray logo will be displayed; the table must have both column and row labels, and the distribution 'logo.inc' file must have been included before calling the macro. default, when omitted, is show the logo, setting the key to 'false' suppresses.
.Verbose: bool
when enabled, Tabulated() will print a summary "header" of the various settings to POV-Ray's debug stream. default 'false'.

for all strings, whether Row/Column labels or type "S" data columns, all provisos regarding POV-Ray's 'text {}' object apply, that is, no newlines and such.

as mentioned above, Tabulated() calculates the required dimensions using a font's largest glyph. to clarify, for each font only the glyphs corresponding to chr(1) through to chr(255) are looked at; fonts may have larger glyphs in subsequent, higher "pages", than in the first.

examples

this section introduces the demo code, example scenes which, taken together, illustrate the basic usage of the Tabulated() macro. each of the three examples named 'ex?.pov' demonstrates the use of one particular "shape" of array. two other scenes included simply show further usages of the macro, but they use specific fonts which you may need to change/adapt.

quote by Eleanor Roosevelt.
a table used as a billboard.

the archive also includes a little script which generates a "sampler" scene, a simple viewer for a given TrueType font. you will need a BASH (Bourne Again Shell) to run the script, as discussed below.

note – all scene code can be accessed by clicking on the corresponding image, which shows a render of the table "after adding bling". each example scene file, as shipped, will render the (mostly) all-defaults "before bling" version, the modifications needed are commented out, for quick editing. also, you may prefer to switch to an ortho­graphic type camera instead, particularly when rendering wider tables.

the first image shown is included simply to make the point that a table does not necessarily have to look like "a table". without borders, Tabulated()'s output can make a passable billboard. it is also the only image where I "cheated", using a separate utility to crop the rendered image.

table displaying superellipsoid parameters.
a fully labelled table.

the first example demonstrates use of an "ordinary" 2D data array, where the first dimension represents the rows, and the second the columns. the scene uses the POV-Ray 'superellipsoid {}' object. the array has six sets (rows) of two parameters (columns), corresponding to the shape's "east-west" and "north-south" parameters; hence the array name en_. there are a couple of arrays storing colours and colour names, also with six elements each. these are used to colour-code the shapes, and to provide row labels for the table. the column labels are provided "inline". because the superellipsoids are lined-up to the right of table, the rendered image is taller than wide. to compensate, a camera aspect ratio of 3/4 is used.


  ...

  /* object colours */
  #declare cv_ = array [6] {
    <0,0,1>, <0,1,0>, <0,1,1>,
    <1,0,0>, <1,0,1>, <1,1,0>
  };
  #declare cn_ = array [6] {
    "blue", "green", "cyan",
    "red", "magenta", "yellow"
  };

  /* east + north */
  #declare en_ = array [6][2] {
    {.50,.16}, {.50,.32}, {.50,.48},
    {.50,.64}, {.50,.80}, {.50,.96}
  };

  ...

  #declare D = dictionary {
    .DataColumns: array [2] {"F3","F3"},  
    .DataTable  : en_
  };

  ...

when declaring the input dictionary for the macro, the (most) important thing is to provide the correct data type specification for each column, in order. since both parameters in this data table are of the same type, so are the column types; we use "F3" to get the float values displayed with three decimal digits precision. we then add the data array using its identifier. the second image shows the wildly unexciting result of rendering the table after adding labels and caption, as well as enabling the '.Ledger' style feature. it also shows that tables really need some labelling to provide context, meaning.


  jr@crow:tabu$ cd demo
  jr@crow:demo$ povray ex1.pov +l.. +w600 +h800 +a0.1  

presuming that your current working directory is the archive's top (ie '/path/to/tabu'), the second snippet shows how to quick test render this example, and the others below. the library_path (+l..) option let's POV-Ray find the include file.

the second example, shown in the third image, is based on an 1D array where every element ("row") is an array mixed, think database table, storing information about the objects displayed, both calculated and entered "by hand" values. each of the four objects is a POV-Ray mesh2 type, and part of a fictional science fiction objects collection. the meshes were created from GNU Triangulated Surfaces data files[2] found on the interweb. a plain background image was added to relieve the "drabness".

customised table displaying sci-fi collection objects.
a customised table.

the objects, from left to right, correspond to the data rows, in order. their names are the same as the declared "identifiers", and the include file names; they are used to source the files, and as row labels in the table.

the thing to note is the "V33" type spec for the second column, a macro is used to round the object dimensions, an '<x,y,z>' vector, to the third decimal. the other columns, integers and booleans, do not have a precision.

while functional, the table's default colours sort of clash with the background, hence '.Fg', '.Bg', and '.Lines' keys were set to address this. also, given the image's width, the table can be made a little wider. '.ColWidths' is used to set the columns to uniform widths.

well, there are only so many examples one can contrive ;-), so the third, and final, is even more "lame" than the previous. it demonstrates the use of a single column table (array), with three rows storing the corner points of a 'triangle {}'. the points are randomly generated, and the table displays those values (rounded up to make them look "nice"), as well as the seed value used; the seed is a number derived from the system time, and every render will draw a different triangle. the fourth image shows an instance.

table displaying a random triangle.
another customised table.

as in the second example, the necessary (for the visuals) dictionary keys were added to make the result a little less plain; namely the foreground colours and background pigments, and '.Lines'.

you may feel .. hampered by the limited number of foreground (and line) colours. since the Tabulated() macro uses a simple 1D array of 3-vector RGB values, it is easy, and safe, to override that by providing your own. simply, after including 'tabulated.inc', but before running the Tabulated() macro, declare an array like the one shown in the snippet, that is, the exact same name and number of elements, plus your colour vectors. then those will be used for the remaining scene code.


  #declare tabu__data_colours_ = array [8] {  
    <0,0,0>, <0,0,1>, <0,1,0>, <0,1,1>,
    <1,0,0>, <1,0,1>, <1,1,0>, <1,1,1>
  };

the final, fifth, image was rendered from a scene created by the aforementioned (BASH) script utility, 'mkfntview.sh'. the scene uses the 'verdana.ttf' font, and the utility's output was "calibrated" using that font. some visual "bells and whistles" were added to make an appealing (I hope) example image. each generated scene displays one (of four) 16x16 glyph "pages", and a three-line "sampler" set in the selected font. for convenience, a page can be selected from the command-line via POV-Ray's declare=page=N syntax, where N must be in range 0,..,3.

table displaying font glyphs.
a table displaying a font's glyphs.

note – the camera settings, and the table scales and positions, will, almost always, need tweaking, as fonts are so diverse. the snippet assumes that the utility has been copied to a PATH directory. naming a non-existing font, or a misspelling, will go unnoticed until POV-Ray tries to use the font.


  $ mkfntview.sh verdana.ttf
  $ povray view_verdana_ttf.pov +w600 +h800 +a0.1  


references

although 'tabulated.inc' does not depend on any other includes, one of the three example/demo scenes does, for convenience. the necessary 'foreach.inc' was published in the POV-Ray news groups, but the download link below points to the current version. I also include a link to the latest 'ruled.inc', because, in my (biased :-)) opinion, it complements 'tabulated.inc' well.

POV-Ray
the new to version 3.8 'dictionary {}' array/data type, in particular.
GTS
the GNU GTS file format, and the associated library, make conversion to POV-Ray 'mesh' types very easy.
Foreach()
a macro which calls a supplied-by-you "payload" macro, for every element, or selected, in a given array.
Ruled()
an adaptation of Friedrich Lohmüller's macros for scale-modelling with POV-Ray.

"The End". enjoy.