-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>>, <<"&"/utf8>>),
_pipe@2 = gleam@string:replace(_pipe@1, <<"\""/utf8>>, <<"""/utf8>>),
_pipe@3 = gleam@string:replace(_pipe@2, <<"<"/utf8>>, <<"<"/utf8>>),
gleam@string:replace(_pipe@3, <<">"/utf8>>, <<">"/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)}.