promotional bannermobile promotional banner

LibGraph-2.0 - Revived

Reviving LibGraph-2.0 until the original author comes back and fixes it. They are welcome to any work I do on it to keep it function if they do return.

Lib: Graph-2.0 — Line, Scatter, Realtime and Pie Charts for WoW Addons

LibGraph-2.0 draws graphs inside a WoW addon: line graphs, filled area graphs, scatter plots with linear regression, live scrolling realtime bars, and pie charts. It is a LibStub library with no dependencies beyond LibStub itself, and it works unchanged on Classic Era, TBC, Wrath, Cataclysm, Mists and Retail.

This is the maintained continuation of Cryect and Xinhuan's original library, published as one shared copy so the addons that embed it stop drifting apart.

The Problem

WoW gives addon authors no charting primitives at all. There is no line, no plotted point, no filled region — only textures you can stretch and rotate. Drawing a graph means building that layer yourself: rotating a 1-pixel texture to an arbitrary angle, pooling the textures so a redraw does not leak frames, working out gridline intervals that land on round numbers, and doing it every OnUpdate without costing frames.

Every damage meter and statistics panel that shows a graph has solved this, and LibGraph-2.0 is the solution most of them use. But because it is embedded rather than installed, each addon carries its own private copy — and those copies drift. The two forks merged into this release had both fixed real bugs, neither knew about the other, and one of them was still crashing on Retail.

The Solution

One library, one repository, four graph types, and an API that is a handful of calls: create a graph frame, hand it your data as a table of {x, y} pairs, and it draws and redraws itself. Texture pooling, gridline placement, axis labelling, autoscaling and mouse hit-testing are all handled inside.

Core Features

Line Graphs

  • Multiple data series on one graph — call AddDataSeries once per series, each with its own colour. Two shapes are accepted: a list of {x, y} pairs, or two parallel arrays {xs, ys} with the n2 argument set to true. The table is stored by reference and not copied, and must be a proper sequence — a hole makes the graph stop drawing at the gap, which the library now reports in chat naming the call that was given it
  • Filled area seriesAddFilledDataSeries shades the region between two bounds, for things like a min/max band around an average
  • Custom line texturesSetLineTexture swaps the line art per series or per graph
  • AutoscalingSetAutoScale(true) fits the axes to the data as it changes, with LockXMin, LockXMax, LockYMin and LockYMax to pin any edge you want held still
  • BordersSetBorderSize("left"|"right"|"top"|"bottom", size) replaces the automatic 10% margin on one edge while autoscaling. Mind the sign: the size is added to the data extreme on every edge, so a positive value widens the axis at the top and right but moves it inward at the bottom and left. Pass a negative number on those two edges to get an outward margin. This is the original library's behaviour, kept deliberately so a graph drawn by any copy of LibGraph-2.0 renders identically

Scatter Plots

  • Plots each point as its own pooled texture, from the same {x, y} data series a line graph takes
  • Linear regressionSetLinearFit(true) draws a least-squares fit line through the data; LinearRegression is exposed directly if you want the coefficients rather than the line
  • Shares the full axis, gridline and locking API with line graphs

Realtime Graphs

  • A scrolling bar graph fed one value at a timeAddTimeData(value) as the data arrives, and the graph scrolls left on its own
  • Stop feeding a realtime graph you have hidden. Samples are aged out by the graph's own per-frame update, and the game does not run that on a hidden frame — so a feed that keeps calling AddTimeData into a hidden graph accumulates samples that nothing can reclaim. Unhide it, stop the feed, or call ResetData(). Recount is worth copying here: it unregisters its tracking callback when a realtime window closes, so the feed stops with the window
  • Five modesSetMode("FAST") (default) shifts whole bars and convolves only the newly exposed ones; "SLOW" rebuilds every bar from the raw samples each frame for a smoother trace; "EXP" and "EXPFAST" keep an exponentially decaying running value; "RAW" does no time convolution at all and draws only what AddBar pushes
  • Filter shapegraph.Filter is a field rather than a setter: "RECT" (default) or "TRI" for a triangular weighting, applying to FAST and SLOW
  • FilteringSetFilterRadius and SetDecay control how much each sample bleeds into its neighbours, so a spiky feed reads as a curve rather than a comb
  • Gradient barsSetBarColors(bottom, top) sets a vertical gradient across every bar
  • Frame-rate controlSetUpdateLimit caps how often the graph redraws, independently of how often you feed it

