Add data visualizations to slides with Podium.add_chart/4. Podium supports 29 chart types with editable data backed by embedded Excel workbooks -- your audience can double-click any chart in PowerPoint and modify the underlying data.

Try it yourself

Run mix run demos/charts.exs to generate a presentation with all the examples from this guide.

alias Podium.Chart.ChartData

chart_data =
  ChartData.new()
  |> ChartData.add_categories(["Q1", "Q2", "Q3", "Q4"])
  |> ChartData.add_series("Revenue", [12_500, 14_600, 15_200, 18_100])

slide = Podium.add_chart(slide, :column_clustered, chart_data,
  x: {0.5, :inches}, y: {1, :inches},
  width: {9, :inches}, height: {5, :inches})

The Chart Data Model

Every chart starts with a data structure that defines the categories and series. Podium provides three data modules, each for a different family of chart types:

ModuleUsed ForCategories
Podium.Chart.ChartDataColumn, bar, line, pie, area, doughnut, radarNamed categories (strings)
Podium.Chart.XyChartDataScatter chartsNumeric X values per series
Podium.Chart.BubbleChartDataBubble chartsNumeric X values + bubble sizes

ChartData

Podium.Chart.ChartData is the most common. You build it by adding categories (shared across all series) and then adding one or more named series with numeric values.

alias Podium.Chart.ChartData

data =
  ChartData.new()
  |> ChartData.add_categories(["Engineering", "Marketing", "Sales", "Support"])
  |> ChartData.add_series("Headcount", [230, 85, 120, 65], color: "4472C4")
  |> ChartData.add_series("Budget ($K)", [4200, 2100, 3500, 1800], color: "ED7D31")

Each series accepts these options:

OptionTypeDefaultDescription
:colorString.t()nilHex RGB color for the series
:patternkeywordnilPattern fill (see Series Formatting)
:markerkeywordnilMarker options for line/scatter charts
:point_colorsmap%{}Per-point color overrides by index
:point_formatsmap%{}Per-point fill/line overrides by index
:data_labelsmapnilPer-point data label overrides by index

A ChartData struct supports up to 25 series. All values must be numbers.

Adding a Chart

Use Podium.add_chart/4 to place a chart on a slide. The function takes the slide, chart type atom, chart data, and a keyword list of options. It returns the updated slide.

slide = Podium.add_chart(slide, :bar_clustered, data,
  x: {0.5, :inches}, y: {1, :inches},
  width: {12, :inches}, height: {5.5, :inches},
  title: "Department Overview",
  legend: :bottom)

Column clustered chart with axis config and data labels

OptionTypeDefaultDescription
:xemu_specrequiredHorizontal position
:yemu_specrequiredVertical position
:widthemu_specrequiredChart width
:heightemu_specrequiredChart height
:titleString.t() or keywordnilChart title
:legendatom or keyword or falsenilLegend configuration
:data_labelslist or keyword[]Data label configuration
:category_axiskeyword[]Category axis options
:value_axiskeyword[]Value axis options

Category Charts

Category charts use ChartData with string categories on one axis and numeric values on the other. Here are the available types grouped by family.

Column and Bar Charts

Column charts display vertical bars; bar charts display horizontal bars.

TypeDescription
:column_clusteredSide-by-side vertical bars
:column_stackedStacked vertical bars
:column_stacked_100Stacked vertical bars normalized to 100%
:bar_clusteredSide-by-side horizontal bars
:bar_stackedStacked horizontal bars
:bar_stacked_100Stacked horizontal bars normalized to 100%
# Stacked bar chart showing channel breakdown
channel_data =
  ChartData.new()
  |> ChartData.add_categories(["Jan", "Feb", "Mar", "Apr", "May", "Jun"])
  |> ChartData.add_series("Email", [250, 230, 210, 200, 185, 170], color: "4472C4")
  |> ChartData.add_series("Chat", [180, 200, 220, 250, 280, 310], color: "ED7D31")
  |> ChartData.add_series("Phone", [100, 95, 90, 85, 80, 75], color: "A5A5A5")

slide = Podium.add_chart(slide, :bar_stacked, channel_data,
  x: {0.5, :inches}, y: {1, :inches},
  width: {12, :inches}, height: {5.5, :inches},
  title: "Support Tickets by Channel",
  legend: :right)

Stacked bar chart showing channel breakdown

Line Charts

