Expresso.Element.Chart.Csv (expresso v0.1.0)

Copy Markdown View Source

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.

Summary

Types

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.

Functions

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

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

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

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

Divide the text of a chart file into rows of cells

Read a CSV file of categories and series

Types

opts()

@type opts() :: [
  levels: [String.t()] | nil,
  delimiter: String.t(),
  cell: (String.t() -> 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.

Functions

frames(text, column, src, opts \\ [])

@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(text, src, opts \\ [], frames \\ nil)

@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(rows, levels, src)

@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(text)

@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(text, src, opts \\ [])

@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(text, src, opts \\ [])

@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]}]}