PrettyGraphs

Hex.pm VersionHex.pm DownloadsLicenseDocs

PrettyGraphs is a tiny Elixir library for generating good‑looking SVG/HTML charts suitable for Phoenix LiveView and other HTML renderers.

The initial release provides a horizontal bar chart with:

Installation

Add pretty_graphs to your dependencies in mix.exs:

def deps do
  [
    {:pretty_graphs, "~> 0.1.0"}
  ]
end

Then fetch your dependencies:

mix deps.get

Quick start

iex> svg = PrettyGraphs.bar_chart(
...>   [{"Apples", 10}, {"Bananas", 25}, {"Cherries", 18}],
...>   title: "Fruit Sales"
...> )
iex> String.starts_with?(svg, "<svg")
true

The returned value is a standalone <svg> element as a string. You can embed it anywhere HTML is accepted.

Phoenix LiveView usage

Render the returned SVG string with Phoenix.HTML.raw/1 so it isn’t escaped. Assign it in mount/3 (or a handler) and render in your template:

defmodule MyAppWeb.DemoLive do
  use MyAppWeb, :live_view

  @impl true
  def mount(_params, _session, socket) do
    data = [{"Apples", 10}, {"Bananas", 25}, {"Cherries", 18}]
    svg =
      PrettyGraphs.bar_chart(data,
        title: "Fruit Sales",
        gradient: [from: "#4f46e5", to: "#a78bfa", direction: :right]
      )

    {:ok, assign(socket, bar_svg: svg)}
  end

  @impl true
  def render(assigns) do
    ~H"""
    <div class="w-full max-w-3xl">
      <%= Phoenix.HTML.raw(@bar_svg) %>
    </div>
    """
  end
end

Adding LiveView events (phx-click, phx-value-*, hooks)

You can attach Phoenix attributes and classes to either:

Per-bar attributes/classes can also be set at the data point level, so you can, for example, attach a global phx-click to every bar and a specific phx-value-* to each item.

Global SVG and bar attributes/classes:

data = [{"Apples", 10}, {"Bananas", 25}, {"Cherries", 18}]

svg =
  PrettyGraphs.bar_chart(
    data,
    title: "Interactive Bars",
    # Attach a hook to the root SVG:
    svg_attrs: %{"phx-hook" => "Chart"},
    svg_class: ["w-full", "h-auto"],
    # Attach a click handler to each bar rect and add classes:
    bar_attrs: [data_role: "bar", %{"phx-click" => "bar_clicked"}],
    bar_class: ["cursor-pointer", "hover:opacity-80"]
  )

Per-data-point attributes and classes using a 3-tuple:

data = [
  {"Apples", 10, [attrs: %{"phx-value-fruit" => "apples"}]},
  {"Bananas", 25, [attrs: %{"phx-value-fruit" => "bananas"}]},
  {"Cherries", 18, [attrs: %{"phx-value-fruit" => "cherries"}]}
]

svg =
  PrettyGraphs.bar_chart(
    data,
    title: "Per-bar values + global click",
    # Global click for all bars:
    bar_attrs: [%{"phx-click" => "bar_clicked"}],
    bar_class: "cursor-pointer hover:opacity-80"
  )

Per-data-point attributes using the map shape:

data = %{
  "Apples" => {10, [attrs: %{"phx-value-fruit" => "apples"}, class: "fill-lime-600"]},
  "Bananas" => {25, [attrs: %{"phx-value-fruit" => "bananas"}]},
  "Cherries" => {18, [attrs: %{"phx-value-fruit" => "cherries"}]}
}

svg =
  PrettyGraphs.bar_chart(
    data,
    title: "Map shape with per-point attrs/classes",
    bar_attrs: [%{"phx-click" => "bar_clicked"}],
    bar_class: "cursor-pointer hover:opacity-80"
  )

In your LiveView, handle the event as usual:

@impl true
def handle_event("bar_clicked", %{"fruit" => fruit}, socket) do
  # Do something with fruit e.g., "apples"
  {:noreply, socket}
end

Notes:

Data shapes

You can pass data in any of the following forms:

Values can be integers, floats, or numeric strings (e.g., "42", "3.14").

Bar chart API

PrettyGraphs.bar_chart(data, opts \\ [])

Options and defaults

Example: Theming and formatting

data = %{"One" => 12, "Two" => 7.5, "Three" => 19.2}

svg =
  PrettyGraphs.bar_chart(data,
    title: "Custom Theme",
    width: 720,
    bar_height: 24,
    bar_gap: 10,
    bar_radius: 8,
    bar_color: "#0ea5e9",
    label_color: "#374151",
    value_color: "#111827",
    title_color: "#111827",
    background: "#ffffff",
    font_family: "Inter, ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial, sans-serif",
    value_formatter: fn v -> :erlang.float_to_binary(v * 1.0, [:compact, {:decimals, 1}]) <> "%" end
  )

Example: Gradient fill

data = [{"Apples", 10}, {"Bananas", 25}, {"Cherries", 18}]

svg =
  PrettyGraphs.bar_chart(data,
    title: "Gradient Bars",
    width: 640,
    gradient: [from: "#4f46e5", to: "#a78bfa", direction: :right]
  )

You can change the direction to :down for a vertical gradient:

PrettyGraphs.bar_chart(data, gradient: [from: "#4f46e5", to: "#a78bfa", direction: :down])

Diagonal directions are also supported: :down_right, :down_left, :up_right, and :up_left.

# Top-left -> bottom-right
PrettyGraphs.bar_chart(data, gradient: [from: "#0ea5e9", to: "#a78bfa", direction: :down_right])

# Top-right -> bottom-left
PrettyGraphs.bar_chart(data, gradient: [from: "#0ea5e9", to: "#a78bfa", direction: :down_left])

# Bottom-left -> top-right
PrettyGraphs.bar_chart(data, gradient: [from: "#0ea5e9", to: "#a78bfa", direction: :up_right])

# Bottom-right -> top-left
PrettyGraphs.bar_chart(data, gradient: [from: "#0ea5e9", to: "#a78bfa", direction: :up_left])

IEx-ready snippet you can paste:

iex> data = [{"Apples", 10}, {"Bananas", 25}, {"Cherries", 18}]
iex> svg = PrettyGraphs.bar_chart(data, title: "Diagonal Gradient", gradient: [from: "#0ea5e9", to: "#a78bfa", direction: :down_right])
iex> String.starts_with?(svg, "<svg")
true

Defaults tuned for LiveView

Accessibility

Testing

Run the test suite with:

mix test

example

Roadmap

Contributions and suggestions are welcome!

License

MIT