TypeDescription
:lineLines without markers
:line_markersLines with data point markers
:line_stackedStacked lines
:line_markers_stackedStacked lines with markers
:line_stacked_100Stacked lines normalized to 100%
:line_markers_stacked_100Stacked lines with markers, normalized to 100%
trend_data =
  ChartData.new()
  |> ChartData.add_categories(["Jan", "Feb", "Mar", "Apr", "May", "Jun"])
  |> ChartData.add_series("Web", [45, 48, 52, 55, 60, 63], color: "4472C4")
  |> ChartData.add_series("Mobile", [30, 35, 38, 42, 50, 55], color: "ED7D31")

slide = Podium.add_chart(slide, :line_markers, trend_data,
  x: {0.5, :inches}, y: {1, :inches},
  width: {12, :inches}, height: {5.5, :inches},
  title: "Monthly Active Users",
  legend: :top,
  value_axis: [title: "Users (thousands)", major_gridlines: true])

Line chart with markers showing monthly active users

Pie and Doughnut Charts

Pie and doughnut charts show proportions. They do not have axes.

TypeDescription
:pieStandard pie chart
:pie_explodedPie with slices pulled out (25% explosion)
:doughnutRing chart with 50% hole
:doughnut_explodedRing chart with slices pulled out
market_data =
  ChartData.new()
  |> ChartData.add_categories(["North America", "Europe", "Asia Pacific", "Latin America"])
  |> ChartData.add_series("Revenue", [42, 28, 18, 12],
    point_colors: %{0 => "2E75B6", 1 => "BDD7EE", 2 => "ED7D31", 3 => "FBE5D6"})

slide = Podium.add_chart(slide, :pie, market_data,
  x: {2, :inches}, y: {1, :inches},
  width: {9, :inches}, height: {5.5, :inches},
  title: "Revenue by Region",
  legend: :right,
  data_labels: [:category, :percent])

Pie chart with per-point colors and category labels

Area Charts

TypeDescription
:areaStandard area chart
:area_stackedStacked area
:area_stacked_100Stacked area normalized to 100%

Radar Charts

TypeDescription
:radarRadar without markers
:radar_markersRadar with data point markers
:radar_filledFilled radar (spider chart)
skill_data =
  ChartData.new()
  |> ChartData.add_categories(["Speed", "Power", "Range", "Durability", "Precision"])
  |> ChartData.add_series("Model A", [80, 90, 70, 85, 75], color: "4472C4")
  |> ChartData.add_series("Model B", [70, 65, 95, 70, 90], color: "ED7D31")

slide = Podium.add_chart(slide, :radar_filled, skill_data,
  x: {2, :inches}, y: {1, :inches},
  width: {9, :inches}, height: {5.5, :inches},
  title: "Product Comparison",
  legend: :bottom)

Filled radar chart comparing two models

Chart Titles

Pass a string for a plain title, or a keyword list for formatted titles. Pass nil (the default) for no title.

# Plain title
Podium.add_chart(slide, :column_clustered, data,
  x: {0.5, :inches}, y: {1, :inches}, width: {12, :inches}, height: {5, :inches},
  title: "Quarterly Revenue")

# Formatted title
Podium.add_chart(slide, :column_clustered, data,
  x: {0.5, :inches}, y: {1, :inches}, width: {12, :inches}, height: {5, :inches},
  title: [text: "Quarterly Revenue", font_size: 18, bold: true, color: "003366", font: "Arial"])

Title formatting options: :text, :font_size, :bold, :italic, :color, :font.

Legends

Control legend visibility and position. Pass false or nil for no legend, an atom for position only, or a keyword list for full formatting.

# Position only
legend: :bottom

# Formatted legend
legend: [position: :bottom, font_size: 10, bold: true, font: "Arial", color: "333333"]

Legend positions: :left, :right, :top, :bottom.

Data Labels

Data labels display values directly on chart elements. Pass a list of atoms to control what information appears, or a keyword list for full configuration.

# Show category names and percentages (common for pie charts)
data_labels: [:category, :percent]

# Show values with positioning and number format
data_labels: [show: [:value], position: :outside_end, number_format: "$#,##0"]

# White labels for dark backgrounds
data_labels: [show: [:value], color: "FFFFFF"]

Available label content: :value, :category, :series, :percent. Use :color with a hex string to set the text color (useful for charts on dark backgrounds).

PositionDescription
:centerCentered on the data point
:inside_endInside the bar, near the end
:inside_baseInside the bar, near the base
:outside_endOutside the bar end
:topAbove the point
:bottomBelow the point
:leftLeft of the point
:rightRight of the point
:best_fitPowerPoint chooses the position

Category Axis

Customize the category (horizontal) axis with the :category_axis option.

