Skip to main content

src/sparklinekit@line.erl

-module(sparklinekit@line).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/sparklinekit/line.gleam").
-export([with_values/2, with_int_values/2, with_theme/2, with_smoothing/2, with_spot_color/2, with_gradient_area/2, with_color/2, with_background_color/2, with_area_fill/2, with_area_color/2, with_size/3, to_svg/1, new/1, new_ints/1, to_png/1, with_stroke_width/2, with_spot/2]).
-export_type([area_fill/0, builder/0]).

-if(?OTP_RELEASE >= 27).
-define(MODULEDOC(Str), -moduledoc(Str)).
-define(DOC(Str), -doc(Str)).
-else.
-define(MODULEDOC(Str), -compile([])).
-define(DOC(Str), -compile([])).
-endif.

?MODULEDOC(
    " SVG and PNG line sparklines.\n"
    "\n"
    " ```gleam\n"
    " import sparklinekit/line\n"
    " import sparklinekit/theme\n"
    "\n"
    " pub fn small_chart() -> String {\n"
    "   line.new([1.0, 5.0, 3.0, 8.0, 4.0])\n"
    "   |> line.with_theme(theme.ocean())\n"
    "   |> line.with_area_fill(True)\n"
    "   |> line.to_svg\n"
    " }\n"
    " ```\n"
    "\n"
    " The same `Builder` value can be rendered to either SVG ([`to_svg`](#to_svg))\n"
    " or PNG ([`to_png`](#to_png)). The SVG output is a self-contained\n"
    " `<svg>` element string with `preserveAspectRatio=\"none\"`; the PNG\n"
    " output is a `BitArray` ready for `simplifile.write_bits` or\n"
    " `bit_array.base64_encode`.\n"
).

-type area_fill() :: no_area | area_auto | {area_explicit, binary()}.

-opaque builder() :: {builder,
        list(float()),
        sparklinekit@theme:theme(),
        binary(),
        binary(),
        integer(),
        integer(),
        float(),
        area_fill(),
        boolean(),
        float(),
        float(),
        binary(),
        boolean()}.

-file("src/sparklinekit/line.gleam", 127).
?DOC(
    " Replace the data series after construction. Useful when the\n"
    " chart's styling is shared between multiple data sets.\n"
).
-spec with_values(builder(), list(float())) -> builder().
with_values(Builder, Values) ->
    {builder,
        Values,
        erlang:element(3, Builder),
        erlang:element(4, Builder),
        erlang:element(5, Builder),
        erlang:element(6, Builder),
        erlang:element(7, Builder),
        erlang:element(8, Builder),
        erlang:element(9, Builder),
        erlang:element(10, Builder),
        erlang:element(11, Builder),
        erlang:element(12, Builder),
        erlang:element(13, Builder),
        erlang:element(14, Builder)}.

-file("src/sparklinekit/line.gleam", 132).
?DOC(" Replace the data series with a list of `Int`s.\n").
-spec with_int_values(builder(), list(integer())) -> builder().
with_int_values(Builder, Values) ->
    {builder,
        gleam@list:map(Values, fun erlang:float/1),
        erlang:element(3, Builder),
        erlang:element(4, Builder),
        erlang:element(5, Builder),
        erlang:element(6, Builder),
        erlang:element(7, Builder),
        erlang:element(8, Builder),
        erlang:element(9, Builder),
        erlang:element(10, Builder),
        erlang:element(11, Builder),
        erlang:element(12, Builder),
        erlang:element(13, Builder),
        erlang:element(14, Builder)}.

-file("src/sparklinekit/line.gleam", 139).
?DOC(
    " Apply every colour slot from `theme`. Subsequent\n"
    " `with_color` / `with_background_color` / `with_area_color` calls\n"
    " override one slot at a time.\n"
).
-spec with_theme(builder(), sparklinekit@theme:theme()) -> builder().
with_theme(Builder, Theme) ->
    {builder,
        erlang:element(2, Builder),
        Theme,
        sparklinekit@theme:foreground(Theme),
        sparklinekit@theme:background(Theme),
        erlang:element(6, Builder),
        erlang:element(7, Builder),
        erlang:element(8, Builder),
        erlang:element(9, Builder),
        erlang:element(10, Builder),
        erlang:element(11, Builder),
        erlang:element(12, Builder),
        sparklinekit@theme:foreground(Theme),
        erlang:element(14, Builder)}.

-file("src/sparklinekit/line.gleam", 154).
?DOC(
    " Smooth the polyline into a cubic Bézier curve. `factor` controls\n"
    " how round the corners get — `0.0` keeps sharp polylines, `0.25`\n"
    " matches the default in `react-sparklines`, and `0.5` is the\n"
    " roundest reasonable setting. Values outside `[0.0, 0.5]` are\n"
    " clamped to that range.\n"
).
-spec with_smoothing(builder(), float()) -> builder().
with_smoothing(Builder, Factor) ->
    Clamped = case Factor of
        F when F < +0.0 ->
            +0.0;

        F@1 when F@1 > 0.5 ->
            0.5;

        F@2 ->
            F@2
    end,
    {builder,
        erlang:element(2, Builder),
        erlang:element(3, Builder),
        erlang:element(4, Builder),
        erlang:element(5, Builder),
        erlang:element(6, Builder),
        erlang:element(7, Builder),
        erlang:element(8, Builder),
        erlang:element(9, Builder),
        erlang:element(10, Builder),
        Clamped,
        erlang:element(12, Builder),
        erlang:element(13, Builder),
        erlang:element(14, Builder)}.

-file("src/sparklinekit/line.gleam", 175).
?DOC(
    " Override the colour used for the spot. Defaults to the stroke\n"
    " colour (or, if a theme is active, the theme's foreground).\n"
).
-spec with_spot_color(builder(), binary()) -> builder().
with_spot_color(Builder, Color) ->
    {builder,
        erlang:element(2, Builder),
        erlang:element(3, Builder),
        erlang:element(4, Builder),
        erlang:element(5, Builder),
        erlang:element(6, Builder),
        erlang:element(7, Builder),
        erlang:element(8, Builder),
        erlang:element(9, Builder),
        erlang:element(10, Builder),
        erlang:element(11, Builder),
        erlang:element(12, Builder),
        Color,
        erlang:element(14, Builder)}.

-file("src/sparklinekit/line.gleam", 183).
?DOC(
    " Toggle the vertical gradient applied to the area fill. When\n"
    " enabled (the default) the fill fades from the area colour at the\n"
    " top to transparent at the baseline; when disabled the fill is a\n"
    " solid tint.\n"
).
-spec with_gradient_area(builder(), boolean()) -> builder().
with_gradient_area(Builder, Enabled) ->
    {builder,
        erlang:element(2, Builder),
        erlang:element(3, Builder),
        erlang:element(4, Builder),
        erlang:element(5, Builder),
        erlang:element(6, Builder),
        erlang:element(7, Builder),
        erlang:element(8, Builder),
        erlang:element(9, Builder),
        erlang:element(10, Builder),
        erlang:element(11, Builder),
        erlang:element(12, Builder),
        erlang:element(13, Builder),
        Enabled}.

