Chart

Code

Chart module · lib/phx_demo/examples/chart.ex
defmodule PhxDemo.Examples.Chart do
  def render do
    width = 600
    height = 400
    padding = 60

    data = [
      {"2018", 45},
      {"2019", 62},
      {"2020", 78},
      {"2021", 95},
      {"2022", 120},
      {"2023", 155},
      {"2024", 190},
      {"2025", 230}
    ]

    max_val = data |> Enum.map(&elem(&1, 1)) |> Enum.max()
    bar_count = length(data)
    chart_w = width - padding * 2
    chart_h = height - padding * 2
    bar_w = chart_w / bar_count * 0.7
    gap = chart_w / bar_count * 0.3

    colors = [
      "#6366f1",
      "#8b5cf6",
      "#a78bfa",
      "#c084fc",
      "#d946ef",
      "#ec4899",
      "#f43f5e",
      "#ef4444"
    ]

    canvas =
      Easel.new(width, height)
      |> Easel.set_fill_style("#fafafa")
      |> Easel.fill_rect(0, 0, width, height)
      |> Easel.set_fill_style("#1f2937")
      |> Easel.set_font("bold 18px sans-serif")
      |> Easel.set_text_align("center")
      |> Easel.fill_text("Elixir Popularity Index", width / 2, 30)
      |> Easel.set_stroke_style("#9ca3af")
      |> Easel.set_line_width(1)
      |> Easel.begin_path()
      |> Easel.move_to(padding, padding)
      |> Easel.line_to(padding, height - padding)
      |> Easel.line_to(width - padding, height - padding)
      |> Easel.stroke()

    canvas =
      Enum.reduce(0..4, canvas, fn i, acc ->
        y = padding + chart_h * (1 - i / 4)
        val = round(max_val * i / 4)

        acc
        |> Easel.set_stroke_style("#e5e7eb")
        |> Easel.set_line_width(0.5)
        |> Easel.begin_path()
        |> Easel.move_to(padding, y)
        |> Easel.line_to(width - padding, y)
        |> Easel.stroke()
        |> Easel.set_fill_style("#6b7280")
        |> Easel.set_font("12px sans-serif")
        |> Easel.set_text_align("right")
        |> Easel.fill_text("#{val}", padding - 8, y + 4)
      end)

    data
    |> Enum.with_index()
    |> Enum.reduce(canvas, fn {{label, value}, i}, acc ->
      x = padding + i * (bar_w + gap) + gap / 2
      bar_h = value / max_val * chart_h
      y = height - padding - bar_h
      color = Enum.at(colors, i)

      acc
      |> Easel.set_fill_style(color)
      |> Easel.fill_rect(x, y, bar_w, bar_h)
      |> Easel.set_stroke_style("rgba(0,0,0,0.1)")
      |> Easel.set_line_width(1)
      |> Easel.stroke_rect(x, y, bar_w, bar_h)
      |> Easel.set_fill_style("#374151")
      |> Easel.set_font("bold 12px sans-serif")
      |> Easel.set_text_align("center")
      |> Easel.fill_text("#{value}", x + bar_w / 2, y - 8)
      |> Easel.set_fill_style("#6b7280")
      |> Easel.set_font("12px sans-serif")
      |> Easel.fill_text(label, x + bar_w / 2, height - padding + 20)
    end)
    |> Easel.render()
  end
end