coredocs

The character grid underneath every plot, and the glyphs that fill it.

A plot is ultimately a rectangle of coloured unicode characters. This module provides that rectangle, along with the routines that turn numeric data into characters dense enough to draw with. The plot types in matthewplotlib.plots are built on top of it.

The grid:

  • CharArray: A grid of unicode codepoints with optional foreground and background colors. Supports composition (stacking, layering, padding), rendering to ANSI strings---including differential updates that repaint only the cells that changed between frames---and rendering to images and animated gifs using an embedded pixel font.

Drawing characters, each packing several data points into one character cell:

  • unicode_braille_array: Boolean matrices to braille characters, at 2 by 4 dots per cell.
  • rasterise_points, rasterise_segments, and the unicode_braille_points and unicode_braille_segments that draw with them: points or line segments, given in dots, to a grid of dots or straight to braille characters.
  • unicode_bar and unicode_col: Values to horizontal or vertical bars, using partial block characters for eighth-of-a-cell resolution.
  • unicode_image: Images to half-block characters, at 1 by 2 pixels per cell.
  • unicode_boxes, with Orientation: Two nested intervals a group, with optional caps, interior mark and outlying points, lying either way and drawn filled or outlined. Both box plots and candlesticks are settings of it.
  • unicode_box and BoxStyle: Box-drawing borders, optionally titled.
  • unicode_frame and unicode_grid, with LineStyle: Rules along the sides of a plot, or between the cells of a grid, with the corners and junctions where they meet derived from which of them are drawn.

CharArray : dataclass source docs

A grid of possibly-coloured characters comprising a plot. For internal use.

Fields:

  • codes: uint32[h,w]. Unicode code point for the character.
  • fg: bool[h,w]. Whether to use a custom foreground color.
  • fg_rgb: uint8[h,w,3]. (If fg) RGB for custom foreground color.
  • bg: bool[h,w]. Whether to use a custom background color.
  • bg_rgb: uint8[h,w,3]. (If bg) RGB for custom background color.

crop(above: int = 0, below: int = 0, left: int = 0, right: int = 0) -> CharArray : source docs

Take rows and columns off the edges of the array, the inverse of pad.

Inputs:

  • above, below, left, right : int. How many rows or columns to take off each edge.

Returns:

  • cropped : CharArray. What is left, which must be at least one row and one column.

isblank() -> NDArray : source docs

True where the character has no visible content.

isnonblank() -> NDArray : source docs

True where the character has visible content.

to_ansi_str() -> str : source docs

Render a CharArray as a sequence of characters and ANSI control codes (merging codes where possible).

to_ansi_diff_str(prev: CharArray) -> str : source docs

Render the minimal ANSI sequence that updates a terminal already showing prev so that it shows self instead, repainting only the cells that differ.

This is the differential counterpart to to_ansi_str: where redrawing a whole frame re-emits every cell, this jumps the cursor to just the changed cells. For an animation whose frames are mostly stable, that is dramatically fewer bytes down the wire.

Cursor contract (the sequence is shaped for a plain print):

  • On entry, the cursor is assumed to be at column 0 on the line immediately below where prev was rendered -- exactly where it sits after printing prev.
  • On exit, the cursor is left at column 0 on the last row of the plot, so the newline print appends carries it to the line below, ready for the next frame to diff against this one in the same way.

In other words the sequence is incomplete on its own: it expects a trailing newline, just as to_ansi_str does. Write it with print(...), not print(..., end="").

Assumes prev was rendered starting at column 0 and that all glyphs are single-width (the standard animated-plot layout).

The two need not be the same size, and only the difference is sent in that case too. Cells outside the region prev covered are always painted, since nothing is on screen there to keep. Rows and columns prev covered but self does not are erased. Growing taller overwrites the rows immediately below the plot: they have to be written into, and a string cannot push them out of the way without knowing where it sits on the screen. Growing shorter leaves those rows blank rather than closing the gap, for the same reason.

Note that an unchanged frame does not return "": it returns a bare cursor-up, so that the newline print appends still lands the cursor where the contract promises rather than a row lower.

to_plain_str() -> str : source docs

Render a CharArray as a sequence of characters without colour.

to_rgba_array(bgcolor: ColorLike | None = None) -> np.ndarray : source docs

Convert a CharArray to an RGBA image array

to_bit_array() -> np.ndarray : source docs

Convert a CharArray to an bitmap image array

ords(chrs: Sequence[str]) -> list[int] : source docs