Pie Charts

  • AddPie(percent, color) per slice, CompletePie(color) to fill whatever is left, ResetPie() to start over
  • Slices are drawn from rotated textures, so they are smooth at any angle rather than stepped
  • Mouse trackingSetSelectionFunc is called with the slice under the cursor as it moves, so you can highlight a slice or drive a tooltip from it

Axes, Gridlines and Labels

  • SetXAxis(min, max) and SetYAxis(min, max), or SetYMax alone when the bottom is fixed at zero
  • SetGridSpacing(x, y) for the interval, and SetGridSecondaryMultiple(x, y) for a heavier line every n gridlines
  • SetAxisColor, SetGridColor and SetGridColorSecondary, each taking an {r, g, b, a} table
  • SetAxisDrawing(xaxis, yaxis) to turn either axis off entirely
  • SetYLabels(left, right) puts numeric labels down either side, or both

Localisable Labels — graph.LabelHook

  • The library draws its own axis numbers, which used to make them unlocalisable. Set graph.LabelHook to a function and it is handed each label's FontString right after the text is set and before it is shown
  • That is enough to substitute a locale's own digits, or apply a font that can draw them, without the library knowing anything about localisation
  • Called at every label site on every graph type. Leave it nil and nothing changes

X-Axis Labels — graph.XLabelsEnabled

  • Set graph.XLabelsEnabled = true and a line graph labels its X gridlines along the bottom, following the same secondary-gridline spacing the gridlines use
  • The rightmost label is skipped so it cannot run off the edge of the frame
  • Off by default — the original library drew no X-axis labels at all

Drawing Primitives

  • DrawLine, DrawVLine, DrawHLine and DrawBar are public, so you can draw arbitrary rotated lines onto any frame without creating a graph at all
  • Every texture is pooled and reused — HideLines and HideBars return them rather than destroying them, so a graph that redraws every frame does not leak

How It Works

Drawing a graph

  1. Create itlocal graph = LibStub("LibGraph-2.0"):CreateGraphLine(name, parent, "CENTER", "CENTER", 0, 0, width, height) returns an ordinary Frame with the graph methods attached
  2. Set the axesgraph:SetXAxis(0, 60), graph:SetYAxis(0, 5000), or turn on SetAutoScale(true) and let it follow the data
  3. Add datagraph:AddDataSeries({{0, 120}, {1, 340}, {2, 90}}, {1, 0, 0, 1}), once per series
  4. That is all — the graph redraws itself on its own OnUpdate; ResetData() clears the series when you want to start again

Why the version is bumped

  1. Every consumer embeds its own copy of LibGraph-2.0, and LibStub loads whichever copy registers the highest minor
  2. Stock registers 90000 + SVN revision; this copy registers 90000 + 2000 + SVN revision
  3. Without that bump, an addon in your install shipping a stock copy would win the load race and the Retail fix, LabelHook and XLabelsEnabled would silently not exist
  4. It is +2000 rather than +1000 because LibStub refuses an equal minor, and +1000 collided with an older private fork that still crashes on Retail — a tie is decided by load order, and the broken copy was winning it
  5. The bump is what makes this copy the one that loads, whatever else is installed alongside it
  6. Graphs another copy already created are retrofitted when this one loads over it, so the fix reaches you without every addon that embeds LibGraph needing an update first