-file("src/sparklinekit/line.gleam", 193).
?DOC(
    " Set the stroke colour (any CSS colour string).\n"
    "\n"
    " The value is attribute-escaped before being written into the SVG;\n"
    " for PNG output it is parsed as `#rgb` / `#rgba` / `#rrggbb` /\n"
    " `#rrggbbaa` and falls back to the active theme's foreground if\n"
    " unparseable.\n"
).
-spec with_color(builder(), binary()) -> builder().
with_color(Builder, Color) ->
    {builder,
        erlang:element(2, Builder),
        erlang:element(3, Builder),
        Color,
        erlang:element(5, Builder),
        erlang:element(6, Builder),
        erlang:element(7, Builder),
        erlang:element(8, Builder),
        erlang:element(9, Builder),
        erlang:element(10, Builder),
        erlang:element(11, Builder),
        erlang:element(12, Builder),
        erlang:element(13, Builder),
        erlang:element(14, Builder)}.

-file("src/sparklinekit/line.gleam", 200).
?DOC(
    " Set the background rectangle colour. `\"none\"` disables the\n"
    " background (the SVG omits the `<rect>` and the PNG canvas stays\n"
    " transparent).\n"
).
-spec with_background_color(builder(), binary()) -> builder().
with_background_color(Builder, Color) ->
    {builder,
        erlang:element(2, Builder),
        erlang:element(3, Builder),
        erlang:element(4, Builder),
        Color,
        erlang:element(6, Builder),
        erlang:element(7, Builder),
        erlang:element(8, Builder),
        erlang:element(9, Builder),
        erlang:element(10, Builder),
        erlang:element(11, Builder),
        erlang:element(12, Builder),
        erlang:element(13, Builder),
        erlang:element(14, Builder)}.

-file("src/sparklinekit/line.gleam", 212).
?DOC(
    " Toggle the area fill under the line. When enabled without\n"
    " [`with_area_color`](#with_area_color) the renderer derives a\n"
    " translucent tint from the stroke colour.\n"
    "\n"
    " Disabling and re-enabling the fill preserves an earlier\n"
    " [`with_area_color`](#with_area_color) — the explicit colour is\n"
    " remembered across the toggle so callers can hide / show the area\n"
    " without losing their chosen tint.\n"
).
-spec with_area_fill(builder(), boolean()) -> builder().
with_area_fill(Builder, Enabled) ->
    case {Enabled, erlang:element(9, Builder)} of
        {true, no_area} ->
            {builder,
                erlang:element(2, Builder),
                erlang:element(3, Builder),
                erlang:element(4, Builder),
                erlang:element(5, Builder),
                erlang:element(6, Builder),
                erlang:element(7, Builder),
                erlang:element(8, Builder),
                area_auto,
                true,
                erlang:element(11, Builder),
                erlang:element(12, Builder),
                erlang:element(13, Builder),
                erlang:element(14, Builder)};

        {true, _} ->
            {builder,
                erlang:element(2, Builder),
                erlang:element(3, Builder),
                erlang:element(4, Builder),
                erlang:element(5, Builder),
                erlang:element(6, Builder),
                erlang:element(7, Builder),
                erlang:element(8, Builder),
                erlang:element(9, Builder),
                true,
                erlang:element(11, Builder),
                erlang:element(12, Builder),
                erlang:element(13, Builder),
                erlang:element(14, Builder)};

        {false, _} ->
            {builder,
                erlang:element(2, Builder),
                erlang:element(3, Builder),
                erlang:element(4, Builder),
                erlang:element(5, Builder),
                erlang:element(6, Builder),
                erlang:element(7, Builder),
                erlang:element(8, Builder),
                erlang:element(9, Builder),
                false,
                erlang:element(11, Builder),
                erlang:element(12, Builder),
                erlang:element(13, Builder),
                erlang:element(14, Builder)}
    end.

-file("src/sparklinekit/line.gleam", 224).
?DOC(
    " Explicitly set the area-fill colour. Implicitly enables the area\n"
    " fill — pass `with_area_fill(False)` afterwards to hide the fill\n"
    " without losing the explicit colour, then `with_area_fill(True)`\n"
    " to restore it.\n"
).
-spec with_area_color(builder(), binary()) -> builder().
with_area_color(Builder, Color) ->
    {builder,
        erlang:element(2, Builder),
        erlang:element(3, Builder),
        erlang:element(4, Builder),
        erlang:element(5, Builder),
        erlang:element(6, Builder),
        erlang:element(7, Builder),
        erlang:element(8, Builder),
        {area_explicit, Color},
        true,
        erlang:element(11, Builder),
        erlang:element(12, Builder),
        erlang:element(13, Builder),
        erlang:element(14, Builder)}.

-file("src/sparklinekit/line.gleam", 547).
-spec last_point(list({float(), float()})) -> {ok, {float(), float()}} |
    {error, nil}.
last_point(Points) ->
    case lists:reverse(Points) of
        [Head | _] ->
            {ok, Head};

        [] ->
            {error, nil}
    end.

-file("src/sparklinekit/line.gleam", 568).
-spec linear_segments(list({float(), float()})) -> binary().
linear_segments(Points) ->
    gleam@list:fold(
        Points,
        <<""/utf8>>,
        fun(Acc, P) ->
            {X, Y} = P,
            <<<<<<<<Acc/binary, " L "/utf8>>/binary,
                        (sparklinekit@internal@format:coord(X))/binary>>/binary,
                    " "/utf8>>/binary,
                (sparklinekit@internal@format:coord(Y))/binary>>
        end
    ).

-file("src/sparklinekit/line.gleam", 575).
-spec bezier_segments({float(), float()}, list({float(), float()}), float()) -> binary().
bezier_segments(Start, Rest, Factor) ->
    {_, Out} = gleam@list:fold(
        Rest,
        {Start, <<""/utf8>>},
        fun(State, P) ->
            {Prev, Acc} = State,
            {X0, Y0} = Prev,
            {X, Y} = P,
            Dx = (X - X0) * Factor,
            C1x = X0 + Dx,
            C1y = Y0,
            C2x = X - Dx,
            C2y = Y,
            Segment = <<<<<<<<<<<<<<<<<<<<<<" C "/utf8,
                                                        (sparklinekit@internal@format:coord(
                                                            C1x
                                                        ))/binary>>/binary,
                                                    " "/utf8>>/binary,
                                                (sparklinekit@internal@format:coord(
                                                    C1y
                                                ))/binary>>/binary,
                                            ", "/utf8>>/binary,
                                        (sparklinekit@internal@format:coord(C2x))/binary>>/binary,
                                    " "/utf8>>/binary,
                                (sparklinekit@internal@format:coord(C2y))/binary>>/binary,
                            ", "/utf8>>/binary,
                        (sparklinekit@internal@format:coord(X))/binary>>/binary,
                    " "/utf8>>/binary,
                (sparklinekit@internal@format:coord(Y))/binary>>,
            {P, <<Acc/binary, Segment/binary>>}
        end
    ),
    Out.

