MIT licensed by Michael Chavinda
Maintained by [email protected]
This version can be pinned in stack with:
granite-0.6.0.0@sha256:f3c8bb3479960ed40184de6d1d049e6ab366e0e7cf51512de1caf1bbad509b64,3205Module documentation for 0.6.0.0
Used by 2 packages in nightly-2026-05-15(full list with versions):
Granite
A library for producing terminal and SVG plots. It depends only on Haskell’s
base and text packages so it stays easy to install in any environment.
Supported graph types
- Scatter plots
- Histograms
- (Stacked) bar charts
- Pie charts
- Box plots
- Line charts
- Heat maps
Examples
Scatter plot

import Control.Monad
import System.Random.Stateful
import Granite.String
main :: IO ()
main = do
g <- newIOGenM =<< newStdGen
let range = (0 :: Double, 1 :: Double)
ptsA_x <- replicateM 600 (uniformRM range g)
ptsA_y <- replicateM 600 (uniformRM range g)
ptsB_x <- replicateM 600 (uniformRM range g)
ptsB_y <- replicateM 600 (uniformRM range g)
putStrLn (scatter [series "A" (zip ptsA_x ptsA_y), series "B" (zip ptsB_x ptsB_y)]
defPlot{widthChars=68,heightChars=22,plotTitle="Random points"})
Bar chart