API Summary

  • lib:CreateGraphLine(name, parent, relative, relativeTo, offsetX, offsetY, width, height)
  • lib:CreateGraphScatterPlot(name, parent, relative, relativeTo, offsetX, offsetY, width, height)
  • lib:CreateGraphRealtime(name, parent, relative, relativeTo, offsetX, offsetY, width, height)
  • lib:CreateGraphPieChart(name, parent, relative, relativeTo, offsetX, offsetY, width, height)
  • lib:DrawLine(frame, sx, sy, ex, ey, width, color, layer, texture) · DrawVLine(frame, x, sy, ey, width, color, layer) · DrawHLine(frame, sx, ex, y, width, color, layer)
  • lib:DrawBar(frame, sx, sy, ex, ey, color, level) · HideLines(frame) · HideBars(frame) — level puts the bar in a sub-frame at frame:GetFrameLevel() + level and lifts the frame's text above it; omit it to draw on the frame itself
  • graph:AddDataSeries(points, color, n2, lineTexture) · AddFilledDataSeries(points, color, n2) · ResetData()
  • graph:AddTimeData(value) · AddBar(value) · GetValue(time) · GetMaxValue()
  • graph:AddPie(percent, color) · CompletePie(color) · ResetPie() · SetSelectionFunc(fn)
  • graph:SetXAxis(min, max) · SetYAxis(min, max) · SetYMax(max) · SetMinMaxY(value)
  • graph:SetAutoScale(bool) · LockXMin / LockXMax / LockYMin / LockYMax(bool)
  • graph:SetGridSpacing(x, y) · SetGridSecondaryMultiple(x, y) · SetAxisDrawing(x, y) · CreateGridlines()
  • graph:SetAxisColor(color) · SetGridColor(color) · SetGridColorSecondary(color) · SetBarColors(bottom, top)
  • graph:SetYLabels(left, right) · SetLineTexture(texture) · SetBorderSize(border, size)
  • graph:SetMode("FAST" | "SLOW" | "EXP" | "EXPFAST" | "RAW") · SetFilterRadius(r) · SetDecay(d) · SetUpdateLimit(seconds) · graph.Filter = "RECT" | "TRI"
  • graph:SetLinearFit(bool) · LinearRegression(data) · RefreshGraph()
  • graph.LabelHook = function(fontString) — called for each axis label before it is shown
  • graph.XLabelsEnabled = true — draw numeric labels along the X axis

Requirements

  • LibStub — bundled, and registered only if your install does not already have a newer one
  • Ace3 and VersionCheck-1.0 — required by this standalone addon only, and installed for you by CurseForge. They exist so this install can tell you when it is out of date; VersionCheck needs Ace3's AceComm and AceSerializer and does not embed them
  • The drawing code itself has no dependencies. An addon that embeds LibGraph-2.0 in its own Libs folder needs LibStub and nothing more — an embedded copy never loads this TOC, so it never pulls Ace3 or VersionCheck in, and it deliberately does not register for version checking (it cannot be updated on its own, so the advice would be unactionable)
  • WoW version: Classic Era (11509) · TBC Classic (20506) · Wrath Classic (30405) · Cataclysm Classic (40402) · MoP Classic (50504) · Retail (110207 / 120005 / 120007 / 120100)
  • This is a library, not an addon with a user interface. It loads on demand when an addon that declares it asks for it. Installing it on its own does nothing visible

Recent Updates

v2.0.7 (2026-08-25)

Fixed

  • The realtime graph dropped only half its stale samples. The five places that age old samples out of the sample list — SLOW/RECT, SLOW/TRI, FAST/RECT, EXP and EXPFAST — each removed entries while counting forwards through the same table, so the element that slid into a vacated slot was never visited. With a backlog of six stale samples, three of them survived — exactly half. All five now walk backwards, so a removal only touches indices already passed. This was never a crash — the leaked samples keep contributing to the smoothing, so a busy realtime graph plausibly read a little high and held values a little too long. That part is not demonstrated and is not claimed.
  • AddPie had the number of slice textures written as a literal 7. It is now derived from the texture list, so the two pie entry points cannot disagree if that list ever changes.
  • Two defects in DrawBar, both reachable only through the public drawing primitive. When a bar's level would bury the graph's text, the library lifts the text frame and asks for a redraw — but that request was addressed to the wrong object, so on the public path it set a field nothing reads and the redraw never came. Separately, the texture pool returns a bar without restoring its parent while the parent was only ever set when a level was given, so a recycled bar could be drawn by an unlevelled call at whatever level the previous caller used. Neither is reachable from inside the library, so no existing graph renders differently.