-file("src/sparklinekit/line.gleam", 554).
-spec path_data(list({float(), float()}), float()) -> binary().
path_data(Points, Smoothing) ->
    case Points of
        [] ->
            <<""/utf8>>;

        [{X, Y}] ->
            <<<<<<"M "/utf8, (sparklinekit@internal@format:coord(X))/binary>>/binary,
                    " "/utf8>>/binary,
                (sparklinekit@internal@format:coord(Y))/binary>>;

        [{X@1, Y@1} | Rest] ->
            Head = <<<<<<"M "/utf8,
                        (sparklinekit@internal@format:coord(X@1))/binary>>/binary,
                    " "/utf8>>/binary,
                (sparklinekit@internal@format:coord(Y@1))/binary>>,
            case Smoothing =< +0.0 of
                true ->
                    <<Head/binary, (linear_segments(Rest))/binary>>;

                false ->
                    <<Head/binary,
                        (bezier_segments({X@1, Y@1}, Rest, Smoothing))/binary>>
            end
    end.

-file("src/sparklinekit/line.gleam", 608).
-spec positive_or_one(integer()) -> integer().
positive_or_one(Value) ->
    case Value < 1 of
        true ->
            1;

        false ->
            Value
    end.

-file("src/sparklinekit/line.gleam", 232).
?DOC(
    " Set the viewBox / pixel dimensions.\n"
    "\n"
    " Non-positive values are normalised to `1` so the SVG / PNG stays\n"
    " valid.\n"
).
-spec with_size(builder(), integer(), integer()) -> builder().
with_size(Builder, Width, Height) ->
    {builder,
        erlang:element(2, Builder),
        erlang:element(3, Builder),
        erlang:element(4, Builder),
        erlang:element(5, Builder),
        positive_or_one(Width),
        positive_or_one(Height),
        erlang:element(8, Builder),
        erlang:element(9, Builder),
        erlang:element(10, Builder),
        erlang:element(11, Builder),
        erlang:element(12, Builder),
        erlang:element(13, Builder),
        erlang:element(14, Builder)}.

-file("src/sparklinekit/line.gleam", 615).
-spec positive_float_or(float(), float()) -> float().
positive_float_or(Value, Default) ->
    case Value > +0.0 of
        true ->
            Value;

        false ->
            Default
    end.

-file("src/sparklinekit/line.gleam", 622).
-spec escape_attribute(binary()) -> binary().
escape_attribute(Value) ->
    _pipe = Value,
    _pipe@1 = gleam@string:replace(_pipe, <<"&"/utf8>>, <<"&amp;"/utf8>>),
    _pipe@2 = gleam@string:replace(_pipe@1, <<"\""/utf8>>, <<"&quot;"/utf8>>),
    _pipe@3 = gleam@string:replace(_pipe@2, <<"<"/utf8>>, <<"&lt;"/utf8>>),
    gleam@string:replace(_pipe@3, <<">"/utf8>>, <<"&gt;"/utf8>>).

-file("src/sparklinekit/line.gleam", 326).
-spec background_rect(integer(), integer(), binary()) -> gleam@string_tree:string_tree().
background_rect(Width, Height, Background) ->
    case (Background =:= <<"none"/utf8>>) orelse (Background =:= <<""/utf8>>) of
        true ->
            gleam@string_tree:new();

        false ->
            _pipe = gleam@string_tree:new(),
            _pipe@1 = gleam@string_tree:append(
                _pipe,
                <<"<rect x=\"0\" y=\"0\" width=\""/utf8>>
            ),
            _pipe@2 = gleam@string_tree:append(
                _pipe@1,
                erlang:integer_to_binary(Width)
            ),
            _pipe@3 = gleam@string_tree:append(_pipe@2, <<"\" height=\""/utf8>>),
            _pipe@4 = gleam@string_tree:append(
                _pipe@3,
                erlang:integer_to_binary(Height)
            ),
            _pipe@5 = gleam@string_tree:append(_pipe@4, <<"\" fill=\""/utf8>>),
            _pipe@6 = gleam@string_tree:append(
                _pipe@5,
                escape_attribute(Background)
            ),
            gleam@string_tree:append(_pipe@6, <<"\"/>"/utf8>>)
    end.

-file("src/sparklinekit/line.gleam", 505).
-spec stroke_svg(builder(), list({float(), float()})) -> gleam@string_tree:string_tree().
stroke_svg(Builder, Points) ->
    case Points of
        [] ->
            gleam@string_tree:new();

        _ ->
            D = path_data(Points, erlang:element(11, Builder)),
            _pipe = gleam@string_tree:new(),
            _pipe@1 = gleam@string_tree:append(
                _pipe,
                <<"<path fill=\"none\" stroke=\""/utf8>>
            ),
            _pipe@2 = gleam@string_tree:append(
                _pipe@1,
                escape_attribute(erlang:element(4, Builder))
            ),
            _pipe@3 = gleam@string_tree:append(
                _pipe@2,
                <<"\" stroke-width=\""/utf8>>
            ),
            _pipe@4 = gleam@string_tree:append(
                _pipe@3,
                sparklinekit@internal@format:coord(erlang:element(8, Builder))
            ),
            _pipe@5 = gleam@string_tree:append(
                _pipe@4,
                <<"\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\""/utf8>>
            ),
            _pipe@6 = gleam@string_tree:append(_pipe@5, D),
            gleam@string_tree:append(_pipe@6, <<"\"/>"/utf8>>)
    end.