slide = Podium.add_chart(slide, :column_clustered, data,
  x: {0.5, :inches}, y: {1, :inches}, width: {12, :inches}, height: {5, :inches},
  category_axis: [
    title: "Quarter",
    label_rotation: -45,
    major_tick_mark: :cross,
    minor_tick_mark: :in
  ])
OptionTypeDefaultDescription
:titleString.t() or keywordnilAxis title (plain string or formatted)
:label_rotationnumbernilLabel rotation in degrees
:reversebooleanfalseReverse axis direction
:visiblebooleantrueShow or hide the axis
:major_tick_markatomnil:out, :in, :cross, or :none
:minor_tick_markatomnil:out, :in, :cross, or :none
:crossesatom or numbernilWhere the value axis crosses: :auto_zero, :min, :max, or a number
:colorString.t()nilHex color for tick label text (e.g. "FFFFFF")
:line_colorString.t()nilHex color for the axis line
:type:datenilSet to :date for date axis mode

Axis titles support the same formatting options as chart titles: :text, :font_size, :bold, :italic, :color, :font.

category_axis: [title: [text: "Quarter", font_size: 12, bold: true, color: "333333"]]

Value Axis

Customize the value (numeric) axis with the :value_axis option.

slide = Podium.add_chart(slide, :column_clustered, data,
  x: {0.5, :inches}, y: {1, :inches}, width: {12, :inches}, height: {5, :inches},
  value_axis: [
    title: "Amount ($)",
    number_format: "$#,##0",
    major_gridlines: true,
    min: 0,
    max: 20_000,
    major_unit: 5000
  ])
OptionTypeDefaultDescription
:titleString.t() or keywordnilAxis title
:number_formatString.t()nilExcel-style number format (e.g. "$#,##0")
:major_gridlinesbooleantrueShow major gridlines
:minor_gridlinesbooleanfalseShow minor gridlines
:minnumbernilMinimum axis value
:maxnumbernilMaximum axis value
:major_unitnumbernilMajor unit step
:minor_unitnumbernilMinor unit step
:crossesatom or numbernilWhere the category axis crosses
:label_rotationnumbernilLabel rotation in degrees
:reversebooleanfalseReverse axis direction
:visiblebooleantrueShow or hide the axis
:major_tick_markatomnil:out, :in, :cross, or :none
:minor_tick_markatomnil:out, :in, :cross, or :none
:colorString.t()nilHex color for tick label text (e.g. "FFFFFF")
:line_colorString.t()nilHex color for the axis line
:major_gridlines_colorString.t()nilHex color for major gridlines
:minor_gridlines_colorString.t()nilHex color for minor gridlines

Series Formatting

Colors

Set a series color with the :color option on ChartData.add_series/4:

ChartData.add_series(data, "Revenue", [100, 200, 300], color: "4472C4")

Pattern Fills

Use pattern fills instead of solid colors:

ChartData.add_series(data, "Email", [250, 230, 210],
  pattern: [type: :dn_diag, foreground: "4472C4", background: "FFFFFF"])

Pattern fills and per-point formatting on columns

The :type must be one of the 54 pattern presets from Podium.Pattern (e.g. :dn_diag, :lt_horz, :dk_vert, :sm_grid, :cross).

Markers

For line and scatter charts, customize data point markers:

ChartData.add_series(data, "Trend", [20, 35, 45, 50, 55, 70],
  color: "4472C4",
  marker: [style: :diamond, size: 10, fill: "4472C4", line: "002060"])

Marker styles: :circle, :square, :diamond, :triangle, :star, :x, :plus, :dash, :dot, :none.

Per-Point Formatting

Override the appearance of individual data points within a series.

Point Colors

Color specific points by index:

ChartData.add_series(data, "Revenue", [42, 28, 18, 35, 22],
  point_colors: %{0 => "2E75B6", 3 => "ED7D31"})

Point Formats

Apply fill and line formatting to specific points:

ChartData.add_series(data, "Revenue", [42, 28, 18, 35, 22],
  point_formats: %{
    0 => [fill: "2E75B6", line: [color: "001133", width: {2, :pt}]],
    3 => [fill: "ED7D31", line: [color: "7F3300", width: {2, :pt}]]
  })

Per-Point Data Labels

Show data labels on specific points only:

ChartData.add_series(data, "Revenue ($K)", [42, 28, 18, 35, 22],
  data_labels: %{
    0 => [show: [:value], position: :outside_end, number_format: "$#,##0K"],
    3 => [show: [:value, :category], position: :outside_end]
  })

XY (Scatter) Charts

Scatter charts plot data as X/Y coordinate pairs. Use Podium.Chart.XyChartData instead of ChartData -- there are no shared categories, each series provides its own X and Y value lists.