{-# LANGUAGE OverloadedStrings #-}
import qualified Data.Text.IO as T
import Granite
main :: IO ()
main = T.putStrLn (bars [("Q1",12),("Q2",18),("Q3",9),("Q4",15)] defPlot {plotTitle="Sales"})
Stacked bar chart

{-# LANGUAGE OverloadedStrings #-}
import qualified Data.Text.IO as T
import Granite
main :: IO ()
main = T.putStrLn (stackedBars [ ("Q1", [("Hardware", 120), ("Software", 200), ("Services", 80)])
, ("Q2", [("Hardware", 135), ("Software", 220), ("Services", 95)])
, ("Q3", [("Hardware", 110), ("Software", 240), ("Services", 110)])
, ("Q4", [("Hardware", 145), ("Software", 260), ("Services", 125)])
] defPlot {plotTitle="Quarterly Revenue Breakdown"})
Pie chart

{-# LANGUAGE OverloadedStrings #-}
import qualified Data.Text.IO as T
import Granite
main :: IO ()
main = T.putStrLn (pie [("Alpha",0.35),("Beta",0.25),("Gamma",0.20),("Delta",0.20)] defPlot{widthChars=46,heightChars=18,legendPos=LegendRight,plotTitle="Share"})
Box plot

{-# LANGUAGE OverloadedStrings #-}
import qualified Data.Text.IO as T
import Granite
main :: IO ()
main = T.putStrLn $ boxPlot [ ("Class A", [78, 82, 85, 88, 90, 92, 85, 87, 89, 91, 76, 94, 88])
, ("Class B", [70, 75, 72, 80, 85, 78, 82, 77, 79, 81, 74, 83])
, ("Class C", [88, 92, 95, 90, 93, 89, 91, 94, 96, 87, 90, 92])
, ("Class D", [65, 70, 72, 68, 75, 80, 73, 71, 69, 74, 77, 76])
] defPlot {plotTitle="Test Score Distribution by Class"}
Line graph

{-# LANGUAGE OverloadedStrings #-}
import qualified Data.Text.IO as T
import Granite
main :: IO ()
main = T.putStrLn $ lineGraph [ ("Product A", [(1, 100), (2, 120), (3, 115), (4, 140), (5, 155), (6, 148)])
, ("Product B", [(1, 80), (2, 85), (3, 95), (4, 92), (5, 110), (6, 125)])
, ("Product C", [(1, 60), (2, 62), (3, 70), (4, 85), (5, 82), (6, 90)])
] defPlot {plotTitle="Monthly Sales Trends"}
Heatmap

{-# LANGUAGE OverloadedStrings #-}
import qualified Data.Text.IO as T
import Granite
main :: IO ()
main = do
let matrix = [ [1.0, 0.8, 0.3, -0.2, 0.1]
, [0.8, 1.0, 0.5, -0.1, 0.2]
, [0.3, 0.5, 1.0, 0.6, 0.4]
, [-0.2, -0.1, 0.6, 1.0, 0.7]
, [0.1, 0.2, 0.4, 0.7, 1.0]
]
T.putStrLn $ heatmap matrix defPlot {plotTitle="Correlation Matrix"}
Histogram

{-# LANGUAGE OverloadedStrings #-}
import qualified Data.Text as T
import qualified Data.Text.IO as T
import Control.Monad
import Granite
import System.Random.Stateful
main :: IO ()
main = do
g <- newIOGenM =<< newStdGen
heights <- replicateM 5000 (uniformRM (160 :: Double, 190 :: Double) g)
T.putStrLn $
histogram
(bins 30 155 195)
heights
defPlot
{ widthChars = 68
, heightChars = 18
, legendPos = LegendBottom
, xFormatter = \_ _ v -> T.pack (show (round v :: Int))
, xNumTicks = 10
, yNumTicks = 5
, plotTitle = "Heights (cm)"
}
Changes
Revision history for granite
0.5.0.0 – 2026-05-13
A major refactor introducing a declarative, Grammar-of-Graphics-style
IR shared between the terminal and SVG backends. Legacy chart
functions (scatter, bars, pie, …) remain unchanged and continue
to work; the new IR is additive.
- New module
Granite.Specexposing the chart IR:Chart,Layer,Mapping,Geom,Stat,Position,Scale,Coord,Facet,Theme,Size,ColorSpec,Formatter. All types are plain ADTs of basic Haskell types so a downstream user can deriveToJSON/FromJSONinstances with the JSON library of their choice. Granite itself ships no JSON instances; the dependency footprint remainsbase + text. - New module
Granite.Data.Frameproviding a column-orientedDataFrame(ColNum,ColCat,ColTime,ColBool). - New module
Granite.Formatwith a declarativeFormatterenum (FormatPrecision,FormatScientific,FormatPercent,FormatComma,FormatSI, …). Replaces the function-typedLabelFormatterfor IR users; legacyPlot { xFormatter, yFormatter }remains in place. - New module
Granite.Scalewith linear, log (base 2/e/10), sqrt, reverse, and identity scales, plus Heckbert-style “nice” tick selection. (Talbot–Lin–Hanrahan “Extended” tick scoring is a future upgrade — see the source for the TODO.) - New module
Granite.Render.Scenewith backend-agnostic primitive marks:MCircle,MRect,MPolyline,MPath,MText,MArc,MGroup. Both backends consume the sameScene. - New module
Granite.Render.Terminalwith the unified terminal backend. The Braille canvas and AVL-backedArray2Dwere extracted fromGraniteand now power both the legacy chart functions and the new IR pipeline. - New module
Granite.Render.Svgwith the unified SVG backend.renderSceneResponsiveproduces SVG usingviewBox+preserveAspectRatio+width="100%"so charts embed responsively. - New module
Granite.Render.Chromewith axes, ticks, gridlines, title, and legend as primitive marks — chrome code is no longer duplicated between backends. - New module
Granite.Render.PipelineexposingchartToScene,renderChartTerminal, andrenderChartSvg. Phase 3 supports the cartesian path with identity stat and identity position. Polar coord, facets, stats, positions, ribbons / errorbars, and multi- chart figures are scheduled for later releases. - New module
Granite.Chartwith thin builders (scatterChart,lineChart) that construct an IRChartfrom the same input shapes the legacy chart functions accept. - New module
Granite.Colorextracted fromGranite; exports the ANSI palette, ANSI escape helpers, and hex mapping. - New module
Granite.Internal.Utilwith the shared numeric, list, text, and tick helpers that used to live (duplicated) in bothGraniteandGranite.Svg. ColorSpecaddsRGBandHexconstructors for cross-renderer color fidelity, with terminal backends quantising to the nearest ANSI color.
0.4.0.0 – 2026-02-27
- Add Svg support.
0.3.0.5 – 2025-11-10
- Fix x-axis label alignment for continuous histograms.
0.3.0.4 – 2025-09-26
- Fix issue with x-axis spacing when categories is greater than width.
0.3.0.3 – 2025-09-26
- slot width for bar charts now depends on xNumTicks not categories.
0.3.0.2 – 2025-09-26
- Make legend optional.
- xNumTicks now applies to categories.
0.3.0.1 – 2025-09-17
- Left edge of plots now has an elbow instead of disjoint bars.
- Remove dependency on random.
0.3.0.0 – 2025-08-31
- Export a
LabelFormatterfunction along with AxisEnv to define smarter labels. - Change API of formatter to use AxisEnv and slot budget.
- Users can now specify the number of ticks to show and the colour palette for a plot.
0.2.0.2 – 2025-08-30
- Add plot option to define the format of labels on both axes.
0.2.0.1 – 2025-08-26
- Loosen bounds for text
0.2.0.0 – 2025-08-26
- Plot title is now part of the configuration options.
- API now uses Text instead of String.
- You can now specify the bounds of a plot e.g.
T.putStrLn $ lineGraph [("Foo", [(0, 0), (10, 1)]), ("Bar", [(0, 1), (10, 0)])] defPlot { xBounds = (Just 0, Just 10), yBounds = (Just 0, Just 1) }
0.1.0.3 – 2025-08-21
- Fix heatmaps: matrix grid wasn’t properly mapping to canvas + axes were reversed.
0.1.0.2 – 2025-08-20
- Add README to doc files.
0.1.0.1 – 2025-08-20
- Remove IO monad from plotting functions.
- Faster plotting after moving from lists as arrays to AVL trees.
- All functions are now strict by default.
0.1.0.0 – 2025-08-18
- Support for pie charts, bar charts, heatmaps, line graphs, box plots, and histograms.