-file("src/sparklinekit/line.gleam", 527).
-spec spot_svg(builder(), list({float(), float()})) -> gleam@string_tree:string_tree().
spot_svg(Builder, Points) ->
    case {erlang:element(12, Builder) > +0.0, last_point(Points)} of
        {true, {ok, {X, Y}}} ->
            _pipe = gleam@string_tree:new(),
            _pipe@1 = gleam@string_tree:append(_pipe, <<"<circle cx=\""/utf8>>),
            _pipe@2 = gleam@string_tree:append(
                _pipe@1,
                sparklinekit@internal@format:coord(X)
            ),
            _pipe@3 = gleam@string_tree:append(_pipe@2, <<"\" cy=\""/utf8>>),
            _pipe@4 = gleam@string_tree:append(
                _pipe@3,
                sparklinekit@internal@format:coord(Y)
            ),
            _pipe@5 = gleam@string_tree:append(_pipe@4, <<"\" r=\""/utf8>>),
            _pipe@6 = gleam@string_tree:append(
                _pipe@5,
                sparklinekit@internal@format:coord(erlang:element(12, Builder))
            ),
            _pipe@7 = gleam@string_tree:append(_pipe@6, <<"\" fill=\""/utf8>>),
            _pipe@8 = gleam@string_tree:append(
                _pipe@7,
                escape_attribute(erlang:element(13, Builder))
            ),
            gleam@string_tree:append(_pipe@8, <<"\"/>"/utf8>>);

        {_, _} ->
            gleam@string_tree:new()
    end.

-file("src/sparklinekit/line.gleam", 638).
-spec parse_string_colour(binary(), binary()) -> sparklinekit@internal@color:rgba().
parse_string_colour(Value, Theme_fallback) ->
    case sparklinekit@internal@color:parse_hex(Value) of
        {ok, Rgba} ->
            Rgba;

        {error, _} ->
            sparklinekit@internal@color:parse_or(
                Theme_fallback,
                {rgba, 0, 0, 0, 255}
            )
    end.

-file("src/sparklinekit/line.gleam", 645).
-spec parse_string_colour_or(binary(), sparklinekit@internal@color:rgba()) -> sparklinekit@internal@color:rgba().
parse_string_colour_or(Value, Fallback) ->
    case sparklinekit@internal@color:parse_hex(Value) of
        {ok, Rgba} ->
            Rgba;

        {error, _} ->
            Fallback
    end.

-file("src/sparklinekit/line.gleam", 717).
-spec y_for_padded(float(), float(), float(), float(), float(), float()) -> float().
y_for_padded(Value, Lo, Hi, Top, Bottom, Mid) ->
    case Lo =:= Hi of
        true ->
            Mid;

        false ->
            N = sparklinekit@internal@scale:unit(Value, Lo, Hi),
            Bottom - (N * (Bottom - Top))
    end.

-file("src/sparklinekit/line.gleam", 673).
-spec pixel_points(builder()) -> list({float(), float()}).
pixel_points(Builder) ->
    Width_f = erlang:float(erlang:element(6, Builder)),
    Height_f = erlang:float(erlang:element(7, Builder)),
    Raw_inset = begin
        _pipe = gleam@float:max(
            erlang:element(8, Builder) / 2.0,
            erlang:element(12, Builder) + 1.0
        ),
        gleam@float:max(_pipe, 1.0)
    end,
    Max_inset = gleam@float:min(Width_f / 2.0, Height_f / 2.0),
    Inset = gleam@float:min(Raw_inset, Max_inset),
    Pad_x = Inset,
    Pad_top = Inset,
    Pad_bottom = Inset,
    Usable_w = gleam@float:max(Width_f - (2.0 * Pad_x), +0.0),
    Top_y = Pad_top,
    Bottom_y = gleam@float:max(Height_f - Pad_bottom, Top_y),
    Mid = (Top_y + Bottom_y) / 2.0,
    case erlang:element(2, Builder) of
        [] ->
            [];

        [_] ->
            [{Pad_x, Mid}, {Pad_x + Usable_w, Mid}];

        Values ->
            {Lo, Hi} = sparklinekit@internal@scale:min_max(Values),
            Count = erlang:length(Values),
            Step = case Count > 1 of
                true ->
                    case erlang:float(Count - 1) of
                        +0.0 -> +0.0;
                        -0.0 -> -0.0;
                        Gleam@denominator -> Usable_w / Gleam@denominator
                    end;

                false ->
                    +0.0
            end,
            {Out, _} = gleam@list:fold(
                Values,
                {[], 0},
                fun(Acc, Value) ->
                    {Pts, I} = Acc,
                    X = Pad_x + (erlang:float(I) * Step),
                    Y = y_for_padded(Value, Lo, Hi, Top_y, Bottom_y, Mid),
                    {[{X, Y} | Pts], I + 1}
                end
            ),
            lists:reverse(Out)
    end.

-file("src/sparklinekit/line.gleam", 802).
-spec cubic_at(
    {float(), float()},
    {float(), float()},
    {float(), float()},
    {float(), float()},
    float()
) -> {float(), float()}.
cubic_at(P0, P1, P2, P3, T) ->
    One_minus = 1.0 - T,
    B0 = (One_minus * One_minus) * One_minus,
    B1 = ((3.0 * One_minus) * One_minus) * T,
    B2 = ((3.0 * One_minus) * T) * T,
    B3 = (T * T) * T,
    {X0, Y0} = P0,
    {X1, Y1} = P1,
    {X2, Y2} = P2,
    {X3, Y3} = P3,
    {(((B0 * X0) + (B1 * X1)) + (B2 * X2)) + (B3 * X3),
        (((B0 * Y0) + (B1 * Y1)) + (B2 * Y2)) + (B3 * Y3)}.

-file("src/sparklinekit/line.gleam", 783).
-spec do_bezier_subdivide(
    {float(), float()},
    {float(), float()},
    {float(), float()},
    {float(), float()},
    integer(),
    integer(),
    list({float(), float()})
) -> list({float(), float()}).
do_bezier_subdivide(P0, P1, P2, P3, Step, Total, Acc) ->
    case Step > Total of
        true ->
            lists:reverse(Acc);

        false ->
            T = case erlang:float(Total) of
                +0.0 -> +0.0;
                -0.0 -> -0.0;
                Gleam@denominator -> erlang:float(Step) / Gleam@denominator
            end,
            Point = cubic_at(P0, P1, P2, P3, T),
            do_bezier_subdivide(P0, P1, P2, P3, Step + 1, Total, [Point | Acc])
    end.

-file("src/sparklinekit/line.gleam", 769).
-spec bezier_subdivide(
    {float(), float()},
    {float(), float()},
    {float(), float()},
    {float(), float()},
    integer()
) -> list({float(), float()}).
bezier_subdivide(P0, P1, P2, P3, Steps) ->
    Actual_steps = case Steps < 1 of
        true ->
            1;

        false ->
            Steps
    end,
    do_bezier_subdivide(P0, P1, P2, P3, 1, Actual_steps, []).

-file("src/sparklinekit/line.gleam", 905).
-spec clamp_float(float(), float(), float()) -> float().
clamp_float(Value, Lo, Hi) ->
    case {Value < Lo, Value > Hi} of
        {true, _} ->
            Lo;

        {_, true} ->
            Hi;

        {_, _} ->
            Value
    end.

