tessella 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (188) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +21 -0
  3. data/README.md +900 -0
  4. data/doc/README.md +74 -0
  5. data/doc/bitfield.md +123 -0
  6. data/doc/charts.md +1370 -0
  7. data/doc/check.rb +75 -0
  8. data/doc/cli.md +483 -0
  9. data/doc/composition.md +599 -0
  10. data/doc/getting-started.md +121 -0
  11. data/doc/glyphs.md +134 -0
  12. data/doc/images.md +325 -0
  13. data/doc/library.md +239 -0
  14. data/doc/lint.md +270 -0
  15. data/doc/meter.md +210 -0
  16. data/doc/rule.md +51 -0
  17. data/doc/seq.md +169 -0
  18. data/doc/sparkline.md +63 -0
  19. data/doc/table.md +347 -0
  20. data/doc/targets.md +222 -0
  21. data/doc/text.md +84 -0
  22. data/doc/tree.md +108 -0
  23. data/doc/wave.md +122 -0
  24. data/examples/README.md +103 -0
  25. data/examples/ages.txt +9 -0
  26. data/examples/bad.csv +3 -0
  27. data/examples/boxes.txt +3 -0
  28. data/examples/broken.txt +4 -0
  29. data/examples/calibration.rb +12 -0
  30. data/examples/cities.csv +4 -0
  31. data/examples/cjk.rb +13 -0
  32. data/examples/comment.rb +9 -0
  33. data/examples/comment.sh +13 -0
  34. data/examples/cpu.txt +60 -0
  35. data/examples/dashboard.sh +25 -0
  36. data/examples/datasheet.rb +16 -0
  37. data/examples/disc.pgm +0 -0
  38. data/examples/disks.sh +18 -0
  39. data/examples/disks.txt +5 -0
  40. data/examples/dose.txt +7 -0
  41. data/examples/drift.txt +4 -0
  42. data/examples/ethernet.txt +3 -0
  43. data/examples/figure.c +5 -0
  44. data/examples/fit.rb +16 -0
  45. data/examples/floors.rb +21 -0
  46. data/examples/gallery.sh +20 -0
  47. data/examples/gauges.sh +31 -0
  48. data/examples/gradient.rb +16 -0
  49. data/examples/growth.txt +6 -0
  50. data/examples/hosts.csv +4 -0
  51. data/examples/icmp.txt +3 -0
  52. data/examples/ipv4.txt +12 -0
  53. data/examples/kinds.csv +4 -0
  54. data/examples/langs.csv +4 -0
  55. data/examples/lint.rb +16 -0
  56. data/examples/load.rb +14 -0
  57. data/examples/login.txt +10 -0
  58. data/examples/neg.csv +4 -0
  59. data/examples/normal.txt +200 -0
  60. data/examples/note.txt +2 -0
  61. data/examples/notes-fixed.md +17 -0
  62. data/examples/notes.md +17 -0
  63. data/examples/ohlc.txt +7 -0
  64. data/examples/os.csv +10 -0
  65. data/examples/outline.txt +8 -0
  66. data/examples/plot.txt +5 -0
  67. data/examples/refusals.rb +17 -0
  68. data/examples/report.sh +21 -0
  69. data/examples/rfc.sh +22 -0
  70. data/examples/samples.txt +7 -0
  71. data/examples/shapes.pgm +0 -0
  72. data/examples/sizes.tsv +3 -0
  73. data/examples/spi.txt +6 -0
  74. data/examples/status.sh +23 -0
  75. data/examples/stem.txt +12 -0
  76. data/examples/table-drift.txt +4 -0
  77. data/examples/temps.txt +31 -0
  78. data/examples/trig.sh +13 -0
  79. data/examples/trig.txt +102 -0
  80. data/examples/week.txt +8 -0
  81. data/examples/wide.txt +2 -0
  82. data/exe/tessella +6 -0
  83. data/lib/tessella/axis.rb +71 -0
  84. data/lib/tessella/bitfield.rb +194 -0
  85. data/lib/tessella/block.rb +56 -0
  86. data/lib/tessella/borders.rb +85 -0
  87. data/lib/tessella/canvas/area.rb +155 -0
  88. data/lib/tessella/canvas/ascii.rb +120 -0
  89. data/lib/tessella/canvas/block.rb +40 -0
  90. data/lib/tessella/canvas/braille.rb +30 -0
  91. data/lib/tessella/canvas/clip.rb +49 -0
  92. data/lib/tessella/canvas/density.rb +51 -0
  93. data/lib/tessella/canvas/dot.rb +27 -0
  94. data/lib/tessella/canvas/eighths.rb +43 -0
  95. data/lib/tessella/canvas/half.rb +51 -0
  96. data/lib/tessella/canvas/sextant.rb +36 -0
  97. data/lib/tessella/canvas.rb +168 -0
  98. data/lib/tessella/cell.rb +10 -0
  99. data/lib/tessella/chart.rb +211 -0
  100. data/lib/tessella/check.rb +52 -0
  101. data/lib/tessella/cli/bitfield.rb +121 -0
  102. data/lib/tessella/cli/chart.rb +615 -0
  103. data/lib/tessella/cli/chart_candle.rb +36 -0
  104. data/lib/tessella/cli/chart_heatmap.rb +21 -0
  105. data/lib/tessella/cli/chart_help.rb +85 -0
  106. data/lib/tessella/cli/chart_pie.rb +38 -0
  107. data/lib/tessella/cli/compose.rb +156 -0
  108. data/lib/tessella/cli/compose_options.rb +259 -0
  109. data/lib/tessella/cli/glyphs.rb +97 -0
  110. data/lib/tessella/cli/image.rb +250 -0
  111. data/lib/tessella/cli/input.rb +44 -0
  112. data/lib/tessella/cli/iso_time.rb +28 -0
  113. data/lib/tessella/cli/lint.rb +102 -0
  114. data/lib/tessella/cli/lint_options.rb +99 -0
  115. data/lib/tessella/cli/main.rb +121 -0
  116. data/lib/tessella/cli/meter.rb +80 -0
  117. data/lib/tessella/cli/rule.rb +57 -0
  118. data/lib/tessella/cli/seq.rb +117 -0
  119. data/lib/tessella/cli/sparkline.rb +72 -0
  120. data/lib/tessella/cli/table.rb +167 -0
  121. data/lib/tessella/cli/text.rb +78 -0
  122. data/lib/tessella/cli/tree.rb +100 -0
  123. data/lib/tessella/cli/wave.rb +148 -0
  124. data/lib/tessella/cli.rb +36 -0
  125. data/lib/tessella/color.rb +106 -0
  126. data/lib/tessella/compose.rb +243 -0
  127. data/lib/tessella/compose_fill.rb +176 -0
  128. data/lib/tessella/compose_grid.rb +36 -0
  129. data/lib/tessella/compose_shadow.rb +41 -0
  130. data/lib/tessella/error.rb +13 -0
  131. data/lib/tessella/frame.rb +170 -0
  132. data/lib/tessella/glyphs.rb +106 -0
  133. data/lib/tessella/image.rb +454 -0
  134. data/lib/tessella/image_octant.rb +46 -0
  135. data/lib/tessella/image_petscii.rb +116 -0
  136. data/lib/tessella/kinds/bar.rb +344 -0
  137. data/lib/tessella/kinds/box.rb +213 -0
  138. data/lib/tessella/kinds/box_upright.rb +110 -0
  139. data/lib/tessella/kinds/candlestick.rb +164 -0
  140. data/lib/tessella/kinds/columns.rb +259 -0
  141. data/lib/tessella/kinds/date_axis.rb +102 -0
  142. data/lib/tessella/kinds/error_bars.rb +71 -0
  143. data/lib/tessella/kinds/heatmap.rb +130 -0
  144. data/lib/tessella/kinds/line.rb +173 -0
  145. data/lib/tessella/kinds/multibar.rb +98 -0
  146. data/lib/tessella/kinds/multibar_columns.rb +130 -0
  147. data/lib/tessella/kinds/multibar_fine.rb +45 -0
  148. data/lib/tessella/kinds/multibar_rows.rb +93 -0
  149. data/lib/tessella/kinds/pie.rb +123 -0
  150. data/lib/tessella/kinds/pie_rim.rb +78 -0
  151. data/lib/tessella/kinds/sideways.rb +62 -0
  152. data/lib/tessella/kinds/stem.rb +134 -0
  153. data/lib/tessella/kinds/threshold.rb +73 -0
  154. data/lib/tessella/legend.rb +31 -0
  155. data/lib/tessella/lint/alphabet.rb +56 -0
  156. data/lib/tessella/lint/drift.rb +82 -0
  157. data/lib/tessella/lint/fences.rb +51 -0
  158. data/lib/tessella/lint/geometry.rb +120 -0
  159. data/lib/tessella/lint/geometry_checks.rb +224 -0
  160. data/lib/tessella/lint/lines.rb +115 -0
  161. data/lib/tessella/lint/mixing.rb +61 -0
  162. data/lib/tessella/lint/runner.rb +73 -0
  163. data/lib/tessella/lint/shadows.rb +120 -0
  164. data/lib/tessella/lint/target_rows.rb +66 -0
  165. data/lib/tessella/lint.rb +158 -0
  166. data/lib/tessella/meter.rb +133 -0
  167. data/lib/tessella/meter_upright.rb +43 -0
  168. data/lib/tessella/png.rb +456 -0
  169. data/lib/tessella/raster.rb +92 -0
  170. data/lib/tessella/rmagick_reader.rb +52 -0
  171. data/lib/tessella/rule.rb +59 -0
  172. data/lib/tessella/seq.rb +522 -0
  173. data/lib/tessella/sparkline.rb +57 -0
  174. data/lib/tessella/stats.rb +199 -0
  175. data/lib/tessella/table.rb +547 -0
  176. data/lib/tessella/table_cells.rb +225 -0
  177. data/lib/tessella/target.rb +105 -0
  178. data/lib/tessella/text.rb +76 -0
  179. data/lib/tessella/text_forms.rb +150 -0
  180. data/lib/tessella/tree.rb +221 -0
  181. data/lib/tessella/version.rb +5 -0
  182. data/lib/tessella/wave.rb +353 -0
  183. data/lib/tessella/width.rb +122 -0
  184. data/lib/tessella/width_class.rb +77 -0
  185. data/lib/tessella/width_invisible.rb +66 -0
  186. data/lib/tessella/width_invisible_high.rb +60 -0
  187. data/lib/tessella.rb +94 -0
  188. metadata +226 -0
