MIT licensed by Michael Chavinda
Maintained by [email protected]
This version can be pinned in stack with:granite-0.6.0.0@sha256:f3c8bb3479960ed40184de6d1d049e6ab366e0e7cf51512de1caf1bbad509b64,3205
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

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

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

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

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

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

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

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

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.Spec exposing 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 derive ToJSON / FromJSON instances with the JSON library of their choice. Granite itself ships no JSON instances; the dependency footprint remains base + text.
  • New module Granite.Data.Frame providing a column-oriented DataFrame (ColNum, ColCat, ColTime, ColBool).
  • New module Granite.Format with a declarative Formatter enum (FormatPrecision, FormatScientific, FormatPercent, FormatComma, FormatSI, …). Replaces the function-typed LabelFormatter for IR users; legacy Plot { xFormatter, yFormatter } remains in place.
  • New module Granite.Scale with 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.Scene with backend-agnostic primitive marks: MCircle, MRect, MPolyline, MPath, MText, MArc, MGroup. Both backends consume the same Scene.
  • New module Granite.Render.Terminal with the unified terminal backend. The Braille canvas and AVL-backed Array2D were extracted from Granite and now power both the legacy chart functions and the new IR pipeline.
  • New module Granite.Render.Svg with the unified SVG backend. renderSceneResponsive produces SVG using viewBox + preserveAspectRatio + width="100%" so charts embed responsively.
  • New module Granite.Render.Chrome with axes, ticks, gridlines, title, and legend as primitive marks — chrome code is no longer duplicated between backends.
  • New module Granite.Render.Pipeline exposing chartToScene, renderChartTerminal, and renderChartSvg. 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.Chart with thin builders (scatterChart, lineChart) that construct an IR Chart from the same input shapes the legacy chart functions accept.
  • New module Granite.Color extracted from Granite; exports the ANSI palette, ANSI escape helpers, and hex mapping.
  • New module Granite.Internal.Util with the shared numeric, list, text, and tick helpers that used to live (duplicated) in both Granite and Granite.Svg.
  • ColorSpec adds RGB and Hex constructors 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 LabelFormatter function 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.