-file("src/sparklinekit/line.gleam", 863).
-spec fill_area_column(
    sparklinekit@internal@raster:canvas(),
    integer(),
    float(),
    float(),
    float(),
    float(),
    float(),
    sparklinekit@internal@color:rgba(),
    boolean()
) -> sparklinekit@internal@raster:canvas().
fill_area_column(Canvas, Col, X0, Y0, X1, Y1, Height_f, Fill, Gradient) ->
    Cf = erlang:float(Col),
    Span = X1 - X0,
    T = case Span =:= +0.0 of
        true ->
            +0.0;

        false ->
            case Span of
                +0.0 -> +0.0;
                -0.0 -> -0.0;
                Gleam@denominator -> (Cf - X0) / Gleam@denominator
            end
    end,
    Top = Y0 + ((Y1 - Y0) * T),
    Top_clamped = clamp_float(Top, +0.0, Height_f),
    case Gradient of
        false ->
            sparklinekit@internal@raster:fill_rect(
                Canvas,
                Cf,
                Top_clamped,
                1.0,
                Height_f - Top_clamped,
                Fill
            );

        true ->
            sparklinekit@internal@raster:fill_vertical_gradient(
                Canvas,
                Cf,
                Top_clamped,
                Cf + 1.0,
                Height_f,
                Fill,
                sparklinekit@internal@color:with_alpha(Fill, +0.0)
            )
    end.

-file("src/sparklinekit/line.gleam", 913).
-spec draw_stroke_png(
    sparklinekit@internal@raster:canvas(),
    list({float(), float()}),
    float(),
    sparklinekit@internal@color:rgba()
) -> sparklinekit@internal@raster:canvas().
draw_stroke_png(Canvas, Path_points, Stroke_width, Colour) ->
    case Path_points of
        [] ->
            Canvas;

        [{X, Y}] ->
            sparklinekit@internal@raster:draw_line(
                Canvas,
                X - Stroke_width,
                Y,
                X + Stroke_width,
                Y,
                Stroke_width,
                Colour
            );

        _ ->
            _pipe = gleam@list:window_by_2(Path_points),
            gleam@list:fold(
                _pipe,
                Canvas,
                fun(C, Pair) ->
                    {{X0, Y0}, {X1, Y1}} = Pair,
                    sparklinekit@internal@raster:draw_line(
                        C,
                        X0,
                        Y0,
                        X1,
                        Y1,
                        Stroke_width,
                        Colour
                    )
                end
            )
    end.

-file("src/sparklinekit/line.gleam", 940).
-spec draw_spot_png(
    sparklinekit@internal@raster:canvas(),
    list({float(), float()}),
    builder(),
    sparklinekit@internal@color:rgba()
) -> sparklinekit@internal@raster:canvas().
draw_spot_png(Canvas, Anchors, Builder, Fg) ->
    case {erlang:element(12, Builder) > +0.0, last_point(Anchors)} of
        {true, {ok, {X, Y}}} ->
            Spot_colour = parse_string_colour_or(
                erlang:element(13, Builder),
                Fg
            ),
            sparklinekit@internal@raster:fill_circle(
                Canvas,
                X,
                Y,
                erlang:element(12, Builder),
                Spot_colour
            );

        {_, _} ->
            Canvas
    end.

-file("src/sparklinekit/line.gleam", 962).
-spec do_int_range(integer(), integer(), list(integer())) -> list(integer()).
do_int_range(Current, Lo, Acc) ->
    case Current < Lo of
        true ->
            Acc;

        false ->
            do_int_range(Current - 1, Lo, [Current | Acc])
    end.

-file("src/sparklinekit/line.gleam", 955).
-spec int_range(integer(), integer()) -> list(integer()).
int_range(Lo, Hi) ->
    case Lo > Hi of
        true ->
            [];

        false ->
            do_int_range(Hi, Lo, [])
    end.

-file("src/sparklinekit/line.gleam", 844).
-spec fill_segment_columns(
    sparklinekit@internal@raster:canvas(),
    {{float(), float()}, {float(), float()}},
    float(),
    sparklinekit@internal@color:rgba(),
    boolean()
) -> sparklinekit@internal@raster:canvas().
fill_segment_columns(Canvas, Segment, Height_f, Fill, Gradient) ->
    {{X0, Y0}, {X1, Y1}} = Segment,
    case (X1 - X0) =< +0.0 of
        true ->
            Canvas;

        false ->
            Columns = int_range(erlang:round(X0), erlang:round(X1) - 1),
            gleam@list:fold(
                Columns,
                Canvas,
                fun(C, Col) ->
                    fill_area_column(
                        C,
                        Col,
                        X0,
                        Y0,
                        X1,
                        Y1,
                        Height_f,
                        Fill,
                        Gradient
                    )
                end
            )
    end.

-file("src/sparklinekit/line.gleam", 824).
-spec draw_area_png(
    sparklinekit@internal@raster:canvas(),
    list({float(), float()}),
    integer(),
    sparklinekit@internal@color:rgba(),
    boolean()
) -> sparklinekit@internal@raster:canvas().
draw_area_png(Canvas, Path_points, Height, Fill, Gradient) ->
    case Path_points of
        [] ->
            Canvas;

        [_] ->
            Canvas;

        _ ->
            Height_f = erlang:float(Height),
            _pipe = gleam@list:window_by_2(Path_points),
            gleam@list:fold(
                _pipe,
                Canvas,
                fun(C, Pair) ->
                    fill_segment_columns(C, Pair, Height_f, Fill, Gradient)
                end
            )
    end.

-file("src/sparklinekit/line.gleam", 993).
?DOC(
    " Project the builder's `area` field through the `area_enabled`\n"
    " toggle. When the caller has disabled the area fill via\n"
    " `with_area_fill(False)`, the renderer should behave as if no\n"
    " area was configured — but the underlying `area` field still\n"
    " carries the user's last explicit / auto choice so it can be\n"
    " restored by a subsequent `with_area_fill(True)`.\n"
).
-spec effective_area(builder()) -> area_fill().
effective_area(Builder) ->
    case erlang:element(10, Builder) of
        true ->
            erlang:element(9, Builder);

        false ->
            no_area
    end.

-file("src/sparklinekit/line.gleam", 412).
?DOC(
    " Decide whether the SVG output should emit a `<linearGradient>` and\n"
    " reference it via `url(#...)`. The gradient stops are hex literals,\n"
    " so we can only produce a meaningful gradient when the area colour\n"
    " — or its derivation source — actually parses as hex.\n"
).
-spec should_use_gradient(builder()) -> boolean().
should_use_gradient(Builder) ->
    case {erlang:element(14, Builder), effective_area(Builder)} of
        {false, _} ->
            false;

        {_, no_area} ->
            false;

        {true, {area_explicit, C}} ->
            case sparklinekit@internal@color:parse_hex(C) of
                {ok, _} ->
                    true;

                {error, _} ->
                    false
            end;

        {true, area_auto} ->
            case sparklinekit@internal@color:parse_hex(
                sparklinekit@theme:area(erlang:element(3, Builder))
            ) of
                {ok, _} ->
                    true;

                {error, _} ->
                    case sparklinekit@internal@color:parse_hex(
                        erlang:element(4, Builder)
                    ) of
                        {ok, _} ->
                            true;

                        {error, _} ->
                            false
                    end
            end
    end.