New

  • The standalone addon now tells you when it is out of date. It depends on Ace3 and VersionCheck-1.0 (both installed for you by CurseForge) and registers with VersionCheck, which runs one batched guild-wide check at login and pops a single alert if a guildmate is on a newer copy. An addon that embeds LibGraph-2.0 is unaffected — an embedded copy never loads this addon's TOC, so it pulls in neither dependency, and it deliberately does not register: a copy that arrived inside another addon is updated when that addon is, so the alert would be advice nobody could act on.
  • A data series with a hole in it is now diagnosed instead of quietly drawn short. AddDataSeries and AddFilledDataSeries store the caller's table by reference and never checked it was a proper sequence — so a consumer building points with explicit indices, or from a filtered source that skips entries, could hand over a table with a gap and get a clean, plausible, shorter line: a graph that looks correct and is missing data. Both entry points now compare a counted walk against the length and print one chat line naming which call was given the bad table, then store it anyway.
  • It warns rather than refuses, and it warns once. Refusing would not draw the missing points either — the outcome is truncation whichever way, so the real choice was truncating loudly or truncating silently. And it reports once per graph per entry point: AddDataSeries is half the reset-then-re-add refresh idiom, so an unbounded per-call warning on a chart redrawing on a timer would be one chat line per refresh, forever, naming a function the player never called.
  • The check covers both data shapes. Both entry points accept a list of {x, y} pairs or two parallel arrays (the n2 form), and the parallel-array form is rebuilt internally into a series that is well-formed by construction — so a check placed after that rebuild is blind to a hole in the input. The check now runs before it, on the array that is actually walked, and the message names which array it is. This is the form real consumers use: Recount passes it at all four of its call sites.

Changed

  • Four loops that depend on order now ask for order. Slice decomposition in AddPie / CompletePie, and the point-to-point chaining in both line and filled series, are only correct in sequence order — and Lua does not promise that order for the loop they were using, even for numeric indices. They have always drawn correctly, which is exactly the problem: the code was relying on an implementation detail to draw a graph. Nothing about the rendering changes.
  • Two long-standing behaviours are documented rather than changed. SetBorderSize's size is added to the data extreme on every edge, so a positive value widens the axis at the top and right but crops at the bottom and left — the sign rule is now stated on the function itself and in the error it raises when misused. And feeding a hidden realtime graph accumulates samples nothing can reclaim, because the per-frame update that ages them out does not run on a hidden frame. Both are the original library's behaviour and are kept, so a graph drawn by any copy of LibGraph-2.0 renders identically.

v2.0.6 (2026-08-25)

Fixed

  • 148 identical errors per session on Retail 12.0. The pie chart's mouse test used the MouseIsOver global, which Retail 12.0 no longer defines. This file caches its globals at load time, so the value was nil for the whole session and every frame the pie chart updated threw. It now calls self:IsMouseOver() — which is what that global always forwarded to, and which exists on every flavour, so no version guard is needed.
  • DrawHLine drew its texture upside down. One fork had replaced the original 8-parameter SetTexCoord with a 4-parameter call described as the simpler equivalent. It was not equivalent: the 4-parameter order is (left, right, bottom, top), and the replacement swapped the last two, flipping the texture vertically. Corrected to the true equivalent. You may never have seen this — if the line texture is symmetric top to bottom, both forms render the same.

New

  • graph.LabelHook — localisable axis labels. The library draws its own axis numbers, which made them impossible to localise from outside. Set this to a function and it is handed each label's FontString after the text is set and before it is shown, which is enough to substitute a locale's own digits or apply a font that can draw them. Leave it unset and nothing changes.
  • graph.XLabelsEnabled — numeric labels along the X axis. The original library labelled the Y axis and left the X axis bare. Switch this on and a line graph labels its X gridlines along the bottom, on the same spacing the gridlines use, skipping the rightmost so it cannot run off the edge. Off by default.