data/doc/charts.md ADDED
@@ -0,0 +1,1370 @@
1
+ # Charts
2
+
3
+ A chart turns numbers into a framed figure: a plot area of character
4
+ cells, the y extremes on its left, the x extremes under it, a title
5
+ above and a legend on the right. There are eleven kinds:
6
+
7
+ | Kind | Draws | Input |
8
+ |-------------|--------------------------------------------|-----------------|
9
+ | `line` | points joined by straight segments | y, or x and y |
10
+ | `scatter` | points, not joined | y, or x and y |
11
+ | `stairs` | a step path between points | y, or x and y |
12
+ | `density` | points, shaded by how many share a cell | y, or x and y |
13
+ | `bar` | one horizontal bar per label | labels, values |
14
+ | `histogram` | counts of samples per bin, as bars | samples |
15
+ | `box` | a box-and-whisker plot per sample set | sets of samples |
16
+ | `stem` | a stem-and-leaf plot, as text | values |
17
+ | `heatmap` | a matrix as a grid of shaded cells | rows of values |
18
+ | `candlestick` | open, high, low, close per x, as candles | x, four prices |
19
+ | `pie` | shares of a whole, as a disc or a ring | labels, values |
20
+
21
+ Most charts on this page are drawn with `--target web`, which uses
22
+ quadrant blocks instead of braille: these pages are read on GitHub,
23
+ where braille is drawn about 1.2 columns wide and a braille chart
24
+ does not line up ([width targets](targets.md#web)). The few that stay
25
+ in braille, because braille is their subject, say so; read them in a
26
+ terminal. Every example on this page is the command's output, byte
27
+ for byte;
28
+ the input files are in [examples/](../examples/), and every command
29
+ runs there.
30
+
31
+ ## From the command line
32
+
33
+ `tessella chart` reads one file, or stdin when no file is given, and
34
+ prints one chart. `--kind` picks the kind; `line` is the default.
35
+
36
+ ```text
37
+ $ tessella --target web chart --title trig --width 30 --height 6 trig.txt
38
+ trig
39
+ ┌──────────────────────────────┐
40
+ 1 │▀▜▄▀▀▀▚▖ ▗▀▀▀▚▟▀▀▀▚ │ sin
41
+ │ ▟▀▙ ▝▖ ▟▘ ▞▀▄ ▀▖ │ cos
42
+ │▞ ▝▚ ▝▖ ▞▘ ▗▞ ▝▖ ▜▖ │
43
+ │▀▀▀▀▀█▀▀▀▀▛▀▀█▀▀▀▀▛▀▀▀▀▀▛▀▀▀▜▛│
44
+ │ ▙ ▝▖▞ ▗▛ ▝▄ ▀│
45
+ -1 │ ▜▄▄▄▛▚▄▄▟▀ ▝▚▄▄▄│
46
+ └──────────────────────────────┘
47
+ 0 10
48
+ ```
49
+
50
+ `trig.txt` is a header row `x sin cos` and 101 rows of numbers. The
51
+ first column is x; each further column is one series, named by the
52
+ header.
53
+
54
+ ### The input format
55
+
56
+ - A line holding a comma splits on commas, one holding a tab on
57
+ tabs, any other on runs of spaces. A CSV or TSV label may therefore
58
+ hold spaces.
59
+ - Blank lines and lines starting with `#` are skipped.
60
+ - A first row that is not all numbers is a header: it names the
61
+ series (line, scatter, stairs, density), the boxes (box) or the two
62
+ sides (back-to-back stem). For `bar`, the first row is a header
63
+ when its value field is not a number.
64
+ - A number keeps its kind: `12` stays an Integer and prints `12`,
65
+ `7.5` stays a Float.
66
+
67
+ What each kind reads:
68
+
69
+ | Kind | Columns |
70
+ |---------------------------------------|-----------------------------------------------------------------|
71
+ | `line`, `scatter`, `stairs`, `density` | one column: y against 1..n; two: x y; more: x, then one series each |
72
+ | `bar` | label, value; or label and one value per series |
73
+ | `histogram` | one column of samples (further columns are ignored) |
74
+ | `box` | one or more columns, one box per column |
75
+ | `stem` | one column; two for the back-to-back form |
76
+ | `heatmap` | a label, then the row's values; a header names the columns |
77
+ | `candlestick` | x, open, high, low, close |
78
+ | `pie` | label, value |
79
+
80
+ ## The kinds
81
+
82
+ ### line, scatter, stairs
83
+
84
+ `line` joins consecutive points; `scatter` draws them alone; `stairs`
85
+ draws a step path. Each series gets the next colour of the cycle
86
+ (used only under `--color`) and, when it has a name, a legend entry.
87
+
88
+ ```text
89
+ $ tessella --target web chart --kind scatter --width 30 --height 6 trig.txt
90
+ ┌──────────────────────────────┐
91
+ 1 │▀▜▄▀▀▀▚▖ ▗▀▀▀▚▟▀▀▀▚ │ sin
92
+ │ ▟▀▙ ▝▖ ▟▘ ▞▀▄ ▀▖ │ cos
93
+ │▞ ▝▚ ▝▖ ▞▘ ▗▞ ▝▖ ▜▖ │
94
+ │▀▀▀▀▀█▀▀▀▀▛▀▀█▀▀▀▀▛▀▀▀▀▀▛▀▀▀▜▛│
95
+ │ ▙ ▝▖▞ ▗▛ ▝▄ ▜│
96
+ -1 │ ▜▄▄▄▛▚▄▄▟▀ ▝▚▄▄▄│
97
+ └──────────────────────────────┘
98
+ 0 10
99
+ ```
100
+
101
+ `stairs` steps after each point by default (`--style post`); `--style
102
+ pre` steps before it:
103
+
104
+ ```text
105
+ $ printf '1 1\n2 3\n3 2\n4 4\n' | \
106
+ tessella --target web chart --kind stairs --style pre \
107
+ --width 20 --height 4
108
+ ┌────────────────────┐
109
+ 4 │ ▛▀▀▀▀▀▀│
110
+ │▛▀▀▀▀▀▜ ▌ │
111
+ │▌ ▐▄▄▄▄▄▄▌ │
112
+ 1 │▌ │
113
+ └────────────────────┘
114
+ 1 4
115
+ ```
116
+
117
+ ### Filled areas
118
+
119
+ `--area` (`area: true`) fills under each line or stairs series, from
120
+ the floor up, as btop draws its graphs. Every pixel column is a bar
121
+ of its own, so a block cell holds two bars of 0 to 2 (a braille cell
122
+ two of 0 to 4), and a peak narrower than a cell still shows. The y axis starts at 0 unless
123
+ `--ylim` says otherwise. `cpu.txt` is 60 load samples between 10 and
124
+ 90:
125
+
126
+ ```text
127
+ $ tessella --target web chart --area --width 30 --height 6 cpu.txt
128
+ ┌──────────────────────────────┐
129
+ 90 │ ▄██▙▄ ▟██▙▖ │
130
+ │ ▗██████▙ ▗██████▙ │
131
+ │▗████████▙▖ ▗████████▙ │
132
+ │▐██████████▖ ▟██████████▙│
133
+ │▐███████████▄ ▗▟████████████│
134
+ 0 │▐█████████████▄▟██████████████│
135
+ └──────────────────────────────┘
136
+ 0 60
137
+ ```
138
+
139
+ The same in braille, finer, and aligned only in a terminal; under
140
+ `--ascii` the fill is drawn in ASCII strokes:
141
+
142
+ ```text
143
+ $ tessella chart --area --canvas braille --width 30 --height 6 cpu.txt
144
+ ┌──────────────────────────────┐
145
+ 90 │ ⢀⣴⣾⣿⣷⣄ ⢀⣴⣿⣿⣶⣄ │
146
+ │ ⢠⣾⣿⣿⣿⣿⣿⣧⡀ ⣠⣿⣿⣿⣿⣿⣿⣦ │
147
+ │⢠⣿⣿⣿⣿⣿⣿⣿⣿⣷⡀ ⣰⣿⣿⣿⣿⣿⣿⣿⣿⣧⡀│
148
+ │⢸⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⡄ ⣴⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷│
149
+ │⢸⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣄ ⢀⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿│
150
+ 0 │⢸⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⣶⣶⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿│
151
+ └──────────────────────────────┘
152
+ 0 60
153
+ ```
154
+
155
+ ```text
156
+ $ tessella chart --area --ascii --width 30 --height 6 cpu.txt
157
+ +------------------------------+
158
+ 90 | .+###- .+###_ |
159
+ | _######+ -######- |
160
+ |-########+. -########+ |
161
+ |+##########. +##########+|
162
+ |+###########- .#############|
163
+ 0 |+#############-###############|
164
+ +------------------------------+
165
+ 0 60
166
+ ```
167
+
168
+ `--canvas eighths` fills one bar per character instead, in eighths
169
+ of a cell, `▁` to `█` (`▏` to `█` sideways): coarser across than
170
+ braille, finer up. It is for areas only (`--canvas eighths` without
171
+ `--area` is a usage error) and takes no `--threshold`.
172
+
173
+ ```text
174
+ $ tessella chart --area --canvas eighths --width 30 --height 6 cpu.txt
175
+ ┌──────────────────────────────┐
176
+ 90 │ ▅▇█▇▄ ▁▅▇█▇▄ │
177
+ │ ▂██████▇▁ ▃██████▆ │
178
+ │▃█████████▂ ▄█████████▁│
179
+ │███████████▃ ▅███████████│
180
+ │████████████▅▁ ▁▆████████████│
181
+ 0 │██████████████▆▆██████████████│
182
+ └──────────────────────────────┘
183
+ 0 60
184
+ ```
185
+
186
+ With `--ylim` the floor moves up, so the variation fills the height:
187
+
188
+ ```text
189
+ $ tessella --target web chart --area --ylim 30,100 \
190
+ --width 30 --height 4 cpu.txt
191
+ ┌──────────────────────────────┐
192
+ 100 │ ▄▄▖ ▄▄▖ │
193
+ │ ▄█████▖ ▟████▙▖ │
194
+ │▗▟███████▙ ▗████████▄ │
195
+ 30 │▐█████████▙▖ ▄██████████▙│
196
+ └──────────────────────────────┘
197
+ 0 60
198
+ ```
199
+
200
+ Under `--color` a filled cell is coloured by its height, bottom row
201
+ first, from a gradient: `--gradient C,C,...` (`gradient: [...]`), by
202
+ default green, yellow, red in equal bands. A gradient whose colours
203
+ are all `#rrggbb` blends smoothly from the bottom row to the top
204
+ instead of in bands. `--gradient none` (`gradient: false`) keeps the
205
+ series' own colour. Without `--color` a gradient changes nothing and
206
+ the glyphs are plain.
207
+
208
+ `--area` is for line and stairs only, and `--gradient` needs
209
+ `--area`; anything else is a usage error:
210
+
211
+ ```text
212
+ $ tessella chart --gradient red,blue cpu.txt
213
+ tessella chart: gradient: needs area: true
214
+ Usage: tessella chart [options] [file]
215
+ ```
216
+
217
+ ### density
218
+
219
+ `density` draws the points on its own canvas, where a cell's shade
220
+ grows with the number of points that fall in it (` ░▒▓█`, or `
221
+ .:+#` under `--ascii`). It ignores `--canvas`, and draws no x = 0
222
+ or y = 0 axis line.
223
+
224
+ ```text
225
+ $ tessella chart --kind density --width 30 --height 6 trig.txt
226
+ ┌──────────────────────────────┐
227
+ 1 │▓▒▓▓▒▒▓░ ▒▒▓▒▒█▒▒▓▒ │ sin
228
+ │ ▒▒▒ ▒▒ ▒░ ▒▒▒ ▒░ │ cos
229
+ │▒ ░▒ ░▒ ▒░ ░▒ ░▒ ▒░ │
230
+ │░ ░▒ ▒░ ▒░ ▒ ░▒ ▒░│
231
+ │ ▓ ▒▒▓ ░▒ ▒▒ ▒│
232
+ -1 │ ▒▒▓▒▓▓▒▒▓▒ ░▒▓▒▓│
233
+ └──────────────────────────────┘
234
+ 0 10
235
+ ```
236
+
237
+ ### bar
238
+
239
+ One bar per row, labelled on the left, with its value after it.
240
+
241
+ ```text
242
+ $ tessella chart --kind bar --width 30 langs.csv
243
+ ┌ ┐
244
+ ruby ┤██████████▍··············· 12
245
+ c ┤██████████████████████████ 30
246
+ rust ┤██████▌··················· 7.5
247
+ └ ┘
248
+ ```
249
+
250
+ The default look draws the last, fractional cell in eighths
251
+ (`▏▎▍▌▋▊▉`) and fills the rest of each bar's width with a `·` track,
252
+ so a bar shows both its fraction and its share. Three options change
253
+ it: `--fill G` the bar glyph, `--no-eighths` whole cells only, and
254
+ `--track G` the track glyph (`--track none` for no track):
255
+
256
+ ```text
257
+ $ tessella chart --kind bar --width 30 \
258
+ --fill ■ --no-eighths --track none langs.csv
259
+ ┌ ┐
260
+ ruby ┤■■■■■■■■■■ 12
261
+ c ┤■■■■■■■■■■■■■■■■■■■■■■■■■■ 30
262
+ rust ┤■■■■■■■ 7.5
263
+ └ ┘
264
+ ```
265
+
266
+ Values may be negative. The bar area then splits at a zero column,
267
+ each side as wide as its share of the values' span; negative bars
268
+ grow left, and a negative bar's fractional end is a half cell (`▐`):
269
+
270
+ ```text
271
+ $ tessella chart --kind bar --width 30 neg.csv
272
+ ┌ ┐
273
+ a ┤█████████████████········· -6
274
+ b ┤·················█████████ 3
275
+ c ┤·························· 0
276
+ └ ┘
277
+ ```
278
+
279
+ #### Several series
280
+
281
+ A row with more than one value draws one bar per series; the header
282
+ names the series, and a legend right of the frame shows each one's
283
+ fill. `hosts.csv` holds a load per host for three resources:
284
+
285
+ ```text
286
+ $ cat hosts.csv
287
+ host,cpu,io,net
288
+ web,40,15,20
289
+ db,25,50,5
290
+ cache,10,5,30
291
+ ```
292
+
293
+ By default the bars are grouped: each label gets one bar per series,
294
+ with a blank row between labels.
295
+
296
+ ```text
297
+ $ tessella chart --kind bar --width 30 hosts.csv
298
+ ┌ ┐
299
+ web ┤████████████████████▊····· 40 █ cpu
300
+ ┤▓▓▓▓▓▓▓▓·················· 15 ▓ io
301
+ ┤▒▒▒▒▒▒▒▒▒▒················ 20 ▒ net
302
+ ┤
303
+ db ┤█████████████············· 25
304
+ ┤▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ 50
305
+ ┤▒▒▒······················· 5
306
+ ┤
307
+ cache ┤█████▎···················· 10
308
+ ┤▓▓▓······················· 5
309
+ ┤▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒·········· 30
310
+ └ ┘
311
+ ```
312
+
313
+ `--stacked` (`stacked: true`) sets them end to end in one bar, its
314
+ total as the label:
315
+
316
+ ```text
317
+ $ tessella chart --kind bar --stacked --width 30 hosts.csv
318
+ ┌ ┐
319
+ web ┤█████████████▓▓▓▓▓▒▒▒▒▒▒·· 75 █ cpu
320
+ db ┤████████▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▒ 80 ▓ io
321
+ cache ┤███▓▓▒▒▒▒▒▒▒▒▒▒··········· 45 ▒ net
322
+ └ ┘
323
+ ```
324
+
325
+ Both stand upright with `--orientation vertical`:
326
+
327
+ ```text
328
+ $ tessella chart --kind bar --stacked --orientation vertical \
329
+ --width 20 --height 6 hosts.csv
330
+ ┌────────────────────┐
331
+ 80 │▒▒▒▒▒▒ ▓▓▓▓▓▓ │ █ cpu
332
+ │▒▒▒▒▒▒ ▓▓▓▓▓▓ │ ▓ io
333
+ │▓▓▓▓▓▓ ▓▓▓▓▓▓ │ ▒ net
334
+ │██████ ▓▓▓▓▓▓ ▒▒▒▒▒▒│
335
+ │██████ ██████ ▒▒▒▒▒▒│
336
+ 0 │██████ ██████ ██████│
337
+ └────────────────────┘
338
+ web db cache
339
+ ```
340
+
341
+ With more series than labels the legend no longer fits beside the
342
+ bars, so it goes under the frame, wrapped to the plot's width (for
343
+ upright columns,
344
+ whose names take that line, on the x-label line, unless `--xlabel`
345
+ is given). Here the first two hosts only:
346
+
347
+ ```text
348
+ $ head -3 hosts.csv | tessella chart --kind bar --stacked --width 30
349
+ ┌ ┐
350
+ web ┤█████████████▓▓▓▓▓▒▒▒▒▒▒·· 75
351
+ db ┤████████▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▒ 80
352
+ └ ┘
353
+ █ cpu ▓ io ▒ net
354
+ ```
355
+
356
+ Each series has a fill of its own, from twelve: `█ ▓ ▒ ░ ╳ ╱ ╲ ∴ ● ○
357
+ ◆ ·`, or `# = + @ * : . o % & ~ x` under `--ascii`. So a chart holds
358
+ at most 12 series; a 13th is refused. Only a `█` series ends in an
359
+ eighth. Bars of several series need values of 0 or more.
360
+
361
+ Under `--color`, where two stacked series meet inside a cell, that
362
+ cell is split to the eighth: an eighths glyph in the first series'
363
+ colour on the second's as background, so the boundary falls exactly
364
+ where the values put it. Without colour the cell keeps the plain
365
+ glyph of one series.
366
+
367
+ ```text
368
+ $ tessella chart --kind bar --stacked --ascii --width 30 hosts.csv
369
+ + +
370
+ web |#############=====++++++.. 75 # cpu
371
+ db |########================++ 80 = io
372
+ cache |###==++++++++++........... 45 + net
373
+ + +
374
+ ```
375
+
376
+ In Ruby, the values are an Array of Arrays, one per label, and
377
+ `series:` names them:
378
+
379
+ ```ruby
380
+ Tessella.bar(%w[web db cache], [[40, 15, 20], [25, 50, 5], [10, 5, 30]],
381
+ series: %w[cpu io net], stacked: true, width: 30)
382
+ ```
383
+
384
+ ### histogram
385
+
386
+ `histogram` bins one column of samples and draws the counts as bars,
387
+ with the same bar options. `--bins N` sets the number of bins (by
388
+ default Sturges' rule, ⌈log₂ n⌉ + 1, as unicode_plot picks it);
389
+ `--closed left` (the default) bins as `[a, b)`, `--closed right` as
390
+ `(a, b]`. Every sample lands in exactly one bin.
391
+
392
+ ```text
393
+ $ tessella chart --kind histogram --width 30 --bins 8 normal.txt
394
+ ┌ ┐
395
+ [-3.0, -2.0) ┤██▉······················· 8
396
+ [-2.0, -1.0) ┤██████████▏··············· 28
397
+ [-1.0, 0.0) ┤██████████████████████████ 72
398
+ [ 0.0, 1.0) ┤██████████████████████···· 61
399
+ [ 1.0, 2.0) ┤██████████▏··············· 28
400
+ [ 2.0, 3.0) ┤█▏························ 3
401
+ └ ┘
402
+ Frequency
403
+ ```
404
+
405
+ `--bins` is a request: the edges are rounded to nice values, which
406
+ can give fewer bins than asked, as here.
407
+
408
+ ### box
409
+
410
+ One box per column: whiskers from minimum to maximum, the box from
411
+ the first to the third quartile, the median inside it. The axis under
412
+ the boxes shows the minimum, the middle and the maximum of the range.
413
+
414
+ ```text
415
+ $ tessella chart --kind box --width 40 samples.txt
416
+ ┌ ┐
417
+ ╷ ┬─┐ ╷
418
+ a ├─│ ├─┤
419
+ ╵ ┴─┘ ╵
420
+ ╷┌┬┐ ╷
421
+ b ├┤│├─────┤
422
+ ╵└┴┘ ╵
423
+ ╷ ┌┬┐ ╷
424
+ c ├──┤│├──────────────────────────────┤
425
+ ╵ └┴┘ ╵
426
+ └ ┘
427
+ 0 15 30
428
+ ```
429
+
430
+ The box draws its own open frame; it takes no `border:`.
431
+
432
+ ### stem
433
+
434
+ A stem-and-leaf plot is text, not a canvas: one line per stem, its
435
+ leaves after the bar, and a key under it. `--scale N` sets what one
436
+ stem step is worth (10 by default): at scale 10, `2 | 37` reads 23
437
+ and 27.
438
+
439
+ ```text
440
+ $ tessella chart --kind stem stem.txt
441
+ 1 | 25
442
+ 2 | 1337
443
+ 3 | 148
444
+ 4 | 57
445
+ 5 | 2
446
+ Key: 1|0 = 10
447
+ The decimal is 1 digit(s) to the right of |
448
+ ```
449
+
450
+ Two columns give the back-to-back form, the first column's leaves
451
+ read leftwards:
452
+
453
+ ```text
454
+ $ tessella chart --kind stem ages.txt
455
+ 52 | 1 | 8
456
+ 731 | 2 | 25
457
+ 84 | 3 | 136
458
+ 5 | 4 | 14
459
+ Key: 1|0 = 10
460
+ The decimal is 1 digit(s) to the right of |
461
+ ```
462
+
463
+ A plot that would need more than 1000 stems is refused: raise
464
+ `--scale`.
465
+
466
+ ### heatmap
467
+
468
+ A heatmap draws a matrix as a grid of cells, each shaded by its value
469
+ along ` ░▒▓█`, with the row names on the left and a key on the right
470
+ giving the range. `week.txt` is a load per day and per three hours.
471
+ Its first row names the columns because `0h`, `3h` are not numbers;
472
+ a header of `00 03 06` would be read as a row of data, by the rule of
473
+ [the input format](#the-input-format):
474
+
475
+ ```text
476
+ $ head -3 week.txt
477
+ day 0h 3h 6h 9h 12h 15h 18h 21h
478
+ mon 10 14 18 72 76 63 67 21
479
+ tue 23 10 14 68 72 76 63 17
480
+ ```
481
+
482
+ ```text
483
+ $ tessella chart --kind heatmap week.txt
484
+ ┌────────────────┐
485
+ mon │░░░░░░████▓▓██░░│ █ 76
486
+ tue │░░░░░░██████▓▓░░│
487
+ wed │░░░░░░▓▓██████░░│
488
+ thu │░░░░░░▓▓▓▓████░░│
489
+ fri │░░░░░░██▓▓▓▓██░░│
490
+ sat │ ▒▒▒▒▒▒▒▒ │
491
+ sun │ ▒▒▒▒▒▒▒▒ │ 0
492
+ └────────────────┘
493
+ 0h 21h
494
+ ```
495
+
496
+ A cell is `--cell-width` columns wide, 2 by default (near square in a
497
+ terminal). The column names go under the frame when each is narrower
498
+ than a cell, else only the first and last, at the two ends, as in
499
+ the map above. At four columns a cell, every name fits:
500
+
501
+ ```text
502
+ $ tessella chart --kind heatmap --cell-width 4 week.txt
503
+ ┌────────────────────────────────┐
504
+ mon │░░░░░░░░░░░░████████▓▓▓▓████░░░░│ █ 76
505
+ tue │░░░░░░░░░░░░████████████▓▓▓▓░░░░│
506
+ wed │░░░░░░░░░░░░▓▓▓▓████████████░░░░│
507
+ thu │░░░░░░░░░░░░▓▓▓▓▓▓▓▓████████░░░░│
508
+ fri │░░░░░░░░░░░░████▓▓▓▓▓▓▓▓████░░░░│
509
+ sat │ ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ │
510
+ sun │ ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ │ 0
511
+ └────────────────────────────────┘
512
+ 0h 3h 6h 9h 12h 15h 18h 21h
513
+ ```
514
+
515
+ Under `--ascii` the ramp is ` .:-=+*#%@`. Under `--color` each cell is
516
+ a `█` coloured along a colormap, a viridis-like blend by default, or
517
+ `--colormap C,C,...` stops in any [colour form](#colour); the key then
518
+ says "max" and "min" in words. `--min` and `--max` fix the range, so
519
+ maps drawn apart share one scale. A map of one row gives its key as
520
+ `lo..hi`:
521
+
522
+ ```text
523
+ $ printf 'x 1 2 3 4\n' | tessella chart --kind heatmap
524
+ ┌────────┐
525
+ x │ ░░▓▓██│ 1..4
526
+ └────────┘
527
+ ```
528
+
529
+ The matrix sets the size, so `--width` and `--height` are refused. In
530
+ Ruby:
531
+
532
+ ```ruby
533
+ Tessella.heatmap(matrix, xlabels: nil, ylabels: nil, cell_width: 2,
534
+ colormap: nil, min: nil, max: nil, **opts)
535
+ ```
536
+
537
+ ### candlestick
538
+
539
+ A candlestick chart draws, per x, the range a price moved in (the
540
+ wick, `│`, from low to high) and where it opened and closed (the
541
+ body). An up candle, closing at or above its open, is `█`; a down
542
+ one `▒`; one that closes where it opened, `─`. Under `--color` up is
543
+ green and down red. `ohlc.txt` is a week of prices, x as ISO dates:
544
+
545
+ ```text
546
+ $ cat ohlc.txt
547
+ date open high low close
548
+ 2026-11-02 100 104 98 103
549
+ 2026-11-03 103 107 101 102
550
+ 2026-11-04 102 103 95 96
551
+ 2026-11-05 96 99 94 98
552
+ 2026-11-06 98 105 97 104
553
+ 2026-11-09 104 106 102 104
554
+ ```
555
+
556
+ ```text
557
+ $ tessella chart --kind candlestick --xdate --width 30 --height 8 ohlc.txt
558
+ ┌─────────────────────────────┐
559
+ 110 │ │
560
+ │ │ │ │
561
+ │████ ▒▒▒▒ │ ████ ────│
562
+ │████ ▒▒▒▒ ▒▒▒▒ ████ │ │
563
+ │████ ▒▒▒▒ ████ ████ │
564
+ │ ▒▒▒▒ ████ │ │
565
+ │ │ │
566
+ 90 │ │
567
+ └─────────────────────────────┘
568
+ 2026-11-02 2026-11-09
569
+ ```
570
+
571
+ The candles share the width, each as wide as its share allows with a
572
+ blank column between (one column each and no gap when they do not
573
+ fit), the wick in the middle column. The first and last x go under
574
+ the frame, as dates with `--xdate` (formatted as on a
575
+ [date axis](#dates-on-the-x-axis)). Under `--ascii` an up candle is
576
+ `#`, a down one `=`:
577
+
578
+ ```text
579
+ $ tessella chart --kind candlestick --xdate --ascii \
580
+ --width 30 --height 8 ohlc.txt
581
+ +-----------------------------+
582
+ 110 | |
583
+ | | | |
584
+ |#### ==== | #### ----|
585
+ |#### ==== ==== #### | |
586
+ |#### ==== #### #### |
587
+ | ==== #### | |
588
+ | | |
589
+ 90 | |
590
+ +-----------------------------+
591
+ 2026-11-02 2026-11-09
592
+ ```
593
+
594
+ A low above the open or the close, or a high below them, is refused.
595
+ In Ruby:
596
+
597
+ ```ruby
598
+ Tessella.candlestick(x, open:, high:, low:, close:, **opts)
599
+ ```
600
+
601
+ ### pie
602
+
603
+ A pie draws each value as a slice of a disc, clockwise from the top,
604
+ with a legend giving each label's share in whole percents that add
605
+ up to 100. It has no frame and no axes.
606
+
607
+ ```text
608
+ $ tessella chart --kind pie langs.csv
609
+ ▒▒▒▒▒█████
610
+ ▒▒▒▒▒▒▒▒████████
611
+ ▒▒▒▒▒▒▒▒▒▒██████████
612
+ ▓▓▓▓▒▒▒▒▒▒▒███████████
613
+ ▓▓▓▓▓▓▓▓▒▒▒▒████████████ █ ruby 24 %
614
+ ▓▓▓▓▓▓▓▓▓▓▓▒████████████ ▓ c 61 %
615
+ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▒ rust 15 %
616
+ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓
617
+ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓
618
+ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓
619
+ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓
620
+ ▓▓▓▓▓▓▓▓▓▓
621
+ ```
622
+
623
+ `--donut` (`donut: true`) cuts out the middle:
624
+
625
+ ```text
626
+ $ tessella chart --kind pie --donut langs.csv
627
+ ▒▒▒▒▒█████
628
+ ▒▒▒▒▒▒▒▒████████
629
+ ▒▒▒▒▒▒▒▒▒▒██████████
630
+ ▓▓▓▓▒▒▒▒ ████████
631
+ ▓▓▓▓▓▓▓ ███████ █ ruby 24 %
632
+ ▓▓▓▓▓▓ ██████ ▓ c 61 %
633
+ ▓▓▓▓▓▓ ▓▓▓▓▓▓ ▒ rust 15 %
634
+ ▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓
635
+ ▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓
636
+ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓
637
+ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓
638
+ ▓▓▓▓▓▓▓▓▓▓
639
+ ```
640
+
641
+ Each slice has a fill of its own, the twelve bars use (`█ ▓ ▒ ░ ╳ ╱
642
+ ╲ ∴ ● ○ ◆ ·`, or `# = + @ * : . o % & ~ x` under `--ascii`), and a
643
+ colour under `--color`; a 13th slice is refused. `--height` sizes
644
+ the disc, 12 rows by default; its width follows, a cell being about
645
+ twice as tall as wide. With more slices than the disc has rows, the
646
+ legend goes under it, wrapped to the disc's width or 40 columns,
647
+ whichever is wider. `os.csv` has nine slices:
648
+
649
+ ```text
650
+ $ tessella chart --kind pie --height 6 os.csv
651
+ ╳╱∴███
652
+ ░░░╳╲█████
653
+ ▒▒▒░░╳██████
654
+ ▒▒▒▒▒▓▓█████
655
+ ▒▒▓▓▓▓▓███
656
+ ▓▓▓▓▓▓
657
+ █ linux 41 % ▓ macos 22 %
658
+ ▒ windows 18 % ░ freebsd 9 %
659
+ ╳ openbsd 4 % ╱ netbsd 2 % ╲ haiku 2 %
660
+ ∴ plan9 1 % ● qnx 1 %
661
+ ```
662
+
663
+ ```text
664
+ $ tessella chart --kind pie --ascii --height 8 langs.csv
665
+ ++++####
666
+ ++++++######
667
+ ===++++####### # ruby 24 %
668
+ =======+######## = c 61 %
669
+ ================ + rust 15 %
670
+ ==============
671
+ ============
672
+ ========
673
+ ```
674
+
675
+ By default a cell belongs whole to one slice, so the disc's edge is
676
+ stepped. `--rim smooth` (`rim: :smooth`) draws each cell the disc, or
677
+ a donut's hole, only partly covers as the shape glyph that fits it
678
+ best (`◢◣◤◥`, halves, quadrants, quarter blocks), in its slice's
679
+ colour:
680
+
681
+ ```text
682
+ $ tessella chart --kind pie --donut --rim smooth --height 8 langs.csv
683
+ ▁▂▄▆█▒██▆▄▂▁
684
+ ▗█▒▒▒▒▒██████▖
685
+ ◢▓▓▓█▀▔ ▔▀████◣ █ ruby 24 %
686
+ █▓▓▓▘ ▝████ ▓ c 61 %
687
+ █▓▓▓▖ ▗▓▓▓█ ▒ rust 15 %
688
+ ◥▓▓▓█▄▁ ▁▄█▓▓▓◤
689
+ ▝█▓▓▓▓▓▓▓▓▓▓█▘
690
+ ▔🮂▀🮅█▓▓█🮅▀🮂▔
691
+ ```
692
+
693
+ The rim glyphs are solid, so without colour they draw the edge as an
694
+ outline in the nearest slice's fill. `--rim basic` leaves out `🮊 🮂
695
+ 🮅`, which older fonts lack. `--rim cells` is the default; `--ascii`
696
+ refuses the other two, and only a pie takes `--rim`.
697
+
698
+ A pie takes no `--width`, `--xlim`, `--ylim`, `--canvas` or
699
+ `--xscale` (usage errors, exit 2), and reads exactly a label and a
700
+ value a row:
701
+
702
+ ```text
703
+ $ tessella chart --kind pie hosts.csv
704
+ tessella chart: row 1 has 4 columns: pie reads label value
705
+ ```
706
+
707
+ In Ruby:
708
+
709
+ ```ruby
710
+ Tessella.pie(labels, values, donut: false, rim: :cells, **opts)
711
+ ```
712
+
713
+ ## Orientation
714
+
715
+ `--orientation horizontal|vertical` (`orientation:`) is the direction
716
+ the value axis runs. Every kind but stem takes it; bar, histogram and
717
+ box are horizontal by default, the plotted kinds vertical.
718
+
719
+ ### Upright bars
720
+
721
+ `--orientation vertical` draws a bar chart as columns rising from a
722
+ floor, the value axis on the left from 0 to the largest value. The
723
+ names go under their columns when each fits, else the first and last
724
+ name go at the two ends:
725
+
726
+ ```text
727
+ $ tessella chart --kind bar --orientation vertical --width 20 --height 5 \
728
+ langs.csv
729
+ ┌────────────────────┐
730
+ 30 │ ██████ │
731
+ │ ██████ │
732
+ │ ██████ │
733
+ │██████ ██████ ▂▂▂▂▂▂│
734
+ 0 │██████ ██████ ██████│
735
+ └────────────────────┘
736
+ ruby c rust
737
+ ```
738
+
739
+ A histogram stands its bins up the same way, with its axis's two ends
740
+ under them:
741
+
742
+ ```text
743
+ $ tessella chart --kind histogram --orientation vertical \
744
+ --width 24 --height 6 normal.txt
745
+ ┌───────────────────────┐
746
+ 72 │ ███ ▁▁▁ │
747
+ │ ███ ███ │
748
+ Frequency │ ███ ███ │
749
+ │ ▃▃▃ ███ ███ ▃▃▃ │
750
+ │ ███ ███ ███ ███ │
751
+ 0 │▅▅▅ ███ ███ ███ ███ ▂▂▂│
752
+ └───────────────────────┘
753
+ -3.0 3.0
754
+ ```
755
+
756
+ Upright bars need values of 0 or more. `--fill` sets the glyph of
757
+ their full cells; `--track` is for bars across only, and is refused
758
+ here. A value above 0 always draws at least one step, so a bin that
759
+ holds anything never looks empty, even in ASCII, where a step is a
760
+ whole row:
761
+
762
+ ```text
763
+ $ tessella chart --kind histogram --orientation vertical --ascii \
764
+ --width 18 --height 4 normal.txt
765
+ +-----------------+
766
+ 72 | ## |
767
+ Frequency | ## ## |
768
+ | ## ## ## ## |
769
+ 0 |## ## ## ## ## ##|
770
+ +-----------------+
771
+ -3.0 3.0
772
+ ```
773
+
774
+ ```text
775
+ $ tessella chart --kind bar --orientation vertical neg.csv
776
+ tessella chart: a vertical eighths bar chart needs values of 0 or more
777
+ Usage: tessella chart [options] [file]
778
+ ```
779
+
780
+ ### Upright boxes
781
+
782
+ A vertical box stands three columns wide, its whiskers running up and
783
+ down: `╶┬╴` at the maximum, `┌┴┐` at the third quartile, `├─┤` at the
784
+ median, `└┬┘` at the first quartile, `╶┴╴` at the minimum. The value
785
+ axis on the left shows its two ends and middle (`--ylim` sets the
786
+ range), the names go under the boxes, and where the median shares a
787
+ row with an edge of the box, the median is drawn.
788
+
789
+ ```text
790
+ $ tessella chart --kind box --orientation vertical --height 10 samples.txt
791
+ ┌────────────┐
792
+ 30 │ ╶┬╴ │
793
+ │ │ │
794
+ │ │ │
795
+ │ │ │
796
+ 15 │ │ │
797
+ │ │ │
798
+ │ ╶┬╴ │ │
799
+ │ │ ├─┤ │
800
+ │├─┤ ├─┤ ╶┴╴ │
801
+ 0 │╶┴╴ │
802
+ └────────────┘
803
+ a b c
804
+ ```
805
+
806
+ ### Sideways plots
807
+
808
+ For line, scatter, stairs and density, `--orientation horizontal`
809
+ swaps the axes: values run along the horizontal axis and x runs up,
810
+ so a horizontal chart of (x, y) is the vertical chart of (y, x).
811
+ `--xlim`, `--ylim`, `--xlabel` and `--ylabel` keep their meaning and
812
+ move with their axis; an area fills from the left edge, its gradient
813
+ running left to right.
814
+
815
+ ```text
816
+ $ tessella --target web chart --orientation horizontal --area \
817
+ --width 24 --height 8 cpu.txt
818
+ ┌────────────────────────┐
819
+ 60 │██████████████▙▄▄▄▄▄▄ │
820
+ │████████████████████████│
821
+ │█████████████████▛▀▀▀▀▘ │
822
+ │████▛▀▀▀▀▀▘ │
823
+ │███▙▄▄▄▄ │
824
+ │███████████████▄▄▄▄▖ │
825
+ │███████████████████████▙│
826
+ 0 │█████████████████▀▀▀▀▀▘ │
827
+ └────────────────────────┘
828
+ 0 90
829
+ ```
830
+
831
+ ## Bar glyphs
832
+
833
+ `--blocks` (`blocks:`) picks the glyphs bars and histograms are built
834
+ of:
835
+
836
+ | Blocks | Upright | Across |
837
+ |-----------|------------------------------------|------------------------------------|
838
+ | `eighths` | `▁`…`█`, 8 steps a cell (default) | `▏`…`█`, 8 steps a cell (default) |
839
+ | `half` | `▄` `█`, one bar a column | `▌` `█`, one bar a row |
840
+ | `block` | quadrants, two bars a column | quadrants, two bars a row |
841
+ | `braille` | dots, two bars a column | dots, four bars a row |
842
+ | `sextant` | sextants, two bars a column | sextants, three bars a row |
843
+ | `octant` | octants, two bars a column | octants, four bars a row |
844
+
845
+ `quadrant` is another name for `block`. The finer glyphs pack more
846
+ bars into the same width; across, a text row holds as many bars as
847
+ its cell has pixel rows, and is named by its first and last bar,
848
+ `ruby..c`. (The braille example below is aligned only in a
849
+ terminal.) Every glyph set but `eighths` draws one series only (bars
850
+ of [several series](#several-series) need `eighths`), and `sextant`
851
+ and `octant` need a font that has them (see [canvases](#canvases)).
852
+
853
+ ```text
854
+ $ tessella chart --kind bar --orientation vertical --blocks braille \
855
+ --width 20 --height 5 langs.csv
856
+ ┌────────────────────┐
857
+ 30 │ ⢸⣿⣿⣿⣿⣿⡇ │
858
+ │ ⢸⣿⣿⣿⣿⣿⡇ │
859
+ │ ⢸⣿⣿⣿⣿⣿⡇ │
860
+ │⣿⣿⣿⣿⣿⣿⢸⣿⣿⣿⣿⣿⡇⣀⣀⣀⣀⣀⣀ │
861
+ 0 │⣿⣿⣿⣿⣿⣿⢸⣿⣿⣿⣿⣿⡇⣿⣿⣿⣿⣿⣿ │
862
+ └────────────────────┘
863
+ ruby rust
864
+ ```
865
+
866
+ ```text
867
+ $ tessella chart --kind bar --blocks block --width 30 langs.csv
868
+ ┌──────────────────────────────┐
869
+ ruby..c │████████████▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄│
870
+ rust │▀▀▀▀▀▀▀▘ │
871
+ └──────────────────────────────┘
872
+ 0 30
873
+ ```
874
+
875
+ ```text
876
+ $ tessella chart --kind histogram --blocks half --width 24 normal.txt
877
+ ┌────────────────────────┐
878
+ -3.0..-2.0 │██▌ │
879
+ -2.0..-1.0 │█████████▌ │
880
+ -1.0..0.0 │████████████████████████│
881
+ 0.0..1.0 │████████████████████▌ │
882
+ 1.0..2.0 │█████████▌ │
883
+ 2.0..3.0 │█ │
884
+ └────────────────────────┘
885
+ 0 72
886
+ Frequency
887
+ ```
888
+
889
+ Every glyph set but the default `eighths` needs values of 0 or more,
890
+ and `braille` and `block` take no `--ascii`, `--fill`,
891
+ `--no-eighths` or `--track`. As upright, a value above 0 draws at
892
+ least one step (a dot, a quadrant, a half cell); only the default
893
+ eighths bars across round as unicode_plot does, so a small value can
894
+ draw nothing there.
895
+
896
+ A refused combination is a usage error:
897
+
898
+ ```text
899
+ $ tessella chart --kind bar --blocks braille --ascii langs.csv
900
+ tessella chart: blocks: braille conflicts with ascii: true
901
+ Usage: tessella chart [options] [file]
902
+ ```
903
+
904
+ ## Canvases
905
+
906
+ The line, scatter and stairs kinds draw on a canvas, a grid of cells
907
+ each holding a few pixels. `--canvas` picks it:
908
+
909
+ | Canvas | Pixels per cell | Glyphs |
910
+ |-----------|-----------------|------------------------------|
911
+ | `braille` | 2 × 4 | braille patterns (default) |
912
+ | `block` | 2 × 2 | quadrant blocks `▘▝▀▖▌▞▛…` (also named `quadrant`) |
913
+ | `half` | 1 × 2 | half blocks `▀ ▄ █` |
914
+ | `sextant` | 2 × 3 | teletext sextants (Unicode 13) |
915
+ | `octant` | 2 × 4 | octants (Unicode 16) |
916
+ | `eighths` | 1 × 8 | `▁`…`█`, for `--area` only |
917
+ | `dot` | 1 × 2 | `'` `.` `:` |
918
+ | `ascii` | 3 × 3 | ASCII strokes `- \| / \ "`… |
919
+ | `density` | 1 × 2 | shades by hit count |
920
+
921
+ `sextant` and `octant` draw solid blocks where braille draws dots.
922
+ Their glyphs are recent: sextants are Unicode 13 (2020), octants
923
+ Unicode 16 (2024), drawn only by the newest terminals (Ghostty and its
924
+ like), so this page shows no octant render. Check the reader's font
925
+ before using either.
926
+
927
+ ```text
928
+ $ tessella chart --canvas sextant --width 24 --height 5 trig.txt
929
+ ┌────────────────────────┐
930
+ 1 │🬂🬪🬔🬂🬂🬩 🬞🬜🬂🬂🬧🬜🬂🬂🬩 │ sin
931
+ │🬦🬀🬈🬏 🬪 🬕 🬞🬆🬉🬓 🬧🬏 │ cos
932
+ │🬌🬋🬋🬎🬚🬋🬋🬪🬋🬋🬋🬜🬋🬋🬩🬌🬋🬋🬌🬚🬋🬋🬪🬚│
933
+ │ 🬊🬏 🬪🬞🬔 🬦🬄 🬈🬏 🬈│
934
+ -1 │ 🬉🬱🬭🬵🬎🬭🬭🬖🬀 🬊🬢🬭🬭│
935
+ └────────────────────────┘
936
+ 0 10
937
+ ```
938
+
939
+ Braille gives the finest line, but only a terminal keeps it aligned;
940
+ block lines up on the web too; ASCII survives any font:
941
+
942
+ ```text
943
+ $ tessella chart --canvas braille --width 24 --height 5 trig.txt
944
+ ┌────────────────────────┐
945
+ 1 │⠉⢢⡞⠉⠉⢢ ⢀⠔⠉⠉⢳⡔⠉⠉⢦ │ sin
946
+ │⢠⠃⠱⡀ ⢣ ⡎ ⢠⠊⠘⡄ ⠱⡀ │ cos
947
+ │⠧⠤⠤⠵⡤⠤⠤⢧⠤⠤⠤⡼⠤⠤⢤⠧⠤⠤⠵⡤⠤⠤⠵⡤│
948
+ │ ⠱⡀ ⢣⢀⠜ ⢠⠃ ⠱⡀ ⠱│
949
+ -1 │ ⠘⣄⣀⡠⠻⣄⣀⡴⠃ ⠙⢄⣀⣠│
950
+ └────────────────────────┘
951
+ 0 10
952
+ ```
953
+
954
+
955
+ ```text
956
+ $ tessella chart --canvas block --width 24 --height 5 trig.txt
957
+ ┌────────────────────────┐
958
+ 1 │▀▚▛▀▀▚ ▗▞▀▀▜▞▀▀▙ │ sin
959
+ │▗▘▜▖ ▚ ▛ ▗▀▝▖ ▜▖ │ cos
960
+ │▙▄▄█▄▄▄▙▄▄▄▟▄▄▄▙▄▄█▄▄▄█▄│
961
+ │ ▜▖ ▚▗▞ ▗▘ ▜▖ ▜│
962
+ -1 │ ▝▄▄▄▜▄▄▟▘ ▀▄▄▄│
963
+ └────────────────────────┘
964
+ 0 10
965
+ ```
966
+
967
+ <!-- tessella: allow-mixed -->
968
+ ```text
969
+ $ tessella chart --canvas ascii --width 24 --height 5 trig.txt
970
+ ┌────────────────────────┐
971
+ 1 │"\/""\ ./""\/""\ │ sin
972
+ │|"-. \ / _--| \. │ cos
973
+ │------------------------│
974
+ │ -. \./ |- -. \│
975
+ -1 │ --_--__-" -\__│
976
+ └────────────────────────┘
977
+ 0 10
978
+ ```
979
+
980
+ A blank cell is always a space (U+0020), never the empty braille
981
+ pattern U+2800, which some fonts draw as dots and which is not
982
+ whitespace to a text tool.
983
+
984
+ ## Common options
985
+
986
+ | CLI | Library | Default |
987
+ |---------------------|-----------------------|--------------------------------|
988
+ | `--width N` | `width: N` | 40 (plot area, in cells) |
989
+ | `--height N` | `height: N` | 15 (plot area, in cells) |
990
+ | `--title S` | `title: "S"` | none |
991
+ | `--xlabel S` | `xlabel: "S"` | none |
992
+ | `--ylabel S` | `ylabel: "S"` | none |
993
+ | `--xlim A,B` | `xlim: [a, b]` | the data's extent, rounded |
994
+ | `--ylim A,B` | `ylim: [a, b]` | the data's extent, rounded |
995
+ | `--canvas C` | `canvas: :c` | `braille` (`ascii` with ASCII) |
996
+ | `--xscale F` | `xscale: :f` | none |
997
+ | `--yscale F` | `yscale: :f` | none (plotted kinds only) |
998
+ | `--xdate [FMT]` | `xdate: "FMT"` | by span (plotted kinds only) |
999
+ | `--yerr` | `yerr: [...]` | none (line, scatter) |
1000
+ | `--threshold N` | `threshold: n` | none (line, scatter, stairs) |
1001
+ | `--above C`, `--below C` | `above:`, `below:` | red; the series' colour |
1002
+ | `--ascii` | `ascii: true` | off |
1003
+ | `--color` | `color: true` | off |
1004
+ | (none) | `border: :box` | by kind |
1005
+
1006
+ `--width` and `--height` size the plot area, not the whole figure:
1007
+ labels, frame and legend add to it. A limit of `0,0` means "from the
1008
+ data", as if none were given.
1009
+
1010
+ `--xscale ln|log2|log10` runs every x through the transform before
1011
+ limits and drawing, so the axis reads in the transformed unit (1 to
1012
+ 1000 under `log10` reads 0 to 3, as unicode_plot's axes do).
1013
+ `--yscale` does the same to y, for line, scatter, stairs and density
1014
+ only:
1015
+
1016
+ ```text
1017
+ $ tessella --target web chart --xscale log10 --width 30 --height 6 \
1018
+ --xlabel 'log10 n' growth.txt
1019
+ ┌──────────────────────────────┐
1020
+ 10 │ ▗▄▟│ t
1021
+ │ ▄▄▞▀▀▘ │
1022
+ │ ▗▄▄▀▀▀ │
1023
+ │ ▄▄▞▀▀▘ │
1024
+ │ ▗▄▄▄▀▀▀ │
1025
+ 0 │▄▀▀▀▘ │
1026
+ └──────────────────────────────┘
1027
+ 0 4
1028
+ log10 n
1029
+ ```
1030
+
1031
+ `border:` (library only) is `:box` for a full frame, `:bar` for the
1032
+ corner-and-tick frame bar charts use, or `:none`.
1033
+
1034
+ ### Options by kind
1035
+
1036
+ | CLI | Library | Kinds |
1037
+ |-----------------------------|--------------------|----------------------|
1038
+ | `--style post\|pre` | `style: :post` | stairs |
1039
+ | `--area` | `area: true` | line, stairs |
1040
+ | `--gradient C,C,...\|none` | `gradient: [...]` | line, stairs, with area |
1041
+ | `--bins N` | `bins: N` | histogram |
1042
+ | `--closed left\|right` | `closed: :left` | histogram |
1043
+ | `--scale N` | `scale: N` | stem |
1044
+ | `--fill G` | `fill: "G"` | bar, histogram |
1045
+ | `--no-eighths` | `eighths: false` | bar, histogram |
1046
+ | `--track G\|none` | `track: "G"`/`nil` | bar, histogram |
1047
+ | `--orientation O` | `orientation: :o` | all but stem |
1048
+ | `--blocks B` | `blocks: :b` | bar, histogram |
1049
+ | `--cell-width N` | `cell_width: N` | heatmap |
1050
+ | `--colormap C,C,...` | `colormap: [...]` | heatmap |
1051
+ | `--min A`, `--max B` | `min:`, `max:` | heatmap |
1052
+ | `--donut` | `donut: true` | pie |
1053
+ | `--rim cells\|smooth\|basic` | `rim: :cells` | pie |
1054
+
1055
+ The bar defaults are `fill: "█"`, `eighths: true`, `track: "·"`;
1056
+ under `ascii: true`, `#`, no eighths, and `.`.
1057
+
1058
+ Giving an option to a kind that does not take it is an error naming
1059
+ both, not something silently ignored:
1060
+
1061
+ ```text
1062
+ $ tessella chart --kind bar --bins 3 langs.csv
1063
+ tessella chart: --bins is not valid with --kind bar
1064
+ Usage: tessella chart [options] [file]
1065
+ ```
1066
+
1067
+ ## Dates on the x axis
1068
+
1069
+ When x is a time, the axis is labelled with dates. On the command
1070
+ line `--xdate` reads the x column as ISO 8601: a date (`2026-11-05`),
1071
+ or a date and time (`2026-11-05T14:30`), with an offset (`Z`,
1072
+ `+01:00`) or none, which means UTC. `temps.txt` is a month of daily
1073
+ temperatures:
1074
+
1075
+ ```text
1076
+ $ head -3 temps.txt
1077
+ date temp
1078
+ 2026-10-01 12.0
1079
+ 2026-10-02 13.5
1080
+ ```
1081
+
1082
+ ```text
1083
+ $ tessella --target web chart --xdate --width 40 --height 6 temps.txt
1084
+ ┌────────────────────────────────────────┐
1085
+ 20 │ ▄▄▄▄▄▄▄▖ │ temp
1086
+ │ ▄▄▀▀ ▝▀▚▄ ▄▞▀▀│
1087
+ │▀▘ ▀▜▄ ▄▛▀ │
1088
+ │ ▀▀▄▄▖ ▗▄▄▀▀ │
1089
+ │ ▀▀▀▀▀▀ │
1090
+ 0 │ │
1091
+ └────────────────────────────────────────┘
1092
+ 2026-10-01 2026-10-30
1093
+ ```
1094
+
1095
+ The labels are in UTC, and their format follows the span: `%Y-%m-%d`
1096
+ for a day or more, `%m-%d %H:%M` for an hour or more, `%H:%M:%S`
1097
+ below. `--xdate FMT` sets it, in `strftime` form:
1098
+
1099
+ ```text
1100
+ $ tessella --target web chart --xdate %d/%m --width 40 --height 6 temps.txt
1101
+ ┌────────────────────────────────────────┐
1102
+ 20 │ ▄▄▄▄▄▄▄▖ │ temp
1103
+ │ ▄▄▀▀ ▝▀▚▄ ▄▞▀▀│
1104
+ │▀▘ ▀▜▄ ▄▛▀ │
1105
+ │ ▀▀▄▄▖ ▗▄▄▀▀ │
1106
+ │ ▀▀▀▀▀▀ │
1107
+ 0 │ │
1108
+ └────────────────────────────────────────┘
1109
+ 01/10 30/10
1110
+ ```
1111
+
1112
+ An offset is converted, so the last point here, 23:00 at `+01:00`,
1113
+ is 22:00 UTC:
1114
+
1115
+ ```text
1116
+ $ printf '%s\n' 't v' '2026-11-05T14:00 1' '2026-11-05T18:30 3' \
1117
+ '2026-11-05T23:00+01:00 2' | \
1118
+ tessella --target web chart --xdate --width 30 --height 4
1119
+ ┌──────────────────────────────┐
1120
+ 3 │ ▄▄▀▀▀▀▀▄▄▄▖ │ v
1121
+ │ ▗▄▄▀▀ ▝▀▀▚▄▄▄│
1122
+ │ ▗▄▞▀▘ │
1123
+ 1 │▄▄▞▀▘ │
1124
+ └──────────────────────────────┘
1125
+ 11-05 14:00 11-05 22:00
1126
+ ```
1127
+
1128
+ The axis spans the data exactly, unless `--xlim` says otherwise
1129
+ (given as dates too); a single date, or one date repeated, spans a
1130
+ day on either side. Under `--xdate` a first row holding a date and
1131
+ numbers is data, not a header. `--xdate` takes the word after it as
1132
+ a format only when it holds a `%`, so `--xdate temps.txt` reads the
1133
+ file. A date axis takes no `--xscale`; sideways
1134
+ (`--orientation horizontal`), the dates label the rows. A value that
1135
+ is not an ISO 8601 date is bad input, exit 1:
1136
+
1137
+ ```text
1138
+ $ printf 'x v\n2026-13-01 1\n' | tessella chart --xdate
1139
+ tessella chart: row 1: x is not an ISO 8601 date: "2026-13-01"
1140
+ ```
1141
+
1142
+ In Ruby the x values are `Time`s (or `Date`s, taken as midnight UTC,
1143
+ or `DateTime`s), and `xdate: "FMT"` sets the format:
1144
+
1145
+ ```ruby
1146
+ Tessella.line(times, temps, xdate: "%b %d")
1147
+ ```
1148
+
1149
+ ## Error bars
1150
+
1151
+ `--yerr` (line and scatter) reads a third column as each point's
1152
+ error, and draws a bar from y − e to y + e along the value axis, after
1153
+ the line or the points. The axis limits take the bars in. `dose.txt`
1154
+ holds a measured effect and its error per dose:
1155
+
1156
+ ```text
1157
+ $ cat dose.txt
1158
+ dose effect err
1159
+ 1 2.1 0.4
1160
+ 2 3.0 0.5
1161
+ 3 4.6 0.3
1162
+ 4 5.2 0.8
1163
+ 5 6.9 0.6
1164
+ 6 7.4 1.0
1165
+ ```
1166
+
1167
+ ```text
1168
+ $ tessella --target web chart --yerr --width 30 --height 8 dose.txt
1169
+ ┌──────────────────────────────┐
1170
+ 9 │ ▗│ effect
1171
+ │ ▖▗▄▄▄▟│
1172
+ │ ▄▄▀▛▘ ▐│
1173
+ │ ▗▄▙▞▀ │
1174
+ │ ▄▄▛▀▀▀▘ ▌ │
1175
+ │ ▄▄▀▀ │
1176
+ │▄▄▄▀▀▀▌ │
1177
+ 1 │▘ │
1178
+ └──────────────────────────────┘
1179
+ 1 6
1180
+ ```
1181
+
1182
+ ```text
1183
+ $ tessella chart --kind scatter --yerr --ascii --width 30 --height 8 dose.txt
1184
+ +------------------------------+
1185
+ 9 | || effect
1186
+ | . ||
1187
+ | | ||
1188
+ | | |
1189
+ | | | |
1190
+ | | |
1191
+ || | |
1192
+ 1 |` |
1193
+ +------------------------------+
1194
+ 1 6
1195
+ ```
1196
+
1197
+ Sideways (`--orientation horizontal`) the bars run across. Errors are
1198
+ in data units, and go through `--yscale` with y. `--yerr` needs
1199
+ exactly three columns, x, y and the error:
1200
+
1201
+ ```text
1202
+ $ printf 'x y e f\n1 2 1 1\n' | tessella chart --yerr
1203
+ tessella chart: --yerr reads x, y and the error: 4 columns
1204
+ ```
1205
+
1206
+ In Ruby, `yerr:` on `Tessella.line`, `Tessella.scatter` and `add`
1207
+ takes one error a point, or `[lows, highs]` for bars that are not
1208
+ symmetric. An error list of the wrong length, or a negative error, is
1209
+ refused. With `mark:`, the point stays visible at the middle of its
1210
+ bar:
1211
+
1212
+ ```ruby
1213
+ Tessella.scatter([1, 2, 3], [2, 3, 5], yerr: [[0.5, 0.2, 1], [1, 0.4, 0.5]],
1214
+ mark: "●", width: 20, height: 6)
1215
+ ```
1216
+
1217
+ ## Thresholds
1218
+
1219
+ `--threshold N` (line, scatter, stairs) draws a dashed rule at N
1220
+ across the plot, readable without colour, and the value axis takes N
1221
+ in. Under `--color` the cells beyond it take `--above C` (red by
1222
+ default) and the rest `--below C` (the series' own colour unless
1223
+ given), each in any [colour form](#colour). Sideways the rule runs
1224
+ down the plot.
1225
+
1226
+ ```text
1227
+ $ tessella --target web chart --threshold 70 --width 30 --height 6 cpu.txt
1228
+ ┌──────────────────────────────┐
1229
+ 90 │ ▞▀▀▀▄ ▞▀▀▀▄ │
1230
+ │▖▖▞▖▖▖▖▖▙▖▖▖▖▖▖▖▖▖▖▖▄▛▖▖▖▖▖▙▖▄│
1231
+ │▗▀ ▚ ▟▘ ▚ │
1232
+ │▝ ▝▙ ▗▘ ▝▙│
1233
+ │ ▚▖ ▗▘ ▝│
1234
+ 10 │ ▝▄▄▄▞▘ │
1235
+ └──────────────────────────────┘
1236
+ 0 60
1237
+ ```
1238
+
1239
+ On the braille canvas the rule is the row of single dots, `⠄`, at 70.
1240
+ In Ruby: `threshold: 70, above: :red, below: :green`.
1241
+
1242
+ ## Several series, and marks
1243
+
1244
+ From the library, `chart.add` adds a series to a line, scatter,
1245
+ stairs or density chart. `mark:` draws a series as one glyph in the
1246
+ cell of each point, over whatever the cell holds, instead of the
1247
+ kind's own line or dots; the legend shows the glyph before the name.
1248
+ That is how measured points sit on a fitted curve:
1249
+
1250
+ ```ruby
1251
+ # frozen_string_literal: true
1252
+
1253
+ # examples/calibration.rb: measured points marked on a fitted line.
1254
+ require "tessella"
1255
+
1256
+ xs = (0..20).map { it * 0.5 }
1257
+ fit = Tessella.line(xs, xs.map { (0.5 * it) + 1 },
1258
+ name: "fit", width: 30, height: 6, title: "calibration",
1259
+ canvas: :block)
1260
+ fit.add([1, 3, 4.5, 7, 9], [1.4, 2.7, 3.0, 4.9, 5.4],
1261
+ name: "measured", mark: "●")
1262
+ puts fit
1263
+ ```
1264
+
1265
+ ```text
1266
+ $ ruby calibration.rb
1267
+ calibration
1268
+ ┌──────────────────────────────┐
1269
+ 6 │ ▗▄●▀▀│ fit
1270
+ │ ▗●▟▀▀▘ │ ● measured
1271
+ │ ▄▄▄▀▀▘ │
1272
+ │ ●▗▄▞●▀▘ │
1273
+ │ ▗▄▄▀▀▘ │
1274
+ 1 │▄▄▄●▀▘ │
1275
+ └──────────────────────────────┘
1276
+ 0 10
1277
+ ```
1278
+
1279
+ A mark is one character, and ASCII under `ascii: true`. Bar,
1280
+ histogram, box and stem take no mark.
1281
+
1282
+ ## Colour
1283
+
1284
+ Colour is off by default, because a chart's first home is a document,
1285
+ where an escape code is garbage. `--color` (`color: true`) colours the
1286
+ plotted cells with ANSI escapes, one colour per series from the cycle
1287
+ red, green, yellow, blue, magenta, cyan, white; `add(..., color:
1288
+ :blue)` picks one. The frame, labels and legend stay plain. Where two
1289
+ series cross in a cell, the last one drawn colours it.
1290
+
1291
+ A colour, in `add(..., color:)` as in a gradient, is one of three
1292
+ forms: a name (`:red`, `:green`, `:yellow`, `:blue`, `:magenta`,
1293
+ `:cyan`, `:white`), a 256-colour index (`0..255`, `208` is orange),
1294
+ or a truecolor `"#rrggbb"`. On the command line, `--gradient` takes
1295
+ the same three forms, comma-separated: `--gradient
1296
+ '#2e7d32,#f9a825,#c62828'`. A filled area's colours are covered in
1297
+ [Filled areas](#filled-areas).
1298
+
1299
+ ## ASCII
1300
+
1301
+ `--ascii` (`ascii: true`) makes the whole chart ASCII from the first
1302
+ byte (`--no-ascii` asks for Unicode, which matters only under the
1303
+ [cjk target](targets.md#cjk), where ASCII is the default): the frame is `+ - |`, the canvas is the ASCII one, bars are `#`
1304
+ on a `.` track. No part draws Unicode and lets a later pass rewrite
1305
+ it, so the output is safe in a man page, an RFC-style text or a
1306
+ source file kept to ASCII. A non-ASCII title, label, bar or series
1307
+ name is refused, not drawn: on the command line, a non-ASCII data
1308
+ row is bad input (exit 1) and a non-ASCII `--title`, `--xlabel` or
1309
+ `--ylabel` a usage error (exit 2).
1310
+
1311
+ ```text
1312
+ $ tessella chart --kind histogram --width 30 --bins 8 --ascii normal.txt
1313
+ + +
1314
+ [-3.0, -2.0) |###....................... 8
1315
+ [-2.0, -1.0) |##########................ 28
1316
+ [-1.0, 0.0) |########################## 72
1317
+ [ 0.0, 1.0) |######################.... 61
1318
+ [ 1.0, 2.0) |##########................ 28
1319
+ [ 2.0, 3.0) |#......................... 3
1320
+ + +
1321
+ Frequency
1322
+ ```
1323
+
1324
+ ## What a chart refuses
1325
+
1326
+ A chart checks its input before drawing, and refuses it naming what
1327
+ is wrong. Two kinds of refusal:
1328
+
1329
+ - **A bad call** raises `ArgumentError`, exit 2 on the command line:
1330
+ an unknown option, kind, canvas, xscale, stairs style or closed
1331
+ side; a width or height that is not a positive Integer; x and y of
1332
+ different lengths; a stem scale that is not positive.
1333
+ - **A value it cannot draw** raises `Tessella::ValueError` (an
1334
+ `ArgumentError` too), exit 1: a value that is not a finite number
1335
+ (`nil`, a String, NaN, ±Infinity, or a number past Float's range),
1336
+ an x of 0 or less under a log `xscale`, a negative pie slice, a
1337
+ candle whose low and high do not hold its open and close, an empty
1338
+ sample.
1339
+
1340
+ ```text
1341
+ $ printf '1\n1e400\n' | tessella chart
1342
+ tessella chart: y must be a finite number: Infinity
1343
+ ```
1344
+
1345
+ A row that cannot be read is bad input too, exit 1, naming the row
1346
+ and the field, and so is invalid UTF-8, in every command:
1347
+
1348
+ ```text
1349
+ $ printf '1\nx\n' | tessella chart
1350
+ tessella chart: row 2: y is not a number: "x"
1351
+ ```
1352
+
1353
+ Every row of a bar chart must have as many columns as the first:
1354
+
1355
+ ```text
1356
+ $ printf 'a,1,2\nb,3\n' | tessella chart --kind bar
1357
+ tessella chart: row 2 has 2 columns, row 1 has 3
1358
+ ```
1359
+
1360
+ ## How tessella differs from unicode_plot
1361
+
1362
+ tessella's chart kinds and canvases come from
1363
+ [unicode_plot](https://github.com/red-data-tools/unicode_plot), which
1364
+ the test suite uses as an oracle; with unicode_plot's own bar
1365
+ settings (`fill: "■"`, `"▇"` for histograms, `eighths: false`, `track:
1366
+ nil`) the output is identical. The departures — sizes below 5 on the
1367
+ density canvas, wide histogram bins, `10` not `10.0` on an axis,
1368
+ exact stem leaves, very large constant data, negative bars, CJK
1369
+ labels — are listed with their reasons in
1370
+ [DESIGN.md](../DESIGN.md#why-it-is-shaped-so).