-file("src/sparklinekit/line.gleam", 367).
-spec gradient_top_colour(builder()) -> sparklinekit@internal@color:rgba().
gradient_top_colour(Builder) ->
    Fg = parse_string_colour(
        erlang:element(4, Builder),
        sparklinekit@theme:foreground(erlang:element(3, Builder))
    ),
    case effective_area(Builder) of
        {area_explicit, C} ->
            parse_string_colour_or(C, Fg);

        area_auto ->
            case sparklinekit@internal@color:parse_hex(
                sparklinekit@theme:area(erlang:element(3, Builder))
            ) of
                {ok, C@1} ->
                    C@1;

                {error, _} ->
                    sparklinekit@internal@color:with_alpha(Fg, 0.22)
            end;

        no_area ->
            Fg
    end.

-file("src/sparklinekit/line.gleam", 352).
?DOC(
    " Compute the gradient `<linearGradient>` id for a builder.\n"
    "\n"
    " The id includes the resolved top colour (as an 8-digit hex) so two\n"
    " charts that share dimensions and value count but use different\n"
    " themes get distinct ids — without this, browsers inlining multiple\n"
    " SVGs into the same page would reuse the first gradient definition\n"
    " for every subsequent chart.\n"
).
-spec gradient_id(builder()) -> binary().
gradient_id(Builder) ->
    Top = gradient_top_colour(Builder),
    Top_suffix = begin
        _pipe = sparklinekit@internal@color:to_hex_rgba(Top),
        gleam@string:replace(_pipe, <<"#"/utf8>>, <<""/utf8>>)
    end,
    <<<<<<<<<<<<<<"sparklinekit-area-"/utf8,
                                (erlang:integer_to_binary(
                                    erlang:element(6, Builder)
                                ))/binary>>/binary,
                            "x"/utf8>>/binary,
                        (erlang:integer_to_binary(erlang:element(7, Builder)))/binary>>/binary,
                    "-"/utf8>>/binary,
                (erlang:integer_to_binary(
                    erlang:length(erlang:element(2, Builder))
                ))/binary>>/binary,
            "-"/utf8>>/binary,
        Top_suffix/binary>>.

-file("src/sparklinekit/line.gleam", 380).
-spec gradient_defs_svg(builder(), list({float(), float()})) -> gleam@string_tree:string_tree().
gradient_defs_svg(Builder, Points) ->
    case {should_use_gradient(Builder), Points} of
        {false, _} ->
            gleam@string_tree:new();

        {_, []} ->
            gleam@string_tree:new();

        {_, [_]} ->
            gleam@string_tree:new();

        {true, _} ->
            Top = gradient_top_colour(Builder),
            Bottom = sparklinekit@internal@color:with_alpha(Top, +0.0),
            Top_attr = sparklinekit@internal@color:to_hex_rgba(Top),
            Bottom_attr = sparklinekit@internal@color:to_hex_rgba(Bottom),
            _pipe = gleam@string_tree:new(),
            _pipe@1 = gleam@string_tree:append(
                _pipe,
                <<"<defs><linearGradient id=\""/utf8>>
            ),
            _pipe@2 = gleam@string_tree:append(_pipe@1, gradient_id(Builder)),
            _pipe@3 = gleam@string_tree:append(
                _pipe@2,
                <<"\" x1=\"0\" y1=\"0\" x2=\"0\" y2=\"1\">"/utf8>>
            ),
            _pipe@4 = gleam@string_tree:append(
                _pipe@3,
                <<"<stop offset=\"0%\" stop-color=\""/utf8>>
            ),
            _pipe@5 = gleam@string_tree:append(_pipe@4, Top_attr),
            _pipe@6 = gleam@string_tree:append(_pipe@5, <<"\"/>"/utf8>>),
            _pipe@7 = gleam@string_tree:append(
                _pipe@6,
                <<"<stop offset=\"100%\" stop-color=\""/utf8>>
            ),
            _pipe@8 = gleam@string_tree:append(_pipe@7, Bottom_attr),
            _pipe@9 = gleam@string_tree:append(_pipe@8, <<"\"/>"/utf8>>),
            gleam@string_tree:append(
                _pipe@9,
                <<"</linearGradient></defs>"/utf8>>
            )
    end.

-file("src/sparklinekit/line.gleam", 630).
-spec auto_area_color(binary()) -> binary().
auto_area_color(Stroke_color) ->
    case sparklinekit@internal@color:parse_hex(Stroke_color) of
        {ok, Rgba} ->
            sparklinekit@internal@color:to_hex_rgba(
                sparklinekit@internal@color:with_alpha(Rgba, 0.22)
            );

        {error, _} ->
            Stroke_color
    end.

-file("src/sparklinekit/line.gleam", 446).
-spec non_gradient_area_fill(builder()) -> {binary(), binary()}.
non_gradient_area_fill(Builder) ->
    case effective_area(Builder) of
        no_area ->
            {escape_attribute(erlang:element(4, Builder)), <<""/utf8>>};

        {area_explicit, C} ->
            {escape_attribute(C), <<""/utf8>>};

        area_auto ->
            case sparklinekit@internal@color:parse_hex(
                erlang:element(4, Builder)
            ) of
                {ok, _} ->
                    {escape_attribute(
                            auto_area_color(erlang:element(4, Builder))
                        ),
                        <<""/utf8>>};

                {error, _} ->
                    Opacity_attr = <<<<" fill-opacity=\""/utf8,
                            (gleam_stdlib:float_to_string(0.22))/binary>>/binary,
                        "\""/utf8>>,
                    {escape_attribute(erlang:element(4, Builder)), Opacity_attr}
            end
    end.

-file("src/sparklinekit/line.gleam", 439).
?DOC(
    " Resolve the `fill` attribute string and an optional\n"
    " `fill-opacity` fragment for the area path. The opacity fragment\n"
    " is non-empty only when the area colour is a CSS keyword such as\n"
    " `currentColor` that can't carry alpha inside its own hex form —\n"
    " in that case the renderer emits the literal colour and a\n"
    " separate `fill-opacity` so the area still looks translucent.\n"
).
-spec area_fill_pair(builder()) -> {binary(), binary()}.
area_fill_pair(Builder) ->
    case should_use_gradient(Builder) of
        true ->
            {<<<<"url(#"/utf8, (gradient_id(Builder))/binary>>/binary,
                    ")"/utf8>>,
                <<""/utf8>>};

        false ->
            non_gradient_area_fill(Builder)
    end.

