Package

purescript-chartjs

Repository
philippedev101/purescript-chartjs
License
Apache-2.0
Uploaded by
pacchettibotti
Published on
2026-02-25T13:58:08Z

A PureScript wrapper for Chart.js, providing type-safe, config-driven chart rendering via FFI.

This is a monorepo with two packages:

  • chartjs — Core types, config generation, FFI bindings, callbacks, and colors. Framework-agnostic.
  • chartjs-halogen — A Halogen component wrapping the core library.

Installation

Add the packages to your spago.yaml extraPackages (since these aren't in the registry yet):

extraPackages:
  chartjs:
    git: https://your-gitea-instance/you/purescript-chartjs.git
    ref: main
    subdir: core
  chartjs-halogen:
    git: https://your-gitea-instance/you/purescript-chartjs.git
    ref: main
    subdir: halogen

Then add the dependencies you need:

# For Halogen users (pulls in core automatically):
dependencies:
  - chartjs
  - chartjs-halogen

# For framework-agnostic usage (core only):
dependencies:
  - chartjs

Install the JS dependency:

bun add chart.js

Quick Start (Halogen)

module MyApp where

import Prelude
import Chartjs.Halogen as Chart
import Chartjs.Types (ChartType(..), defaultConfig, defaultDataset, defaultOptions,
  defaultPluginsConfig, defaultTitleConfig, fromNumbers, single, css)
import Chartjs.Callbacks (simpleInput)
import Data.Maybe (Maybe(..))
import Halogen.HTML as HH
import Type.Proxy (Proxy(..))

_chart = Proxy :: Proxy "chart"

myChart = simpleInput $ defaultConfig
  { chartType = Bar
  , labels = ["Jan", "Feb", "Mar", "Apr"]
  , datasets =
      [ defaultDataset
          { label = "Revenue"
          , "data" = fromNumbers [100.0, 200.0, 150.0, 300.0]
          , backgroundColor = single (css "#4CAF50")
          }
      ]
  , options = defaultOptions
      { plugins = Just defaultPluginsConfig
          { title = Just defaultTitleConfig
              { display = Just true, text = Just "Monthly Revenue" }
          }
      }
  }

-- In your Halogen render function:
render state =
  HH.div_
    [ HH.slot_ _chart unit Chart.component myChart ]

Framework-Agnostic Usage

Use the core FFI bindings directly to create and manage charts without any framework:

import Chartjs.Config (toChartJsConfig)
import Chartjs.FFI (createChart, updateChart, destroyChart)
import Chartjs.Types (defaultConfig, defaultDataset, fromNumbers, ChartType(..))

-- Create a chart on a canvas element:
let json = toChartJsConfig myConfig
inst <- createChart canvasElement json

-- Update with new config:
let json2 = toChartJsConfig newConfig
updateChart inst json2

-- Clean up:
destroyChart inst

For charts with callbacks or gradient/pattern colors, use createChartWithCallbacks / updateChartWithCallbacks with overlays built via buildOverlays.

Chart Types

All standard Chart.js chart types are supported:

data ChartType = Line | Bar | Pie | Doughnut | Radar | Scatter | Bubble | PolarArea

API Overview

Core Types

Type Description
ChartConfig Top-level config: chart type, labels, datasets, options
Dataset A single data series (80+ optional fields matching Chart.js)
ChartOptions Nested options: plugins, scales, animation, interaction, layout, elements
ComponentInput Input record: { config :: ChartConfig, callbacks :: Callbacks }
Callbacks Optional event handlers: onClick, onHover, onResize, tooltip/legend/tick callbacks
Output Halogen component output: ChartError String when Chart.js throws (from Chartjs.Halogen)

Defaults

Every config type has a default* that sets all optional fields to Nothing:

defaultConfig      :: ChartConfig
defaultDataset     :: Dataset
defaultOptions     :: ChartOptions
defaultPluginsConfig :: PluginsConfig
defaultTitleConfig :: TitleConfig
defaultLegendConfig :: LegendConfig
defaultScaleConfig :: ScaleConfig
-- ... and many more

Use PureScript's record update syntax to override what you need:

defaultDataset
  { label = "Sales"
  , "data" = fromNumbers [10.0, 20.0, 30.0]
  , borderWidth = Just 2.0
  }

Data Points

Datasets accept Array DataPoint with smart constructors:

fromNumbers :: Array Number -> Array DataPoint    -- simple values
fromXY :: Array { x :: Number, y :: Number } -> Array DataPoint  -- scatter/line
fromBubble :: Array { x :: Number, y :: Number, r :: Number } -> Array DataPoint

Colors

Colors can be CSS strings, canvas gradients, or canvas patterns:

data Color = CSSColor String | GradientColor CanvasGradient | PatternColor CanvasPattern

-- Convenience constructor:
css :: String -> Color
css = CSSColor

Color fields on datasets use Indexable Color — either a single color or one per data point:

single  :: Color -> Maybe (Indexable Color)        -- one color for all points
perItem :: Array Color -> Maybe (Indexable Color)   -- one color per point

-- Examples:
backgroundColor = single (css "#4CAF50")
backgroundColor = perItem [css "#FF6384", css "#36A2EB", css "#FFCE56"]

Gradients and patterns are created via effectful FFI calls:

createLinearGradient :: HTMLCanvasElement -> { x0, y0, x1, y1 :: Number } -> Array ColorStop -> Effect CanvasGradient
createRadialGradient :: HTMLCanvasElement -> { x0, y0, r0, x1, y1, r1 :: Number } -> Array ColorStop -> Effect CanvasGradient
createPattern :: HTMLCanvasElement -> image -> String -> Effect CanvasPattern

Scales

Scales are keyed by axis ID in a Foreign.Object:

import Foreign.Object as Object
import Data.Tuple (Tuple(..))

options = defaultOptions
  { scales = Just $ Object.fromFoldable
      [ Tuple "x" $ defaultScaleConfig { stacked = Just true }
      , Tuple "y" $ defaultScaleConfig
          { beginAtZero = Just true
          , title = Just $ defaultScaleTitleConfig
              { display = Just true, text = Just "Amount ($)" }
          }
      ]
  }

Callbacks

Use simpleInput when you don't need callbacks, or build a full ComponentInput with handlers:

import Chartjs.Callbacks (simpleInput, defaultCallbacks, ComponentInput)
import Effect.Uncurried (mkEffectFn3)

-- No callbacks:
input = simpleInput myConfig

-- With onClick:
input =
  { config: myConfig
  , callbacks: defaultCallbacks
      { onClick = Just $ mkEffectFn3 \event elements chart -> do
          -- handle click
          pure unit
      }
  }

Available callbacks:

Callback Signature
onClick EffectFn3 Event (Array ActiveElement) ChartInstance Unit
onHover EffectFn3 Event (Array ActiveElement) ChartInstance Unit
onResize EffectFn2 ChartInstance { width :: Number, height :: Number } Unit
legendOnClick EffectFn3 Event LegendItem Legend Unit
legendOnHover EffectFn3 Event LegendItem Legend Unit
legendOnLeave EffectFn3 Event LegendItem Legend Unit
tooltipCallbacks.label EffectFn1 TooltipItem String
tooltipCallbacks.title EffectFn1 (Array TooltipItem) String
tooltipCallbacks.footer EffectFn1 (Array TooltipItem) String
tickCallbacks Object (EffectFn3 Foreign Int (Array Foreign) String) — keyed by scale ID

Error Handling

The Halogen component emits ChartError String as output when Chart.js throws during create or update:

import Chartjs.Halogen as Chart

-- Ignore errors:
HH.slot_ _chart unit Chart.component myInput

-- Handle errors:
HH.slot _chart unit Chart.component myInput handleOutput

handleOutput :: Chart.Output -> _
handleOutput (Chart.ChartError msg) = -- log or display the error

Component Lifecycle

The Halogen component manages the full Chart.js lifecycle:

  1. Initialize — creates a <canvas>, instantiates Chart.js via FFI
  2. Receive — on input change, calls chart.update() (no destroy/recreate)
  3. Finalize — calls chart.destroy() to prevent memory leaks

Building

bunx spago build
bunx spago test

Build individual packages:

bunx spago build --package chartjs
bunx spago build --package chartjs-halogen

License

Apache-2.0

Modules
Chartjs
Chartjs.Callbacks
Chartjs.Colors
Chartjs.Config
Chartjs.FFI
Chartjs.Types
Dependencies