Skip to content

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.

Edit this page