-file("src/sparklinekit/line.gleam", 462).
-spec area_svg(builder(), list({float(), float()})) -> gleam@string_tree:string_tree().
area_svg(Builder, Points) ->
    case {effective_area(Builder), Points} of
        {no_area, _} ->
            gleam@string_tree:new();

        {_, []} ->
            gleam@string_tree:new();

        {_, [_]} ->
            gleam@string_tree:new();

        {_, _} ->
            {Fill_attr, Opacity_attr} = area_fill_pair(Builder),
            D = path_data(Points, erlang:element(11, Builder)),
            Bottom = erlang:float(erlang:element(7, Builder)),
            Last_x = case last_point(Points) of
                {ok, {X, _}} ->
                    X;

                {error, _} ->
                    erlang:float(erlang:element(6, Builder))
            end,
            First_x = case Points of
                [{X@1, _} | _] ->
                    X@1;

                _ ->
                    +0.0
            end,
            Close = <<<<<<<<<<<<<<<<" L "/utf8,
                                            (sparklinekit@internal@format:coord(
                                                Last_x
                                            ))/binary>>/binary,
                                        " "/utf8>>/binary,
                                    (sparklinekit@internal@format:coord(Bottom))/binary>>/binary,
                                " L "/utf8>>/binary,
                            (sparklinekit@internal@format:coord(First_x))/binary>>/binary,
                        " "/utf8>>/binary,
                    (sparklinekit@internal@format:coord(Bottom))/binary>>/binary,
                " Z"/utf8>>,
            _pipe = gleam@string_tree:new(),
            _pipe@1 = gleam@string_tree:append(_pipe, <<"<path fill=\""/utf8>>),
            _pipe@2 = gleam@string_tree:append(_pipe@1, Fill_attr),
            _pipe@3 = gleam@string_tree:append(_pipe@2, <<"\""/utf8>>),
            _pipe@4 = gleam@string_tree:append(_pipe@3, Opacity_attr),
            _pipe@5 = gleam@string_tree:append(
                _pipe@4,
                <<" stroke=\"none\" d=\""/utf8>>
            ),
            _pipe@6 = gleam@string_tree:append(_pipe@5, D),
            _pipe@7 = gleam@string_tree:append(_pipe@6, Close),
            gleam@string_tree:append(_pipe@7, <<"\"/>"/utf8>>)
    end.

-file("src/sparklinekit/line.gleam", 256).
?DOC(
    " Render the builder to a self-contained `<svg>` element string.\n"
    "\n"
    " The `<svg>` carries `width`, `height`, and `viewBox` attributes so\n"
    " the chart sizes itself correctly both when embedded inside a\n"
    " CSS-sized container and when displayed standalone.\n"
).
-spec to_svg(builder()) -> binary().
to_svg(Builder) ->
    Points = pixel_points(Builder),
    Defs_layer = gradient_defs_svg(Builder, Points),
    Bg_layer = background_rect(
        erlang:element(6, Builder),
        erlang:element(7, Builder),
        erlang:element(5, Builder)
    ),
    Area_layer = area_svg(Builder, Points),
    Line_layer = stroke_svg(Builder, Points),
    Spot_layer = spot_svg(Builder, Points),
    _pipe = gleam@string_tree:new(),
    _pipe@1 = gleam@string_tree:append(
        _pipe,
        <<"<svg xmlns=\"http://www.w3.org/2000/svg\" width=\""/utf8>>
    ),
    _pipe@2 = gleam@string_tree:append(
        _pipe@1,
        erlang:integer_to_binary(erlang:element(6, Builder))
    ),
    _pipe@3 = gleam@string_tree:append(_pipe@2, <<"\" height=\""/utf8>>),
    _pipe@4 = gleam@string_tree:append(
        _pipe@3,
        erlang:integer_to_binary(erlang:element(7, Builder))
    ),
    _pipe@5 = gleam@string_tree:append(_pipe@4, <<"\" viewBox=\"0 0 "/utf8>>),
    _pipe@6 = gleam@string_tree:append(
        _pipe@5,
        erlang:integer_to_binary(erlang:element(6, Builder))
    ),
    _pipe@7 = gleam@string_tree:append(_pipe@6, <<" "/utf8>>),
    _pipe@8 = gleam@string_tree:append(
        _pipe@7,
        erlang:integer_to_binary(erlang:element(7, Builder))
    ),
    _pipe@9 = gleam@string_tree:append(
        _pipe@8,
        <<"\" preserveAspectRatio=\"none\">"/utf8>>
    ),
    _pipe@10 = gleam_stdlib:iodata_append(_pipe@9, Defs_layer),
    _pipe@11 = gleam_stdlib:iodata_append(_pipe@10, Bg_layer),
    _pipe@12 = gleam_stdlib:iodata_append(_pipe@11, Area_layer),
    _pipe@13 = gleam_stdlib:iodata_append(_pipe@12, Line_layer),
    _pipe@14 = gleam_stdlib:iodata_append(_pipe@13, Spot_layer),
    _pipe@15 = gleam@string_tree:append(_pipe@14, <<"</svg>"/utf8>>),
    unicode:characters_to_binary(_pipe@15).

-file("src/sparklinekit/line.gleam", 652).
-spec resolve_area_colour(
    area_fill(),
    sparklinekit@internal@color:rgba(),
    sparklinekit@theme:theme()
) -> {ok, sparklinekit@internal@color:rgba()} | {error, nil}.
resolve_area_colour(Area, Stroke_fg, Theme) ->
    case Area of
        no_area ->
            {error, nil};

        area_auto ->
            case sparklinekit@internal@color:parse_hex(
                sparklinekit@theme:area(Theme)
            ) of
                {ok, C} ->
                    {ok, C};

                {error, _} ->
                    {ok,
                        sparklinekit@internal@color:with_alpha(Stroke_fg, 0.22)}
            end;

        {area_explicit, C@1} ->
            case sparklinekit@internal@color:parse_hex(C@1) of
                {ok, Rgba} ->
                    {ok, Rgba};

                {error, _} ->
                    {ok,
                        sparklinekit@internal@color:with_alpha(Stroke_fg, 0.22)}
            end
    end.

-file("src/sparklinekit/line.gleam", 99).
?DOC(
    " Start a new line sparkline builder from a list of floats.\n"
    "\n"
    " Defaults: 240x60 viewBox, `currentColor` stroke (inherits the\n"
    " surrounding CSS colour), 2.0 stroke width, no background fill,\n"
    " no area fill, no smoothing, no end-point spot.\n"
).
-spec new(list(float())) -> builder().
new(Values) ->
    Base = sparklinekit@theme:default(),
    {builder,
        Values,
        Base,
        sparklinekit@theme:foreground(Base),
        sparklinekit@theme:background(Base),
        240,
        60,
        2.0,
        no_area,
        false,
        +0.0,
        +0.0,
        sparklinekit@theme:foreground(Base),
        true}.

