cat / 012
clone

a spreadsheet for the terminal: lotus 1-2-3 looks, google sheets keys and formulas, one go binary

Merge the chart types and options

Dami committed eb1d3bd
Both sides moved code out of the file format's file: cell encoding went
to cellfile.go and chart encoding to chartfile.go. The newer chart screens
move to screencharts_test.go, keeping screenlist_test.go under 500 lines.
commit
eb1d3bdf1b5bb54f712ff08bdc684b31784bf211
parent
9d14c38f764ca621c7011087618bc8bd99e5ffb0
parent
6490bfaf8b3e51fbec308bac66008bbe9f5714ab
author
Dami <dami.etoile@gmail.com>
84 files changed, +2695 -429
MREADME.md3+-
MROADMAP.md2+-
Mdocs/architecture.md21++-
Mdocs/charts.md26+++-
Mdocs/extending.md2+-
Mdocs/files.md11+-
Mdocs/keys.md2+-
Me2e/charts_test.go43+++++
Me2e/notes_test.go4+-
Ae2e/screencharts_test.go35++++
Me2e/screensetup_test.go12++
Ae2e/testdata/screens/chart-area-light.html30++++
Ae2e/testdata/screens/chart-area.html30++++
Ae2e/testdata/screens/chart-axes-light.html30++++
Ae2e/testdata/screens/chart-axes-narrow.html15++
Ae2e/testdata/screens/chart-axes.html30++++
Ae2e/testdata/screens/chart-bar-percent.html30++++
Me2e/testdata/screens/chart-bar.html18+-
Me2e/testdata/screens/chart-editor-light.html8+-
Me2e/testdata/screens/chart-editor.html8+-
Me2e/testdata/screens/chart-light.html6+-
Me2e/testdata/screens/chart-line-light.html10+-
Me2e/testdata/screens/chart-line.html10+-
Me2e/testdata/screens/chart-narrow.html9+-
Ae2e/testdata/screens/chart-scatter-light.html30++++
Ae2e/testdata/screens/chart-scatter.html30++++
Me2e/testdata/screens/chart-selected.html6+-
Ae2e/testdata/screens/chart-stacked-light.html30++++
Ae2e/testdata/screens/chart-stacked.html30++++
Me2e/testdata/screens/chart-wide.html6+-
Me2e/testdata/screens/chart.html6+-
Ainternal/chart/area.go161++++++++++++++++++++
Ainternal/chart/axis.go150+++++++++++++++++++
Minternal/chart/bar.go127++++++++--------
Minternal/chart/chart.go71+++------
Minternal/chart/chart_test.go78++++++++--
Minternal/chart/column.go222++++++++++++++++++----------
Minternal/chart/image.go64++++----
Ainternal/chart/legend.go114++++++++++++++
Minternal/chart/pie.go19++-
Ainternal/chart/scatter.go239++++++++++++++++++++++++++++++
Ainternal/chart/stack.go197+++++++++++++++++++++++++
Minternal/chart/stress_test.go2+
Ainternal/chart/testdata/area-negative.txt10+
Ainternal/chart/testdata/area-stacked.txt12++
Ainternal/chart/testdata/area.txt12++
Ainternal/chart/testdata/axis-log.txt12++
Ainternal/chart/testdata/axis-min-max.txt12++
Ainternal/chart/testdata/axis-min.txt12++
Ainternal/chart/testdata/bar-log.txt10+
Ainternal/chart/testdata/bar-percent.txt12++
Minternal/chart/testdata/bar-single.txt10+-
Ainternal/chart/testdata/bar-stacked.txt12++
Minternal/chart/testdata/bar.txt14+-
Minternal/chart/testdata/column-narrow.txt4+-
Minternal/chart/testdata/column-negative.txt6+-
Ainternal/chart/testdata/column-percent.txt12++
Minternal/chart/testdata/column-single.txt6+-
Ainternal/chart/testdata/column-stacked-negative.txt12++
Ainternal/chart/testdata/column-stacked.txt12++
Minternal/chart/testdata/column.txt6+-
Ainternal/chart/testdata/images.txt17++
Ainternal/chart/testdata/legend-none.txt12++
Ainternal/chart/testdata/legend-right.txt12++
Minternal/chart/testdata/line-single.txt8+-
Minternal/chart/testdata/line.txt6+-
Ainternal/chart/testdata/no-gridlines.txt10+
Ainternal/chart/testdata/pie-no-legend.txt10+
Ainternal/chart/testdata/scatter-single.txt10+
Ainternal/chart/testdata/scatter-trend.txt12++
Ainternal/chart/testdata/scatter.txt12++
Minternal/chart/types.go66++++++--
Ainternal/chart/types_test.go155+++++++++++++++++++
Minternal/sheet/chart.go16+-
Minternal/sheet/chart_test.go9+
Ainternal/sheet/chartfile.go81++++++++++
Ainternal/sheet/chartopts.go106+++++++++++++
Minternal/sheet/file.go42-----
Ainternal/ui/chartaxes.go97++++++++++++
Ainternal/ui/chartaxes_test.go133+++++++++++++++++
Minternal/ui/charteditor.go134+++++++++++++---
Minternal/ui/charts.go3+-
Minternal/ui/graphics.go1+
Minternal/ui/prompt.go9+-
M README.md +2 -1
M ROADMAP.md +1 -1
@@ -125,7 +125,7 @@ work left open. Sizes: S (a day or two), M (about a week), L (weeks).
125125 | Wrap text, row heights, borders, merged cells | Layout changes in the grid renderer | M to L |126126 | Notes on cells (shown on hover and in the context line). Done: Insert > Note (Shift+F2), a corner mark, the context line and a hover box, saved and in XLSX as Excel's notes; see [data.md](docs/data.md#notes) | | S |127127 | Locale: decimal comma, date order, list separator in formulas | Sheets' File > Settings > Locale | M |128-| Charts: scatter, area, stacked columns and bars, axis options, a legend position | Registry entries in `internal/chart` | M |128+| Charts: scatter, area, stacked columns and bars, axis options, a legend position. Done: area and scatter (with trend lines) types, stacked and 100% stacked columns, bars and areas, value axis minimum, maximum and log scale, gridlines on and off, the legend at the bottom, right or none; see [charts.md](docs/charts.md) | Registry entries in `internal/chart` | M |129129 | Pivot tables: column subtotals, renaming value columns, check the "(blank)" label against Sheets. Done: a subtotal column per outer column group, R renames a value in the editor, and blank groups have a blank label as in Sheets ([data.md](docs/data.md#pivot-tables)) | Left open by pivots | S |130130 | Protected ranges and sheets (warn on edit). Done: Data > Protect sheets and ranges, edits ask on the context line; an XLSX sheet's protection is noted on import; see [data.md](docs/data.md#protected-sheets-and-ranges) | | S |131131
M docs/architecture.md +14 -7
@@ -394,13 +394,20 @@ the user's editor (`AllowEditor`), since that starts a program.
394394 395395 `internal/chart` draws a chart in two layers that share one layout: text396396 for any terminal (block elements for bars, braille for lines, half blocks397-for pies) and, on terminals with kitty graphics, an image of the plot area398-with the axes and legend still terminal text. Each chart type is a layout399-in the `types` table, returning a plan that draws it both ways; the type400-constants and their names belong to `internal/sheet`, since charts are401-saved with sheets. Work is bounded by the chart's size, not its data: only402-the categories that fit are drawn, pie slices are found by binary search,403-and a pie image supersamples only pixels on a slice edge or the rim.397+for pies, eighths filled column by column for areas) and, on terminals398+with kitty graphics, an image of the plot area with the axes and legend399+still terminal text. Each chart type is a layout in the `types` table,400+returning a plan that draws it both ways, and the series its legend401+lists; `Draw` places the legend and gives the layout the room left. The402+type constants and their names, and the options of each chart403+(`sheet.ChartOptions`: stacking, trend lines, the value axis, gridlines,404+the legend), belong to `internal/sheet`, since charts are saved with405+sheets. Stacked bars are piles of spans, and a cell where two meet draws406+the lower as eighths over the upper as background. Work is bounded by the407+chart's size, not its data: only the categories that fit are drawn, areas408+are sampled once per column (per pixel column in images), pie slices are409+found by binary search, and a pie image supersamples only pixels on a410+slice edge or the rim.404411 405412 ## Serving over SSH406413
M docs/charts.md +22 -4
@@ -6,10 +6,28 @@ Insert > Chart charts the selection, or the table around the active cell,
66 guessing the header row, the label column and a title. The chart floats over77 the grid, pinned to a cell, and redraws when its data changes.88 9-The chart editor is a bar on the context line: Left/Right (or 1 to 4) picks10-column, bar, line or pie; S switches series between rows and columns; H and11-L toggle the header row and labels; R points at a new range; T edits the12-title. Enter keeps the changes, Esc undoes them.9+The chart editor is a bar on the context line: Left/Right (or 1 to 6) picks10+column, bar, line, pie, area or scatter; S switches series between rows and11+columns; H and L toggle the header row and labels; R points at a new range;12+T edits the title. Enter keeps the changes, Esc undoes them.13+14+| Type | Draws |15+|---|---|16+| Column, Bar | a bar per series in each category; K cycles Not stacked, Stacked and 100% stacked |17+| Line | a line per series |18+| Area | a line per series filled down to the axis, the smaller series in front; K stacks them as columns do |19+| Pie | the first series as slices, with each slice's share |20+| Scatter | the first series as X and every other one as Y against it, as in Sheets (with one series, the labels are X when they're numbers); E adds a linear trend line per series |21+22+A opens a second bar, as Sheets' Customize tab: N and X type the value23+axis' minimum and maximum (a number as a cell takes one, blank for24+automatic; values past a fixed end stop at it), L puts it on a log scale25+(values that aren't positive are left out, and its ends round out to26+powers of ten), G turns the gridlines off and on, and P moves the legend27+between the bottom, the right and none. A legend lists two series or more,28+and a pie's legend of slices always sits on its right. Enter or Esc goes29+back to the first bar, keeping the changes. On the value axis of a bar30+chart these options apply along the bottom; on a scatter, to Y.1331 1432 Click a chart to select it. Drag it to move it, drag its corner to resize it,1533 or use the arrows and Shift+arrows; Enter edits it, Del deletes it, and
M docs/extending.md +1 -1
@@ -17,7 +17,7 @@ shows it is derived from the table rather than listing it again.
1717 | `functions.FuncDef` (one file per category in `internal/functions`) | a function: name, signature, description, arity, eval, result format, decimal twin | parsing, autocomplete, argument hints, help, [functions.md](functions.md) |1818 | `ui.command` | an action: id, title, description, run, enabled, checked, what it edits, and how macros treat it | key bindings (Sheets and vim), menu bar, context menus, palette, shortcuts help, the `:` command line and its completions, macro recording and `run()` in scripts |1919 | `fileio` formats (`formats.go`) | a format: name, extensions, labels, traits, importer, exporter | `Import`, `Export`, detection, import picker, File > Download, command line |20-| `chart.types`, with `sheet.ChartTypes` | a type: name and order (sheet, saved in files), a layout drawing text and image (chart) | `chart.Draw`, `chart.Image`, chart editor, Insert > Chart |20+| `chart.types`, with `sheet.ChartTypes` | a type: name and order (sheet, saved in files), a layout drawing text and image and the series its legend lists (chart) | `chart.Draw`, `chart.Image`, chart editor, Insert > Chart |2121 | theme roles (`theme.Theme`) | a role: dark and light styles on the 16 ANSI colors, with a contrast minimum for schemes (`minContrast`) | every style in the UI, drawn in the terminal's palette or any color scheme (`FromPalette`); `TestEveryThemeReadable` checks each role under all 349 schemes |2222 | `config.Options` | an option: name, type, default, environment variables, flag, live or not, description, check | parsing and warnings, flags, `012 config` and its default file, [config.md](config.md), Reload config |2323
M docs/files.md +7 -4
M docs/keys.md +1 -1
M e2e/charts_test.go +43 -0
M e2e/notes_test.go +3 -1
A e2e/screencharts_test.go +35 -0
M e2e/screensetup_test.go +12 -0
A e2e/testdata/screens/chart-area-light.html +30 -0
@@ -0,0 +1,30 @@
1+<pre class="screen"> <span style="text-decoration:underline">F</span>ile <span style="text-decoration:underline">E</span>dit <span style="text-decoration:underline">V</span>iew <span style="text-decoration:underline">I</span>nsert F<span style="text-decoration:underline">o</span>rmat <span style="text-decoration:underline">D</span>ata <span style="text-decoration:underline">H</span>elp <span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">READY</span><span style="background:var(--vt-palette-6)"> </span>2+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">A1</span><span style="background:var(--vt-palette-7)"> </span> Month3+4+<span style="background:var(--vt-palette-7)"> </span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">A</span><span style="background:var(--vt-palette-6)"> </span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">B</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">C</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">D</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">E</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">F</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">G</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">H</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">I</span><span style="background:var(--vt-palette-7)"> </span>5+<span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">1</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">Month</span><span style="background:var(--vt-palette-6)"> </span> Rent Food <span style="color:var(--vt-palette-8)">┌─</span> <span style="font-weight:bold">Rent</span> <span style="font-weight:bold">and</span> <span style="font-weight:bold">Food</span> <span style="color:var(--vt-palette-8)">──────────────────────────────┐</span>6+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">2</span><span style="background:var(--vt-palette-7)"> </span> Jan $1,450 $600 <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span>7+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">3</span><span style="background:var(--vt-palette-7)"> </span> Feb $1,450 $640 <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span>8+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">4</span><span style="background:var(--vt-palette-7)"> </span> Mar $1,500 $612 <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-0)">$1,500</span> <span style="color:var(--vt-palette-8)">┤</span><span style="color:var(--vt-palette-4)">▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▇▇▇▇▇██████████████</span> <span style="color:var(--vt-palette-8)">│</span>9+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">5</span><span style="background:var(--vt-palette-7)"> </span> Apr $1,500 $700 <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span><span style="color:var(--vt-palette-4)">███████████████████████████████████</span> <span style="color:var(--vt-palette-8)">│</span>10+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">6</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span><span style="color:var(--vt-palette-4)">███████████████████████████████████</span> <span style="color:var(--vt-palette-8)">│</span>11+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">7</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-0)">$1,000</span> <span style="color:var(--vt-palette-8)">┤</span><span style="color:var(--vt-palette-4)">███████████████████████████████████</span> <span style="color:var(--vt-palette-8)">│</span>12+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">8</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span><span style="color:var(--vt-palette-4)">████████████████████████████████</span><span style="color:var(--vt-palette-5);background:var(--vt-palette-4)">▁▁▂</span> <span style="color:var(--vt-palette-8)">│</span>13+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">9</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span><span style="color:var(--vt-palette-5);background:var(--vt-palette-4)">▅▅▅▅▆▆▆▆▆▆▇▇▇▇▆▆▆▆▆▆▆▆▅▅▆▆▇▇▇</span><span style="color:var(--vt-palette-5)">██████</span> <span style="color:var(--vt-palette-8)">│</span>14+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">10</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-0)">$500</span> <span style="color:var(--vt-palette-8)">┤</span><span style="color:var(--vt-palette-5)">███████████████████████████████████</span> <span style="color:var(--vt-palette-8)">│</span>15+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">11</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span><span style="color:var(--vt-palette-5)">███████████████████████████████████</span> <span style="color:var(--vt-palette-8)">│</span>16+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">12</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span><span style="color:var(--vt-palette-5)">███████████████████████████████████</span> <span style="color:var(--vt-palette-8)">│</span>17+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">13</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-0)">$0</span> <span style="color:var(--vt-palette-8)">┼────────────────────────────────────</span> <span style="color:var(--vt-palette-8)">│</span>18+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">14</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-0)">Jan</span> <span style="color:var(--vt-palette-0)">Feb</span> <span style="color:var(--vt-palette-0)">Mar</span> <span style="color:var(--vt-palette-0)">Apr</span> <span style="color:var(--vt-palette-8)">│</span>19+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">15</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-4)">■</span> <span style="color:var(--vt-palette-0)">Rent</span> <span style="color:var(--vt-palette-5)">■</span> <span style="color:var(--vt-palette-0)">Food</span> <span style="color:var(--vt-palette-8)">│</span>20+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">16</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">└──────────────────────────────────────</span> <span style="color:var(--vt-palette-0)">A1:C5</span> <span style="color:var(--vt-palette-8)">─┘</span>21+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">17</span><span style="background:var(--vt-palette-7)"> </span>22+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">18</span><span style="background:var(--vt-palette-7)"> </span>23+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">19</span><span style="background:var(--vt-palette-7)"> </span>24+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">20</span><span style="background:var(--vt-palette-7)"> </span>25+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">21</span><span style="background:var(--vt-palette-7)"> </span>26+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">22</span><span style="background:var(--vt-palette-7)"> </span>27+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">23</span><span style="background:var(--vt-palette-7)"> </span>28+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">24</span><span style="background:var(--vt-palette-7)"> </span>29+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">25</span><span style="background:var(--vt-palette-7)"> </span>30+<span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">Sheet1</span><span style="background:var(--vt-palette-6)"> </span> <span style="color:var(--vt-palette-0)">+</span> <span style="color:var(--vt-palette-8)">│</span> untitled <span style="color:var(--vt-palette-0)">modified</span> <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">Ctrl+K</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-0)">search</span> <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">Ctrl+/</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-0)">shortcuts</span> <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">F10</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-0)">menu</span></pre>\ No newline at end of file
A e2e/testdata/screens/chart-area.html +30 -0
@@ -0,0 +1,30 @@
1+<pre class="screen"> <span style="text-decoration:underline">F</span>ile <span style="text-decoration:underline">E</span>dit <span style="text-decoration:underline">V</span>iew <span style="text-decoration:underline">I</span>nsert F<span style="text-decoration:underline">o</span>rmat <span style="text-decoration:underline">D</span>ata <span style="text-decoration:underline">H</span>elp <span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">READY</span><span style="background:var(--vt-palette-6)"> </span>2+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">A1</span><span style="background:var(--vt-palette-8)"> </span> Month3+4+<span style="background:var(--vt-palette-8)"> </span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">A</span><span style="background:var(--vt-palette-6)"> </span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">B</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">C</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">D</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">E</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">F</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">G</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">H</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">I</span><span style="background:var(--vt-palette-8)"> </span>5+<span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">1</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">Month</span><span style="background:var(--vt-palette-6)"> </span> Rent Food <span style="color:var(--vt-palette-8)">┌─</span> <span style="font-weight:bold">Rent</span> <span style="font-weight:bold">and</span> <span style="font-weight:bold">Food</span> <span style="color:var(--vt-palette-8)">──────────────────────────────┐</span>6+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">2</span><span style="background:var(--vt-palette-8)"> </span> Jan $1,450 $600 <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span>7+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">3</span><span style="background:var(--vt-palette-8)"> </span> Feb $1,450 $640 <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span>8+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">4</span><span style="background:var(--vt-palette-8)"> </span> Mar $1,500 $612 <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">$1,500</span> <span style="color:var(--vt-palette-8)">┤</span><span style="color:var(--vt-palette-6)">▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▇▇▇▇▇██████████████</span> <span style="color:var(--vt-palette-8)">│</span>9+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">5</span><span style="background:var(--vt-palette-8)"> </span> Apr $1,500 $700 <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span><span style="color:var(--vt-palette-6)">███████████████████████████████████</span> <span style="color:var(--vt-palette-8)">│</span>10+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">6</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span><span style="color:var(--vt-palette-6)">███████████████████████████████████</span> <span style="color:var(--vt-palette-8)">│</span>11+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">7</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">$1,000</span> <span style="color:var(--vt-palette-8)">┤</span><span style="color:var(--vt-palette-6)">███████████████████████████████████</span> <span style="color:var(--vt-palette-8)">│</span>12+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">8</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span><span style="color:var(--vt-palette-6)">████████████████████████████████</span><span style="color:var(--vt-palette-5);background:var(--vt-palette-6)">▁▁▂</span> <span style="color:var(--vt-palette-8)">│</span>13+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">9</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span><span style="color:var(--vt-palette-5);background:var(--vt-palette-6)">▅▅▅▅▆▆▆▆▆▆▇▇▇▇▆▆▆▆▆▆▆▆▅▅▆▆▇▇▇</span><span style="color:var(--vt-palette-5)">██████</span> <span style="color:var(--vt-palette-8)">│</span>14+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">10</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">$500</span> <span style="color:var(--vt-palette-8)">┤</span><span style="color:var(--vt-palette-5)">███████████████████████████████████</span> <span style="color:var(--vt-palette-8)">│</span>15+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">11</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span><span style="color:var(--vt-palette-5)">███████████████████████████████████</span> <span style="color:var(--vt-palette-8)">│</span>16+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">12</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span><span style="color:var(--vt-palette-5)">███████████████████████████████████</span> <span style="color:var(--vt-palette-8)">│</span>17+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">13</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">$0</span> <span style="color:var(--vt-palette-8)">┼────────────────────────────────────</span> <span style="color:var(--vt-palette-8)">│</span>18+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">14</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">Jan</span> <span style="color:var(--vt-palette-8)">Feb</span> <span style="color:var(--vt-palette-8)">Mar</span> <span style="color:var(--vt-palette-8)">Apr</span> <span style="color:var(--vt-palette-8)">│</span>19+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">15</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-6)">■</span> <span style="color:var(--vt-palette-8)">Rent</span> <span style="color:var(--vt-palette-5)">■</span> <span style="color:var(--vt-palette-8)">Food</span> <span style="color:var(--vt-palette-8)">│</span>20+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">16</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">└──────────────────────────────────────</span> <span style="color:var(--vt-palette-8)">A1:C5</span> <span style="color:var(--vt-palette-8)">─┘</span>21+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">17</span><span style="background:var(--vt-palette-8)"> </span>22+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">18</span><span style="background:var(--vt-palette-8)"> </span>23+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">19</span><span style="background:var(--vt-palette-8)"> </span>24+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">20</span><span style="background:var(--vt-palette-8)"> </span>25+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">21</span><span style="background:var(--vt-palette-8)"> </span>26+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">22</span><span style="background:var(--vt-palette-8)"> </span>27+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">23</span><span style="background:var(--vt-palette-8)"> </span>28+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">24</span><span style="background:var(--vt-palette-8)"> </span>29+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">25</span><span style="background:var(--vt-palette-8)"> </span>30+<span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">Sheet1</span><span style="background:var(--vt-palette-6)"> </span> <span style="color:var(--vt-palette-8)">+</span> <span style="color:var(--vt-palette-8)">│</span> untitled <span style="color:var(--vt-palette-8)">modified</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+K</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">search</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+/</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">shortcuts</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">F10</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">menu</span></pre>\ No newline at end of file
A e2e/testdata/screens/chart-axes-light.html +30 -0
@@ -0,0 +1,30 @@
1+<pre class="screen"> <span style="text-decoration:underline">F</span>ile <span style="text-decoration:underline">E</span>dit <span style="text-decoration:underline">V</span>iew <span style="text-decoration:underline">I</span>nsert F<span style="text-decoration:underline">o</span>rmat <span style="text-decoration:underline">D</span>ata <span style="text-decoration:underline">H</span>elp <span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">CHART</span><span style="background:var(--vt-palette-6)"> </span>2+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">A1</span><span style="background:var(--vt-palette-7)"> </span> Month3+ <span style="color:var(--vt-palette-0)">Min</span> <span style="color:var(--vt-palette-0)">auto</span> <span style="color:var(--vt-palette-0)">Max</span> <span style="color:var(--vt-palette-0)">auto</span> <span style="color:var(--vt-palette-0)">Log</span> <span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">Gridlines</span><span style="background:var(--vt-palette-6)"> </span> <span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">Legend</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">right</span><span style="background:var(--vt-palette-6)"> </span>4+<span style="background:var(--vt-palette-7)"> </span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">A</span><span style="background:var(--vt-palette-6)"> </span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">B</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">C</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">D</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">E</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">F</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">G</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">H</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">I</span><span style="background:var(--vt-palette-7)"> </span>5+<span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">1</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">Month</span><span style="background:var(--vt-palette-6)"> </span> Rent Food <span style="color:var(--vt-palette-6);font-weight:bold">┌─</span> <span style="font-weight:bold">Rent</span> <span style="font-weight:bold">and</span> <span style="font-weight:bold">Food</span> <span style="color:var(--vt-palette-6);font-weight:bold">──────────────────────────────┐</span>6+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">2</span><span style="background:var(--vt-palette-7)"> </span> Jan $1,450 $600 <span style="color:var(--vt-palette-6);font-weight:bold">│</span> <span style="color:var(--vt-palette-0)">$1,500</span> <span style="color:var(--vt-palette-8)">┤</span><span style="color:var(--vt-palette-4)">▅▅▅</span><span style="color:var(--vt-palette-8)">┈┈┈┈</span><span style="color:var(--vt-palette-4)">▅▅▅</span><span style="color:var(--vt-palette-8)">┈┈┈┈</span><span style="color:var(--vt-palette-4)">███</span><span style="color:var(--vt-palette-8)">┈┈┈┈</span><span style="color:var(--vt-palette-4)">███</span><span style="color:var(--vt-palette-8)">┈┈┈┈</span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span>7+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">3</span><span style="background:var(--vt-palette-7)"> </span> Feb $1,450 $640 <span style="color:var(--vt-palette-6);font-weight:bold">│</span> <span style="color:var(--vt-palette-8)">│</span><span style="color:var(--vt-palette-4)">███</span> <span style="color:var(--vt-palette-4)">███</span> <span style="color:var(--vt-palette-4)">███</span> <span style="color:var(--vt-palette-4)">███</span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span>8+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">4</span><span style="background:var(--vt-palette-7)"> </span> Mar $1,500 $612 <span style="color:var(--vt-palette-6);font-weight:bold">│</span> <span style="color:var(--vt-palette-0)">$1,250</span> <span style="color:var(--vt-palette-8)">┤</span><span style="color:var(--vt-palette-4)">███</span><span style="color:var(--vt-palette-8)">┈┈┈┈</span><span style="color:var(--vt-palette-4)">███</span><span style="color:var(--vt-palette-8)">┈┈┈┈</span><span style="color:var(--vt-palette-4)">███</span><span style="color:var(--vt-palette-8)">┈┈┈┈</span><span style="color:var(--vt-palette-4)">███</span><span style="color:var(--vt-palette-8)">┈┈┈┈</span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span>9+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">5</span><span style="background:var(--vt-palette-7)"> </span> Apr $1,500 $700 <span style="color:var(--vt-palette-6);font-weight:bold">│</span> <span style="color:var(--vt-palette-8)">│</span><span style="color:var(--vt-palette-4)">███</span> <span style="color:var(--vt-palette-4)">███</span> <span style="color:var(--vt-palette-4)">███</span> <span style="color:var(--vt-palette-4)">███</span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span>10+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">6</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span> <span style="color:var(--vt-palette-0)">$1,000</span> <span style="color:var(--vt-palette-8)">┤</span><span style="color:var(--vt-palette-4)">███</span><span style="color:var(--vt-palette-8)">┈┈┈┈</span><span style="color:var(--vt-palette-4)">███</span><span style="color:var(--vt-palette-8)">┈┈┈┈</span><span style="color:var(--vt-palette-4)">███</span><span style="color:var(--vt-palette-8)">┈┈┈┈</span><span style="color:var(--vt-palette-4)">███</span><span style="color:var(--vt-palette-8)">┈┈┈┈</span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span>11+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">7</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span> <span style="color:var(--vt-palette-8)">│</span><span style="color:var(--vt-palette-4)">███</span> <span style="color:var(--vt-palette-4)">███</span> <span style="color:var(--vt-palette-4)">███</span> <span style="color:var(--vt-palette-4)">███</span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span>12+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">8</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span> <span style="color:var(--vt-palette-0)">$750</span> <span style="color:var(--vt-palette-8)">┤</span><span style="color:var(--vt-palette-4)">███</span><span style="color:var(--vt-palette-8)">┈┈┈┈</span><span style="color:var(--vt-palette-4)">███</span><span style="color:var(--vt-palette-5)">▁▁▁</span><span style="color:var(--vt-palette-8)">┈</span><span style="color:var(--vt-palette-4)">███</span><span style="color:var(--vt-palette-8)">┈┈┈┈</span><span style="color:var(--vt-palette-4)">███</span><span style="color:var(--vt-palette-5)">▅▅▅</span><span style="color:var(--vt-palette-8)">┈</span> <span style="color:var(--vt-palette-4)">■</span> <span style="color:var(--vt-palette-0)">Rent</span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span>13+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">9</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span> <span style="color:var(--vt-palette-8)">│</span><span style="color:var(--vt-palette-4)">███</span><span style="color:var(--vt-palette-5)">▆▆▆</span> <span style="color:var(--vt-palette-4)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-4)">███</span><span style="color:var(--vt-palette-5)">▇▇▇</span> <span style="color:var(--vt-palette-4)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-5)">■</span> <span style="color:var(--vt-palette-0)">Food</span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span>14+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">10</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span> <span style="color:var(--vt-palette-0)">$500</span> <span style="color:var(--vt-palette-8)">┤</span><span style="color:var(--vt-palette-4)">███</span><span style="color:var(--vt-palette-5)">███</span><span style="color:var(--vt-palette-8)">┈</span><span style="color:var(--vt-palette-4)">███</span><span style="color:var(--vt-palette-5)">███</span><span style="color:var(--vt-palette-8)">┈</span><span style="color:var(--vt-palette-4)">███</span><span style="color:var(--vt-palette-5)">███</span><span style="color:var(--vt-palette-8)">┈</span><span style="color:var(--vt-palette-4)">███</span><span style="color:var(--vt-palette-5)">███</span><span style="color:var(--vt-palette-8)">┈</span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span>15+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">11</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span> <span style="color:var(--vt-palette-8)">│</span><span style="color:var(--vt-palette-4)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-4)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-4)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-4)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span>16+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">12</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span> <span style="color:var(--vt-palette-0)">$250</span> <span style="color:var(--vt-palette-8)">┤</span><span style="color:var(--vt-palette-4)">███</span><span style="color:var(--vt-palette-5)">███</span><span style="color:var(--vt-palette-8)">┈</span><span style="color:var(--vt-palette-4)">███</span><span style="color:var(--vt-palette-5)">███</span><span style="color:var(--vt-palette-8)">┈</span><span style="color:var(--vt-palette-4)">███</span><span style="color:var(--vt-palette-5)">███</span><span style="color:var(--vt-palette-8)">┈</span><span style="color:var(--vt-palette-4)">███</span><span style="color:var(--vt-palette-5)">███</span><span style="color:var(--vt-palette-8)">┈</span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span>17+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">13</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span> <span style="color:var(--vt-palette-8)">│</span><span style="color:var(--vt-palette-4)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-4)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-4)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-4)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span>18+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">14</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span> <span style="color:var(--vt-palette-0)">$0</span> <span style="color:var(--vt-palette-8)">┼─────────────────────────────</span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span>19+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">15</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span> <span style="color:var(--vt-palette-0)">Jan</span> <span style="color:var(--vt-palette-0)">Feb</span> <span style="color:var(--vt-palette-0)">Mar</span> <span style="color:var(--vt-palette-0)">Apr</span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span>20+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">16</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-6);font-weight:bold">└──────────────────────────────────────</span> <span style="color:var(--vt-palette-0)">A1:C5</span> <span style="color:var(--vt-palette-6);font-weight:bold">─◢</span>21+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">17</span><span style="background:var(--vt-palette-7)"> </span>22+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">18</span><span style="background:var(--vt-palette-7)"> </span>23+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">19</span><span style="background:var(--vt-palette-7)"> </span>24+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">20</span><span style="background:var(--vt-palette-7)"> </span>25+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">21</span><span style="background:var(--vt-palette-7)"> </span>26+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">22</span><span style="background:var(--vt-palette-7)"> </span>27+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">23</span><span style="background:var(--vt-palette-7)"> </span>28+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">24</span><span style="background:var(--vt-palette-7)"> </span>29+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">25</span><span style="background:var(--vt-palette-7)"> </span>30+ <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">N</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-0)">min</span> <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">X</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-0)">max</span> <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">L</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-0)">log</span> <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">G</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-0)">grid</span> <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">P</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-0)">legend</span> <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">Esc</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-0)">back</span></pre>\ No newline at end of file
A e2e/testdata/screens/chart-axes-narrow.html +15 -0
@@ -0,0 +1,15 @@
1+<pre class="screen"> <span style="text-decoration:underline">F</span>ile <span style="text-decoration:underline">E</span>dit <span style="text-decoration:underline">V</span>iew <span style="text-decoration:underline">I</span>nsert F<span style="text-decoration:underline">o</span>rmat <span style="text-decoration:underline">D</span>ata <span style="text-decoration:underline">H</span>elp <span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">CHART</span><span style="background:var(--vt-palette-6)"> </span>2+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">A1</span><span style="background:var(--vt-palette-8)"> </span> Month3+ <span style="color:var(--vt-palette-8)">Min</span> <span style="color:var(--vt-palette-8)">auto</span> <span style="color:var(--vt-palette-8)">Max</span> <span style="color:var(--vt-palette-8)">auto</span> <span style="color:var(--vt-palette-8)">Log</span> <span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">Gridlines</span><span style="background:var(--vt-palette-6)"> </span> <span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">Legend</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">bottom</span><span style="background:var(--vt-palette-6)"> </span>4+<span style="background:var(--vt-palette-8)"> </span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">A</span><span style="background:var(--vt-palette-6)"> </span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">B</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">C</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">D</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">E</span><span style="background:var(--vt-palette-8)"> </span>5+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">6</span><span style="background:var(--vt-palette-8)"> </span>6+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">7</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-6);font-weight:bold">┌─</span> <span style="font-weight:bold">Rent</span> <span style="font-weight:bold">and</span> <span style="font-weight:bold">Food</span> <span style="color:var(--vt-palette-6);font-weight:bold">──────────────────────────────┐</span>7+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">8</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-6);font-weight:bold">│</span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span>8+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">9</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-6);font-weight:bold">│</span> <span style="color:var(--vt-palette-8)">$2,000</span> <span style="color:var(--vt-palette-8)">┤┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈</span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span>9+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">10</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-6);font-weight:bold">│</span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-6)">▇▇▇</span> <span style="color:var(--vt-palette-6)">▇▇▇</span> <span style="color:var(--vt-palette-6)">███</span> <span style="color:var(--vt-palette-6)">███</span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span>10+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">11</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-6);font-weight:bold">│</span> <span style="color:var(--vt-palette-8)">$1,000</span> <span style="color:var(--vt-palette-8)">┤┈┈</span><span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">▂▂▂</span><span style="color:var(--vt-palette-8)">┈┈</span><span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">▂▂▂</span><span style="color:var(--vt-palette-8)">┈┈</span><span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">▂▂▂</span><span style="color:var(--vt-palette-8)">┈┈</span><span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">▃▃▃</span><span style="color:var(--vt-palette-8)">┈┈┈</span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span>11+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">12</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-6);font-weight:bold">│</span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span>12+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">13</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-6);font-weight:bold">│</span> <span style="color:var(--vt-palette-8)">$0</span> <span style="color:var(--vt-palette-8)">┼────────────────────────────────────</span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span>13+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">14</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-6);font-weight:bold">│</span> <span style="color:var(--vt-palette-8)">Jan</span> <span style="color:var(--vt-palette-8)">Feb</span> <span style="color:var(--vt-palette-8)">Mar</span> <span style="color:var(--vt-palette-8)">Apr</span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span>14+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">15</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-6);font-weight:bold">│</span> <span style="color:var(--vt-palette-6)">■</span> <span style="color:var(--vt-palette-8)">Rent</span> <span style="color:var(--vt-palette-5)">■</span> <span style="color:var(--vt-palette-8)">Food</span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span>15+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">16</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-6);font-weight:bold">└──────────────────────────────────────</span> <span style="color:var(--vt-palette-8)">A1:C5</span> <span style="color:var(--vt-palette-6);font-weight:bold">─◢</span></pre>\ No newline at end of file
A e2e/testdata/screens/chart-axes.html +30 -0
@@ -0,0 +1,30 @@
1+<pre class="screen"> <span style="text-decoration:underline">F</span>ile <span style="text-decoration:underline">E</span>dit <span style="text-decoration:underline">V</span>iew <span style="text-decoration:underline">I</span>nsert F<span style="text-decoration:underline">o</span>rmat <span style="text-decoration:underline">D</span>ata <span style="text-decoration:underline">H</span>elp <span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">CHART</span><span style="background:var(--vt-palette-6)"> </span>2+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">A1</span><span style="background:var(--vt-palette-8)"> </span> Month3+ <span style="color:var(--vt-palette-8)">Min</span> <span style="color:var(--vt-palette-8)">auto</span> <span style="color:var(--vt-palette-8)">Max</span> <span style="color:var(--vt-palette-8)">auto</span> <span style="color:var(--vt-palette-8)">Log</span> <span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">Gridlines</span><span style="background:var(--vt-palette-6)"> </span> <span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">Legend</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">right</span><span style="background:var(--vt-palette-6)"> </span>4+<span style="background:var(--vt-palette-8)"> </span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">A</span><span style="background:var(--vt-palette-6)"> </span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">B</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">C</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">D</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">E</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">F</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">G</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">H</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">I</span><span style="background:var(--vt-palette-8)"> </span>5+<span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">1</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">Month</span><span style="background:var(--vt-palette-6)"> </span> Rent Food <span style="color:var(--vt-palette-6);font-weight:bold">┌─</span> <span style="font-weight:bold">Rent</span> <span style="font-weight:bold">and</span> <span style="font-weight:bold">Food</span> <span style="color:var(--vt-palette-6);font-weight:bold">──────────────────────────────┐</span>6+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">2</span><span style="background:var(--vt-palette-8)"> </span> Jan $1,450 $600 <span style="color:var(--vt-palette-6);font-weight:bold">│</span> <span style="color:var(--vt-palette-8)">$1,500</span> <span style="color:var(--vt-palette-8)">┤</span><span style="color:var(--vt-palette-6)">▅▅▅</span><span style="color:var(--vt-palette-8)">┈┈┈┈</span><span style="color:var(--vt-palette-6)">▅▅▅</span><span style="color:var(--vt-palette-8)">┈┈┈┈</span><span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-8)">┈┈┈┈</span><span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-8)">┈┈┈┈</span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span>7+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">3</span><span style="background:var(--vt-palette-8)"> </span> Feb $1,450 $640 <span style="color:var(--vt-palette-6);font-weight:bold">│</span> <span style="color:var(--vt-palette-8)">│</span><span style="color:var(--vt-palette-6)">███</span> <span style="color:var(--vt-palette-6)">███</span> <span style="color:var(--vt-palette-6)">███</span> <span style="color:var(--vt-palette-6)">███</span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span>8+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">4</span><span style="background:var(--vt-palette-8)"> </span> Mar $1,500 $612 <span style="color:var(--vt-palette-6);font-weight:bold">│</span> <span style="color:var(--vt-palette-8)">$1,250</span> <span style="color:var(--vt-palette-8)">┤</span><span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-8)">┈┈┈┈</span><span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-8)">┈┈┈┈</span><span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-8)">┈┈┈┈</span><span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-8)">┈┈┈┈</span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span>9+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">5</span><span style="background:var(--vt-palette-8)"> </span> Apr $1,500 $700 <span style="color:var(--vt-palette-6);font-weight:bold">│</span> <span style="color:var(--vt-palette-8)">│</span><span style="color:var(--vt-palette-6)">███</span> <span style="color:var(--vt-palette-6)">███</span> <span style="color:var(--vt-palette-6)">███</span> <span style="color:var(--vt-palette-6)">███</span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span>10+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">6</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span> <span style="color:var(--vt-palette-8)">$1,000</span> <span style="color:var(--vt-palette-8)">┤</span><span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-8)">┈┈┈┈</span><span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-8)">┈┈┈┈</span><span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-8)">┈┈┈┈</span><span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-8)">┈┈┈┈</span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span>11+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">7</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span> <span style="color:var(--vt-palette-8)">│</span><span style="color:var(--vt-palette-6)">███</span> <span style="color:var(--vt-palette-6)">███</span> <span style="color:var(--vt-palette-6)">███</span> <span style="color:var(--vt-palette-6)">███</span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span>12+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">8</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span> <span style="color:var(--vt-palette-8)">$750</span> <span style="color:var(--vt-palette-8)">┤</span><span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-8)">┈┈┈┈</span><span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">▁▁▁</span><span style="color:var(--vt-palette-8)">┈</span><span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-8)">┈┈┈┈</span><span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">▅▅▅</span><span style="color:var(--vt-palette-8)">┈</span> <span style="color:var(--vt-palette-6)">■</span> <span style="color:var(--vt-palette-8)">Rent</span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span>13+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">9</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span> <span style="color:var(--vt-palette-8)">│</span><span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">▆▆▆</span> <span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">▇▇▇</span> <span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-5)">■</span> <span style="color:var(--vt-palette-8)">Food</span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span>14+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">10</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span> <span style="color:var(--vt-palette-8)">$500</span> <span style="color:var(--vt-palette-8)">┤</span><span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">███</span><span style="color:var(--vt-palette-8)">┈</span><span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">███</span><span style="color:var(--vt-palette-8)">┈</span><span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">███</span><span style="color:var(--vt-palette-8)">┈</span><span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">███</span><span style="color:var(--vt-palette-8)">┈</span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span>15+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">11</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span> <span style="color:var(--vt-palette-8)">│</span><span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span>16+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">12</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span> <span style="color:var(--vt-palette-8)">$250</span> <span style="color:var(--vt-palette-8)">┤</span><span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">███</span><span style="color:var(--vt-palette-8)">┈</span><span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">███</span><span style="color:var(--vt-palette-8)">┈</span><span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">███</span><span style="color:var(--vt-palette-8)">┈</span><span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">███</span><span style="color:var(--vt-palette-8)">┈</span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span>17+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">13</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span> <span style="color:var(--vt-palette-8)">│</span><span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span>18+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">14</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span> <span style="color:var(--vt-palette-8)">$0</span> <span style="color:var(--vt-palette-8)">┼─────────────────────────────</span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span>19+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">15</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span> <span style="color:var(--vt-palette-8)">Jan</span> <span style="color:var(--vt-palette-8)">Feb</span> <span style="color:var(--vt-palette-8)">Mar</span> <span style="color:var(--vt-palette-8)">Apr</span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span>20+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">16</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-6);font-weight:bold">└──────────────────────────────────────</span> <span style="color:var(--vt-palette-8)">A1:C5</span> <span style="color:var(--vt-palette-6);font-weight:bold">─◢</span>21+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">17</span><span style="background:var(--vt-palette-8)"> </span>22+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">18</span><span style="background:var(--vt-palette-8)"> </span>23+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">19</span><span style="background:var(--vt-palette-8)"> </span>24+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">20</span><span style="background:var(--vt-palette-8)"> </span>25+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">21</span><span style="background:var(--vt-palette-8)"> </span>26+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">22</span><span style="background:var(--vt-palette-8)"> </span>27+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">23</span><span style="background:var(--vt-palette-8)"> </span>28+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">24</span><span style="background:var(--vt-palette-8)"> </span>29+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">25</span><span style="background:var(--vt-palette-8)"> </span>30+ <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">N</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">min</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">X</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">max</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">L</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">log</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">G</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">grid</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">P</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">legend</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Esc</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">back</span></pre>\ No newline at end of file
A e2e/testdata/screens/chart-bar-percent.html +30 -0
@@ -0,0 +1,30 @@
1+<pre class="screen"> <span style="text-decoration:underline">F</span>ile <span style="text-decoration:underline">E</span>dit <span style="text-decoration:underline">V</span>iew <span style="text-decoration:underline">I</span>nsert F<span style="text-decoration:underline">o</span>rmat <span style="text-decoration:underline">D</span>ata <span style="text-decoration:underline">H</span>elp <span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">READY</span><span style="background:var(--vt-palette-6)"> </span>2+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">A1</span><span style="background:var(--vt-palette-8)"> </span> Month3+4+<span style="background:var(--vt-palette-8)"> </span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">A</span><span style="background:var(--vt-palette-6)"> </span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">B</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">C</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">D</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">E</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">F</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">G</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">H</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">I</span><span style="background:var(--vt-palette-8)"> </span>5+<span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">1</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">Month</span><span style="background:var(--vt-palette-6)"> </span> Rent Food <span style="color:var(--vt-palette-8)">┌─</span> <span style="font-weight:bold">Rent</span> <span style="font-weight:bold">and</span> <span style="font-weight:bold">Food</span> <span style="color:var(--vt-palette-8)">──────────────────────────────┐</span>6+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">2</span><span style="background:var(--vt-palette-8)"> </span> Jan $1,450 $600 <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">Jan</span> <span style="color:var(--vt-palette-8)">│</span><span style="color:var(--vt-palette-6)">████████████████████████</span><span style="color:var(--vt-palette-6);background:var(--vt-palette-5)">▊</span><span style="color:var(--vt-palette-5)">██████████</span> <span style="color:var(--vt-palette-8)">│</span>7+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">3</span><span style="background:var(--vt-palette-8)"> </span> Feb $1,450 $640 <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">┊</span> <span style="color:var(--vt-palette-8)">┊</span> <span style="color:var(--vt-palette-8)">┊</span> <span style="color:var(--vt-palette-8)">┊</span> <span style="color:var(--vt-palette-8)">┊</span> <span style="color:var(--vt-palette-8)">│</span>8+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">4</span><span style="background:var(--vt-palette-8)"> </span> Mar $1,500 $612 <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">Feb</span> <span style="color:var(--vt-palette-8)">│</span><span style="color:var(--vt-palette-6)">████████████████████████</span><span style="color:var(--vt-palette-6);background:var(--vt-palette-5)">▎</span><span style="color:var(--vt-palette-5)">██████████</span> <span style="color:var(--vt-palette-8)">│</span>9+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">5</span><span style="background:var(--vt-palette-8)"> </span> Apr $1,500 $700 <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">┊</span> <span style="color:var(--vt-palette-8)">┊</span> <span style="color:var(--vt-palette-8)">┊</span> <span style="color:var(--vt-palette-8)">┊</span> <span style="color:var(--vt-palette-8)">┊</span> <span style="color:var(--vt-palette-8)">│</span>10+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">6</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">Mar</span> <span style="color:var(--vt-palette-8)">│</span><span style="color:var(--vt-palette-6)">████████████████████████</span><span style="color:var(--vt-palette-6);background:var(--vt-palette-5)">▉</span><span style="color:var(--vt-palette-5)">██████████</span> <span style="color:var(--vt-palette-8)">│</span>11+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">7</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">┊</span> <span style="color:var(--vt-palette-8)">┊</span> <span style="color:var(--vt-palette-8)">┊</span> <span style="color:var(--vt-palette-8)">┊</span> <span style="color:var(--vt-palette-8)">┊</span> <span style="color:var(--vt-palette-8)">│</span>12+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">8</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">Apr</span> <span style="color:var(--vt-palette-8)">│</span><span style="color:var(--vt-palette-6)">███████████████████████</span><span style="color:var(--vt-palette-6);background:var(--vt-palette-5)">▉</span><span style="color:var(--vt-palette-5)">███████████</span> <span style="color:var(--vt-palette-8)">│</span>13+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">9</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">┊</span> <span style="color:var(--vt-palette-8)">┊</span> <span style="color:var(--vt-palette-8)">┊</span> <span style="color:var(--vt-palette-8)">┊</span> <span style="color:var(--vt-palette-8)">┊</span> <span style="color:var(--vt-palette-8)">│</span>14+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">10</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">┊</span> <span style="color:var(--vt-palette-8)">┊</span> <span style="color:var(--vt-palette-8)">┊</span> <span style="color:var(--vt-palette-8)">┊</span> <span style="color:var(--vt-palette-8)">┊</span> <span style="color:var(--vt-palette-8)">│</span>15+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">11</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">┊</span> <span style="color:var(--vt-palette-8)">┊</span> <span style="color:var(--vt-palette-8)">┊</span> <span style="color:var(--vt-palette-8)">┊</span> <span style="color:var(--vt-palette-8)">┊</span> <span style="color:var(--vt-palette-8)">│</span>16+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">12</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">┊</span> <span style="color:var(--vt-palette-8)">┊</span> <span style="color:var(--vt-palette-8)">┊</span> <span style="color:var(--vt-palette-8)">┊</span> <span style="color:var(--vt-palette-8)">┊</span> <span style="color:var(--vt-palette-8)">│</span>17+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">13</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">└──────┬──────┬──────┬──────┬──────┬</span> <span style="color:var(--vt-palette-8)">│</span>18+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">14</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">0%</span> <span style="color:var(--vt-palette-8)">20%</span> <span style="color:var(--vt-palette-8)">40%</span> <span style="color:var(--vt-palette-8)">60%</span> <span style="color:var(--vt-palette-8)">80%</span> <span style="color:var(--vt-palette-8)">100%</span> <span style="color:var(--vt-palette-8)">│</span>19+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">15</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-6)">■</span> <span style="color:var(--vt-palette-8)">Rent</span> <span style="color:var(--vt-palette-5)">■</span> <span style="color:var(--vt-palette-8)">Food</span> <span style="color:var(--vt-palette-8)">│</span>20+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">16</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">└──────────────────────────────────────</span> <span style="color:var(--vt-palette-8)">A1:C5</span> <span style="color:var(--vt-palette-8)">─┘</span>21+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">17</span><span style="background:var(--vt-palette-8)"> </span>22+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">18</span><span style="background:var(--vt-palette-8)"> </span>23+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">19</span><span style="background:var(--vt-palette-8)"> </span>24+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">20</span><span style="background:var(--vt-palette-8)"> </span>25+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">21</span><span style="background:var(--vt-palette-8)"> </span>26+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">22</span><span style="background:var(--vt-palette-8)"> </span>27+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">23</span><span style="background:var(--vt-palette-8)"> </span>28+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">24</span><span style="background:var(--vt-palette-8)"> </span>29+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">25</span><span style="background:var(--vt-palette-8)"> </span>30+<span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">Sheet1</span><span style="background:var(--vt-palette-6)"> </span> <span style="color:var(--vt-palette-8)">+</span> <span style="color:var(--vt-palette-8)">│</span> untitled <span style="color:var(--vt-palette-8)">modified</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+K</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">search</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+/</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">shortcuts</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">F10</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">menu</span></pre>\ No newline at end of file
M e2e/testdata/screens/chart-bar.html +9 -9
@@ -3,17 +3,17 @@
33 44 <span style="background:var(--vt-palette-8)"> </span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">A</span><span style="background:var(--vt-palette-6)"> </span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">B</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">C</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">D</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">E</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">F</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">G</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">H</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">I</span><span style="background:var(--vt-palette-8)"> </span>55 <span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">1</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">Month</span><span style="background:var(--vt-palette-6)"> </span> Rent Food <span style="color:var(--vt-palette-8)">┌─</span> <span style="font-weight:bold">Rent</span> <span style="font-weight:bold">and</span> <span style="font-weight:bold">Food</span> <span style="color:var(--vt-palette-8)">──────────────────────────────┐</span>6-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">2</span><span style="background:var(--vt-palette-8)"> </span> Jan $1,450 $600 <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">Jan</span> <span style="color:var(--vt-palette-8)">│</span><span style="color:var(--vt-palette-6)">██████████████████████████████████▊</span> <span style="color:var(--vt-palette-8)">│</span>7-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">3</span><span style="background:var(--vt-palette-8)"> </span> Feb $1,450 $640 <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span><span style="color:var(--vt-palette-5)">██████████████▍</span> <span style="color:var(--vt-palette-8)">│</span>8-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">4</span><span style="background:var(--vt-palette-8)"> </span> Mar $1,500 $612 <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span>9-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">5</span><span style="background:var(--vt-palette-8)"> </span> Apr $1,500 $700 <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">Feb</span> <span style="color:var(--vt-palette-8)">│</span><span style="color:var(--vt-palette-6)">██████████████████████████████████▊</span> <span style="color:var(--vt-palette-8)">│</span>10-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">6</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span><span style="color:var(--vt-palette-5)">███████████████▍</span> <span style="color:var(--vt-palette-8)">│</span>11-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">7</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span>6+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">2</span><span style="background:var(--vt-palette-8)"> </span> Jan $1,450 $600 <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">Jan</span> <span style="color:var(--vt-palette-8)">│</span><span style="color:var(--vt-palette-6)">██████████████████████████████████▊</span><span style="color:var(--vt-palette-8)">┊</span> <span style="color:var(--vt-palette-8)">│</span>7+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">3</span><span style="background:var(--vt-palette-8)"> </span> Feb $1,450 $640 <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span><span style="color:var(--vt-palette-5)">██████████████▍</span> <span style="color:var(--vt-palette-8)">┊</span> <span style="color:var(--vt-palette-8)">┊</span> <span style="color:var(--vt-palette-8)">│</span>8+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">4</span><span style="background:var(--vt-palette-8)"> </span> Mar $1,500 $612 <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">┊</span> <span style="color:var(--vt-palette-8)">┊</span> <span style="color:var(--vt-palette-8)">┊</span> <span style="color:var(--vt-palette-8)">│</span>9+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">5</span><span style="background:var(--vt-palette-8)"> </span> Apr $1,500 $700 <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">Feb</span> <span style="color:var(--vt-palette-8)">│</span><span style="color:var(--vt-palette-6)">██████████████████████████████████▊</span><span style="color:var(--vt-palette-8)">┊</span> <span style="color:var(--vt-palette-8)">│</span>10+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">6</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span><span style="color:var(--vt-palette-5)">███████████████▍</span> <span style="color:var(--vt-palette-8)">┊</span> <span style="color:var(--vt-palette-8)">┊</span> <span style="color:var(--vt-palette-8)">│</span>11+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">7</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">┊</span> <span style="color:var(--vt-palette-8)">┊</span> <span style="color:var(--vt-palette-8)">┊</span> <span style="color:var(--vt-palette-8)">│</span>1212 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">8</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">Mar</span> <span style="color:var(--vt-palette-8)">│</span><span style="color:var(--vt-palette-6)">████████████████████████████████████</span> <span style="color:var(--vt-palette-8)">│</span>13-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">9</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span><span style="color:var(--vt-palette-5)">██████████████▊</span> <span style="color:var(--vt-palette-8)">│</span>14-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">10</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span>13+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">9</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span><span style="color:var(--vt-palette-5)">██████████████▊</span> <span style="color:var(--vt-palette-8)">┊</span> <span style="color:var(--vt-palette-8)">┊</span> <span style="color:var(--vt-palette-8)">│</span>14+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">10</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">┊</span> <span style="color:var(--vt-palette-8)">┊</span> <span style="color:var(--vt-palette-8)">┊</span> <span style="color:var(--vt-palette-8)">│</span>1515 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">11</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">Apr</span> <span style="color:var(--vt-palette-8)">│</span><span style="color:var(--vt-palette-6)">████████████████████████████████████</span> <span style="color:var(--vt-palette-8)">│</span>16-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">12</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span><span style="color:var(--vt-palette-5)">████████████████▊</span> <span style="color:var(--vt-palette-8)">│</span>16+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">12</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span><span style="color:var(--vt-palette-5)">████████████████▊</span> <span style="color:var(--vt-palette-8)">┊</span> <span style="color:var(--vt-palette-8)">┊</span> <span style="color:var(--vt-palette-8)">│</span>1717 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">13</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">└───────────┬───────────┬───────────┬</span> <span style="color:var(--vt-palette-8)">│</span>1818 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">14</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">$0</span> <span style="color:var(--vt-palette-8)">$500</span> <span style="color:var(--vt-palette-8)">$1,000</span> <span style="color:var(--vt-palette-8)">$1,500</span> <span style="color:var(--vt-palette-8)">│</span>1919 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">15</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-6)">■</span> <span style="color:var(--vt-palette-8)">Rent</span> <span style="color:var(--vt-palette-5)">■</span> <span style="color:var(--vt-palette-8)">Food</span> <span style="color:var(--vt-palette-8)">│</span>
M e2e/testdata/screens/chart-editor-light.html +4 -4
@@ -1,17 +1,17 @@
11 <pre class="screen"> <span style="text-decoration:underline">F</span>ile <span style="text-decoration:underline">E</span>dit <span style="text-decoration:underline">V</span>iew <span style="text-decoration:underline">I</span>nsert F<span style="text-decoration:underline">o</span>rmat <span style="text-decoration:underline">D</span>ata <span style="text-decoration:underline">H</span>elp <span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">CHART</span><span style="background:var(--vt-palette-6)"> </span>22 <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">A1</span><span style="background:var(--vt-palette-7)"> </span> Month3-<span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">Column</span><span style="background:var(--vt-palette-6)"> </span> <span style="color:var(--vt-palette-0)">Bar</span> <span style="color:var(--vt-palette-0)">Line</span> <span style="color:var(--vt-palette-0)">Pie</span> <span style="font-weight:bold">A1:C5</span> <span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">Series</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">in</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">columns</span><span style="background:var(--vt-palette-6)"> </span> <span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">Header</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">row</span><span style="background:var(--vt-palette-6)"> </span> <span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">Labels</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">in</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">column</span><span style="background:var(--vt-palette-6)"> </span>3+<span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">◂</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">Column</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">▸</span><span style="background:var(--vt-palette-6)"> </span> <span style="font-weight:bold">A1:C5</span> <span style="color:var(--vt-palette-0)">Not</span> <span style="color:var(--vt-palette-0)">stacked</span> <span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">Series</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">in</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">columns</span><span style="background:var(--vt-palette-6)"> </span> <span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">Header</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">row</span><span style="background:var(--vt-palette-6)"> </span> <span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">Labels</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">in</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">column</span><span style="background:var(--vt-palette-6)"> </span>44 <span style="background:var(--vt-palette-7)"> </span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">A</span><span style="background:var(--vt-palette-6)"> </span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">B</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">C</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">D</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">E</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">F</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">G</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">H</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">I</span><span style="background:var(--vt-palette-7)"> </span>55 <span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">1</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">Month</span><span style="background:var(--vt-palette-6)"> </span> Rent Food <span style="color:var(--vt-palette-6);font-weight:bold">┌─</span> <span style="font-weight:bold">Rent</span> <span style="font-weight:bold">and</span> <span style="font-weight:bold">Food</span> <span style="color:var(--vt-palette-6);font-weight:bold">──────────────────────────────┐</span>66 <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">2</span><span style="background:var(--vt-palette-7)"> </span> Jan $1,450 $600 <span style="color:var(--vt-palette-6);font-weight:bold">│</span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span>77 <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">3</span><span style="background:var(--vt-palette-7)"> </span> Feb $1,450 $640 <span style="color:var(--vt-palette-6);font-weight:bold">│</span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span>8-<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">4</span><span style="background:var(--vt-palette-7)"> </span> Mar $1,500 $612 <span style="color:var(--vt-palette-6);font-weight:bold">│</span> <span style="color:var(--vt-palette-0)">$1,500</span> <span style="color:var(--vt-palette-8)">┤</span> <span style="color:var(--vt-palette-4)">▆▆▆</span> <span style="color:var(--vt-palette-4)">▆▆▆</span> <span style="color:var(--vt-palette-4)">███</span> <span style="color:var(--vt-palette-4)">███</span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span>8+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">4</span><span style="background:var(--vt-palette-7)"> </span> Mar $1,500 $612 <span style="color:var(--vt-palette-6);font-weight:bold">│</span> <span style="color:var(--vt-palette-0)">$1,500</span> <span style="color:var(--vt-palette-8)">┤┈┈</span><span style="color:var(--vt-palette-4)">▆▆▆</span><span style="color:var(--vt-palette-8)">┈┈┈┈┈</span><span style="color:var(--vt-palette-4)">▆▆▆</span><span style="color:var(--vt-palette-8)">┈┈┈┈┈</span><span style="color:var(--vt-palette-4)">███</span><span style="color:var(--vt-palette-8)">┈┈┈┈┈</span><span style="color:var(--vt-palette-4)">███</span><span style="color:var(--vt-palette-8)">┈┈┈┈┈┈</span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span>99 <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">5</span><span style="background:var(--vt-palette-7)"> </span> Apr $1,500 $700 <span style="color:var(--vt-palette-6);font-weight:bold">│</span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-4)">███</span> <span style="color:var(--vt-palette-4)">███</span> <span style="color:var(--vt-palette-4)">███</span> <span style="color:var(--vt-palette-4)">███</span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span>1010 <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">6</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-4)">███</span> <span style="color:var(--vt-palette-4)">███</span> <span style="color:var(--vt-palette-4)">███</span> <span style="color:var(--vt-palette-4)">███</span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span>11-<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">7</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span> <span style="color:var(--vt-palette-0)">$1,000</span> <span style="color:var(--vt-palette-8)">┤</span> <span style="color:var(--vt-palette-4)">███</span> <span style="color:var(--vt-palette-4)">███</span> <span style="color:var(--vt-palette-4)">███</span> <span style="color:var(--vt-palette-4)">███</span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span>11+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">7</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span> <span style="color:var(--vt-palette-0)">$1,000</span> <span style="color:var(--vt-palette-8)">┤┈┈</span><span style="color:var(--vt-palette-4)">███</span><span style="color:var(--vt-palette-8)">┈┈┈┈┈</span><span style="color:var(--vt-palette-4)">███</span><span style="color:var(--vt-palette-8)">┈┈┈┈┈</span><span style="color:var(--vt-palette-4)">███</span><span style="color:var(--vt-palette-8)">┈┈┈┈┈</span><span style="color:var(--vt-palette-4)">███</span><span style="color:var(--vt-palette-8)">┈┈┈┈┈┈</span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span>1212 <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">8</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-4)">███</span> <span style="color:var(--vt-palette-4)">███</span> <span style="color:var(--vt-palette-4)">███</span> <span style="color:var(--vt-palette-4)">███</span><span style="color:var(--vt-palette-5)">▂▂▂</span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span>1313 <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">9</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-4)">███</span><span style="color:var(--vt-palette-5)">▅▅▅</span> <span style="color:var(--vt-palette-4)">███</span><span style="color:var(--vt-palette-5)">▇▇▇</span> <span style="color:var(--vt-palette-4)">███</span><span style="color:var(--vt-palette-5)">▅▅▅</span> <span style="color:var(--vt-palette-4)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span>14-<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">10</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span> <span style="color:var(--vt-palette-0)">$500</span> <span style="color:var(--vt-palette-8)">┤</span> <span style="color:var(--vt-palette-4)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-4)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-4)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-4)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span>14+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">10</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span> <span style="color:var(--vt-palette-0)">$500</span> <span style="color:var(--vt-palette-8)">┤┈┈</span><span style="color:var(--vt-palette-4)">███</span><span style="color:var(--vt-palette-5)">███</span><span style="color:var(--vt-palette-8)">┈┈</span><span style="color:var(--vt-palette-4)">███</span><span style="color:var(--vt-palette-5)">███</span><span style="color:var(--vt-palette-8)">┈┈</span><span style="color:var(--vt-palette-4)">███</span><span style="color:var(--vt-palette-5)">███</span><span style="color:var(--vt-palette-8)">┈┈</span><span style="color:var(--vt-palette-4)">███</span><span style="color:var(--vt-palette-5)">███</span><span style="color:var(--vt-palette-8)">┈┈┈</span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span>1515 <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">11</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-4)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-4)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-4)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-4)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span>1616 <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">12</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-4)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-4)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-4)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-4)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span>1717 <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">13</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span> <span style="color:var(--vt-palette-0)">$0</span> <span style="color:var(--vt-palette-8)">┼────────────────────────────────────</span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span>
M e2e/testdata/screens/chart-editor.html +4 -4
@@ -1,17 +1,17 @@
11 <pre class="screen"> <span style="text-decoration:underline">F</span>ile <span style="text-decoration:underline">E</span>dit <span style="text-decoration:underline">V</span>iew <span style="text-decoration:underline">I</span>nsert F<span style="text-decoration:underline">o</span>rmat <span style="text-decoration:underline">D</span>ata <span style="text-decoration:underline">H</span>elp <span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">CHART</span><span style="background:var(--vt-palette-6)"> </span>22 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">A1</span><span style="background:var(--vt-palette-8)"> </span> Month3-<span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">Column</span><span style="background:var(--vt-palette-6)"> </span> <span style="color:var(--vt-palette-8)">Bar</span> <span style="color:var(--vt-palette-8)">Line</span> <span style="color:var(--vt-palette-8)">Pie</span> <span style="font-weight:bold">A1:C5</span> <span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">Series</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">in</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">columns</span><span style="background:var(--vt-palette-6)"> </span> <span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">Header</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">row</span><span style="background:var(--vt-palette-6)"> </span> <span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">Labels</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">in</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">column</span><span style="background:var(--vt-palette-6)"> </span>3+<span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">◂</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">Column</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">▸</span><span style="background:var(--vt-palette-6)"> </span> <span style="font-weight:bold">A1:C5</span> <span style="color:var(--vt-palette-8)">Not</span> <span style="color:var(--vt-palette-8)">stacked</span> <span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">Series</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">in</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">columns</span><span style="background:var(--vt-palette-6)"> </span> <span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">Header</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">row</span><span style="background:var(--vt-palette-6)"> </span> <span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">Labels</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">in</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">column</span><span style="background:var(--vt-palette-6)"> </span>44 <span style="background:var(--vt-palette-8)"> </span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">A</span><span style="background:var(--vt-palette-6)"> </span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">B</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">C</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">D</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">E</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">F</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">G</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">H</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">I</span><span style="background:var(--vt-palette-8)"> </span>55 <span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">1</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">Month</span><span style="background:var(--vt-palette-6)"> </span> Rent Food <span style="color:var(--vt-palette-6);font-weight:bold">┌─</span> <span style="font-weight:bold">Rent</span> <span style="font-weight:bold">and</span> <span style="font-weight:bold">Food</span> <span style="color:var(--vt-palette-6);font-weight:bold">──────────────────────────────┐</span>66 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">2</span><span style="background:var(--vt-palette-8)"> </span> Jan $1,450 $600 <span style="color:var(--vt-palette-6);font-weight:bold">│</span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span>77 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">3</span><span style="background:var(--vt-palette-8)"> </span> Feb $1,450 $640 <span style="color:var(--vt-palette-6);font-weight:bold">│</span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span>8-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">4</span><span style="background:var(--vt-palette-8)"> </span> Mar $1,500 $612 <span style="color:var(--vt-palette-6);font-weight:bold">│</span> <span style="color:var(--vt-palette-8)">$1,500</span> <span style="color:var(--vt-palette-8)">┤</span> <span style="color:var(--vt-palette-6)">▆▆▆</span> <span style="color:var(--vt-palette-6)">▆▆▆</span> <span style="color:var(--vt-palette-6)">███</span> <span style="color:var(--vt-palette-6)">███</span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span>8+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">4</span><span style="background:var(--vt-palette-8)"> </span> Mar $1,500 $612 <span style="color:var(--vt-palette-6);font-weight:bold">│</span> <span style="color:var(--vt-palette-8)">$1,500</span> <span style="color:var(--vt-palette-8)">┤┈┈</span><span style="color:var(--vt-palette-6)">▆▆▆</span><span style="color:var(--vt-palette-8)">┈┈┈┈┈</span><span style="color:var(--vt-palette-6)">▆▆▆</span><span style="color:var(--vt-palette-8)">┈┈┈┈┈</span><span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-8)">┈┈┈┈┈</span><span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-8)">┈┈┈┈┈┈</span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span>99 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">5</span><span style="background:var(--vt-palette-8)"> </span> Apr $1,500 $700 <span style="color:var(--vt-palette-6);font-weight:bold">│</span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-6)">███</span> <span style="color:var(--vt-palette-6)">███</span> <span style="color:var(--vt-palette-6)">███</span> <span style="color:var(--vt-palette-6)">███</span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span>1010 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">6</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-6)">███</span> <span style="color:var(--vt-palette-6)">███</span> <span style="color:var(--vt-palette-6)">███</span> <span style="color:var(--vt-palette-6)">███</span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span>11-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">7</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span> <span style="color:var(--vt-palette-8)">$1,000</span> <span style="color:var(--vt-palette-8)">┤</span> <span style="color:var(--vt-palette-6)">███</span> <span style="color:var(--vt-palette-6)">███</span> <span style="color:var(--vt-palette-6)">███</span> <span style="color:var(--vt-palette-6)">███</span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span>11+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">7</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span> <span style="color:var(--vt-palette-8)">$1,000</span> <span style="color:var(--vt-palette-8)">┤┈┈</span><span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-8)">┈┈┈┈┈</span><span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-8)">┈┈┈┈┈</span><span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-8)">┈┈┈┈┈</span><span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-8)">┈┈┈┈┈┈</span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span>1212 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">8</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-6)">███</span> <span style="color:var(--vt-palette-6)">███</span> <span style="color:var(--vt-palette-6)">███</span> <span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">▂▂▂</span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span>1313 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">9</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">▅▅▅</span> <span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">▇▇▇</span> <span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">▅▅▅</span> <span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span>14-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">10</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span> <span style="color:var(--vt-palette-8)">$500</span> <span style="color:var(--vt-palette-8)">┤</span> <span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span>14+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">10</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span> <span style="color:var(--vt-palette-8)">$500</span> <span style="color:var(--vt-palette-8)">┤┈┈</span><span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">███</span><span style="color:var(--vt-palette-8)">┈┈</span><span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">███</span><span style="color:var(--vt-palette-8)">┈┈</span><span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">███</span><span style="color:var(--vt-palette-8)">┈┈</span><span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">███</span><span style="color:var(--vt-palette-8)">┈┈┈</span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span>1515 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">11</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span>1616 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">12</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span>1717 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">13</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span> <span style="color:var(--vt-palette-8)">$0</span> <span style="color:var(--vt-palette-8)">┼────────────────────────────────────</span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span>
M e2e/testdata/screens/chart-light.html +3 -3
@@ -5,13 +5,13 @@
55 <span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">1</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">Month</span><span style="background:var(--vt-palette-6)"> </span> Rent Food <span style="color:var(--vt-palette-8)">┌─</span> <span style="font-weight:bold">Rent</span> <span style="font-weight:bold">and</span> <span style="font-weight:bold">Food</span> <span style="color:var(--vt-palette-8)">──────────────────────────────┐</span>66 <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">2</span><span style="background:var(--vt-palette-7)"> </span> Jan $1,450 $600 <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span>77 <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">3</span><span style="background:var(--vt-palette-7)"> </span> Feb $1,450 $640 <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span>8-<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">4</span><span style="background:var(--vt-palette-7)"> </span> Mar $1,500 $612 <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-0)">$1,500</span> <span style="color:var(--vt-palette-8)">┤</span> <span style="color:var(--vt-palette-4)">▆▆▆</span> <span style="color:var(--vt-palette-4)">▆▆▆</span> <span style="color:var(--vt-palette-4)">███</span> <span style="color:var(--vt-palette-4)">███</span> <span style="color:var(--vt-palette-8)">│</span>8+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">4</span><span style="background:var(--vt-palette-7)"> </span> Mar $1,500 $612 <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-0)">$1,500</span> <span style="color:var(--vt-palette-8)">┤┈┈</span><span style="color:var(--vt-palette-4)">▆▆▆</span><span style="color:var(--vt-palette-8)">┈┈┈┈┈</span><span style="color:var(--vt-palette-4)">▆▆▆</span><span style="color:var(--vt-palette-8)">┈┈┈┈┈</span><span style="color:var(--vt-palette-4)">███</span><span style="color:var(--vt-palette-8)">┈┈┈┈┈</span><span style="color:var(--vt-palette-4)">███</span><span style="color:var(--vt-palette-8)">┈┈┈┈┈┈</span> <span style="color:var(--vt-palette-8)">│</span>99 <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">5</span><span style="background:var(--vt-palette-7)"> </span> Apr $1,500 $700 <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-4)">███</span> <span style="color:var(--vt-palette-4)">███</span> <span style="color:var(--vt-palette-4)">███</span> <span style="color:var(--vt-palette-4)">███</span> <span style="color:var(--vt-palette-8)">│</span>1010 <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">6</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-4)">███</span> <span style="color:var(--vt-palette-4)">███</span> <span style="color:var(--vt-palette-4)">███</span> <span style="color:var(--vt-palette-4)">███</span> <span style="color:var(--vt-palette-8)">│</span>11-<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">7</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-0)">$1,000</span> <span style="color:var(--vt-palette-8)">┤</span> <span style="color:var(--vt-palette-4)">███</span> <span style="color:var(--vt-palette-4)">███</span> <span style="color:var(--vt-palette-4)">███</span> <span style="color:var(--vt-palette-4)">███</span> <span style="color:var(--vt-palette-8)">│</span>11+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">7</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-0)">$1,000</span> <span style="color:var(--vt-palette-8)">┤┈┈</span><span style="color:var(--vt-palette-4)">███</span><span style="color:var(--vt-palette-8)">┈┈┈┈┈</span><span style="color:var(--vt-palette-4)">███</span><span style="color:var(--vt-palette-8)">┈┈┈┈┈</span><span style="color:var(--vt-palette-4)">███</span><span style="color:var(--vt-palette-8)">┈┈┈┈┈</span><span style="color:var(--vt-palette-4)">███</span><span style="color:var(--vt-palette-8)">┈┈┈┈┈┈</span> <span style="color:var(--vt-palette-8)">│</span>1212 <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">8</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-4)">███</span> <span style="color:var(--vt-palette-4)">███</span> <span style="color:var(--vt-palette-4)">███</span> <span style="color:var(--vt-palette-4)">███</span><span style="color:var(--vt-palette-5)">▂▂▂</span> <span style="color:var(--vt-palette-8)">│</span>1313 <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">9</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-4)">███</span><span style="color:var(--vt-palette-5)">▅▅▅</span> <span style="color:var(--vt-palette-4)">███</span><span style="color:var(--vt-palette-5)">▇▇▇</span> <span style="color:var(--vt-palette-4)">███</span><span style="color:var(--vt-palette-5)">▅▅▅</span> <span style="color:var(--vt-palette-4)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-8)">│</span>14-<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">10</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-0)">$500</span> <span style="color:var(--vt-palette-8)">┤</span> <span style="color:var(--vt-palette-4)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-4)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-4)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-4)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-8)">│</span>14+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">10</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-0)">$500</span> <span style="color:var(--vt-palette-8)">┤┈┈</span><span style="color:var(--vt-palette-4)">███</span><span style="color:var(--vt-palette-5)">███</span><span style="color:var(--vt-palette-8)">┈┈</span><span style="color:var(--vt-palette-4)">███</span><span style="color:var(--vt-palette-5)">███</span><span style="color:var(--vt-palette-8)">┈┈</span><span style="color:var(--vt-palette-4)">███</span><span style="color:var(--vt-palette-5)">███</span><span style="color:var(--vt-palette-8)">┈┈</span><span style="color:var(--vt-palette-4)">███</span><span style="color:var(--vt-palette-5)">███</span><span style="color:var(--vt-palette-8)">┈┈┈</span> <span style="color:var(--vt-palette-8)">│</span>1515 <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">11</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-4)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-4)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-4)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-4)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-8)">│</span>1616 <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">12</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-4)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-4)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-4)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-4)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-8)">│</span>1717 <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">13</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-0)">$0</span> <span style="color:var(--vt-palette-8)">┼────────────────────────────────────</span> <span style="color:var(--vt-palette-8)">│</span>
M e2e/testdata/screens/chart-line-light.html +5 -5
@@ -4,15 +4,15 @@
44 <span style="background:var(--vt-palette-7)"> </span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">A</span><span style="background:var(--vt-palette-6)"> </span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">B</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">C</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">D</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">E</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">F</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">G</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">H</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">I</span><span style="background:var(--vt-palette-7)"> </span>55 <span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">1</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">Month</span><span style="background:var(--vt-palette-6)"> </span> Rent Food <span style="color:var(--vt-palette-8)">┌─</span> <span style="font-weight:bold">Rent</span> <span style="font-weight:bold">and</span> <span style="font-weight:bold">Food</span> <span style="color:var(--vt-palette-8)">──────────────────────────────┐</span>66 <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">2</span><span style="background:var(--vt-palette-7)"> </span> Jan $1,450 $600 <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span>7-<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">3</span><span style="background:var(--vt-palette-7)"> </span> Feb $1,450 $640 <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-0)">$1,600</span> <span style="color:var(--vt-palette-8)">┤</span> <span style="color:var(--vt-palette-8)">│</span>7+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">3</span><span style="background:var(--vt-palette-7)"> </span> Feb $1,450 $640 <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-0)">$1,600</span> <span style="color:var(--vt-palette-8)">┤┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈</span> <span style="color:var(--vt-palette-8)">│</span>88 <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">4</span><span style="background:var(--vt-palette-7)"> </span> Mar $1,500 $612 <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span><span style="color:var(--vt-palette-4)">⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠔⠒⠒⠒⠒⠒⠊⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉</span> <span style="color:var(--vt-palette-8)">│</span>9-<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">5</span><span style="background:var(--vt-palette-7)"> </span> Apr $1,500 $700 <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-0)">$1,400</span> <span style="color:var(--vt-palette-8)">┤</span> <span style="color:var(--vt-palette-8)">│</span>9+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">5</span><span style="background:var(--vt-palette-7)"> </span> Apr $1,500 $700 <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-0)">$1,400</span> <span style="color:var(--vt-palette-8)">┤┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈</span> <span style="color:var(--vt-palette-8)">│</span>1010 <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">6</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span>11-<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">7</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-0)">$1,200</span> <span style="color:var(--vt-palette-8)">┤</span> <span style="color:var(--vt-palette-8)">│</span>11+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">7</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-0)">$1,200</span> <span style="color:var(--vt-palette-8)">┤┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈</span> <span style="color:var(--vt-palette-8)">│</span>1212 <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">8</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span>13-<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">9</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-0)">$1,000</span> <span style="color:var(--vt-palette-8)">┤</span> <span style="color:var(--vt-palette-8)">│</span>13+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">9</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-0)">$1,000</span> <span style="color:var(--vt-palette-8)">┤┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈</span> <span style="color:var(--vt-palette-8)">│</span>1414 <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">10</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span>15-<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">11</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-0)">$800</span> <span style="color:var(--vt-palette-8)">┤</span> <span style="color:var(--vt-palette-5)">⢀⣀</span> <span style="color:var(--vt-palette-8)">│</span>15+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">11</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-0)">$800</span> <span style="color:var(--vt-palette-8)">┤┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈</span><span style="color:var(--vt-palette-5)">⢀⣀</span> <span style="color:var(--vt-palette-8)">│</span>1616 <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">12</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span><span style="color:var(--vt-palette-5)">⣀⣀⣀⠤⠤⠤⠤⠤⠤⠒⠒⠒⠒⠒⠢⠤⠤⠤⠤⠤⢄⣀⣀⣀⡠⠤⠤⠔⠒⠒⠊⠉⠉⠁</span> <span style="color:var(--vt-palette-8)">│</span>1717 <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">13</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-0)">$600</span> <span style="color:var(--vt-palette-8)">┼────────────────────────────────────</span> <span style="color:var(--vt-palette-8)">│</span>1818 <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">14</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-0)">Jan</span> <span style="color:var(--vt-palette-0)">Feb</span> <span style="color:var(--vt-palette-0)">Mar</span> <span style="color:var(--vt-palette-0)">Apr</span> <span style="color:var(--vt-palette-8)">│</span>
M e2e/testdata/screens/chart-line.html +5 -5
@@ -4,15 +4,15 @@
44 <span style="background:var(--vt-palette-8)"> </span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">A</span><span style="background:var(--vt-palette-6)"> </span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">B</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">C</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">D</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">E</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">F</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">G</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">H</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">I</span><span style="background:var(--vt-palette-8)"> </span>55 <span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">1</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">Month</span><span style="background:var(--vt-palette-6)"> </span> Rent Food <span style="color:var(--vt-palette-8)">┌─</span> <span style="font-weight:bold">Rent</span> <span style="font-weight:bold">and</span> <span style="font-weight:bold">Food</span> <span style="color:var(--vt-palette-8)">──────────────────────────────┐</span>66 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">2</span><span style="background:var(--vt-palette-8)"> </span> Jan $1,450 $600 <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span>7-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">3</span><span style="background:var(--vt-palette-8)"> </span> Feb $1,450 $640 <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">$1,600</span> <span style="color:var(--vt-palette-8)">┤</span> <span style="color:var(--vt-palette-8)">│</span>7+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">3</span><span style="background:var(--vt-palette-8)"> </span> Feb $1,450 $640 <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">$1,600</span> <span style="color:var(--vt-palette-8)">┤┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈</span> <span style="color:var(--vt-palette-8)">│</span>88 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">4</span><span style="background:var(--vt-palette-8)"> </span> Mar $1,500 $612 <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span><span style="color:var(--vt-palette-6)">⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠔⠒⠒⠒⠒⠒⠊⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉</span> <span style="color:var(--vt-palette-8)">│</span>9-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">5</span><span style="background:var(--vt-palette-8)"> </span> Apr $1,500 $700 <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">$1,400</span> <span style="color:var(--vt-palette-8)">┤</span> <span style="color:var(--vt-palette-8)">│</span>9+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">5</span><span style="background:var(--vt-palette-8)"> </span> Apr $1,500 $700 <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">$1,400</span> <span style="color:var(--vt-palette-8)">┤┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈</span> <span style="color:var(--vt-palette-8)">│</span>1010 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">6</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span>11-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">7</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">$1,200</span> <span style="color:var(--vt-palette-8)">┤</span> <span style="color:var(--vt-palette-8)">│</span>11+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">7</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">$1,200</span> <span style="color:var(--vt-palette-8)">┤┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈</span> <span style="color:var(--vt-palette-8)">│</span>1212 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">8</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span>13-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">9</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">$1,000</span> <span style="color:var(--vt-palette-8)">┤</span> <span style="color:var(--vt-palette-8)">│</span>13+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">9</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">$1,000</span> <span style="color:var(--vt-palette-8)">┤┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈</span> <span style="color:var(--vt-palette-8)">│</span>1414 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">10</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span>15-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">11</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">$800</span> <span style="color:var(--vt-palette-8)">┤</span> <span style="color:var(--vt-palette-5)">⢀⣀</span> <span style="color:var(--vt-palette-8)">│</span>15+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">11</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">$800</span> <span style="color:var(--vt-palette-8)">┤┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈</span><span style="color:var(--vt-palette-5)">⢀⣀</span> <span style="color:var(--vt-palette-8)">│</span>1616 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">12</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span><span style="color:var(--vt-palette-5)">⣀⣀⣀⠤⠤⠤⠤⠤⠤⠒⠒⠒⠒⠒⠢⠤⠤⠤⠤⠤⢄⣀⣀⣀⡠⠤⠤⠔⠒⠒⠊⠉⠉⠁</span> <span style="color:var(--vt-palette-8)">│</span>1717 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">13</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">$600</span> <span style="color:var(--vt-palette-8)">┼────────────────────────────────────</span> <span style="color:var(--vt-palette-8)">│</span>1818 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">14</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">Jan</span> <span style="color:var(--vt-palette-8)">Feb</span> <span style="color:var(--vt-palette-8)">Mar</span> <span style="color:var(--vt-palette-8)">Apr</span> <span style="color:var(--vt-palette-8)">│</span>
M e2e/testdata/screens/chart-narrow.html +4 -5
@@ -1,16 +1,15 @@
11 <pre class="screen"> <span style="text-decoration:underline">F</span>ile <span style="text-decoration:underline">E</span>dit <span style="text-decoration:underline">V</span>iew <span style="text-decoration:underline">I</span>nsert F<span style="text-decoration:underline">o</span>rmat <span style="text-decoration:underline">D</span>ata <span style="text-decoration:underline">H</span>elp <span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">CHART</span><span style="background:var(--vt-palette-6)"> </span>22 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">A1</span><span style="background:var(--vt-palette-8)"> </span> Month3-<span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">Column</span><span style="background:var(--vt-palette-6)"> </span> <span style="color:var(--vt-palette-8)">Bar</span> <span style="color:var(--vt-palette-8)">Line</span> <span style="color:var(--vt-palette-8)">Pie</span> <span style="font-weight:bold">A1:C5</span> <span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">Series</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">in</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">columns</span><span style="background:var(--vt-palette-6)"> </span>3+<span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">◂</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">Column</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">▸</span><span style="background:var(--vt-palette-6)"> </span> <span style="font-weight:bold">A1:C5</span> <span style="color:var(--vt-palette-8)">Not</span> <span style="color:var(--vt-palette-8)">stacked</span> <span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">Series</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">in</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">columns</span><span style="background:var(--vt-palette-6)"> </span>44 <span style="background:var(--vt-palette-8)"> </span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">A</span><span style="background:var(--vt-palette-6)"> </span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">B</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">C</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">D</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">E</span><span style="background:var(--vt-palette-8)"> </span>55 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">6</span><span style="background:var(--vt-palette-8)"> </span>66 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">7</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-6);font-weight:bold">┌─</span> <span style="font-weight:bold">Rent</span> <span style="font-weight:bold">and</span> <span style="font-weight:bold">Food</span> <span style="color:var(--vt-palette-6);font-weight:bold">──────────────────────────────┐</span>77 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">8</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-6);font-weight:bold">│</span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span>8-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">9</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-6);font-weight:bold">│</span> <span style="color:var(--vt-palette-8)">$2,000</span> <span style="color:var(--vt-palette-8)">┤</span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span>8+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">9</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-6);font-weight:bold">│</span> <span style="color:var(--vt-palette-8)">$2,000</span> <span style="color:var(--vt-palette-8)">┤┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈</span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span>99 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">10</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-6);font-weight:bold">│</span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-6)">▇▇▇</span> <span style="color:var(--vt-palette-6)">▇▇▇</span> <span style="color:var(--vt-palette-6)">███</span> <span style="color:var(--vt-palette-6)">███</span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span>10-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">11</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-6);font-weight:bold">│</span> <span style="color:var(--vt-palette-8)">$1,000</span> <span style="color:var(--vt-palette-8)">┤</span> <span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">▂▂▂</span> <span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">▂▂▂</span> <span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">▂▂▂</span> <span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">▃▃▃</span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span>10+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">11</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-6);font-weight:bold">│</span> <span style="color:var(--vt-palette-8)">$1,000</span> <span style="color:var(--vt-palette-8)">┤┈┈</span><span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">▂▂▂</span><span style="color:var(--vt-palette-8)">┈┈</span><span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">▂▂▂</span><span style="color:var(--vt-palette-8)">┈┈</span><span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">▂▂▂</span><span style="color:var(--vt-palette-8)">┈┈</span><span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">▃▃▃</span><span style="color:var(--vt-palette-8)">┈┈┈</span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span>1111 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">12</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-6);font-weight:bold">│</span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span>1212 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">13</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-6);font-weight:bold">│</span> <span style="color:var(--vt-palette-8)">$0</span> <span style="color:var(--vt-palette-8)">┼────────────────────────────────────</span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span>1313 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">14</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-6);font-weight:bold">│</span> <span style="color:var(--vt-palette-8)">Jan</span> <span style="color:var(--vt-palette-8)">Feb</span> <span style="color:var(--vt-palette-8)">Mar</span> <span style="color:var(--vt-palette-8)">Apr</span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span>1414 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">15</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-6);font-weight:bold">│</span> <span style="color:var(--vt-palette-6)">■</span> <span style="color:var(--vt-palette-8)">Rent</span> <span style="color:var(--vt-palette-5)">■</span> <span style="color:var(--vt-palette-8)">Food</span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span>15-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">16</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-6);font-weight:bold">└──────────────────────────────────────</span> <span style="color:var(--vt-palette-8)">A1:C5</span> <span style="color:var(--vt-palette-6);font-weight:bold">─◢</span>16- <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">←/→</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">type</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Enter</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">done</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Esc</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">cancel</span></pre>\ No newline at end of file15+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">16</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-6);font-weight:bold">└──────────────────────────────────────</span> <span style="color:var(--vt-palette-8)">A1:C5</span> <span style="color:var(--vt-palette-6);font-weight:bold">─◢</span></pre>\ No newline at end of file
A e2e/testdata/screens/chart-scatter-light.html +30 -0
@@ -0,0 +1,30 @@
1+<pre class="screen"> <span style="text-decoration:underline">F</span>ile <span style="text-decoration:underline">E</span>dit <span style="text-decoration:underline">V</span>iew <span style="text-decoration:underline">I</span>nsert F<span style="text-decoration:underline">o</span>rmat <span style="text-decoration:underline">D</span>ata <span style="text-decoration:underline">H</span>elp <span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">READY</span><span style="background:var(--vt-palette-6)"> </span>2+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">A1</span><span style="background:var(--vt-palette-7)"> </span> Month3+4+<span style="background:var(--vt-palette-7)"> </span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">A</span><span style="background:var(--vt-palette-6)"> </span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">B</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">C</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">D</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">E</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">F</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">G</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">H</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">I</span><span style="background:var(--vt-palette-7)"> </span>5+<span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">1</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">Month</span><span style="background:var(--vt-palette-6)"> </span> Rent Food <span style="color:var(--vt-palette-8)">┌─</span> <span style="font-weight:bold">Rent</span> <span style="font-weight:bold">and</span> <span style="font-weight:bold">Food</span> <span style="color:var(--vt-palette-8)">──────────────────────────────┐</span>6+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">2</span><span style="background:var(--vt-palette-7)"> </span> Jan $1,450 $600 <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span>7+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">3</span><span style="background:var(--vt-palette-7)"> </span> Feb $1,450 $640 <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-4)">⣀</span> <span style="color:var(--vt-palette-8)">│</span>8+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">4</span><span style="background:var(--vt-palette-7)"> </span> Mar $1,500 $612 <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-0)">$700</span> <span style="color:var(--vt-palette-8)">┤┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈</span><span style="color:var(--vt-palette-4)">⠉</span> <span style="color:var(--vt-palette-8)">│</span>9+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">5</span><span style="background:var(--vt-palette-7)"> </span> Apr $1,500 $700 <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span>10+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">6</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-0)">$680</span> <span style="color:var(--vt-palette-8)">┤┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈</span> <span style="color:var(--vt-palette-8)">│</span>11+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">7</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span>12+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">8</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-0)">$660</span> <span style="color:var(--vt-palette-8)">┤┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈</span><span style="color:var(--vt-palette-4)">⣀⣀⠤⠤⠒</span> <span style="color:var(--vt-palette-8)">│</span>13+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">9</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-4)">⢠⡄</span> <span style="color:var(--vt-palette-4)">⢀⣀⡠⠤⠒⠒⠉⠉</span> <span style="color:var(--vt-palette-8)">│</span>14+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">10</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-0)">$640</span> <span style="color:var(--vt-palette-8)">┤┈┈┈┈┈┈┈┈┈┈┈┈┈</span><span style="color:var(--vt-palette-4)">⣀⣀⠤⠴⠒⠊⠉⠁</span><span style="color:var(--vt-palette-8)">┈┈┈┈┈┈┈┈┈┈┈┈</span> <span style="color:var(--vt-palette-8)">│</span>15+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">11</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-4)">⣀⣀⠤⠤⠒⠒⠉⠉</span> <span style="color:var(--vt-palette-8)">│</span>16+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">12</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-0)">$620</span> <span style="color:var(--vt-palette-8)">┤</span><span style="color:var(--vt-palette-4)">⠤⠒⠒⠉⠉</span><span style="color:var(--vt-palette-8)">┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈</span><span style="color:var(--vt-palette-4)">⠶</span> <span style="color:var(--vt-palette-8)">│</span>17+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">13</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-4)">⢠⡄</span> <span style="color:var(--vt-palette-8)">│</span>18+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">14</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-0)">$600</span> <span style="color:var(--vt-palette-8)">┼──────────┬──────────┬──────────┬</span> <span style="color:var(--vt-palette-8)">│</span>19+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">15</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-0)">$1,440</span> <span style="color:var(--vt-palette-0)">$1,460</span> <span style="color:var(--vt-palette-0)">$1,480</span> <span style="color:var(--vt-palette-0)">$1,500</span> <span style="color:var(--vt-palette-8)">│</span>20+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">16</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">└──────────────────────────────────────</span> <span style="color:var(--vt-palette-0)">A1:C5</span> <span style="color:var(--vt-palette-8)">─┘</span>21+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">17</span><span style="background:var(--vt-palette-7)"> </span>22+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">18</span><span style="background:var(--vt-palette-7)"> </span>23+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">19</span><span style="background:var(--vt-palette-7)"> </span>24+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">20</span><span style="background:var(--vt-palette-7)"> </span>25+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">21</span><span style="background:var(--vt-palette-7)"> </span>26+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">22</span><span style="background:var(--vt-palette-7)"> </span>27+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">23</span><span style="background:var(--vt-palette-7)"> </span>28+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">24</span><span style="background:var(--vt-palette-7)"> </span>29+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">25</span><span style="background:var(--vt-palette-7)"> </span>30+<span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">Sheet1</span><span style="background:var(--vt-palette-6)"> </span> <span style="color:var(--vt-palette-0)">+</span> <span style="color:var(--vt-palette-8)">│</span> untitled <span style="color:var(--vt-palette-0)">modified</span> <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">Ctrl+K</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-0)">search</span> <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">Ctrl+/</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-0)">shortcuts</span> <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">F10</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-0)">menu</span></pre>\ No newline at end of file
A e2e/testdata/screens/chart-scatter.html +30 -0
@@ -0,0 +1,30 @@
1+<pre class="screen"> <span style="text-decoration:underline">F</span>ile <span style="text-decoration:underline">E</span>dit <span style="text-decoration:underline">V</span>iew <span style="text-decoration:underline">I</span>nsert F<span style="text-decoration:underline">o</span>rmat <span style="text-decoration:underline">D</span>ata <span style="text-decoration:underline">H</span>elp <span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">READY</span><span style="background:var(--vt-palette-6)"> </span>2+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">A1</span><span style="background:var(--vt-palette-8)"> </span> Month3+4+<span style="background:var(--vt-palette-8)"> </span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">A</span><span style="background:var(--vt-palette-6)"> </span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">B</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">C</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">D</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">E</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">F</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">G</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">H</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">I</span><span style="background:var(--vt-palette-8)"> </span>5+<span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">1</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">Month</span><span style="background:var(--vt-palette-6)"> </span> Rent Food <span style="color:var(--vt-palette-8)">┌─</span> <span style="font-weight:bold">Rent</span> <span style="font-weight:bold">and</span> <span style="font-weight:bold">Food</span> <span style="color:var(--vt-palette-8)">──────────────────────────────┐</span>6+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">2</span><span style="background:var(--vt-palette-8)"> </span> Jan $1,450 $600 <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span>7+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">3</span><span style="background:var(--vt-palette-8)"> </span> Feb $1,450 $640 <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-6)">⣀</span> <span style="color:var(--vt-palette-8)">│</span>8+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">4</span><span style="background:var(--vt-palette-8)"> </span> Mar $1,500 $612 <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">$700</span> <span style="color:var(--vt-palette-8)">┤┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈</span><span style="color:var(--vt-palette-6)">⠉</span> <span style="color:var(--vt-palette-8)">│</span>9+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">5</span><span style="background:var(--vt-palette-8)"> </span> Apr $1,500 $700 <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span>10+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">6</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">$680</span> <span style="color:var(--vt-palette-8)">┤┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈</span> <span style="color:var(--vt-palette-8)">│</span>11+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">7</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span>12+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">8</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">$660</span> <span style="color:var(--vt-palette-8)">┤┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈</span><span style="color:var(--vt-palette-6)">⣀⣀⠤⠤⠒</span> <span style="color:var(--vt-palette-8)">│</span>13+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">9</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-6)">⢠⡄</span> <span style="color:var(--vt-palette-6)">⢀⣀⡠⠤⠒⠒⠉⠉</span> <span style="color:var(--vt-palette-8)">│</span>14+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">10</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">$640</span> <span style="color:var(--vt-palette-8)">┤┈┈┈┈┈┈┈┈┈┈┈┈┈</span><span style="color:var(--vt-palette-6)">⣀⣀⠤⠴⠒⠊⠉⠁</span><span style="color:var(--vt-palette-8)">┈┈┈┈┈┈┈┈┈┈┈┈</span> <span style="color:var(--vt-palette-8)">│</span>15+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">11</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-6)">⣀⣀⠤⠤⠒⠒⠉⠉</span> <span style="color:var(--vt-palette-8)">│</span>16+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">12</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">$620</span> <span style="color:var(--vt-palette-8)">┤</span><span style="color:var(--vt-palette-6)">⠤⠒⠒⠉⠉</span><span style="color:var(--vt-palette-8)">┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈</span><span style="color:var(--vt-palette-6)">⠶</span> <span style="color:var(--vt-palette-8)">│</span>17+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">13</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-6)">⢠⡄</span> <span style="color:var(--vt-palette-8)">│</span>18+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">14</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">$600</span> <span style="color:var(--vt-palette-8)">┼──────────┬──────────┬──────────┬</span> <span style="color:var(--vt-palette-8)">│</span>19+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">15</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">$1,440</span> <span style="color:var(--vt-palette-8)">$1,460</span> <span style="color:var(--vt-palette-8)">$1,480</span> <span style="color:var(--vt-palette-8)">$1,500</span> <span style="color:var(--vt-palette-8)">│</span>20+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">16</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">└──────────────────────────────────────</span> <span style="color:var(--vt-palette-8)">A1:C5</span> <span style="color:var(--vt-palette-8)">─┘</span>21+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">17</span><span style="background:var(--vt-palette-8)"> </span>22+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">18</span><span style="background:var(--vt-palette-8)"> </span>23+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">19</span><span style="background:var(--vt-palette-8)"> </span>24+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">20</span><span style="background:var(--vt-palette-8)"> </span>25+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">21</span><span style="background:var(--vt-palette-8)"> </span>26+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">22</span><span style="background:var(--vt-palette-8)"> </span>27+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">23</span><span style="background:var(--vt-palette-8)"> </span>28+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">24</span><span style="background:var(--vt-palette-8)"> </span>29+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">25</span><span style="background:var(--vt-palette-8)"> </span>30+<span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">Sheet1</span><span style="background:var(--vt-palette-6)"> </span> <span style="color:var(--vt-palette-8)">+</span> <span style="color:var(--vt-palette-8)">│</span> untitled <span style="color:var(--vt-palette-8)">modified</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+K</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">search</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+/</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">shortcuts</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">F10</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">menu</span></pre>\ No newline at end of file
M e2e/testdata/screens/chart-selected.html +3 -3
@@ -5,13 +5,13 @@
55 <span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">1</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">Month</span><span style="background:var(--vt-palette-6)"> </span> Rent Food <span style="color:var(--vt-palette-6);font-weight:bold">┌─</span> <span style="font-weight:bold">Rent</span> <span style="font-weight:bold">and</span> <span style="font-weight:bold">Food</span> <span style="color:var(--vt-palette-6);font-weight:bold">──────────────────────────────┐</span>66 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">2</span><span style="background:var(--vt-palette-8)"> </span> Jan $1,450 $600 <span style="color:var(--vt-palette-6);font-weight:bold">│</span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span>77 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">3</span><span style="background:var(--vt-palette-8)"> </span> Feb $1,450 $640 <span style="color:var(--vt-palette-6);font-weight:bold">│</span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span>8-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">4</span><span style="background:var(--vt-palette-8)"> </span> Mar $1,500 $612 <span style="color:var(--vt-palette-6);font-weight:bold">│</span> <span style="color:var(--vt-palette-8)">$1,500</span> <span style="color:var(--vt-palette-8)">┤</span> <span style="color:var(--vt-palette-6)">▆▆▆</span> <span style="color:var(--vt-palette-6)">▆▆▆</span> <span style="color:var(--vt-palette-6)">███</span> <span style="color:var(--vt-palette-6)">███</span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span>8+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">4</span><span style="background:var(--vt-palette-8)"> </span> Mar $1,500 $612 <span style="color:var(--vt-palette-6);font-weight:bold">│</span> <span style="color:var(--vt-palette-8)">$1,500</span> <span style="color:var(--vt-palette-8)">┤┈┈</span><span style="color:var(--vt-palette-6)">▆▆▆</span><span style="color:var(--vt-palette-8)">┈┈┈┈┈</span><span style="color:var(--vt-palette-6)">▆▆▆</span><span style="color:var(--vt-palette-8)">┈┈┈┈┈</span><span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-8)">┈┈┈┈┈</span><span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-8)">┈┈┈┈┈┈</span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span>99 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">5</span><span style="background:var(--vt-palette-8)"> </span> Apr $1,500 $700 <span style="color:var(--vt-palette-6);font-weight:bold">│</span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-6)">███</span> <span style="color:var(--vt-palette-6)">███</span> <span style="color:var(--vt-palette-6)">███</span> <span style="color:var(--vt-palette-6)">███</span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span>1010 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">6</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-6)">███</span> <span style="color:var(--vt-palette-6)">███</span> <span style="color:var(--vt-palette-6)">███</span> <span style="color:var(--vt-palette-6)">███</span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span>11-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">7</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span> <span style="color:var(--vt-palette-8)">$1,000</span> <span style="color:var(--vt-palette-8)">┤</span> <span style="color:var(--vt-palette-6)">███</span> <span style="color:var(--vt-palette-6)">███</span> <span style="color:var(--vt-palette-6)">███</span> <span style="color:var(--vt-palette-6)">███</span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span>11+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">7</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span> <span style="color:var(--vt-palette-8)">$1,000</span> <span style="color:var(--vt-palette-8)">┤┈┈</span><span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-8)">┈┈┈┈┈</span><span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-8)">┈┈┈┈┈</span><span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-8)">┈┈┈┈┈</span><span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-8)">┈┈┈┈┈┈</span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span>1212 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">8</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-6)">███</span> <span style="color:var(--vt-palette-6)">███</span> <span style="color:var(--vt-palette-6)">███</span> <span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">▂▂▂</span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span>1313 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">9</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">▅▅▅</span> <span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">▇▇▇</span> <span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">▅▅▅</span> <span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span>14-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">10</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span> <span style="color:var(--vt-palette-8)">$500</span> <span style="color:var(--vt-palette-8)">┤</span> <span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span>14+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">10</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span> <span style="color:var(--vt-palette-8)">$500</span> <span style="color:var(--vt-palette-8)">┤┈┈</span><span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">███</span><span style="color:var(--vt-palette-8)">┈┈</span><span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">███</span><span style="color:var(--vt-palette-8)">┈┈</span><span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">███</span><span style="color:var(--vt-palette-8)">┈┈</span><span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">███</span><span style="color:var(--vt-palette-8)">┈┈┈</span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span>1515 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">11</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span>1616 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">12</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span>1717 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">13</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span> <span style="color:var(--vt-palette-8)">$0</span> <span style="color:var(--vt-palette-8)">┼────────────────────────────────────</span> <span style="color:var(--vt-palette-6);font-weight:bold">│</span>
A e2e/testdata/screens/chart-stacked-light.html +30 -0
@@ -0,0 +1,30 @@
1+<pre class="screen"> <span style="text-decoration:underline">F</span>ile <span style="text-decoration:underline">E</span>dit <span style="text-decoration:underline">V</span>iew <span style="text-decoration:underline">I</span>nsert F<span style="text-decoration:underline">o</span>rmat <span style="text-decoration:underline">D</span>ata <span style="text-decoration:underline">H</span>elp <span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">READY</span><span style="background:var(--vt-palette-6)"> </span>2+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">A1</span><span style="background:var(--vt-palette-7)"> </span> Month3+4+<span style="background:var(--vt-palette-7)"> </span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">A</span><span style="background:var(--vt-palette-6)"> </span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">B</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">C</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">D</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">E</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">F</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">G</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">H</span><span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">I</span><span style="background:var(--vt-palette-7)"> </span>5+<span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">1</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">Month</span><span style="background:var(--vt-palette-6)"> </span> Rent Food <span style="color:var(--vt-palette-8)">┌─</span> <span style="font-weight:bold">Rent</span> <span style="font-weight:bold">and</span> <span style="font-weight:bold">Food</span> <span style="color:var(--vt-palette-8)">──────────────────────────────┐</span>6+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">2</span><span style="background:var(--vt-palette-7)"> </span> Jan $1,450 $600 <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span>7+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">3</span><span style="background:var(--vt-palette-7)"> </span> Feb $1,450 $640 <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-0)">$2,500</span> <span style="color:var(--vt-palette-8)">┤┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈</span> <span style="color:var(--vt-palette-8)">│</span>8+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">4</span><span style="background:var(--vt-palette-7)"> </span> Mar $1,500 $612 <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-5)">▂▂▂▂▂▂</span> <span style="color:var(--vt-palette-5)">▃▃▃▃▃▃</span> <span style="color:var(--vt-palette-5)">▄▄▄▄▄▄</span> <span style="color:var(--vt-palette-5)">▆▆▆▆▆▆</span> <span style="color:var(--vt-palette-8)">│</span>9+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">5</span><span style="background:var(--vt-palette-7)"> </span> Apr $1,500 $700 <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-0)">$2,000</span> <span style="color:var(--vt-palette-8)">┤┈┈</span><span style="color:var(--vt-palette-5)">██████</span><span style="color:var(--vt-palette-8)">┈┈</span><span style="color:var(--vt-palette-5)">██████</span><span style="color:var(--vt-palette-8)">┈┈</span><span style="color:var(--vt-palette-5)">██████</span><span style="color:var(--vt-palette-8)">┈┈</span><span style="color:var(--vt-palette-5)">██████</span><span style="color:var(--vt-palette-8)">┈┈┈</span> <span style="color:var(--vt-palette-8)">│</span>10+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">6</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-5)">██████</span> <span style="color:var(--vt-palette-5)">██████</span> <span style="color:var(--vt-palette-5)">██████</span> <span style="color:var(--vt-palette-5)">██████</span> <span style="color:var(--vt-palette-8)">│</span>11+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">7</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-0)">$1,500</span> <span style="color:var(--vt-palette-8)">┤┈┈</span><span style="color:var(--vt-palette-4);background:var(--vt-palette-5)">▆▆▆▆▆▆</span><span style="color:var(--vt-palette-8)">┈┈</span><span style="color:var(--vt-palette-4);background:var(--vt-palette-5)">▆▆▆▆▆▆</span><span style="color:var(--vt-palette-8)">┈┈</span><span style="color:var(--vt-palette-4)">██████</span><span style="color:var(--vt-palette-8)">┈┈</span><span style="color:var(--vt-palette-4)">██████</span><span style="color:var(--vt-palette-8)">┈┈┈</span> <span style="color:var(--vt-palette-8)">│</span>12+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">8</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-4)">██████</span> <span style="color:var(--vt-palette-4)">██████</span> <span style="color:var(--vt-palette-4)">██████</span> <span style="color:var(--vt-palette-4)">██████</span> <span style="color:var(--vt-palette-8)">│</span>13+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">9</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-0)">$1,000</span> <span style="color:var(--vt-palette-8)">┤┈┈</span><span style="color:var(--vt-palette-4)">██████</span><span style="color:var(--vt-palette-8)">┈┈</span><span style="color:var(--vt-palette-4)">██████</span><span style="color:var(--vt-palette-8)">┈┈</span><span style="color:var(--vt-palette-4)">██████</span><span style="color:var(--vt-palette-8)">┈┈</span><span style="color:var(--vt-palette-4)">██████</span><span style="color:var(--vt-palette-8)">┈┈┈</span> <span style="color:var(--vt-palette-8)">│</span>14+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">10</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-4)">██████</span> <span style="color:var(--vt-palette-4)">██████</span> <span style="color:var(--vt-palette-4)">██████</span> <span style="color:var(--vt-palette-4)">██████</span> <span style="color:var(--vt-palette-8)">│</span>15+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">11</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-0)">$500</span> <span style="color:var(--vt-palette-8)">┤┈┈</span><span style="color:var(--vt-palette-4)">██████</span><span style="color:var(--vt-palette-8)">┈┈</span><span style="color:var(--vt-palette-4)">██████</span><span style="color:var(--vt-palette-8)">┈┈</span><span style="color:var(--vt-palette-4)">██████</span><span style="color:var(--vt-palette-8)">┈┈</span><span style="color:var(--vt-palette-4)">██████</span><span style="color:var(--vt-palette-8)">┈┈┈</span> <span style="color:var(--vt-palette-8)">│</span>16+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">12</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-4)">██████</span> <span style="color:var(--vt-palette-4)">██████</span> <span style="color:var(--vt-palette-4)">██████</span> <span style="color:var(--vt-palette-4)">██████</span> <span style="color:var(--vt-palette-8)">│</span>17+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">13</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-0)">$0</span> <span style="color:var(--vt-palette-8)">┼────────────────────────────────────</span> <span style="color:var(--vt-palette-8)">│</span>18+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">14</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-0)">Jan</span> <span style="color:var(--vt-palette-0)">Feb</span> <span style="color:var(--vt-palette-0)">Mar</span> <span style="color:var(--vt-palette-0)">Apr</span> <span style="color:var(--vt-palette-8)">│</span>19+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">15</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-4)">■</span> <span style="color:var(--vt-palette-0)">Rent</span> <span style="color:var(--vt-palette-5)">■</span> <span style="color:var(--vt-palette-0)">Food</span> <span style="color:var(--vt-palette-8)">│</span>20+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">16</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-8)">└──────────────────────────────────────</span> <span style="color:var(--vt-palette-0)">A1:C5</span> <span style="color:var(--vt-palette-8)">─┘</span>21+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">17</span><span style="background:var(--vt-palette-7)"> </span>22+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">18</span><span style="background:var(--vt-palette-7)"> </span>23+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">19</span><span style="background:var(--vt-palette-7)"> </span>24+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">20</span><span style="background:var(--vt-palette-7)"> </span>25+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">21</span><span style="background:var(--vt-palette-7)"> </span>26+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">22</span><span style="background:var(--vt-palette-7)"> </span>27+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">23</span><span style="background:var(--vt-palette-7)"> </span>28+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">24</span><span style="background:var(--vt-palette-7)"> </span>29+<span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">25</span><span style="background:var(--vt-palette-7)"> </span>30+<span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">Sheet1</span><span style="background:var(--vt-palette-6)"> </span> <span style="color:var(--vt-palette-0)">+</span> <span style="color:var(--vt-palette-8)">│</span> untitled <span style="color:var(--vt-palette-0)">modified</span> <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">Ctrl+K</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-0)">search</span> <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">Ctrl+/</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-0)">shortcuts</span> <span style="background:var(--vt-palette-7)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-7)">F10</span><span style="background:var(--vt-palette-7)"> </span> <span style="color:var(--vt-palette-0)">menu</span></pre>\ No newline at end of file
A e2e/testdata/screens/chart-stacked.html +30 -0
@@ -0,0 +1,30 @@
1+<pre class="screen"> <span style="text-decoration:underline">F</span>ile <span style="text-decoration:underline">E</span>dit <span style="text-decoration:underline">V</span>iew <span style="text-decoration:underline">I</span>nsert F<span style="text-decoration:underline">o</span>rmat <span style="text-decoration:underline">D</span>ata <span style="text-decoration:underline">H</span>elp <span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">READY</span><span style="background:var(--vt-palette-6)"> </span>2+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">A1</span><span style="background:var(--vt-palette-8)"> </span> Month3+4+<span style="background:var(--vt-palette-8)"> </span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">A</span><span style="background:var(--vt-palette-6)"> </span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">B</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">C</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">D</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">E</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">F</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">G</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">H</span><span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">I</span><span style="background:var(--vt-palette-8)"> </span>5+<span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">1</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">Month</span><span style="background:var(--vt-palette-6)"> </span> Rent Food <span style="color:var(--vt-palette-8)">┌─</span> <span style="font-weight:bold">Rent</span> <span style="font-weight:bold">and</span> <span style="font-weight:bold">Food</span> <span style="color:var(--vt-palette-8)">──────────────────────────────┐</span>6+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">2</span><span style="background:var(--vt-palette-8)"> </span> Jan $1,450 $600 <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span>7+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">3</span><span style="background:var(--vt-palette-8)"> </span> Feb $1,450 $640 <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">$2,500</span> <span style="color:var(--vt-palette-8)">┤┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈</span> <span style="color:var(--vt-palette-8)">│</span>8+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">4</span><span style="background:var(--vt-palette-8)"> </span> Mar $1,500 $612 <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-5)">▂▂▂▂▂▂</span> <span style="color:var(--vt-palette-5)">▃▃▃▃▃▃</span> <span style="color:var(--vt-palette-5)">▄▄▄▄▄▄</span> <span style="color:var(--vt-palette-5)">▆▆▆▆▆▆</span> <span style="color:var(--vt-palette-8)">│</span>9+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">5</span><span style="background:var(--vt-palette-8)"> </span> Apr $1,500 $700 <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">$2,000</span> <span style="color:var(--vt-palette-8)">┤┈┈</span><span style="color:var(--vt-palette-5)">██████</span><span style="color:var(--vt-palette-8)">┈┈</span><span style="color:var(--vt-palette-5)">██████</span><span style="color:var(--vt-palette-8)">┈┈</span><span style="color:var(--vt-palette-5)">██████</span><span style="color:var(--vt-palette-8)">┈┈</span><span style="color:var(--vt-palette-5)">██████</span><span style="color:var(--vt-palette-8)">┈┈┈</span> <span style="color:var(--vt-palette-8)">│</span>10+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">6</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-5)">██████</span> <span style="color:var(--vt-palette-5)">██████</span> <span style="color:var(--vt-palette-5)">██████</span> <span style="color:var(--vt-palette-5)">██████</span> <span style="color:var(--vt-palette-8)">│</span>11+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">7</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">$1,500</span> <span style="color:var(--vt-palette-8)">┤┈┈</span><span style="color:var(--vt-palette-6);background:var(--vt-palette-5)">▆▆▆▆▆▆</span><span style="color:var(--vt-palette-8)">┈┈</span><span style="color:var(--vt-palette-6);background:var(--vt-palette-5)">▆▆▆▆▆▆</span><span style="color:var(--vt-palette-8)">┈┈</span><span style="color:var(--vt-palette-6)">██████</span><span style="color:var(--vt-palette-8)">┈┈</span><span style="color:var(--vt-palette-6)">██████</span><span style="color:var(--vt-palette-8)">┈┈┈</span> <span style="color:var(--vt-palette-8)">│</span>12+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">8</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-6)">██████</span> <span style="color:var(--vt-palette-6)">██████</span> <span style="color:var(--vt-palette-6)">██████</span> <span style="color:var(--vt-palette-6)">██████</span> <span style="color:var(--vt-palette-8)">│</span>13+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">9</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">$1,000</span> <span style="color:var(--vt-palette-8)">┤┈┈</span><span style="color:var(--vt-palette-6)">██████</span><span style="color:var(--vt-palette-8)">┈┈</span><span style="color:var(--vt-palette-6)">██████</span><span style="color:var(--vt-palette-8)">┈┈</span><span style="color:var(--vt-palette-6)">██████</span><span style="color:var(--vt-palette-8)">┈┈</span><span style="color:var(--vt-palette-6)">██████</span><span style="color:var(--vt-palette-8)">┈┈┈</span> <span style="color:var(--vt-palette-8)">│</span>14+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">10</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-6)">██████</span> <span style="color:var(--vt-palette-6)">██████</span> <span style="color:var(--vt-palette-6)">██████</span> <span style="color:var(--vt-palette-6)">██████</span> <span style="color:var(--vt-palette-8)">│</span>15+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">11</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">$500</span> <span style="color:var(--vt-palette-8)">┤┈┈</span><span style="color:var(--vt-palette-6)">██████</span><span style="color:var(--vt-palette-8)">┈┈</span><span style="color:var(--vt-palette-6)">██████</span><span style="color:var(--vt-palette-8)">┈┈</span><span style="color:var(--vt-palette-6)">██████</span><span style="color:var(--vt-palette-8)">┈┈</span><span style="color:var(--vt-palette-6)">██████</span><span style="color:var(--vt-palette-8)">┈┈┈</span> <span style="color:var(--vt-palette-8)">│</span>16+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">12</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-6)">██████</span> <span style="color:var(--vt-palette-6)">██████</span> <span style="color:var(--vt-palette-6)">██████</span> <span style="color:var(--vt-palette-6)">██████</span> <span style="color:var(--vt-palette-8)">│</span>17+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">13</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">$0</span> <span style="color:var(--vt-palette-8)">┼────────────────────────────────────</span> <span style="color:var(--vt-palette-8)">│</span>18+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">14</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">Jan</span> <span style="color:var(--vt-palette-8)">Feb</span> <span style="color:var(--vt-palette-8)">Mar</span> <span style="color:var(--vt-palette-8)">Apr</span> <span style="color:var(--vt-palette-8)">│</span>19+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">15</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-6)">■</span> <span style="color:var(--vt-palette-8)">Rent</span> <span style="color:var(--vt-palette-5)">■</span> <span style="color:var(--vt-palette-8)">Food</span> <span style="color:var(--vt-palette-8)">│</span>20+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">16</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">└──────────────────────────────────────</span> <span style="color:var(--vt-palette-8)">A1:C5</span> <span style="color:var(--vt-palette-8)">─┘</span>21+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">17</span><span style="background:var(--vt-palette-8)"> </span>22+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">18</span><span style="background:var(--vt-palette-8)"> </span>23+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">19</span><span style="background:var(--vt-palette-8)"> </span>24+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">20</span><span style="background:var(--vt-palette-8)"> </span>25+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">21</span><span style="background:var(--vt-palette-8)"> </span>26+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">22</span><span style="background:var(--vt-palette-8)"> </span>27+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">23</span><span style="background:var(--vt-palette-8)"> </span>28+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">24</span><span style="background:var(--vt-palette-8)"> </span>29+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">25</span><span style="background:var(--vt-palette-8)"> </span>30+<span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">Sheet1</span><span style="background:var(--vt-palette-6)"> </span> <span style="color:var(--vt-palette-8)">+</span> <span style="color:var(--vt-palette-8)">│</span> untitled <span style="color:var(--vt-palette-8)">modified</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+K</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">search</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">Ctrl+/</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">shortcuts</span> <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">F10</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">menu</span></pre>\ No newline at end of file
M e2e/testdata/screens/chart-wide.html +3 -3
@@ -5,13 +5,13 @@
55 <span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">1</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">Month</span><span style="background:var(--vt-palette-6)"> </span> Rent Food <span style="color:var(--vt-palette-8)">┌─</span> <span style="font-weight:bold">Rent</span> <span style="font-weight:bold">and</span> <span style="font-weight:bold">Food</span> <span style="color:var(--vt-palette-8)">──────────────────────────────┐</span>66 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">2</span><span style="background:var(--vt-palette-8)"> </span> Jan $1,450 $600 <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span>77 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">3</span><span style="background:var(--vt-palette-8)"> </span> Feb $1,450 $640 <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span>8-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">4</span><span style="background:var(--vt-palette-8)"> </span> Mar $1,500 $612 <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">$1,500</span> <span style="color:var(--vt-palette-8)">┤</span> <span style="color:var(--vt-palette-6)">▆▆▆</span> <span style="color:var(--vt-palette-6)">▆▆▆</span> <span style="color:var(--vt-palette-6)">███</span> <span style="color:var(--vt-palette-6)">███</span> <span style="color:var(--vt-palette-8)">│</span>8+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">4</span><span style="background:var(--vt-palette-8)"> </span> Mar $1,500 $612 <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">$1,500</span> <span style="color:var(--vt-palette-8)">┤┈┈</span><span style="color:var(--vt-palette-6)">▆▆▆</span><span style="color:var(--vt-palette-8)">┈┈┈┈┈</span><span style="color:var(--vt-palette-6)">▆▆▆</span><span style="color:var(--vt-palette-8)">┈┈┈┈┈</span><span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-8)">┈┈┈┈┈</span><span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-8)">┈┈┈┈┈┈</span> <span style="color:var(--vt-palette-8)">│</span>99 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">5</span><span style="background:var(--vt-palette-8)"> </span> Apr $1,500 $700 <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-6)">███</span> <span style="color:var(--vt-palette-6)">███</span> <span style="color:var(--vt-palette-6)">███</span> <span style="color:var(--vt-palette-6)">███</span> <span style="color:var(--vt-palette-8)">│</span>1010 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">6</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-6)">███</span> <span style="color:var(--vt-palette-6)">███</span> <span style="color:var(--vt-palette-6)">███</span> <span style="color:var(--vt-palette-6)">███</span> <span style="color:var(--vt-palette-8)">│</span>11-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">7</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">$1,000</span> <span style="color:var(--vt-palette-8)">┤</span> <span style="color:var(--vt-palette-6)">███</span> <span style="color:var(--vt-palette-6)">███</span> <span style="color:var(--vt-palette-6)">███</span> <span style="color:var(--vt-palette-6)">███</span> <span style="color:var(--vt-palette-8)">│</span>11+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">7</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">$1,000</span> <span style="color:var(--vt-palette-8)">┤┈┈</span><span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-8)">┈┈┈┈┈</span><span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-8)">┈┈┈┈┈</span><span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-8)">┈┈┈┈┈</span><span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-8)">┈┈┈┈┈┈</span> <span style="color:var(--vt-palette-8)">│</span>1212 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">8</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-6)">███</span> <span style="color:var(--vt-palette-6)">███</span> <span style="color:var(--vt-palette-6)">███</span> <span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">▂▂▂</span> <span style="color:var(--vt-palette-8)">│</span>1313 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">9</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">▅▅▅</span> <span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">▇▇▇</span> <span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">▅▅▅</span> <span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-8)">│</span>14-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">10</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">$500</span> <span style="color:var(--vt-palette-8)">┤</span> <span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-8)">│</span>14+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">10</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">$500</span> <span style="color:var(--vt-palette-8)">┤┈┈</span><span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">███</span><span style="color:var(--vt-palette-8)">┈┈</span><span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">███</span><span style="color:var(--vt-palette-8)">┈┈</span><span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">███</span><span style="color:var(--vt-palette-8)">┈┈</span><span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">███</span><span style="color:var(--vt-palette-8)">┈┈┈</span> <span style="color:var(--vt-palette-8)">│</span>1515 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">11</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-8)">│</span>1616 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">12</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-8)">│</span>1717 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">13</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">$0</span> <span style="color:var(--vt-palette-8)">┼────────────────────────────────────</span> <span style="color:var(--vt-palette-8)">│</span>
M e2e/testdata/screens/chart.html +3 -3
@@ -5,13 +5,13 @@
55 <span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6);font-weight:bold">1</span><span style="background:var(--vt-palette-6)"> </span><span style="color:var(--vt-palette-0);background:var(--vt-palette-6)">Month</span><span style="background:var(--vt-palette-6)"> </span> Rent Food <span style="color:var(--vt-palette-8)">┌─</span> <span style="font-weight:bold">Rent</span> <span style="font-weight:bold">and</span> <span style="font-weight:bold">Food</span> <span style="color:var(--vt-palette-8)">──────────────────────────────┐</span>66 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">2</span><span style="background:var(--vt-palette-8)"> </span> Jan $1,450 $600 <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span>77 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">3</span><span style="background:var(--vt-palette-8)"> </span> Feb $1,450 $640 <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span>8-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">4</span><span style="background:var(--vt-palette-8)"> </span> Mar $1,500 $612 <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">$1,500</span> <span style="color:var(--vt-palette-8)">┤</span> <span style="color:var(--vt-palette-6)">▆▆▆</span> <span style="color:var(--vt-palette-6)">▆▆▆</span> <span style="color:var(--vt-palette-6)">███</span> <span style="color:var(--vt-palette-6)">███</span> <span style="color:var(--vt-palette-8)">│</span>8+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">4</span><span style="background:var(--vt-palette-8)"> </span> Mar $1,500 $612 <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">$1,500</span> <span style="color:var(--vt-palette-8)">┤┈┈</span><span style="color:var(--vt-palette-6)">▆▆▆</span><span style="color:var(--vt-palette-8)">┈┈┈┈┈</span><span style="color:var(--vt-palette-6)">▆▆▆</span><span style="color:var(--vt-palette-8)">┈┈┈┈┈</span><span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-8)">┈┈┈┈┈</span><span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-8)">┈┈┈┈┈┈</span> <span style="color:var(--vt-palette-8)">│</span>99 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">5</span><span style="background:var(--vt-palette-8)"> </span> Apr $1,500 $700 <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-6)">███</span> <span style="color:var(--vt-palette-6)">███</span> <span style="color:var(--vt-palette-6)">███</span> <span style="color:var(--vt-palette-6)">███</span> <span style="color:var(--vt-palette-8)">│</span>1010 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">6</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-6)">███</span> <span style="color:var(--vt-palette-6)">███</span> <span style="color:var(--vt-palette-6)">███</span> <span style="color:var(--vt-palette-6)">███</span> <span style="color:var(--vt-palette-8)">│</span>11-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">7</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">$1,000</span> <span style="color:var(--vt-palette-8)">┤</span> <span style="color:var(--vt-palette-6)">███</span> <span style="color:var(--vt-palette-6)">███</span> <span style="color:var(--vt-palette-6)">███</span> <span style="color:var(--vt-palette-6)">███</span> <span style="color:var(--vt-palette-8)">│</span>11+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">7</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">$1,000</span> <span style="color:var(--vt-palette-8)">┤┈┈</span><span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-8)">┈┈┈┈┈</span><span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-8)">┈┈┈┈┈</span><span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-8)">┈┈┈┈┈</span><span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-8)">┈┈┈┈┈┈</span> <span style="color:var(--vt-palette-8)">│</span>1212 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">8</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-6)">███</span> <span style="color:var(--vt-palette-6)">███</span> <span style="color:var(--vt-palette-6)">███</span> <span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">▂▂▂</span> <span style="color:var(--vt-palette-8)">│</span>1313 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">9</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">▅▅▅</span> <span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">▇▇▇</span> <span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">▅▅▅</span> <span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-8)">│</span>14-<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">10</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">$500</span> <span style="color:var(--vt-palette-8)">┤</span> <span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-8)">│</span>14+<span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">10</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">$500</span> <span style="color:var(--vt-palette-8)">┤┈┈</span><span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">███</span><span style="color:var(--vt-palette-8)">┈┈</span><span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">███</span><span style="color:var(--vt-palette-8)">┈┈</span><span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">███</span><span style="color:var(--vt-palette-8)">┈┈</span><span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">███</span><span style="color:var(--vt-palette-8)">┈┈┈</span> <span style="color:var(--vt-palette-8)">│</span>1515 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">11</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-8)">│</span>1616 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">12</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-6)">███</span><span style="color:var(--vt-palette-5)">███</span> <span style="color:var(--vt-palette-8)">│</span>1717 <span style="background:var(--vt-palette-8)"> </span><span style="color:var(--vt-palette-15);background:var(--vt-palette-8)">13</span><span style="background:var(--vt-palette-8)"> </span> <span style="color:var(--vt-palette-8)">│</span> <span style="color:var(--vt-palette-8)">$0</span> <span style="color:var(--vt-palette-8)">┼────────────────────────────────────</span> <span style="color:var(--vt-palette-8)">│</span>
A internal/chart/area.go +161 -0
@@ -0,0 +1,161 @@
1+package chart2+3+import (4+ "math"5+ "slices"6+)7+8+// Area charts are line charts filled down to the axis. Each column of9+// the plot (each pixel column of the image) samples the series between10+// the two categories around it, so drawing costs the plot's width times11+// the series, whatever the data.12+13+// pointX is category i's point in cells from the plot's left edge: the14+// center of its braille dot, so fills meet the lines.15+func (p *columnPlan) pointX(i int) float64 { return (float64(p.dotX(i)) + 0.5) / 2 }16+17+// areaCursor finds the categories around points along the plot, left to18+// right.19+type areaCursor struct {20+ p *columnPlan21+ k int22+}23+24+// at returns category i and how far x (cells into the plot) lies from its25+// point toward the next one's, or false outside the first and last points.26+func (c *areaCursor) at(x float64) (int, float64, bool) {27+ p := c.p28+ if p.shown == 1 {29+ return 0, 0, math.Abs(x-p.pointX(0)) <= 0.530+ }31+ if x < p.pointX(0) || x > p.pointX(p.shown-1) {32+ return 0, 0, false33+ }34+ for c.k < p.shown-2 && p.pointX(c.k+1) < x {35+ c.k++36+ }37+ x0, x1 := p.pointX(c.k), p.pointX(c.k+1)38+ if x1 <= x0 {39+ return c.k, 0, true40+ }41+ return c.k, (x - x0) / (x1 - x0), true42+}43+44+// valueAt is series j between categories i and i+1, t of the way, or NaN45+// where either is missing.46+func (p *columnPlan) valueAt(j, i int, t float64) float64 {47+ s := p.d.Series[j]48+ a := value(s, i)49+ if t == 0 || i+1 >= p.shown {50+ return a51+ }52+ b := value(s, i+1)53+ return a + (b-a)*t54+}55+56+// areaSpans returns the area's spans at one point: stacked, the series'57+// piles; otherwise each series from the axis to its value, the smaller58+// in front, so each shows the part no smaller one covers.59+func (p *columnPlan) areaSpans(i int, t float64, vals []float64, out []span) []span {60+ vals = vals[:0]61+ for j := range p.d.Series {62+ vals = append(vals, p.valueAt(j, i, t))63+ }64+ if p.stacked {65+ return pileUp(len(vals), func(j int) float64 { return vals[j] }, out)66+ }67+ return overlap(vals, p.sc.base(), out)68+}69+70+// overlap turns values drawn from base, each in front of the larger ones,71+// into the visible spans: from base up through the positive values in72+// order, and down through the negative ones.73+func overlap(vals []float64, base float64, out []span) []span {74+ out = out[:0]75+ order := make([]int, 0, len(vals))76+ for j, v := range vals {77+ if !math.IsNaN(v) {78+ order = append(order, j)79+ }80+ }81+ slices.SortStableFunc(order, func(a, b int) int { return cmpDistance(vals[a]-base, vals[b]-base) })82+ up, down := base, base83+ for _, j := range order {84+ switch v := vals[j]; {85+ case v > up:86+ out = append(out, span{up, v, j})87+ up = v88+ case v < down:89+ out = append(out, span{v, down, j})90+ down = v91+ }92+ }93+ return out94+}95+96+// cmpDistance orders offsets by their distance from zero.97+func cmpDistance(a, b float64) int {98+ switch a, b = math.Abs(a), math.Abs(b); {99+ case a < b:100+ return -1101+ case a > b:102+ return 1103+ }104+ return 0105+}106+107+// drawArea fills each column of the plot under the series with eighths.108+func (p *columnPlan) drawArea(g *Grid) {109+ cur := areaCursor{p: p}110+ vals := make([]float64, 0, len(p.d.Series))111+ var spans, cells []span112+ for x := range p.plot.Dx() {113+ i, t, ok := cur.at(float64(x) + 0.5)114+ if !ok {115+ continue116+ }117+ spans = p.areaSpans(i, t, vals, spans)118+ cells = inCells(p.sc, spans, cells)119+ from, to := pileExtent(cells)120+ for r := from; r <= to; r++ {121+ if c, ok := pileCell(cells, r, lowerEighths, "▀"); ok {122+ if at := g.At(p.plot.Min.X+x, p.axisRow-1-r); at != nil {123+ *at = c124+ }125+ }126+ }127+ }128+}129+130+// areaImage fills the area a pixel column at a time: stacked piles131+// opaque, otherwise each series translucent over the ones before, then132+// strokes the lines on top.133+func (p *columnPlan) areaImage(cv *canvas, y func(float64) float64) {134+ cur := areaCursor{p: p}135+ vals := make([]float64, 0, len(p.d.Series))136+ var spans []span137+ for px := range cv.Rect.Dx() {138+ i, t, ok := cur.at((float64(px) + 0.5) / cv.cw)139+ if !ok {140+ continue141+ }142+ if p.stacked {143+ spans = p.areaSpans(i, t, vals, spans)144+ for _, s := range spans {145+ cv.rect(float64(px), y(s.hi), float64(px+1), y(s.lo), cv.pal.Series[s.j%Colors])146+ }147+ continue148+ }149+ base := y(p.sc.base())150+ for j := range p.d.Series {151+ if v := p.valueAt(j, i, t); !math.IsNaN(v) {152+ c := cv.pal.Series[j%Colors]153+ c.A = 150154+ cv.rect(float64(px), min(y(v), base), float64(px+1), max(y(v), base), c)155+ }156+ }157+ }158+ if !p.stacked {159+ p.lineImage(cv, y)160+ }161+}
A internal/chart/axis.go +150 -0
@@ -0,0 +1,150 @@
1+package chart2+3+import (4+ "math"5+6+ "github.com/FelineStateMachine/012/internal/sheet"7+)8+9+// newAxis fits dlo..dhi onto a value axis of length cells as newScale10+// does, with the chart's axis options: a fixed minimum or maximum, and a11+// log scale.12+func newAxis(dlo, dhi float64, length, minK, maxN int, f sheet.Format, o sheet.ChartOptions) scale {13+ if o.HasMin {14+ dlo = o.Min15+ }16+ if o.HasMax {17+ dhi = o.Max18+ }19+ if o.HasMin && !o.HasMax && dhi <= dlo {20+ dhi = dlo + max(math.Abs(dlo), 1)21+ }22+ if o.HasMax && !o.HasMin && dlo >= dhi {23+ dlo = dhi - max(math.Abs(dhi), 1)24+ }25+ if dlo > dhi {26+ dlo, dhi = dhi, dlo27+ }28+ switch {29+ case o.Log:30+ return newLogScale(dlo, dhi, length, minK, f)31+ case (o.HasMin || o.HasMax) && dlo < dhi:32+ return newFixedScale(dlo, dhi, o.HasMin, o.HasMax, length, minK, maxN, f)33+ }34+ return newScale(dlo, dhi, length, minK, maxN, f)35+}36+37+// newFixedScale is newScale keeping the ends that are fixed where they38+// are: the other end rounds out to a whole step, and with both fixed the39+// step divides the range, a round one where it can.40+func newFixedScale(dlo, dhi float64, fixLo, fixHi bool, length, minK, maxN int, f sheet.Format) scale {41+ best := scale{lo: dlo, hi: dhi, step: dhi - dlo, n: 1, k: max(length, 1)}42+ fallback := false43+ for target := min(maxN, max(length/minK, 1)); target >= 1; target-- {44+ s, ok := fixedTicks(dlo, dhi, fixLo, fixHi, target)45+ if !ok {46+ continue47+ }48+ s.k = length / s.n49+ if s.k < minK && (target > 1 || s.k < 1) {50+ continue51+ }52+ if fixLo && fixHi && niceStep(s.step) != s.step {53+ if !fallback { // the finest division, if no round step fits54+ best, fallback = s, true55+ }56+ continue57+ }58+ best = s59+ break60+ }61+ best.label = tickFormat(best, f)62+ return best63+}64+65+// fixedTicks divides dlo..dhi into about target steps, moving only the66+// ends that aren't fixed.67+func fixedTicks(dlo, dhi float64, fixLo, fixHi bool, target int) (scale, bool) {68+ if fixLo && fixHi {69+ return scale{lo: dlo, hi: dhi, step: (dhi - dlo) / float64(target), n: target}, true70+ }71+ step := niceStep((dhi - dlo) / float64(target))72+ n := int(math.Ceil((dhi-dlo)/step - 1e-9))73+ if n < 1 {74+ return scale{}, false75+ }76+ if fixLo {77+ return scale{lo: dlo, hi: dlo + float64(n)*step, step: step, n: n}, true78+ }79+ return scale{lo: dhi - float64(n)*step, hi: dhi, step: step, n: n}, true80+}81+82+// newLogScale is a log axis over positive dlo..dhi: its ends round out to83+// powers of ten, and ticks are one decade apart, or more when decades84+// would be closer than minK cells.85+func newLogScale(dlo, dhi float64, length, minK int, f sheet.Format) scale {86+ if dhi <= 0 {87+ dlo, dhi = 1, 1088+ }89+ if dlo <= 0 {90+ dlo = dhi / 1091+ }92+ lo, hi := math.Floor(math.Log10(dlo)+1e-9), math.Ceil(math.Log10(dhi)-1e-9)93+ if hi <= lo {94+ hi = lo + 195+ }96+ decades := int(hi - lo)97+ s := scale{lo: lo, log: true}98+ for _, step := range []int{1, 2, 3, 5, 10, 20, 50, 100} {99+ n := (decades + step - 1) / step100+ s.step, s.n, s.k = float64(step), n, length/n101+ if s.k >= minK || n == 1 {102+ break103+ }104+ }105+ s.k = max(s.k, 1)106+ s.hi = lo + float64(s.n)*s.step107+ s.label = func(v float64) string {108+ return tickFormat(scale{lo: v, hi: v, step: v}, f)(v)109+ }110+ return s111+}112+113+// base is where bars start: zero, or the end of the axis nearest it.114+func (s scale) base() float64 {115+ if s.log {116+ return s.tick(0)117+ }118+ return min(max(0, s.lo), s.hi)119+}120+121+// at is pos kept on the axis, for values beyond a fixed end.122+func (s scale) at(v float64) float64 {123+ p := s.pos(v)124+ if math.IsNaN(p) {125+ return 0126+ }127+ return min(max(p, 0), float64(s.cells()))128+}129+130+// crossesZero reports whether zero lies inside the axis, where images131+// draw a line.132+func (s scale) crossesZero() bool { return !s.log && s.lo < 0 && s.hi > 0 }133+134+// positive is d with every value that isn't positive left out, as a log135+// scale can't show them.136+func positive(d sheet.ChartData) sheet.ChartData {137+ out := d138+ out.Series = make([]sheet.ChartSeries, len(d.Series))139+ for j, s := range d.Series {140+ vals := make([]float64, len(s.Values))141+ for i, v := range s.Values {142+ if v <= 0 {143+ v = math.NaN()144+ }145+ vals[i] = v146+ }147+ out.Series[j] = sheet.ChartSeries{Name: s.Name, Values: vals}148+ }149+ return out150+}
M internal/chart/bar.go +66 -61
@@ -10,7 +10,8 @@ import (
1010 )1111 1212 // barPlan lays out horizontal bar charts: category labels on the left,13-// bars growing right, and the value axis along the bottom.13+// bars growing right, and the value axis along the bottom. Draw adds the14+// legend.1415 //1516 // Jan │██████████▌1617 // Feb │███████▎
@@ -18,32 +19,36 @@ import (
1819 // 0 1,000 2,0001920 type barPlan struct {2021 d sheet.ChartData22+ stacked bool23+ grid bool2124 msg string2225 sc scale2326 catW int // width of the category labels2427 axisX int2528 axisRow int2629 tickRow int27- legend int2830 plot image.Rectangle2931 shown int32+ slots int // bars per category: the series, or one stack3033 rows int // rows per category, including a gap row3134 }3235 33-func newBarPlan(d sheet.ChartData, w, h int) *barPlan {34- p := &barPlan{d: d, legend: -1}36+func newBarPlan(d sheet.ChartData, w, h int, o sheet.ChartOptions) *barPlan {37+ d = prepare(d, o, true)38+ p := &barPlan{d: d, stacked: o.Stack != sheet.StackNone, grid: !o.NoGrid, slots: len(d.Series)}3539 lo, hi, ok := finite(d.Series)40+ if p.stacked {41+ lo, hi, ok = stackedRange(d, len(d.Categories))42+ p.slots = 143+ }3644 if !ok || len(d.Categories) == 0 {3745 p.msg = "No numbers to chart"3846 return p3947 }40- lo, hi = min(lo, 0), max(hi, 0)41- p.tickRow = h - 142- ns := len(d.Series)43- if ns > 1 && h >= 7 {44- p.legend = h - 145- p.tickRow = h - 248+ if !o.Log {49+ lo, hi = min(lo, 0), max(hi, 0)4650 }51+ p.tickRow = h - 14752 p.axisRow = p.tickRow - 14853 if p.axisRow < 1 {4954 p.msg = "Too small to chart"
@@ -64,56 +69,69 @@ func newBarPlan(d sheet.ChartData, w, h int) *barPlan {
6469 p.msg = "Too small to chart"6570 return p6671 }67- p.sc = newScale(lo, hi, plotW, labelW+2, 6, d.Format)72+ p.sc = newAxis(lo, hi, plotW, labelW+2, 6, d.Format, o)6873 for j := 0; j <= p.sc.n; j++ {6974 labelW = max(labelW, ansi.StringWidth(p.sc.label(p.sc.tick(j))))7075 }7176 }7277 p.plot = image.Rect(x0, 0, x0+p.sc.cells(), p.axisRow)73- p.rows = ns + 178+ p.rows = p.slots + 17479 if len(d.Categories)*p.rows-1 > p.axisRow {75- p.rows = ns80+ p.rows = p.slots7681 }77- p.shown = max(min(len(d.Categories), (p.axisRow+p.rows-ns)/p.rows), 1)82+ p.shown = max(min(len(d.Categories), (p.axisRow+p.rows-p.slots)/p.rows), 1)7883 return p7984 }8085 81-// barRow returns the row of series j's bar in category i.82-func (p *barPlan) barRow(i, j int) int { return i*p.rows + j }86+// barRow returns the row of series j's bar in category i; stacked, all87+// series share one.88+func (p *barPlan) barRow(i, j int) int {89+ if p.stacked {90+ j = 091+ }92+ return i*p.rows + j93+}94+95+// piles returns category i's bars as spans, as columnPlan.piles does.96+func (p *barPlan) piles(i int, buf []span) []span {97+ if p.stacked {98+ return stack(p.d, i, buf)99+ }100+ buf = buf[:0]101+ base := p.sc.base()102+ for j, s := range p.d.Series {103+ if v := value(s, i); !math.IsNaN(v) {104+ buf = append(buf, span{min(v, base), max(v, base), j})105+ }106+ }107+ return buf108+}83109 84110 func (p *barPlan) draw(g *Grid, o Options) {85111 p.drawAxis(g)86- ns := len(p.d.Series)87112 for i := 0; i < p.shown; i++ {88113 label := ansi.Truncate(p.d.Categories[i], p.catW, "…")89- g.text(p.catW-ansi.StringWidth(label), p.barRow(i, (ns-1)/2), label, Label)90- }91- if p.legend >= 0 {92- g.legend(p.legend, seriesNames(p.d))114+ g.text(p.catW-ansi.StringWidth(label), p.barRow(i, (p.slots-1)/2), label, Label)93115 }94116 if o.Image {95117 return96118 }97- zero := p.sc.pos(0)98- for j, s := range p.d.Series {99- for i := 0; i < p.shown && i < len(s.Values); i++ {100- v := s.Values[i]101- if math.IsNaN(v) {102- continue103- }104- lo, hi := min(zero, p.sc.pos(v)), max(zero, p.sc.pos(v))105- for c := int(math.Floor(lo)); float64(c) < hi; c++ {106- // Left of the axis, bars fill from the right.107- if glyph := barGlyph(lo, hi, c, v >= 0, leftEighths, "▐"); glyph != "" {108- g.set(p.plot.Min.X+c, p.barRow(i, j), glyph, SeriesRole(j))109- }119+ if p.grid {120+ for j := 1; j <= p.sc.n; j++ {121+ for y := 0; y < p.axisRow; y++ {122+ g.set(p.plot.Min.X+j*p.sc.k-1, y, "┊", Gridline)110123 }111124 }112125 }126+ drawPiles(p.sc, p.shown, p.stacked, p.piles, leftEighths, "▐", func(i, j, c int, cell Cell) {127+ if at := g.At(p.plot.Min.X+c, p.barRow(i, j)); at != nil {128+ *at = cell129+ }130+ })113131 }114132 115133 // drawAxis draws the category axis on the left and the value axis along116-// the bottom, labeling as many ticks as fit without touching.134+// the bottom.117135 func (p *barPlan) drawAxis(g *Grid) {118136 for y := 0; y < p.axisRow; y++ {119137 g.set(p.axisX, y, "│", Axis)
@@ -122,39 +140,26 @@ func (p *barPlan) drawAxis(g *Grid) {
122140 for x := p.axisX + 1; x < p.plot.Max.X; x++ {123141 g.set(x, p.axisRow, "─", Axis)124142 }143+ drawXTicks(g, p.sc, p.axisX, p.plot.Min.X, p.axisRow, p.tickRow)144+}145+146+// drawXTicks marks the ticks of a horizontal axis on row axisRow, the147+// first at column axisX and the rest ending intervals from x0, and labels148+// as many on row tickRow as fit without touching.149+func drawXTicks(g *Grid, sc scale, axisX, x0, axisRow, tickRow int) {125150 next := 0 // first free column126- for j := 0; j <= p.sc.n; j++ {127- x := p.axisX151+ for j := 0; j <= sc.n; j++ {152+ x := axisX128153 if j > 0 {129- x = p.plot.Min.X + j*p.sc.k - 1130- g.set(x, p.axisRow, "┬", Axis)154+ x = x0 + j*sc.k - 1155+ g.set(x, axisRow, "┬", Axis)131156 }132- label := p.sc.label(p.sc.tick(j))157+ label := sc.label(sc.tick(j))133158 lw := ansi.StringWidth(label)134159 lx := min(max(x-(lw-1)/2, 0), g.W-lw)135160 if lx >= next {136- g.text(lx, p.tickRow, label, Label)161+ g.text(lx, tickRow, label, Label)137162 next = lx + lw + 1138163 }139164 }140165 }141-142-// barGlyph is the glyph of cell c of a bar from lo to hi, distances143-// along its axis in cells, or "" for none. A positive bar grows away144-// from the axis in eighths; a negative one has no eighths growing the145-// other way, so its partly covered cells are a full block or the half146-// block half.147-func barGlyph(lo, hi float64, c int, positive bool, eighths []string, half string) string {148- cover := min(hi, float64(c+1)) - max(lo, float64(c))149- switch {150- case positive:151- if g := eighths[int(math.Round(cover*8))]; g != " " {152- return g153- }154- case cover >= 0.75:155- return "█"156- case cover >= 0.25:157- return half158- }159- return ""160-}
M internal/chart/chart.go +26 -45
@@ -21,11 +21,12 @@ import (
2121 type Role uint82222 2323 const (24- None Role = iota // background25- Axis // axis lines and tick marks26- Label // tick values, category labels and legend text27- Muted // messages such as "No numbers to chart"28- Series // Series+i is the color of series (or slice) i24+ None Role = iota // background25+ Axis // axis lines and tick marks26+ Label // tick values, category labels and legend text27+ Muted // messages such as "No numbers to chart"28+ Gridline // gridlines29+ Series // Series+i is the color of series (or slice) i2930 )3031 3132 // Colors is how many series colors there are; later series reuse them.
@@ -57,6 +58,9 @@ type Options struct {
5758 // CellW and CellH are the size of a terminal cell in pixels; they set5859 // a pie's proportions. Zero means the usual 1:2.5960 CellW, CellH int61+ // Chart is the chart's own settings: stacking, the value axis, the62+ // gridlines and the legend.63+ Chart sheet.ChartOptions6064 }6165 6266 func (o Options) aspect() float64 {
@@ -128,13 +132,14 @@ func Draw(t sheet.ChartType, d sheet.ChartData, w, h int, o Options) *Grid {
128132 if w < 8 || h < 3 {129133 return g130134 }131- p := planFor(t, d, w, h, o)135+ p, lg := planFor(t, d, w, h, o)132136 if msg := p.note(); msg != "" {133137 g.message(msg)134138 return g135139 }136140 g.Plot = p.plotArea()137141 p.draw(g, o)142+ lg.draw(g)138143 return g139144 }140145
@@ -159,10 +164,12 @@ func finite(series []sheet.ChartSeries) (lo, hi float64, ok bool) {
159164 }160165 161166 // scale is a value axis: lo..hi divided into n intervals of step, each k162-// cells long, so every tick lands on a cell boundary.167+// cells long, so every tick lands on a cell boundary. On a log scale lo,168+// hi and step are powers of ten (their logarithms), see axis.go.163169 type scale struct {164170 lo, hi, step float64165171 n, k int172+ log bool166173 label func(float64) string167174 }168175
@@ -171,11 +178,22 @@ func (s scale) cells() int { return s.n * s.k }
171178 172179 // pos maps a value to a distance along the axis in cells.173180 func (s scale) pos(v float64) float64 {181+ if s.log {182+ if v <= 0 {183+ return math.Inf(-1)184+ }185+ v = math.Log10(v)186+ }174187 return (v - s.lo) / (s.hi - s.lo) * float64(s.cells())175188 }176189 177190 // tick returns the value of tick j.178-func (s scale) tick(j int) float64 { return s.lo + float64(j)*s.step }191+func (s scale) tick(j int) float64 {192+ if s.log {193+ return math.Pow(10, s.lo+float64(j)*s.step)194+ }195+ return s.lo + float64(j)*s.step196+}179197 180198 // newScale fits dlo..dhi onto length cells, with ticks at least minK181199 // cells apart and at most maxN intervals.
@@ -274,43 +292,6 @@ func groupDigits(s string) string {
274292 return b.String()275293 }276294 277-// legend draws "■ Rent ■ Food" centered on row y, shortening names to278-// fit.279-func (g *Grid) legend(y int, names []string) {280- const gap = " "281- for limit := 24; limit >= 1; limit-- {282- items := make([]string, len(names))283- width := 0284- for i, n := range names {285- items[i] = "■ " + ansi.Truncate(n, limit, "…")286- width += ansi.StringWidth(items[i])287- }288- width += len(gap) * (len(items) - 1)289- if width > g.W && limit > 1 {290- continue291- }292- x := max((g.W-width)/2, 0)293- for i, it := range items {294- if x+ansi.StringWidth(it) > g.W {295- g.text(max(x, 0), y, "…", Label)296- return297- }298- g.set(x, y, "■", SeriesRole(i))299- g.text(x+2, y, it[len("■ "):], Label)300- x += ansi.StringWidth(it) + len(gap)301- }302- return303- }304-}305-306-func seriesNames(d sheet.ChartData) []string {307- names := make([]string, len(d.Series))308- for i, s := range d.Series {309- names[i] = s.Name310- }311- return names312-}313-314295 // eighths are the block elements filling 0/8 to 8/8 of a cell from the315296 // bottom, and from the left.316297 var (
M internal/chart/chart_test.go +62 -16
@@ -43,6 +43,32 @@ var mixed = sheet.ChartData{
4343 Format: sheet.Format{Kind: sheet.FmtPercent},4444 }4545 46+// signs has positive and negative values in the same category.47+var signs = sheet.ChartData{48+ Categories: []string{"Q1", "Q2", "Q3"},49+ Series: []sheet.ChartSeries{50+ {Name: "Sales", Values: []float64{30, 40, 20}},51+ {Name: "Costs", Values: []float64{-20, -10, -30}},52+ {Name: "Other", Values: []float64{10, -5, 15}},53+ },54+}55+56+// xy is a scatter: X in the first series, two series of Y against it.57+var xy = sheet.ChartData{58+ Categories: []string{"1", "2", "3", "4", "5", "6"},59+ Series: []sheet.ChartSeries{60+ {Name: "Hours", Values: []float64{1, 2, 3, 5, 6, 8}},61+ {Name: "Score", Values: []float64{52, 55, 61, 70, 71, 80}},62+ {Name: "Retake", Values: []float64{40, nan, 50, 58, 66, 69}},63+ },64+}65+66+// growth spans several powers of ten, for log scales.67+var growth = sheet.ChartData{68+ Categories: []string{"Y1", "Y2", "Y3", "Y4", "Y5"},69+ Series: []sheet.ChartSeries{{Name: "Users", Values: []float64{3, 40, 700, 9000, 0}}},70+}71+4672 // TestDrawGoldens renders every chart type as text; the goldens in4773 // testdata are the review surface for how charts look.4874 func TestDrawGoldens(t *testing.T) {
@@ -51,22 +77,42 @@ func TestDrawGoldens(t *testing.T) {
5177 t sheet.ChartType5278 d sheet.ChartData5379 w, h int80+ o sheet.ChartOptions5481 }{55- {"column", sheet.ChartColumn, budget, 44, 12},56- {"column-single", sheet.ChartColumn, single, 40, 10},57- {"column-negative", sheet.ChartColumn, mixed, 36, 10},58- {"column-narrow", sheet.ChartColumn, budget, 22, 8},59- {"bar", sheet.ChartBar, budget, 44, 12},60- {"bar-single", sheet.ChartBar, single, 40, 8},61- {"line", sheet.ChartLine, budget, 44, 12},62- {"line-single", sheet.ChartLine, single, 40, 10},63- {"pie", sheet.ChartPie, single, 40, 10},64- {"empty", sheet.ChartColumn, sheet.ChartData{Categories: []string{"a"}, Series: []sheet.ChartSeries{{Values: []float64{nan}}}}, 30, 8},65- {"pie-negative", sheet.ChartPie, sheet.ChartData{Categories: []string{"a"}, Series: []sheet.ChartSeries{{Values: []float64{-1}}}}, 30, 8},82+ {"column", sheet.ChartColumn, budget, 44, 12, sheet.ChartOptions{}},83+ {"column-single", sheet.ChartColumn, single, 40, 10, sheet.ChartOptions{}},84+ {"column-negative", sheet.ChartColumn, mixed, 36, 10, sheet.ChartOptions{}},85+ {"column-narrow", sheet.ChartColumn, budget, 22, 8, sheet.ChartOptions{}},86+ {"bar", sheet.ChartBar, budget, 44, 12, sheet.ChartOptions{}},87+ {"bar-single", sheet.ChartBar, single, 40, 8, sheet.ChartOptions{}},88+ {"line", sheet.ChartLine, budget, 44, 12, sheet.ChartOptions{}},89+ {"line-single", sheet.ChartLine, single, 40, 10, sheet.ChartOptions{}},90+ {"pie", sheet.ChartPie, single, 40, 10, sheet.ChartOptions{}},91+ {"empty", sheet.ChartColumn, sheet.ChartData{Categories: []string{"a"}, Series: []sheet.ChartSeries{{Values: []float64{nan}}}}, 30, 8, sheet.ChartOptions{}},92+ {"pie-negative", sheet.ChartPie, sheet.ChartData{Categories: []string{"a"}, Series: []sheet.ChartSeries{{Values: []float64{-1}}}}, 30, 8, sheet.ChartOptions{}},93+ {"pie-no-legend", sheet.ChartPie, single, 40, 10, sheet.ChartOptions{Legend: sheet.LegendNone}},94+ {"area", sheet.ChartArea, budget, 44, 12, sheet.ChartOptions{}},95+ {"area-stacked", sheet.ChartArea, budget, 44, 12, sheet.ChartOptions{Stack: sheet.StackNormal}},96+ {"area-negative", sheet.ChartArea, mixed, 36, 10, sheet.ChartOptions{}},97+ {"column-stacked", sheet.ChartColumn, budget, 44, 12, sheet.ChartOptions{Stack: sheet.StackNormal}},98+ {"column-percent", sheet.ChartColumn, budget, 44, 12, sheet.ChartOptions{Stack: sheet.StackPercent}},99+ {"column-stacked-negative", sheet.ChartColumn, signs, 40, 12, sheet.ChartOptions{Stack: sheet.StackNormal}},100+ {"bar-stacked", sheet.ChartBar, budget, 44, 12, sheet.ChartOptions{Stack: sheet.StackNormal}},101+ {"bar-percent", sheet.ChartBar, budget, 44, 12, sheet.ChartOptions{Stack: sheet.StackPercent}},102+ {"scatter", sheet.ChartScatter, xy, 44, 12, sheet.ChartOptions{}},103+ {"scatter-trend", sheet.ChartScatter, xy, 44, 12, sheet.ChartOptions{Trend: true}},104+ {"scatter-single", sheet.ChartScatter, single, 40, 10, sheet.ChartOptions{}},105+ {"legend-right", sheet.ChartColumn, budget, 44, 12, sheet.ChartOptions{Legend: sheet.LegendRight}},106+ {"legend-none", sheet.ChartLine, budget, 44, 12, sheet.ChartOptions{Legend: sheet.LegendNone}},107+ {"no-gridlines", sheet.ChartColumn, single, 40, 10, sheet.ChartOptions{NoGrid: true}},108+ {"axis-min-max", sheet.ChartColumn, budget, 44, 12, sheet.ChartOptions{Min: 500, HasMin: true, Max: 1600, HasMax: true}},109+ {"axis-min", sheet.ChartLine, budget, 44, 12, sheet.ChartOptions{Min: 400, HasMin: true}},110+ {"axis-log", sheet.ChartColumn, growth, 40, 12, sheet.ChartOptions{Log: true}},111+ {"bar-log", sheet.ChartBar, growth, 40, 10, sheet.ChartOptions{Log: true}},66112 }67113 for _, tc := range cases {68114 t.Run(tc.name, func(t *testing.T) {69- got := Draw(tc.t, tc.d, tc.w, tc.h, Options{}).String() + "\n"115+ got := Draw(tc.t, tc.d, tc.w, tc.h, Options{Chart: tc.o}).String() + "\n"70116 path := filepath.Join("testdata", tc.name+".txt")71117 if *update {72118 os.MkdirAll("testdata", 0o755)
@@ -122,7 +168,7 @@ func TestScale(t *testing.T) {
122168 123169 func TestLayout(t *testing.T) {124170 // Columns: every bar lies inside the plot and bars don't overlap.125- p := newColumnPlan(budget, 44, 12, false)171+ p := newColumnPlan(budget, 44, 12, columnBars, sheet.ChartOptions{})126172 prev := 0127173 for i := range 4 {128174 for j := range 2 {
@@ -143,7 +189,7 @@ func TestLayout(t *testing.T) {
143189 many.Categories = append(many.Categories, "c")144190 many.Series[0].Values = append(many.Series[0].Values, float64(i))145191 }146- if p := newColumnPlan(many, 30, 10, false); p.shown != p.plot.Dx() {192+ if p := newColumnPlan(many, 30, 10, columnBars, sheet.ChartOptions{}); p.shown != p.plot.Dx() {147193 t.Errorf("shown %d categories in %d columns", p.shown, p.plot.Dx())148194 }149195 // The plot rectangle is what Image fills.
@@ -160,7 +206,7 @@ func TestLayout(t *testing.T) {
160206 // TestTypes checks every chart type the editor offers has a layout.161207 func TestTypes(t *testing.T) {162208 for _, ty := range sheet.ChartTypes {163- if types[ty] == nil {209+ if types[ty].layout == nil {164210 t.Errorf("%v has no layout", ty)165211 }166212 }
@@ -190,7 +236,7 @@ var testPalette = Palette{
190236 191237 func TestImagePixels(t *testing.T) {192238 o := Options{CellW: 8, CellH: 16}193- p := newColumnPlan(single, 40, 10, false)239+ p := newColumnPlan(single, 40, 10, columnBars, sheet.ChartOptions{})194240 img := Image(sheet.ChartColumn, single, 40, 10, o, testPalette)195241 x0, x1 := p.bar(0, 0)196242 mid := ((x0+x1)/2 - p.plot.Min.X) * 8
M internal/chart/column.go +143 -79
@@ -9,54 +9,65 @@ import (
99 "github.com/FelineStateMachine/012/internal/sheet"1010 )1111 12-// columnPlan lays out column and line charts: a value axis on the left13-// with labeled ticks, the plot, a category axis and labels below it, and a14-// legend when there are several series.12+// columnPlan lays out column, line and area charts: a value axis on the13+// left with labeled ticks, the plot, and a category axis and labels14+// below it. Draw adds the legend.1515 //1616 // 1,500 ┤ █▆1717 // 1,000 ┤ ██ ▆1818 // 500 ┤ ██ █1919 // 0 ┼─────────2020 // Jan Feb21-// ■ Rent ■ Food2221 type columnPlan struct {2322 d sheet.ChartData24- line bool23+ kind columnKind24+ stacked bool25+ grid bool2526 msg string2627 sc scale2728 labelW int // width of the tick labels2829 axisX int // column of the value axis2930 axisRow int // row of the category axis3031 catRow int31- legend int // row of the legend, -1 for none3232 plot image.Rectangle3333 shown int // categories drawn3434 // Columns: each category is a group gw cells wide, starting off cells35- // into the plot; each series' bar in it is bw wide.35+ // into the plot; each series' bar in it (the stack, when stacked) is36+ // bw wide.3637 gw, off, bw, inset int3738 }3839 39-func newColumnPlan(d sheet.ChartData, w, h int, line bool) *columnPlan {40- p := &columnPlan{d: d, line: line, legend: -1}40+// columnKind is what a columnPlan draws at each category.41+type columnKind uint842+43+const (44+ columnBars columnKind = iota45+ columnLine46+ columnArea47+)48+49+func newColumnPlan(d sheet.ChartData, w, h int, kind columnKind, o sheet.ChartOptions) *columnPlan {50+ stacked := o.Stack != sheet.StackNone && kind != columnLine51+ d = prepare(d, o, kind != columnLine)52+ p := &columnPlan{d: d, kind: kind, stacked: stacked, grid: !o.NoGrid}4153 lo, hi, ok := finite(d.Series)54+ if stacked {55+ lo, hi, ok = stackedRange(d, len(d.Categories))56+ }4257 if !ok || len(d.Categories) == 0 {4358 p.msg = "No numbers to chart"4459 return p4560 }46- if !line {61+ if kind != columnLine && !o.Log {4762 lo, hi = min(lo, 0), max(hi, 0)4863 }4964 p.catRow = h - 150- if len(d.Series) > 1 && h >= 7 {51- p.legend = h - 152- p.catRow = h - 253- }5465 p.axisRow = p.catRow - 15566 if p.axisRow < 2 {5667 p.msg = "Too small to chart"5768 return p5869 }59- p.sc = newScale(lo, hi, p.axisRow, 2, 6, d.Format)70+ p.sc = newAxis(lo, hi, p.axisRow, 2, 6, d.Format, o)6071 for j := 0; j <= p.sc.n; j++ {6172 p.labelW = max(p.labelW, ansi.StringWidth(p.sc.label(p.sc.tick(j))))6273 }
@@ -67,29 +78,53 @@ func newColumnPlan(d sheet.ChartData, w, h int, line bool) *columnPlan {
6778 p.msg = "Too small to chart"6879 return p6980 }70- ns := len(d.Series)71- if line {81+ if kind != columnBars {7282 p.shown = min(len(d.Categories), plotW*2)7383 return p7484 }75- p.shown = max(min(len(d.Categories), plotW/ns), 1)85+ slots := len(d.Series)86+ if stacked {87+ slots = 188+ }89+ p.shown = max(min(len(d.Categories), plotW/slots), 1)7690 p.gw = plotW / p.shown7791 gap := 078- if p.gw > ns {92+ if p.gw > slots {7993 gap = max(1, p.gw/4)8094 }81- p.bw = max((p.gw-gap)/ns, 1)82- p.inset = (p.gw - p.bw*ns) / 295+ p.bw = max((p.gw-gap)/slots, 1)96+ p.inset = (p.gw - p.bw*slots) / 28397 p.off = (plotW - p.shown*p.gw) / 28498 return p8599 }86100 87-// bar returns the columns of series j's bar in category i.101+// bar returns the columns of series j's bar in category i; stacked, all102+// series share the first.88103 func (p *columnPlan) bar(i, j int) (x0, x1 int) {104+ if p.stacked {105+ j = 0106+ }89107 x0 = p.plot.Min.X + p.off + i*p.gw + p.inset + j*p.bw90108 return x0, x0 + p.bw91109 }92110 111+// piles returns category i's bars as piles of spans in values, each with112+// the series whose slot it takes: one pile when stacked, one per series113+// otherwise.114+func (p *columnPlan) piles(i int, buf []span) []span {115+ if p.stacked {116+ return stack(p.d, i, buf)117+ }118+ buf = buf[:0]119+ base := p.sc.base()120+ for j, s := range p.d.Series {121+ if v := value(s, i); !math.IsNaN(v) {122+ buf = append(buf, span{min(v, base), max(v, base), j})123+ }124+ }125+ return buf126+}127+93128 // dotX returns the x of category i's point in braille dots (two per cell).94129 func (p *columnPlan) dotX(i int) int {95130 dots := p.plot.Dx() * 2
@@ -101,48 +136,67 @@ func (p *columnPlan) dotX(i int) int {
101136 102137 // catCenter is the column under which category i's label is centered.103138 func (p *columnPlan) catCenter(i int) int {104- if p.line {139+ if p.kind != columnBars {105140 return p.plot.Min.X + p.dotX(i)/2106141 }107142 return p.plot.Min.X + p.off + i*p.gw + p.gw/2108143 }109144 110145 func (p *columnPlan) draw(g *Grid, o Options) {111- // Value axis with a label at each tick, up to the top one.112- for y := p.axisRow - p.sc.cells(); y < p.axisRow; y++ {113- g.set(p.axisX, y, "│", Axis)146+ drawValueAxis(g, p.sc, p.labelW, p.axisX, p.axisRow, p.plot.Min.X, p.plot.Max.X+1)147+ p.drawCategories(g)148+ if o.Image {149+ return114150 }115- for x := p.plot.Min.X; x < g.W; x++ {116- g.set(x, p.axisRow, "─", Axis)151+ if p.grid {152+ hGridlines(g, p.sc, p.axisRow, p.plot)117153 }118- for j := 0; j <= p.sc.n; j++ {119- y := p.axisRow - j*p.sc.k120- label := p.sc.label(p.sc.tick(j))121- g.text(p.labelW-ansi.StringWidth(label), y, label, Label)154+ switch p.kind {155+ case columnLine:156+ p.drawLines(g)157+ case columnArea:158+ p.drawArea(g)159+ default:160+ p.drawBars(g)161+ }162+}163+164+// drawValueAxis draws a vertical value axis at column axisX with a label165+// at each tick, and the category axis along row axisRow from x0 to x1.166+func drawValueAxis(g *Grid, sc scale, labelW, axisX, axisRow, x0, x1 int) {167+ for y := axisRow - sc.cells(); y < axisRow; y++ {168+ g.set(axisX, y, "│", Axis)169+ }170+ for x := x0; x < min(x1, g.W); x++ {171+ g.set(x, axisRow, "─", Axis)172+ }173+ for j := 0; j <= sc.n; j++ {174+ y := axisRow - j*sc.k175+ label := sc.label(sc.tick(j))176+ g.text(labelW-ansi.StringWidth(label), y, label, Label)122177 mark := "┤"123178 if j == 0 {124179 mark = "┼"125180 }126- g.set(p.axisX, y, mark, Axis)127- }128- p.drawCategories(g)129- if p.legend >= 0 {130- g.legend(p.legend, seriesNames(p.d))131- }132- if o.Image {133- return181+ g.set(axisX, y, mark, Axis)134182 }135- if p.line {136- p.drawLines(g)137- } else {138- p.drawBars(g)183+}184+185+// hGridlines draws a dashed line across the plot at each tick above the186+// axis.187+func hGridlines(g *Grid, sc scale, axisRow int, plot image.Rectangle) {188+ for j := 1; j <= sc.n; j++ {189+ y := axisRow - j*sc.k190+ for x := plot.Min.X; x < plot.Max.X; x++ {191+ g.set(x, y, "┈", Gridline)192+ }139193 }140194 }141195 142196 // drawCategories labels as many categories as fit without touching.143197 func (p *columnPlan) drawCategories(g *Grid) {144198 room := p.gw - 1145- if p.line {199+ if p.kind != columnBars {146200 room = p.plot.Dx()/max(p.shown-1, 1) - 1147201 }148202 room = max(room, 3)
@@ -160,48 +214,28 @@ func (p *columnPlan) drawCategories(g *Grid) {
160214 }161215 162216 func (p *columnPlan) drawBars(g *Grid) {163- zero := p.sc.pos(0)164- for j, s := range p.d.Series {165- for i := 0; i < p.shown && i < len(s.Values); i++ {166- v := s.Values[i]167- if math.IsNaN(v) {168- continue169- }170- x0, x1 := p.bar(i, j)171- lo, hi := min(zero, p.sc.pos(v)), max(zero, p.sc.pos(v))172- for r := int(math.Floor(lo)); float64(r) < hi; r++ {173- // Below the axis, bars fill from the top.174- glyph := barGlyph(lo, hi, r, v >= 0, lowerEighths, "▀")175- for x := x0; glyph != "" && x < x1; x++ {176- g.set(x, p.axisRow-1-r, glyph, SeriesRole(j))177- }217+ drawPiles(p.sc, p.shown, p.stacked, p.piles, lowerEighths, "▀", func(i, j, r int, cell Cell) {218+ x0, x1 := p.bar(i, j)219+ for x := x0; x < x1; x++ {220+ if at := g.At(x, p.axisRow-1-r); at != nil {221+ *at = cell178222 }179223 }180- }224+ })181225 }182226 183227 // dotY maps a value to a braille dot row, four per cell, from the top.184228 func (p *columnPlan) dotY(v float64) int {185229 rows := p.axisRow * 4186230 full := p.sc.cells()*4 - 1187- y := rows - 1 - int(math.Round(p.sc.pos(v)/float64(p.sc.cells())*float64(full)))231+ y := rows - 1 - int(math.Round(p.sc.at(v)/float64(p.sc.cells())*float64(full)))188232 return min(max(y, 0), rows-1)189233 }190234 191235 // drawLines draws each series as a braille line; where lines cross a192236 // cell, the later series' color wins.193237 func (p *columnPlan) drawLines(g *Grid) {194- w, h := p.plot.Dx(), p.axisRow195- bits := make([]uint8, w*h)196- owner := make([]int, w*h)197- dot := func(x, y, series int) {198- cx, cy := x/2, y/4199- if cx < 0 || cx >= w || cy < 0 || cy >= h {200- return201- }202- bits[cy*w+cx] |= brailleBit[x%2][y%4]203- owner[cy*w+cx] = series204- }238+ b := newBraille(p.plot.Dx(), p.axisRow)205239 for j, s := range p.d.Series {206240 px, py, have := 0, 0, false207241 for i := 0; i < p.shown && i < len(s.Values); i++ {
@@ -212,17 +246,47 @@ func (p *columnPlan) drawLines(g *Grid) {
212246 }213247 x, y := p.dotX(i), p.dotY(v)214248 if have {215- bresenham(px, py, x, y, func(x, y int) { dot(x, y, j) })249+ b.line(px, py, x, y, j)216250 } else {217- dot(x, y, j)251+ b.dot(x, y, j)218252 }219253 px, py, have = x, y, true220254 }221255 }222- for cy := range h {223- for cx := range w {224- if b := bits[cy*w+cx]; b != 0 {225- g.set(p.plot.Min.X+cx, cy, string(rune(0x2800+int(b))), SeriesRole(owner[cy*w+cx]))256+ b.draw(g, p.plot.Min.X, 0)257+}258+259+// braille is a canvas of braille dots, two by four per cell, each cell260+// colored by the series that drew in it last.261+type braille struct {262+ w, h int263+ bits []uint8264+ owner []int265+}266+267+func newBraille(w, h int) *braille {268+ return &braille{w: w, h: h, bits: make([]uint8, w*h), owner: make([]int, w*h)}269+}270+271+func (b *braille) dot(x, y, series int) {272+ cx, cy := x/2, y/4273+ if x < 0 || y < 0 || cx >= b.w || cy >= b.h {274+ return275+ }276+ b.bits[cy*b.w+cx] |= brailleBit[x%2][y%4]277+ b.owner[cy*b.w+cx] = series278+}279+280+func (b *braille) line(x0, y0, x1, y1, series int) {281+ bresenham(x0, y0, x1, y1, func(x, y int) { b.dot(x, y, series) })282+}283+284+// draw puts the dots on g with the top-left cell at x, y.285+func (b *braille) draw(g *Grid, x, y int) {286+ for cy := range b.h {287+ for cx := range b.w {288+ if bits := b.bits[cy*b.w+cx]; bits != 0 {289+ g.set(x+cx, y+cy, string(rune(0x2800+int(bits))), SeriesRole(b.owner[cy*b.w+cx]))226290 }227291 }228292 }
M internal/chart/image.go +34 -30
@@ -28,7 +28,7 @@ func Image(t sheet.ChartType, d sheet.ChartData, w, h int, o Options, pal Palett
2828 if w < 8 || h < 3 {2929 return nil3030 }31- p := planFor(t, d, w, h, o)31+ p, _ := planFor(t, d, w, h, o)3232 plot := p.plotArea()3333 if p.note() != "" || plot.Empty() {3434 return nil
@@ -131,32 +131,36 @@ func segDist(px, py, ax, ay, bx, by float64) float64 {
131131 return math.Hypot(px-(ax+t*dx), py-(ay+t*dy))132132 }133133 134-// image draws columns or lines. Values map exactly, while gridlines sit135-// on the cell boundaries the text ticks label.134+// image draws columns, lines or areas. Values map exactly, while135+// gridlines sit on the cell boundaries the text ticks label.136136 func (p *columnPlan) image(cv *canvas) {137137 bottom := float64(p.plot.Dy()) * cv.ch138- y := func(v float64) float64 { return bottom - p.sc.pos(v)*cv.ch }139- for j := 1; j <= p.sc.n; j++ {140- cv.hline(int(math.Round(y(p.sc.tick(j)))), cv.pal.Grid)138+ y := func(v float64) float64 { return bottom - p.sc.at(v)*cv.ch }139+ if p.grid {140+ for j := 1; j <= p.sc.n; j++ {141+ cv.hline(int(math.Round(y(p.sc.tick(j)))), cv.pal.Grid)142+ }143+ }144+ if p.kind != columnLine && p.sc.crossesZero() {145+ cv.hline(int(math.Round(y(0))), cv.pal.Grid)141146 }142- if p.line {147+ switch p.kind {148+ case columnLine:143149 p.lineImage(cv, y)144150 return145- }146- if p.sc.lo < 0 {147- cv.hline(int(math.Round(y(0))), cv.pal.Grid)151+ case columnArea:152+ p.areaImage(cv, y)153+ return148154 }149155 inset := min(cv.cw/6, 2)150- for j, s := range p.d.Series {151- for i := 0; i < p.shown && i < len(s.Values); i++ {152- v := s.Values[i]153- if math.IsNaN(v) {154- continue155- }156- x0, x1 := p.bar(i, j)156+ var piles []span157+ for i := 0; i < p.shown; i++ {158+ piles = p.piles(i, piles)159+ for _, s := range piles {160+ x0, x1 := p.bar(i, s.j)157161 px0 := float64(x0-p.plot.Min.X)*cv.cw + inset158162 px1 := float64(x1-p.plot.Min.X)*cv.cw - inset159- cv.rect(px0, min(y(v), y(0)), px1, max(y(v), y(0)), cv.pal.Series[j%Colors])163+ cv.rect(px0, y(s.hi), px1, y(s.lo), cv.pal.Series[s.j%Colors])160164 }161165 }162166 }
@@ -187,22 +191,22 @@ func (p *columnPlan) lineImage(cv *canvas, y func(float64) float64) {
187191 188192 // image draws horizontal bars.189193 func (p *barPlan) image(cv *canvas) {190- x := func(v float64) float64 { return p.sc.pos(v) * cv.cw }191- for j := 1; j <= p.sc.n; j++ {192- cv.vline(int(math.Round(x(p.sc.tick(j))))-1, cv.pal.Grid)194+ x := func(v float64) float64 { return p.sc.at(v) * cv.cw }195+ if p.grid {196+ for j := 1; j <= p.sc.n; j++ {197+ cv.vline(int(math.Round(x(p.sc.tick(j))))-1, cv.pal.Grid)198+ }193199 }194- if p.sc.lo < 0 {200+ if p.sc.crossesZero() {195201 cv.vline(int(math.Round(x(0))), cv.pal.Grid)196202 }197203 inset := min(cv.ch/8, 2)198- for j, s := range p.d.Series {199- for i := 0; i < p.shown && i < len(s.Values); i++ {200- v := s.Values[i]201- if math.IsNaN(v) {202- continue203- }204- row := float64(p.barRow(i, j))205- cv.rect(min(x(v), x(0)), row*cv.ch+inset, max(x(v), x(0)), (row+1)*cv.ch-inset, cv.pal.Series[j%Colors])204+ var piles []span205+ for i := 0; i < p.shown; i++ {206+ piles = p.piles(i, piles)207+ for _, s := range piles {208+ row := float64(p.barRow(i, s.j))209+ cv.rect(x(s.lo), row*cv.ch+inset, x(s.hi), (row+1)*cv.ch-inset, cv.pal.Series[s.j%Colors])206210 }207211 }208212 }
A internal/chart/legend.go +114 -0
@@ -0,0 +1,114 @@
1+package chart2+3+import (4+ "strconv"5+6+ "github.com/charmbracelet/x/ansi"7+8+ "github.com/FelineStateMachine/012/internal/sheet"9+)10+11+// legendBox is where a chart's legend of series goes: a row under the12+// plot, a column right of it, or nowhere. Draw lays the chart out in the13+// room the legend leaves.14+type legendBox struct {15+ names []string16+ pos sheet.ChartLegend17+ row int // the bottom legend's row18+ x, w int // the right legend's first column and width19+}20+21+// Legend sizes.22+const (23+ legendNameW = 16 // longest name in a right legend24+ minRightPlot = 16 // narrowest chart a right legend leaves25+)26+27+// placeLegend makes room for a legend of names in a w by h chart and28+// returns the room left for the chart. A legend needs two series or more,29+// and space: a bottom one seven rows, a right one a chart still30+// minRightPlot wide beside it.31+func placeLegend(names []string, pos sheet.ChartLegend, w, h int) (legendBox, int, int) {32+ lg := legendBox{pos: sheet.LegendNone}33+ if len(names) < 2 {34+ return lg, w, h35+ }36+ switch pos {37+ case sheet.LegendBottom:38+ if h >= 7 {39+ return legendBox{names: names, pos: pos, row: h - 1}, w, h - 140+ }41+ case sheet.LegendRight:42+ nameW := 143+ for _, n := range names {44+ nameW = max(nameW, ansi.StringWidth(n))45+ }46+ lw := 2 + min(nameW, legendNameW)47+ if w-lw-1 >= minRightPlot {48+ return legendBox{names: names, pos: pos, x: w - lw, w: lw}, w - lw - 1, h49+ }50+ }51+ return lg, w, h52+}53+54+func (lg legendBox) draw(g *Grid) {55+ switch lg.pos {56+ case sheet.LegendBottom:57+ g.legend(lg.row, lg.names)58+ case sheet.LegendRight:59+ g.legendColumn(lg.x, lg.w, lg.names)60+ }61+}62+63+// legend draws "■ Rent ■ Food" centered on row y, shortening names to64+// fit.65+func (g *Grid) legend(y int, names []string) {66+ const gap = " "67+ for limit := 24; limit >= 1; limit-- {68+ items := make([]string, len(names))69+ width := 070+ for i, n := range names {71+ items[i] = "■ " + ansi.Truncate(n, limit, "…")72+ width += ansi.StringWidth(items[i])73+ }74+ width += len(gap) * (len(items) - 1)75+ if width > g.W && limit > 1 {76+ continue77+ }78+ x := max((g.W-width)/2, 0)79+ for i, it := range items {80+ if x+ansi.StringWidth(it) > g.W {81+ g.text(max(x, 0), y, "…", Label)82+ return83+ }84+ g.set(x, y, "■", SeriesRole(i))85+ g.text(x+2, y, it[len("■ "):], Label)86+ x += ansi.StringWidth(it) + len(gap)87+ }88+ return89+ }90+}91+92+// legendColumn draws the legend one series a row from column x, w wide,93+// centered down the chart, ending in "… 3 more" when they don't all fit.94+func (g *Grid) legendColumn(x, w int, names []string) {95+ n := min(len(names), g.H)96+ y := (g.H - n) / 297+ for i, name := range names {98+ if y == g.H-1 && i < len(names)-1 {99+ g.text(x, y, ansi.Truncate("… "+strconv.Itoa(len(names)-i)+" more", w, "…"), Label)100+ return101+ }102+ g.set(x, y, "■", SeriesRole(i))103+ g.text(x+2, y, ansi.Truncate(name, w-2, "…"), Label)104+ y++105+ }106+}107+108+func seriesNames(series []sheet.ChartSeries) []string {109+ names := make([]string, len(series))110+ for i, s := range series {111+ names[i] = s.Name112+ }113+ return names114+}
M internal/chart/pie.go +14 -5
@@ -12,7 +12,8 @@ import (
1212 )1313 1414 // piePlan lays out a pie of the first series, clockwise from twelve15-// o'clock as in Sheets, with a legend of labels and shares to its right.15+// o'clock as in Sheets, with a legend of labels and shares to its right16+// unless the legend is off: a pie's legend is always on the right.1617 // As text, the disc is half blocks: two square pixels per cell, each1718 // colored by its slice.1819 type piePlan struct {
@@ -21,7 +22,9 @@ type piePlan struct {
2122 slices []slice2223 disc image.Rectangle // in cells2324 legend image.Point // top-left of the legend24- nameW int25+ // noLegend leaves the legend out, for Legend: none.26+ noLegend bool27+ nameW int2528 }2629 2730 type slice struct {
@@ -59,15 +62,18 @@ func newPiePlan(d sheet.ChartData, w, h int, o Options) *piePlan {
5962 }6063 // "■ " name " " share: shares are at most "100%".6164 p.nameW = max(min(p.nameW, w/2-8), 3)62- legendW := 2 + p.nameW + 2 + 465+ legendW, gap := 2+p.nameW+2+4, 366+ if o.Chart.Legend == sheet.LegendNone {67+ legendW, gap, p.noLegend = 0, 0, true68+ }6369 a := o.aspect()64- rows := min(h, int(float64(w-legendW-3)/a))70+ rows := min(h, int(float64(w-legendW-gap)/a))6571 if rows < 2 {6672 p.msg = "Too small to chart"6773 return p6874 }6975 cols := int(math.Round(float64(rows) * a))70- x0 := max((w-(cols+3+legendW))/2, 0)76+ x0 := max((w-(cols+gap+legendW))/2, 0)7177 y0 := (h - rows) / 27278 p.disc = image.Rect(x0, y0, x0+cols, y0+rows)7379 n := min(len(p.slices), h)
@@ -124,6 +130,9 @@ func (p *piePlan) draw(g *Grid, o Options) {
124130 }125131 126132 func (p *piePlan) drawLegend(g *Grid) {133+ if p.noLegend {134+ return135+ }127136 x, y := p.legend.X, p.legend.Y128137 for i, s := range p.slices {129138 if y >= g.H {
A internal/chart/scatter.go +239 -0
@@ -0,0 +1,239 @@
1+package chart2+3+import (4+ "image"5+ "image/color"6+ "math"7+ "strconv"8+9+ "github.com/charmbracelet/x/ansi"10+11+ "github.com/FelineStateMachine/012/internal/sheet"12+)13+14+// scatterPlan lays out a scatter chart as Sheets does: the first series15+// is the X values and every other one a series of Y values plotted16+// against them, each point a dot, with an optional linear trend line per17+// series. The value axis on the left is Y, with the chart's axis options;18+// X runs along the bottom.19+//20+// 60 ┤ ⠛ ⠛⠛21+// 40 ┤ ⠛⠛ ⠛22+// 20 ┤⠛23+// 0 ┼──┬──┬──┬24+// 0 2 4 625+type scatterPlan struct {26+ xs []float6427+ ys []sheet.ChartSeries28+ trend bool29+ grid bool30+ msg string31+ xsc scale32+ ysc scale33+ labelW int34+ axisX int35+ axisRow int36+ tickRow int37+ plot image.Rectangle38+}39+40+// scatterSeries splits d into X values and Y series: the first series is41+// X when there are two or more, and otherwise the categories are, read as42+// numbers or else counted from 1.43+func scatterSeries(d sheet.ChartData) ([]float64, []sheet.ChartSeries) {44+ if len(d.Series) >= 2 {45+ return d.Series[0].Values, d.Series[1:]46+ }47+ xs := make([]float64, len(d.Categories))48+ for i, c := range d.Categories {49+ x, err := strconv.ParseFloat(c, 64)50+ if err != nil {51+ x = float64(i + 1)52+ }53+ xs[i] = x54+ }55+ return xs, d.Series56+}57+58+func newScatterPlan(d sheet.ChartData, w, h int, o sheet.ChartOptions) *scatterPlan {59+ d = prepare(d, o, false)60+ xs, ys := scatterSeries(d)61+ p := &scatterPlan{xs: xs, ys: ys, trend: o.Trend, grid: !o.NoGrid}62+ xlo, xhi, okX := finite([]sheet.ChartSeries{{Values: xs}})63+ ylo, yhi, okY := finite(ys)64+ if !okX || !okY {65+ p.msg = "No numbers to chart"66+ return p67+ }68+ p.tickRow = h - 169+ p.axisRow = h - 270+ if p.axisRow < 2 {71+ p.msg = "Too small to chart"72+ return p73+ }74+ p.ysc = newAxis(ylo, yhi, p.axisRow, 2, 6, d.Format, o)75+ for j := 0; j <= p.ysc.n; j++ {76+ p.labelW = max(p.labelW, ansi.StringWidth(p.ysc.label(p.ysc.tick(j))))77+ }78+ p.axisX = p.labelW + 179+ x0 := p.axisX + 180+ labelW := 181+ for range 2 {82+ plotW := w - x0 - (labelW+1)/283+ if plotW < 2 {84+ p.msg = "Too small to chart"85+ return p86+ }87+ p.xsc = newScale(xlo, xhi, plotW, labelW+2, 6, d.Format)88+ for j := 0; j <= p.xsc.n; j++ {89+ labelW = max(labelW, ansi.StringWidth(p.xsc.label(p.xsc.tick(j))))90+ }91+ }92+ p.plot = image.Rect(x0, 0, x0+p.xsc.cells(), p.axisRow)93+ return p94+}95+96+// points calls fn with each point of every series, in cells from the97+// plot's bottom left.98+func (p *scatterPlan) points(fn func(j int, x, y float64)) {99+ for j, s := range p.ys {100+ for i, x := range p.xs {101+ y := value(s, i)102+ if math.IsNaN(y) || math.IsNaN(x) || math.IsInf(x, 0) {103+ continue104+ }105+ fn(j, p.xsc.at(x), p.ysc.at(y))106+ }107+ }108+}109+110+// fit returns the least squares line y = a + b*x through series j's111+// points, and false with fewer than two distinct X values.112+func (p *scatterPlan) fit(j int) (a, b float64, ok bool) {113+ var n, sx, sy, sxx, sxy float64114+ for i, x := range p.xs {115+ y := value(p.ys[j], i)116+ if math.IsNaN(y) || math.IsNaN(x) || math.IsInf(x, 0) {117+ continue118+ }119+ n++120+ sx, sy, sxx, sxy = sx+x, sy+y, sxx+x*x, sxy+x*y121+ }122+ d := n*sxx - sx*sx123+ if n < 2 || math.Abs(d) < 1e-12*max(1, sxx*n) {124+ return 0, 0, false125+ }126+ b = (n*sxy - sx*sy) / d127+ return (sy - b*sx) / n, b, true128+}129+130+// trendPoints calls fn with points along series j's trend line, in cells131+// from the plot's bottom left, steps per cell across the plot; ok is132+// false where the line leaves the value axis.133+func (p *scatterPlan) trendPoints(j int, steps float64, fn func(x, y float64, ok bool)) {134+ a, b, ok := p.fit(j)135+ if !ok {136+ return137+ }138+ cells := float64(p.xsc.cells())139+ n := int(cells * steps)140+ for k := 0; k <= n; k++ {141+ x := cells * float64(k) / float64(max(n, 1))142+ v := a + b*(p.xsc.lo+x/cells*(p.xsc.hi-p.xsc.lo))143+ y := p.ysc.pos(v)144+ fn(x, y, !math.IsNaN(y) && y >= 0 && y <= float64(p.ysc.cells()))145+ }146+}147+148+func (p *scatterPlan) draw(g *Grid, o Options) {149+ drawValueAxis(g, p.ysc, p.labelW, p.axisX, p.axisRow, p.plot.Min.X, p.plot.Max.X)150+ drawXTicks(g, p.xsc, p.axisX, p.plot.Min.X, p.axisRow, p.tickRow)151+ if o.Image {152+ return153+ }154+ if p.grid {155+ hGridlines(g, p.ysc, p.axisRow, p.plot)156+ }157+ w, h := p.plot.Dx(), p.axisRow158+ b := newBraille(w, h)159+ // Dots: two per cell across, four down, from the top left.160+ dot := func(x, y float64) (int, int) {161+ dx := int(math.Round(x / float64(max(p.xsc.cells(), 1)) * float64(w*2-1)))162+ dy := h*4 - 1 - int(math.Round(y/float64(max(p.ysc.cells(), 1))*float64(p.ysc.cells()*4-1)))163+ return min(max(dx, 0), w*2-1), min(max(dy, 0), h*4-1)164+ }165+ if p.trend {166+ for j := range p.ys {167+ px, py, have := 0, 0, false168+ p.trendPoints(j, 2, func(x, y float64, ok bool) {169+ if !ok {170+ have = false171+ return172+ }173+ dx, dy := dot(x, y)174+ if have {175+ b.line(px, py, dx, dy, j)176+ }177+ px, py, have = dx, dy, true178+ })179+ }180+ }181+ p.points(func(j int, x, y float64) {182+ dx, dy := dot(x, y)183+ // A point is two dots by two, kept inside the plot.184+ dx, dy = min(dx, w*2-2), max(dy-1, 0)185+ for k := range 4 {186+ b.dot(dx+k%2, dy+k/2, j)187+ }188+ })189+ b.draw(g, p.plot.Min.X, 0)190+}191+192+// image draws the points as discs and the trend lines through them.193+func (p *scatterPlan) image(cv *canvas) {194+ bottom := float64(p.plot.Dy()) * cv.ch195+ px := func(x float64) float64 { return x * cv.cw }196+ py := func(y float64) float64 { return bottom - y*cv.ch }197+ if p.grid {198+ for j := 1; j <= p.ysc.n; j++ {199+ cv.hline(int(math.Round(py(float64(j*p.ysc.k)))), cv.pal.Grid)200+ }201+ for j := 1; j <= p.xsc.n; j++ {202+ cv.vline(int(math.Round(px(float64(j*p.xsc.k))))-1, cv.pal.Grid)203+ }204+ }205+ if p.trend {206+ width := max(cv.ch/12, 1.5)207+ for j := range p.ys {208+ var run [][2]float64209+ flush := func() {210+ if len(run) > 1 {211+ cv.polyline(run, width, cv.pal.Series[j%Colors])212+ }213+ run = run[:0]214+ }215+ p.trendPoints(j, 1, func(x, y float64, ok bool) {216+ if !ok {217+ flush()218+ return219+ }220+ run = append(run, [2]float64{px(x), py(y)})221+ })222+ flush()223+ }224+ }225+ r := max(min(cv.cw, cv.ch)*0.3, 2.5)226+ p.points(func(j int, x, y float64) {227+ cv.disc(px(x), py(y), r, cv.pal.Series[j%Colors])228+ })229+}230+231+// disc fills a circle of radius r centered at cx, cy, antialiased.232+func (cv *canvas) disc(cx, cy, r float64, c color.RGBA) {233+ for y := int(math.Floor(cy - r - 1)); y <= int(math.Ceil(cy+r+1)); y++ {234+ for x := int(math.Floor(cx - r - 1)); x <= int(math.Ceil(cx+r+1)); x++ {235+ d := math.Hypot(float64(x)+0.5-cx, float64(y)+0.5-cy)236+ cv.blend(x, y, c, r+0.5-d)237+ }238+ }239+}
A internal/chart/stack.go +197 -0
@@ -0,0 +1,197 @@
1+package chart2+3+import (4+ "math"5+6+ "github.com/FelineStateMachine/012/internal/sheet"7+)8+9+// A span is a piece of a bar, or of an area at one point, from lo to hi10+// along the value axis, in values, colored as series j.11+type span struct {12+ lo, hi float6413+ j int14+}15+16+// prepare readies d for a chart's options: values a log scale can't show17+// left out, and with 100% stacking each value as its share of its18+// category.19+func prepare(d sheet.ChartData, o sheet.ChartOptions, stackable bool) sheet.ChartData {20+ if o.Log {21+ d = positive(d)22+ }23+ if stackable && o.Stack == sheet.StackPercent {24+ d = shares(d)25+ }26+ return d27+}28+29+// shares is d with each value divided by the sum of its category's sizes,30+// formatted as percentages.31+func shares(d sheet.ChartData) sheet.ChartData {32+ out := sheet.ChartData{Categories: d.Categories, Format: sheet.Format{Kind: sheet.FmtPercent}}33+ totals := make([]float64, len(d.Categories))34+ for _, s := range d.Series {35+ for i, v := range s.Values {36+ if i < len(totals) && !math.IsNaN(v) && !math.IsInf(v, 0) {37+ totals[i] += math.Abs(v)38+ }39+ }40+ }41+ for _, s := range d.Series {42+ vals := make([]float64, len(s.Values))43+ for i, v := range s.Values {44+ vals[i] = math.NaN()45+ if i < len(totals) && totals[i] > 0 && !math.IsInf(v, 0) {46+ vals[i] = v / totals[i]47+ }48+ }49+ out.Series = append(out.Series, sheet.ChartSeries{Name: s.Name, Values: vals})50+ }51+ return out52+}53+54+// value returns series s's value i, NaN when missing or infinite.55+func value(s sheet.ChartSeries, i int) float64 {56+ if i >= len(s.Values) || math.IsInf(s.Values[i], 0) {57+ return math.NaN()58+ }59+ return s.Values[i]60+}61+62+// stackedRange is the range of the stacks' ends over the first n63+// categories: positive values pile up from zero and negative ones down.64+func stackedRange(d sheet.ChartData, n int) (lo, hi float64, ok bool) {65+ for i := range n {66+ up, down := 0.0, 0.067+ for _, s := range d.Series {68+ if v := value(s, i); v > 0 {69+ up += v70+ ok = true71+ } else if v <= 0 {72+ down += v73+ ok = true74+ }75+ }76+ lo, hi = min(lo, down), max(hi, up)77+ }78+ return lo, hi, ok79+}80+81+// stack returns category i's spans, one per series with a value, piled82+// up from zero (positive values) and down from it (negative ones).83+func stack(d sheet.ChartData, i int, out []span) []span {84+ return pileUp(len(d.Series), func(j int) float64 { return value(d.Series[j], i) }, out)85+}86+87+// pileUp stacks n values, v(j) for series j, into out.88+func pileUp(n int, v func(j int) float64, out []span) []span {89+ out = out[:0]90+ up, down := 0.0, 0.091+ for j := range n {92+ switch v := v(j); {93+ case v > 0:94+ out = append(out, span{up, up + v, j})95+ up += v96+ case v < 0:97+ out = append(out, span{down + v, down, j})98+ down += v99+ }100+ }101+ return out102+}103+104+// pileCell is cell c, counted in cells from the start of the axis, of a105+// pile of spans given in cells along it, drawn with eighths growing from106+// the start of the cell (lowerEighths or leftEighths) and half, the107+// glyph for its far half. The span covering the near edge of the cell108+// is the foreground, reaching as far as it goes; the one covering the far109+// edge is the background. A cell only its far part of which is covered110+// is a full block or the half block, as eighths can't grow the other111+// way. ok is false for an empty cell.112+func pileCell(spans []span, c int, eighths []string, half string) (Cell, bool) {113+ const edge = 1.0 / 16114+ near, far := -1, -1115+ for k, s := range spans {116+ if s.lo <= float64(c)+edge && s.hi > float64(c)+edge {117+ near = k118+ }119+ if s.lo < float64(c+1)-edge && s.hi >= float64(c+1)-edge {120+ far = k121+ }122+ }123+ switch {124+ case near >= 0 && near == far:125+ return Cell{Text: "█", Fg: SeriesRole(spans[near].j)}, true126+ case near >= 0:127+ n := int(math.Round((spans[near].hi - float64(c)) * 8))128+ if n >= 8 || far < 0 && n >= 1 {129+ bg := None130+ if far >= 0 {131+ bg = SeriesRole(spans[far].j)132+ }133+ return Cell{Text: eighths[min(n, 8)], Fg: SeriesRole(spans[near].j), Bg: bg}, true134+ }135+ if n >= 1 {136+ return Cell{Text: eighths[n], Fg: SeriesRole(spans[near].j), Bg: SeriesRole(spans[far].j)}, true137+ }138+ }139+ if far < 0 {140+ return Cell{}, false141+ }142+ switch cover := float64(c+1) - spans[far].lo; {143+ case cover >= 0.75:144+ return Cell{Text: "█", Fg: SeriesRole(spans[far].j)}, true145+ case cover >= 0.25:146+ return Cell{Text: half, Fg: SeriesRole(spans[far].j)}, true147+ }148+ return Cell{}, false149+}150+151+// drawPiles draws the bars of the first shown categories cell by cell:152+// piles gives category i's spans in values, one pile of them all when153+// stacked, and put draws cell c along the axis of series j's bar (the154+// stack's, when stacked) in category i.155+func drawPiles(sc scale, shown int, stacked bool, piles func(int, []span) []span, eighths []string, half string, put func(i, j, c int, cell Cell)) {156+ var vals, cells []span157+ for i := 0; i < shown; i++ {158+ vals = piles(i, vals)159+ for k := range vals {160+ pile := vals[k : k+1]161+ if stacked {162+ pile = vals163+ }164+ cells = inCells(sc, pile, cells)165+ from, to := pileExtent(cells)166+ for c := from; c <= to; c++ {167+ if cell, ok := pileCell(cells, c, eighths, half); ok {168+ put(i, vals[k].j, c, cell)169+ }170+ }171+ if stacked {172+ break173+ }174+ }175+ }176+}177+178+// inCells maps spans in values to spans in cells along sc, into out.179+func inCells(sc scale, spans []span, out []span) []span {180+ out = out[:0]181+ for _, s := range spans {182+ out = append(out, span{sc.at(s.lo), sc.at(s.hi), s.j})183+ }184+ return out185+}186+187+// pileExtent is the first and last cell a pile in cells touches.188+func pileExtent(spans []span) (from, to int) {189+ lo, hi := math.Inf(1), math.Inf(-1)190+ for _, s := range spans {191+ lo, hi = min(lo, s.lo), max(hi, s.hi)192+ }193+ if lo > hi {194+ return 0, -1195+ }196+ return int(math.Floor(lo)), int(math.Ceil(hi)) - 1197+}
M internal/chart/stress_test.go +2 -0
A internal/chart/testdata/area-negative.txt +10 -0
A internal/chart/testdata/area-stacked.txt +12 -0
A internal/chart/testdata/area.txt +12 -0
A internal/chart/testdata/axis-log.txt +12 -0
A internal/chart/testdata/axis-min-max.txt +12 -0
A internal/chart/testdata/axis-min.txt +12 -0
A internal/chart/testdata/bar-log.txt +10 -0
A internal/chart/testdata/bar-percent.txt +12 -0
M internal/chart/testdata/bar-single.txt +5 -5
A internal/chart/testdata/bar-stacked.txt +12 -0
M internal/chart/testdata/bar.txt +7 -7
M internal/chart/testdata/column-narrow.txt +2 -2
M internal/chart/testdata/column-negative.txt +3 -3
A internal/chart/testdata/column-percent.txt +12 -0
M internal/chart/testdata/column-single.txt +3 -3
A internal/chart/testdata/column-stacked-negative.txt +12 -0
A internal/chart/testdata/column-stacked.txt +12 -0
M internal/chart/testdata/column.txt +3 -3
A internal/chart/testdata/images.txt +17 -0
A internal/chart/testdata/legend-none.txt +12 -0
A internal/chart/testdata/legend-right.txt +12 -0
M internal/chart/testdata/line-single.txt +4 -4
M internal/chart/testdata/line.txt +3 -3
A internal/chart/testdata/no-gridlines.txt +10 -0
A internal/chart/testdata/pie-no-legend.txt +10 -0
A internal/chart/testdata/scatter-single.txt +10 -0
A internal/chart/testdata/scatter-trend.txt +12 -0
A internal/chart/testdata/scatter.txt +12 -0
M internal/chart/types.go +47 -19
@@ -20,33 +20,61 @@ type plan interface {
2020 image(cv *canvas)2121 }2222 23-// A layout lays out data as one type of chart.24-type layout func(d sheet.ChartData, w, h int, o Options) plan23+// A chartType lays out data as one type of chart. The legend of series24+// is Draw's: it gives the layout the room the legend leaves, and names25+// returns the series it lists, nil for a type that draws its own.26+type chartType struct {27+ layout func(d sheet.ChartData, w, h int, o Options) plan28+ names func(d sheet.ChartData) []string29+}2530 2631 // types are the chart types, each a layout whose plan draws it as text2732 // and as an image. The types themselves, their names and the order the2833 // chart editor offers them, are in the sheet package, since charts are2934 // saved with sheets; adding one is a constant there and a row here.30-var types = map[sheet.ChartType]layout{31- sheet.ChartColumn: func(d sheet.ChartData, w, h int, _ Options) plan { return newColumnPlan(d, w, h, false) },32- sheet.ChartBar: func(d sheet.ChartData, w, h int, _ Options) plan { return newBarPlan(d, w, h) },33- sheet.ChartLine: func(d sheet.ChartData, w, h int, _ Options) plan { return newColumnPlan(d, w, h, true) },34- sheet.ChartPie: func(d sheet.ChartData, w, h int, o Options) plan { return newPiePlan(d, w, h, o) },35+var types = map[sheet.ChartType]chartType{36+ sheet.ChartColumn: {columnLayout(columnBars), allSeries},37+ sheet.ChartBar: {func(d sheet.ChartData, w, h int, o Options) plan {38+ return newBarPlan(d, w, h, o.Chart)39+ }, allSeries},40+ sheet.ChartLine: {columnLayout(columnLine), allSeries},41+ sheet.ChartPie: {func(d sheet.ChartData, w, h int, o Options) plan {42+ return newPiePlan(d, w, h, o)43+ }, nil},44+ sheet.ChartArea: {columnLayout(columnArea), allSeries},45+ sheet.ChartScatter: {func(d sheet.ChartData, w, h int, o Options) plan {46+ return newScatterPlan(d, w, h, o.Chart)47+ }, func(d sheet.ChartData) []string {48+ _, ys := scatterSeries(d)49+ return seriesNames(ys)50+ }},51+}52+53+func columnLayout(kind columnKind) func(d sheet.ChartData, w, h int, o Options) plan {54+ return func(d sheet.ChartData, w, h int, o Options) plan { return newColumnPlan(d, w, h, kind, o.Chart) }3555 }3656 37-// planFor lays out d as a chart of type t; an unknown type is drawn as38-// columns.39-func planFor(t sheet.ChartType, d sheet.ChartData, w, h int, o Options) plan {40- lay, ok := types[t]57+func allSeries(d sheet.ChartData) []string { return seriesNames(d.Series) }58+59+// planFor lays out d as a chart of type t, with its legend; an unknown60+// type is drawn as columns.61+func planFor(t sheet.ChartType, d sheet.ChartData, w, h int, o Options) (plan, legendBox) {62+ ct, ok := types[t]4163 if !ok {42- lay = types[sheet.ChartColumn]64+ ct = types[sheet.ChartColumn]65+ }66+ lg := legendBox{pos: sheet.LegendNone}67+ if ct.names != nil {68+ lg, w, h = placeLegend(ct.names(d), o.Chart.Legend, w, h)4369 }44- return lay(d, w, h, o)70+ return ct.layout(d, w, h, o), lg4571 }4672 47-func (p *columnPlan) note() string { return p.msg }48-func (p *columnPlan) plotArea() image.Rectangle { return p.plot }49-func (p *barPlan) note() string { return p.msg }50-func (p *barPlan) plotArea() image.Rectangle { return p.plot }51-func (p *piePlan) note() string { return p.msg }52-func (p *piePlan) plotArea() image.Rectangle { return p.disc }73+func (p *columnPlan) note() string { return p.msg }74+func (p *columnPlan) plotArea() image.Rectangle { return p.plot }75+func (p *barPlan) note() string { return p.msg }76+func (p *barPlan) plotArea() image.Rectangle { return p.plot }77+func (p *piePlan) note() string { return p.msg }78+func (p *piePlan) plotArea() image.Rectangle { return p.disc }79+func (p *scatterPlan) note() string { return p.msg }80+func (p *scatterPlan) plotArea() image.Rectangle { return p.plot }
A internal/chart/types_test.go +155 -0
@@ -0,0 +1,155 @@
1+package chart2+3+import (4+ "crypto/sha256"5+ "fmt"6+ "os"7+ "path/filepath"8+ "strings"9+ "testing"10+11+ "github.com/FelineStateMachine/012/internal/sheet"12+)13+14+// imageCases are the images TestImageHashes pins: every type, and the15+// options that change how the plot is drawn.16+var imageCases = []struct {17+ name string18+ t sheet.ChartType19+ d sheet.ChartData20+ o sheet.ChartOptions21+}{22+ {"column", sheet.ChartColumn, budget, sheet.ChartOptions{}},23+ {"column-negative", sheet.ChartColumn, mixed, sheet.ChartOptions{}},24+ {"bar", sheet.ChartBar, budget, sheet.ChartOptions{}},25+ {"line", sheet.ChartLine, budget, sheet.ChartOptions{}},26+ {"pie", sheet.ChartPie, single, sheet.ChartOptions{}},27+ {"area", sheet.ChartArea, budget, sheet.ChartOptions{}},28+ {"area-stacked", sheet.ChartArea, budget, sheet.ChartOptions{Stack: sheet.StackNormal}},29+ {"column-stacked", sheet.ChartColumn, signs, sheet.ChartOptions{Stack: sheet.StackNormal}},30+ {"column-percent", sheet.ChartColumn, budget, sheet.ChartOptions{Stack: sheet.StackPercent}},31+ {"bar-stacked", sheet.ChartBar, signs, sheet.ChartOptions{Stack: sheet.StackNormal}},32+ {"bar-percent", sheet.ChartBar, budget, sheet.ChartOptions{Stack: sheet.StackPercent}},33+ {"scatter", sheet.ChartScatter, xy, sheet.ChartOptions{}},34+ {"scatter-trend", sheet.ChartScatter, xy, sheet.ChartOptions{Trend: true}},35+ {"no-gridlines", sheet.ChartColumn, budget, sheet.ChartOptions{NoGrid: true}},36+ {"axis-min-max", sheet.ChartLine, budget, sheet.ChartOptions{Min: 500, HasMin: true, Max: 1600, HasMax: true}},37+ {"axis-log", sheet.ChartColumn, growth, sheet.ChartOptions{Log: true}},38+ {"legend-right", sheet.ChartColumn, budget, sheet.ChartOptions{Legend: sheet.LegendRight}},39+}40+41+// TestImageHashes pins the image of each case by a hash of its pixels,42+// kept in testdata/images.txt (go test ./internal/chart -update rewrites43+// it). A changed hash means the image changed: look at it before44+// updating.45+func TestImageHashes(t *testing.T) {46+ var got strings.Builder47+ for _, tc := range imageCases {48+ img := Image(tc.t, tc.d, 40, 12, Options{CellW: 8, CellH: 16, Chart: tc.o}, testPalette)49+ if img == nil {50+ t.Errorf("%s: no image", tc.name)51+ continue52+ }53+ sum := sha256.Sum256(img.Pix)54+ fmt.Fprintf(&got, "%s %dx%d %x\n", tc.name, img.Bounds().Dx(), img.Bounds().Dy(), sum[:8])55+ }56+ path := filepath.Join("testdata", "images.txt")57+ if *update {58+ os.WriteFile(path, []byte(got.String()), 0o644)59+ return60+ }61+ want, err := os.ReadFile(path)62+ if err != nil {63+ t.Fatalf("missing hashes (go test ./internal/chart -update): %v", err)64+ }65+ if got.String() != string(want) {66+ t.Errorf("images changed:\n%s\nwant:\n%s", got.String(), want)67+ }68+}69+70+// TestNewTypesPixels checks a few pixels of the stacked, scatter and71+// area images directly.72+func TestNewTypesPixels(t *testing.T) {73+ o := Options{CellW: 8, CellH: 16}74+ // A stacked column: Rent at the bottom, Food above it.75+ o.Chart = sheet.ChartOptions{Stack: sheet.StackNormal}76+ p := newColumnPlan(budget, 44, 11, columnBars, o.Chart)77+ img := Image(sheet.ChartColumn, budget, 44, 12, o, testPalette)78+ x0, x1 := p.bar(0, 0)79+ mid := ((x0+x1)/2 - p.plot.Min.X) * 880+ bottom := img.Bounds().Dy()81+ rentTop := int(float64(bottom) - p.sc.pos(1450)*16)82+ if c := img.RGBAAt(mid, bottom-2); c != testPalette.Series[0] {83+ t.Errorf("stack bottom %v, want Rent's color", c)84+ }85+ if c := img.RGBAAt(mid, rentTop-4); c != testPalette.Series[1] {86+ t.Errorf("stack top %v, want Food's color", c)87+ }88+ if x0b, _ := p.bar(0, 1); x0b != x0 {89+ t.Errorf("stacked series at %d and %d, want one column", x0, x0b)90+ }91+ // A scatter point is its series' color at its center.92+ o.Chart = sheet.ChartOptions{}93+ sp := newScatterPlan(xy, 40, 11, o.Chart)94+ simg := Image(sheet.ChartScatter, xy, 40, 12, o, testPalette)95+ cx := int(sp.xsc.at(1) * 8)96+ cy := int(float64(simg.Bounds().Dy()) - sp.ysc.at(52)*16)97+ if c := simg.RGBAAt(cx, cy); c != testPalette.Series[0] {98+ t.Errorf("scatter point %v at %d,%d", c, cx, cy)99+ }100+ // The area under a line is filled, above it is clear.101+ aimg := Image(sheet.ChartArea, single, 40, 10, o, testPalette)102+ b := aimg.Bounds()103+ if c := aimg.RGBAAt(2, b.Dy()-3); c.A == 0 {104+ t.Errorf("under the area %v, want filled", c)105+ }106+ if c := aimg.RGBAAt(b.Dx()-2, 2); c.A != 0 {107+ t.Errorf("above the area %v, want clear", c)108+ }109+}110+111+func TestLogAndFixedScales(t *testing.T) {112+ s := newAxis(3, 9000, 10, 2, 6, sheet.Format{}, sheet.ChartOptions{Log: true})113+ if s.tick(0) != 1 || s.tick(s.n) != 10000 || s.n != 4 {114+ t.Errorf("log scale %v..%v in %d", s.tick(0), s.tick(s.n), s.n)115+ }116+ if got := s.label(1000); got != "1,000" {117+ t.Errorf("log label %q", got)118+ }119+ if p := s.pos(100); p != float64(2*s.k) {120+ t.Errorf("100 at %v cells", p)121+ }122+ f := newAxis(0, 1500, 9, 2, 6, sheet.Format{}, sheet.ChartOptions{Min: 200, HasMin: true})123+ if f.lo != 200 || f.hi < 1500 {124+ t.Errorf("fixed minimum: %v..%v", f.lo, f.hi)125+ }126+ f = newAxis(0, 1500, 8, 2, 6, sheet.Format{}, sheet.ChartOptions{Min: 0, HasMin: true, Max: 2000, HasMax: true})127+ if f.lo != 0 || f.hi != 2000 || niceStep(f.step) != f.step {128+ t.Errorf("fixed ends: %v..%v step %v", f.lo, f.hi, f.step)129+ }130+ // A value past a fixed end stops at the end of the axis.131+ if at := f.at(5000); at != float64(f.cells()) {132+ t.Errorf("clipped at %v", at)133+ }134+}135+136+// TestLegendPlacement checks the legend takes its room from the chart.137+func TestLegendPlacement(t *testing.T) {138+ names := []string{"Rent", "Food"}139+ if lg, w, h := placeLegend(names, sheet.LegendRight, 44, 12); lg.pos != sheet.LegendRight || w != 44-6-1 || h != 12 || lg.x != 38 {140+ t.Errorf("right: %+v in %dx%d", lg, w, h)141+ }142+ if lg, w, h := placeLegend(names, sheet.LegendBottom, 44, 12); lg.row != 11 || w != 44 || h != 11 {143+ t.Errorf("bottom: %+v in %dx%d", lg, w, h)144+ }145+ if lg, w, h := placeLegend(names, sheet.LegendNone, 44, 12); lg.pos != sheet.LegendNone || w != 44 || h != 12 {146+ t.Errorf("none: %+v in %dx%d", lg, w, h)147+ }148+ // One series, or too little room: no legend.149+ if lg, _, _ := placeLegend(names[:1], sheet.LegendBottom, 44, 12); lg.pos != sheet.LegendNone {150+ t.Errorf("one series: %+v", lg)151+ }152+ if lg, _, _ := placeLegend(names, sheet.LegendRight, 20, 12); lg.pos != sheet.LegendNone {153+ t.Errorf("narrow: %+v", lg)154+ }155+}
M internal/sheet/chart.go +11 -5
M internal/sheet/chart_test.go +9 -0
A internal/sheet/chartfile.go +81 -0
@@ -0,0 +1,81 @@
1+package sheet2+3+import (4+ "encoding/json"5+ "fmt"6+)7+8+// fileChart is a chart, one per line after the cells:9+//10+// {"type": "column", "data": "A1:C7", "at": "E2", "width": 44, "height": 14, "header": true, "labels": true}11+//12+// Options at their defaults are left out, so they need no version bump:13+// older versions of 012 draw such a chart with the defaults, and older14+// versions still ignore the field and drop the charts. A type they don't15+// know makes them refuse the file.16+type fileChart struct {17+ Type string `json:"type"`18+ Data string `json:"data"`19+ At string `json:"at"`20+ Width int `json:"width"`21+ Height int `json:"height"`22+ ByRow bool `json:"byRow,omitempty"`23+ Header bool `json:"header,omitempty"`24+ Labels bool `json:"labels,omitempty"`25+ Title string `json:"title,omitempty"`26+ // Options: "stacked" or "percent"; a trend line; the ends of the value27+ // axis; a log scale; gridlines, only when off; "right" or "none".28+ Stack string `json:"stack,omitempty"`29+ Trend bool `json:"trend,omitempty"`30+ Min *float64 `json:"min,omitempty"`31+ Max *float64 `json:"max,omitempty"`32+ Log bool `json:"log,omitempty"`33+ Gridlines *bool `json:"gridlines,omitempty"`34+ Legend string `json:"legend,omitempty"`35+}36+37+func encodeChart(c Chart) ([]byte, error) {38+ fc := fileChart{39+ Type: c.Type.String(), Data: c.Data.String(), At: c.At.String(), Width: c.W, Height: c.H,40+ ByRow: c.ByRow, Header: c.Header, Labels: c.Labels, Title: c.Title,41+ Stack: c.Stack.String(), Trend: c.Trend, Log: c.Log,42+ Min: axisEnd(c.Min, c.HasMin), Max: axisEnd(c.Max, c.HasMax),43+ }44+ if c.NoGrid {45+ fc.Gridlines = new(bool)46+ }47+ if c.Legend != LegendBottom {48+ fc.Legend = c.Legend.String()49+ }50+ return json.Marshal(fc)51+}52+53+func decodeChart(fc fileChart) (Chart, error) {54+ t, ok := ParseChartType(fc.Type)55+ if !ok {56+ return Chart{}, fmt.Errorf("unknown chart type %q", fc.Type)57+ }58+ data, ok := ParseRange(fc.Data)59+ if !ok {60+ return Chart{}, fmt.Errorf("invalid chart range %q", fc.Data)61+ }62+ at, ok := ParseAddr(fc.At)63+ if !ok {64+ return Chart{}, fmt.Errorf("invalid chart position %q", fc.At)65+ }66+ c := Chart{Type: t, Data: data, At: at, W: fc.Width, H: fc.Height,67+ ByRow: fc.ByRow, Header: fc.Header, Labels: fc.Labels, Title: fc.Title}68+ // Options a later build writes and this one doesn't know are left at69+ // their defaults, as an older build would.70+ c.Stack, _ = ParseChartStack(fc.Stack)71+ c.Legend, _ = ParseChartLegend(fc.Legend)72+ c.Trend, c.Log = fc.Trend, fc.Log73+ c.NoGrid = fc.Gridlines != nil && !*fc.Gridlines74+ if fc.Min != nil {75+ c.Min, c.HasMin = *fc.Min, true76+ }77+ if fc.Max != nil {78+ c.Max, c.HasMax = *fc.Max, true79+ }80+ return c.clamped(), nil81+}
A internal/sheet/chartopts.go +106 -0
@@ -0,0 +1,106 @@
1+package sheet2+3+import (4+ "math"5+ "slices"6+ "strings"7+)8+9+// ChartOptions are a chart's settings beyond its type and data, as in10+// the Customize tab of Sheets' chart editor. The zero value is Sheets'11+// default: not stacked, no trend line, an automatic linear value axis12+// with gridlines, and the legend at the bottom. Every field is a plain13+// value, so charts stay comparable with ==.14+type ChartOptions struct {15+ // Stack stacks the series of column, bar and area charts.16+ Stack ChartStack17+ // Trend draws a linear trend line through each series of a scatter18+ // chart.19+ Trend bool20+ // Min and Max fix the ends of the value axis when HasMin and HasMax21+ // are set; otherwise the axis fits the data.22+ Min, Max float6423+ HasMin, HasMax bool24+ // Log puts the value axis on a logarithmic scale, leaving out values25+ // that aren't positive.26+ Log bool27+ // NoGrid hides the gridlines.28+ NoGrid bool29+ // Legend is where the legend goes.30+ Legend ChartLegend31+}32+33+// ChartStack is how the series of a chart pile up.34+type ChartStack int35+36+const (37+ StackNone ChartStack = iota // side by side38+ StackNormal // on top of each other39+ StackPercent // on top of each other, as shares of 100%40+)41+42+var chartStackNames = [...]string{StackNone: "", StackNormal: "stacked", StackPercent: "percent"}43+44+// String is the stacking as files store it, "" for none.45+func (s ChartStack) String() string {46+ if s < 0 || int(s) >= len(chartStackNames) {47+ return ""48+ }49+ return chartStackNames[s]50+}51+52+// Title is the stacking as the chart editor shows it.53+func (s ChartStack) Title() string {54+ switch s {55+ case StackNormal:56+ return "Stacked"57+ case StackPercent:58+ return "100% stacked"59+ }60+ return "Not stacked"61+}62+63+// ParseChartStack parses a stacking as String writes it.64+func ParseChartStack(s string) (ChartStack, bool) {65+ i := slices.Index(chartStackNames[:], strings.ToLower(s))66+ return ChartStack(max(i, 0)), i >= 067+}68+69+// Stackable reports whether charts of type t can stack their series.70+func (t ChartType) Stackable() bool {71+ return t == ChartColumn || t == ChartBar || t == ChartArea72+}73+74+// ChartLegend is where a chart's legend goes.75+type ChartLegend int76+77+const (78+ LegendBottom ChartLegend = iota // under the plot, centered79+ LegendRight // a column right of the plot80+ LegendNone // no legend81+)82+83+var chartLegendNames = [...]string{LegendBottom: "bottom", LegendRight: "right", LegendNone: "none"}84+85+// String is the position as files store it.86+func (l ChartLegend) String() string {87+ if l < 0 || int(l) >= len(chartLegendNames) {88+ return chartLegendNames[0]89+ }90+ return chartLegendNames[l]91+}92+93+// ParseChartLegend parses a position as String writes it.94+func ParseChartLegend(s string) (ChartLegend, bool) {95+ i := slices.Index(chartLegendNames[:], strings.ToLower(s))96+ return ChartLegend(max(i, 0)), i >= 097+}98+99+// axisEnd is an optional end of the value axis as files store it: nil100+// for automatic.101+func axisEnd(v float64, ok bool) *float64 {102+ if !ok || math.IsNaN(v) || math.IsInf(v, 0) {103+ return nil104+ }105+ return &v106+}
M internal/sheet/file.go +0 -42
@@ -69,48 +69,6 @@ type fileSheet struct {
6969 Pivot *filePivot `json:"pivot,omitempty"` // version 57070 }7171 72-// fileChart is a chart, one per line after the cells:73-//74-// {"type": "column", "data": "A1:C7", "at": "E2", "width": 44, "height": 14, "header": true, "labels": true}75-//76-// Older versions of 012 ignore the field and drop the charts.77-type fileChart struct {78- Type string `json:"type"`79- Data string `json:"data"`80- At string `json:"at"`81- Width int `json:"width"`82- Height int `json:"height"`83- ByRow bool `json:"byRow,omitempty"`84- Header bool `json:"header,omitempty"`85- Labels bool `json:"labels,omitempty"`86- Title string `json:"title,omitempty"`87-}88-89-func encodeChart(c Chart) ([]byte, error) {90- return json.Marshal(fileChart{91- Type: c.Type.String(), Data: c.Data.String(), At: c.At.String(), Width: c.W, Height: c.H,92- ByRow: c.ByRow, Header: c.Header, Labels: c.Labels, Title: c.Title,93- })94-}95-96-func decodeChart(fc fileChart) (Chart, error) {97- t, ok := ParseChartType(fc.Type)98- if !ok {99- return Chart{}, fmt.Errorf("unknown chart type %q", fc.Type)100- }101- data, ok := ParseRange(fc.Data)102- if !ok {103- return Chart{}, fmt.Errorf("invalid chart range %q", fc.Data)104- }105- at, ok := ParseAddr(fc.At)106- if !ok {107- return Chart{}, fmt.Errorf("invalid chart position %q", fc.At)108- }109- c := Chart{Type: t, Data: data, At: at, W: fc.Width, H: fc.Height,110- ByRow: fc.ByRow, Header: fc.Header, Labels: fc.Labels, Title: fc.Title}111- return c.clamped(), nil112-}113-11472 // Write saves the workbook the sheet belongs to; see Workbook.Write.11573 func (s *Sheet) Write(w io.Writer) error { return s.wb.Write(w) }11674
A internal/ui/chartaxes.go +97 -0
@@ -0,0 +1,97 @@
1+package ui2+3+import (4+ "strconv"5+ "strings"6+7+ tea "charm.land/bubbletea/v2"8+9+ "github.com/FelineStateMachine/012/internal/sheet"10+)11+12+// The chart editor's axis bar: the ends of the value axis, typed on the13+// context line (blank for automatic), a log scale, gridlines and where14+// the legend goes. Enter and Esc go back to the main bar, keeping the15+// changes; Esc there undoes them all.16+17+// Axis bar keys, also the chips' mouse targets.18+const (19+ axisMin = "n"20+ axisMax = "x"21+ axisLog = "l"22+ axisGrid = "g"23+ axisLegend = "p"24+)25+26+func (e *chartEditor) axisParts(m *Model) []editorPart {27+ c := e.chart(m)28+ end := func(name string, v float64, set bool, key string) editorPart {29+ text := name + " auto"30+ if set {31+ text = name + " " + strconv.FormatFloat(v, 'g', 10, 64)32+ }33+ return e.chip(set, text, key)34+ }35+ return []editorPart{36+ end("Min", c.Min, c.HasMin, axisMin),37+ end("Max", c.Max, c.HasMax, axisMax),38+ e.chip(c.Log, "Log", axisLog),39+ e.chip(!c.NoGrid, "Gridlines", axisGrid),40+ e.chip(c.Legend != sheet.LegendNone, "Legend "+c.Legend.String(), axisLegend),41+ }42+}43+44+func (e *chartEditor) axisKey(m *Model, key string) {45+ switch key {46+ case "enter", "esc":47+ e.axes = false48+ case axisMin:49+ e.editAxisEnd(m, "Axis minimum (blank for auto):", func(c *sheet.Chart) (*float64, *bool) { return &c.Min, &c.HasMin })50+ case axisMax:51+ e.editAxisEnd(m, "Axis maximum (blank for auto):", func(c *sheet.Chart) (*float64, *bool) { return &c.Max, &c.HasMax })52+ case axisLog:53+ e.set(m, func(c *sheet.Chart) { c.Log = !c.Log })54+ case axisGrid:55+ e.set(m, func(c *sheet.Chart) { c.NoGrid = !c.NoGrid })56+ case axisLegend:57+ e.set(m, func(c *sheet.Chart) { c.Legend = (c.Legend + 1) % 3 })58+ }59+}60+61+// editAxisEnd asks for an end of the value axis, as a number the way62+// cells take one ("500", "$1,200", "10%"); blank makes it automatic.63+func (e *chartEditor) editAxisEnd(m *Model, label string, end func(*sheet.Chart) (*float64, *bool)) {64+ c := e.chart(m)65+ v, set := end(&c)66+ initial := ""67+ if *set {68+ initial = strconv.FormatFloat(*v, 'g', -1, 64)69+ }70+ m.closeOverlay()71+ e.askAxisEnd(m, label, label, initial, end)72+}73+74+// askAxisEnd asks with the prompt label, and asks again, saying so, until75+// the answer is a number or blank.76+func (e *chartEditor) askAxisEnd(m *Model, label, question, initial string, end func(*sheet.Chart) (*float64, *bool)) {77+ m.openText(label, initial, func(m *Model, text string) tea.Cmd {78+ text = strings.TrimSpace(text)79+ n, _, ok := sheet.ParseValue(text)80+ switch {81+ case text == "":82+ e.set(m, func(c *sheet.Chart) { _, set := end(c); *set = false })83+ case !ok:84+ e.askAxisEnd(m, "Not a number. "+question, question, text, end)85+ return nil86+ default:87+ e.set(m, func(c *sheet.Chart) { v, set := end(c); *v, *set = n, true })88+ }89+ e.reopen(m)90+ return nil91+ })92+ m.prompt.onCancel = e.reopen93+}94+95+func (e *chartEditor) axisStatus(m *Model) string {96+ return fitHints(m, []string{"N", "min", "X", "max", "L", "log", "G", "grid", "P", "legend", "Esc", "back"})97+}
A internal/ui/chartaxes_test.go +133 -0
@@ -0,0 +1,133 @@
1+package ui2+3+import (4+ "strings"5+ "testing"6+7+ "github.com/charmbracelet/x/ansi"8+9+ "github.com/FelineStateMachine/012/internal/sheet"10+)11+12+func TestChartEditorStackingAndTrend(t *testing.T) {13+ m := wideModel()14+ chartTable(t, m)15+ m.runCommand("insert.chart")16+ if _, keys := m.overlay.Status(); !strings.Contains(line(m, contextLine), "Not stacked") || !strings.Contains(keys, "stacking") {17+ t.Errorf("no stacking chip: %q / %q", line(m, contextLine), keys)18+ }19+ press(t, m, "k")20+ if c := m.sheet.Charts()[0]; c.Stack != sheet.StackNormal || !strings.Contains(line(m, contextLine), "Stacked") {21+ t.Errorf("stack %v: %q", c.Stack, line(m, contextLine))22+ }23+ press(t, m, "k")24+ if c := m.sheet.Charts()[0]; c.Stack != sheet.StackPercent || !strings.Contains(screen(m), "100% ┤") {25+ t.Errorf("100%% stacking %v:\n%s", c.Stack, screen(m))26+ }27+ // Scatter has a trend line instead; K does nothing there.28+ press(t, m, "6")29+ if !strings.Contains(line(m, contextLine), "Trend line") || strings.Contains(line(m, contextLine), "stacked") {30+ t.Errorf("scatter bar: %q", line(m, contextLine))31+ }32+ press(t, m, "e", "k")33+ if c := m.sheet.Charts()[0]; c.Type != sheet.ChartScatter || !c.Trend || c.Stack != sheet.StackPercent {34+ t.Errorf("after E and K: %+v", c)35+ }36+ // Esc undoes every change, removing the new chart.37+ press(t, m, "<esc>")38+ if n := len(m.sheet.Charts()); n != 0 {39+ t.Errorf("%d charts after Esc", n)40+ }41+}42+43+func TestChartEditorAxes(t *testing.T) {44+ m := wideModel()45+ chartTable(t, m)46+ m.runCommand("insert.chart")47+ press(t, m, "a")48+ ctx := line(m, contextLine)49+ for _, want := range []string{"Min auto", "Max auto", "Log", "Gridlines", "Legend bottom"} {50+ if !strings.Contains(ctx, want) {51+ t.Errorf("axis bar lacks %q: %q", want, ctx)52+ }53+ }54+ // The minimum is typed as a cell takes a number.55+ press(t, m, "n")56+ if m.mode != modePrompt {57+ t.Fatalf("minimum prompt not open, mode %v", m.mode)58+ }59+ m.line.Buf, m.line.Pos = []rune("$500"), 460+ press(t, m, "<enter>")61+ if c := m.sheet.Charts()[0]; !c.HasMin || c.Min != 500 || !strings.Contains(line(m, contextLine), "Min 500") {62+ t.Errorf("min %v %v: %q", c.HasMin, c.Min, line(m, contextLine))63+ }64+ // Not a number: asked again, and Esc leaves the axis as it was.65+ press(t, m, "x")66+ m.line.Buf, m.line.Pos = []rune("lots"), 467+ press(t, m, "<enter>")68+ if m.mode != modePrompt || !strings.Contains(line(m, contextLine), "Not a number. Axis maximum") {69+ t.Errorf("not asked again: %q", line(m, contextLine))70+ }71+ press(t, m, "<esc>")72+ if c := m.sheet.Charts()[0]; c.HasMax || !strings.Contains(line(m, contextLine), "Max auto") {73+ t.Errorf("max %v %v: %q", c.HasMax, c.Max, line(m, contextLine))74+ }75+ press(t, m, "l", "g", "p")76+ c := m.sheet.Charts()[0]77+ if !c.Log || !c.NoGrid || c.Legend != sheet.LegendRight {78+ t.Errorf("toggles: %+v", c.ChartOptions)79+ }80+ if !strings.Contains(screen(m), "■ Rent") {81+ t.Errorf("right legend missing:\n%s", screen(m))82+ }83+ press(t, m, "p")84+ if c := m.sheet.Charts()[0]; c.Legend != sheet.LegendNone || strings.Contains(screen(m), "■ Rent") {85+ t.Errorf("legend none: %v", c.Legend)86+ }87+ // A blank minimum is automatic again.88+ press(t, m, "n")89+ m.line.Buf, m.line.Pos = nil, 090+ press(t, m, "<enter>")91+ if c := m.sheet.Charts()[0]; c.HasMin {92+ t.Errorf("min still set: %v", c.Min)93+ }94+ // Esc goes back to the main bar, keeping the changes.95+ press(t, m, "<esc>")96+ if _, ok := m.overlay.(*chartEditor); !ok || !strings.Contains(line(m, contextLine), "Column") {97+ t.Fatalf("not back on the main bar: %T %q", m.overlay, line(m, contextLine))98+ }99+ press(t, m, "<enter>")100+ if c := m.sheet.Charts()[0]; !c.Log || c.Legend != sheet.LegendNone {101+ t.Errorf("changes lost: %+v", c.ChartOptions)102+ }103+ // Undo takes back the last change.104+ press(t, m, "<esc>", "<ctrl+z>")105+ if c := m.sheet.Charts()[0]; !c.HasMin {106+ t.Errorf("undo: %+v", c.ChartOptions)107+ }108+}109+110+func TestChartEditorFitsNarrow(t *testing.T) {111+ m := wideModel()112+ chartTable(t, m)113+ m.runCommand("insert.chart")114+ press(t, m, "a")115+ m.Update(teaSize(60, 16))116+ if ctx := line(m, contextLine); !strings.Contains(ctx, "Legend bottom") {117+ t.Errorf("axis bar clipped at 60 columns: %q", ctx)118+ }119+ if _, keys := m.overlay.Status(); !strings.Contains(keys, "grid") || !strings.Contains(keys, "legend") || ansi.StringWidth(keys) > 60 {120+ t.Errorf("axis keys at 60 columns: %q", ansi.Strip(keys))121+ }122+ press(t, m, "<esc>")123+ for _, want := range []string{"◂ Column ▸", "Series in columns"} {124+ if ctx := line(m, contextLine); !strings.Contains(ctx, want) {125+ t.Errorf("main bar lacks %q at 60 columns: %q", want, ctx)126+ }127+ }128+ // Clicking the one type chip moves on to the next type.129+ leftClick(m, 3, contextLine)130+ if c := m.sheet.Charts()[0]; c.Type != sheet.ChartBar || !strings.Contains(line(m, contextLine), "◂ Bar ▸") {131+ t.Errorf("after a click: %v %q", c.Type, line(m, contextLine))132+ }133+}
M internal/ui/charteditor.go +107 -27
@@ -13,13 +13,16 @@ import (
1313 1414 // chartEditor is the chart editor: a bar on the context line, in the1515 // spirit of the find bar, with the type as a row of chips and the options16-// as toggles. Changes show at once; Enter keeps them and Esc undoes them,17-// removing a chart that was just inserted.16+// as toggles. A second bar (A) holds the value axis, gridlines and17+// legend, as Sheets' Customize tab does; see chartaxes.go. Changes show18+// at once; Enter keeps them and Esc undoes them, removing a chart that19+// was just inserted.1820 type chartEditor struct {1921 m *Model // the model it acts on2022 i int2123 start int // the sheet's state before editing, to undo back to2224 isNew bool25+ axes bool // showing the axis options2326 }2427 2528 // openChartEditor edits chart i. start is the sheet's state to return to
@@ -52,7 +55,12 @@ func (e *chartEditor) Key(k tea.KeyPressMsg) tea.Cmd {
5255 m.closeOverlay()5356 return nil5457 }55- switch key := strings.ToLower(k.String()); key {58+ key := strings.ToLower(k.String())59+ if e.axes {60+ e.axisKey(m, key)61+ return nil62+ }63+ switch key {5664 case "enter":5765 m.selectChart(e.i)5866 if e.isNew {
@@ -74,20 +82,41 @@ func (e *chartEditor) Key(k tea.KeyPressMsg) tea.Cmd {
7482 n := len(sheet.ChartTypes)7583 c.Type = sheet.ChartTypes[((int(c.Type)+d)%n+n)%n]7684 })77- case "1", "2", "3", "4":78- e.set(m, func(c *sheet.Chart) { c.Type = sheet.ChartTypes[key[0]-'1'] })85+ case editorAxes:86+ e.axes = true87+ default:88+ e.optionKey(m, key)89+ }90+ return nil91+}92+93+// optionKey handles the keys of the main bar's options: a type's number,94+// the toggles, the range and the title.95+func (e *chartEditor) optionKey(m *Model, key string) {96+ if n, err := strconv.Atoi(key); err == nil && n >= 1 && n <= len(sheet.ChartTypes) {97+ e.set(m, func(c *sheet.Chart) { c.Type = sheet.ChartTypes[n-1] })98+ return99+ }100+ switch key {79101 case editorSwitch:80102 e.set(m, func(c *sheet.Chart) { c.ByRow = !c.ByRow })81103 case editorHeader:82104 e.set(m, func(c *sheet.Chart) { c.Header = !c.Header })83105 case editorLabels:84106 e.set(m, func(c *sheet.Chart) { c.Labels = !c.Labels })107+ case editorStack:108+ if e.chart(m).Type.Stackable() {109+ e.set(m, func(c *sheet.Chart) { c.Stack = (c.Stack + 1) % 3 })110+ }111+ case editorTrend:112+ if e.chart(m).Type == sheet.ChartScatter {113+ e.set(m, func(c *sheet.Chart) { c.Trend = !c.Trend })114+ }85115 case editorRange:86116 e.editRange(m)87117 case editorTitle:88118 e.editTitle(m)89119 }90- return nil91120 }92121 93122 // reopen returns to the editor after a prompt.
@@ -130,18 +159,40 @@ type editorPart struct {
130159 key string // the key it stands for131160 }132161 133-func (e *chartEditor) parts(m *Model) []editorPart {134- c := e.chart(m)135- chip := func(on bool, name, key string) editorPart {136- style := m.th.Muted137- if on {138- style = m.th.MenuSelected139- }140- return editorPart{text: style.Render(" " + name + " "), key: key}162+// chip is a part drawn as a toggle, highlighted when on.163+func (e *chartEditor) chip(on bool, name, key string) editorPart {164+ style := e.m.th.Muted165+ if on {166+ style = e.m.th.MenuSelected141167 }168+ return editorPart{text: style.Render(" " + name + " "), key: key}169+}170+171+// parts returns the bar's parts, and how many of them lead it: the172+// types, which are never dropped and have a wider gap after them. Compact,173+// the types are one chip of the current type, which a click moves on.174+func (e *chartEditor) parts(m *Model, compact bool) ([]editorPart, int) {175+ if e.axes {176+ return e.axisParts(m), 0177+ }178+ c := e.chart(m)142179 var parts []editorPart143180 for i, t := range sheet.ChartTypes {144- parts = append(parts, chip(t == c.Type, t.Title(), strconv.Itoa(i+1)))181+ switch {182+ case !compact:183+ parts = append(parts, e.chip(t == c.Type, t.Title(), strconv.Itoa(i+1)))184+ case t == c.Type:185+ next := (i+1)%len(sheet.ChartTypes) + 1186+ parts = append(parts, e.chip(true, "◂ "+t.Title()+" ▸", strconv.Itoa(next)))187+ }188+ }189+ lead := len(parts)190+ parts = append(parts, editorPart{text: m.th.Key.Render(c.Data.String()), key: editorRange})191+ switch {192+ case c.Type.Stackable():193+ parts = append(parts, e.chip(c.Stack != sheet.StackNone, c.Stack.Title(), editorStack))194+ case c.Type == sheet.ChartScatter:195+ parts = append(parts, e.chip(c.Trend, "Trend line", editorTrend))145196 }146197 series := "Series in columns"147198 if c.ByRow {
@@ -152,27 +203,39 @@ func (e *chartEditor) parts(m *Model) []editorPart {
152203 header, labels = "Header column", "Labels in row"153204 }154205 return append(parts,155- editorPart{text: m.th.Key.Render(c.Data.String()), key: editorRange},156- chip(true, series, editorSwitch),157- chip(c.Header, header, editorHeader),158- chip(c.Labels, labels, editorLabels))206+ e.chip(true, series, editorSwitch),207+ e.chip(c.Header, header, editorHeader),208+ e.chip(c.Labels, labels, editorLabels)), lead159209 }160210 161-// spans lays the parts out on the context line, dropping options from the162-// right on narrow screens, and returns each part's x.211+// spans lays the parts out on the context line and returns each part's212+// x. On narrow screens the types become one chip when that leaves room213+// for more options, and options that still don't fit are dropped from the214+// right.163215 func (e *chartEditor) spans(m *Model) ([]editorPart, []int) {164- parts := e.parts(m)216+ parts, xs := e.fit(m, false)217+ if all, _ := e.parts(m, false); len(parts) < len(all) && !e.axes {218+ if cp, cxs := e.fit(m, true); len(cp)+len(sheet.ChartTypes)-1 > len(parts) {219+ return cp, cxs220+ }221+ }222+ return parts, xs223+}224+225+// fit lays out the parts, dropping options from the right until they fit.226+func (e *chartEditor) fit(m *Model, compact bool) ([]editorPart, []int) {227+ parts, lead := e.parts(m, compact)165228 for {166229 xs := make([]int, len(parts))167230 x := 0168231 for i, p := range parts {169- if i == len(sheet.ChartTypes) {232+ if i == lead && lead > 0 {170233 x += 2 // a wider gap between the types and the options171234 }172235 xs[i] = x173236 x += ansi.StringWidth(p.text) + 1174237 }175- if x-1 <= m.width || len(parts) <= len(sheet.ChartTypes) {238+ if x-1 <= m.width || len(parts) <= max(lead, 1) {176239 return parts, xs177240 }178241 parts = parts[:len(parts)-1]
@@ -214,13 +277,27 @@ func (e *chartEditor) Mouse(ev overlay.MouseEvent) tea.Cmd {
214277 215278 func (e *chartEditor) Status() (string, string) {216279 m := e.m217- pairs := []string{"←/→", "type", "S", "switch rows/columns", "H", "header", "L", "labels", "R", "range", "T", "title", "Enter", "done", "Esc", "cancel"}280+ if e.axes {281+ return "", e.axisStatus(m)282+ }283+ pairs := []string{"←/→", "type", "A", "axes and legend", "S", "switch rows/columns", "H", "header", "L", "labels", "R", "range", "T", "title", "Enter", "done", "Esc", "cancel"}284+ if c := e.chart(m); c.Type.Stackable() {285+ pairs = append(pairs[:4], append([]string{"K", "stacking"}, pairs[4:]...)...)286+ } else if c.Type == sheet.ChartScatter {287+ pairs = append(pairs[:4], append([]string{"E", "trend line"}, pairs[4:]...)...)288+ }289+ return "", fitHints(m, pairs)290+}291+292+// fitHints renders key hints, leaving out the pairs before the last two293+// (Enter and Esc) from the right until they fit.294+func fitHints(m *Model, pairs []string) string {218295 for {219296 keys := m.th.KeyHints(pairs...)220297 if ansi.StringWidth(keys) <= m.width || len(pairs) <= 4 {221- return "", keys298+ return keys222299 }223- pairs = append(pairs[:len(pairs)-6], pairs[len(pairs)-4:]...) // keep Enter and Esc300+ pairs = append(pairs[:len(pairs)-6], pairs[len(pairs)-4:]...)224301 }225302 }226303
@@ -231,4 +308,7 @@ const (
231308 editorLabels = "l"232309 editorRange = "r"233310 editorTitle = "t"311+ editorStack = "k"312+ editorTrend = "e"313+ editorAxes = "a"234314 )
M internal/ui/charts.go +2 -1
M internal/ui/graphics.go +1 -0
M internal/ui/prompt.go +4 -5