Convert a string or list of glyphs to a list of unicode code points.

C0 and C1 control characters are not glyphs and are rejected. In particular, raw ANSI formatting is not supported: terminal styling has to be represented in the character array so that its size and rendering stay well-defined.

Align = Literal['left', 'center', 'right'] : source docs

Where a line of text sits in the width it is written into.

Only has room to act where that width is wider than the line itself.

unicode_text(lines: Sequence[str], height: int = 0, width: int = 0, align: Align = 'left', fgcolor: ColorLike | None = None, bgcolor: ColorLike | None = None) -> CharArray : source docs

Write lines of text into an array of characters, one line to a row.

Inputs:

  • lines : sequence of str. The lines to write, the first at the top. Each must be free of control characters, line breaks included: splitting the text into lines is the caller's, since where a line is allowed to be broken or cut depends on what the text is for.
  • height : int (default: 0). The least number of rows to write into. More are taken if there are more lines than this.
  • width : int (default: 0). The least number of columns to write into. More are taken if a line is longer than this.
  • align : Align (default: "left"). Where each line sits in the width, which only has room to act where the width is wider than the line.
  • fgcolor : optional ColorLike. The colour of the text. Defaults to the terminal's foreground colour.
  • bgcolor : optional ColorLike. The colour behind it, the rows and columns no line reaches included. Defaults to a transparent background.

Returns:

  • chars : CharArray[max(height, len(lines)), max(width, longest line)]. The text, in an array at least the size asked for.

No lines at all gives an array of no rows, which is a plot of nothing rather than an error, so that one empty line and no lines stay distinct.

unicode_braille_array(dots: NDArray, dotc: NDArray | None = None, dotw: NDArray | None = None, fgcolor: ColorLike | None = None, bgcolor: ColorLike | None = None) -> CharArray : source docs

Turns a H by W array of dots into a h=ceil(H/4) by w=ceil(W/2) array of braille Unicode characters.

Inputs:

  • dots: bool[H, W]. Array of booleans or counts. Dots are placed where this array contains nonzero.
  • dotc: optional uint8[H, W, RGB]. Array of colours to use for the fg of each dot. Where multiple dots are coloured within one one character, mixes the colours according to dotw.
  • dotw: optional float[H, W]. Weights for combining colors when multiple dots occur in one cell. If not provided, combine uniformly. If dotc is not provided, this is not used.
  • fgcolor: optional ColorLike. Foreground color used for all braille characters. Overrides dotc if both are provided.
  • bgcolor: optional ColorLike. Background color used for all characters.

Returns:

  • chars: CharArray. An array of Braille characters with h rows and w columns.

An illustrated example, not including colour combination, is as follows:

