Skip to content

Charting

Tova includes built-in SVG chart generation with zero external dependencies. All chart functions take a Table (or array of objects) and return a self-contained SVG string.

Charts are clean and minimal (Tufte-inspired) with auto-scaled axes, gridlines, and smart tick intervals. The default size is 600x400 via viewBox (responsive). No interactivity, no animation, no external fonts.


Quick Start

tova
sales = read("sales.csv")

// Generate a chart and save it
sales
  |> groupBy(.region)
  |> agg(revenue: sum(.amount))
  |> barChart(x: .region, y: .revenue, title: "Revenue by Region")
  |> writeText("chart.svg")

All chart functions are pipe-friendly. They return SVG strings that you can save with writeText(), embed in HTML, or inspect in a browser.


Common Options

Every chart function accepts these options:

OptionTypeDefaultDescription
titleString""Chart title (displayed at top center)
widthInt600SVG width in pixels
heightInt400SVG height in pixels
colorString"#4f46e5"Primary fill color (hex)

The default palette for multi-series or multi-category charts uses 8 perceptually distinct colors: indigo, emerald, amber, red, violet, cyan, pink, lime.


bar_chart

tova
barChart(data, x:, y:, title?, width?, height?, color?, colors?) -> String

Vertical bar chart with one bar per row. The x column provides category labels, y provides bar heights.

tova
sales |> barChart(x: .region, y: .revenue)
sales |> barChart(x: .region, y: .revenue, title: "Revenue by Region")

Options:

OptionTypeDefaultDescription
xColumnrequiredCategory labels
yColumnrequiredBar heights (numeric)
colorString"#4f46e5"Single color for all bars
colors[String]paletteArray of colors, one per bar

Behavior:

  • X-axis labels rotate at -45 degrees when there are more than 6 categories
  • Y-axis starts at 0 with auto-scaled gridlines
  • Bars have 15% gap between them and rounded corners (2px radius)

line_chart

tova
lineChart(data, x:, y:, title?, width?, height?, color?, points?) -> String

Line chart connecting data points with a <polyline>. Supports both numeric and categorical x-axes.

tova
prices |> lineChart(x: .date, y: .price, title: "Price History")
prices |> lineChart(x: .date, y: .price, points: true)

Options:

OptionTypeDefaultDescription
xColumnrequiredX-axis values (numeric or categorical)
yColumnrequiredY-axis values (numeric)
colorString"#4f46e5"Line color
pointsBoolfalseShow dots at data points

Behavior:

  • Numeric x-values are scaled proportionally; categorical values are evenly spaced
  • X-axis labels are thinned to at most 8 labels to avoid overlap
  • Line stroke width is 2px with rounded joins

scatter_chart

tova
scatterChart(data, x:, y:, title?, width?, height?, color?, r?) -> String

Scatter plot with one <circle> per data point. Both axes are numeric with auto-scaled gridlines.

tova
users |> scatterChart(x: .age, y: .income, title: "Age vs Income")

Options:

OptionTypeDefaultDescription
xColumnrequiredX-axis values (numeric)
yColumnrequiredY-axis values (numeric)
colorString or [String]"#4f46e5"Dot color(s)
rInt5Dot radius in pixels

Behavior:

  • Points are rendered with 70% opacity to reveal overlapping data
  • Both axes show gridlines
  • Color can be an array to color each point differently

histogram

tova
histogram(data, col:, bins?, title?, width?, height?, color?) -> String

Distribution chart that bins continuous data into uniform intervals and displays counts as bars.

tova
users |> histogram(col: .age, title: "Age Distribution")
users |> histogram(col: .salary, bins: 30, title: "Salary Distribution")

Options:

OptionTypeDefaultDescription
colColumnrequiredColumn of numeric values to bin
binsInt20Number of bins
colorString"#4f46e5"Bar fill color

Behavior:

  • Non-numeric values are filtered out
  • Bins are uniform width from data min to data max
  • The last bin includes the maximum value
  • X-axis shows up to 8 bin edge labels

pie_chart

tova
pieChart(data, label:, value:, title?, width?, height?, colors?) -> String

Circular pie chart with labeled segments. Default size is 400x400.

tova
sales |> pieChart(label: .category, value: .revenue, title: "Revenue Split")

Options:

OptionTypeDefaultDescription
labelColumnrequiredSlice labels
valueColumnrequiredSlice sizes (numeric)
colors[String]paletteColors for each slice

Behavior:

  • Slices start from the top (12 o'clock position) and go clockwise
  • Each slice shows its label and percentage at the arc midpoint
  • A single slice renders as a full circle
  • Zero total shows "No data" message

heatmap

tova
heatmap(data, x:, y:, value:, title?, width?, height?) -> String

Grid of colored cells for visualizing relationships between two categorical variables and a numeric value.

tova
data |> heatmap(x: .month, y: .product, value: .sales, title: "Sales Heatmap")

Options:

OptionTypeDefaultDescription
xColumnrequiredX-axis categories
yColumnrequiredY-axis categories
valueColumnrequiredCell values (numeric)

Behavior:

  • Color scale interpolates from white (low) to indigo (high)
  • Cell values are displayed as text in each cell when cells are large enough
  • Missing combinations show as white cells
  • Category order matches the order of first appearance in the data

Saving Charts

All chart functions return SVG strings. Use writeText() to save:

tova
// Save single chart
chart = sales |> barChart(x: .region, y: .revenue)
writeText("chart.svg", chart)

// Pipe-friendly
sales
  |> barChart(x: .region, y: .revenue)
  |> writeText("chart.svg")

SVG files can be opened directly in any browser, embedded in HTML, or converted to PNG/PDF with external tools.


Pipeline Integration

Charts compose naturally with table pipelines:

tova
// Transform, aggregate, then visualize
orders = read("orders.csv")

orders
  |> where(.status == "completed")
  |> groupBy(.category)
  |> agg(total: sum(.amount), orders: count())
  |> sortBy(.total, desc: true)
  |> limit(10)
  |> barChart(x: .category, y: .total, title: "Top 10 Categories")
  |> writeText("top_categories.svg")

// Multiple charts from the same data
by_month = orders
  |> groupBy(.month)
  |> agg(revenue: sum(.amount))

by_month |> lineChart(x: .month, y: .revenue) |> writeText("trend.svg")
by_month |> barChart(x: .month, y: .revenue) |> writeText("bars.svg")

Empty Data

All chart functions handle empty data gracefully. When the input table has zero rows, they return an SVG with a centered "No data" message instead of crashing.

Released under the MIT License.