Skip to main content

Getting Started

Plottr is a strictly-typed, animated GUI graph rendering library for Roblox. It renders line and bar charts out of plain GUI instances — no UI framework dependencies — with animated entries, gradient infill, curve smoothing, tooltips, legends, and realtime streaming.

Installation

Wally

Add Plottr to your wally.toml:

[dependencies]
Plottr = "alternativelua/plottr@0.2.0"

Then run wally install.

Manual

Copy the src folder into your project (e.g. as ReplicatedStorage.Plottr) using Rojo or by importing a model file.

Your first graph

local Plottr = require(ReplicatedStorage.Plottr)

-- Any GuiObject works as a container; the graph fills it.
local graph = Plottr.new(containerFrame, {
theme = Plottr.Themes.Midnight,
smoothing = { enabled = true },
xLabels = { "Jan", "Feb", "Mar", "Apr", "May", "Jun" },
})

graph:setSeries({
{ name = "Revenue", values = { 4200, 5100, 4800, 6900, 8400, 9600 } },
{ name = "Costs", values = { 3100, 3300, 3600, 4100, 4400, 4900 }, kind = "bar" },
})

The graph animates in, re-renders automatically when data or config changes, and redraws instantly when the container resizes.

Realtime data

Use Graph:push with the maxPoints window to stream values without replaying the entry animation. Pin the axis with yMin/yMax so it does not rescale under the incoming data:

local live = Plottr.new(containerFrame, {
maxPoints = 40,
showPoints = false,
yMin = 0,
yMax = 100,
})
live:setSeries({ { name = "CPU %", values = {} } })

task.spawn(function()
while task.wait(0.2) do
live:push("CPU %", getCpuSample())
end
end)

Each redraw recycles the instances the last one built, so streaming costs property writes rather than a rebuild of the chart.

Interaction

Points and bars show a tooltip on hover or tap. With more than one series the legend appears, and clicking an entry hides or shows that series — pass legendToggle = false to opt out. Clicks on the data itself are reported to handlers registered with Graph:onPointClicked:

graph:onPointClicked(function(event)
print(`{event.seriesName} #{event.index} = {event.value}`)
end)

Configuration

Every option lives on Config, and both Plottr.new and Graph:configure take a partial that is merged over the current values — anything you leave out keeps what it had. To clear an optional field back to nil, pass Plottr.None:

graph:configure({ maxPoints = Plottr.None, title = Plottr.None })

Wrong types, non-finite numbers, and negative sizes throw. Options that only affect cost or appearance are clamped instead: yTickCount to 2–50, maxXTickCount to 1–50, smoothing.amount to 0–1, smoothing.samples to 2–64, fill.resolution to at least 1, and the transparencies to 0–1.

Labels are yours to format. formatValue writes the y-axis ticks and tooltips, and xLabels takes either an array or a function — use the function form for streaming data, where array indices stop lining up once values scroll off the front:

local graph = Plottr.new(containerFrame, {
title = "Server load",
xAxisTitle = "Sample",
yAxisTitle = "Percent",
formatValue = function(value)
return string.format("%d%%", value)
end,
xLabels = function(index)
return if index % 5 == 0 then `t-{index}` else nil
end,
})

Themes

Start from a preset and override what you need with Themes.extend. font and textSize are optional and default to BuilderSans Medium at 12px; palette must have at least one colour.

local brand = Plottr.Themes.extend(Plottr.Themes.Dark, {
name = "Brand",
palette = { Color3.fromRGB(255, 115, 0) },
textSize = 14,
})
graph:setTheme(brand)

Dashboards

Plottr.newGroup lays out several graphs in a grid:

local group = Plottr.newGroup(containerFrame, { columns = 2, spacing = 12 })
local monthly = group:addGraph("Monthly")
local live = group:addGraph("Live", { maxPoints = 40 })

Next steps