alias Podium.Chart.XyChartData

xy_data =
  XyChartData.new()
  |> XyChartData.add_series("Series A", [1, 2, 3, 4, 5], [2.3, 4.1, 3.7, 5.2, 4.8],
    color: "4472C4")
  |> XyChartData.add_series("Series B", [1, 2, 3, 4, 5], [1.5, 3.2, 2.8, 4.5, 3.9],
    color: "ED7D31")

slide = Podium.add_chart(slide, :scatter, xy_data,
  x: {0.5, :inches}, y: {1, :inches},
  width: {12, :inches}, height: {5.5, :inches},
  title: "Scatter Analysis")

Scatter chart with two series

TypeDescription
:scatterPoints only (no connecting lines)
:scatter_linesPoints connected with straight lines
:scatter_lines_no_markersStraight lines, no point markers
:scatter_smoothPoints connected with smooth curves
:scatter_smooth_no_markersSmooth curves, no point markers

XyChartData.add_series/5 takes the series name, X values list, Y values list, and optional formatting options. The X and Y lists must have the same length, and all values must be numbers.

Bubble Charts

Bubble charts extend scatter charts with a third dimension: bubble size. Use Podium.Chart.BubbleChartData.

alias Podium.Chart.BubbleChartData

bubble_data =
  BubbleChartData.new()
  |> BubbleChartData.add_series("Region A",
    [1, 3, 5, 7], [10, 25, 15, 30], [5, 12, 8, 15],
    color: "4472C4")
  |> BubbleChartData.add_series("Region B",
    [2, 4, 6, 8], [20, 15, 30, 10], [10, 6, 14, 8],
    color: "ED7D31")

slide = Podium.add_chart(slide, :bubble, bubble_data,
  x: {0.5, :inches}, y: {1, :inches},
  width: {12, :inches}, height: {5.5, :inches},
  title: "Market Opportunity Analysis")

Bubble chart with sized data points

TypeDescription
:bubbleStandard bubble chart
:bubble_3dBubble chart with 3D effect

BubbleChartData.add_series/6 takes the series name, X values, Y values, bubble sizes, and optional formatting options. All three lists must have the same length.

Date Axes

When your categories represent dates, set the category axis type to :date to get proper date-based spacing and formatting.

date_data =
  ChartData.new()
  |> ChartData.add_categories(["2025-01", "2025-04", "2025-07", "2025-10", "2026-01"])
  |> ChartData.add_series("Sales", [120, 145, 190, 210, 250], color: "4472C4")

slide = Podium.add_chart(slide, :line_markers, date_data,
  x: {0.5, :inches}, y: {1, :inches},
  width: {12, :inches}, height: {5.5, :inches},
  title: "Quarterly Sales",
  category_axis: [
    type: :date,
    title: "Date",
    base_time_unit: :months,
    major_time_unit: :months,
    major_unit: 3
  ],
  value_axis: [title: "Sales ($K)", major_gridlines: true])

Date axis options:

OptionTypeDescription
:base_time_unit:days, :months, or :yearsBase time unit for date axis
:major_time_unit:days, :months, or :yearsMajor tick time unit
:minor_time_unit:days, :months, or :yearsMinor tick time unit
:major_unitnumberNumber of time units between major ticks
:minor_unitnumberNumber of time units between minor ticks

Chart Type Reference

All 29 chart types supported by Podium:

CategoryType AtomData Module
Column:column_clusteredChartData
Column:column_stackedChartData
Column:column_stacked_100ChartData
Bar:bar_clusteredChartData
Bar:bar_stackedChartData
Bar:bar_stacked_100ChartData
Line:lineChartData
Line:line_markersChartData
Line:line_stackedChartData
Line:line_markers_stackedChartData
Line:line_stacked_100ChartData
Line:line_markers_stacked_100ChartData
Pie:pieChartData
Pie:pie_explodedChartData
Area:areaChartData
Area:area_stackedChartData
Area:area_stacked_100ChartData
Doughnut:doughnutChartData
Doughnut:doughnut_explodedChartData
Radar:radarChartData
Radar:radar_markersChartData
Radar:radar_filledChartData
Scatter:scatterXyChartData
Scatter:scatter_linesXyChartData
Scatter:scatter_lines_no_markersXyChartData
Scatter:scatter_smoothXyChartData
Scatter:scatter_smooth_no_markersXyChartData
Bubble:bubbleBubbleChartData
Bubble:bubble_3dBubbleChartData

For even more charting power, see Combo Charts to combine multiple chart types in a single plot area.