ApexCharts
Partas.Solid.ApexCharts binds solid-apexcharts. See its docs for usage and options. The chart options are built with Pojo constructors.
Example
[<SolidComponent>]
let ApexChartExample () =
let options,_ = createSignal(Options(
theme = Theme(
monochrome = Theme.Monochrome(
enabled = true, color = "#1e293b",
shadeTo = Enums.Theme.Monochrome.ShadeTo.Light
)
),
tooltip = Tooltip(followCursor = true),
xaxis = XAxis(categories = [| 1991 .. 1998 |])
))
let chartSeries = AxisChartSeries(
name = "series-1",
data = !^[|30;40;35;50;49;60;70;91|]
)
let series,_ = createSignal([|chartSeries|])
div(class' = "flex w-full items-center justify-center p-8 gap-8") {
SolidApexCharts(
width = "500",
type' = Chart.Type.Bar,
options = options(),
series = series()
)
SolidApexCharts(
width = "500",
type' = Chart.Type.Line,
options = options(),
series = series()
)
}
This renders a bar chart and a line chart of the same series, side by side, in a monochrome theme.
Binding apexcharts directly on Solid 2
ApexCharts itself does not depend on Solid, only the solid-apexcharts wrapper does, so you can use it on Solid 2 today. Declare the class with [<Import("default", "apexcharts")>] and give it only the members you call; MutationObserver is a browser global, so it uses [<Global>]. Construct the chart through a plain function such as newChart, because inside a component the plugin treats a call to an imported constructor as a JSX tag. Options are anonymous records, which Fable compiles to plain objects.
[<Import("default", "apexcharts")>]
type ApexCharts(element: Browser.Types.HTMLElement, options: obj) =
member _.render(): JS.Promise<unit> = jsNative
member _.updateSeries(series: obj): JS.Promise<unit> = jsNative
member _.destroy(): unit = jsNative
/// Inside a component, the plugin reads a call to an imported constructor as a JSX tag,
/// so construct the chart from a plain function.
let newChart (element: Browser.Types.HTMLElement, options: obj) =
ApexCharts(element, options)
[<Global>]
type MutationObserver(callback: obj -> unit) =
member _.observe(target: Browser.Types.Node, options: obj): unit = jsNative
member _.disconnect(): unit = jsNative
/// "light" or "dark", read from the attribute the site's theme switch sets.
let siteScheme () =
match Browser.Dom.document.documentElement.getAttribute "data-theme" with
| "dark" -> "dark"
| _ -> "light"
Effects run after the first render, so the host div exists when the first effect builds the chart. That effect tracks the theme; its cleanup destroys the chart, on a theme change and when the component is disposed. A second, deferred effect sends new data to updateSeries, which animates the bars. The --apx-* variables on the host are ApexCharts design tokens pointed at the site's colours.
[<SolidComponent>]
let DownloadsChart () =
let data, setData = createSignal [| 42; 58; 51; 73; 66; 88; 97 |]
let total = createMemo (fun (_: int option) -> Array.sum (data ()))
let scheme, setScheme = createSignal (siteScheme ())
let mutable host: Browser.Types.HTMLDivElement = JS.undefined
let mutable chart: ApexCharts option = None
let chartTokens =
"--apx-accent: var(--nacara-primary); --apx-fore: var(--nacara-text-muted);"
+ " --apx-grid: var(--nacara-border)"
onSettled (fun () ->
let observer = MutationObserver(fun _ -> setScheme (siteScheme ()))
observer.observe (
Browser.Dom.document.documentElement,
{| attributes = true; attributeFilter = [| "data-theme" |] |}
)
fun () -> observer.disconnect ())
createEffect (
(fun (_: string option) -> scheme ()),
fun (mode: string) ->
let c =
newChart (
host,
{| chart =
{| ``type`` = "bar"; height = 240; background = "transparent"
fontFamily = "inherit"; toolbar = {| show = false |} |}
theme = {| mode = mode |}
series = [| {| name = "Downloads"; data = data () |} |]
xaxis = {| categories = [| "Mon"; "Tue"; "Wed"; "Thu"; "Fri"; "Sat"; "Sun" |] |}
plotOptions = {| bar = {| borderRadius = 4; columnWidth = "50%" |} |}
dataLabels = {| enabled = false |}
grid = {| strokeDashArray = 4 |} |}
)
c.render () |> ignore
chart <- Some c
fun () -> c.destroy ()
)
createEffect (
(fun (_: int array option) -> data ()),
(fun (d: int array) ->
chart
|> Option.iter (fun c -> c.updateSeries [| {| name = "Downloads"; data = d |} |] |> ignore)),
defer = true
)
div (style = "width: 100%; display: grid; gap: .25rem") {
div (style = "display: flex; align-items: end; justify-content: space-between") {
div () {
div (style = "font-size: .8125rem; color: var(--nacara-text-muted)") { "Downloads this week" }
div (style = "font-size: 1.75rem; font-weight: 600; line-height: 1.2") {
$"{total ()}k"
}
}
button (
class' = "p-btn p-btn--secondary",
onClick = fun _ -> setData (Array.init 7 (fun _ -> 20 + int (JS.Math.random () * 80.)))
) { "Randomise" }
}
div(style = chartTokens).ref (host)
}
export function DownloadsChart() {
const patternInput = createSignal(new Int32Array([42, 58, 51, 73, 66, 88, 97]));
const data = patternInput[0];
const total = createMemo((_arg) => (sum(data(), {
GetZero: () => 0,
Add: (x, y) => ((x + y) | 0),
}) | 0));
const patternInput_1 = createSignal(siteScheme());
let host = void 0;
let chart = undefined;
const chartTokens = "--apx-accent: var(--nacara-primary); --apx-fore: var(--nacara-text-muted); --apx-grid: var(--nacara-border)";
onSettled(() => {
const observer = new MutationObserver((_arg_1) => {
patternInput_1[1](siteScheme());
});
observer.observe(document.documentElement, {
attributeFilter: ["data-theme"],
attributes: true,
});
return () => {
observer.disconnect();
};
});
createEffect((_arg_2) => patternInput_1[0](), (mode) => {
let theme;
const c = newChart(host, (theme = {
mode: mode,
}, {
chart: {
background: "transparent",
fontFamily: "inherit",
height: 240,
toolbar: {
show: false,
},
type: "bar",
},
dataLabels: {
enabled: false,
},
grid: {
strokeDashArray: 4,
},
plotOptions: {
bar: {
borderRadius: 4,
columnWidth: "50%",
},
},
series: [{
data: data(),
name: "Downloads",
}],
theme: theme,
xaxis: {
categories: ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"],
},
}));
c.render();
chart = c;
return () => {
c.destroy();
};
});
createEffect((_arg_3) => data(), (d) => {
const option_1 = chart;
if (option_1 != null) {
const c_1 = option_1;
return c_1.updateSeries([{
data: d,
name: "Downloads",
}]);
}
else {
return undefined;
}
}, {
defer: true,
});
return <div style="width: 100%; display: grid; gap: .25rem">
<div style="display: flex; align-items: end; justify-content: space-between">
<div>
<div style="font-size: .8125rem; color: var(--nacara-text-muted)">
Downloads this week
</div>
<div style="font-size: 1.75rem; font-weight: 600; line-height: 1.2">
{`${total()}k`}
</div>
</div>
<button class="p-btn p-btn--secondary"
onClick={(_arg_4) => {
patternInput[1](initialize(7, (_arg_5) => ((20 + ~~(Math.random() * 80)) | 0), Int32Array));
}}>
Randomise
</button>
</div>
<div style={chartTokens}
ref={host} />
</div>;
}
Click Randomise to animate the bars to new values, or switch the site theme to see the chart follow it.
