defmodule Pdf.Component.Metric do
@moduledoc """
Metric comparison component for PDF documents.
Renders a before/after or current vs previous value with delta indicator.
Useful for reports and dashboards.
## Examples
doc |> Pdf.Component.Metric.render({50, 700}, %{
label: "Revenue",
current: "$12,450",
previous: "$10,200",
delta: "+22%",
delta_direction: :up
})
"""
@default_font "Helvetica"
@default_width 200
@default_label_color {0.45, 0.45, 0.45}
@default_value_color {0.1, 0.1, 0.1}
@default_up_color {0.2, 0.7, 0.3}
@default_down_color {0.8, 0.2, 0.2}
@default_neutral_color {0.5, 0.5, 0.5}
@doc """
Render a metric at `{x, y}`.
## Style options
- `:label` — metric name
- `:current` — current/primary value (large)
- `:previous` — previous value (small, muted)
- `:delta` — change string (e.g. "+22%")
- `:delta_direction` — `:up`, `:down`, or `:neutral`
- `:width` — component width (default `200`)
- `:font` — font name
- `:background` — optional background color
- `:border_radius` — corner radius (default `6`)
"""
def render(doc, {x, y}, style \\ %{}) do
label = Map.get(style, :label, "")
current = Map.get(style, :current, "0")
previous = Map.get(style, :previous)
delta = Map.get(style, :delta)
direction = Map.get(style, :delta_direction, :neutral)
width = Map.get(style, :width, @default_width)
font = Map.get(style, :font, @default_font)
label_color = Map.get(style, :label_color, @default_label_color)
value_color = Map.get(style, :value_color, @default_value_color)
bg = Map.get(style, :background)
radius = Map.get(style, :border_radius, 6)
height = 70
padding = 10
delta_color = case direction do
:up -> Map.get(style, :up_color, @default_up_color)
:down -> Map.get(style, :down_color, @default_down_color)
_ -> Map.get(style, :neutral_color, @default_neutral_color)
end
# Background
doc =
if bg do
doc
|> Pdf.save_state()
|> Pdf.set_fill_color(bg)
|> Pdf.rounded_rectangle({x, y - height}, {width, height}, radius)
|> Pdf.fill()
|> Pdf.restore_state()
else
doc
end
# Label
doc =
doc
|> Pdf.set_font(font, 9)
|> Pdf.set_fill_color(label_color)
|> Pdf.text_at({x + padding, y - 14}, label)
# Current value (large)
doc =
doc
|> Pdf.set_font(font, 20, bold: true)
|> Pdf.set_fill_color(value_color)
|> Pdf.text_at({x + padding, y - 38}, current)
# Delta
doc =
if delta do
arrow = case direction do
:up -> "^ "
:down -> "v "
_ -> ""
end
doc
|> Pdf.set_font(font, 10, bold: true)
|> Pdf.set_fill_color(delta_color)
|> Pdf.text_at({x + padding, y - 56}, arrow <> delta)
else
doc
end
# Previous value
if previous do
prev_text = "vs #{previous}"
prev_w = String.length(prev_text) * 9 * 0.5
doc
|> Pdf.set_font(font, 9)
|> Pdf.set_fill_color(label_color)
|> Pdf.text_at({x + width - padding - prev_w, y - 56}, prev_text)
else
doc
end
end
end