Start with an array. Assume height is divisible by 4 and width divisible by
2, otherwise pad with 0s until that is the case.
    ____
   [1  0] 0  1  0  1  1  1  1  0  1  0  0  0  0  1  0  0  0  0  0  1  1  0
   [1  0] 0  1  0  1  0  0  0  0  1  0  0  0  0  1  0  0  0  0  1  0  0  1
 .-[1  0] 0  1  0  1  0  0  0  0  1  0  0  0  0  1  0  0  0  0  1  0  0  1
 | [1__0] 0  1  0  1  0  0  0  0  1  0  0  0  0  1  0  0  0  0  1  0  0  1
 |  1  1  1  1  0  1  1  1  1  0  1  0  0  0  0  1  0  0  0  0  1  0  0  1
 |  1  0  0  1  0  1  0  0  0  0  1  0  0  0  0  1  0  0  0  0  1  0  0  1
 |  1  0  0  1  0  1  0  0  0  0  1  0  0  0  0  1  0  0  0  0  1  0  0  1
 |  1  0  0  1  0  1  1  1  1  0  1  1  1  1  0  1  1  1  1  0  0  1  1  0
 |
 | take each 4x2 subarray and ...
 |                                                               braille
 | identify the 4x2 bits with the                                unicode
 | eight numbered braille dots:                                  start pt
 |                                                               |
 |  (dot 1) 1 0 (dot 4)     convert to                           v
 `> (dot 2) 1 0 (dot 5) -----------------> 0 b 0 1 0 0 0 1 1 1 + 0x2800 -.
    (dot 3) 1 0 (dot 6)    braille code        | | | | | | | |           |
    (dot 7) 1 0 (dot 8)                    dot 8 7 6 5 4 3 2 1           |
                                                                         |
  convert the braille code to a unicode character and collate into array |
 .-----------------------------------------------------------------------'
 |  '''
 `->⡇⢸⢸⠉⠁⡇⠀⢸⠀⠀⡎⢱  (Note: this function returns a CharArray, use
    ⡏⢹⢸⣉⡁⣇⣀⢸⣀⡀⢇⡸  .to_plain_str() to get a string.)
    '''

disc_offsets(thickness: float) -> NDArray : source docs

The dots covered by a disc of the given thickness centred on a dot, as offsets from that dot.

A dot is included when its centre lies within thickness / 2 of the centre of the disc, so a thickness of 1 covers one dot, 2 covers a plus of five, 3 covers a three-by-three square, and so on.

Inputs:

  • thickness: float. Diameter of the disc, measured in dots.

Returns:

  • offsets: int[k, 2]. One (row, col) offset per covered dot, always including (0, 0).

rasterise_segments(starts: NDArray, ends: NDArray, height: int, width: int, start_colors: NDArray | None = None, end_colors: NDArray | None = None, thickness: float = 1.0) -> tuple[NDArray, NDArray | None, NDArray | None] : source docs

Draw straight line segments onto a grid of dots.

Coordinates are in dots, with rows increasing downwards, so that dot (i, j) covers the unit square from (i, j) up to but not including (i+1, j+1), and the centre of that dot is at (i+0.5, j+0.5). Whatever falls outside the grid is clipped away.

Inputs:

  • starts: float[n, 2]. One (row, col) coordinate per segment, where each segment begins.
  • ends: float[n, 2]. Where each segment ends.
  • height: int. Number of rows of dots in the grid.
  • width: int. Number of columns of dots in the grid.
  • start_colors: optional uint8[n, 3]. Color at the start of each segment. If omitted, no colors are computed and the color outputs are None.
  • end_colors: optional uint8[n, 3]. Color at the end of each segment, interpolated along it. If omitted while start_colors is given, segments are a single flat color.
  • thickness: float (default 1.0). Width of the stroke, in dots. See disc_offsets.

Returns:

  • dots: int[height, width]. How many times each dot was covered. Zero where the dot is not part of any segment.
  • dotc: optional uint8[height, width, 3]. The average of the colors covering each dot, or None if no colors were given.
  • dotw: optional float[height, width]. The coverage counts again, as the weights that mix these colors together within a character cell, or None if no colors were given.

The three outputs are exactly the dots, dotc and dotw inputs of unicode_braille_array.

Segments with a non-finite endpoint are skipped, which is how a gap in a sequence of points becomes a gap in the line drawn through them.

Notes:

  • Segments are clipped to the grid before they are drawn, so a segment may run arbitrarily far outside it without costing anything to skip.
  • A stroke is the segment thickened by a disc, which is to say the union of discs centred along it. Round caps and correctly filled joins between consecutive segments both follow from that, without either being a case to handle.

rasterise_points(points: NDArray, height: int, width: int, colors: NDArray | None = None) -> tuple[NDArray, NDArray | None, NDArray | None] : source docs

Mark the dots that a set of points falls in.

Coordinates are in dots and mean what they mean for rasterise_segments, which also describes the three arrays this returns. Points that are not fully specified are skipped, as is anything outside the grid.

accumulate_dots(dot: NDArray, colors: NDArray | None, height: int, width: int) -> tuple[NDArray, NDArray | None, NDArray | None] : source docs

Count how many times each dot of a grid is covered, and average the colors covering it.

Whatever falls outside the grid is dropped. The counts double as the weights that mix the colors of one character cell together, so they come back a second time as those weights, which is what unicode_braille_array takes.

unicode_braille_points(points: NDArray, height: int, width: int, colors: NDArray | None = None) -> CharArray : source docs

Draw a set of points as a grid of braille characters.

Coordinates are in dots, as for rasterise_points, which this draws with.

unicode_braille_segments(starts: NDArray, ends: NDArray, height: int, width: int, start_colors: NDArray | None = None, end_colors: NDArray | None = None, thickness: float = 1.0) -> CharArray : source docs

Draw line segments as a grid of braille characters.

Coordinates are in dots, as for rasterise_segments, which this draws with and which documents what the arguments mean.

unicode_bar(proportion: float, width: int, height: int = 1, fgcolor: ColorLike | None = None, bgcolor: ColorLike | None = None) -> CharArray : source docs

Generates a Unicode progress bar as a list of characters.

This function creates a fixed-width left-to-right bar using Unicode block elements to represent the proportion rounded down to nearest 1/8th of a block.

Inputs:

  • proportion: float. The fraction of the bar to fill. Should be between 0.0 and 1.0 inclusive.
  • width: int (positive). The width of the full bar in characters.
  • height: int (positive, default 1). The number of rows that the bar takes up.
  • fgcolor: optional ColorLike. Foreground color used for the progress bar characters.
  • bgcolor: optional ColorLike. Background color used for the progress bar remainder.

Returns:

  • chars: CharArray A character array representing the bar.

Examples:

>>> unicode_bar(0.5, 10).to_plain_str()
'█████     '
>>> unicode_bar(0.625, 10).to_plain_str()
'██████▎   '

unicode_col(proportion: float, height: int, width: int = 1, fgcolor: ColorLike | None = None, bgcolor: ColorLike | None = None) -> CharArray : source docs

Generates a Unicode progress column as a list of characters.

This function creates a fixed-height column using Unicode block elements to represent a proportion rounded down to nearest 1/8th of a block. The list goes from the top of the bar to the bottom, but the bar grows from the bottom towards the top.

Inputs:

  • proportion: float. The fraction of the column to fill. Should be between 0.0 and 1.0 inclusive.
  • height: int (positive). The height of the full bar in characters.
  • width: int (positive, default 1). The number of columns that the bar takes up.
  • fgcolor: optional ColorLike. Foreground color used for the progress bar characters.
  • bgcolor: optional ColorLike. Background color used for the progress bar remainder.

Returns:

  • chars: CharArray A char array representing the column.

Examples:

>>> unicode_col(0.5, 3).to_plain_str()
' \n▄\n█'

BoxStyle : source docs

Bases: str, Enum

A string enum defining preset styles for the border plot.

Each style is a string of eight characters representing the border elements.

Available Styles:

  • LIGHT: A standard, single-line border.
  • HEAVY: A thicker, bold border.
  • DOUBLE: A double-line border.
  • DASHED: A dashed single-line border.
  • BLANK: An invisible border (easily add 1-width padding).
  • ROUND: A single-line border with rounded corners.
  • BUMPER: A single-line border with corners made of blocks.
  • BLOCK1: A blocky border with half-width left and right walls.
  • BLOCK2: A uniform blocky border.
  • TIGER1: A stripy block border.
  • TIGER2: An alternative stripy block border.

Demo:

┌──────┐ ┏━━━━━━┓ ╔══════╗ ┌╌╌╌╌╌╌┐ ⡤⠤⠤⠤⠤⠤⠤⢤ ╭──────╮
│LIGHT │ ┃HEAVY ┃ ║DOUBLE║ ┊DASHED┊ ⡇DOTTED⢸ │ROUND │
└──────┘ ┗━━━━━━┛ ╚══════╝ └╌╌╌╌╌╌┘ ⠓⠒⠒⠒⠒⠒⠒⠚ ╰──────╯
         ▛──────▜ ▛▀▀▀▀▀▀▜ █▀▀▀▀▀▀█ ▞▝▝▝▝▝▝▝ ▘▘▘▘▘▘▘▚
 BLANK   │BUMPER│ ▌BLOCK1▐ █BLOCK2█ ▖TIGER1▝ ▘TIGER2▗
         ▙──────▟ ▙▄▄▄▄▄▄▟ █▄▄▄▄▄▄█ ▖▖▖▖▖▖▖▞ ▚▗▗▗▗▗▗▗

TODO:

  • It might make sense to consider borders with two characters on the left and right sides of the contents. Would open up new design possibilities.

unicode_box(chars: CharArray, style: BoxStyle, fgcolor: ColorLike | None = None, bgcolor: ColorLike | None = None, title: str = '') -> CharArray : source docs

Wrap a character array in an outline of box drawing characters.

LineStyle : source docs

Bases: str, Enum

A string enum defining the weights of line available to draw axes with.

Each style is a string of sixteen characters, one for every combination of directions a character can reach out in. Index the string by the sum of 1 for up, 2 for down, 4 for left and 8 for right to find the character that joins exactly those directions.

Available Styles:

  • LIGHT: Single lines meeting at square corners.
  • HEAVY: Thick single lines.
  • ROUND: Single lines meeting at rounded corners.
  • DOUBLE: Double lines. This set has no half-length stubs, so a line that ends without either a corner or a tick to finish it runs to the edge of its final cell instead of stopping halfway.

Demo:

┌─┬─┐  ┏━┳━┓  ╭─┬─╮  ╔═╦═╗
├─┼─┤  ┣━╋━┫  ├─┼─┤  ╠═╬═╣
└─┴─┘  ┗━┻━┛  ╰─┴─╯  ╚═╩═╝

unicode_frame(chars: CharArray, style: LineStyle, cells: tuple[bool, bool, bool, bool], rules: tuple[bool, bool, bool, bool], ticks: tuple[bool, bool, bool, bool], title: str = '', fgcolor: ColorLike | None = None) -> CharArray : source docs

Surround a character array with a rule along any of its four sides.

Each side, in the order north, east, south, west, is described by three flags: whether it takes a cell at all, whether a line is drawn in that cell, and whether the ends of that line are ticked. A tick is an arm reaching outward from the end of a line, towards wherever a label goes.

Every character is derived rather than chosen. A cell reaches towards each neighbouring cell that is also part of a rule, and outward wherever a tick is called for, and the resulting set of directions selects the character from the style. So the corner where two ruled sides meet turns, the corner where one of them is missing finishes, and a ticked end grows the arm that points at its label, without any of the three being written down.

A rule runs the length of the array it is drawn beside, and reaches into the corner cell it shares with a neighbouring side only when that side is ruled as well, so that a side left blank stays outside the frame.

Inputs:

  • chars : CharArray. The array to surround.
  • style : LineStyle. The weight of line to draw.
  • cells : (bool, bool, bool, bool). Whether the north, east, south and west sides each take a cell.
  • rules : (bool, bool, bool, bool). Whether a line is drawn in that cell. A side that draws one must take a cell.
  • ticks : (bool, bool, bool, bool). Whether the ends of that line are ticked. A side that ticks must draw a line.
  • title : str. Written along the north side, centred over the array and truncated to fit. The north side must take a cell.
  • fgcolor : optional ColorLike. The colour of the rules and the title. Defaults to the terminal's foreground colour.

Returns:

  • framed : CharArray. The array, surrounded by whichever of the four sides took a cell.

unicode_grid(cells: Sequence[Sequence[CharArray]], hcells: Sequence[bool], hrules: Sequence[LineStyle | None], vcells: Sequence[bool], vrules: Sequence[LineStyle | None], fgcolor: ColorLike | None = None, bgcolor: ColorLike | None = None) -> CharArray : source docs

Lay a rectangular grid of character arrays out with rules between them.

A grid of cells has one more horizontal rule than it has rows, one above each row and one below the last, and one more vertical rule than it has columns, one to the left of each column and one to the right of the last. Each rule is described by two things: whether it takes a row or column of cells at all, and which weight of line, if any, is drawn in it. A rule that takes no cells does not appear in the output at all; one that takes cells but draws no line is a row or column of blank space, which any rule crossing it still runs through.

Every character is derived rather than chosen. A rule runs the whole length of the grid, so each of its cells reaches out along it, and reaches across wherever a rule of the other orientation is drawn. The resulting set of directions selects the character. So the corner where two rules meet turns, the junction where they cross joins, and a rule that ends at the edge of the grid fills its last cell rather than stopping halfway. Where a rule of one weight crosses one of another, the character joining them belongs to neither weight, and that too is derived.

Inputs:

  • cells : CharArray[nrows, ncols]. The contents of the grid. Every array in a row must be the same height, and every array in a column the same width.
  • hcells : bool[nrows+1]. Whether each horizontal rule takes a row of cells.
  • hrules : (LineStyle | None)[nrows+1]. The weight of line drawn in each horizontal rule, or None to leave its row blank. A rule that draws a line must take a row.
  • vcells : bool[ncols+1]. Whether each vertical rule takes a column of cells.
  • vrules : (LineStyle | None)[ncols+1]. The weight of line drawn in each vertical rule, or None to leave its column blank. A rule that draws a line must take a column.
  • fgcolor : optional ColorLike. The colour of the rules. Defaults to the terminal's foreground colour.
  • bgcolor : optional ColorLike. The colour behind the rules, and behind any blank row or column between the cells. The cells bring their own. Defaults to a transparent background.

Returns:

  • grid : CharArray. The cells, laid out with whichever rules took cells between them.

Only LineStyle.LIGHT and LineStyle.DOUBLE may be drawn. Those are the two weights Unicode provides a complete set of crossings for.

unicode_image(image: NDArray) -> CharArray : source docs

Convert an RGB image into an array of coloured Unicode half-block characters representing the pixels of the image.

Inputs:

  • image: u8[h, w, rgb]. The pixels of the image.

Returns:

  • chars: CharArray[ceil(h/2), w]. The array of coloured half-block characters. If the image has odd height, the bottom half of the final row is set to the default background colour.

Orientation = Literal['horizontal', 'vertical'] : source docs

Which way a mark lies, and so which way its value axis runs.

  • "horizontal": the mark lies flat, and its values are read across the screen, left to right.
  • "vertical": the mark stands up, and its values are read up the screen.

The tables below are keyed by this, since which screen direction counts as along the value axis and which as across the mark's thickness is the whole of the difference between the two.

unicode_boxes(outer_los: NDArray, outer_his: NDArray, inner_los: NDArray, inner_his: NDArray, length: int, box_colors: NDArray | None, outer_colors: NDArray | None = None, interiors: NDArray | None = None, outliers: NDArray | None = None, outlier_boxes: NDArray | None = None, direction: Orientation = 'horizontal', filled: bool = False, thickness: int = 3, spacing: int = 1, caps: bool = True, background: ColorLike | None = None, style: LineStyle = LineStyle.LIGHT, interior_style: LineStyle = LineStyle.LIGHT) -> CharArray : source docs

Draw a row of interval marks, one per set of values.

Each mark is an outer interval drawn thin and an inner interval drawn thick, and then caps across the ends of the outer interval, a mark at one value inside the inner interval, and points beyond the outer interval, each drawn only where it is asked for. A box plot is every part present; a candlestick is the two intervals alone.

Inputs:

  • outer_los, outer_his : float[n]. The ends of each mark's outer interval, each as a proportion of the way along the value axis: 0.0 at the low edge of the first character cell, 1.0 at the high edge of the last. Values outside that range are clipped into it.
  • inner_los, inner_his : float[n]. The ends of each mark's inner interval, in the same units. Each is clipped into its own outer interval.
  • length : int (positive). The number of character cells along the value axis.
  • box_colors : optional uint8[n, rgb]. The color of each mark. Omitted, the marks take the terminal's own foreground color, which a filled mark cannot do, since a color it draws negatives against has to be named.
  • outer_colors : optional uint8[n, rgb]. The color of each mark's outer interval and caps. Omitted, each takes the color of the mark it belongs to. In an outlined mark this colors only the cells the outer interval has to itself, since the cells it shares with the outline can show one color between them.
  • interiors : optional float[n]. The value of each mark's interior mark, in the same units. Omitted, no interior mark is drawn.
  • outliers : optional float[m]. The values of the points beyond the outer intervals, in the same units, every mark's points together in one array.
  • outlier_boxes : optional int[m]. Which mark each of those points belongs to.
  • direction : Orientation (default: "horizontal"). Which way one mark lies. Horizontal marks lie flat and stack up the screen; vertical marks stand up and march across it.
  • filled : bool (default: False). Whether the inner interval is a solid fill rather than an outline.
  • thickness : int (default: 3). The number of character cells across one mark. At least 3 for an outlined mark, which needs two edges and an interior between them, and at least 1 for a filled one.
  • spacing : int (default: 1). The number of blank cells between one mark and the next.
  • caps : bool (default: True). Whether to draw a cap across each end of the outer interval.
  • background : optional ColorLike. The color behind the marks. Required for filled marks, which reach every eighth of a cell only by drawing half of them as negatives.
  • style : LineStyle (default: LineStyle.LIGHT). The weight of the outer interval, the caps, and an outlined mark's outline.
  • interior_style : LineStyle (default: LineStyle.LIGHT). The weight of a filled mark's interior mark. An outlined mark's interior mark joins its outline and so takes style instead.

Returns:

  • chars : CharArray. A character array holding the marks side by side, length cells along the value axis and n * (thickness + spacing) - spacing across.

An outlined mark is drawn at one position per cell throughout, its corners and junctions derived from which of its neighbours are drawn, exactly as a frame's are. A filled mark reaches finer: its fill lands on the nearest eighth of a cell, its outer interval on the nearest half, and its interior mark on one of the three thin bands a cell can show.

Where there is no room for it the interior mark is dropped, rather than drawn about nothing. An outlined mark needs a whole cell to spare between the edges of its inner interval. A filled one needs the band's cell filled from edge to edge, because the band is drawn over the fill's color and would otherwise paint the mark outside the interval the band divides.