# `Expresso.Element.Chart.Csv`
[🔗](https://github.com/rellen/expresso/blob/main/lib/expresso/element/chart/csv.ex#L1)

The reader of the CSV file of a chart

A delimiter, a comma by default, divides the cells of a row. A cell in
double quotes can hold the delimiter, a line break and a quote, which two
quotes give. The first row holds the headings, and each other row holds a
category and one number for each series. `table/3` reads a file without
frames. `frames/4` reads a file with a column that names the frame of each
row. Each function returns the text of an error for a file that it cannot
read, and `Expresso.Element.Chart` gives that error to the author.

The `cell` option gives the function that reads a cell of a value. The
default is `number/1`, and a waterfall chart also reads the mark of a
total.

For a treemap or a sunburst, the `levels` option can name the columns that
hold the levels of a category, from the top level down. The category of a
row is then the list of its levels, and an empty cell at the end of the
list ends the category at a higher level.

# `opts`

```elixir
@type opts() :: [
  levels: [String.t()] | nil,
  delimiter: String.t(),
  cell: (String.t() -&gt; term() | nil),
  needs: String.t()
]
```

The options of a reader: `levels`, the headings of the columns that hold
the levels of a category, `delimiter`, the character between two cells,
`cell`, the function that reads a cell of a value and returns `nil` for a
cell that is not a value, and `needs`, the words after the number of values
in the error of a row with a bad cell.

# `frames`

```elixir
@spec frames(String.t(), String.t(), String.t(), opts()) ::
  {:ok, [String.t() | [String.t()]], [Expresso.Element.Frame.t()]}
  | {:error, String.t()}
```

Read a CSV file with a column that names the frame of each row

The column with the heading `column` names the frame. Of the other columns,
the first holds the category, and each other column holds a series. The
frames have the order of their first row, and each frame holds each
category of the first frame one time, in any order. The function puts the
categories of each frame into the order of the first frame.

    iex> text = "year,region,Orders\n2024,North,1\n2024,South,2\n2025,South,4\n2025,North,3\n"
    iex> {:ok, categories, [_first, second]} = Expresso.Element.Chart.Csv.frames(text, "year", "a.csv")
    iex> {categories, second.label, hd(second.elements).values}
    {["North", "South"], "2025", [3, 4]}

# `heading`

```elixir
@spec heading(String.t(), String.t(), opts(), String.t() | nil) :: String.t() | nil
```

Return the heading of the column of the categories, or `nil`

The heading is the first cell of the first row. In a file with a column of
frames, it is the first cell of the other columns. A file with levels joins
the headings of its levels. An empty heading, or a file that the other
functions refuse, returns `nil`.

    iex> Expresso.Element.Chart.Csv.heading("Year,Orders\n2024,1\n", "a.csv")
    "Year"
    iex> Expresso.Element.Chart.Csv.heading("year,region,Orders\n2024,North,1\n", "a.csv", [], "year")
    "region"
    iex> Expresso.Element.Chart.Csv.heading(",Orders\n2024,1\n", "a.csv")
    nil

# `levels`

```elixir
@spec levels([{pos_integer(), [String.t()]}], [String.t()] | nil, String.t()) ::
  {:ok, [{pos_integer(), [term()]}]} | {:error, String.t()}
```

Join the columns of the levels of each row into one category

The category is the first cell of the row, and the other cells keep their
order. Without levels, the function returns the rows as they are.

    iex> rows = [{1, ["Division", "Team", "Hours"]}, {2, ["Production", "Press", "62"]}, {3, ["Office", "", "20"]}]
    iex> Expresso.Element.Chart.Csv.levels(rows, ["Division", "Team"], "a.csv")
    {:ok, [{1, ["Division/Team", "Hours"]}, {2, [["Production", "Press"], "62"]}, {3, [["Office"], "20"]}]}

# `number`

```elixir
@spec number(String.t()) :: number() | nil
```

Read the number of a cell, or return `nil` for a cell that is not a number

A number can have a comma between each group of three digits, as a
spreadsheet writes it.

    iex> Expresso.Element.Chart.Csv.number(" 1,250 ")
    1250
    iex> Expresso.Element.Chart.Csv.number("2.5")
    2.5
    iex> Expresso.Element.Chart.Csv.number("many")
    nil

# `rows`

```elixir
@spec rows(String.t(), String.t(), opts()) ::
  {:ok, [{pos_integer(), [String.t()]}]} | {:error, String.t()}
```

Divide the text of a chart file into rows of cells

Each row has the number of its first line. The function trims the spaces
around a cell without quotes, and it keeps the text in quotes as it is. It
skips a line with only white space. It removes a byte order mark at the
start of the text, as the format "CSV UTF-8" of a spreadsheet writes it, so
the first cell keeps its name.

    iex> Expresso.Element.Chart.Csv.rows("\uFEFFyear,Orders\n2024,3\n", "a.csv")
    {:ok, [{1, ["year", "Orders"]}, {2, ["2024", "3"]}]}

    iex> Expresso.Element.Chart.Csv.rows("a; b\n\n\"x\"\"y\";\"1\n2\"\n", "a.csv", delimiter: ";")
    {:ok, [{1, ["a", "b"]}, {3, [~s(x"y), "1\n2"]}]}

# `table`

```elixir
@spec table(String.t(), String.t(), opts()) ::
  {:ok, [String.t() | [String.t()]], [Expresso.Element.Series.t()]}
  | {:error, String.t()}
```

Read a CSV file of categories and series

The first row holds a heading for the categories, then the name of each
series. Each other row holds a category, then one number for each series.

    iex> Expresso.Element.Chart.Csv.table("year,Orders\n2024,180\n2025,240\n", "a.csv")
    {:ok, ["2024", "2025"], [%Expresso.Element.Series{name: "Orders", values: [180, 240]}]}

A cell in quotes can hold the delimiter, and a number in quotes can have a
comma between each group of three digits:

    iex> text = ~s(Plant,Orders\n"Leeds, Line 4","1,250"\n)
    iex> Expresso.Element.Chart.Csv.table(text, "a.csv")
    {:ok, ["Leeds, Line 4"], [%Expresso.Element.Series{name: "Orders", values: [1250]}]}

---

*Consult [api-reference.md](api-reference.md) for complete listing*