Changed

  • One shared copy, replacing several private ones. LibGraph-2.0 is embedded rather than installed, so every addon that used it carried its own copy and the copies had drifted apart. This release merges the two forks that had real changes in them — the Retail fix came from one, the label additions from the other, and neither knew about the other. Consumers can now embed the same file.
  • The library version is bumped +2000 over stock, so that when several addons in one install each ship a copy, this one wins LibStub's load race and the additions above are actually present. +1000 was tried first and was not enough: LibStub refuses an equal minor, and +1000 tied exactly with an older private fork that still crashes on Retail — a tie is decided by load order, and the broken copy was winning it.
  • Packaged for release — a TOC per live flavour, a .pkgmeta, a packager workflow, and a LICENSE, none of which the library had before.

Credits

Cryect — original author of GraphLib / LibGraph-2.0; designed and built the graph engine, the rotated-texture line drawing, and all four graph types.

Xinhuan — long-running maintenance of the original library on WowAce.

Nelson Minar — caught several errors where width was used in place of height, credited in the original source.

Pimptasty — this consolidation: merging the divergent forks, the Retail 12.0 fix, the LabelHook and XLabelsEnabled additions, multi-flavour TOCs, packaging and documentation.

LibStub — Kaelten, Cladhaire, ckknight, Mikk, Ammo, Nevcairiel, joshborke. Public domain.

Licence

The original library remains All Rights Reserved to Cryect and Xinhuan. Maintenance from v2.0.6 onward is MIT. LibStub.lua is public domain. Full terms in the LICENSE file that ships with the addon.

Community

Bug reports, feature requests, or questions: Join the Discord, or open an issue on GitHub.

The LibGraph-2.0 - Revived Team

Forgeborn tier frameprofile avatar
  • 2
    Followers
  • 28
    Projects
  • 434.4K
    Downloads

More from PmptastyView all

  • Fast Guild Invite - Revived project image

    Fast Guild Invite - Revived

    A WoW Classic AddOn to help recruit new members into your guild.

    • 74.6K
    • August 27, 2026
  • LibLocaleOverride project image

    LibLocaleOverride

    A small embeddable library that lets an addon offer its own in-game language picker — so a player can read your addon in Dutch while their WoW client runs in English, without touching any other addon.

    • 11.0K
    • August 27, 2026
  • Recount - Revived project image

    Recount - Revived

    Recount is a graphical damage meter written by Cryect. Recount (Preservation) is an attempt to preserve it and keep it running moving forward

    • 42.6K
    • August 26, 2026
  • Dibs project image

    Dibs

    A master‑loot tool for Classic Era raids: everyone calls dibs on the gear they want before the raid, so when an item drops the masterlooter instantly sees who's competing — for fast, fair, drama‑free distribution. Works for pugs too.

    • 539
    • August 25, 2026
  • Fast Guild Invite - Revived project image

    Fast Guild Invite - Revived

    A WoW Classic AddOn to help recruit new members into your guild.

    • 74.6K
    • August 27, 2026
  • LibLocaleOverride project image

    LibLocaleOverride

    A small embeddable library that lets an addon offer its own in-game language picker — so a player can read your addon in Dutch while their WoW client runs in English, without touching any other addon.

    • 11.0K
    • August 27, 2026
  • Recount - Revived project image

    Recount - Revived

    Recount is a graphical damage meter written by Cryect. Recount (Preservation) is an attempt to preserve it and keep it running moving forward

    • 42.6K
    • August 26, 2026
  • Dibs project image

    Dibs

    A master‑loot tool for Classic Era raids: everyone calls dibs on the gear they want before the raid, so when an item drops the masterlooter instantly sees who's competing — for fast, fair, drama‑free distribution. Works for pugs too.

    • 539
    • August 25, 2026