-file("src/sparklinekit/line.gleam", 121).
?DOC(
    " Start a builder from a list of `Int` values. Equivalent to\n"
    " `new(list.map(values, int.to_float))`, exposed so the call site\n"
    " stays free of integer-to-float adapters.\n"
).
-spec new_ints(list(integer())) -> builder().
new_ints(Values) ->
    new(gleam@list:map(Values, fun erlang:float/1)).

-file("src/sparklinekit/line.gleam", 749).
-spec sample_bezier({float(), float()}, list({float(), float()}), float()) -> list({float(),
    float()}).
sample_bezier(Start, Rest, Factor) ->
    {_, Samples} = gleam@list:fold(
        Rest,
        {Start, []},
        fun(State, P) ->
            {Prev, Acc} = State,
            {X0, Y0} = Prev,
            {X1, Y1} = P,
            Dx = (X1 - X0) * Factor,
            C1 = {X0 + Dx, Y0},
            C2 = {X1 - Dx, Y1},
            Segment = bezier_subdivide(Prev, C1, C2, P, 16),
            {P, lists:append(Acc, Segment)}
        end
    ),
    Samples.

-file("src/sparklinekit/line.gleam", 734).
-spec stroke_path_points(builder(), list({float(), float()})) -> list({float(),
    float()}).
stroke_path_points(Builder, Anchors) ->
    case {erlang:element(11, Builder) =< +0.0, Anchors} of
        {_, []} ->
            [];

        {_, [_]} ->
            Anchors;

        {true, _} ->
            Anchors;

        {false, [Head | Rest]} ->
            [Head | sample_bezier(Head, Rest, erlang:element(11, Builder))]
    end.

-file("src/sparklinekit/line.gleam", 295).
?DOC(
    " Render the builder to PNG bytes (8-bit RGBA truecolor).\n"
    "\n"
    " `with_size` doubles as the pixel size for PNG — a 240x60 builder\n"
    " produces a 240x60 image. The canvas starts at the background\n"
    " colour (transparent when `background == \"none\"`) and the stroke\n"
    " is drawn with Xiaolin Wu anti-aliasing.\n"
    "\n"
    " The PNG IDAT payload is written using DEFLATE's uncompressed\n"
    " \"store\" blocks (no Huffman coding) so the encoder stays pure\n"
    " Gleam with zero FFI. As a result the output is roughly\n"
    " `width * height * 4` bytes regardless of how uniform the image\n"
    " is — for visual size context, prefer SVG.\n"
).
-spec to_png(builder()) -> bitstring().
to_png(Builder) ->
    Fg = parse_string_colour(
        erlang:element(4, Builder),
        sparklinekit@theme:foreground(erlang:element(3, Builder))
    ),
    Bg = case erlang:element(5, Builder) of
        <<"none"/utf8>> ->
            {rgba, 0, 0, 0, 0};

        Other ->
            parse_string_colour_or(Other, {rgba, 0, 0, 0, 0})
    end,
    Area_colour = resolve_area_colour(
        effective_area(Builder),
        Fg,
        erlang:element(3, Builder)
    ),
    Points = pixel_points(Builder),
    Stroke_path = stroke_path_points(Builder, Points),
    Canvas = sparklinekit@internal@raster:new(
        erlang:element(6, Builder),
        erlang:element(7, Builder),
        Bg
    ),
    Canvas@1 = case Area_colour of
        {error, _} ->
            Canvas;

        {ok, C} ->
            draw_area_png(
                Canvas,
                Stroke_path,
                erlang:element(7, Builder),
                C,
                erlang:element(14, Builder)
            )
    end,
    Canvas@2 = draw_stroke_png(
        Canvas@1,
        Stroke_path,
        erlang:element(8, Builder),
        Fg
    ),
    Canvas@3 = draw_spot_png(Canvas@2, Points, Builder, Fg),
    sparklinekit@internal@png:encode(
        sparklinekit@internal@raster:to_grid(Canvas@3),
        erlang:element(6, Builder),
        erlang:element(7, Builder)
    ).

-file("src/sparklinekit/line.gleam", 969).
-spec clamp_stroke_width(float()) -> float().
clamp_stroke_width(Value) ->
    case Value > 1.0e4 of
        true ->
            1.0e4;

        false ->
            Value
    end.

-file("src/sparklinekit/line.gleam", 244).
?DOC(
    " Set the stroke width in user units. Non-positive values fall\n"
    " back to a hairline (`0.5`); excessively large values are clamped\n"
    " to `1.0e4` so the raster's per-pixel perpendicular sweep cannot\n"
    " stall the renderer.\n"
).
-spec with_stroke_width(builder(), float()) -> builder().
with_stroke_width(Builder, Stroke_width) ->
    {builder,
        erlang:element(2, Builder),
        erlang:element(3, Builder),
        erlang:element(4, Builder),
        erlang:element(5, Builder),
        erlang:element(6, Builder),
        erlang:element(7, Builder),
        clamp_stroke_width(positive_float_or(Stroke_width, 0.5)),
        erlang:element(9, Builder),
        erlang:element(10, Builder),
        erlang:element(11, Builder),
        erlang:element(12, Builder),
        erlang:element(13, Builder),
        erlang:element(14, Builder)}.

-file("src/sparklinekit/line.gleam", 976).
-spec clamp_spot_radius(float()) -> float().
clamp_spot_radius(Value) ->
    case Value < +0.0 of
        true ->
            +0.0;

        false ->
            case Value > 1.0e4 of
                true ->
                    1.0e4;

                false ->
                    Value
            end
    end.

-file("src/sparklinekit/line.gleam", 169).
?DOC(
    " Draw a filled circle at the last data point. `radius` of `0.0`\n"
    " turns the spot off (the default).\n"
    "\n"
    " Values are clamped to `[0.0, 1.0e4]` so an adversarial radius\n"
    " cannot stall the raster's disc fill (which iterates the bounding\n"
    " square pixel-by-pixel).\n"
).
-spec with_spot(builder(), float()) -> builder().
with_spot(Builder, Radius) ->
    {builder,
        erlang:element(2, Builder),
        erlang:element(3, Builder),
        erlang:element(4, Builder),
        erlang:element(5, Builder),
        erlang:element(6, Builder),
        erlang:element(7, Builder),
        erlang:element(8, Builder),
        erlang:element(9, Builder),
        erlang:element(10, Builder),
        erlang:element(11, Builder),
        clamp_spot_radius(Radius),
        erlang:element(13, Builder),
        erlang:element(14, Builder)}.