-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" " `` 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 `` 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, <<<<<<<>/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, <>} 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 -> <>; false -> <> 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, <<"> ), _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, <<"> ), _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, <<">), _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 `` 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 `` 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, <<"> ), _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, <<"> ), _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, <<"> ), _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, <<""/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, <<">), _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 `` element string.\n" "\n" " The `` 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, <<"> ), _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, <<""/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)}.