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.
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: halogenThen add the dependencies you need:
# For Halogen users (pulls in core automatically):
dependencies:
- chartjs
- chartjs-halogen
# For framework-agnostic usage (core only):
dependencies:
- chartjsInstall the JS dependency:
bun add chart.js
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 ]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 instFor charts with callbacks or gradient/pattern colors, use createChartWithCallbacks / updateChartWithCallbacks with overlays built via buildOverlays.
All standard Chart.js chart types are supported:
data ChartType = Line | Bar | Pie | Doughnut | Radar | Scatter | Bubble | PolarArea| 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) |
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 moreUse 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
}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 DataPointColors can be CSS strings, canvas gradients, or canvas patterns:
data Color = CSSColor String | GradientColor CanvasGradient | PatternColor CanvasPattern
-- Convenience constructor:
css :: String -> Color
css = CSSColorColor 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 CanvasPatternScales 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 ($)" }
}
]
}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 |
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 errorThe Halogen component manages the full Chart.js lifecycle:
- Initialize — creates a
<canvas>, instantiates Chart.js via FFI - Receive — on input change, calls
chart.update()(no destroy/recreate) - Finalize — calls
chart.destroy()to prevent memory leaks
bunx spago build
bunx spago test
Build individual packages:
bunx spago build --package chartjs
bunx spago build --package chartjs-halogen
Apache-2.0